diff --git a/CMakeLists.txt b/CMakeLists.txt index 29acf794c..fe556ab7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,11 +305,11 @@ if(WITH_COVERAGE AND CMAKE_COMPILER_IS_CLANGXX) # `find src -iname '*.h' -or -iname '*.cpp'` endif() -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") +if(UNIX AND NOT APPLE) check_add_gcc_compiler_flag("-Qunused-arguments") - add_gcc_compiler_flags("-pie -fPIE") + check_add_gcc_compiler_flag("-fPIC") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed -Wl,--no-undefined") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now -pie") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro,-z,now") endif() @@ -399,7 +399,7 @@ include(CLangFormat) set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools) if(UNIX AND NOT APPLE) - find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus REQUIRED) + find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus X11Extras REQUIRED) elseif(APPLE) find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED HINTS /usr/local/opt/qt/lib/cmake /usr/local/Cellar/qt/*/lib/cmake ENV PATH) find_package(Qt5 COMPONENTS MacExtras HINTS /usr/local/opt/qt/lib/cmake /usr/local/Cellar/qt/*/lib/cmake ENV PATH) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2044784b6..a21b0057e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -206,7 +206,8 @@ if(UNIX AND NOT APPLE) ${keepassx_SOURCES} gui/MainWindowAdaptor.cpp gui/osutils/nixutils/ScreenLockListenerDBus.cpp - gui/osutils/nixutils/NixUtils.cpp) + gui/osutils/nixutils/NixUtils.cpp + gui/osutils/nixutils/X11Funcs.cpp) endif() if(MINGW) set(keepassx_SOURCES @@ -214,11 +215,6 @@ if(MINGW) gui/osutils/winutils/ScreenLockListenerWin.cpp gui/osutils/winutils/WinUtils.cpp) endif() -if(MINGW OR (UNIX AND NOT APPLE)) - set(keepassx_SOURCES - ${keepassx_SOURCES} - gui/osutils/OSEventFilter.cpp) -endif() set(keepassx_SOURCES ${keepassx_SOURCES} ../share/icons/icons.qrc @@ -343,7 +339,7 @@ if(WITH_XC_KEESHARE) endif() if(APPLE) - target_link_libraries(keepassx_core "-framework Foundation -framework AppKit") + target_link_libraries(keepassx_core "-framework Foundation -framework AppKit -framework Carbon") if(Qt5MacExtras_FOUND) target_link_libraries(keepassx_core Qt5::MacExtras) endif() @@ -356,7 +352,7 @@ if(HAIKU) target_link_libraries(keepassx_core network) endif() if(UNIX AND NOT APPLE) - target_link_libraries(keepassx_core Qt5::DBus X11) + target_link_libraries(keepassx_core Qt5::DBus Qt5::X11Extras X11) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() if(MINGW) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 8bc318295..94dd432c9 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -37,18 +37,13 @@ #include "core/Tools.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" - -#ifdef Q_OS_MAC -#include "gui/osutils/macutils/MacUtils.h" -#endif +#include "gui/osutils/OSUtils.h" AutoType* AutoType::m_instance = nullptr; AutoType::AutoType(QObject* parent, bool test) : QObject(parent) , m_autoTypeDelay(0) - , m_currentGlobalKey(static_cast(0)) - , m_currentGlobalModifiers(nullptr) , m_pluginLoader(new QPluginLoader(this)) , m_plugin(nullptr) , m_executor(nullptr) @@ -96,7 +91,11 @@ void AutoType::loadPlugin(const QString& pluginPath) if (m_plugin) { if (m_plugin->isAvailable()) { m_executor = m_plugin->createExecutor(); - connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SLOT(startGlobalAutoType())); + connect(osUtils, &OSUtilsBase::globalShortcutTriggered, this, [this](QString name) { + if (name == "autotype") { + startGlobalAutoType(); + } + }); } else { unloadPlugin(); } @@ -153,44 +152,18 @@ void AutoType::raiseWindow() #endif } -bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error) { - Q_ASSERT(key); - Q_ASSERT(modifiers); - if (!m_plugin) { return false; } - if (key != m_currentGlobalKey || modifiers != m_currentGlobalModifiers) { - if (m_currentGlobalKey && m_currentGlobalModifiers) { - m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); - } - - if (m_plugin->registerGlobalShortcut(key, modifiers)) { - m_currentGlobalKey = key; - m_currentGlobalModifiers = modifiers; - return true; - } - return false; - } - return true; + return osUtils->registerGlobalShortcut("autotype", key, modifiers, error); } void AutoType::unregisterGlobalShortcut() { - if (m_plugin && m_currentGlobalKey && m_currentGlobalModifiers) { - m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); - } -} - -int AutoType::callEventFilter(void* event) -{ - if (!m_plugin) { - return -1; - } - - return m_plugin->platformEventFilter(event); + osUtils->unregisterGlobalShortcut("autotype"); } /** @@ -303,6 +276,23 @@ void AutoType::startGlobalAutoType() m_windowForGlobal = m_plugin->activeWindow(); m_windowTitleForGlobal = m_plugin->activeWindowTitle(); #ifdef Q_OS_MACOS + // Determine if the user has given proper permissions to KeePassXC to perform Auto-Type + static bool accessibilityChecked = false; + if (!accessibilityChecked) { + if (macUtils()->enableAccessibility() && macUtils()->enableScreenRecording()) { + accessibilityChecked = true; + } else if (getMainWindow()) { + // Does not have required permissions to Auto-Type, ignore the event + MessageBox::information( + nullptr, + tr("Permission Required"), + tr("KeePassXC requires the Accessibility and Screen Recorder permission in order to perform global " + "Auto-Type. Screen Recording is necessary to use the window title to find entries. If you " + "already granted permission, you may have to restart KeePassXC.")); + return; + } + } + m_windowState = WindowState::Normal; if (getMainWindow()) { if (getMainWindow()->isMinimized()) { diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index 78cd42f88..8d0cf3d14 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -39,9 +39,8 @@ class AutoType : public QObject public: QStringList windowTitles(); - bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers); + bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error = nullptr); void unregisterGlobalShortcut(); - int callEventFilter(void* event); static bool checkSyntax(const QString& string); static bool checkHighRepetition(const QString& string); static bool checkSlowKeypress(const QString& string); @@ -99,8 +98,6 @@ private: QMutex m_inAutoType; QMutex m_inGlobalAutoTypeDialog; int m_autoTypeDelay; - Qt::Key m_currentGlobalKey; - Qt::KeyboardModifiers m_currentGlobalModifiers; QPluginLoader* m_pluginLoader; AutoTypePlatformInterface* m_plugin; AutoTypeExecutor* m_executor; diff --git a/src/autotype/AutoTypePlatformPlugin.h b/src/autotype/AutoTypePlatformPlugin.h index 059e7e134..7154dcdd7 100644 --- a/src/autotype/AutoTypePlatformPlugin.h +++ b/src/autotype/AutoTypePlatformPlugin.h @@ -32,9 +32,6 @@ public: virtual QStringList windowTitles() = 0; virtual WId activeWindow() = 0; virtual QString activeWindowTitle() = 0; - virtual bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) = 0; - virtual void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) = 0; - virtual int platformEventFilter(void* event) = 0; virtual bool raiseWindow(WId window) = 0; virtual void unload() { diff --git a/src/autotype/ShortcutWidget.cpp b/src/autotype/ShortcutWidget.cpp index 3dcc669d9..91b2fda93 100644 --- a/src/autotype/ShortcutWidget.cpp +++ b/src/autotype/ShortcutWidget.cpp @@ -18,6 +18,7 @@ #include "ShortcutWidget.h" #include +#include #include "autotype/AutoType.h" @@ -48,9 +49,11 @@ void ShortcutWidget::setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) displayShortcut(m_key, m_modifiers); - if (autoType()->registerGlobalShortcut(m_key, m_modifiers)) { + QString error; + if (autoType()->registerGlobalShortcut(m_key, m_modifiers, &error)) { setStyleSheet(""); } else { + QToolTip::showText(mapToGlobal(rect().bottomLeft()), error); setStyleSheet("background-color: #FF9696;"); } } diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 1e52f58fe..6f9cb997c 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -22,24 +22,12 @@ #include -#define HOTKEY_ID 1 #define MAX_WINDOW_TITLE_LENGTH 1024 #define INVALID_KEYCODE 0xFFFF -namespace { -bool accessibilityChecked = false; -} - AutoTypePlatformMac::AutoTypePlatformMac() - : m_hotkeyRef(nullptr) - , m_hotkeyId({ 'kpx2', HOTKEY_ID }) { - EventTypeSpec eventSpec; - eventSpec.eventClass = kEventClassKeyboard; - eventSpec.eventKind = kEventHotKeyPressed; - MessageBox::initializeButtonDefs(); - ::InstallApplicationEventHandler(AutoTypePlatformMac::hotkeyHandler, 1, &eventSpec, this, nullptr); } /** @@ -120,44 +108,6 @@ QString AutoTypePlatformMac::activeWindowTitle() return title; } -// -// Register global hotkey -// -bool AutoTypePlatformMac::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) -{ - uint16 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; - } - - return true; -} - -// -// Unregister global hotkey -// -void AutoTypePlatformMac::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) -{ - Q_UNUSED(key); - Q_UNUSED(modifiers); - - ::UnregisterEventHotKey(m_hotkeyRef); -} - -int AutoTypePlatformMac::platformEventFilter(void* event) -{ - Q_UNUSED(event); - Q_ASSERT(false); - - return -1; -} - AutoTypeExecutor* AutoTypePlatformMac::createExecutor() { return new AutoTypeExecutorMac(this); @@ -208,13 +158,13 @@ void AutoTypePlatformMac::sendChar(const QChar& ch, bool isKeyDown) // void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers = 0) { - uint16 keyCode = qtToNativeKeyCode(key); + uint16 keyCode = macUtils()->qtToNativeKeyCode(key); if (keyCode == INVALID_KEYCODE) { return; } CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); - CGEventFlags nativeModifiers = qtToNativeModifiers(modifiers, true); + CGEventFlags nativeModifiers = macUtils()->qtToNativeModifiers(modifiers, true); if (keyEvent != nullptr) { ::CGEventSetFlags(keyEvent, nativeModifiers); ::CGEventPost(kCGSessionEventTap, keyEvent); @@ -222,223 +172,6 @@ void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModif } } -// -// Translate qt key code to mac os key code -// see: HIToolbox/Events.h -// -uint16 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; - } -} - -// -// 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) -{ - 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; - } - - - if (modifiers & Qt::ShiftModifier) { - nativeModifiers = CGEventFlags(nativeModifiers | shiftMod); - } - if (modifiers & Qt::ControlModifier) { - nativeModifiers = CGEventFlags(nativeModifiers | cmdMod); - } - if (modifiers & Qt::AltModifier) { - nativeModifiers = CGEventFlags(nativeModifiers | optionMod); - } - if (modifiers & Qt::MetaModifier) { - nativeModifiers = CGEventFlags(nativeModifiers | controlMod); - } - - return nativeModifiers; -} - // // Get window layer/level // @@ -472,39 +205,6 @@ QString AutoTypePlatformMac::windowTitle(CFDictionaryRef window) return title; } -// -// Carbon hotkey handler -// -OSStatus AutoTypePlatformMac::hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData) -{ - Q_UNUSED(nextHandler); - - // Determine if the user has given proper permissions to KeePassXC to perform Auto-Type - if (!accessibilityChecked) { - if (macUtils()->enableAccessibility() && macUtils()->enableScreenRecording()) { - accessibilityChecked = true; - } else { - // Does not have required permissions to Auto-Type, ignore the keypress - MessageBox::information(nullptr, - tr("Permission Required"), - tr("KeePassXC requires the Accessibility and Screen Recorder permission in order to perform global " - "Auto-Type. Screen Recording is necessary to use the window title to find entries. If you " - "already granted permission, you may have to restart KeePassXC.")); - return noErr; - } - } - - 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 ------------------------------ // diff --git a/src/autotype/mac/AutoTypeMac.h b/src/autotype/mac/AutoTypeMac.h index 56c35fd2c..4ae9e2140 100644 --- a/src/autotype/mac/AutoTypeMac.h +++ b/src/autotype/mac/AutoTypeMac.h @@ -38,9 +38,6 @@ public: QStringList windowTitles() override; WId activeWindow() override; QString activeWindowTitle() override; - bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; - void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; - int platformEventFilter(void* event) override; bool raiseWindow(WId pid) override; AutoTypeExecutor* createExecutor() override; @@ -50,18 +47,9 @@ public: void sendChar(const QChar& ch, bool isKeyDown); void sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers); -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 int windowLayer(CFDictionaryRef window); static QString windowTitle(CFDictionaryRef window); - static OSStatus hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData); }; class AutoTypeExecutorMac : public AutoTypeExecutor diff --git a/src/autotype/test/AutoTypeTest.cpp b/src/autotype/test/AutoTypeTest.cpp index 225698e62..aa768238f 100644 --- a/src/autotype/test/AutoTypeTest.cpp +++ b/src/autotype/test/AutoTypeTest.cpp @@ -42,37 +42,11 @@ QString AutoTypePlatformTest::activeWindowTitle() return m_activeWindowTitle; } -bool AutoTypePlatformTest::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) -{ - Q_UNUSED(key); - Q_UNUSED(modifiers); - - return true; -} - -void AutoTypePlatformTest::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) -{ - Q_UNUSED(key); - Q_UNUSED(modifiers); -} - -int AutoTypePlatformTest::platformEventFilter(void* event) -{ - Q_UNUSED(event); - - return -1; -} - AutoTypeExecutor* AutoTypePlatformTest::createExecutor() { return new AutoTypeExecutorTest(this); } -void AutoTypePlatformTest::triggerGlobalAutoType() -{ - emit globalShortcutTriggered(); -} - void AutoTypePlatformTest::setActiveWindowTitle(const QString& title) { m_activeWindowTitle = title; diff --git a/src/autotype/test/AutoTypeTest.h b/src/autotype/test/AutoTypeTest.h index ef19c1dd7..dbbbe4642 100644 --- a/src/autotype/test/AutoTypeTest.h +++ b/src/autotype/test/AutoTypeTest.h @@ -37,9 +37,6 @@ public: QStringList windowTitles() override; WId activeWindow() override; QString activeWindowTitle() override; - bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; - void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; - int platformEventFilter(void* event) override; bool raiseWindow(WId window) override; AutoTypeExecutor* createExecutor() override; @@ -48,7 +45,6 @@ public: bool raiseOwnWindow() override; #endif - void triggerGlobalAutoType() override; void setActiveWindowTitle(const QString& title) override; QString actionChars() override; @@ -58,9 +54,6 @@ public: void addActionChar(AutoTypeChar* action); void addActionKey(AutoTypeKey* action); -signals: - void globalShortcutTriggered(); - private: QString m_activeWindowTitle; QList m_actionList; diff --git a/src/autotype/test/AutoTypeTestInterface.h b/src/autotype/test/AutoTypeTestInterface.h index 4595b0eb8..7681f2ecb 100644 --- a/src/autotype/test/AutoTypeTestInterface.h +++ b/src/autotype/test/AutoTypeTestInterface.h @@ -26,7 +26,6 @@ public: virtual ~AutoTypeTestInterface() { } - virtual void triggerGlobalAutoType() = 0; virtual void setActiveWindowTitle(const QString& title) = 0; virtual QString actionChars() = 0; diff --git a/src/autotype/windows/AutoTypeWindows.cpp b/src/autotype/windows/AutoTypeWindows.cpp index 59dafcf14..435c52d09 100644 --- a/src/autotype/windows/AutoTypeWindows.cpp +++ b/src/autotype/windows/AutoTypeWindows.cpp @@ -17,6 +17,7 @@ */ #include "AutoTypeWindows.h" +#include "gui/osutils/OSUtils.h" #include @@ -61,49 +62,6 @@ QString AutoTypePlatformWin::activeWindowTitle() return windowTitle(::GetForegroundWindow()); } -// -// Register global hotkey -// -bool AutoTypePlatformWin::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) -{ - DWORD nativeKeyCode = qtToNativeKeyCode(key); - if (nativeKeyCode < 1 || nativeKeyCode > 254) { - return false; - } - DWORD nativeModifiers = qtToNativeModifiers(modifiers); - if (!::RegisterHotKey(nullptr, HOTKEY_ID, nativeModifiers | MOD_NOREPEAT, nativeKeyCode)) { - return false; - } - - return true; -} - -// -// Unregister global hotkey -// -void AutoTypePlatformWin::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) -{ - Q_UNUSED(key); - Q_UNUSED(modifiers); - - ::UnregisterHotKey(nullptr, HOTKEY_ID); -} - -// -// Native event filter -// -int AutoTypePlatformWin::platformEventFilter(void* event) -{ - MSG* msg = static_cast(event); - - if (msg->message == WM_HOTKEY && msg->wParam == HOTKEY_ID) { - emit globalShortcutTriggered(); - return 1; - } - - return -1; -} - AutoTypeExecutor* AutoTypePlatformWin::createExecutor() { return new AutoTypeExecutorWin(this); @@ -145,7 +103,7 @@ void AutoTypePlatformWin::sendChar(const QChar& ch, bool isKeyDown) // void AutoTypePlatformWin::sendKey(Qt::Key key, bool isKeyDown) { - DWORD nativeKeyCode = qtToNativeKeyCode(key); + DWORD nativeKeyCode = winUtils()->qtToNativeKeyCode(key); if (nativeKeyCode < 1 || nativeKeyCode > 254) { return; } @@ -168,234 +126,12 @@ void AutoTypePlatformWin::sendKey(Qt::Key key, bool isKeyDown) ::SendInput(1, &in, sizeof(INPUT)); } -// clang-format off -// -// Translate qt key code to windows virtual key code -// see: https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx -// -DWORD AutoTypePlatformWin::qtToNativeKeyCode(Qt::Key key) -{ - switch (key) { - case Qt::Key_Backspace: - return VK_BACK; // 0x08 - case Qt::Key_Tab: - case Qt::Key_Backtab: - return VK_TAB; // 0x09 - case Qt::Key_Clear: - return VK_CLEAR; // 0x0C - case Qt::Key_Enter: - case Qt::Key_Return: - return VK_RETURN; // 0x0D - case Qt::Key_Shift: - return VK_SHIFT; // 0x10 - case Qt::Key_Control: - return VK_CONTROL; // 0x11 - case Qt::Key_Pause: - return VK_PAUSE; // 0x13 - case Qt::Key_CapsLock: - return VK_CAPITAL; // 0x14 - case Qt::Key_Escape: - return VK_ESCAPE; // 0x1B - case Qt::Key_Space: - return VK_SPACE; // 0x20 - case Qt::Key_PageUp: - return VK_PRIOR; // 0x21 - case Qt::Key_PageDown: - return VK_NEXT; // 0x22 - case Qt::Key_End: - return VK_END; // 0x23 - case Qt::Key_Home: - return VK_HOME; // 0x24 - case Qt::Key_Left: - return VK_LEFT; // 0x25 - case Qt::Key_Up: - return VK_UP; // 0x26 - case Qt::Key_Right: - return VK_RIGHT; // 0x27 - case Qt::Key_Down: - return VK_DOWN; // 0x28 - case Qt::Key_Print: - return VK_SNAPSHOT; // 0x2C - case Qt::Key_Insert: - return VK_INSERT; // 0x2D - case Qt::Key_Delete: - return VK_DELETE; // 0x2E - case Qt::Key_Help: - return VK_HELP; // 0x2F - - case Qt::Key_0: - return 0x30; // 0x30 - case Qt::Key_1: - return 0x31; // 0x31 - case Qt::Key_2: - return 0x32; // 0x32 - case Qt::Key_3: - return 0x33; // 0x33 - case Qt::Key_4: - return 0x34; // 0x34 - case Qt::Key_5: - return 0x35; // 0x35 - case Qt::Key_6: - return 0x36; // 0x36 - case Qt::Key_7: - return 0x37; // 0x37 - case Qt::Key_8: - return 0x38; // 0x38 - case Qt::Key_9: - return 0x39; // 0x39 - - case Qt::Key_A: - return 0x41; // 0x41 - case Qt::Key_B: - return 0x42; // 0x42 - case Qt::Key_C: - return 0x43; // 0x43 - case Qt::Key_D: - return 0x44; // 0x44 - case Qt::Key_E: - return 0x45; // 0x45 - case Qt::Key_F: - return 0x46; // 0x46 - case Qt::Key_G: - return 0x47; // 0x47 - case Qt::Key_H: - return 0x48; // 0x48 - case Qt::Key_I: - return 0x49; // 0x49 - case Qt::Key_J: - return 0x4A; // 0x4A - case Qt::Key_K: - return 0x4B; // 0x4B - case Qt::Key_L: - return 0x4C; // 0x4C - case Qt::Key_M: - return 0x4D; // 0x4D - case Qt::Key_N: - return 0x4E; // 0x4E - case Qt::Key_O: - return 0x4F; // 0x4F - case Qt::Key_P: - return 0x50; // 0x50 - case Qt::Key_Q: - return 0x51; // 0x51 - case Qt::Key_R: - return 0x52; // 0x52 - case Qt::Key_S: - return 0x53; // 0x53 - case Qt::Key_T: - return 0x54; // 0x54 - case Qt::Key_U: - return 0x55; // 0x55 - case Qt::Key_V: - return 0x56; // 0x56 - case Qt::Key_W: - return 0x57; // 0x57 - case Qt::Key_X: - return 0x58; // 0x58 - case Qt::Key_Y: - return 0x59; // 0x59 - case Qt::Key_Z: - return 0x5A; // 0x5A - - case Qt::Key_F1: - return VK_F1; // 0x70 - case Qt::Key_F2: - return VK_F2; // 0x71 - case Qt::Key_F3: - return VK_F3; // 0x72 - case Qt::Key_F4: - return VK_F4; // 0x73 - case Qt::Key_F5: - return VK_F5; // 0x74 - case Qt::Key_F6: - return VK_F6; // 0x75 - case Qt::Key_F7: - return VK_F7; // 0x76 - case Qt::Key_F8: - return VK_F8; // 0x77 - case Qt::Key_F9: - return VK_F9; // 0x78 - case Qt::Key_F10: - return VK_F10; // 0x79 - case Qt::Key_F11: - return VK_F11; // 0x7A - case Qt::Key_F12: - return VK_F12; // 0x7B - case Qt::Key_F13: - return VK_F13; // 0x7C - case Qt::Key_F14: - return VK_F14; // 0x7D - case Qt::Key_F15: - return VK_F15; // 0x7E - case Qt::Key_F16: - return VK_F16; // 0x7F - case Qt::Key_F17: - return VK_F17; // 0x80 - case Qt::Key_F18: - return VK_F18; // 0x81 - case Qt::Key_F19: - return VK_F19; // 0x82 - case Qt::Key_F20: - return VK_F20; // 0x83 - case Qt::Key_F21: - return VK_F21; // 0x84 - case Qt::Key_F22: - return VK_F22; // 0x85 - case Qt::Key_F23: - return VK_F23; // 0x86 - case Qt::Key_F24: - return VK_F24; // 0x87 - - case Qt::Key_NumLock: - return VK_NUMLOCK; // 0x90 - case Qt::Key_ScrollLock: - return VK_SCROLL; // 0x91 - - case Qt::Key_Exclam: // ! - case Qt::Key_QuoteDbl: // " - case Qt::Key_NumberSign: // # - case Qt::Key_Dollar: // $ - case Qt::Key_Percent: // % - case Qt::Key_Ampersand: // & - case Qt::Key_Apostrophe: // ' - case Qt::Key_ParenLeft: // ( - case Qt::Key_ParenRight: // ) - case Qt::Key_Asterisk: // * - case Qt::Key_Plus: // + - case Qt::Key_Comma: // , - case Qt::Key_Minus: // - - case Qt::Key_Period: // . - case Qt::Key_Slash: // / - case Qt::Key_Colon: // : - case Qt::Key_Semicolon: // ; - case Qt::Key_Less: // < - case Qt::Key_Equal: // = - case Qt::Key_Greater: // > - case Qt::Key_Question: // ? - case Qt::Key_BracketLeft: // [ - case Qt::Key_Backslash: // '\' - case Qt::Key_BracketRight: // ] - case Qt::Key_AsciiCircum: // ^ - case Qt::Key_Underscore: // _ - case Qt::Key_QuoteLeft: // ` - case Qt::Key_BraceLeft: // { - case Qt::Key_Bar: // | - case Qt::Key_BraceRight: // } - case Qt::Key_AsciiTilde: // ~ - return LOBYTE(::VkKeyScanExW(key, ::GetKeyboardLayout(0))); - - default: - Q_ASSERT(false); - return 0; - } -} - // // The extended-key flag indicates whether the keystroke message originated // from one of the additional keys on the enhanced keyboard // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646267%28v=vs.85%29.aspx#EXTENDED_KEY_FLAG // -BOOL AutoTypePlatformWin::isExtendedKey(DWORD nativeKeyCode) +bool AutoTypePlatformWin::isExtendedKey(DWORD nativeKeyCode) { switch (nativeKeyCode) { case VK_RMENU: @@ -417,44 +153,21 @@ BOOL AutoTypePlatformWin::isExtendedKey(DWORD nativeKeyCode) case VK_LWIN: case VK_RWIN: case VK_APPS: - return TRUE; + return true; default: - return FALSE; + return false; } } // clang-format on -// -// Translate qt key modifiers to windows modifiers -// -DWORD AutoTypePlatformWin::qtToNativeModifiers(Qt::KeyboardModifiers modifiers) -{ - DWORD nativeModifiers = 0; - - if (modifiers & Qt::ShiftModifier) { - nativeModifiers |= MOD_SHIFT; - } - if (modifiers & Qt::ControlModifier) { - nativeModifiers |= MOD_CONTROL; - } - if (modifiers & Qt::AltModifier) { - nativeModifiers |= MOD_ALT; - } - if (modifiers & Qt::MetaModifier) { - nativeModifiers |= MOD_WIN; - } - - return nativeModifiers; -} - // // Test if window is in Alt+Tab list // see: https://blogs.msdn.microsoft.com/oldnewthing/20071008-00/?p=24863 // -BOOL AutoTypePlatformWin::isAltTabWindow(HWND hwnd) +bool AutoTypePlatformWin::isAltTabWindow(HWND hwnd) { if (!::IsWindowVisible(hwnd)) { - return FALSE; + return false; } // Start at the root owner diff --git a/src/autotype/windows/AutoTypeWindows.h b/src/autotype/windows/AutoTypeWindows.h index f9dc249fc..2f624e924 100644 --- a/src/autotype/windows/AutoTypeWindows.h +++ b/src/autotype/windows/AutoTypeWindows.h @@ -20,7 +20,7 @@ #define KEEPASSX_AUTOTYPEWINDOWS_H #include -#include +#include #include "autotype/AutoTypeAction.h" #include "autotype/AutoTypePlatformPlugin.h" @@ -36,23 +36,15 @@ public: QStringList windowTitles() override; WId activeWindow() override; QString activeWindowTitle() override; - bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; - void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; - int platformEventFilter(void* event) override; bool raiseWindow(WId window) override; AutoTypeExecutor* createExecutor() override; void sendChar(const QChar& ch, bool isKeyDown); void sendKey(Qt::Key key, bool isKeyDown); -signals: - void globalShortcutTriggered(); - private: - static DWORD qtToNativeKeyCode(Qt::Key key); - static DWORD qtToNativeModifiers(Qt::KeyboardModifiers modifiers); - static BOOL isExtendedKey(DWORD nativeKeyCode); - static BOOL isAltTabWindow(HWND hwnd); + static bool isExtendedKey(DWORD nativeKeyCode); + static bool isAltTabWindow(HWND hwnd); static BOOL CALLBACK windowTitleEnumProc(_In_ HWND hwnd, _In_ LPARAM lParam); static QString windowTitle(HWND hwnd); }; diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index d2d757b4e..950a91178 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -18,15 +18,6 @@ */ #include "AutoTypeXCB.h" -#include "KeySymMap.h" -#include "core/Tools.h" - -#include -#include - -bool AutoTypePlatformX11::m_catchXErrors = false; -bool AutoTypePlatformX11::m_xErrorOccurred = false; -int (*AutoTypePlatformX11::m_oldXErrorHandler)(Display*, XErrorEvent*) = nullptr; AutoTypePlatformX11::AutoTypePlatformX11() { @@ -49,17 +40,14 @@ AutoTypePlatformX11::AutoTypePlatformX11() m_classBlacklist << "xfdesktop" << "xfce4-panel"; // Xfce 4 - m_currentGlobalKey = static_cast(0); - m_currentGlobalModifiers = nullptr; - m_keysymTable = nullptr; m_xkb = nullptr; m_remapKeycode = 0; m_currentRemapKeysym = NoSymbol; - m_modifierMask = ControlMask | ShiftMask | Mod1Mask | Mod4Mask; m_loaded = true; + connect(nixUtils(), &NixUtils::keymapChanged, this, [this] { updateKeymap(); }); updateKeymap(); } @@ -142,105 +130,6 @@ QString AutoTypePlatformX11::activeWindowTitle() return windowTitle(activeWindow(), true); } -bool AutoTypePlatformX11::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) -{ - int keycode = XKeysymToKeycode(m_dpy, charToKeySym(key)); - uint nativeModifiers = qtToNativeModifiers(modifiers); - - startCatchXErrors(); - XGrabKey(m_dpy, keycode, nativeModifiers, m_rootWindow, True, GrabModeAsync, GrabModeAsync); - XGrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask, m_rootWindow, True, GrabModeAsync, GrabModeAsync); - XGrabKey(m_dpy, keycode, nativeModifiers | LockMask, m_rootWindow, True, GrabModeAsync, GrabModeAsync); - XGrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask | LockMask, m_rootWindow, True, GrabModeAsync, GrabModeAsync); - stopCatchXErrors(); - - if (!m_xErrorOccurred) { - m_currentGlobalKey = key; - m_currentGlobalModifiers = modifiers; - m_currentGlobalKeycode = keycode; - m_currentGlobalNativeModifiers = nativeModifiers; - return true; - } else { - unregisterGlobalShortcut(key, modifiers); - return false; - } -} - -uint AutoTypePlatformX11::qtToNativeModifiers(Qt::KeyboardModifiers modifiers) -{ - uint nativeModifiers = 0; - - if (modifiers & Qt::ShiftModifier) { - nativeModifiers |= ShiftMask; - } - if (modifiers & Qt::ControlModifier) { - nativeModifiers |= ControlMask; - } - if (modifiers & Qt::AltModifier) { - nativeModifiers |= Mod1Mask; - } - if (modifiers & Qt::MetaModifier) { - nativeModifiers |= Mod4Mask; - } - - return nativeModifiers; -} - -void AutoTypePlatformX11::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) -{ - KeyCode keycode = XKeysymToKeycode(m_dpy, charToKeySym(key)); - uint nativeModifiers = qtToNativeModifiers(modifiers); - - XUngrabKey(m_dpy, keycode, nativeModifiers, m_rootWindow); - XUngrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask, m_rootWindow); - XUngrabKey(m_dpy, keycode, nativeModifiers | LockMask, m_rootWindow); - XUngrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask | LockMask, m_rootWindow); - - m_currentGlobalKey = static_cast(0); - m_currentGlobalModifiers = nullptr; - m_currentGlobalKeycode = 0; - m_currentGlobalNativeModifiers = 0; -} - -int AutoTypePlatformX11::platformEventFilter(void* event) -{ - xcb_generic_event_t* genericEvent = static_cast(event); - quint8 type = genericEvent->response_type & 0x7f; - - if (type == XCB_KEY_PRESS || type == XCB_KEY_RELEASE) { - xcb_key_press_event_t* keyPressEvent = static_cast(event); - if (keyPressEvent->detail == m_currentGlobalKeycode - && (keyPressEvent->state & m_modifierMask) == m_currentGlobalNativeModifiers - && (!QApplication::activeWindow() || QApplication::activeWindow()->isMinimized()) && m_loaded) { - if (type == XCB_KEY_PRESS) { - emit globalShortcutTriggered(); - } - - return 1; - } - } else if (type == XCB_MAPPING_NOTIFY) { - xcb_mapping_notify_event_t* mappingNotifyEvent = static_cast(event); - if (mappingNotifyEvent->request == XCB_MAPPING_KEYBOARD - || mappingNotifyEvent->request == XCB_MAPPING_MODIFIER) { - XMappingEvent xMappingEvent; - memset(&xMappingEvent, 0, sizeof(xMappingEvent)); - xMappingEvent.type = MappingNotify; - xMappingEvent.display = m_dpy; - if (mappingNotifyEvent->request == XCB_MAPPING_KEYBOARD) { - xMappingEvent.request = MappingKeyboard; - } else { - xMappingEvent.request = MappingModifier; - } - xMappingEvent.first_keycode = mappingNotifyEvent->first_keycode; - xMappingEvent.count = mappingNotifyEvent->count; - XRefreshKeyboardMapping(&xMappingEvent); - updateKeymap(); - } - } - - return -1; -} - AutoTypeExecutor* AutoTypePlatformX11::createExecutor() { return new AutoTypeExecutorX11(this); @@ -395,89 +284,6 @@ bool AutoTypePlatformX11::isTopLevelWindow(Window window) return result; } -KeySym AutoTypePlatformX11::charToKeySym(const QChar& ch) -{ - ushort unicode = ch.unicode(); - - /* first check for Latin-1 characters (1:1 mapping) */ - if ((unicode >= 0x0020 && unicode <= 0x007e) || (unicode >= 0x00a0 && unicode <= 0x00ff)) { - return unicode; - } - - /* mapping table generated from keysymdef.h */ - const uint* match = Tools::binaryFind(m_unicodeToKeysymKeys, m_unicodeToKeysymKeys + m_unicodeToKeysymLen, unicode); - int index = match - m_unicodeToKeysymKeys; - if (index != m_unicodeToKeysymLen) { - return m_unicodeToKeysymValues[index]; - } - - if (unicode >= 0x0100) { - return unicode | 0x01000000; - } - - return NoSymbol; -} - -KeySym AutoTypePlatformX11::keyToKeySym(Qt::Key key) -{ - switch (key) { - case Qt::Key_Tab: - return XK_Tab; - case Qt::Key_Enter: - return XK_Return; - case Qt::Key_Space: - return XK_space; - case Qt::Key_Up: - return XK_Up; - case Qt::Key_Down: - return XK_Down; - case Qt::Key_Left: - return XK_Left; - case Qt::Key_Right: - return XK_Right; - case Qt::Key_Insert: - return XK_Insert; - case Qt::Key_Delete: - return XK_Delete; - case Qt::Key_Home: - return XK_Home; - case Qt::Key_End: - return XK_End; - case Qt::Key_PageUp: - return XK_Page_Up; - case Qt::Key_PageDown: - return XK_Page_Down; - case Qt::Key_Backspace: - return XK_BackSpace; - case Qt::Key_Pause: - return XK_Break; - case Qt::Key_CapsLock: - return XK_Caps_Lock; - case Qt::Key_Escape: - return XK_Escape; - case Qt::Key_Help: - return XK_Help; - case Qt::Key_NumLock: - return XK_Num_Lock; - case Qt::Key_Print: - return XK_Print; - case Qt::Key_ScrollLock: - return XK_Scroll_Lock; - case Qt::Key_Shift: - return XK_Shift_L; - case Qt::Key_Control: - return XK_Control_L; - case Qt::Key_Alt: - return XK_Alt_L; - default: - if (key >= Qt::Key_F1 && key <= Qt::Key_F16) { - return XK_F1 + (key - Qt::Key_F1); - } else { - return NoSymbol; - } - } -} - /* * Update the keyboard and modifier mapping. * We need the KeyboardMapping for AddKeysym. @@ -491,8 +297,9 @@ void AutoTypePlatformX11::updateKeymap() m_xkb = getKeyboard(); XDisplayKeycodes(m_dpy, &m_minKeycode, &m_maxKeycode); - if (m_keysymTable != nullptr) + if (m_keysymTable != nullptr) { XFree(m_keysymTable); + } m_keysymTable = XGetKeyboardMapping(m_dpy, m_minKeycode, m_maxKeycode - m_minKeycode + 1, &m_keysymPerKeycode); /* determine the keycode to use for remapped keys */ @@ -523,11 +330,7 @@ void AutoTypePlatformX11::updateKeymap() /* Xlib needs some time until the mapping is distributed to all clients */ - // TODO: we should probably only sleep while in the middle of typing something - timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = 30 * 1000 * 1000; - nanosleep(&ts, nullptr); + Tools::sleep(30); } bool AutoTypePlatformX11::isRemapKeycodeValid() @@ -542,36 +345,6 @@ bool AutoTypePlatformX11::isRemapKeycodeValid() return false; } -void AutoTypePlatformX11::startCatchXErrors() -{ - Q_ASSERT(!m_catchXErrors); - - m_catchXErrors = true; - m_xErrorOccurred = false; - m_oldXErrorHandler = XSetErrorHandler(x11ErrorHandler); -} - -void AutoTypePlatformX11::stopCatchXErrors() -{ - Q_ASSERT(m_catchXErrors); - - XSync(m_dpy, False); - XSetErrorHandler(m_oldXErrorHandler); - m_catchXErrors = false; -} - -int AutoTypePlatformX11::x11ErrorHandler(Display* display, XErrorEvent* error) -{ - Q_UNUSED(display) - Q_UNUSED(error) - - if (m_catchXErrors) { - m_xErrorOccurred = true; - } - - return 1; -} - XkbDescPtr AutoTypePlatformX11::getKeyboard() { int num_devices; @@ -696,7 +469,7 @@ bool AutoTypePlatformX11::keysymModifiers(KeySym keysym, int keycode, unsigned i * window to simulate keyboard. If modifiers (shift, control, etc) * are set ON, many events will be sent. */ -void AutoTypePlatformX11::SendKey(KeySym keysym, unsigned int modifiers) +void AutoTypePlatformX11::sendKey(KeySym keysym, unsigned int modifiers) { if (keysym == NoSymbol) { qWarning("No such key: keysym=0x%lX", keysym); @@ -798,12 +571,12 @@ AutoTypeExecutorX11::AutoTypeExecutorX11(AutoTypePlatformX11* platform) void AutoTypeExecutorX11::execChar(AutoTypeChar* action) { - m_platform->SendKey(m_platform->charToKeySym(action->character)); + m_platform->sendKey(qcharToNativeKeyCode(action->character)); } void AutoTypeExecutorX11::execKey(AutoTypeKey* action) { - m_platform->SendKey(m_platform->keyToKeySym(action->key)); + m_platform->sendKey(qtToNativeKeyCode(action->key)); } void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr) @@ -814,13 +587,13 @@ void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr) ts.tv_sec = 0; ts.tv_nsec = 25 * 1000 * 1000; - m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Home), static_cast(ControlMask)); + m_platform->sendKey(qtToNativeKeyCode(Qt::Key_Home), static_cast(ControlMask)); nanosleep(&ts, nullptr); - m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_End), static_cast(ControlMask | ShiftMask)); + m_platform->sendKey(qtToNativeKeyCode(Qt::Key_End), static_cast(ControlMask | ShiftMask)); nanosleep(&ts, nullptr); - m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Backspace)); + m_platform->sendKey(qtToNativeKeyCode(Qt::Key_Backspace)); nanosleep(&ts, nullptr); } diff --git a/src/autotype/xcb/AutoTypeXCB.h b/src/autotype/xcb/AutoTypeXCB.h index 221d2ba7b..54bb675ec 100644 --- a/src/autotype/xcb/AutoTypeXCB.h +++ b/src/autotype/xcb/AutoTypeXCB.h @@ -26,12 +26,16 @@ #include #include +#include "autotype/AutoTypeAction.h" +#include "autotype/AutoTypePlatformPlugin.h" +#include "core/Tools.h" +#include "gui/osutils/OSUtils.h" +#include "gui/osutils/nixutils/X11Funcs.h" + #include #include #include - -#include "autotype/AutoTypeAction.h" -#include "autotype/AutoTypePlatformPlugin.h" +#include #define N_MOD_INDICES (Mod5MapIndex + 1) @@ -48,19 +52,10 @@ public: QStringList windowTitles() override; WId activeWindow() override; QString activeWindowTitle() override; - bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; - void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; - int platformEventFilter(void* event) override; bool raiseWindow(WId window) override; AutoTypeExecutor* createExecutor() override; - KeySym charToKeySym(const QChar& ch); - KeySym keyToKeySym(Qt::Key key); - - void SendKey(KeySym keysym, unsigned int modifiers = 0); - -signals: - void globalShortcutTriggered(); + void sendKey(KeySym keysym, unsigned int modifiers = 0); private: QString windowTitle(Window window, bool useBlacklist); @@ -68,10 +63,6 @@ private: QString windowClassName(Window window); QList widgetsToX11Windows(const QWidgetList& widgetList); bool isTopLevelWindow(Window window); - uint qtToNativeModifiers(Qt::KeyboardModifiers modifiers); - void startCatchXErrors(); - void stopCatchXErrors(); - static int x11ErrorHandler(Display* display, XErrorEvent* error); XkbDescPtr getKeyboard(); void updateKeymap(); @@ -94,18 +85,6 @@ private: Atom m_atomUtf8String; Atom m_atomNetActiveWindow; QSet m_classBlacklist; - Qt::Key m_currentGlobalKey; - Qt::KeyboardModifiers m_currentGlobalModifiers; - uint m_currentGlobalKeycode; - uint m_currentGlobalNativeModifiers; - int m_modifierMask; - static bool m_catchXErrors; - static bool m_xErrorOccurred; - static int (*m_oldXErrorHandler)(Display*, XErrorEvent*); - - static const int m_unicodeToKeysymLen; - static const uint m_unicodeToKeysymKeys[]; - static const uint m_unicodeToKeysymValues[]; XkbDescPtr m_xkb; KeySym* m_keysymTable; diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 060e86ee1..15460259b 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -36,10 +36,6 @@ #include #include -#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) -#include "gui/osutils/OSEventFilter.h" -#endif - #if defined(Q_OS_UNIX) #include #include @@ -60,9 +56,7 @@ Application::Application(int& argc, char** argv) , m_alreadyRunning(false) , m_lockFile(nullptr) #if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) - , m_osEventFilter(new OSEventFilter()) { - installNativeEventFilter(m_osEventFilter.data()); #else { #endif @@ -160,6 +154,7 @@ void Application::bootstrap() QApplication::setFont(QApplication::font("QMessageBox")); #endif + osUtils->registerNativeEventFilter(); MessageBox::initializeButtonDefs(); #ifdef Q_OS_MACOS diff --git a/src/gui/Application.h b/src/gui/Application.h index 62095658e..5f79f64b2 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -81,9 +81,6 @@ private: QLockFile* m_lockFile; QLocalServer m_lockServer; QString m_socketName; -#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) - QScopedPointer m_osEventFilter; -#endif }; #define kpxcApp qobject_cast(Application::instance()) diff --git a/src/gui/PasswordEdit.cpp b/src/gui/PasswordEdit.cpp index 1773a4365..e882ea435 100644 --- a/src/gui/PasswordEdit.cpp +++ b/src/gui/PasswordEdit.cpp @@ -60,7 +60,7 @@ PasswordEdit::PasswordEdit(QWidget* parent) m_toggleVisibleAction = new QAction( icons()->icon("password-show-off"), tr("Toggle Password (%1)").arg(QKeySequence(modifier + Qt::Key_H).toString(QKeySequence::NativeText)), - nullptr); + this); m_toggleVisibleAction->setCheckable(true); m_toggleVisibleAction->setShortcut(modifier + Qt::Key_H); m_toggleVisibleAction->setShortcutContext(Qt::WidgetShortcut); @@ -70,7 +70,7 @@ PasswordEdit::PasswordEdit(QWidget* parent) m_passwordGeneratorAction = new QAction( icons()->icon("password-generator"), tr("Generate Password (%1)").arg(QKeySequence(modifier + Qt::Key_G).toString(QKeySequence::NativeText)), - nullptr); + this); m_passwordGeneratorAction->setShortcut(modifier + Qt::Key_G); m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut); addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition); @@ -79,7 +79,7 @@ PasswordEdit::PasswordEdit(QWidget* parent) m_capslockAction = new QAction(icons()->icon("dialog-warning", true, StateColorPalette().color(StateColorPalette::Error)), tr("Warning: Caps Lock enabled!"), - nullptr); + this); addAction(m_capslockAction, QLineEdit::LeadingPosition); m_capslockAction->setVisible(false); } diff --git a/src/gui/osutils/OSEventFilter.cpp b/src/gui/osutils/OSEventFilter.cpp deleted file mode 100644 index f1f4d97a9..000000000 --- a/src/gui/osutils/OSEventFilter.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2013 Felix Geyer - * Copyright (C) 2018 KeePassXC Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "OSEventFilter.h" - -#include - -#include "autotype/AutoType.h" -#include "gui/MainWindow.h" -#ifdef Q_OS_WIN -#include -#endif - -OSEventFilter::OSEventFilter() -{ -} - -bool OSEventFilter::nativeEventFilter(const QByteArray& eventType, void* message, long* result) -{ - Q_UNUSED(result) - -#if defined(Q_OS_UNIX) - if (eventType == QByteArrayLiteral("xcb_generic_event_t")) { -#elif defined(Q_OS_WIN) - if (eventType == QByteArrayLiteral("windows_generic_MSG") - || eventType == QByteArrayLiteral("windows_dispatcher_MSG")) { -#endif - return autoType()->callEventFilter(message) == 1; - } - - return false; -} diff --git a/src/gui/osutils/OSEventFilter.h b/src/gui/osutils/OSEventFilter.h deleted file mode 100644 index 10434c0c2..000000000 --- a/src/gui/osutils/OSEventFilter.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2013 Felix Geyer - * Copyright (C) 2018 KeePassXC Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef OSEVENTFILTER_H -#define OSEVENTFILTER_H -#include - -class QByteArray; - -class OSEventFilter : public QAbstractNativeEventFilter -{ -public: - OSEventFilter(); - bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; - -private: - Q_DISABLE_COPY(OSEventFilter) -}; - -#endif // OSEVENTFILTER_H diff --git a/src/gui/osutils/OSUtilsBase.h b/src/gui/osutils/OSUtilsBase.h index 340e9bf7e..23f5b926e 100644 --- a/src/gui/osutils/OSUtilsBase.h +++ b/src/gui/osutils/OSUtilsBase.h @@ -50,6 +50,17 @@ public: */ virtual bool isCapslockEnabled() = 0; + virtual void registerNativeEventFilter() = 0; + + virtual bool registerGlobalShortcut(const QString& name, + Qt::Key key, + Qt::KeyboardModifiers modifiers, + QString* error = nullptr) = 0; + virtual bool unregisterGlobalShortcut(const QString& name) = 0; + +signals: + void globalShortcutTriggered(const QString& name); + protected: explicit OSUtilsBase(QObject* parent = nullptr); virtual ~OSUtilsBase(); diff --git a/src/gui/osutils/macutils/MacUtils.cpp b/src/gui/osutils/macutils/MacUtils.cpp index d32a15612..73ee29627 100644 --- a/src/gui/osutils/macutils/MacUtils.cpp +++ b/src/gui/osutils/macutils/MacUtils.cpp @@ -23,8 +23,10 @@ #include #include +#include #include +#define INVALID_KEYCODE 0xFFFF QPointer MacUtils::m_instance = nullptr; @@ -95,9 +97,10 @@ bool MacUtils::isDarkMode() const QString MacUtils::getLaunchAgentFilename() const { - auto launchAgentDir = QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/../LaunchAgents")); - return QFile(launchAgentDir.absoluteFilePath( - qApp->property("KPXC_QUALIFIED_APPNAME").toString().append(".plist"))).fileName(); + auto launchAgentDir = + QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/../LaunchAgents")); + return QFile(launchAgentDir.absoluteFilePath(qApp->property("KPXC_QUALIFIED_APPNAME").toString().append(".plist"))) + .fileName(); } bool MacUtils::isLaunchAtStartupEnabled() const @@ -134,3 +137,306 @@ void MacUtils::toggleForegroundApp(bool foreground) { m_appkit->toggleForegroundApp(foreground); } + +void MacUtils::registerNativeEventFilter() +{ + EventTypeSpec eventSpec; + eventSpec.eventClass = kEventClassKeyboard; + eventSpec.eventKind = kEventHotKeyPressed; + ::InstallApplicationEventHandler(MacUtils::hotkeyHandler, 1, &eventSpec, this, nullptr); +} + +bool MacUtils::registerGlobalShortcut(const QString& name, Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error) +{ + auto keycode = qtToNativeKeyCode(key); + auto modifierscode = qtToNativeModifiers(modifiers, false); + if (keycode == INVALID_KEYCODE) { + if (error) { + *error = tr("Invalid key code"); + } + return false; + } + + // Check if this key combo is registered to another shortcut + QHashIterator> i(m_globalShortcuts); + while (i.hasNext()) { + i.next(); + if (i.value()->nativeKeyCode == keycode && i.value()->nativeModifiers == modifierscode && i.key() != name) { + if (error) { + *error = tr("Global shortcut already registered to %1").arg(i.key()); + } + return false; + } + } + + // Remove existing registration for this name + unregisterGlobalShortcut(name); + + auto gs = QSharedPointer::create(); + gs->hotkeyId.signature = 'kpxc'; + gs->hotkeyId.id = m_nextShortcutId; + gs->nativeKeyCode = keycode; + gs->nativeModifiers = modifierscode; + if (::RegisterEventHotKey( + gs->nativeKeyCode, gs->nativeModifiers, gs->hotkeyId, GetApplicationEventTarget(), 0, &gs->hotkeyRef) + != noErr) { + if (error) { + *error = tr("Could not register global shortcut"); + } + return false; + } + + m_globalShortcuts.insert(name, gs); + ++m_nextShortcutId; + + return true; +} + +bool MacUtils::unregisterGlobalShortcut(const QString& name) +{ + if (m_globalShortcuts.contains(name)) { + auto gs = m_globalShortcuts.value(name); + ::UnregisterEventHotKey(gs->hotkeyRef); + m_globalShortcuts.remove(name); + return true; + } + return false; +} + +OSStatus MacUtils::hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData) +{ + Q_UNUSED(nextHandler); + + auto self = static_cast(userData); + EventHotKeyID hotkeyId; + if (::GetEventParameter( + theEvent, kEventParamDirectObject, typeEventHotKeyID, nullptr, sizeof(hotkeyId), nullptr, &hotkeyId) + == noErr) { + QHashIterator> i(self->m_globalShortcuts); + while (i.hasNext()) { + i.next(); + if (i.value()->hotkeyId.id == hotkeyId.id) { + emit self->globalShortcutTriggered(i.key()); + return noErr; + } + } + } + + return eventNotHandledErr; +} + +// +// Translate qt key code to mac os key code +// see: HIToolbox/Events.h +// +uint16 MacUtils::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; + } +} + +// +// Translate qt key modifiers to mac os modifiers +// see: https://doc.qt.io/qt-5/osx-issues.html#special-keys +// +CGEventFlags MacUtils::qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native) +{ + 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; + } + + if (modifiers & Qt::ShiftModifier) { + nativeModifiers = CGEventFlags(nativeModifiers | shiftMod); + } + if (modifiers & Qt::ControlModifier) { + nativeModifiers = CGEventFlags(nativeModifiers | cmdMod); + } + if (modifiers & Qt::AltModifier) { + nativeModifiers = CGEventFlags(nativeModifiers | optionMod); + } + if (modifiers & Qt::MetaModifier) { + nativeModifiers = CGEventFlags(nativeModifiers | controlMod); + } + + return nativeModifiers; +} diff --git a/src/gui/osutils/macutils/MacUtils.h b/src/gui/osutils/macutils/MacUtils.h index 281c5438c..0a3e0f6a4 100644 --- a/src/gui/osutils/macutils/MacUtils.h +++ b/src/gui/osutils/macutils/MacUtils.h @@ -19,8 +19,9 @@ #ifndef KEEPASSXC_MACUTILS_H #define KEEPASSXC_MACUTILS_H -#include "gui/osutils/OSUtilsBase.h" #include "AppKit.h" +#include "gui/osutils/OSUtilsBase.h" +#include #include #include @@ -48,6 +49,17 @@ public: bool enableScreenRecording(); void toggleForegroundApp(bool foreground); + void registerNativeEventFilter() override; + + bool registerGlobalShortcut(const QString& name, + Qt::Key key, + Qt::KeyboardModifiers modifiers, + QString* error = nullptr) override; + bool unregisterGlobalShortcut(const QString& name) override; + + uint16 qtToNativeKeyCode(Qt::Key key); + CGEventFlags qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native); + signals: void lockDatabases(); @@ -57,10 +69,22 @@ protected: private: QString getLaunchAgentFilename() const; + static OSStatus hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData); QScopedPointer m_appkit; static QPointer m_instance; + struct globalShortcut + { + EventHotKeyRef hotkeyRef; + EventHotKeyID hotkeyId; + uint16 nativeKeyCode; + CGEventFlags nativeModifiers; + }; + + int m_nextShortcutId = 1; + QHash> m_globalShortcuts; + Q_DISABLE_COPY(MacUtils) }; diff --git a/src/autotype/xcb/KeySymMap.h b/src/gui/osutils/nixutils/KeySymMap.h similarity index 98% rename from src/autotype/xcb/KeySymMap.h rename to src/gui/osutils/nixutils/KeySymMap.h index 2e73da530..efce4df8a 100644 --- a/src/autotype/xcb/KeySymMap.h +++ b/src/gui/osutils/nixutils/KeySymMap.h @@ -2,10 +2,10 @@ * Automatically generated by keysymmap.py from parsing keysymdef.h. */ -const int AutoTypePlatformX11::m_unicodeToKeysymLen = 632; +const int unicodeToKeysymLen = 632; // clang-format off -const uint AutoTypePlatformX11::m_unicodeToKeysymKeys[] = { +const uint unicodeToKeysymKeys[] = { 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a, 0x010b, 0x010c, 0x010d, 0x010e, 0x010f, 0x0110, 0x0111, 0x0112, 0x0113, 0x0116, 0x0117, 0x0118, 0x0119, @@ -87,7 +87,7 @@ const uint AutoTypePlatformX11::m_unicodeToKeysymKeys[] = { 0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f2, 0x30f3, 0x30fb, 0x30fc }; -const uint AutoTypePlatformX11::m_unicodeToKeysymValues[] = { +const uint unicodeToKeysymValues[] = { 0x03c0, 0x03e0, 0x01c3, 0x01e3, 0x01a1, 0x01b1, 0x01c6, 0x01e6, 0x02c6, 0x02e6, 0x02c5, 0x02e5, 0x01c8, 0x01e8, 0x01cf, 0x01ef, 0x01d0, 0x01f0, 0x03aa, 0x03ba, 0x03cc, 0x03ec, 0x01ca, 0x01ea, diff --git a/src/gui/osutils/nixutils/NixUtils.cpp b/src/gui/osutils/nixutils/NixUtils.cpp index b252458e5..58a69dedc 100644 --- a/src/gui/osutils/nixutils/NixUtils.cpp +++ b/src/gui/osutils/nixutils/NixUtils.cpp @@ -16,6 +16,8 @@ */ #include "NixUtils.h" +#include "KeySymMap.h" +#include "core/Tools.h" #include #include @@ -26,13 +28,26 @@ #include #include #include +#include #include -// namespace required to avoid name clashes with declarations in XKBlib.h -namespace X11 -{ + +#include "X11Funcs.h" #include -} +#include + +namespace +{ + Display* dpy; + Window rootWindow; + bool x11ErrorOccurred = false; + + int x11ErrorHandler(Display*, XErrorEvent*) + { + x11ErrorOccurred = true; + return 1; + } +} // namespace QPointer NixUtils::m_instance = nullptr; @@ -48,6 +63,8 @@ NixUtils* NixUtils::instance() NixUtils::NixUtils(QObject* parent) : OSUtilsBase(parent) { + dpy = QX11Info::display(); + rootWindow = QX11Info::appRootWindow(); } NixUtils::~NixUtils() @@ -125,7 +142,7 @@ bool NixUtils::isCapslockEnabled() QString platform = QGuiApplication::platformName(); if (platform == "xcb") { unsigned state = 0; - if (X11::XkbGetIndicatorState(reinterpret_cast(display), XkbUseCoreKbd, &state) == Success) { + if (XkbGetIndicatorState(reinterpret_cast(display), XkbUseCoreKbd, &state) == Success) { return ((state & 1u) != 0); } } @@ -134,3 +151,119 @@ bool NixUtils::isCapslockEnabled() return false; } + +void NixUtils::registerNativeEventFilter() +{ + qApp->installNativeEventFilter(this); +} + +bool NixUtils::nativeEventFilter(const QByteArray& eventType, void* message, long*) +{ + if (eventType != QByteArrayLiteral("xcb_generic_event_t")) { + return false; + } + + auto* genericEvent = static_cast(message); + quint8 type = genericEvent->response_type & 0x7f; + + if (type == XCB_KEY_PRESS) { + auto* keyPressEvent = static_cast(message); + auto modifierMask = ControlMask | ShiftMask | Mod1Mask | Mod4Mask; + return triggerGlobalShortcut(keyPressEvent->detail, keyPressEvent->state & modifierMask); + } else if (type == XCB_MAPPING_NOTIFY) { + auto* mappingNotifyEvent = static_cast(message); + if (mappingNotifyEvent->request == XCB_MAPPING_KEYBOARD + || mappingNotifyEvent->request == XCB_MAPPING_MODIFIER) { + XMappingEvent xMappingEvent; + memset(&xMappingEvent, 0, sizeof(xMappingEvent)); + xMappingEvent.type = MappingNotify; + xMappingEvent.display = dpy; + if (mappingNotifyEvent->request == XCB_MAPPING_KEYBOARD) { + xMappingEvent.request = MappingKeyboard; + } else { + xMappingEvent.request = MappingModifier; + } + xMappingEvent.first_keycode = mappingNotifyEvent->first_keycode; + xMappingEvent.count = mappingNotifyEvent->count; + XRefreshKeyboardMapping(&xMappingEvent); + // Notify listeners that the keymap has changed + emit keymapChanged(); + } + } + + return false; +} + +bool NixUtils::triggerGlobalShortcut(uint keycode, uint modifiers) +{ + QHashIterator> i(m_globalShortcuts); + while (i.hasNext()) { + i.next(); + if (i.value()->nativeKeyCode == keycode && i.value()->nativeModifiers == modifiers) { + emit globalShortcutTriggered(i.key()); + return true; + } + } + return false; +} + +bool NixUtils::registerGlobalShortcut(const QString& name, Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error) +{ + auto keycode = XKeysymToKeycode(dpy, qcharToNativeKeyCode(key)); + auto modifierscode = qtToNativeModifiers(modifiers); + + // Check if this key combo is registered to another shortcut + QHashIterator> i(m_globalShortcuts); + while (i.hasNext()) { + i.next(); + if (i.value()->nativeKeyCode == keycode && i.value()->nativeModifiers == modifierscode && i.key() != name) { + if (error) { + *error = tr("Global shortcut already registered to %1").arg(i.key()); + } + return false; + } + } + + unregisterGlobalShortcut(name); + + x11ErrorOccurred = false; + auto prevHandler = XSetErrorHandler(x11ErrorHandler); + + XGrabKey(dpy, keycode, modifierscode, rootWindow, True, GrabModeAsync, GrabModeAsync); + XGrabKey(dpy, keycode, modifierscode | Mod2Mask, rootWindow, True, GrabModeAsync, GrabModeAsync); + XGrabKey(dpy, keycode, modifierscode | LockMask, rootWindow, True, GrabModeAsync, GrabModeAsync); + XGrabKey(dpy, keycode, modifierscode | Mod2Mask | LockMask, rootWindow, True, GrabModeAsync, GrabModeAsync); + + XSync(dpy, False); + XSetErrorHandler(prevHandler); + + if (x11ErrorOccurred) { + x11ErrorOccurred = false; + if (error) { + *error = tr("Could not register global shortcut"); + } + return false; + } + + auto gs = QSharedPointer::create(); + gs->nativeKeyCode = keycode; + gs->nativeModifiers = modifierscode; + m_globalShortcuts.insert(name, gs); + return true; +} + +bool NixUtils::unregisterGlobalShortcut(const QString& name) +{ + if (!m_globalShortcuts.contains(name)) { + return false; + } + + auto gs = m_globalShortcuts.value(name); + XUngrabKey(dpy, gs->nativeKeyCode, gs->nativeModifiers, rootWindow); + XUngrabKey(dpy, gs->nativeKeyCode, gs->nativeModifiers | Mod2Mask, rootWindow); + XUngrabKey(dpy, gs->nativeKeyCode, gs->nativeModifiers | LockMask, rootWindow); + XUngrabKey(dpy, gs->nativeKeyCode, gs->nativeModifiers | Mod2Mask | LockMask, rootWindow); + + m_globalShortcuts.remove(name); + return true; +} diff --git a/src/gui/osutils/nixutils/NixUtils.h b/src/gui/osutils/nixutils/NixUtils.h index c91580796..4e0e5ca4a 100644 --- a/src/gui/osutils/nixutils/NixUtils.h +++ b/src/gui/osutils/nixutils/NixUtils.h @@ -19,9 +19,10 @@ #define KEEPASSXC_NIXUTILS_H #include "gui/osutils/OSUtilsBase.h" +#include #include -class NixUtils : public OSUtilsBase +class NixUtils : public OSUtilsBase, QAbstractNativeEventFilter { Q_OBJECT @@ -33,15 +34,35 @@ public: void setLaunchAtStartup(bool enable) override; bool isCapslockEnabled() override; + void registerNativeEventFilter() override; + + bool registerGlobalShortcut(const QString& name, + Qt::Key key, + Qt::KeyboardModifiers modifiers, + QString* error = nullptr) override; + bool unregisterGlobalShortcut(const QString& name) override; + +signals: + void keymapChanged(); + private: explicit NixUtils(QObject* parent = nullptr); ~NixUtils() override; -private: + bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override; QString getAutostartDesktopFilename(bool createDirs = false) const; + bool triggerGlobalShortcut(uint keycode, uint modifiers); + static QPointer m_instance; + struct globalShortcut + { + uint nativeKeyCode; + uint nativeModifiers; + }; + QHash> m_globalShortcuts; + Q_DISABLE_COPY(NixUtils) }; diff --git a/src/gui/osutils/nixutils/X11Funcs.cpp b/src/gui/osutils/nixutils/X11Funcs.cpp new file mode 100644 index 000000000..a942b42ee --- /dev/null +++ b/src/gui/osutils/nixutils/X11Funcs.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2020 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "X11Funcs.h" + +#include "KeySymMap.h" +#include "core/Tools.h" + +#include + +KeySym qcharToNativeKeyCode(const QChar& ch) +{ + ushort unicode = ch.unicode(); + + /* first check for Latin-1 characters (1:1 mapping) */ + if ((unicode >= 0x0020 && unicode <= 0x007e) || (unicode >= 0x00a0 && unicode <= 0x00ff)) { + return unicode; + } + + /* mapping table generated from keysymdef.h */ + const uint* match = Tools::binaryFind(unicodeToKeysymKeys, unicodeToKeysymKeys + unicodeToKeysymLen, unicode); + int index = match - unicodeToKeysymKeys; + if (index != unicodeToKeysymLen) { + return unicodeToKeysymValues[index]; + } + + if (unicode >= 0x0100) { + return unicode | 0x01000000; + } + + return NoSymbol; +} + +KeySym qtToNativeKeyCode(Qt::Key key) +{ + switch (key) { + case Qt::Key_Tab: + return XK_Tab; + case Qt::Key_Enter: + return XK_Return; + case Qt::Key_Space: + return XK_space; + case Qt::Key_Up: + return XK_Up; + case Qt::Key_Down: + return XK_Down; + case Qt::Key_Left: + return XK_Left; + case Qt::Key_Right: + return XK_Right; + case Qt::Key_Insert: + return XK_Insert; + case Qt::Key_Delete: + return XK_Delete; + case Qt::Key_Home: + return XK_Home; + case Qt::Key_End: + return XK_End; + case Qt::Key_PageUp: + return XK_Page_Up; + case Qt::Key_PageDown: + return XK_Page_Down; + case Qt::Key_Backspace: + return XK_BackSpace; + case Qt::Key_Pause: + return XK_Break; + case Qt::Key_CapsLock: + return XK_Caps_Lock; + case Qt::Key_Escape: + return XK_Escape; + case Qt::Key_Help: + return XK_Help; + case Qt::Key_NumLock: + return XK_Num_Lock; + case Qt::Key_Print: + return XK_Print; + case Qt::Key_ScrollLock: + return XK_Scroll_Lock; + case Qt::Key_Shift: + return XK_Shift_L; + case Qt::Key_Control: + return XK_Control_L; + case Qt::Key_Alt: + return XK_Alt_L; + default: + if (key >= Qt::Key_F1 && key <= Qt::Key_F16) { + return XK_F1 + (key - Qt::Key_F1); + } else { + return NoSymbol; + } + } +} + +uint qtToNativeModifiers(Qt::KeyboardModifiers modifiers) +{ + uint nativeModifiers = 0; + + if (modifiers & Qt::ShiftModifier) { + nativeModifiers |= ShiftMask; + } + if (modifiers & Qt::ControlModifier) { + nativeModifiers |= ControlMask; + } + if (modifiers & Qt::AltModifier) { + nativeModifiers |= Mod1Mask; + } + if (modifiers & Qt::MetaModifier) { + nativeModifiers |= Mod4Mask; + } + + return nativeModifiers; +} diff --git a/src/gui/osutils/nixutils/X11Funcs.h b/src/gui/osutils/nixutils/X11Funcs.h new file mode 100644 index 000000000..4fef4055c --- /dev/null +++ b/src/gui/osutils/nixutils/X11Funcs.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_X11FUNCS_H +#define KEEPASSXC_X11FUNCS_H + +#include +#include + +#include + +KeySym qcharToNativeKeyCode(const QChar& ch); +KeySym qtToNativeKeyCode(Qt::Key key); +uint qtToNativeModifiers(Qt::KeyboardModifiers modifiers); + +#endif diff --git a/src/autotype/xcb/keysymmap.py b/src/gui/osutils/nixutils/keysymmap.py old mode 100755 new mode 100644 similarity index 91% rename from src/autotype/xcb/keysymmap.py rename to src/gui/osutils/nixutils/keysymmap.py index ed45ee7fe..dd074e792 --- a/src/autotype/xcb/keysymmap.py +++ b/src/gui/osutils/nixutils/keysymmap.py @@ -62,11 +62,11 @@ print("""/* */ """) -print("const int AutoTypePlatformX11::m_unicodeToKeysymLen = {0};".format(len(keysymMap))) +print("const int unicodeToKeysymLen = {0};".format(len(keysymMap))) print() -print("const uint AutoTypePlatformX11::m_unicodeToKeysymKeys[] = {") +print("const uint unicodeToKeysymKeys[] = {") keys = keysymMap.keys() keyLen = len(keys) for idx, val in enumerate(keys, start=1): @@ -84,7 +84,7 @@ print("};") print() -print("const uint AutoTypePlatformX11::m_unicodeToKeysymValues[] = {") +print("const uint unicodeToKeysymValues[] = {") values = keysymMap.values() valuesLen = len(values) for idx, val in enumerate(values, start=1): diff --git a/src/gui/osutils/winutils/WinUtils.cpp b/src/gui/osutils/winutils/WinUtils.cpp index 67b272922..26c8b955b 100644 --- a/src/gui/osutils/winutils/WinUtils.cpp +++ b/src/gui/osutils/winutils/WinUtils.cpp @@ -24,7 +24,6 @@ #include QPointer WinUtils::m_instance = nullptr; -QScopedPointer WinUtils::m_eventFilter; WinUtils* WinUtils::instance() { @@ -48,39 +47,34 @@ WinUtils::WinUtils(QObject* parent) { } -WinUtils::~WinUtils() -{ -} - /** - * Register event filters to handle native platform events such as theme changes. + * Register event filters to handle native platform events such as global hotkeys */ -void WinUtils::registerEventFilters() +void WinUtils::registerNativeEventFilter() { - if (!m_eventFilter) { - m_eventFilter.reset(new DWMEventFilter); - qApp->installNativeEventFilter(m_eventFilter.data()); - } + qApp->installNativeEventFilter(this); } -bool WinUtils::DWMEventFilter::nativeEventFilter(const QByteArray& eventType, void* message, long*) +bool WinUtils::nativeEventFilter(const QByteArray& eventType, void* message, long*) { if (eventType != "windows_generic_MSG") { return false; } auto* msg = static_cast(message); - if (!msg->hwnd) { - return false; - } switch (msg->message) { + /* TODO: indicate dark mode support for black title bar case WM_CREATE: case WM_INITDIALOG: { - if (winUtils()->isDarkMode()) { - // TODO: indicate dark mode support for black title bar + if (msg->hwnd && winUtils()->isDarkMode()) { + } break; } + */ + case WM_HOTKEY: + triggerGlobalShortcut(msg->wParam); + break; } return false; @@ -119,3 +113,319 @@ bool WinUtils::isHighContrastMode() const QSettings settings(R"(HKEY_CURRENT_USER\Control Panel\Accessibility\HighContrast)", QSettings::NativeFormat); return (settings.value("Flags").toInt() & 1u) != 0; } + +bool WinUtils::registerGlobalShortcut(const QString& name, Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error) +{ + auto keycode = qtToNativeKeyCode(key); + auto modifierscode = qtToNativeModifiers(modifiers); + if (keycode < 1 || keycode > 254) { + if (error) { + *error = tr("Invalid key code"); + } + return false; + } + + // Check if this key combo is registered to another shortcut + QHashIterator> i(m_globalShortcuts); + while (i.hasNext()) { + i.next(); + if (i.value()->nativeKeyCode == keycode && i.value()->nativeModifiers == modifierscode && i.key() != name) { + if (error) { + *error = tr("Global shortcut already registered to %1").arg(i.key()); + } + return false; + } + } + + unregisterGlobalShortcut(name); + + auto gs = QSharedPointer::create(); + gs->id = m_nextShortcutId; + gs->nativeKeyCode = keycode; + gs->nativeModifiers = modifierscode; + if (!::RegisterHotKey(nullptr, gs->id, gs->nativeModifiers | MOD_NOREPEAT, gs->nativeKeyCode)) { + if (error) { + *error = tr("Could not register global shortcut"); + } + return false; + } + + m_globalShortcuts.insert(name, gs); + + if (++m_nextShortcutId > 0xBFFF) { + // Roll over if greater than the max id per + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey#remarks + m_nextShortcutId = 1; + } + return true; +} + +bool WinUtils::unregisterGlobalShortcut(const QString& name) +{ + if (m_globalShortcuts.contains(name)) { + auto gs = m_globalShortcuts.value(name); + if (::UnregisterHotKey(nullptr, gs->id)) { + m_globalShortcuts.remove(name); + return true; + } + } + return false; +} + +void WinUtils::triggerGlobalShortcut(int id) +{ + QHashIterator> i(m_globalShortcuts); + while (i.hasNext()) { + i.next(); + if (i.value()->id == id) { + emit globalShortcutTriggered(i.key()); + break; + } + } +} + +// clang-format off +// +// Translate qt key code to windows virtual key code +// see: https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx +// +DWORD WinUtils::qtToNativeKeyCode(Qt::Key key) +{ + switch (key) { + case Qt::Key_Backspace: + return VK_BACK; // 0x08 + case Qt::Key_Tab: + case Qt::Key_Backtab: + return VK_TAB; // 0x09 + case Qt::Key_Clear: + return VK_CLEAR; // 0x0C + case Qt::Key_Enter: + case Qt::Key_Return: + return VK_RETURN; // 0x0D + case Qt::Key_Shift: + return VK_SHIFT; // 0x10 + case Qt::Key_Control: + return VK_CONTROL; // 0x11 + case Qt::Key_Pause: + return VK_PAUSE; // 0x13 + case Qt::Key_CapsLock: + return VK_CAPITAL; // 0x14 + case Qt::Key_Escape: + return VK_ESCAPE; // 0x1B + case Qt::Key_Space: + return VK_SPACE; // 0x20 + case Qt::Key_PageUp: + return VK_PRIOR; // 0x21 + case Qt::Key_PageDown: + return VK_NEXT; // 0x22 + case Qt::Key_End: + return VK_END; // 0x23 + case Qt::Key_Home: + return VK_HOME; // 0x24 + case Qt::Key_Left: + return VK_LEFT; // 0x25 + case Qt::Key_Up: + return VK_UP; // 0x26 + case Qt::Key_Right: + return VK_RIGHT; // 0x27 + case Qt::Key_Down: + return VK_DOWN; // 0x28 + case Qt::Key_Print: + return VK_SNAPSHOT; // 0x2C + case Qt::Key_Insert: + return VK_INSERT; // 0x2D + case Qt::Key_Delete: + return VK_DELETE; // 0x2E + case Qt::Key_Help: + return VK_HELP; // 0x2F + + case Qt::Key_0: + return 0x30; // 0x30 + case Qt::Key_1: + return 0x31; // 0x31 + case Qt::Key_2: + return 0x32; // 0x32 + case Qt::Key_3: + return 0x33; // 0x33 + case Qt::Key_4: + return 0x34; // 0x34 + case Qt::Key_5: + return 0x35; // 0x35 + case Qt::Key_6: + return 0x36; // 0x36 + case Qt::Key_7: + return 0x37; // 0x37 + case Qt::Key_8: + return 0x38; // 0x38 + case Qt::Key_9: + return 0x39; // 0x39 + + case Qt::Key_A: + return 0x41; // 0x41 + case Qt::Key_B: + return 0x42; // 0x42 + case Qt::Key_C: + return 0x43; // 0x43 + case Qt::Key_D: + return 0x44; // 0x44 + case Qt::Key_E: + return 0x45; // 0x45 + case Qt::Key_F: + return 0x46; // 0x46 + case Qt::Key_G: + return 0x47; // 0x47 + case Qt::Key_H: + return 0x48; // 0x48 + case Qt::Key_I: + return 0x49; // 0x49 + case Qt::Key_J: + return 0x4A; // 0x4A + case Qt::Key_K: + return 0x4B; // 0x4B + case Qt::Key_L: + return 0x4C; // 0x4C + case Qt::Key_M: + return 0x4D; // 0x4D + case Qt::Key_N: + return 0x4E; // 0x4E + case Qt::Key_O: + return 0x4F; // 0x4F + case Qt::Key_P: + return 0x50; // 0x50 + case Qt::Key_Q: + return 0x51; // 0x51 + case Qt::Key_R: + return 0x52; // 0x52 + case Qt::Key_S: + return 0x53; // 0x53 + case Qt::Key_T: + return 0x54; // 0x54 + case Qt::Key_U: + return 0x55; // 0x55 + case Qt::Key_V: + return 0x56; // 0x56 + case Qt::Key_W: + return 0x57; // 0x57 + case Qt::Key_X: + return 0x58; // 0x58 + case Qt::Key_Y: + return 0x59; // 0x59 + case Qt::Key_Z: + return 0x5A; // 0x5A + + case Qt::Key_F1: + return VK_F1; // 0x70 + case Qt::Key_F2: + return VK_F2; // 0x71 + case Qt::Key_F3: + return VK_F3; // 0x72 + case Qt::Key_F4: + return VK_F4; // 0x73 + case Qt::Key_F5: + return VK_F5; // 0x74 + case Qt::Key_F6: + return VK_F6; // 0x75 + case Qt::Key_F7: + return VK_F7; // 0x76 + case Qt::Key_F8: + return VK_F8; // 0x77 + case Qt::Key_F9: + return VK_F9; // 0x78 + case Qt::Key_F10: + return VK_F10; // 0x79 + case Qt::Key_F11: + return VK_F11; // 0x7A + case Qt::Key_F12: + return VK_F12; // 0x7B + case Qt::Key_F13: + return VK_F13; // 0x7C + case Qt::Key_F14: + return VK_F14; // 0x7D + case Qt::Key_F15: + return VK_F15; // 0x7E + case Qt::Key_F16: + return VK_F16; // 0x7F + case Qt::Key_F17: + return VK_F17; // 0x80 + case Qt::Key_F18: + return VK_F18; // 0x81 + case Qt::Key_F19: + return VK_F19; // 0x82 + case Qt::Key_F20: + return VK_F20; // 0x83 + case Qt::Key_F21: + return VK_F21; // 0x84 + case Qt::Key_F22: + return VK_F22; // 0x85 + case Qt::Key_F23: + return VK_F23; // 0x86 + case Qt::Key_F24: + return VK_F24; // 0x87 + + case Qt::Key_NumLock: + return VK_NUMLOCK; // 0x90 + case Qt::Key_ScrollLock: + return VK_SCROLL; // 0x91 + + case Qt::Key_Exclam: // ! + case Qt::Key_QuoteDbl: // " + case Qt::Key_NumberSign: // # + case Qt::Key_Dollar: // $ + case Qt::Key_Percent: // % + case Qt::Key_Ampersand: // & + case Qt::Key_Apostrophe: // ' + case Qt::Key_ParenLeft: // ( + case Qt::Key_ParenRight: // ) + case Qt::Key_Asterisk: // * + case Qt::Key_Plus: // + + case Qt::Key_Comma: // , + case Qt::Key_Minus: // - + case Qt::Key_Period: // . + case Qt::Key_Slash: // / + case Qt::Key_Colon: // : + case Qt::Key_Semicolon: // ; + case Qt::Key_Less: // < + case Qt::Key_Equal: // = + case Qt::Key_Greater: // > + case Qt::Key_Question: // ? + case Qt::Key_BracketLeft: // [ + case Qt::Key_Backslash: // '\' + case Qt::Key_BracketRight: // ] + case Qt::Key_AsciiCircum: // ^ + case Qt::Key_Underscore: // _ + case Qt::Key_QuoteLeft: // ` + case Qt::Key_BraceLeft: // { + case Qt::Key_Bar: // | + case Qt::Key_BraceRight: // } + case Qt::Key_AsciiTilde: // ~ + return LOBYTE(::VkKeyScanExW(key, ::GetKeyboardLayout(0))); + + default: + Q_ASSERT(false); + return 0; + } +} +// clang-format on + +// +// Translate qt key modifiers to windows modifiers +// +DWORD WinUtils::qtToNativeModifiers(Qt::KeyboardModifiers modifiers) +{ + DWORD nativeModifiers = 0; + + if (modifiers & Qt::ShiftModifier) { + nativeModifiers |= MOD_SHIFT; + } + if (modifiers & Qt::ControlModifier) { + nativeModifiers |= MOD_CONTROL; + } + if (modifiers & Qt::AltModifier) { + nativeModifiers |= MOD_ALT; + } + if (modifiers & Qt::MetaModifier) { + nativeModifiers |= MOD_WIN; + } + + return nativeModifiers; +} diff --git a/src/gui/osutils/winutils/WinUtils.h b/src/gui/osutils/winutils/WinUtils.h index c19040274..d5e79f806 100644 --- a/src/gui/osutils/winutils/WinUtils.h +++ b/src/gui/osutils/winutils/WinUtils.h @@ -23,14 +23,16 @@ #include #include #include +#include -class WinUtils : public OSUtilsBase +#include + +class WinUtils : public OSUtilsBase, QAbstractNativeEventFilter { Q_OBJECT public: static WinUtils* instance(); - static void registerEventFilters(); bool isDarkMode() const override; bool isLaunchAtStartupEnabled() const override; @@ -38,19 +40,36 @@ public: bool isCapslockEnabled() override; bool isHighContrastMode() const; + void registerNativeEventFilter() override; + + bool registerGlobalShortcut(const QString& name, + Qt::Key key, + Qt::KeyboardModifiers modifiers, + QString* error = nullptr) override; + bool unregisterGlobalShortcut(const QString& name) override; + + DWORD qtToNativeKeyCode(Qt::Key key); + DWORD qtToNativeModifiers(Qt::KeyboardModifiers modifiers); + protected: explicit WinUtils(QObject* parent = nullptr); - ~WinUtils() override; + ~WinUtils() override = default; + + bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override; + void triggerGlobalShortcut(int id); private: - class DWMEventFilter : public QAbstractNativeEventFilter + static QPointer m_instance; + + struct globalShortcut { - public: - bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override; + int id; + DWORD nativeKeyCode; + DWORD nativeModifiers; }; - static QPointer m_instance; - static QScopedPointer m_eventFilter; + int m_nextShortcutId = 1; + QHash> m_globalShortcuts; Q_DISABLE_COPY(WinUtils) }; diff --git a/src/gui/styles/dark/DarkStyle.cpp b/src/gui/styles/dark/DarkStyle.cpp index 25f75e5ab..9fa03e332 100644 --- a/src/gui/styles/dark/DarkStyle.cpp +++ b/src/gui/styles/dark/DarkStyle.cpp @@ -119,8 +119,6 @@ void DarkStyle::polish(QWidget* widget) palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0x252525)); } #elif defined(Q_OS_WIN) - // Register event filter for better dark mode support - WinUtils::registerEventFilters(); palette.setColor(QPalette::All, QPalette::Window, QRgb(0x2F2F30)); #else palette.setColor(QPalette::Active, QPalette::Window, QRgb(0x2F2F30)); diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 5898f0477..5403670bd 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -28,6 +28,7 @@ #include "core/Resources.h" #include "crypto/Crypto.h" #include "gui/MessageBox.h" +#include "gui/osutils/OSUtils.h" QTEST_GUILESS_MAIN(TestAutoType) @@ -157,7 +158,7 @@ void TestAutoType::testGlobalAutoTypeWithNoMatch() void TestAutoType::testGlobalAutoTypeWithOneMatch() { m_test->setActiveWindowTitle("custom window"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("%1association%2").arg(m_entry1->username()).arg(m_entry1->password())); @@ -168,7 +169,7 @@ void TestAutoType::testGlobalAutoTypeTitleMatch() config()->set(Config::AutoTypeEntryTitleMatch, true); m_test->setActiveWindowTitle("An Entry Title!"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter))); @@ -179,7 +180,7 @@ void TestAutoType::testGlobalAutoTypeUrlMatch() config()->set(Config::AutoTypeEntryTitleMatch, true); m_test->setActiveWindowTitle("Dummy - http://example.org/ - "); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); @@ -190,7 +191,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch() config()->set(Config::AutoTypeEntryTitleMatch, true); m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - "); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); @@ -199,7 +200,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch() void TestAutoType::testGlobalAutoTypeTitleMatchDisabled() { m_test->setActiveWindowTitle("An Entry Title!"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); MessageBox::setNextAnswer(MessageBox::Ok); m_autoType->performGlobalAutoType(m_dbList); @@ -210,68 +211,68 @@ void TestAutoType::testGlobalAutoTypeRegExp() { // substring matches are ok m_test->setActiveWindowTitle("lorem REGEX1 ipsum"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("regex1")); m_test->clearActions(); // should be case-insensitive m_test->setActiveWindowTitle("lorem regex1 ipsum"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("regex1")); m_test->clearActions(); // exact match m_test->setActiveWindowTitle("REGEX2"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("regex2")); m_test->clearActions(); // a bit more complicated regex m_test->setActiveWindowTitle("REGEX3-R2D2"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("regex3")); m_test->clearActions(); // with custom attributes m_test->setActiveWindowTitle("CustomAttr1"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute")); m_test->clearActions(); // with (non uppercase) undefined custom attributes m_test->setActiveWindowTitle("CustomAttr2"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("")); m_test->clearActions(); // with mixedcase default attributes m_test->setActiveWindowTitle("CustomAttr3"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr")); m_test->clearActions(); // with resolve placeholders in window association title m_test->setActiveWindowTitle("AttrValueFirst"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr_first")); m_test->clearActions(); m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second")); m_test->clearActions(); m_test->setActiveWindowTitle("lorem AttrValueThird ipsum"); - m_test->triggerGlobalAutoType(); + emit osUtils->globalShortcutTriggered("autotype"); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString("custom_attr_third")); m_test->clearActions(); diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index 648aba07a..838d761a6 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -70,13 +70,13 @@ void TestCli::initTestCase() Config::createTempFileInstance(); Bootstrap::bootstrap(); - auto fd = new QFile(); + m_devNull.reset(new QFile()); #ifdef Q_OS_WIN - fd->open(fopen("nul", "w"), QIODevice::WriteOnly); + m_devNull->open(fopen("nul", "w"), QIODevice::WriteOnly); #else - fd->open(fopen("/dev/null", "w"), QIODevice::WriteOnly); + m_devNull->open(fopen("/dev/null", "w"), QIODevice::WriteOnly); #endif - Utils::DEVNULL.setDevice(fd); + Utils::DEVNULL.setDevice(m_devNull.data()); } void TestCli::init() @@ -131,6 +131,11 @@ void TestCli::cleanup() Utils::STDIN.setDevice(nullptr); } +void TestCli::cleanupTestCase() +{ + m_devNull.reset(); +} + QSharedPointer TestCli::readDatabase(const QString& filename, const QString& pw, const QString& keyfile) { auto db = QSharedPointer::create(); @@ -520,8 +525,7 @@ void TestCli::testClip() setInput("a"); execCmd(clipCmd, {"clip", m_dbFile->fileName(), "-a", "TESTAttribute1", "/Sample Entry"}); - QVERIFY(m_stderr->readAll().contains( - "ERROR: attribute TESTAttribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); + QVERIFY(m_stderr->readAll().contains("ERROR: attribute TESTAttribute1 is ambiguous")); setInput("a"); execCmd(clipCmd, {"clip", m_dbFile2->fileName(), "--attribute", "Username", "--totp", "/Sample Entry"}); @@ -1776,8 +1780,7 @@ void TestCli::testShow() setInput("a"); execCmd(showCmd, {"show", m_dbFile->fileName(), "-a", "Testattribute1", "/Sample Entry"}); QCOMPARE(m_stdout->readAll(), QByteArray()); - QVERIFY(m_stderr->readAll().contains( - "ERROR: attribute Testattribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n")); + QVERIFY(m_stderr->readAll().contains("ERROR: attribute Testattribute1 is ambiguous")); } void TestCli::testInvalidDbFiles() diff --git a/tests/TestCli.h b/tests/TestCli.h index 066e28e58..b868b1485 100644 --- a/tests/TestCli.h +++ b/tests/TestCli.h @@ -45,6 +45,7 @@ private slots: void initTestCase(); void init(); void cleanup(); + void cleanupTestCase(); void testBatchCommands(); void testAdd(); @@ -81,6 +82,7 @@ private slots: void testYubiKeyOption(); private: + QScopedPointer m_devNull; QScopedPointer m_dbFile; QScopedPointer m_dbFile2; QScopedPointer m_dbFileMulti;