Several fixes for Auto-Type

* On Windows, offer explicit methods to use the virtual keyboard style of typing. This partially reverts 1150b69836 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
This commit is contained in:
Jonathan White 2022-03-07 23:05:24 -05:00
parent 392cab2e36
commit 8a7eb36950
14 changed files with 207 additions and 129 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -60,6 +60,7 @@ image::autotype_entry_sequences.png[]
|{DELAY X} |Pause typing for X milliseconds |{DELAY X} |Pause typing for X milliseconds
|{CLEARFIELD} |Clear the input field |{CLEARFIELD} |Clear the input field
|{PICKCHARS} |Pick specific password characters from a dialog |{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 === Performing Global Auto-Type

View File

@ -705,14 +705,6 @@
<source>Double click a row to perform Auto-Type or find an entry using the search:</source> <source>Double click a row to perform Auto-Type or find an entry using the search:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </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> <message>
<source>Search all open databases</source> <source>Search all open databases</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@ -753,6 +745,19 @@ Ctrl+3 - Type TOTP&lt;/p&gt;</source>
<source>Copy TOTP</source> <source>Copy TOTP</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </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>
<context> <context>
<name>BrowserAccessControlDialog</name> <name>BrowserAccessControlDialog</name>

View File

@ -254,7 +254,10 @@ void AutoType::unregisterGlobalShortcut()
/** /**
* Core Autotype function that will execute actions * 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; QString error;
auto actions = parseSequence(sequence, entry, error); auto actions = parseSequence(sequence, entry, error);
@ -274,7 +277,8 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
return; return;
} }
if (hideWindow) { // Explicitly hide the main window if no target window is specified
if (window == 0) {
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
// Check for accessibility permission // Check for accessibility permission
if (!macUtils()->enableAccessibility()) { if (!macUtils()->enableAccessibility()) {
@ -289,20 +293,22 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
macUtils()->raiseLastActiveWindow(); macUtils()->raiseLastActiveWindow();
m_plugin->hideOwnWindow(); m_plugin->hideOwnWindow();
#else #else
getMainWindow()->minimizeOrHide(); if (getMainWindow()) {
getMainWindow()->minimizeOrHide();
}
#endif #endif
} QCoreApplication::processEvents();
// 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) {
window = m_plugin->activeWindow(); 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()); int delay = qMax(100, config()->get(Config::AutoTypeStartDelay).toInt());
Tools::wait(delay); Tools::wait(delay);
@ -346,7 +352,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
* Single Autotype entry-point function * Single Autotype entry-point function
* Look up the Auto-Type sequence for the given entry then perfom Auto-Type in the active window * 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) { if (!m_plugin) {
return; return;
@ -354,7 +360,7 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
auto sequences = entry->autoTypeSequences(); auto sequences = entry->autoTypeSequences();
if (!sequences.isEmpty()) { if (!sequences.isEmpty()) {
executeAutoTypeActions(entry, hideWindow, sequences.first()); executeAutoTypeActions(entry, sequences.first());
} }
} }
@ -362,13 +368,13 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
* Extra Autotype entry-point function * Extra Autotype entry-point function
* Perfom Auto-Type of the directly specified sequence in the active window * 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) { if (!m_plugin) {
return; return;
} }
executeAutoTypeActions(entry, hideWindow, sequence); executeAutoTypeActions(entry, sequence);
} }
void AutoType::startGlobalAutoType(const QString& search) void AutoType::startGlobalAutoType(const QString& search)
@ -467,12 +473,19 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
} }
connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject); connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject);
connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](const AutoTypeMatch& match) { connect(selectDialog,
m_lastMatch = match; &AutoTypeSelectDialog::matchActivated,
m_lastMatchRetypeTimer.start(config()->get(Config::GlobalAutoTypeRetypeTime).toInt() * 1000); this,
executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal); [this](const AutoTypeMatch& match, bool virtualMode) {
resetAutoTypeState(); 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] { connect(selectDialog, &QDialog::rejected, this, [this] {
restoreWindowState(); restoreWindowState();
resetAutoTypeState(); resetAutoTypeState();
@ -488,7 +501,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
selectDialog->activateWindow(); selectDialog->activateWindow();
} else if (!matchList.isEmpty()) { } else if (!matchList.isEmpty()) {
// Only one match and not asking, do it! // 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(); resetAutoTypeState();
} else { } else {
// We should never get here // We should never get here
@ -717,6 +730,12 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder); error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
return {}; 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") } else if (placeholder == "beep" || placeholder.startsWith("vkey") || placeholder.startsWith("appactivate")
|| placeholder.startsWith("c:")) { || placeholder.startsWith("c:")) {
// Ignore these commands // Ignore these commands

View File

@ -19,15 +19,15 @@
#ifndef KEEPASSX_AUTOTYPE_H #ifndef KEEPASSX_AUTOTYPE_H
#define KEEPASSX_AUTOTYPE_H #define KEEPASSX_AUTOTYPE_H
#include "AutoTypeAction.h"
#include <QMutex> #include <QMutex>
#include <QObject>
#include <QTimer> #include <QTimer>
#include <QWidget> #include <QWidget>
#include "AutoTypeAction.h"
#include "AutoTypeMatch.h" #include "AutoTypeMatch.h"
class AutoTypeAction;
class AutoTypeExecutor;
class AutoTypePlatformInterface; class AutoTypePlatformInterface;
class Database; class Database;
class Entry; class Entry;
@ -41,8 +41,8 @@ public:
QStringList windowTitles(); QStringList windowTitles();
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error = nullptr); bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error = nullptr);
void unregisterGlobalShortcut(); void unregisterGlobalShortcut();
void performAutoType(const Entry* entry, QWidget* hideWindow = nullptr); void performAutoType(const Entry* entry);
void performAutoTypeWithSequence(const Entry* entry, const QString& sequence, QWidget* hideWindow = nullptr); void performAutoTypeWithSequence(const Entry* entry, const QString& sequence);
static bool verifyAutoTypeSyntax(const QString& sequence, const Entry* entry, QString& error); static bool verifyAutoTypeSyntax(const QString& sequence, const Entry* entry, QString& error);
@ -80,9 +80,9 @@ private:
~AutoType() override; ~AutoType() override;
void loadPlugin(const QString& pluginPath); void loadPlugin(const QString& pluginPath);
void executeAutoTypeActions(const Entry* entry, void executeAutoTypeActions(const Entry* entry,
QWidget* hideWindow = nullptr, const QString& sequence = QString(),
const QString& customSequence = QString(), WId window = 0,
WId window = 0); AutoTypeExecutor::Mode mode = AutoTypeExecutor::Mode::NORMAL);
void restoreWindowState(); void restoreWindowState();
void resetAutoTypeState(); void resetAutoTypeState();

View File

@ -65,3 +65,14 @@ AutoTypeAction::Result AutoTypeBegin::exec(AutoTypeExecutor* executor) const
{ {
return executor->execBegin(this); 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();
}

View File

@ -121,13 +121,29 @@ public:
class KEEPASSXC_EXPORT AutoTypeExecutor class KEEPASSXC_EXPORT AutoTypeExecutor
{ {
public: public:
enum class Mode
{
NORMAL,
VIRTUAL
};
virtual ~AutoTypeExecutor() = default; virtual ~AutoTypeExecutor() = default;
virtual AutoTypeAction::Result execBegin(const AutoTypeBegin* action) = 0; virtual AutoTypeAction::Result execBegin(const AutoTypeBegin* action) = 0;
virtual AutoTypeAction::Result execType(const AutoTypeKey* action) = 0; virtual AutoTypeAction::Result execType(const AutoTypeKey* action) = 0;
virtual AutoTypeAction::Result execClearField(const AutoTypeClearField* action) = 0; virtual AutoTypeAction::Result execClearField(const AutoTypeClearField* action) = 0;
int execDelayMs = 25; int execDelayMs = 25;
Mode mode = Mode::NORMAL;
QString error; 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 #endif // KEEPASSX_AUTOTYPEACTION_H

View File

@ -58,6 +58,8 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
} }
}); });
m_ui->helpButton->setIcon(icons()->icon("system-help"));
m_ui->search->installEventFilter(this); m_ui->search->installEventFilter(this);
m_searchTimer.setInterval(300); m_searchTimer.setInterval(300);
@ -118,7 +120,7 @@ void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match)
if (match.first) { if (match.first) {
m_accepted = true; m_accepted = true;
accept(); accept();
emit matchActivated(std::move(match)); emit matchActivated(std::move(match), m_virtualMode);
} }
} }
@ -274,34 +276,54 @@ void AutoTypeSelectDialog::buildActionMenu()
m_actionMenu->addAction(typeUsernameAction); m_actionMenu->addAction(typeUsernameAction);
m_actionMenu->addAction(typePasswordAction); m_actionMenu->addAction(typePasswordAction);
m_actionMenu->addAction(typeTotpAction); 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(copyUsernameAction);
m_actionMenu->addAction(copyPasswordAction); m_actionMenu->addAction(copyPasswordAction);
m_actionMenu->addAction(copyTotpAction); m_actionMenu->addAction(copyTotpAction);
auto shortcut = new QShortcut(Qt::CTRL + Qt::Key_1, this); typeUsernameAction->setShortcut(Qt::CTRL + Qt::Key_1);
connect(shortcut, &QShortcut::activated, typeUsernameAction, &QAction::trigger);
connect(typeUsernameAction, &QAction::triggered, this, [&] { connect(typeUsernameAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch(); auto match = m_ui->view->currentMatch();
match.second = "{USERNAME}"; match.second = "{USERNAME}";
submitAutoTypeMatch(match); submitAutoTypeMatch(match);
}); });
shortcut = new QShortcut(Qt::CTRL + Qt::Key_2, this); typePasswordAction->setShortcut(Qt::CTRL + Qt::Key_2);
connect(shortcut, &QShortcut::activated, typePasswordAction, &QAction::trigger);
connect(typePasswordAction, &QAction::triggered, this, [&] { connect(typePasswordAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch(); auto match = m_ui->view->currentMatch();
match.second = "{PASSWORD}"; match.second = "{PASSWORD}";
submitAutoTypeMatch(match); submitAutoTypeMatch(match);
}); });
shortcut = new QShortcut(Qt::CTRL + Qt::Key_3, this); typeTotpAction->setShortcut(Qt::CTRL + Qt::Key_3);
connect(shortcut, &QShortcut::activated, typeTotpAction, &QAction::trigger);
connect(typeTotpAction, &QAction::triggered, this, [&] { connect(typeTotpAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch(); auto match = m_ui->view->currentMatch();
match.second = "{TOTP}"; match.second = "{TOTP}";
submitAutoTypeMatch(match); 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, [&] { connect(copyUsernameAction, &QAction::triggered, this, [&] {
auto entry = m_ui->view->currentMatch().first; auto entry = m_ui->view->currentMatch().first;
if (entry) { if (entry) {

View File

@ -45,7 +45,7 @@ public:
void setSearchString(const QString& search); void setSearchString(const QString& search);
signals: signals:
void matchActivated(AutoTypeMatch match); void matchActivated(AutoTypeMatch match, bool virtualMode = false);
protected: protected:
bool eventFilter(QObject* obj, QEvent* event) override; bool eventFilter(QObject* obj, QEvent* event) override;
@ -69,6 +69,7 @@ private:
QTimer m_searchTimer; QTimer m_searchTimer;
QPointer<QMenu> m_actionMenu; QPointer<QMenu> m_actionMenu;
bool m_virtualMode = false;
bool m_accepted = false; bool m_accepted = false;
}; };

View File

@ -43,34 +43,29 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QLabel" name="label_2"> <widget class="QToolButton" name="helpButton">
<property name="sizePolicy"> <property name="cursor">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <cursorShape>PointingHandCursor</cursorShape>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<property name="maximumSize"> <property name="focusPolicy">
<size> <enum>Qt::NoFocus</enum>
<width>14</width>
<height>14</height>
</size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&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; <string>&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+F - Toggle database search&lt;br/&gt;
Ctrl+1 - Type username&lt;br/&gt; Ctrl+1 - Type username&lt;br/&gt;
Ctrl+2 - Type password&lt;br/&gt; Ctrl+2 - Type password&lt;br/&gt;
Ctrl+3 - Type TOTP&lt;/p&gt;</string> Ctrl+3 - Type TOTP&lt;br/&gt;
Ctrl+4 - Use Virtual Keyboard (Windows Only)&lt;/p&gt;</string>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
border: none;
background: none;
}</string>
</property> </property>
<property name="text"> <property name="text">
<string/> <string notr="true">?</string>
</property>
<property name="pixmap">
<pixmap resource="../../share/icons/icons.qrc">:/icons/application/scalable/actions/system-help.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -81,67 +81,71 @@ bool AutoTypePlatformWin::raiseWindow(WId window)
// //
// Send unicode character to foreground window // Send unicode character to foreground window
// //
void AutoTypePlatformWin::sendChar(const QChar& ch, bool isKeyDown) void AutoTypePlatformWin::sendChar(const QChar& ch)
{ {
auto vkey = VkKeyScanExW(ch.unicode(), GetKeyboardLayout(0)); DWORD nativeFlags = KEYEVENTF_UNICODE;
if (vkey == -1) {
// VKey not found, send as Unicode character
DWORD flags = KEYEVENTF_UNICODE;
if (!isKeyDown) {
flags |= KEYEVENTF_KEYUP;
}
INPUT in; INPUT in[2];
in.type = INPUT_KEYBOARD; in[0].type = INPUT_KEYBOARD;
in.ki.wVk = 0; in[0].ki.wVk = 0;
in.ki.wScan = ch.unicode(); in[0].ki.wScan = ch.unicode();
in.ki.dwFlags = flags; in[0].ki.dwFlags = KEYEVENTF_UNICODE;
in.ki.time = 0; in[0].ki.time = 0;
in.ki.dwExtraInfo = ::GetMessageExtraInfo(); in[0].ki.dwExtraInfo = ::GetMessageExtraInfo();
::SendInput(1, &in, sizeof(INPUT));
in[1] = in[0];
in[1].ki.dwFlags |= KEYEVENTF_KEYUP;
::SendInput(2, &in[0], sizeof(INPUT));
}
void AutoTypePlatformWin::sendCharVirtual(const QChar& ch)
{
auto vKey = VkKeyScanExW(ch.unicode(), GetKeyboardLayout(0));
if (vKey == -1) {
// VKey not found, send as Unicode character
sendChar(ch);
return; return;
} }
if (HIBYTE(vkey) & 0x6) { if (HIBYTE(vKey) & 0x6) {
sendKey(Qt::Key_AltGr, true); setKeyState(Qt::Key_AltGr, true);
} else { } else {
if (HIBYTE(vkey) & 0x1) { if (HIBYTE(vKey) & 0x1) {
sendKey(Qt::Key_Shift, true); setKeyState(Qt::Key_Shift, true);
} }
if (HIBYTE(vkey) & 0x2) { if (HIBYTE(vKey) & 0x2) {
sendKey(Qt::Key_Control, true); setKeyState(Qt::Key_Control, true);
} }
if (HIBYTE(vkey) & 0x4) { if (HIBYTE(vKey) & 0x4) {
sendKey(Qt::Key_Alt, true); setKeyState(Qt::Key_Alt, true);
} }
} }
DWORD flags = KEYEVENTF_SCANCODE; INPUT in[2];
if (!isKeyDown) { in[0].type = INPUT_KEYBOARD;
flags |= KEYEVENTF_KEYUP; in[0].ki.wVk = 0;
} in[0].ki.wScan = MapVirtualKey(LOBYTE(vKey), MAPVK_VK_TO_VSC);
in[0].ki.dwFlags = KEYEVENTF_SCANCODE;
in[0].ki.time = 0;
in[0].ki.dwExtraInfo = ::GetMessageExtraInfo();
INPUT in; in[1] = in[0];
in.type = INPUT_KEYBOARD; in[1].ki.dwFlags |= KEYEVENTF_KEYUP;
in.ki.wVk = 0;
in.ki.wScan = MapVirtualKey(LOBYTE(vkey), MAPVK_VK_TO_VSC);
in.ki.dwFlags = flags;
in.ki.time = 0;
in.ki.dwExtraInfo = ::GetMessageExtraInfo();
::SendInput(1, &in, sizeof(INPUT)); ::SendInput(2, &in[0], sizeof(INPUT));
if (HIBYTE(vkey) & 0x6) { if (HIBYTE(vKey) & 0x6) {
sendKey(Qt::Key_AltGr, false); setKeyState(Qt::Key_AltGr, false);
} else { } else {
if (HIBYTE(vkey) & 0x1) { if (HIBYTE(vKey) & 0x1) {
sendKey(Qt::Key_Shift, false); setKeyState(Qt::Key_Shift, false);
} }
if (HIBYTE(vkey) & 0x2) { if (HIBYTE(vKey) & 0x2) {
sendKey(Qt::Key_Control, false); setKeyState(Qt::Key_Control, false);
} }
if (HIBYTE(vkey) & 0x4) { if (HIBYTE(vKey) & 0x4) {
sendKey(Qt::Key_Alt, false); setKeyState(Qt::Key_Alt, false);
} }
} }
} }
@ -149,14 +153,14 @@ void AutoTypePlatformWin::sendChar(const QChar& ch, bool isKeyDown)
// //
// Send virtual key code to foreground window // Send virtual key code to foreground window
// //
void AutoTypePlatformWin::sendKey(Qt::Key key, bool isKeyDown) void AutoTypePlatformWin::setKeyState(Qt::Key key, bool down)
{ {
WORD nativeKeyCode = winUtils()->qtToNativeKeyCode(key); WORD nativeKeyCode = winUtils()->qtToNativeKeyCode(key);
DWORD nativeFlags = KEYEVENTF_SCANCODE; DWORD nativeFlags = KEYEVENTF_SCANCODE;
if (isExtendedKey(nativeKeyCode)) { if (isExtendedKey(nativeKeyCode)) {
nativeFlags |= KEYEVENTF_EXTENDEDKEY; nativeFlags |= KEYEVENTF_EXTENDEDKEY;
} }
if (!isKeyDown) { if (!down) {
nativeFlags |= KEYEVENTF_KEYUP; nativeFlags |= KEYEVENTF_KEYUP;
} }
@ -279,37 +283,40 @@ AutoTypeAction::Result AutoTypeExecutorWin::execBegin(const AutoTypeBegin* actio
AutoTypeAction::Result AutoTypeExecutorWin::execType(const AutoTypeKey* action) AutoTypeAction::Result AutoTypeExecutorWin::execType(const AutoTypeKey* action)
{ {
if (action->modifiers & Qt::ShiftModifier) { if (action->modifiers & Qt::ShiftModifier) {
m_platform->sendKey(Qt::Key_Shift, true); m_platform->setKeyState(Qt::Key_Shift, true);
} }
if (action->modifiers & Qt::ControlModifier) { if (action->modifiers & Qt::ControlModifier) {
m_platform->sendKey(Qt::Key_Control, true); m_platform->setKeyState(Qt::Key_Control, true);
} }
if (action->modifiers & Qt::AltModifier) { if (action->modifiers & Qt::AltModifier) {
m_platform->sendKey(Qt::Key_Alt, true); m_platform->setKeyState(Qt::Key_Alt, true);
} }
if (action->modifiers & Qt::MetaModifier) { if (action->modifiers & Qt::MetaModifier) {
m_platform->sendKey(Qt::Key_Meta, true); m_platform->setKeyState(Qt::Key_Meta, true);
} }
if (action->key != Qt::Key_unknown) { if (action->key != Qt::Key_unknown) {
m_platform->sendKey(action->key, true); m_platform->setKeyState(action->key, true);
m_platform->sendKey(action->key, false); m_platform->setKeyState(action->key, false);
} else { } else {
m_platform->sendChar(action->character, true); if (mode == Mode::VIRTUAL) {
m_platform->sendChar(action->character, false); m_platform->sendCharVirtual(action->character);
} else {
m_platform->sendChar(action->character);
}
} }
if (action->modifiers & Qt::ShiftModifier) { if (action->modifiers & Qt::ShiftModifier) {
m_platform->sendKey(Qt::Key_Shift, false); m_platform->setKeyState(Qt::Key_Shift, false);
} }
if (action->modifiers & Qt::ControlModifier) { if (action->modifiers & Qt::ControlModifier) {
m_platform->sendKey(Qt::Key_Control, false); m_platform->setKeyState(Qt::Key_Control, false);
} }
if (action->modifiers & Qt::AltModifier) { if (action->modifiers & Qt::AltModifier) {
m_platform->sendKey(Qt::Key_Alt, false); m_platform->setKeyState(Qt::Key_Alt, false);
} }
if (action->modifiers & Qt::MetaModifier) { if (action->modifiers & Qt::MetaModifier) {
m_platform->sendKey(Qt::Key_Meta, false); m_platform->setKeyState(Qt::Key_Meta, false);
} }
Tools::sleep(execDelayMs); Tools::sleep(execDelayMs);

View File

@ -43,8 +43,9 @@ public:
bool raiseWindow(WId window) override; bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override; AutoTypeExecutor* createExecutor() override;
void sendChar(const QChar& ch, bool isKeyDown); void sendCharVirtual(const QChar& ch);
void sendKey(Qt::Key key, bool isKeyDown); void sendChar(const QChar& ch);
void setKeyState(Qt::Key key, bool down);
private: private:
static bool isExtendedKey(DWORD nativeKeyCode); static bool isExtendedKey(DWORD nativeKeyCode);

View File

@ -782,9 +782,9 @@ void DatabaseWidget::performAutoType(const QString& sequence)
} }
if (sequence.isEmpty()) { if (sequence.isEmpty()) {
autoType()->performAutoType(currentEntry, window()); autoType()->performAutoType(currentEntry);
} else { } else {
autoType()->performAutoTypeWithSequence(currentEntry, sequence, window()); autoType()->performAutoTypeWithSequence(currentEntry, sequence);
} }
} }
} }

View File

@ -140,7 +140,7 @@ void TestAutoType::testInternal()
void TestAutoType::testSingleAutoType() void TestAutoType::testSingleAutoType()
{ {
m_autoType->performAutoType(m_entry1, nullptr); m_autoType->performAutoType(m_entry1);
QCOMPARE(m_test->actionCount(), 14); QCOMPARE(m_test->actionCount(), 14);
QCOMPARE(m_test->actionChars(), QCOMPARE(m_test->actionChars(),