Move global shortcut handling into OSUtils (#5566)

Move global shortcut handling into OSUtils
This commit is contained in:
Jonathan White 2020-12-13 23:23:25 -05:00 committed by GitHub
parent a6f01349e8
commit 404fd941e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1124 additions and 1136 deletions

View file

@ -305,11 +305,11 @@ if(WITH_COVERAGE AND CMAKE_COMPILER_IS_CLANGXX)
# `find src -iname '*.h' -or -iname '*.cpp'` # `find src -iname '*.h' -or -iname '*.cpp'`
endif() endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux") if(UNIX AND NOT APPLE)
check_add_gcc_compiler_flag("-Qunused-arguments") 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,--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,--no-add-needed -Wl,--as-needed")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro,-z,now") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro,-z,now")
endif() endif()
@ -399,7 +399,7 @@ include(CLangFormat)
set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools) set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools)
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus REQUIRED) find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus X11Extras REQUIRED)
elseif(APPLE) 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 ${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) find_package(Qt5 COMPONENTS MacExtras HINTS /usr/local/opt/qt/lib/cmake /usr/local/Cellar/qt/*/lib/cmake ENV PATH)

View file

@ -206,7 +206,8 @@ if(UNIX AND NOT APPLE)
${keepassx_SOURCES} ${keepassx_SOURCES}
gui/MainWindowAdaptor.cpp gui/MainWindowAdaptor.cpp
gui/osutils/nixutils/ScreenLockListenerDBus.cpp gui/osutils/nixutils/ScreenLockListenerDBus.cpp
gui/osutils/nixutils/NixUtils.cpp) gui/osutils/nixutils/NixUtils.cpp
gui/osutils/nixutils/X11Funcs.cpp)
endif() endif()
if(MINGW) if(MINGW)
set(keepassx_SOURCES set(keepassx_SOURCES
@ -214,11 +215,6 @@ if(MINGW)
gui/osutils/winutils/ScreenLockListenerWin.cpp gui/osutils/winutils/ScreenLockListenerWin.cpp
gui/osutils/winutils/WinUtils.cpp) gui/osutils/winutils/WinUtils.cpp)
endif() endif()
if(MINGW OR (UNIX AND NOT APPLE))
set(keepassx_SOURCES
${keepassx_SOURCES}
gui/osutils/OSEventFilter.cpp)
endif()
set(keepassx_SOURCES ${keepassx_SOURCES} set(keepassx_SOURCES ${keepassx_SOURCES}
../share/icons/icons.qrc ../share/icons/icons.qrc
@ -343,7 +339,7 @@ if(WITH_XC_KEESHARE)
endif() endif()
if(APPLE) 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) if(Qt5MacExtras_FOUND)
target_link_libraries(keepassx_core Qt5::MacExtras) target_link_libraries(keepassx_core Qt5::MacExtras)
endif() endif()
@ -356,7 +352,7 @@ if(HAIKU)
target_link_libraries(keepassx_core network) target_link_libraries(keepassx_core network)
endif() endif()
if(UNIX AND NOT APPLE) 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}) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif() endif()
if(MINGW) if(MINGW)

View file

@ -37,18 +37,13 @@
#include "core/Tools.h" #include "core/Tools.h"
#include "gui/MainWindow.h" #include "gui/MainWindow.h"
#include "gui/MessageBox.h" #include "gui/MessageBox.h"
#include "gui/osutils/OSUtils.h"
#ifdef Q_OS_MAC
#include "gui/osutils/macutils/MacUtils.h"
#endif
AutoType* AutoType::m_instance = nullptr; AutoType* AutoType::m_instance = nullptr;
AutoType::AutoType(QObject* parent, bool test) AutoType::AutoType(QObject* parent, bool test)
: QObject(parent) : QObject(parent)
, m_autoTypeDelay(0) , m_autoTypeDelay(0)
, m_currentGlobalKey(static_cast<Qt::Key>(0))
, m_currentGlobalModifiers(nullptr)
, m_pluginLoader(new QPluginLoader(this)) , m_pluginLoader(new QPluginLoader(this))
, m_plugin(nullptr) , m_plugin(nullptr)
, m_executor(nullptr) , m_executor(nullptr)
@ -96,7 +91,11 @@ void AutoType::loadPlugin(const QString& pluginPath)
if (m_plugin) { if (m_plugin) {
if (m_plugin->isAvailable()) { if (m_plugin->isAvailable()) {
m_executor = m_plugin->createExecutor(); m_executor = m_plugin->createExecutor();
connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SLOT(startGlobalAutoType())); connect(osUtils, &OSUtilsBase::globalShortcutTriggered, this, [this](QString name) {
if (name == "autotype") {
startGlobalAutoType();
}
});
} else { } else {
unloadPlugin(); unloadPlugin();
} }
@ -153,44 +152,18 @@ void AutoType::raiseWindow()
#endif #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) { if (!m_plugin) {
return false; return false;
} }
if (key != m_currentGlobalKey || modifiers != m_currentGlobalModifiers) { return osUtils->registerGlobalShortcut("autotype", key, modifiers, error);
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;
} }
void AutoType::unregisterGlobalShortcut() void AutoType::unregisterGlobalShortcut()
{ {
if (m_plugin && m_currentGlobalKey && m_currentGlobalModifiers) { osUtils->unregisterGlobalShortcut("autotype");
m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers);
}
}
int AutoType::callEventFilter(void* event)
{
if (!m_plugin) {
return -1;
}
return m_plugin->platformEventFilter(event);
} }
/** /**
@ -303,6 +276,23 @@ void AutoType::startGlobalAutoType()
m_windowForGlobal = m_plugin->activeWindow(); m_windowForGlobal = m_plugin->activeWindow();
m_windowTitleForGlobal = m_plugin->activeWindowTitle(); m_windowTitleForGlobal = m_plugin->activeWindowTitle();
#ifdef Q_OS_MACOS #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; m_windowState = WindowState::Normal;
if (getMainWindow()) { if (getMainWindow()) {
if (getMainWindow()->isMinimized()) { if (getMainWindow()->isMinimized()) {

View file

@ -39,9 +39,8 @@ class AutoType : public QObject
public: public:
QStringList windowTitles(); QStringList windowTitles();
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers); bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error = nullptr);
void unregisterGlobalShortcut(); void unregisterGlobalShortcut();
int callEventFilter(void* event);
static bool checkSyntax(const QString& string); static bool checkSyntax(const QString& string);
static bool checkHighRepetition(const QString& string); static bool checkHighRepetition(const QString& string);
static bool checkSlowKeypress(const QString& string); static bool checkSlowKeypress(const QString& string);
@ -99,8 +98,6 @@ private:
QMutex m_inAutoType; QMutex m_inAutoType;
QMutex m_inGlobalAutoTypeDialog; QMutex m_inGlobalAutoTypeDialog;
int m_autoTypeDelay; int m_autoTypeDelay;
Qt::Key m_currentGlobalKey;
Qt::KeyboardModifiers m_currentGlobalModifiers;
QPluginLoader* m_pluginLoader; QPluginLoader* m_pluginLoader;
AutoTypePlatformInterface* m_plugin; AutoTypePlatformInterface* m_plugin;
AutoTypeExecutor* m_executor; AutoTypeExecutor* m_executor;

View file

@ -32,9 +32,6 @@ public:
virtual QStringList windowTitles() = 0; virtual QStringList windowTitles() = 0;
virtual WId activeWindow() = 0; virtual WId activeWindow() = 0;
virtual QString activeWindowTitle() = 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 bool raiseWindow(WId window) = 0;
virtual void unload() virtual void unload()
{ {

View file

@ -18,6 +18,7 @@
#include "ShortcutWidget.h" #include "ShortcutWidget.h"
#include <QKeyEvent> #include <QKeyEvent>
#include <QToolTip>
#include "autotype/AutoType.h" #include "autotype/AutoType.h"
@ -48,9 +49,11 @@ void ShortcutWidget::setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
displayShortcut(m_key, m_modifiers); displayShortcut(m_key, m_modifiers);
if (autoType()->registerGlobalShortcut(m_key, m_modifiers)) { QString error;
if (autoType()->registerGlobalShortcut(m_key, m_modifiers, &error)) {
setStyleSheet(""); setStyleSheet("");
} else { } else {
QToolTip::showText(mapToGlobal(rect().bottomLeft()), error);
setStyleSheet("background-color: #FF9696;"); setStyleSheet("background-color: #FF9696;");
} }
} }

View file

@ -22,24 +22,12 @@
#include <ApplicationServices/ApplicationServices.h> #include <ApplicationServices/ApplicationServices.h>
#define HOTKEY_ID 1
#define MAX_WINDOW_TITLE_LENGTH 1024 #define MAX_WINDOW_TITLE_LENGTH 1024
#define INVALID_KEYCODE 0xFFFF #define INVALID_KEYCODE 0xFFFF
namespace {
bool accessibilityChecked = false;
}
AutoTypePlatformMac::AutoTypePlatformMac() AutoTypePlatformMac::AutoTypePlatformMac()
: m_hotkeyRef(nullptr)
, m_hotkeyId({ 'kpx2', HOTKEY_ID })
{ {
EventTypeSpec eventSpec;
eventSpec.eventClass = kEventClassKeyboard;
eventSpec.eventKind = kEventHotKeyPressed;
MessageBox::initializeButtonDefs(); MessageBox::initializeButtonDefs();
::InstallApplicationEventHandler(AutoTypePlatformMac::hotkeyHandler, 1, &eventSpec, this, nullptr);
} }
/** /**
@ -120,44 +108,6 @@ QString AutoTypePlatformMac::activeWindowTitle()
return title; 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() AutoTypeExecutor* AutoTypePlatformMac::createExecutor()
{ {
return new AutoTypeExecutorMac(this); 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) 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) { if (keyCode == INVALID_KEYCODE) {
return; return;
} }
CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown);
CGEventFlags nativeModifiers = qtToNativeModifiers(modifiers, true); CGEventFlags nativeModifiers = macUtils()->qtToNativeModifiers(modifiers, true);
if (keyEvent != nullptr) { if (keyEvent != nullptr) {
::CGEventSetFlags(keyEvent, nativeModifiers); ::CGEventSetFlags(keyEvent, nativeModifiers);
::CGEventPost(kCGSessionEventTap, keyEvent); ::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 // Get window layer/level
// //
@ -472,39 +205,6 @@ QString AutoTypePlatformMac::windowTitle(CFDictionaryRef window)
return title; 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<AutoTypePlatformMac*>(userData);
EventHotKeyID hotkeyId;
if (::GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, nullptr, sizeof(hotkeyId), nullptr, &hotkeyId) == noErr
&& hotkeyId.id == HOTKEY_ID) {
emit self->globalShortcutTriggered();
}
return noErr;
}
// //
// ------------------------------ AutoTypeExecutorMac ------------------------------ // ------------------------------ AutoTypeExecutorMac ------------------------------
// //

View file

@ -38,9 +38,6 @@ public:
QStringList windowTitles() override; QStringList windowTitles() override;
WId activeWindow() override; WId activeWindow() override;
QString activeWindowTitle() 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; bool raiseWindow(WId pid) override;
AutoTypeExecutor* createExecutor() override; AutoTypeExecutor* createExecutor() override;
@ -50,18 +47,9 @@ public:
void sendChar(const QChar& ch, bool isKeyDown); 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);
signals:
void globalShortcutTriggered();
private: 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 int windowLayer(CFDictionaryRef window);
static QString windowTitle(CFDictionaryRef window); static QString windowTitle(CFDictionaryRef window);
static OSStatus hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData);
}; };
class AutoTypeExecutorMac : public AutoTypeExecutor class AutoTypeExecutorMac : public AutoTypeExecutor

View file

@ -42,37 +42,11 @@ QString AutoTypePlatformTest::activeWindowTitle()
return m_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() AutoTypeExecutor* AutoTypePlatformTest::createExecutor()
{ {
return new AutoTypeExecutorTest(this); return new AutoTypeExecutorTest(this);
} }
void AutoTypePlatformTest::triggerGlobalAutoType()
{
emit globalShortcutTriggered();
}
void AutoTypePlatformTest::setActiveWindowTitle(const QString& title) void AutoTypePlatformTest::setActiveWindowTitle(const QString& title)
{ {
m_activeWindowTitle = title; m_activeWindowTitle = title;

View file

@ -37,9 +37,6 @@ public:
QStringList windowTitles() override; QStringList windowTitles() override;
WId activeWindow() override; WId activeWindow() override;
QString activeWindowTitle() 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; bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override; AutoTypeExecutor* createExecutor() override;
@ -48,7 +45,6 @@ public:
bool raiseOwnWindow() override; bool raiseOwnWindow() override;
#endif #endif
void triggerGlobalAutoType() override;
void setActiveWindowTitle(const QString& title) override; void setActiveWindowTitle(const QString& title) override;
QString actionChars() override; QString actionChars() override;
@ -58,9 +54,6 @@ public:
void addActionChar(AutoTypeChar* action); void addActionChar(AutoTypeChar* action);
void addActionKey(AutoTypeKey* action); void addActionKey(AutoTypeKey* action);
signals:
void globalShortcutTriggered();
private: private:
QString m_activeWindowTitle; QString m_activeWindowTitle;
QList<AutoTypeAction*> m_actionList; QList<AutoTypeAction*> m_actionList;

View file

@ -26,7 +26,6 @@ public:
virtual ~AutoTypeTestInterface() virtual ~AutoTypeTestInterface()
{ {
} }
virtual void triggerGlobalAutoType() = 0;
virtual void setActiveWindowTitle(const QString& title) = 0; virtual void setActiveWindowTitle(const QString& title) = 0;
virtual QString actionChars() = 0; virtual QString actionChars() = 0;

View file

@ -17,6 +17,7 @@
*/ */
#include "AutoTypeWindows.h" #include "AutoTypeWindows.h"
#include "gui/osutils/OSUtils.h"
#include <VersionHelpers.h> #include <VersionHelpers.h>
@ -61,49 +62,6 @@ QString AutoTypePlatformWin::activeWindowTitle()
return windowTitle(::GetForegroundWindow()); 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<MSG*>(event);
if (msg->message == WM_HOTKEY && msg->wParam == HOTKEY_ID) {
emit globalShortcutTriggered();
return 1;
}
return -1;
}
AutoTypeExecutor* AutoTypePlatformWin::createExecutor() AutoTypeExecutor* AutoTypePlatformWin::createExecutor()
{ {
return new AutoTypeExecutorWin(this); return new AutoTypeExecutorWin(this);
@ -145,7 +103,7 @@ void AutoTypePlatformWin::sendChar(const QChar& ch, bool isKeyDown)
// //
void AutoTypePlatformWin::sendKey(Qt::Key key, bool isKeyDown) void AutoTypePlatformWin::sendKey(Qt::Key key, bool isKeyDown)
{ {
DWORD nativeKeyCode = qtToNativeKeyCode(key); DWORD nativeKeyCode = winUtils()->qtToNativeKeyCode(key);
if (nativeKeyCode < 1 || nativeKeyCode > 254) { if (nativeKeyCode < 1 || nativeKeyCode > 254) {
return; return;
} }
@ -168,234 +126,12 @@ void AutoTypePlatformWin::sendKey(Qt::Key key, bool isKeyDown)
::SendInput(1, &in, sizeof(INPUT)); ::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 // The extended-key flag indicates whether the keystroke message originated
// from one of the additional keys on the enhanced keyboard // 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 // 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) { switch (nativeKeyCode) {
case VK_RMENU: case VK_RMENU:
@ -417,44 +153,21 @@ BOOL AutoTypePlatformWin::isExtendedKey(DWORD nativeKeyCode)
case VK_LWIN: case VK_LWIN:
case VK_RWIN: case VK_RWIN:
case VK_APPS: case VK_APPS:
return TRUE; return true;
default: default:
return FALSE; return false;
} }
} }
// clang-format on // 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 // Test if window is in Alt+Tab list
// see: https://blogs.msdn.microsoft.com/oldnewthing/20071008-00/?p=24863 // see: https://blogs.msdn.microsoft.com/oldnewthing/20071008-00/?p=24863
// //
BOOL AutoTypePlatformWin::isAltTabWindow(HWND hwnd) bool AutoTypePlatformWin::isAltTabWindow(HWND hwnd)
{ {
if (!::IsWindowVisible(hwnd)) { if (!::IsWindowVisible(hwnd)) {
return FALSE; return false;
} }
// Start at the root owner // Start at the root owner

View file

@ -20,7 +20,7 @@
#define KEEPASSX_AUTOTYPEWINDOWS_H #define KEEPASSX_AUTOTYPEWINDOWS_H
#include <QtPlugin> #include <QtPlugin>
#include <Windows.h> #include <windows.h>
#include "autotype/AutoTypeAction.h" #include "autotype/AutoTypeAction.h"
#include "autotype/AutoTypePlatformPlugin.h" #include "autotype/AutoTypePlatformPlugin.h"
@ -36,23 +36,15 @@ public:
QStringList windowTitles() override; QStringList windowTitles() override;
WId activeWindow() override; WId activeWindow() override;
QString activeWindowTitle() 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; bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override; AutoTypeExecutor* createExecutor() override;
void sendChar(const QChar& ch, bool isKeyDown); void sendChar(const QChar& ch, bool isKeyDown);
void sendKey(Qt::Key key, bool isKeyDown); void sendKey(Qt::Key key, bool isKeyDown);
signals:
void globalShortcutTriggered();
private: private:
static DWORD qtToNativeKeyCode(Qt::Key key); static bool isExtendedKey(DWORD nativeKeyCode);
static DWORD qtToNativeModifiers(Qt::KeyboardModifiers modifiers); 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 BOOL CALLBACK windowTitleEnumProc(_In_ HWND hwnd, _In_ LPARAM lParam);
static QString windowTitle(HWND hwnd); static QString windowTitle(HWND hwnd);
}; };

