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
This commit is contained in:
TheZ3ro 2016-11-08 22:13:57 +01:00 committed by Jonathan White
parent e25cd9ba48
commit 753b9c9e67
19 changed files with 1454 additions and 9 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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<Database*>& 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();

View File

@ -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
};

View File

@ -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()

42
src/autotype/mac/AppKit.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2016 Lennart Glauer <mail@lennart-glauer.de>
*
* 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 KEEPASSX_APPKIT_H
#define KEEPASSX_APPKIT_H
#include <unistd.h>
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

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2016 Lennart Glauer <mail@lennart-glauer.de>
*
* 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/>.
*/
#import "AppKit.h"
#import <Foundation/Foundation.h>
#import <AppKit/NSRunningApplication.h>
@interface AppKitImpl : NSObject
@property (strong) NSRunningApplication *lastActiveApplication;
- (pid_t) activeProcessId;
- (pid_t) ownProcessId;
- (bool) activateProcess:(pid_t) pid;
@end

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2016 Lennart Glauer <mail@lennart-glauer.de>
*
* 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/>.
*/
#import "AppKitImpl.h"
#import <AppKit/NSWorkspace.h>
@implementation AppKitImpl
AppKit::AppKit()
{
self = [[AppKitImpl alloc] init];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
selector:@selector(didDeactivateApplicationObserver:)
name:NSWorkspaceDidDeactivateApplicationNotification
object:nil];
}
AppKit::~AppKit()
{
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)];
[static_cast<id>(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<id>(self) lastActiveApplication].processIdentifier;
}
pid_t AppKit::activeProcessId()
{
return [static_cast<id>(self) activeProcessId];
}
pid_t AppKit::ownProcessId()
{
return [static_cast<id>(self) ownProcessId];
}
bool AppKit::activateProcess(pid_t pid)
{
return [static_cast<id>(self) activateProcess:pid];
}
@end

View File

@ -0,0 +1,488 @@
/*
* Copyright (C) 2016 Lennart Glauer <mail@lennart-glauer.de>
*
* 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 "AutoTypeMac.h"
#include <ApplicationServices/ApplicationServices.h>
#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<CFDictionaryRef>(::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<CFDictionaryRef>(::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<CFNumberRef>(::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<CFStringRef>(::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<AutoTypePlatformMac *>(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);
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2016 Lennart Glauer <mail@lennart-glauer.de>
*
* 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 KEEPASSX_AUTOTYPEMAC_H
#define KEEPASSX_AUTOTYPEMAC_H
#include <Carbon/Carbon.h>
#include <QtPlugin>
#include <memory>
#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<AppKit> 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

View File

@ -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")

View File

@ -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)
{

View File

@ -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;

View File

@ -0,0 +1,529 @@
/*
* Copyright (C) 2016 Lennart Glauer <mail@lennart-glauer.de>
*
* 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 "AutoTypeWindows.h"
#include <VersionHelpers.h>
#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<LPARAM>(&list));
return list;
}
//
// Get foreground window hwnd
//
WId AutoTypePlatformWin::activeWindow()
{
return reinterpret_cast<WId>(::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<MSG *>(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<HWND>(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<QStringList *>(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<const ushort *>(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);
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2016 Lennart Glauer <mail@lennart-glauer.de>
*
* 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 KEEPASSX_AUTOTYPEWINDOWS_H
#define KEEPASSX_AUTOTYPEWINDOWS_H
#include <QtPlugin>
#include <Windows.h>
#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

View File

@ -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)

View File

@ -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;

View File

@ -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
}

View File

@ -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);