Added global autotype support for OSX.

This commit is contained in:
Keith Bennett 2014-03-22 16:29:16 +00:00
parent 6ef5f34070
commit b87097a7ab
4 changed files with 678 additions and 0 deletions

View File

@ -9,6 +9,10 @@ if(Q_WS_X11)
endif()
endif()
if(Q_WS_MAC)
add_subdirectory(mac)
endif()
if(WITH_TESTS)
add_subdirectory(test)
endif()

View File

@ -0,0 +1,551 @@
/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.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 <map>
static pid_t keepassxPID2;
#define UNICODE_BUFFER_SIZE 1
static UniChar unicodeBuffer[UNICODE_BUFFER_SIZE];
static UniCharCount unicodePtr = 0;
// reusable events
static CGEventRef unicodeEvent = CGEventCreateKeyboardEvent(NULL, 0, true);
CGEventRef AutoTypePlatformMac::keyEvent =
CGEventCreateKeyboardEvent(NULL, 0, true);
bool AutoTypePlatformMac::inHotKeyEvent = false;
static const KeycodeWithMods NoKeycodeWithMods =
(KeycodeWithMods){ NoKeycode, 0 };
static uint orderedModifiers[] = {
0,
( shiftKey ) >> 8,
(controlKey ) >> 8,
( optionKey ) >> 8,
( cmdKey ) >> 8,
( shiftKey | controlKey ) >> 8,
( shiftKey | optionKey ) >> 8,
( shiftKey | cmdKey ) >> 8,
(controlKey | optionKey ) >> 8,
(controlKey | cmdKey ) >> 8,
( optionKey | cmdKey ) >> 8,
( shiftKey | controlKey | optionKey ) >> 8,
( shiftKey | controlKey | cmdKey ) >> 8,
( shiftKey | optionKey | cmdKey ) >> 8,
(controlKey | optionKey | cmdKey ) >> 8,
( shiftKey | controlKey | optionKey | cmdKey) >> 8
};
static std::map<uint,KeycodeWithMods> unicodeToKeycodeWithModsMap;
AutoTypePlatformMac::AutoTypePlatformMac() : first(true), keepassxPID(0)
{
globalKey = 0;
globalMod = 0;
inGlobalAutoType = false;
// initialize hot key handling
hotKeyRef = NULL;
hotKeyID.signature = 'kpsx';
hotKeyID.id = 1;
EventTypeSpec eventType;
eventType.eventClass = kEventClassKeyboard;
eventType.eventKind = kEventHotKeyPressed;
InstallApplicationEventHandler(&hotKeyHandler, 1, &eventType, this, NULL);
AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap();
}
QStringList AutoTypePlatformMac::windowTitles()
{
pid_t pid;
return getTargetWindowInfo(&pid, NULL);
}
WId AutoTypePlatformMac::activeWindow()
{
WId mid;
pid_t pid;
getTargetWindowInfo(&pid, &mid);
AutoTypePlatformMac::processToFront(pid);
if (first)
first = false;
return mid;
}
QString AutoTypePlatformMac::activeWindowTitle()
{
QStringList sl = getTargetWindowInfo(NULL, NULL);
return sl[0];
}
bool AutoTypePlatformMac::registerGlobalShortcut(Qt::Key key,
Qt::KeyboardModifiers modifiers)
{
if (key == 0)
return false;
//KeycodeWithMods kc = keyToKeycodeWithMods(key);
KeycodeWithMods kc = AutoTypePlatformMac::keysymToKeycodeWithMods2(static_cast<KeySym>(key));
int code = kc.keycode;
uint mod = qtToNativeModifiers(modifiers);
if (code==globalKey && mod==globalMod)
return true;
// need to unregister old before registering new
unregisterGlobalShortcut(key, modifiers);
OSStatus status = RegisterEventHotKey(code, mod, hotKeyID,
GetApplicationEventTarget(), 0, &hotKeyRef);
if (noErr == status) {
globalKey = code;
globalMod = mod;
return true;
} else {
qWarning("Error registering global shortcut: %d", status);
RegisterEventHotKey(globalKey, globalMod, hotKeyID,
GetApplicationEventTarget(), 0, &hotKeyRef);
return false;
}
}
void AutoTypePlatformMac::unregisterGlobalShortcut(Qt::Key,
Qt::KeyboardModifiers)
{
globalKey = 0;
globalMod = 0;
UnregisterEventHotKey(hotKeyRef);
}
int AutoTypePlatformMac::platformEventFilter(void* event)
{
return -1;
}
int AutoTypePlatformMac::initialTimeout()
{
first = true;
return 500;
}
AutoTypeExecutor* AutoTypePlatformMac::createExecutor()
{
return new AutoTypeExecturorMac(this);
}
void AutoTypePlatformMac::sendUnicode(KeySym keysym)
{
if (onlySendKeycodes) {
KeycodeWithMods keycodeWithMods =
AutoTypePlatformMac::keysymToKeycodeWithMods2(keysym);
if (NoKeycode == keycodeWithMods.keycode) return;
sendKeycode(keycodeWithMods);
return;
}
unicodeBuffer[unicodePtr++] = keysym;
if (UNICODE_BUFFER_SIZE == unicodePtr) flushUnicode();
}
void AutoTypePlatformMac::sendKeycode(KeycodeWithMods keycodeWithMods)
{
flushUnicode();
uint keycode = keycodeWithMods.keycode;
uint mods = keycodeWithMods.mods << 8;
uint flags = 0;
if (0 != ( shiftKey & mods)) flags |= kCGEventFlagMaskShift;
if (0 != (controlKey & mods)) flags |= kCGEventFlagMaskControl;
if (0 != ( optionKey & mods)) flags |= kCGEventFlagMaskAlternate;
if (0 != ( cmdKey & mods)) flags |= kCGEventFlagMaskCommand;
CGEventSetIntegerValueField(keyEvent, kCGKeyboardEventKeycode, keycode);
CGEventSetFlags(AutoTypePlatformMac::keyEvent, flags);
keyDownUp(AutoTypePlatformMac::keyEvent);
sleepKeyStrokeDelay();
}
KeycodeWithMods AutoTypePlatformMac::keyToKeycodeWithMods(Qt::Key key)
{
KeycodeWithMods kc;
kc.mods = 0;
switch (key) {
case Qt::Key_Tab:
kc.keycode = kVK_Tab;
break;
case Qt::Key_Enter:
kc.keycode = kVK_Return;
break;
case Qt::Key_Up:
kc.keycode = kVK_UpArrow;
break;
case Qt::Key_Down:
kc.keycode = kVK_DownArrow;
break;
case Qt::Key_Left:
kc.keycode = kVK_LeftArrow;
break;
case Qt::Key_Right:
kc.keycode = kVK_RightArrow;
break;
case Qt::Key_Insert:
kc.keycode = kVK_Help;
break;
case Qt::Key_Delete:
kc.keycode = kVK_ForwardDelete;
break;
case Qt::Key_Home:
kc.keycode = kVK_Home;
break;
case Qt::Key_End:
kc.keycode = kVK_End;
break;
case Qt::Key_PageUp:
kc.keycode = kVK_PageUp;
break;
case Qt::Key_PageDown:
kc.keycode = kVK_PageDown;
break;
case Qt::Key_Backspace:
kc.keycode = kVK_Delete;
break;
//case Qt::Key_Pause:
// return XK_Break;
case Qt::Key_CapsLock:
kc.keycode = kVK_CapsLock;
break;
case Qt::Key_Escape:
kc.keycode = kVK_Escape;
break;
case Qt::Key_Help:
kc.keycode = kVK_Help;
break;
case Qt::Key_NumLock:
kc.keycode = kVK_ANSI_KeypadClear;
break;
case Qt::Key_Print:
kc.keycode = kVK_F13;
break;
//case Qt::Key_ScrollLock:
// return XK_Scroll_Lock;
default:
if (key >= Qt::Key_F1 && key <= Qt::Key_F16)
kc.keycode = kVK_F1 + key - Qt::Key_F1;
else
kc.keycode = NoSymbol;
}
return kc;
}
// Private
QStringList AutoTypePlatformMac::getTargetWindowInfo(pid_t *pidPtr,
WId *windowNumberPtr)
{
QStringList titles;
const int maxWindowNameSize = 512;
char windowName[maxWindowNameSize];
onlySendKeycodes = false;
CFArrayRef windowInfo = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
0);
CFIndex windowCount = CFArrayGetCount(windowInfo);
for (CFIndex i = 0; i < windowCount; i++) {
CFDictionaryRef window = static_cast<CFDictionaryRef>
(CFArrayGetValueAtIndex(windowInfo, i));
// only want windows in layer 0
CFNumberRef windowLayerRef = static_cast<CFNumberRef>
(CFDictionaryGetValue(window, kCGWindowLayer));
int windowLayer = -1;
CFNumberGetValue(windowLayerRef, kCFNumberIntType, &windowLayer);
if (0 != windowLayer) continue;
// get the pid owning this window
CFNumberRef pidRef = static_cast<CFNumberRef>
(CFDictionaryGetValue(window, kCGWindowOwnerPID));
pid_t pid = -1;
CFNumberGetValue(pidRef, kCFNumberIntType, &pid);
// skip KeePassX windows
if (getKeepassxPID() == pid) continue;
// get window name; continue if no name
CFStringRef windowNameRef = static_cast<CFStringRef>
(CFDictionaryGetValue(window, kCGWindowName));
if (!windowNameRef) continue;
windowName[0] = 0;
if (!CFStringGetCString(windowNameRef, windowName,
maxWindowNameSize, kCFStringEncodingUTF8) ||
(0 == windowName[0])) continue;
if (NULL != pidPtr)
*pidPtr = pid;
if (NULL != windowNumberPtr) {
CGWindowID wid;
CFNumberRef windowNumberRef = static_cast<CFNumberRef>
(CFDictionaryGetValue(window, kCGWindowNumber));
CFNumberGetValue(windowNumberRef, kCGWindowIDCFNumberType, &wid);
*windowNumberPtr = wid;
return titles;
}
titles.append(QString(windowName));
}
return titles;
}
uint AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modifiers)
{
uint nativeModifiers = 0;
if (modifiers & Qt::ShiftModifier) {
nativeModifiers |= shiftKey;
}
if (modifiers & Qt::ControlModifier) {
nativeModifiers |= controlKey;
}
if (modifiers & Qt::AltModifier) {
nativeModifiers |= optionKey;
}
if (modifiers & Qt::MetaModifier) {
nativeModifiers |= cmdKey;
}
return nativeModifiers;
}
void AutoTypePlatformMac::flushUnicode()
{
if (0 == unicodePtr) return;
CGEventKeyboardSetUnicodeString(unicodeEvent, unicodePtr, unicodeBuffer);
keyDownUp(unicodeEvent);
unicodePtr = 0;
sleepKeyStrokeDelay();
}
void AutoTypePlatformMac::keyDownUp(CGEventRef theEvent)
{
// posting Key Down/Up events also annoyingly sets mouse location so mus
// current mouse location and set it in the event
CGEventRef eventLocation = CGEventCreate(NULL);
CGEventSetLocation(theEvent, CGEventGetLocation(eventLocation));
CFRelease(eventLocation);
CGEventSetType(theEvent, kCGEventKeyDown);
CGEventPost(kCGHIDEventTap, theEvent);
CGEventSetType(theEvent, kCGEventKeyUp);
CGEventPost(kCGHIDEventTap, theEvent);
}
void AutoTypePlatformMac::sleepTime(int msec)
{
if (msec == 0) return;
timespec timeOut, remains;
timeOut.tv_sec = msec/1000;
timeOut.tv_nsec = (msec%1000)*1000000;
nanosleep(&timeOut, &remains);
}
OSStatus AutoTypePlatformMac::hotKeyHandler(EventHandlerCallRef, EventRef,
void *userData)
{
// ignore nextHandler - should not be called
if ((inHotKeyEvent) ||
AutoTypePlatformMac::isFrontProcess(AutoTypePlatformMac::getKeepassxPID2())) return noErr;
AutoTypePlatformMac::inHotKeyEvent = true;
Q_EMIT static_cast<AutoTypePlatformMac*>(userData)->globalShortcutTriggered();
AutoTypePlatformMac::inHotKeyEvent = false;
return noErr;
}
pid_t AutoTypePlatformMac::getKeepassxPID() {
if (0 == keepassxPID) {
ProcessSerialNumber processSerialNumber;
GetCurrentProcess(&processSerialNumber);
GetProcessPID(&processSerialNumber, &keepassxPID);
}
return keepassxPID;
}
Boolean AutoTypePlatformMac::isFrontProcess(pid_t pid)
{
Boolean result;
ProcessSerialNumber pidPSN;
ProcessSerialNumber frontPSN;
OSStatus status = GetProcessForPID(pid, &pidPSN);
if (noErr != status) {
qWarning("AutoTypePlatformMac::isFrontProcess: GetProcessForPID "
"error for pid %d: %d", pid, status);
return false;
}
GetFrontProcess(&frontPSN);
SameProcess(&pidPSN, &frontPSN, &result);
return result;
}
void AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap()
{
unicodeToKeycodeWithModsMap.clear();
TISInputSourceRef inputSourceRef = TISCopyCurrentKeyboardInputSource();
if (NULL == inputSourceRef) {
qWarning("AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap: "
"inputSourceRef is NULL");
return;
}
CFDataRef unicodeKeyLayoutDataRef = static_cast<CFDataRef>
(TISGetInputSourceProperty(inputSourceRef,
kTISPropertyUnicodeKeyLayoutData));
if (NULL == unicodeKeyLayoutDataRef) {
qWarning("AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap: "
"unicodeKeyLayoutDataRef is NULL");
return;
}
const UCKeyboardLayout *unicodeKeyLayoutDataPtr =
reinterpret_cast<const UCKeyboardLayout*>
(CFDataGetBytePtr(unicodeKeyLayoutDataRef));
UInt32 deadKeyState;
UniChar unicodeString[8];
UniCharCount len;
for (int m = 0; m < 16; m++) {
uint mods = orderedModifiers[m];
for (uint keycode = 0; keycode < 0x80; keycode++) {
deadKeyState = 0;
len = 0;
OSStatus status = UCKeyTranslate(unicodeKeyLayoutDataPtr, keycode,
kUCKeyActionDown, mods, LMGetKbdType(),
kUCKeyTranslateNoDeadKeysMask, &deadKeyState,
sizeof(unicodeString), &len, unicodeString);
if (noErr != status) {
qWarning("AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap: "
"UCKeyTranslate error: %d keycode 0x%02X modifiers 0x%02X",
status, keycode, mods);
continue;
}
// store if only one char and not already in store
if ((1 != len) ||
(0 < unicodeToKeycodeWithModsMap.count(unicodeString[0])))
continue;
KeycodeWithMods kc;
kc.keycode = keycode;
kc.mods = mods;
unicodeToKeycodeWithModsMap[unicodeString[0]] = kc;
}
}
}
void AutoTypePlatformMac::processToFront(pid_t pid)
{
OSStatus status;
ProcessSerialNumber processSerialNumber;
status = GetProcessForPID(pid, &processSerialNumber);
if (noErr != status) {
qWarning("AutoTypePlatformMac::processToFront: GetProcessForPID "
"error for pid %d: %d", pid, status);
return;
}
status = SetFrontProcessWithOptions(&processSerialNumber,
kSetFrontProcessFrontWindowOnly);
if (noErr != status) {
qWarning("AutoTypePlatformMac::processToFront: "
"SetFrontProcessWithOptions for pid %d: %d", pid, status);
return;
}
}
KeycodeWithMods AutoTypePlatformMac::keysymToKeycodeWithMods2(KeySym keysym)
{
return 0 == unicodeToKeycodeWithModsMap.count(keysym)
? NoKeycodeWithMods : unicodeToKeycodeWithModsMap[keysym];
}
pid_t AutoTypePlatformMac::getKeepassxPID2()
{
if (0 == keepassxPID2) {
ProcessSerialNumber processSerialNumber;
GetCurrentProcess(&processSerialNumber);
GetProcessPID(&processSerialNumber, &keepassxPID2);
}
return keepassxPID2;
}
AutoTypeExecturorMac::AutoTypeExecturorMac(AutoTypePlatformMac* platform)
: m_platform(platform)
{
}
void AutoTypeExecturorMac::execChar(AutoTypeChar* action)
{
m_platform->sendUnicode(action->character.unicode());
}
void AutoTypeExecturorMac::execKey(AutoTypeKey* action)
{
m_platform->sendKeycode(m_platform->keyToKeycodeWithMods(action->key));
}
Q_EXPORT_PLUGIN2(keepassx-autotype-mac, AutoTypePlatformMac)

