From bc207714da5bf702c5b07e5220f5322aeb7c71df Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Thu, 12 Jul 2012 22:33:20 +0200 Subject: [PATCH] Add initial auto-type implementation. The platform dependent bits are separated in plugins. A plugin for X11 using Xlib is already done. --- CMakeLists.txt | 2 + COPYING | 1 + src/CMakeLists.txt | 7 + src/autotype/AutoType.cpp | 406 ++++++++++++++ src/autotype/AutoType.h | 76 +++ src/autotype/AutoTypeAction.cpp | 93 ++++ src/autotype/AutoTypeAction.h | 82 +++ src/autotype/AutoTypePlatformPlugin.h | 43 ++ src/autotype/CMakeLists.txt | 7 + src/autotype/ShortcutWidget.cpp | 128 +++++ src/autotype/ShortcutWidget.h | 48 ++ src/autotype/x11/AutoTypeX11.cpp | 773 ++++++++++++++++++++++++++ src/autotype/x11/AutoTypeX11.h | 113 ++++ src/autotype/x11/CMakeLists.txt | 14 + src/core/Entry.cpp | 86 +++ src/core/Entry.h | 3 + src/gui/Application.cpp | 19 + src/gui/Application.h | 4 + 18 files changed, 1905 insertions(+) create mode 100644 src/autotype/AutoType.cpp create mode 100644 src/autotype/AutoType.h create mode 100644 src/autotype/AutoTypeAction.cpp create mode 100644 src/autotype/AutoTypeAction.h create mode 100644 src/autotype/AutoTypePlatformPlugin.h create mode 100644 src/autotype/CMakeLists.txt create mode 100644 src/autotype/ShortcutWidget.cpp create mode 100644 src/autotype/ShortcutWidget.h create mode 100644 src/autotype/x11/AutoTypeX11.cpp create mode 100644 src/autotype/x11/AutoTypeX11.h create mode 100644 src/autotype/x11/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index ed1be3fc3..65546b9a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,8 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed -Wl,--no-undefined") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro") endif() if(WITH_LTO) diff --git a/COPYING b/COPYING index aa464174a..1c51f7fcd 100644 --- a/COPYING +++ b/COPYING @@ -24,6 +24,7 @@ Copyright: 2010-2012, Felix Geyer 2007, Trolltech ASA 2012, Intel Corporation 2012, Nokia Corporation and/or its subsidiary(-ies) + 2000-2008, Tom Sato License: GPL-2 or GPL-3 Files: share/icons/database/*.png diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 46d6482dc..313241245 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,9 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h) set(keepassx_SOURCES + autotype/AutoType.cpp + autotype/AutoTypeAction.cpp + autotype/ShortcutWidget.cpp core/Config.cpp core/Database.cpp core/DatabaseIcons.cpp @@ -92,6 +95,8 @@ set(keepassx_SOURCES ) set(keepassx_MOC + autotype/AutoType.h + autotype/ShortcutWidget.h core/Config.h core/Database.h core/Entry.h @@ -181,6 +186,8 @@ install(TARGETS ${PROGNAME} BUNDLE DESTINATION . COMPONENT Runtime RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime) +add_subdirectory(autotype) + if(APPLE AND NOT (${CMAKE_VERSION} VERSION_LESS 2.8.8)) if(QT_MAC_USE_COCOA AND EXISTS "${QT_LIBRARY_DIR}/Resources/qt_menu.nib") install(DIRECTORY "${QT_LIBRARY_DIR}/Resources/qt_menu.nib" diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp new file mode 100644 index 000000000..528293e3f --- /dev/null +++ b/src/autotype/AutoType.cpp @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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 "AutoType.h" + +#include +#include + +#include "autotype/AutoTypePlatformPlugin.h" +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "core/ListDeleter.h" +#include "core/Tools.h" + +AutoType* AutoType::m_instance = Q_NULLPTR; + +AutoType::AutoType(QObject* parent) + : QObject(parent) + , m_inAutoType(false) + , m_pluginLoader(new QPluginLoader(this)) + , m_plugin(Q_NULLPTR) + , m_executor(Q_NULLPTR) +{ + // prevent crash when the plugin has unresolved symbols + m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint); + + // TODO: scan in proper paths +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + m_pluginLoader->setFileName(QCoreApplication::applicationDirPath() + "/autotype/x11/libkeepassx-autotype-x11.so"); +#endif + + QObject* pluginInstance = m_pluginLoader->instance(); + if (pluginInstance) { + m_plugin = qobject_cast(pluginInstance); + if (m_plugin) { + m_executor = m_plugin->createExecutor(); + connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SIGNAL(globalShortcutTriggered())); + } + } + + if (!m_plugin) { + qWarning("Unable to load auto-type plugin:\n%s", qPrintable(m_pluginLoader->errorString())); + } +} + +AutoType::~AutoType() +{ + delete m_executor; +} + +AutoType* AutoType::instance() +{ + if (!m_instance) { + m_instance = new AutoType(qApp); + } + + return m_instance; +} + +QStringList AutoType::windowTitles() +{ + if (!m_plugin) { + return QStringList(); + } + + return m_plugin->windowTitles(); +} + +void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow, const QString& customSequence) +{ + if (m_inAutoType || !m_plugin) { + return; + } + m_inAutoType = true; + + QString sequence; + if (customSequence.isEmpty()) { + sequence = entry->resolvePlaceholders(entry->autoTypeSequence()); + } + else { + sequence = customSequence; + } + + QList actions; + ListDeleter actionsDeleter(&actions); + + if (!parseActions(sequence, entry, actions)) { + m_inAutoType = false; // TODO: make this automatic + return; + } + + if (hideWindow) { + hideWindow->showMinimized(); + } + + Tools::wait(500); + + WId activeWindow = m_plugin->activeWindow(); + + Q_FOREACH (AutoTypeAction* action, actions) { + if (m_plugin->activeWindow() != activeWindow) { + qWarning("Active window changed, interrupting auto-type."); + break; + } + + action->accept(m_executor); + QCoreApplication::processEvents(QEventLoop::AllEvents, 10); + } + + m_inAutoType = false; +} + +void AutoType::performGlobalAutoType(const QList dbList) +{ + if (m_inAutoType || !m_plugin) { + return; + } + + QString windowTitle = m_plugin->activeWindowTitle(); + + if (windowTitle.isEmpty()) { + return; + } + + m_inAutoType = true; + + QList > entryList; + + Q_FOREACH (Database* db, dbList) { + Q_FOREACH (Entry* entry, db->rootGroup()->entriesRecursive()) { + QString sequence = entry->autoTypeSequence(windowTitle); + if (!sequence.isEmpty()) { + entryList << QPair(entry, sequence); + } + } + } + + if (entryList.isEmpty()) { + m_inAutoType = false; + } + else if (entryList.size() == 1) { + m_inAutoType = false; + performAutoType(entryList.first().first, 0, entryList.first().second); + } + else { + // TODO: implement + m_inAutoType = false; + } +} + +bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + Q_ASSERT(key); + Q_ASSERT(modifiers); + + if (!m_plugin) { + return false; + } + + if (key != m_currentGlobalKey || modifiers != m_currentGlobalModifiers) { + if (m_currentGlobalKey && m_currentGlobalModifiers) { + m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); + } + + if (m_plugin->registerGlobalShortcut(key, modifiers)) { + m_currentGlobalKey = key; + m_currentGlobalModifiers = modifiers; + return true; + } + else { + return false; + } + } + else { + return true; + } +} + +void AutoType::unregisterGlobalShortcut() +{ + if (m_plugin && m_currentGlobalKey && m_currentGlobalModifiers) { + m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); + } +} + +int AutoType::callEventFilter(void* event) +{ + if (!m_plugin) { + return -1; + } + + return m_plugin->platformEventFilter(event); +} + +bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList& actions) +{ + QString tmpl; + bool inTmpl = false; + + Q_FOREACH (const QChar& ch, sequence) { + // TODO: implement support for {{}, {}} and {DELAY=X} + + if (inTmpl) { + if (ch == '{') { + qWarning("Syntax error in auto-type sequence."); + return false; + } + else if (ch == '}') { + actions.append(createActionFromTemplate(tmpl, entry)); + inTmpl = false; + tmpl.clear(); + } + else { + tmpl += ch; + } + } + else if (ch == '{') { + inTmpl = true; + } + else if (ch == '}') { + qWarning("Syntax error in auto-type sequence."); + return false; + } + else { + actions.append(new AutoTypeChar(ch)); + } + } + + return true; +} + +QList AutoType::createActionFromTemplate(const QString& tmpl, const Entry* entry) +{ + QString tmplName = tmpl.toLower(); + int num = -1; + QList list; + + QRegExp repeatRegEx("(.+) (\\d+)", Qt::CaseSensitive, QRegExp::RegExp2); + if (repeatRegEx.exactMatch(tmplName)) { + tmplName = repeatRegEx.cap(1); + num = repeatRegEx.cap(2).toInt(); + + if (num == 0) { + return list; + } + // some safety checks + else if (tmplName == "delay") { + if (num > 10000) { + return list; + } + } + else if (num > 100) { + return list; + } + } + + if (tmplName == "tab") { + list.append(new AutoTypeKey(Qt::Key_Tab)); + } + else if (tmplName == "enter") { + list.append(new AutoTypeKey(Qt::Key_Enter)); + } + else if (tmplName == "up") { + list.append(new AutoTypeKey(Qt::Key_Up)); + } + else if (tmplName == "down") { + list.append(new AutoTypeKey(Qt::Key_Down)); + } + else if (tmplName == "left") { + list.append(new AutoTypeKey(Qt::Key_Left)); + } + else if (tmplName == "right") { + list.append(new AutoTypeKey(Qt::Key_Right)); + } + else if (tmplName == "insert" || tmplName == "ins") { + list.append(new AutoTypeKey(Qt::Key_Insert)); + } + else if (tmplName == "delete" || tmplName == "del") { + list.append(new AutoTypeKey(Qt::Key_Delete)); + } + else if (tmplName == "home") { + list.append(new AutoTypeKey(Qt::Key_Home)); + } + else if (tmplName == "end") { + list.append(new AutoTypeKey(Qt::Key_End)); + } + else if (tmplName == "pgup") { + list.append(new AutoTypeKey(Qt::Key_PageUp)); + } + else if (tmplName == "pgdown") { + list.append(new AutoTypeKey(Qt::Key_PageDown)); + } + else if (tmplName == "backspace" || tmplName == "bs" || tmplName == "bksp") { + list.append(new AutoTypeKey(Qt::Key_Backspace)); + } + else if (tmplName == "break") { + list.append(new AutoTypeKey(Qt::Key_Pause)); + } + else if (tmplName == "capslock") { + list.append(new AutoTypeKey(Qt::Key_CapsLock)); + } + else if (tmplName == "esc") { + list.append(new AutoTypeKey(Qt::Key_Escape)); + } + else if (tmplName == "help") { + list.append(new AutoTypeKey(Qt::Key_Help)); + } + else if (tmplName == "numlock") { + list.append(new AutoTypeKey(Qt::Key_NumLock)); + } + else if (tmplName == "ptrsc") { + list.append(new AutoTypeKey(Qt::Key_Print)); + } + else if (tmplName == "scolllock") { + list.append(new AutoTypeKey(Qt::Key_ScrollLock)); + } + // Qt doesn't know about keypad keys so use the normal ones instead + else if (tmplName == "add" || tmplName == "+") { + list.append(new AutoTypeChar('+')); + } + else if (tmplName == "subtract") { + list.append(new AutoTypeChar('-')); + } + else if (tmplName == "multiply") { + list.append(new AutoTypeChar('*')); + } + else if (tmplName == "divide") { + list.append(new AutoTypeChar('/')); + } + else if (tmplName == "^") { + list.append(new AutoTypeChar('^')); + } + else if (tmplName == "%") { + list.append(new AutoTypeChar('%')); + } + else if (tmplName == "~") { + list.append(new AutoTypeChar('~')); + } + else if (tmplName == "(") { + list.append(new AutoTypeChar('(')); + } + else if (tmplName == ")") { + list.append(new AutoTypeChar(')')); + } + else if (tmplName == "{") { + list.append(new AutoTypeChar('{')); + } + else if (tmplName == "}") { + list.append(new AutoTypeChar('}')); + } + else { + QRegExp fnRegexp("f(\\d+)", Qt::CaseInsensitive, QRegExp::RegExp2); + if (fnRegexp.exactMatch(tmplName)) { + int fnNo = fnRegexp.cap(1).toInt(); + if (fnNo >= 1 && fnNo <= 16) { + list.append(new AutoTypeKey(static_cast(Qt::Key_F1 - 1 + fnNo))); + } + } + } + + if (!list.isEmpty()) { + for (int i = 1; i < num; i++) { + list.append(list.at(0)->clone()); + } + + return list; + } + + + if (tmplName == "delay" && num > 0) { + list.append(new AutoTypeDelay(num)); + } + else if (tmplName == "clearfield") { + list.append(new AutoTypeClearField()); + } + + if (!list.isEmpty()) { + return list; + } + + + QString placeholder = QString("{%1}").arg(tmplName); + QString resolved = entry->resolvePlaceholders(placeholder); + if (placeholder != resolved) { + Q_FOREACH (const QChar& ch, resolved) { + list.append(new AutoTypeChar(ch)); + } + } + + return list; +} diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h new file mode 100644 index 000000000..9ddc39978 --- /dev/null +++ b/src/autotype/AutoType.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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_AUTOTYPE_H +#define KEEPASSX_AUTOTYPE_H + +#include +#include + +#include "core/Global.h" + +class AutoTypeAction; +class AutoTypeExecutor; +class AutoTypePlatformInterface; +class Database; +class Entry; +class QPluginLoader; + +class AutoType : public QObject +{ + Q_OBJECT + +public: + QStringList windowTitles(); + void performAutoType(const Entry* entry, QWidget* hideWindow = Q_NULLPTR, + const QString& customSequence = QString()); + void performGlobalAutoType(const QList dbList); + bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers); + void unregisterGlobalShortcut(); + int callEventFilter(void* event); + + inline bool isAvailable() { + return m_plugin; + } + + static AutoType* instance(); + +Q_SIGNALS: + void globalShortcutTriggered(); + +private: + explicit AutoType(QObject* parent = Q_NULLPTR); + ~AutoType(); + bool parseActions(const QString& sequence, const Entry* entry, QList& actions); + QList createActionFromTemplate(const QString& tmpl, const Entry* entry); + + bool m_inAutoType; + Qt::Key m_currentGlobalKey; + Qt::KeyboardModifiers m_currentGlobalModifiers; + QPluginLoader* m_pluginLoader; + AutoTypePlatformInterface* m_plugin; + AutoTypeExecutor* m_executor; + static AutoType* m_instance; + + Q_DISABLE_COPY(AutoType) +}; + +inline AutoType* autoType() { + return AutoType::instance(); +} + +#endif // KEEPASSX_AUTOTYPE_H diff --git a/src/autotype/AutoTypeAction.cpp b/src/autotype/AutoTypeAction.cpp new file mode 100644 index 000000000..cc751abe0 --- /dev/null +++ b/src/autotype/AutoTypeAction.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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 "AutoTypeAction.h" + +#include "core/Tools.h" + +AutoTypeChar::AutoTypeChar(const QChar& character) + : character(character) +{ +} + +AutoTypeAction* AutoTypeChar::clone() +{ + return new AutoTypeChar(character); +} + +void AutoTypeChar::accept(AutoTypeExecutor* executor) +{ + executor->execChar(this); +} + + +AutoTypeKey::AutoTypeKey(Qt::Key key) + : key(key) +{ +} + +AutoTypeAction* AutoTypeKey::clone() +{ + return new AutoTypeKey(key); +} + +void AutoTypeKey::accept(AutoTypeExecutor* executor) +{ + executor->execKey(this); +} + + +AutoTypeDelay::AutoTypeDelay(int delayMs) + : delayMs(delayMs) +{ +} + +AutoTypeAction* AutoTypeDelay::clone() +{ + return new AutoTypeDelay(delayMs); +} + +void AutoTypeDelay::accept(AutoTypeExecutor* executor) +{ + executor->execDelay(this); +} + + +AutoTypeClearField::AutoTypeClearField() +{ +} + +AutoTypeAction* AutoTypeClearField::clone() +{ + return new AutoTypeClearField(); +} + +void AutoTypeClearField::accept(AutoTypeExecutor* executor) +{ + executor->execClearField(this); +} + + +void AutoTypeExecutor::execDelay(AutoTypeDelay* action) +{ + Tools::wait(action->delayMs); +} + +void AutoTypeExecutor::execClearField(AutoTypeClearField* action) +{ + // TODO: implement +} diff --git a/src/autotype/AutoTypeAction.h b/src/autotype/AutoTypeAction.h new file mode 100644 index 000000000..efaabeb57 --- /dev/null +++ b/src/autotype/AutoTypeAction.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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_AUTOTYPEACTION_H +#define KEEPASSX_AUTOTYPEACTION_H + +#include +#include + +class AutoTypeExecutor; + +class AutoTypeAction +{ +public: + virtual ~AutoTypeAction() {} + virtual AutoTypeAction* clone() = 0; + virtual void accept(AutoTypeExecutor* executor) = 0; +}; + +class AutoTypeChar : public AutoTypeAction +{ +public: + explicit AutoTypeChar(const QChar& character); + AutoTypeAction* clone(); + void accept(AutoTypeExecutor* executor); + + const QChar character; +}; + +class AutoTypeKey : public AutoTypeAction +{ +public: + explicit AutoTypeKey(Qt::Key key); + AutoTypeAction* clone(); + void accept(AutoTypeExecutor* executor); + + const Qt::Key key; +}; + +class AutoTypeDelay : public AutoTypeAction +{ +public: + explicit AutoTypeDelay(int delayMs); + AutoTypeAction* clone(); + void accept(AutoTypeExecutor* executor); + + const int delayMs; +}; + +class AutoTypeClearField : public AutoTypeAction +{ +public: + explicit AutoTypeClearField(); + AutoTypeAction* clone(); + void accept(AutoTypeExecutor* executor); +}; + +class AutoTypeExecutor +{ +public: + virtual ~AutoTypeExecutor() {} + virtual void execChar(AutoTypeChar* action) = 0; + virtual void execKey(AutoTypeKey* action) = 0; + virtual void execDelay(AutoTypeDelay* action); + virtual void execClearField(AutoTypeClearField* action); +}; + +#endif // KEEPASSX_AUTOTYPEACTION_H diff --git a/src/autotype/AutoTypePlatformPlugin.h b/src/autotype/AutoTypePlatformPlugin.h new file mode 100644 index 000000000..bf8b5c47a --- /dev/null +++ b/src/autotype/AutoTypePlatformPlugin.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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_AUTOTYPEPLATFORMPLUGIN_H +#define KEEPASSX_AUTOTYPEPLATFORMPLUGIN_H + +#include + +#include "autotype/AutoTypeAction.h" + +class AutoTypePlatformInterface +{ +public: + virtual ~AutoTypePlatformInterface() {} + virtual QStringList windowTitles() = 0; + virtual WId activeWindow() = 0; + virtual QString activeWindowTitle() = 0; + virtual bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) = 0; + virtual void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) = 0; + virtual int platformEventFilter(void* event) = 0; + + virtual AutoTypeExecutor* createExecutor() = 0; + + // implementations should also provide a globalShortcutTriggered() signal +}; + +Q_DECLARE_INTERFACE(AutoTypePlatformInterface, "org.keepassx.AutoTypePlatformInterface/1") + +#endif // KEEPASSX_AUTOTYPEPLATFORMPLUGIN_H diff --git a/src/autotype/CMakeLists.txt b/src/autotype/CMakeLists.txt new file mode 100644 index 000000000..767c8ab49 --- /dev/null +++ b/src/autotype/CMakeLists.txt @@ -0,0 +1,7 @@ +if(Q_WS_X11) + find_package(X11) + + if(X11_FOUND AND X11_XTest_FOUND) + add_subdirectory(x11) + endif() +endif() diff --git a/src/autotype/ShortcutWidget.cpp b/src/autotype/ShortcutWidget.cpp new file mode 100644 index 000000000..9477131aa --- /dev/null +++ b/src/autotype/ShortcutWidget.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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 "ShortcutWidget.h" + +#include + +#include "autotype/AutoType.h" + +ShortcutWidget::ShortcutWidget(QWidget* parent) + : QLineEdit(parent) + , m_key(static_cast(0)) + , m_modifiers(0) + , m_locked(false) +{ + setReadOnly(true); +} + +Qt::Key ShortcutWidget::key() const +{ + return m_key; +} + +Qt::KeyboardModifiers ShortcutWidget::modifiers() const +{ + return m_modifiers; +} + +void ShortcutWidget::setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + m_key = key; + m_modifiers = modifiers; + m_locked = true; + + displayShortcut(m_key, m_modifiers); + + if (autoType()->registerGlobalShortcut(m_key, m_modifiers)) { + setStyleSheet(""); + } + else { + setStyleSheet("background-color: #FF9696;"); + } +} + +void ShortcutWidget::keyPressEvent(QKeyEvent* event) +{ + keyEvent(event); +} + +void ShortcutWidget::keyReleaseEvent(QKeyEvent* event) +{ + keyEvent(event); +} + +void ShortcutWidget::keyEvent(QKeyEvent* event) +{ + event->accept(); + + if (event->type() != QEvent::KeyPress && event->type() != QEvent::KeyRelease) { + return; + } + + bool release = (event->type() == QEvent::KeyRelease); + + if (m_locked && release) { + return; + } + + Qt::Key key = static_cast(event->key()); + + if (key <= 0 || key == Qt::Key_unknown) { + return; + } + + Qt::KeyboardModifiers modifiers = event->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META); + + bool keyIsModifier; + switch (key) { + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Meta: + case Qt::Key_Alt: + case Qt::Key_AltGr: + keyIsModifier = true; + break; + default: + keyIsModifier = false; + } + + if (!release && !keyIsModifier) { + if (modifiers != 0) { + setShortcut(key, modifiers); + } + else { + m_locked = false; + setStyleSheet(""); + displayShortcut(key, modifiers); + } + } + else { + if (m_locked) { + m_locked = false; + m_key = static_cast(0); + setStyleSheet(""); + } + + displayShortcut(static_cast(0), modifiers); + } +} + +void ShortcutWidget::displayShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + setText(QKeySequence(key | modifiers).toString(QKeySequence::NativeText)); +} diff --git a/src/autotype/ShortcutWidget.h b/src/autotype/ShortcutWidget.h new file mode 100644 index 000000000..028dd8300 --- /dev/null +++ b/src/autotype/ShortcutWidget.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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_SHORTCUTWIDGET_H +#define KEEPASSX_SHORTCUTWIDGET_H + +#include + +#include "core/Global.h" + +class ShortcutWidget : public QLineEdit +{ + Q_OBJECT + +public: + explicit ShortcutWidget(QWidget* parent = Q_NULLPTR); + Qt::Key key() const; + Qt::KeyboardModifiers modifiers() const; + void setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers); + +protected: + void keyPressEvent(QKeyEvent* event) Q_DECL_OVERRIDE; + void keyReleaseEvent(QKeyEvent* event) Q_DECL_OVERRIDE; + +private: + void keyEvent(QKeyEvent* event); + void displayShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers); + + Qt::Key m_key; + Qt::KeyboardModifiers m_modifiers; + bool m_locked; +}; + +#endif // KEEPASSX_SHORTCUTWIDGET_H diff --git a/src/autotype/x11/AutoTypeX11.cpp b/src/autotype/x11/AutoTypeX11.cpp new file mode 100644 index 000000000..ab9a7a6bb --- /dev/null +++ b/src/autotype/x11/AutoTypeX11.cpp @@ -0,0 +1,773 @@ +/* + * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2000-2008 Tom Sato + * + * 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 "AutoTypeX11.h" + +bool AutoTypePlatformX11::m_catchXErrors = false; +bool AutoTypePlatformX11::m_xErrorOccured = false; +int (*AutoTypePlatformX11::m_oldXErrorHandler) (Display*, XErrorEvent*) = Q_NULLPTR; + +AutoTypePlatformX11::AutoTypePlatformX11() +{ + m_dpy = QX11Info::display(); + m_rootWindow = QX11Info::appRootWindow(); + + m_atomWmState = XInternAtom(m_dpy, "WM_STATE", true); + m_atomWmName = XInternAtom(m_dpy, "_NET_WM_NAME", true); + m_atomUtf8String = XInternAtom(m_dpy, "UTF8_STRING", true); + + m_classBlacklist << "desktop_window" << "gnome-panel"; // Gnome + m_classBlacklist << "kdesktop" << "kicker"; // KDE 3 + m_classBlacklist << "Plasma"; // KDE 4 + m_classBlacklist << "xfdesktop" << "xfce4-panel"; // Xfce 4 + + m_currentGlobalKey = static_cast(0); + m_currentGlobalModifiers = 0; + + m_keysymTable = Q_NULLPTR; + m_altMask = 0; + m_metaMask = 0; + m_altgrMask = 0; + m_altgrKeysym = NoSymbol; + + updateKeymap(); +} + +QStringList AutoTypePlatformX11::windowTitles() +{ + return windowTitlesRecursive(m_rootWindow); +} + +WId AutoTypePlatformX11::activeWindow() +{ + Window window; + int revert_to_return; + XGetInputFocus(m_dpy, &window, &revert_to_return); + + int tree; + do { + if (isTopLevelWindow(window)) { + break; + } + + Window root; + Window parent; + Window* children = Q_NULLPTR; + unsigned int numChildren; + tree = XQueryTree(m_dpy, window, &root, &parent, &children, &numChildren); + window = parent; + if (children) { + XFree(children); + } + } while (tree && window); + + return window; +} + +QString AutoTypePlatformX11::activeWindowTitle() +{ + return windowTitle(activeWindow(), true); +} + +bool AutoTypePlatformX11::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + int keycode = XKeysymToKeycode(m_dpy, charToKeySym(key)); + uint nativeModifiers = qtToNativeModifiers(modifiers); + + startCatchXErrors(); + XGrabKey(m_dpy, keycode, nativeModifiers, m_rootWindow, true, GrabModeAsync, GrabModeAsync); + XGrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask, m_rootWindow, true, GrabModeAsync, + GrabModeAsync); + XGrabKey(m_dpy, keycode, nativeModifiers | LockMask, m_rootWindow, true, GrabModeAsync, + GrabModeAsync); + XGrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask | LockMask, m_rootWindow, true, + GrabModeAsync, GrabModeAsync); + stopCatchXErrors(); + + if (!m_xErrorOccured) { + m_currentGlobalKey = key; + m_currentGlobalModifiers = modifiers; + m_currentGlobalKeycode = keycode; + m_currentGlobalNativeModifiers = nativeModifiers; + return true; + } + else { + unregisterGlobalShortcut(key, modifiers); + return false; + } +} + +uint AutoTypePlatformX11::qtToNativeModifiers(Qt::KeyboardModifiers modifiers) +{ + uint nativeModifiers = 0; + + if (modifiers & Qt::ShiftModifier) { + nativeModifiers |= ShiftMask; + } + if (modifiers & Qt::ControlModifier) { + nativeModifiers |= ControlMask; + } + if (modifiers & Qt::AltModifier) { + nativeModifiers |= m_altMask; + } + if (modifiers & Qt::MetaModifier) { + nativeModifiers |= m_metaMask; + } + + return nativeModifiers; +} + +void AutoTypePlatformX11::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + KeyCode keycode = XKeysymToKeycode(m_dpy, keyToKeySym(key)); + uint nativeModifiers = qtToNativeModifiers(modifiers); + + XUngrabKey(m_dpy, keycode, nativeModifiers, m_rootWindow); + XUngrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask, m_rootWindow); + XUngrabKey(m_dpy, keycode, nativeModifiers | LockMask, m_rootWindow); + XUngrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask | LockMask, m_rootWindow); + + m_currentGlobalKey = static_cast(0); + m_currentGlobalModifiers = 0; + m_currentGlobalKeycode = 0; + m_currentGlobalNativeModifiers = 0; +} + +int AutoTypePlatformX11::platformEventFilter(void* event) +{ + XEvent* xevent = static_cast(event); + + if (xevent->type == KeyPress && m_currentGlobalKey && xevent->xkey.keycode == m_currentGlobalKeycode + && (xevent->xkey.state & m_modifierMask) == m_currentGlobalNativeModifiers + && !QApplication::focusWidget()) { + QMetaObject::invokeMethod(this, "globalShortcutTriggered", Qt::QueuedConnection); + return 1; + } + if (xevent->type == MappingNotify) { + updateKeymap(); + } + + return -1; +} + +AutoTypeExecutor* AutoTypePlatformX11::createExecutor() +{ + return new AutoTypeExecturorX11(this); +} + +QString AutoTypePlatformX11::windowTitle(Window window, bool useBlacklist) +{ + QString title; + + Atom type; + int format; + unsigned long nitems; + unsigned long after; + unsigned char* data = Q_NULLPTR; + + int retVal = XGetWindowProperty(m_dpy, window, m_atomWmName, 0, 1000, false, m_atomUtf8String, + &type, &format, &nitems, &after, &data); + + if (retVal != 0) { + if (data) { + XFree(data); + } + } + else if (data) { + title = QString::fromUtf8(reinterpret_cast(data)); + XFree(data); + + if (useBlacklist) { + if (window == m_rootWindow) { + return QString(); + } + + QString className = windowClassName(window); + if (m_classBlacklist.contains(className)) { + return QString(); + } + + QList keepassxWindows = widgetsToX11Windows(QApplication::topLevelWidgets()); + if (keepassxWindows.contains(window)) { + return QString(); + } + } + } + + return title; +} + +QString AutoTypePlatformX11::windowClassName(Window window) +{ + QString className; + + XClassHint wmClass; + wmClass.res_name = Q_NULLPTR; + wmClass.res_class = Q_NULLPTR; + + if (XGetClassHint(m_dpy, window, &wmClass) && wmClass.res_name) { + className = QString::fromLocal8Bit(wmClass.res_name); + } + if (wmClass.res_name) { + XFree(wmClass.res_name); + } + if (wmClass.res_class) { + XFree(wmClass.res_class); + } + + return className; +} + +QList AutoTypePlatformX11::widgetsToX11Windows(const QWidgetList& widgetList) +{ + QList windows; + + Q_FOREACH (const QWidget* widget, widgetList) { + windows.append(widget->winId()); + } + + return windows; +} + +QStringList AutoTypePlatformX11::windowTitlesRecursive(Window window) +{ + QStringList titles; + + if (isTopLevelWindow(window)) { + QString title = windowTitle(window, true); + if (!title.isEmpty()) { + titles.append(title); + } + } + + Window root; + Window parent; + Window* children = Q_NULLPTR; + unsigned int numChildren; + if (XQueryTree(m_dpy, window, &root, &parent, &children, &numChildren) && children) { + for (uint i = 0; i < numChildren; i++) { + titles.append(windowTitlesRecursive(children[i])); + } + } + if (children) { + XFree(children); + } + + return titles; +} + +bool AutoTypePlatformX11::isTopLevelWindow(Window window) +{ + Atom type = None; + int format; + unsigned long nitems; + unsigned long after; + unsigned char* data = Q_NULLPTR; + int retVal = XGetWindowProperty(m_dpy, window, m_atomWmState, 0, 0, false, AnyPropertyType, &type, &format, + &nitems, &after, &data); + if (data) { + XFree(data); + } + + return (retVal == 0) && type; +} + +KeySym AutoTypePlatformX11::charToKeySym(const QChar& ch) +{ + ushort unicode = ch.unicode(); + + /* first check for Latin-1 characters (1:1 mapping) */ + if ((unicode >= 0x0020 && unicode <= 0x007e) + || (unicode >= 0x00a0 && unicode <= 0x00ff)) { + return unicode; + } + else if (unicode >= 0x0100) { + return unicode | 0x01000000; + } + else { + return NoSymbol; + } +} + +KeySym AutoTypePlatformX11::keyToKeySym(Qt::Key key) +{ + switch (key) { + case Qt::Key_Tab: + return XK_Tab; + case Qt::Key_Enter: + return XK_Return; + case Qt::Key_Up: + return XK_Up; + case Qt::Key_Down: + return XK_Down; + case Qt::Key_Left: + return XK_Left; + case Qt::Key_Right: + return XK_Right; + case Qt::Key_Insert: + return XK_Insert; + case Qt::Key_Delete: + return XK_Delete; + case Qt::Key_Home: + return XK_Home; + case Qt::Key_End: + return XK_End; + case Qt::Key_PageUp: + return XK_Page_Up; + case Qt::Key_PageDown: + return XK_Page_Down; + case Qt::Key_Backspace: + return XK_BackSpace; + case Qt::Key_Pause: + return XK_Break; + case Qt::Key_CapsLock: + return XK_Caps_Lock; + case Qt::Key_Escape: + return XK_Escape; + case Qt::Key_Help: + return XK_Help; + case Qt::Key_NumLock: + return XK_Num_Lock; + case Qt::Key_Print: + return XK_Print; + case Qt::Key_ScrollLock: + return XK_Scroll_Lock; + default: + if (key >= Qt::Key_F1 && key <= Qt::Key_F16) { + return XK_F1 + (key - Qt::Key_F1); + } + else { + return NoSymbol; + } + } +} + +void AutoTypePlatformX11::updateKeymap() { + ReadKeymap(); + + if (!m_altgrMask) { + AddModifier(XK_Mode_switch); + } + if (!m_metaMask) { + m_metaMask = Mod4Mask; + } + + m_modifierMask = ControlMask | ShiftMask | m_altMask | m_metaMask; + + if (m_currentGlobalKey && m_currentGlobalModifiers) { + unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); + registerGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); + } +} + +void AutoTypePlatformX11::startCatchXErrors() +{ + Q_ASSERT(!m_catchXErrors); + + m_catchXErrors = true; + m_xErrorOccured = false; + m_oldXErrorHandler = XSetErrorHandler(x11ErrorHandler); +} + +void AutoTypePlatformX11::stopCatchXErrors() +{ + Q_ASSERT(m_catchXErrors); + + XSync(m_dpy, false); + XSetErrorHandler(m_oldXErrorHandler); + m_catchXErrors = false; +} + +int AutoTypePlatformX11::x11ErrorHandler(Display* display, XErrorEvent* error) +{ + Q_UNUSED(display) + Q_UNUSED(error) + + if (m_catchXErrors) { + m_xErrorOccured = true; + } + + return 1; +} + +// -------------------------------------------------------------------------- +// The following code is taken from xvkbd 3.0 and has been slightly modified. +// -------------------------------------------------------------------------- + +/* + * Insert a specified keysym to unused position in the keymap table. + * This will be called to add required keysyms on-the-fly. + * if the second parameter is TRUE, the keysym will be added to the + * non-shifted position - this may be required for modifier keys + * (e.g. Mode_switch) and some special keys (e.g. F20). + */ +int AutoTypePlatformX11::AddKeysym(KeySym keysym, bool top) +{ + int keycode, pos, max_pos, inx, phase; + + if (top) { + max_pos = 0; + } else { + max_pos = m_keysymPerKeycode - 1; + if (4 <= max_pos) max_pos = 3; + if (2 <= max_pos && m_altgrKeysym != XK_Mode_switch) max_pos = 1; + } + + for (phase = 0; phase < 2; phase++) { + for (keycode = m_maxKeycode; m_minKeycode <= keycode; keycode--) { + for (pos = max_pos; 0 <= pos; pos--) { + inx = (keycode - m_minKeycode) * m_keysymPerKeycode; + if ((phase != 0 || m_keysymTable[inx] == NoSymbol) && m_keysymTable[inx] < 0xFF00) { + /* In the first phase, to avoid modifing existing keys, */ + /* add the keysym only to the keys which has no keysym in the first position. */ + /* If no place fuond in the first phase, add the keysym for any keys except */ + /* for modifier keys and other special keys */ + if (m_keysymTable[inx + pos] == NoSymbol) { + m_keysymTable[inx + pos] = keysym; + XChangeKeyboardMapping(m_dpy, keycode, m_keysymPerKeycode, &m_keysymTable[inx], 1); + XFlush(m_dpy); + return keycode; + } + } + } + } + } + qWarning("Couldn't add \"%s\" to keymap", XKeysymToString(keysym)); + return NoSymbol; +} + +/* + * Add the specified key as a new modifier. + * This is used to use Mode_switch (AltGr) as a modifier. + */ +void AutoTypePlatformX11::AddModifier(KeySym keysym) +{ + XModifierKeymap *modifiers; + int keycode, i, pos; + + keycode = XKeysymToKeycode(m_dpy, keysym); + if (keycode == NoSymbol) keycode = AddKeysym(keysym, TRUE); + + modifiers = XGetModifierMapping(m_dpy); + for (i = 7; 3 < i; i--) { + if (modifiers->modifiermap[i * modifiers->max_keypermod] == NoSymbol + || ((m_keysymTable[(modifiers->modifiermap[i * modifiers->max_keypermod] + - m_minKeycode) * m_keysymPerKeycode]) == XK_ISO_Level3_Shift + && keysym == XK_Mode_switch)) + { + for (pos = 0; pos < modifiers->max_keypermod; pos++) { + if (modifiers->modifiermap[i * modifiers->max_keypermod + pos] == NoSymbol) { + modifiers->modifiermap[i * modifiers->max_keypermod + pos] = keycode; + XSetModifierMapping(m_dpy, modifiers); + return; + } + } + } + } + qWarning("Couldn't add \"%s\" as modifier", XKeysymToString(keysym)); +} + +/* + * Read keyboard mapping and modifier mapping. + * Keyboard mapping is used to know what keys are in shifted position. + * Modifier mapping is required because we should know Alt and Meta + * key are used as which modifier. + */ +void AutoTypePlatformX11::ReadKeymap() +{ + int i; + int keycode, inx, pos; + KeySym keysym; + XModifierKeymap *modifiers; + + XDisplayKeycodes(m_dpy, &m_minKeycode, &m_maxKeycode); + if (m_keysymTable != NULL) XFree(m_keysymTable); + m_keysymTable = XGetKeyboardMapping(m_dpy, + m_minKeycode, m_maxKeycode - m_minKeycode + 1, + &m_keysymPerKeycode); + for (keycode = m_minKeycode; keycode <= m_maxKeycode; keycode++) { + /* if the first keysym is alphabet and the second keysym is NoSymbol, + it is equivalent to pair of lowercase and uppercase alphabet */ + inx = (keycode - m_minKeycode) * m_keysymPerKeycode; + if (m_keysymTable[inx + 1] == NoSymbol + && ((XK_A <= m_keysymTable[inx] && m_keysymTable[inx] <= XK_Z) + || (XK_a <= m_keysymTable[inx] && m_keysymTable[inx] <= XK_z))) + { + if (XK_A <= m_keysymTable[inx] && m_keysymTable[inx] <= XK_Z) + m_keysymTable[inx] = m_keysymTable[inx] - XK_A + XK_a; + m_keysymTable[inx + 1] = m_keysymTable[inx] - XK_a + XK_A; + } + } + + m_altMask = 0; + m_metaMask = 0; + m_altgrMask = 0; + m_altgrKeysym = NoSymbol; + modifiers = XGetModifierMapping(m_dpy); + for (i = 0; i < 8; i++) { + for (pos = 0; pos < modifiers->max_keypermod; pos++) { + keycode = modifiers->modifiermap[i * modifiers->max_keypermod + pos]; + if (keycode < m_minKeycode || m_maxKeycode < keycode) continue; + + keysym = m_keysymTable[(keycode - m_minKeycode) * m_keysymPerKeycode]; + if (keysym == XK_Alt_L || keysym == XK_Alt_R) { + m_altMask = 1 << i; + } else if (keysym == XK_Meta_L || keysym == XK_Meta_R) { + m_metaMask = 1 << i; + } else if (keysym == XK_Mode_switch) { + if (m_altgrKeysym == XK_ISO_Level3_Shift) { + } else { + m_altgrMask = 0x0101 << i; + /* I don't know why, but 0x2000 was required for mod3 on my Linux box */ + m_altgrKeysym = keysym; + } + } else if (keysym == XK_ISO_Level3_Shift) { + /* if no Mode_switch, try to use ISO_Level3_Shift instead */ + /* however, it may not work as intended - I don't know why */ + m_altgrMask = 1 << i; + m_altgrKeysym = keysym; + } + } + } + XFreeModifiermap(modifiers); +} + +/* + * Send event to the focused window. + * If input focus is specified explicitly, select the window + * before send event to the window. + */ +void AutoTypePlatformX11::SendEvent(XKeyEvent *event) +{ + XSync(event->display, FALSE); + int (*oldHandler) (Display*, XErrorEvent*) = XSetErrorHandler(MyErrorHandler); + + XTestFakeKeyEvent(event->display, event->keycode, event->type == KeyPress, 0); + XFlush(event->display); + + XSetErrorHandler(oldHandler); +} + +/* + * Send sequence of KeyPressed/KeyReleased events to the focused + * window to simulate keyboard. If modifiers (shift, control, etc) + * are set ON, many events will be sent. + */ +void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym, unsigned int shift) +{ + Window cur_focus; + int revert_to; + XKeyEvent event; + int keycode; + int phase, inx; + bool found; + + XGetInputFocus(m_dpy, &cur_focus, &revert_to); + + found = FALSE; + keycode = 0; + if (keysym != NoSymbol) { + for (phase = 0; phase < 2; phase++) { + for (keycode = m_minKeycode; !found && (keycode <= m_maxKeycode); keycode++) { + /* Determine keycode for the keysym: we use this instead + of XKeysymToKeycode() because we must know shift_state, too */ + inx = (keycode - m_minKeycode) * m_keysymPerKeycode; + if (m_keysymTable[inx] == keysym) { + shift &= ~m_altgrMask; + if (m_keysymTable[inx + 1] != NoSymbol) shift &= ~ShiftMask; + found = TRUE; + break; + } else if (m_keysymTable[inx + 1] == keysym) { + shift &= ~m_altgrMask; + shift |= ShiftMask; + found = TRUE; + break; + } + } + if (!found && m_altgrMask && 3 <= m_keysymPerKeycode) { + for (keycode = m_minKeycode; !found && (keycode <= m_maxKeycode); keycode++) { + inx = (keycode - m_minKeycode) * m_keysymPerKeycode; + if (m_keysymTable[inx + 2] == keysym) { + shift &= ~ShiftMask; + shift |= m_altgrMask; + found = TRUE; + break; + } else if (4 <= m_keysymPerKeycode && m_keysymTable[inx + 3] == keysym) { + shift |= ShiftMask | m_altgrMask; + found = TRUE; + break; + } + } + } + if (found) break; + + if (0xF000 <= keysym) { + /* for special keys such as function keys, + first try to add it in the non-shifted position of the keymap */ + if (AddKeysym(keysym, TRUE) == NoSymbol) AddKeysym(keysym, FALSE); + } else { + AddKeysym(keysym, FALSE); + } + } + } + + event.display = m_dpy; + event.window = cur_focus; + event.root = m_rootWindow; + event.subwindow = None; + event.time = CurrentTime; + event.x = 1; + event.y = 1; + event.x_root = 1; + event.y_root = 1; + event.same_screen = TRUE; + + Window root, child; + int root_x, root_y, x, y; + unsigned int mask; + + XQueryPointer(m_dpy, event.root, &root, &child, &root_x, &root_y, &x, &y, &mask); + + event.type = KeyRelease; + event.state = 0; + if (mask & ControlMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Control_L); + SendEvent(&event); + } + if (mask & m_altMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Alt_L); + SendEvent(&event); + } + if (mask & m_metaMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Meta_L); + SendEvent(&event); + } + if (mask & m_altgrMask) { + event.keycode = XKeysymToKeycode(m_dpy, m_altgrKeysym); + SendEvent(&event); + } + if (mask & ShiftMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Shift_L); + SendEvent(&event); + } + if (mask & LockMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Caps_Lock); + SendEvent(&event); + } + + event.type = KeyPress; + event.state = 0; + if (shift & ControlMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Control_L); + SendEvent(&event); + event.state |= ControlMask; + } + if (shift & m_altMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Alt_L); + SendEvent(&event); + event.state |= m_altMask; + } + if (shift & m_metaMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Meta_L); + SendEvent(&event); + event.state |= m_metaMask; + } + if (shift & m_altgrMask) { + event.keycode = XKeysymToKeycode(m_dpy, m_altgrKeysym); + SendEvent(&event); + event.state |= m_altgrMask; + } + if (shift & ShiftMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Shift_L); + SendEvent(&event); + event.state |= ShiftMask; + } + + if (keysym != NoSymbol) { /* send event for the key itself */ + event.keycode = found ? keycode : XKeysymToKeycode(m_dpy, keysym); + if (event.keycode == NoSymbol) { + if ((keysym & ~0x7f) == 0 && QChar(static_cast(keysym)).isPrint()) + qWarning("No such key: %c", static_cast(keysym)); + else if (XKeysymToString(keysym) != NULL) + qWarning("No such key: keysym=%s (0x%lX)", XKeysymToString(keysym), static_cast(keysym)); + else + qWarning("No such key: keysym=0x%lX", static_cast(keysym)); + } else { + SendEvent(&event); + event.type = KeyRelease; + SendEvent(&event); + } + } + + event.type = KeyRelease; + if (shift & ShiftMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Shift_L); + SendEvent(&event); + event.state &= ~ShiftMask; + } + if (shift & m_altgrMask) { + event.keycode = XKeysymToKeycode(m_dpy, m_altgrKeysym); + SendEvent(&event); + event.state &= ~m_altgrMask; + } + if (shift & m_metaMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Meta_L); + SendEvent(&event); + event.state &= ~m_metaMask; + } + if (shift & m_altMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Alt_L); + SendEvent(&event); + event.state &= ~m_altMask; + } + if (shift & ControlMask) { + event.keycode = XKeysymToKeycode(m_dpy, XK_Control_L); + SendEvent(&event); + event.state &= ~ControlMask; + } +} + +int AutoTypePlatformX11::MyErrorHandler(Display *my_dpy, XErrorEvent *event) +{ + char msg[200]; + + if (event->error_code == BadWindow) { + return 0; + } + XGetErrorText(my_dpy, event->error_code, msg, sizeof(msg) - 1); + qWarning("X error trapped: %s, request-code=%d\n", msg, event->request_code); + return 0; +} + + +AutoTypeExecturorX11::AutoTypeExecturorX11(AutoTypePlatformX11* platform) + : m_platform(platform) +{ +} + +void AutoTypeExecturorX11::execChar(AutoTypeChar* action) +{ + m_platform->SendKeyPressedEvent(m_platform->charToKeySym(action->character)); +} + +void AutoTypeExecturorX11::execKey(AutoTypeKey* action) +{ + m_platform->SendKeyPressedEvent(m_platform->keyToKeySym(action->key)); +} + +Q_EXPORT_PLUGIN2(keepassx-autotype-x11, AutoTypePlatformX11) diff --git a/src/autotype/x11/AutoTypeX11.h b/src/autotype/x11/AutoTypeX11.h new file mode 100644 index 000000000..dc5cc90ab --- /dev/null +++ b/src/autotype/x11/AutoTypeX11.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2000-2008 Tom Sato + * + * 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_AUTOTYPEX11_H +#define KEEPASSX_AUTOTYPEX11_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "autotype/AutoTypePlatformPlugin.h" +#include "autotype/AutoTypeAction.h" +#include "core/Global.h" + +class AutoTypePlatformX11 : public QObject, public AutoTypePlatformInterface +{ + Q_OBJECT + Q_INTERFACES(AutoTypePlatformInterface) + +public: + AutoTypePlatformX11(); + 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); + AutoTypeExecutor* createExecutor(); + + KeySym charToKeySym(const QChar& ch); + KeySym keyToKeySym(Qt::Key key); + + void SendKeyPressedEvent(KeySym keysym, unsigned int shift = 0); + +Q_SIGNALS: + void globalShortcutTriggered(); + +private: + QString windowTitle(Window window, bool useBlacklist); + QStringList windowTitlesRecursive(Window window); + QString windowClassName(Window window); + QList widgetsToX11Windows(const QWidgetList& widgetList); + bool isTopLevelWindow(Window window); + uint qtToNativeModifiers(Qt::KeyboardModifiers modifiers); + void startCatchXErrors(); + void stopCatchXErrors(); + static int x11ErrorHandler(Display* display, XErrorEvent* error); + + void updateKeymap(); + int AddKeysym(KeySym keysym, bool top); + void AddModifier(KeySym keysym); + void ReadKeymap(); + void SendEvent(XKeyEvent *event); + static int MyErrorHandler(Display *my_dpy, XErrorEvent *event); + + Display* m_dpy; + Window m_rootWindow; + Atom m_atomWmState; + Atom m_atomWmName; + Atom m_atomUtf8String; + QSet m_classBlacklist; + Qt::Key m_currentGlobalKey; + Qt::KeyboardModifiers m_currentGlobalModifiers; + uint m_currentGlobalKeycode; + uint m_currentGlobalNativeModifiers; + int m_modifierMask; + static bool m_catchXErrors; + static bool m_xErrorOccured; + static int (*m_oldXErrorHandler) (Display*, XErrorEvent*); + + KeySym* m_keysymTable; + int m_minKeycode; + int m_maxKeycode; + int m_keysymPerKeycode; + int m_altMask; + int m_metaMask; + int m_altgrMask; + KeySym m_altgrKeysym; +}; + +class AutoTypeExecturorX11 : public AutoTypeExecutor +{ +public: + explicit AutoTypeExecturorX11(AutoTypePlatformX11* platform); + + void execChar(AutoTypeChar* action); + void execKey(AutoTypeKey* action); + +private: + AutoTypePlatformX11* const m_platform; +}; + +#endif // KEEPASSX_AUTOTYPEX11_H diff --git a/src/autotype/x11/CMakeLists.txt b/src/autotype/x11/CMakeLists.txt new file mode 100644 index 000000000..113e9e0fa --- /dev/null +++ b/src/autotype/x11/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories(SYSTEM ${X11_X11_INCLUDE_PATH}) + +set(autotype_X11_SOURCES + AutoTypeX11.cpp +) + +set(autotype_X11_MOC + AutoTypeX11.h +) + +qt4_wrap_cpp(autotype_X11_SOURCES ${autotype_X11_MOC}) + +add_library(keepassx-autotype-x11 MODULE ${autotype_X11_SOURCES}) +target_link_libraries(keepassx-autotype-x11 ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${X11_X11_LIB} ${X11_XTest_LIB}) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 8e5499f24..c03b4b74e 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -171,6 +171,60 @@ const QList& Entry::autoTypeAssociations() const return m_data.autoTypeAssociations; } +QString Entry::autoTypeSequence(const QString& windowTitle) const +{ + if (!m_data.autoTypeEnabled) { + return QString(); + } + + bool enableSet = false; + QString sequence; + if (!windowTitle.isEmpty()) { + Q_FOREACH (const AutoTypeAssociation& autoTypeAssoc, m_data.autoTypeAssociations) { + if (windowMatches(windowTitle, autoTypeAssoc.window)) { + sequence = autoTypeAssoc.sequence; + break; + } + } + } + + if (sequence.isEmpty()) { + sequence = m_data.defaultAutoTypeSequence; + } + + Group* group = m_group; + do { + if (!enableSet) { + if (group->autoTypeEnabled() == Group::Disable) { + return QString(); + } + else if (group->autoTypeEnabled() == Group::Enable) { + enableSet = true; + } + } + + if (sequence.isEmpty()) { + sequence = group->defaultAutoTypeSequence(); + } + + group = group->parentGroup(); + } while (group && (!enableSet || sequence.isEmpty())); + + if (sequence.isEmpty() && (!username().isEmpty() || !password().isEmpty())) { + if (username().isEmpty()) { + sequence = "{PASSWORD}{ENTER}"; + } + else if (password().isEmpty()) { + sequence = "{USERNAME}{ENTER}"; + } + else { + sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; + } + } + + return sequence; +} + QString Entry::title() const { return m_attributes->value("Title"); @@ -536,3 +590,35 @@ bool Entry::match(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity url().contains(searchTerm, caseSensitivity) || notes().contains(searchTerm, caseSensitivity); } + +bool Entry::windowMatches(const QString& windowTitle, const QString& windowPattern) +{ + QRegExp regExp; + regExp.setCaseSensitivity(Qt::CaseInsensitive); + + if (windowPattern.startsWith("//") && windowPattern.endsWith("//") && windowPattern.size() >= 4) { + regExp.setPatternSyntax(QRegExp::RegExp2); + regExp.setPattern(windowPattern.mid(2, windowPattern.size() - 4)); + } + else { + regExp.setPatternSyntax(QRegExp::Wildcard); + regExp.setPattern(windowPattern); + } + + return regExp.exactMatch(windowTitle); +} + +QString Entry::resolvePlaceholders(const QString& str) const +{ + QString result = str; + + result.replace("{TITLE}", title(), Qt::CaseInsensitive); + result.replace("{USERNAME}", username(), Qt::CaseInsensitive); + result.replace("{URL}", url(), Qt::CaseInsensitive); + result.replace("{PASSWORD}", password(), Qt::CaseInsensitive); + result.replace("{NOTES}", notes(), Qt::CaseInsensitive); + + // TODO: lots of other placeholders missing + + return result; +} diff --git a/src/core/Entry.h b/src/core/Entry.h index 212da4957..599d35926 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -80,6 +80,7 @@ public: int autoTypeObfuscation() const; QString defaultAutoTypeSequence() const; const QList& autoTypeAssociations() const; + QString autoTypeSequence(const QString& windowTitle = QString()) const; QString title() const; QString url() const; QString username() const; @@ -120,6 +121,7 @@ public: void removeHistoryItems(QList historyEntries); void truncateHistory(); Entry* clone() const; + QString resolvePlaceholders(const QString& str) const; /** * Call before and after set*() methods to create a history item @@ -150,6 +152,7 @@ private Q_SLOTS: private: const Database* database() const; template inline bool set(T& property, const T& value); + static bool windowMatches(const QString& windowTitle, const QString& windowPattern); Uuid m_uuid; EntryData m_data; diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index d1354990e..50e341769 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Tobias Tangemann + * Copyright (C) 2012 Felix Geyer * * 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 @@ -19,6 +20,8 @@ #include +#include "autotype/AutoType.h" + Application::Application(int& argc, char** argv) : QApplication(argc, argv) { @@ -34,3 +37,19 @@ bool Application::event(QEvent* event) return QApplication::event(event); } + +#ifdef Q_WS_X11 +bool Application::x11EventFilter(XEvent* event) +{ + int retCode = autoType()->callEventFilter(event); + + if (retCode == 0) { + return false; + } + else if (retCode == 1) { + return true; + } + + return QApplication::x11EventFilter(event); +} +#endif diff --git a/src/gui/Application.h b/src/gui/Application.h index c66f271bb..ef3646f15 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Tobias Tangemann + * Copyright (C) 2012 Felix Geyer * * 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 @@ -30,6 +31,9 @@ public: Application(int& argc, char** argv); bool event(QEvent* event) Q_DECL_OVERRIDE; +#ifdef Q_WS_X11 + bool x11EventFilter(XEvent* event) Q_DECL_OVERRIDE; +#endif Q_SIGNALS: void openFile(const QString& filename);