View file

@ -18,15 +18,6 @@
*/ */
#include "AutoTypeXCB.h" #include "AutoTypeXCB.h"
#include "KeySymMap.h"
#include "core/Tools.h"
#include <time.h>
#include <xcb/xcb.h>
bool AutoTypePlatformX11::m_catchXErrors = false;
bool AutoTypePlatformX11::m_xErrorOccurred = false;
int (*AutoTypePlatformX11::m_oldXErrorHandler)(Display*, XErrorEvent*) = nullptr;
AutoTypePlatformX11::AutoTypePlatformX11() AutoTypePlatformX11::AutoTypePlatformX11()
{ {
@ -49,17 +40,14 @@ AutoTypePlatformX11::AutoTypePlatformX11()
m_classBlacklist << "xfdesktop" m_classBlacklist << "xfdesktop"
<< "xfce4-panel"; // Xfce 4 << "xfce4-panel"; // Xfce 4
m_currentGlobalKey = static_cast<Qt::Key>(0);
m_currentGlobalModifiers = nullptr;
m_keysymTable = nullptr; m_keysymTable = nullptr;
m_xkb = nullptr; m_xkb = nullptr;
m_remapKeycode = 0; m_remapKeycode = 0;
m_currentRemapKeysym = NoSymbol; m_currentRemapKeysym = NoSymbol;
m_modifierMask = ControlMask | ShiftMask | Mod1Mask | Mod4Mask;
m_loaded = true; m_loaded = true;
connect(nixUtils(), &NixUtils::keymapChanged, this, [this] { updateKeymap(); });
updateKeymap(); updateKeymap();
} }
@ -142,105 +130,6 @@ QString AutoTypePlatformX11::activeWindowTitle()
return windowTitle(activeWindow(), true); 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<Qt::Key>(0);
m_currentGlobalModifiers = nullptr;
m_currentGlobalKeycode = 0;
m_currentGlobalNativeModifiers = 0;
}
int AutoTypePlatformX11::platformEventFilter(void* event)
{
xcb_generic_event_t* genericEvent = static_cast<xcb_generic_event_t*>(event);
quint8 type = genericEvent->response_type & 0x7f;
if (type == XCB_KEY_PRESS || type == XCB_KEY_RELEASE) {
xcb_key_press_event_t* keyPressEvent = static_cast<xcb_key_press_event_t*>(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<xcb_mapping_notify_event_t*>(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() AutoTypeExecutor* AutoTypePlatformX11::createExecutor()
{ {
return new AutoTypeExecutorX11(this); return new AutoTypeExecutorX11(this);
@ -395,89 +284,6 @@ bool AutoTypePlatformX11::isTopLevelWindow(Window window)
return result; 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. * Update the keyboard and modifier mapping.
* We need the KeyboardMapping for AddKeysym. * We need the KeyboardMapping for AddKeysym.
@ -491,8 +297,9 @@ void AutoTypePlatformX11::updateKeymap()
m_xkb = getKeyboard(); m_xkb = getKeyboard();
XDisplayKeycodes(m_dpy, &m_minKeycode, &m_maxKeycode); XDisplayKeycodes(m_dpy, &m_minKeycode, &m_maxKeycode);
if (m_keysymTable != nullptr) if (m_keysymTable != nullptr) {
XFree(m_keysymTable); XFree(m_keysymTable);
}
m_keysymTable = XGetKeyboardMapping(m_dpy, m_minKeycode, m_maxKeycode - m_minKeycode + 1, &m_keysymPerKeycode); m_keysymTable = XGetKeyboardMapping(m_dpy, m_minKeycode, m_maxKeycode - m_minKeycode + 1, &m_keysymPerKeycode);
/* determine the keycode to use for remapped keys */ /* 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 /* Xlib needs some time until the mapping is distributed to
all clients */ all clients */
// TODO: we should probably only sleep while in the middle of typing something Tools::sleep(30);
timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 30 * 1000 * 1000;
nanosleep(&ts, nullptr);
} }
bool AutoTypePlatformX11::isRemapKeycodeValid() bool AutoTypePlatformX11::isRemapKeycodeValid()
@ -542,36 +345,6 @@ bool AutoTypePlatformX11::isRemapKeycodeValid()
return false; 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() XkbDescPtr AutoTypePlatformX11::getKeyboard()
{ {
int num_devices; 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) * window to simulate keyboard. If modifiers (shift, control, etc)
* are set ON, many events will be sent. * 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) { if (keysym == NoSymbol) {
qWarning("No such key: keysym=0x%lX", keysym); qWarning("No such key: keysym=0x%lX", keysym);
@ -798,12 +571,12 @@ AutoTypeExecutorX11::AutoTypeExecutorX11(AutoTypePlatformX11* platform)
void AutoTypeExecutorX11::execChar(AutoTypeChar* action) void AutoTypeExecutorX11::execChar(AutoTypeChar* action)
{ {
m_platform->SendKey(m_platform->charToKeySym(action->character)); m_platform->sendKey(qcharToNativeKeyCode(action->character));
} }
void AutoTypeExecutorX11::execKey(AutoTypeKey* action) 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) void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr)
@ -814,13 +587,13 @@ void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr)
ts.tv_sec = 0; ts.tv_sec = 0;
ts.tv_nsec = 25 * 1000 * 1000; ts.tv_nsec = 25 * 1000 * 1000;
m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Home), static_cast<unsigned int>(ControlMask)); m_platform->sendKey(qtToNativeKeyCode(Qt::Key_Home), static_cast<unsigned int>(ControlMask));
nanosleep(&ts, nullptr); nanosleep(&ts, nullptr);
m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_End), static_cast<unsigned int>(ControlMask | ShiftMask)); m_platform->sendKey(qtToNativeKeyCode(Qt::Key_End), static_cast<unsigned int>(ControlMask | ShiftMask));
nanosleep(&ts, nullptr); nanosleep(&ts, nullptr);
m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Backspace)); m_platform->sendKey(qtToNativeKeyCode(Qt::Key_Backspace));
nanosleep(&ts, nullptr); nanosleep(&ts, nullptr);
} }

