66 CFRunLoopSourceRef runLoopSource;
77 EventTapCallback callback;
88 void * userData;
9+ NSString * hintModeHotkey;
10+ NSString * hintModeWithActionsHotkey;
11+ NSString * scrollModeHotkey;
912} EventTapContext;
1013
14+ // Helper function to check if current key combination matches a hotkey
15+ BOOL isHotkeyMatch (CGKeyCode keyCode, CGEventFlags flags, NSString * hotkeyString) {
16+ if (!hotkeyString || [hotkeyString length ] == 0 ) {
17+ return NO ;
18+ }
19+
20+ NSArray *parts = [hotkeyString componentsSeparatedByString: @" +" ];
21+ NSString *mainKey = nil ;
22+ BOOL needsCmd = NO , needsShift = NO , needsAlt = NO , needsCtrl = NO ;
23+
24+ // Parse hotkey string
25+ for (NSString *part in parts) {
26+ NSString *trimmed = [part stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet ]];
27+
28+ if ([trimmed isEqualToString: @" Cmd" ] || [trimmed isEqualToString: @" Command" ]) {
29+ needsCmd = YES ;
30+ } else if ([trimmed isEqualToString: @" Shift" ]) {
31+ needsShift = YES ;
32+ } else if ([trimmed isEqualToString: @" Alt" ] || [trimmed isEqualToString: @" Option" ]) {
33+ needsAlt = YES ;
34+ } else if ([trimmed isEqualToString: @" Ctrl" ] || [trimmed isEqualToString: @" Control" ]) {
35+ needsCtrl = YES ;
36+ } else {
37+ mainKey = trimmed;
38+ }
39+ }
40+
41+ if (!mainKey) return NO ;
42+
43+ // Check modifier flags
44+ BOOL hasCmd = (flags & kCGEventFlagMaskCommand ) != 0 ;
45+ BOOL hasShift = (flags & kCGEventFlagMaskShift ) != 0 ;
46+ BOOL hasAlt = (flags & kCGEventFlagMaskAlternate ) != 0 ;
47+ BOOL hasCtrl = (flags & kCGEventFlagMaskControl ) != 0 ;
48+
49+ if (needsCmd != hasCmd || needsShift != hasShift || needsAlt != hasAlt || needsCtrl != hasCtrl) {
50+ return NO ;
51+ }
52+
53+ // Map key names to key codes (same as in hotkeys.m)
54+ NSDictionary *keyMap = @{
55+ @" Space" : @(49 ),
56+ @" Return" : @(36 ),
57+ @" Enter" : @(36 ),
58+ @" Escape" : @(53 ),
59+ @" Tab" : @(48 ),
60+ @" Delete" : @(51 ),
61+ @" Backspace" : @(51 ),
62+
63+ // Letters
64+ @" A" : @(0 ), @" B" : @(11 ), @" C" : @(8 ), @" D" : @(2 ), @" E" : @(14 ),
65+ @" F" : @(3 ), @" G" : @(5 ), @" H" : @(4 ), @" I" : @(34 ), @" J" : @(38 ),
66+ @" K" : @(40 ), @" L" : @(37 ), @" M" : @(46 ), @" N" : @(45 ), @" O" : @(31 ),
67+ @" P" : @(35 ), @" Q" : @(12 ), @" R" : @(15 ), @" S" : @(1 ), @" T" : @(17 ),
68+ @" U" : @(32 ), @" V" : @(9 ), @" W" : @(13 ), @" X" : @(7 ), @" Y" : @(16 ),
69+ @" Z" : @(6 ),
70+
71+ // Numbers
72+ @" 0" : @(29 ), @" 1" : @(18 ), @" 2" : @(19 ), @" 3" : @(20 ), @" 4" : @(21 ),
73+ @" 5" : @(23 ), @" 6" : @(22 ), @" 7" : @(26 ), @" 8" : @(28 ), @" 9" : @(25 ),
74+
75+ // Function keys
76+ @" F1" : @(122 ), @" F2" : @(120 ), @" F3" : @(99 ), @" F4" : @(118 ),
77+ @" F5" : @(96 ), @" F6" : @(97 ), @" F7" : @(98 ), @" F8" : @(100 ),
78+ @" F9" : @(101 ), @" F10" : @(109 ), @" F11" : @(103 ), @" F12" : @(111 ),
79+
80+ // Arrow keys
81+ @" Left" : @(123 ), @" Right" : @(124 ), @" Down" : @(125 ), @" Up" : @(126 ),
82+ };
83+
84+ NSNumber *expectedKeyCode = keyMap[mainKey];
85+ if (!expectedKeyCode) {
86+ // Try lowercase
87+ expectedKeyCode = keyMap[[mainKey lowercaseString]] ;
88+ }
89+
90+ return expectedKeyCode && [expectedKeyCode intValue ] == keyCode;
91+ }
92+
1193CGEventRef eventTapCallback (CGEventTapProxy proxy, CGEventType type, CGEventRef event, void * refcon) {
1294 EventTapContext* context = (EventTapContext*)refcon;
1395
1496 if (type == kCGEventKeyDown ) {
1597 CGKeyCode keyCode = (CGKeyCode)CGEventGetIntegerValueField (event, kCGKeyboardEventKeycode );
1698 CGEventFlags flags = CGEventGetFlags (event);
1799
100+ // Check if this key combination matches any of our hotkeys
101+ // If so, let it pass through to the system (return event instead of NULL)
102+ if (isHotkeyMatch (keyCode, flags, context->hintModeHotkey ) ||
103+ isHotkeyMatch (keyCode, flags, context->hintModeWithActionsHotkey ) ||
104+ isHotkeyMatch (keyCode, flags, context->scrollModeHotkey )) {
105+ // Let the hotkey pass through to the global hotkey system
106+ return event;
107+ }
108+
18109 // Special handling for delete/backspace key (keycode 51)
19110 if (keyCode == 51 ) {
20111 if (context->callback ) {
@@ -85,6 +176,9 @@ EventTap createEventTap(EventTapCallback callback, void* userData) {
85176 EventTapContext* context = (EventTapContext*)malloc (sizeof (EventTapContext));
86177 context->callback = callback;
87178 context->userData = userData;
179+ context->hintModeHotkey = nil ;
180+ context->hintModeWithActionsHotkey = nil ;
181+ context->scrollModeHotkey = nil ;
88182
89183 CGEventMask eventMask = (1 << kCGEventKeyDown );
90184 context->eventTap = CGEventTapCreate (
@@ -106,6 +200,37 @@ EventTap createEventTap(EventTapCallback callback, void* userData) {
106200 return (EventTap)context;
107201}
108202
203+ void setEventTapHotkeys (EventTap tap, const char * hintModeHotkey, const char * hintModeWithActionsHotkey, const char * scrollModeHotkey) {
204+ if (!tap) return ;
205+
206+ EventTapContext* context = (EventTapContext*)tap;
207+
208+ // Release old strings
209+ if (context->hintModeHotkey ) {
210+ [context->hintModeHotkey release ];
211+ context->hintModeHotkey = nil ;
212+ }
213+ if (context->hintModeWithActionsHotkey ) {
214+ [context->hintModeWithActionsHotkey release ];
215+ context->hintModeWithActionsHotkey = nil ;
216+ }
217+ if (context->scrollModeHotkey ) {
218+ [context->scrollModeHotkey release ];
219+ context->scrollModeHotkey = nil ;
220+ }
221+
222+ // Set new strings
223+ if (hintModeHotkey && strlen (hintModeHotkey) > 0 ) {
224+ context->hintModeHotkey = [[NSString stringWithUTF8String: hintModeHotkey] retain ];
225+ }
226+ if (hintModeWithActionsHotkey && strlen (hintModeWithActionsHotkey) > 0 ) {
227+ context->hintModeWithActionsHotkey = [[NSString stringWithUTF8String: hintModeWithActionsHotkey] retain ];
228+ }
229+ if (scrollModeHotkey && strlen (scrollModeHotkey) > 0 ) {
230+ context->scrollModeHotkey = [[NSString stringWithUTF8String: scrollModeHotkey] retain ];
231+ }
232+ }
233+
109234void enableEventTap (EventTap tap) {
110235 if (!tap) return ;
111236
@@ -144,5 +269,16 @@ void destroyEventTap(EventTap tap) {
144269 CFRelease (context->runLoopSource );
145270 }
146271
272+ // Release hotkey strings
273+ if (context->hintModeHotkey ) {
274+ [context->hintModeHotkey release ];
275+ }
276+ if (context->hintModeWithActionsHotkey ) {
277+ [context->hintModeWithActionsHotkey release ];
278+ }
279+ if (context->scrollModeHotkey ) {
280+ [context->scrollModeHotkey release ];
281+ }
282+
147283 free (context);
148284}
0 commit comments