Skip to content

Commit

Permalink
Several fixes for Auto-Type
Browse files Browse the repository at this point in the history
* On Windows, offer explicit methods to use the virtual keyboard style of typing. This partially reverts 1150b69 by going back to the standard unicode method by default. However, uses can either add {MODE=VIRTUAL} to their sequence or choose "Use Virtual Keyboard" / CTRL+4 from the selection dialog.

* Took this opportunity to clean up the signature of  AutoType::performAutoType and AutoType::performAutoTypeWithSequence by removing the "hideWindow" attribute.

* Show keyboard shortcuts on the selection dialog context menu

* Fix selection dialog help icon color when in dark theme
  • Loading branch information
droidmonkey committed Mar 8, 2022
1 parent bd3de48 commit e6fb363
Show file tree
Hide file tree
Showing 14 changed files with 207 additions and 129 deletions.
Binary file modified docs/images/autotype_selection_dialog_type_menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/topics/AutoType.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ image::autotype_entry_sequences.png[]
|{DELAY X} |Pause typing for X milliseconds
|{CLEARFIELD} |Clear the input field
|{PICKCHARS} |Pick specific password characters from a dialog
|{MODE=VIRTUAL} |(Experimental) Use virtual key presses on Windows, useful for virtual machines
|===

=== Performing Global Auto-Type
Expand Down
21 changes: 13 additions & 8 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,14 +705,6 @@
<source>Double click a row to perform Auto-Type or find an entry using the search:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&lt;p&gt;You can use advanced search queries to find any entry in your open databases. The following shortcuts are useful:&lt;br/&gt;
Ctrl+F - Toggle database search&lt;br/&gt;
Ctrl+1 - Type username&lt;br/&gt;
Ctrl+2 - Type password&lt;br/&gt;
Ctrl+3 - Type TOTP&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Search all open databases</source>
<translation type="unfinished"></translation>
Expand Down Expand Up @@ -753,6 +745,19 @@ Ctrl+3 - Type TOTP&lt;/p&gt;</source>
<source>Copy TOTP</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&lt;p&gt;You can use advanced search queries to find any entry in your open databases. The following shortcuts are useful:&lt;br/&gt;
Ctrl+F - Toggle database search&lt;br/&gt;
Ctrl+1 - Type username&lt;br/&gt;
Ctrl+2 - Type password&lt;br/&gt;
Ctrl+3 - Type TOTP&lt;br/&gt;
Ctrl+4 - Use Virtual Keyboard (Windows Only)&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use Virtual Keyboard</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>BrowserAccessControlDialog</name>
Expand Down
65 changes: 42 additions & 23 deletions src/autotype/AutoType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ void AutoType::unregisterGlobalShortcut()
/**
* Core Autotype function that will execute actions
*/
void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window)
void AutoType::executeAutoTypeActions(const Entry* entry,
const QString& sequence,
WId window,
AutoTypeExecutor::Mode mode)
{
QString error;
auto actions = parseSequence(sequence, entry, error);
Expand All @@ -274,7 +277,8 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
return;
}

