| | |
| | | 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 volatile unsigned long long g_modifierEventSeq[4]; |
| | | static volatile int g_modifierEventDown[4]; |
| | | static volatile int g_modifierEventType[4]; |
| | | static volatile unsigned long long g_modifierEventFlags[4]; |
| | | static volatile double g_modifierEventAt[4]; |
| | | static id g_activityToken = nil; |
| | | |
| | | static double nowSeconds(void) { |
| | |
| | | if (isDown) g_lastDownAt[keyCode] = nowSeconds(); |
| | | } |
| | | |
| | | static int altIndexForKey(unsigned short keyCode) { |
| | | static int modifierDiagnosticIndexForKey(unsigned short keyCode) { |
| | | if (keyCode == 58) return 0; // L-Option |
| | | if (keyCode == 61) return 1; // R-Option |
| | | if (keyCode == 55) return 2; // L-Command |
| | | if (keyCode == 54) return 3; // R-Command |
| | | return -1; |
| | | } |
| | | |
| | | static void recordAltEvent(unsigned short keyCode, int eventType, CGEventFlags flags) { |
| | | int index = altIndexForKey(keyCode); |
| | | static void recordModifierEvent(unsigned short keyCode, int eventType, CGEventFlags flags) { |
| | | int index = modifierDiagnosticIndexForKey(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(); |
| | | g_modifierEventSeq[index]++; |
| | | g_modifierEventDown[index] = g_keysDown[keyCode] ? 1 : 0; |
| | | g_modifierEventType[index] = eventType; |
| | | g_modifierEventFlags[index] = flags; |
| | | g_modifierEventAt[index] = nowSeconds(); |
| | | } |
| | | |
| | | static unsigned long long modifierMaskForKey(unsigned short keyCode) { |
| | |
| | | BOOL physicalDown = CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, (CGKeyCode)keyCode); |
| | | BOOL eventDown = (flags & mask) ? YES : NO; |
| | | handleKeyEvent(keyCode, (physicalDown || eventDown) ? YES : NO); |
| | | recordAltEvent(keyCode, 3, flags); |
| | | recordModifierEvent(keyCode, 3, flags); |
| | | } |
| | | |
| | | static CGEventRef keyboardEventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { |
| | |
| | | handleModifierEvent(keyCode, CGEventGetFlags(event)); |
| | | } else { |
| | | handleKeyEvent(keyCode, type == kCGEventKeyDown); |
| | | recordAltEvent(keyCode, type == kCGEventKeyDown ? 1 : 2, CGEventGetFlags(event)); |
| | | recordModifierEvent(keyCode, type == kCGEventKeyDown ? 1 : 2, CGEventGetFlags(event)); |
| | | } |
| | | return event; |
| | | } |
| | |
| | | return (flags & flagMask) ? 1 : 0; |
| | | } |
| | | |
| | | static int isSideModifierDown(int keyCode) { |
| | | static int isModifierDownCombined(unsigned long long flagMask) { |
| | | CGEventFlags flags = CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState); |
| | | return (flags & flagMask) ? 1 : 0; |
| | | } |
| | | |
| | | static int isModifierDownAny(unsigned long long flagMask) { |
| | | if (isModifierDown(flagMask)) return 1; |
| | | return isModifierDownCombined(flagMask); |
| | | } |
| | | |
| | | static int isCommandKey(int keyCode) { |
| | | return keyCode == 54 || keyCode == 55; |
| | | } |
| | | |
| | | static int isPhysicalKeyDownHID(int keyCode) { |
| | | if (keyCode < 0 || keyCode >= 128) return 0; |
| | | return CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, (CGKeyCode)keyCode) ? 1 : 0; |
| | | } |
| | | |
| | | static int isPhysicalKeyDownCombined(int keyCode) { |
| | | if (keyCode < 0 || keyCode >= 128) return 0; |
| | | return CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, (CGKeyCode)keyCode) ? 1 : 0; |
| | | } |
| | | |
| | | static int sideModifierDownSource(int keyCode) { |
| | | if (keyCode >= 0 && keyCode < 128) { |
| | | if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, (CGKeyCode)keyCode)) return 1; |
| | | if (isPhysicalKeyDownHID(keyCode)) return 1; |
| | | if (isPhysicalKeyDownCombined(keyCode)) return 2; |
| | | unsigned long long mask = modifierMaskForKey((unsigned short)keyCode); |
| | | int modifierDown = mask != 0 ? isModifierDown(mask) : 0; |
| | | int modifierDown = mask != 0 ? isModifierDownAny(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; |
| | | if (modifierDown) return 3; |
| | | if (age < 0.25) return 4; |
| | | g_keysDown[keyCode] = 0; |
| | | return 0; |
| | | } |
| | | if (age < 0.12) return 1; |
| | | if (age < 0.12) return 4; |
| | | if (isCommandKey(keyCode) && modifierDown) return 5; |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | static int isSideModifierDown(int keyCode) { |
| | | int source = sideModifierDownSource(keyCode); |
| | | return source != 0 && source != 5; |
| | | } |
| | | |
| | | static int isKeyDown(int keyCode) { |
| | | if (keyCode >= 0 && keyCode < 128) { |
| | | if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, (CGKeyCode)keyCode)) return 1; |
| | | if (isPhysicalKeyDownHID(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; |
| | |
| | | } |
| | | |
| | | static int isPhysicalKeyDown(int keyCode) { |
| | | if (keyCode < 0 || keyCode >= 128) return 0; |
| | | return CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, (CGKeyCode)keyCode) ? 1 : 0; |
| | | return isPhysicalKeyDownHID(keyCode); |
| | | } |
| | | |
| | | static int isTrackedKeyDown(int keyCode) { |
| | |
| | | return g_eventTapDisabledCount; |
| | | } |
| | | |
| | | static unsigned long long altEventSeq(int index) { |
| | | if (index < 0 || index > 1) return 0; |
| | | return g_altEventSeq[index]; |
| | | static unsigned long long modifierEventSeq(int index) { |
| | | if (index < 0 || index > 3) return 0; |
| | | return g_modifierEventSeq[index]; |
| | | } |
| | | |
| | | static int altEventDown(int index) { |
| | | if (index < 0 || index > 1) return 0; |
| | | return g_altEventDown[index]; |
| | | static int modifierEventDown(int index) { |
| | | if (index < 0 || index > 3) return 0; |
| | | return g_modifierEventDown[index]; |
| | | } |
| | | |
| | | static int altEventType(int index) { |
| | | if (index < 0 || index > 1) return 0; |
| | | return g_altEventType[index]; |
| | | static int modifierEventType(int index) { |
| | | if (index < 0 || index > 3) return 0; |
| | | return g_modifierEventType[index]; |
| | | } |
| | | |
| | | static unsigned long long altEventFlags(int index) { |
| | | if (index < 0 || index > 1) return 0; |
| | | return g_altEventFlags[index]; |
| | | static unsigned long long modifierEventFlags(int index) { |
| | | if (index < 0 || index > 3) return 0; |
| | | return g_modifierEventFlags[index]; |
| | | } |
| | | |
| | | static double altEventAge(int index) { |
| | | if (index < 0 || index > 1 || g_altEventAt[index] <= 0) return -1; |
| | | return nowSeconds() - g_altEventAt[index]; |
| | | static double modifierEventAge(int index) { |
| | | if (index < 0 || index > 3 || g_modifierEventAt[index] <= 0) return -1; |
| | | return nowSeconds() - g_modifierEventAt[index]; |
| | | } |
| | | */ |
| | | import "C" |
| | |
| | | } |
| | | |
| | | type darwinListener struct { |
| | | lastAltSeq map[int]uint64 |
| | | lastAltState map[int]bool |
| | | lastMonitorRetry time.Time |
| | | lastModifierSeq map[int]uint64 |
| | | lastModifierState map[int]bool |
| | | lastMonitorRetry time.Time |
| | | } |
| | | |
| | | func newPlatformListener() Listener { |
| | |
| | | ) |
| | | } |
| | | return &darwinListener{ |
| | | lastAltSeq: map[int]uint64{}, |
| | | lastAltState: map[int]bool{}, |
| | | lastMonitorRetry: time.Now(), |
| | | lastModifierSeq: map[int]uint64{}, |
| | | lastModifierState: 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) |
| | | source := int(C.sideModifierDownSource(C.int(keyCode))) |
| | | down := sideModifierSourceAccepted(source) |
| | | l.logModifierDiagnostics(vk, keyCode, down) |
| | | return down |
| | | } |
| | | // Generic modifier keys: poll system flags directly. |
| | |
| | | C.startKeyMonitor() |
| | | } |
| | | |
| | | func (l *darwinListener) logAltDiagnostics(vk, keyCode int, down bool) { |
| | | index := altDiagnosticIndex(keyCode) |
| | | func (l *darwinListener) logModifierDiagnostics(vk, keyCode int, down bool) { |
| | | index := modifierDiagnosticIndex(keyCode) |
| | | if index < 0 { |
| | | return |
| | | } |
| | | |
| | | physicalDown := C.isPhysicalKeyDown(C.int(keyCode)) != 0 |
| | | physicalHIDDown := C.isPhysicalKeyDown(C.int(keyCode)) != 0 |
| | | physicalCombinedDown := C.isPhysicalKeyDownCombined(C.int(keyCode)) != 0 |
| | | trackedDown := C.isTrackedKeyDown(C.int(keyCode)) != 0 |
| | | recentDown := C.isRecentKeyDown(C.int(keyCode)) != 0 |
| | | source := hotkeySourceName(int(C.sideModifierDownSource(C.int(keyCode)))) |
| | | var aggregateFlagDown bool |
| | | if mask := modifierFlagMaskForKeyCode(keyCode); mask != 0 { |
| | | aggregateFlagDown = C.isModifierDownAny(mask) != 0 |
| | | } |
| | | |
| | | seq := uint64(C.altEventSeq(C.int(index))) |
| | | if seq != l.lastAltSeq[vk] { |
| | | l.lastAltSeq[vk] = seq |
| | | seq := uint64(C.modifierEventSeq(C.int(index))) |
| | | if seq != l.lastModifierSeq[vk] { |
| | | l.lastModifierSeq[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", |
| | | "Hotkey event tap: key=%s vk=0x%X keyCode=%d seq=%d event=%s eventDown=%t pollDown=%t source=%s physicalHID=%t physicalCombined=%t trackedDown=%t recentDown=%t aggregateFlagDown=%t flags=0x%X age=%.3fs tapStarted=%t launching=%t attempts=%d failures=%d disabled=%d", |
| | | GetKeyName(vk), |
| | | vk, |
| | | keyCode, |
| | | seq, |
| | | altEventTypeName(int(C.altEventType(C.int(index)))), |
| | | C.altEventDown(C.int(index)) != 0, |
| | | modifierEventTypeName(int(C.modifierEventType(C.int(index)))), |
| | | C.modifierEventDown(C.int(index)) != 0, |
| | | down, |
| | | physicalDown, |
| | | source, |
| | | physicalHIDDown, |
| | | physicalCombinedDown, |
| | | trackedDown, |
| | | recentDown, |
| | | uint64(C.altEventFlags(C.int(index))), |
| | | float64(C.altEventAge(C.int(index))), |
| | | aggregateFlagDown, |
| | | uint64(C.modifierEventFlags(C.int(index))), |
| | | float64(C.modifierEventAge(C.int(index))), |
| | | C.isMonitorStarted() != 0, |
| | | C.isMonitorLaunching() != 0, |
| | | uint64(C.monitorStartAttempts()), |
| | | uint64(C.eventTapCreateFailures()), |
| | | uint64(C.eventTapDisabledCount()), |
| | | ) |
| | | } |
| | | |
| | | if previous, ok := l.lastAltState[vk]; !ok || previous != down { |
| | | l.lastAltState[vk] = down |
| | | if previous, ok := l.lastModifierState[vk]; !ok || previous != down { |
| | | l.lastModifierState[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", |
| | | "Hotkey poll: key=%s vk=0x%X keyCode=%d down=%t source=%s physicalHID=%t physicalCombined=%t trackedDown=%t recentDown=%t aggregateFlagDown=%t tapStarted=%t launching=%t attempts=%d failures=%d disabled=%d", |
| | | GetKeyName(vk), |
| | | vk, |
| | | keyCode, |
| | | down, |
| | | hotkeySource(down, physicalDown, trackedDown, recentDown), |
| | | physicalDown, |
| | | source, |
| | | physicalHIDDown, |
| | | physicalCombinedDown, |
| | | trackedDown, |
| | | recentDown, |
| | | aggregateFlagDown, |
| | | C.isMonitorStarted() != 0, |
| | | C.isMonitorLaunching() != 0, |
| | | uint64(C.monitorStartAttempts()), |
| | | uint64(C.eventTapCreateFailures()), |
| | | uint64(C.eventTapDisabledCount()), |
| | | ) |
| | | } |
| | | } |
| | | |
| | | func hotkeySource(down, physicalDown, trackedDown, recentDown bool) string { |
| | | switch { |
| | | case !down: |
| | | func sideModifierSourceAccepted(source int) bool { |
| | | return source > 0 && source != 5 |
| | | } |
| | | |
| | | func modifierFlagMaskForKeyCode(keyCode int) C.ulonglong { |
| | | switch keyCode { |
| | | case 59, 62: |
| | | return maskControl |
| | | case 58, 61: |
| | | return maskOption |
| | | case 56, 60: |
| | | return maskShift |
| | | case 55, 54: |
| | | return maskCommand |
| | | default: |
| | | return 0 |
| | | } |
| | | } |
| | | |
| | | func hotkeySourceName(source int) string { |
| | | switch source { |
| | | case 0: |
| | | return "none" |
| | | case physicalDown: |
| | | return "physical" |
| | | case trackedDown: |
| | | case 1: |
| | | return "physicalHID" |
| | | case 2: |
| | | return "physicalCombined" |
| | | case 3: |
| | | return "eventTap" |
| | | case recentDown: |
| | | case 4: |
| | | return "recent" |
| | | case 5: |
| | | return "aggregateCommandFlag" |
| | | default: |
| | | return "unknown" |
| | | } |
| | | } |
| | | |
| | | func altDiagnosticIndex(keyCode int) int { |
| | | func modifierDiagnosticIndex(keyCode int) int { |
| | | switch keyCode { |
| | | case 58: |
| | | return 0 |
| | | case 61: |
| | | return 1 |
| | | case 55: |
| | | return 2 |
| | | case 54: |
| | | return 3 |
| | | default: |
| | | return -1 |
| | | } |
| | | } |
| | | |
| | | func altEventTypeName(eventType int) string { |
| | | func modifierEventTypeName(eventType int) string { |
| | | switch eventType { |
| | | case 1: |
| | | return "keyDown" |
| | |
| | | return 2 |
| | | case 0x10, 0xA0, 0xA1: |
| | | return 3 |
| | | case 0x5B, 0x5C: |
| | | return 4 |
| | | default: |
| | | return 0 |
| | | } |