mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merge pull request #5864 from keepassxreboot/feature/autotype-upgrade-part2
This commit is contained in:
commit
c0d673b46f
@ -34,3 +34,5 @@ include::topics/AutoType.adoc[tags=*]
|
|||||||
include::topics/KeeShare.adoc[tags=*]
|
include::topics/KeeShare.adoc[tags=*]
|
||||||
|
|
||||||
include::topics/SSHAgent.adoc[tags=*]
|
include::topics/SSHAgent.adoc[tags=*]
|
||||||
|
|
||||||
|
include::topics/Reference.adoc[tags=*]
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 82 KiB |
Binary file not shown.
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 27 KiB |
BIN
docs/images/autotype_selection_dialog_search.png
Normal file
BIN
docs/images/autotype_selection_dialog_search.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
docs/images/autotype_selection_dialog_type_menu.png
Normal file
BIN
docs/images/autotype_selection_dialog_type_menu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
@ -31,26 +31,9 @@ To configure Auto-Type sequences for your entries, perform the following steps:
|
|||||||
.Auto-Type entry sequences
|
.Auto-Type entry sequences
|
||||||
image::autotype_entry_sequences.png[]
|
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. A complete list of supported actions and placeholders can be found at https://keepass.info/help/base/autotype.html#autoseq[KeePass Auto-Type Action Codes] and https://keepass.info/help/base/placeholders.html[KeePass Placeholders]. 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=70%]
|
[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
|
|
||||||
|{DELAY=X} |Set key press delay to X milliseconds
|
|
||||||
|{DELAY X} |Delay typing start by X milliseconds
|
|
||||||
|{CLEARFIELD} |Clear the input field before typing
|
|
||||||
|{TOTP} |Insert calculated TOTP value (if configured)
|
|
||||||
|{<ACTION> X} |Repeat <ACTION> X times (e.g., {SPACE 5} inserts five spaces)
|
|
||||||
|===
|
|
||||||
+
|
|
||||||
[grid=rows, frame=none, width=70%]
|
|
||||||
|===
|
|===
|
||||||
|Placeholder |Description
|
|Placeholder |Description
|
||||||
|
|
||||||
@ -60,21 +43,44 @@ image::autotype_entry_sequences.png[]
|
|||||||
|{URL} |URL
|
|{URL} |URL
|
||||||
|{NOTES} |Notes
|
|{NOTES} |Notes
|
||||||
|{TOTP} |Current TOTP value (if configured)
|
|{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
|
|{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.
|
|===
|
||||||
|
+
|
||||||
|
[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
|
||||||
|
|{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
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== Performing Global Auto-Type
|
=== 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.
|
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.
|
||||||
|
|
||||||
Pressing the global Auto-Type hotkey cause KeePassXC to search the database for entries that match the window title. Multiple matches may be returned and will cause the sequence selection dialog to appear. Click on a sequence line will immediately execute the Auto-Type action. A search box is also available in case numerous matches are returned.
|
When you press the global Auto-Type hotkey, KeePassXC searches all unlocked databases for entries that match the focused window title. The Auto-Type selection dialog will appear in the following circumstances: there are no matches found, there are multiple matches found, or the setting "Always ask before performing Auto-Type" is enabled.
|
||||||
|
|
||||||
.Auto-Type sequence selection
|
.Auto-Type sequence selection
|
||||||
image::autotype_selection_dialog.png[,70%]
|
image::autotype_selection_dialog.png[,70%]
|
||||||
|
|
||||||
TIP: The _Sequence_ column will only appear when there are different sequences defined by one or more entries displayed in the selection dialog.
|
Perform the selected Auto-Type sequence by double clicking the desired row or pressing _Enter_. Press the up and down arrows to navigate the list. Sequences can be filtered through the text edit field.
|
||||||
|
|
||||||
|
.Auto-Type search database
|
||||||
|
image::autotype_selection_dialog_search.png[,70%]
|
||||||
|
|
||||||
|
Search the unlocked databases by activating Search Database radio button. Use the text edit field to issue search queries using the same syntax as database searching.
|
||||||
|
|
||||||
|
.Additional Auto-Type choices
|
||||||
|
image::autotype_selection_dialog_type_menu.png[,70%]
|
||||||
|
|
||||||
|
The option to type just the username, password, or current TOTP value is available by right clicking the desired row or expanding the Type Sequence button options.
|
||||||
|
|
||||||
=== Performing Entry-Level Auto-Type
|
=== Performing Entry-Level Auto-Type
|
||||||
You can quickly activate the default Auto-Type sequence for a particular entry using Entry-Level Auto-Type. For this operation, the KeePassXC window will be minimized and the Auto-Type sequence occurs in the previously selected window. You can perform Entry-Level Auto-Type from the toolbar icon *(A)*, entry context menu *(B)*, or by pressing `Ctrl+Shift+V`.
|
You can quickly activate the default Auto-Type sequence for a particular entry using Entry-Level Auto-Type. For this operation, the KeePassXC window will be minimized and the Auto-Type sequence occurs in the previously selected window. You can perform Entry-Level Auto-Type from the toolbar icon *(A)*, entry context menu *(B)*, or by pressing `Ctrl+Shift+V`.
|
||||||
|
@ -120,18 +120,7 @@ image::clone_entry_dialog.png[,70%]
|
|||||||
.References in a cloned entry
|
.References in a cloned entry
|
||||||
image::clone_entry_references.png[]
|
image::clone_entry_references.png[]
|
||||||
|
|
||||||
4. You can create your own references using the following syntax:
|
4. You can create your own references using the xref:UserGuide.adoc#_entry_cross_reference[Entry Reference 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
|
|
||||||
|
|
||||||
== Searching the Database
|
== 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.
|
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
|
.Database credentials
|
||||||
image::database_security_credentials.png[]
|
image::database_security_credentials.png[]
|
||||||
|
+
|
||||||
WARNING: Consider creating a backup of your YubiKey. Please refer to <<Creating a YubiKey backup>>
|
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.*
|
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.*
|
||||||
|
@ -60,3 +60,5 @@ To install the KeePassXC app on macOS, double click on the downloaded DMG file a
|
|||||||
image::macos_install.png[,80%]
|
image::macos_install.png[,80%]
|
||||||
|
|
||||||
// end::content[]
|
// end::content[]
|
||||||
|
// tag::advanced[]
|
||||||
|
// end::advanced[]
|
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[]
|
@ -44,3 +44,5 @@ KeePassXC has numerous features for novice and power users alike. This guide wil
|
|||||||
** FreeDesktop.org Secret Service (replace Gnome keyring, etc.)
|
** FreeDesktop.org Secret Service (replace Gnome keyring, etc.)
|
||||||
** Additional encryption choices: Twofish and ChaCha20
|
** Additional encryption choices: Twofish and ChaCha20
|
||||||
// end::content[]
|
// end::content[]
|
||||||
|
// tag::advanced[]
|
||||||
|
// end::advanced[]
|
@ -33,7 +33,6 @@ endif(NOT ZXCVBN_LIBRARIES)
|
|||||||
set(keepassx_SOURCES
|
set(keepassx_SOURCES
|
||||||
core/Alloc.cpp
|
core/Alloc.cpp
|
||||||
core/AutoTypeAssociations.cpp
|
core/AutoTypeAssociations.cpp
|
||||||
core/AutoTypeMatch.cpp
|
|
||||||
core/Base32.cpp
|
core/Base32.cpp
|
||||||
core/Bootstrap.cpp
|
core/Bootstrap.cpp
|
||||||
core/Clock.cpp
|
core/Clock.cpp
|
||||||
@ -139,8 +138,6 @@ set(keepassx_SOURCES
|
|||||||
gui/csvImport/CsvImportWizard.cpp
|
gui/csvImport/CsvImportWizard.cpp
|
||||||
gui/csvImport/CsvParserModel.cpp
|
gui/csvImport/CsvParserModel.cpp
|
||||||
gui/entry/AutoTypeAssociationsModel.cpp
|
gui/entry/AutoTypeAssociationsModel.cpp
|
||||||
gui/entry/AutoTypeMatchModel.cpp
|
|
||||||
gui/entry/AutoTypeMatchView.cpp
|
|
||||||
gui/entry/EditEntryWidget.cpp
|
gui/entry/EditEntryWidget.cpp
|
||||||
gui/entry/EntryAttachmentsModel.cpp
|
gui/entry/EntryAttachmentsModel.cpp
|
||||||
gui/entry/EntryAttachmentsWidget.cpp
|
gui/entry/EntryAttachmentsWidget.cpp
|
||||||
@ -273,11 +270,11 @@ set(autotype_SOURCES
|
|||||||
core/Tools.cpp
|
core/Tools.cpp
|
||||||
autotype/AutoType.cpp
|
autotype/AutoType.cpp
|
||||||
autotype/AutoTypeAction.cpp
|
autotype/AutoTypeAction.cpp
|
||||||
autotype/AutoTypeFilterLineEdit.cpp
|
autotype/AutoTypeMatchModel.cpp
|
||||||
|
autotype/AutoTypeMatchView.cpp
|
||||||
autotype/AutoTypeSelectDialog.cpp
|
autotype/AutoTypeSelectDialog.cpp
|
||||||
autotype/AutoTypeSelectView.cpp
|
autotype/PickcharsDialog.cpp
|
||||||
autotype/ShortcutWidget.cpp
|
autotype/ShortcutWidget.cpp
|
||||||
autotype/WildcardMatcher.cpp
|
|
||||||
autotype/WindowSelectComboBox.cpp)
|
autotype/WindowSelectComboBox.cpp)
|
||||||
|
|
||||||
if(MINGW)
|
if(MINGW)
|
||||||
|
@ -20,30 +20,106 @@
|
|||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QPluginLoader>
|
#include <QPluginLoader>
|
||||||
#include <QRegularExpression>
|
#include <QWindow>
|
||||||
|
|
||||||
#include "config-keepassx.h"
|
#include "config-keepassx.h"
|
||||||
|
|
||||||
#include "autotype/AutoTypePlatformPlugin.h"
|
#include "autotype/AutoTypePlatformPlugin.h"
|
||||||
#include "autotype/AutoTypeSelectDialog.h"
|
#include "autotype/AutoTypeSelectDialog.h"
|
||||||
#include "autotype/WildcardMatcher.h"
|
#include "autotype/PickcharsDialog.h"
|
||||||
#include "core/AutoTypeMatch.h"
|
|
||||||
#include "core/Config.h"
|
#include "core/Config.h"
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
#include "core/Entry.h"
|
#include "core/Entry.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "core/ListDeleter.h"
|
|
||||||
#include "core/Resources.h"
|
#include "core/Resources.h"
|
||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
#include "gui/MainWindow.h"
|
#include "gui/MainWindow.h"
|
||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
#include "gui/osutils/OSUtils.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::m_instance = nullptr;
|
||||||
|
|
||||||
AutoType::AutoType(QObject* parent, bool test)
|
AutoType::AutoType(QObject* parent, bool test)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_autoTypeDelay(0)
|
|
||||||
, m_pluginLoader(new QPluginLoader(this))
|
, m_pluginLoader(new QPluginLoader(this))
|
||||||
, m_plugin(nullptr)
|
, m_plugin(nullptr)
|
||||||
, m_executor(nullptr)
|
, m_executor(nullptr)
|
||||||
@ -53,7 +129,7 @@ AutoType::AutoType(QObject* parent, bool test)
|
|||||||
// prevent crash when the plugin has unresolved symbols
|
// prevent crash when the plugin has unresolved symbols
|
||||||
m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
||||||
|
|
||||||
QString pluginName = "keepassx-autotype-";
|
QString pluginName = "keepassxc-autotype-";
|
||||||
if (!test) {
|
if (!test) {
|
||||||
pluginName += QApplication::platformName();
|
pluginName += QApplication::platformName();
|
||||||
} else {
|
} else {
|
||||||
@ -91,7 +167,7 @@ void AutoType::loadPlugin(const QString& pluginPath)
|
|||||||
if (m_plugin) {
|
if (m_plugin) {
|
||||||
if (m_plugin->isAvailable()) {
|
if (m_plugin->isAvailable()) {
|
||||||
m_executor = m_plugin->createExecutor();
|
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") {
|
if (name == "autotype") {
|
||||||
startGlobalAutoType();
|
startGlobalAutoType();
|
||||||
}
|
}
|
||||||
@ -171,26 +247,24 @@ void AutoType::unregisterGlobalShortcut()
|
|||||||
*/
|
*/
|
||||||
void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window)
|
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()) {
|
if (!m_inAutoType.tryLock()) {
|
||||||
return;
|
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 (hideWindow) {
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
// Check for accessibility permission
|
// Check for accessibility permission
|
||||||
@ -210,16 +284,19 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Tools::wait(qMax(100, config()->get(Config::AutoTypeStartDelay).toInt()));
|
// Restore window state in case app stole focus
|
||||||
|
restoreWindowState();
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
m_plugin->raiseWindow(m_windowForGlobal);
|
||||||
|
|
||||||
// Used only for selected entry auto-type
|
// Used only for selected entry auto-type
|
||||||
if (!window) {
|
if (!window) {
|
||||||
window = m_plugin->activeWindow();
|
window = m_plugin->activeWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
Tools::wait(qMax(100, config()->get(Config::AutoTypeStartDelay).toInt()));
|
||||||
|
|
||||||
for (AutoTypeAction* action : asConst(actions)) {
|
for (const auto& action : asConst(actions)) {
|
||||||
if (m_plugin->activeWindow() != window) {
|
if (m_plugin->activeWindow() != window) {
|
||||||
qWarning("Active window changed, interrupting auto-type.");
|
qWarning("Active window changed, interrupting auto-type.");
|
||||||
emit autotypeRejected();
|
emit autotypeRejected();
|
||||||
@ -227,16 +304,14 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
action->accept(m_executor);
|
action->exec(m_executor);
|
||||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_windowForGlobal = 0;
|
m_windowForGlobal = 0;
|
||||||
m_windowTitleForGlobal.clear();
|
m_windowTitleForGlobal.clear();
|
||||||
|
|
||||||
// emit signal only if autotype performed correctly
|
|
||||||
emit autotypePerformed();
|
emit autotypePerformed();
|
||||||
|
|
||||||
m_inAutoType.unlock();
|
m_inAutoType.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,12 +325,10 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> sequences = autoTypeSequences(entry);
|
auto sequences = entry->autoTypeSequences();
|
||||||
if (sequences.isEmpty()) {
|
if (!sequences.isEmpty()) {
|
||||||
return;
|
executeAutoTypeActions(entry, hideWindow, sequences.first());
|
||||||
}
|
}
|
||||||
|
|
||||||
executeAutoTypeActions(entry, hideWindow, sequences.first());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -273,6 +346,11 @@ void AutoType::performAutoTypeWithSequence(const Entry* entry, const QString& se
|
|||||||
|
|
||||||
void AutoType::startGlobalAutoType()
|
void AutoType::startGlobalAutoType()
|
||||||
{
|
{
|
||||||
|
// Never Auto-Type into KeePassXC itself
|
||||||
|
if (qApp->focusWindow()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_windowForGlobal = m_plugin->activeWindow();
|
m_windowForGlobal = m_plugin->activeWindow();
|
||||||
m_windowTitleForGlobal = m_plugin->activeWindowTitle();
|
m_windowTitleForGlobal = m_plugin->activeWindowTitle();
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
@ -331,58 +409,57 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
|||||||
|
|
||||||
for (const auto& db : dbList) {
|
for (const auto& db : dbList) {
|
||||||
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
|
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
|
||||||
for (Entry* entry : dbEntries) {
|
for (auto entry : dbEntries) {
|
||||||
|
auto group = entry->group();
|
||||||
|
if (!group || !group->resolveAutoTypeEnabled() || !entry->autoTypeEnabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (hideExpired && entry->isExpired()) {
|
if (hideExpired && entry->isExpired()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const QSet<QString> sequences = autoTypeSequences(entry, m_windowTitleForGlobal).toSet();
|
auto sequences = entry->autoTypeSequences(m_windowTitleForGlobal).toSet();
|
||||||
for (const QString& sequence : sequences) {
|
for (const auto& sequence : sequences) {
|
||||||
if (!sequence.isEmpty()) {
|
matchList << AutoTypeMatch(entry, sequence);
|
||||||
matchList << AutoTypeMatch(entry, sequence);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchList.isEmpty()) {
|
// Show the selection dialog if we always ask, have multiple matches, or no matches
|
||||||
if (qobject_cast<QApplication*>(QCoreApplication::instance())) {
|
if (getMainWindow()
|
||||||
auto* msgBox = new QMessageBox();
|
&& (config()->get(Config::Security_AutoTypeAsk).toBool() || matchList.size() > 1 || matchList.isEmpty())) {
|
||||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
// Close any open modal windows that would interfere with the process
|
||||||
msgBox->setWindowTitle(tr("Auto-Type - KeePassXC"));
|
getMainWindow()->closeModalWindow();
|
||||||
msgBox->setText(tr("Couldn't find an entry that matches the window title:")
|
|
||||||
.append("\n\n")
|
|
||||||
.append(m_windowTitleForGlobal));
|
|
||||||
msgBox->setIcon(QMessageBox::Information);
|
|
||||||
msgBox->setStandardButtons(QMessageBox::Ok);
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
m_plugin->raiseOwnWindow();
|
|
||||||
Tools::wait(200);
|
|
||||||
#endif
|
|
||||||
msgBox->exec();
|
|
||||||
restoreWindowState();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_inGlobalAutoTypeDialog.unlock();
|
|
||||||
emit autotypeRejected();
|
|
||||||
} else if ((matchList.size() == 1) && !config()->get(Config::Security_AutoTypeAsk).toBool()) {
|
|
||||||
executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence, m_windowForGlobal);
|
|
||||||
m_inGlobalAutoTypeDialog.unlock();
|
|
||||||
} else {
|
|
||||||
auto* selectDialog = new AutoTypeSelectDialog();
|
auto* selectDialog = new AutoTypeSelectDialog();
|
||||||
|
selectDialog->setMatches(matchList, dbList);
|
||||||
|
|
||||||
// connect slots, both of which must unlock the m_inGlobalAutoTypeDialog mutex
|
connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject);
|
||||||
connect(selectDialog, SIGNAL(matchActivated(AutoTypeMatch)), SLOT(performAutoTypeFromGlobal(AutoTypeMatch)));
|
connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](const AutoTypeMatch& match) {
|
||||||
connect(selectDialog, SIGNAL(rejected()), SLOT(autoTypeRejectedFromGlobal()));
|
executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal);
|
||||||
|
resetAutoTypeState();
|
||||||
|
});
|
||||||
|
connect(selectDialog, &QDialog::rejected, this, [this] {
|
||||||
|
restoreWindowState();
|
||||||
|
resetAutoTypeState();
|
||||||
|
emit autotypeRejected();
|
||||||
|
});
|
||||||
|
|
||||||
selectDialog->setMatchList(matchList);
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
m_plugin->raiseOwnWindow();
|
m_plugin->raiseOwnWindow();
|
||||||
Tools::wait(200);
|
Tools::wait(50);
|
||||||
#endif
|
#endif
|
||||||
selectDialog->show();
|
selectDialog->show();
|
||||||
selectDialog->raise();
|
selectDialog->raise();
|
||||||
// necessary when the main window is minimized
|
|
||||||
selectDialog->activateWindow();
|
selectDialog->activateWindow();
|
||||||
|
} else if (!matchList.isEmpty()) {
|
||||||
|
// Only one match and not asking, do it!
|
||||||
|
executeAutoTypeActions(matchList.first().first, nullptr, matchList.first().second, m_windowForGlobal);
|
||||||
|
resetAutoTypeState();
|
||||||
|
} else {
|
||||||
|
// We should never get here
|
||||||
|
resetAutoTypeState();
|
||||||
|
emit autotypeRejected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,437 +476,240 @@ void AutoType::restoreWindowState()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match)
|
void AutoType::resetAutoTypeState()
|
||||||
{
|
{
|
||||||
restoreWindowState();
|
|
||||||
|
|
||||||
m_plugin->raiseWindow(m_windowForGlobal);
|
|
||||||
executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowForGlobal);
|
|
||||||
|
|
||||||
// make sure the mutex is definitely locked before we unlock it
|
|
||||||
Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock());
|
|
||||||
m_inGlobalAutoTypeDialog.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoType::autoTypeRejectedFromGlobal()
|
|
||||||
{
|
|
||||||
// this slot can be called twice when the selection dialog is deleted,
|
|
||||||
// so make sure the mutex is locked before we try unlocking it
|
|
||||||
Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock());
|
|
||||||
m_inGlobalAutoTypeDialog.unlock();
|
|
||||||
m_windowForGlobal = 0;
|
m_windowForGlobal = 0;
|
||||||
m_windowTitleForGlobal.clear();
|
m_windowTitleForGlobal.clear();
|
||||||
|
Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock());
|
||||||
restoreWindowState();
|
m_inGlobalAutoTypeDialog.unlock();
|
||||||
emit autotypeRejected();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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::parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly)
|
||||||
{
|
{
|
||||||
QString tmpl;
|
const int maxTypeDelay = 100;
|
||||||
bool inTmpl = false;
|
const int maxWaitDelay = 10000;
|
||||||
m_autoTypeDelay = qMax(config()->get(Config::AutoTypeDelay).toInt(), 0);
|
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("{{}", "{LEFTBRACE}");
|
||||||
sequence.replace("{}}", "{RIGHTBRACE}");
|
sequence.replace("{}}", "{RIGHTBRACE}");
|
||||||
|
|
||||||
for (const QChar& ch : sequence) {
|
// Quick test for bracket syntax
|
||||||
if (inTmpl) {
|
if (sequence.count("{") != sequence.count("}")) {
|
||||||
if (ch == '{') {
|
error = tr("Bracket imbalance detected, found extra { or }");
|
||||||
qWarning("Syntax error in Auto-Type sequence.");
|
return {};
|
||||||
return false;
|
}
|
||||||
} else if (ch == '}') {
|
|
||||||
QList<AutoTypeAction*> autoType = createActionFromTemplate(tmpl, entry);
|
// Group 1 = modifier key (opt)
|
||||||
if (!autoType.isEmpty()) {
|
// Group 2 = full placeholder
|
||||||
actions.append(autoType);
|
// 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 (!character.isEmpty()) {
|
||||||
|
// Type a single character with modifier
|
||||||
|
actions << QSharedPointer<AutoTypeKey>::create(character[0], modifiers);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pickcharsDialog.pressTab()) {
|
||||||
|
// Remove extra tab
|
||||||
|
actions.removeLast();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inTmpl = false;
|
|
||||||
tmpl.clear();
|
|
||||||
} else {
|
|
||||||
tmpl += ch;
|
|
||||||
}
|
}
|
||||||
} else if (ch == '{') {
|
} else if (placeholder.startsWith("t-conv:")) {
|
||||||
inTmpl = true;
|
// Reset to the original capture to preserve case
|
||||||
} else if (ch == '}') {
|
placeholder = match.captured(3);
|
||||||
qWarning("Syntax error in Auto-Type sequence.");
|
placeholder.replace("t-conv:", "", Qt::CaseInsensitive);
|
||||||
return false;
|
if (!placeholder.isEmpty()) {
|
||||||
} else {
|
auto sep = placeholder[0];
|
||||||
actions.append(new AutoTypeChar(ch));
|
auto parts = placeholder.split(sep);
|
||||||
}
|
if (parts.size() >= 4) {
|
||||||
}
|
auto resolved = entry->resolveMultiplePlaceholders(parts[1]);
|
||||||
if (m_autoTypeDelay > 0) {
|
auto type = parts[2].toLower();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (type == "base64") {
|
||||||
* Convert an autotype Template/command to its AutoTypeAction that will be executed by the plugin executor
|
resolved = resolved.toUtf8().toBase64();
|
||||||
*/
|
} else if (type == "hex") {
|
||||||
QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, const Entry* entry)
|
resolved = resolved.toUtf8().toHex();
|
||||||
{
|
} else if (type == "uri") {
|
||||||
QString tmplName = tmpl;
|
resolved = QUrl::toPercentEncoding(resolved.toUtf8());
|
||||||
int num = -1;
|
} else if (type == "uri-dec") {
|
||||||
QList<AutoTypeAction*> list;
|
resolved = QUrl::fromPercentEncoding(resolved.toUtf8());
|
||||||
|
} else if (type.startsWith("u")) {
|
||||||
QRegExp delayRegEx("delay=(\\d+)", Qt::CaseInsensitive, QRegExp::RegExp2);
|
resolved = resolved.toUpper();
|
||||||
if (delayRegEx.exactMatch(tmplName)) {
|
} else if (type.startsWith("l")) {
|
||||||
num = delayRegEx.cap(1).toInt();
|
resolved = resolved.toLower();
|
||||||
m_autoTypeDelay = std::max(0, std::min(num, 10000));
|
} else {
|
||||||
return list;
|
error = tr("Invalid conversion type: %1").arg(type);
|
||||||
}
|
return {};
|
||||||
|
}
|
||||||
QRegExp repeatRegEx("(.+) (\\d+)", Qt::CaseInsensitive, QRegExp::RegExp2);
|
for (const QChar& ch : resolved) {
|
||||||
if (repeatRegEx.exactMatch(tmplName)) {
|
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrive the autotype sequences matches for a given windowTitle
|
|
||||||
* This returns a list with priority ordering. If you don't want duplicates call .toSet() on it.
|
|
||||||
*/
|
|
||||||
QList<QString> AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle)
|
|
||||||
{
|
|
||||||
QList<QString> sequenceList;
|
|
||||||
const Group* group = entry->group();
|
|
||||||
|
|
||||||
if (!group || !entry->autoTypeEnabled()) {
|
|
||||||
return sequenceList;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (group->autoTypeEnabled() == Group::Disable) {
|
|
||||||
return sequenceList;
|
|
||||||
} else if (group->autoTypeEnabled() == Group::Enable) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
group = group->parentGroup();
|
|
||||||
|
|
||||||
} while (group);
|
|
||||||
|
|
||||||
if (!windowTitle.isEmpty()) {
|
|
||||||
const QList<AutoTypeAssociations::Association> assocList = entry->autoTypeAssociations()->getAll();
|
|
||||||
for (const AutoTypeAssociations::Association& assoc : assocList) {
|
|
||||||
const QString window = entry->resolveMultiplePlaceholders(assoc.window);
|
|
||||||
if (windowMatches(windowTitle, window)) {
|
|
||||||
if (!assoc.sequence.isEmpty()) {
|
|
||||||
sequenceList.append(assoc.sequence);
|
|
||||||
} else {
|
} else {
|
||||||
sequenceList.append(entry->effectiveAutoTypeSequence());
|
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 {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config()->get(Config::AutoTypeEntryTitleMatch).toBool()
|
|
||||||
&& windowMatchesTitle(windowTitle, entry->resolvePlaceholder(entry->title()))) {
|
|
||||||
sequenceList.append(entry->effectiveAutoTypeSequence());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config()->get(Config::AutoTypeEntryURLMatch).toBool()
|
|
||||||
&& windowMatchesUrl(windowTitle, entry->resolvePlaceholder(entry->url()))) {
|
|
||||||
sequenceList.append(entry->effectiveAutoTypeSequence());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sequenceList.isEmpty()) {
|
|
||||||
return sequenceList;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sequenceList.append(entry->effectiveAutoTypeSequence());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sequenceList;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a window title matches a pattern
|
* Verify if the syntax of an autotype sequence is correct and doesn't have invalid parameters
|
||||||
*/
|
*/
|
||||||
bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPattern)
|
bool AutoType::verifyAutoTypeSyntax(const QString& sequence, const Entry* entry, QString& error)
|
||||||
{
|
{
|
||||||
if (windowPattern.startsWith("//") && windowPattern.endsWith("//") && windowPattern.size() >= 4) {
|
error.clear();
|
||||||
QRegExp regExp(windowPattern.mid(2, windowPattern.size() - 4), Qt::CaseInsensitive, QRegExp::RegExp2);
|
if (!sequence.isEmpty()) {
|
||||||
return (regExp.indexIn(windowTitle) != -1);
|
parseSequence(sequence, entry, error, true);
|
||||||
}
|
}
|
||||||
return WildcardMatcher(windowTitle).match(windowPattern);
|
return error.isEmpty();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a window title matches an entry Title
|
|
||||||
* The entry title should be Spr-compiled by the caller
|
|
||||||
*/
|
|
||||||
bool AutoType::windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle)
|
|
||||||
{
|
|
||||||
return !resolvedTitle.isEmpty() && windowTitle.contains(resolvedTitle, Qt::CaseInsensitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a window title matches an entry URL
|
|
||||||
* The entry URL should be Spr-compiled by the caller
|
|
||||||
*/
|
|
||||||
bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl)
|
|
||||||
{
|
|
||||||
if (!resolvedUrl.isEmpty() && windowTitle.contains(resolvedUrl, Qt::CaseInsensitive)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl url(resolvedUrl);
|
|
||||||
if (url.isValid() && !url.host().isEmpty()) {
|
|
||||||
return windowTitle.contains(url.host(), Qt::CaseInsensitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "core/AutoTypeMatch.h"
|
#include "autotype/AutoTypeMatch.h"
|
||||||
|
|
||||||
class AutoTypeAction;
|
class AutoTypeAction;
|
||||||
class AutoTypeExecutor;
|
class AutoTypeExecutor;
|
||||||
@ -41,14 +41,11 @@ 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();
|
||||||
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 performAutoType(const Entry* entry, QWidget* hideWindow = nullptr);
|
||||||
void performAutoTypeWithSequence(const Entry* entry, const QString& sequence, 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()
|
inline bool isAvailable()
|
||||||
{
|
{
|
||||||
return m_plugin;
|
return m_plugin;
|
||||||
@ -68,8 +65,6 @@ signals:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void startGlobalAutoType();
|
void startGlobalAutoType();
|
||||||
void performAutoTypeFromGlobal(AutoTypeMatch match);
|
|
||||||
void autoTypeRejectedFromGlobal();
|
|
||||||
void unloadPlugin();
|
void unloadPlugin();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -87,17 +82,14 @@ private:
|
|||||||
QWidget* hideWindow = nullptr,
|
QWidget* hideWindow = nullptr,
|
||||||
const QString& customSequence = QString(),
|
const QString& customSequence = QString(),
|
||||||
WId window = 0);
|
WId window = 0);
|
||||||
bool parseActions(const QString& sequence, const Entry* entry, QList<AutoTypeAction*>& actions);
|
|
||||||
QList<AutoTypeAction*> createActionFromTemplate(const QString& tmpl, const Entry* entry);
|
|
||||||
QList<QString> autoTypeSequences(const Entry* entry, const QString& windowTitle = QString());
|
|
||||||
bool windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle);
|
|
||||||
bool windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl);
|
|
||||||
bool windowMatches(const QString& windowTitle, const QString& windowPattern);
|
|
||||||
void restoreWindowState();
|
void restoreWindowState();
|
||||||
|
void resetAutoTypeState();
|
||||||
|
|
||||||
|
static QList<QSharedPointer<AutoTypeAction>>
|
||||||
|
parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly = false);
|
||||||
|
|
||||||
QMutex m_inAutoType;
|
QMutex m_inAutoType;
|
||||||
QMutex m_inGlobalAutoTypeDialog;
|
QMutex m_inGlobalAutoTypeDialog;
|
||||||
int m_autoTypeDelay;
|
|
||||||
QPluginLoader* m_pluginLoader;
|
QPluginLoader* m_pluginLoader;
|
||||||
AutoTypePlatformInterface* m_plugin;
|
AutoTypePlatformInterface* m_plugin;
|
||||||
AutoTypeExecutor* m_executor;
|
AutoTypeExecutor* m_executor;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -19,77 +20,41 @@
|
|||||||
|
|
||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
|
|
||||||
AutoTypeChar::AutoTypeChar(const QChar& character)
|
AutoTypeKey::AutoTypeKey(Qt::Key key, Qt::KeyboardModifiers modifiers)
|
||||||
: character(character)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTypeAction* AutoTypeChar::clone()
|
|
||||||
{
|
|
||||||
return new AutoTypeChar(character);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeChar::accept(AutoTypeExecutor* executor)
|
|
||||||
{
|
|
||||||
executor->execChar(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTypeKey::AutoTypeKey(Qt::Key key)
|
|
||||||
: key(key)
|
: 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)
|
: 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)
|
void AutoTypeClearField::exec(AutoTypeExecutor* executor) const
|
||||||
{
|
|
||||||
executor->execDelay(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTypeClearField::AutoTypeClearField()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTypeAction* AutoTypeClearField::clone()
|
|
||||||
{
|
|
||||||
return new AutoTypeClearField();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeClearField::accept(AutoTypeExecutor* executor)
|
|
||||||
{
|
{
|
||||||
executor->execClearField(this);
|
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>
|
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -19,69 +20,55 @@
|
|||||||
#define KEEPASSX_AUTOTYPEACTION_H
|
#define KEEPASSX_AUTOTYPEACTION_H
|
||||||
|
|
||||||
#include <QChar>
|
#include <QChar>
|
||||||
#include <QObject>
|
|
||||||
#include <Qt>
|
|
||||||
|
|
||||||
#include "core/Global.h"
|
#include "core/Global.h"
|
||||||
|
|
||||||
class AutoTypeExecutor;
|
class AutoTypeExecutor;
|
||||||
|
|
||||||
class KEEPASSX_EXPORT AutoTypeAction
|
class KEEPASSXC_EXPORT AutoTypeAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual AutoTypeAction* clone() = 0;
|
AutoTypeAction() = default;
|
||||||
virtual void accept(AutoTypeExecutor* executor) = 0;
|
virtual void exec(AutoTypeExecutor* executor) const = 0;
|
||||||
virtual ~AutoTypeAction();
|
virtual ~AutoTypeAction() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
class KEEPASSX_EXPORT AutoTypeChar : public AutoTypeAction
|
class KEEPASSXC_EXPORT AutoTypeKey : public AutoTypeAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit AutoTypeChar(const QChar& character);
|
explicit AutoTypeKey(const QChar& character, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
|
||||||
AutoTypeAction* clone() override;
|
explicit AutoTypeKey(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
|
||||||
void accept(AutoTypeExecutor* executor) override;
|
void exec(AutoTypeExecutor* executor) const override;
|
||||||
|
|
||||||
const QChar character;
|
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:
|
public:
|
||||||
explicit AutoTypeKey(Qt::Key key);
|
explicit AutoTypeDelay(int delayMs, bool setExecDelay = false);
|
||||||
AutoTypeAction* clone() override;
|
void exec(AutoTypeExecutor* executor) const 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;
|
|
||||||
|
|
||||||
const int delayMs;
|
const int delayMs;
|
||||||
|
const bool setExecDelay;
|
||||||
};
|
};
|
||||||
|
|
||||||
class KEEPASSX_EXPORT AutoTypeClearField : public AutoTypeAction
|
class KEEPASSXC_EXPORT AutoTypeClearField : public AutoTypeAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AutoTypeClearField();
|
void exec(AutoTypeExecutor* executor) const override;
|
||||||
AutoTypeAction* clone() override;
|
|
||||||
void accept(AutoTypeExecutor* executor) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class KEEPASSX_EXPORT AutoTypeExecutor
|
class KEEPASSXC_EXPORT AutoTypeExecutor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~AutoTypeExecutor()
|
virtual ~AutoTypeExecutor() = default;
|
||||||
{
|
virtual void execType(const AutoTypeKey* action) = 0;
|
||||||
}
|
virtual void execClearField(const AutoTypeClearField* action) = 0;
|
||||||
virtual void execChar(AutoTypeChar* action) = 0;
|
|
||||||
virtual void execKey(AutoTypeKey* action) = 0;
|
int execDelayMs = 25;
|
||||||
virtual void execDelay(AutoTypeDelay* action);
|
|
||||||
virtual void execClearField(AutoTypeClearField* action);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_AUTOTYPEACTION_H
|
#endif // KEEPASSX_AUTOTYPEACTION_H
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
|
||||||
* version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "AutoTypeFilterLineEdit.h"
|
|
||||||
#include <QKeyEvent>
|
|
||||||
|
|
||||||
void AutoTypeFilterLineEdit::keyPressEvent(QKeyEvent* event)
|
|
||||||
{
|
|
||||||
if (event->key() == Qt::Key_Up) {
|
|
||||||
emit keyUpPressed();
|
|
||||||
} else if (event->key() == Qt::Key_Down) {
|
|
||||||
emit keyDownPressed();
|
|
||||||
} else {
|
|
||||||
QLineEdit::keyPressEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeFilterLineEdit::keyReleaseEvent(QKeyEvent* event)
|
|
||||||
{
|
|
||||||
if (event->key() == Qt::Key_Escape) {
|
|
||||||
emit escapeReleased();
|
|
||||||
} else {
|
|
||||||
QLineEdit::keyReleaseEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
|
||||||
* version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef KEEPASSX_AUTOTYPEFILTERLINEEDIT_H
|
|
||||||
#define KEEPASSX_AUTOTYPEFILTERLINEEDIT_H
|
|
||||||
|
|
||||||
#include <QLineEdit>
|
|
||||||
|
|
||||||
class AutoTypeFilterLineEdit : public QLineEdit
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
AutoTypeFilterLineEdit(QWidget* widget)
|
|
||||||
: QLineEdit(widget)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void keyPressEvent(QKeyEvent* event);
|
|
||||||
virtual void keyReleaseEvent(QKeyEvent* event);
|
|
||||||
signals:
|
|
||||||
void keyUpPressed();
|
|
||||||
void keyDownPressed();
|
|
||||||
void escapeReleased();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // KEEPASSX_AUTOTYPEFILTERLINEEDIT_H
|
|
@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,26 +15,14 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef KEEPASSX_AUTOTYPEMATCH_H
|
#ifndef KPXC_AUTOTYPEMATCH_H
|
||||||
#define KEEPASSX_AUTOTYPEMATCH_H
|
#define KPXC_AUTOTYPEMATCH_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QPair>
|
||||||
|
#include <QPointer>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
class Entry;
|
class Entry;
|
||||||
|
typedef QPair<QPointer<Entry>, QString> AutoTypeMatch;
|
||||||
|
|
||||||
struct AutoTypeMatch
|
#endif // KPXC_AUTOTYPEMATCH_H
|
||||||
{
|
|
||||||
Entry* entry;
|
|
||||||
QString sequence;
|
|
||||||
|
|
||||||
AutoTypeMatch();
|
|
||||||
AutoTypeMatch(Entry* entry, QString sequence);
|
|
||||||
|
|
||||||
bool operator==(const AutoTypeMatch& other) const;
|
|
||||||
bool operator!=(const AutoTypeMatch& other) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
Q_DECLARE_TYPEINFO(AutoTypeMatch, Q_MOVABLE_TYPE);
|
|
||||||
|
|
||||||
#endif // KEEPASSX_AUTOTYPEMATCH_H
|
|
@ -56,7 +56,7 @@ void AutoTypeMatchModel::setMatchList(const QList<AutoTypeMatch>& matches)
|
|||||||
QSet<Database*> databases;
|
QSet<Database*> databases;
|
||||||
|
|
||||||
for (AutoTypeMatch& match : m_matches) {
|
for (AutoTypeMatch& match : m_matches) {
|
||||||
databases.insert(match.entry->group()->database());
|
databases.insert(match.first->group()->database());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Database* db : asConst(databases)) {
|
for (Database* db : asConst(databases)) {
|
||||||
@ -88,7 +88,6 @@ int AutoTypeMatchModel::rowCount(const QModelIndex& parent) const
|
|||||||
int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const
|
int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
|
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,30 +102,30 @@ QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const
|
|||||||
if (role == Qt::DisplayRole) {
|
if (role == Qt::DisplayRole) {
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case ParentGroup:
|
case ParentGroup:
|
||||||
if (match.entry->group()) {
|
if (match.first->group()) {
|
||||||
return match.entry->group()->name();
|
return match.first->group()->name();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Title:
|
case Title:
|
||||||
return match.entry->resolveMultiplePlaceholders(match.entry->title());
|
return match.first->resolveMultiplePlaceholders(match.first->title());
|
||||||
case Username:
|
case Username:
|
||||||
return match.entry->resolveMultiplePlaceholders(match.entry->username());
|
return match.first->resolveMultiplePlaceholders(match.first->username());
|
||||||
case Sequence:
|
case Sequence:
|
||||||
return match.sequence;
|
return match.second;
|
||||||
}
|
}
|
||||||
} else if (role == Qt::DecorationRole) {
|
} else if (role == Qt::DecorationRole) {
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case ParentGroup:
|
case ParentGroup:
|
||||||
if (match.entry->group()) {
|
if (match.first->group()) {
|
||||||
return match.entry->group()->iconPixmap();
|
return match.first->group()->iconPixmap();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Title:
|
case Title:
|
||||||
return match.entry->iconPixmap();
|
return match.first->iconPixmap();
|
||||||
}
|
}
|
||||||
} else if (role == Qt::FontRole) {
|
} else if (role == Qt::FontRole) {
|
||||||
QFont font;
|
QFont font;
|
||||||
if (match.entry->isExpired()) {
|
if (match.first->isExpired()) {
|
||||||
font.setStrikeOut(true);
|
font.setStrikeOut(true);
|
||||||
}
|
}
|
||||||
return font;
|
return font;
|
||||||
@ -157,7 +156,7 @@ void AutoTypeMatchModel::entryDataChanged(Entry* entry)
|
|||||||
{
|
{
|
||||||
for (int row = 0; row < m_matches.size(); ++row) {
|
for (int row = 0; row < m_matches.size(); ++row) {
|
||||||
AutoTypeMatch match = m_matches[row];
|
AutoTypeMatch match = m_matches[row];
|
||||||
if (match.entry == entry) {
|
if (match.first == entry) {
|
||||||
emit dataChanged(index(row, 0), index(row, columnCount() - 1));
|
emit dataChanged(index(row, 0), index(row, columnCount() - 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,7 +166,7 @@ void AutoTypeMatchModel::entryAboutToRemove(Entry* entry)
|
|||||||
{
|
{
|
||||||
for (int row = 0; row < m_matches.size(); ++row) {
|
for (int row = 0; row < m_matches.size(); ++row) {
|
||||||
AutoTypeMatch match = m_matches[row];
|
AutoTypeMatch match = m_matches[row];
|
||||||
if (match.entry == entry) {
|
if (match.first == entry) {
|
||||||
beginRemoveRows(QModelIndex(), row, row);
|
beginRemoveRows(QModelIndex(), row, row);
|
||||||
m_matches.removeAt(row);
|
m_matches.removeAt(row);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
#include <QAbstractTableModel>
|
#include <QAbstractTableModel>
|
||||||
|
|
||||||
#include "core/AutoTypeMatch.h"
|
#include "autotype/AutoTypeMatch.h"
|
||||||
|
|
||||||
class Entry;
|
class Entry;
|
||||||
class Group;
|
class Group;
|
120
src/autotype/AutoTypeMatchView.cpp
Normal file
120
src/autotype/AutoTypeMatchView.cpp
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||||
|
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AutoTypeMatchView.h"
|
||||||
|
|
||||||
|
#include "core/Entry.h"
|
||||||
|
#include "gui/Clipboard.h"
|
||||||
|
#include "gui/Icons.h"
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
class CustomSortFilterProxyModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit CustomSortFilterProxyModel(QObject* parent = nullptr)
|
||||||
|
: QSortFilterProxyModel(parent){};
|
||||||
|
~CustomSortFilterProxyModel() override = default;
|
||||||
|
|
||||||
|
// Only search the first three columns (ie, ignore sequence column)
|
||||||
|
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override
|
||||||
|
{
|
||||||
|
auto index0 = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||||
|
auto index1 = sourceModel()->index(sourceRow, 1, sourceParent);
|
||||||
|
auto index2 = sourceModel()->index(sourceRow, 2, sourceParent);
|
||||||
|
|
||||||
|
return sourceModel()->data(index0).toString().contains(filterRegExp())
|
||||||
|
|| sourceModel()->data(index1).toString().contains(filterRegExp())
|
||||||
|
|| sourceModel()->data(index2).toString().contains(filterRegExp());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AutoTypeMatchView::AutoTypeMatchView(QWidget* parent)
|
||||||
|
: QTableView(parent)
|
||||||
|
, m_model(new AutoTypeMatchModel(this))
|
||||||
|
, m_sortModel(new CustomSortFilterProxyModel(this))
|
||||||
|
{
|
||||||
|
m_sortModel->setSourceModel(m_model);
|
||||||
|
m_sortModel->setDynamicSortFilter(true);
|
||||||
|
m_sortModel->setSortLocaleAware(true);
|
||||||
|
m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||||
|
m_sortModel->setFilterKeyColumn(-1);
|
||||||
|
m_sortModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||||
|
setModel(m_sortModel);
|
||||||
|
|
||||||
|
sortByColumn(0, Qt::AscendingOrder);
|
||||||
|
|
||||||
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
|
||||||
|
connect(this, &QTableView::doubleClicked, this, [this](const QModelIndex& index) {
|
||||||
|
emit matchActivated(matchFromIndex(index));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeMatchView::keyPressEvent(QKeyEvent* event)
|
||||||
|
{
|
||||||
|
if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && currentIndex().isValid()) {
|
||||||
|
emit matchActivated(matchFromIndex(currentIndex()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QTableView::keyPressEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeMatchView::setMatchList(const QList<AutoTypeMatch>& matches)
|
||||||
|
{
|
||||||
|
m_model->setMatchList(matches);
|
||||||
|
m_sortModel->setFilterWildcard({});
|
||||||
|
|
||||||
|
horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
|
||||||
|
selectionModel()->setCurrentIndex(m_sortModel->index(0, 0),
|
||||||
|
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||||
|
emit currentMatchChanged(currentMatch());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeMatchView::filterList(const QString& filter)
|
||||||
|
{
|
||||||
|
m_sortModel->setFilterWildcard(filter);
|
||||||
|
setCurrentIndex(m_sortModel->index(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoTypeMatch AutoTypeMatchView::currentMatch()
|
||||||
|
{
|
||||||
|
QModelIndexList list = selectionModel()->selectedRows();
|
||||||
|
if (list.size() == 1) {
|
||||||
|
return m_model->matchFromIndex(m_sortModel->mapToSource(list.first()));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoTypeMatch AutoTypeMatchView::matchFromIndex(const QModelIndex& index)
|
||||||
|
{
|
||||||
|
if (index.isValid()) {
|
||||||
|
return m_model->matchFromIndex(m_sortModel->mapToSource(index));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeMatchView::currentChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||||
|
{
|
||||||
|
auto match = matchFromIndex(current);
|
||||||
|
emit currentMatchChanged(match);
|
||||||
|
QTableView::currentChanged(current, previous);
|
||||||
|
}
|
@ -19,42 +19,37 @@
|
|||||||
#ifndef KEEPASSX_AUTOTYPEMATCHVIEW_H
|
#ifndef KEEPASSX_AUTOTYPEMATCHVIEW_H
|
||||||
#define KEEPASSX_AUTOTYPEMATCHVIEW_H
|
#define KEEPASSX_AUTOTYPEMATCHVIEW_H
|
||||||
|
|
||||||
#include <QTreeView>
|
#include <QTableView>
|
||||||
|
|
||||||
#include "core/AutoTypeMatch.h"
|
#include "autotype/AutoTypeMatch.h"
|
||||||
|
#include "autotype/AutoTypeMatchModel.h"
|
||||||
|
|
||||||
#include "gui/entry/AutoTypeMatchModel.h"
|
class QSortFilterProxyModel;
|
||||||
|
|
||||||
class SortFilterHideProxyModel;
|
class AutoTypeMatchView : public QTableView
|
||||||
|
|
||||||
class AutoTypeMatchView : public QTreeView
|
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AutoTypeMatchView(QWidget* parent = nullptr);
|
explicit AutoTypeMatchView(QWidget* parent = nullptr);
|
||||||
AutoTypeMatch currentMatch();
|
AutoTypeMatch currentMatch();
|
||||||
void setCurrentMatch(const AutoTypeMatch& match);
|
|
||||||
AutoTypeMatch matchFromIndex(const QModelIndex& index);
|
AutoTypeMatch matchFromIndex(const QModelIndex& index);
|
||||||
void setMatchList(const QList<AutoTypeMatch>& matches);
|
void setMatchList(const QList<AutoTypeMatch>& matches);
|
||||||
void setFirstMatchActive();
|
void filterList(const QString& filter);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void currentMatchChanged(AutoTypeMatch match);
|
||||||
void matchActivated(AutoTypeMatch match);
|
void matchActivated(AutoTypeMatch match);
|
||||||
void matchSelectionChanged();
|
|
||||||
void matchTextCopied();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void keyPressEvent(QKeyEvent* event) override;
|
void keyPressEvent(QKeyEvent* event) override;
|
||||||
|
|
||||||
private slots:
|
protected slots:
|
||||||
void emitMatchActivated(const QModelIndex& index);
|
void currentChanged(const QModelIndex& current, const QModelIndex& previous) override;
|
||||||
void userNameCopied();
|
|
||||||
void passwordCopied();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AutoTypeMatchModel* const m_model;
|
AutoTypeMatchModel* const m_model;
|
||||||
SortFilterHideProxyModel* const m_sortModel;
|
QSortFilterProxyModel* const m_sortModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_AUTOTYPEMATCHVIEW_H
|
#endif // KEEPASSX_AUTOTYPEMATCHVIEW_H
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -17,168 +17,323 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "AutoTypeSelectDialog.h"
|
#include "AutoTypeSelectDialog.h"
|
||||||
|
#include "ui_AutoTypeSelectDialog.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QCloseEvent>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QShortcut>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#else
|
#else
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#endif
|
#endif
|
||||||
#include <QDialogButtonBox>
|
|
||||||
#include <QHeaderView>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QSortFilterProxyModel>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
#include "autotype/AutoTypeSelectView.h"
|
|
||||||
#include "core/AutoTypeMatch.h"
|
|
||||||
#include "core/Config.h"
|
#include "core/Config.h"
|
||||||
|
#include "core/Database.h"
|
||||||
|
#include "core/Entry.h"
|
||||||
|
#include "core/EntrySearcher.h"
|
||||||
|
#include "gui/Clipboard.h"
|
||||||
#include "gui/Icons.h"
|
#include "gui/Icons.h"
|
||||||
#include "gui/entry/AutoTypeMatchModel.h"
|
|
||||||
|
|
||||||
AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
|
AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_view(new AutoTypeSelectView(this))
|
, m_ui(new Ui::AutoTypeSelectDialog())
|
||||||
, m_filterLineEdit(new AutoTypeFilterLineEdit(this))
|
|
||||||
, m_matchActivatedEmitted(false)
|
|
||||||
, m_rejected(false)
|
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_DeleteOnClose);
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
// Places the window on the active (virtual) desktop instead of where the main window is.
|
// Places the window on the active (virtual) desktop instead of where the main window is.
|
||||||
setAttribute(Qt::WA_X11BypassTransientForHint);
|
setAttribute(Qt::WA_X11BypassTransientForHint);
|
||||||
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
setWindowFlags((windowFlags() | Qt::WindowStaysOnTopHint) & ~Qt::WindowContextHelpButtonHint);
|
||||||
setWindowTitle(tr("Auto-Type - KeePassXC"));
|
|
||||||
setWindowIcon(icons()->applicationIcon());
|
setWindowIcon(icons()->applicationIcon());
|
||||||
|
|
||||||
|
buildActionMenu();
|
||||||
|
|
||||||
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
connect(m_ui->view, &AutoTypeMatchView::matchActivated, this, &AutoTypeSelectDialog::submitAutoTypeMatch);
|
||||||
|
connect(m_ui->view, &AutoTypeMatchView::currentMatchChanged, this, &AutoTypeSelectDialog::updateActionMenu);
|
||||||
|
connect(m_ui->view, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
|
||||||
|
if (m_ui->view->currentMatch().first) {
|
||||||
|
m_actionMenu->popup(m_ui->view->viewport()->mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
m_ui->search->setFocus();
|
||||||
|
m_ui->search->installEventFilter(this);
|
||||||
|
|
||||||
|
m_searchTimer.setInterval(300);
|
||||||
|
m_searchTimer.setSingleShot(true);
|
||||||
|
|
||||||
|
connect(m_ui->search, SIGNAL(textChanged(QString)), &m_searchTimer, SLOT(start()));
|
||||||
|
connect(m_ui->search, SIGNAL(returnPressed()), SLOT(activateCurrentMatch()));
|
||||||
|
connect(&m_searchTimer, SIGNAL(timeout()), SLOT(performSearch()));
|
||||||
|
|
||||||
|
connect(m_ui->filterRadio, &QRadioButton::toggled, this, [this](bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
// Reset to original match list
|
||||||
|
m_ui->view->setMatchList(m_matches);
|
||||||
|
performSearch();
|
||||||
|
m_ui->search->setFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_ui->searchRadio, &QRadioButton::toggled, this, [this](bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
performSearch();
|
||||||
|
m_ui->search->setFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
m_actionMenu->installEventFilter(this);
|
||||||
|
m_ui->action->setMenu(m_actionMenu);
|
||||||
|
m_ui->action->installEventFilter(this);
|
||||||
|
connect(m_ui->action, &QToolButton::clicked, this, &AutoTypeSelectDialog::activateCurrentMatch);
|
||||||
|
|
||||||
|
connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(reject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required for QScopedPointer
|
||||||
|
AutoTypeSelectDialog::~AutoTypeSelectDialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::setMatches(const QList<AutoTypeMatch>& matches, const QList<QSharedPointer<Database>>& dbs)
|
||||||
|
{
|
||||||
|
m_matches = matches;
|
||||||
|
m_dbs = dbs;
|
||||||
|
|
||||||
|
m_ui->view->setMatchList(m_matches);
|
||||||
|
if (m_matches.isEmpty()) {
|
||||||
|
m_ui->searchRadio->setChecked(true);
|
||||||
|
} else {
|
||||||
|
m_ui->filterRadio->setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match)
|
||||||
|
{
|
||||||
|
m_accepted = true;
|
||||||
|
accept();
|
||||||
|
emit matchActivated(std::move(match));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::performSearch()
|
||||||
|
{
|
||||||
|
if (m_ui->filterRadio->isChecked()) {
|
||||||
|
m_ui->view->filterList(m_ui->search->text());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto searchText = m_ui->search->text();
|
||||||
|
// If no search text, find all entries
|
||||||
|
if (searchText.isEmpty()) {
|
||||||
|
searchText.append("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
EntrySearcher searcher;
|
||||||
|
QList<AutoTypeMatch> matches;
|
||||||
|
for (const auto& db : m_dbs) {
|
||||||
|
auto found = searcher.search(searchText, db->rootGroup());
|
||||||
|
for (auto* entry : found) {
|
||||||
|
QSet<QString> sequences;
|
||||||
|
auto defSequence = entry->effectiveAutoTypeSequence();
|
||||||
|
if (!defSequence.isEmpty()) {
|
||||||
|
matches.append({entry, defSequence});
|
||||||
|
sequences << defSequence;
|
||||||
|
}
|
||||||
|
for (const auto& assoc : entry->autoTypeAssociations()->getAll()) {
|
||||||
|
if (!sequences.contains(assoc.sequence) && !assoc.sequence.isEmpty()) {
|
||||||
|
matches.append({entry, assoc.sequence});
|
||||||
|
sequences << assoc.sequence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ui->view->setMatchList(matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::moveSelectionUp()
|
||||||
|
{
|
||||||
|
auto current = m_ui->view->currentIndex();
|
||||||
|
auto previous = current.sibling(current.row() - 1, 0);
|
||||||
|
|
||||||
|
if (previous.isValid()) {
|
||||||
|
m_ui->view->setCurrentIndex(previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::moveSelectionDown()
|
||||||
|
{
|
||||||
|
auto current = m_ui->view->currentIndex();
|
||||||
|
auto next = current.sibling(current.row() + 1, 0);
|
||||||
|
|
||||||
|
if (next.isValid()) {
|
||||||
|
m_ui->view->setCurrentIndex(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::activateCurrentMatch()
|
||||||
|
{
|
||||||
|
submitAutoTypeMatch(m_ui->view->currentMatch());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoTypeSelectDialog::eventFilter(QObject* obj, QEvent* event)
|
||||||
|
{
|
||||||
|
if (obj == m_ui->action) {
|
||||||
|
if (event->type() == QEvent::FocusIn) {
|
||||||
|
m_ui->action->showMenu();
|
||||||
|
return true;
|
||||||
|
} else if (event->type() == QEvent::KeyPress && static_cast<QKeyEvent*>(event)->key() == Qt::Key_Return) {
|
||||||
|
// handle case where the menu is closed but the button has focus
|
||||||
|
activateCurrentMatch();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (obj == m_actionMenu) {
|
||||||
|
if (event->type() == QEvent::KeyPress) {
|
||||||
|
auto* keyEvent = static_cast<QKeyEvent*>(event);
|
||||||
|
switch (keyEvent->key()) {
|
||||||
|
case Qt::Key_Tab:
|
||||||
|
m_actionMenu->close();
|
||||||
|
focusNextPrevChild(true);
|
||||||
|
return true;
|
||||||
|
case Qt::Key_Backtab:
|
||||||
|
m_actionMenu->close();
|
||||||
|
focusNextPrevChild(false);
|
||||||
|
return true;
|
||||||
|
case Qt::Key_Return:
|
||||||
|
// accept the dialog with default sequence if no action selected
|
||||||
|
if (!m_actionMenu->activeAction()) {
|
||||||
|
activateCurrentMatch();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (obj == m_ui->search) {
|
||||||
|
if (event->type() == QEvent::KeyPress) {
|
||||||
|
auto* keyEvent = static_cast<QKeyEvent*>(event);
|
||||||
|
switch (keyEvent->key()) {
|
||||||
|
case Qt::Key_Up:
|
||||||
|
moveSelectionUp();
|
||||||
|
return true;
|
||||||
|
case Qt::Key_Down:
|
||||||
|
moveSelectionDown();
|
||||||
|
return true;
|
||||||
|
case Qt::Key_Escape:
|
||||||
|
if (m_ui->search->text().isEmpty()) {
|
||||||
|
reject();
|
||||||
|
} else {
|
||||||
|
m_ui->search->clear();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QDialog::eventFilter(obj, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::updateActionMenu(const AutoTypeMatch& match)
|
||||||
|
{
|
||||||
|
if (!match.first) {
|
||||||
|
m_ui->action->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ui->action->setEnabled(true);
|
||||||
|
|
||||||
|
bool hasUsername = !match.first->username().isEmpty();
|
||||||
|
bool hasPassword = !match.first->password().isEmpty();
|
||||||
|
bool hasTotp = match.first->hasTotp();
|
||||||
|
|
||||||
|
auto actions = m_actionMenu->actions();
|
||||||
|
Q_ASSERT(actions.size() >= 6);
|
||||||
|
actions[0]->setEnabled(hasUsername);
|
||||||
|
actions[1]->setEnabled(hasPassword);
|
||||||
|
actions[2]->setEnabled(hasTotp);
|
||||||
|
actions[3]->setEnabled(hasUsername);
|
||||||
|
actions[4]->setEnabled(hasPassword);
|
||||||
|
actions[5]->setEnabled(hasTotp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::buildActionMenu()
|
||||||
|
{
|
||||||
|
m_actionMenu = new QMenu(this);
|
||||||
|
auto typeUsernameAction = new QAction(icons()->icon("auto-type"), tr("Type {USERNAME}"), this);
|
||||||
|
auto typePasswordAction = new QAction(icons()->icon("auto-type"), tr("Type {PASSWORD}"), this);
|
||||||
|
auto typeTotpAction = new QAction(icons()->icon("auto-type"), tr("Type {TOTP}"), this);
|
||||||
|
auto copyUsernameAction = new QAction(icons()->icon("username-copy"), tr("Copy Username"), this);
|
||||||
|
auto copyPasswordAction = new QAction(icons()->icon("password-copy"), tr("Copy Password"), this);
|
||||||
|
auto copyTotpAction = new QAction(icons()->icon("chronometer"), tr("Copy TOTP"), this);
|
||||||
|
m_actionMenu->addAction(typeUsernameAction);
|
||||||
|
m_actionMenu->addAction(typePasswordAction);
|
||||||
|
m_actionMenu->addAction(typeTotpAction);
|
||||||
|
m_actionMenu->addAction(copyUsernameAction);
|
||||||
|
m_actionMenu->addAction(copyPasswordAction);
|
||||||
|
m_actionMenu->addAction(copyTotpAction);
|
||||||
|
|
||||||
|
connect(typeUsernameAction, &QAction::triggered, this, [&] {
|
||||||
|
auto match = m_ui->view->currentMatch();
|
||||||
|
match.second = "{USERNAME}";
|
||||||
|
submitAutoTypeMatch(match);
|
||||||
|
});
|
||||||
|
connect(typePasswordAction, &QAction::triggered, this, [&] {
|
||||||
|
auto match = m_ui->view->currentMatch();
|
||||||
|
match.second = "{PASSWORD}";
|
||||||
|
submitAutoTypeMatch(match);
|
||||||
|
});
|
||||||
|
connect(typeTotpAction, &QAction::triggered, this, [&] {
|
||||||
|
auto match = m_ui->view->currentMatch();
|
||||||
|
match.second = "{TOTP}";
|
||||||
|
submitAutoTypeMatch(match);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(copyUsernameAction, &QAction::triggered, this, [&] {
|
||||||
|
clipboard()->setText(m_ui->view->currentMatch().first->username());
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
connect(copyPasswordAction, &QAction::triggered, this, [&] {
|
||||||
|
clipboard()->setText(m_ui->view->currentMatch().first->password());
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
connect(copyTotpAction, &QAction::triggered, this, [&] {
|
||||||
|
clipboard()->setText(m_ui->view->currentMatch().first->totp());
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoTypeSelectDialog::showEvent(QShowEvent* event)
|
||||||
|
{
|
||||||
|
QDialog::showEvent(event);
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
QRect screenGeometry = QApplication::screenAt(QCursor::pos())->availableGeometry();
|
auto screen = QApplication::screenAt(QCursor::pos());
|
||||||
|
if (!screen) {
|
||||||
|
// screenAt can return a nullptr, default to the primary screen
|
||||||
|
screen = QApplication::primaryScreen();
|
||||||
|
}
|
||||||
|
QRect screenGeometry = screen->availableGeometry();
|
||||||
#else
|
#else
|
||||||
QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos());
|
QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Resize to last used size
|
||||||
QSize size = config()->get(Config::GUI_AutoTypeSelectDialogSize).toSize();
|
QSize size = config()->get(Config::GUI_AutoTypeSelectDialogSize).toSize();
|
||||||
size.setWidth(qMin(size.width(), screenGeometry.width()));
|
size.setWidth(qMin(size.width(), screenGeometry.width()));
|
||||||
size.setHeight(qMin(size.height(), screenGeometry.height()));
|
size.setHeight(qMin(size.height(), screenGeometry.height()));
|
||||||
resize(size);
|
resize(size);
|
||||||
|
|
||||||
// move dialog to the center of the screen
|
// move dialog to the center of the screen
|
||||||
QPoint screenCenter = screenGeometry.center();
|
move(screenGeometry.center().x() - (size.width() / 2), screenGeometry.center().y() - (size.height() / 2));
|
||||||
move(screenCenter.x() - (size.width() / 2), screenCenter.y() - (size.height() / 2));
|
|
||||||
|
|
||||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
|
||||||
|
|
||||||
QLabel* descriptionLabel = new QLabel(tr("Select entry to Auto-Type:"), this);
|
|
||||||
layout->addWidget(descriptionLabel);
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
connect(m_view, SIGNAL(activated(QModelIndex)), SLOT(emitMatchActivated(QModelIndex)));
|
|
||||||
connect(m_view, SIGNAL(clicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex)));
|
|
||||||
connect(m_view->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(matchRemoved()));
|
|
||||||
connect(m_view, SIGNAL(rejected()), SLOT(reject()));
|
|
||||||
connect(m_view, SIGNAL(matchTextCopied()), SLOT(reject()));
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
QSortFilterProxyModel* proxy = qobject_cast<QSortFilterProxyModel*>(m_view->model());
|
|
||||||
if (proxy) {
|
|
||||||
proxy->setFilterKeyColumn(-1);
|
|
||||||
proxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
layout->addWidget(m_view);
|
|
||||||
|
|
||||||
connect(m_filterLineEdit, SIGNAL(textChanged(QString)), SLOT(filterList(QString)));
|
|
||||||
connect(m_filterLineEdit, SIGNAL(returnPressed()), SLOT(activateCurrentIndex()));
|
|
||||||
connect(m_filterLineEdit, SIGNAL(keyUpPressed()), SLOT(moveSelectionUp()));
|
|
||||||
connect(m_filterLineEdit, SIGNAL(keyDownPressed()), SLOT(moveSelectionDown()));
|
|
||||||
connect(m_filterLineEdit, SIGNAL(escapeReleased()), SLOT(reject()));
|
|
||||||
|
|
||||||
m_filterLineEdit->setPlaceholderText(tr("Search…"));
|
|
||||||
layout->addWidget(m_filterLineEdit);
|
|
||||||
|
|
||||||
QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, Qt::Horizontal, this);
|
|
||||||
connect(buttonBox, SIGNAL(rejected()), SLOT(reject()));
|
|
||||||
layout->addWidget(buttonBox);
|
|
||||||
|
|
||||||
m_filterLineEdit->setFocus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoTypeSelectDialog::setMatchList(const QList<AutoTypeMatch>& matchList)
|
void AutoTypeSelectDialog::hideEvent(QHideEvent* event)
|
||||||
{
|
|
||||||
m_view->setMatchList(matchList);
|
|
||||||
|
|
||||||
m_view->header()->resizeSections(QHeaderView::ResizeToContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectDialog::done(int r)
|
|
||||||
{
|
{
|
||||||
config()->set(Config::GUI_AutoTypeSelectDialogSize, size());
|
config()->set(Config::GUI_AutoTypeSelectDialogSize, size());
|
||||||
|
if (!m_accepted) {
|
||||||
QDialog::done(r);
|
emit rejected();
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectDialog::reject()
|
|
||||||
{
|
|
||||||
m_rejected = true;
|
|
||||||
|
|
||||||
QDialog::reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index)
|
|
||||||
{
|
|
||||||
// make sure we don't emit the signal twice when both activated() and clicked() are triggered
|
|
||||||
if (m_matchActivatedEmitted) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
m_matchActivatedEmitted = true;
|
QDialog::hideEvent(event);
|
||||||
|
|
||||||
AutoTypeMatch match = m_view->matchFromIndex(index);
|
|
||||||
accept();
|
|
||||||
emit matchActivated(match);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectDialog::matchRemoved()
|
|
||||||
{
|
|
||||||
if (m_rejected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_view->model()->rowCount() == 0 && m_filterLineEdit->text().isEmpty()) {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectDialog::filterList(QString filterString)
|
|
||||||
{
|
|
||||||
QSortFilterProxyModel* proxy = qobject_cast<QSortFilterProxyModel*>(m_view->model());
|
|
||||||
if (proxy) {
|
|
||||||
proxy->setFilterWildcard(filterString);
|
|
||||||
if (!m_view->currentIndex().isValid()) {
|
|
||||||
m_view->setCurrentIndex(m_view->model()->index(0, 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectDialog::moveSelectionUp()
|
|
||||||
{
|
|
||||||
auto current = m_view->currentIndex();
|
|
||||||
auto previous = current.sibling(current.row() - 1, 0);
|
|
||||||
|
|
||||||
if (previous.isValid()) {
|
|
||||||
m_view->setCurrentIndex(previous);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectDialog::moveSelectionDown()
|
|
||||||
{
|
|
||||||
auto current = m_view->currentIndex();
|
|
||||||
auto next = current.sibling(current.row() + 1, 0);
|
|
||||||
|
|
||||||
if (next.isValid()) {
|
|
||||||
m_view->setCurrentIndex(next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectDialog::activateCurrentIndex()
|
|
||||||
{
|
|
||||||
emitMatchActivated(m_view->currentIndex());
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -18,14 +19,17 @@
|
|||||||
#ifndef KEEPASSX_AUTOTYPESELECTDIALOG_H
|
#ifndef KEEPASSX_AUTOTYPESELECTDIALOG_H
|
||||||
#define KEEPASSX_AUTOTYPESELECTDIALOG_H
|
#define KEEPASSX_AUTOTYPESELECTDIALOG_H
|
||||||
|
|
||||||
#include <QAbstractItemModel>
|
#include "autotype/AutoTypeMatch.h"
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QHash>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "autotype/AutoTypeFilterLineEdit.h"
|
class Database;
|
||||||
#include "core/AutoTypeMatch.h"
|
class QMenu;
|
||||||
|
|
||||||
class AutoTypeSelectView;
|
namespace Ui
|
||||||
|
{
|
||||||
|
class AutoTypeSelectDialog;
|
||||||
|
}
|
||||||
|
|
||||||
class AutoTypeSelectDialog : public QDialog
|
class AutoTypeSelectDialog : public QDialog
|
||||||
{
|
{
|
||||||
@ -33,28 +37,37 @@ class AutoTypeSelectDialog : public QDialog
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AutoTypeSelectDialog(QWidget* parent = nullptr);
|
explicit AutoTypeSelectDialog(QWidget* parent = nullptr);
|
||||||
void setMatchList(const QList<AutoTypeMatch>& matchList);
|
~AutoTypeSelectDialog() override;
|
||||||
|
|
||||||
|
void setMatches(const QList<AutoTypeMatch>& matchList, const QList<QSharedPointer<Database>>& dbs);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void matchActivated(AutoTypeMatch match);
|
void matchActivated(AutoTypeMatch match);
|
||||||
|
|
||||||
public slots:
|
protected:
|
||||||
void done(int r) override;
|
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
void reject() override;
|
void showEvent(QShowEvent* event) override;
|
||||||
|
void hideEvent(QHideEvent* event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void emitMatchActivated(const QModelIndex& index);
|
void submitAutoTypeMatch(AutoTypeMatch match);
|
||||||
void matchRemoved();
|
void performSearch();
|
||||||
void filterList(QString filterString);
|
|
||||||
void moveSelectionUp();
|
void moveSelectionUp();
|
||||||
void moveSelectionDown();
|
void moveSelectionDown();
|
||||||
void activateCurrentIndex();
|
void activateCurrentMatch();
|
||||||
|
void updateActionMenu(const AutoTypeMatch& match);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AutoTypeSelectView* const m_view;
|
void buildActionMenu();
|
||||||
AutoTypeFilterLineEdit* const m_filterLineEdit;
|
|
||||||
bool m_matchActivatedEmitted;
|
QScopedPointer<Ui::AutoTypeSelectDialog> m_ui;
|
||||||
bool m_rejected;
|
|
||||||
|
QList<QSharedPointer<Database>> m_dbs;
|
||||||
|
QList<AutoTypeMatch> m_matches;
|
||||||
|
QTimer m_searchTimer;
|
||||||
|
QPointer<QMenu> m_actionMenu;
|
||||||
|
|
||||||
|
bool m_accepted = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_AUTOTYPESELECTDIALOG_H
|
#endif // KEEPASSX_AUTOTYPESELECTDIALOG_H
|
||||||
|
197
src/autotype/AutoTypeSelectDialog.ui
Normal file
197
src/autotype/AutoTypeSelectDialog.ui
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AutoTypeSelectDialog</class>
|
||||||
|
<widget class="QDialog" name="AutoTypeSelectDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>418</width>
|
||||||
|
<height>295</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Auto-Type - KeePassXC</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Double click a row to perform Auto-Type or find an entry using the search:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="AutoTypeMatchView" name="view">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>175</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="cursor" stdset="0">
|
||||||
|
<cursorShape>PointingHandCursor</cursorShape>
|
||||||
|
</property>
|
||||||
|
<property name="tabKeyNavigation">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="showDropIndicator" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sortingEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="horizontalHeaderStretchLastSection">
|
||||||
|
<bool>true</bool>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="verticalHeaderVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="buttonBox_2" native="true">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="filterRadio">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Filter Matches</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="searchRadio">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Search Database</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="search">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>400</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Filter or Search…</string>
|
||||||
|
</property>
|
||||||
|
<property name="clearButtonEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="action">
|
||||||
|
<property name="text">
|
||||||
|
<string>Type Sequence</string>
|
||||||
|
</property>
|
||||||
|
<property name="popupMode">
|
||||||
|
<enum>QToolButton::MenuButtonPopup</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="cancelButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Cancel</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>AutoTypeMatchView</class>
|
||||||
|
<extends>QTableView</extends>
|
||||||
|
<header>autotype/AutoTypeMatchView.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>view</tabstop>
|
||||||
|
<tabstop>filterRadio</tabstop>
|
||||||
|
<tabstop>searchRadio</tabstop>
|
||||||
|
<tabstop>search</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
|
||||||
* version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "AutoTypeSelectView.h"
|
|
||||||
|
|
||||||
#include <QKeyEvent>
|
|
||||||
#include <QMouseEvent>
|
|
||||||
|
|
||||||
AutoTypeSelectView::AutoTypeSelectView(QWidget* parent)
|
|
||||||
: AutoTypeMatchView(parent)
|
|
||||||
{
|
|
||||||
setMouseTracking(true);
|
|
||||||
setAllColumnsShowFocus(true);
|
|
||||||
|
|
||||||
connect(model(), SIGNAL(modelReset()), SLOT(selectFirstMatch()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event)
|
|
||||||
{
|
|
||||||
QModelIndex index = indexAt(event->pos());
|
|
||||||
|
|
||||||
if (index.isValid()) {
|
|
||||||
setCurrentIndex(index);
|
|
||||||
setCursor(Qt::PointingHandCursor);
|
|
||||||
} else {
|
|
||||||
unsetCursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTypeMatchView::mouseMoveEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectView::selectFirstMatch()
|
|
||||||
{
|
|
||||||
QModelIndex index = model()->index(0, 0);
|
|
||||||
|
|
||||||
if (index.isValid()) {
|
|
||||||
setCurrentIndex(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeSelectView::keyReleaseEvent(QKeyEvent* e)
|
|
||||||
{
|
|
||||||
if (e->key() == Qt::Key_Escape) {
|
|
||||||
emit rejected();
|
|
||||||
} else {
|
|
||||||
e->ignore();
|
|
||||||
}
|
|
||||||
}
|
|
172
src/autotype/PickcharsDialog.cpp
Normal file
172
src/autotype/PickcharsDialog.cpp
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PickcharsDialog.h"
|
||||||
|
#include "ui_PickcharsDialog.h"
|
||||||
|
|
||||||
|
#include "core/Entry.h"
|
||||||
|
#include "gui/Icons.h"
|
||||||
|
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QShortcut>
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
#include <QScreen>
|
||||||
|
#else
|
||||||
|
#include <QDesktopWidget>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PickcharsDialog::PickcharsDialog(const QString& string, QWidget* parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
, m_ui(new Ui::PickcharsDialog())
|
||||||
|
{
|
||||||
|
if (string.isEmpty()) {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Places the window on the active (virtual) desktop instead of where the main window is.
|
||||||
|
setAttribute(Qt::WA_X11BypassTransientForHint);
|
||||||
|
setWindowFlags((windowFlags() | Qt::WindowStaysOnTopHint | Qt::MSWindowsFixedSizeDialogHint)
|
||||||
|
& ~Qt::WindowContextHelpButtonHint);
|
||||||
|
setWindowIcon(icons()->applicationIcon());
|
||||||
|
|
||||||
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
// Increase max columns with longer passwords for better display
|
||||||
|
int width = 10;
|
||||||
|
if (string.length() >= 100) {
|
||||||
|
width = 20;
|
||||||
|
} else if (string.length() >= 60) {
|
||||||
|
width = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (const auto& ch : string) {
|
||||||
|
auto btn = new QPushButton(QString::number(count + 1));
|
||||||
|
btn->setProperty("char", ch);
|
||||||
|
btn->setProperty("count", count);
|
||||||
|
connect(btn, &QPushButton::clicked, this, &PickcharsDialog::charSelected);
|
||||||
|
m_ui->charsGrid->addWidget(btn, count / width, count % width);
|
||||||
|
m_lastSelected = count;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
// Prevent stretched buttons
|
||||||
|
if (m_ui->charsGrid->rowCount() == 1 && m_ui->charsGrid->columnCount() < 5) {
|
||||||
|
m_ui->charsGrid->addItem(new QSpacerItem(5, 5, QSizePolicy::MinimumExpanding), count / width, count % width);
|
||||||
|
}
|
||||||
|
m_ui->charsGrid->itemAtPosition(0, 0)->widget()->setFocus();
|
||||||
|
|
||||||
|
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
|
||||||
|
// Navigate grid layout using up/down/left/right motion
|
||||||
|
new QShortcut(Qt::Key_Up, this, SLOT(upPressed()));
|
||||||
|
new QShortcut(Qt::Key_Down, this, SLOT(downPressed()));
|
||||||
|
// Remove last selected character
|
||||||
|
auto shortcut = new QShortcut(Qt::Key_Backspace, this);
|
||||||
|
connect(shortcut, &QShortcut::activated, this, [this] {
|
||||||
|
auto text = m_ui->selectedChars->text();
|
||||||
|
m_ui->selectedChars->setText(text.left(text.size() - 1));
|
||||||
|
});
|
||||||
|
// Submit the form
|
||||||
|
shortcut = new QShortcut(Qt::CTRL + Qt::Key_S, this);
|
||||||
|
connect(shortcut, &QShortcut::activated, this, [this] { accept(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickcharsDialog::upPressed()
|
||||||
|
{
|
||||||
|
auto focus = focusWidget();
|
||||||
|
if (!focus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto count = focus->property("count");
|
||||||
|
if (count.isValid()) {
|
||||||
|
// Lower bound not checked by QGridLayout::itemAt https://bugreports.qt.io/browse/QTBUG-91261
|
||||||
|
auto upCount = count.toInt() - m_ui->charsGrid->columnCount();
|
||||||
|
if (upCount >= 0) {
|
||||||
|
m_ui->charsGrid->itemAt(upCount)->widget()->setFocus();
|
||||||
|
}
|
||||||
|
} else if (focus == m_ui->selectedChars) {
|
||||||
|
// Move back to the last selected button
|
||||||
|
auto item = m_ui->charsGrid->itemAt(m_lastSelected);
|
||||||
|
if (item) {
|
||||||
|
item->widget()->setFocus();
|
||||||
|
}
|
||||||
|
} else if (focus == m_ui->pressTab) {
|
||||||
|
m_ui->selectedChars->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickcharsDialog::downPressed()
|
||||||
|
{
|
||||||
|
auto focus = focusWidget();
|
||||||
|
if (!focus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto count = focus->property("count");
|
||||||
|
if (count.isValid()) {
|
||||||
|
auto item = m_ui->charsGrid->itemAt(count.toInt() + m_ui->charsGrid->columnCount());
|
||||||
|
if (item) {
|
||||||
|
item->widget()->setFocus();
|
||||||
|
} else {
|
||||||
|
// Store the currently selected button and move to the line edit
|
||||||
|
m_lastSelected = count.toInt();
|
||||||
|
m_ui->selectedChars->setFocus();
|
||||||
|
}
|
||||||
|
} else if (focus == m_ui->selectedChars) {
|
||||||
|
m_ui->pressTab->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PickcharsDialog::selectedChars()
|
||||||
|
{
|
||||||
|
return m_ui->selectedChars->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PickcharsDialog::pressTab()
|
||||||
|
{
|
||||||
|
return m_ui->pressTab->isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickcharsDialog::charSelected()
|
||||||
|
{
|
||||||
|
auto btn = qobject_cast<QPushButton*>(sender());
|
||||||
|
if (!btn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ui->selectedChars->setText(m_ui->selectedChars->text() + btn->property("char").toChar());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PickcharsDialog::showEvent(QShowEvent* event)
|
||||||
|
{
|
||||||
|
QDialog::showEvent(event);
|
||||||
|
|
||||||
|
// Center on active screen
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
auto screen = QApplication::screenAt(QCursor::pos());
|
||||||
|
if (!screen) {
|
||||||
|
// screenAt can return a nullptr, default to the primary screen
|
||||||
|
screen = QApplication::primaryScreen();
|
||||||
|
}
|
||||||
|
QRect screenGeometry = screen->availableGeometry();
|
||||||
|
#else
|
||||||
|
QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos());
|
||||||
|
#endif
|
||||||
|
move(screenGeometry.center().x() - (size().width() / 2), screenGeometry.center().y() - (size().height() / 2));
|
||||||
|
}
|
@ -1,41 +1,52 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
* version 3 of the License.
|
* version 3 of the License.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef KEEPASSX_AUTOTYPESELECTVIEW_H
|
#ifndef KEEPASSXC_PICKCHARSDIALOG_H
|
||||||
#define KEEPASSX_AUTOTYPESELECTVIEW_H
|
#define KEEPASSXC_PICKCHARSDIALOG_H
|
||||||
|
|
||||||
#include "gui/entry/AutoTypeMatchView.h"
|
#include <QDialog>
|
||||||
|
#include <QPointer>
|
||||||
class AutoTypeSelectView : public AutoTypeMatchView
|
#include <QString>
|
||||||
{
|
|
||||||
Q_OBJECT
|
namespace Ui
|
||||||
|
{
|
||||||
public:
|
class PickcharsDialog;
|
||||||
explicit AutoTypeSelectView(QWidget* parent = nullptr);
|
}
|
||||||
|
|
||||||
protected:
|
class PickcharsDialog : public QDialog
|
||||||
void mouseMoveEvent(QMouseEvent* event) override;
|
{
|
||||||
void keyReleaseEvent(QKeyEvent* e) override;
|
Q_OBJECT
|
||||||
|
|
||||||
private slots:
|
public:
|
||||||
void selectFirstMatch();
|
explicit PickcharsDialog(const QString& string, QWidget* parent = nullptr);
|
||||||
|
QString selectedChars();
|
||||||
signals:
|
bool pressTab();
|
||||||
void rejected();
|
|
||||||
};
|
protected:
|
||||||
|
void showEvent(QShowEvent*) override;
|
||||||
#endif // KEEPASSX_AUTOTYPESELECTVIEW_H
|
|
||||||
|
private slots:
|
||||||
|
void charSelected();
|
||||||
|
void upPressed();
|
||||||
|
void downPressed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedPointer<Ui::PickcharsDialog> m_ui;
|
||||||
|
int m_lastSelected;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KEEPASSXC_PICKCHARSDIALOG_H
|
87
src/autotype/PickcharsDialog.ui
Normal file
87
src/autotype/PickcharsDialog.ui
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>PickcharsDialog</class>
|
||||||
|
<widget class="QDialog" name="PickcharsDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>418</width>
|
||||||
|
<height>188</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>KeePassXC - Pick Characters</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetFixedSize</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Select characters to type, navigate with arrow keys, Ctrl + S submits.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="charsGrid"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="PasswordEdit" name="selectedChars">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>150</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="pressTab">
|
||||||
|
<property name="text">
|
||||||
|
<string>Press &Tab between characters</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>PasswordEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>gui/PasswordEdit.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>selectedChars</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
|
||||||
* version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "WildcardMatcher.h"
|
|
||||||
|
|
||||||
#include <QStringList>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
const QChar WildcardMatcher::Wildcard = '*';
|
|
||||||
const Qt::CaseSensitivity WildcardMatcher::Sensitivity = Qt::CaseInsensitive;
|
|
||||||
|
|
||||||
WildcardMatcher::WildcardMatcher(QString text)
|
|
||||||
: m_text(std::move(text))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WildcardMatcher::match(const QString& pattern)
|
|
||||||
{
|
|
||||||
m_pattern = pattern;
|
|
||||||
|
|
||||||
if (patternContainsWildcard()) {
|
|
||||||
return matchWithWildcards();
|
|
||||||
} else {
|
|
||||||
return patternEqualsText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WildcardMatcher::patternContainsWildcard()
|
|
||||||
{
|
|
||||||
return m_pattern.contains(Wildcard);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WildcardMatcher::patternEqualsText()
|
|
||||||
{
|
|
||||||
return m_text.compare(m_pattern, Sensitivity) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WildcardMatcher::matchWithWildcards()
|
|
||||||
{
|
|
||||||
QStringList parts = m_pattern.split(Wildcard, QString::KeepEmptyParts);
|
|
||||||
Q_ASSERT(parts.size() >= 2);
|
|
||||||
|
|
||||||
if (startOrEndDoesNotMatch(parts)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return partsMatch(parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WildcardMatcher::startOrEndDoesNotMatch(const QStringList& parts)
|
|
||||||
{
|
|
||||||
return !m_text.startsWith(parts.first(), Sensitivity) || !m_text.endsWith(parts.last(), Sensitivity);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WildcardMatcher::partsMatch(const QStringList& parts)
|
|
||||||
{
|
|
||||||
int index = 0;
|
|
||||||
for (const QString& part : parts) {
|
|
||||||
int matchIndex = getMatchIndex(part, index);
|
|
||||||
if (noMatchFound(matchIndex)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
index = calculateNewIndex(matchIndex, part.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WildcardMatcher::getMatchIndex(const QString& part, int startIndex)
|
|
||||||
{
|
|
||||||
return m_text.indexOf(part, startIndex, Sensitivity);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WildcardMatcher::noMatchFound(int index)
|
|
||||||
{
|
|
||||||
return index == -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WildcardMatcher::calculateNewIndex(int matchIndex, int partLength)
|
|
||||||
{
|
|
||||||
return matchIndex + partLength;
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
|
||||||
* version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef KEEPASSX_WILDCARDMATCHER_H
|
|
||||||
#define KEEPASSX_WILDCARDMATCHER_H
|
|
||||||
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
class WildcardMatcher
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit WildcardMatcher(QString text);
|
|
||||||
bool match(const QString& pattern);
|
|
||||||
|
|
||||||
static const QChar Wildcard;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool patternEqualsText();
|
|
||||||
bool patternContainsWildcard();
|
|
||||||
bool matchWithWildcards();
|
|
||||||
bool startOrEndDoesNotMatch(const QStringList& parts);
|
|
||||||
bool partsMatch(const QStringList& parts);
|
|
||||||
int getMatchIndex(const QString& part, int startIndex);
|
|
||||||
bool noMatchFound(int index);
|
|
||||||
int calculateNewIndex(int matchIndex, int partLength);
|
|
||||||
|
|
||||||
static const Qt::CaseSensitivity Sensitivity;
|
|
||||||
const QString m_text;
|
|
||||||
QString m_pattern;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // KEEPASSX_WILDCARDMATCHER_H
|
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "AutoTypeMac.h"
|
#include "AutoTypeMac.h"
|
||||||
|
#include "core/Tools.h"
|
||||||
#include "gui/osutils/macutils/MacUtils.h"
|
#include "gui/osutils/macutils/MacUtils.h"
|
||||||
#include "gui/MessageBox.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);
|
if (action->modifiers & Qt::ShiftModifier) {
|
||||||
m_platform->sendChar(action->character, false);
|
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)
|
void AutoTypeExecutorMac::execClearField(const AutoTypeClearField* action)
|
||||||
{
|
|
||||||
m_platform->sendKey(action->key, true);
|
|
||||||
m_platform->sendKey(action->key, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr)
|
|
||||||
{
|
{
|
||||||
Q_UNUSED(action);
|
Q_UNUSED(action);
|
||||||
|
execType(new AutoTypeKey(Qt::Key_Up, Qt::ControlModifier));
|
||||||
m_platform->sendKey(Qt::Key_Control, true, Qt::ControlModifier);
|
execType(new AutoTypeKey(Qt::Key_Down, Qt::ControlModifier | Qt::ShiftModifier));
|
||||||
m_platform->sendKey(Qt::Key_Up, true, Qt::ControlModifier);
|
execType(new AutoTypeKey(Qt::Key_Backspace));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
@ -57,9 +57,8 @@ class AutoTypeExecutorMac : public AutoTypeExecutor
|
|||||||
public:
|
public:
|
||||||
explicit AutoTypeExecutorMac(AutoTypePlatformMac* platform);
|
explicit AutoTypeExecutorMac(AutoTypePlatformMac* platform);
|
||||||
|
|
||||||
void execChar(AutoTypeChar* action) override;
|
void execType(const AutoTypeKey* action) override;
|
||||||
void execKey(AutoTypeKey* action) override;
|
void execClearField(const AutoTypeClearField* action) override;
|
||||||
void execClearField(AutoTypeClearField* action) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AutoTypePlatformMac* const m_platform;
|
AutoTypePlatformMac* const m_platform;
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
set(autotype_mac_SOURCES AutoTypeMac.cpp)
|
set(autotype_mac_SOURCES AutoTypeMac.cpp)
|
||||||
|
|
||||||
add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES})
|
add_library(keepassxc-autotype-cocoa MODULE ${autotype_mac_SOURCES})
|
||||||
set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
|
set_target_properties(keepassxc-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
|
||||||
target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
|
target_link_libraries(keepassxc-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
|
||||||
|
|
||||||
if(WITH_APP_BUNDLE)
|
if(WITH_APP_BUNDLE)
|
||||||
add_custom_command(TARGET keepassx-autotype-cocoa
|
add_custom_command(TARGET keepassxc-autotype-cocoa
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so
|
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}/libkeepassx-autotype-cocoa.so -no-plugins 2> /dev/null
|
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassxc-autotype-cocoa.so -no-plugins 2> /dev/null
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
|
||||||
COMMENT "Deploying autotype plugin")
|
COMMENT "Deploying autotype plugin")
|
||||||
else()
|
else()
|
||||||
install(TARGETS keepassx-autotype-cocoa
|
install(TARGETS keepassxc-autotype-cocoa
|
||||||
BUNDLE DESTINATION . COMPONENT Runtime
|
BUNDLE DESTINATION . COMPONENT Runtime
|
||||||
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
|
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
|
||||||
endif()
|
endif()
|
||||||
|
@ -59,27 +59,23 @@ QString AutoTypePlatformTest::actionChars()
|
|||||||
|
|
||||||
int AutoTypePlatformTest::actionCount()
|
int AutoTypePlatformTest::actionCount()
|
||||||
{
|
{
|
||||||
return m_actionList.size();
|
return m_actionCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoTypePlatformTest::clearActions()
|
void AutoTypePlatformTest::clearActions()
|
||||||
{
|
{
|
||||||
qDeleteAll(m_actionList);
|
|
||||||
m_actionList.clear();
|
|
||||||
|
|
||||||
m_actionChars.clear();
|
m_actionChars.clear();
|
||||||
|
m_actionCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoTypePlatformTest::addActionChar(AutoTypeChar* action)
|
void AutoTypePlatformTest::addAction(const AutoTypeKey* action)
|
||||||
{
|
{
|
||||||
m_actionList.append(action->clone());
|
++m_actionCount;
|
||||||
m_actionChars += action->character;
|
if (action->key != Qt::Key_unknown) {
|
||||||
}
|
m_actionChars += keyToString(action->key);
|
||||||
|
} else {
|
||||||
void AutoTypePlatformTest::addActionKey(AutoTypeKey* action)
|
m_actionChars += action->character;
|
||||||
{
|
}
|
||||||
m_actionList.append(action->clone());
|
|
||||||
m_actionChars.append(keyToString(action->key));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AutoTypePlatformTest::raiseWindow(WId window)
|
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;
|
int actionCount() override;
|
||||||
void clearActions() override;
|
void clearActions() override;
|
||||||
|
|
||||||
void addActionChar(AutoTypeChar* action);
|
void addAction(const AutoTypeKey* action);
|
||||||
void addActionKey(AutoTypeKey* action);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_activeWindowTitle;
|
QString m_activeWindowTitle;
|
||||||
QList<AutoTypeAction*> m_actionList;
|
int m_actionCount = 0;
|
||||||
QString m_actionChars;
|
QString m_actionChars;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,8 +64,8 @@ class AutoTypeExecutorTest : public AutoTypeExecutor
|
|||||||
public:
|
public:
|
||||||
explicit AutoTypeExecutorTest(AutoTypePlatformTest* platform);
|
explicit AutoTypeExecutorTest(AutoTypePlatformTest* platform);
|
||||||
|
|
||||||
void execChar(AutoTypeChar* action) override;
|
void execType(const AutoTypeKey* action) override;
|
||||||
void execKey(AutoTypeKey* action) override;
|
void execClearField(const AutoTypeClearField* action) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AutoTypePlatformTest* const m_platform;
|
AutoTypePlatformTest* const m_platform;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
set(autotype_test_SOURCES AutoTypeTest.cpp)
|
set(autotype_test_SOURCES AutoTypeTest.cpp)
|
||||||
|
|
||||||
add_library(keepassx-autotype-test MODULE ${autotype_test_SOURCES})
|
add_library(keepassxc-autotype-test MODULE ${autotype_test_SOURCES})
|
||||||
target_link_libraries(keepassx-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
|
target_link_libraries(keepassxc-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "AutoTypeWindows.h"
|
#include "AutoTypeWindows.h"
|
||||||
|
#include "core/Tools.h"
|
||||||
#include "gui/osutils/OSUtils.h"
|
#include "gui/osutils/OSUtils.h"
|
||||||
|
|
||||||
#include <VersionHelpers.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);
|
if (action->modifiers & Qt::ShiftModifier) {
|
||||||
m_platform->sendChar(action->character, false);
|
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)
|
void AutoTypeExecutorWin::execClearField(const AutoTypeClearField* action)
|
||||||
{
|
|
||||||
m_platform->sendKey(action->key, true);
|
|
||||||
m_platform->sendKey(action->key, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeExecutorWin::execClearField(AutoTypeClearField* action = nullptr)
|
|
||||||
{
|
{
|
||||||
Q_UNUSED(action);
|
Q_UNUSED(action);
|
||||||
|
execType(new AutoTypeKey(Qt::Key_Home, Qt::ControlModifier));
|
||||||
m_platform->sendKey(Qt::Key_Control, true);
|
execType(new AutoTypeKey(Qt::Key_End, Qt::ControlModifier | Qt::ShiftModifier));
|
||||||
m_platform->sendKey(Qt::Key_Home, true);
|
execType(new AutoTypeKey(Qt::Key_Backspace));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
@ -54,9 +54,8 @@ class AutoTypeExecutorWin : public AutoTypeExecutor
|
|||||||
public:
|
public:
|
||||||
explicit AutoTypeExecutorWin(AutoTypePlatformWin* platform);
|
explicit AutoTypeExecutorWin(AutoTypePlatformWin* platform);
|
||||||
|
|
||||||
void execChar(AutoTypeChar* action) override;
|
void execType(const AutoTypeKey* action) override;
|
||||||
void execKey(AutoTypeKey* action) override;
|
void execClearField(const AutoTypeClearField* action) override;
|
||||||
void execClearField(AutoTypeClearField* action) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AutoTypePlatformWin* const m_platform;
|
AutoTypePlatformWin* const m_platform;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
set(autotype_win_SOURCES AutoTypeWindows.cpp)
|
set(autotype_win_SOURCES AutoTypeWindows.cpp)
|
||||||
|
|
||||||
add_library(keepassx-autotype-windows MODULE ${autotype_win_SOURCES})
|
add_library(keepassxc-autotype-windows MODULE ${autotype_win_SOURCES})
|
||||||
target_link_libraries(keepassx-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
|
target_link_libraries(keepassxc-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
|
||||||
install(TARGETS keepassx-autotype-windows
|
install(TARGETS keepassxc-autotype-windows
|
||||||
BUNDLE DESTINATION . COMPONENT Runtime
|
BUNDLE DESTINATION . COMPONENT Runtime
|
||||||
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
|
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
|
||||||
|
@ -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)
|
void AutoTypeExecutorX11::execClearField(const AutoTypeClearField* action)
|
||||||
{
|
|
||||||
m_platform->sendKey(qtToNativeKeyCode(action->key));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr)
|
|
||||||
{
|
{
|
||||||
Q_UNUSED(action);
|
Q_UNUSED(action);
|
||||||
|
execType(new AutoTypeKey(Qt::Key_Home, Qt::ControlModifier));
|
||||||
timespec ts;
|
execType(new AutoTypeKey(Qt::Key_End, Qt::ControlModifier | Qt::ShiftModifier));
|
||||||
ts.tv_sec = 0;
|
execType(new AutoTypeKey(Qt::Key_Backspace));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AutoTypePlatformX11::raiseWindow(WId window)
|
bool AutoTypePlatformX11::raiseWindow(WId window)
|
||||||
|
@ -103,9 +103,8 @@ class AutoTypeExecutorX11 : public AutoTypeExecutor
|
|||||||
public:
|
public:
|
||||||
explicit AutoTypeExecutorX11(AutoTypePlatformX11* platform);
|
explicit AutoTypeExecutorX11(AutoTypePlatformX11* platform);
|
||||||
|
|
||||||
void execChar(AutoTypeChar* action) override;
|
void execType(const AutoTypeKey* action) override;
|
||||||
void execKey(AutoTypeKey* action) override;
|
void execClearField(const AutoTypeClearField* action) override;
|
||||||
void execClearField(AutoTypeClearField* action) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AutoTypePlatformX11* const m_platform;
|
AutoTypePlatformX11* const m_platform;
|
||||||
|
@ -2,8 +2,8 @@ include_directories(SYSTEM ${X11_X11_INCLUDE_PATH})
|
|||||||
|
|
||||||
set(autotype_XCB_SOURCES AutoTypeXCB.cpp)
|
set(autotype_XCB_SOURCES AutoTypeXCB.cpp)
|
||||||
|
|
||||||
add_library(keepassx-autotype-xcb MODULE ${autotype_XCB_SOURCES})
|
add_library(keepassxc-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})
|
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 keepassx-autotype-xcb
|
install(TARGETS keepassxc-autotype-xcb
|
||||||
BUNDLE DESTINATION . COMPONENT Runtime
|
BUNDLE DESTINATION . COMPONENT Runtime
|
||||||
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
|
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
|
||||||
|
@ -70,6 +70,10 @@ BrowserService::BrowserService()
|
|||||||
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
|
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
|
||||||
{
|
{
|
||||||
connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processClientMessage);
|
connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processClientMessage);
|
||||||
|
connect(getMainWindow(), &MainWindow::databaseUnlocked, this, &BrowserService::databaseUnlocked);
|
||||||
|
connect(getMainWindow(), &MainWindow::databaseLocked, this, &BrowserService::databaseLocked);
|
||||||
|
connect(getMainWindow(), &MainWindow::activeDatabaseChanged, this, &BrowserService::activeDatabaseChanged);
|
||||||
|
|
||||||
setEnabled(browserSettings()->isEnabled());
|
setEnabled(browserSettings()->isEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
|
||||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
|
||||||
* version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "AutoTypeMatch.h"
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
AutoTypeMatch::AutoTypeMatch()
|
|
||||||
: entry(nullptr)
|
|
||||||
, sequence()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTypeMatch::AutoTypeMatch(Entry* entry, QString sequence)
|
|
||||||
: entry(entry)
|
|
||||||
, sequence(std::move(sequence))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AutoTypeMatch::operator==(const AutoTypeMatch& other) const
|
|
||||||
{
|
|
||||||
return entry == other.entry && sequence == other.sequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AutoTypeMatch::operator!=(const AutoTypeMatch& other) const
|
|
||||||
{
|
|
||||||
return entry != other.entry || sequence != other.sequence;
|
|
||||||
}
|
|
@ -20,6 +20,7 @@
|
|||||||
#include "config-keepassx.h"
|
#include "config-keepassx.h"
|
||||||
|
|
||||||
#include "core/Clock.h"
|
#include "core/Clock.h"
|
||||||
|
#include "core/Config.h"
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
#include "core/DatabaseIcons.h"
|
#include "core/DatabaseIcons.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
@ -280,6 +281,75 @@ QString Entry::effectiveAutoTypeSequence() const
|
|||||||
return sequence;
|
return sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrive the autotype sequences matches for a given windowTitle
|
||||||
|
* This returns a list with priority ordering. If you don't want duplicates call .toSet() on it.
|
||||||
|
*/
|
||||||
|
QList<QString> Entry::autoTypeSequences(const QString& windowTitle) const
|
||||||
|
{
|
||||||
|
// If no window just return the effective sequence
|
||||||
|
if (windowTitle.isEmpty()) {
|
||||||
|
return {effectiveAutoTypeSequence()};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define helper functions to match window titles
|
||||||
|
auto windowMatches = [&](const QString& pattern) {
|
||||||
|
// Regex searching
|
||||||
|
if (pattern.startsWith("//") && pattern.endsWith("//") && pattern.size() >= 4) {
|
||||||
|
QRegExp regExp(pattern.mid(2, pattern.size() - 4), Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||||
|
return (regExp.indexIn(windowTitle) != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wildcard searching
|
||||||
|
auto regex = Tools::convertToRegex(pattern, true, false, false);
|
||||||
|
return windowTitle.contains(regex);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto windowMatchesTitle = [&](const QString& entryTitle) {
|
||||||
|
return !entryTitle.isEmpty() && windowTitle.contains(entryTitle, Qt::CaseInsensitive);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto windowMatchesUrl = [&](const QString& entryUrl) {
|
||||||
|
if (!entryUrl.isEmpty() && windowTitle.contains(entryUrl, Qt::CaseInsensitive)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url(entryUrl);
|
||||||
|
if (url.isValid() && !url.host().isEmpty()) {
|
||||||
|
return windowTitle.contains(url.host(), Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
QList<QString> sequenceList;
|
||||||
|
|
||||||
|
// Add window association matches
|
||||||
|
const auto assocList = autoTypeAssociations()->getAll();
|
||||||
|
for (const auto& assoc : assocList) {
|
||||||
|
auto window = resolveMultiplePlaceholders(assoc.window);
|
||||||
|
if (windowMatches(window)) {
|
||||||
|
if (!assoc.sequence.isEmpty()) {
|
||||||
|
sequenceList << assoc.sequence;
|
||||||
|
} else {
|
||||||
|
sequenceList << effectiveAutoTypeSequence();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to match window title
|
||||||
|
if (config()->get(Config::AutoTypeEntryTitleMatch).toBool() && windowMatchesTitle(resolvePlaceholder(title()))) {
|
||||||
|
sequenceList << effectiveAutoTypeSequence();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to match url in window title
|
||||||
|
if (config()->get(Config::AutoTypeEntryURLMatch).toBool() && windowMatchesUrl(resolvePlaceholder(url()))) {
|
||||||
|
sequenceList << effectiveAutoTypeSequence();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequenceList;
|
||||||
|
}
|
||||||
|
|
||||||
AutoTypeAssociations* Entry::autoTypeAssociations()
|
AutoTypeAssociations* Entry::autoTypeAssociations()
|
||||||
{
|
{
|
||||||
return m_autoTypeAssociations;
|
return m_autoTypeAssociations;
|
||||||
|
@ -95,6 +95,7 @@ public:
|
|||||||
QString defaultAutoTypeSequence() const;
|
QString defaultAutoTypeSequence() const;
|
||||||
QString effectiveAutoTypeSequence() const;
|
QString effectiveAutoTypeSequence() const;
|
||||||
QString effectiveNewAutoTypeSequence() const;
|
QString effectiveNewAutoTypeSequence() const;
|
||||||
|
QList<QString> autoTypeSequences(const QString& pattern = {}) const;
|
||||||
AutoTypeAssociations* autoTypeAssociations();
|
AutoTypeAssociations* autoTypeAssociations();
|
||||||
const AutoTypeAssociations* autoTypeAssociations() const;
|
const AutoTypeAssociations* autoTypeAssociations() const;
|
||||||
QString title() const;
|
QString title() const;
|
||||||
|
@ -25,12 +25,12 @@
|
|||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
#if defined(KEEPASSX_BUILDING_CORE)
|
#if defined(KEEPASSX_BUILDING_CORE)
|
||||||
#define KEEPASSX_EXPORT Q_DECL_IMPORT
|
#define KEEPASSXC_EXPORT Q_DECL_IMPORT
|
||||||
#else
|
#else
|
||||||
#define KEEPASSX_EXPORT Q_DECL_EXPORT
|
#define KEEPASSXC_EXPORT Q_DECL_EXPORT
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
#define KEEPASSX_EXPORT Q_DECL_EXPORT
|
#define KEEPASSXC_EXPORT Q_DECL_EXPORT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef QUINT32_MAX
|
#ifndef QUINT32_MAX
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
#ifndef KEEPASSXC_MACPASTEBOARD_H
|
#ifndef KEEPASSXC_MACPASTEBOARD_H
|
||||||
#define KEEPASSXC_MACPASTEBOARD_H
|
#define KEEPASSXC_MACPASTEBOARD_H
|
||||||
|
|
||||||
#include <QMacPasteboardMime>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTextCodec>
|
#include <QTextCodec>
|
||||||
|
#include <QtMacExtras/QMacPasteboardMime>
|
||||||
|
|
||||||
class MacPasteboard : public QObject, public QMacPasteboardMime
|
class MacPasteboard : public QObject, public QMacPasteboardMime
|
||||||
{
|
{
|
||||||
|
@ -82,7 +82,7 @@ namespace FdoSecrets
|
|||||||
});
|
});
|
||||||
|
|
||||||
// make default alias track current activated database
|
// make default alias track current activated database
|
||||||
connect(m_databases.data(), &DatabaseTabWidget::activateDatabaseChanged, this, &Service::ensureDefaultAlias);
|
connect(m_databases.data(), &DatabaseTabWidget::activeDatabaseChanged, this, &Service::ensureDefaultAlias);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -57,8 +57,8 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
|||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabaseTab(int)));
|
connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabaseTab(int)));
|
||||||
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged()));
|
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActiveDatabaseChanged()));
|
||||||
connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
|
connect(this, SIGNAL(activeDatabaseChanged(DatabaseWidget*)),
|
||||||
m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*)));
|
m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*)));
|
||||||
connect(autoType(), SIGNAL(globalAutoTypeTriggered()), SLOT(performGlobalAutoType()));
|
connect(autoType(), SIGNAL(globalAutoTypeTriggered()), SLOT(performGlobalAutoType()));
|
||||||
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
|
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
|
||||||
@ -715,9 +715,9 @@ void DatabaseTabWidget::updateLastDatabases(const QString& filename)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseTabWidget::emitActivateDatabaseChanged()
|
void DatabaseTabWidget::emitActiveDatabaseChanged()
|
||||||
{
|
{
|
||||||
emit activateDatabaseChanged(currentDatabaseWidget());
|
emit activeDatabaseChanged(currentDatabaseWidget());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseTabWidget::emitDatabaseLockChanged()
|
void DatabaseTabWidget::emitDatabaseLockChanged()
|
||||||
|
@ -89,7 +89,7 @@ signals:
|
|||||||
void databaseClosed(const QString& filePath);
|
void databaseClosed(const QString& filePath);
|
||||||
void databaseUnlocked(DatabaseWidget* dbWidget);
|
void databaseUnlocked(DatabaseWidget* dbWidget);
|
||||||
void databaseLocked(DatabaseWidget* dbWidget);
|
void databaseLocked(DatabaseWidget* dbWidget);
|
||||||
void activateDatabaseChanged(DatabaseWidget* dbWidget);
|
void activeDatabaseChanged(DatabaseWidget* dbWidget);
|
||||||
void tabNameChanged();
|
void tabNameChanged();
|
||||||
void tabVisibilityChanged(bool tabsVisible);
|
void tabVisibilityChanged(bool tabsVisible);
|
||||||
void messageGlobal(const QString&, MessageWidget::MessageType type);
|
void messageGlobal(const QString&, MessageWidget::MessageType type);
|
||||||
@ -98,7 +98,7 @@ signals:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void toggleTabbar();
|
void toggleTabbar();
|
||||||
void emitActivateDatabaseChanged();
|
void emitActiveDatabaseChanged();
|
||||||
void emitDatabaseLockChanged();
|
void emitDatabaseLockChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -759,44 +759,44 @@ void DatabaseWidget::removeFromAgent()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void DatabaseWidget::performAutoType()
|
void DatabaseWidget::performAutoType(const QString& sequence)
|
||||||
{
|
{
|
||||||
auto currentEntry = currentSelectedEntry();
|
auto currentEntry = currentSelectedEntry();
|
||||||
if (currentEntry) {
|
if (currentEntry) {
|
||||||
autoType()->performAutoType(currentEntry, window());
|
// TODO: Include name of previously active window in confirmation question
|
||||||
|
if (config()->get(Config::Security_AutoTypeAsk).toBool()
|
||||||
|
&& MessageBox::question(
|
||||||
|
this, tr("Confirm Auto-Type"), tr("Perform Auto-Type into the previously active window?"))
|
||||||
|
!= MessageBox::Yes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequence.isEmpty()) {
|
||||||
|
autoType()->performAutoType(currentEntry, window());
|
||||||
|
} else {
|
||||||
|
autoType()->performAutoTypeWithSequence(currentEntry, sequence, window());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseWidget::performAutoTypeUsername()
|
void DatabaseWidget::performAutoTypeUsername()
|
||||||
{
|
{
|
||||||
auto currentEntry = currentSelectedEntry();
|
performAutoType(QStringLiteral("{USERNAME}"));
|
||||||
if (currentEntry) {
|
|
||||||
autoType()->performAutoTypeWithSequence(currentEntry, QStringLiteral("{USERNAME}"), window());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseWidget::performAutoTypeUsernameEnter()
|
void DatabaseWidget::performAutoTypeUsernameEnter()
|
||||||
{
|
{
|
||||||
auto currentEntry = currentSelectedEntry();
|
performAutoType(QStringLiteral("{USERNAME}{ENTER}"));
|
||||||
if (currentEntry) {
|
|
||||||
autoType()->performAutoTypeWithSequence(currentEntry, QStringLiteral("{USERNAME}{ENTER}"), window());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseWidget::performAutoTypePassword()
|
void DatabaseWidget::performAutoTypePassword()
|
||||||
{
|
{
|
||||||
auto currentEntry = currentSelectedEntry();
|
performAutoType(QStringLiteral("{PASSWORD}"));
|
||||||
if (currentEntry) {
|
|
||||||
autoType()->performAutoTypeWithSequence(currentEntry, QStringLiteral("{PASSWORD}"), window());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseWidget::performAutoTypePasswordEnter()
|
void DatabaseWidget::performAutoTypePasswordEnter()
|
||||||
{
|
{
|
||||||
auto currentEntry = currentSelectedEntry();
|
performAutoType(QStringLiteral("{PASSWORD}{ENTER}"));
|
||||||
if (currentEntry) {
|
|
||||||
autoType()->performAutoTypeWithSequence(currentEntry, QStringLiteral("{PASSWORD}{ENTER}"), window());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseWidget::openUrl()
|
void DatabaseWidget::openUrl()
|
||||||
|
@ -105,6 +105,7 @@ public:
|
|||||||
QStringList customEntryAttributes() const;
|
QStringList customEntryAttributes() const;
|
||||||
bool isEditWidgetModified() const;
|
bool isEditWidgetModified() const;
|
||||||
void clearAllWidgets();
|
void clearAllWidgets();
|
||||||
|
Entry* currentSelectedEntry();
|
||||||
bool currentEntryHasTitle();
|
bool currentEntryHasTitle();
|
||||||
bool currentEntryHasUsername();
|
bool currentEntryHasUsername();
|
||||||
bool currentEntryHasPassword();
|
bool currentEntryHasPassword();
|
||||||
@ -181,7 +182,7 @@ public slots:
|
|||||||
void addToAgent();
|
void addToAgent();
|
||||||
void removeFromAgent();
|
void removeFromAgent();
|
||||||
#endif
|
#endif
|
||||||
void performAutoType();
|
void performAutoType(const QString& sequence = {});
|
||||||
void performAutoTypeUsername();
|
void performAutoTypeUsername();
|
||||||
void performAutoTypeUsernameEnter();
|
void performAutoTypeUsernameEnter();
|
||||||
void performAutoTypePassword();
|
void performAutoTypePassword();
|
||||||
@ -257,7 +258,6 @@ private:
|
|||||||
bool confirmDeleteEntries(QList<Entry*> entries, bool permanent);
|
bool confirmDeleteEntries(QList<Entry*> entries, bool permanent);
|
||||||
void performIconDownloads(const QList<Entry*>& entries, bool force = false);
|
void performIconDownloads(const QList<Entry*>& entries, bool force = false);
|
||||||
bool performSave(QString& errorMessage, const QString& fileName = {});
|
bool performSave(QString& errorMessage, const QString& fileName = {});
|
||||||
Entry* currentSelectedEntry();
|
|
||||||
|
|
||||||
QSharedPointer<Database> m_db;
|
QSharedPointer<Database> m_db;
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QToolButton>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
|
|
||||||
#include "config-keepassx.h"
|
#include "config-keepassx.h"
|
||||||
@ -128,7 +129,6 @@ MainWindow::MainWindow()
|
|||||||
m_entryContextMenu->addAction(m_ui->menuEntryTotp->menuAction());
|
m_entryContextMenu->addAction(m_ui->menuEntryTotp->menuAction());
|
||||||
m_entryContextMenu->addSeparator();
|
m_entryContextMenu->addSeparator();
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryAutoType);
|
m_entryContextMenu->addAction(m_ui->actionEntryAutoType);
|
||||||
m_entryContextMenu->addAction(m_ui->menuEntryAutoTypeWithSequence->menuAction());
|
|
||||||
m_entryContextMenu->addSeparator();
|
m_entryContextMenu->addSeparator();
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryEdit);
|
m_entryContextMenu->addAction(m_ui->actionEntryEdit);
|
||||||
m_entryContextMenu->addAction(m_ui->actionEntryClone);
|
m_entryContextMenu->addAction(m_ui->actionEntryClone);
|
||||||
@ -144,16 +144,29 @@ MainWindow::MainWindow()
|
|||||||
m_entryNewContextMenu = new QMenu(this);
|
m_entryNewContextMenu = new QMenu(this);
|
||||||
m_entryNewContextMenu->addAction(m_ui->actionEntryNew);
|
m_entryNewContextMenu->addAction(m_ui->actionEntryNew);
|
||||||
|
|
||||||
|
// Build Entry Level Auto-Type menu
|
||||||
|
auto autotypeMenu = new QMenu({}, this);
|
||||||
|
autotypeMenu->addAction(m_ui->actionEntryAutoTypeSequence);
|
||||||
|
autotypeMenu->addSeparator();
|
||||||
|
autotypeMenu->addAction(m_ui->actionEntryAutoTypeUsername);
|
||||||
|
autotypeMenu->addAction(m_ui->actionEntryAutoTypeUsernameEnter);
|
||||||
|
autotypeMenu->addAction(m_ui->actionEntryAutoTypePassword);
|
||||||
|
autotypeMenu->addAction(m_ui->actionEntryAutoTypePasswordEnter);
|
||||||
|
m_ui->actionEntryAutoType->setMenu(autotypeMenu);
|
||||||
|
auto autoTypeButton = qobject_cast<QToolButton*>(m_ui->toolBar->widgetForAction(m_ui->actionEntryAutoType));
|
||||||
|
if (autoTypeButton) {
|
||||||
|
autoTypeButton->setPopupMode(QToolButton::MenuButtonPopup);
|
||||||
|
}
|
||||||
|
|
||||||
restoreGeometry(config()->get(Config::GUI_MainWindowGeometry).toByteArray());
|
restoreGeometry(config()->get(Config::GUI_MainWindowGeometry).toByteArray());
|
||||||
restoreState(config()->get(Config::GUI_MainWindowState).toByteArray());
|
restoreState(config()->get(Config::GUI_MainWindowState).toByteArray());
|
||||||
|
|
||||||
|
connect(m_ui->tabWidget, &DatabaseTabWidget::databaseLocked, this, &MainWindow::databaseLocked);
|
||||||
|
connect(m_ui->tabWidget, &DatabaseTabWidget::databaseUnlocked, this, &MainWindow::databaseUnlocked);
|
||||||
|
connect(m_ui->tabWidget, &DatabaseTabWidget::activeDatabaseChanged, this, &MainWindow::activeDatabaseChanged);
|
||||||
|
|
||||||
#ifdef WITH_XC_BROWSER
|
#ifdef WITH_XC_BROWSER
|
||||||
m_ui->settingsWidget->addSettingsPage(new BrowserSettingsPage());
|
m_ui->settingsWidget->addSettingsPage(new BrowserSettingsPage());
|
||||||
connect(m_ui->tabWidget, &DatabaseTabWidget::databaseLocked, browserService(), &BrowserService::databaseLocked);
|
|
||||||
connect(m_ui->tabWidget, &DatabaseTabWidget::databaseUnlocked, browserService(), &BrowserService::databaseUnlocked);
|
|
||||||
connect(m_ui->tabWidget,
|
|
||||||
&DatabaseTabWidget::activateDatabaseChanged,
|
|
||||||
browserService(),
|
|
||||||
&BrowserService::activeDatabaseChanged);
|
|
||||||
connect(
|
connect(
|
||||||
browserService(), &BrowserService::requestUnlock, m_ui->tabWidget, &DatabaseTabWidget::performBrowserUnlock);
|
browserService(), &BrowserService::requestUnlock, m_ui->tabWidget, &DatabaseTabWidget::performBrowserUnlock);
|
||||||
#endif
|
#endif
|
||||||
@ -223,12 +236,7 @@ MainWindow::MainWindow()
|
|||||||
m_ui->toolbarSeparator->setVisible(false);
|
m_ui->toolbarSeparator->setVisible(false);
|
||||||
m_showToolbarSeparator = config()->get(Config::GUI_ApplicationTheme).toString() != "classic";
|
m_showToolbarSeparator = config()->get(Config::GUI_ApplicationTheme).toString() != "classic";
|
||||||
|
|
||||||
bool isAutoTypeAvailable = autoType()->isAvailable();
|
m_ui->actionEntryAutoType->setVisible(autoType()->isAvailable());
|
||||||
m_ui->actionEntryAutoType->setVisible(isAutoTypeAvailable);
|
|
||||||
m_ui->actionEntryAutoTypeUsername->setVisible(isAutoTypeAvailable);
|
|
||||||
m_ui->actionEntryAutoTypeUsernameEnter->setVisible(isAutoTypeAvailable);
|
|
||||||
m_ui->actionEntryAutoTypePassword->setVisible(isAutoTypeAvailable);
|
|
||||||
m_ui->actionEntryAutoTypePasswordEnter->setVisible(isAutoTypeAvailable);
|
|
||||||
|
|
||||||
m_inactivityTimer = new InactivityTimer(this);
|
m_inactivityTimer = new InactivityTimer(this);
|
||||||
connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity()));
|
connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity()));
|
||||||
@ -257,7 +265,7 @@ MainWindow::MainWindow()
|
|||||||
m_ui->actionEntryMoveDown->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_Down);
|
m_ui->actionEntryMoveDown->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_Down);
|
||||||
m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B);
|
m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B);
|
||||||
m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C);
|
m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C);
|
||||||
m_ui->actionEntryAutoType->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V);
|
m_ui->actionEntryAutoTypeSequence->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V);
|
||||||
m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U);
|
m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U);
|
||||||
m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::Key_U);
|
m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::Key_U);
|
||||||
|
|
||||||
@ -284,7 +292,7 @@ MainWindow::MainWindow()
|
|||||||
m_ui->actionEntryMoveDown->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryMoveDown->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryCopyUsername->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryCopyUsername->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryCopyPassword->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryCopyPassword->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryAutoType->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryAutoTypeSequence->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryOpenUrl->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryOpenUrl->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryCopyURL->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryCopyURL->setShortcutVisibleInContextMenu(true);
|
||||||
m_ui->actionEntryAddToAgent->setShortcutVisibleInContextMenu(true);
|
m_ui->actionEntryAddToAgent->setShortcutVisibleInContextMenu(true);
|
||||||
@ -360,7 +368,7 @@ MainWindow::MainWindow()
|
|||||||
m_ui->actionEntryEdit->setIcon(icons()->icon("entry-edit"));
|
m_ui->actionEntryEdit->setIcon(icons()->icon("entry-edit"));
|
||||||
m_ui->actionEntryDelete->setIcon(icons()->icon("entry-delete"));
|
m_ui->actionEntryDelete->setIcon(icons()->icon("entry-delete"));
|
||||||
m_ui->actionEntryAutoType->setIcon(icons()->icon("auto-type"));
|
m_ui->actionEntryAutoType->setIcon(icons()->icon("auto-type"));
|
||||||
m_ui->menuEntryAutoTypeWithSequence->setIcon(icons()->icon("auto-type"));
|
m_ui->actionEntryAutoTypeSequence->setIcon(icons()->icon("auto-type"));
|
||||||
m_ui->actionEntryAutoTypeUsername->setIcon(icons()->icon("auto-type"));
|
m_ui->actionEntryAutoTypeUsername->setIcon(icons()->icon("auto-type"));
|
||||||
m_ui->actionEntryAutoTypeUsernameEnter->setIcon(icons()->icon("auto-type"));
|
m_ui->actionEntryAutoTypeUsernameEnter->setIcon(icons()->icon("auto-type"));
|
||||||
m_ui->actionEntryAutoTypePassword->setIcon(icons()->icon("auto-type"));
|
m_ui->actionEntryAutoTypePassword->setIcon(icons()->icon("auto-type"));
|
||||||
@ -402,7 +410,7 @@ MainWindow::MainWindow()
|
|||||||
|
|
||||||
// Notify search when the active database changes or gets locked
|
// Notify search when the active database changes or gets locked
|
||||||
connect(m_ui->tabWidget,
|
connect(m_ui->tabWidget,
|
||||||
SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
|
SIGNAL(activeDatabaseChanged(DatabaseWidget*)),
|
||||||
m_searchWidget,
|
m_searchWidget,
|
||||||
SLOT(databaseChanged(DatabaseWidget*)));
|
SLOT(databaseChanged(DatabaseWidget*)));
|
||||||
connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), m_searchWidget, SLOT(databaseChanged()));
|
connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), m_searchWidget, SLOT(databaseChanged()));
|
||||||
@ -458,6 +466,7 @@ MainWindow::MainWindow()
|
|||||||
m_actionMultiplexer.connect(m_ui->actionEntryCopyURL, SIGNAL(triggered()), SLOT(copyURL()));
|
m_actionMultiplexer.connect(m_ui->actionEntryCopyURL, SIGNAL(triggered()), SLOT(copyURL()));
|
||||||
m_actionMultiplexer.connect(m_ui->actionEntryCopyNotes, SIGNAL(triggered()), SLOT(copyNotes()));
|
m_actionMultiplexer.connect(m_ui->actionEntryCopyNotes, SIGNAL(triggered()), SLOT(copyNotes()));
|
||||||
m_actionMultiplexer.connect(m_ui->actionEntryAutoType, SIGNAL(triggered()), SLOT(performAutoType()));
|
m_actionMultiplexer.connect(m_ui->actionEntryAutoType, SIGNAL(triggered()), SLOT(performAutoType()));
|
||||||
|
m_actionMultiplexer.connect(m_ui->actionEntryAutoTypeSequence, SIGNAL(triggered()), SLOT(performAutoType()));
|
||||||
m_actionMultiplexer.connect(
|
m_actionMultiplexer.connect(
|
||||||
m_ui->actionEntryAutoTypeUsername, SIGNAL(triggered()), SLOT(performAutoTypeUsername()));
|
m_ui->actionEntryAutoTypeUsername, SIGNAL(triggered()), SLOT(performAutoTypeUsername()));
|
||||||
m_actionMultiplexer.connect(
|
m_actionMultiplexer.connect(
|
||||||
@ -782,7 +791,11 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
|||||||
m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
|
m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
|
||||||
m_ui->menuEntryTotp->setEnabled(singleEntrySelected);
|
m_ui->menuEntryTotp->setEnabled(singleEntrySelected);
|
||||||
m_ui->actionEntryAutoType->setEnabled(singleEntrySelected);
|
m_ui->actionEntryAutoType->setEnabled(singleEntrySelected);
|
||||||
m_ui->menuEntryAutoTypeWithSequence->setEnabled(singleEntrySelected);
|
m_ui->actionEntryAutoType->menu()->setEnabled(singleEntrySelected);
|
||||||
|
m_ui->actionEntryAutoTypeSequence->setText(
|
||||||
|
singleEntrySelected ? dbWidget->currentSelectedEntry()->effectiveAutoTypeSequence()
|
||||||
|
: Group::RootAutoTypeSequence);
|
||||||
|
m_ui->actionEntryAutoTypeSequence->setEnabled(singleEntrySelected);
|
||||||
m_ui->actionEntryAutoTypeUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername());
|
m_ui->actionEntryAutoTypeUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername());
|
||||||
m_ui->actionEntryAutoTypeUsernameEnter->setEnabled(singleEntrySelected
|
m_ui->actionEntryAutoTypeUsernameEnter->setEnabled(singleEntrySelected
|
||||||
&& dbWidget->currentEntryHasUsername());
|
&& dbWidget->currentEntryHasUsername());
|
||||||
@ -841,7 +854,6 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
|||||||
m_ui->actionEntryCopyURL,
|
m_ui->actionEntryCopyURL,
|
||||||
m_ui->actionEntryOpenUrl,
|
m_ui->actionEntryOpenUrl,
|
||||||
m_ui->actionEntryAutoType,
|
m_ui->actionEntryAutoType,
|
||||||
m_ui->menuEntryAutoTypeWithSequence->menuAction(),
|
|
||||||
m_ui->actionEntryDownloadIcon,
|
m_ui->actionEntryDownloadIcon,
|
||||||
m_ui->actionEntryCopyNotes,
|
m_ui->actionEntryCopyNotes,
|
||||||
m_ui->actionEntryCopyTitle,
|
m_ui->actionEntryCopyTitle,
|
||||||
@ -1601,6 +1613,13 @@ void MainWindow::toggleWindow()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::closeModalWindow()
|
||||||
|
{
|
||||||
|
if (qApp->modalWindow()) {
|
||||||
|
qApp->modalWindow()->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::lockDatabasesAfterInactivity()
|
void MainWindow::lockDatabasesAfterInactivity()
|
||||||
{
|
{
|
||||||
m_ui->tabWidget->lockDatabases();
|
m_ui->tabWidget->lockDatabases();
|
||||||
|
@ -60,6 +60,11 @@ public:
|
|||||||
PasswordGeneratorScreen = 3
|
PasswordGeneratorScreen = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void databaseUnlocked(DatabaseWidget* dbWidget);
|
||||||
|
void databaseLocked(DatabaseWidget* dbWidget);
|
||||||
|
void activeDatabaseChanged(DatabaseWidget* dbWidget);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void openDatabase(const QString& filePath, const QString& password = {}, const QString& keyfile = {});
|
void openDatabase(const QString& filePath, const QString& password = {}, const QString& keyfile = {});
|
||||||
void appExit();
|
void appExit();
|
||||||
@ -82,6 +87,7 @@ public slots:
|
|||||||
void bringToFront();
|
void bringToFront();
|
||||||
void closeAllDatabases();
|
void closeAllDatabases();
|
||||||
void lockAllDatabases();
|
void lockAllDatabases();
|
||||||
|
void closeModalWindow();
|
||||||
void displayDesktopNotification(const QString& msg, QString title = "", int msTimeoutHint = 10000);
|
void displayDesktopNotification(const QString& msg, QString title = "", int msTimeoutHint = 10000);
|
||||||
void restartApp(const QString& message);
|
void restartApp(const QString& message);
|
||||||
|
|
||||||
@ -136,8 +142,6 @@ private slots:
|
|||||||
void obtainContextFocusLock();
|
void obtainContextFocusLock();
|
||||||
void releaseContextFocusLock();
|
void releaseContextFocusLock();
|
||||||
void agentEnabled(bool enabled);
|
void agentEnabled(bool enabled);
|
||||||
|
|
||||||
private slots:
|
|
||||||
void updateTrayIcon();
|
void updateTrayIcon();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -310,18 +310,6 @@
|
|||||||
<addaction name="actionEntryTotpQRCode"/>
|
<addaction name="actionEntryTotpQRCode"/>
|
||||||
<addaction name="actionEntrySetupTotp"/>
|
<addaction name="actionEntrySetupTotp"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuEntryAutoTypeWithSequence">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="title">
|
|
||||||
<string>Perform Auto-Type Sequence</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionEntryAutoTypeUsername"/>
|
|
||||||
<addaction name="actionEntryAutoTypeUsernameEnter"/>
|
|
||||||
<addaction name="actionEntryAutoTypePassword"/>
|
|
||||||
<addaction name="actionEntryAutoTypePasswordEnter"/>
|
|
||||||
</widget>
|
|
||||||
<addaction name="actionEntryNew"/>
|
<addaction name="actionEntryNew"/>
|
||||||
<addaction name="actionEntryEdit"/>
|
<addaction name="actionEntryEdit"/>
|
||||||
<addaction name="actionEntryClone"/>
|
<addaction name="actionEntryClone"/>
|
||||||
@ -336,7 +324,6 @@
|
|||||||
<addaction name="menuEntryTotp"/>
|
<addaction name="menuEntryTotp"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionEntryAutoType"/>
|
<addaction name="actionEntryAutoType"/>
|
||||||
<addaction name="menuEntryAutoTypeWithSequence"/>
|
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionEntryOpenUrl"/>
|
<addaction name="actionEntryOpenUrl"/>
|
||||||
<addaction name="actionEntryDownloadIcon"/>
|
<addaction name="actionEntryDownloadIcon"/>
|
||||||
@ -704,6 +691,12 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true">{USERNAME}</string>
|
<string notr="true">{USERNAME}</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="iconText">
|
||||||
|
<string notr="true">{USERNAME}</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string notr="true">{USERNAME}</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionEntryAutoTypeUsernameEnter">
|
<action name="actionEntryAutoTypeUsernameEnter">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
@ -712,6 +705,12 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true">{USERNAME}{ENTER}</string>
|
<string notr="true">{USERNAME}{ENTER}</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="iconText">
|
||||||
|
<string notr="true">{USERNAME}{ENTER}</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string notr="true">{USERNAME}{ENTER}</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionEntryAutoTypePassword">
|
<action name="actionEntryAutoTypePassword">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
@ -720,6 +719,12 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true">{PASSWORD}</string>
|
<string notr="true">{PASSWORD}</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="iconText">
|
||||||
|
<string notr="true">{PASSWORD}</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string notr="true">{PASSWORD}</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionEntryAutoTypePasswordEnter">
|
<action name="actionEntryAutoTypePasswordEnter">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
@ -728,6 +733,12 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true">{PASSWORD}{ENTER}</string>
|
<string notr="true">{PASSWORD}{ENTER}</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="iconText">
|
||||||
|
<string notr="true">{PASSWORD}{ENTER}</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string notr="true">{PASSWORD}{ENTER}</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionEntryDownloadIcon">
|
<action name="actionEntryDownloadIcon">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -1023,6 +1034,17 @@
|
|||||||
<string notr="true">Ctrl+Shift+C</string>
|
<string notr="true">Ctrl+Shift+C</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionEntryAutoTypeSequence">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">{USERNAME}{TAB}{PASSWORD}{ENTER}</string>
|
||||||
|
</property>
|
||||||
|
<property name="iconText">
|
||||||
|
<string notr="true">{USERNAME}{TAB}{PASSWORD}{ENTER}</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string notr="true">{USERNAME}{TAB}{PASSWORD}{ENTER}</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
@ -118,7 +118,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="label_16">
|
<widget class="QLabel" name="label_25">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>10</width>
|
<width>10</width>
|
||||||
|
@ -51,8 +51,8 @@ SearchWidget::SearchWidget(QWidget* parent)
|
|||||||
connect(m_clearSearchTimer, SIGNAL(timeout()), m_ui->searchEdit, SLOT(clear()));
|
connect(m_clearSearchTimer, SIGNAL(timeout()), m_ui->searchEdit, SLOT(clear()));
|
||||||
connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear()));
|
connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear()));
|
||||||
|
|
||||||
new QShortcut(QKeySequence::Find, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut);
|
new QShortcut(QKeySequence::Find, this, SLOT(searchFocus()));
|
||||||
new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut);
|
new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()));
|
||||||
|
|
||||||
m_ui->searchEdit->setPlaceholderText(tr("Search (%1)…", "Search placeholder text, %1 is the keyboard shortcut")
|
m_ui->searchEdit->setPlaceholderText(tr("Search (%1)…", "Search placeholder text, %1 is the keyboard shortcut")
|
||||||
.arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText)));
|
.arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText)));
|
||||||
|
@ -1,154 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
|
||||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
|
||||||
* version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "AutoTypeMatchView.h"
|
|
||||||
|
|
||||||
#include "core/Entry.h"
|
|
||||||
#include "gui/Clipboard.h"
|
|
||||||
#include "gui/SortFilterHideProxyModel.h"
|
|
||||||
|
|
||||||
#include <QAction>
|
|
||||||
#include <QHeaderView>
|
|
||||||
#include <QKeyEvent>
|
|
||||||
|
|
||||||
AutoTypeMatchView::AutoTypeMatchView(QWidget* parent)
|
|
||||||
: QTreeView(parent)
|
|
||||||
, m_model(new AutoTypeMatchModel(this))
|
|
||||||
, m_sortModel(new SortFilterHideProxyModel(this))
|
|
||||||
{
|
|
||||||
m_sortModel->setSourceModel(m_model);
|
|
||||||
m_sortModel->setDynamicSortFilter(true);
|
|
||||||
m_sortModel->setSortLocaleAware(true);
|
|
||||||
m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
|
||||||
setModel(m_sortModel);
|
|
||||||
|
|
||||||
setUniformRowHeights(true);
|
|
||||||
setRootIsDecorated(false);
|
|
||||||
setAlternatingRowColors(true);
|
|
||||||
setDragEnabled(false);
|
|
||||||
setSortingEnabled(true);
|
|
||||||
setSelectionMode(QAbstractItemView::SingleSelection);
|
|
||||||
header()->setDefaultSectionSize(150);
|
|
||||||
|
|
||||||
setContextMenuPolicy(Qt::ActionsContextMenu);
|
|
||||||
auto* copyUserNameAction = new QAction(tr("Copy &username"), this);
|
|
||||||
auto* copyPasswordAction = new QAction(tr("Copy &password"), this);
|
|
||||||
addAction(copyUserNameAction);
|
|
||||||
addAction(copyPasswordAction);
|
|
||||||
|
|
||||||
connect(copyUserNameAction, SIGNAL(triggered()), this, SLOT(userNameCopied()));
|
|
||||||
connect(copyPasswordAction, SIGNAL(triggered()), this, SLOT(passwordCopied()));
|
|
||||||
|
|
||||||
connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex)));
|
|
||||||
// clang-format off
|
|
||||||
connect(selectionModel(),
|
|
||||||
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
|
||||||
SIGNAL(matchSelectionChanged()));
|
|
||||||
// clang-format on
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeMatchView::userNameCopied()
|
|
||||||
{
|
|
||||||
clipboard()->setText(currentMatch().entry->username());
|
|
||||||
emit matchTextCopied();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeMatchView::passwordCopied()
|
|
||||||
{
|
|
||||||
clipboard()->setText(currentMatch().entry->password());
|
|
||||||
emit matchTextCopied();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeMatchView::keyPressEvent(QKeyEvent* event)
|
|
||||||
{
|
|
||||||
if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && currentIndex().isValid()) {
|
|
||||||
emitMatchActivated(currentIndex());
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
// Pressing return does not emit the QTreeView::activated signal on mac os
|
|
||||||
emit activated(currentIndex());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
QTreeView::keyPressEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeMatchView::setMatchList(const QList<AutoTypeMatch>& matches)
|
|
||||||
{
|
|
||||||
m_model->setMatchList(matches);
|
|
||||||
|
|
||||||
bool sameSequences = true;
|
|
||||||
if (matches.count() > 1) {
|
|
||||||
QString sequenceTest = matches[0].sequence;
|
|
||||||
for (const auto& match : matches) {
|
|
||||||
if (match.sequence != sequenceTest) {
|
|
||||||
sameSequences = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setColumnHidden(AutoTypeMatchModel::Sequence, sameSequences);
|
|
||||||
|
|
||||||
for (int i = 0; i < m_model->columnCount(); ++i) {
|
|
||||||
resizeColumnToContents(i);
|
|
||||||
if (columnWidth(i) > 250) {
|
|
||||||
setColumnWidth(i, 250);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setFirstMatchActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeMatchView::setFirstMatchActive()
|
|
||||||
{
|
|
||||||
if (m_model->rowCount() > 0) {
|
|
||||||
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0));
|
|
||||||
setCurrentMatch(m_model->matchFromIndex(index));
|
|
||||||
} else {
|
|
||||||
emit matchSelectionChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeMatchView::emitMatchActivated(const QModelIndex& index)
|
|
||||||
{
|
|
||||||
AutoTypeMatch match = matchFromIndex(index);
|
|
||||||
|
|
||||||
emit matchActivated(match);
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTypeMatch AutoTypeMatchView::currentMatch()
|
|
||||||
{
|
|
||||||
QModelIndexList list = selectionModel()->selectedRows();
|
|
||||||
if (list.size() == 1) {
|
|
||||||
return m_model->matchFromIndex(m_sortModel->mapToSource(list.first()));
|
|
||||||
}
|
|
||||||
return AutoTypeMatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoTypeMatchView::setCurrentMatch(const AutoTypeMatch& match)
|
|
||||||
{
|
|
||||||
selectionModel()->setCurrentIndex(m_sortModel->mapFromSource(m_model->indexFromMatch(match)),
|
|
||||||
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTypeMatch AutoTypeMatchView::matchFromIndex(const QModelIndex& index)
|
|
||||||
{
|
|
||||||
if (index.isValid()) {
|
|
||||||
return m_model->matchFromIndex(m_sortModel->mapToSource(index));
|
|
||||||
}
|
|
||||||
return AutoTypeMatch();
|
|
||||||
}
|
|
@ -1028,8 +1028,36 @@ bool EditEntryWidget::commitEntry()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check Auto-Type validity early
|
// Check Auto-Type validity early
|
||||||
if (!AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text())) {
|
QString error;
|
||||||
return false;
|
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()) {
|
if (m_advancedUi->attributesView->currentIndex().isValid() && m_advancedUi->attributesEdit->isEnabled()) {
|
||||||
|
@ -99,6 +99,8 @@ KeySym qtToNativeKeyCode(Qt::Key key)
|
|||||||
default:
|
default:
|
||||||
if (key >= Qt::Key_F1 && key <= Qt::Key_F16) {
|
if (key >= Qt::Key_F1 && key <= Qt::Key_F16) {
|
||||||
return XK_F1 + (key - Qt::Key_F1);
|
return XK_F1 + (key - Qt::Key_F1);
|
||||||
|
} else if (key >= Qt::Key_Space && key <= Qt::Key_AsciiTilde) {
|
||||||
|
return key && 0xff;
|
||||||
} else {
|
} else {
|
||||||
return NoSymbol;
|
return NoSymbol;
|
||||||
}
|
}
|
||||||
|
@ -149,9 +149,6 @@ add_unit_test(NAME testkeepass1reader SOURCES TestKeePass1Reader.cpp
|
|||||||
add_unit_test(NAME testopvaultreader SOURCES TestOpVaultReader.cpp
|
add_unit_test(NAME testopvaultreader SOURCES TestOpVaultReader.cpp
|
||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
|
||||||
add_unit_test(NAME testwildcardmatcher SOURCES TestWildcardMatcher.cpp
|
|
||||||
LIBS ${TEST_LIBRARIES})
|
|
||||||
|
|
||||||
if(WITH_XC_NETWORKING)
|
if(WITH_XC_NETWORKING)
|
||||||
add_unit_test(NAME testupdatecheck SOURCES TestUpdateCheck.cpp
|
add_unit_test(NAME testupdatecheck SOURCES TestUpdateCheck.cpp
|
||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
@ -40,7 +40,7 @@ void TestAutoType::initTestCase()
|
|||||||
config()->set(Config::Security_AutoTypeAsk, false);
|
config()->set(Config::Security_AutoTypeAsk, false);
|
||||||
AutoType::createTestInstance();
|
AutoType::createTestInstance();
|
||||||
|
|
||||||
QPluginLoader loader(resources()->pluginPath("keepassx-autotype-test"));
|
QPluginLoader loader(resources()->pluginPath("keepassxc-autotype-test"));
|
||||||
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
||||||
QVERIFY(loader.instance());
|
QVERIFY(loader.instance());
|
||||||
|
|
||||||
@ -278,45 +278,93 @@ void TestAutoType::testGlobalAutoTypeRegExp()
|
|||||||
m_test->clearActions();
|
m_test->clearActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestAutoType::testAutoTypeResults()
|
||||||
|
{
|
||||||
|
QScopedPointer<Entry> entry(new Entry());
|
||||||
|
entry->setUsername("Username");
|
||||||
|
entry->setPassword("Password@1");
|
||||||
|
entry->setUrl("https://example.com");
|
||||||
|
entry->attributes()->set("attr1", "value1");
|
||||||
|
entry->attributes()->set("attr2", "decode%20me");
|
||||||
|
|
||||||
|
QFETCH(QString, sequence);
|
||||||
|
QFETCH(QString, expectedResult);
|
||||||
|
|
||||||
|
m_autoType->performAutoTypeWithSequence(entry.data(), sequence);
|
||||||
|
QCOMPARE(m_test->actionChars(), expectedResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAutoType::testAutoTypeResults_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("sequence");
|
||||||
|
QTest::addColumn<QString>("expectedResult");
|
||||||
|
|
||||||
|
// Normal Sequences
|
||||||
|
QTest::newRow("Sequence with Attributes") << QString("{USERNAME} {PASSWORD} {URL} {S:attr1}")
|
||||||
|
<< QString("Username Password@1 https://example.com value1");
|
||||||
|
QTest::newRow("Sequence with Comment") << QString("{USERNAME}{TAB}{C:Extra Tab}{TAB}{S:attr1}")
|
||||||
|
<< QString("Username[Key0x1000001][Key0x1000001]value1");
|
||||||
|
|
||||||
|
// Conversions and Replacements
|
||||||
|
QTest::newRow("T-CONV UPPER") << QString("{T-CONV:/{USERNAME}/UPPER/}") << QString("USERNAME");
|
||||||
|
QTest::newRow("T-CONV LOWER") << QString("{T-CONV:/{USERNAME}/LOWER/}") << QString("username");
|
||||||
|
QTest::newRow("T-CONV BASE64") << QString("{T-CONV:/{USERNAME}/BASE64/}") << QString("VXNlcm5hbWU=");
|
||||||
|
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)/$1Pass/}") << QString("UserPassname");
|
||||||
|
}
|
||||||
|
|
||||||
void TestAutoType::testAutoTypeSyntaxChecks()
|
void TestAutoType::testAutoTypeSyntaxChecks()
|
||||||
{
|
{
|
||||||
|
auto entry = new Entry();
|
||||||
|
QString error;
|
||||||
|
|
||||||
// Huge sequence
|
// Huge sequence
|
||||||
QVERIFY(AutoType::checkSyntax(
|
QVERIFY2(AutoType::verifyAutoTypeSyntax(
|
||||||
"{word 23}{F1 23}{~ 23}{% 23}{^}{F12}{(}{) 23}{[}{[}{]}{Delay=23}{+}{SUBTRACT}~+%@fixedstring"));
|
"{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}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIALTOKEN}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{S:SPECIAL TOKEN}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL TOKEN}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{S:SPECIAL_TOKEN}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL_TOKEN}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{S:SPECIAL-TOKEN}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL-TOKEN}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{S:SPECIAL:TOKEN}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL:TOKEN}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{S:SPECIAL_TOKEN}{ENTER}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:SPECIAL_TOKEN}{ENTER}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{S:FOO}{S:HELLO WORLD}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{S:FOO}{S:HELLO WORLD}", entry, error), error.toLatin1());
|
||||||
QVERIFY(!AutoType::checkSyntax("{S:SPECIAL_TOKEN{}}"));
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{S:SPECIAL_TOKEN{}}", entry, error), error.toLatin1());
|
||||||
|
|
||||||
QVERIFY(AutoType::checkSyntax("{BEEP 3 3}"));
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{BEEP 3 3}", entry, error), error.toLatin1());
|
||||||
QVERIFY(!AutoType::checkSyntax("{BEEP 3}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{BEEP 3}", entry, error), error.toLatin1());
|
||||||
|
|
||||||
QVERIFY(AutoType::checkSyntax("{VKEY 0x01}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{VKEY 0x01}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{VKEY VK_LBUTTON}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{VKEY VK_LBUTTON}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{VKEY-EX 0x01}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{VKEY-EX 0x01}", entry, error), error.toLatin1());
|
||||||
// Bad sequence
|
// Bad sequence
|
||||||
QVERIFY(!AutoType::checkSyntax("{{{}}{}{}}{{}}"));
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{{{}}{}{}}{{}}", entry, error), error.toLatin1());
|
||||||
// Good sequence
|
// Good sequence
|
||||||
QVERIFY(AutoType::checkSyntax("{{}{}}{}}{{}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{{}{}}{}}{{}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{]}{[}{[}{]}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{]}{[}{[}{]}", entry, error), error.toLatin1());
|
||||||
QVERIFY(AutoType::checkSyntax("{)}{(}{(}{)}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{)}{(}{(}{)}", entry, error), error.toLatin1());
|
||||||
// High DelAY / low delay
|
// High delay
|
||||||
QVERIFY(AutoType::checkHighDelay("{DelAY 50000}"));
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{DELAY 50000}", entry, error), error.toLatin1());
|
||||||
QVERIFY(!AutoType::checkHighDelay("{delay 50}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{delay 50}", entry, error), error.toLatin1());
|
||||||
// Slow typing
|
// Slow typing
|
||||||
QVERIFY(AutoType::checkSlowKeypress("{DelAY=50000}"));
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{DELAY=50000}", entry, error), error.toLatin1());
|
||||||
QVERIFY(!AutoType::checkSlowKeypress("{delay=50}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{delay=50}", entry, error), error.toLatin1());
|
||||||
// Many repetition / few repetition / delay not repetition
|
// Many repetition
|
||||||
QVERIFY(AutoType::checkHighRepetition("{LEFT 50000000}"));
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{LEFT 50000000}", entry, error), error.toLatin1());
|
||||||
QVERIFY(!AutoType::checkHighRepetition("{SPACE 10}{TAB 3}{RIGHT 50}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{SPACE 10}{TAB 3}{RIGHT 50}", entry, error), error.toLatin1());
|
||||||
QVERIFY(!AutoType::checkHighRepetition("{delay 5000000000}"));
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{delay 5000000000}", entry, error), error.toLatin1());
|
||||||
|
// Conversion and Regex
|
||||||
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{T-CONV:/{USERNAME}/base64/}", entry, error), error.toLatin1());
|
||||||
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{T-CONV:/{USERNAME}/junk/}", entry, error), error.toLatin1());
|
||||||
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{T-CONV:}", entry, error), error.toLatin1());
|
||||||
|
QVERIFY2(AutoType::verifyAutoTypeSyntax("{T-REPLACE-RX:/{USERNAME}/a/b/}", entry, error), error.toLatin1());
|
||||||
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{T-REPLACE-RX:/{USERNAME}/a/}", entry, error), error.toLatin1());
|
||||||
|
QVERIFY2(!AutoType::verifyAutoTypeSyntax("{T-REPLACE-RX:}", entry, error), error.toLatin1());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestAutoType::testAutoTypeEffectiveSequences()
|
void TestAutoType::testAutoTypeEffectiveSequences()
|
||||||
|
@ -47,6 +47,8 @@ private slots:
|
|||||||
void testGlobalAutoTypeUrlSubdomainMatch();
|
void testGlobalAutoTypeUrlSubdomainMatch();
|
||||||
void testGlobalAutoTypeTitleMatchDisabled();
|
void testGlobalAutoTypeTitleMatchDisabled();
|
||||||
void testGlobalAutoTypeRegExp();
|
void testGlobalAutoTypeRegExp();
|
||||||
|
void testAutoTypeResults();
|
||||||
|
void testAutoTypeResults_data();
|
||||||
void testAutoTypeSyntaxChecks();
|
void testAutoTypeSyntaxChecks();
|
||||||
void testAutoTypeEffectiveSequences();
|
void testAutoTypeEffectiveSequences();
|
||||||
|
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
|
||||||
* version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "TestWildcardMatcher.h"
|
|
||||||
#include "TestGlobal.h"
|
|
||||||
#include "autotype/WildcardMatcher.h"
|
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(TestWildcardMatcher)
|
|
||||||
|
|
||||||
const QString TestWildcardMatcher::DefaultText = QString("some text");
|
|
||||||
const QString TestWildcardMatcher::AlternativeText = QString("some other text");
|
|
||||||
|
|
||||||
void TestWildcardMatcher::testMatcher_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QString>("text");
|
|
||||||
QTest::addColumn<QString>("pattern");
|
|
||||||
QTest::addColumn<bool>("match");
|
|
||||||
|
|
||||||
QTest::newRow("MatchWithoutWildcard") << DefaultText << DefaultText << true;
|
|
||||||
QTest::newRow("NoMatchWithoutWildcard") << DefaultText << QString("no match text") << false;
|
|
||||||
QTest::newRow("MatchWithOneWildcardInTheMiddle") << AlternativeText << QString("some*text") << true;
|
|
||||||
QTest::newRow("NoMatchWithOneWildcardInTheMiddle") << AlternativeText << QString("differen*text") << false;
|
|
||||||
QTest::newRow("MatchWithOneWildcardAtBegin") << DefaultText << QString("*text") << true;
|
|
||||||
QTest::newRow("NoMatchWithOneWildcardAtBegin") << DefaultText << QString("*text something else") << false;
|
|
||||||
QTest::newRow("NatchWithOneWildcardAtEnd") << DefaultText << QString("some*") << true;
|
|
||||||
QTest::newRow("NoMatchWithOneWildcardAtEnd") << DefaultText << QString("some other*") << false;
|
|
||||||
QTest::newRow("MatchWithMultipleWildcards") << AlternativeText << QString("some*th*text") << true;
|
|
||||||
QTest::newRow("NoMatchWithMultipleWildcards") << AlternativeText << QString("some*abc*text") << false;
|
|
||||||
QTest::newRow("MatchJustWildcard") << DefaultText << QString("*") << true;
|
|
||||||
QTest::newRow("MatchFollowingWildcards") << DefaultText << QString("some t**t") << true;
|
|
||||||
QTest::newRow("CaseSensitivity") << DefaultText.toUpper() << QString("some t**t") << true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestWildcardMatcher::testMatcher()
|
|
||||||
{
|
|
||||||
QFETCH(QString, text);
|
|
||||||
QFETCH(QString, pattern);
|
|
||||||
QFETCH(bool, match);
|
|
||||||
|
|
||||||
initMatcher(text);
|
|
||||||
verifyMatchResult(pattern, match);
|
|
||||||
cleanupMatcher();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestWildcardMatcher::initMatcher(QString text)
|
|
||||||
{
|
|
||||||
m_matcher = new WildcardMatcher(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestWildcardMatcher::cleanupMatcher()
|
|
||||||
{
|
|
||||||
delete m_matcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestWildcardMatcher::verifyMatchResult(QString pattern, bool expected)
|
|
||||||
{
|
|
||||||
if (expected) {
|
|
||||||
verifyMatch(pattern);
|
|
||||||
} else {
|
|
||||||
verifyNoMatch(pattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestWildcardMatcher::verifyMatch(QString pattern)
|
|
||||||
{
|
|
||||||
bool matchResult = m_matcher->match(pattern);
|
|
||||||
QVERIFY(matchResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestWildcardMatcher::verifyNoMatch(QString pattern)
|
|
||||||
{
|
|
||||||
bool matchResult = m_matcher->match(pattern);
|
|
||||||
QVERIFY(!matchResult);
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 or (at your option)
|
|
||||||
* version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef KEEPASSX_TESTWILDCARDMATCHER_H
|
|
||||||
#define KEEPASSX_TESTWILDCARDMATCHER_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
class WildcardMatcher;
|
|
||||||
|
|
||||||
class TestWildcardMatcher : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void testMatcher();
|
|
||||||
void testMatcher_data();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static const QString DefaultText;
|
|
||||||
static const QString AlternativeText;
|
|
||||||
|
|
||||||
void initMatcher(QString text);
|
|
||||||
void cleanupMatcher();
|
|
||||||
void verifyMatchResult(QString pattern, bool expected);
|
|
||||||
void verifyMatch(QString pattern);
|
|
||||||
void verifyNoMatch(QString pattern);
|
|
||||||
|
|
||||||
WildcardMatcher* m_matcher;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // KEEPASSX_TESTWILDCARDMATCHER_H
|
|
Loading…
Reference in New Issue
Block a user