Skip to content

Commit 5f8e29e

Browse files
authored
fix: ensure we can activate other modes when in a mode (#21)
1 parent 707eb8d commit 5f8e29e

4 files changed

Lines changed: 169 additions & 1 deletion

File tree

cmd/govim/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ func NewApp(cfg *config.Config) (*App, error) {
122122
if app.eventTap == nil {
123123
log.Warn("Event tap creation failed - key capture won't work")
124124
} else {
125+
// Configure hotkeys that should pass through to the global hotkey system
126+
app.eventTap.SetHotkeys(
127+
cfg.Hotkeys.ActivateHintMode,
128+
cfg.Hotkeys.ActivateHintModeWithActions,
129+
cfg.Hotkeys.ActivateScrollMode,
130+
)
125131
// Ensure event tap is disabled initially (only enable in active modes)
126132
app.eventTap.Disable()
127133
}

internal/bridge/eventtap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ EventTap createEventTap(EventTapCallback callback, void* userData);
1414
void enableEventTap(EventTap tap);
1515
void disableEventTap(EventTap tap);
1616
void destroyEventTap(EventTap tap);
17+
void setEventTapHotkeys(EventTap tap, const char* hintModeHotkey, const char* hintModeWithActionsHotkey, const char* scrollModeHotkey);
1718

1819
#endif // EVENTTAP_H

internal/bridge/eventtap.m

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,106 @@
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+
1193
CGEventRef 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+
109234
void 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
}

internal/eventtap/eventtap.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,35 @@ func NewEventTap(callback Callback, logger *zap.Logger) *EventTap {
5050
func (et *EventTap) Enable() {
5151
if et.handle != nil {
5252
C.enableEventTap(et.handle)
53-
et.logger.Debug("Event tap enabled")
5453
}
5554
}
5655

56+
// SetHotkeys configures which hotkey combinations should pass through to the system
57+
func (et *EventTap) SetHotkeys(hintModeHotkey, hintModeWithActionsHotkey, scrollModeHotkey string) {
58+
if et.handle == nil {
59+
return
60+
}
61+
62+
var hintModeCStr, hintModeWithActionsCStr, scrollModeCStr *C.char
63+
64+
if hintModeHotkey != "" {
65+
hintModeCStr = C.CString(hintModeHotkey)
66+
defer C.free(unsafe.Pointer(hintModeCStr))
67+
}
68+
69+
if hintModeWithActionsHotkey != "" {
70+
hintModeWithActionsCStr = C.CString(hintModeWithActionsHotkey)
71+
defer C.free(unsafe.Pointer(hintModeWithActionsCStr))
72+
}
73+
74+
if scrollModeHotkey != "" {
75+
scrollModeCStr = C.CString(scrollModeHotkey)
76+
defer C.free(unsafe.Pointer(scrollModeCStr))
77+
}
78+
79+
C.setEventTapHotkeys(et.handle, hintModeCStr, hintModeWithActionsCStr, scrollModeCStr)
80+
}
81+
5782
// Disable disables the event tap
5883
func (et *EventTap) Disable() {
5984
if et.handle != nil {

0 commit comments

Comments
 (0)