if (hideWindow) {
// Explicitly hide the main window if no target window is specified
if (window == 0) {
#if defined(Q_OS_MACOS)
// Check for accessibility permission
if (!macUtils()->enableAccessibility()) {
Expand All @@ -289,20 +293,22 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
macUtils()->raiseLastActiveWindow();
m_plugin->hideOwnWindow();
#else
getMainWindow()->minimizeOrHide();
if (getMainWindow()) {
getMainWindow()->minimizeOrHide();
}
#endif
}

// Restore window state in case app stole focus
restoreWindowState();
QCoreApplication::processEvents();
m_plugin->raiseWindow(m_windowForGlobal);

// Used only for selected entry auto-type
if (!window) {
QCoreApplication::processEvents();
window = m_plugin->activeWindow();
} else {
// Restore window state (macOS only) then raise the target window
restoreWindowState();
QCoreApplication::processEvents();
m_plugin->raiseWindow(window);
}

// Restore executor mode
m_executor->mode = mode;

int delay = qMax(100, config()->get(Config::AutoTypeStartDelay).toInt());
Tools::wait(delay);

Expand Down Expand Up @@ -346,29 +352,29 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
* Single Autotype entry-point function
* Look up the Auto-Type sequence for the given entry then perfom Auto-Type in the active window
*/
void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
void AutoType::performAutoType(const Entry* entry)
{
if (!m_plugin) {
return;
}

auto sequences = entry->autoTypeSequences();
if (!sequences.isEmpty()) {
executeAutoTypeActions(entry, hideWindow, sequences.first());
executeAutoTypeActions(entry, sequences.first());
}
}

/**
* Extra Autotype entry-point function
* Perfom Auto-Type of the directly specified sequence in the active window
*/
void AutoType::performAutoTypeWithSequence(const Entry* entry, const QString& sequence, QWidget* hideWindow)
void AutoType::performAutoTypeWithSequence(const Entry* entry, const QString& sequence)
{
if (!m_plugin) {
return;
}

executeAutoTypeActions(entry, hideWindow, sequence);
executeAutoTypeActions(entry, sequence);
}

void AutoType::startGlobalAutoType(const QString& search)
Expand Down Expand Up @@ -467,12 +473,19 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
}

connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject);
connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](const AutoTypeMatch& match) {
m_lastMatch = match;
m_lastMatchRetypeTimer.start(config()->get(Config::GlobalAutoTypeRetypeTime).toInt() * 1000);
executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal);
resetAutoTypeState();
});
connect(selectDialog,
&AutoTypeSelectDialog::matchActivated,
this,
[this](const AutoTypeMatch& match, bool virtualMode) {
m_lastMatch = match;
m_lastMatchRetypeTimer.start(config()->get(Config::GlobalAutoTypeRetypeTime).toInt() * 1000);
executeAutoTypeActions(match.first,
match.second,
m_windowForGlobal,
virtualMode ? AutoTypeExecutor::Mode::VIRTUAL
: AutoTypeExecutor::Mode::NORMAL);
resetAutoTypeState();
});
connect(selectDialog, &QDialog::rejected, this, [this] {
restoreWindowState();
resetAutoTypeState();
Expand All @@ -488,7 +501,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
selectDialog->activateWindow();
} else if (!matchList.isEmpty()) {
// Only one match and not asking, do it!
executeAutoTypeActions(matchList.first().first, nullptr, matchList.first().second, m_windowForGlobal);
executeAutoTypeActions(matchList.first().first, matchList.first().second, m_windowForGlobal);
resetAutoTypeState();
} else {
// We should never get here
Expand Down Expand Up @@ -717,6 +730,12 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
return {};
}
} else if (placeholder.startsWith("mode=")) {
auto mode = AutoTypeExecutor::Mode::NORMAL;
if (placeholder.endsWith("virtual")) {
mode = AutoTypeExecutor::Mode::VIRTUAL;
}
actions << QSharedPointer<AutoTypeMode>::create(mode);
} else if (placeholder == "beep" || placeholder.startsWith("vkey") || placeholder.startsWith("appactivate")
|| placeholder.startsWith("c:")) {
// Ignore these commands
Expand Down
16 changes: 8 additions & 8 deletions src/autotype/AutoType.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
#ifndef KEEPASSX_AUTOTYPE_H
#define KEEPASSX_AUTOTYPE_H

#include "AutoTypeAction.h"

#include <QMutex>
#include <QObject>
#include <QTimer>
#include <QWidget>

#include "AutoTypeAction.h"
#include "AutoTypeMatch.h"

class AutoTypeAction;
class AutoTypeExecutor;
class AutoTypePlatformInterface;
class Database;
class Entry;
Expand All @@ -41,8 +41,8 @@ class AutoType : public QObject
QStringList windowTitles();
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error = nullptr);
void unregisterGlobalShortcut();
void performAutoType(const Entry* entry, QWidget* hideWindow = nullptr);
void performAutoTypeWithSequence(const Entry* entry, const QString& sequence, QWidget* hideWindow = nullptr);
void performAutoType(const Entry* entry);
void performAutoTypeWithSequence(const Entry* entry, const QString& sequence);

static bool verifyAutoTypeSyntax(const QString& sequence, const Entry* entry, QString& error);

Expand Down Expand Up @@ -80,9 +80,9 @@ private slots:
~AutoType() override;
void loadPlugin(const QString& pluginPath);
void executeAutoTypeActions(const Entry* entry,
QWidget* hideWindow = nullptr,
const QString& customSequence = QString(),
WId window = 0);
const QString& sequence = QString(),
WId window = 0,
AutoTypeExecutor::Mode mode = AutoTypeExecutor::Mode::NORMAL);
void restoreWindowState();
void resetAutoTypeState();

Expand Down
11 changes: 11 additions & 0 deletions src/autotype/AutoTypeAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,14 @@ AutoTypeAction::Result AutoTypeBegin::exec(AutoTypeExecutor* executor) const
{
return executor->execBegin(this);
}

AutoTypeMode::AutoTypeMode(AutoTypeExecutor::Mode mode)
: mode(mode)
{
}