View file

@ -26,12 +26,16 @@
#include <QX11Info> #include <QX11Info>
#include <QtPlugin> #include <QtPlugin>
#include "autotype/AutoTypeAction.h"
#include "autotype/AutoTypePlatformPlugin.h"
#include "core/Tools.h"
#include "gui/osutils/OSUtils.h"
#include "gui/osutils/nixutils/X11Funcs.h"
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/extensions/XTest.h> #include <X11/extensions/XTest.h>
#include <xcb/xcb.h>
#include "autotype/AutoTypeAction.h"
#include "autotype/AutoTypePlatformPlugin.h"
#define N_MOD_INDICES (Mod5MapIndex + 1) #define N_MOD_INDICES (Mod5MapIndex + 1)
@ -48,19 +52,10 @@ public:
QStringList windowTitles() override; QStringList windowTitles() override;
WId activeWindow() override; WId activeWindow() override;
QString activeWindowTitle() 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; bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override; AutoTypeExecutor* createExecutor() override;
KeySym charToKeySym(const QChar& ch); void sendKey(KeySym keysym, unsigned int modifiers = 0);
KeySym keyToKeySym(Qt::Key key);
void SendKey(KeySym keysym, unsigned int modifiers = 0);
signals:
void globalShortcutTriggered();
private: private:
QString windowTitle(Window window, bool useBlacklist); QString windowTitle(Window window, bool useBlacklist);
@ -68,10 +63,6 @@ private:
QString windowClassName(Window window); QString windowClassName(Window window);
QList<Window> widgetsToX11Windows(const QWidgetList& widgetList); QList<Window> widgetsToX11Windows(const QWidgetList& widgetList);
bool isTopLevelWindow(Window window); bool isTopLevelWindow(Window window);
uint qtToNativeModifiers(Qt::KeyboardModifiers modifiers);
void startCatchXErrors();
void stopCatchXErrors();
static int x11ErrorHandler(Display* display, XErrorEvent* error);
XkbDescPtr getKeyboard(); XkbDescPtr getKeyboard();
void updateKeymap(); void updateKeymap();
@ -94,18 +85,6 @@ private:
Atom m_atomUtf8String; Atom m_atomUtf8String;
Atom m_atomNetActiveWindow; Atom m_atomNetActiveWindow;
QSet<QString> m_classBlacklist; QSet<QString> 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; XkbDescPtr m_xkb;
KeySym* m_keysymTable; KeySym* m_keysymTable;

