diff options
| -rw-r--r-- | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | 7 | ||||
| -rw-r--r-- | DDHotKeyCenter.h | 93 | ||||
| -rw-r--r-- | DDHotKeyCenter.m | 284 | ||||
| -rw-r--r-- | DDHotKeyTextField.h | 20 | ||||
| -rw-r--r-- | DDHotKeyTextField.m | 138 | ||||
| -rw-r--r-- | DDHotKeyUtilities.h | 14 | ||||
| -rw-r--r-- | DDHotKeyUtilities.m | 144 | ||||
| -rw-r--r-- | Package.swift | 1 | ||||
| -rw-r--r-- | README.markdown | 39 | ||||
| -rw-r--r-- | Sources/DDHotKey/DDHotKey.swift | 6 | ||||
| -rw-r--r-- | Sources/DDHotKey/DDHotKeyCenter.swift | 8 | ||||
| -rw-r--r-- | Sources/DDHotKey/DDHotKeyTextField.swift | 124 |
12 files changed, 143 insertions, 735 deletions
diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Workspace + version = "1.0"> + <FileRef + location = "self:"> + </FileRef> +</Workspace> diff --git a/DDHotKeyCenter.h b/DDHotKeyCenter.h deleted file mode 100644 index 6f79bbb..0000000 --- a/DDHotKeyCenter.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - DDHotKey -- DDHotKeyCenter.h - - Copyright (c) Dave DeLong <http://www.davedelong.com> - - Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - - The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. - */ - -#import <Cocoa/Cocoa.h> - -//a convenient typedef for the required signature of a hotkey block callback -typedef void (^DDHotKeyTask)(NSEvent*); - -@interface DDHotKey : NSObject - -// creates a new hotkey but does not register it -+ (instancetype)hotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task; - -@property (nonatomic, assign, readonly) id target; -@property (nonatomic, readonly) SEL action; -@property (nonatomic, strong, readonly) id object; -@property (nonatomic, copy, readonly) DDHotKeyTask task; - -@property (nonatomic, readonly) unsigned short keyCode; -@property (nonatomic, readonly) NSUInteger modifierFlags; - -@end - -#pragma mark - - -@interface DDHotKeyCenter : NSObject - -+ (instancetype)sharedHotKeyCenter; - -/** - Register a hotkey. - */ -- (DDHotKey *)registerHotKey:(DDHotKey *)hotKey; - -/** - Register a target/action hotkey. - The modifierFlags must be a bitwise OR of NSCommandKeyMask, NSAlternateKeyMask, NSControlKeyMask, or NSShiftKeyMask; - Returns the hotkey registered. If registration failed, returns nil. - */ -- (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object; - -/** - Register a block callback hotkey. - The modifierFlags must be a bitwise OR of NSCommandKeyMask, NSAlternateKeyMask, NSControlKeyMask, or NSShiftKeyMask; - Returns the hotkey registered. If registration failed, returns nil. - */ -- (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task; - -/** - See if a hotkey exists with the specified keycode and modifier flags. - NOTE: this will only check among hotkeys you have explicitly registered with DDHotKeyCenter. This does not check all globally registered hotkeys. - */ -- (BOOL)hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags; - -/** - Unregister a specific hotkey - */ -- (void)unregisterHotKey:(DDHotKey *)hotKey; - -/** - Unregister all hotkeys - */ -- (void)unregisterAllHotKeys; - -/** - Unregister all hotkeys with a specific target - */ -- (void)unregisterHotKeysWithTarget:(id)target; - -/** - Unregister all hotkeys with a specific target and action - */ -- (void)unregisterHotKeysWithTarget:(id)target action:(SEL)action; - -/** - Unregister a hotkey with a specific keycode and modifier flags - */ -- (void)unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags; - -/** - Returns a set of currently registered hotkeys - **/ -- (NSSet *)registeredHotKeys; - -@end - diff --git a/DDHotKeyCenter.m b/DDHotKeyCenter.m deleted file mode 100644 index e2d5997..0000000 --- a/DDHotKeyCenter.m +++ /dev/null @@ -1,284 +0,0 @@ -/* - DDHotKey -- DDHotKeyCenter.m - - Copyright (c) Dave DeLong <http://www.davedelong.com> - - Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - - The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. - */ - -#import <Carbon/Carbon.h> -#import <objc/runtime.h> - -#import "DDHotKeyCenter.h" -#import "DDHotKeyUtilities.h" - -#pragma mark Private Global Declarations - -OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData); - -#pragma mark DDHotKey - -@interface DDHotKey () - -@property (nonatomic, retain) NSValue *hotKeyRef; -@property (nonatomic) UInt32 hotKeyID; - - -@property (nonatomic, assign, setter = _setTarget:) id target; -@property (nonatomic, setter = _setAction:) SEL action; -@property (nonatomic, strong, setter = _setObject:) id object; -@property (nonatomic, copy, setter = _setTask:) DDHotKeyTask task; - -@property (nonatomic, setter = _setKeyCode:) unsigned short keyCode; -@property (nonatomic, setter = _setModifierFlags:) NSUInteger modifierFlags; - -@end - -@implementation DDHotKey - -+ (instancetype)hotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task { - DDHotKey *newHotKey = [[self alloc] init]; - [newHotKey _setTask:task]; - [newHotKey _setKeyCode:keyCode]; - [newHotKey _setModifierFlags:flags]; - return newHotKey; -} - -- (void) dealloc { - [[DDHotKeyCenter sharedHotKeyCenter] unregisterHotKey:self]; -} - -- (NSUInteger)hash { - return [self keyCode] ^ [self modifierFlags]; -} - -- (BOOL)isEqual:(id)object { - BOOL equal = NO; - if ([object isKindOfClass:[DDHotKey class]]) { - equal = ([object keyCode] == [self keyCode]); - equal &= ([object modifierFlags] == [self modifierFlags]); - } - return equal; -} - -- (NSString *)description { - NSMutableArray *bits = [NSMutableArray array]; - if ((_modifierFlags & NSControlKeyMask) > 0) { [bits addObject:@"NSControlKeyMask"]; } - if ((_modifierFlags & NSCommandKeyMask) > 0) { [bits addObject:@"NSCommandKeyMask"]; } - if ((_modifierFlags & NSShiftKeyMask) > 0) { [bits addObject:@"NSShiftKeyMask"]; } - if ((_modifierFlags & NSAlternateKeyMask) > 0) { [bits addObject:@"NSAlternateKeyMask"]; } - - NSString *flags = [NSString stringWithFormat:@"(%@)", [bits componentsJoinedByString:@" | "]]; - NSString *invokes = @"(block)"; - if ([self target] != nil && [self action] != nil) { - invokes = [NSString stringWithFormat:@"[%@ %@]", [self target], NSStringFromSelector([self action])]; - } - return [NSString stringWithFormat:@"%@\n\t(key: %hu\n\tflags: %@\n\tinvokes: %@)", [super description], [self keyCode], flags, invokes]; -} - -- (void)invokeWithEvent:(NSEvent *)event { - if (_target != nil && _action != nil && [_target respondsToSelector:_action]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [_target performSelector:_action withObject:event withObject:_object]; -#pragma clang diagnostic pop - } else if (_task != nil) { - _task(event); - } -} - -@end - -#pragma mark DDHotKeyCenter - -static DDHotKeyCenter *sharedHotKeyCenter = nil; - -@implementation DDHotKeyCenter { - NSMutableSet *_registeredHotKeys; - UInt32 _nextHotKeyID; -} - -+ (instancetype)sharedHotKeyCenter { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedHotKeyCenter = [super allocWithZone:nil]; - sharedHotKeyCenter = [sharedHotKeyCenter init]; - - EventTypeSpec eventSpec; - eventSpec.eventClass = kEventClassKeyboard; - eventSpec.eventKind = kEventHotKeyReleased; - InstallApplicationEventHandler(&dd_hotKeyHandler, 1, &eventSpec, NULL, NULL); - }); - return sharedHotKeyCenter; -} - -+ (id)allocWithZone:(NSZone *)zone { - return sharedHotKeyCenter; -} - -- (id)init { - if (self != sharedHotKeyCenter) { return sharedHotKeyCenter; } - - self = [super init]; - if (self) { - _registeredHotKeys = [[NSMutableSet alloc] init]; - _nextHotKeyID = 1; - } - return self; -} - -- (NSSet *)hotKeysMatching:(BOOL(^)(DDHotKey *hotkey))matcher { - NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { - return matcher(evaluatedObject); - }]; - return [_registeredHotKeys filteredSetUsingPredicate:predicate]; -} - -- (BOOL)hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { - return [self hotKeysMatching:^BOOL(DDHotKey *hotkey) { - return hotkey.keyCode == keyCode && hotkey.modifierFlags == flags; - }].count > 0; -} - -- (DDHotKey *)_registerHotKey:(DDHotKey *)hotKey { - if ([_registeredHotKeys containsObject:hotKey]) { - return hotKey; - } - - EventHotKeyID keyID; - keyID.signature = 'htk1'; - keyID.id = _nextHotKeyID; - - EventHotKeyRef carbonHotKey; - UInt32 flags = DDCarbonModifierFlagsFromCocoaModifiers([hotKey modifierFlags]); - OSStatus err = RegisterEventHotKey([hotKey keyCode], flags, keyID, GetEventDispatcherTarget(), 0, &carbonHotKey); - - //error registering hot key - if (err != 0) { return nil; } - - NSValue *refValue = [NSValue valueWithPointer:carbonHotKey]; - [hotKey setHotKeyRef:refValue]; - [hotKey setHotKeyID:_nextHotKeyID]; - - _nextHotKeyID++; - [_registeredHotKeys addObject:hotKey]; - - return hotKey; -} - -- (DDHotKey *)registerHotKey:(DDHotKey *)hotKey { - return [self _registerHotKey:hotKey]; -} - -- (void)unregisterHotKey:(DDHotKey *)hotKey { - NSValue *hotKeyRef = [hotKey hotKeyRef]; - if (hotKeyRef) { - EventHotKeyRef carbonHotKey = (EventHotKeyRef)[hotKeyRef pointerValue]; - UnregisterEventHotKey(carbonHotKey); - [hotKey setHotKeyRef:nil]; - } - - [_registeredHotKeys removeObject:hotKey]; -} - -- (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task { - //we can't add a new hotkey if something already has this combo - if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return NO; } - - DDHotKey *newHotKey = [[DDHotKey alloc] init]; - [newHotKey _setTask:task]; - [newHotKey _setKeyCode:keyCode]; - [newHotKey _setModifierFlags:flags]; - - return [self _registerHotKey:newHotKey]; -} - -- (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object { - //we can't add a new hotkey if something already has this combo - if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return NO; } - - //build the hotkey object: - DDHotKey *newHotKey = [[DDHotKey alloc] init]; - [newHotKey _setTarget:target]; - [newHotKey _setAction:action]; - [newHotKey _setObject:object]; - [newHotKey _setKeyCode:keyCode]; - [newHotKey _setModifierFlags:flags]; - return [self _registerHotKey:newHotKey]; -} - -- (void)unregisterHotKeysMatching:(BOOL(^)(DDHotKey *hotkey))matcher { - //explicitly unregister the hotkey, since relying on the unregistration in -dealloc can be problematic - @autoreleasepool { - NSSet *matches = [self hotKeysMatching:matcher]; - for (DDHotKey *hotKey in matches) { - [self unregisterHotKey:hotKey]; - } - } -} - -- (void)unregisterHotKeysWithTarget:(id)target { - [self unregisterHotKeysMatching:^BOOL(DDHotKey *hotkey) { - return hotkey.target == target; - }]; -} - -- (void)unregisterHotKeysWithTarget:(id)target action:(SEL)action { - [self unregisterHotKeysMatching:^BOOL(DDHotKey *hotkey) { - return hotkey.target == target && sel_isEqual(hotkey.action, action); - }]; -} - -- (void)unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { - [self unregisterHotKeysMatching:^BOOL(DDHotKey *hotkey) { - return hotkey.keyCode == keyCode && hotkey.modifierFlags == flags; - }]; -} - -- (void)unregisterAllHotKeys { - NSSet *keys = [_registeredHotKeys copy]; - for (DDHotKey *key in keys) { - [self unregisterHotKey:key]; - } -} - -- (NSSet *)registeredHotKeys { - return [self hotKeysMatching:^BOOL(DDHotKey *hotkey) { - return hotkey.hotKeyRef != NULL; - }]; -} - -@end - -OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) { - @autoreleasepool { - EventHotKeyID hotKeyID; - GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID); - - UInt32 keyID = hotKeyID.id; - - NSSet *matchingHotKeys = [[DDHotKeyCenter sharedHotKeyCenter] hotKeysMatching:^BOOL(DDHotKey *hotkey) { - return hotkey.hotKeyID == keyID; - }]; - if ([matchingHotKeys count] > 1) { NSLog(@"ERROR!"); } - DDHotKey *matchingHotKey = [matchingHotKeys anyObject]; - - NSEvent *event = [NSEvent eventWithEventRef:theEvent]; - NSEvent *keyEvent = [NSEvent keyEventWithType:NSKeyUp - location:[event locationInWindow] - modifierFlags:[event modifierFlags] - timestamp:[event timestamp] - windowNumber:-1 - context:nil - characters:@"" - charactersIgnoringModifiers:@"" - isARepeat:NO - keyCode:[matchingHotKey keyCode]]; - - [matchingHotKey invokeWithEvent:keyEvent]; - } - - return noErr; -} diff --git a/DDHotKeyTextField.h b/DDHotKeyTextField.h deleted file mode 100644 index c399d62..0000000 --- a/DDHotKeyTextField.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - DDHotKey -- DDHotKeyTextField.h - - Copyright (c) Dave DeLong <http://www.davedelong.com> - - Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - - The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. - */ - -#import <Foundation/Foundation.h> -#import "DDHotKeyCenter.h" - -@interface DDHotKeyTextField : NSTextField - -@property (nonatomic, strong) DDHotKey *hotKey; - -@end - -@interface DDHotKeyTextFieldCell : NSTextFieldCell @end
\ No newline at end of file diff --git a/DDHotKeyTextField.m b/DDHotKeyTextField.m deleted file mode 100644 index 4f3da2b..0000000 --- a/DDHotKeyTextField.m +++ /dev/null @@ -1,138 +0,0 @@ -/* - DDHotKey -- DDHotKeyTextField.m - - Copyright (c) Dave DeLong <http://www.davedelong.com> - - Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - - The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. - */ - -#import <Carbon/Carbon.h> - -#import "DDHotKeyTextField.h" -#import "DDHotKeyUtilities.h" - -@interface DDHotKeyTextFieldEditor : NSTextView - -@property (nonatomic, weak) DDHotKeyTextField *hotKeyField; - -@end - -static DDHotKeyTextFieldEditor *DDFieldEditor(void); -static DDHotKeyTextFieldEditor *DDFieldEditor(void) { - static DDHotKeyTextFieldEditor *editor; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - editor = [[DDHotKeyTextFieldEditor alloc] initWithFrame:NSMakeRect(0, 0, 100, 32)]; - [editor setFieldEditor:YES]; - }); - return editor; -} - -@implementation DDHotKeyTextFieldCell - -- (NSTextView *)fieldEditorForView:(NSView *)view { - if ([view isKindOfClass:[DDHotKeyTextField class]]) { - DDHotKeyTextFieldEditor *editor = DDFieldEditor(); - editor.insertionPointColor = editor.backgroundColor; - editor.hotKeyField = (DDHotKeyTextField *)view; - return editor; - } - return nil; -} - -@end - -@implementation DDHotKeyTextField - -+ (Class)cellClass { - return [DDHotKeyTextFieldCell class]; -} - -- (void)setHotKey:(DDHotKey *)hotKey { - if (_hotKey != hotKey) { - _hotKey = hotKey; - [super setStringValue:[DDStringFromKeyCode(hotKey.keyCode, hotKey.modifierFlags) uppercaseString]]; - } -} - -- (void)setStringValue:(NSString *)aString { - NSLog(@"-[DDHotKeyTextField setStringValue:] is not what you want. Use -[DDHotKeyTextField setHotKey:] instead."); - [super setStringValue:aString]; -} - -- (NSString *)stringValue { - NSLog(@"-[DDHotKeyTextField stringValue] is not what you want. Use -[DDHotKeyTextField hotKey] instead."); - return [super stringValue]; -} - -@end - -@implementation DDHotKeyTextFieldEditor { - BOOL _hasSeenKeyDown; - id _globalMonitor; - DDHotKey *_originalHotKey; -} - -- (void)setHotKeyField:(DDHotKeyTextField *)hotKeyField { - _hotKeyField = hotKeyField; - _originalHotKey = _hotKeyField.hotKey; -} - -- (void)processHotkeyEvent:(NSEvent *)event { - NSUInteger flags = event.modifierFlags; - BOOL hasModifier = (flags & (NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask | NSShiftKeyMask | NSFunctionKeyMask)) > 0; - - if (event.type == NSKeyDown) { - _hasSeenKeyDown = YES; - unichar character = [event.charactersIgnoringModifiers characterAtIndex:0]; - - - if (hasModifier == NO && ([[NSCharacterSet newlineCharacterSet] characterIsMember:character] || event.keyCode == kVK_Escape)) { - if (event.keyCode == kVK_Escape) { - self.hotKeyField.hotKey = _originalHotKey; - - NSString *str = DDStringFromKeyCode(_originalHotKey.keyCode, _originalHotKey.modifierFlags); - self.textStorage.mutableString.string = [str uppercaseString]; - } - [self.hotKeyField sendAction:self.hotKeyField.action to:self.hotKeyField.target]; - [self.window makeFirstResponder:nil]; - return; - } - } - - if ((event.type == NSKeyDown || (event.type == NSFlagsChanged && _hasSeenKeyDown == NO)) && hasModifier) { - self.hotKeyField.hotKey = [DDHotKey hotKeyWithKeyCode:event.keyCode modifierFlags:flags task:_originalHotKey.task]; - NSString *str = DDStringFromKeyCode(event.keyCode, flags); - [self.textStorage.mutableString setString:[str uppercaseString]]; - [self.hotKeyField sendAction:self.hotKeyField.action to:self.hotKeyField.target]; - } -} - -- (BOOL)becomeFirstResponder { - BOOL ok = [super becomeFirstResponder]; - if (ok) { - _hasSeenKeyDown = NO; - _globalMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyDownMask | NSFlagsChangedMask) handler:^NSEvent*(NSEvent *event){ - [self processHotkeyEvent:event]; - return nil; - }]; - } - return ok; -} - -- (BOOL)resignFirstResponder { - BOOL ok = [super resignFirstResponder]; - if (ok) { - self.hotKeyField = nil; - if (_globalMonitor) { - [NSEvent removeMonitor:_globalMonitor]; - _globalMonitor = nil; - } - } - - return ok; -} - -@end diff --git a/DDHotKeyUtilities.h b/DDHotKeyUtilities.h deleted file mode 100644 index 54b25a4..0000000 --- a/DDHotKeyUtilities.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - DDHotKey -- DDHotKeyUtilities.h - - Copyright (c) Dave DeLong <http://www.davedelong.com> - - Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - - The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. - */ - -#import <Foundation/Foundation.h> - -extern NSString *DDStringFromKeyCode(unsigned short keyCode, NSUInteger modifiers); -extern UInt32 DDCarbonModifierFlagsFromCocoaModifiers(NSUInteger flags); diff --git a/DDHotKeyUtilities.m b/DDHotKeyUtilities.m deleted file mode 100644 index f298a43..0000000 --- a/DDHotKeyUtilities.m +++ /dev/null @@ -1,144 +0,0 @@ -/* - DDHotKey -- DDHotKeyUtilities.m - - Copyright (c) Dave DeLong <http://www.davedelong.com> - - Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - - The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. - */ - -#import "DDHotKeyUtilities.h" -#import <Carbon/Carbon.h> - -static NSDictionary *_DDKeyCodeToCharacterMap(void); -static NSDictionary *_DDKeyCodeToCharacterMap(void) { - static NSDictionary *keyCodeMap = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - keyCodeMap = @{ - @(kVK_Return) : @"↩", - @(kVK_Tab) : @"⇥", - @(kVK_Space) : @"⎵", - @(kVK_Delete) : @"⌫", - @(kVK_Escape) : @"⎋", - @(kVK_Command) : @"⌘", - @(kVK_Shift) : @"⇧", - @(kVK_CapsLock) : @"⇪", - @(kVK_Option) : @"⌥", - @(kVK_Control) : @"⌃", - @(kVK_RightShift) : @"⇧", - @(kVK_RightOption) : @"⌥", - @(kVK_RightControl) : @"⌃", - @(kVK_VolumeUp) : @"🔊", - @(kVK_VolumeDown) : @"🔈", - @(kVK_Mute) : @"🔇", - @(kVK_Function) : @"\u2318", - @(kVK_F1) : @"F1", - @(kVK_F2) : @"F2", - @(kVK_F3) : @"F3", - @(kVK_F4) : @"F4", - @(kVK_F5) : @"F5", - @(kVK_F6) : @"F6", - @(kVK_F7) : @"F7", - @(kVK_F8) : @"F8", - @(kVK_F9) : @"F9", - @(kVK_F10) : @"F10", - @(kVK_F11) : @"F11", - @(kVK_F12) : @"F12", - @(kVK_F13) : @"F13", - @(kVK_F14) : @"F14", - @(kVK_F15) : @"F15", - @(kVK_F16) : @"F16", - @(kVK_F17) : @"F17", - @(kVK_F18) : @"F18", - @(kVK_F19) : @"F19", - @(kVK_F20) : @"F20", - // @(kVK_Help) : @"", - @(kVK_ForwardDelete) : @"⌦", - @(kVK_Home) : @"↖", - @(kVK_End) : @"↘", - @(kVK_PageUp) : @"⇞", - @(kVK_PageDown) : @"⇟", - @(kVK_LeftArrow) : @"←", - @(kVK_RightArrow) : @"→", - @(kVK_DownArrow) : @"↓", - @(kVK_UpArrow) : @"↑", - }; - }); - return keyCodeMap; -} - -NSString *DDStringFromKeyCode(unsigned short keyCode, NSUInteger modifiers) { - NSMutableString *final = [NSMutableString stringWithString:@""]; - NSDictionary *characterMap = _DDKeyCodeToCharacterMap(); - - if (modifiers & NSControlKeyMask) { - [final appendString:[characterMap objectForKey:@(kVK_Control)]]; - } - if (modifiers & NSAlternateKeyMask) { - [final appendString:[characterMap objectForKey:@(kVK_Option)]]; - } - if (modifiers & NSShiftKeyMask) { - [final appendString:[characterMap objectForKey:@(kVK_Shift)]]; - } - if (modifiers & NSCommandKeyMask) { - [final appendString:[characterMap objectForKey:@(kVK_Command)]]; - } - - if (keyCode == kVK_Control || keyCode == kVK_Option || keyCode == kVK_Shift || keyCode == kVK_Command) { - return final; - } - - NSString *mapped = [characterMap objectForKey:@(keyCode)]; - if (mapped != nil) { - [final appendString:mapped]; - } else { - - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - - // Fix crash using non-unicode layouts, such as Chinese or Japanese. - if (!uchr) { - CFRelease(currentKeyboard); - currentKeyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); - uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - } - - const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); - - if (keyboardLayout) { - UInt32 deadKeyState = 0; - UniCharCount maxStringLength = 255; - UniCharCount actualStringLength = 0; - UniChar unicodeString[maxStringLength]; - - UInt32 keyModifiers = DDCarbonModifierFlagsFromCocoaModifiers(modifiers); - - OSStatus status = UCKeyTranslate(keyboardLayout, - keyCode, kUCKeyActionDown, keyModifiers, - LMGetKbdType(), 0, - &deadKeyState, - maxStringLength, - &actualStringLength, unicodeString); - - if (actualStringLength > 0 && status == noErr) { - NSString *characterString = [NSString stringWithCharacters:unicodeString length:(NSUInteger)actualStringLength]; - - [final appendString:characterString]; - } - } - } - - return final; -} - -UInt32 DDCarbonModifierFlagsFromCocoaModifiers(NSUInteger flags) { - UInt32 newFlags = 0; - if ((flags & NSControlKeyMask) > 0) { newFlags |= controlKey; } - if ((flags & NSCommandKeyMask) > 0) { newFlags |= cmdKey; } - if ((flags & NSShiftKeyMask) > 0) { newFlags |= shiftKey; } - if ((flags & NSAlternateKeyMask) > 0) { newFlags |= optionKey; } - if ((flags & NSAlphaShiftKeyMask) > 0) { newFlags |= alphaLock; } - return newFlags; -} diff --git a/Package.swift b/Package.swift index 573352b..9fdbf77 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,7 @@ import PackageDescription let package = Package( name: "DDHotKey", + platforms: [.macOS(.v10_10)], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. .library( diff --git a/README.markdown b/README.markdown index 26a6b85..1964fc1 100644 --- a/README.markdown +++ b/README.markdown @@ -4,7 +4,7 @@ Copyright © Dave DeLong <http://www.davedelong.com> ## About -DDHotKey is an easy-to-use Cocoa class for registering an application to respond to system key events, or "hotkeys". +DDHotKey is an easy-to-use package for registering an application to respond to system key events, or "hotkeys". A global hotkey is a key combination that always executes a specific action, regardless of which app is frontmost. For example, the Mac OS X default hotkey of "command-space" shows the Spotlight search bar, even if Finder is not the frontmost application. @@ -16,41 +16,10 @@ Permission to use, copy, modify, and/or distribute this software for any purpose ## Usage -### Including DDHotKey in your project - -You will need to copy these six files into your project: - -- DDHotKeyCenter.h -- DDHotKeyCenter.m -- DDHotKeyUtilities.h -- DDHotKeyUtilities.m -- DDHotKeyTextField.h -- DDHotKeyTextField.m - -Your application will need to link against `Carbon.framework`, and you will need to compile your application with the Clang compiler. DDHotKey has been tested with Xcode 5 on OS X Mavericks. No attempt has been made to preserve backwards compatibility. - -### Using DDHotKey in your code - When you wish to create a hotkey, you'll need to do so via the `DDHotKeyCenter` singleton. -You can register a hotkey in one of two ways: via a target/action mechanism, or with a block. The target/action mechanism can take a single extra "object" parameter, which it will pass into the action when the hotkey is fired. Only the `object` parameter is retained by the `DDHotKeyCenter`. In addition, an `NSEvent` object is passed, which contains information regarding the hotkey event (such as the location, the keyCode, the modifierFlags, etc). - -Hotkey actions must have one of two method signatures (the actual selector is irrelevant): - - //a method with a single NSEvent parameter - - (void)hotkeyAction:(NSEvent*)hotKeyEvent; - - OR - - //a method with an NSEvent parameter and an object parameter - - (void)hotkeyAction:(NSEvent*)hotKeyEvent withObject:(id)anObject; - -The other way to register a hotkey is with a block callback. The block must have the following signature: - - void (^)(NSEvent *); - -`DDHotKeyCenter.h` contains a typedef statement to typedef this signature as a `DDHotKeyTask`, for convenience. +All hotkeys execute a block (closure) when invoked. The block is passed an `NSEvent` object, which contains information regarding the hotkey event (such as the location, the keyCode, the modifierFlags, etc). -Any hotkey that you have registered via `DDHotKeyCenter` can be unregistered based on its target, its target and action, or its keycode and modifier flags. +Any hotkey that you have registered via `DDHotKeyCenter` can be unregistered by passing the `DDHotKey` instance back to the `unregister(hotKey:)` method. -DDHotKey also includes a rudimentary `DDHotKeyTextField`, which is an `NSTextField` subclass that simplifies the process of creating a key combination. Simply drop an `NSTextField` into your xib and change its class to `DDHotKeyTextField`. Programmatically, you'll get an NSTextField into which you can type arbitrary key combinations. You access the resulting combination via the textfield's `hotKey` property.
\ No newline at end of file +DDHotKey also includes a rudimentary `DDHotKeyTextField`: an `NSTextField` subclass that simplifies the process of creating a key combination. Simply drop an `NSTextField` into your xib and change its class to `DDHotKeyTextField`. Programmatically, you'll get an `NSTextField` into which you can type arbitrary key combinations. You access the resulting combination via the textfield's `hotKey` property. diff --git a/Sources/DDHotKey/DDHotKey.swift b/Sources/DDHotKey/DDHotKey.swift index 0f3c93e..e8523bf 100644 --- a/Sources/DDHotKey/DDHotKey.swift +++ b/Sources/DDHotKey/DDHotKey.swift @@ -15,9 +15,9 @@ public class DDHotKey { internal let uuid = UUID() internal let keyCode: CGKeyCode internal let modifiers: NSEvent.ModifierFlags - internal let handler: (NSEvent) -> Void + public var handler: ((NSEvent) -> Void)? - public init(keyCode: CGKeyCode, modifiers: NSEvent.ModifierFlags, handler: @escaping (NSEvent) -> Void) { + public init(keyCode: CGKeyCode, modifiers: NSEvent.ModifierFlags, handler: ((NSEvent) -> Void)? = nil) { self.keyCode = keyCode self.modifiers = modifiers self.handler = handler @@ -32,7 +32,7 @@ public class DDHotKey { } internal func invoke(with event: NSEvent) { - handler(event) + handler?(event) } } diff --git a/Sources/DDHotKey/DDHotKeyCenter.swift b/Sources/DDHotKey/DDHotKeyCenter.swift index 84a456d..9625890 100644 --- a/Sources/DDHotKey/DDHotKeyCenter.swift +++ b/Sources/DDHotKey/DDHotKeyCenter.swift @@ -14,13 +14,13 @@ public class DDHotKeyCenter { case alreadyRegistered case tooManyHotKeys case conflictsWithExistingHotKey(DDHotKey) - case unableToRegisterHotKey(OSStatus) + case cannotRegisterHotKey(OSStatus) } public enum UnregistrationError: Error { case notRegistered case unknownHotKey - case unableToUnregisterHotKey(OSStatus) + case cannotUnregisterHotKey(OSStatus) } public static let shared = DDHotKeyCenter() @@ -72,7 +72,7 @@ public class DDHotKeyCenter { let error = RegisterEventHotKey(UInt32(hotKey.keyCode), flags, hotKeyID, GetEventDispatcherTarget(), 0, &hotKeyRef) if error != noErr { - throw RegistrationError.unableToRegisterHotKey(error) + throw RegistrationError.cannotRegisterHotKey(error) } hotKey.hotKeyRef = hotKeyRef @@ -94,7 +94,7 @@ public class DDHotKeyCenter { let status = UnregisterEventHotKey(ref) guard status == noErr else { - throw UnregistrationError.unableToUnregisterHotKey(status) + throw UnregistrationError.cannotUnregisterHotKey(status) } registered.removeValue(forKey: hotKey.uuid) diff --git a/Sources/DDHotKey/DDHotKeyTextField.swift b/Sources/DDHotKey/DDHotKeyTextField.swift new file mode 100644 index 0000000..8c71408 --- /dev/null +++ b/Sources/DDHotKey/DDHotKeyTextField.swift @@ -0,0 +1,124 @@ +// +// DDHotKeyTextField.swift +// +// +// Created by Dave DeLong on 12/8/19. +// + +#if canImport(AppKit) + +import AppKit +import Carbon + +fileprivate let DDFieldEditor: DDHotKeyTextFieldEditor = { + let editor = DDHotKeyTextFieldEditor(frame: NSRect(x: 0, y: 0, width: 100, height: 32)) + editor.isFieldEditor = true + return editor +}() + +open class DDHotKeyTextField: NSTextField { + + open override class var cellClass: AnyClass? { + get { return DDHotKeyTextFieldCell.self } + set { super.cellClass = DDHotKeyTextFieldCell.self } + } + + public var hotKey: DDHotKey? { + didSet { + super.stringValue = "" + } + } + + open override var stringValue: String { + get { + print("DDHotKeyTextField.stringValue is not what you want. Use DDHotKeyTextField.hotKey instead.") + return super.stringValue + } + set { + print("DDHotKeyTextField.stringValue is not what you want. Use DDHotKeyTextField.hotKey instead.") + super.stringValue = newValue + } + } + +} + +private class DDHotKeyTextFieldCell: NSTextFieldCell { + + override func fieldEditor(for controlView: NSView) -> NSTextView? { + guard let hkField = controlView as? DDHotKeyTextField else { return nil } + + let editor = DDFieldEditor + editor.insertionPointColor = editor.backgroundColor + editor.hotKeyField = hkField + return editor + } + +} + +fileprivate class DDHotKeyTextFieldEditor: NSTextView { + private var hasSeenKeyDown: Bool = false + private var globalMonitor: Any? + private var originalHotKey: DDHotKey? + + weak var hotKeyField: DDHotKeyTextField? { + didSet { + originalHotKey = hotKeyField?.hotKey + } + } + + override func becomeFirstResponder() -> Bool { + guard super.becomeFirstResponder() else { return false } + + hasSeenKeyDown = false + globalMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .flagsChanged], handler: self.processHotKeyEvent(_:)) + + return true + } + + override func resignFirstResponder() -> Bool { + guard super.resignFirstResponder() else { return false } + hasSeenKeyDown = false + hotKeyField = nil + if let m = globalMonitor { + NSEvent.removeMonitor(m) + globalMonitor = nil + } + return true + } + + private func processHotKeyEvent(_ event: NSEvent) -> NSEvent? { + let flags = event.modifierFlags + let hasModifier = flags.contains(.command) || flags.contains(.option) || flags.contains(.control) || flags.contains(.shift) || flags.contains(.function) + + if event.type == .keyDown { + hasSeenKeyDown = true + let char = event.charactersIgnoringModifiers?.first + + if hasModifier == false && (char?.isNewline == true || event.keyCode == Int16(kVK_Escape)) { + if event.keyCode == Int16(kVK_Escape) { + hotKeyField?.hotKey = originalHotKey + + let str = stringFrom(keyCode: event.keyCode, modifiers: flags) + textStorage?.mutableString.setString(str.uppercased()) + } + + hotKeyField?.sendAction(hotKeyField?.action, to: hotKeyField?.target) + window?.makeFirstResponder(nil) + } + } + + if hasModifier && (event.type == .keyDown || (event.type == .flagsChanged && hasSeenKeyDown == false)) { + + hotKeyField?.hotKey = DDHotKey(keyCode: event.keyCode, modifiers: flags, handler: originalHotKey?.handler) + + let str = stringFrom(keyCode: event.keyCode, modifiers: flags) + textStorage?.mutableString.setString(str.uppercased()) + hotKeyField?.sendAction(hotKeyField?.action, to: hotKeyField?.target) + } + + return nil + } + +} + +#endif |