AutoTypeAction::Result AutoTypeMode::exec(AutoTypeExecutor* executor) const
{
executor->mode = mode;
return AutoTypeAction::Result::Ok();
}
16 changes: 16 additions & 0 deletions src/autotype/AutoTypeAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,29 @@ class KEEPASSXC_EXPORT AutoTypeBegin : public AutoTypeAction
class KEEPASSXC_EXPORT AutoTypeExecutor
{
public:
enum class Mode
{
NORMAL,
VIRTUAL
};

virtual ~AutoTypeExecutor() = default;
virtual AutoTypeAction::Result execBegin(const AutoTypeBegin* action) = 0;
virtual AutoTypeAction::Result execType(const AutoTypeKey* action) = 0;
virtual AutoTypeAction::Result execClearField(const AutoTypeClearField* action) = 0;

int execDelayMs = 25;
Mode mode = Mode::NORMAL;
QString error;
};

class KEEPASSXC_EXPORT AutoTypeMode : public AutoTypeAction
{
public:
AutoTypeMode(AutoTypeExecutor::Mode mode = AutoTypeExecutor::Mode::NORMAL);
Result exec(AutoTypeExecutor* executor) const override;

const AutoTypeExecutor::Mode mode;
};

#endif // KEEPASSX_AUTOTYPEACTION_H
36 changes: 29 additions & 7 deletions src/autotype/AutoTypeSelectDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
}
});

m_ui->helpButton->setIcon(icons()->icon("system-help"));

m_ui->search->installEventFilter(this);

m_searchTimer.setInterval(300);
Expand Down Expand Up @@ -118,7 +120,7 @@ void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match)
if (match.first) {
m_accepted = true;
accept();
emit matchActivated(std::move(match));
emit matchActivated(std::move(match), m_virtualMode);
}
}

Expand Down Expand Up @@ -274,34 +276,54 @@ void AutoTypeSelectDialog::buildActionMenu()
m_actionMenu->addAction(typeUsernameAction);
m_actionMenu->addAction(typePasswordAction);
m_actionMenu->addAction(typeTotpAction);
#ifdef Q_OS_WIN
auto typeVirtualAction = new QAction(icons()->icon("auto-type"), tr("Use Virtual Keyboard"));
m_actionMenu->addAction(typeVirtualAction);
#endif
m_actionMenu->addAction(copyUsernameAction);
m_actionMenu->addAction(copyPasswordAction);
m_actionMenu->addAction(copyTotpAction);

auto shortcut = new QShortcut(Qt::CTRL + Qt::Key_1, this);
connect(shortcut, &QShortcut::activated, typeUsernameAction, &QAction::trigger);
typeUsernameAction->setShortcut(Qt::CTRL + Qt::Key_1);
connect(typeUsernameAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch();
match.second = "{USERNAME}";
submitAutoTypeMatch(match);
});

shortcut = new QShortcut(Qt::CTRL + Qt::Key_2, this);
connect(shortcut, &QShortcut::activated, typePasswordAction, &QAction::trigger);
typePasswordAction->setShortcut(Qt::CTRL + Qt::Key_2);
connect(typePasswordAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch();
match.second = "{PASSWORD}";
submitAutoTypeMatch(match);
});

shortcut = new QShortcut(Qt::CTRL + Qt::Key_3, this);
connect(shortcut, &QShortcut::activated, typeTotpAction, &QAction::trigger);
typeTotpAction->setShortcut(Qt::CTRL + Qt::Key_3);
connect(typeTotpAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch();
match.second = "{TOTP}";
submitAutoTypeMatch(match);
});

#ifdef Q_OS_WIN
typeVirtualAction->setShortcut(Qt::CTRL + Qt::Key_4);
connect(typeVirtualAction, &QAction::triggered, this, [&] {
m_virtualMode = true;
activateCurrentMatch();
});
#endif

#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
// Qt 5.10 introduced a new "feature" to hide shortcuts in context menus
// Unfortunately, Qt::AA_DontShowShortcutsInContextMenus is broken, have to manually enable them
typeUsernameAction->setShortcutVisibleInContextMenu(true);
typePasswordAction->setShortcutVisibleInContextMenu(true);
typeTotpAction->setShortcutVisibleInContextMenu(true);
#ifdef Q_OS_WIN
typeVirtualAction->setShortcutVisibleInContextMenu(true);
#endif
#endif

connect(copyUsernameAction, &QAction::triggered, this, [&] {
auto entry = m_ui->view->currentMatch().first;
if (entry) {
Expand Down
3 changes: 2 additions & 1 deletion src/autotype/AutoTypeSelectDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class AutoTypeSelectDialog : public QDialog
void setSearchString(const QString& search);

signals:
void matchActivated(AutoTypeMatch match);
void matchActivated(AutoTypeMatch match, bool virtualMode = false);

protected:
bool eventFilter(QObject* obj, QEvent* event) override;
Expand All @@ -69,6 +69,7 @@ private slots:
QTimer m_searchTimer;
QPointer<QMenu> m_actionMenu;

bool m_virtualMode = false;
bool m_accepted = false;
};

Expand Down
Loading

0 comments on commit e6fb363

Please sign in to comment.