diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ccdc955f2..4b7c07cd6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,6 +39,7 @@ configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) set(keepassx_SOURCES core/AutoTypeAssociations.cpp core/AsyncTask.h + core/AutoTypeMatch.cpp core/Config.cpp core/CsvParser.cpp core/Database.cpp @@ -137,6 +138,8 @@ set(keepassx_SOURCES gui/csvImport/CsvImportWizard.cpp gui/csvImport/CsvParserModel.cpp gui/entry/AutoTypeAssociationsModel.cpp + gui/entry/AutoTypeMatchModel.cpp + gui/entry/AutoTypeMatchView.cpp gui/entry/EditEntryWidget.cpp gui/entry/EditEntryWidget_p.h gui/entry/EntryAttachmentsModel.cpp diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 2a77c4c1d..0dfdedaec 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -27,6 +27,7 @@ #include "autotype/AutoTypePlatformPlugin.h" #include "autotype/AutoTypeSelectDialog.h" #include "autotype/WildcardMatcher.h" +#include "core/AutoTypeMatch.h" #include "core/Config.h" #include "core/Database.h" #include "core/Entry.h" @@ -40,7 +41,6 @@ AutoType* AutoType::m_instance = nullptr; AutoType::AutoType(QObject* parent, bool test) : QObject(parent) - , m_inAutoType(false) , m_autoTypeDelay(0) , m_currentGlobalKey(static_cast(0)) , m_currentGlobalModifiers(0) @@ -102,6 +102,19 @@ void AutoType::loadPlugin(const QString& pluginPath) } } +void AutoType::unloadPlugin() +{ + if (m_executor) { + delete m_executor; + m_executor = nullptr; + } + + if (m_plugin) { + m_plugin->unload(); + m_plugin = nullptr; + } +} + AutoType* AutoType::instance() { if (!m_instance) { @@ -127,32 +140,72 @@ QStringList AutoType::windowTitles() return m_plugin->windowTitles(); } -void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& customSequence, WId window) +void AutoType::resetInAutoType() { - if (m_inAutoType || !m_plugin) { + m_inAutoType.unlock(); +} + +void AutoType::raiseWindow() +{ +#if defined(Q_OS_MAC) + m_plugin->raiseOwnWindow(); +#endif +} + +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; + } + return false; + } + 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); +} + +/** + * Core Autotype function that will execute actions + */ +void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window) +{ + // no edit to the sequence beyond this point + if (!verifyAutoTypeSyntax(sequence)) { return; } - m_inAutoType = true; - - QString sequence; - if (customSequence.isEmpty()) { - sequence = autoTypeSequence(entry); - } else { - sequence = customSequence; - } - - if (!checkSyntax(sequence)) { - return; - } - - sequence.replace("{{}", "{LEFTBRACE}"); - sequence.replace("{}}", "{RIGHTBRACE}"); QList actions; ListDeleter actionsDeleter(&actions); if (!parseActions(sequence, entry, actions)) { - m_inAutoType = false; // TODO: make this automatic return; } @@ -181,13 +234,39 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c action->accept(m_executor); QCoreApplication::processEvents(QEventLoop::AllEvents, 10); } - - m_inAutoType = false; } +/** + * Single Autotype entry-point function + * Perfom autotype sequence in the active window + */ +void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) +{ + if (!m_plugin) { + return; + } + + QList sequences = autoTypeSequences(entry); + if (sequences.isEmpty()) { + return; + } + + if (!m_inAutoType.tryLock()) { + return; + } + + executeAutoTypeActions(entry, hideWindow, sequences.first()); + + m_inAutoType.unlock(); +} + +/** + * Global Autotype entry-point funcion + * Perform global autotype on the active window + */ void AutoType::performGlobalAutoType(const QList& dbList) { - if (m_inAutoType || !m_plugin) { + if (!m_plugin) { return; } @@ -197,38 +276,40 @@ void AutoType::performGlobalAutoType(const QList& dbList) return; } - m_inAutoType = true; + if (!m_inAutoType.tryLock()) { + return; + } - QList entryList; - QHash sequenceHash; + QList matchList; for (Database* db : dbList) { const QList dbEntries = db->rootGroup()->entriesRecursive(); for (Entry* entry : dbEntries) { - QString sequence = autoTypeSequence(entry, windowTitle); - if (!sequence.isEmpty()) { - entryList << entry; - sequenceHash.insert(entry, sequence); + const QSet sequences = autoTypeSequences(entry, windowTitle).toSet(); + for (const QString& sequence : sequences) { + if (!sequence.isEmpty()) { + matchList << AutoTypeMatch(entry, sequence); + } } } } - if (entryList.isEmpty()) { - m_inAutoType = false; + if (matchList.isEmpty()) { + m_inAutoType.unlock(); QString message = tr("Couldn't find an entry that matches the window title:"); message.append("\n\n"); message.append(windowTitle); MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message); - } else if ((entryList.size() == 1) && !config()->get("security/autotypeask").toBool()) { - m_inAutoType = false; - performAutoType(entryList.first(), nullptr, sequenceHash[entryList.first()]); + } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { + executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); + m_inAutoType.unlock(); } else { m_windowFromGlobal = m_plugin->activeWindow(); AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog(); - connect( - selectDialog, SIGNAL(entryActivated(Entry*, QString)), SLOT(performAutoTypeFromGlobal(Entry*, QString))); + connect(selectDialog, SIGNAL(matchActivated(AutoTypeMatch)), + SLOT(performAutoTypeFromGlobal(AutoTypeMatch))); connect(selectDialog, SIGNAL(rejected()), SLOT(resetInAutoType())); - selectDialog->setEntries(entryList, sequenceHash); + selectDialog->setMatchList(matchList); #if defined(Q_OS_MAC) m_plugin->raiseOwnWindow(); Tools::wait(500); @@ -239,92 +320,31 @@ void AutoType::performGlobalAutoType(const QList& dbList) } } -void AutoType::performAutoTypeFromGlobal(Entry* entry, const QString& sequence) +void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) { - Q_ASSERT(m_inAutoType); + // We don't care about the result here, the mutex should already be locked. Now it's locked for sure + m_inAutoType.tryLock(); m_plugin->raiseWindow(m_windowFromGlobal); - m_inAutoType = false; + executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); - performAutoType(entry, nullptr, sequence, m_windowFromGlobal); + m_inAutoType.unlock(); } -void AutoType::resetInAutoType() -{ - Q_ASSERT(m_inAutoType); - - m_inAutoType = false; -} - -void AutoType::raiseWindow() -{ -#if defined(Q_OS_MAC) - m_plugin->raiseOwnWindow(); -#endif -} - -void AutoType::unloadPlugin() -{ - if (m_executor) { - delete m_executor; - m_executor = nullptr; - } - - if (m_plugin) { - m_plugin->unload(); - m_plugin = nullptr; - } -} - -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) +/** + * Parse an autotype sequence and resolve its Template/command inside as AutoTypeActions + */ +bool AutoType::parseActions(const QString& actionSequence, const Entry* entry, QList& actions) { QString tmpl; bool inTmpl = false; m_autoTypeDelay = config()->get("AutoTypeDelay").toInt(); + QString sequence = actionSequence; + sequence.replace("{{}", "{LEFTBRACE}"); + sequence.replace("{}}", "{RIGHTBRACE}"); + for (const QChar& ch : sequence) { if (inTmpl) { if (ch == '{') { @@ -363,6 +383,9 @@ bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList AutoType::createActionFromTemplate(const QString& tmpl, const Entry* entry) { QString tmplName = tmpl; @@ -506,80 +529,65 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c return list; } -QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitle) +/** + * Retrive the autotype sequences matches for a given windowTitle + * This returns a list with priority ordering. If you don't want duplicates call .toSet() on it. + */ +QList AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle) { + QList sequenceList; + if (!entry->autoTypeEnabled()) { - return QString(); + return sequenceList; } - bool enableSet = false; - QString sequence; + const Group* group = entry->group(); + do { + if (group->autoTypeEnabled() == Group::Disable) { + return sequenceList; + } else if (group->autoTypeEnabled() == Group::Enable) { + break; + } + group = group->parentGroup(); + + } while (group); + if (!windowTitle.isEmpty()) { - bool match = false; const QList assocList = entry->autoTypeAssociations()->getAll(); for (const AutoTypeAssociations::Association& assoc : assocList) { const QString window = entry->resolveMultiplePlaceholders(assoc.window); if (windowMatches(windowTitle, window)) { if (!assoc.sequence.isEmpty()) { - sequence = assoc.sequence; + sequenceList.append(assoc.sequence); } else { - sequence = entry->defaultAutoTypeSequence(); + sequenceList.append(entry->effectiveAutoTypeSequence()); } - match = true; - break; } } - if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && + if (config()->get("AutoTypeEntryTitleMatch").toBool() && windowMatchesTitle(windowTitle, entry->resolvePlaceholder(entry->title()))) { - sequence = entry->defaultAutoTypeSequence(); - match = true; + sequenceList.append(entry->effectiveAutoTypeSequence()); } - if (!match && config()->get("AutoTypeEntryURLMatch").toBool() && + if (config()->get("AutoTypeEntryURLMatch").toBool() && windowMatchesUrl(windowTitle, entry->resolvePlaceholder(entry->url()))) { - sequence = entry->defaultAutoTypeSequence(); - match = true; + sequenceList.append(entry->effectiveAutoTypeSequence()); } - if (!match) { - return QString(); + if (sequenceList.isEmpty()) { + return sequenceList; } } else { - sequence = entry->defaultAutoTypeSequence(); + sequenceList.append(entry->effectiveAutoTypeSequence()); } - const Group* group = entry->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() && (!entry->resolvePlaceholder(entry->username()).isEmpty() || - !entry->resolvePlaceholder(entry->password()).isEmpty())) { - if (entry->resolvePlaceholder(entry->username()).isEmpty()) { - sequence = "{PASSWORD}{ENTER}"; - } else if (entry->resolvePlaceholder(entry->password()).isEmpty()) { - sequence = "{USERNAME}{ENTER}"; - } else { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; - } - } - - return sequence; + return sequenceList; } +/** + * Checks if a window title matches a pattern + */ bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPattern) { if (windowPattern.startsWith("//") && windowPattern.endsWith("//") && windowPattern.size() >= 4) { @@ -590,11 +598,19 @@ bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPa } } +/** + * Checks if a window title matches an entry Title + * The entry title should be Spr-compiled by the caller + */ bool AutoType::windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle) { return !resolvedTitle.isEmpty() && windowTitle.contains(resolvedTitle, Qt::CaseInsensitive); } +/** + * Checks if a window title matches an entry URL + * The entry URL should be Spr-compiled by the caller + */ bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl) { if (!resolvedUrl.isEmpty() && windowTitle.contains(resolvedUrl, Qt::CaseInsensitive)) { @@ -609,6 +625,9 @@ bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resol return false; } +/** + * Checks if the overall syntax of an autotype sequence is fine + */ bool AutoType::checkSyntax(const QString& string) { QString allowRepetition = "(?:\\s\\d+)?"; @@ -634,6 +653,9 @@ bool AutoType::checkSyntax(const QString& 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 @@ -642,6 +664,9 @@ bool AutoType::checkHighDelay(const QString& 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 @@ -650,6 +675,9 @@ bool AutoType::checkSlowKeypress(const QString& string) return match.hasMatch(); } +/** + * Checks an autotype sequence for high repetition command + */ bool AutoType::checkHighRepetition(const QString& string) { // 3 digit numbers are too much @@ -658,6 +686,9 @@ bool AutoType::checkHighRepetition(const QString& 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)) { @@ -691,12 +722,3 @@ bool AutoType::verifyAutoTypeSyntax(const QString& sequence) } return true; } - - -void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow, const QString& customSequence, WId window) -{ - auto sequence = entry->effectiveAutoTypeSequence(); - if (verifyAutoTypeSyntax(sequence)) { - executeAutoTypeActions(entry, hideWindow, customSequence, window); - } -} diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index eb366ae9c..3b22106bd 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -1,4 +1,4 @@ -/* + /* * Copyright (C) 2012 Felix Geyer * Copyright (C) 2017 KeePassXC Team * @@ -22,6 +22,9 @@ #include #include #include +#include + +#include "core/AutoTypeMatch.h" class AutoTypeAction; class AutoTypeExecutor; @@ -36,10 +39,6 @@ class AutoType : public QObject public: QStringList windowTitles(); - void executeAutoTypeActions(const Entry* entry, - QWidget* hideWindow = nullptr, - const QString& customSequence = QString(), - WId window = 0); bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers); void unregisterGlobalShortcut(); int callEventFilter(void* event); @@ -49,9 +48,7 @@ public: static bool checkHighDelay(const QString& string); static bool verifyAutoTypeSyntax(const QString& sequence); void performAutoType(const Entry* entry, - QWidget* hideWindow = nullptr, - const QString& customSequence = QString(), - WId window = 0); + QWidget* hideWindow = nullptr); inline bool isAvailable() { @@ -69,7 +66,7 @@ signals: void globalShortcutTriggered(); private slots: - void performAutoTypeFromGlobal(Entry* entry, const QString& sequence); + void performAutoTypeFromGlobal(AutoTypeMatch match); void resetInAutoType(); void unloadPlugin(); @@ -77,14 +74,18 @@ private: explicit AutoType(QObject* parent = nullptr, bool test = false); ~AutoType(); void loadPlugin(const QString& pluginPath); + void executeAutoTypeActions(const Entry* entry, + 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); - QString autoTypeSequence(const Entry* entry, const QString& windowTitle = QString()); + QList autoTypeSequences(const Entry* entry, const QString& windowTitle = QString()); bool windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle); bool windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl); bool windowMatches(const QString& windowTitle, const QString& windowPattern); - bool m_inAutoType; + QMutex m_inAutoType; int m_autoTypeDelay; Qt::Key m_currentGlobalKey; Qt::KeyboardModifiers m_currentGlobalModifiers; diff --git a/src/autotype/AutoTypeAction.h b/src/autotype/AutoTypeAction.h index 490f0d89f..7f0d829c0 100644 --- a/src/autotype/AutoTypeAction.h +++ b/src/autotype/AutoTypeAction.h @@ -20,6 +20,7 @@ #include #include +#include #include "core/Global.h" diff --git a/src/autotype/AutoTypeSelectDialog.cpp b/src/autotype/AutoTypeSelectDialog.cpp index b39c78e1d..3ef086481 100644 --- a/src/autotype/AutoTypeSelectDialog.cpp +++ b/src/autotype/AutoTypeSelectDialog.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * 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 @@ -25,14 +26,15 @@ #include #include "autotype/AutoTypeSelectView.h" +#include "core/AutoTypeMatch.h" #include "core/Config.h" #include "core/FilePath.h" -#include "gui/entry/EntryModel.h" +#include "gui/entry/AutoTypeMatchModel.h" AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) : QDialog(parent) , m_view(new AutoTypeSelectView(this)) - , m_entryActivatedEmitted(false) + , m_matchActivatedEmitted(false) { setAttribute(Qt::WA_DeleteOnClose); // Places the window on the active (virtual) desktop instead of where the main window is. @@ -42,7 +44,7 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) setWindowIcon(filePath()->applicationIcon()); QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos()); - QSize size = config()->get("GUI/AutoTypeSelectDialogSize", QSize(400, 250)).toSize(); + QSize size = config()->get("GUI/AutoTypeSelectDialogSize", QSize(600, 250)).toSize(); size.setWidth(qMin(size.width(), screenGeometry.width())); size.setHeight(qMin(size.height(), screenGeometry.height())); resize(size); @@ -56,10 +58,10 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) QLabel* descriptionLabel = new QLabel(tr("Select entry to Auto-Type:"), this); layout->addWidget(descriptionLabel); - connect(m_view, SIGNAL(activated(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); - connect(m_view, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); + connect(m_view, SIGNAL(activated(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); + connect(m_view, SIGNAL(clicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); + connect(m_view->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(matchRemoved())); connect(m_view, SIGNAL(rejected()), SLOT(reject())); - connect(m_view->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(entryRemoved())); layout->addWidget(m_view); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, Qt::Horizontal, this); @@ -67,10 +69,9 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) layout->addWidget(buttonBox); } -void AutoTypeSelectDialog::setEntries(const QList& entries, const QHash& sequences) +void AutoTypeSelectDialog::setMatchList(const QList& matchList) { - m_sequences = sequences; - m_view->setEntryList(entries); + m_view->setMatchList(matchList); m_view->header()->resizeSections(QHeaderView::ResizeToContents); } @@ -82,20 +83,20 @@ void AutoTypeSelectDialog::done(int r) QDialog::done(r); } -void AutoTypeSelectDialog::emitEntryActivated(const QModelIndex& index) +void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index) { // make sure we don't emit the signal twice when both activated() and clicked() are triggered - if (m_entryActivatedEmitted) { + if (m_matchActivatedEmitted) { return; } - m_entryActivatedEmitted = true; + m_matchActivatedEmitted = true; - Entry* entry = m_view->entryFromIndex(index); + AutoTypeMatch match = m_view->matchFromIndex(index); accept(); - emit entryActivated(entry, m_sequences[entry]); + emit matchActivated(match); } -void AutoTypeSelectDialog::entryRemoved() +void AutoTypeSelectDialog::matchRemoved() { if (m_view->model()->rowCount() == 0) { reject(); diff --git a/src/autotype/AutoTypeSelectDialog.h b/src/autotype/AutoTypeSelectDialog.h index 3d9c684ed..83abd2d80 100644 --- a/src/autotype/AutoTypeSelectDialog.h +++ b/src/autotype/AutoTypeSelectDialog.h @@ -22,8 +22,9 @@ #include #include +#include "core/AutoTypeMatch.h" + class AutoTypeSelectView; -class Entry; class AutoTypeSelectDialog : public QDialog { @@ -31,22 +32,21 @@ class AutoTypeSelectDialog : public QDialog public: explicit AutoTypeSelectDialog(QWidget* parent = nullptr); - void setEntries(const QList& entries, const QHash& sequences); + void setMatchList(const QList& matchList); signals: - void entryActivated(Entry* entry, const QString& sequence); + void matchActivated(AutoTypeMatch match); public slots: void done(int r) override; private slots: - void emitEntryActivated(const QModelIndex& index); - void entryRemoved(); + void emitMatchActivated(const QModelIndex& index); + void matchRemoved(); private: AutoTypeSelectView* const m_view; - QHash m_sequences; - bool m_entryActivatedEmitted; + bool m_matchActivatedEmitted; }; #endif // KEEPASSX_AUTOTYPESELECTDIALOG_H diff --git a/src/autotype/AutoTypeSelectView.cpp b/src/autotype/AutoTypeSelectView.cpp index 7d9db4130..e4dba0515 100644 --- a/src/autotype/AutoTypeSelectView.cpp +++ b/src/autotype/AutoTypeSelectView.cpp @@ -21,15 +21,12 @@ #include AutoTypeSelectView::AutoTypeSelectView(QWidget* parent) - : EntryView(parent) + : AutoTypeMatchView(parent) { - hideColumn(3); setMouseTracking(true); setAllColumnsShowFocus(true); - setDragEnabled(false); - setSelectionMode(QAbstractItemView::SingleSelection); - connect(model(), SIGNAL(modelReset()), SLOT(selectFirstEntry())); + connect(model(), SIGNAL(modelReset()), SLOT(selectFirstMatch())); } void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event) @@ -44,10 +41,10 @@ void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event) unsetCursor(); } - EntryView::mouseMoveEvent(event); + AutoTypeMatchView::mouseMoveEvent(event); } -void AutoTypeSelectView::selectFirstEntry() +void AutoTypeSelectView::selectFirstMatch() { QModelIndex index = model()->index(0, 0); diff --git a/src/autotype/AutoTypeSelectView.h b/src/autotype/AutoTypeSelectView.h index aadf99fa6..e6a2ec652 100644 --- a/src/autotype/AutoTypeSelectView.h +++ b/src/autotype/AutoTypeSelectView.h @@ -18,11 +18,9 @@ #ifndef KEEPASSX_AUTOTYPESELECTVIEW_H #define KEEPASSX_AUTOTYPESELECTVIEW_H -#include "gui/entry/EntryView.h" +#include "gui/entry/AutoTypeMatchView.h" -class Entry; - -class AutoTypeSelectView : public EntryView +class AutoTypeSelectView : public AutoTypeMatchView { Q_OBJECT @@ -34,7 +32,7 @@ protected: void keyReleaseEvent(QKeyEvent* e) override; private slots: - void selectFirstEntry(); + void selectFirstMatch(); signals: void rejected(); diff --git a/src/core/AutoTypeMatch.cpp b/src/core/AutoTypeMatch.cpp new file mode 100644 index 000000000..c1faab9e6 --- /dev/null +++ b/src/core/AutoTypeMatch.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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 "AutoTypeMatch.h" + +AutoTypeMatch::AutoTypeMatch() + : entry(nullptr), + sequence() +{} + +AutoTypeMatch::AutoTypeMatch(Entry* entry, QString sequence) + : entry(entry), + sequence(sequence) +{} + +bool AutoTypeMatch::operator==(const AutoTypeMatch& other) const +{ + return entry == other.entry && sequence == other.sequence; +} + +bool AutoTypeMatch::operator!=(const AutoTypeMatch& other) const +{ + return entry != other.entry || sequence != other.sequence; +} diff --git a/src/core/AutoTypeMatch.h b/src/core/AutoTypeMatch.h new file mode 100644 index 000000000..768cf1682 --- /dev/null +++ b/src/core/AutoTypeMatch.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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_AUTOTYPEMATCH_H +#define KEEPASSX_AUTOTYPEMATCH_H + +#include +#include + +class Entry; + +struct AutoTypeMatch +{ + Entry* entry; + QString sequence; + + AutoTypeMatch(); + AutoTypeMatch(Entry* entry, QString sequence); + + bool operator==(const AutoTypeMatch& other) const; + bool operator!=(const AutoTypeMatch& other) const; +}; + +Q_DECLARE_TYPEINFO(AutoTypeMatch, Q_MOVABLE_TYPE); + +#endif // KEEPASSX_AUTOTYPEMATCH_H diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index d30adeeca..8db955c93 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -29,6 +29,8 @@ const int Entry::DefaultIconNumber = 0; const int Entry::ResolveMaximumDepth = 10; +const QString Entry::AutoTypeSequenceUsername = "{USERNAME}{ENTER}"; +const QString Entry::AutoTypeSequencePassword = "{PASSWORD}{ENTER}"; Entry::Entry() @@ -218,30 +220,37 @@ QString Entry::defaultAutoTypeSequence() const return m_data.defaultAutoTypeSequence; } +/** + * Determine the effective sequence that will be injected + * This function return an empty string if a parent group has autotype disabled or if the entry has no parent + */ QString Entry::effectiveAutoTypeSequence() const { + if (!autoTypeEnabled()) { + return {}; + } + + const Group* parent = group(); + if (!parent) { + return {}; + } + + QString sequence = parent->effectiveAutoTypeSequence(); + if (sequence.isEmpty()) { + return {}; + } + if (!m_data.defaultAutoTypeSequence.isEmpty()) { return m_data.defaultAutoTypeSequence; } - QString sequence; - const Group* grp = group(); - if(grp) { - sequence = grp->effectiveAutoTypeSequence(); - } else { - return QString(); - } - - if (sequence.isEmpty() && (!username().isEmpty() || !password().isEmpty())) { + if (sequence == Group::RootAutoTypeSequence && (!username().isEmpty() || !password().isEmpty())) { if (username().isEmpty()) { - sequence = "{PASSWORD}{ENTER}"; - } - else if (password().isEmpty()) { - sequence = "{USERNAME}{ENTER}"; - } - else { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; + return AutoTypeSequencePassword; + } else if (password().isEmpty()) { + return AutoTypeSequenceUsername; } + return Group::RootAutoTypeSequence; } return sequence; diff --git a/src/core/Entry.h b/src/core/Entry.h index 7b995b7ae..8579f9533 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -85,6 +85,7 @@ public: int autoTypeObfuscation() const; QString defaultAutoTypeSequence() const; QString effectiveAutoTypeSequence() const; + QString effectiveNewAutoTypeSequence() const; AutoTypeAssociations* autoTypeAssociations(); const AutoTypeAssociations* autoTypeAssociations() const; QString title() const; @@ -109,6 +110,8 @@ public: static const int DefaultIconNumber; static const int ResolveMaximumDepth; + static const QString AutoTypeSequenceUsername; + static const QString AutoTypeSequencePassword; void setUuid(const Uuid& uuid); void setIcon(int iconNumber); diff --git a/src/core/Group.cpp b/src/core/Group.cpp index bdcfeff73..51b24c199 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -25,6 +25,7 @@ const int Group::DefaultIconNumber = 48; const int Group::RecycleBinIconNumber = 43; +const QString Group::RootAutoTypeSequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; Group::CloneFlags Group::DefaultCloneFlags = static_cast( Group::CloneNewUuid | Group::CloneResetTimeInfo | Group::CloneIncludeEntries); @@ -192,6 +193,10 @@ QString Group::defaultAutoTypeSequence() const return m_data.defaultAutoTypeSequence; } +/** + * Determine the effective sequence that will be injected + * This function return an empty string if the current group or any parent has autotype disabled + */ QString Group::effectiveAutoTypeSequence() const { QString sequence; @@ -207,7 +212,7 @@ QString Group::effectiveAutoTypeSequence() const } while (group && sequence.isEmpty()); if (sequence.isEmpty()) { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; + sequence = RootAutoTypeSequence; } return sequence; diff --git a/src/core/Group.h b/src/core/Group.h index 70f033196..b1654a236 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -88,6 +88,7 @@ public: static const int RecycleBinIconNumber; static CloneFlags DefaultCloneFlags; static Entry::CloneFlags DefaultEntryCloneFlags; + static const QString RootAutoTypeSequence; Group* findChildByName(const QString& name); Group* findChildByUuid(const Uuid& uuid); diff --git a/src/gui/entry/AutoTypeMatchModel.cpp b/src/gui/entry/AutoTypeMatchModel.cpp new file mode 100644 index 000000000..6a370dea5 --- /dev/null +++ b/src/gui/entry/AutoTypeMatchModel.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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 "AutoTypeMatchModel.h" + +#include + +#include "core/DatabaseIcons.h" +#include "core/Entry.h" +#include "core/Global.h" +#include "core/Group.h" +#include "core/Metadata.h" + +AutoTypeMatchModel::AutoTypeMatchModel(QObject* parent) + : QAbstractTableModel(parent) +{ +} + +AutoTypeMatch AutoTypeMatchModel::matchFromIndex(const QModelIndex& index) const +{ + Q_ASSERT(index.isValid() && index.row() < m_matches.size()); + return m_matches.at(index.row()); +} + +QModelIndex AutoTypeMatchModel::indexFromMatch(AutoTypeMatch match) const +{ + int row = m_matches.indexOf(match); + Q_ASSERT(row != -1); + return index(row, 1); +} + +void AutoTypeMatchModel::setMatchList(const QList& matches) +{ + beginResetModel(); + + severConnections(); + + m_allGroups.clear(); + m_matches = matches; + + QSet databases; + + for (AutoTypeMatch& match : m_matches) { + databases.insert(match.entry->group()->database()); + } + + for (Database* db : asConst(databases)) { + Q_ASSERT(db); + for (const Group* group : db->rootGroup()->groupsRecursive(true)) { + m_allGroups.append(group); + } + + if (db->metadata()->recycleBin()) { + m_allGroups.removeOne(db->metadata()->recycleBin()); + } + } + + for (const Group* group : asConst(m_allGroups)) { + makeConnections(group); + } + + endResetModel(); +} + +int AutoTypeMatchModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } + return m_matches.size(); +} + +int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent); + + return 4; +} + +QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) { + return {}; + } + + AutoTypeMatch match = matchFromIndex(index); + + if (role == Qt::DisplayRole) { + QString result; + switch (index.column()) { + case ParentGroup: + if (match.entry->group()) { + return match.entry->group()->name(); + } + break; + case Title: + return match.entry->resolveMultiplePlaceholders(match.entry->title()); + case Username: + return match.entry->resolveMultiplePlaceholders(match.entry->username()); + case Sequence: + return match.sequence; + } + } else if (role == Qt::DecorationRole) { + switch (index.column()) { + case ParentGroup: + if (match.entry->group()) { + return match.entry->group()->iconScaledPixmap(); + } + break; + case Title: + if (match.entry->isExpired()) { + return databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex); + } else { + return match.entry->iconScaledPixmap(); + } + } + } else if (role == Qt::FontRole) { + QFont font; + if (match.entry->isExpired()) { + font.setStrikeOut(true); + } + return font; + } + + return {}; +} + +QVariant AutoTypeMatchModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch (section) { + case ParentGroup: + return tr("Group"); + case Title: + return tr("Title"); + case Username: + return tr("Username"); + case Sequence: + return tr("Sequence"); + } + } + + return {}; +} + +void AutoTypeMatchModel::entryDataChanged(Entry* entry) +{ + for (int row = 0; row < m_matches.size(); ++row) { + AutoTypeMatch match = m_matches[row]; + if (match.entry == entry) { + emit dataChanged(index(row, 0), index(row, columnCount()-1)); + } + } +} + + +void AutoTypeMatchModel::entryAboutToRemove(Entry* entry) +{ + for (int row = 0; row < m_matches.size(); ++row) { + AutoTypeMatch match = m_matches[row]; + if (match.entry == entry) { + beginRemoveRows(QModelIndex(), row, row); + m_matches.removeAt(row); + endRemoveRows(); + --row; + } + } +} + +void AutoTypeMatchModel::entryRemoved() +{ +} + +void AutoTypeMatchModel::severConnections() +{ + for (const Group* group : asConst(m_allGroups)) { + disconnect(group, nullptr, this, nullptr); + } +} + +void AutoTypeMatchModel::makeConnections(const Group* group) +{ + connect(group, SIGNAL(entryAboutToRemove(Entry*)), SLOT(entryAboutToRemove(Entry*))); + connect(group, SIGNAL(entryRemoved(Entry*)), SLOT(entryRemoved())); + connect(group, SIGNAL(entryDataChanged(Entry*)), SLOT(entryDataChanged(Entry*))); +} diff --git a/src/gui/entry/AutoTypeMatchModel.h b/src/gui/entry/AutoTypeMatchModel.h new file mode 100644 index 000000000..791dbc3df --- /dev/null +++ b/src/gui/entry/AutoTypeMatchModel.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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_AUTOTYPEMATCHMODEL_H +#define KEEPASSX_AUTOTYPEMATCHMODEL_H + +#include + +#include "core/AutoTypeMatch.h" + +class Entry; +class Group; + +class AutoTypeMatchModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum ModelColumn + { + ParentGroup = 0, + Title = 1, + Username = 2, + Sequence = 3 + }; + + explicit AutoTypeMatchModel(QObject* parent = nullptr); + AutoTypeMatch matchFromIndex(const QModelIndex& index) const; + QModelIndex indexFromMatch(AutoTypeMatch match) const; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void setMatchList(const QList& matches); + +private slots: + void entryAboutToRemove(Entry* entry); + void entryRemoved(); + void entryDataChanged(Entry* entry); + +private: + void severConnections(); + void makeConnections(const Group* group); + + QList m_matches; + QList m_allGroups; +}; + +#endif // KEEPASSX_AUTOTYPEMATCHMODEL_H diff --git a/src/gui/entry/AutoTypeMatchView.cpp b/src/gui/entry/AutoTypeMatchView.cpp new file mode 100644 index 000000000..67f38c79e --- /dev/null +++ b/src/gui/entry/AutoTypeMatchView.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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 "AutoTypeMatchView.h" + +#include +#include + +#include "gui/SortFilterHideProxyModel.h" + +AutoTypeMatchView::AutoTypeMatchView(QWidget* parent) + : QTreeView(parent) + , m_model(new AutoTypeMatchModel(this)) + , m_sortModel(new SortFilterHideProxyModel(this)) +{ + m_sortModel->setSourceModel(m_model); + m_sortModel->setDynamicSortFilter(true); + m_sortModel->setSortLocaleAware(true); + m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive); + QTreeView::setModel(m_sortModel); + + setUniformRowHeights(true); + setRootIsDecorated(false); + setAlternatingRowColors(true); + setDragEnabled(false); + setSortingEnabled(true); + setSelectionMode(QAbstractItemView::SingleSelection); + header()->setDefaultSectionSize(150); + + connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); + connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(matchSelectionChanged())); +} + +void AutoTypeMatchView::keyPressEvent(QKeyEvent* event) +{ + if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && currentIndex().isValid()) { + emitMatchActivated(currentIndex()); +#ifdef Q_OS_MAC + // Pressing return does not emit the QTreeView::activated signal on mac os + emit activated(currentIndex()); +#endif + } + + QTreeView::keyPressEvent(event); +} + +void AutoTypeMatchView::setMatchList(const QList& matches) +{ + m_model->setMatchList(matches); + for (int i = 0; i < m_model->columnCount(); ++i) { + resizeColumnToContents(i); + if (columnWidth(i) > 250) { + setColumnWidth(i, 250); + } + } + setFirstMatchActive(); +} + +void AutoTypeMatchView::setFirstMatchActive() +{ + if (m_model->rowCount() > 0) { + QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0)); + setCurrentMatch(m_model->matchFromIndex(index)); + } else { + emit matchSelectionChanged(); + } +} + +void AutoTypeMatchView::emitMatchActivated(const QModelIndex& index) +{ + AutoTypeMatch match = matchFromIndex(index); + + emit matchActivated(match); +} + +AutoTypeMatch AutoTypeMatchView::currentMatch() +{ + QModelIndexList list = selectionModel()->selectedRows(); + if (list.size() == 1) { + return m_model->matchFromIndex(m_sortModel->mapToSource(list.first())); + } + return AutoTypeMatch(); +} + +void AutoTypeMatchView::setCurrentMatch(AutoTypeMatch match) +{ + selectionModel()->setCurrentIndex(m_sortModel->mapFromSource(m_model->indexFromMatch(match)), + QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); +} + +AutoTypeMatch AutoTypeMatchView::matchFromIndex(const QModelIndex& index) +{ + if (index.isValid()) { + return m_model->matchFromIndex(m_sortModel->mapToSource(index)); + } + return AutoTypeMatch(); +} diff --git a/src/gui/entry/AutoTypeMatchView.h b/src/gui/entry/AutoTypeMatchView.h new file mode 100644 index 000000000..14ad9ea2a --- /dev/null +++ b/src/gui/entry/AutoTypeMatchView.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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_AUTOTYPEMATCHVIEW_H +#define KEEPASSX_AUTOTYPEMATCHVIEW_H + +#include + +#include "core/AutoTypeMatch.h" + +#include "gui/entry/AutoTypeMatchModel.h" + +class SortFilterHideProxyModel; + +class AutoTypeMatchView : public QTreeView +{ + Q_OBJECT + +public: + explicit AutoTypeMatchView(QWidget* parent = nullptr); + AutoTypeMatch currentMatch(); + void setCurrentMatch(AutoTypeMatch match); + AutoTypeMatch matchFromIndex(const QModelIndex& index); + void setMatchList(const QList& matches); + void setFirstMatchActive(); + +signals: + void matchActivated(AutoTypeMatch match); + void matchSelectionChanged(); + +protected: + void keyPressEvent(QKeyEvent* event) override; + +private slots: + void emitMatchActivated(const QModelIndex& index); + +private: + AutoTypeMatchModel* const m_model; + SortFilterHideProxyModel* const m_sortModel; +}; + +#endif // KEEPASSX_AUTOTYPEMATCHVIEW_H diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index c146da691..bab5a0728 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -170,8 +170,6 @@ void EditEntryWidget::setupAutoType() m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->inheritSequenceButton); m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->customSequenceButton); - m_autoTypeWindowSequenceGroup->addButton(m_autoTypeUi->defaultWindowSequenceButton); - m_autoTypeWindowSequenceGroup->addButton(m_autoTypeUi->customWindowSequenceButton); m_autoTypeAssocModel->setAutoTypeAssociations(m_autoTypeAssoc); m_autoTypeUi->assocView->setModel(m_autoTypeAssocModel); m_autoTypeUi->assocView->setColumnHidden(1, true); @@ -190,8 +188,6 @@ void EditEntryWidget::setupAutoType() connect(m_autoTypeAssocModel, SIGNAL(modelReset()), SLOT(clearCurrentAssoc())); connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), SLOT(applyCurrentAssoc())); - connect(m_autoTypeUi->defaultWindowSequenceButton, SIGNAL(toggled(bool)), - SLOT(applyCurrentAssoc())); connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), SLOT(applyCurrentAssoc())); } @@ -644,7 +640,7 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore) } m_autoTypeUi->sequenceEdit->setText(entry->effectiveAutoTypeSequence()); m_autoTypeUi->windowTitleCombo->lineEdit()->clear(); - m_autoTypeUi->defaultWindowSequenceButton->setChecked(true); + m_autoTypeUi->customWindowSequenceButton->setChecked(false); m_autoTypeUi->windowSequenceEdit->setText(""); m_autoTypeAssoc->copyDataFrom(entry->autoTypeAssociations()); m_autoTypeAssocModel->setEntry(entry); @@ -998,7 +994,6 @@ void EditEntryWidget::updateAutoTypeEnabled() m_autoTypeUi->windowTitleLabel->setEnabled(autoTypeEnabled && validIndex); m_autoTypeUi->windowTitleCombo->setEnabled(autoTypeEnabled && validIndex); - m_autoTypeUi->defaultWindowSequenceButton->setEnabled(!m_history && autoTypeEnabled && validIndex); m_autoTypeUi->customWindowSequenceButton->setEnabled(!m_history && autoTypeEnabled && validIndex); m_autoTypeUi->windowSequenceEdit->setEnabled(autoTypeEnabled && validIndex && m_autoTypeUi->customWindowSequenceButton->isChecked()); @@ -1029,16 +1024,15 @@ void EditEntryWidget::loadCurrentAssoc(const QModelIndex& current) AutoTypeAssociations::Association assoc = m_autoTypeAssoc->get(current.row()); m_autoTypeUi->windowTitleCombo->setEditText(assoc.window); if (assoc.sequence.isEmpty()) { - m_autoTypeUi->defaultWindowSequenceButton->setChecked(true); - } - else { + m_autoTypeUi->customWindowSequenceButton->setChecked(false); + m_autoTypeUi->windowSequenceEdit->setText(m_entry->effectiveAutoTypeSequence()); + } else { m_autoTypeUi->customWindowSequenceButton->setChecked(true); + m_autoTypeUi->windowSequenceEdit->setText(assoc.sequence); } - m_autoTypeUi->windowSequenceEdit->setText(assoc.sequence); updateAutoTypeEnabled(); - } - else { + } else { clearCurrentAssoc(); } } @@ -1047,7 +1041,7 @@ void EditEntryWidget::clearCurrentAssoc() { m_autoTypeUi->windowTitleCombo->setEditText(""); - m_autoTypeUi->defaultWindowSequenceButton->setChecked(true); + m_autoTypeUi->customWindowSequenceButton->setChecked(false); m_autoTypeUi->windowSequenceEdit->setText(""); updateAutoTypeEnabled(); diff --git a/src/gui/entry/EditEntryWidgetAutoType.ui b/src/gui/entry/EditEntryWidgetAutoType.ui index a8090f768..3d4ec7a3e 100644 --- a/src/gui/entry/EditEntryWidgetAutoType.ui +++ b/src/gui/entry/EditEntryWidgetAutoType.ui @@ -203,16 +203,9 @@ - + - Use default se&quence - - - - - - - Set custo&m sequence: + Use a specific sequence for this association: @@ -277,7 +270,6 @@ sequenceEdit assocView windowTitleCombo - defaultWindowSequenceButton customWindowSequenceButton windowSequenceEdit assocAddButton diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 9d2f063e8..7590bc613 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -136,7 +136,7 @@ void TestAutoType::testInternal() QCOMPARE(m_platform->activeWindowTitle(), QString("Test")); } -void TestAutoType::testAutoTypeWithoutSequence() +void TestAutoType::testSingleAutoType() { m_autoType->performAutoType(m_entry1, nullptr); @@ -147,17 +147,6 @@ void TestAutoType::testAutoTypeWithoutSequence() .arg(m_test->keyToString(Qt::Key_Enter))); } -void TestAutoType::testAutoTypeWithSequence() -{ - m_autoType->performAutoType(m_entry1, nullptr, "{Username}abc{PaSsWoRd}"); - - QCOMPARE(m_test->actionCount(), 15); - QCOMPARE(m_test->actionChars(), - QString("%1abc%2") - .arg(m_entry1->username()) - .arg(m_entry1->password())); -} - void TestAutoType::testGlobalAutoTypeWithNoMatch() { m_test->setActiveWindowTitle("nomatch"); @@ -310,4 +299,82 @@ void TestAutoType::testAutoTypeSyntaxChecks() QCOMPARE(true, AutoType::checkHighRepetition("{LEFT 50000000}")); QCOMPARE(false, AutoType::checkHighRepetition("{SPACE 10}{TAB 3}{RIGHT 50}")); QCOMPARE(false, AutoType::checkHighRepetition("{delay 5000000000}")); +} + +void TestAutoType::testAutoTypeEffectiveSequences() +{ + QString defaultSequence("{USERNAME}{TAB}{PASSWORD}{ENTER}"); + QString sequenceG1("{TEST_GROUP1}"); + QString sequenceG3("{TEST_GROUP3}"); + QString sequenceE2("{TEST_ENTRY2}"); + QString sequenceDisabled("{TEST_DISABLED}"); + QString sequenceOrphan("{TEST_ORPHAN}"); + + QScopedPointer db(new Database()); + QPointer rootGroup = db->rootGroup(); + + // Group with autotype enabled and custom default sequence + QPointer group1 = new Group(); + group1->setParent(rootGroup); + group1->setDefaultAutoTypeSequence(sequenceG1); + + // Child group with inherit + QPointer group2 = new Group(); + group2->setParent(group1); + + // Group with autotype disabled and custom default sequence + QPointer group3 = new Group(); + group3->setParent(group1); + group3->setAutoTypeEnabled(Group::Disable); + group3->setDefaultAutoTypeSequence(sequenceG3); + + QCOMPARE(rootGroup->defaultAutoTypeSequence(), QString()); + QCOMPARE(rootGroup->effectiveAutoTypeSequence(), defaultSequence); + QCOMPARE(group1->defaultAutoTypeSequence(), sequenceG1); + QCOMPARE(group1->effectiveAutoTypeSequence(), sequenceG1); + QCOMPARE(group2->defaultAutoTypeSequence(), QString()); + QCOMPARE(group2->effectiveAutoTypeSequence(), sequenceG1); + QCOMPARE(group3->defaultAutoTypeSequence(), sequenceG3); + QCOMPARE(group3->effectiveAutoTypeSequence(), QString()); + + // Entry from root group + QPointer entry1 = new Entry(); + entry1->setGroup(rootGroup); + + // Entry with custom default sequence + QPointer entry2 = new Entry(); + entry2->setDefaultAutoTypeSequence(sequenceE2); + entry2->setGroup(rootGroup); + + // Entry from enabled child group + QPointer entry3 = new Entry(); + entry3->setGroup(group2); + + // Entry from disabled group + QPointer entry4 = new Entry(); + entry4->setDefaultAutoTypeSequence(sequenceDisabled); + entry4->setGroup(group3); + + // Entry from enabled group with disabled autotype + QPointer entry5 = new Entry(); + entry5->setGroup(group2); + entry5->setDefaultAutoTypeSequence(sequenceDisabled); + entry5->setAutoTypeEnabled(false); + + // Entry with no parent + QScopedPointer entry6(new Entry()); + entry6->setDefaultAutoTypeSequence(sequenceOrphan); + + QCOMPARE(entry1->defaultAutoTypeSequence(), QString()); + QCOMPARE(entry1->effectiveAutoTypeSequence(), defaultSequence); + QCOMPARE(entry2->defaultAutoTypeSequence(), sequenceE2); + QCOMPARE(entry2->effectiveAutoTypeSequence(), sequenceE2); + QCOMPARE(entry3->defaultAutoTypeSequence(), QString()); + QCOMPARE(entry3->effectiveAutoTypeSequence(), sequenceG1); + QCOMPARE(entry4->defaultAutoTypeSequence(), sequenceDisabled); + QCOMPARE(entry4->effectiveAutoTypeSequence(), QString()); + QCOMPARE(entry5->defaultAutoTypeSequence(), sequenceDisabled); + QCOMPARE(entry5->effectiveAutoTypeSequence(), QString()); + QCOMPARE(entry6->defaultAutoTypeSequence(), sequenceOrphan); + QCOMPARE(entry6->effectiveAutoTypeSequence(), QString()); } \ No newline at end of file diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index b7c33823b..93a7d682c 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -38,8 +38,7 @@ private slots: void cleanup(); void testInternal(); - void testAutoTypeWithoutSequence(); - void testAutoTypeWithSequence(); + void testSingleAutoType(); void testGlobalAutoTypeWithNoMatch(); void testGlobalAutoTypeWithOneMatch(); void testGlobalAutoTypeTitleMatch(); @@ -48,6 +47,7 @@ private slots: void testGlobalAutoTypeTitleMatchDisabled(); void testGlobalAutoTypeRegExp(); void testAutoTypeSyntaxChecks(); + void testAutoTypeEffectiveSequences(); private: AutoTypePlatformInterface* m_platform;