View file

@ -36,10 +36,6 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <QtNetwork/QLocalSocket> #include <QtNetwork/QLocalSocket>
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
#include "gui/osutils/OSEventFilter.h"
#endif
#if defined(Q_OS_UNIX) #if defined(Q_OS_UNIX)
#include <signal.h> #include <signal.h>
#include <sys/socket.h> #include <sys/socket.h>
@ -60,9 +56,7 @@ Application::Application(int& argc, char** argv)
, m_alreadyRunning(false) , m_alreadyRunning(false)
, m_lockFile(nullptr) , m_lockFile(nullptr)
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) #if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
, m_osEventFilter(new OSEventFilter())
{ {
installNativeEventFilter(m_osEventFilter.data());
#else #else
{ {
#endif #endif
@ -160,6 +154,7 @@ void Application::bootstrap()
QApplication::setFont(QApplication::font("QMessageBox")); QApplication::setFont(QApplication::font("QMessageBox"));
#endif #endif
osUtils->registerNativeEventFilter();
MessageBox::initializeButtonDefs(); MessageBox::initializeButtonDefs();
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS

View file

@ -81,9 +81,6 @@ private:
QLockFile* m_lockFile; QLockFile* m_lockFile;
QLocalServer m_lockServer; QLocalServer m_lockServer;
QString m_socketName; QString m_socketName;
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
QScopedPointer<OSEventFilter> m_osEventFilter;
#endif
}; };
#define kpxcApp qobject_cast<Application*>(Application::instance()) #define kpxcApp qobject_cast<Application*>(Application::instance())

View file

@ -60,7 +60,7 @@ PasswordEdit::PasswordEdit(QWidget* parent)
m_toggleVisibleAction = new QAction( m_toggleVisibleAction = new QAction(
icons()->icon("password-show-off"), icons()->icon("password-show-off"),
tr("Toggle Password (%1)").arg(QKeySequence(modifier + Qt::Key_H).toString(QKeySequence::NativeText)), tr("Toggle Password (%1)").arg(QKeySequence(modifier + Qt::Key_H).toString(QKeySequence::NativeText)),
nullptr); this);
m_toggleVisibleAction->setCheckable(true); m_toggleVisibleAction->setCheckable(true);
m_toggleVisibleAction->setShortcut(modifier + Qt::Key_H); m_toggleVisibleAction->setShortcut(modifier + Qt::Key_H);
m_toggleVisibleAction->setShortcutContext(Qt::WidgetShortcut); m_toggleVisibleAction->setShortcutContext(Qt::WidgetShortcut);
@ -70,7 +70,7 @@ PasswordEdit::PasswordEdit(QWidget* parent)
m_passwordGeneratorAction = new QAction( m_passwordGeneratorAction = new QAction(
icons()->icon("password-generator"), icons()->icon("password-generator"),
tr("Generate Password (%1)").arg(QKeySequence(modifier + Qt::Key_G).toString(QKeySequence::NativeText)), tr("Generate Password (%1)").arg(QKeySequence(modifier + Qt::Key_G).toString(QKeySequence::NativeText)),
nullptr); this);
m_passwordGeneratorAction->setShortcut(modifier + Qt::Key_G); m_passwordGeneratorAction->setShortcut(modifier + Qt::Key_G);
m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut); m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut);
addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition); addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition);
@ -79,7 +79,7 @@ PasswordEdit::PasswordEdit(QWidget* parent)
m_capslockAction = m_capslockAction =
new QAction(icons()->icon("dialog-warning", true, StateColorPalette().color(StateColorPalette::Error)), new QAction(icons()->icon("dialog-warning", true, StateColorPalette().color(StateColorPalette::Error)),
tr("Warning: Caps Lock enabled!"), tr("Warning: Caps Lock enabled!"),
nullptr); this);
addAction(m_capslockAction, QLineEdit::LeadingPosition); addAction(m_capslockAction, QLineEdit::LeadingPosition);
m_capslockAction->setVisible(false); m_capslockAction->setVisible(false);
} }

