From 027ff9f2bfac35ca74b0e91bd72625a2b790f050 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 13 Feb 2021 22:13:10 -0500 Subject: [PATCH] Overhaul Auto-Type Action Handling * Close #2603 - Add support for modifier syntax (+, ^, and %) * Fix #2633 - Allow reference syntax {REF:...} in Auto-Type sequences * Close #5334 - Tell the user which part of the Auto-Type sequence is invalid for easy correction * Fix #2401 - Select the right window on macOS prior to starting Auto-Type * Allow for nested placeholders --- src/autotype/AutoType.cpp | 532 +++++++++-------------- src/autotype/AutoType.h | 13 +- src/autotype/AutoTypeAction.cpp | 73 +--- src/autotype/AutoTypeAction.h | 59 +-- src/autotype/AutoTypeSelectDialog.cpp | 1 - src/autotype/mac/AutoTypeMac.cpp | 62 +-- src/autotype/mac/AutoTypeMac.h | 5 +- src/autotype/test/AutoTypeTest.cpp | 30 +- src/autotype/test/AutoTypeTest.h | 9 +- src/autotype/windows/AutoTypeWindows.cpp | 62 +-- src/autotype/windows/AutoTypeWindows.h | 5 +- src/autotype/xcb/AutoTypeXCB.cpp | 33 +- src/autotype/xcb/AutoTypeXCB.h | 5 +- src/core/Global.h | 6 +- src/core/MacPasteboard.h | 2 +- src/gui/MainWindow.cpp | 7 + src/gui/MainWindow.h | 1 + src/gui/entry/EditEntryWidget.cpp | 32 +- src/gui/osutils/nixutils/X11Funcs.cpp | 2 + tests/TestAutoType.cpp | 62 +-- 20 files changed, 435 insertions(+), 566 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 7a52491b3..3373342ef 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -38,11 +38,89 @@ #include "gui/MessageBox.h" #include "gui/osutils/OSUtils.h" +namespace +{ + // Basic Auto-Type placeholder associations + const QHash g_placeholderToKey = {{"tab", Qt::Key_Tab}, + {"enter", Qt::Key_Enter}, + {"space", Qt::Key_Space}, + {"up", Qt::Key_Up}, + {"down", Qt::Key_Down}, + {"left", Qt::Key_Left}, + {"right", Qt::Key_Right}, + {"insert", Qt::Key_Insert}, + {"ins", Qt::Key_Insert}, + {"delete", Qt::Key_Delete}, + {"del", Qt::Key_Delete}, + {"home", Qt::Key_Home}, + {"end", Qt::Key_End}, + {"pgup", Qt::Key_PageUp}, + {"pgdown", Qt::Key_PageDown}, + {"backspace", Qt::Key_Backspace}, + {"bs", Qt::Key_Backspace}, + {"bksp", Qt::Key_Backspace}, + {"break", Qt::Key_Pause}, + {"capslock", Qt::Key_CapsLock}, + {"esc", Qt::Key_Escape}, + {"help", Qt::Key_Help}, + {"numlock", Qt::Key_NumLock}, + {"ptrsc", Qt::Key_Print}, + {"scrolllock", Qt::Key_ScrollLock}, + {"win", Qt::Key_Meta}, + {"lwin", Qt::Key_Meta}, + {"rwin", Qt::Key_Meta}, + {"apps", Qt::Key_Menu}, + {"help", Qt::Key_Help}, + {"add", Qt::Key_Plus}, + {"subtract", Qt::Key_Minus}, + {"multiply", Qt::Key_Asterisk}, + {"divide", Qt::Key_Slash}, + {"leftbrace", Qt::Key_BraceLeft}, + {"{", Qt::Key_BraceLeft}, + {"rightbrace", Qt::Key_BraceRight}, + {"}", Qt::Key_BraceRight}, + {"leftparen", Qt::Key_ParenLeft}, + {"(", Qt::Key_ParenLeft}, + {"rightparen", Qt::Key_ParenRight}, + {")", Qt::Key_ParenRight}, + {"[", Qt::Key_BracketLeft}, + {"]", Qt::Key_BracketRight}, + {"+", Qt::Key_Plus}, + {"%", Qt::Key_Percent}, + {"^", Qt::Key_AsciiCircum}, + {"~", Qt::Key_AsciiTilde}, + {"numpad0", Qt::Key_0}, + {"numpad1", Qt::Key_1}, + {"numpad2", Qt::Key_2}, + {"numpad3", Qt::Key_3}, + {"numpad4", Qt::Key_4}, + {"numpad5", Qt::Key_5}, + {"numpad6", Qt::Key_6}, + {"numpad7", Qt::Key_7}, + {"numpad8", Qt::Key_8}, + {"numpad9", Qt::Key_9}, + {"f1", Qt::Key_F1}, + {"f2", Qt::Key_F2}, + {"f3", Qt::Key_F3}, + {"f4", Qt::Key_F4}, + {"f5", Qt::Key_F5}, + {"f6", Qt::Key_F6}, + {"f7", Qt::Key_F7}, + {"f8", Qt::Key_F8}, + {"f9", Qt::Key_F9}, + {"f10", Qt::Key_F10}, + {"f11", Qt::Key_F11}, + {"f12", Qt::Key_F12}, + {"f13", Qt::Key_F13}, + {"f14", Qt::Key_F14}, + {"f15", Qt::Key_F15}, + {"f16", Qt::Key_F16}}; +} // namespace + AutoType* AutoType::m_instance = nullptr; AutoType::AutoType(QObject* parent, bool test) : QObject(parent) - , m_autoTypeDelay(0) , m_pluginLoader(new QPluginLoader(this)) , m_plugin(nullptr) , m_executor(nullptr) @@ -174,22 +252,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c return; } - // no edit to the sequence beyond this point - if (!verifyAutoTypeSyntax(sequence)) { - emit autotypeRejected(); - m_inAutoType.unlock(); - return; - } - - QList actions; - ListDeleter actionsDeleter(&actions); - - if (!parseActions(sequence, entry, actions)) { - emit autotypeRejected(); - m_inAutoType.unlock(); - return; - } - if (hideWindow) { #if defined(Q_OS_MACOS) // Check for accessibility permission @@ -209,16 +271,21 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c #endif } - Tools::wait(qMax(100, config()->get(Config::AutoTypeStartDelay).toInt())); + auto actions = parseActions(sequence, entry); + + // Restore window state in case app stole focus + restoreWindowState(); + QApplication::processEvents(); + m_plugin->raiseWindow(m_windowForGlobal); // Used only for selected entry auto-type if (!window) { window = m_plugin->activeWindow(); } - QCoreApplication::processEvents(QEventLoop::AllEvents, 10); + Tools::wait(qMax(100, config()->get(Config::AutoTypeStartDelay).toInt())); - for (AutoTypeAction* action : asConst(actions)) { + for (auto action : asConst(actions)) { if (m_plugin->activeWindow() != window) { qWarning("Active window changed, interrupting auto-type."); emit autotypeRejected(); @@ -226,7 +293,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c return; } - action->accept(m_executor); + action->exec(m_executor); QCoreApplication::processEvents(QEventLoop::AllEvents, 10); } @@ -235,7 +302,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c // emit signal only if autotype performed correctly emit autotypePerformed(); - m_inAutoType.unlock(); } @@ -350,20 +416,16 @@ void AutoType::performGlobalAutoType(const QList>& dbLi } // Show the selection dialog if we always ask, have multiple matches, or no matches - if (config()->get(Config::Security_AutoTypeAsk).toBool() || matchList.size() > 1 || matchList.isEmpty()) { + if (getMainWindow() + && (config()->get(Config::Security_AutoTypeAsk).toBool() || matchList.size() > 1 || matchList.isEmpty())) { // Close any open modal windows that would interfere with the process - if (qApp->modalWindow()) { - qApp->modalWindow()->close(); - } + getMainWindow()->closeModalWindow(); auto* selectDialog = new AutoTypeSelectDialog(); selectDialog->setMatches(matchList, dbList); connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject); connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](AutoTypeMatch match) { - restoreWindowState(); - QApplication::processEvents(); - m_plugin->raiseWindow(m_windowForGlobal); executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal); resetAutoTypeState(); }); @@ -375,7 +437,7 @@ void AutoType::performGlobalAutoType(const QList>& dbLi #ifdef Q_OS_MACOS m_plugin->raiseOwnWindow(); - Tools::wait(200); + Tools::wait(50); #endif selectDialog->show(); selectDialog->raise(); @@ -386,7 +448,6 @@ void AutoType::performGlobalAutoType(const QList>& dbLi resetAutoTypeState(); } else { // We should never get here - Q_ASSERT(false); resetAutoTypeState(); emit autotypeRejected(); } @@ -414,316 +475,135 @@ void AutoType::resetAutoTypeState() } /** - * Parse an autotype sequence and resolve its Template/command inside as AutoTypeActions + * Parse an autotype sequence into a list of AutoTypeActions. + * If error is provided then syntax checking will be performed. */ -bool AutoType::parseActions(const QString& actionSequence, const Entry* entry, QList& actions) +QList> +AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString* error) { - QString tmpl; - bool inTmpl = false; - m_autoTypeDelay = qMax(config()->get(Config::AutoTypeDelay).toInt(), 0); + const int maxTypeDelay = 100; + const int maxWaitDelay = 10000; + const int maxRepetition = 100; - QString sequence = actionSequence; + QList> actions; + actions << QSharedPointer::create(qMax(0, config()->get(Config::AutoTypeDelay).toInt()), true); + + // Replace escaped braces with a template for easier regex + QString sequence = entrySequence; sequence.replace("{{}", "{LEFTBRACE}"); sequence.replace("{}}", "{RIGHTBRACE}"); - for (const QChar& ch : sequence) { - if (inTmpl) { - if (ch == '{') { - qWarning("Syntax error in Auto-Type sequence."); - return false; - } else if (ch == '}') { - QList autoType = createActionFromTemplate(tmpl, entry); - if (!autoType.isEmpty()) { - actions.append(autoType); + // Quick test for bracket syntax + if ((sequence.count("{") + sequence.count("}")) % 2 != 0 && error) { + *error = tr("Bracket imbalance detected, found extra { or }"); + return {}; + } + + // Group 1 = modifier key (opt) + // Group 2 = full placeholder + // Group 3 = inner placeholder (allows nested placeholders) + // Group 4 = repeat (opt) + // Group 5 = character + QRegularExpression regex("([+%^]*)({((?>[^{}]+?|(?2))+?)(?:\\s+(\\d+))?})|(.)"); + auto results = regex.globalMatch(sequence); + while (results.hasNext()) { + auto match = results.next(); + + // Parse modifier keys + Qt::KeyboardModifiers modifiers; + if (match.captured(1).contains('+')) { + modifiers |= Qt::ShiftModifier; + } + if (match.captured(1).contains('^')) { + modifiers |= Qt::ControlModifier; + } + if (match.captured(1).contains('%')) { + modifiers |= Qt::AltModifier; + } + + const auto fullPlaceholder = match.captured(2); + auto placeholder = match.captured(3).toLower(); + auto repeat = 1; + if (!match.captured(4).isEmpty()) { + repeat = match.captured(4).toInt(); + } + auto character = match.captured(5); + + if (!placeholder.isEmpty()) { + if (g_placeholderToKey.contains(placeholder)) { + // Basic key placeholder, allow repeat + if (repeat > maxRepetition && error) { + *error = tr("Too many repetitions detected, max is %1: %2").arg(maxRepetition).arg(fullPlaceholder); + return {}; } - inTmpl = false; - tmpl.clear(); + auto action = QSharedPointer::create(g_placeholderToKey[placeholder], modifiers); + for (int i = 1; i <= repeat && i <= maxRepetition; ++i) { + actions << action; + } + } else if (placeholder.startsWith("delay=")) { + // Change keypress delay + int delay = placeholder.replace("delay=", "").toInt(); + if (delay > maxTypeDelay && error) { + *error = tr("Very slow key press detected, max is %1: %2").arg(maxTypeDelay).arg(fullPlaceholder); + return {}; + } + actions << QSharedPointer::create(qBound(0, delay, maxTypeDelay), true); + } else if (placeholder == "delay") { + // Mid typing delay (wait) + if (repeat > maxWaitDelay && error) { + *error = tr("Very long delay detected, max is %1: %2").arg(maxWaitDelay).arg(fullPlaceholder); + return {}; + } + actions << QSharedPointer::create(qBound(0, repeat, maxWaitDelay)); + } else if (placeholder == "clearfield") { + // Platform-specific field clearing + actions << QSharedPointer::create(); + } else if (placeholder == "totp") { + // Entry totp (requires special handling) + QString totp = entry->totp(); + for (const auto& ch : totp) { + actions << QSharedPointer::create(ch); + } + } else if (placeholder == "beep" || placeholder.startsWith("vkey") + || placeholder.startsWith("appactivate")) { + // Ignore these commands } else { - tmpl += ch; + // Attempt to resolve an entry attribute + auto resolved = entry->resolvePlaceholder(fullPlaceholder); + if (resolved != fullPlaceholder) { + // Attribute resolved, add characters to action list + for (const QChar& ch : resolved) { + if (ch == '\n') { + actions << QSharedPointer::create(g_placeholderToKey["enter"]); + } else if (ch == '\t') { + actions << QSharedPointer::create(g_placeholderToKey["tab"]); + } else { + actions << QSharedPointer::create(ch); + } + } + } else if (error) { + // Invalid placeholder, issue error and stop processing + *error = tr("Invalid placeholder: %1").arg(fullPlaceholder); + return {}; + } } - } else if (ch == '{') { - inTmpl = true; - } else if (ch == '}') { - qWarning("Syntax error in Auto-Type sequence."); - return false; - } else { - actions.append(new AutoTypeChar(ch)); + } else if (!character.isEmpty()) { + // Type a single character with modifier + actions << QSharedPointer::create(character[0], modifiers); } } - if (m_autoTypeDelay > 0) { - QList::iterator i; - i = actions.begin(); - while (i != actions.end()) { - ++i; - if (i != actions.end()) { - i = actions.insert(i, new AutoTypeDelay(m_autoTypeDelay)); - ++i; - } - } - } - return true; + + return actions; } /** - * Convert an autotype Template/command to its AutoTypeAction that will be executed by the plugin executor + * Verify if the syntax of an autotype sequence is correct and doesn't have invalid parameters */ -QList AutoType::createActionFromTemplate(const QString& tmpl, const Entry* entry) +bool AutoType::verifyAutoTypeSyntax(const QString& sequence, const Entry* entry, QString& error) { - QString tmplName = tmpl; - int num = -1; - QList list; - - QRegExp delayRegEx("delay=(\\d+)", Qt::CaseInsensitive, QRegExp::RegExp2); - if (delayRegEx.exactMatch(tmplName)) { - num = delayRegEx.cap(1).toInt(); - m_autoTypeDelay = std::max(0, std::min(num, 10000)); - return list; + error.clear(); + if (!sequence.isEmpty()) { + parseActions(sequence, entry, &error); } - - QRegExp repeatRegEx("(.+) (\\d+)", Qt::CaseInsensitive, QRegExp::RegExp2); - if (repeatRegEx.exactMatch(tmplName)) { - tmplName = repeatRegEx.cap(1); - num = repeatRegEx.cap(2).toInt(); - - if (num == 0) { - return list; - } - } - - if (tmplName.compare("tab", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Tab)); - } else if (tmplName.compare("enter", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Enter)); - } else if (tmplName.compare("space", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Space)); - } else if (tmplName.compare("up", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Up)); - } else if (tmplName.compare("down", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Down)); - } else if (tmplName.compare("left", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Left)); - } else if (tmplName.compare("right", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Right)); - } else if (tmplName.compare("insert", Qt::CaseInsensitive) == 0 - || tmplName.compare("ins", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Insert)); - } else if (tmplName.compare("delete", Qt::CaseInsensitive) == 0 - || tmplName.compare("del", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Delete)); - } else if (tmplName.compare("home", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Home)); - } else if (tmplName.compare("end", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_End)); - } else if (tmplName.compare("pgup", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_PageUp)); - } else if (tmplName.compare("pgdown", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_PageDown)); - } else if (tmplName.compare("backspace", Qt::CaseInsensitive) == 0 - || tmplName.compare("bs", Qt::CaseInsensitive) == 0 - || tmplName.compare("bksp", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Backspace)); - } else if (tmplName.compare("break", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Pause)); - } else if (tmplName.compare("capslock", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_CapsLock)); - } else if (tmplName.compare("esc", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Escape)); - } else if (tmplName.compare("help", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Help)); - } else if (tmplName.compare("numlock", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_NumLock)); - } else if (tmplName.compare("ptrsc", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_Print)); - } else if (tmplName.compare("scrolllock", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeKey(Qt::Key_ScrollLock)); - } - // Qt doesn't know about keypad keys so use the normal ones instead - else if (tmplName.compare("add", Qt::CaseInsensitive) == 0 || tmplName.compare("+", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar('+')); - } else if (tmplName.compare("subtract", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar('-')); - } else if (tmplName.compare("multiply", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar('*')); - } else if (tmplName.compare("divide", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar('/')); - } else if (tmplName.compare("^", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar('^')); - } else if (tmplName.compare("%", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar('%')); - } else if (tmplName.compare("~", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar('~')); - } else if (tmplName.compare("(", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar('(')); - } else if (tmplName.compare(")", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar(')')); - } else if (tmplName.compare("leftbrace", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeChar('{')); - } else if (tmplName.compare("rightbrace", Qt::CaseInsensitive) == 0) { - 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.compare("delay", Qt::CaseInsensitive) == 0 && num > 0) { - list.append(new AutoTypeDelay(num)); - } else if (tmplName.compare("clearfield", Qt::CaseInsensitive) == 0) { - list.append(new AutoTypeClearField()); - } else if (tmplName.compare("totp", Qt::CaseInsensitive) == 0) { - QString totp = entry->totp(); - if (!totp.isEmpty()) { - for (const QChar& ch : totp) { - list.append(new AutoTypeChar(ch)); - } - } - } - - if (!list.isEmpty()) { - return list; - } - - const QString placeholder = QString("{%1}").arg(tmplName); - const QString resolved = entry->resolvePlaceholder(placeholder); - if (placeholder != resolved) { - for (const QChar& ch : resolved) { - if (ch == '\n') { - list.append(new AutoTypeKey(Qt::Key_Enter)); - } else if (ch == '\t') { - list.append(new AutoTypeKey(Qt::Key_Tab)); - } else { - list.append(new AutoTypeChar(ch)); - } - } - } - - return list; -} - -/** - * Checks if the overall syntax of an autotype sequence is fine - */ -bool AutoType::checkSyntax(const QString& string) -{ - QString allowRepetition = "(?:\\s\\d+)?"; - // the ":" allows custom commands with syntax S:Field - // exclude BEEP otherwise will be checked as valid - QString normalCommands = "(?!BEEP\\s)[A-Z:_]*" + allowRepetition; - QString specialLiterals = "[\\^\\%\\(\\)~\\{\\}\\[\\]\\+]" + allowRepetition; - QString functionKeys = "(?:F[1-9]" + allowRepetition + "|F1[0-2])" + allowRepetition; - QString numpad = "NUMPAD\\d" + allowRepetition; - QString delay = "DELAY=\\d+"; - QString beep = "BEEP\\s\\d+\\s\\d+"; - QString vkey = "VKEY(?:-[EN]X)?\\s\\w+"; - QString customAttributes = "S:(?:[^\\{\\}])+"; - - // these chars aren't in parentheses - QString shortcutKeys = "[\\^\\%~\\+@]"; - // a normal string not in parentheses - QString fixedStrings = "[^\\^\\%~\\+@\\{\\}]*"; - // clang-format off - QRegularExpression autoTypeSyntax( - "^(?:" + shortcutKeys + "|" + fixedStrings + "|\\{(?:" + normalCommands + "|" + specialLiterals + "|" - + functionKeys - + "|" - + numpad - + "|" - + delay - + "|" - + beep - + "|" - + vkey - + ")\\}|\\{" - + customAttributes - + "\\})*$", - QRegularExpression::CaseInsensitiveOption); - // clang-format on - QRegularExpressionMatch match = autoTypeSyntax.match(string); - return match.hasMatch(); -} - -/** - * Checks an autotype sequence for high delay - */ -bool AutoType::checkHighDelay(const QString& string) -{ - // 5 digit numbers(10 seconds) are too much - QRegularExpression highDelay("\\{DELAY\\s\\d{5,}\\}", QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch match = highDelay.match(string); - return match.hasMatch(); -} - -/** - * Checks an autotype sequence for slow keypress - */ -bool AutoType::checkSlowKeypress(const QString& string) -{ - // 3 digit numbers(100 milliseconds) are too much - QRegularExpression slowKeypress("\\{DELAY=\\d{3,}\\}", QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch match = slowKeypress.match(string); - return match.hasMatch(); -} - -/** - * Checks an autotype sequence for high repetition command - */ -bool AutoType::checkHighRepetition(const QString& string) -{ - // 3 digit numbers are too much - QRegularExpression highRepetition("\\{(?!DELAY\\s)\\w+\\s\\d{3,}\\}", QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch match = highRepetition.match(string); - return match.hasMatch(); -} - -/** - * Verify if the syntax of an autotype sequence is correct and doesn't have silly parameters - */ -bool AutoType::verifyAutoTypeSyntax(const QString& sequence) -{ - if (!AutoType::checkSyntax(sequence)) { - QMessageBox::critical(nullptr, tr("Auto-Type"), tr("The Syntax of your Auto-Type statement is incorrect!")); - return false; - } else if (AutoType::checkHighDelay(sequence)) { - QMessageBox::StandardButton reply; - reply = QMessageBox::question( - nullptr, - tr("Auto-Type"), - tr("This Auto-Type command contains a very long delay. Do you really want to proceed?")); - - if (reply == QMessageBox::No) { - return false; - } - } else if (AutoType::checkSlowKeypress(sequence)) { - QMessageBox::StandardButton reply; - reply = QMessageBox::question( - nullptr, - tr("Auto-Type"), - tr("This Auto-Type command contains very slow key presses. Do you really want to proceed?")); - - if (reply == QMessageBox::No) { - return false; - } - } else if (AutoType::checkHighRepetition(sequence)) { - QMessageBox::StandardButton reply; - reply = QMessageBox::question(nullptr, - tr("Auto-Type"), - tr("This Auto-Type command contains arguments which are " - "repeated very often. Do you really want to proceed?")); - - if (reply == QMessageBox::No) { - return false; - } - } - return true; + return error.isEmpty(); } diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index 0af78c4c5..c11845d41 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -41,14 +41,11 @@ public: QStringList windowTitles(); bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error = nullptr); void unregisterGlobalShortcut(); - static bool checkSyntax(const QString& string); - static bool checkHighRepetition(const QString& string); - static bool checkSlowKeypress(const QString& string); - static bool checkHighDelay(const QString& string); - static bool verifyAutoTypeSyntax(const QString& sequence); void performAutoType(const Entry* entry, QWidget* hideWindow = nullptr); void performAutoTypeWithSequence(const Entry* entry, const QString& sequence, QWidget* hideWindow = nullptr); + static bool verifyAutoTypeSyntax(const QString& sequence, const Entry* entry, QString& error); + inline bool isAvailable() { return m_plugin; @@ -85,14 +82,14 @@ private: QWidget* hideWindow = nullptr, const QString& customSequence = QString(), WId window = 0); - bool parseActions(const QString& sequence, const Entry* entry, QList& actions); - QList createActionFromTemplate(const QString& tmpl, const Entry* entry); void restoreWindowState(); void resetAutoTypeState(); + static QList> + parseActions(const QString& entrySequence, const Entry* entry, QString* error = nullptr); + QMutex m_inAutoType; QMutex m_inGlobalAutoTypeDialog; - int m_autoTypeDelay; QPluginLoader* m_pluginLoader; AutoTypePlatformInterface* m_plugin; AutoTypeExecutor* m_executor; diff --git a/src/autotype/AutoTypeAction.cpp b/src/autotype/AutoTypeAction.cpp index f9d928f0d..469243371 100644 --- a/src/autotype/AutoTypeAction.cpp +++ b/src/autotype/AutoTypeAction.cpp @@ -1,4 +1,5 @@ /* + * Copyright (C) 2021 Team KeePassXC * Copyright (C) 2012 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -19,77 +20,41 @@ #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) +AutoTypeKey::AutoTypeKey(Qt::Key key, Qt::KeyboardModifiers modifiers) : key(key) + , modifiers(modifiers) { } -AutoTypeAction* AutoTypeKey::clone() +AutoTypeKey::AutoTypeKey(const QChar& character, Qt::KeyboardModifiers modifiers) + : character(character) + , modifiers(modifiers) { - return new AutoTypeKey(key); } -void AutoTypeKey::accept(AutoTypeExecutor* executor) +void AutoTypeKey::exec(AutoTypeExecutor* executor) const { - executor->execKey(this); + executor->execType(this); } -AutoTypeDelay::AutoTypeDelay(int delayMs) +AutoTypeDelay::AutoTypeDelay(int delayMs, bool setExecDelay) : delayMs(delayMs) + , setExecDelay(setExecDelay) { } -AutoTypeAction* AutoTypeDelay::clone() +void AutoTypeDelay::exec(AutoTypeExecutor* executor) const { - return new AutoTypeDelay(delayMs); + if (setExecDelay) { + // Change the delay between actions + executor->execDelayMs = delayMs; + } else { + // Pause execution + Tools::wait(delayMs); + } } -void AutoTypeDelay::accept(AutoTypeExecutor* executor) -{ - executor->execDelay(this); -} - -AutoTypeClearField::AutoTypeClearField() -{ -} - -AutoTypeAction* AutoTypeClearField::clone() -{ - return new AutoTypeClearField(); -} - -void AutoTypeClearField::accept(AutoTypeExecutor* executor) +void AutoTypeClearField::exec(AutoTypeExecutor* executor) const { executor->execClearField(this); } - -void AutoTypeExecutor::execDelay(AutoTypeDelay* action) -{ - Tools::wait(action->delayMs); -} - -void AutoTypeExecutor::execClearField(AutoTypeClearField* action) -{ - Q_UNUSED(action); -} - -AutoTypeAction::~AutoTypeAction() -{ - // This makes sure that AutoTypeAction's vtable is placed - // in this translation unit. -} diff --git a/src/autotype/AutoTypeAction.h b/src/autotype/AutoTypeAction.h index e598b1dcc..fdb068736 100644 --- a/src/autotype/AutoTypeAction.h +++ b/src/autotype/AutoTypeAction.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2021 Team KeePassXC * Copyright (C) 2012 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -19,69 +20,55 @@ #define KEEPASSX_AUTOTYPEACTION_H #include -#include -#include #include "core/Global.h" class AutoTypeExecutor; -class KEEPASSX_EXPORT AutoTypeAction +class KEEPASSXC_EXPORT AutoTypeAction { public: - virtual AutoTypeAction* clone() = 0; - virtual void accept(AutoTypeExecutor* executor) = 0; - virtual ~AutoTypeAction(); + AutoTypeAction() = default; + virtual void exec(AutoTypeExecutor* executor) const = 0; + virtual ~AutoTypeAction() = default; }; -class KEEPASSX_EXPORT AutoTypeChar : public AutoTypeAction +class KEEPASSXC_EXPORT AutoTypeKey : public AutoTypeAction { public: - explicit AutoTypeChar(const QChar& character); - AutoTypeAction* clone() override; - void accept(AutoTypeExecutor* executor) override; + explicit AutoTypeKey(const QChar& character, Qt::KeyboardModifiers modifiers = Qt::NoModifier); + explicit AutoTypeKey(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier); + void exec(AutoTypeExecutor* executor) const override; const QChar character; + const Qt::Key key = Qt::Key_unknown; + const Qt::KeyboardModifiers modifiers; }; -class KEEPASSX_EXPORT AutoTypeKey : public AutoTypeAction +class KEEPASSXC_EXPORT AutoTypeDelay : public AutoTypeAction { public: - explicit AutoTypeKey(Qt::Key key); - AutoTypeAction* clone() override; - void accept(AutoTypeExecutor* executor) override; - - const Qt::Key key; -}; - -class KEEPASSX_EXPORT AutoTypeDelay : public AutoTypeAction -{ -public: - explicit AutoTypeDelay(int delayMs); - AutoTypeAction* clone() override; - void accept(AutoTypeExecutor* executor) override; + explicit AutoTypeDelay(int delayMs, bool setExecDelay = false); + void exec(AutoTypeExecutor* executor) const override; const int delayMs; + const bool setExecDelay; }; -class KEEPASSX_EXPORT AutoTypeClearField : public AutoTypeAction +class KEEPASSXC_EXPORT AutoTypeClearField : public AutoTypeAction { public: - AutoTypeClearField(); - AutoTypeAction* clone() override; - void accept(AutoTypeExecutor* executor) override; + void exec(AutoTypeExecutor* executor) const override; }; -class KEEPASSX_EXPORT AutoTypeExecutor +class KEEPASSXC_EXPORT 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); + virtual ~AutoTypeExecutor() = default; + virtual void execType(const AutoTypeKey* action) = 0; + virtual void execClearField(const AutoTypeClearField* action) = 0; + + int execDelayMs = 25; }; #endif // KEEPASSX_AUTOTYPEACTION_H diff --git a/src/autotype/AutoTypeSelectDialog.cpp b/src/autotype/AutoTypeSelectDialog.cpp index 836ba3070..8b80f491c 100644 --- a/src/autotype/AutoTypeSelectDialog.cpp +++ b/src/autotype/AutoTypeSelectDialog.cpp @@ -57,7 +57,6 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) } }); - m_ui->search->setFocus(); m_ui->search->installEventFilter(this); diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 6f9cb997c..1ae8b38d5 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -17,6 +17,7 @@ */ #include "AutoTypeMac.h" +#include "core/Tools.h" #include "gui/osutils/macutils/MacUtils.h" #include "gui/MessageBox.h" @@ -214,36 +215,43 @@ AutoTypeExecutorMac::AutoTypeExecutorMac(AutoTypePlatformMac* platform) { } -void AutoTypeExecutorMac::execChar(AutoTypeChar* action) +void AutoTypeExecutorMac::execType(const AutoTypeKey* action) { - m_platform->sendChar(action->character, true); - m_platform->sendChar(action->character, false); + if (action->modifiers & Qt::ShiftModifier) { + m_platform->sendKey(Qt::Key_Shift, true); + } + if (action->modifiers & Qt::ControlModifier) { + m_platform->sendKey(Qt::Key_Control, true); + } + if (action->modifiers & Qt::AltModifier) { + m_platform->sendKey(Qt::Key_Alt, true); + } + + if (action->key != Qt::Key_unknown) { + m_platform->sendKey(action->key, true); + m_platform->sendKey(action->key, false); + } else { + m_platform->sendChar(action->character, true); + m_platform->sendChar(action->character, false); + } + + if (action->modifiers & Qt::ShiftModifier) { + m_platform->sendKey(Qt::Key_Shift, false); + } + if (action->modifiers & Qt::ControlModifier) { + m_platform->sendKey(Qt::Key_Control, false); + } + if (action->modifiers & Qt::AltModifier) { + m_platform->sendKey(Qt::Key_Alt, false); + } + + Tools::sleep(execDelayMs); } -void AutoTypeExecutorMac::execKey(AutoTypeKey* action) -{ - m_platform->sendKey(action->key, true); - m_platform->sendKey(action->key, false); -} - -void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr) +void AutoTypeExecutorMac::execClearField(const AutoTypeClearField* action) { Q_UNUSED(action); - - m_platform->sendKey(Qt::Key_Control, true, Qt::ControlModifier); - m_platform->sendKey(Qt::Key_Up, true, Qt::ControlModifier); - m_platform->sendKey(Qt::Key_Up, false, Qt::ControlModifier); - m_platform->sendKey(Qt::Key_Control, false); - usleep(25 * 1000); - m_platform->sendKey(Qt::Key_Shift, true, Qt::ShiftModifier); - m_platform->sendKey(Qt::Key_Control, true, Qt::ShiftModifier | Qt::ControlModifier); - m_platform->sendKey(Qt::Key_Down, true, Qt::ShiftModifier | Qt::ControlModifier); - m_platform->sendKey(Qt::Key_Down, false, Qt::ShiftModifier | Qt::ControlModifier); - m_platform->sendKey(Qt::Key_Control, false, Qt::ShiftModifier); - m_platform->sendKey(Qt::Key_Shift, false); - usleep(25 * 1000); - m_platform->sendKey(Qt::Key_Backspace, true); - m_platform->sendKey(Qt::Key_Backspace, false); - - usleep(25 * 1000); + execType(new AutoTypeKey(Qt::Key_Up, Qt::ControlModifier)); + execType(new AutoTypeKey(Qt::Key_Down, Qt::ControlModifier | Qt::ShiftModifier)); + execType(new AutoTypeKey(Qt::Key_Backspace)); } diff --git a/src/autotype/mac/AutoTypeMac.h b/src/autotype/mac/AutoTypeMac.h index 4ae9e2140..f23a7c1d8 100644 --- a/src/autotype/mac/AutoTypeMac.h +++ b/src/autotype/mac/AutoTypeMac.h @@ -57,9 +57,8 @@ class AutoTypeExecutorMac : public AutoTypeExecutor public: explicit AutoTypeExecutorMac(AutoTypePlatformMac* platform); - void execChar(AutoTypeChar* action) override; - void execKey(AutoTypeKey* action) override; - void execClearField(AutoTypeClearField* action) override; + void execType(const AutoTypeKey* action) override; + void execClearField(const AutoTypeClearField* action) override; private: AutoTypePlatformMac* const m_platform; diff --git a/src/autotype/test/AutoTypeTest.cpp b/src/autotype/test/AutoTypeTest.cpp index aa768238f..906edae34 100644 --- a/src/autotype/test/AutoTypeTest.cpp +++ b/src/autotype/test/AutoTypeTest.cpp @@ -59,27 +59,23 @@ QString AutoTypePlatformTest::actionChars() int AutoTypePlatformTest::actionCount() { - return m_actionList.size(); + return m_actionCount; } void AutoTypePlatformTest::clearActions() { - qDeleteAll(m_actionList); - m_actionList.clear(); - m_actionChars.clear(); + m_actionCount = 0; } -void AutoTypePlatformTest::addActionChar(AutoTypeChar* action) +void AutoTypePlatformTest::addAction(const AutoTypeKey* action) { - m_actionList.append(action->clone()); - m_actionChars += action->character; -} - -void AutoTypePlatformTest::addActionKey(AutoTypeKey* action) -{ - m_actionList.append(action->clone()); - m_actionChars.append(keyToString(action->key)); + ++m_actionCount; + if (action->key != Qt::Key_unknown) { + m_actionChars += keyToString(action->key); + } else { + m_actionChars += action->character; + } } bool AutoTypePlatformTest::raiseWindow(WId window) @@ -106,12 +102,12 @@ AutoTypeExecutorTest::AutoTypeExecutorTest(AutoTypePlatformTest* platform) { } -void AutoTypeExecutorTest::execChar(AutoTypeChar* action) +void AutoTypeExecutorTest::execType(const AutoTypeKey* action) { - m_platform->addActionChar(action); + m_platform->addAction(action); } -void AutoTypeExecutorTest::execKey(AutoTypeKey* action) +void AutoTypeExecutorTest::execClearField(const AutoTypeClearField* action) { - m_platform->addActionKey(action); + Q_UNUSED(action); } diff --git a/src/autotype/test/AutoTypeTest.h b/src/autotype/test/AutoTypeTest.h index dbbbe4642..9758d281f 100644 --- a/src/autotype/test/AutoTypeTest.h +++ b/src/autotype/test/AutoTypeTest.h @@ -51,12 +51,11 @@ public: int actionCount() override; void clearActions() override; - void addActionChar(AutoTypeChar* action); - void addActionKey(AutoTypeKey* action); + void addAction(const AutoTypeKey* action); private: QString m_activeWindowTitle; - QList m_actionList; + int m_actionCount = 0; QString m_actionChars; }; @@ -65,8 +64,8 @@ class AutoTypeExecutorTest : public AutoTypeExecutor public: explicit AutoTypeExecutorTest(AutoTypePlatformTest* platform); - void execChar(AutoTypeChar* action) override; - void execKey(AutoTypeKey* action) override; + void execType(const AutoTypeKey* action) override; + void execClearField(const AutoTypeClearField* action) override; private: AutoTypePlatformTest* const m_platform; diff --git a/src/autotype/windows/AutoTypeWindows.cpp b/src/autotype/windows/AutoTypeWindows.cpp index 435c52d09..6e796e5c7 100644 --- a/src/autotype/windows/AutoTypeWindows.cpp +++ b/src/autotype/windows/AutoTypeWindows.cpp @@ -17,6 +17,7 @@ */ #include "AutoTypeWindows.h" +#include "core/Tools.h" #include "gui/osutils/OSUtils.h" #include @@ -225,36 +226,43 @@ AutoTypeExecutorWin::AutoTypeExecutorWin(AutoTypePlatformWin* platform) { } -void AutoTypeExecutorWin::execChar(AutoTypeChar* action) +void AutoTypeExecutorWin::execType(const AutoTypeKey* action) { - m_platform->sendChar(action->character, true); - m_platform->sendChar(action->character, false); + if (action->modifiers & Qt::ShiftModifier) { + m_platform->sendKey(Qt::Key_Shift, true); + } + if (action->modifiers & Qt::ControlModifier) { + m_platform->sendKey(Qt::Key_Control, true); + } + if (action->modifiers & Qt::AltModifier) { + m_platform->sendKey(Qt::Key_Alt, true); + } + + if (action->key != Qt::Key_unknown) { + m_platform->sendKey(action->key, true); + m_platform->sendKey(action->key, false); + } else { + m_platform->sendChar(action->character, true); + m_platform->sendChar(action->character, false); + } + + if (action->modifiers & Qt::ShiftModifier) { + m_platform->sendKey(Qt::Key_Shift, false); + } + if (action->modifiers & Qt::ControlModifier) { + m_platform->sendKey(Qt::Key_Control, false); + } + if (action->modifiers & Qt::AltModifier) { + m_platform->sendKey(Qt::Key_Alt, false); + } + + Tools::sleep(execDelayMs); } -void AutoTypeExecutorWin::execKey(AutoTypeKey* action) -{ - m_platform->sendKey(action->key, true); - m_platform->sendKey(action->key, false); -} - -void AutoTypeExecutorWin::execClearField(AutoTypeClearField* action = nullptr) +void AutoTypeExecutorWin::execClearField(const AutoTypeClearField* action) { Q_UNUSED(action); - - m_platform->sendKey(Qt::Key_Control, true); - m_platform->sendKey(Qt::Key_Home, true); - m_platform->sendKey(Qt::Key_Home, false); - m_platform->sendKey(Qt::Key_Control, false); - ::Sleep(25); - m_platform->sendKey(Qt::Key_Control, true); - m_platform->sendKey(Qt::Key_Shift, true); - m_platform->sendKey(Qt::Key_End, true); - m_platform->sendKey(Qt::Key_End, false); - m_platform->sendKey(Qt::Key_Shift, false); - m_platform->sendKey(Qt::Key_Control, false); - ::Sleep(25); - m_platform->sendKey(Qt::Key_Backspace, true); - m_platform->sendKey(Qt::Key_Backspace, false); - - ::Sleep(25); + execType(new AutoTypeKey(Qt::Key_Home, Qt::ControlModifier)); + execType(new AutoTypeKey(Qt::Key_End, Qt::ControlModifier | Qt::ShiftModifier)); + execType(new AutoTypeKey(Qt::Key_Backspace)); } diff --git a/src/autotype/windows/AutoTypeWindows.h b/src/autotype/windows/AutoTypeWindows.h index 2f624e924..fe3ea82ec 100644 --- a/src/autotype/windows/AutoTypeWindows.h +++ b/src/autotype/windows/AutoTypeWindows.h @@ -54,9 +54,8 @@ class AutoTypeExecutorWin : public AutoTypeExecutor public: explicit AutoTypeExecutorWin(AutoTypePlatformWin* platform); - void execChar(AutoTypeChar* action) override; - void execKey(AutoTypeKey* action) override; - void execClearField(AutoTypeClearField* action) override; + void execType(const AutoTypeKey* action) override; + void execClearField(const AutoTypeClearField* action) override; private: AutoTypePlatformWin* const m_platform; diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index 950a91178..40127c9a7 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -569,32 +569,23 @@ AutoTypeExecutorX11::AutoTypeExecutorX11(AutoTypePlatformX11* platform) { } -void AutoTypeExecutorX11::execChar(AutoTypeChar* action) +void AutoTypeExecutorX11::execType(const AutoTypeKey* action) { - m_platform->sendKey(qcharToNativeKeyCode(action->character)); + if (action->key != Qt::Key_unknown) { + m_platform->sendKey(qtToNativeKeyCode(action->key), qtToNativeModifiers(action->modifiers)); + } else { + m_platform->sendKey(qcharToNativeKeyCode(action->character), qtToNativeModifiers(action->modifiers)); + } + + Tools::sleep(execDelayMs); } -void AutoTypeExecutorX11::execKey(AutoTypeKey* action) -{ - m_platform->sendKey(qtToNativeKeyCode(action->key)); -} - -void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr) +void AutoTypeExecutorX11::execClearField(const AutoTypeClearField* action) { Q_UNUSED(action); - - timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = 25 * 1000 * 1000; - - m_platform->sendKey(qtToNativeKeyCode(Qt::Key_Home), static_cast(ControlMask)); - nanosleep(&ts, nullptr); - - m_platform->sendKey(qtToNativeKeyCode(Qt::Key_End), static_cast(ControlMask | ShiftMask)); - nanosleep(&ts, nullptr); - - m_platform->sendKey(qtToNativeKeyCode(Qt::Key_Backspace)); - nanosleep(&ts, nullptr); + execType(new AutoTypeKey(Qt::Key_Home, Qt::ControlModifier)); + execType(new AutoTypeKey(Qt::Key_End, Qt::ControlModifier | Qt::ShiftModifier)); + execType(new AutoTypeKey(Qt::Key_Backspace)); } bool AutoTypePlatformX11::raiseWindow(WId window) diff --git a/src/autotype/xcb/AutoTypeXCB.h b/src/autotype/xcb/AutoTypeXCB.h index 54bb675ec..5f4795552 100644 --- a/src/autotype/xcb/AutoTypeXCB.h +++ b/src/autotype/xcb/AutoTypeXCB.h @@ -103,9 +103,8 @@ class AutoTypeExecutorX11 : public AutoTypeExecutor public: explicit AutoTypeExecutorX11(AutoTypePlatformX11* platform); - void execChar(AutoTypeChar* action) override; - void execKey(AutoTypeKey* action) override; - void execClearField(AutoTypeClearField* action) override; + void execType(const AutoTypeKey* action) override; + void execClearField(const AutoTypeClearField* action) override; private: AutoTypePlatformX11* const m_platform; diff --git a/src/core/Global.h b/src/core/Global.h index 5e7375148..6f48977b6 100644 --- a/src/core/Global.h +++ b/src/core/Global.h @@ -25,12 +25,12 @@ #if defined(Q_OS_WIN) #if defined(KEEPASSX_BUILDING_CORE) -#define KEEPASSX_EXPORT Q_DECL_IMPORT +#define KEEPASSXC_EXPORT Q_DECL_IMPORT #else -#define KEEPASSX_EXPORT Q_DECL_EXPORT +#define KEEPASSXC_EXPORT Q_DECL_EXPORT #endif #else -#define KEEPASSX_EXPORT Q_DECL_EXPORT +#define KEEPASSXC_EXPORT Q_DECL_EXPORT #endif #ifndef QUINT32_MAX diff --git a/src/core/MacPasteboard.h b/src/core/MacPasteboard.h index f2a71e73f..503741ca8 100644 --- a/src/core/MacPasteboard.h +++ b/src/core/MacPasteboard.h @@ -18,9 +18,9 @@ #ifndef KEEPASSXC_MACPASTEBOARD_H #define KEEPASSXC_MACPASTEBOARD_H -#include #include #include +#include class MacPasteboard : public QObject, public QMacPasteboardMime { diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 5b40a9b8e..dcc4d34cf 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1613,6 +1613,13 @@ void MainWindow::toggleWindow() } } +void MainWindow::closeModalWindow() +{ + if (qApp->modalWindow()) { + qApp->modalWindow()->close(); + } +} + void MainWindow::lockDatabasesAfterInactivity() { m_ui->tabWidget->lockDatabases(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 13e59b5ae..4163ba71a 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -87,6 +87,7 @@ public slots: void bringToFront(); void closeAllDatabases(); void lockAllDatabases(); + void closeModalWindow(); void displayDesktopNotification(const QString& msg, QString title = "", int msTimeoutHint = 10000); void restartApp(const QString& message); diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index d6d696ac6..3a8a606ec 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -1028,8 +1028,36 @@ bool EditEntryWidget::commitEntry() } // Check Auto-Type validity early - if (!AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text())) { - return false; + QString error; + if (m_autoTypeUi->customSequenceButton->isChecked() + && !AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text(), m_entry, error)) { + auto res = MessageBox::question(this, + tr("Auto-Type Validation Error"), + tr("An error occurred while validating the custom Auto-Type sequence:\n%1\n" + "Would you like to correct it?") + .arg(error), + MessageBox::Yes | MessageBox::No, + MessageBox::Yes); + if (res == MessageBox::Yes) { + setCurrentPage(3); + return false; + } + } + for (const auto& assoc : m_autoTypeAssoc->getAll()) { + if (!AutoType::verifyAutoTypeSyntax(assoc.sequence, m_entry, error)) { + auto res = + MessageBox::question(this, + tr("Auto-Type Validation Error"), + tr("An error occurred while validating the Auto-Type sequence for \"%1\":\n%2\n" + "Would you like to correct it?") + .arg(assoc.window.left(40), error), + MessageBox::Yes | MessageBox::No, + MessageBox::Yes); + if (res == MessageBox::Yes) { + setCurrentPage(3); + return false; + } + } } if (m_advancedUi->attributesView->currentIndex().isValid() && m_advancedUi->attributesEdit->isEnabled()) { diff --git a/src/gui/osutils/nixutils/X11Funcs.cpp b/src/gui/osutils/nixutils/X11Funcs.cpp index a942b42ee..5f3313035 100644 --- a/src/gui/osutils/nixutils/X11Funcs.cpp +++ b/src/gui/osutils/nixutils/X11Funcs.cpp @@ -99,6 +99,8 @@ KeySym qtToNativeKeyCode(Qt::Key key) default: if (key >= Qt::Key_F1 && key <= Qt::Key_F16) { return XK_F1 + (key - Qt::Key_F1); + } else if (key >= Qt::Key_Space && key <= Qt::Key_AsciiTilde) { + return key && 0xff; } else { return NoSymbol; } diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 5403670bd..fba75189a 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -280,43 +280,47 @@ void TestAutoType::testGlobalAutoTypeRegExp() void TestAutoType::testAutoTypeSyntaxChecks() { + auto entry = new Entry(); + QString error; + // Huge sequence - QVERIFY(AutoType::checkSyntax( - "{word 23}{F1 23}{~ 23}{% 23}{^}{F12}{(}{) 23}{[}{[}{]}{Delay=23}{+}{SUBTRACT}~+%@fixedstring")); + QVERIFY2(AutoType::verifyAutoTypeSyntax( + "{F1 23}{~ 23}{% 23}{^}{F12}{(}{) 23}{[}{[}{]}{Delay=23}{+}{SUBTRACT}~+%@fixedstring", entry, error), + error.toLatin1()); - QVERIFY(AutoType::checkSyntax("{NUMPAD1 3}")); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{NUMPAD1 3}", entry, error), error.toLatin1()); - QVERIFY(AutoType::checkSyntax("{S:SPECIALTOKEN}")); - QVERIFY(AutoType::checkSyntax("{S:SPECIAL TOKEN}")); - QVERIFY(AutoType::checkSyntax("{S:SPECIAL_TOKEN}")); - QVERIFY(AutoType::checkSyntax("{S:SPECIAL-TOKEN}")); - QVERIFY(AutoType::checkSyntax("{S:SPECIAL:TOKEN}")); - QVERIFY(AutoType::checkSyntax("{S:SPECIAL_TOKEN}{ENTER}")); - QVERIFY(AutoType::checkSyntax("{S:FOO}{S:HELLO WORLD}")); - QVERIFY(!AutoType::checkSyntax("{S:SPECIAL_TOKEN{}}")); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIALTOKEN}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL TOKEN}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL_TOKEN}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL-TOKEN}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL:TOKEN}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL_TOKEN}{ENTER}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:FOO}{S:HELLO WORLD}", entry, error), error.toLatin1()); + QVERIFY2(!AutoType::verifyAutoTypeSyntax("{S:SPECIAL_TOKEN{}}", entry, error), error.toLatin1()); - QVERIFY(AutoType::checkSyntax("{BEEP 3 3}")); - QVERIFY(!AutoType::checkSyntax("{BEEP 3}")); + QVERIFY2(!AutoType::verifyAutoTypeSyntax("{BEEP 3 3}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{BEEP 3}", entry, error), error.toLatin1()); - QVERIFY(AutoType::checkSyntax("{VKEY 0x01}")); - QVERIFY(AutoType::checkSyntax("{VKEY VK_LBUTTON}")); - QVERIFY(AutoType::checkSyntax("{VKEY-EX 0x01}")); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{VKEY 0x01}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{VKEY VK_LBUTTON}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{VKEY-EX 0x01}", entry, error), error.toLatin1()); // Bad sequence - QVERIFY(!AutoType::checkSyntax("{{{}}{}{}}{{}}")); + QVERIFY2(!AutoType::verifyAutoTypeSyntax("{{{}}{}{}}{{}}", entry, error), error.toLatin1()); // Good sequence - QVERIFY(AutoType::checkSyntax("{{}{}}{}}{{}")); - QVERIFY(AutoType::checkSyntax("{]}{[}{[}{]}")); - QVERIFY(AutoType::checkSyntax("{)}{(}{(}{)}")); - // High DelAY / low delay - QVERIFY(AutoType::checkHighDelay("{DelAY 50000}")); - QVERIFY(!AutoType::checkHighDelay("{delay 50}")); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{{}{}}{}}{{}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{]}{[}{[}{]}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{)}{(}{(}{)}", entry, error), error.toLatin1()); + // High delay + QVERIFY2(!AutoType::verifyAutoTypeSyntax("{DELAY 50000}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{delay 50}", entry, error), error.toLatin1()); // Slow typing - QVERIFY(AutoType::checkSlowKeypress("{DelAY=50000}")); - QVERIFY(!AutoType::checkSlowKeypress("{delay=50}")); - // Many repetition / few repetition / delay not repetition - QVERIFY(AutoType::checkHighRepetition("{LEFT 50000000}")); - QVERIFY(!AutoType::checkHighRepetition("{SPACE 10}{TAB 3}{RIGHT 50}")); - QVERIFY(!AutoType::checkHighRepetition("{delay 5000000000}")); + QVERIFY2(!AutoType::verifyAutoTypeSyntax("{DELAY=50000}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{delay=50}", entry, error), error.toLatin1()); + // Many repetition + QVERIFY2(!AutoType::verifyAutoTypeSyntax("{LEFT 50000000}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{SPACE 10}{TAB 3}{RIGHT 50}", entry, error), error.toLatin1()); + QVERIFY2(AutoType::verifyAutoTypeSyntax("{delay 5000000000}", entry, error), error.toLatin1()); } void TestAutoType::testAutoTypeEffectiveSequences()