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