View file

@ -1,47 +0,0 @@
/*
* Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "OSEventFilter.h"
#include <QByteArray>
#include "autotype/AutoType.h"
#include "gui/MainWindow.h"
#ifdef Q_OS_WIN
#include <windows.h>
#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;
}

View file

@ -1,35 +0,0 @@
/*
* Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OSEVENTFILTER_H
#define OSEVENTFILTER_H
#include <QAbstractNativeEventFilter>
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

View file

@ -50,6 +50,17 @@ public:
*/ */
virtual bool isCapslockEnabled() = 0; 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: protected:
explicit OSUtilsBase(QObject* parent = nullptr); explicit OSUtilsBase(QObject* parent = nullptr);
virtual ~OSUtilsBase(); virtual ~OSUtilsBase();

View file

@ -23,8 +23,10 @@
#include <QSettings> #include <QSettings>
#include <QStandardPaths> #include <QStandardPaths>
#include <ApplicationServices/ApplicationServices.h>
#include <CoreGraphics/CGEventSource.h> #include <CoreGraphics/CGEventSource.h>
#define INVALID_KEYCODE 0xFFFF
QPointer<MacUtils> MacUtils::m_instance = nullptr; QPointer<MacUtils> MacUtils::m_instance = nullptr;
@ -95,9 +97,10 @@ bool MacUtils::isDarkMode() const
QString MacUtils::getLaunchAgentFilename() const QString MacUtils::getLaunchAgentFilename() const
{ {
auto launchAgentDir = QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/../LaunchAgents")); auto launchAgentDir =
return QFile(launchAgentDir.absoluteFilePath( QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/../LaunchAgents"));
qApp->property("KPXC_QUALIFIED_APPNAME").toString().append(".plist"))).fileName(); return QFile(launchAgentDir.absoluteFilePath(qApp->property("KPXC_QUALIFIED_APPNAME").toString().append(".plist")))
.fileName();
} }
bool MacUtils::isLaunchAtStartupEnabled() const bool MacUtils::isLaunchAtStartupEnabled() const
@ -134,3 +137,306 @@ void MacUtils::toggleForegroundApp(bool foreground)
{ {
m_appkit->toggleForegroundApp(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<QString, QSharedPointer<globalShortcut>> 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<globalShortcut>::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<MacUtils*>(userData);
EventHotKeyID hotkeyId;
if (::GetEventParameter(
theEvent, kEventParamDirectObject, typeEventHotKeyID, nullptr, sizeof(hotkeyId), nullptr, &hotkeyId)
== noErr) {
QHashIterator<QString, QSharedPointer<globalShortcut>> 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;
}

View file

@ -19,8 +19,9 @@
#ifndef KEEPASSXC_MACUTILS_H #ifndef KEEPASSXC_MACUTILS_H
#define KEEPASSXC_MACUTILS_H #define KEEPASSXC_MACUTILS_H
#include "gui/osutils/OSUtilsBase.h"
#include "AppKit.h" #include "AppKit.h"
#include "gui/osutils/OSUtilsBase.h"
#include <Carbon/Carbon.h>
#include <QPointer> #include <QPointer>
#include <QScopedPointer> #include <QScopedPointer>
@ -48,6 +49,17 @@ public:
bool enableScreenRecording(); bool enableScreenRecording();
void toggleForegroundApp(bool foreground); 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: signals:
void lockDatabases(); void lockDatabases();
@ -57,10 +69,22 @@ protected:
private: private:
QString getLaunchAgentFilename() const; QString getLaunchAgentFilename() const;
static OSStatus hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData);
QScopedPointer<AppKit> m_appkit; QScopedPointer<AppKit> m_appkit;
static QPointer<MacUtils> m_instance; static QPointer<MacUtils> m_instance;
struct globalShortcut
{
EventHotKeyRef hotkeyRef;
EventHotKeyID hotkeyId;
uint16 nativeKeyCode;
CGEventFlags nativeModifiers;
};
int m_nextShortcutId = 1;
QHash<QString, QSharedPointer<globalShortcut>> m_globalShortcuts;
Q_DISABLE_COPY(MacUtils) Q_DISABLE_COPY(MacUtils)
}; };

