Skip to content

Commit

Permalink
Callback for key is now called repeatedly when key is held down (#119)
Browse files Browse the repository at this point in the history
  • Loading branch information
kasper committed Jul 31, 2016
1 parent 1c618c9 commit 2501f49
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 6 deletions.
2 changes: 1 addition & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ end
- `off(int identifier)` disables the managed handler for a key with the given identifier
- `key` read-only property for the key character in lower case or case sensitive special key
- `modifiers` read-only property for the key modifiers in lower case
- `new Key(String key, Array<String> modifiers, Function callback)` constructs and binds the key character with the specified modifiers (can be an empty list) to a callback function and returns the handler, you must keep a reference to the handler in order for your callback to get called, you can have multiple handlers for a single key combination, however only one can be enabled at a time, any previous handler for the same key combination will automatically be disabled, the callback function receives its handler as the only argument
- `new Key(String key, Array<String> modifiers, Function callback)` constructs and binds the key character with the specified modifiers (can be an empty list) to a callback function and returns the handler, you must keep a reference to the handler in order for your callback to get called, you can have multiple handlers for a single key combination, however only one can be enabled at a time, any previous handler for the same key combination will automatically be disabled, the callback function receives its handler as the first argument and as the second argument a boolean that indicates if the key was repeated
- `isEnabled()` returns `true` if the key handler is enabled, by default `true`
- `enable()` enables the key handler, any previous handler for the same key combination will automatically be disabled, returns `true` if successful
- `disable()` disables the key handler, returns `true` if successful
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ Changelog

Release: dd.mm.yyyy

### Improvements

- Callback for key is now called repeatedly when key is held down ([#119](https://github.com/kasper/phoenix/issues/119)).

### Bug Fixes

- Fix issue that prevented `Window#setFrame(...)` to only set origin for windows that cannot be resized ([#124](https://github.com/kasper/phoenix/issues/124)).

### API

#### Key

- Change: The callback function receives its handler as the first argument and as the second argument a boolean that indicates if the key was repeated ([#119](https://github.com/kasper/phoenix/issues/119)).

#### Screen

- New: Function `currentSpace()` returns the current space for the screen (macOS 10.11+, returns `undefined` otherwise) ([#120](https://github.com/kasper/phoenix/issues/120)).
Expand Down
81 changes: 76 additions & 5 deletions Phoenix/PHKeyHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

@import Carbon;
@import Cocoa;

#import "PHKeyHandler.h"
#import "PHKeyTranslator.h"
Expand All @@ -14,6 +15,7 @@ @interface PHKeyHandler ()
@property UInt32 modifierFlags;
@property EventHotKeyRef reference;
@property BOOL enabled;
@property NSTimer *timer;

@property (copy) NSString *key;
@property (copy) NSArray<NSString *> *modifiers;
Expand All @@ -27,6 +29,7 @@ @implementation PHKeyHandler

static NSString * const PHKeyHandlerIdentifierKey = @"PHKeyHandlerIdentifier";
static NSString * const PHKeyHandlerKeyDownNotification = @"PHKeyHandlerKeyDownNotification";
static NSString * const PHKeyHandlerKeyUpNotification = @"PHKeyHandlerKeyUpNotification";
static NSString * const PHKeyHandlerWillEnableNotification = @"PHKeyHandlerWillEnableNotification";

#pragma mark - CarbonEventCallback
Expand All @@ -49,7 +52,11 @@ static OSStatus PHCarbonEventCallback(__unused EventHandlerCallRef handler,
return error;
}

[[NSNotificationCenter defaultCenter] postNotificationName:PHKeyHandlerKeyDownNotification
NSString *notification = (GetEventKind(event) == kEventHotKeyPressed) ?
PHKeyHandlerKeyDownNotification :
PHKeyHandlerKeyUpNotification;

[[NSNotificationCenter defaultCenter] postNotificationName:notification
object:nil
userInfo:@{ PHKeyHandlerIdentifierKey: @(identifier.id) }];
return noErr;
Expand All @@ -62,11 +69,15 @@ + (void) initialize {

if (self == [PHKeyHandler self]) {

EventTypeSpec keyDown = { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed };
EventTypeSpec events[] = {
{ .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed },
{ .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyReleased }
};

OSStatus error = InstallEventHandler(GetEventDispatcherTarget(),
PHCarbonEventCallback,
1,
&keyDown,
2,
events,
NULL,
NULL);
if (error != noErr) {
Expand Down Expand Up @@ -102,6 +113,11 @@ - (instancetype) initWithKey:(NSString *)key modifiers:(NSArray<NSString *> *)mo
selector:@selector(keyDown:)
name:PHKeyHandlerKeyDownNotification
object:nil];
// Observe key up event
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyUp:)
name:PHKeyHandlerKeyUpNotification
object:nil];
[self enable];
}

Expand All @@ -114,6 +130,7 @@ - (void) dealloc {

[[NSNotificationCenter defaultCenter] removeObserver:self name:PHKeyHandlerWillEnableNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:PHKeyHandlerKeyDownNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:PHKeyHandlerKeyUpNotification object:nil];
[self disable];
}

Expand Down Expand Up @@ -169,6 +186,8 @@ - (BOOL) disable {
return YES;
}

[self.timer invalidate];

OSStatus error = UnregisterEventHotKey(self.reference);

if (error != noErr) {
Expand All @@ -188,6 +207,7 @@ - (void) willEnable:(NSNotification *)notification {

// Yield for other key handler
if ([self hashForKeyCombination] == [notification.object hashForKeyCombination]) {
[self.timer invalidate]; // Invalidate repeat immediately
[self disable];
}
}
Expand All @@ -196,7 +216,58 @@ - (void) keyDown:(NSNotification *)notification {

// This handler should handle this notification
if (self.identifier == [notification.userInfo[PHKeyHandlerIdentifierKey] unsignedIntegerValue]) {
[self callWithArguments:@[ self ]];

[self callWithArguments:@[ self, @NO ]];

// Delay repeat timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:[NSEvent keyRepeatDelay]
target:self
selector:@selector(delayTimerDidFire:)
userInfo:nil
repeats:NO];
}
}

- (void) keyUp:(NSNotification *)notification {

// This handler should handle this notification
if (self.identifier == [notification.userInfo[PHKeyHandlerIdentifierKey] unsignedIntegerValue]) {
[self.timer invalidate];
}
}

#pragma mark - Timing

- (BOOL) validateTimer:(NSTimer *)timer {

// Not enabled
if (![self isEnabled]) {
[timer invalidate];
return NO;
}

return YES;
}

- (void) delayTimerDidFire:(NSTimer *)timer {

if ([self validateTimer:timer]) {

[self callWithArguments:@[ self, @YES ]];

// Repeat until key up
self.timer = [NSTimer scheduledTimerWithTimeInterval:[NSEvent keyRepeatInterval]
target:self
selector:@selector(repeatTimerDidFire:)
userInfo:nil
repeats:YES];
}
}

- (void) repeatTimerDidFire:(NSTimer *)timer {

if ([self validateTimer:timer]) {
[self callWithArguments:@[ self, @YES ]];
}
}

Expand Down

0 comments on commit 2501f49

Please sign in to comment.