//go:build darwin package hotkey /* #cgo CFLAGS: -x objective-c #cgo LDFLAGS: -framework Cocoa -framework ApplicationServices #import #include #include static volatile int g_keysDown[128]; static volatile double g_lastDownAt[128]; static volatile int g_monitorStarted = 0; static volatile int g_monitorLaunching = 0; static CFMachPortRef g_eventTap = NULL; static CFRunLoopSourceRef g_eventTapSource = NULL; static volatile unsigned long long g_monitorStartAttempts = 0; static volatile unsigned long long g_eventTapCreateFailures = 0; static volatile unsigned long long g_eventTapDisabledCount = 0; static volatile unsigned long long g_altEventSeq[2]; static volatile int g_altEventDown[2]; static volatile int g_altEventType[2]; static volatile unsigned long long g_altEventFlags[2]; static volatile double g_altEventAt[2]; static id g_activityToken = nil; static double nowSeconds(void) { return CFAbsoluteTimeGetCurrent(); } static void handleKeyEvent(unsigned short keyCode, BOOL isDown) { if (keyCode >= 128) return; g_keysDown[keyCode] = isDown ? 1 : 0; if (isDown) g_lastDownAt[keyCode] = nowSeconds(); } static int altIndexForKey(unsigned short keyCode) { if (keyCode == 58) return 0; // L-Option if (keyCode == 61) return 1; // R-Option return -1; } static void recordAltEvent(unsigned short keyCode, int eventType, CGEventFlags flags) { int index = altIndexForKey(keyCode); if (index < 0) return; g_altEventSeq[index]++; g_altEventDown[index] = g_keysDown[keyCode] ? 1 : 0; g_altEventType[index] = eventType; g_altEventFlags[index] = flags; g_altEventAt[index] = nowSeconds(); } static unsigned long long modifierMaskForKey(unsigned short keyCode) { switch (keyCode) { case 59: // L-Ctrl case 62: // R-Ctrl return kCGEventFlagMaskControl; case 58: // L-Option case 61: // R-Option return kCGEventFlagMaskAlternate; case 56: // L-Shift case 60: // R-Shift return kCGEventFlagMaskShift; case 55: // L-Command case 54: // R-Command return kCGEventFlagMaskCommand; default: return 0; } } static void handleModifierEvent(unsigned short keyCode, CGEventFlags flags) { if (keyCode >= 128) return; unsigned long long mask = modifierMaskForKey(keyCode); if (mask == 0) return; BOOL physicalDown = CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, (CGKeyCode)keyCode); BOOL eventDown = (flags & mask) ? YES : NO; handleKeyEvent(keyCode, (physicalDown || eventDown) ? YES : NO); recordAltEvent(keyCode, 3, flags); } static CGEventRef keyboardEventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { if (type == kCGEventTapDisabledByTimeout || type == kCGEventTapDisabledByUserInput) { g_eventTapDisabledCount++; if (g_eventTap != NULL) CGEventTapEnable(g_eventTap, true); return event; } if (type != kCGEventKeyDown && type != kCGEventKeyUp && type != kCGEventFlagsChanged) { return event; } unsigned short keyCode = (unsigned short)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); if (type == kCGEventFlagsChanged) { handleModifierEvent(keyCode, CGEventGetFlags(event)); } else { handleKeyEvent(keyCode, type == kCGEventKeyDown); recordAltEvent(keyCode, type == kCGEventKeyDown ? 1 : 2, CGEventGetFlags(event)); } return event; } static void startEventTapMonitor(void) { if (g_monitorStarted) { g_monitorLaunching = 0; return; } g_monitorStartAttempts++; CGEventMask eventMask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged); g_eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, eventMask, keyboardEventTapCallback, NULL); if (g_eventTap == NULL) { g_eventTapCreateFailures++; g_monitorLaunching = 0; return; } g_eventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, g_eventTap, 0); if (g_eventTapSource == NULL) { g_eventTapCreateFailures++; CFRelease(g_eventTap); g_eventTap = NULL; g_monitorLaunching = 0; return; } CFRunLoopAddSource(CFRunLoopGetCurrent(), g_eventTapSource, kCFRunLoopCommonModes); CGEventTapEnable(g_eventTap, true); g_monitorStarted = 1; g_monitorLaunching = 0; CFRunLoopRun(); g_monitorStarted = 0; } static int ensureAccessibility(void) { NSDictionary* opts = @{(__bridge NSString*)kAXTrustedCheckOptionPrompt: @YES}; return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts) ? 1 : 0; } static void beginHotkeyActivity(void) { if (g_activityToken != nil) return; g_activityToken = [[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiatedAllowingIdleSystemSleep reason:@"PrivateVoice Input global hotkey listener"]; } static void* eventTapThreadMain(void* arg) { (void)arg; @autoreleasepool { startEventTapMonitor(); } g_monitorLaunching = 0; return NULL; } static void startKeyMonitor(void) { if (g_monitorStarted || g_monitorLaunching) return; g_monitorLaunching = 1; pthread_t thread; int err = pthread_create(&thread, NULL, eventTapThreadMain, NULL); if (err != 0) { g_monitorLaunching = 0; g_eventTapCreateFailures++; return; } pthread_detach(thread); } // isModifierDown polls the current system modifier flags via CGEventSource. // This works globally without needing event tap or NSEvent monitor callbacks. static int isModifierDown(unsigned long long flagMask) { CGEventFlags flags = CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState); return (flags & flagMask) ? 1 : 0; } static int isSideModifierDown(int keyCode) { if (keyCode >= 0 && keyCode < 128) { if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, (CGKeyCode)keyCode)) return 1; unsigned long long mask = modifierMaskForKey((unsigned short)keyCode); int modifierDown = mask != 0 ? isModifierDown(mask) : 0; double age = g_lastDownAt[keyCode] > 0 ? nowSeconds() - g_lastDownAt[keyCode] : 999.0; if (g_keysDown[keyCode]) { if (modifierDown) return 1; if (age < 0.25) return 1; g_keysDown[keyCode] = 0; return 0; } if (age < 0.12) return 1; } return 0; } static int isKeyDown(int keyCode) { if (keyCode >= 0 && keyCode < 128) { if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, (CGKeyCode)keyCode)) return 1; if (g_keysDown[keyCode] && g_lastDownAt[keyCode] > 0 && nowSeconds() - g_lastDownAt[keyCode] < 0.25) return 1; if (g_keysDown[keyCode]) g_keysDown[keyCode] = 0; if (g_lastDownAt[keyCode] > 0 && nowSeconds() - g_lastDownAt[keyCode] < 0.12) return 1; } return 0; } static int isPhysicalKeyDown(int keyCode) { if (keyCode < 0 || keyCode >= 128) return 0; return CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, (CGKeyCode)keyCode) ? 1 : 0; } static int isTrackedKeyDown(int keyCode) { if (keyCode < 0 || keyCode >= 128) return 0; return g_keysDown[keyCode] ? 1 : 0; } static int isRecentKeyDown(int keyCode) { if (keyCode < 0 || keyCode >= 128 || g_lastDownAt[keyCode] <= 0) return 0; return nowSeconds() - g_lastDownAt[keyCode] < 0.12 ? 1 : 0; } static double lastKeyDownAge(int keyCode) { if (keyCode < 0 || keyCode >= 128 || g_lastDownAt[keyCode] <= 0) return -1; return nowSeconds() - g_lastDownAt[keyCode]; } static int isMonitorStarted(void) { return g_monitorStarted; } static int isMonitorLaunching(void) { return g_monitorLaunching; } static unsigned long long monitorStartAttempts(void) { return g_monitorStartAttempts; } static unsigned long long eventTapCreateFailures(void) { return g_eventTapCreateFailures; } static unsigned long long eventTapDisabledCount(void) { return g_eventTapDisabledCount; } static unsigned long long altEventSeq(int index) { if (index < 0 || index > 1) return 0; return g_altEventSeq[index]; } static int altEventDown(int index) { if (index < 0 || index > 1) return 0; return g_altEventDown[index]; } static int altEventType(int index) { if (index < 0 || index > 1) return 0; return g_altEventType[index]; } static unsigned long long altEventFlags(int index) { if (index < 0 || index > 1) return 0; return g_altEventFlags[index]; } static double altEventAge(int index) { if (index < 0 || index > 1 || g_altEventAt[index] <= 0) return -1; return nowSeconds() - g_altEventAt[index]; } */ import "C" import ( "fmt" "time" "voicesnap/internal/logger" ) // macOS modifier flag masks for CGEventSourceFlagsState polling. const ( maskControl = 0x40000 // kCGEventFlagMaskControl maskShift = 0x20000 // kCGEventFlagMaskShift maskOption = 0x80000 // kCGEventFlagMaskAlternate maskCommand = 0x100000 // kCGEventFlagMaskCommand maskCapsLock = 0x10000 // kCGEventFlagMaskAlphaShift ) // Generic modifier VK codes use flag polling; left/right variants use physical // key polling because CGEvent modifier flags do not distinguish sides. var vkToFlagMask = map[int]C.ulonglong{ 0x11: maskControl, // Ctrl 0x12: maskOption, // Alt 0x10: maskShift, // Shift 0x14: maskCapsLock, // Caps Lock 0x5B: maskCommand, // Cmd 0x5C: maskCommand, // Cmd } var vkToPhysicalModifier = map[int]int{ 0xA2: 59, // L-Ctrl 0xA3: 62, // R-Ctrl 0xA4: 58, // L-Alt / Option 0xA5: 61, // R-Alt / Option 0xA0: 56, // L-Shift 0xA1: 60, // R-Shift 0x5B: 55, // L-Cmd 0x5C: 54, // R-Cmd } // macOS key code mapping for non-modifier keys. var vkToMac = map[int]int{ 0x20: 0x31, // Space 0x09: 0x30, // Tab 0x0D: 0x24, // Enter/Return 0x1B: 0x35, // Esc } type darwinListener struct { lastAltSeq map[int]uint64 lastAltState map[int]bool lastMonitorRetry time.Time } func newPlatformListener() Listener { C.beginHotkeyActivity() trusted := C.ensureAccessibility() if trusted == 0 { logger.Info("Accessibility permission not granted for current app identity — hotkey won't work until PrivateVoice Input is removed and re-added in System Settings > Privacy & Security > Accessibility") } else { logger.Info("Accessibility permission granted") } C.startKeyMonitor() deadline := time.Now().Add(time.Second) for C.isMonitorStarted() == 0 && C.isMonitorLaunching() != 0 && time.Now().Before(deadline) { time.Sleep(50 * time.Millisecond) } if C.isMonitorStarted() == 0 { logger.Error( "Keyboard event tap not running yet; attempts=%d failures=%d launching=%t. Physical hotkey polling fallback is enabled.", uint64(C.monitorStartAttempts()), uint64(C.eventTapCreateFailures()), C.isMonitorLaunching() != 0, ) } else { logger.Info( "Keyboard event tap started; attempts=%d failures=%d disabled=%d", uint64(C.monitorStartAttempts()), uint64(C.eventTapCreateFailures()), uint64(C.eventTapDisabledCount()), ) } return &darwinListener{ lastAltSeq: map[int]uint64{}, lastAltState: map[int]bool{}, lastMonitorRetry: time.Now(), } } func (l *darwinListener) IsKeyDown(vk int) bool { l.retryMonitorIfNeeded() if keyCode, ok := vkToPhysicalModifier[vk]; ok { down := C.isSideModifierDown(C.int(keyCode)) != 0 l.logAltDiagnostics(vk, keyCode, down) return down } // Generic modifier keys: poll system flags directly. if mask, ok := vkToFlagMask[vk]; ok { return C.isModifierDown(mask) != 0 } // Regular keys: check NSEvent-tracked state macKey, ok := vkToMac[vk] if !ok { if vk >= 0x41 && vk <= 0x5A { macKey = vkToMacAlpha(vk) } else { return false } } return C.isKeyDown(C.int(macKey)) != 0 } func (l *darwinListener) retryMonitorIfNeeded() { if C.isMonitorStarted() != 0 || C.isMonitorLaunching() != 0 { return } if time.Since(l.lastMonitorRetry) < 10*time.Second { return } l.lastMonitorRetry = time.Now() logger.Info( "Keyboard event tap retry requested; attempts=%d failures=%d disabled=%d", uint64(C.monitorStartAttempts()), uint64(C.eventTapCreateFailures()), uint64(C.eventTapDisabledCount()), ) C.startKeyMonitor() } func (l *darwinListener) logAltDiagnostics(vk, keyCode int, down bool) { index := altDiagnosticIndex(keyCode) if index < 0 { return } physicalDown := C.isPhysicalKeyDown(C.int(keyCode)) != 0 trackedDown := C.isTrackedKeyDown(C.int(keyCode)) != 0 recentDown := C.isRecentKeyDown(C.int(keyCode)) != 0 seq := uint64(C.altEventSeq(C.int(index))) if seq != l.lastAltSeq[vk] { l.lastAltSeq[vk] = seq logger.Info( "Hotkey event tap: key=%s vk=0x%X keyCode=%d seq=%d event=%s eventDown=%t pollDown=%t physicalDown=%t trackedDown=%t recentDown=%t flags=0x%X age=%.3fs tapStarted=%t", GetKeyName(vk), vk, keyCode, seq, altEventTypeName(int(C.altEventType(C.int(index)))), C.altEventDown(C.int(index)) != 0, down, physicalDown, trackedDown, recentDown, uint64(C.altEventFlags(C.int(index))), float64(C.altEventAge(C.int(index))), C.isMonitorStarted() != 0, ) } if previous, ok := l.lastAltState[vk]; !ok || previous != down { l.lastAltState[vk] = down logger.Info( "Hotkey poll: key=%s vk=0x%X keyCode=%d down=%t source=%s physicalDown=%t trackedDown=%t recentDown=%t tapStarted=%t", GetKeyName(vk), vk, keyCode, down, hotkeySource(down, physicalDown, trackedDown, recentDown), physicalDown, trackedDown, recentDown, C.isMonitorStarted() != 0, ) } } func hotkeySource(down, physicalDown, trackedDown, recentDown bool) string { switch { case !down: return "none" case physicalDown: return "physical" case trackedDown: return "eventTap" case recentDown: return "recent" default: return "unknown" } } func altDiagnosticIndex(keyCode int) int { switch keyCode { case 58: return 0 case 61: return 1 default: return -1 } } func altEventTypeName(eventType int) string { switch eventType { case 1: return "keyDown" case 2: return "keyUp" case 3: return "flagsChanged" default: return fmt.Sprintf("unknown(%d)", eventType) } } func (l *darwinListener) IsAnyOtherKeyPressed(excludeVK int) bool { return l.IsAnyOtherKeyPressedSince(excludeVK, time.Time{}) } func (l *darwinListener) IsAnyOtherKeyPressedSince(excludeVK int, since time.Time) bool { excludeGroup := modifierGroup(excludeVK) modifiers := []int{0x11, 0xA2, 0xA3, 0x12, 0xA4, 0xA5, 0x10, 0xA0, 0xA1} for _, k := range modifiers { if k == excludeVK { continue } if excludeGroup != 0 && modifierGroup(k) == excludeGroup { continue } if l.wasPressedSince(k, since) { logger.Info("Hotkey combination source: key=%s vk=0x%X", GetKeyName(k), k) return true } } for vk := 0x41; vk <= 0x5A; vk++ { if vk == excludeVK { continue } if l.wasPressedSince(vk, since) { logger.Info("Hotkey combination source: key=%s vk=0x%X", GetKeyName(vk), vk) return true } } return false } func (l *darwinListener) wasPressedSince(vk int, since time.Time) bool { if since.IsZero() { return l.IsKeyDown(vk) } keyCode, ok := macKeyCodeForVK(vk) if !ok { return false } age := float64(C.lastKeyDownAge(C.int(keyCode))) if age < 0 { return false } return time.Since(since).Seconds()-age >= -0.02 } func modifierGroup(vk int) int { switch vk { case 0x11, 0xA2, 0xA3: return 1 case 0x12, 0xA4, 0xA5: return 2 case 0x10, 0xA0, 0xA1: return 3 default: return 0 } } func isGenericModifier(vk int) bool { return vk == 0x11 || vk == 0x12 || vk == 0x10 } func vkToMacAlpha(vk int) int { alphaMap := map[int]int{ 0x41: 0x00, 0x42: 0x0B, 0x43: 0x08, 0x44: 0x02, 0x45: 0x0E, 0x46: 0x03, 0x47: 0x05, 0x48: 0x04, 0x49: 0x22, 0x4A: 0x26, 0x4B: 0x28, 0x4C: 0x25, 0x4D: 0x2E, 0x4E: 0x2D, 0x4F: 0x1F, 0x50: 0x23, 0x51: 0x0C, 0x52: 0x0F, 0x53: 0x01, 0x54: 0x11, 0x55: 0x20, 0x56: 0x09, 0x57: 0x0D, 0x58: 0x07, 0x59: 0x10, 0x5A: 0x06, } if code, ok := alphaMap[vk]; ok { return code } return 0 } func macKeyCodeForVK(vk int) (int, bool) { if keyCode, ok := vkToPhysicalModifier[vk]; ok { return keyCode, true } if keyCode, ok := vkToMac[vk]; ok { return keyCode, true } if vk >= 0x41 && vk <= 0x5A { return vkToMacAlpha(vk), true } return 0, false }