View file

@ -2,10 +2,10 @@
* Automatically generated by keysymmap.py from parsing keysymdef.h. * Automatically generated by keysymmap.py from parsing keysymdef.h.
*/ */
const int AutoTypePlatformX11::m_unicodeToKeysymLen = 632; const int unicodeToKeysymLen = 632;
// clang-format off // clang-format off
const uint AutoTypePlatformX11::m_unicodeToKeysymKeys[] = { const uint unicodeToKeysymKeys[] = {
0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107,
0x0108, 0x0109, 0x010a, 0x010b, 0x010c, 0x010d, 0x010e, 0x010f, 0x0108, 0x0109, 0x010a, 0x010b, 0x010c, 0x010d, 0x010e, 0x010f,
0x0110, 0x0111, 0x0112, 0x0113, 0x0116, 0x0117, 0x0118, 0x0119, 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 0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f2, 0x30f3, 0x30fb, 0x30fc
}; };
const uint AutoTypePlatformX11::m_unicodeToKeysymValues[] = { const uint unicodeToKeysymValues[] = {
0x03c0, 0x03e0, 0x01c3, 0x01e3, 0x01a1, 0x01b1, 0x01c6, 0x01e6, 0x03c0, 0x03e0, 0x01c3, 0x01e3, 0x01a1, 0x01b1, 0x01c6, 0x01e6,
0x02c6, 0x02e6, 0x02c5, 0x02e5, 0x01c8, 0x01e8, 0x01cf, 0x01ef, 0x02c6, 0x02e6, 0x02c5, 0x02e5, 0x01c8, 0x01e8, 0x01cf, 0x01ef,
0x01d0, 0x01f0, 0x03aa, 0x03ba, 0x03cc, 0x03ec, 0x01ca, 0x01ea, 0x01d0, 0x01f0, 0x03aa, 0x03ba, 0x03cc, 0x03ec, 0x01ca, 0x01ea,

View file

@ -16,6 +16,8 @@
*/ */
#include "NixUtils.h" #include "NixUtils.h"
#include "KeySymMap.h"
#include "core/Tools.h"
#include <QApplication> #include <QApplication>
#include <QColor> #include <QColor>
@ -26,13 +28,26 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <QStyle> #include <QStyle>
#include <QTextStream> #include <QTextStream>
#include <QtX11Extras/QX11Info>
#include <qpa/qplatformnativeinterface.h> #include <qpa/qplatformnativeinterface.h>
// namespace required to avoid name clashes with declarations in XKBlib.h
namespace X11 #include "X11Funcs.h"
{
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
#include <xcb/xproto.h>
namespace
{
Display* dpy;
Window rootWindow;
bool x11ErrorOccurred = false;
int x11ErrorHandler(Display*, XErrorEvent*)
{
x11ErrorOccurred = true;
return 1;
} }
} // namespace
QPointer<NixUtils> NixUtils::m_instance = nullptr; QPointer<NixUtils> NixUtils::m_instance = nullptr;
@ -48,6 +63,8 @@ NixUtils* NixUtils::instance()
NixUtils::NixUtils(QObject* parent) NixUtils::NixUtils(QObject* parent)
: OSUtilsBase(parent) : OSUtilsBase(parent)
{ {
dpy = QX11Info::display();
rootWindow = QX11Info::appRootWindow();
} }
NixUtils::~NixUtils() NixUtils::~NixUtils()
@ -125,7 +142,7 @@ bool NixUtils::isCapslockEnabled()
QString platform = QGuiApplication::platformName(); QString platform = QGuiApplication::platformName();
if (platform == "xcb") { if (platform == "xcb") {
unsigned state = 0; unsigned state = 0;
if (X11::XkbGetIndicatorState(reinterpret_cast<X11::Display*>(display), XkbUseCoreKbd, &state) == Success) { if (XkbGetIndicatorState(reinterpret_cast<Display*>(display), XkbUseCoreKbd, &state) == Success) {
return ((state & 1u) != 0); return ((state & 1u) != 0);
} }
} }
@ -134,3 +151,119 @@ bool NixUtils::isCapslockEnabled()
return false; 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<xcb_generic_event_t*>(message);
quint8 type = genericEvent->response_type & 0x7f;
if (type == XCB_KEY_PRESS) {
auto* keyPressEvent = static_cast<xcb_key_press_event_t*>(message);
auto modifierMask = ControlMask | ShiftMask | Mod1Mask | Mod4Mask;
return triggerGlobalShortcut(keyPressEvent->detail, keyPressEvent->state & modifierMask);
} else if (type == XCB_MAPPING_NOTIFY) {
auto* mappingNotifyEvent = static_cast<xcb_mapping_notify_event_t*>(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<QString, QSharedPointer<globalShortcut>> 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<QString, QSharedPointer<globalShortcut>> 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<globalShortcut>::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;
}

View file

@ -19,9 +19,10 @@
#define KEEPASSXC_NIXUTILS_H #define KEEPASSXC_NIXUTILS_H
#include "gui/osutils/OSUtilsBase.h" #include "gui/osutils/OSUtilsBase.h"
#include <QAbstractNativeEventFilter>
#include <QPointer> #include <QPointer>
class NixUtils : public OSUtilsBase class NixUtils : public OSUtilsBase, QAbstractNativeEventFilter
{ {
Q_OBJECT Q_OBJECT
@ -33,15 +34,35 @@ public:
void setLaunchAtStartup(bool enable) override; void setLaunchAtStartup(bool enable) override;
bool isCapslockEnabled() 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: private:
explicit NixUtils(QObject* parent = nullptr); explicit NixUtils(QObject* parent = nullptr);
~NixUtils() override; ~NixUtils() override;
private: bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override;
QString getAutostartDesktopFilename(bool createDirs = false) const; QString getAutostartDesktopFilename(bool createDirs = false) const;
bool triggerGlobalShortcut(uint keycode, uint modifiers);
static QPointer<NixUtils> m_instance; static QPointer<NixUtils> m_instance;
struct globalShortcut
{
uint nativeKeyCode;
uint nativeModifiers;
};
QHash<QString, QSharedPointer<globalShortcut>> m_globalShortcuts;
Q_DISABLE_COPY(NixUtils) Q_DISABLE_COPY(NixUtils)
}; };

View file

@ -0,0 +1,126 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "X11Funcs.h"
#include "KeySymMap.h"
#include "core/Tools.h"
#include <X11/Xutil.h>
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;
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_X11FUNCS_H
#define KEEPASSXC_X11FUNCS_H
#include <QChar>
#include <qnamespace.h>
#include <X11/X.h>
KeySym qcharToNativeKeyCode(const QChar& ch);
KeySym qtToNativeKeyCode(Qt::Key key);
uint qtToNativeModifiers(Qt::KeyboardModifiers modifiers);
#endif

View file

@ -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()
print("const uint AutoTypePlatformX11::m_unicodeToKeysymKeys[] = {") print("const uint unicodeToKeysymKeys[] = {")
keys = keysymMap.keys() keys = keysymMap.keys()
keyLen = len(keys) keyLen = len(keys)
for idx, val in enumerate(keys, start=1): for idx, val in enumerate(keys, start=1):
@ -84,7 +84,7 @@ print("};")
print() print()
print("const uint AutoTypePlatformX11::m_unicodeToKeysymValues[] = {") print("const uint unicodeToKeysymValues[] = {")
values = keysymMap.values() values = keysymMap.values()
valuesLen = len(values) valuesLen = len(values)
for idx, val in enumerate(values, start=1): for idx, val in enumerate(values, start=1):

View file

@ -24,7 +24,6 @@
#include <windows.h> #include <windows.h>
QPointer<WinUtils> WinUtils::m_instance = nullptr; QPointer<WinUtils> WinUtils::m_instance = nullptr;
QScopedPointer<WinUtils::DWMEventFilter> WinUtils::m_eventFilter;
WinUtils* WinUtils::instance() 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) { qApp->installNativeEventFilter(this);
m_eventFilter.reset(new DWMEventFilter);
qApp->installNativeEventFilter(m_eventFilter.data());
}
} }
bool WinUtils::DWMEventFilter::nativeEventFilter(const QByteArray& eventType, void* message, long*) bool WinUtils::nativeEventFilter(const QByteArray& eventType, void* message, long*)
{ {
if (eventType != "windows_generic_MSG") { if (eventType != "windows_generic_MSG") {
return false; return false;
} }
auto* msg = static_cast<MSG*>(message); auto* msg = static_cast<MSG*>(message);
if (!msg->hwnd) {
return false;
}
switch (msg->message) { switch (msg->message) {
/* TODO: indicate dark mode support for black title bar
case WM_CREATE: case WM_CREATE:
case WM_INITDIALOG: { case WM_INITDIALOG: {
if (winUtils()->isDarkMode()) { if (msg->hwnd && winUtils()->isDarkMode()) {
// TODO: indicate dark mode support for black title bar
} }
break; break;
} }
*/
case WM_HOTKEY:
triggerGlobalShortcut(msg->wParam);
break;
} }
return false; return false;
@ -119,3 +113,319 @@ bool WinUtils::isHighContrastMode() const
QSettings settings(R"(HKEY_CURRENT_USER\Control Panel\Accessibility\HighContrast)", QSettings::NativeFormat); QSettings settings(R"(HKEY_CURRENT_USER\Control Panel\Accessibility\HighContrast)", QSettings::NativeFormat);
return (settings.value("Flags").toInt() & 1u) != 0; 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<QString, QSharedPointer<globalShortcut>> 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<globalShortcut>::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<QString, QSharedPointer<globalShortcut>> 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;
}

View file

@ -23,14 +23,16 @@
#include <QAbstractNativeEventFilter> #include <QAbstractNativeEventFilter>
#include <QPointer> #include <QPointer>
#include <QScopedPointer> #include <QScopedPointer>
#include <QSharedPointer>
class WinUtils : public OSUtilsBase #include <windef.h>
class WinUtils : public OSUtilsBase, QAbstractNativeEventFilter
{ {
Q_OBJECT Q_OBJECT
public: public:
static WinUtils* instance(); static WinUtils* instance();
static void registerEventFilters();
bool isDarkMode() const override; bool isDarkMode() const override;
bool isLaunchAtStartupEnabled() const override; bool isLaunchAtStartupEnabled() const override;
@ -38,19 +40,36 @@ public:
bool isCapslockEnabled() override; bool isCapslockEnabled() override;
bool isHighContrastMode() const; 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: protected:
explicit WinUtils(QObject* parent = nullptr); explicit WinUtils(QObject* parent = nullptr);
~WinUtils() override; ~WinUtils() override = default;
bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override;
void triggerGlobalShortcut(int id);
private: private:
class DWMEventFilter : public QAbstractNativeEventFilter static QPointer<WinUtils> m_instance;
struct globalShortcut
{ {
public: int id;
bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override; DWORD nativeKeyCode;
DWORD nativeModifiers;
}; };
static QPointer<WinUtils> m_instance; int m_nextShortcutId = 1;
static QScopedPointer<DWMEventFilter> m_eventFilter; QHash<QString, QSharedPointer<globalShortcut>> m_globalShortcuts;
Q_DISABLE_COPY(WinUtils) Q_DISABLE_COPY(WinUtils)
}; };

View file

@ -119,8 +119,6 @@ void DarkStyle::polish(QWidget* widget)
palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0x252525)); palette.setColor(QPalette::Disabled, QPalette::Window, QRgb(0x252525));
} }
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
// Register event filter for better dark mode support
WinUtils::registerEventFilters();
palette.setColor(QPalette::All, QPalette::Window, QRgb(0x2F2F30)); palette.setColor(QPalette::All, QPalette::Window, QRgb(0x2F2F30));
#else #else
palette.setColor(QPalette::Active, QPalette::Window, QRgb(0x2F2F30)); palette.setColor(QPalette::Active, QPalette::Window, QRgb(0x2F2F30));

