diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 7d1ec6b79..cd7e1f665 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -79,9 +79,16 @@ void AutoType::loadPlugin(const QString& pluginPath) QObject* pluginInstance = m_pluginLoader->instance(); if (pluginInstance) { m_plugin = qobject_cast(pluginInstance); + m_executor = Q_NULLPTR; + if (m_plugin) { - m_executor = m_plugin->createExecutor(); - connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SIGNAL(globalShortcutTriggered())); + if (m_plugin->isAvailable()) { + m_executor = m_plugin->createExecutor(); + connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SIGNAL(globalShortcutTriggered())); + } + else { + unloadPlugin(); + } } } @@ -568,7 +575,7 @@ bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPa { if (windowPattern.startsWith("//") && windowPattern.endsWith("//") && windowPattern.size() >= 4) { QRegExp regExp(windowPattern.mid(2, windowPattern.size() - 4), Qt::CaseInsensitive, QRegExp::RegExp2); - return regExp.exactMatch(windowTitle); + return (regExp.indexIn(windowTitle) != -1); } else { return WildcardMatcher(windowTitle).match(windowPattern); diff --git a/src/autotype/AutoTypePlatformPlugin.h b/src/autotype/AutoTypePlatformPlugin.h index 614c8060b..dadc7a0d2 100644 --- a/src/autotype/AutoTypePlatformPlugin.h +++ b/src/autotype/AutoTypePlatformPlugin.h @@ -26,6 +26,7 @@ class AutoTypePlatformInterface { public: virtual ~AutoTypePlatformInterface() {} + virtual bool isAvailable() = 0; virtual QStringList windowTitles() = 0; virtual WId activeWindow() = 0; virtual QString activeWindowTitle() = 0; diff --git a/src/autotype/test/AutoTypeTest.cpp b/src/autotype/test/AutoTypeTest.cpp index 19f0f357e..979af8bde 100644 --- a/src/autotype/test/AutoTypeTest.cpp +++ b/src/autotype/test/AutoTypeTest.cpp @@ -17,6 +17,11 @@ #include "AutoTypeTest.h" +bool AutoTypePlatformTest::isAvailable() +{ + return true; +} + QString AutoTypePlatformTest::keyToString(Qt::Key key) { return QString("[Key0x%1]").arg(key, 0, 16); diff --git a/src/autotype/test/AutoTypeTest.h b/src/autotype/test/AutoTypeTest.h index e7bd3a96a..bd2dfc02a 100644 --- a/src/autotype/test/AutoTypeTest.h +++ b/src/autotype/test/AutoTypeTest.h @@ -35,6 +35,7 @@ class AutoTypePlatformTest : public QObject, public: QString keyToString(Qt::Key key) Q_DECL_OVERRIDE; + bool isAvailable() Q_DECL_OVERRIDE; QStringList windowTitles() Q_DECL_OVERRIDE; WId activeWindow() Q_DECL_OVERRIDE; QString activeWindowTitle() Q_DECL_OVERRIDE; diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index 054aacb0a..8502a93c8 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -58,6 +58,31 @@ AutoTypePlatformX11::AutoTypePlatformX11() updateKeymap(); } +bool AutoTypePlatformX11::isAvailable() +{ + int ignore; + + if (!XQueryExtension(m_dpy, "XInputExtension", &ignore, &ignore, &ignore)) { + return false; + } + + if (!XQueryExtension(m_dpy, "XTEST", &ignore, &ignore, &ignore)) { + return false; + } + + if (!m_xkb) { + XkbDescPtr kbd = getKeyboard(); + + if (!kbd) { + return false; + } + + XkbFreeKeyboard(kbd, XkbAllComponentsMask, True); + } + + return true; +} + void AutoTypePlatformX11::unload() { // Restore the KeyboardMapping to its original state. @@ -185,7 +210,7 @@ int AutoTypePlatformX11::platformEventFilter(void* event) xcb_key_press_event_t* keyPressEvent = static_cast(event); if (keyPressEvent->detail == m_currentGlobalKeycode && (keyPressEvent->state & m_modifierMask) == m_currentGlobalNativeModifiers - && !QApplication::focusWidget() + && (!QApplication::activeWindow() || QApplication::activeWindow()->isMinimized()) && m_loaded) { if (type == XCB_KEY_PRESS) { Q_EMIT globalShortcutTriggered(); @@ -458,21 +483,10 @@ void AutoTypePlatformX11::updateKeymap() int mod_index, mod_key; XModifierKeymap *modifiers; - if (m_xkb != NULL) XkbFreeKeyboard(m_xkb, XkbAllComponentsMask, True); - - XDeviceInfo* devices; - int num_devices; - XID keyboard_id = XkbUseCoreKbd; - devices = XListInputDevices(m_dpy, &num_devices); - - for (int i = 0; i < num_devices; i++) { - if (QString(devices[i].name) == "Virtual core XTEST keyboard") { - keyboard_id = devices[i].id; - break; - } + if (m_xkb) { + XkbFreeKeyboard(m_xkb, XkbAllComponentsMask, True); } - - m_xkb = XkbGetKeyboard(m_dpy, XkbCompatMapMask | XkbGeometryMask, keyboard_id); + m_xkb = getKeyboard(); XDisplayKeycodes(m_dpy, &m_minKeycode, &m_maxKeycode); if (m_keysymTable != NULL) XFree(m_keysymTable); @@ -558,6 +572,27 @@ int AutoTypePlatformX11::x11ErrorHandler(Display* display, XErrorEvent* error) return 1; } +XkbDescPtr AutoTypePlatformX11::getKeyboard() +{ + int num_devices; + XID keyboard_id = XkbUseCoreKbd; + XDeviceInfo* devices = XListInputDevices(m_dpy, &num_devices); + if (!devices) { + return Q_NULLPTR; + } + + for (int i = 0; i < num_devices; i++) { + if (QString(devices[i].name) == "Virtual core XTEST keyboard") { + keyboard_id = devices[i].id; + break; + } + } + + XFreeDeviceList(devices); + + return XkbGetKeyboard(m_dpy, XkbCompatMapMask | XkbGeometryMask, keyboard_id); +} + // -------------------------------------------------------------------------- // The following code is taken from xvkbd 3.0 and has been slightly modified. // -------------------------------------------------------------------------- diff --git a/src/autotype/xcb/AutoTypeXCB.h b/src/autotype/xcb/AutoTypeXCB.h index 63764ba81..c23d69a79 100644 --- a/src/autotype/xcb/AutoTypeXCB.h +++ b/src/autotype/xcb/AutoTypeXCB.h @@ -42,6 +42,7 @@ class AutoTypePlatformX11 : public QObject, public AutoTypePlatformInterface public: AutoTypePlatformX11(); + bool isAvailable() override; void unload() override; QStringList windowTitles() override; WId activeWindow() override; @@ -72,6 +73,7 @@ private: void stopCatchXErrors(); static int x11ErrorHandler(Display* display, XErrorEvent* error); + XkbDescPtr getKeyboard(); void updateKeymap(); bool isRemapKeycodeValid(); int AddKeysym(KeySym keysym); diff --git a/src/crypto/Crypto.cpp b/src/crypto/Crypto.cpp index d39d12aa0..1ff6fdedf 100644 --- a/src/crypto/Crypto.cpp +++ b/src/crypto/Crypto.cpp @@ -27,6 +27,7 @@ bool Crypto::m_initalized(false); QString Crypto::m_errorStr; +QString Crypto::m_backendVersion; Crypto::Crypto() { @@ -39,7 +40,7 @@ bool Crypto::init() return true; } - gcry_check_version(0); + m_backendVersion = QString::fromLocal8Bit(gcry_check_version(0)); gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); if (!checkAlgorithms()) { @@ -67,6 +68,11 @@ QString Crypto::errorString() return m_errorStr; } +QString Crypto::backendVersion() +{ + return QString("libgcrypt ").append(m_backendVersion); +} + bool Crypto::backendSelfTest() { return (gcry_control(GCRYCTL_SELFTEST) == 0); diff --git a/src/crypto/Crypto.h b/src/crypto/Crypto.h index e6187c28a..0ce2903c6 100644 --- a/src/crypto/Crypto.h +++ b/src/crypto/Crypto.h @@ -27,6 +27,7 @@ public: static bool initalized(); static bool backendSelfTest(); static QString errorString(); + static QString backendVersion(); private: Crypto(); @@ -41,6 +42,7 @@ private: static bool m_initalized; static QString m_errorStr; + static QString m_backendVersion; }; #endif // KEEPASSX_CRYPTO_H diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index d4b212d44..6ca02d1aa 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -21,6 +21,7 @@ #include "config-keepassx.h" #include "version.h" #include "core/FilePath.h" +#include "crypto/Crypto.h" AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent) @@ -49,6 +50,12 @@ AboutDialog::AboutDialog(QWidget* parent) m_ui->label_git->setText(labelText); } + QString libs = QString("%1\n- Qt %2\n- %3") + .arg(m_ui->label_libs->text()) + .arg(QString::fromLocal8Bit(qVersion())) + .arg(Crypto::backendVersion()); + m_ui->label_libs->setText(libs); + setAttribute(Qt::WA_DeleteOnClose); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close())); } diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index 79dd5e1ca..0a66b602a 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -14,10 +14,20 @@ About KeePassX + + QLayout::SetFixedSize + - + + + + 0 + 0 + + + @@ -36,6 +46,12 @@ + + + 0 + 0 + + <a href="http://www.keepassx.org/">http://www.keepassx.org/</a> @@ -46,6 +62,12 @@ + + + 0 + 0 + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. @@ -64,6 +86,13 @@ + + + + Using: + + + diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index a334fded9..779a6cc84 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -45,12 +45,18 @@ public: Application::Application(int& argc, char** argv) : QApplication(argc, argv) + , m_mainWindow(Q_NULLPTR) { #if defined(Q_OS_UNIX) && !defined(Q_OS_OSX) installNativeEventFilter(new XcbEventFilter()); #endif } +void Application::setMainWindow(QWidget* mainWindow) +{ + m_mainWindow = mainWindow; +} + bool Application::event(QEvent* event) { // Handle Apple QFileOpenEvent from finder (double click on .kdbx file) @@ -58,6 +64,16 @@ bool Application::event(QEvent* event) Q_EMIT openFile(static_cast(event)->file()); return true; } +#ifdef Q_OS_MAC + // restore main window when clicking on the docker icon + else if ((event->type() == QEvent::ApplicationActivate) && m_mainWindow) { + m_mainWindow->ensurePolished(); + m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized); + m_mainWindow->show(); + m_mainWindow->raise(); + m_mainWindow->activateWindow(); + } +#endif return QApplication::event(event); } diff --git a/src/gui/Application.h b/src/gui/Application.h index 57df5fe0f..149b61ddf 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -27,11 +27,15 @@ class Application : public QApplication public: Application(int& argc, char** argv); + void setMainWindow(QWidget* mainWindow); bool event(QEvent* event) override; Q_SIGNALS: void openFile(const QString& filename); + +private: + QWidget* m_mainWindow; }; #endif // KEEPASSX_APPLICATION_H diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index b6dc6a4e4..157a96265 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "autotype/AutoType.h" #include "core/Config.h" @@ -449,7 +450,7 @@ void MainWindow::changeEvent(QEvent *event) && isTrayIconEnabled() && config()->get("GUI/MinimizeToTray").toBool()) { event->ignore(); - hide(); + QTimer::singleShot(0, this, SLOT(hide())); } else { QMainWindow::changeEvent(event); @@ -575,10 +576,12 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason) void MainWindow::toggleWindow() { - if (QApplication::activeWindow() == this) { + if ((QApplication::activeWindow() == this) && isVisible() && !isMinimized()) { hide(); } else { + ensurePolished(); + setWindowState(windowState() & ~Qt::WindowMinimized); show(); raise(); activateWindow(); @@ -597,6 +600,11 @@ void MainWindow::lockDatabasesAfterInactivity() bool MainWindow::isTrayIconEnabled() const { +#ifdef Q_OS_MAC + // systray not useful on OS X + return false; +#else return config()->get("GUI/ShowTrayIcon").toBool() && QSystemTrayIcon::isSystemTrayAvailable(); +#endif } diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index e774ed21a..8b536a53d 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -41,6 +41,11 @@ SettingsWidget::SettingsWidget(QWidget* parent) m_generalUi->autoTypeShortcutWidget->setVisible(autoType()->isAvailable()); m_generalUi->autoTypeShortcutLabel->setVisible(autoType()->isAvailable()); +#ifdef Q_OS_MAC + // systray not useful on OS X + m_generalUi->systrayShowCheckBox->setVisible(false); + m_generalUi->systrayMinimizeToTrayCheckBox->setVisible(false); +#endif connect(this, SIGNAL(accepted()), SLOT(saveSettings())); connect(this, SIGNAL(rejected()), SLOT(reject())); diff --git a/src/gui/UnlockDatabaseWidget.cpp b/src/gui/UnlockDatabaseWidget.cpp index de703a689..a005d0e60 100644 --- a/src/gui/UnlockDatabaseWidget.cpp +++ b/src/gui/UnlockDatabaseWidget.cpp @@ -33,5 +33,6 @@ void UnlockDatabaseWidget::clearForms() m_ui->comboKeyFile->clear(); m_ui->checkPassword->setChecked(false); m_ui->checkKeyFile->setChecked(false); + m_ui->buttonTogglePassword->setChecked(false); m_db = nullptr; } diff --git a/src/main.cpp b/src/main.cpp index 48de9c782..114dfbb06 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -82,6 +82,7 @@ int main(int argc, char** argv) MainWindow mainWindow; mainWindow.show(); + app.setMainWindow(&mainWindow); QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 3d4a66304..cbd927fe6 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -37,7 +37,6 @@ void TestAutoType::initTestCase() QVERIFY(Crypto::init()); Config::createTempFileInstance(); AutoType::createTestInstance(); - config()->set("AutoTypeEntryTitleMatch", false); config()->set("security/autotypeask", false); QPluginLoader loader(filePath()->pluginPath("keepassx-autotype-test")); @@ -55,6 +54,7 @@ void TestAutoType::initTestCase() void TestAutoType::init() { + config()->set("AutoTypeEntryTitleMatch", false); m_test->clearActions(); m_db = new Database(); @@ -63,11 +63,12 @@ void TestAutoType::init() m_group = new Group(); m_db->setRootGroup(m_group); + AutoTypeAssociations::Association association; + m_entry1 = new Entry(); m_entry1->setGroup(m_group); m_entry1->setUsername("myuser"); m_entry1->setPassword("mypass"); - AutoTypeAssociations::Association association; association.window = "custom window"; association.sequence = "{username}association{password}"; m_entry1->autoTypeAssociations()->add(association); @@ -76,6 +77,19 @@ void TestAutoType::init() m_entry2->setGroup(m_group); m_entry2->setPassword("myuser"); m_entry2->setTitle("entry title"); + + m_entry3 = new Entry(); + m_entry3->setGroup(m_group); + m_entry3->setPassword("regex"); + association.window = "//REGEX1//"; + association.sequence = "regex1"; + m_entry3->autoTypeAssociations()->add(association); + association.window = "//^REGEX2$//"; + association.sequence = "regex2"; + m_entry3->autoTypeAssociations()->add(association); + association.window = "//^REGEX3-([rd]\\d){2}$//"; + association.sequence = "regex3"; + m_entry3->autoTypeAssociations()->add(association); } void TestAutoType::cleanup() @@ -146,12 +160,36 @@ void TestAutoType::testGlobalAutoTypeTitleMatch() void TestAutoType::testGlobalAutoTypeTitleMatchDisabled() { - config()->set("AutoTypeEntryTitleMatch", false); - m_test->setActiveWindowTitle("An Entry Title!"); MessageBox::setNextAnswer(QMessageBox::Ok); m_autoType->performGlobalAutoType(m_dbList); QCOMPARE(m_test->actionChars(), QString()); - +} + +void TestAutoType::testGlobalAutoTypeRegExp() +{ + // substring matches are ok + m_test->setActiveWindowTitle("lorem REGEX1 ipsum"); + m_autoType->performGlobalAutoType(m_dbList); + QCOMPARE(m_test->actionChars(), QString("regex1")); + m_test->clearActions(); + + // should be case-insensitive + m_test->setActiveWindowTitle("lorem regex1 ipsum"); + m_autoType->performGlobalAutoType(m_dbList); + QCOMPARE(m_test->actionChars(), QString("regex1")); + m_test->clearActions(); + + // exact match + m_test->setActiveWindowTitle("REGEX2"); + m_autoType->performGlobalAutoType(m_dbList); + QCOMPARE(m_test->actionChars(), QString("regex2")); + m_test->clearActions(); + + // a bit more complicated regex + m_test->setActiveWindowTitle("REGEX3-R2D2"); + m_autoType->performGlobalAutoType(m_dbList); + QCOMPARE(m_test->actionChars(), QString("regex3")); + m_test->clearActions(); } diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index d46a55969..c12817b1d 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -43,6 +43,7 @@ private Q_SLOTS: void testGlobalAutoTypeWithOneMatch(); void testGlobalAutoTypeTitleMatch(); void testGlobalAutoTypeTitleMatchDisabled(); + void testGlobalAutoTypeRegExp(); private: AutoTypePlatformInterface* m_platform; @@ -53,6 +54,7 @@ private: Group* m_group; Entry* m_entry1; Entry* m_entry2; + Entry* m_entry3; }; #endif // KEEPASSX_TESTAUTOTYPE_H