From b129c030a9797bf0aa1306ff955d773a98abc9b1 Mon Sep 17 00:00:00 2001 From: MacRae Linton Date: Mon, 5 Oct 2020 01:01:00 -0700 Subject: [PATCH 1/6] First pass at adding fn key support --- Sources/ShortcutRecorder/SRCommon.m | 1 + .../SRKeyBindingTransformer.m | 6 ++ .../SRModifierFlagsTransformer.m | 8 ++ Sources/ShortcutRecorder/SRRecorderControl.m | 2 + Sources/ShortcutRecorder/SRShortcut.m | 20 +++- Sources/ShortcutRecorder/SRShortcutAction.m | 94 ++++++++++++++++++- .../include/ShortcutRecorder/SRCommon.h | 11 ++- 7 files changed, 134 insertions(+), 8 deletions(-) diff --git a/Sources/ShortcutRecorder/SRCommon.m b/Sources/ShortcutRecorder/SRCommon.m index 27996012..c4945768 100644 --- a/Sources/ShortcutRecorder/SRCommon.m +++ b/Sources/ShortcutRecorder/SRCommon.m @@ -34,6 +34,7 @@ SRModifierFlagString const SRModifierFlagStringOption = @"⌥"; SRModifierFlagString const SRModifierFlagStringShift = @"⇧"; SRModifierFlagString const SRModifierFlagStringControl = @"⌃"; +SRModifierFlagString const SRModifierFlagStringFunction = @"f"; NSBundle *SRBundle() diff --git a/Sources/ShortcutRecorder/SRKeyBindingTransformer.m b/Sources/ShortcutRecorder/SRKeyBindingTransformer.m index 9ca91dc3..0e676451 100644 --- a/Sources/ShortcutRecorder/SRKeyBindingTransformer.m +++ b/Sources/ShortcutRecorder/SRKeyBindingTransformer.m @@ -73,6 +73,9 @@ - (SRShortcut *)transformedValue:(NSString *)aValue if ([modifierFlagsString containsString:@"@"]) modifierFlags |= NSEventModifierFlagCommand; + + if ([modifierFlagsString containsString:@"_"]) + modifierFlags |= NSEventModifierFlagFunction; keyCodeString = keyCodeString.lowercaseString; NSNumber *keyCode = [SRASCIISymbolicKeyCodeTransformer.sharedTransformer reverseTransformedValue:keyCodeString]; @@ -214,6 +217,9 @@ - (NSString *)reverseTransformedValue:(SRShortcut *)aValue if (modifierFlagsValue & NSEventModifierFlagCommand) [keyBinding appendString:@"@"]; + + if (modifierFlagsValue & NSEventModifierFlagFunction) + [keyBinding appendString:@"_"]; if (isNumPad) [keyBinding appendString:@"#"]; diff --git a/Sources/ShortcutRecorder/SRModifierFlagsTransformer.m b/Sources/ShortcutRecorder/SRModifierFlagsTransformer.m index 93841a89..3a75f876 100644 --- a/Sources/ShortcutRecorder/SRModifierFlagsTransformer.m +++ b/Sources/ShortcutRecorder/SRModifierFlagsTransformer.m @@ -107,6 +107,9 @@ - (NSString *)transformedValue:(NSNumber *)aValue layoutDirection:(NSUserInterfa if (flags & NSEventModifierFlagCommand) [flagsStringComponents addObject:SRLoc(@"Command")]; + + if (flags & NSEventModifierFlagFunction) + [flagsStringComponents addObject:SRLoc(@"Function")]; if (aDirection == NSUserInterfaceLayoutDirectionRightToLeft) return [[[flagsStringComponents reverseObjectEnumerator] allObjects] componentsJoinedByString:SRLoc(@"-")]; @@ -158,6 +161,9 @@ - (NSString *)transformedValue:(NSNumber *)aValue layoutDirection:(NSUserInterfa if (flags & NSEventModifierFlagCommand) [flagsStringFragments addObject:SRModifierFlagStringCommand]; + + if (flags & NSEventModifierFlagFunction) + [flagsStringFragments addObject:SRModifierFlagStringFunction]; if (aDirection == NSUserInterfaceLayoutDirectionRightToLeft) return [[[flagsStringFragments reverseObjectEnumerator] allObjects] componentsJoinedByString:@""]; @@ -188,6 +194,8 @@ - (NSNumber *)reverseTransformedValue:(NSString *)aValue flags |= NSEventModifierFlagShift; else if ([substring isEqualToString:SRModifierFlagStringCommand] && (flags & NSEventModifierFlagCommand) == 0) flags |= NSEventModifierFlagCommand; + else if ([substring isEqualToString:SRModifierFlagStringFunction] && (flags & NSEventModifierFlagFunction) == 0) + flags |= NSEventModifierFlagFunction; else { foundInvalidSubstring = YES; diff --git a/Sources/ShortcutRecorder/SRRecorderControl.m b/Sources/ShortcutRecorder/SRRecorderControl.m index 87f350ed..7c039165 100644 --- a/Sources/ShortcutRecorder/SRRecorderControl.m +++ b/Sources/ShortcutRecorder/SRRecorderControl.m @@ -1803,6 +1803,8 @@ - (void)flagsChanged:(NSEvent *)anEvent nextModifierFlags ^= NSEventModifierFlagShift; else if ((modifierFlags & NSEventModifierFlagControl) && (keyCode == kVK_Control || keyCode == kVK_RightControl)) nextModifierFlags ^= NSEventModifierFlagControl; + else if ((modifierFlags & NSEventModifierFlagFunction) && (keyCode == kVK_Function)) + nextModifierFlags ^= NSEventModifierFlagFunction; else if (modifierFlags == 0 && _lastSeenModifierFlags != 0) { SRShortcut *newObjectValue = [SRShortcut shortcutWithCode:SRKeyCodeNone diff --git a/Sources/ShortcutRecorder/SRShortcut.m b/Sources/ShortcutRecorder/SRShortcut.m index 07707bfe..7bd721f4 100644 --- a/Sources/ShortcutRecorder/SRShortcut.m +++ b/Sources/ShortcutRecorder/SRShortcut.m @@ -64,6 +64,8 @@ + (instancetype)shortcutWithEvent:(NSEvent *)aKeyboardEvent ignoringCharacters:( modifierFlags |= NSEventModifierFlagShift; else if (keyCode == kVK_Control || keyCode == kVK_RightControl) modifierFlags |= NSEventModifierFlagControl; + else if (keyCode == kVK_Function) + modifierFlags |= NSEventModifierFlagFunction; keyCode = SRKeyCodeNone; } @@ -321,16 +323,30 @@ - (BOOL)isEqualToKeyEquivalent:(NSString *)aKeyEquivalent NSEventModifierFlagCommand, NSEventModifierFlagShift, NSEventModifierFlagOption, + NSEventModifierFlagFunction, NSEventModifierFlagControl | NSEventModifierFlagCommand, NSEventModifierFlagControl | NSEventModifierFlagShift, NSEventModifierFlagControl | NSEventModifierFlagOption, + NSEventModifierFlagControl | NSEventModifierFlagFunction, NSEventModifierFlagCommand | NSEventModifierFlagShift, NSEventModifierFlagCommand | NSEventModifierFlagOption, + NSEventModifierFlagCommand | NSEventModifierFlagFunction, NSEventModifierFlagShift | NSEventModifierFlagOption, + NSEventModifierFlagShift | NSEventModifierFlagFunction, + NSEventModifierFlagOption | NSEventModifierFlagFunction, NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagShift, NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagOption, + NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagFunction, NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagOption, - NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagOption + NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagFunction, + NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagFunction, + NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagOption, + NSEventModifierFlagFunction | NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagOption, + NSEventModifierFlagControl | NSEventModifierFlagFunction | NSEventModifierFlagShift | NSEventModifierFlagOption, + NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagFunction | NSEventModifierFlagOption, + NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagFunction, + NSEventModifierFlagControl | NSEventModifierFlagCommand | NSEventModifierFlagShift | NSEventModifierFlagOption, NSEventModifierFlagFunction + }; static const size_t PossibleFlagsSize = sizeof(PossibleFlags) / sizeof(NSEventModifierFlags); @@ -514,6 +530,7 @@ - (UInt32)carbonModifierFlags (aModifierFlags & NSEventModifierFlagOption ? SRLoc(@"Option-") : @""), (aModifierFlags & NSEventModifierFlagControl ? SRLoc(@"Control-") : @""), (aModifierFlags & NSEventModifierFlagShift ? SRLoc(@"Shift-") : @""), + (aModifierFlags & NSEventModifierFlagFunction ? SRLoc(@"Fn-") : @""), c]; } @@ -531,6 +548,7 @@ - (UInt32)carbonModifierFlags (aModifierFlags & NSEventModifierFlagOption ? SRLoc(@"Option-") : @""), (aModifierFlags & NSEventModifierFlagControl ? SRLoc(@"Control-") : @""), (aModifierFlags & NSEventModifierFlagShift ? SRLoc(@"Shift-") : @""), + (aModifierFlags & NSEventModifierFlagFunction ? SRLoc(@"Fn-") : @""), c]; } diff --git a/Sources/ShortcutRecorder/SRShortcutAction.m b/Sources/ShortcutRecorder/SRShortcutAction.m index 67dd603a..a1b3f0b6 100644 --- a/Sources/ShortcutRecorder/SRShortcutAction.m +++ b/Sources/ShortcutRecorder/SRShortcutAction.m @@ -373,6 +373,9 @@ + (SRKeyEventType)SR_keyEventTypeForEventType:(NSEventType)anEventType keyCode:(unsigned short)aKeyCode modifierFlags:(NSEventModifierFlags)aModifierFlags { + // picking the event type here, need to have key-up go right. + // if the ctrl key is down, we interpret other flag-ups as an up. + // if the ctrl key is up, we don't get shit. SRKeyEventType eventType = SRKeyEventTypeDown; switch (anEventType) @@ -391,11 +394,22 @@ + (SRKeyEventType)SR_keyEventTypeForEventType:(NSEventType)anEventType if (keyCode == kVK_Command || keyCode == kVK_RightCommand) eventType = modifierFlags & NSEventModifierFlagCommand ? SRKeyEventTypeDown : SRKeyEventTypeUp; else if (keyCode == kVK_Option || keyCode == kVK_RightOption) - eventType = modifierFlags & NSEventModifierFlagOption ? SRKeyEventTypeDown : SRKeyEventTypeUp; + { + NSLog(@"KEY CO OPTION"); + eventType = modifierFlags & NSEventModifierFlagOption ? SRKeyEventTypeDown : SRKeyEventTypeUp; + } else if (keyCode == kVK_Shift || keyCode == kVK_RightShift) eventType = modifierFlags & NSEventModifierFlagShift ? SRKeyEventTypeDown : SRKeyEventTypeUp; else if (keyCode == kVK_Control || keyCode == kVK_RightControl) - eventType = modifierFlags & NSEventModifierFlagControl ? SRKeyEventTypeDown : SRKeyEventTypeUp; + { + NSLog(@"EKYCOD CONTROLLL %lu", (unsigned long)modifierFlags); + eventType = modifierFlags & NSEventModifierFlagControl ? SRKeyEventTypeDown : SRKeyEventTypeUp; + } + else if (keyCode == kVK_Function) + { + NSLog(@"ECONTLR FUNCTIONOIN %lu", (unsigned long)modifierFlags); + eventType = modifierFlags & NSEventModifierFlagFunction ? SRKeyEventTypeDown : SRKeyEventTypeUp; + } else os_trace("#Error Unexpected key code %hu for the FlagsChanged event", keyCode); break; @@ -1049,6 +1063,7 @@ - (void)pause - (OSStatus)handleEvent:(EventRef)anEvent { + NSLog(@"HANDLING ENEV"); __block OSStatus error = eventNotHandledErr; os_activity_initiate("-[SRGlobalShortcutMonitor handleEvent:]", OS_ACTIVITY_FLAG_DETACHED, ^{ @@ -1187,6 +1202,7 @@ - (void)_registerHotKeyForShortcutIfNeeded:(SRShortcut *)aShortcut if (aShortcut.keyCode == SRKeyCodeNone) { + NSLog(@"Acutally aaborting//"); os_trace_error("#Error Shortcut without a key code cannot be registered as Carbon hot key"); return; } @@ -1347,6 +1363,7 @@ - (void)dealloc - (CGEventRef)handleEvent:(CGEventRef)anEvent { + NSLog(@"AX HANDLING EVENT"); __block __auto_type result = anEvent; os_activity_initiate("-[SRAXGlobalShortcutMonitor handleEvent:]", OS_ACTIVITY_FLAG_DETACHED, ^{ @@ -1354,34 +1371,100 @@ - (CGEventRef)handleEvent:(CGEventRef)anEvent __auto_type eventType = CGEventGetType(anEvent); __auto_type cocoaModifierFlags = SRCoreGraphicsToCocoaFlags(CGEventGetFlags(anEvent)); NSEventType cocoaEventType; + + __auto_type isRepeat = CGEventGetIntegerValueField(anEvent, kCGKeyboardEventAutorepeat); + + if (isRepeat) { + NSLog(@"IS REPEAT!!"); + return; + } else { + NSLog(@"is not repeat"); + } + + switch (eventType) { case kCGEventKeyDown: + NSLog(@"DOWN"); cocoaEventType = NSEventTypeKeyDown; break; case kCGEventKeyUp: + NSLog(@"UPP"); cocoaEventType = NSEventTypeKeyUp; break; case kCGEventFlagsChanged: + NSLog(@"FLARGS"); cocoaEventType = NSEventTypeFlagsChanged; break; default: cocoaEventType = 0; break; } + + NSString *keycode = @""; + switch(eventKeyCode) { + case kVK_Function: + keycode = @"Function"; + break; + case kVK_Control: + keycode = @"Control"; + break; + case kVK_Option: + keycode = @"Option"; + break; + case kVK_Command: + keycode = @"Command"; + break; + case kVK_ANSI_K: + keycode = @"K"; + break; + default: + keycode = @"UKNOWN"; + break; + } + NSLog(@"Keycode: %@", keycode); + // Find the event type + __auto_type keyEventType = [NSEvent SR_keyEventTypeForEventType:cocoaEventType + keyCode:(unsigned short)eventKeyCode + modifierFlags:cocoaModifierFlags]; + NSString *evType; + switch (keyEventType) { + case SRKeyEventTypeDown: + evType = @"DOWN"; + break; + case SRKeyEventTypeUp: + evType = @"UP"; + break; + } + NSLog(@"KEY EVENT: %@", evType); + + // here's my fix for this problem. if it's an up, and modifiers, or the modifier back into the flags + if (eventType == kCGEventFlagsChanged && keyEventType == SRKeyEventTypeUp) { + if (eventKeyCode == kVK_Command || eventKeyCode == kVK_RightCommand) + cocoaModifierFlags |= NSEventModifierFlagCommand; + else if (eventKeyCode == kVK_Option || eventKeyCode == kVK_RightOption) + cocoaModifierFlags |= NSEventModifierFlagOption; + else if (eventKeyCode == kVK_Shift || eventKeyCode == kVK_RightShift) + cocoaModifierFlags |= NSEventModifierFlagShift; + else if (eventKeyCode == kVK_Control || eventKeyCode == kVK_RightControl) + cocoaModifierFlags |= NSEventModifierFlagControl; + else if (eventKeyCode == kVK_Function) + cocoaModifierFlags |= NSEventModifierFlagFunction; + } + __auto_type shortcut = [SRShortcut shortcutWithCode:eventType != kCGEventFlagsChanged ? (SRKeyCode)eventKeyCode : SRKeyCodeNone modifierFlags:cocoaModifierFlags characters:nil charactersIgnoringModifiers:nil]; - __auto_type keyEventType = [NSEvent SR_keyEventTypeForEventType:cocoaEventType - keyCode:(unsigned short)eventKeyCode - modifierFlags:cocoaModifierFlags]; + __auto_type actions = [self enabledActionsForShortcut:shortcut keyEvent:keyEventType]; + NSLog(@"FOUND ACTIONS %lu", (unsigned long)[actions count]); __block BOOL isHandled = NO; [actions enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(SRShortcutAction *obj, NSUInteger idx, BOOL *stop) { + NSLog(@"CHECKING SOMETHING>>"); *stop = isHandled = [obj performActionOnTarget:nil]; }]; @@ -1398,6 +1481,7 @@ - (CGEventRef)handleEvent:(CGEventRef)anEvent - (void)didAddShortcut:(SRShortcut *)aShortcut { + NSLog(@"ADDING A SHORTUCT FO RAX"); if (_shortcuts.count) CGEventTapEnable(_eventTap, true); } diff --git a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h index 37e8ebdf..f1a10b90 100644 --- a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h +++ b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN Mask representing subset of Cocoa modifier flags suitable for shortcuts. */ NS_SWIFT_NAME(CocoaModifierFlagsMask) -static const NSEventModifierFlags SRCocoaModifierFlagsMask = NSEventModifierFlagCommand | NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagControl; +static const NSEventModifierFlags SRCocoaModifierFlagsMask = NSEventModifierFlagCommand | NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagControl | NSEventModifierFlagFunction; /*! @@ -25,7 +25,7 @@ static const UInt32 SRCarbonModifierFlagsMask = cmdKey | optionKey | shiftKey | NS_SWIFT_NAME(CoreGraphicsModifierFlagsMask) -static const CGEventFlags SRCoreGraphicsModifierFlagsMask = kCGEventFlagMaskCommand | kCGEventFlagMaskAlternate | kCGEventFlagMaskShift | kCGEventFlagMaskControl; +static const CGEventFlags SRCoreGraphicsModifierFlagsMask = kCGEventFlagMaskCommand | kCGEventFlagMaskAlternate | kCGEventFlagMaskShift | kCGEventFlagMaskControl | kCGEventFlagMaskSecondaryFn; /*! Dawable unicode characters for key codes that do not have appropriate constants in Carbon and Cocoa. @@ -246,6 +246,7 @@ extern SRModifierFlagString const SRModifierFlagStringCommand; extern SRModifierFlagString const SRModifierFlagStringOption; extern SRModifierFlagString const SRModifierFlagStringShift; extern SRModifierFlagString const SRModifierFlagStringControl; +extern SRModifierFlagString const SRModifierFlagStringFunction; /*! @@ -300,6 +301,9 @@ NS_INLINE UInt32 SRCocoaToCarbonFlags(NSEventModifierFlags aCocoaFlags) if (aCocoaFlags & NSEventModifierFlagShift) carbonFlags |= shiftKey; + + if (aCocoaFlags & NSEventModifierFlagFunction) + NSLog(@"WELL WE TRIED TO CONVERT"); return carbonFlags; } @@ -320,6 +324,9 @@ NS_INLINE NSEventModifierFlags SRCoreGraphicsToCocoaFlags(CGEventFlags aCoreGrap if (aCoreGraphicsFlags & kCGEventFlagMaskShift) cocoaFlags |= NSEventModifierFlagShift; + + if (aCoreGraphicsFlags & kCGEventFlagMaskSecondaryFn) + cocoaFlags |= NSEventModifierFlagFunction; return cocoaFlags; } From 7baa8e1f19560d4e8c67bfdc65b9c9d1703b5e80 Mon Sep 17 00:00:00 2001 From: MacRae Linton Date: Thu, 8 Oct 2020 01:30:00 -0700 Subject: [PATCH 2/6] I think our detection for down and up are correct now. --- Sources/ShortcutRecorder/SRRecorderControl.m | 5 +- Sources/ShortcutRecorder/SRShortcut.m | 32 +++++ Sources/ShortcutRecorder/SRShortcutAction.m | 115 ++++++++++++++++-- .../include/ShortcutRecorder/SRCommon.h | 17 +++ .../include/ShortcutRecorder/SRShortcut.h | 19 +++ .../ShortcutRecorder/SRShortcutAction.h | 20 +++ 6 files changed, 196 insertions(+), 12 deletions(-) diff --git a/Sources/ShortcutRecorder/SRRecorderControl.m b/Sources/ShortcutRecorder/SRRecorderControl.m index 7c039165..b0a8a26f 100644 --- a/Sources/ShortcutRecorder/SRRecorderControl.m +++ b/Sources/ShortcutRecorder/SRRecorderControl.m @@ -573,7 +573,7 @@ - (BOOL)beginRecording if (self.pausesGlobalShortcutMonitorWhileRecording) { _didPauseGlobalShortcutMonitor = YES; - [SRGlobalShortcutMonitor.sharedMonitor pause]; + [SRAXGlobalShortcutMonitor.sharedMonitor pause]; } NSDictionary *bindingInfo = [self infoForBinding:NSValueBinding]; @@ -649,8 +649,9 @@ - (void)endRecordingWithObjectValue:(SRShortcut *)anObjectValue if (_didPauseGlobalShortcutMonitor) { + NSLog(@"UNPAUSING"); // So, even if this is correctly pausing (even though it seems like the wrong thing) it's unpaused before the UP _didPauseGlobalShortcutMonitor = NO; - [SRGlobalShortcutMonitor.sharedMonitor resume]; + [SRAXGlobalShortcutMonitor.sharedMonitor resume]; } NSDictionary *bindingInfo = [self infoForBinding:NSValueBinding]; diff --git a/Sources/ShortcutRecorder/SRShortcut.m b/Sources/ShortcutRecorder/SRShortcut.m index 7bd721f4..80b3dd94 100644 --- a/Sources/ShortcutRecorder/SRShortcut.m +++ b/Sources/ShortcutRecorder/SRShortcut.m @@ -243,6 +243,38 @@ - (NSString *)readableStringRepresentation:(BOOL)isASCII #pragma clang diagnostic pop } +- (BOOL) shouldFireForShortcut:(SRShortcut *)shortcut { + + if (self.keyCode != SRKeyCodeNone) { + return [self isEqualToShortcut:shortcut]; + } + + // for a modifier only shortcut, we fire if the incoming shortcut is + // 1. also a modifier only shortcut + // 2. the currently pressed modifiers &'s cleanly with self + // This way, if you have extranious modifiers pressed, we still fire when you hit the target ones. + + if (shortcut.keyCode == SRKeyCodeNone) { + if ((self.modifierFlags & shortcut.modifierFlags) == self.modifierFlags) { + return true; + } else { + return false; + } + } else { + return false; + } + +} + +// for non-modifiers, it's just if the keycode matches. +// for modifiers, it's the key is in the set, it's an up. +- (BOOL) keyBreaksShortcut:(int)keyCode { + if (self.keyCode != SRKeyCodeNone) { + return self.keyCode == keyCode; + } else { + return SRKeyCodeToCocoaFlag(keyCode) & self.modifierFlags; + } +} #pragma mark Equality diff --git a/Sources/ShortcutRecorder/SRShortcutAction.m b/Sources/ShortcutRecorder/SRShortcutAction.m index a1b3f0b6..c5a35b75 100644 --- a/Sources/ShortcutRecorder/SRShortcutAction.m +++ b/Sources/ShortcutRecorder/SRShortcutAction.m @@ -1289,6 +1289,18 @@ - (void)willRemoveShortcut:(SRShortcut *)aShortcut @implementation SRAXGlobalShortcutMonitor { BOOL _canActivelyFilterEvents; + NSInteger _disableCounter; + SRShortcut *_downShortcut; +} + ++ (SRAXGlobalShortcutMonitor *)sharedMonitor +{ + static SRAXGlobalShortcutMonitor *Shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Shared = [SRAXGlobalShortcutMonitor new]; + }); + return Shared; } CGEventRef _Nullable _SRQuartzEventHandler(CGEventTapProxy aProxy, CGEventType aType, CGEventRef anEvent, void * _Nullable aUserInfo) @@ -1361,6 +1373,40 @@ - (void)dealloc #pragma mark Methods +- (void)resume +{ + @synchronized (_actions) + { + os_trace_debug("Global Shortcut Monitor counter: %ld -> %ld", _disableCounter, _disableCounter - 1); + _disableCounter -= 1; + +// if (_disableCounter == 0) +// { +// for (SRShortcut *shortcut in _shortcuts) +// [self _registerHotKeyForShortcutIfNeeded:shortcut]; +// } +// +// [self _installEventHandlerIfNeeded]; + } +} + +- (void)pause +{ + @synchronized (_actions) + { + os_trace_debug("Global Shortcut Monitor counter: %ld -> %ld", _disableCounter, _disableCounter + 1); + _disableCounter += 1; + +// if (_disableCounter == 1) +// { +// for (SRShortcut *shortcut in _shortcuts) +// [self _unregisterHotKeyForShortcutIfNeeded:shortcut]; +// } +// +// [self _removeEventHandlerIfNeeded]; + } +} + - (CGEventRef)handleEvent:(CGEventRef)anEvent { NSLog(@"AX HANDLING EVENT"); @@ -1372,6 +1418,11 @@ - (CGEventRef)handleEvent:(CGEventRef)anEvent __auto_type cocoaModifierFlags = SRCoreGraphicsToCocoaFlags(CGEventGetFlags(anEvent)); NSEventType cocoaEventType; + if (_disableCounter != 0) { + NSLog(@"Pausedddd."); + return; + } + __auto_type isRepeat = CGEventGetIntegerValueField(anEvent, kCGKeyboardEventAutorepeat); if (isRepeat) { @@ -1439,18 +1490,11 @@ - (CGEventRef)handleEvent:(CGEventRef)anEvent } NSLog(@"KEY EVENT: %@", evType); + // This is a normalization problem. The only way a keycode is a flag is if we have flags changed. // here's my fix for this problem. if it's an up, and modifiers, or the modifier back into the flags if (eventType == kCGEventFlagsChanged && keyEventType == SRKeyEventTypeUp) { - if (eventKeyCode == kVK_Command || eventKeyCode == kVK_RightCommand) - cocoaModifierFlags |= NSEventModifierFlagCommand; - else if (eventKeyCode == kVK_Option || eventKeyCode == kVK_RightOption) - cocoaModifierFlags |= NSEventModifierFlagOption; - else if (eventKeyCode == kVK_Shift || eventKeyCode == kVK_RightShift) - cocoaModifierFlags |= NSEventModifierFlagShift; - else if (eventKeyCode == kVK_Control || eventKeyCode == kVK_RightControl) - cocoaModifierFlags |= NSEventModifierFlagControl; - else if (eventKeyCode == kVK_Function) - cocoaModifierFlags |= NSEventModifierFlagFunction; + NSEventModifierFlags keyFlag = SRKeyCodeToCocoaFlag(eventKeyCode); + cocoaModifierFlags |= keyFlag; } __auto_type shortcut = [SRShortcut shortcutWithCode:eventType != kCGEventFlagsChanged ? (SRKeyCode)eventKeyCode : SRKeyCodeNone @@ -1458,6 +1502,57 @@ - (CGEventRef)handleEvent:(CGEventRef)anEvent characters:nil charactersIgnoringModifiers:nil]; + + // SO this is a down for this shortcut? + // is it ok if we limit it to one shortcut at a time? + // we can't get stuck . + + // "hold ctl, hold opt, hold k, release opt, release k" + // "hold ctrl, hold fn, release ctrl, release fn" + // do we up on any up after we've downed? + // for modifiers, up is if any of the modifiers is upped., extra modifiers ignored for up. + // might be the same for down, actually. + + // i think we should change the comparison strategy. instead of looking for it in a dict, we shoulf have a fn. + + + // IF shortcutCode != SRKEyCodeName + // IF Down SET IS DOWN (for key?) + // IF UP// check if down is set, then go. + + // So maybe there isn't a way to tell if a key is related to other keys? + // IF ShortcutCode == CODENONE + // IF DOwn ( // check the flargs. If any of them are the key? -- hard to get there. rn we can only ask for them by name. + // IF UP // check if down is set, then compare, if everything is still down, do nothing, if any have come up, then done. + // if up, we have a keycap, so we can just ask if that keycap is in the down flargs + + // what happens if you change the key combo? Does that update the actions? + // I'm still working out how the key combo is stored. + + if (self->_downShortcut == nil) { + if (keyEventType == SRKeyEventTypeDown) { + __auto_type targetShortcuts = [self shortcuts]; + + for (SRShortcut *targetShortcut in targetShortcuts) { + NSLog(@"MONITORING %@", targetShortcut); + if ([targetShortcut shouldFireForShortcut: shortcut]) { + NSLog(@"FIRING DOWN"); + self->_downShortcut = targetShortcut; + break; + } + } + } + } else { + if (keyEventType == SRKeyEventTypeUp) { + if ([self->_downShortcut keyBreaksShortcut:eventKeyCode]) { + NSLog(@"FIRING UP"); + self->_downShortcut = nil; + } + } + } + + // next, actually fire the action when the above fires. + __auto_type actions = [self enabledActionsForShortcut:shortcut keyEvent:keyEventType]; NSLog(@"FOUND ACTIONS %lu", (unsigned long)[actions count]); __block BOOL isHandled = NO; diff --git a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h index f1a10b90..08753d78 100644 --- a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h +++ b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h @@ -331,6 +331,23 @@ NS_INLINE NSEventModifierFlags SRCoreGraphicsToCocoaFlags(CGEventFlags aCoreGrap return cocoaFlags; } +NS_SWIFT_NAME(keyCodeToCocoaFlag(_:)) +NS_INLINE NSEventModifierFlags SRKeyCodeToCocoaFlag(int keyCode) +{ + if (keyCode == kVK_Command || keyCode == kVK_RightCommand) + return NSEventModifierFlagCommand; + else if (keyCode == kVK_Option || keyCode == kVK_RightOption) + return NSEventModifierFlagOption; + else if (keyCode == kVK_Shift || keyCode == kVK_RightShift) + return NSEventModifierFlagShift; + else if (keyCode == kVK_Control || keyCode == kVK_RightControl) + return NSEventModifierFlagControl; + else if (keyCode == kVK_Function) + return NSEventModifierFlagFunction; + else + return 0; +} + /*! Return Bundle where resources can be found. diff --git a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcut.h b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcut.h index 001e085f..dc5cce14 100644 --- a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcut.h +++ b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcut.h @@ -186,6 +186,25 @@ NS_SWIFT_NAME(Shortcut) */ - (NSString *)readableStringRepresentation:(BOOL)isASCII NS_SWIFT_NAME(readableStringRepresentation(isASCII:)); + +/*! + Return true if the pressed shortcut matches self. + + For most shotrcuts, that will be a simple equality check. + For modifier only shortcuts, we fire on &. + + */ +- (BOOL) shouldFireForShortcut:(SRShortcut *)shortcut; + +/*! + Return true if this key going up breaks the shortcut. + + For most shotrcuts, that is if the keycode matches the shortcut. Modifiers are ignored for ending a press. + For modifier only shortcuts, it's if that key is included in any of the shortcut's modifiers. + + */ +- (BOOL) keyBreaksShortcut:(int)keyCode; + /*! Compare the shortcut to another shortcut. diff --git a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h index 2884df85..ca05ea32 100644 --- a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h +++ b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h @@ -406,6 +406,8 @@ NS_SWIFT_NAME(GlobalShortcutMonitor) NS_SWIFT_NAME(AXGlobalShortcutMonitor) @interface SRAXGlobalShortcutMonitor : SRShortcutMonitor +@property (class, readonly) SRAXGlobalShortcutMonitor *sharedMonitor NS_SWIFT_NAME(shared); + /*! Mach port that corresponds to the event tap used under the hood. @@ -471,6 +473,24 @@ NS_SWIFT_NAME(AXGlobalShortcutMonitor) */ - (nullable CGEventRef)handleEvent:(CGEventRef)anEvent; +/*! + Enable system-wide shortcut monitoring. + + @discussion + This method has an underlying counter, i.e. every pause must be matched with a resume. + The initial state is resumed. + */ +- (void)resume; + +/*! + Disable system-wide shortcut monitoring. + + @discussion + This method has an underlying counter, i.e. every resume must be matched with a pause. + The initial state is resumed. + */ +- (void)pause; + @end From 00747312c42fc57018f0f564ce18eca3fbba91bb Mon Sep 17 00:00:00 2001 From: MacRae Linton Date: Thu, 8 Oct 2020 23:09:45 -0700 Subject: [PATCH 3/6] Logic for doing modifiers correctly is set. --- Sources/ShortcutRecorder/SRShortcut.m | 8 +- Sources/ShortcutRecorder/SRShortcutAction.m | 89 +++++-------------- .../include/ShortcutRecorder/SRCommon.h | 2 +- .../include/ShortcutRecorder/SRShortcut.h | 2 +- 4 files changed, 27 insertions(+), 74 deletions(-) diff --git a/Sources/ShortcutRecorder/SRShortcut.m b/Sources/ShortcutRecorder/SRShortcut.m index 80b3dd94..0b2aaf99 100644 --- a/Sources/ShortcutRecorder/SRShortcut.m +++ b/Sources/ShortcutRecorder/SRShortcut.m @@ -268,11 +268,11 @@ - (BOOL) shouldFireForShortcut:(SRShortcut *)shortcut { // for non-modifiers, it's just if the keycode matches. // for modifiers, it's the key is in the set, it's an up. -- (BOOL) keyBreaksShortcut:(int)keyCode { +- (BOOL) keyBreaksShortcut:(SRKeyCode)keyCode { if (self.keyCode != SRKeyCodeNone) { return self.keyCode == keyCode; } else { - return SRKeyCodeToCocoaFlag(keyCode) & self.modifierFlags; + return (SRKeyCodeToCocoaFlag(keyCode) & self.modifierFlags) != 0; } } @@ -557,7 +557,7 @@ - (UInt32)carbonModifierFlags if (!c) c = [NSString stringWithFormat:@"<%hu>", aKeyCode]; - return [NSString stringWithFormat:@"%@%@%@%@%@", + return [NSString stringWithFormat:@"%@%@%@%@%@%@", (aModifierFlags & NSEventModifierFlagCommand ? SRLoc(@"Command-") : @""), (aModifierFlags & NSEventModifierFlagOption ? SRLoc(@"Option-") : @""), (aModifierFlags & NSEventModifierFlagControl ? SRLoc(@"Control-") : @""), @@ -575,7 +575,7 @@ - (UInt32)carbonModifierFlags if (!c) c = [NSString stringWithFormat:@"<%hu>", aKeyCode]; - return [NSString stringWithFormat:@"%@%@%@%@%@", + return [NSString stringWithFormat:@"%@%@%@%@%@%@", (aModifierFlags & NSEventModifierFlagCommand ? SRLoc(@"Command-") : @""), (aModifierFlags & NSEventModifierFlagOption ? SRLoc(@"Option-") : @""), (aModifierFlags & NSEventModifierFlagControl ? SRLoc(@"Control-") : @""), diff --git a/Sources/ShortcutRecorder/SRShortcutAction.m b/Sources/ShortcutRecorder/SRShortcutAction.m index c5a35b75..189abe24 100644 --- a/Sources/ShortcutRecorder/SRShortcutAction.m +++ b/Sources/ShortcutRecorder/SRShortcutAction.m @@ -395,19 +395,16 @@ + (SRKeyEventType)SR_keyEventTypeForEventType:(NSEventType)anEventType eventType = modifierFlags & NSEventModifierFlagCommand ? SRKeyEventTypeDown : SRKeyEventTypeUp; else if (keyCode == kVK_Option || keyCode == kVK_RightOption) { - NSLog(@"KEY CO OPTION"); eventType = modifierFlags & NSEventModifierFlagOption ? SRKeyEventTypeDown : SRKeyEventTypeUp; } else if (keyCode == kVK_Shift || keyCode == kVK_RightShift) eventType = modifierFlags & NSEventModifierFlagShift ? SRKeyEventTypeDown : SRKeyEventTypeUp; else if (keyCode == kVK_Control || keyCode == kVK_RightControl) { - NSLog(@"EKYCOD CONTROLLL %lu", (unsigned long)modifierFlags); eventType = modifierFlags & NSEventModifierFlagControl ? SRKeyEventTypeDown : SRKeyEventTypeUp; } else if (keyCode == kVK_Function) { - NSLog(@"ECONTLR FUNCTIONOIN %lu", (unsigned long)modifierFlags); eventType = modifierFlags & NSEventModifierFlagFunction ? SRKeyEventTypeDown : SRKeyEventTypeUp; } else @@ -1409,7 +1406,6 @@ - (void)pause - (CGEventRef)handleEvent:(CGEventRef)anEvent { - NSLog(@"AX HANDLING EVENT"); __block __auto_type result = anEvent; os_activity_initiate("-[SRAXGlobalShortcutMonitor handleEvent:]", OS_ACTIVITY_FLAG_DETACHED, ^{ @@ -1419,76 +1415,35 @@ - (CGEventRef)handleEvent:(CGEventRef)anEvent NSEventType cocoaEventType; if (_disableCounter != 0) { - NSLog(@"Pausedddd."); return; } __auto_type isRepeat = CGEventGetIntegerValueField(anEvent, kCGKeyboardEventAutorepeat); if (isRepeat) { - NSLog(@"IS REPEAT!!"); return; - } else { - NSLog(@"is not repeat"); } - switch (eventType) { case kCGEventKeyDown: - NSLog(@"DOWN"); cocoaEventType = NSEventTypeKeyDown; break; case kCGEventKeyUp: - NSLog(@"UPP"); cocoaEventType = NSEventTypeKeyUp; break; case kCGEventFlagsChanged: - NSLog(@"FLARGS"); cocoaEventType = NSEventTypeFlagsChanged; break; default: cocoaEventType = 0; break; } - - NSString *keycode = @""; - switch(eventKeyCode) { - case kVK_Function: - keycode = @"Function"; - break; - case kVK_Control: - keycode = @"Control"; - break; - case kVK_Option: - keycode = @"Option"; - break; - case kVK_Command: - keycode = @"Command"; - break; - case kVK_ANSI_K: - keycode = @"K"; - break; - default: - keycode = @"UKNOWN"; - break; - } - NSLog(@"Keycode: %@", keycode); // Find the event type __auto_type keyEventType = [NSEvent SR_keyEventTypeForEventType:cocoaEventType keyCode:(unsigned short)eventKeyCode modifierFlags:cocoaModifierFlags]; - NSString *evType; - switch (keyEventType) { - case SRKeyEventTypeDown: - evType = @"DOWN"; - break; - case SRKeyEventTypeUp: - evType = @"UP"; - break; - } - NSLog(@"KEY EVENT: %@", evType); // This is a normalization problem. The only way a keycode is a flag is if we have flags changed. // here's my fix for this problem. if it's an up, and modifiers, or the modifier back into the flags @@ -1528,38 +1483,37 @@ - (CGEventRef)handleEvent:(CGEventRef)anEvent // what happens if you change the key combo? Does that update the actions? // I'm still working out how the key combo is stored. + + NSArray *actionsToFire= ^NSArray*() { - if (self->_downShortcut == nil) { - if (keyEventType == SRKeyEventTypeDown) { - __auto_type targetShortcuts = [self shortcuts]; - - for (SRShortcut *targetShortcut in targetShortcuts) { - NSLog(@"MONITORING %@", targetShortcut); - if ([targetShortcut shouldFireForShortcut: shortcut]) { - NSLog(@"FIRING DOWN"); - self->_downShortcut = targetShortcut; - break; + if (self->_downShortcut == nil) { + if (keyEventType == SRKeyEventTypeDown) { + __auto_type targetShortcuts = [self shortcuts]; + + for (SRShortcut *targetShortcut in targetShortcuts) { + if ([targetShortcut shouldFireForShortcut: shortcut]) { + self->_downShortcut = targetShortcut; + return [self enabledActionsForShortcut:targetShortcut keyEvent:keyEventType]; + } } } - } - } else { - if (keyEventType == SRKeyEventTypeUp) { - if ([self->_downShortcut keyBreaksShortcut:eventKeyCode]) { - NSLog(@"FIRING UP"); + } else { + if (keyEventType == SRKeyEventTypeUp) { + if ([self->_downShortcut keyBreaksShortcut:eventKeyCode]) { + NSArray *actions = [self enabledActionsForShortcut:self->_downShortcut keyEvent:keyEventType]; self->_downShortcut = nil; + return actions; + } } } - } - - // next, actually fire the action when the above fires. + + return @[]; + }(); - __auto_type actions = [self enabledActionsForShortcut:shortcut keyEvent:keyEventType]; - NSLog(@"FOUND ACTIONS %lu", (unsigned long)[actions count]); __block BOOL isHandled = NO; - [actions enumerateObjectsWithOptions:NSEnumerationReverse + [actionsToFire enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(SRShortcutAction *obj, NSUInteger idx, BOOL *stop) { - NSLog(@"CHECKING SOMETHING>>"); *stop = isHandled = [obj performActionOnTarget:nil]; }]; @@ -1576,7 +1530,6 @@ - (CGEventRef)handleEvent:(CGEventRef)anEvent - (void)didAddShortcut:(SRShortcut *)aShortcut { - NSLog(@"ADDING A SHORTUCT FO RAX"); if (_shortcuts.count) CGEventTapEnable(_eventTap, true); } diff --git a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h index 08753d78..0e9c9885 100644 --- a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h +++ b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h @@ -332,7 +332,7 @@ NS_INLINE NSEventModifierFlags SRCoreGraphicsToCocoaFlags(CGEventFlags aCoreGrap } NS_SWIFT_NAME(keyCodeToCocoaFlag(_:)) -NS_INLINE NSEventModifierFlags SRKeyCodeToCocoaFlag(int keyCode) +NS_INLINE NSEventModifierFlags SRKeyCodeToCocoaFlag(SRKeyCode keyCode) { if (keyCode == kVK_Command || keyCode == kVK_RightCommand) return NSEventModifierFlagCommand; diff --git a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcut.h b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcut.h index dc5cce14..80976d13 100644 --- a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcut.h +++ b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcut.h @@ -203,7 +203,7 @@ NS_SWIFT_NAME(Shortcut) For modifier only shortcuts, it's if that key is included in any of the shortcut's modifiers. */ -- (BOOL) keyBreaksShortcut:(int)keyCode; +- (BOOL) keyBreaksShortcut:(SRKeyCode)keyCode; /*! Compare the shortcut to another shortcut. From 5bde12ba01a8874beddb8e8966a711d10c03f68a Mon Sep 17 00:00:00 2001 From: MacRae Linton Date: Tue, 13 Oct 2020 21:36:16 -0700 Subject: [PATCH 4/6] Add fn key support --- Sources/ShortcutRecorder/SRCommon.m | 2 +- .../SRModifierFlagsTransformer.m | 42 ++++++++-------- Sources/ShortcutRecorder/SRRecorderControl.m | 1 - Sources/ShortcutRecorder/SRShortcut.m | 25 +++------- .../include/ShortcutRecorder/SRCommon.h | 50 ++++++++++++++++++- .../ShortcutRecorderTests/SRCommonTests.swift | 2 +- .../SRModifierFlagsTransformerTests.swift | 2 +- .../SRShortcutTests.swift | 35 +++++++++++++ 8 files changed, 117 insertions(+), 42 deletions(-) diff --git a/Sources/ShortcutRecorder/SRCommon.m b/Sources/ShortcutRecorder/SRCommon.m index c4945768..c138426f 100644 --- a/Sources/ShortcutRecorder/SRCommon.m +++ b/Sources/ShortcutRecorder/SRCommon.m @@ -34,7 +34,7 @@ SRModifierFlagString const SRModifierFlagStringOption = @"⌥"; SRModifierFlagString const SRModifierFlagStringShift = @"⇧"; SRModifierFlagString const SRModifierFlagStringControl = @"⌃"; -SRModifierFlagString const SRModifierFlagStringFunction = @"f"; +SRModifierFlagString const SRModifierFlagStringFunction = @"fn"; NSBundle *SRBundle() diff --git a/Sources/ShortcutRecorder/SRModifierFlagsTransformer.m b/Sources/ShortcutRecorder/SRModifierFlagsTransformer.m index 3a75f876..332af7a2 100644 --- a/Sources/ShortcutRecorder/SRModifierFlagsTransformer.m +++ b/Sources/ShortcutRecorder/SRModifierFlagsTransformer.m @@ -149,7 +149,10 @@ - (NSString *)transformedValue:(NSNumber *)aValue layoutDirection:(NSUserInterfa NSEventModifierFlags flags = aValue.unsignedIntegerValue; NSMutableArray *flagsStringFragments = NSMutableArray.array; - + + if (flags & NSEventModifierFlagFunction) + [flagsStringFragments addObject:SRModifierFlagStringFunction]; + if (flags & NSEventModifierFlagControl) [flagsStringFragments addObject:SRModifierFlagStringControl]; @@ -161,9 +164,6 @@ - (NSString *)transformedValue:(NSNumber *)aValue layoutDirection:(NSUserInterfa if (flags & NSEventModifierFlagCommand) [flagsStringFragments addObject:SRModifierFlagStringCommand]; - - if (flags & NSEventModifierFlagFunction) - [flagsStringFragments addObject:SRModifierFlagStringFunction]; if (aDirection == NSUserInterfaceLayoutDirectionRightToLeft) return [[[flagsStringFragments reverseObjectEnumerator] allObjects] componentsJoinedByString:@""]; @@ -180,12 +180,25 @@ - (NSNumber *)reverseTransformedValue:(NSString *)aValue } __block NSEventModifierFlags flags = 0; - __block BOOL foundInvalidSubstring = NO; + + NSError *error = NULL; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: [NSString stringWithFormat: @"(?:%@|%@|%@|%@|%@)", SRModifierFlagStringFunction, SRModifierFlagStringControl, SRModifierFlagStringOption, SRModifierFlagStringShift, SRModifierFlagStringCommand] + options:NSRegularExpressionCaseInsensitive + error:&error]; + + if (error != NULL) { + NSLog(@"Got an error making a regex: %@", error); + panic("bad modifier transformer regex"); + } - [aValue enumerateSubstringsInRange:NSMakeRange(0, aValue.length) - options:NSStringEnumerationByComposedCharacterSequences - usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) - { + NSArray *matches = [regex matchesInString:aValue + options:0 + range:NSMakeRange(0, [aValue length])]; + + for (NSTextCheckingResult *match in matches) { + + NSString *substring = [aValue substringWithRange: [match rangeAtIndex:0]]; + if ([substring isEqualToString:SRModifierFlagStringControl] && (flags & NSEventModifierFlagControl) == 0) flags |= NSEventModifierFlagControl; else if ([substring isEqualToString:SRModifierFlagStringOption] && (flags & NSEventModifierFlagOption) == 0) @@ -196,17 +209,6 @@ - (NSNumber *)reverseTransformedValue:(NSString *)aValue flags |= NSEventModifierFlagCommand; else if ([substring isEqualToString:SRModifierFlagStringFunction] && (flags & NSEventModifierFlagFunction) == 0) flags |= NSEventModifierFlagFunction; - else - { - foundInvalidSubstring = YES; - *stop = YES; - } - }]; - - if (foundInvalidSubstring) - { - os_trace_error("#Error Invalid value for reverse transformation"); - return nil; } return @(flags); diff --git a/Sources/ShortcutRecorder/SRRecorderControl.m b/Sources/ShortcutRecorder/SRRecorderControl.m index b0a8a26f..2ec6bf30 100644 --- a/Sources/ShortcutRecorder/SRRecorderControl.m +++ b/Sources/ShortcutRecorder/SRRecorderControl.m @@ -649,7 +649,6 @@ - (void)endRecordingWithObjectValue:(SRShortcut *)anObjectValue if (_didPauseGlobalShortcutMonitor) { - NSLog(@"UNPAUSING"); // So, even if this is correctly pausing (even though it seems like the wrong thing) it's unpaused before the UP _didPauseGlobalShortcutMonitor = NO; [SRAXGlobalShortcutMonitor.sharedMonitor resume]; } diff --git a/Sources/ShortcutRecorder/SRShortcut.m b/Sources/ShortcutRecorder/SRShortcut.m index 0b2aaf99..37dd6803 100644 --- a/Sources/ShortcutRecorder/SRShortcut.m +++ b/Sources/ShortcutRecorder/SRShortcut.m @@ -136,23 +136,14 @@ + (instancetype)shortcutWithDictionary:(NSDictionary *)aDictionary + (instancetype)shortcutWithKeyEquivalent:(NSString *)aKeyEquivalent { - static NSCharacterSet *PossibleFlags = nil; - static dispatch_once_t OnceToken; - dispatch_once(&OnceToken, ^{ - PossibleFlags = [NSCharacterSet characterSetWithCharactersInString:[NSString stringWithFormat:@"%C%C%C%C", - SRModifierFlagGlyphCommand, - SRModifierFlagGlyphOption, - SRModifierFlagGlyphShift, - SRModifierFlagGlyphControl]]; - }); - - NSScanner *parser = [NSScanner scannerWithString:aKeyEquivalent]; - parser.caseSensitive = NO; - - NSString *modifierFlagsString = @""; - [parser scanCharactersFromSet:PossibleFlags intoString:&modifierFlagsString]; - NSString *keyCodeString = [aKeyEquivalent substringFromIndex:parser.scanLocation]; - + NSString *both = SRSplitKeycodeEquivalent(aKeyEquivalent); + + NSArray *splits = [both componentsSeparatedByString:SRSplitKeycodeSeparator]; + assert([splits count] == 2); + + NSString *modifierFlagsString = splits[0]; + NSString *keyCodeString = splits[1]; + if (!modifierFlagsString.length && !keyCodeString.length) return nil; diff --git a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h index 0e9c9885..ee279310 100644 --- a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h +++ b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRCommon.h @@ -303,7 +303,7 @@ NS_INLINE UInt32 SRCocoaToCarbonFlags(NSEventModifierFlags aCocoaFlags) carbonFlags |= shiftKey; if (aCocoaFlags & NSEventModifierFlagFunction) - NSLog(@"WELL WE TRIED TO CONVERT"); + NSLog(@"WELL WE TRIED TO CONVERT FN TO CARBON"); return carbonFlags; } @@ -348,6 +348,54 @@ NS_INLINE NSEventModifierFlags SRKeyCodeToCocoaFlag(SRKeyCode keyCode) return 0; } +NS_SWIFT_NAME(keyFatCodeToCocoaFlag(_:)) +NS_INLINE NSEventModifierFlags SRKeyFatCodeToCocoaFlag(SRKeyCode keyCode) +{ + if (keyCode == kVK_Command || keyCode == kVK_RightCommand) + return NSEventModifierFlagCommand; + else if (keyCode == kVK_Option || keyCode == kVK_RightOption) + return NSEventModifierFlagOption; + else if (keyCode == kVK_Shift || keyCode == kVK_RightShift) + return NSEventModifierFlagShift; + else if (keyCode == kVK_Control || keyCode == kVK_RightControl) + return NSEventModifierFlagControl; + else if (keyCode == kVK_Function) + return NSEventModifierFlagFunction; + else + return 10; +} + +static NSString* const SRSplitKeycodeSeparator = @"ΩΩ"; + +/*! + Return string represeeeeentation of a shortcut with modifier flags replaced with their + localized readable equivalents (e.g. ⌥ -> Option) and ASCII character with a key code. + + */ +NS_SWIFT_NAME(splitKeycodeEquivalent(_:)) +NS_INLINE NSString* SRSplitKeycodeEquivalent(NSString *keyEquiv) { + + NSError *error = NULL; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: [NSString stringWithFormat: @"^((?:%@|%@|%@|%@|%@)*)(.*)$", SRModifierFlagStringFunction, SRModifierFlagStringControl, SRModifierFlagStringOption, SRModifierFlagStringShift, SRModifierFlagStringCommand] + options:NSRegularExpressionCaseInsensitive + error:&error]; + + if (error != NULL) { + NSLog(@"Got an error making a regex: %@", error); + panic("bad modifier regex"); + } + + NSArray *matches = [regex matchesInString:keyEquiv + options:0 + range:NSMakeRange(0, [keyEquiv length])]; + + NSString *modifiers = [keyEquiv substringWithRange:[matches[0] rangeAtIndex:1]]; + NSString *keycode = [keyEquiv substringWithRange:[matches[0] rangeAtIndex:2]]; + + NSString *splits = [NSString stringWithFormat:@"%@%@%@", modifiers, SRSplitKeycodeSeparator, keycode]; + + return splits; +} /*! Return Bundle where resources can be found. diff --git a/Tests/ShortcutRecorderTests/SRCommonTests.swift b/Tests/ShortcutRecorderTests/SRCommonTests.swift index 0f57aae3..fa42cdf9 100644 --- a/Tests/ShortcutRecorderTests/SRCommonTests.swift +++ b/Tests/ShortcutRecorderTests/SRCommonTests.swift @@ -12,7 +12,7 @@ class SRCommonTests: XCTestCase { func testCocoaModifierFlagsMask() { let allFlags = NSEvent.ModifierFlags.init(rawValue: UInt.max) let cocoaFlags = allFlags.intersection(CocoaModifierFlagsMask) - let expectedFlags: NSEvent.ModifierFlags = [.command, .control, .option, .shift] + let expectedFlags: NSEvent.ModifierFlags = [.command, .control, .option, .shift, .function] XCTAssertEqual(cocoaFlags, expectedFlags) } diff --git a/Tests/ShortcutRecorderTests/SRModifierFlagsTransformerTests.swift b/Tests/ShortcutRecorderTests/SRModifierFlagsTransformerTests.swift index afdee847..220a1426 100644 --- a/Tests/ShortcutRecorderTests/SRModifierFlagsTransformerTests.swift +++ b/Tests/ShortcutRecorderTests/SRModifierFlagsTransformerTests.swift @@ -9,7 +9,7 @@ import ShortcutRecorder class SRModifierFlagsTransformerTests: XCTestCase { func testSymbolicTransformerIsReversible() { - let flags: NSEvent.ModifierFlags = [.control, .option, .shift, .command] + let flags: NSEvent.ModifierFlags = [.control, .option, .shift, .command, .function] let transformer = SymbolicModifierFlagsTransformer.shared let string = transformer.transformedValue(flags.rawValue as NSNumber)! let restoredFlags = NSEvent.ModifierFlags(rawValue: transformer.reverseTransformedValue(string) as! UInt) diff --git a/Tests/ShortcutRecorderTests/SRShortcutTests.swift b/Tests/ShortcutRecorderTests/SRShortcutTests.swift index 0c17f29a..bfb568a3 100644 --- a/Tests/ShortcutRecorderTests/SRShortcutTests.swift +++ b/Tests/ShortcutRecorderTests/SRShortcutTests.swift @@ -492,4 +492,39 @@ class SRShortcutTests: XCTestCase { XCTAssertEqual(shift_cmd, Shortcut(event: shift_cmd_down_event)) XCTAssertEqual(shift_cmd, Shortcut(event: shift_cmd_up_event)) } + + func testSplittingKeyEquiv() { + + struct splitTestCase { + var keyEquiv: String + var expectedModifiers: String + var expectedKeyCode: String + } + + + let testCases = [ splitTestCase(keyEquiv: "⇧⌘K", expectedModifiers: "⇧⌘", expectedKeyCode: "K"), + splitTestCase(keyEquiv: "⇧⌘", expectedModifiers: "⇧⌘", expectedKeyCode: ""), + splitTestCase(keyEquiv: "fn⇧⌘K", expectedModifiers: "fn⇧⌘", expectedKeyCode: "K"), + splitTestCase(keyEquiv: "fn", expectedModifiers: "fn", expectedKeyCode: ""), + splitTestCase(keyEquiv: "fnf", expectedModifiers: "fn", expectedKeyCode: "f"), + splitTestCase(keyEquiv: "fnF", expectedModifiers: "fn", expectedKeyCode: "F"), + splitTestCase(keyEquiv: "⇧⌘KS", expectedModifiers: "⇧⌘", expectedKeyCode: "KS"), + ] + + for testCase in testCases { + + let both = splitKeycodeEquivalent(testCase.keyEquiv) + + let splits = both.components(separatedBy: SRSplitKeycodeSeparator) + + XCTAssertEqual(splits.count, 2) + + XCTAssertEqual(splits[0], testCase.expectedModifiers) + XCTAssertEqual(splits[1], testCase.expectedKeyCode) + + print(both) + + } + + } } From 9614516c313359bbe2f763cd723906c4317abe7e Mon Sep 17 00:00:00 2001 From: MacRae Linton Date: Tue, 13 Oct 2020 23:36:03 -0700 Subject: [PATCH 5/6] Setup eventmonitor to be resetable when AX is toggled. --- Sources/ShortcutRecorder/SRShortcutAction.m | 60 ++++++++++++------- .../ShortcutRecorder/SRShortcutAction.h | 13 ++++ 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/Sources/ShortcutRecorder/SRShortcutAction.m b/Sources/ShortcutRecorder/SRShortcutAction.m index 189abe24..ecd999fa 100644 --- a/Sources/ShortcutRecorder/SRShortcutAction.m +++ b/Sources/ShortcutRecorder/SRShortcutAction.m @@ -1295,7 +1295,7 @@ + (SRAXGlobalShortcutMonitor *)sharedMonitor static SRAXGlobalShortcutMonitor *Shared = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - Shared = [SRAXGlobalShortcutMonitor new]; + Shared = [[SRAXGlobalShortcutMonitor alloc] initWithRunLoop:NSRunLoop.currentRunLoop tapOptions:kCGEventTapOptionDefault]; }); return Shared; } @@ -1331,29 +1331,15 @@ - (instancetype)initWithRunLoop:(NSRunLoop *)aRunLoop - (instancetype)initWithRunLoop:(NSRunLoop *)aRunLoop tapOptions:(CGEventTapOptions)aTapOptions { - static const CGEventMask Mask = (CGEventMaskBit(kCGEventKeyDown) | - CGEventMaskBit(kCGEventKeyUp) | - CGEventMaskBit(kCGEventFlagsChanged)); - __auto_type eventTap = CGEventTapCreate(kCGSessionEventTap, - kCGHeadInsertEventTap, - aTapOptions, - Mask, - _SRQuartzEventHandler, - (__bridge void *)self); - if (!eventTap) - { - os_trace_error("#Critical Unable to create event tap: make sure Accessibility is enabled"); - return nil; - } self = [super init]; if (self) { - _eventTap = eventTap; - _eventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0); _canActivelyFilterEvents = (aTapOptions & kCGEventTapOptionListenOnly) == 0; - CFRunLoopAddSource(aRunLoop.getCFRunLoop, _eventTapSource, kCFRunLoopDefaultMode); + _eventTapRunLoop = aRunLoop; + _eventTapOptions = aTapOptions; + [self resetEventTap]; } return self; @@ -1370,6 +1356,40 @@ - (void)dealloc #pragma mark Methods +- (void)resetEventTap +{ + if (_eventTap) { + CFRunLoopRemoveSource(_eventTapRunLoop.getCFRunLoop, _eventTapSource, kCFRunLoopDefaultMode); + + CFRelease(_eventTap); + CFRelease(_eventTapSource); + + _eventTap = NULL; + _eventTapSource = NULL; + } + + static const CGEventMask Mask = (CGEventMaskBit(kCGEventKeyDown) | + CGEventMaskBit(kCGEventKeyUp) | + CGEventMaskBit(kCGEventFlagsChanged)); + __auto_type eventTap = CGEventTapCreate(kCGSessionEventTap, + kCGHeadInsertEventTap, + _eventTapOptions, + Mask, + _SRQuartzEventHandler, + (__bridge void *)self); + + _eventTap = eventTap; + if (!eventTap) + { + os_trace_error("#Critical Unable to create event tap: make sure Accessibility is enabled"); + return; + } + + _eventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0); + CFRunLoopAddSource(_eventTapRunLoop.getCFRunLoop, _eventTapSource, kCFRunLoopDefaultMode); + +} + - (void)resume { @synchronized (_actions) @@ -1530,13 +1550,13 @@ - (CGEventRef)handleEvent:(CGEventRef)anEvent - (void)didAddShortcut:(SRShortcut *)aShortcut { - if (_shortcuts.count) + if (_shortcuts.count && _eventTap) CGEventTapEnable(_eventTap, true); } - (void)willRemoveShortcut:(SRShortcut *)aShortcut { - if (_shortcuts.count == 1 && [_shortcuts countForObject:aShortcut] == 1) + if (_shortcuts.count == 1 && [_shortcuts countForObject:aShortcut] == 1 && _eventTap) CGEventTapEnable(_eventTap, false); } diff --git a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h index ca05ea32..0c8f4b8c 100644 --- a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h +++ b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h @@ -429,6 +429,11 @@ NS_SWIFT_NAME(AXGlobalShortcutMonitor) */ @property (readonly) NSRunLoop *eventTapRunLoop; +/*! + Options for the event tap + */ +@property (readonly) CGEventTapOptions eventTapOptions; + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnullability" /*! @@ -473,6 +478,14 @@ NS_SWIFT_NAME(AXGlobalShortcutMonitor) */ - (nullable CGEventRef)handleEvent:(CGEventRef)anEvent; +/*! + Reset the event tap for the monitor. + + @discussion +If there are problems with the event tap, like because of AX not being enabled upon its creation, this method will reset it. + */ +- (void)resetEventTap; + /*! Enable system-wide shortcut monitoring. From 8e6935ee1904906ca8168d60eaf02e4377e76ad3 Mon Sep 17 00:00:00 2001 From: MacRae Linton Date: Sat, 24 Oct 2020 03:45:18 -0700 Subject: [PATCH 6/6] Allow removing the event tap --- Sources/ShortcutRecorder/SRShortcutAction.m | 24 ++++++------------- .../ShortcutRecorder/SRShortcutAction.h | 8 +++++++ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Sources/ShortcutRecorder/SRShortcutAction.m b/Sources/ShortcutRecorder/SRShortcutAction.m index ecd999fa..4e014ed6 100644 --- a/Sources/ShortcutRecorder/SRShortcutAction.m +++ b/Sources/ShortcutRecorder/SRShortcutAction.m @@ -1356,7 +1356,7 @@ - (void)dealloc #pragma mark Methods -- (void)resetEventTap +- (void)removeEventTap { if (_eventTap) { CFRunLoopRemoveSource(_eventTapRunLoop.getCFRunLoop, _eventTapSource, kCFRunLoopDefaultMode); @@ -1367,6 +1367,12 @@ - (void)resetEventTap _eventTap = NULL; _eventTapSource = NULL; } +} + +- (void)resetEventTap +{ + + [self removeEventTap]; static const CGEventMask Mask = (CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | @@ -1396,14 +1402,6 @@ - (void)resume { os_trace_debug("Global Shortcut Monitor counter: %ld -> %ld", _disableCounter, _disableCounter - 1); _disableCounter -= 1; - -// if (_disableCounter == 0) -// { -// for (SRShortcut *shortcut in _shortcuts) -// [self _registerHotKeyForShortcutIfNeeded:shortcut]; -// } -// -// [self _installEventHandlerIfNeeded]; } } @@ -1413,14 +1411,6 @@ - (void)pause { os_trace_debug("Global Shortcut Monitor counter: %ld -> %ld", _disableCounter, _disableCounter + 1); _disableCounter += 1; - -// if (_disableCounter == 1) -// { -// for (SRShortcut *shortcut in _shortcuts) -// [self _unregisterHotKeyForShortcutIfNeeded:shortcut]; -// } -// -// [self _removeEventHandlerIfNeeded]; } } diff --git a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h index 0c8f4b8c..4c67243f 100644 --- a/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h +++ b/Sources/ShortcutRecorder/include/ShortcutRecorder/SRShortcutAction.h @@ -478,6 +478,14 @@ NS_SWIFT_NAME(AXGlobalShortcutMonitor) */ - (nullable CGEventRef)handleEvent:(CGEventRef)anEvent; +/*! + Remove the event tap for the monitor. + + @discussion +If there are problems with the event tap, like because of AX not being enabled upon its creation, this method will reset it. + */ +- (void)removeEventTap; + /*! Reset the event tap for the monitor.