View file

@ -28,6 +28,7 @@
#include "core/Resources.h" #include "core/Resources.h"
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
#include "gui/MessageBox.h" #include "gui/MessageBox.h"
#include "gui/osutils/OSUtils.h"
QTEST_GUILESS_MAIN(TestAutoType) QTEST_GUILESS_MAIN(TestAutoType)
@ -157,7 +158,7 @@ void TestAutoType::testGlobalAutoTypeWithNoMatch()
void TestAutoType::testGlobalAutoTypeWithOneMatch() void TestAutoType::testGlobalAutoTypeWithOneMatch()
{ {
m_test->setActiveWindowTitle("custom window"); m_test->setActiveWindowTitle("custom window");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1association%2").arg(m_entry1->username()).arg(m_entry1->password())); 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); config()->set(Config::AutoTypeEntryTitleMatch, true);
m_test->setActiveWindowTitle("An Entry Title!"); m_test->setActiveWindowTitle("An Entry Title!");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter))); 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); config()->set(Config::AutoTypeEntryTitleMatch, true);
m_test->setActiveWindowTitle("Dummy - http://example.org/ - <My Browser>"); m_test->setActiveWindowTitle("Dummy - http://example.org/ - <My Browser>");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); 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); config()->set(Config::AutoTypeEntryTitleMatch, true);
m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - <My Browser>"); m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - <My Browser>");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); 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() void TestAutoType::testGlobalAutoTypeTitleMatchDisabled()
{ {
m_test->setActiveWindowTitle("An Entry Title!"); m_test->setActiveWindowTitle("An Entry Title!");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
MessageBox::setNextAnswer(MessageBox::Ok); MessageBox::setNextAnswer(MessageBox::Ok);
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
@ -210,68 +211,68 @@ void TestAutoType::testGlobalAutoTypeRegExp()
{ {
// substring matches are ok // substring matches are ok
m_test->setActiveWindowTitle("lorem REGEX1 ipsum"); m_test->setActiveWindowTitle("lorem REGEX1 ipsum");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex1")); QCOMPARE(m_test->actionChars(), QString("regex1"));
m_test->clearActions(); m_test->clearActions();
// should be case-insensitive // should be case-insensitive
m_test->setActiveWindowTitle("lorem regex1 ipsum"); m_test->setActiveWindowTitle("lorem regex1 ipsum");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex1")); QCOMPARE(m_test->actionChars(), QString("regex1"));
m_test->clearActions(); m_test->clearActions();
// exact match // exact match
m_test->setActiveWindowTitle("REGEX2"); m_test->setActiveWindowTitle("REGEX2");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex2")); QCOMPARE(m_test->actionChars(), QString("regex2"));
m_test->clearActions(); m_test->clearActions();
// a bit more complicated regex // a bit more complicated regex
m_test->setActiveWindowTitle("REGEX3-R2D2"); m_test->setActiveWindowTitle("REGEX3-R2D2");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex3")); QCOMPARE(m_test->actionChars(), QString("regex3"));
m_test->clearActions(); m_test->clearActions();
// with custom attributes // with custom attributes
m_test->setActiveWindowTitle("CustomAttr1"); m_test->setActiveWindowTitle("CustomAttr1");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute")); QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute"));
m_test->clearActions(); m_test->clearActions();
// with (non uppercase) undefined custom attributes // with (non uppercase) undefined custom attributes
m_test->setActiveWindowTitle("CustomAttr2"); m_test->setActiveWindowTitle("CustomAttr2");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("")); QCOMPARE(m_test->actionChars(), QString(""));
m_test->clearActions(); m_test->clearActions();
// with mixedcase default attributes // with mixedcase default attributes
m_test->setActiveWindowTitle("CustomAttr3"); m_test->setActiveWindowTitle("CustomAttr3");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr")); QCOMPARE(m_test->actionChars(), QString("custom_attr"));
m_test->clearActions(); m_test->clearActions();
// with resolve placeholders in window association title // with resolve placeholders in window association title
m_test->setActiveWindowTitle("AttrValueFirst"); m_test->setActiveWindowTitle("AttrValueFirst");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr_first")); QCOMPARE(m_test->actionChars(), QString("custom_attr_first"));
m_test->clearActions(); m_test->clearActions();
m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum"); m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second")); QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second"));
m_test->clearActions(); m_test->clearActions();
m_test->setActiveWindowTitle("lorem AttrValueThird ipsum"); m_test->setActiveWindowTitle("lorem AttrValueThird ipsum");
m_test->triggerGlobalAutoType(); emit osUtils->globalShortcutTriggered("autotype");
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr_third")); QCOMPARE(m_test->actionChars(), QString("custom_attr_third"));
m_test->clearActions(); m_test->clearActions();