View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.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 <QtCore/QtPlugin>
#include <Carbon/Carbon.h>
#include "autotype/AutoTypePlatformPlugin.h"
#include "autotype/AutoTypeAction.h"
#include "core/Global.h"
typedef quint32 KeySym;
#define NoSymbol static_cast<KeySym>(0)
#define NoKeycode static_cast<uint16>(-1)
struct KeycodeWithMods {
uint16 keycode;
uint16 mods;
};
class AutoTypePlatformMac : public QObject, public AutoTypePlatformInterface
{
Q_OBJECT
Q_INTERFACES(AutoTypePlatformInterface)
public:
AutoTypePlatformMac();
QStringList windowTitles();
WId activeWindow();
QString activeWindowTitle();
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers);
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers);
int platformEventFilter(void* event);
int initialTimeout();
AutoTypeExecutor* createExecutor();
void sendUnicode(KeySym keysym);
void sendKeycode(KeycodeWithMods keycodeWithMods);
KeycodeWithMods keyToKeycodeWithMods(Qt::Key key);
Q_SIGNALS:
void globalShortcutTriggered();
private:
QStringList getTargetWindowInfo(pid_t *pidPtr, WId *windowNumberPtr);
uint qtToNativeModifiers(Qt::KeyboardModifiers modifiers);
void flushUnicode();
void keyDownUp(CGEventRef theEvent);
void sleepTime(int msec);
inline void sleepKeyStrokeDelay(){ sleepTime(5); };
static OSStatus hotKeyHandler(EventHandlerCallRef,
EventRef, void *userData);
pid_t getKeepassxPID();
bool first;
bool onlySendKeycodes;
bool inGlobalAutoType;
int globalKey;
uint globalMod;
pid_t keepassxPID;
EventHotKeyRef hotKeyRef;
EventHotKeyID hotKeyID;
static bool inHotKeyEvent;
static CGEventRef keyEvent;
private:
static void initUnicodeToKeycodeWithModsMap();
static void processToFront(pid_t pid);
static KeycodeWithMods keysymToKeycodeWithMods2(KeySym keysym);
static Boolean isFrontProcess(pid_t pid);
static pid_t getKeepassxPID2();
};
class AutoTypeExecturorMac : public AutoTypeExecutor
{
public:
explicit AutoTypeExecturorMac(AutoTypePlatformMac* platform);
void execChar(AutoTypeChar* action);
void execKey(AutoTypeKey* action);
private:
AutoTypePlatformMac* const m_platform;
};
#endif // KEEPASSX_AUTOTYPEMAC_H

View File

@ -0,0 +1,18 @@
set(autotype_mac_SOURCES
AutoTypeMac.cpp
)
set(autotype_mac_MOC
AutoTypeMac.h
)
qt4_wrap_cpp(autotype_mac_SOURCES ${autotype_mac_MOC})
add_library(keepassx-autotype-mac MODULE ${autotype_mac_SOURCES})
if(APPLE)
set_target_properties(keepassx-autotype-mac PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
endif()
target_link_libraries(keepassx-autotype-mac testautotype ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY})
install(TARGETS keepassx-autotype-mac
BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)