From 753b9c9e67c9aa1553994d65afb837bb32db57e8 Mon Sep 17 00:00:00 2001 From: TheZ3ro Date: Tue, 8 Nov 2016 22:13:57 +0100 Subject: [PATCH] Add Autotype on Windows and MacOS (#63) * Add Autotype on Windows, including Windows 10 * Add MacOS autotype, fix macdeployqt build * Make QT_BINARY_DIR overwritable at compile time --- CMakeLists.txt | 2 +- src/CMakeLists.txt | 16 +- src/autotype/AutoType.cpp | 8 + src/autotype/AutoTypePlatformPlugin.h | 5 + src/autotype/CMakeLists.txt | 11 +- src/autotype/mac/AppKit.h | 42 ++ src/autotype/mac/AppKitImpl.h | 31 ++ src/autotype/mac/AppKitImpl.mm | 102 +++++ src/autotype/mac/AutoTypeMac.cpp | 488 +++++++++++++++++++++ src/autotype/mac/AutoTypeMac.h | 81 ++++ src/autotype/mac/CMakeLists.txt | 20 + src/autotype/test/AutoTypeTest.cpp | 12 + src/autotype/test/AutoTypeTest.h | 5 + src/autotype/windows/AutoTypeWindows.cpp | 529 +++++++++++++++++++++++ src/autotype/windows/AutoTypeWindows.h | 73 ++++ src/autotype/windows/CMakeLists.txt | 9 + src/core/FilePath.cpp | 4 + src/gui/Application.cpp | 21 + src/gui/entry/EntryView.cpp | 4 + 19 files changed, 1454 insertions(+), 9 deletions(-) create mode 100644 src/autotype/mac/AppKit.h create mode 100644 src/autotype/mac/AppKitImpl.h create mode 100644 src/autotype/mac/AppKitImpl.mm create mode 100644 src/autotype/mac/AutoTypeMac.cpp create mode 100644 src/autotype/mac/AutoTypeMac.h create mode 100644 src/autotype/mac/CMakeLists.txt create mode 100644 src/autotype/windows/AutoTypeWindows.cpp create mode 100644 src/autotype/windows/AutoTypeWindows.h create mode 100644 src/autotype/windows/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 56128fe2b..cbef4bc45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,7 +142,7 @@ if(MINGW) set(DATA_INSTALL_DIR "share") elseif(APPLE) set(BIN_INSTALL_DIR ".") - set(PLUGIN_INSTALL_DIR ".") + set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns") set(DATA_INSTALL_DIR "${PROGNAME}.app/Contents/Resources") else() include(GNUInstallDirs) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3d3d3f5f8..0f25ad920 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -216,16 +216,26 @@ if(APPLE) set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION_NUM}") include(CPack) - include(DeployQt4) - install_qt4_executable(${PROGNAME}.app "qjpeg;qgif;qico;qtaccessiblewidgets") + if(NOT DEFINED QT_BINARY_DIR) + set(QT_BINARY_DIR "/usr/local/opt/qt5/bin" CACHE PATH "QT binary folder") + endif() + add_custom_command(TARGET ${PROGNAME} + POST_BUILD + COMMAND ${QT_BINARY_DIR}/macdeployqt ${PROGNAME}.app + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src + COMMENT "Deploying app bundle") endif() -if(MINGW ) +if(MINGW) set(CPACK_GENERATOR "ZIP") set(CPACK_STRIP_FILES ON) set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION_NUM}") include(CPack) + install(CODE " + set(gp_tool \"objdump\") + " COMPONENT Runtime) + include(DeployQt4) install_qt4_executable(${PROGNAME}.exe "qjpeg;qgif;qico;qtaccessiblewidgets") endif() diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 7f14e5b09..e48191902 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -147,7 +147,11 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow, const QS } if (hideWindow) { +#if defined(Q_OS_MAC) + m_plugin->raiseLastActiveWindow(); +#else hideWindow->showMinimized(); +#endif } Tools::wait(m_plugin->initialTimeout()); @@ -220,6 +224,10 @@ void AutoType::performGlobalAutoType(const QList& dbList) SLOT(performAutoTypeFromGlobal(Entry*,QString))); connect(selectDialog, SIGNAL(rejected()), SLOT(resetInAutoType())); selectDialog->setEntries(entryList, sequenceHash); +#if defined(Q_OS_MAC) + m_plugin->raiseOwnWindow(); + Tools::wait(500); +#endif selectDialog->show(); // necessary when the main window is minimized selectDialog->activateWindow(); diff --git a/src/autotype/AutoTypePlatformPlugin.h b/src/autotype/AutoTypePlatformPlugin.h index dadc7a0d2..2945e98c5 100644 --- a/src/autotype/AutoTypePlatformPlugin.h +++ b/src/autotype/AutoTypePlatformPlugin.h @@ -39,6 +39,11 @@ public: virtual AutoTypeExecutor* createExecutor() = 0; +#if defined(Q_OS_MAC) + virtual bool raiseLastActiveWindow() = 0; + virtual bool raiseOwnWindow() = 0; +#endif + // implementations should also provide a globalShortcutTriggered() signal }; diff --git a/src/autotype/CMakeLists.txt b/src/autotype/CMakeLists.txt index 417e7af4c..40dd449db 100644 --- a/src/autotype/CMakeLists.txt +++ b/src/autotype/CMakeLists.txt @@ -10,11 +10,12 @@ if(UNIX AND NOT APPLE) if(X11_FOUND AND X11_Xi_FOUND AND X11_XTest_FOUND AND Qt5X11Extras_FOUND) add_subdirectory(xcb) endif() +elseif(APPLE) + add_subdirectory(mac) +elseif(WIN32) + add_subdirectory(windows) endif() if(WITH_TESTS) - # autotype non supported on Windows right now - if(UNIX) - add_subdirectory(test) - endif() -endif() \ No newline at end of file + add_subdirectory(test) +endif() diff --git a/src/autotype/mac/AppKit.h b/src/autotype/mac/AppKit.h new file mode 100644 index 000000000..114038d63 --- /dev/null +++ b/src/autotype/mac/AppKit.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 Lennart Glauer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_APPKIT_H +#define KEEPASSX_APPKIT_H + +#include + +extern "C" { + +class AppKit +{ +public: + AppKit(); + ~AppKit(); + + pid_t lastActiveProcessId(); + pid_t activeProcessId(); + pid_t ownProcessId(); + bool activateProcess(pid_t pid); + +private: + void *self; +}; + +} // extern "C" + +#endif // KEEPASSX_APPKIT_H diff --git a/src/autotype/mac/AppKitImpl.h b/src/autotype/mac/AppKitImpl.h new file mode 100644 index 000000000..1b57d7b14 --- /dev/null +++ b/src/autotype/mac/AppKitImpl.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 Lennart Glauer + * + * 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 . + */ + +#import "AppKit.h" + +#import +#import + +@interface AppKitImpl : NSObject + +@property (strong) NSRunningApplication *lastActiveApplication; + +- (pid_t) activeProcessId; +- (pid_t) ownProcessId; +- (bool) activateProcess:(pid_t) pid; + +@end diff --git a/src/autotype/mac/AppKitImpl.mm b/src/autotype/mac/AppKitImpl.mm new file mode 100644 index 000000000..ad600cb95 --- /dev/null +++ b/src/autotype/mac/AppKitImpl.mm @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 Lennart Glauer + * + * 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 . + */ + +#import "AppKitImpl.h" + +#import + +@implementation AppKitImpl + +AppKit::AppKit() +{ + self = [[AppKitImpl alloc] init]; + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast(self) + selector:@selector(didDeactivateApplicationObserver:) + name:NSWorkspaceDidDeactivateApplicationNotification + object:nil]; +} + +AppKit::~AppKit() +{ + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast(self)]; + [static_cast(self) dealloc]; +} + +// +// Update last active application property +// +- (void) didDeactivateApplicationObserver:(NSNotification *) notification +{ + NSDictionary *userInfo = notification.userInfo; + NSRunningApplication *app = userInfo[NSWorkspaceApplicationKey]; + + if (app.processIdentifier != [self ownProcessId]) { + self.lastActiveApplication = app; + } +} + +// +// Get process id of frontmost application (-> keyboard input) +// +- (pid_t) activeProcessId +{ + return [NSWorkspace sharedWorkspace].frontmostApplication.processIdentifier; +} + +// +// Get process id of own process +// +- (pid_t) ownProcessId +{ + return [NSProcessInfo processInfo].processIdentifier; +} + +// +// Activate application by process id +// +- (bool) activateProcess:(pid_t) pid +{ + NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; + + return [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; +} + +// +// ------------------------- C++ Trampolines ------------------------- +// + +pid_t AppKit::lastActiveProcessId() +{ + return [static_cast(self) lastActiveApplication].processIdentifier; +} + +pid_t AppKit::activeProcessId() +{ + return [static_cast(self) activeProcessId]; +} + +pid_t AppKit::ownProcessId() +{ + return [static_cast(self) ownProcessId]; +} + +bool AppKit::activateProcess(pid_t pid) +{ + return [static_cast(self) activateProcess:pid]; +} + +@end diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp new file mode 100644 index 000000000..90563a23a --- /dev/null +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2016 Lennart Glauer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "AutoTypeMac.h" + +#include + +#define HOTKEY_ID 1 +#define MAX_WINDOW_TITLE_LENGTH 1024 +#define INVALID_KEYCODE 0xFFFF + +AutoTypePlatformMac::AutoTypePlatformMac() + : m_appkit(new AppKit()) + , m_hotkeyRef(nullptr) + , m_hotkeyId({ 'kpx2', HOTKEY_ID }) +{ + EventTypeSpec eventSpec; + eventSpec.eventClass = kEventClassKeyboard; + eventSpec.eventKind = kEventHotKeyPressed; + + ::InstallApplicationEventHandler(AutoTypePlatformMac::hotkeyHandler, 1, &eventSpec, this, nullptr); +} + +// +// Keepassx requires mac os 10.7 +// +bool AutoTypePlatformMac::isAvailable() +{ + return true; +} + +// +// Get list of visible window titles +// see: Quartz Window Services +// +QStringList AutoTypePlatformMac::windowTitles() +{ + QStringList list; + + CFArrayRef windowList = ::CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID); + if (windowList != nullptr) { + CFIndex count = ::CFArrayGetCount(windowList); + + for (CFIndex i = 0; i < count; i++) { + CFDictionaryRef window = static_cast(::CFArrayGetValueAtIndex(windowList, i)); + if (windowLayer(window) != 0) { + continue; + } + + QString title = windowTitle(window); + if (!title.isEmpty()) { + list.append(title); + } + } + + ::CFRelease(windowList); + } + + return list; +} + +// +// Get active window process id +// +WId AutoTypePlatformMac::activeWindow() +{ + return m_appkit->activeProcessId(); +} + +// +// Get active window title +// see: Quartz Window Services +// +QString AutoTypePlatformMac::activeWindowTitle() +{ + QString title; + + CFArrayRef windowList = ::CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID); + if (windowList != nullptr) { + CFIndex count = ::CFArrayGetCount(windowList); + + for (CFIndex i = 0; i < count; i++) { + CFDictionaryRef window = static_cast(::CFArrayGetValueAtIndex(windowList, i)); + if (windowLayer(window) == 0) { + // First toplevel window in list (front to back order) + title = windowTitle(window); + break; + } + } + + ::CFRelease(windowList); + } + + 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; + } + uint16 nativeModifiers = qtToNativeModifiers(modifiers); + if (::RegisterEventHotKey(nativeKeyCode, nativeModifiers, m_hotkeyId, GetApplicationEventTarget(), 0, &m_hotkeyRef) != noErr) { + qWarning("Register hotkey failed"); + return false; + } + + return true; +} + +// +// Unregister global hotkey +// +void AutoTypePlatformMac::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + Q_UNUSED(key); + Q_UNUSED(modifiers); + + ::UnregisterEventHotKey(m_hotkeyRef); +} + +int AutoTypePlatformMac::platformEventFilter(void* event) +{ + Q_UNUSED(event); + Q_ASSERT(false); + + return -1; +} + +AutoTypeExecutor* AutoTypePlatformMac::createExecutor() +{ + return new AutoTypeExecutorMac(this); +} + +int AutoTypePlatformMac::initialTimeout() +{ + return 500; +} + +// +// Activate window by process id +// +bool AutoTypePlatformMac::raiseWindow(WId pid) +{ + return m_appkit->activateProcess(pid); +} + +// +// Activate last active window +// +bool AutoTypePlatformMac::raiseLastActiveWindow() +{ + return m_appkit->activateProcess(m_appkit->lastActiveProcessId()); +} + +// +// Activate keepassx window +// +bool AutoTypePlatformMac::raiseOwnWindow() +{ + return m_appkit->activateProcess(m_appkit->ownProcessId()); +} + +// +// Send unicode character to active window +// see: Quartz Event Services +// +void AutoTypePlatformMac::sendChar(const QChar& ch, bool isKeyDown) +{ + CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, 0, isKeyDown); + if (keyEvent != nullptr) { + UniChar unicode = ch.unicode(); + ::CGEventKeyboardSetUnicodeString(keyEvent, 1, &unicode); + ::CGEventPost(kCGSessionEventTap, keyEvent); + ::CFRelease(keyEvent); + } +} + +// +// Send key code to active window +// see: Quartz Event Services +// +void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown) +{ + uint16 keyCode = qtToNativeKeyCode(key); + if (keyCode == INVALID_KEYCODE) { + return; + } + + CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); + if (keyEvent != nullptr) { + ::CGEventPost(kCGSessionEventTap, keyEvent); + ::CFRelease(keyEvent); + } +} + +// +// 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_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 +// +uint16 AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modifiers) +{ + uint16 nativeModifiers = 0; + + if (modifiers & Qt::ShiftModifier) { + nativeModifiers |= shiftKey; + } + if (modifiers & Qt::ControlModifier) { + nativeModifiers |= cmdKey; + } + if (modifiers & Qt::AltModifier) { + nativeModifiers |= optionKey; + } + if (modifiers & Qt::MetaModifier) { + nativeModifiers |= controlKey; + } + + return nativeModifiers; +} + +// +// Get window layer/level +// +int AutoTypePlatformMac::windowLayer(CFDictionaryRef window) +{ + int layer; + + CFNumberRef layerRef = static_cast(::CFDictionaryGetValue(window, kCGWindowLayer)); + if (layerRef != nullptr + && ::CFNumberGetValue(layerRef, kCFNumberIntType, &layer)) { + return layer; + } + + return -1; +} + +// +// Get window title +// +QString AutoTypePlatformMac::windowTitle(CFDictionaryRef window) +{ + char buffer[MAX_WINDOW_TITLE_LENGTH]; + QString title; + + CFStringRef titleRef = static_cast(::CFDictionaryGetValue(window, kCGWindowName)); + if (titleRef != nullptr + && ::CFStringGetCString(titleRef, buffer, MAX_WINDOW_TITLE_LENGTH, kCFStringEncodingUTF8)) { + title = QString::fromUtf8(buffer); + } + + return title; +} + +// +// Carbon hotkey handler +// +OSStatus AutoTypePlatformMac::hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) +{ + Q_UNUSED(nextHandler); + + AutoTypePlatformMac *self = static_cast(userData); + EventHotKeyID hotkeyId; + + if (::GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, nullptr, sizeof(hotkeyId), nullptr, &hotkeyId) == noErr + && hotkeyId.id == HOTKEY_ID) { + Q_EMIT self->globalShortcutTriggered(); + } + + return noErr; +} + +// +// ------------------------------ AutoTypeExecutorMac ------------------------------ +// + +AutoTypeExecutorMac::AutoTypeExecutorMac(AutoTypePlatformMac* platform) + : m_platform(platform) +{ +} + +void AutoTypeExecutorMac::execChar(AutoTypeChar* action) +{ + m_platform->sendChar(action->character, true); + m_platform->sendChar(action->character, false); + usleep(25 * 1000); +} + +void AutoTypeExecutorMac::execKey(AutoTypeKey* action) +{ + m_platform->sendKey(action->key, true); + m_platform->sendKey(action->key, false); + usleep(25 * 1000); +} diff --git a/src/autotype/mac/AutoTypeMac.h b/src/autotype/mac/AutoTypeMac.h new file mode 100644 index 000000000..475a4b99b --- /dev/null +++ b/src/autotype/mac/AutoTypeMac.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 Lennart Glauer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_AUTOTYPEMAC_H +#define KEEPASSX_AUTOTYPEMAC_H + +#include +#include +#include + +#include "AppKit.h" +#include "autotype/AutoTypePlatformPlugin.h" +#include "autotype/AutoTypeAction.h" + +class AutoTypePlatformMac : public QObject, public AutoTypePlatformInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.keepassx.AutoTypePlatformMac") + Q_INTERFACES(AutoTypePlatformInterface) + +public: + AutoTypePlatformMac(); + bool isAvailable() override; + QStringList windowTitles() override; + WId activeWindow() override; + QString activeWindowTitle() override; + bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; + void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; + int platformEventFilter(void* event) override; + int initialTimeout() override; + bool raiseWindow(WId pid) override; + AutoTypeExecutor* createExecutor() override; + + bool raiseLastActiveWindow() override; + bool raiseOwnWindow() override; + + void sendChar(const QChar& ch, bool isKeyDown); + void sendKey(Qt::Key key, bool isKeyDown); + +Q_SIGNALS: + void globalShortcutTriggered(); + +private: + std::unique_ptr m_appkit; + EventHotKeyRef m_hotkeyRef; + EventHotKeyID m_hotkeyId; + + static uint16 qtToNativeKeyCode(Qt::Key key); + static uint16 qtToNativeModifiers(Qt::KeyboardModifiers modifiers); + static int windowLayer(CFDictionaryRef window); + static QString windowTitle(CFDictionaryRef window); + static OSStatus hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData); +}; + +class AutoTypeExecutorMac : public AutoTypeExecutor +{ +public: + explicit AutoTypeExecutorMac(AutoTypePlatformMac* platform); + + void execChar(AutoTypeChar* action) override; + void execKey(AutoTypeKey* action) override; + +private: + AutoTypePlatformMac* const m_platform; +}; + +#endif // KEEPASSX_AUTOTYPEMAC_H diff --git a/src/autotype/mac/CMakeLists.txt b/src/autotype/mac/CMakeLists.txt new file mode 100644 index 000000000..076dd5038 --- /dev/null +++ b/src/autotype/mac/CMakeLists.txt @@ -0,0 +1,20 @@ +set(autotype_mac_SOURCES + AutoTypeMac.cpp +) + +set(autotype_mac_mm_SOURCES + AppKitImpl.mm +) + +add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES} ${autotype_mac_mm_SOURCES}) +set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon") +target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets) +if(NOT DEFINED QT_BINARY_DIR) + set(QT_BINARY_DIR "/usr/local/opt/qt5/bin" CACHE PATH "QT binary folder") +endif() +add_custom_command(TARGET keepassx-autotype-cocoa + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR} + COMMAND ${QT_BINARY_DIR}/macdeployqt ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src +COMMENT "Deploying autotype plugin") diff --git a/src/autotype/test/AutoTypeTest.cpp b/src/autotype/test/AutoTypeTest.cpp index 979af8bde..1ac815b0e 100644 --- a/src/autotype/test/AutoTypeTest.cpp +++ b/src/autotype/test/AutoTypeTest.cpp @@ -115,6 +115,18 @@ bool AutoTypePlatformTest::raiseWindow(WId window) return false; } +#if defined(Q_OS_MAC) +bool AutoTypePlatformTest::raiseLastActiveWindow() +{ + return false; +} + +bool AutoTypePlatformTest::raiseOwnWindow() +{ + return false; +} +#endif + AutoTypeExecturorTest::AutoTypeExecturorTest(AutoTypePlatformTest* platform) : m_platform(platform) { diff --git a/src/autotype/test/AutoTypeTest.h b/src/autotype/test/AutoTypeTest.h index 8c6e52443..33487778a 100644 --- a/src/autotype/test/AutoTypeTest.h +++ b/src/autotype/test/AutoTypeTest.h @@ -46,6 +46,11 @@ public: bool raiseWindow(WId window) override; AutoTypeExecutor* createExecutor() override; +#if defined(Q_OS_MAC) + bool raiseLastActiveWindow() override; + bool raiseOwnWindow() override; +#endif + void setActiveWindowTitle(const QString& title) override; QString actionChars() override; diff --git a/src/autotype/windows/AutoTypeWindows.cpp b/src/autotype/windows/AutoTypeWindows.cpp new file mode 100644 index 000000000..481caa83f --- /dev/null +++ b/src/autotype/windows/AutoTypeWindows.cpp @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2016 Lennart Glauer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "AutoTypeWindows.h" + +#include + +#define HOTKEY_ID 1 +#define MAX_WINDOW_TITLE_LENGTH 1024 + +#define MOD_NOREPEAT 0x4000 // Missing in MinGW + +// +// Test if os version is Windows 7 or later +// +bool AutoTypePlatformWin::isAvailable() +{ + return IsWindows7OrGreater(); +} + +// +// Get list of all visible window titles +// +QStringList AutoTypePlatformWin::windowTitles() +{ + QStringList list; + + ::EnumWindows(AutoTypePlatformWin::windowTitleEnumProc, reinterpret_cast(&list)); + + return list; +} + +// +// Get foreground window hwnd +// +WId AutoTypePlatformWin::activeWindow() +{ + return reinterpret_cast(::GetForegroundWindow()); +} + +// +// Get foreground window title +// +QString AutoTypePlatformWin::activeWindowTitle() +{ + return windowTitle(::GetForegroundWindow()); +} + +// +// Register global hotkey +// +bool AutoTypePlatformWin::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + DWORD nativeKeyCode = qtToNativeKeyCode(key); + if (nativeKeyCode < 1 || nativeKeyCode > 254) { + return false; + } + DWORD nativeModifiers = qtToNativeModifiers(modifiers); + if (!::RegisterHotKey(nullptr, HOTKEY_ID, nativeModifiers | MOD_NOREPEAT, nativeKeyCode)) { + return false; + } + + return true; +} + +// +// Unregister global hotkey +// +void AutoTypePlatformWin::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + Q_UNUSED(key); + Q_UNUSED(modifiers); + + ::UnregisterHotKey(nullptr, HOTKEY_ID); +} + +// +// Native event filter +// +int AutoTypePlatformWin::platformEventFilter(void* event) +{ + MSG *msg = static_cast(event); + + if (msg->message == WM_HOTKEY && msg->wParam == HOTKEY_ID) { + Q_EMIT globalShortcutTriggered(); + return 1; + } + + return -1; +} + +AutoTypeExecutor* AutoTypePlatformWin::createExecutor() +{ + return new AutoTypeExecutorWin(this); +} + +int AutoTypePlatformWin::initialTimeout() +{ + return 500; +} + +// +// Set foreground window +// +bool AutoTypePlatformWin::raiseWindow(WId window) +{ + HWND hwnd = reinterpret_cast(window); + + return ::BringWindowToTop(hwnd) && ::SetForegroundWindow(hwnd); +} + +// +// Send unicode character to foreground window +// +void AutoTypePlatformWin::sendChar(const QChar& ch, bool isKeyDown) +{ + DWORD nativeFlags = KEYEVENTF_UNICODE; + if (!isKeyDown) { + nativeFlags |= KEYEVENTF_KEYUP; + } + + INPUT in; + in.type = INPUT_KEYBOARD; + in.ki.wVk = 0; + in.ki.wScan = ch.unicode(); + in.ki.dwFlags = nativeFlags; + in.ki.time = 0; + in.ki.dwExtraInfo = ::GetMessageExtraInfo(); + + ::SendInput(1, &in, sizeof(INPUT)); +} + +// +// Send virtual key code to foreground window +// +void AutoTypePlatformWin::sendKey(Qt::Key key, bool isKeyDown) +{ + DWORD nativeKeyCode = qtToNativeKeyCode(key); + if (nativeKeyCode < 1 || nativeKeyCode > 254) { + return; + } + DWORD nativeFlags = 0; + if (isExtendedKey(nativeKeyCode)) { + nativeFlags |= KEYEVENTF_EXTENDEDKEY; + } + if (!isKeyDown) { + nativeFlags |= KEYEVENTF_KEYUP; + } + + INPUT in; + in.type = INPUT_KEYBOARD; + in.ki.wVk = LOWORD(nativeKeyCode); + in.ki.wScan = LOWORD(::MapVirtualKeyW(nativeKeyCode, MAPVK_VK_TO_VSC)); + in.ki.dwFlags = nativeFlags; + in.ki.time = 0; + in.ki.dwExtraInfo = ::GetMessageExtraInfo(); + + ::SendInput(1, &in, sizeof(INPUT)); +} + +// +// 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_Pause: + return VK_PAUSE; // 0x13 + case Qt::Key_CapsLock: + return VK_CAPITAL; // 0x14 + case Qt::Key_Escape: + return VK_ESCAPE; // 0x1B + case Qt::Key_Space: + return VK_SPACE; // 0x20 + case Qt::Key_PageUp: + return VK_PRIOR; // 0x21 + case Qt::Key_PageDown: + return VK_NEXT; // 0x22 + case Qt::Key_End: + return VK_END; // 0x23 + case Qt::Key_Home: + return VK_HOME; // 0x24 + case Qt::Key_Left: + return VK_LEFT; // 0x25 + case Qt::Key_Up: + return VK_UP; // 0x26 + case Qt::Key_Right: + return VK_RIGHT; // 0x27 + case Qt::Key_Down: + return VK_DOWN; // 0x28 + case Qt::Key_Print: + return VK_SNAPSHOT; // 0x2C + case Qt::Key_Insert: + return VK_INSERT; // 0x2D + case Qt::Key_Delete: + return VK_DELETE; // 0x2E + case Qt::Key_Help: + return VK_HELP; // 0x2F + + case Qt::Key_0: + return 0x30; // 0x30 + case Qt::Key_1: + return 0x31; // 0x31 + case Qt::Key_2: + return 0x32; // 0x32 + case Qt::Key_3: + return 0x33; // 0x33 + case Qt::Key_4: + return 0x34; // 0x34 + case Qt::Key_5: + return 0x35; // 0x35 + case Qt::Key_6: + return 0x36; // 0x36 + case Qt::Key_7: + return 0x37; // 0x37 + case Qt::Key_8: + return 0x38; // 0x38 + case Qt::Key_9: + return 0x39; // 0x39 + + case Qt::Key_A: + return 0x41; // 0x41 + case Qt::Key_B: + return 0x42; // 0x42 + case Qt::Key_C: + return 0x43; // 0x43 + case Qt::Key_D: + return 0x44; // 0x44 + case Qt::Key_E: + return 0x45; // 0x45 + case Qt::Key_F: + return 0x46; // 0x46 + case Qt::Key_G: + return 0x47; // 0x47 + case Qt::Key_H: + return 0x48; // 0x48 + case Qt::Key_I: + return 0x49; // 0x49 + case Qt::Key_J: + return 0x4A; // 0x4A + case Qt::Key_K: + return 0x4B; // 0x4B + case Qt::Key_L: + return 0x4C; // 0x4C + case Qt::Key_M: + return 0x4D; // 0x4D + case Qt::Key_N: + return 0x4E; // 0x4E + case Qt::Key_O: + return 0x4F; // 0x4F + case Qt::Key_P: + return 0x50; // 0x50 + case Qt::Key_Q: + return 0x51; // 0x51 + case Qt::Key_R: + return 0x52; // 0x52 + case Qt::Key_S: + return 0x53; // 0x53 + case Qt::Key_T: + return 0x54; // 0x54 + case Qt::Key_U: + return 0x55; // 0x55 + case Qt::Key_V: + return 0x56; // 0x56 + case Qt::Key_W: + return 0x57; // 0x57 + case Qt::Key_X: + return 0x58; // 0x58 + case Qt::Key_Y: + return 0x59; // 0x59 + case Qt::Key_Z: + return 0x5A; // 0x5A + + case Qt::Key_F1: + return VK_F1; // 0x70 + case Qt::Key_F2: + return VK_F2; // 0x71 + case Qt::Key_F3: + return VK_F3; // 0x72 + case Qt::Key_F4: + return VK_F4; // 0x73 + case Qt::Key_F5: + return VK_F5; // 0x74 + case Qt::Key_F6: + return VK_F6; // 0x75 + case Qt::Key_F7: + return VK_F7; // 0x76 + case Qt::Key_F8: + return VK_F8; // 0x77 + case Qt::Key_F9: + return VK_F9; // 0x78 + case Qt::Key_F10: + return VK_F10; // 0x79 + case Qt::Key_F11: + return VK_F11; // 0x7A + case Qt::Key_F12: + return VK_F12; // 0x7B + case Qt::Key_F13: + return VK_F13; // 0x7C + case Qt::Key_F14: + return VK_F14; // 0x7D + case Qt::Key_F15: + return VK_F15; // 0x7E + case Qt::Key_F16: + return VK_F16; // 0x7F + case Qt::Key_F17: + return VK_F17; // 0x80 + case Qt::Key_F18: + return VK_F18; // 0x81 + case Qt::Key_F19: + return VK_F19; // 0x82 + case Qt::Key_F20: + return VK_F20; // 0x83 + case Qt::Key_F21: + return VK_F21; // 0x84 + case Qt::Key_F22: + return VK_F22; // 0x85 + case Qt::Key_F23: + return VK_F23; // 0x86 + case Qt::Key_F24: + return VK_F24; // 0x87 + + case Qt::Key_NumLock: + return VK_NUMLOCK; // 0x90 + case Qt::Key_ScrollLock: + return VK_SCROLL; // 0x91 + + case Qt::Key_Exclam: // ! + case Qt::Key_QuoteDbl: // " + case Qt::Key_NumberSign: // # + case Qt::Key_Dollar: // $ + case Qt::Key_Percent: // % + case Qt::Key_Ampersand: // & + case Qt::Key_Apostrophe: // ' + case Qt::Key_ParenLeft: // ( + case Qt::Key_ParenRight: // ) + case Qt::Key_Asterisk: // * + case Qt::Key_Plus: // + + case Qt::Key_Comma: // , + case Qt::Key_Minus: // - + case Qt::Key_Period: // . + case Qt::Key_Slash: // / + case Qt::Key_Colon: // : + case Qt::Key_Semicolon: // ; + case Qt::Key_Less: // < + case Qt::Key_Equal: // = + case Qt::Key_Greater: // > + case Qt::Key_Question: // ? + case Qt::Key_BracketLeft: // [ + case Qt::Key_Backslash: // '\' + case Qt::Key_BracketRight: // ] + case Qt::Key_AsciiCircum: // ^ + case Qt::Key_Underscore: // _ + case Qt::Key_QuoteLeft: // ` + case Qt::Key_BraceLeft: // { + case Qt::Key_Bar: // | + case Qt::Key_BraceRight: // } + case Qt::Key_AsciiTilde: // ~ + return LOBYTE(::VkKeyScanExW(key, ::GetKeyboardLayout(0))); + + default: + Q_ASSERT(false); + return 0; + } +} + +// +// The extended-key flag indicates whether the keystroke message originated +// from one of the additional keys on the enhanced keyboard +// see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646267%28v=vs.85%29.aspx#EXTENDED_KEY_FLAG +// +BOOL AutoTypePlatformWin::isExtendedKey(DWORD nativeKeyCode) +{ + switch (nativeKeyCode) { + case VK_RMENU: + case VK_RCONTROL: + case VK_INSERT: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_PRIOR: + case VK_NEXT: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + case VK_NUMLOCK: + case VK_CANCEL: + case VK_SNAPSHOT: + case VK_DIVIDE: + case VK_LWIN: + case VK_RWIN: + case VK_APPS: + return TRUE; + default: + return FALSE; + } +} + +// +// Translate qt key modifiers to windows modifiers +// +DWORD AutoTypePlatformWin::qtToNativeModifiers(Qt::KeyboardModifiers modifiers) +{ + DWORD nativeModifiers = 0; + + if (modifiers & Qt::ShiftModifier) { + nativeModifiers |= MOD_SHIFT; + } + if (modifiers & Qt::ControlModifier) { + nativeModifiers |= MOD_CONTROL; + } + if (modifiers & Qt::AltModifier) { + nativeModifiers |= MOD_ALT; + } + if (modifiers & Qt::MetaModifier) { + nativeModifiers |= MOD_WIN; + } + + return nativeModifiers; +} + +// +// Test if window is in Alt+Tab list +// see: https://blogs.msdn.microsoft.com/oldnewthing/20071008-00/?p=24863 +// +BOOL AutoTypePlatformWin::isAltTabWindow(HWND hwnd) +{ + if (!::IsWindowVisible(hwnd)) { + return FALSE; + } + + // Start at the root owner + HWND hwndWalk = ::GetAncestor(hwnd, GA_ROOTOWNER); + HWND hwndTry; + + // See if we are the last active visible popup + while ((hwndTry = ::GetLastActivePopup(hwndWalk)) != hwndWalk) { + if (::IsWindowVisible(hwndTry)) { + break; + } + hwndWalk = hwndTry; + } + + return hwndWalk == hwnd; +} + +// +// Window title enum proc +// +BOOL CALLBACK AutoTypePlatformWin::windowTitleEnumProc( + _In_ HWND hwnd, + _In_ LPARAM lParam +) +{ + if (!isAltTabWindow(hwnd)) { + // Skip window + return TRUE; + } + + QStringList *list = reinterpret_cast(lParam); + QString title = windowTitle(hwnd); + + if (!title.isEmpty()) { + list->append(title); + } + + return TRUE; +} + +// +// Get window title +// +QString AutoTypePlatformWin::windowTitle(HWND hwnd) +{ + wchar_t title[MAX_WINDOW_TITLE_LENGTH]; + int count = ::GetWindowTextW(hwnd, title, MAX_WINDOW_TITLE_LENGTH); + + return QString::fromUtf16(reinterpret_cast(title), count); +} + +// +// ------------------------------ AutoTypeExecutorWin ------------------------------ +// + +AutoTypeExecutorWin::AutoTypeExecutorWin(AutoTypePlatformWin* platform) + : m_platform(platform) +{ +} + +void AutoTypeExecutorWin::execChar(AutoTypeChar* action) +{ + m_platform->sendChar(action->character, true); + m_platform->sendChar(action->character, false); + ::Sleep(25); +} + +void AutoTypeExecutorWin::execKey(AutoTypeKey* action) +{ + m_platform->sendKey(action->key, true); + m_platform->sendKey(action->key, false); + ::Sleep(25); +} + diff --git a/src/autotype/windows/AutoTypeWindows.h b/src/autotype/windows/AutoTypeWindows.h new file mode 100644 index 000000000..7a8c4bcab --- /dev/null +++ b/src/autotype/windows/AutoTypeWindows.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 Lennart Glauer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_AUTOTYPEWINDOWS_H +#define KEEPASSX_AUTOTYPEWINDOWS_H + +#include +#include + +#include "autotype/AutoTypePlatformPlugin.h" +#include "autotype/AutoTypeAction.h" + +class AutoTypePlatformWin : public QObject, public AutoTypePlatformInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.keepassx.AutoTypePlatformWindows") + Q_INTERFACES(AutoTypePlatformInterface) + +public: + bool isAvailable() override; + QStringList windowTitles() override; + WId activeWindow() override; + QString activeWindowTitle() override; + bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; + void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override; + int platformEventFilter(void* event) override; + int initialTimeout() override; + bool raiseWindow(WId window) override; + AutoTypeExecutor* createExecutor() override; + + void sendChar(const QChar& ch, bool isKeyDown); + void sendKey(Qt::Key key, bool isKeyDown); + +Q_SIGNALS: + void globalShortcutTriggered(); + +private: + static DWORD qtToNativeKeyCode(Qt::Key key); + static DWORD qtToNativeModifiers(Qt::KeyboardModifiers modifiers); + static BOOL isExtendedKey(DWORD nativeKeyCode); + static BOOL isAltTabWindow(HWND hwnd); + static BOOL CALLBACK windowTitleEnumProc(_In_ HWND hwnd, _In_ LPARAM lParam); + static QString windowTitle(HWND hwnd); +}; + +class AutoTypeExecutorWin : public AutoTypeExecutor +{ +public: + explicit AutoTypeExecutorWin(AutoTypePlatformWin* platform); + + void execChar(AutoTypeChar* action) override; + void execKey(AutoTypeKey* action) override; + +private: + AutoTypePlatformWin* const m_platform; +}; + +#endif // KEEPASSX_AUTOTYPEWINDOWS_H + diff --git a/src/autotype/windows/CMakeLists.txt b/src/autotype/windows/CMakeLists.txt new file mode 100644 index 000000000..dac8a7867 --- /dev/null +++ b/src/autotype/windows/CMakeLists.txt @@ -0,0 +1,9 @@ +set(autotype_win_SOURCES + AutoTypeWindows.cpp +) + +add_library(keepassx-autotype-windows MODULE ${autotype_win_SOURCES}) +target_link_libraries(keepassx-autotype-windows ${PROGNAME} Qt5::Core Qt5::Widgets) +install(TARGETS keepassx-autotype-windows + BUNDLE DESTINATION . COMPONENT Runtime +LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) \ No newline at end of file diff --git a/src/core/FilePath.cpp b/src/core/FilePath.cpp index 021234a11..24c9e3528 100644 --- a/src/core/FilePath.cpp +++ b/src/core/FilePath.cpp @@ -49,6 +49,10 @@ QString FilePath::pluginPath(const QString& name) // for TestAutoType pluginPaths << QCoreApplication::applicationDirPath() + "/../src/autotype/test"; +#if defined(Q_OS_MAC) + pluginPaths << QCoreApplication::applicationDirPath() + "/../PlugIns"; +#endif + pluginPaths << QCoreApplication::applicationDirPath(); QString configuredPluginDir = KEEPASSX_PLUGIN_DIR; diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 70550c557..d982f22ca 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -41,6 +41,25 @@ public: return false; } }; +#elif defined(Q_OS_WIN) +class WinEventFilter : public QAbstractNativeEventFilter +{ +public: + bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override + { + Q_UNUSED(result); + + if (eventType == QByteArrayLiteral("windows_generic_MSG") + || eventType == QByteArrayLiteral("windows_dispatcher_MSG")) { + int retCode = autoType()->callEventFilter(message); + if (retCode == 1) { + return true; + } + } + + return false; + } +}; #endif Application::Application(int& argc, char** argv) @@ -49,6 +68,8 @@ Application::Application(int& argc, char** argv) { #if defined(Q_OS_UNIX) && !defined(Q_OS_OSX) installNativeEventFilter(new XcbEventFilter()); +#elif defined(Q_OS_WIN) + installNativeEventFilter(new WinEventFilter()); #endif } diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index 1c900a578..d692c23c6 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -55,6 +55,10 @@ void EntryView::keyPressEvent(QKeyEvent* event) { if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && currentIndex().isValid()) { emitEntryActivated(currentIndex()); +#ifdef Q_OS_MAC + // Pressing return does not emit the QTreeView::activated signal on mac os + Q_EMIT activated(currentIndex()); +#endif } QTreeView::keyPressEvent(event);