From 065a85e05c9422df33a936b30f871abb8655b5f4 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Tue, 16 Jan 2018 17:23:29 +0100 Subject: [PATCH 1/9] fix effective autotype sequence --- src/core/Entry.cpp | 34 +++++++++--------- src/core/Group.cpp | 4 +++ tests/TestAutoType.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++ tests/TestAutoType.h | 1 + 4 files changed, 101 insertions(+), 18 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index d30adeeca9..951c2184bf 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -218,30 +218,28 @@ 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 (!m_data.defaultAutoTypeSequence.isEmpty()) { - return m_data.defaultAutoTypeSequence; + if (autoTypeEnabled() == false) { + return QString(); } - QString sequence; - const Group* grp = group(); - if(grp) { - sequence = grp->effectiveAutoTypeSequence(); - } else { - return QString(); + const Group* parent = group(); + if (!parent) { + return QString(); + } + + QString sequence = parent->effectiveAutoTypeSequence(); + if (sequence.isEmpty()) { + return QString(); } - if (sequence.isEmpty() && (!username().isEmpty() || !password().isEmpty())) { - if (username().isEmpty()) { - sequence = "{PASSWORD}{ENTER}"; - } - else if (password().isEmpty()) { - sequence = "{USERNAME}{ENTER}"; - } - else { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; - } + if (!m_data.defaultAutoTypeSequence.isEmpty()) { + return m_data.defaultAutoTypeSequence; } return sequence; diff --git a/src/core/Group.cpp b/src/core/Group.cpp index bdcfeff73d..e75f45268d 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -192,6 +192,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; diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 9d2f063e8d..7d26a6afe6 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -310,4 +310,84 @@ 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}"); + + Database* 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 + QPointer 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()); + + delete db; } \ No newline at end of file diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index b7c33823b8..516481282d 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -48,6 +48,7 @@ private slots: void testGlobalAutoTypeTitleMatchDisabled(); void testGlobalAutoTypeRegExp(); void testAutoTypeSyntaxChecks(); + void testAutoTypeEffectiveSequences(); private: AutoTypePlatformInterface* m_platform; From b5cabbeb43fb655a93d2a4cd14fc98a40e356f58 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Tue, 16 Jan 2018 22:05:58 +0100 Subject: [PATCH 2/9] add support for multiple autotype sequence, fix #559 --- src/CMakeLists.txt | 3 + src/autotype/AutoType.cpp | 98 ++++++------- src/autotype/AutoType.h | 6 +- src/autotype/AutoTypeSelectDialog.cpp | 31 ++-- src/autotype/AutoTypeSelectDialog.h | 14 +- src/autotype/AutoTypeSelectView.cpp | 11 +- src/autotype/AutoTypeSelectView.h | 8 +- src/core/AutoTypeMatch.cpp | 39 +++++ src/core/AutoTypeMatch.h | 41 ++++++ src/gui/entry/AutoTypeMatchModel.cpp | 202 ++++++++++++++++++++++++++ src/gui/entry/AutoTypeMatchModel.h | 66 +++++++++ src/gui/entry/AutoTypeMatchView.cpp | 116 +++++++++++++++ src/gui/entry/AutoTypeMatchView.h | 58 ++++++++ 13 files changed, 600 insertions(+), 93 deletions(-) create mode 100644 src/core/AutoTypeMatch.cpp create mode 100644 src/core/AutoTypeMatch.h create mode 100644 src/gui/entry/AutoTypeMatchModel.cpp create mode 100644 src/gui/entry/AutoTypeMatchModel.h create mode 100644 src/gui/entry/AutoTypeMatchView.cpp create mode 100644 src/gui/entry/AutoTypeMatchView.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ccdc955f27..4b7c07cd6c 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 2a77c4c1db..e3b5b54c08 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" @@ -136,7 +137,12 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c QString sequence; if (customSequence.isEmpty()) { - sequence = autoTypeSequence(entry); + QList sequences = autoTypeSequences(entry); + if(sequences.isEmpty()) { + sequence = ""; + } else { + sequence = sequences.first(); + } } else { sequence = customSequence; } @@ -199,36 +205,36 @@ void AutoType::performGlobalAutoType(const QList& dbList) m_inAutoType = true; - 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 QList sequences = autoTypeSequences(entry, windowTitle); + for (QString sequence : sequences) { + if (!sequence.isEmpty()) { + matchList << AutoTypeMatch(entry,sequence); + } } } } - if (entryList.isEmpty()) { + if (matchList.isEmpty()) { m_inAutoType = false; 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()) { + } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { m_inAutoType = false; - performAutoType(entryList.first(), nullptr, sequenceHash[entryList.first()]); + performAutoType(matchList.first().entry, nullptr, matchList.first().sequence); } 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,7 +245,7 @@ void AutoType::performGlobalAutoType(const QList& dbList) } } -void AutoType::performAutoTypeFromGlobal(Entry* entry, const QString& sequence) +void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) { Q_ASSERT(m_inAutoType); @@ -247,7 +253,7 @@ void AutoType::performAutoTypeFromGlobal(Entry* entry, const QString& sequence) m_inAutoType = false; - performAutoType(entry, nullptr, sequence, m_windowFromGlobal); + performAutoType(match.entry, nullptr, match.sequence, m_windowFromGlobal); } void AutoType::resetInAutoType() @@ -506,78 +512,56 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c return list; } -QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitle) +QList AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle) { + QList sequenceList; + if (!entry->autoTypeEnabled()) { - return QString(); + return sequenceList; } - bool enableSet = false; - QString sequence; 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 (group->autoTypeEnabled() == Group::Disable) { + return QList(); + } else if (group->autoTypeEnabled() == Group::Enable) { + return sequenceList; } - - 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; + } while (group); + + return sequenceList; } bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPattern) diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index eb366ae9ce..5c89b4fa63 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -23,6 +23,8 @@ #include #include +#include "core/AutoTypeMatch.h" + class AutoTypeAction; class AutoTypeExecutor; class AutoTypePlatformInterface; @@ -69,7 +71,7 @@ public slots: void globalShortcutTriggered(); private slots: - void performAutoTypeFromGlobal(Entry* entry, const QString& sequence); + void performAutoTypeFromGlobal(AutoTypeMatch match); void resetInAutoType(); void unloadPlugin(); @@ -79,7 +81,7 @@ private slots: void loadPlugin(const QString& pluginPath); 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); diff --git a/src/autotype/AutoTypeSelectDialog.cpp b/src/autotype/AutoTypeSelectDialog.cpp index b39c78e1db..3ef0864811 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 3d9c684ed1..83abd2d800 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 7d9db4130b..e4dba05158 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 aadf99fa62..e6a2ec6529 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 @@ class AutoTypeSelectView : public EntryView 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 0000000000..c1faab9e65 --- /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 0000000000..768cf1682f --- /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/gui/entry/AutoTypeMatchModel.cpp b/src/gui/entry/AutoTypeMatchModel.cpp new file mode 100644 index 0000000000..3a48e17371 --- /dev/null +++ b/src/gui/entry/AutoTypeMatchModel.cpp @@ -0,0 +1,202 @@ +/* + * 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 : asConst(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; + } else { + 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 QVariant(); + } + + 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(); +} + +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 QVariant(); +} + +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 0000000000..8d341f5f41 --- /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 Q_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 0000000000..013d192fc6 --- /dev/null +++ b/src/gui/entry/AutoTypeMatchView.cpp @@ -0,0 +1,116 @@ +/* + * 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()); + } + + 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); +} + +void AutoTypeMatchView::setModel(QAbstractItemModel* model) +{ + Q_UNUSED(model); + Q_ASSERT(false); +} + +AutoTypeMatch AutoTypeMatchView::currentMatch() +{ + QModelIndexList list = selectionModel()->selectedRows(); + if (list.size() == 1) { + return m_model->matchFromIndex(m_sortModel->mapToSource(list.first())); + } else { + 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)); + } else { + return AutoTypeMatch(); + } +} diff --git a/src/gui/entry/AutoTypeMatchView.h b/src/gui/entry/AutoTypeMatchView.h new file mode 100644 index 0000000000..08c177005b --- /dev/null +++ b/src/gui/entry/AutoTypeMatchView.h @@ -0,0 +1,58 @@ +/* + * 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); + void setModel(QAbstractItemModel* model) override; + AutoTypeMatch currentMatch(); + void setCurrentMatch(AutoTypeMatch match); + AutoTypeMatch matchFromIndex(const QModelIndex& index); + void setMatchList(const QList& matches); + void setFirstMatchActive(); + +Q_SIGNALS: + void matchActivated(AutoTypeMatch match); + void matchSelectionChanged(); + +protected: + void keyPressEvent(QKeyEvent* event) override; + +private Q_SLOTS: + void emitMatchActivated(const QModelIndex& index); + +private: + AutoTypeMatchModel* const m_model; + SortFilterHideProxyModel* const m_sortModel; +}; + +#endif // KEEPASSX_AUTOTYPEMATCHVIEW_H From a9479fd66260495133a4f516b69b31ff91495040 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 18 Jan 2018 23:45:09 +0100 Subject: [PATCH 3/9] refactor autotype sequences and entry-point functions --- src/autotype/AutoType.cpp | 108 ++++++++++++++++++++++++++------------ src/autotype/AutoType.h | 12 ++--- src/core/Entry.cpp | 13 ++++- src/core/Entry.h | 3 ++ src/core/Group.cpp | 3 +- src/core/Group.h | 1 + tests/TestAutoType.cpp | 13 +---- tests/TestAutoType.h | 3 +- 8 files changed, 99 insertions(+), 57 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index e3b5b54c08..c72b61ef62 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -128,32 +128,17 @@ QStringList AutoType::windowTitles() return m_plugin->windowTitles(); } -void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& customSequence, WId window) +/** + * Core Autotype function that will execute actions + */ +void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window) { - if (m_inAutoType || !m_plugin) { - return; - } - m_inAutoType = true; - - QString sequence; - if (customSequence.isEmpty()) { - QList sequences = autoTypeSequences(entry); - if(sequences.isEmpty()) { - sequence = ""; - } else { - sequence = sequences.first(); - } - } else { - sequence = customSequence; - } - - if (!checkSyntax(sequence)) { + // no edit to the sequence beyond this point + if (!verifyAutoTypeSyntax(sequence)) { + m_inAutoType = false; // TODO: make this automatic return; } - sequence.replace("{{}", "{LEFTBRACE}"); - sequence.replace("{}}", "{RIGHTBRACE}"); - QList actions; ListDeleter actionsDeleter(&actions); @@ -191,6 +176,30 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c 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_inAutoType || !m_plugin) { + return; + } + + QList sequences = autoTypeSequences(entry); + if(sequences.isEmpty()) { + return; + } + + m_inAutoType = true; + + executeAutoTypeActions(entry, hideWindow, sequences.first()); +} + +/** + * Global Autotype entry-point funcion + * Perform global autotype on the active window + */ void AutoType::performGlobalAutoType(const QList& dbList) { if (m_inAutoType || !m_plugin) { @@ -227,7 +236,7 @@ void AutoType::performGlobalAutoType(const QList& dbList) MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message); } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { m_inAutoType = false; - performAutoType(matchList.first().entry, nullptr, matchList.first().sequence); + executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); } else { m_windowFromGlobal = m_plugin->activeWindow(); AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog(); @@ -253,7 +262,7 @@ void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) m_inAutoType = false; - performAutoType(match.entry, nullptr, match.sequence, m_windowFromGlobal); + executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); } void AutoType::resetInAutoType() @@ -283,6 +292,7 @@ void AutoType::unloadPlugin() } } + bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) { Q_ASSERT(key); @@ -325,12 +335,19 @@ int AutoType::callEventFilter(void* event) 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 == '{') { @@ -369,6 +386,9 @@ bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList AutoType::createActionFromTemplate(const QString& tmpl, const Entry* entry) { QString tmplName = tmpl; @@ -512,6 +532,9 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c return list; } +/** + * Retrive the autotype sequences matches for a given windowTitle + */ QList AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle) { QList sequenceList; @@ -564,6 +587,9 @@ QList AutoType::autoTypeSequences(const Entry* entry, const QString& wi 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) { @@ -574,11 +600,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)) { @@ -593,6 +627,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+)?"; @@ -618,6 +655,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 @@ -626,6 +666,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 @@ -634,6 +677,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 @@ -642,6 +688,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)) { @@ -675,12 +724,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 5c89b4fa63..db60133aee 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -38,10 +38,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); @@ -51,9 +47,7 @@ class AutoType : public QObject 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() { @@ -79,6 +73,10 @@ private slots: 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); QList autoTypeSequences(const Entry* entry, const QString& windowTitle = QString()); diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 951c2184bf..e4c9e720e4 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() @@ -232,7 +234,7 @@ QString Entry::effectiveAutoTypeSequence() const if (!parent) { return QString(); } - + QString sequence = parent->effectiveAutoTypeSequence(); if (sequence.isEmpty()) { return QString(); @@ -242,6 +244,15 @@ QString Entry::effectiveAutoTypeSequence() const return m_data.defaultAutoTypeSequence; } + if (sequence == Group::RootAutoTypeSequence && (!username().isEmpty() || !password().isEmpty())) { + if (username().isEmpty()) { + 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 7b995b7aec..8579f95331 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -85,6 +85,7 @@ class Entry : public QObject 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 @@ class Entry : public QObject 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 e75f45268d..51b24c1995 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); @@ -211,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 70f0331968..b1654a2361 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -88,6 +88,7 @@ class Group : public QObject 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/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 7d26a6afe6..53f455ce68 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"); diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index 516481282d..93a7d682c3 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(); From 12a4b9aaa3bdbb2ca6036b17244bd19d32e493dc Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 18 Jan 2018 23:47:24 +0100 Subject: [PATCH 4/9] reorder functions by logic --- src/autotype/AutoType.cpp | 139 +++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 70 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index c72b61ef62..5c936bf13e 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -103,6 +103,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) { @@ -128,6 +141,62 @@ QStringList AutoType::windowTitles() return m_plugin->windowTitles(); } +void AutoType::resetInAutoType() +{ + Q_ASSERT(m_inAutoType); + + m_inAutoType = false; +} + +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; + } 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); +} + /** * Core Autotype function that will execute actions */ @@ -265,76 +334,6 @@ void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); } -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); -} - /** * Parse an autotype sequence and resolve its Template/command inside as AutoTypeActions */ From ba4ef52e9ead8d33edb30d4bd23796706c6f3ba8 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 19 Jan 2018 00:50:22 +0100 Subject: [PATCH 5/9] improve Window Associations UI/UX --- src/gui/entry/EditEntryWidget.cpp | 21 ++++++++------------- src/gui/entry/EditEntryWidgetAutoType.ui | 12 ++---------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index c146da691e..71366651fd 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -170,8 +170,7 @@ 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_autoTypeWindowSequenceGroup->addButton(m_autoTypeUi->customWindowSequenceButton); m_autoTypeAssocModel->setAutoTypeAssociations(m_autoTypeAssoc); m_autoTypeUi->assocView->setModel(m_autoTypeAssocModel); m_autoTypeUi->assocView->setColumnHidden(1, true); @@ -190,8 +189,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 +641,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 +995,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 +1025,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 +1042,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 a8090f7680..3d4ec7a3e5 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 From a76c92ed9a3eb9d087b6a3f06836bbcac77fc492 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 24 Jan 2018 20:08:56 +0100 Subject: [PATCH 6/9] change inAutotype logic, preventing multiple autotype call --- src/autotype/AutoType.cpp | 47 ++++++++++++++-------------- src/core/Entry.cpp | 8 ++--- src/gui/entry/AutoTypeMatchModel.cpp | 17 +++++----- src/gui/entry/AutoTypeMatchModel.h | 2 +- src/gui/entry/AutoTypeMatchView.cpp | 14 ++------- src/gui/entry/AutoTypeMatchView.h | 5 ++- src/gui/entry/EditEntryWidget.cpp | 1 - tests/TestAutoType.cpp | 6 ++-- 8 files changed, 44 insertions(+), 56 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 5c936bf13e..5bd10115a4 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -173,12 +173,10 @@ bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifie m_currentGlobalKey = key; m_currentGlobalModifiers = modifiers; return true; - } else { - return false; } - } else { - return true; + return false; } + return true; } void AutoType::unregisterGlobalShortcut() @@ -202,9 +200,13 @@ int AutoType::callEventFilter(void* event) */ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window) { + Q_ASSERT(m_inAutoType); + if (!m_inAutoType) { + return; + } + // no edit to the sequence beyond this point if (!verifyAutoTypeSyntax(sequence)) { - m_inAutoType = false; // TODO: make this automatic return; } @@ -212,7 +214,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c ListDeleter actionsDeleter(&actions); if (!parseActions(sequence, entry, actions)) { - m_inAutoType = false; // TODO: make this automatic return; } @@ -241,8 +242,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c action->accept(m_executor); QCoreApplication::processEvents(QEventLoop::AllEvents, 10); } - - m_inAutoType = false; } /** @@ -256,13 +255,15 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) } QList sequences = autoTypeSequences(entry); - if(sequences.isEmpty()) { + if (sequences.isEmpty()) { return; } m_inAutoType = true; executeAutoTypeActions(entry, hideWindow, sequences.first()); + + m_inAutoType = false; } /** @@ -304,8 +305,8 @@ void AutoType::performGlobalAutoType(const QList& dbList) message.append(windowTitle); MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message); } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { - m_inAutoType = false; executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); + m_inAutoType = false; } else { m_windowFromGlobal = m_plugin->activeWindow(); AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog(); @@ -329,9 +330,9 @@ void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) m_plugin->raiseWindow(m_windowFromGlobal); - m_inAutoType = false; - executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); + + m_inAutoType = false; } /** @@ -542,6 +543,17 @@ QList AutoType::autoTypeSequences(const Entry* entry, const QString& wi return sequenceList; } + 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()) { const QList assocList = entry->autoTypeAssociations()->getAll(); for (const AutoTypeAssociations::Association& assoc : assocList) { @@ -572,17 +584,6 @@ QList AutoType::autoTypeSequences(const Entry* entry, const QString& wi sequenceList.append(entry->effectiveAutoTypeSequence()); } - const Group* group = entry->group(); - do { - if (group->autoTypeEnabled() == Group::Disable) { - return QList(); - } else if (group->autoTypeEnabled() == Group::Enable) { - return sequenceList; - } - group = group->parentGroup(); - - } while (group); - return sequenceList; } diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index e4c9e720e4..8db955c93f 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -226,18 +226,18 @@ QString Entry::defaultAutoTypeSequence() const */ QString Entry::effectiveAutoTypeSequence() const { - if (autoTypeEnabled() == false) { - return QString(); + if (!autoTypeEnabled()) { + return {}; } const Group* parent = group(); if (!parent) { - return QString(); + return {}; } QString sequence = parent->effectiveAutoTypeSequence(); if (sequence.isEmpty()) { - return QString(); + return {}; } if (!m_data.defaultAutoTypeSequence.isEmpty()) { diff --git a/src/gui/entry/AutoTypeMatchModel.cpp b/src/gui/entry/AutoTypeMatchModel.cpp index 3a48e17371..6a370dea5f 100644 --- a/src/gui/entry/AutoTypeMatchModel.cpp +++ b/src/gui/entry/AutoTypeMatchModel.cpp @@ -55,7 +55,7 @@ void AutoTypeMatchModel::setMatchList(const QList& matches) QSet databases; - for (AutoTypeMatch match : asConst(m_matches)) { + for (AutoTypeMatch& match : m_matches) { databases.insert(match.entry->group()->database()); } @@ -81,9 +81,8 @@ int AutoTypeMatchModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; - } else { - return m_matches.size(); } + return m_matches.size(); } int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const @@ -96,7 +95,7 @@ int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { - return QVariant(); + return {}; } AutoTypeMatch match = matchFromIndex(index); @@ -138,7 +137,7 @@ QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const return font; } - return QVariant(); + return {}; } QVariant AutoTypeMatchModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -156,12 +155,12 @@ QVariant AutoTypeMatchModel::headerData(int section, Qt::Orientation orientation } } - return QVariant(); + return {}; } void AutoTypeMatchModel::entryDataChanged(Entry* entry) { - for (int row = 0; row < m_matches.size(); row++) { + 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)); @@ -172,13 +171,13 @@ void AutoTypeMatchModel::entryDataChanged(Entry* entry) void AutoTypeMatchModel::entryAboutToRemove(Entry* entry) { - for (int row = 0; row < m_matches.size(); row++) { + 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--; + --row; } } } diff --git a/src/gui/entry/AutoTypeMatchModel.h b/src/gui/entry/AutoTypeMatchModel.h index 8d341f5f41..791dbc3df6 100644 --- a/src/gui/entry/AutoTypeMatchModel.h +++ b/src/gui/entry/AutoTypeMatchModel.h @@ -50,7 +50,7 @@ class AutoTypeMatchModel : public QAbstractTableModel void setMatchList(const QList& matches); -private Q_SLOTS: +private slots: void entryAboutToRemove(Entry* entry); void entryRemoved(); void entryDataChanged(Entry* entry); diff --git a/src/gui/entry/AutoTypeMatchView.cpp b/src/gui/entry/AutoTypeMatchView.cpp index 013d192fc6..ad7d16ddc2 100644 --- a/src/gui/entry/AutoTypeMatchView.cpp +++ b/src/gui/entry/AutoTypeMatchView.cpp @@ -61,7 +61,7 @@ void AutoTypeMatchView::setMatchList(const QList& matches) for (int i = 0; i < m_model->columnCount(); ++i) { resizeColumnToContents(i); if (columnWidth(i) > 250) { - setColumnWidth(i, 250); + setColumnWidth(i, 250); } } setFirstMatchActive(); @@ -84,20 +84,13 @@ void AutoTypeMatchView::emitMatchActivated(const QModelIndex& index) emit matchActivated(match); } -void AutoTypeMatchView::setModel(QAbstractItemModel* model) -{ - Q_UNUSED(model); - Q_ASSERT(false); -} - AutoTypeMatch AutoTypeMatchView::currentMatch() { QModelIndexList list = selectionModel()->selectedRows(); if (list.size() == 1) { return m_model->matchFromIndex(m_sortModel->mapToSource(list.first())); - } else { - return AutoTypeMatch(); } + return AutoTypeMatch(); } void AutoTypeMatchView::setCurrentMatch(AutoTypeMatch match) @@ -110,7 +103,6 @@ AutoTypeMatch AutoTypeMatchView::matchFromIndex(const QModelIndex& index) { if (index.isValid()) { return m_model->matchFromIndex(m_sortModel->mapToSource(index)); - } else { - return AutoTypeMatch(); } + return AutoTypeMatch(); } diff --git a/src/gui/entry/AutoTypeMatchView.h b/src/gui/entry/AutoTypeMatchView.h index 08c177005b..14ad9ea2a5 100644 --- a/src/gui/entry/AutoTypeMatchView.h +++ b/src/gui/entry/AutoTypeMatchView.h @@ -33,21 +33,20 @@ class AutoTypeMatchView : public QTreeView public: explicit AutoTypeMatchView(QWidget* parent = nullptr); - void setModel(QAbstractItemModel* model) override; AutoTypeMatch currentMatch(); void setCurrentMatch(AutoTypeMatch match); AutoTypeMatch matchFromIndex(const QModelIndex& index); void setMatchList(const QList& matches); void setFirstMatchActive(); -Q_SIGNALS: +signals: void matchActivated(AutoTypeMatch match); void matchSelectionChanged(); protected: void keyPressEvent(QKeyEvent* event) override; -private Q_SLOTS: +private slots: void emitMatchActivated(const QModelIndex& index); private: diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 71366651fd..bab5a07280 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -170,7 +170,6 @@ void EditEntryWidget::setupAutoType() m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->inheritSequenceButton); m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->customSequenceButton); - //m_autoTypeWindowSequenceGroup->addButton(m_autoTypeUi->customWindowSequenceButton); m_autoTypeAssocModel->setAutoTypeAssociations(m_autoTypeAssoc); m_autoTypeUi->assocView->setModel(m_autoTypeAssocModel); m_autoTypeUi->assocView->setColumnHidden(1, true); diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 53f455ce68..7590bc613f 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -310,7 +310,7 @@ void TestAutoType::testAutoTypeEffectiveSequences() QString sequenceDisabled("{TEST_DISABLED}"); QString sequenceOrphan("{TEST_ORPHAN}"); - Database* db = new Database(); + QScopedPointer db(new Database()); QPointer rootGroup = db->rootGroup(); // Group with autotype enabled and custom default sequence @@ -362,7 +362,7 @@ void TestAutoType::testAutoTypeEffectiveSequences() entry5->setAutoTypeEnabled(false); // Entry with no parent - QPointer entry6 = new Entry(); + QScopedPointer entry6(new Entry()); entry6->setDefaultAutoTypeSequence(sequenceOrphan); QCOMPARE(entry1->defaultAutoTypeSequence(), QString()); @@ -377,6 +377,4 @@ void TestAutoType::testAutoTypeEffectiveSequences() QCOMPARE(entry5->effectiveAutoTypeSequence(), QString()); QCOMPARE(entry6->defaultAutoTypeSequence(), sequenceOrphan); QCOMPARE(entry6->effectiveAutoTypeSequence(), QString()); - - delete db; } \ No newline at end of file From b4cf98998e579704c453f03897c354e5d01a1f49 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 29 Jan 2018 19:44:32 +0100 Subject: [PATCH 7/9] convert inAutoType from boolean block to QMutex --- src/autotype/AutoType.cpp | 35 ++++++++++++++++------------------- src/autotype/AutoType.h | 5 +++-- src/autotype/AutoTypeAction.h | 1 + 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 5bd10115a4..73bb3ef4b6 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -41,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) @@ -143,9 +142,7 @@ QStringList AutoType::windowTitles() void AutoType::resetInAutoType() { - Q_ASSERT(m_inAutoType); - - m_inAutoType = false; + m_inAutoType.unlock(); } void AutoType::raiseWindow() @@ -200,11 +197,6 @@ int AutoType::callEventFilter(void* event) */ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window) { - Q_ASSERT(m_inAutoType); - if (!m_inAutoType) { - return; - } - // no edit to the sequence beyond this point if (!verifyAutoTypeSyntax(sequence)) { return; @@ -250,7 +242,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c */ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) { - if (m_inAutoType || !m_plugin) { + if (!m_plugin) { return; } @@ -259,11 +251,13 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) return; } - m_inAutoType = true; + if (!m_inAutoType.tryLock()) { + return; + } executeAutoTypeActions(entry, hideWindow, sequences.first()); - m_inAutoType = false; + m_inAutoType.unlock(); } /** @@ -272,7 +266,7 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) */ void AutoType::performGlobalAutoType(const QList& dbList) { - if (m_inAutoType || !m_plugin) { + if (!m_plugin) { return; } @@ -282,7 +276,9 @@ void AutoType::performGlobalAutoType(const QList& dbList) return; } - m_inAutoType = true; + if (!m_inAutoType.tryLock()) { + return; + } QList matchList; @@ -290,7 +286,7 @@ void AutoType::performGlobalAutoType(const QList& dbList) const QList dbEntries = db->rootGroup()->entriesRecursive(); for (Entry* entry : dbEntries) { const QList sequences = autoTypeSequences(entry, windowTitle); - for (QString sequence : sequences) { + for (const QString& sequence : sequences) { if (!sequence.isEmpty()) { matchList << AutoTypeMatch(entry,sequence); } @@ -299,14 +295,14 @@ void AutoType::performGlobalAutoType(const QList& dbList) } if (matchList.isEmpty()) { - m_inAutoType = false; + 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 ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); - m_inAutoType = false; + m_inAutoType.unlock(); } else { m_windowFromGlobal = m_plugin->activeWindow(); AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog(); @@ -326,13 +322,14 @@ void AutoType::performGlobalAutoType(const QList& dbList) 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); executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); - m_inAutoType = false; + m_inAutoType.unlock(); } /** diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index db60133aee..3b22106bd3 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,7 @@ #include #include #include +#include #include "core/AutoTypeMatch.h" @@ -84,7 +85,7 @@ private slots: 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 490f0d89f9..7f0d829c0d 100644 --- a/src/autotype/AutoTypeAction.h +++ b/src/autotype/AutoTypeAction.h @@ -20,6 +20,7 @@ #include #include +#include #include "core/Global.h" From 16fad1aba155573cd417caace672ffb286800c8c Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 31 Jan 2018 21:53:53 +0100 Subject: [PATCH 8/9] fix duplicate autotype sequences --- src/autotype/AutoType.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 73bb3ef4b6..0dfdedaec8 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -285,10 +285,10 @@ void AutoType::performGlobalAutoType(const QList& dbList) for (Database* db : dbList) { const QList dbEntries = db->rootGroup()->entriesRecursive(); for (Entry* entry : dbEntries) { - const QList sequences = autoTypeSequences(entry, windowTitle); + const QSet sequences = autoTypeSequences(entry, windowTitle).toSet(); for (const QString& sequence : sequences) { if (!sequence.isEmpty()) { - matchList << AutoTypeMatch(entry,sequence); + matchList << AutoTypeMatch(entry, sequence); } } } @@ -531,6 +531,7 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c /** * 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) { From aa54c7b6b372100a56eb1772be3f87b0e57a94fd Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sun, 4 Feb 2018 23:32:51 +0100 Subject: [PATCH 9/9] fix MatchView activation with Enter/Return on macOS --- src/gui/entry/AutoTypeMatchView.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/entry/AutoTypeMatchView.cpp b/src/gui/entry/AutoTypeMatchView.cpp index ad7d16ddc2..67f38c79e5 100644 --- a/src/gui/entry/AutoTypeMatchView.cpp +++ b/src/gui/entry/AutoTypeMatchView.cpp @@ -50,6 +50,10 @@ 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);