View file

@ -70,13 +70,13 @@ void TestCli::initTestCase()
Config::createTempFileInstance(); Config::createTempFileInstance();
Bootstrap::bootstrap(); Bootstrap::bootstrap();
auto fd = new QFile(); m_devNull.reset(new QFile());
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
fd->open(fopen("nul", "w"), QIODevice::WriteOnly); m_devNull->open(fopen("nul", "w"), QIODevice::WriteOnly);
#else #else
fd->open(fopen("/dev/null", "w"), QIODevice::WriteOnly); m_devNull->open(fopen("/dev/null", "w"), QIODevice::WriteOnly);
#endif #endif
Utils::DEVNULL.setDevice(fd); Utils::DEVNULL.setDevice(m_devNull.data());
} }
void TestCli::init() void TestCli::init()
@ -131,6 +131,11 @@ void TestCli::cleanup()
Utils::STDIN.setDevice(nullptr); Utils::STDIN.setDevice(nullptr);
} }
void TestCli::cleanupTestCase()
{
m_devNull.reset();
}
QSharedPointer<Database> TestCli::readDatabase(const QString& filename, const QString& pw, const QString& keyfile) QSharedPointer<Database> TestCli::readDatabase(const QString& filename, const QString& pw, const QString& keyfile)
{ {
auto db = QSharedPointer<Database>::create(); auto db = QSharedPointer<Database>::create();
@ -520,8 +525,7 @@ void TestCli::testClip()
setInput("a"); setInput("a");
execCmd(clipCmd, {"clip", m_dbFile->fileName(), "-a", "TESTAttribute1", "/Sample Entry"}); execCmd(clipCmd, {"clip", m_dbFile->fileName(), "-a", "TESTAttribute1", "/Sample Entry"});
QVERIFY(m_stderr->readAll().contains( QVERIFY(m_stderr->readAll().contains("ERROR: attribute TESTAttribute1 is ambiguous"));
"ERROR: attribute TESTAttribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n"));
setInput("a"); setInput("a");
execCmd(clipCmd, {"clip", m_dbFile2->fileName(), "--attribute", "Username", "--totp", "/Sample Entry"}); execCmd(clipCmd, {"clip", m_dbFile2->fileName(), "--attribute", "Username", "--totp", "/Sample Entry"});
@ -1776,8 +1780,7 @@ void TestCli::testShow()
setInput("a"); setInput("a");
execCmd(showCmd, {"show", m_dbFile->fileName(), "-a", "Testattribute1", "/Sample Entry"}); execCmd(showCmd, {"show", m_dbFile->fileName(), "-a", "Testattribute1", "/Sample Entry"});
QCOMPARE(m_stdout->readAll(), QByteArray()); QCOMPARE(m_stdout->readAll(), QByteArray());
QVERIFY(m_stderr->readAll().contains( QVERIFY(m_stderr->readAll().contains("ERROR: attribute Testattribute1 is ambiguous"));
"ERROR: attribute Testattribute1 is ambiguous, it matches TestAttribute1 and testattribute1.\n"));
} }
void TestCli::testInvalidDbFiles() void TestCli::testInvalidDbFiles()

View file

@ -45,6 +45,7 @@ private slots:
void initTestCase(); void initTestCase();
void init(); void init();
void cleanup(); void cleanup();
void cleanupTestCase();
void testBatchCommands(); void testBatchCommands();
void testAdd(); void testAdd();
@ -81,6 +82,7 @@ private slots:
void testYubiKeyOption(); void testYubiKeyOption();
private: private:
QScopedPointer<QFile> m_devNull;
QScopedPointer<TemporaryFile> m_dbFile; QScopedPointer<TemporaryFile> m_dbFile;
QScopedPointer<TemporaryFile> m_dbFile2; QScopedPointer<TemporaryFile> m_dbFile2;
QScopedPointer<TemporaryFile> m_dbFileMulti; QScopedPointer<TemporaryFile> m_dbFileMulti;