mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Additional Auto-Type improvements based on PR feedback
* Improve documentation and remove external links to keepass.info documentation
This commit is contained in:
parent
02446af743
commit
8d058cbd04
@ -34,3 +34,5 @@ include::topics/AutoType.adoc[tags=*]
|
||||
include::topics/KeeShare.adoc[tags=*]
|
||||
|
||||
include::topics/SSHAgent.adoc[tags=*]
|
||||
|
||||
include::topics/Reference.adoc[tags=*]
|
||||
|
@ -31,7 +31,7 @@ To configure Auto-Type sequences for your entries, perform the following steps:
|
||||
.Auto-Type entry sequences
|
||||
image::autotype_entry_sequences.png[]
|
||||
|
||||
2. _(Optional)_ Define a custom Auto-Type sequence for each window title match by selecting the _Use specific sequence for this association_ checkbox. Sequence action codes and field placeholders are detailed in the following table. Beyond the most important ones detailed below, there are additional action codes and placeholders available: https://keepass.info/help/base/autotype.html#autoseq[KeePass Auto-Type Action Codes, window=_blank] and https://keepass.info/help/base/placeholders.html[KeePass Placeholders, window=_blank]. Action codes and placeholders are not case sensitive.
|
||||
2. _(Optional)_ Define a custom Auto-Type sequence for each window title match by selecting the _Use specific sequence for this association_ checkbox. Sequence action codes and field placeholders are detailed in the following table. Beyond the most important ones detailed below, there are additional action codes and placeholders available: xref:UserGuide.adoc#_auto_type_actions[Auto-Type Actions Reference] and xref:UserGuide.adoc#_entry_placeholders[Entry Placeholders Reference]. Action codes and placeholders are not case sensitive.
|
||||
+
|
||||
[grid=rows, frame=none, width=90%]
|
||||
|===
|
||||
@ -43,10 +43,7 @@ image::autotype_entry_sequences.png[]
|
||||
|{URL} |URL
|
||||
|{NOTES} |Notes
|
||||
|{TOTP} |Current TOTP value (if configured)
|
||||
|{DT_SIMPLE} |Current date-time
|
||||
|{DB_DIR} |Absolute directory path for database file
|
||||
|{S:<ATTRIBUTE_NAME>} |Value for the given attribute name
|
||||
|{REF:<FIELD>@<SEARCH_IN>:<SEARCH_TEXT>} |Search for a field in another entry using the reference syntax. https://keepass.info/help/base/fieldrefs.html[Read more…, window=_blank]
|
||||
|===
|
||||
+
|
||||
[grid=rows, frame=none, width=90%]
|
||||
@ -57,22 +54,13 @@ image::autotype_entry_sequences.png[]
|
||||
|Press the corresponding keyboard key
|
||||
|
||||
|{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key
|
||||
|{F1}, {F2}, ..., {F16} |Press F1, F2, etc.
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively
|
||||
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|
||||
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|
||||
|{DELAY=X} |Set delay between key presses to X milliseconds
|
||||
|{DELAY X} |Pause typing for X milliseconds
|
||||
|{CLEARFIELD} |Clear the input field
|
||||
|{PICKCHARS} |Pick specific password characters from a dialog
|
||||
|===
|
||||
+
|
||||
*Text Conversions:*
|
||||
+
|
||||
*{T-CONV:/<PLACEHOLDER>/<METHOD>/}* +
|
||||
Convert resolved placeholder (e.g., {USERNAME}, {PASSWORD}, etc.) using the following methods: UPPER, LOWER, BASE64, HEX, URI, URI-DEC. https://keepass.info/help/base/placeholders.html#conv[Read more…, window=_blank]
|
||||
+
|
||||
*{T-REPLACE-RX:/<PLACEHOLDER>/<SEARCH>/<REPLACE>/}* +
|
||||
Use regular expressions to find and replace data from a resolved placeholder. Refer to match groups using $1, $2, etc. https://keepass.info/help/base/placeholders.html#replacerx[Read more…, window=_blank]
|
||||
|
||||
=== Performing Global Auto-Type
|
||||
The global Auto-Type keyboard shortcut is used when you have focus on the window you want to type into. To make use of this feature, you must have previously configured an Auto-Type hotkey.
|
||||
|
@ -120,18 +120,7 @@ image::clone_entry_dialog.png[,70%]
|
||||
.References in a cloned entry
|
||||
image::clone_entry_references.png[]
|
||||
|
||||
4. You can create your own references using the following syntax:
|
||||
+
|
||||
`{REF:<ShortCode>@I:<UUID>}`
|
||||
+
|
||||
Where `<UUID>` is the Unique Identifier of the entry to pull data from and `<ShortCode>` is from the following:
|
||||
+
|
||||
* T - Title
|
||||
* U - Username
|
||||
* P - Password
|
||||
* A - URL
|
||||
* N - Notes
|
||||
* I - UUID
|
||||
4. You can create your own references using the xref:UserGuide.adoc#_entry_cross_reference[Entry Reference Syntax]
|
||||
|
||||
== Searching the Database
|
||||
KeePassXC provides an enhanced and granular search features the enables you to search for specific entries in the databases using the different modifiers, wild card characters, and logical operators.
|
||||
@ -287,7 +276,7 @@ image::database_security.png[]
|
||||
+
|
||||
.Database credentials
|
||||
image::database_security_credentials.png[]
|
||||
|
||||
+
|
||||
WARNING: Consider creating a backup of your YubiKey. Please refer to <<Creating a YubiKey backup>>
|
||||
|
||||
5. Encryption settings allows you to change the average time it takes to encrypt and decrypt the database. The longer time that is chosen, the harder it will be to brute force attack your database. *We recommend a setting of one second.*
|
||||
|
94
docs/topics/Reference.adoc
Normal file
94
docs/topics/Reference.adoc
Normal file
@ -0,0 +1,94 @@
|
||||
= KeePassXC - Reference
|
||||
include::.sharedheader[]
|
||||
:imagesdir: ../images
|
||||
|
||||
// tag::content[]
|
||||
== Reference
|
||||
This section contains full details on advanced features available in KeePassXC.
|
||||
|
||||
=== Entry Placeholders
|
||||
[grid=rows, frame=none, width=90%]
|
||||
|===
|
||||
|Placeholder |Description
|
||||
|
||||
|{TITLE} |Entry Title
|
||||
|{USERNAME} |Username
|
||||
|{PASSWORD} |Password
|
||||
|{URL} |URL
|
||||
|{NOTES} |Notes
|
||||
|{TOTP} |Current TOTP value (if configured)
|
||||
|{S:<ATTRIBUTE_NAME>} |Value for the given attribute (case sensitive)
|
||||
|{URL:RMVSCM} |URL without scheme (e.g., https)
|
||||
|{URL:WITHOUTSCHEME} |URL without scheme
|
||||
|{URL:SCM} |URL Scheme
|
||||
|{URL:SCHEME} |URL Scheme
|
||||
|{URL:HOST} |URL Host (e.g., example.com)
|
||||
|{URL:PORT} |URL Port
|
||||
|{URL:PATH} |URL Path (e.g., /path/to/page.html)
|
||||
|{URL:QUERY} |URL Query String
|
||||
|{URL:FRAGMENT} |URL Fragment
|
||||
|{URL:USERINFO} |URL Username:Password
|
||||
|{URL:USERNAME} |URL Username
|
||||
|{URL:PASSWORD} |URL Password
|
||||
|{DT_SIMPLE} |Current Date-Time (yyyyMMddhhmmss)
|
||||
|{DT_YEAR} |Current Year (yyyy)
|
||||
|{DT_MONTH} |Current Month (MM)
|
||||
|{DT_DAY} |Current Day (dd)
|
||||
|{DT_HOUR} |Current Hour (hh)
|
||||
|{DT_MINUTE} |Current Minutes (mm)
|
||||
|{DT_SECOND} |Current Seconds (ss)
|
||||
|{DT_UTC_SIMPLE} |Current UTC Date-Time (yyyyMMddhhmmss)
|
||||
|{DT_UTC_YEAR} |Current UTC Year (yyyy)
|
||||
|{DT_UTC_MONTH} |Current UTC Month (MM)
|
||||
|{DT_UTC_DAY} |Current UTC Day (dd)
|
||||
|{DT_UTC_HOUR} |Current UTC Hour (hh)
|
||||
|{DT_UTC_MINUTE} |Current UTC Minutes (mm)
|
||||
|{DT_UTC_SECOND} |Current UTC Seconds (ss)
|
||||
|{DB_DIR} |Absolute directory path of database file
|
||||
|===
|
||||
|
||||
=== Entry Cross-Reference
|
||||
A reference to another entry's field is possible using the short-hand syntax:
|
||||
`{REF:<FIELD>@<SEARCH_IN>:<SEARCH_TEXT>}`
|
||||
|
||||
`<FIELD>` and `<SEARCH_IN>` can be one of following:
|
||||
|
||||
* T - Title
|
||||
* U - Username
|
||||
* P - Password
|
||||
* A - URL
|
||||
* N - Notes
|
||||
* I - UUID (found on entry properties page)
|
||||
* O - Custom Attribute _(SEARCH_IN only)_
|
||||
|
||||
Examples: +
|
||||
`{REF:U@I:033054D445C648C59092CC1D661B1B71}` +
|
||||
`{REF:P@T:Other Entry}` +
|
||||
`{REF:A@O:Attribute 1}`
|
||||
|
||||
=== Auto-Type Actions
|
||||
[grid=rows, frame=none, width=90%]
|
||||
|===
|
||||
|Action Code |Description
|
||||
|
||||
|{TAB}, {ENTER}, {SPACE}, {INSERT}, {DELETE}, {HOME}, {END}, {PGUP}, {PGDN}, {BACKSPACE}, {CAPSLOCK}, {ESC}
|
||||
|Press the corresponding keyboard key
|
||||
|
||||
|{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key
|
||||
|{F1}, {F2}, ..., {F16} |Press F1, F2, etc.
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively
|
||||
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|
||||
|{DELAY=X} |Set delay between key presses to X milliseconds
|
||||
|{DELAY X} |Pause typing for X milliseconds
|
||||
|{CLEARFIELD} |Clear the input field
|
||||
|{PICKCHARS} |Pick specific password characters from a dialog
|
||||
|===
|
||||
|
||||
*Text Conversions:*
|
||||
|
||||
*{T-CONV:/<PLACEHOLDER>/<METHOD>/}* +
|
||||
Convert resolved placeholder (e.g., {USERNAME}, {PASSWORD}, etc.) using the following methods: UPPER, LOWER, BASE64, HEX, URI, URI-DEC.
|
||||
|
||||
*{T-REPLACE-RX:/<PLACEHOLDER>/<SEARCH>/<REPLACE>/}* +
|
||||
Use regular expressions to find and replace data from a resolved placeholder. Refer to match groups using $1, $2, etc.
|
||||
// end::content[]
|
@ -20,7 +20,6 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPluginLoader>
|
||||
#include <QRegularExpression>
|
||||
#include <QWindow>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
@ -32,7 +31,6 @@
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/ListDeleter.h"
|
||||
#include "core/Resources.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/MainWindow.h"
|
||||
@ -131,7 +129,7 @@ AutoType::AutoType(QObject* parent, bool test)
|
||||
// prevent crash when the plugin has unresolved symbols
|
||||
m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
||||
|
||||
QString pluginName = "keepassx-autotype-";
|
||||
QString pluginName = "keepassxc-autotype-";
|
||||
if (!test) {
|
||||
pluginName += QApplication::platformName();
|
||||
} else {
|
||||
@ -169,7 +167,7 @@ void AutoType::loadPlugin(const QString& pluginPath)
|
||||
if (m_plugin) {
|
||||
if (m_plugin->isAvailable()) {
|
||||
m_executor = m_plugin->createExecutor();
|
||||
connect(osUtils, &OSUtilsBase::globalShortcutTriggered, this, [this](QString name) {
|
||||
connect(osUtils, &OSUtilsBase::globalShortcutTriggered, this, [this](const QString& name) {
|
||||
if (name == "autotype") {
|
||||
startGlobalAutoType();
|
||||
}
|
||||
@ -249,6 +247,20 @@ void AutoType::unregisterGlobalShortcut()
|
||||
*/
|
||||
void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window)
|
||||
{
|
||||
QString error;
|
||||
auto actions = parseSequence(sequence, entry, error);
|
||||
|
||||
if (!error.isEmpty()) {
|
||||
auto errorMsg = tr("The requested Auto-Type sequence cannot be used due to an error:");
|
||||
errorMsg.append(QString("\n%1\n%2").arg(sequence, error));
|
||||
if (getMainWindow()) {
|
||||
MessageBox::critical(getMainWindow(), tr("Auto-Type Error"), errorMsg);
|
||||
}
|
||||
qWarning() << errorMsg;
|
||||
emit autotypeRejected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_inAutoType.tryLock()) {
|
||||
return;
|
||||
}
|
||||
@ -272,11 +284,9 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
|
||||
#endif
|
||||
}
|
||||
|
||||
auto actions = parseActions(sequence, entry);
|
||||
|
||||
// Restore window state in case app stole focus
|
||||
restoreWindowState();
|
||||
QApplication::processEvents();
|
||||
QCoreApplication::processEvents();
|
||||
m_plugin->raiseWindow(m_windowForGlobal);
|
||||
|
||||
// Used only for selected entry auto-type
|
||||
@ -286,7 +296,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
|
||||
|
||||
Tools::wait(qMax(100, config()->get(Config::AutoTypeStartDelay).toInt()));
|
||||
|
||||
for (auto action : asConst(actions)) {
|
||||
for (const auto& action : asConst(actions)) {
|
||||
if (m_plugin->activeWindow() != window) {
|
||||
qWarning("Active window changed, interrupting auto-type.");
|
||||
emit autotypeRejected();
|
||||
@ -301,7 +311,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
|
||||
m_windowForGlobal = 0;
|
||||
m_windowTitleForGlobal.clear();
|
||||
|
||||
// emit signal only if autotype performed correctly
|
||||
emit autotypePerformed();
|
||||
m_inAutoType.unlock();
|
||||
}
|
||||
@ -426,7 +435,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
||||
selectDialog->setMatches(matchList, dbList);
|
||||
|
||||
connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject);
|
||||
connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](AutoTypeMatch match) {
|
||||
connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](const AutoTypeMatch& match) {
|
||||
executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal);
|
||||
resetAutoTypeState();
|
||||
});
|
||||
@ -480,7 +489,7 @@ void AutoType::resetAutoTypeState()
|
||||
* If error is provided then syntax checking will be performed.
|
||||
*/
|
||||
QList<QSharedPointer<AutoTypeAction>>
|
||||
AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString* error)
|
||||
AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly)
|
||||
{
|
||||
const int maxTypeDelay = 100;
|
||||
const int maxWaitDelay = 10000;
|
||||
@ -495,8 +504,8 @@ AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString
|
||||
sequence.replace("{}}", "{RIGHTBRACE}");
|
||||
|
||||
// Quick test for bracket syntax
|
||||
if ((sequence.count("{") + sequence.count("}")) % 2 != 0 && error) {
|
||||
*error = tr("Bracket imbalance detected, found extra { or }");
|
||||
if (sequence.count("{") != sequence.count("}")) {
|
||||
error = tr("Bracket imbalance detected, found extra { or }");
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -530,147 +539,163 @@ AutoType::parseActions(const QString& entrySequence, const Entry* entry, QString
|
||||
}
|
||||
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 {};
|
||||
}
|
||||
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 == "pickchars") {
|
||||
if (error) {
|
||||
// Ignore this if we are syntax checking
|
||||
continue;
|
||||
}
|
||||
// Show pickchars dialog for entry's password
|
||||
auto password = entry->resolvePlaceholder(entry->password());
|
||||
if (!password.isEmpty()) {
|
||||
PickcharsDialog pickcharsDialog(password);
|
||||
if (pickcharsDialog.exec() == QDialog::Accepted && !pickcharsDialog.selectedChars().isEmpty()) {
|
||||
auto chars = pickcharsDialog.selectedChars();
|
||||
auto iter = chars.begin();
|
||||
while (iter != chars.end()) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(*iter);
|
||||
++iter;
|
||||
if (pickcharsDialog.pressTab() && iter != chars.end()) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(g_placeholderToKey["tab"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (placeholder.startsWith("t-conv:")) {
|
||||
// Reset to the original capture to preserve case
|
||||
placeholder = match.captured(3);
|
||||
placeholder.replace("t-conv:", "", Qt::CaseInsensitive);
|
||||
if (!placeholder.isEmpty()) {
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 4) {
|
||||
auto resolved = entry->resolveMultiplePlaceholders(parts[1]);
|
||||
auto type = parts[2].toLower();
|
||||
if (placeholder.isEmpty()) {
|
||||
if (!character.isEmpty()) {
|
||||
// Type a single character with modifier
|
||||
actions << QSharedPointer<AutoTypeKey>::create(character[0], modifiers);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == "base64") {
|
||||
resolved = resolved.toUtf8().toBase64();
|
||||
} else if (type == "hex") {
|
||||
resolved = resolved.toUtf8().toHex();
|
||||
} else if (type == "uri") {
|
||||
resolved = QUrl::toPercentEncoding(resolved.toUtf8());
|
||||
} else if (type == "uri-dec") {
|
||||
resolved = QUrl::fromPercentEncoding(resolved.toUtf8());
|
||||
} else if (type.startsWith("u")) {
|
||||
resolved = resolved.toUpper();
|
||||
} else if (type.startsWith("l")) {
|
||||
resolved = resolved.toLower();
|
||||
} else if (error) {
|
||||
*error = tr("Invalid conversion type: %1").arg(type);
|
||||
continue;
|
||||
}
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else if (error) {
|
||||
*error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
}
|
||||
} else if (error) {
|
||||
*error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
}
|
||||
} else if (placeholder.startsWith("t-replace-rx:")) {
|
||||
// Reset to the original capture to preserve case
|
||||
placeholder = match.captured(3);
|
||||
placeholder.replace("t-replace-rx:", "", Qt::CaseInsensitive);
|
||||
if (!placeholder.isEmpty()) {
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 5) {
|
||||
auto resolvedText = entry->resolveMultiplePlaceholders(parts[1]);
|
||||
auto resolvedSearch = entry->resolveMultiplePlaceholders(parts[2]);
|
||||
auto resolvedReplace = entry->resolveMultiplePlaceholders(parts[3]);
|
||||
// Replace $<num> with \<num>s to support Qt substitutions
|
||||
resolvedReplace.replace(QRegularExpression("\\$(\\d+)"), "\\\\1");
|
||||
auto resolved = resolvedText.replace(QRegularExpression(resolvedSearch), resolvedReplace);
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else if (error) {
|
||||
*error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
}
|
||||
} else if (error) {
|
||||
*error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
}
|
||||
} else if (placeholder == "beep" || placeholder.startsWith("vkey") || placeholder.startsWith("appactivate")
|
||||
|| placeholder.startsWith("c:")) {
|
||||
// Ignore these commands
|
||||
} else {
|
||||
// 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') {
|
||||
if (g_placeholderToKey.contains(placeholder)) {
|
||||
// Basic key placeholder, allow repeat
|
||||
if (repeat > maxRepetition) {
|
||||
error = tr("Too many repetitions detected, max is %1: %2").arg(maxRepetition).arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
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 = 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 = 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 == "pickchars") {
|
||||
// Ignore this if we are syntax checking
|
||||
if (syntaxOnly) {
|
||||
continue;
|
||||
}
|
||||
// Show pickchars dialog for entry's password
|
||||
auto password = entry->resolvePlaceholder(entry->password());
|
||||
if (!password.isEmpty()) {
|
||||
PickcharsDialog pickcharsDialog(password);
|
||||
if (pickcharsDialog.exec() == QDialog::Accepted && !pickcharsDialog.selectedChars().isEmpty()) {
|
||||
for (const auto& ch : pickcharsDialog.selectedChars()) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
if (pickcharsDialog.pressTab()) {
|
||||
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 {};
|
||||
if (pickcharsDialog.pressTab()) {
|
||||
// Remove extra tab
|
||||
actions.removeLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!character.isEmpty()) {
|
||||
// Type a single character with modifier
|
||||
actions << QSharedPointer<AutoTypeKey>::create(character[0], modifiers);
|
||||
} else if (placeholder.startsWith("t-conv:")) {
|
||||
// Reset to the original capture to preserve case
|
||||
placeholder = match.captured(3);
|
||||
placeholder.replace("t-conv:", "", Qt::CaseInsensitive);
|
||||
if (!placeholder.isEmpty()) {
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 4) {
|
||||
auto resolved = entry->resolveMultiplePlaceholders(parts[1]);
|
||||
auto type = parts[2].toLower();
|
||||
|
||||
if (type == "base64") {
|
||||
resolved = resolved.toUtf8().toBase64();
|
||||
} else if (type == "hex") {
|
||||
resolved = resolved.toUtf8().toHex();
|
||||
} else if (type == "uri") {
|
||||
resolved = QUrl::toPercentEncoding(resolved.toUtf8());
|
||||
} else if (type == "uri-dec") {
|
||||
resolved = QUrl::fromPercentEncoding(resolved.toUtf8());
|
||||
} else if (type.startsWith("u")) {
|
||||
resolved = resolved.toUpper();
|
||||
} else if (type.startsWith("l")) {
|
||||
resolved = resolved.toLower();
|
||||
} else {
|
||||
error = tr("Invalid conversion type: %1").arg(type);
|
||||
return {};
|
||||
}
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else if (placeholder.startsWith("t-replace-rx:")) {
|
||||
// Reset to the original capture to preserve case
|
||||
placeholder = match.captured(3);
|
||||
placeholder.replace("t-replace-rx:", "", Qt::CaseInsensitive);
|
||||
if (!placeholder.isEmpty()) {
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 5) {
|
||||
auto resolvedText = entry->resolveMultiplePlaceholders(parts[1]);
|
||||
auto resolvedSearch = entry->resolveMultiplePlaceholders(parts[2]);
|
||||
auto resolvedReplace = entry->resolveMultiplePlaceholders(parts[3]);
|
||||
// Replace $<num> with \\<num> to support Qt substitutions
|
||||
resolvedReplace.replace(QRegularExpression(R"(\$(\d+))"), R"(\\1)");
|
||||
|
||||
auto searchRegex = QRegularExpression(resolvedSearch);
|
||||
if (!searchRegex.isValid()) {
|
||||
error = tr("Invalid regular expression syntax %1\n%2")
|
||||
.arg(resolvedSearch, searchRegex.errorString());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto resolved = resolvedText.replace(searchRegex, resolvedReplace);
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else if (placeholder == "beep" || placeholder.startsWith("vkey") || placeholder.startsWith("appactivate")
|
||||
|| placeholder.startsWith("c:")) {
|
||||
// Ignore these commands
|
||||
} else {
|
||||
// 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 {
|
||||
// Invalid placeholder, issue error and stop processing
|
||||
error = tr("Invalid placeholder: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -684,7 +709,7 @@ bool AutoType::verifyAutoTypeSyntax(const QString& sequence, const Entry* entry,
|
||||
{
|
||||
error.clear();
|
||||
if (!sequence.isEmpty()) {
|
||||
parseActions(sequence, entry, &error);
|
||||
parseSequence(sequence, entry, error, true);
|
||||
}
|
||||
return error.isEmpty();
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ private:
|
||||
void resetAutoTypeState();
|
||||
|
||||
static QList<QSharedPointer<AutoTypeAction>>
|
||||
parseActions(const QString& entrySequence, const Entry* entry, QString* error = nullptr);
|
||||
parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly = false);
|
||||
|
||||
QMutex m_inAutoType;
|
||||
QMutex m_inGlobalAutoTypeDialog;
|
||||
|
@ -1,18 +1,18 @@
|
||||
set(autotype_mac_SOURCES AutoTypeMac.cpp)
|
||||
|
||||
add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES})
|
||||
set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
|
||||
target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
|
||||
add_library(keepassxc-autotype-cocoa MODULE ${autotype_mac_SOURCES})
|
||||
set_target_properties(keepassxc-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
|
||||
target_link_libraries(keepassxc-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
|
||||
|
||||
if(WITH_APP_BUNDLE)
|
||||
add_custom_command(TARGET keepassx-autotype-cocoa
|
||||
add_custom_command(TARGET keepassxc-autotype-cocoa
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so
|
||||
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins 2> /dev/null
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassxc-autotype-cocoa.so ${PLUGIN_INSTALL_DIR}/libkeepassxc-autotype-cocoa.so
|
||||
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassxc-autotype-cocoa.so -no-plugins 2> /dev/null
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
|
||||
COMMENT "Deploying autotype plugin")
|
||||
else()
|
||||
install(TARGETS keepassx-autotype-cocoa
|
||||
install(TARGETS keepassxc-autotype-cocoa
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
|
||||
endif()
|
||||
|
@ -1,4 +1,4 @@
|
||||
set(autotype_test_SOURCES AutoTypeTest.cpp)
|
||||
|
||||
add_library(keepassx-autotype-test MODULE ${autotype_test_SOURCES})
|
||||
target_link_libraries(keepassx-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
|
||||
add_library(keepassxc-autotype-test MODULE ${autotype_test_SOURCES})
|
||||
target_link_libraries(keepassxc-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
|
||||
|
@ -1,7 +1,7 @@
|
||||
set(autotype_win_SOURCES AutoTypeWindows.cpp)
|
||||
|
||||
add_library(keepassx-autotype-windows MODULE ${autotype_win_SOURCES})
|
||||
target_link_libraries(keepassx-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
|
||||
install(TARGETS keepassx-autotype-windows
|
||||
add_library(keepassxc-autotype-windows MODULE ${autotype_win_SOURCES})
|
||||
target_link_libraries(keepassxc-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
|
||||
install(TARGETS keepassxc-autotype-windows
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
|
||||
|
@ -2,8 +2,8 @@ include_directories(SYSTEM ${X11_X11_INCLUDE_PATH})
|
||||
|
||||
set(autotype_XCB_SOURCES AutoTypeXCB.cpp)
|
||||
|
||||
add_library(keepassx-autotype-xcb MODULE ${autotype_XCB_SOURCES})
|
||||
target_link_libraries(keepassx-autotype-xcb keepassx_core Qt5::Core Qt5::Widgets Qt5::X11Extras ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_XTest_LIB})
|
||||
install(TARGETS keepassx-autotype-xcb
|
||||
add_library(keepassxc-autotype-xcb MODULE ${autotype_XCB_SOURCES})
|
||||
target_link_libraries(keepassxc-autotype-xcb keepassx_core Qt5::Core Qt5::Widgets Qt5::X11Extras ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_XTest_LIB})
|
||||
install(TARGETS keepassxc-autotype-xcb
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
|
||||
|
@ -40,7 +40,7 @@ void TestAutoType::initTestCase()
|
||||
config()->set(Config::Security_AutoTypeAsk, false);
|
||||
AutoType::createTestInstance();
|
||||
|
||||
QPluginLoader loader(resources()->pluginPath("keepassx-autotype-test"));
|
||||
QPluginLoader loader(resources()->pluginPath("keepassxc-autotype-test"));
|
||||
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
||||
QVERIFY(loader.instance());
|
||||
|
||||
@ -312,7 +312,7 @@ void TestAutoType::testAutoTypeResults_data()
|
||||
QTest::newRow("T-CONV HEX") << QString("{T-CONV:/{USERNAME}/HEX/}") << QString("557365726e616d65");
|
||||
QTest::newRow("T-CONV URI ENCODE") << QString("{T-CONV:/{URL}/URI/}") << QString("https%3A%2F%2Fexample.com");
|
||||
QTest::newRow("T-CONV URI DECODE") << QString("{T-CONV:/{S:attr2}/URI-DEC/}") << QString("decode me");
|
||||
QTest::newRow("T-REPLACE-RX") << QString("{T-REPLACE-RX:/{USERNAME}/User/Pass/}") << QString("Passname");
|
||||
QTest::newRow("T-REPLACE-RX") << QString("{T-REPLACE-RX:/{USERNAME}/(User)/$1Pass/}") << QString("UserPassname");
|
||||
}
|
||||
|
||||
void TestAutoType::testAutoTypeSyntaxChecks()
|
||||
|
Loading…
Reference in New Issue
Block a user