diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index e73e53777e..c88ae6fca9 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -17,47 +17,43 @@ */ #include "AutoTypeMac.h" +#include "AutoTypeMacKeyCodes.h" #include "gui/macutils/MacUtils.h" -#include - -#define HOTKEY_ID 1 #define MAX_WINDOW_TITLE_LENGTH 1024 #define INVALID_KEYCODE 0xFFFF AutoTypePlatformMac::AutoTypePlatformMac() - : m_hotkeyRef(nullptr) - , m_hotkeyId({ 'kpx2', HOTKEY_ID }) + : m_globalMonitor(nullptr) { - EventTypeSpec eventSpec; - eventSpec.eventClass = kEventClassKeyboard; - eventSpec.eventKind = kEventHotKeyPressed; - - ::InstallApplicationEventHandler(AutoTypePlatformMac::hotkeyHandler, 1, &eventSpec, this, nullptr); } -// -// Keepassx requires mac os 10.7 -// +/** + * Request accessibility permissions required for keyboard control + * + * @return true on success + */ bool AutoTypePlatformMac::isAvailable() { - return true; + return macUtils()->enableAccessibility(); } -// -// Get list of visible window titles -// see: Quartz Window Services -// +/** + * Get a list of the currently open window titles + * + * @return list of window titles + */ QStringList AutoTypePlatformMac::windowTitles() { QStringList list; - CFArrayRef windowList = ::CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID); - if (windowList != nullptr) { - CFIndex count = ::CFArrayGetCount(windowList); + auto windowList = ::CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID); + if (windowList) { + auto count = ::CFArrayGetCount(windowList); for (CFIndex i = 0; i < count; i++) { - CFDictionaryRef window = static_cast(::CFArrayGetValueAtIndex(windowList, i)); + auto window = static_cast(::CFArrayGetValueAtIndex(windowList, i)); if (windowLayer(window) != 0) { continue; } @@ -74,24 +70,28 @@ QStringList AutoTypePlatformMac::windowTitles() return list; } -// -// Get active window process id -// +/** + * Get active window ID + * + * @return window ID + */ WId AutoTypePlatformMac::activeWindow() { return macUtils()->activeWindow(); } -// -// Get active window title -// see: Quartz Window Services -// +/** + * Get active window title + * + * @return window title + */ QString AutoTypePlatformMac::activeWindowTitle() { QString title; - CFArrayRef windowList = ::CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID); - if (windowList != nullptr) { + CFArrayRef windowList = ::CGWindowListCopyWindowInfo( + kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID); + if (windowList) { CFIndex count = ::CFArrayGetCount(windowList); for (CFIndex i = 0; i < count; i++) { @@ -111,41 +111,62 @@ QString AutoTypePlatformMac::activeWindowTitle() return title; } -// -// Register global hotkey -// +/** + * Register global hotkey using NS global event monitor. + * Note that this hotkey is not trapped and may trigger + * actions in the local application where it is issued. + * + * @param key key used for hotkey + * @param modifiers modifiers required in addition to key + * @return true on success + */ bool AutoTypePlatformMac::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) { - uint16 nativeKeyCode = qtToNativeKeyCode(key); + auto nativeKeyCode = qtToNativeKeyCode(key); if (nativeKeyCode == INVALID_KEYCODE) { qWarning("Invalid key code"); return false; } - CGEventFlags nativeModifiers = qtToNativeModifiers(modifiers, false); - if (::RegisterEventHotKey(nativeKeyCode, nativeModifiers, m_hotkeyId, GetApplicationEventTarget(), 0, &m_hotkeyRef) != noErr) { - qWarning("Register hotkey failed"); - return false; - } - + auto nativeModifiers = qtToNativeModifiers(modifiers); + m_globalMonitor = + macUtils()->addGlobalMonitor(nativeKeyCode, nativeModifiers, this, AutoTypePlatformMac::hotkeyHandler); return true; } -// -// Unregister global hotkey -// +/** + * Handle global hotkey presses by emitting the trigger signal + * + * @param userData pointer to AutoTypePlatform + */ +void AutoTypePlatformMac::hotkeyHandler(void* userData) +{ + auto* self = static_cast(userData); + emit self->globalShortcutTriggered(); +} + +/** + * Unregister a previously registered global hotkey + * + * @param key unused + * @param modifiers unused + */ void AutoTypePlatformMac::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) { Q_UNUSED(key); Q_UNUSED(modifiers); - - ::UnregisterEventHotKey(m_hotkeyRef); + if (m_globalMonitor) { + macUtils()->removeGlobalMonitor(m_globalMonitor); + m_globalMonitor = nullptr; + } } +/** + * Unused + */ int AutoTypePlatformMac::platformEventFilter(void* event) { Q_UNUSED(event); Q_ASSERT(false); - return -1; } @@ -154,17 +175,21 @@ AutoTypeExecutor* AutoTypePlatformMac::createExecutor() return new AutoTypeExecutorMac(this); } -// -// Activate window by process id -// +/** + * Raise the given window ID + * + * @return true on success + */ bool AutoTypePlatformMac::raiseWindow(WId pid) { return macUtils()->raiseWindow(pid); } -// -// Activate last active window -// +/** + * Hide the KeePassXC window + * + * @return true on success + */ bool AutoTypePlatformMac::hideOwnWindow() { return macUtils()->hideOwnWindow(); @@ -178,309 +203,293 @@ bool AutoTypePlatformMac::raiseOwnWindow() return macUtils()->raiseOwnWindow(); } -// -// Send unicode character to active window -// see: Quartz Event Services -// +/** + * Send provided character as key event to the active window + * + * @param ch unicode character + * @param isKeyDown whether the key is pressed + */ void AutoTypePlatformMac::sendChar(const QChar& ch, bool isKeyDown) { - CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, 0, isKeyDown); - if (keyEvent != nullptr) { - UniChar unicode = ch.unicode(); + auto keyEvent = ::CGEventCreateKeyboardEvent(nullptr, 0, isKeyDown); + if (keyEvent) { + auto unicode = ch.unicode(); ::CGEventKeyboardSetUnicodeString(keyEvent, 1, &unicode); ::CGEventPost(kCGSessionEventTap, keyEvent); ::CFRelease(keyEvent); } } -// -// Send key code to active window -// see: Quartz Event Services -// -void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers = 0) +/** + * Send provided Qt key as key event to the active window + * + * @param key Qt key code + * @param isKeyDown whether the key is pressed + * @param modifiers any modifiers to apply to key + */ +void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers) { - uint16 keyCode = qtToNativeKeyCode(key); + auto keyCode = qtToNativeKeyCode(key); if (keyCode == INVALID_KEYCODE) { return; } - CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); - CGEventFlags nativeModifiers = qtToNativeModifiers(modifiers, true); - if (keyEvent != nullptr) { + auto keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); + auto nativeModifiers = qtToNativeModifiers(modifiers); + if (keyEvent) { ::CGEventSetFlags(keyEvent, nativeModifiers); ::CGEventPost(kCGSessionEventTap, keyEvent); ::CFRelease(keyEvent); } } -// -// Translate qt key code to mac os key code -// see: HIToolbox/Events.h -// -uint16 AutoTypePlatformMac::qtToNativeKeyCode(Qt::Key key) +/** + * Translate Qt key to macOS key code provided by + * AutoTypeMacKeyCodes.h which are derived from + * legacy Carbon "HIToolbox/Events.h" + * + * @param key key to translate + * @returns macOS key code + */ +CGKeyCode AutoTypePlatformMac::qtToNativeKeyCode(Qt::Key key) { switch (key) { - case Qt::Key_A: - return kVK_ANSI_A; - case Qt::Key_B: - return kVK_ANSI_B; - case Qt::Key_C: - return kVK_ANSI_C; - case Qt::Key_D: - return kVK_ANSI_D; - case Qt::Key_E: - return kVK_ANSI_E; - case Qt::Key_F: - return kVK_ANSI_F; - case Qt::Key_G: - return kVK_ANSI_G; - case Qt::Key_H: - return kVK_ANSI_H; - case Qt::Key_I: - return kVK_ANSI_I; - case Qt::Key_J: - return kVK_ANSI_J; - case Qt::Key_K: - return kVK_ANSI_K; - case Qt::Key_L: - return kVK_ANSI_L; - case Qt::Key_M: - return kVK_ANSI_M; - case Qt::Key_N: - return kVK_ANSI_N; - case Qt::Key_O: - return kVK_ANSI_O; - case Qt::Key_P: - return kVK_ANSI_P; - case Qt::Key_Q: - return kVK_ANSI_Q; - case Qt::Key_R: - return kVK_ANSI_R; - case Qt::Key_S: - return kVK_ANSI_S; - case Qt::Key_T: - return kVK_ANSI_T; - case Qt::Key_U: - return kVK_ANSI_U; - case Qt::Key_V: - return kVK_ANSI_V; - case Qt::Key_W: - return kVK_ANSI_W; - case Qt::Key_X: - return kVK_ANSI_X; - case Qt::Key_Y: - return kVK_ANSI_Y; - case Qt::Key_Z: - return kVK_ANSI_Z; - - case Qt::Key_0: - return kVK_ANSI_0; - case Qt::Key_1: - return kVK_ANSI_1; - case Qt::Key_2: - return kVK_ANSI_2; - case Qt::Key_3: - return kVK_ANSI_3; - case Qt::Key_4: - return kVK_ANSI_4; - case Qt::Key_5: - return kVK_ANSI_5; - case Qt::Key_6: - return kVK_ANSI_6; - case Qt::Key_7: - return kVK_ANSI_7; - case Qt::Key_8: - return kVK_ANSI_8; - case Qt::Key_9: - return kVK_ANSI_9; - - case Qt::Key_Equal: - return kVK_ANSI_Equal; - case Qt::Key_Minus: - return kVK_ANSI_Minus; - case Qt::Key_BracketRight: - return kVK_ANSI_RightBracket; - case Qt::Key_BracketLeft: - return kVK_ANSI_LeftBracket; - case Qt::Key_QuoteDbl: - return kVK_ANSI_Quote; - case Qt::Key_Semicolon: - return kVK_ANSI_Semicolon; - case Qt::Key_Backslash: - return kVK_ANSI_Backslash; - case Qt::Key_Comma: - return kVK_ANSI_Comma; - case Qt::Key_Slash: - return kVK_ANSI_Slash; - case Qt::Key_Period: - return kVK_ANSI_Period; - - case Qt::Key_Shift: - return kVK_Shift; - case Qt::Key_Control: - return kVK_Command; - case Qt::Key_Backspace: - return kVK_Delete; - case Qt::Key_Tab: - case Qt::Key_Backtab: - return kVK_Tab; - case Qt::Key_Enter: - case Qt::Key_Return: - return kVK_Return; - case Qt::Key_CapsLock: - return kVK_CapsLock; - case Qt::Key_Escape: - return kVK_Escape; - case Qt::Key_Space: - return kVK_Space; - case Qt::Key_PageUp: - return kVK_PageUp; - case Qt::Key_PageDown: - return kVK_PageDown; - case Qt::Key_End: - return kVK_End; - case Qt::Key_Home: - return kVK_Home; - case Qt::Key_Left: - return kVK_LeftArrow; - case Qt::Key_Up: - return kVK_UpArrow; - case Qt::Key_Right: - return kVK_RightArrow; - case Qt::Key_Down: - return kVK_DownArrow; - case Qt::Key_Delete: - return kVK_ForwardDelete; - case Qt::Key_Help: - return kVK_Help; - - case Qt::Key_F1: - return kVK_F1; - case Qt::Key_F2: - return kVK_F2; - case Qt::Key_F3: - return kVK_F3; - case Qt::Key_F4: - return kVK_F4; - case Qt::Key_F5: - return kVK_F5; - case Qt::Key_F6: - return kVK_F6; - case Qt::Key_F7: - return kVK_F7; - case Qt::Key_F8: - return kVK_F8; - case Qt::Key_F9: - return kVK_F9; - case Qt::Key_F10: - return kVK_F10; - case Qt::Key_F11: - return kVK_F11; - case Qt::Key_F12: - return kVK_F12; - case Qt::Key_F13: - return kVK_F13; - case Qt::Key_F14: - return kVK_F14; - case Qt::Key_F15: - return kVK_F15; - case Qt::Key_F16: - return kVK_F16; - - default: - Q_ASSERT(false); - return INVALID_KEYCODE; + case Qt::Key_A: + return kVK_ANSI_A; + case Qt::Key_B: + return kVK_ANSI_B; + case Qt::Key_C: + return kVK_ANSI_C; + case Qt::Key_D: + return kVK_ANSI_D; + case Qt::Key_E: + return kVK_ANSI_E; + case Qt::Key_F: + return kVK_ANSI_F; + case Qt::Key_G: + return kVK_ANSI_G; + case Qt::Key_H: + return kVK_ANSI_H; + case Qt::Key_I: + return kVK_ANSI_I; + case Qt::Key_J: + return kVK_ANSI_J; + case Qt::Key_K: + return kVK_ANSI_K; + case Qt::Key_L: + return kVK_ANSI_L; + case Qt::Key_M: + return kVK_ANSI_M; + case Qt::Key_N: + return kVK_ANSI_N; + case Qt::Key_O: + return kVK_ANSI_O; + case Qt::Key_P: + return kVK_ANSI_P; + case Qt::Key_Q: + return kVK_ANSI_Q; + case Qt::Key_R: + return kVK_ANSI_R; + case Qt::Key_S: + return kVK_ANSI_S; + case Qt::Key_T: + return kVK_ANSI_T; + case Qt::Key_U: + return kVK_ANSI_U; + case Qt::Key_V: + return kVK_ANSI_V; + case Qt::Key_W: + return kVK_ANSI_W; + case Qt::Key_X: + return kVK_ANSI_X; + case Qt::Key_Y: + return kVK_ANSI_Y; + case Qt::Key_Z: + return kVK_ANSI_Z; + + case Qt::Key_0: + return kVK_ANSI_0; + case Qt::Key_1: + return kVK_ANSI_1; + case Qt::Key_2: + return kVK_ANSI_2; + case Qt::Key_3: + return kVK_ANSI_3; + case Qt::Key_4: + return kVK_ANSI_4; + case Qt::Key_5: + return kVK_ANSI_5; + case Qt::Key_6: + return kVK_ANSI_6; + case Qt::Key_7: + return kVK_ANSI_7; + case Qt::Key_8: + return kVK_ANSI_8; + case Qt::Key_9: + return kVK_ANSI_9; + + case Qt::Key_Equal: + return kVK_ANSI_Equal; + case Qt::Key_Minus: + return kVK_ANSI_Minus; + case Qt::Key_BracketRight: + return kVK_ANSI_RightBracket; + case Qt::Key_BracketLeft: + return kVK_ANSI_LeftBracket; + case Qt::Key_QuoteDbl: + return kVK_ANSI_Quote; + case Qt::Key_Semicolon: + return kVK_ANSI_Semicolon; + case Qt::Key_Backslash: + return kVK_ANSI_Backslash; + case Qt::Key_Comma: + return kVK_ANSI_Comma; + case Qt::Key_Slash: + return kVK_ANSI_Slash; + case Qt::Key_Period: + return kVK_ANSI_Period; + + case Qt::Key_Shift: + return kVK_Shift; + case Qt::Key_Control: + return kVK_Command; + case Qt::Key_Backspace: + return kVK_Delete; + case Qt::Key_Tab: + case Qt::Key_Backtab: + return kVK_Tab; + case Qt::Key_Enter: + case Qt::Key_Return: + return kVK_Return; + case Qt::Key_CapsLock: + return kVK_CapsLock; + case Qt::Key_Escape: + return kVK_Escape; + case Qt::Key_Space: + return kVK_Space; + case Qt::Key_PageUp: + return kVK_PageUp; + case Qt::Key_PageDown: + return kVK_PageDown; + case Qt::Key_End: + return kVK_End; + case Qt::Key_Home: + return kVK_Home; + case Qt::Key_Left: + return kVK_LeftArrow; + case Qt::Key_Up: + return kVK_UpArrow; + case Qt::Key_Right: + return kVK_RightArrow; + case Qt::Key_Down: + return kVK_DownArrow; + case Qt::Key_Delete: + return kVK_ForwardDelete; + case Qt::Key_Help: + return kVK_Help; + + case Qt::Key_F1: + return kVK_F1; + case Qt::Key_F2: + return kVK_F2; + case Qt::Key_F3: + return kVK_F3; + case Qt::Key_F4: + return kVK_F4; + case Qt::Key_F5: + return kVK_F5; + case Qt::Key_F6: + return kVK_F6; + case Qt::Key_F7: + return kVK_F7; + case Qt::Key_F8: + return kVK_F8; + case Qt::Key_F9: + return kVK_F9; + case Qt::Key_F10: + return kVK_F10; + case Qt::Key_F11: + return kVK_F11; + case Qt::Key_F12: + return kVK_F12; + case Qt::Key_F13: + return kVK_F13; + case Qt::Key_F14: + return kVK_F14; + case Qt::Key_F15: + return kVK_F15; + case Qt::Key_F16: + return kVK_F16; + + default: + return INVALID_KEYCODE; } } -// -// Translate qt key modifiers to mac os modifiers -// see: https://doc.qt.io/qt-5/osx-issues.html#special-keys -// -CGEventFlags AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native) +/** + * Translate Qt key modifiers to macOS key modifiers + * provided by the Core Graphics component + * + * @param modifiers Qt keyboard modifier(s) + * @returns macOS key modifier(s) + */ +CGEventFlags AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modifiers) { - CGEventFlags nativeModifiers = CGEventFlags(0); - - CGEventFlags shiftMod = CGEventFlags(shiftKey); - CGEventFlags cmdMod = CGEventFlags(cmdKey); - CGEventFlags optionMod = CGEventFlags(optionKey); - CGEventFlags controlMod = CGEventFlags(controlKey); - - if (native) { - shiftMod = kCGEventFlagMaskShift; - cmdMod = kCGEventFlagMaskCommand; - optionMod = kCGEventFlagMaskAlternate; - controlMod = kCGEventFlagMaskControl; - } - + auto nativeModifiers = CGEventFlags(0); if (modifiers & Qt::ShiftModifier) { - nativeModifiers = CGEventFlags(nativeModifiers | shiftMod); + nativeModifiers = CGEventFlags(nativeModifiers | kCGEventFlagMaskShift); } if (modifiers & Qt::ControlModifier) { - nativeModifiers = CGEventFlags(nativeModifiers | cmdMod); + nativeModifiers = CGEventFlags(nativeModifiers | kCGEventFlagMaskCommand); } if (modifiers & Qt::AltModifier) { - nativeModifiers = CGEventFlags(nativeModifiers | optionMod); + nativeModifiers = CGEventFlags(nativeModifiers | kCGEventFlagMaskAlternate); } if (modifiers & Qt::MetaModifier) { - nativeModifiers = CGEventFlags(nativeModifiers | controlMod); + nativeModifiers = CGEventFlags(nativeModifiers | kCGEventFlagMaskControl); } return nativeModifiers; } -// -// Get window layer/level -// +/** + * Get window layer / level + * + * @param window macOS window ref + * @returns layer number or -1 if window not found + */ int AutoTypePlatformMac::windowLayer(CFDictionaryRef window) { int layer; - CFNumberRef layerRef = static_cast(::CFDictionaryGetValue(window, kCGWindowLayer)); - if (layerRef != nullptr - && ::CFNumberGetValue(layerRef, kCFNumberIntType, &layer)) { + auto layerRef = static_cast(::CFDictionaryGetValue(window, kCGWindowLayer)); + if (layerRef && ::CFNumberGetValue(layerRef, kCFNumberIntType, &layer)) { return layer; } return -1; } -// -// Get window title -// +/** + * Get window title for macOS window ref + * + * @param window macOS window ref + * @returns window title if found + */ QString AutoTypePlatformMac::windowTitle(CFDictionaryRef window) { char buffer[MAX_WINDOW_TITLE_LENGTH]; QString title; - CFStringRef titleRef = static_cast(::CFDictionaryGetValue(window, kCGWindowName)); - if (titleRef != nullptr - && ::CFStringGetCString(titleRef, buffer, MAX_WINDOW_TITLE_LENGTH, kCFStringEncodingUTF8)) { + auto titleRef = static_cast(::CFDictionaryGetValue(window, kCGWindowName)); + if (titleRef && ::CFStringGetCString(titleRef, buffer, MAX_WINDOW_TITLE_LENGTH, kCFStringEncodingUTF8)) { title = QString::fromUtf8(buffer); } return title; } -// -// Carbon hotkey handler -// -OSStatus AutoTypePlatformMac::hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) -{ - Q_UNUSED(nextHandler); - - AutoTypePlatformMac *self = static_cast(userData); - EventHotKeyID hotkeyId; - - if (::GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, nullptr, sizeof(hotkeyId), nullptr, &hotkeyId) == noErr - && hotkeyId.id == HOTKEY_ID) { - emit self->globalShortcutTriggered(); - } - - return noErr; -} - // // ------------------------------ AutoTypeExecutorMac ------------------------------ // @@ -502,7 +511,7 @@ void AutoTypeExecutorMac::execKey(AutoTypeKey* action) m_platform->sendKey(action->key, false); } -void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr) +void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action) { Q_UNUSED(action); diff --git a/src/autotype/mac/AutoTypeMac.h b/src/autotype/mac/AutoTypeMac.h index 55963da51d..904046e331 100644 --- a/src/autotype/mac/AutoTypeMac.h +++ b/src/autotype/mac/AutoTypeMac.h @@ -19,12 +19,12 @@ #ifndef KEEPASSX_AUTOTYPEMAC_H #define KEEPASSX_AUTOTYPEMAC_H -#include +#include #include #include -#include "autotype/AutoTypePlatformPlugin.h" #include "autotype/AutoTypeAction.h" +#include "autotype/AutoTypePlatformPlugin.h" class AutoTypePlatformMac : public QObject, public AutoTypePlatformInterface { @@ -48,20 +48,19 @@ class AutoTypePlatformMac : public QObject, public AutoTypePlatformInterface bool raiseOwnWindow() override; void sendChar(const QChar& ch, bool isKeyDown); - void sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers); + void sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers = 0); signals: void globalShortcutTriggered(); private: - EventHotKeyRef m_hotkeyRef; - EventHotKeyID m_hotkeyId; - - static uint16 qtToNativeKeyCode(Qt::Key key); - static CGEventFlags qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native); + static void hotkeyHandler(void* userData); + static CGKeyCode qtToNativeKeyCode(Qt::Key key); + static CGEventFlags qtToNativeModifiers(Qt::KeyboardModifiers modifiers); static int windowLayer(CFDictionaryRef window); static QString windowTitle(CFDictionaryRef window); - static OSStatus hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData); + + void* m_globalMonitor; }; class AutoTypeExecutorMac : public AutoTypeExecutor @@ -77,4 +76,4 @@ class AutoTypeExecutorMac : public AutoTypeExecutor AutoTypePlatformMac* const m_platform; }; -#endif // KEEPASSX_AUTOTYPEMAC_H +#endif // KEEPASSX_AUTOTYPEMAC_H diff --git a/src/autotype/mac/AutoTypeMacKeyCodes.h b/src/autotype/mac/AutoTypeMacKeyCodes.h new file mode 100644 index 0000000000..c89cf03466 --- /dev/null +++ b/src/autotype/mac/AutoTypeMacKeyCodes.h @@ -0,0 +1,160 @@ +// +// Taken from HIToolbox/Events.h +// + +/* + * Summary: + * Virtual keycodes + * + * Discussion: + * These constants are the virtual keycodes defined originally in + * Inside Mac Volume V, pg. V-191. They identify physical keys on a + * keyboard. Those constants with "ANSI" in the name are labeled + * according to the key position on an ANSI-standard US keyboard. + * For example, kVK_ANSI_A indicates the virtual keycode for the key + * with the letter 'A' in the US keyboard layout. Other keyboard + * layouts may have the 'A' key label on a different physical key; + * in this case, pressing 'A' will generate a different virtual + * keycode. + */ + +#ifndef KEEPASSXC_AUTOTYPEMAC_KEYCODES_H +#define KEEPASSXC_AUTOTYPEMAC_KEYCODES_H + +// clang-format off +enum { + kVK_ANSI_A = 0x00, + kVK_ANSI_S = 0x01, + kVK_ANSI_D = 0x02, + kVK_ANSI_F = 0x03, + kVK_ANSI_H = 0x04, + kVK_ANSI_G = 0x05, + kVK_ANSI_Z = 0x06, + kVK_ANSI_X = 0x07, + kVK_ANSI_C = 0x08, + kVK_ANSI_V = 0x09, + kVK_ANSI_B = 0x0B, + kVK_ANSI_Q = 0x0C, + kVK_ANSI_W = 0x0D, + kVK_ANSI_E = 0x0E, + kVK_ANSI_R = 0x0F, + kVK_ANSI_Y = 0x10, + kVK_ANSI_T = 0x11, + kVK_ANSI_1 = 0x12, + kVK_ANSI_2 = 0x13, + kVK_ANSI_3 = 0x14, + kVK_ANSI_4 = 0x15, + kVK_ANSI_6 = 0x16, + kVK_ANSI_5 = 0x17, + kVK_ANSI_Equal = 0x18, + kVK_ANSI_9 = 0x19, + kVK_ANSI_7 = 0x1A, + kVK_ANSI_Minus = 0x1B, + kVK_ANSI_8 = 0x1C, + kVK_ANSI_0 = 0x1D, + kVK_ANSI_RightBracket = 0x1E, + kVK_ANSI_O = 0x1F, + kVK_ANSI_U = 0x20, + kVK_ANSI_LeftBracket = 0x21, + kVK_ANSI_I = 0x22, + kVK_ANSI_P = 0x23, + kVK_ANSI_L = 0x25, + kVK_ANSI_J = 0x26, + kVK_ANSI_Quote = 0x27, + kVK_ANSI_K = 0x28, + kVK_ANSI_Semicolon = 0x29, + kVK_ANSI_Backslash = 0x2A, + kVK_ANSI_Comma = 0x2B, + kVK_ANSI_Slash = 0x2C, + kVK_ANSI_N = 0x2D, + kVK_ANSI_M = 0x2E, + kVK_ANSI_Period = 0x2F, + kVK_ANSI_Grave = 0x32, + kVK_ANSI_KeypadDecimal = 0x41, + kVK_ANSI_KeypadMultiply = 0x43, + kVK_ANSI_KeypadPlus = 0x45, + kVK_ANSI_KeypadClear = 0x47, + kVK_ANSI_KeypadDivide = 0x4B, + kVK_ANSI_KeypadEnter = 0x4C, + kVK_ANSI_KeypadMinus = 0x4E, + kVK_ANSI_KeypadEquals = 0x51, + kVK_ANSI_Keypad0 = 0x52, + kVK_ANSI_Keypad1 = 0x53, + kVK_ANSI_Keypad2 = 0x54, + kVK_ANSI_Keypad3 = 0x55, + kVK_ANSI_Keypad4 = 0x56, + kVK_ANSI_Keypad5 = 0x57, + kVK_ANSI_Keypad6 = 0x58, + kVK_ANSI_Keypad7 = 0x59, + kVK_ANSI_Keypad8 = 0x5B, + kVK_ANSI_Keypad9 = 0x5C +}; + +/* keycodes for keys that are independent of keyboard layout*/ +enum { + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightCommand = 0x36, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +}; + +/* ISO keyboards only*/ +enum { + kVK_ISO_Section = 0x0A +}; + +/* JIS keyboards only*/ +enum { + kVK_JIS_Yen = 0x5D, + kVK_JIS_Underscore = 0x5E, + kVK_JIS_KeypadComma = 0x5F, + kVK_JIS_Eisu = 0x66, + kVK_JIS_Kana = 0x68 +}; +// clang-format on + +#endif diff --git a/src/autotype/mac/CMakeLists.txt b/src/autotype/mac/CMakeLists.txt index 7427450a10..0179b4a0c7 100644 --- a/src/autotype/mac/CMakeLists.txt +++ b/src/autotype/mac/CMakeLists.txt @@ -1,7 +1,7 @@ set(autotype_mac_SOURCES AutoTypeMac.cpp) add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES}) -set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon") +set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit") target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets) if(WITH_APP_BUNDLE) diff --git a/src/gui/macutils/AppKit.h b/src/gui/macutils/AppKit.h index da81f69135..d7a5ba16e9 100644 --- a/src/gui/macutils/AppKit.h +++ b/src/gui/macutils/AppKit.h @@ -20,7 +20,7 @@ #define KEEPASSX_APPKIT_H #include -#include +#include class AppKit : public QObject { @@ -37,12 +37,15 @@ class AppKit : public QObject bool hideProcess(pid_t pid); bool isHidden(pid_t pid); bool isDarkMode(); + void* addGlobalMonitor(CGKeyCode keycode, CGEventFlags modifier, void* userData, void (*handler)(void*)); + void removeGlobalMonitor(void* monitor); + bool enableAccessibility(); signals: void lockDatabases(); private: - void *self; + void* self; }; #endif // KEEPASSX_APPKIT_H diff --git a/src/gui/macutils/AppKitImpl.h b/src/gui/macutils/AppKitImpl.h index ca2506794b..54f6959195 100644 --- a/src/gui/macutils/AppKitImpl.h +++ b/src/gui/macutils/AppKitImpl.h @@ -19,13 +19,14 @@ #import "AppKit.h" #import +#import #import @interface AppKitImpl : NSObject { - AppKit *m_appkit; + AppKit* m_appkit; } -- (id) initWithObject:(AppKit *)appkit; +- (id) initWithObject:(AppKit*)appkit; @property (strong) NSRunningApplication *lastActiveApplication; @@ -36,5 +37,8 @@ - (bool) isHidden:(pid_t) pid; - (bool) isDarkMode; - (void) userSwitchHandler:(NSNotification*) notification; +- (id) addGlobalMonitor:(NSEventMask) mask handler:(void (^)(NSEvent*)) handler; +- (void) removeGlobalMonitor:(id) monitor; +- (bool) enableAccessibility; @end diff --git a/src/gui/macutils/AppKitImpl.mm b/src/gui/macutils/AppKitImpl.mm index 4165e0d5e2..dec33421eb 100644 --- a/src/gui/macutils/AppKitImpl.mm +++ b/src/gui/macutils/AppKitImpl.mm @@ -22,7 +22,7 @@ @implementation AppKitImpl -- (id) initWithObject:(AppKit *)appkit +- (id) initWithObject:(AppKit*)appkit { self = [super init]; if (self) { @@ -43,10 +43,10 @@ - (id) initWithObject:(AppKit *)appkit // // Update last active application property // -- (void) didDeactivateApplicationObserver:(NSNotification *) notification +- (void) didDeactivateApplicationObserver:(NSNotification*) notification { - NSDictionary *userInfo = notification.userInfo; - NSRunningApplication *app = userInfo[NSWorkspaceApplicationKey]; + NSDictionary* userInfo = notification.userInfo; + NSRunningApplication* app = userInfo[NSWorkspaceApplicationKey]; if (app.processIdentifier != [self ownProcessId]) { self.lastActiveApplication = app; @@ -74,7 +74,7 @@ - (pid_t) ownProcessId // - (bool) activateProcess:(pid_t) pid { - NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; + NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; return [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; } @@ -83,7 +83,7 @@ - (bool) activateProcess:(pid_t) pid // - (bool) hideProcess:(pid_t) pid { - NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; + NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; return [app hide]; } @@ -92,7 +92,7 @@ - (bool) hideProcess:(pid_t) pid // - (bool) isHidden:(pid_t) pid { - NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; + NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; return [app isHidden]; } @@ -101,7 +101,7 @@ - (bool) isHidden:(pid_t) pid // - (bool) isDarkMode { - NSDictionary *dict = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain]; + NSDictionary* dict = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain]; id style = [dict objectForKey:@"AppleInterfaceStyle"]; return ( style && [style isKindOfClass:[NSString class]] && NSOrderedSame == [style caseInsensitiveCompare:@"dark"] ); @@ -118,6 +118,31 @@ - (void) userSwitchHandler:(NSNotification*) notification } } +// +// Add global event monitor +// +- (id) addGlobalMonitor:(NSEventMask) mask handler:(void (^)(NSEvent*)) handler +{ + return [NSEvent addGlobalMonitorForEventsMatchingMask:mask handler:handler]; +} + +// +// Remove global event monitor +// +- (void) removeGlobalMonitor:(id) monitor +{ + [NSEvent removeMonitor:monitor]; +} + +// +// Check if accessibility is enabled, may show an popup asking for permissions +// +- (bool) enableAccessibility +{ + NSDictionary* opts = @{static_cast(kAXTrustedCheckOptionPrompt): @YES}; + return AXIsProcessTrustedWithOptions(static_cast(opts)); +} + @end // @@ -169,3 +194,22 @@ - (void) userSwitchHandler:(NSNotification*) notification { return [static_cast(self) isDarkMode]; } + +void* AppKit::addGlobalMonitor(CGKeyCode keycode, CGEventFlags modifier, void* userData, void (*handler)(void*)) +{ + return [static_cast(self) addGlobalMonitor:NSEventMaskKeyDown handler:^(NSEvent* event) { + if (event.keyCode == keycode && (event.modifierFlags & modifier) == modifier) { + handler(userData); + } + }]; +} + +void AppKit::removeGlobalMonitor(void* monitor) +{ + [static_cast(self) removeGlobalMonitor:static_cast(monitor)]; +} + +bool AppKit::enableAccessibility() +{ + return [static_cast(self) enableAccessibility]; +} diff --git a/src/gui/macutils/MacUtils.cpp b/src/gui/macutils/MacUtils.cpp index 654923c31c..653eb0832e 100644 --- a/src/gui/macutils/MacUtils.cpp +++ b/src/gui/macutils/MacUtils.cpp @@ -75,3 +75,18 @@ bool MacUtils::isDarkMode() { return m_appkit->isDarkMode(); } + +void* MacUtils::addGlobalMonitor(CGKeyCode keycode, CGEventFlags modifier, void* userData, void (*handler)(void*)) +{ + return m_appkit->addGlobalMonitor(keycode, modifier, userData, handler); +} + +void MacUtils::removeGlobalMonitor(void* monitor) +{ + m_appkit->removeGlobalMonitor(monitor); +} + +bool MacUtils::enableAccessibility() +{ + return m_appkit->enableAccessibility(); +} diff --git a/src/gui/macutils/MacUtils.h b/src/gui/macutils/MacUtils.h index 49644795e1..ced6127497 100644 --- a/src/gui/macutils/MacUtils.h +++ b/src/gui/macutils/MacUtils.h @@ -38,6 +38,9 @@ class MacUtils : public QObject bool hideOwnWindow(); bool isHidden(); bool isDarkMode(); + void* addGlobalMonitor(CGKeyCode keycode, CGEventFlags modifier, void* userData, void (*handler)(void*)); + void removeGlobalMonitor(void* monitor); + bool enableAccessibility(); signals: void lockDatabases();