Compare commits

...

138 Commits

Author SHA1 Message Date
Xavier Valls
cc92ec1814 enforce a valid limit for the randomization of the extension deadline 2021-05-20 07:04:56 +02:00
Xavier Valls
c8f3018c4c improve GUI text for randomization 2021-05-20 07:04:56 +02:00
Xavier Valls
a174ec4ee7 fix randomizing interval bug 2021-05-20 07:04:56 +02:00
Xavier Valls
c669765256 add an option for randomizing the extension deadline
Proposed in Issue #6451
2021-05-20 07:04:56 +02:00
Xavier Valls
626f592c98 express auto-expiration settings in a single row 2021-05-20 07:04:56 +02:00
Xavier Valls
9eae0b318e change extension checkbox text, add label, fix indentation 2021-05-20 07:04:56 +02:00
Xavier Valls
bcc81b069b allow for better granularity when choosing password extension
Switches presets for quantity and unit of time pickers. Stores auto extension
on modification data as custom data for the entry to respect the spec
2021-05-20 07:04:56 +02:00
Xavier Valls
fa785e1745 allow automatic expiration date extension on pwd modification
Addressed issue #2010

Creates a new Expiration Settings area in the Edit Entry widget, composed
of the expiration date settings and a new optional feature that allows for
automatic extension of the expiration date on password modification.
2021-05-20 07:04:56 +02:00
Jonathan White
fd0bdaae80 Fix challenge-response key data after Botan
* Fix #6420
* Refactor Challenge-Response key files to be more streamlined. Added a test to confirm raw key data is accurate.
2021-05-19 22:36:30 -04:00
wundrweapon
60adcacaaa Add command line option to lock open databases (#6511)
Closes #6126
2021-05-15 09:48:59 -04:00
Lukas Rytz
cc6f5c3226 MinimizeAfterUnlock also when unlocking through browser
The MinimizeAfterUnlock setting added in #3439 closes the main window
after unlock. However, when the unlock is triggered through
KeePassXC-Browser, a password dialog is shown on top of the main window
and the main window remains open after the unlock. This is fixed
in this commit.
2021-05-15 09:45:26 -04:00
Jonathan White
8c61a73bb0 Show search bar when toolbar is hidden or overflow
* Fix #505 - always show the search bar when the search keyboard shortcut is pressed. If the toolbar is in overflow, the toolbar will be expanded automatically and search focused. If the toolbar is hidden it will be shown and expanded if necessary. When searching is canceled or the down arrow is pressed (to select the first entry) the toolbar will be set back to it's previous configuration.
2021-05-15 09:11:38 -04:00
Xavier Valls
e1c8304c4b Fix unreachable setting of file permissions (#6514)
Fixes #6080
2021-05-15 09:11:19 -04:00
Stefan Sundin
ee92b980bb Set permissions of saved attachments to be private to the current user (#6363) 2021-05-12 23:20:41 -04:00
Jonathan White
17326dc3ec Auto-Type: Resolve username/password when copying to clipboard
* Fix #3882
* Add nullptr checks as well
2021-05-12 23:15:49 -04:00
Jonathan White
3c2abaaa82 Remove obsolete cmake helper files
* Fixes #6490
2021-05-11 06:21:24 -04:00
Xavier Valls
64279bb881 Fix showing preview notes in an entry without notes
Fixes #6461
2021-05-08 17:35:37 -04:00
Jonathan White
8a7be101e4 CLI Improvements
* Fix #6001 - only use `--notes` in Add/Edit commands to prevent clash with password generator option `-n`.

* Fix #6119 - Send Unicode to clip command; Windows only understands UTF-16 encoding.

* Fix #6128 - `clip` command will default to clearing the clipboard after 10 seconds. To disable clearing set timeout to 0.
2021-04-25 07:38:21 -04:00
Jonathan White
be3e77d721 Cleanup CLI includes across all components
* Remove unused include files
* Move includes out of widely shared headers (reduced rebuild time)
* Consolidate code for Analyze command
2021-04-25 07:38:21 -04:00
Martin Mokrejs
dc496fd1d9 Better description text for Key File change dialog 2021-04-24 13:47:28 -04:00
Xavier Valls
01d86760e0 Allow resizing of reports table columns (#6435)
* Fix #5678

Co-authored-by: Jonathan White <support@dmapps.us>
2021-04-24 11:36:15 -04:00
Xavier Valls
7b7f52c8af Introduce security option to enable copy on doubleclick (#6433)
* Fix #1575 - option is disabled by default
2021-04-24 11:35:01 -04:00
Jonathan White
5c709f0da3 Auto-Type: Increase max inter-type delay to 500 ms 2021-04-24 09:31:46 -04:00
Jay Gates
9fee79ea96 Docs: example for how to search using attributes (#6410) 2021-04-24 09:30:59 -04:00
attero
91e74944f3 added pull.yml to automatically update forks 2021-04-19 08:07:25 -04:00
Xavier Valls
c0ae130656 Add CTRL+Enter to apply password generator changes (#6414)
* Fixes #6111
2021-04-18 22:37:12 -04:00
Xavier Valls
7fe0e2629c Allow the Group column to be toggled for entry view
This allows to show/hide the group column both in normal and search
mode, finding a compromise for issue #6163
2021-04-18 22:32:24 -04:00
Toni Spets
805574cac1 Update YubiKey stub implementation for Botan (#6370)
Co-authored-by: Jonathan White <support@dmapps.us>
2021-04-09 07:56:57 -04:00
Toni Spets
31aa5e12e5 Auto-Type: PageUp/PageDown scrolling for entries
Fixes #4530
2021-04-08 20:00:40 -04:00
Toni Spets
9b8feed3ed SSH Agent: Use database location to resolve relative key file path
Closes #5225
2021-04-06 23:39:02 -04:00
Jonathan White
ed0ece304d Reorder startup sequence to display debug information early 2021-04-05 22:56:03 -04:00
Jonathan White
80809ace67 Replace all crypto libraries with Botan
Selected the [Botan crypto library](https://github.com/randombit/botan) due to its feature list, maintainer support, availability across all deployment platforms, and ease of use. Also evaluated Crypto++ as a viable candidate, but the additional features of Botan (PKCS#11, TPM, etc) won out.

The random number generator received a backend upgrade. Botan prefers hardware-based RNG's and will provide one if available. This is transparent to KeePassXC and a significant improvement over gcrypt.

Replaced Argon2 library with built-in Botan implementation that supports i, d, and id. This requires Botan 2.11.0 or higher. Also simplified the parameter test across KDF's.

Aligned SymmetricCipher parameters with available modes. All encrypt and decrypt operations are done in-place instead of returning new objects. This allows use of secure vectors in the future with no additional overhead.

Took this opportunity to decouple KeeShare from SSH Agent. Removed leftover code from OpenSSHKey and consolidated the SSH Agent code into the same directory. Removed bcrypt and blowfish inserts since they are provided by Botan.

Additionally simplified KeeShare settings interface by removing raw certificate byte data from the user interface. KeeShare will be further refactored in a future PR.

NOTE: This PR breaks backwards compatibility with KeeShare certificates due to different RSA key storage with Botan. As a result, new "own" certificates will need to be generated and trust re-established.

Removed YKChallengeResponseKeyCLI in favor of just using the original implementation with signal/slots.

Removed TestRandom stub since it was just faking random numbers and not actually using the backend. TestRandomGenerator now uses the actual RNG.

Greatly simplified Secret Service plugin's use of crypto functions with Botan.
2021-04-05 22:56:03 -04:00
Jonathan White
86ddd702fb Use application font size when setting default or monospace fonts
* Fix #6286
2021-04-03 11:29:54 -04:00
Jonathan White
871c4fffdd OPVault: Use Text instead of Name for attribute and section names
* Fix #6303 - the text attribute in 1Password contains the actual text seen in 1Password whereas the name attribute may contain a ref pointer and not a name.
2021-04-01 22:59:44 -04:00
Toni Spets
ca8abecc4b Auto-Type: Abort keystroke if modifiers held on X11
Releasing lock modifiers during Auto Type does not work reliably
on X11 and can cause some modifiers to get stuck and more precisely
layout switching can get stuck.

Instead of trying to find out what modifiers to release we can just
abort when the user is holding modifiers that may affect the typing
sequence.

Fixes #6350
2021-04-01 22:58:33 -04:00
Toni Spets
371bd2e51b Auto-Type: Shortcuts for selection dialog (#6361)
* Shortcut to toggle search all entries
* Select first match only when we have a window match

When we default to full database search it's possible the user would select the first match without by accident.

In this case when our query is empty we don't select anything and you need to either type something or press down to select the first item.

* Added username, password, and TOTP keyboard shortcuts and a help tip

* Closes #6176

Co-authored-by: Jonathan White <support@dmapps.us>
2021-03-31 23:27:56 -04:00
Sami Vänttinen
c19efb5b19 Remove credential sorting from Browser Integration (#6353) 2021-03-31 23:14:29 -04:00
ByteHamster
439c155552 Show countdown for clipboard clearing (#6333)
* Closes #1843

Co-authored-by: Jonathan White <support@dmapps.us>
2021-03-31 23:12:59 -04:00
Toni Spets
8c9530e3ec Auto-Type: Allow actions to fail and be retried
AutoTypeActions are required to return a result object with
information if they can be retried or not. An error string is
also provided to show a user friendly message why said action did
not succeed if even after retries it keeps failing.

This is a prerequisite for waiting for modifier keys to be released
on X11.
2021-03-31 21:20:51 -04:00
Toni Spets
4d07507739 Auto-Type: Support multiple Xkb layouts
Completely rewritten XCB Auto-Type keymap system.

 - supports multiple simultaneous layouts
 - prefers current layout if it has all keysyms available
 - removed hardcoded KeySymMap
 - removed clunky custom KeySym emulation

Biggest breaking change is removing KeySym emulation for keys that
do not exist in any of the layouts currently in use. It would be
possible to make it work but if you are trying to type syms that
are not available in any of your layouts you are abusing it. It
also adds unnecessary complexity and opens up timing issues when
the keymap is modified on-the-fly. Now we are just reading it.

This also workarounds a Qt related issue where QX11Info::display()
returns a connection to X server that fails to receive updated
keymap data when client settings change. We use our own connection
now to get it working.
2021-03-26 06:16:37 -04:00
louib
2423bede60 Add matrix channel badges (#6294) 2021-03-16 22:11:45 -04:00
Guillaume Turchini
8b8fb9562f Allow CSV import of bare TOTP secrets
Fixes #6167
2021-03-08 21:53:51 -05:00
Chih-Hsuan Yen
e29cf8bfef Make KeePassXC start after the system tray is available on LXQt 2021-03-08 21:51:58 -05:00
mantlabs
d6b69204a6 Persist Always on Top setting 2021-03-07 11:27:28 -05:00
Jonathan White
bc08913c61 Auto-Type: Allow selection of modal dialogs on X11
* Fix #5958 - Modal dialogs do not have WM_STATE set even though they are a valid top-level window with a valid name. In this case, we need to poll for WM_TRANSIENT_FOR which returns the top level window the dialog is a child of.
2021-03-05 18:06:30 -05:00
Patrick Klein
57af7c131d Fix favicon download from URL with non-standard port.
Fixes #5001.

The favicon download URL was constructed from scheme and host only. This is fixed by simply replacing the path of the original URL with "/favicon.ico", thus keeping scheme, host, auth and port intact.

Further modification: URL's with a non-http schema are now rejected.
2021-03-01 21:42:19 -05:00
Jonathan White
6c7b04fee8 Revert zxcvbn changes from f3d88f 2021-03-01 20:57:08 -05:00
Bernhard Kirchen
4e8b00da34 Add custom icon purging and bulk deletion
This change adds a new database settings widget 
named "maintenance", using a wrench icon. This widget is designated to be the home for database related maintenance tasks. 

Initially, managing custom icons is now possible from that new tab. The feature includes bulk removing of
any number of selected custom icons and automatic purging of unused custom icons by the click of a button.

Fixes #2110
2021-02-27 08:13:05 -05:00
Jonathan White
b9ea6fd2e7 Show sort indicators on fixed width columns 2021-02-26 22:10:04 -05:00
Ojas Anand
022154462e Add entry view column for password strength
* Closes #4216

Reduced to three-tiered rating system and fixed column implementation. Hide password strength indicator in entry view if excluded from reports.

Introduce password health caching to prevent unnecessary calculations.
2021-02-26 22:10:04 -05:00
Bernhard
c9c19d043f KeeShare: Default to unsigned container unless specifically chosen
*Fix #6081 - Prevent assert and crash due to user entered data
2021-02-26 14:24:22 -05:00
Brandon Atkinson
c5a2aa0a2a Exclude additional lookalike characters (6G8B)
* Fix #6075
2021-02-25 21:36:30 -05:00
Jesse Ruth
2d66786656 Update MainWindow minimum size to enable smaller verticle space (#6149) 2021-02-24 23:11:33 -05:00
Jonathan White
3ccd4f9b14 Allow setting MSI properties in unattended install 2021-02-24 23:10:33 -05:00
Toni Spets
46f5596e59 Initialize Application before parser arguments (#6177)
Co-authored-by: Jonathan White <support@dmapps.us>
2021-02-24 23:10:13 -05:00
Jonathan White
c0d673b46f Merge pull request #5864 from keepassxreboot/feature/autotype-upgrade-part2 2021-02-22 19:05:07 -05:00
Jonathan White
8d058cbd04 Additional Auto-Type improvements based on PR feedback
* Improve documentation and remove external links to keepass.info documentation
2021-02-22 07:41:23 -05:00
Jonathan White
02446af743 Auto-Type support for T-CONV, T-REPLACE-RX, and Comments
* Close #1825 - Add full support for T-CONV and T-REPLACE-RX placeholders. Exception is support for the "raw" type in T-CONV.

* Close #5333 - Allow comment syntax to be present in the Auto-Type sequence
2021-02-22 07:41:23 -05:00
Jonathan White
813ab47e29 Implement Auto-Type {PICKCHARS}
* Closes #725

Support Auto-Type {PICKCHARS} placeholder. Open a dialog that lets you pick characters of an entry's password by their position. Supports typing {TAB} in between characters to move between fields (if necessary). Also supports using arrow keys to quickly navigate around the choice grid.
2021-02-22 07:41:23 -05:00
Jonathan White
027ff9f2bf Overhaul Auto-Type Action Handling
* Close #2603 - Add support for modifier syntax (+, ^, and %)
* Fix #2633 - Allow reference syntax {REF:...} in Auto-Type sequences
* Close #5334  - Tell the user which part of the Auto-Type sequence is invalid for easy correction
* Fix #2401 - Select the right window on macOS prior to starting Auto-Type

* Allow for nested placeholders
2021-02-21 16:33:54 -05:00
Jonathan White
d9ae449f04 Improve Auto-Type Select Dialog
Significant improvements to the Auto-Type select dialog. Reduce stale and unnecessary code paths.

* Close select dialog when databases are locked.
* Close open modal dialogs prior to showing the Auto-Type select dialog to prevent interference.
* Never perform Auto-Type on the KeePassXC window.
* Only filter match list based on Group, Title, and Username column data (ie, ignore sequence column)
* Always show the sequence column (revert feature)
* Show selection dialog if there are no matches to allow for a database search

* Close #3630 - Allow typing {USERNAME} and {PASSWORD} from selection dialog (right-click menu).
* Close #429 - Ability to search open databases for an entry from the Auto-Type selection dialog.
* Fix #5361 - Default size of selection dialog doesn't cut off matches
2021-02-21 16:33:54 -05:00
Jonathan White
7ce35f81de Cleanup entry level Auto-Type menu
* Show the sequence that will be typed when performing the default action
* Combine default sequence action with Username / Password options
* Fix #4939 - confirm prior to performing entry level auto-type if "Always Ask Before Auto-Type" is enabled
2021-02-21 16:33:54 -05:00
Jonathan White
f3d88fbd36 Address translation feedback from Transifex 2021-02-19 18:37:33 -05:00
Jonathan White
4f7460afbd Refactor Key Component Widgets for translations
* Ensure full labels are applied to buttons instead of splitting the Add/Change/Remove from the component name.
2021-02-19 18:37:33 -05:00
farnbacher
75e4329c80 use save method if new (and existing) created entry has unsaved
changes
2021-02-12 21:32:50 -05:00
Janek Bevendorff
af55d1b1b3 Use macdeployqt for all executables
Since Homebrew moved all its stuff to /opt/homebrew, our hard-coded
install_name_tool patch magic stopped working. This patch uses
macdeployqt for all executables to prevent this kind of behaviour.

Fixes #6042
2021-02-05 15:12:12 -05:00
smlu
a5094dd3ea Prevent screen capture on Windows and macOS
* Closes #5859
2021-02-05 15:10:54 -05:00
Aetf
9a8a5a0006 FdoSecrets: Major Refactor and Code Consolidation (#5747)
* Fixes #3837

* Change objects to use DBusMgr rather than separate adaptors
  - Update all DBus invokable methods to new parameter order
  - Change all usage of DBusReturn to simpler DBusResult
  - Use DBusMgr to handle path and service registration
  - Remove adaptor/*
  - Set path in DBusObject
  - Unregister service when service is destroyed
  - Restore handling of invalid QVariant in prompt complete signal
  - Clean up meta type registration
  - Move dbus related file together
  - Convert to QSharedPointer as much as possible
  - Fix mapping of the Delete method
  - Handle dbus property get all

* Add per-client states
  - Move cipher negotiation to DBusClient
  - Show list of clients instead of sessions in the settings page
  - Add settings for confirmation of accessing items
  - Fix infinite recursion when client disconnected
  - Use optional explicit DBusClient parameter instead. This makes accessing 
    the client info in an async context explicit, and thus prevent accidental 
    assertions in prompts.

* Improve User Interface
  - Add per-item access confirmation (if enabled)
  - Remove the "disable for site" button for the access control dialog
  - Improve the text on the settings page to be more consistent
  - Fix disconnect buttons in settings page not working
  - Make the unlock prompt method nonblocking

* Fix and cleanup unit tests
  - Use QTRY_COMPARE when checking signal spies, as dbus signals are threaded
  - Fixes in meta type registration and type conversion
  - Remove QStringLiteral in COMPARE macros, making diff output readable
  - Add testing for remembering auth decision
2021-02-05 15:07:59 -05:00
Janek Bevendorff
33e6da33ca Update deployment target to 10.13 2021-02-01 01:56:55 +01:00
Janek Bevendorff
1385929089 Fix *.so files not being signed resulting in notarization errors 2021-02-01 01:56:55 +01:00
Jonathan White
61b85183f9 Merge branch 'master' into develop 2021-01-31 17:04:38 -05:00
Janek Bevendorff
4e90cb5818 Fix on/off icons not being redrawn on theme change 2021-01-31 20:30:58 +01:00
Janek Bevendorff
b55f419386 Fix icon alpha blending in QTableView
Some widgets such as QTableView do not call QIconEngine::pixmap(), but do
the drawing immediately through QIconEngine::paint(). This breaks alpha
blending for recolouring, since the underlying image canvas is not
necessarily transparent and also not anchored at (0, 0). This results in
a black box of the size of the icon bounding box.

Icon recolouring is now always done on a temporary QImage with
transparent background and only the finished end result is composed onto
the original canvas.

Fixes #6006
2021-01-31 20:30:58 +01:00
smlu
c7323accf2 Fix adaptive icon painting 2021-01-31 12:50:32 +01:00
Janek Bevendorff
2e6c22d44d Prepare release-tool for Apple Silicon builds
Changes:

- Set correct target architecture when building on ARM64.
- Split signing and notarization into separate commands. This eases the
  workflow when notarization fails because changes to Apple's ToS have
  not yet been accepted on iTunes Connect.
- Sign all binaries and frameworks individually instead of using --deep.
  This is the correct way of signing apps and it avoids weird problems
  during signature verification.
- Fix signing of AppDirs, which was supposed to work, but never did.
2021-01-30 14:28:48 -05:00
Janek Bevendorff
3b30855855 Fix code formatting 2021-01-12 18:27:00 -05:00
Patrick Lenk
fff15d2d4d Set proper year for 2.6.3 in changelog 2021-01-12 18:27:00 -05:00
Janek Bevendorff
86278311d2 Merge branch 'master' into develop 2021-01-12 18:24:59 +01:00
Janek Bevendorff
9a7b20cbfd Add dynamic theme switching on Windows 10 2021-01-07 15:22:48 +01:00
Janek Bevendorff
80c1b9be6a Improve macOS platform integration.
- Allow switching between themes without restart (except classic)
- Rework icon loading and recolouring logic to react to theme changes
- Automatically react to light/dark theme change
- Remove explicit selection of monochrome tray icon variant (selected
  automatically now)
- Update theme background colours for Big Sur
- Update application icon to match Big Sur HIG

The tray icon doesn't respond perfectly to theme changes yet on Big Sur,
since we need different icons for dark and light theme and cannot simply
let the OS recolour the icon for us (we do that, too, but only as an
additional fallback). At the moment, there is no signal to listen to
that would allow this.

This patch adds a few generic methods to OSUtils for detecting and
communicating theme changes, which are only stubs for Windows and Linux at
the moment and need to be implemented in future commits.

Fixes #4933
Fixes #5349
2021-01-07 15:22:48 +01:00
Janek Bevendorff
37dab85df7 Implement empty-area drag.
Uses Qt 5.15's new QWindow::startSystemMove() to implement empty-area
drag, which allows the user to click and drag any empty area on the
menubar, toolbar, or tabbar to move the window around.
2020-12-25 13:42:12 -05:00
Janek Bevendorff
cd0084f21c Add support for version 2 XML key files.
As discussed in #4317, the next KeePass2 release will ship with
support for a new generation of XML key files which enable
hash integrity checks.

This patch adds support for reading and generating this new format.
By default, KeePass2 now uses the .keyx extension for generated
key files, which was added to KeePassXC's key generation file chooser
filter. We continue to generate hashed binary key files by default,
but the user can explicitly save the file with the new .keyx
extension to generate an XML v2 key file (currently undocumented).

When opening a database, the key file type is still determined
by content negotation, so the file extension has no impact here.

As an additional change, the legacy key file warnings have been
improved slightly to be less confusing and more helpful.
2020-12-19 09:42:21 -05:00
Jonathan White
404fd941e8 Move global shortcut handling into OSUtils (#5566)
Move global shortcut handling into OSUtils
2020-12-13 23:23:25 -05:00
Bernhard Berg
a6f01349e8 Redo 'delete entries no confirm' functionality & unit-tests (#5812)
* Fixes #5232
2020-12-12 12:14:18 -05:00
Janek Bevendorff
c9d1512748 Revert "Add "move to recycle bin without confirmation" setting"
This reverts commit de44764efa.
2020-12-11 13:11:42 +01:00
Bernhard Berg
de44764efa Add "move to recycle bin without confirmation" setting 2020-12-11 00:15:24 -05:00
Bernhard
c9e7ffadad Fix reSelect entry & group on loadDb 2020-12-11 00:11:18 -05:00
Carlo Teubner
5ea46d4c12 CODE-OF-CONDUCT.md: fix typo 2020-12-10 12:47:41 +01:00
Janek Bevendorff
748a6b5ce1 Add code of conduct 2020-12-06 14:01:08 +01:00
Aetf
30c2e39e70 Fix QTimer cannot be stopped from another thread warning 2020-12-03 22:20:25 -05:00
Janek Bevendorff
9a96124040 Add Argon2id KDF 2020-12-03 20:06:58 +01:00
Janek Bevendorff
c6bd22aa12 Attach console in debug mode on Windows 2020-11-21 09:41:55 +01:00
Aetf
3d10f31211 Merge pull request #5660 from Aetf/fix/fdosecrets-5279
Secret Service: cleanup and fix crash
2020-11-15 22:34:14 -05:00
Aetf
9f4118974d FdoSecrets: fix signal connections 2020-11-13 17:20:45 -05:00
Aetf
7f85eb77aa FdoSecrets: code formatting 2020-11-13 17:16:22 -05:00
Aetf
a651d7049d FdoSecrets: handle corner cases in collection dbus names, fix #5279
- Use completeBaseName rather than baseName to ensure nonempty name
- Handle two databases have the same name
- Cleanup Service::onDatabaseTabOpened logic
2020-11-13 17:16:22 -05:00
Aetf
804a3b6706 FdoSecrets: simplify collection internal states
This gets rid of the m_registered state, so whenever there is a valid m_backend, it is guaranteed to be registered already.

While at it, this commit also improves DBusObject::registerWithPath a little bit by allowing properly registering multiple paths using the same adaptor, mostly for supporting Collection aliases.

Now when DBus registration fails, the code does not go into an inconsistent state or crash.
2020-11-13 17:16:22 -05:00
Aetf
f5caf3968f FdoSecrets: fix typos 2020-11-13 17:16:22 -05:00
Aetf
000e1823ac FdoSecrets: refactor DBus registration error handling 2020-11-13 17:16:22 -05:00
Jonathan White
f8f2271f33 Document support for managed Microsoft Edge
Add documentation for system administrators to setup support for KeePassXC extension in a managed Microsoft Edge.
2020-11-12 06:23:36 -05:00
Jonathan White
15dc6f062e Ignore format changes for new clang-format version 2020-11-11 17:58:40 -05:00
Jonathan White
91dea9cbc4 Use strict check for std::sort to prevent recursion
* Fixes #5596
2020-11-01 13:28:59 -05:00
Jonathan White
fb8423fdca Bump version to 2.7.0 2020-10-25 09:44:28 -04:00
Janek Bevendorff
cd519e1bf3 Merge branch 'master' into develop 2020-10-21 22:49:02 +02:00
Jonathan White
eb6f0eb346 Add search 'by-path' url for browser 2020-10-17 18:22:47 -04:00
Jonathan White
0c5dd1556a Merge branch 'release/2.6.2' into develop 2020-10-15 00:13:14 -04:00
Bernhard Berg
fd3cc7e8c3 Add keyfile option to keepassxc cli import cmd (#5402)
Fixes #5311

Added the keyFile logic from the create command to the import command and moved the loadFileKey() function
to the Utils class since it is now used in both create & import classes.
2020-10-09 20:31:29 -04:00
Christof Klaus
bf2cad28af Add feature to ignore entries for HTTP-Auth Logins 2020-10-08 22:53:04 -04:00
Carlos E. Salazar
fa546c440e Feature/toggle groups panel option (#5247)
* Closes #5243
2020-10-08 22:52:30 -04:00
Wolfram Rösler
34b44e7496 Add fuzz test support
Describe how to invoke the AFL fuzz tester on the KeePassXC
CLI tool. As suggested in #2729.

Fuzz test build of keepassxc-cli takes database password from
environment variable instead of requiring it to be empty.
Provide two empty kdbx files as initial fuzzer input, one
kdbx 3 and one kdbx 4, both with minimal number of decryption
rounds to speed up the test.
2020-10-08 22:49:34 -04:00
louib
48d9fb3e79 Remove GUI bootstraping from core/ (#5513) 2020-10-08 22:48:45 -04:00
Jonathan White
fb87b1c794 Merge branch 'release/2.6.2' into develop 2020-10-07 11:27:14 -04:00
louib
af4ecb4aa1 Move icon handling from Resources to gui/Icons (#5506) 2020-10-05 20:41:00 -04:00
louib
ba8611cf4c Moving IconDownloader to gui/ 2020-10-04 23:45:25 -04:00
louib
9bffe05020 Moving all OS utils to gui/osutils
The classes used for screen lock detection use QWidget and are only ever used by the GUI,
so moving them there so we can eventually build core/ without Qt5::Widgets.
2020-10-04 23:44:45 -04:00
Shun Sakai
8f84675874 Improve CMakeLists.txt and docs/CMakeLists.txt 2020-10-04 09:10:18 -04:00
Wolfram Rösler
2e7a44de61 Describe HTML export/paper backups in the user manual (#5276) 2020-10-03 16:35:40 -04:00
Jonathan White
7426693f1d CLI: Add support for okon in offline HIBP checks
* Closes #5447
* Add option `--okon <okon-cli path>` to trigger the use of the okon cli tool to process a database's entries. When using this option the `-H, --hibp` option must point to a post-processed okon file instead of the standard HIBP text file.
* Updated documentation
2020-09-27 15:23:03 -04:00
Jonathan White
e1c2537084 Merge branch 'release/2.6.2' into develop 2020-09-27 12:11:02 -04:00
Bernhard Berg
ac5c1af829 Add display number of characters in passphrases (#5449)
Co-authored-by: Jonathan White <support@dmapps.us>
2020-09-21 21:32:49 -04:00
Jonathan White
d3747f40e2 Fix code format 2020-09-21 21:32:02 -04:00
david
55eb855267 Add a -n (--notes) option to keepassxc-cli add and edit commands 2020-09-21 16:04:32 -04:00
Stefan Sundin
8a4a804c8c Use Alt+Tab on macOS to switch between databases (#5407) 2020-09-15 09:43:35 -04:00
piegames
6a35bbea2f Add browser service search for entries via UUID 2020-09-02 23:19:43 -04:00
Jonathan White
f947c96462 Improve CSV export and import capability
* Fixes #3541
* CSV export now includes TOTP settings, Entry Icon (database icon number only), Modified Time, and Created Time.
* CSV import properly understands time in ISO 8601 format and Unix Timestamp.
* CSV import will set the TOTP settings and entry icon based on the chosen column.
2020-09-01 07:57:31 -04:00
Laurent Erignoux
f49f62d3be Add a best option to CLI command clip (#4489)
The best option copy the password from the best match if only one matching entry exists.

Adding clip best option documentation

Adding unit tests on the new clip --best option
2020-08-31 23:06:27 -04:00
fpohtmeh
bbdfbe64da Add ability to rename attachments
* Closes #4758
2020-08-31 23:06:10 -04:00
Shun Sakai
e53850627f Various minor improvements for man page (#5360) 2020-08-31 20:18:53 -04:00
Wolfram Rösler
6b96806914 HTML Export: Don't waste horizontal space
The previous version left a lot of white space to the
right of the table, meaning that more pages needed to
be printed for a paper backup. The table has been
reorganized. HTML Backup for the demo.kdbx database
is now down from 6 to 3 pages.
2020-08-31 20:17:30 -04:00
clonejo
656e6d289a Don't ask when removing an empty URL
There is no harm to deleting an empty URL from the browser integration
URL list when the user never set a value. It's a bit annoying, actually.
2020-08-31 20:16:33 -04:00
Bernhard
745f1befe9 Allow hiding expired entries from Auto-Type
* Add setting to hide expired entries from Auto-Type
* Expired entries will not be shown in selection dialogs or be auto-picked for use
* Fixes #1855
2020-08-30 08:06:22 -04:00
Janek Bevendorff
5c2a1a4284 Merge branch 'master' into develop 2020-08-19 23:59:26 +02:00
Jonathan White
71b05dbcf4 Merge branch 'release/2.6.1' into develop 2020-07-22 12:10:05 -04:00
alcroito
ff9dd43262 Skip referenced passwords in Health check report
Fixes #5036
2020-07-18 09:16:43 -04:00
Jonathan White
88a0a8d35a Merge branch 'release/2.6.1' into develop 2020-07-12 20:51:37 -04:00
Janek Bevendorff
b6787d91a3 Update changelog 2020-07-07 20:18:50 +02:00
Jonathan White
c03a734ebb Merge branch 'master' into develop 2020-07-06 21:32:20 -04:00
Jonathan White
6e99bb178d Merge branch 'release/2.6.0' into develop 2020-07-06 18:48:14 -04:00
472 changed files with 14385 additions and 13300 deletions

3
.gitattributes vendored
View File

@@ -11,3 +11,6 @@ AppImage-Recipe.sh export-ignore
# github-linguist language hints
*.h linguist-language=C++
*.cpp linguist-language=C++
# binary files
*.ai binary

12
.github/pull.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# doc: https://github.com/wei/pull#basic-setup
# manual trigger: https://pull.git.ci/process/${fork-user}/keepassxc
# pull from: https://github.com/keepassxreboot/keepassxc
version: "1"
rules:
- base: master
upstream: keepassxreboot:master
mergeMethod: hardreset
- base: develop
upstream: keepassxreboot:develop
mergeMethod: rebase

View File

@@ -1,6 +1,28 @@
# Changelog
## 2.6.3 (2020-01-12)
## 2.6.4 (2021-01-31)
### Added
- Automatically adapt to light/dark system theme changes (Windows/macOS only) [#6034]
### Changed
- Show window title as tooltip on system tray [#5948]
- Compress Snap release as LZO for faster initial startup [#5877]
- Password generator: Set maximum selectable password length to 999 [#5937]
### Fixed
- Fix crash on app close when using SSH agent [#5935]
- Fix KDF selection showing wrong item when using Argon2id [#5923]
- Automatically close About dialog on database lock if it is still open [#5947]
- Linux: Fix automatic launch at system startup with AppImages [#5901]
- Linux: Fix click-to-move on empty area activating when using menus [#5971]
- Linux: Try multiple times to show tray icon if tray is not ready yet [#5948]
- macOS: Fix KeePassXC blocking clean shutdown [#6002]
## 2.6.3 (2021-01-12)
### Added

View File

@@ -64,11 +64,10 @@ if(WITH_CCACHE)
# Use the Compiler Cache (ccache) program
# (install with: sudo apt get ccache)
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_FOUND})
else()
if(NOT CCACHE_FOUND)
message(FATAL_ERROR "ccache requested but cannot be found.")
endif()
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_FOUND})
endif()
if(WITH_XC_ALL)
@@ -87,12 +86,6 @@ if(WITH_XC_ALL)
endif()
endif()
if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
set(WITH_XC_CRYPTO_SSH ON)
else()
set(WITH_XC_CRYPTO_SSH OFF)
endif()
# Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK
if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK)
message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled")
@@ -100,8 +93,8 @@ if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK)
endif()
set(KEEPASSXC_VERSION_MAJOR "2")
set(KEEPASSXC_VERSION_MINOR "6")
set(KEEPASSXC_VERSION_PATCH "3")
set(KEEPASSXC_VERSION_MINOR "7")
set(KEEPASSXC_VERSION_PATCH "0")
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds")
@@ -251,6 +244,7 @@ if(WITH_APP_BUNDLE)
endif()
add_gcc_compiler_flags("-fno-common")
check_add_gcc_compiler_flag("-fopenmp")
add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long")
add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
add_gcc_compiler_flags("-fvisibility=hidden")
@@ -270,7 +264,6 @@ else()
endif()
endif()
add_gcc_compiler_cxxflags("-fno-exceptions -fno-rtti")
add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual")
add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings")
@@ -306,11 +299,11 @@ if(WITH_COVERAGE AND CMAKE_COMPILER_IS_CLANGXX)
# `find src -iname '*.h' -or -iname '*.cpp'`
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
if(UNIX AND NOT APPLE)
check_add_gcc_compiler_flag("-Qunused-arguments")
add_gcc_compiler_flags("-pie -fPIE")
check_add_gcc_compiler_flag("-fPIC")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed -Wl,--no-undefined")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now -pie")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro,-z,now")
endif()
@@ -325,7 +318,7 @@ if(APPLE AND CMAKE_COMPILER_IS_CLANGXX)
endif()
if(WITH_DEV_BUILD)
add_definitions(-DQT_DEPRECATED_WARNINGS -DGCRYPT_NO_DEPRECATED)
add_definitions(-DQT_DEPRECATED_WARNINGS)
else()
add_definitions(-DQT_NO_DEPRECATED_WARNINGS)
add_gcc_compiler_cxxflags("-Wno-deprecated-declarations")
@@ -400,10 +393,18 @@ include(CLangFormat)
set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools)
if(UNIX AND NOT APPLE)
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus REQUIRED)
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus X11Extras REQUIRED)
elseif(APPLE)
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED HINTS /usr/local/opt/qt/lib/cmake /usr/local/Cellar/qt/*/lib/cmake ENV PATH)
find_package(Qt5 COMPONENTS MacExtras HINTS /usr/local/opt/qt/lib/cmake /usr/local/Cellar/qt/*/lib/cmake ENV PATH)
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED HINTS
/usr/local/opt/qt/lib/cmake
/usr/local/Cellar/qt/*/lib/cmake
/opt/homebrew/opt/qt/lib/cmake
ENV PATH)
find_package(Qt5 COMPONENTS MacExtras HINTS
/usr/local/opt/qt/lib/cmake
/usr/local/Cellar/qt/*/lib/cmake
/opt/homebrew/opt/qt/lib/cmake
ENV PATH)
else()
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED)
endif()
@@ -426,36 +427,36 @@ if(APPLE)
find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
if(NOT MACDEPLOYQT_EXE)
message(FATAL_ERROR "macdeployqt is required to build on macOS")
else()
message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}")
endif()
message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}")
elseif(MINGW)
find_program(WINDEPLOYQT_EXE windeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
if(NOT WINDEPLOYQT_EXE)
message(FATAL_ERROR "windeployqt is required to build on Windows")
else()
message(STATUS "Using windeployqt: ${WINDEPLOYQT_EXE}")
endif()
message(STATUS "Using windeployqt: ${WINDEPLOYQT_EXE}")
endif()
# Debian sets the the build type to None for package builds.
# Make sure we don't enable asserts there.
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
find_package(LibGPGError REQUIRED)
find_package(Gcrypt 1.7.0 REQUIRED)
find_package(Argon2 REQUIRED)
# Find Botan2
find_package(Botan2 REQUIRED)
if(BOTAN2_VERSION VERSION_LESS "2.11.0")
message(FATAL_ERROR "Botan2 2.11.0 or higher is required")
endif()
include_directories(SYSTEM ${BOTAN2_INCLUDE_DIR})
# Find zlib
find_package(ZLIB REQUIRED)
find_package(QREncode REQUIRED)
find_package(sodium 1.0.12 REQUIRED)
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0")
message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format")
endif()
include_directories(SYSTEM ${ZLIB_INCLUDE_DIR})
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR})
# QREncode required for TOTP
find_package(QREncode REQUIRED)
# Optional
if(WITH_XC_YUBIKEY)
@@ -494,7 +495,7 @@ if(UNIX)
endif()
endif()
include_directories(SYSTEM ${GCRYPT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
include_directories(SYSTEM ${ZLIB_INCLUDE_DIR})
add_subdirectory(src)
add_subdirectory(share)

20
CODE-OF-CONDUCT.md Normal file
View File

@@ -0,0 +1,20 @@
# Contributor Code of Conduct
KeePassXC is an open project that welcomes everybody no matter their ethnicity, sex,
sexual identity or orientation, age, socio-economic status, nationality, or religion.
Regardless of what background you come from, feel encouraged to participate in
the project and express your views as long you are respectful to others.
We value all members of our community and so in order to ensure a harassment-free
experience for everyone and mutual respect among members of this community, we
impose the following simple rules:
- No bullying, no insults. Any form of harassment will not be tolerated.
- No racism, no sexism, no homophobia, no hurtful extremist views of any kind.
- Be mindful of what you say, be diligent in how you say it.
- Show respect and, as always, be excellent to each other.
Violations of these rules or any other form of abuse can be reported confidentially
to conduct AT keepassxc DOT org. Members who do not adhere to our code of conduct
will be banned either permanently or until they change their ways so as to be
compatible with a friendly, open, and inclusive community.

View File

@@ -46,6 +46,10 @@ Files: cmake/FindYubiKey.cmake
Copyright: 2014 Kyle Manna <kyle@kylemanna.com>
License: GPL-2 or GPL-3
Files: cmake/FindBotan2.cmake
Copyright: 2018 Ribose Inc.
License: BSD-2-clause
Files: cmake/GenerateProductVersion.cmake
Copyright: 2015 halex2005 <akharlov@gmail.com>
License: MIT
@@ -164,8 +168,11 @@ Files: share/icons/application/scalable/actions/chevron-double-down.svg
share/icons/application/scalable/actions/group-edit.svg
share/icons/application/scalable/actions/group-empty-trash.svg
share/icons/application/scalable/actions/group-new.svg
share/icons/application/scalable/actions/hammer-wrench.svg
share/icons/application/scalable/actions/health.svg
share/icons/application/scalable/actions/help-about.svg
share/icons/application/scalable/actions/key-enter.svg
share/icons/application/scalable/actions/lock-question.svg
share/icons/application/scalable/actions/message-close.svg
share/icons/application/scalable/actions/move-down.svg
share/icons/application/scalable/actions/move-up.svg

View File

@@ -3,6 +3,9 @@
[![codecov](https://codecov.io/gh/keepassxreboot/keepassxc/branch/develop/graph/badge.svg)](https://codecov.io/gh/keepassxreboot/keepassxc)
[![GitHub release](https://img.shields.io/github/release/keepassxreboot/keepassxc)](https://github.com/keepassxreboot/keepassxc/releases/)
[![Matrix community channel](https://img.shields.io/matrix/keepassxc:matrix.org?label=Community%20channel&style=for-the-badge)](https://matrix.to/#/!zUxwGnFkUyycpxeHeM:matrix.org?via=matrix.org)
[![Matrix development channel](https://img.shields.io/matrix/keepassxc-dev:matrix.org?label=Development%20channel&style=for-the-badge)](https://matrix.to/#/!RhJPJPGwQIFVQeXqZa:matrix.org?via=matrix.org)
[KeePassXC](https://keepassxc.org) is a modern, secure, and open-source password manager that stores and manages your most sensitive information. You can run KeePassXC on Windows, macOS, and Linux systems. KeePassXC is for people with extremely high demands of secure personal data management. It saves many different types of information, such as usernames, passwords, URLs, attachments, and notes in an offline, encrypted file that can be stored in any location, including private and public cloud solutions. For easy identification and management, user-defined titles and icons can be specified for entries. In addition, entries are sorted in customizable groups. An integrated search function allows you to use advanced patterns to easily find any entry in your database. A customizable, fast, and easy-to-use password generator utility allows you to create passwords with any combination of characters or easy to remember passphrases.
## Quick Start
@@ -49,6 +52,8 @@ We are always looking for suggestions on how to improve KeePassXC. If you find a
You may directly contribute your own code by submitting a pull request. Please read the [CONTRIBUTING](.github/CONTRIBUTING.md) document for further information.
Contributors are required to adhere to the project's [Code of Conduct](CODE-OF-CONDUCT.md).
## License
KeePassXC code is licensed under GPL-2 or GPL-3. Additional licensing for third-party files is detailed in [COPYING](./COPYING).

View File

@@ -1,36 +0,0 @@
# Copyright (C) 2017 KeePassXC Team
#
# 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/>.
find_path(ARGON2_INCLUDE_DIR argon2.h)
if(MINGW)
# find static library on Windows, and redefine used symbols to
# avoid definition name conflicts with libsodium
find_library(ARGON2_SYS_LIBRARIES libargon2.a)
message(STATUS "Patching libargon2...\n")
execute_process(COMMAND objcopy
--redefine-sym argon2_hash=libargon2_argon2_hash
--redefine-sym _argon2_hash=_libargon2_argon2_hash
--redefine-sym argon2_error_message=libargon2_argon2_error_message
--redefine-sym _argon2_error_message=_libargon2_argon2_error_message
${ARGON2_SYS_LIBRARIES} ${CMAKE_BINARY_DIR}/libargon2_patched.a
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
find_library(ARGON2_LIBRARIES libargon2_patched.a PATHS ${CMAKE_BINARY_DIR} NO_DEFAULT_PATH)
else()
find_library(ARGON2_LIBRARIES argon2)
endif()
mark_as_advanced(ARGON2_LIBRARIES ARGON2_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Argon2 DEFAULT_MSG ARGON2_LIBRARIES ARGON2_INCLUDE_DIR)

121
cmake/FindBotan2.cmake Normal file
View File

@@ -0,0 +1,121 @@
# Copyright (c) 2018 Ribose Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#.rst:
# FindBotan2
# -----------
#
# Find the botan-2 library.
#
# IMPORTED Targets
# ^^^^^^^^^^^^^^^^
#
# This module defines :prop_tgt:`IMPORTED` targets:
#
# ``Botan2::Botan2``
# The botan-2 library, if found.
#
# Result variables
# ^^^^^^^^^^^^^^^^
#
# This module defines the following variables:
#
# ::
#
# BOTAN2_FOUND - true if the headers and library were found
# BOTAN2_INCLUDE_DIRS - where to find headers
# BOTAN2_LIBRARIES - list of libraries to link
# BOTAN2_VERSION - library version that was found, if any
# use pkg-config to get the directories and then use these values
# in the find_path() and find_library() calls
find_package(PkgConfig QUIET)
pkg_check_modules(PC_BOTAN2 QUIET botan-2)
# find the headers
find_path(BOTAN2_INCLUDE_DIR
NAMES botan/version.h
HINTS
${PC_BOTAN2_INCLUDEDIR}
${PC_BOTAN2_INCLUDE_DIRS}
PATH_SUFFIXES botan-2
)
# find the library
find_library(BOTAN2_LIBRARY
NAMES botan-2 libbotan-2
HINTS
${PC_BOTAN2_LIBDIR}
${PC_BOTAN2_LIBRARY_DIRS}
)
# determine the version
if(PC_BOTAN2_VERSION)
set(BOTAN2_VERSION ${PC_BOTAN2_VERSION})
elseif(BOTAN2_INCLUDE_DIR AND EXISTS "${BOTAN2_INCLUDE_DIR}/botan/build.h")
file(STRINGS "${BOTAN2_INCLUDE_DIR}/botan/build.h" botan2_version_str
REGEX "^#define[\t ]+(BOTAN_VERSION_[A-Z]+)[\t ]+[0-9]+")
string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MAJOR[\t ]+([0-9]+).*"
"\\1" _botan2_version_major "${botan2_version_str}")
string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MINOR[\t ]+([0-9]+).*"
"\\1" _botan2_version_minor "${botan2_version_str}")
string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_PATCH[\t ]+([0-9]+).*"
"\\1" _botan2_version_patch "${botan2_version_str}")
set(BOTAN2_VERSION "${_botan2_version_major}.${_botan2_version_minor}.${_botan2_version_patch}"
CACHE INTERNAL "The version of Botan which was detected")
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Botan2
REQUIRED_VARS BOTAN2_LIBRARY BOTAN2_INCLUDE_DIR
VERSION_VAR BOTAN2_VERSION
)
if (BOTAN2_FOUND)
set(BOTAN2_INCLUDE_DIRS ${BOTAN2_INCLUDE_DIR} ${PC_BOTAN2_INCLUDE_DIRS})
set(BOTAN2_LIBRARIES ${BOTAN2_LIBRARY})
endif()
if (BOTAN2_FOUND AND NOT TARGET Botan2::Botan2)
# create the new library target
add_library(Botan2::Botan2 UNKNOWN IMPORTED)
# set the required include dirs for the target
if (BOTAN2_INCLUDE_DIRS)
set_target_properties(Botan2::Botan2
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${BOTAN2_INCLUDE_DIRS}"
)
endif()
# set the required libraries for the target
if (EXISTS "${BOTAN2_LIBRARY}")
set_target_properties(Botan2::Botan2
PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${BOTAN2_LIBRARY}"
)
endif()
endif()
mark_as_advanced(BOTAN2_INCLUDE_DIR BOTAN2_LIBRARY)

View File

@@ -1,31 +0,0 @@
# Copyright (C) 2011 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/>.
find_path(GCRYPT_INCLUDE_DIR gcrypt.h)
find_library(GCRYPT_LIBRARIES gcrypt)
mark_as_advanced(GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIR)
if(GCRYPT_INCLUDE_DIR AND EXISTS "${GCRYPT_INCLUDE_DIR}/gcrypt.h")
file(STRINGS "${GCRYPT_INCLUDE_DIR}/gcrypt.h" GCRYPT_H REGEX "^#define GCRYPT_VERSION \"[^\"]*\"$")
string(REGEX REPLACE "^.*GCRYPT_VERSION \"([0-9]+).*$" "\\1" GCRYPT_VERSION_MAJOR "${GCRYPT_H}")
string(REGEX REPLACE "^.*GCRYPT_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" GCRYPT_VERSION_MINOR "${GCRYPT_H}")
string(REGEX REPLACE "^.*GCRYPT_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" GCRYPT_VERSION_PATCH "${GCRYPT_H}")
set(GCRYPT_VERSION_STRING "${GCRYPT_VERSION_MAJOR}.${GCRYPT_VERSION_MINOR}.${GCRYPT_VERSION_PATCH}")
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Gcrypt DEFAULT_MSG GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIR)

View File

@@ -1,23 +0,0 @@
# 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/>.
find_path(GPGERROR_INCLUDE_DIR gpg-error.h)
find_library(GPGERROR_LIBRARIES gpg-error)
mark_as_advanced(GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
include_directories(${GPGERROR_INCLUDE_DIR})
find_package_handle_standard_args(LibGPGError DEFAULT_MSG GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)

View File

@@ -1,267 +0,0 @@
# Written in 2016 by Henrik Steffen Gaßmann <henrik@gassmann.onl>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
#
# http://creativecommons.org/publicdomain/zero/1.0/
#
########################################################################
# Tries to find the local libsodium installation.
#
# On Windows the sodium_DIR environment variable is used as a default
# hint which can be overridden by setting the corresponding cmake variable.
#
# Once done the following variables will be defined:
#
# sodium_FOUND
# sodium_INCLUDE_DIR
# sodium_LIBRARY_DEBUG
# sodium_LIBRARY_RELEASE
#
#
# Furthermore an imported "sodium" target is created.
#
if (CMAKE_C_COMPILER_ID STREQUAL "GNU"
OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
set(_GCC_COMPATIBLE 1)
endif()
# static library option
option(sodium_USE_STATIC_LIBS "enable to statically link against sodium")
if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST))
unset(sodium_LIBRARY CACHE)
unset(sodium_LIBRARY_DEBUG CACHE)
unset(sodium_LIBRARY_RELEASE CACHE)
unset(sodium_DLL_DEBUG CACHE)
unset(sodium_DLL_RELEASE CACHE)
set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable")
endif()
########################################################################
# UNIX
if (UNIX)
# import pkg-config
find_package(PkgConfig QUIET)
if (PKG_CONFIG_FOUND)
pkg_check_modules(sodium_PKG QUIET libsodium)
endif()
if(sodium_USE_STATIC_LIBS)
set(XPREFIX sodium_PKG_STATIC)
else()
set(XPREFIX sodium_PKG)
endif()
find_path(sodium_INCLUDE_DIR sodium.h
HINTS ${${XPREFIX}_INCLUDE_DIRS}
)
find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} sodium
HINTS ${${XPREFIX}_LIBRARY_DIRS}
)
find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} sodium
HINTS ${${XPREFIX}_LIBRARY_DIRS}
)
########################################################################
# Windows
elseif (WIN32)
set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory")
mark_as_advanced(sodium_DIR)
find_path(sodium_INCLUDE_DIR sodium.h
HINTS ${sodium_DIR}
PATH_SUFFIXES include
)
if (MSVC)
# detect target architecture
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.c" [=[
#if defined _M_IX86
#error ARCH_VALUE x86_32
#elif defined _M_X64
#error ARCH_VALUE x86_64
#endif
#error ARCH_VALUE unknown
]=])
try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.c"
OUTPUT_VARIABLE _COMPILATION_LOG
)
string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}")
# construct library path
if (_TARGET_ARCH STREQUAL "x86_32")
string(APPEND _PLATFORM_PATH "Win32")
elseif(_TARGET_ARCH STREQUAL "x86_64")
string(APPEND _PLATFORM_PATH "x64")
else()
message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.")
endif()
string(APPEND _PLATFORM_PATH "/$$CONFIG$$")
if (MSVC_VERSION LESS 1900)
math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60")
else()
math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50")
endif()
string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}")
if (sodium_USE_STATIC_LIBS)
string(APPEND _PLATFORM_PATH "/static")
else()
string(APPEND _PLATFORM_PATH "/dynamic")
endif()
string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}")
string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}")
find_library(sodium_LIBRARY_DEBUG libsodium.lib
HINTS ${sodium_DIR}
PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
)
find_library(sodium_LIBRARY_RELEASE libsodium.lib
HINTS ${sodium_DIR}
PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
)
if (NOT sodium_USE_STATIC_LIBS)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
find_library(sodium_DLL_DEBUG libsodium
HINTS ${sodium_DIR}
PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
)
find_library(sodium_DLL_RELEASE libsodium
HINTS ${sodium_DIR}
PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
)
endif()
elseif(_GCC_COMPATIBLE)
if (sodium_USE_STATIC_LIBS)
find_library(sodium_LIBRARY_DEBUG libsodium.a
HINTS ${sodium_DIR}
PATH_SUFFIXES lib
)
find_library(sodium_LIBRARY_RELEASE libsodium.a
HINTS ${sodium_DIR}
PATH_SUFFIXES lib
)
else()
find_library(sodium_LIBRARY_DEBUG libsodium.dll.a
HINTS ${sodium_DIR}
PATH_SUFFIXES lib
)
find_library(sodium_LIBRARY_RELEASE libsodium.dll.a
HINTS ${sodium_DIR}
PATH_SUFFIXES lib
)
file(GLOB _DLL
LIST_DIRECTORIES false
RELATIVE "${sodium_DIR}/bin"
"${sodium_DIR}/bin/libsodium*.dll"
)
find_library(sodium_DLL_DEBUG ${_DLL} libsodium
HINTS ${sodium_DIR}
PATH_SUFFIXES bin
)
find_library(sodium_DLL_RELEASE ${_DLL} libsodium
HINTS ${sodium_DIR}
PATH_SUFFIXES bin
)
endif()
else()
message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
endif()
########################################################################
# unsupported
else()
message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
endif()
########################################################################
# common stuff
# extract sodium version
if (sodium_INCLUDE_DIR)
set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h")
if (EXISTS _VERSION_HEADER)
file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT)
string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1"
sodium_VERSION "${_VERSION_HEADER_CONTENT}")
set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE)
endif()
endif()
# communicate results
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(sodium
REQUIRED_VARS
sodium_LIBRARY_RELEASE
sodium_LIBRARY_DEBUG
sodium_INCLUDE_DIR
VERSION_VAR
sodium_VERSION
)
# mark file paths as advanced
mark_as_advanced(sodium_INCLUDE_DIR)
mark_as_advanced(sodium_LIBRARY_DEBUG)
mark_as_advanced(sodium_LIBRARY_RELEASE)
if (WIN32)
mark_as_advanced(sodium_DLL_DEBUG)
mark_as_advanced(sodium_DLL_RELEASE)
endif()
# create imported target
if(sodium_USE_STATIC_LIBS)
set(_LIB_TYPE STATIC)
else()
set(_LIB_TYPE SHARED)
endif()
add_library(sodium ${_LIB_TYPE} IMPORTED)
set_target_properties(sodium PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}"
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
)
if (sodium_USE_STATIC_LIBS)
set_target_properties(sodium PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC"
IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
)
else()
if (UNIX)
set_target_properties(sodium PROPERTIES
IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
)
elseif (WIN32)
set_target_properties(sodium PROPERTIES
IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}"
IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}"
)
if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND"))
set_target_properties(sodium PROPERTIES
IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}"
)
endif()
if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND"))
set_target_properties(sodium PROPERTIES
IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}"
IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}"
IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}"
)
endif()
endif()
endif()

View File

@@ -16,27 +16,27 @@
find_program(ASCIIDOCTOR_EXE asciidoctor)
if(NOT ASCIIDOCTOR_EXE)
message(FATAL_ERROR "asciidoctor is required to build documentation")
else()
message(STATUS "Using asciidoctor: ${ASCIIDOCTOR_EXE}")
endif()
message(STATUS "Using asciidoctor: ${ASCIIDOCTOR_EXE}")
set(DOC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
set(REV -a revnumber=${KEEPASSXC_VERSION})
# Build html documentation on all platforms
file(GLOB html_depends ${DOC_DIR}/topics/* ${DOC_DIR}/styles/* ${DOC_DIR}/images/*)
add_custom_command(OUTPUT KeePassXC_GettingStarted.html
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_GettingStarted.html ${DOC_DIR}/GettingStarted.adoc
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_GettingStarted.html ${REV} ${DOC_DIR}/GettingStarted.adoc
DEPENDS ${html_depends} ${DOC_DIR}/GettingStarted.adoc
VERBATIM)
add_custom_command(OUTPUT KeePassXC_UserGuide.html
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_UserGuide.html ${DOC_DIR}/UserGuide.adoc
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_UserGuide.html ${REV} ${DOC_DIR}/UserGuide.adoc
DEPENDS ${html_depends} ${DOC_DIR}/UserGuide.adoc
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM)
file(GLOB styles_depends ${DOC_DIR}/styles/*)
add_custom_command(OUTPUT KeePassXC_KeyboardShortcuts.html
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_KeyboardShortcuts.html ${DOC_DIR}/topics/KeyboardShortcuts.adoc
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -o KeePassXC_KeyboardShortcuts.html ${REV} ${DOC_DIR}/topics/KeyboardShortcuts.adoc
DEPENDS ${DOC_DIR}/topics/KeyboardShortcuts.adoc ${styles_depends}
VERBATIM)
@@ -49,13 +49,13 @@ install(FILES
DESTINATION ${DATA_INSTALL_DIR}/docs)
# Build Man Pages on Linux and macOS
if(APPLE OR UNIX)
if(UNIX)
add_custom_command(OUTPUT keepassxc.1
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -b manpage ${DOC_DIR}/man/keepassxc.1.adoc
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -b manpage ${REV} ${DOC_DIR}/man/keepassxc.1.adoc
DEPENDS ${DOC_DIR}/man/keepassxc.1.adoc
VERBATIM)
add_custom_command(OUTPUT keepassxc-cli.1
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -b manpage ${DOC_DIR}/man/keepassxc-cli.1.adoc
COMMAND ${ASCIIDOCTOR_EXE} -D ${OUT_DIR} -b manpage ${REV} ${DOC_DIR}/man/keepassxc-cli.1.adoc
DEPENDS ${DOC_DIR}/man/keepassxc-cli.1.adoc
VERBATIM)
add_custom_target(manpages ALL DEPENDS keepassxc.1 keepassxc-cli.1)

68
docs/FuzzTest.md Normal file
View File

@@ -0,0 +1,68 @@
# Fuzz-Testing KeePassXC
Fuzz-testing = feeding random input into a program until it crashes. Be smart about what's "random" by looking at how the program executes the input.
We use the "American Fuzzy Lop" (AFL) fuzz tester (https://lcamtuf.coredump.cx/afl/).
The following assumes that all tools and libraries required to build KeePassXC from source have already been installed.
## Installing AFL
$ sudo apt install afl
Optionally, build AFL from source:
$ git clone https://github.com/google/AFL
$ cd AFL
$ make
$ make install
## Building KeePassXC For Fuzzing
A special "instrumented build" is used that allows the fuzzer to look into the program as it executes. We place it in its own build directory so it doesn't confused with the production build.
$ cd your_keepassxc_source_directory
$ mkdir buildafl
$ cd buildafl
$ CXX=afl-g++ AFL_HARDEN=1 cmake -DWITH_XC_ALL=ON ..
$ make
In the source code, special behavior for fuzz testing can be implemented with `#ifdef __AFL_COMPILER`. For example, in fuzz builds, the KeePassXC CLI takes the database password from environment variable `KEYPASSXC_AFL_PASSWORD` to allow non-interactive operation.
## Prepare Fuzzer Input
To get the fuzzer started, we provide empty password database files (the password is `secret`).
$ cd buildafl
$ mkdir -p findings/testcases
$ cp ../share/empty*.kdbx findings/testcases
The fuzzer works by running KeePassXC with variations of this input, mutated in ways that make the program crash or hang.
## Run The Fuzzer
$ cd buildafl
$ KEYPASSXC_AFL_PASSWORD=secret afl-fuzz -i findings/testcases -o findings -m 2000 -t 1000 src/cli/keepassxc-cli ls @@
This fuzz-tests the `ls` command of the KeePassXC CLI, which loads and decrypts a database file and then lists its contents. The parameters mean:
* `KEYPASSXC_AFL_PASSWORD=secret`: In fuzz test builds, the KeePassXC CLI takes the database password from this environment variable.
* `-i findings/testcases`: The directory which contains the initial fuzzer input.
* `-o findings`: The directory in which to store fuzzer results.
* `-m 2000`: Fuzzer memory (in megabytes). Adjust as required if the fuzzer fails to start up.
* `-t 1000`: Timeout until a hang is detected (in milliseconds).
* `src/cli/keepassxc-cli`: The instrumented executable.
* `ls`: The subcommand we're testing.
* `@@`: The fuzzer replaces this by the name of a file with the generated input.
You may also need `export AFL_SKIP_CPUFREQ=1`.
If KeePassXC crashes or hangs when processing the input, the fuzzer writes the database file (that was used in place of `@@`) to the `findings/crashes` or `findings/hangs` directory, respectively.
To continue where the fuzzer left off, use `-i -`. To start over, remove and re-create the `findings` directory.
## More Information
AFL documentation: https://afl-1.readthedocs.io/en/latest/
Read this if you want to get serious about fuzz-testing.

View File

@@ -1,6 +1,5 @@
= KeePassXC: Getting Started Guide
KeePassXC Team <team@keepassxc.org>
v2.6.0
:data-uri:
:linkcss!:
:homepage: https://keepassxc.org

View File

@@ -1,6 +1,5 @@
= KeePassXC: User Guide
KeePassXC Team <team@keepassxc.org>
v2.6.0
:data-uri:
:homepage: https://keepassxc.org
:icons: font
@@ -35,3 +34,5 @@ include::topics/AutoType.adoc[tags=*]
include::topics/KeeShare.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -16,4 +16,5 @@
== COPYRIGHT
Copyright \(C) 2016-2020 KeePassXC Team <team@keepassxc.org>
*KeePassXC* code is licensed under GPL-2 or GPL-3.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License, either version 2 or version 3.
There is NO WARRANTY, to the extent permitted by law.

View File

@@ -15,9 +15,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
= keepassxc-cli(1)
:docdate: 2020-07-10
KeePassXC Team <team@keepassxc.org>
:docdate: 2020-08-31
:doctype: manpage
:revnumber: 2.6.0
:mansource: KeePassXC {revnumber}
:manmanual: General Commands Manual
@@ -38,14 +38,14 @@ It provides the ability to query and modify the entries of a KeePass database, d
The same password generation options as documented for the generate command can be used when the *-g* option is set.
*analyze* [_options_] <__database__>::
Analyzes passwords in a database for weaknesses.
Analyzes passwords in a database for weaknesses using offline HIBP SHA-1 hash lookup.
*clip* [_options_] <__database__> <__entry__> [_timeout_]::
Copies an attribute or the current TOTP (if the *-t* option is specified) of a database entry to the clipboard.
If no attribute name is specified using the *-a* option, the password is copied.
If multiple entries with the same name exist in different groups, only the attribute for the first one is copied.
For copying the attribute of an entry in a specific group, the group path to the entry should be specified as well, instead of just the name.
Optionally, a timeout in seconds can be specified to automatically clear the clipboard.
Optionally, a timeout in seconds can be specified to automatically clear the clipboard, the default timeout is 10 seconds, set to 0 to disable.
*close*::
In interactive mode, closes the currently opened database (see *open*).
@@ -84,7 +84,12 @@ It provides the ability to query and modify the entries of a KeePass database, d
Displays a list of available commands, or detailed information about the specified command.
*import* [_options_] <__xml__> <__database__>::
Imports the contents of an XML database to the target database.
Imports the contents of an XML exported database to a new created database
with a password and/or key file.
The key file will be created if the file that is referred to does not exist.
If both the key file and password are empty, no database will be created.
The new database will be in kdbx 4 format.
*locate* [_options_] <__database__> <__term__>::
Locates all the entries that match a specific search term in a database.
@@ -177,6 +182,9 @@ The same password generation options as documented for the generate command can
*--url* <__url__>::
Specifies the URL of the entry.
*--notes* <__notes__>::
Specifies the notes of the entry.
*-p*, *--password-prompt*::
Uses a password prompt for the entry's password.
@@ -197,6 +205,10 @@ The same password generation options as documented for the generate command can
Such files are available from https://haveibeenpwned.com/Passwords;
note that they are large, and so this operation typically takes some time (minutes up to an hour or so).
*--okon* <__okon-cli path__>::
Use the specified okon-cli program to perform offline breach checks. You can obtain okon-cli from https://github.com/stryku/okon.
When using this option, *-H, --hibp* must point to a post-processed okon file (e.g. file.okon).
=== Clip options
*-a*, *--attribute*::
Copies the specified attribute to the clipboard.
@@ -208,7 +220,12 @@ The same password generation options as documented for the generate command can
Copies the current TOTP instead of the specified attribute to the clipboard.
Will report an error if no TOTP is configured for the entry.
=== Create options
*-b*, *--best*::
Try to find and copy to clipboard a unique entry matching the input (similar to *-locate*)
If a unique matching entry is found it will be copied to the clipboard.
If multiple entries are found they will be listed to refine the search. (no clip performed)
=== Create and Import options
*-k*, *--set-key-file* <__path__>::
Set the key file for the database.
@@ -292,11 +309,11 @@ The same password generation options as documented for the generate command can
Include characters from every selected group.
[Default: Disabled]
include::section-notes.adoc[]
include::includes/section-notes.adoc[]
== AUTHOR
This manual page was originally written by Manolis Agkopian <m.agkopian@gmail.com>.
include::section-reporting-bugs.adoc[]
include::includes/section-reporting-bugs.adoc[]
include::section-copyright.adoc[]
include::includes/section-copyright.adoc[]

View File

@@ -15,9 +15,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
= keepassxc(1)
:docdate: 2020-07-10
KeePassXC Team <team@keepassxc.org>
:docdate: 2020-08-31
:doctype: manpage
:revnumber: 2.6.0
:mansource: KeePassXC {revnumber}
:manmanual: General Commands Manual
@@ -55,11 +55,11 @@ Your wallet works offline and requires no Internet connection.
*--debug-info*::
Displays debugging information.
include::section-notes.adoc[]
include::includes/section-notes.adoc[]
== AUTHOR
This manual page was originally written by Janek Bevendorff <janek@jbev.net>.
include::section-reporting-bugs.adoc[]
include::includes/section-reporting-bugs.adoc[]
include::section-copyright.adoc[]
include::includes/section-copyright.adoc[]

View File

@@ -1,5 +1,4 @@
KeePassXC Team <team@keepassxc.org>
v2.6.0
:data-uri:
:homepage: https://keepassxc.org
:stylesheet: ../styles/dark.css

View File

@@ -31,26 +31,9 @@ To configure Auto-Type sequences for your entries, perform the following steps:
.Auto-Type entry sequences
image::autotype_entry_sequences.png[]
2. _(Optional)_ Define a custom auto-type sequence for each window title match by selecting the _Use specific sequence for this association_ checkbox. Sequence action codes and field placeholders are detailed in the following table. 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%]
|===
|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%]
[grid=rows, frame=none, width=90%]
|===
|Placeholder |Description
@@ -60,21 +43,44 @@ image::autotype_entry_sequences.png[]
|{URL} |URL
|{NOTES} |Notes
|{TOTP} |Current TOTP value (if configured)
|{DT_SIMPLE} |Current date-time
|{DB_DIR} |Absolute directory path for database file
|{S:<ATTRIBUTE_NAME>} |Value for the given attribute name
|{REF:<FIELD>@<SEARCH_IN>:<SEARCH_TEXT>} |Search for a field in another entry using the reference syntax.
|===
+
[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
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
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
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`.

View File

@@ -101,7 +101,7 @@ NOTE: You can disable the recycle bin within the Database Settings. If the recyc
Creating a clone of an entry provides you a ready-to-use template for creating new entries with similar details of a master entry.
To create a clone of an existing entry, perform the following steps:
1. Right-click on the entry for which you want to create a clone and select _Clone Entry_. Alternatively, select the desired entry and press `Ctrl+K`.
+
.Clone entry from context menu
@@ -120,18 +120,7 @@ image::clone_entry_dialog.png[,70%]
.References in a cloned entry
image::clone_entry_references.png[]
4. You can create your own references using the following syntax:
+
`{REF:<ShortCode>@I:<UUID>}`
+
Where `<UUID>` is the Unique Identifier of the entry to pull data from and `<ShortCode>` is from the following:
+
* T - Title
* U - Username
* P - Password
* A - URL
* N - Notes
* I - UUID
4. You can create your own references using the xref:UserGuide.adoc#_entry_cross_reference[Entry Reference Syntax]
== Searching the Database
KeePassXC provides an enhanced and granular search features the enables you to search for specific entries in the databases using the different modifiers, wild card characters, and logical operators.
@@ -182,6 +171,9 @@ The following tables lists a few samples search queries for your reference:
|`+user:johnsmith -url:www.google.com *notes:"secret note \d"`
|Search the username field for exactly johnsmith, the URL must not contain www.google.com, and notes contains secret note [digit].
|`+attr:mystring123`
|Searches all Additional Attributes for any name OR value equal to mystring123.
|===
== Advanced Entry Options
@@ -287,6 +279,8 @@ image::database_security.png[]
+
.Database credentials
image::database_security_credentials.png[]
+
WARNING: Consider creating a backup of your YubiKey. Please refer to <<Creating a YubiKey backup>>
5. Encryption settings allows you to change the average time it takes to encrypt and decrypt the database. The longer time that is chosen, the harder it will be to brute force attack your database. *We recommend a setting of one second.*
+
@@ -305,6 +299,23 @@ The following key derivation functions are supported:
* AES-KDF (KDBX 4 and KDBX 3.1): This key derivation function is based on iterating AES. Users can change the number of iterations. The more iterations, the harder are dictionary and guessing attacks, but also database loading/saving takes more time (linearly). KDBX 3.1 only supports AES-KDF; any other key derivation function, like for instance Argon2, requires KDBX 4.
* Argon2 (KDBX 4 - recommended): KDBX 4, the Argon2 key derivation function can be used for transforming the composite master key (as protection against dictionary attacks). The main advantage of Argon2 over AES-KDF is that it provides a better resistance against GPU/ASIC attacks (due to being a memory-hard function). The number of iterations scales linearly with the required time. By increasing the memory parameter, GPU/ASIC attacks become harder (and the required time increases). The parallelism parameter can be used to specify how many threads should be used.
=== Creating a YubiKey backup
It is advisable to have a backup replica YubiKey In case your main YubiKey gets damaged, lost, or stolen. The same HMAC key will need to be written to both keys. To do this you can either use the YubiKey Personalization Tool GUI or the ykpersonalize CLI tool. The steps for the CLI tool are shown:
1. Create a 20 byte HMAC key:
+
```
dd status=none if=/dev/random bs=20 count=1 | xxd -p -c 40
```
2. Write the HMAC key to slot 2 _(Set through the first switch. Out of the box the YubiKey OTP resides in slot 1)_:
+
```
ykpersonalize -2 -a -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible -oallow-update
```
You will be asked to enter the HMAC key you created earlier, copy/paste they key output in the first step. Repeat both steps for your second YubiKey. We recommend storing your HMAC key in a safe place (e.g., printed on paper) in case you need to recreate another key.
// end::advanced[]
== Storing a Database File

View File

@@ -60,3 +60,5 @@ To install the KeePassXC app on macOS, double click on the downloaded DMG file a
image::macos_install.png[,80%]
// end::content[]
// tag::advanced[]
// end::advanced[]

View File

@@ -61,4 +61,11 @@ WARNING: Exporting your database will result in all of your passwords and sensit
.Database export menu
image::export_database.png[,80%]
The HTML export file is intended to be human-readable (viewed/printed in a web browser) rather than machine-readable (re-imported into another database file). The intention of HTML export is to provide a "paper backup" functionality for those who want to ensure access to their passwords in case of catastrophic failure of IT infrastructure. To create a paper backup, export the database to an HTML file, print the file with your web browser, then delete the file.
WARNING: Creating a paper backup exposes your passwords to potentially insecure components, like printer drivers on your computer or software inside the printer. Make sure all these components can be trusted.
For more information, check out the https://keepassxc.org/blog/2020-10-03-paper-backup/[blog article about paper backups].
// end::content[]

View File

@@ -22,10 +22,11 @@ image::password_generator.png[]
5. Use the regenerate button (Ctrl + R) to make a new password using the chosen options.
6. Use the clipboard button (Ctrl + C) to copy the generated password to the clipboard.
// tag::advanced[]
7. Click the Advanced button to specify additional conditions for your desired password.
7. Click the Advanced button to specify additional conditions for your desired password.
+
.Advanced Password Generator Options
image::password_generator_advanced.png[]
8. When generating a password for an entry, click the Apply Password button (Ctrl + S or Ctrl + Enter) to close the window and apply your changes.
=== Generating Passphrases
A passphrase is a sequence of words or other text used to control access to your applications and data. A passphrase is similar to a password in usage, but is generally longer for added security. To generate the random passphrase using Password Generator, perform the following steps:
@@ -40,5 +41,6 @@ Word Count slider.
3. In the Word Separator field, enter a character, word, number, or space that you want to use a separator between the words in your passphrase.
4. Click the Regenerate button (Ctrl + R) to generate a new random passphrase.
5. Click the Clipboard button (Ctrl + C) to copy the passphrase to the clipboard.
6. When generating a password for an entry, click the Apply Password button (Ctrl + S or Ctrl + Enter) to close the window and apply your changes.
// end::advanced[]
// end::content[]

View 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[]

View File

@@ -10,7 +10,7 @@ The KeePassXC interface is designed for simplicity and easy access to your infor
.Main database interface
image::main_interface.png[]
*(A) Groups* - Organize your entries into discrete groups to bring order to all of your sensitive information. Groups can be nested under each other to create a hierarchy. Settings from parent groups get applied to their children.
*(A) Groups* - Organize your entries into discrete groups to bring order to all of your sensitive information. Groups can be nested under each other to create a hierarchy. Settings from parent groups get applied to their children. You can hide this panel on the View menu.
*(B) Entries* - Entries contain all the information for each website or application you are storing in KeePassXC. This view shows all the entries in the selected group. Each column can be resized, reordered, and shown or hidden based on your preference. Right click the header row to see all available options.
@@ -46,6 +46,14 @@ For users with smaller screens or those who desire seeing more entries at once,
.Compact mode comparison
image::compact_mode_comparison.png[]
==== Copy entry Columns on Double Click
KeePassXC offers the possibility of copying certain entry columns, such as username and password, into your clipboard on double click, for a limited period of time.
This feature can be enabled by checking the box "Enable double click to copy some entry columns" in the security settings:
.Enable copying on double click
image::enable_copy_dc.png[]
=== Keyboard Shortcuts
include::KeyboardShortcuts.adoc[tag=content, leveloffset=+1]

View File

@@ -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.)
** Additional encryption choices: Twofish and ChaCha20
// end::content[]
// tag::advanced[]
// end::advanced[]

View File

@@ -37,13 +37,14 @@ DOCKER_CONTAINER_NAME="keepassxc-build-container"
CMAKE_OPTIONS=""
CPACK_GENERATORS="WIX;ZIP"
COMPILER="g++"
MAKE_OPTIONS="-j8"
MAKE_OPTIONS="-j$(getconf _NPROCESSORS_ONLN)"
BUILD_PLUGINS="all"
INSTALL_PREFIX="/usr/local"
ORIG_BRANCH=""
ORIG_CWD="$(pwd)"
MACOSX_DEPLOYMENT_TARGET=10.12
MACOSX_DEPLOYMENT_TARGET=10.13
GREP="grep"
TIMESTAMP_SERVER="http://timestamp.sectigo.com"
# -----------------------------------------------------------------------
# helper functions
@@ -53,7 +54,7 @@ printUsage() {
if [ "" == "$1" ] || [ "help" == "$1" ]; then
cmd="COMMAND"
elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] \
|| [ "gpgsign" == "$1" ] || [ "appsign" == "$1" ] || [ "appimage" == "$1" ]; then
|| [ "gpgsign" == "$1" ] || [ "appsign" == "$1" ] || [ "notarize" == "$1" ] || [ "appimage" == "$1" ]; then
cmd="$1"
else
logError "Unknown command: '$1'\n"
@@ -71,6 +72,7 @@ Commands:
build Build and package binary release from sources
gpgsign Sign previously compiled release packages with GPG
appsign Sign binaries with code signing certificates on Windows and macOS
notarize Submit macOS application DMG for notarization
help Show help for the given command
EOF
elif [ "merge" == "$cmd" ]; then
@@ -113,6 +115,7 @@ Options:
--appimage Build a Linux AppImage after compilation.
If this option is set, --install-prefix has no effect
--appsign Perform platform specific App Signing before packaging
--timestamp Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}')
-k, --key Specify the App Signing Key/Identity
-c, --cmake-options Additional CMake options for compiling the sources
--compiler Compiler to use (default: '${COMPILER}')
@@ -144,7 +147,18 @@ Options:
-f, --files Files to sign (required)
-k, --key, -i, --identity
Signing Key or Apple Developer ID (required)
--timestamp Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}')
-u, --username Apple username for notarization (required on macOS)
-h, --help Show this help
EOF
elif [ "notarize" == "$cmd" ]; then
cat << EOF
Submit macOS application DMG for notarization
Options:
-f, --files Files to notarize (required)
-u, --username Apple username for notarization (required)
-c, --keychain Apple keychain entry name storing the notarization
app password (default: 'AC_PASSWORD')
-h, --help Show this help
@@ -401,7 +415,7 @@ performChecks() {
checkTargetBranchExists
logInfo "Checking out '${SOURCE_BRANCH}'..."
git checkout "$SOURCE_BRANCH"
git checkout "$SOURCE_BRANCH" > /dev/null 2>&1
logInfo "Attempting to find '${RELEASE_NAME}' in various files..."
@@ -484,6 +498,10 @@ merge() {
GPG_GIT_KEY="$2"
shift ;;
--timestamp)
TIMESTAMP_SERVER="$2"
shift ;;
-r|--release-branch)
SOURCE_BRANCH="$2"
shift ;;
@@ -534,7 +552,7 @@ merge() {
COMMIT_MSG="Release ${RELEASE_NAME}"
logInfo "Checking out target branch '${TARGET_BRANCH}'..."
git checkout "$TARGET_BRANCH"
git checkout "$TARGET_BRANCH" > /dev/null 2>&1
logInfo "Merging '${SOURCE_BRANCH}' into '${TARGET_BRANCH}'..."
@@ -800,6 +818,10 @@ build() {
--appsign)
build_appsign=true ;;
--timestamp)
TIMESTAMP_SERVER="$2"
shift ;;
-k|--key)
build_key="$2"
shift ;;
@@ -877,7 +899,7 @@ build() {
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Release"
logInfo "Checking out release tag '${TAG_NAME}'..."
fi
git checkout "$TAG_NAME"
git checkout "$TAG_NAME" > /dev/null 2>&1
fi
logInfo "Creating output directory..."
@@ -949,8 +971,8 @@ build() {
logInfo "Configuring build..."
cmake -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
-DCMAKE_PREFIX_PATH="/usr/local/opt/qt/lib/cmake" \
-DCMAKE_OSX_ARCHITECTURES="$(uname -m)" -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
-DCMAKE_PREFIX_PATH="/opt/homebrew/opt/qt/lib/cmake;/usr/local/opt/qt/lib/cmake" \
${CMAKE_OPTIONS} "$SRC_DIR"
logInfo "Compiling and packaging sources..."
@@ -962,7 +984,7 @@ build() {
appsign "-f" "./${APP_NAME}-${RELEASE_NAME}.dmg" "-k" "${build_key}"
fi
mv "./${APP_NAME}-${RELEASE_NAME}.dmg" ../
mv "./${APP_NAME}-${RELEASE_NAME}.dmg" "../${APP_NAME}-${RELEASE_NAME}-$(uname -m).dmg"
elif [ "$(uname -o)" == "Msys" ]; then
# Building on Windows with Msys2
logInfo "Configuring build..."
@@ -1130,8 +1152,6 @@ gpgsign() {
appsign() {
local sign_files=()
local key
local ac_username
local ac_keychain="AC_PASSWORD"
while [ $# -ge 1 ]; do
local arg="$1"
@@ -1146,14 +1166,6 @@ appsign() {
key="$2"
shift ;;
-u|--username)
ac_username="$2"
shift ;;
-c|--keychain)
ac_keychain="$2"
shift ;;
-h|--help)
printUsage "appsign"
exit ;;
@@ -1179,16 +1191,12 @@ appsign() {
fi
for f in "${sign_files[@]}"; do
if [ ! -f "${f}" ]; then
exitError "File '${f}' does not exist or is not a file!"
if [ ! -e "${f}" ]; then
exitError "File '${f}' does not exist!"
fi
done
if [ "$(uname -s)" == "Darwin" ]; then
if [ "$ac_username" == "" ]; then
exitError "Missing arguments, --username is required!"
fi
checkXcodeSetup
checkGrepCompat
@@ -1199,30 +1207,45 @@ appsign() {
logInfo "Unpacking disk image '${f}'..."
local tmp_dir="/tmp/KeePassXC_${RANDOM}"
mkdir -p ${tmp_dir}/mnt
hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}"
if ! hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}"; then
exitError "DMG mount failed!"
fi
cd ${tmp_dir}
cp -a ./mnt ./app
hdiutil detach -quiet ${tmp_dir}/mnt
local app_dir_tmp="./app/KeePassXC.app"
if [ ! -d ./app/KeePassXC.app ]; then
if [ ! -d "$app_dir_tmp" ]; then
cd "${orig_dir}"
exitError "Unpacking failed!"
fi
elif [[ ${f: -4} == '.app' ]]; then
local app_dir_tmp="$f"
else
logWarn "Skipping non-app file '${f}'..."
continue
fi
logInfo "Signing app bundle..."
xcrun codesign --sign "${key}" --verbose --deep --options runtime ./app/KeePassXC.app
# Sign main binary and libraries independently so we can keep using the convenient --deep
# option while avoiding adding entitlements recursively
logInfo "Signing main binary..."
xcrun codesign --sign "${key}" --verbose --force --options runtime --entitlements \
"${real_src_dir}/share/macosx/keepassxc.entitlements" ./app/KeePassXC.app/Contents/MacOS/KeePassXC
if [ 0 -ne $? ]; then
cd "${orig_dir}"
exitError "Signing failed!"
fi
logInfo "Signing libraries and frameworks..."
if ! find "$app_dir_tmp" \( -name '*.dylib' -o -name '*.so' -o -name '*.framework' \) -print0 | xargs -0 \
xcrun codesign --sign "${key}" --verbose --force --options runtime; then
cd "${orig_dir}"
exitError "Signing failed!"
fi
logInfo "Signing executables..."
if ! find "${app_dir_tmp}/Contents/MacOS" \( -type f -not -name KeePassXC \) -print0 | xargs -0 \
xcrun codesign --sign "${key}" --verbose --force --options runtime; then
cd "${orig_dir}"
exitError "Signing failed!"
fi
# Sign main executable with additional entitlements
if ! xcrun codesign --sign "${key}" --verbose --force --options runtime --entitlements \
"${real_src_dir}/share/macosx/keepassxc.entitlements" "${app_dir_tmp}/Contents/MacOS/KeePassXC"; then
cd "${orig_dir}"
exitError "Signing failed!"
fi
if [[ ${f: -4} == '.dmg' ]]; then
logInfo "Repacking disk image..."
hdiutil create \
-volname "KeePassXC" \
@@ -1236,52 +1259,9 @@ appsign() {
cd "${orig_dir}"
cp -f "${tmp_dir}/$(basename "${f}")" "${f}"
rm -Rf ${tmp_dir}
logInfo "Submitting disk image for notarization..."
local status="$(xcrun altool --notarize-app \
--primary-bundle-id "org.keepassxc.keepassxc" \
--username "${ac_username}" \
--password "@keychain:${ac_keychain}" \
--file "${f}")"
if [ 0 -ne $? ]; then
logError "Submission failed!"
exitError "Error message:\n${status}"
fi
local ticket="$(echo "${status}" | $GREP -oP "[a-f0-9-]+$")"
logInfo "Submission successful. Ticket ID: ${ticket}."
logInfo "Waiting for notarization to finish (this may take a while)..."
while true; do
echo -n "."
status="$(xcrun altool --notarization-info "${ticket}" \
--username "${ac_username}" \
--password "@keychain:${ac_keychain}")"
if echo "$status" | $GREP -q "Status Code: 0"; then
logInfo "\nNotarization successful."
break
elif echo "$status" | $GREP -q "Status Code"; then
logError "\nNotarization failed!"
exitError "Error message:\n${status}"
fi
sleep 5
done
logInfo "Stapling ticket to disk image..."
xcrun stapler staple "${f}"
if [ 0 -ne $? ]; then
exitError "Stapling failed!"
fi
logInfo "Disk image successfully signed and notarized."
else
logWarn "Skipping non-DMG file '${f}'..."
fi
logInfo "File '${f}' successfully signed."
done
elif [ "$(uname -o)" == "Msys" ]; then
@@ -1301,7 +1281,7 @@ appsign() {
# osslsigncode does not succeed at signing MSI files at this time...
logInfo "Signing file '${f}' using Microsoft signtool..."
signtool sign -f "${key}" -p "${password}" -d "KeePassXC" -td sha256 \
-fd sha256 -tr "http://timestamp.comodoca.com/authenticode" "${f}"
-fd sha256 -tr "${TIMESTAMP_SERVER}" "${f}"
if [ 0 -ne $? ]; then
exitError "Signing failed!"
@@ -1318,6 +1298,112 @@ appsign() {
logInfo "All done!"
}
# -----------------------------------------------------------------------
# notarize command
# -----------------------------------------------------------------------
notarize() {
local notarize_files=()
local ac_username
local ac_keychain="AC_PASSWORD"
while [ $# -ge 1 ]; do
local arg="$1"
case "$arg" in
-f|--files)
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
notarize_files+=("$2")
shift
done ;;
-u|--username)
ac_username="$2"
shift ;;
-c|--keychain)
ac_keychain="$2"
shift ;;
-h|--help)
printUsage "notarize"
exit ;;
*)
logError "Unknown option '$arg'\n"
printUsage "notarize"
exit 1 ;;
esac
shift
done
if [ "$(uname -s)" != "Darwin" ]; then
exitError "Notarization is only supported on macOS!"
fi
if [ -z "${notarize_files}" ]; then
logError "Missing arguments, --files is required!\n"
printUsage "notarize"
exit 1
fi
if [ "$ac_username" == "" ]; then
logError "Missing arguments, --username is required!"
printUsage "notarize"
exit 1
fi
for f in "${notarize_files[@]}"; do
if [[ ${f: -4} != '.dmg' ]]; then
logWarn "Skipping non-DMG file '${f}'..."
continue
fi
logInfo "Submitting disk image '${f}' for notarization..."
local status
status="$(xcrun altool --notarize-app \
--primary-bundle-id "org.keepassxc.keepassxc" \
--username "${ac_username}" \
--password "@keychain:${ac_keychain}" \
--file "${f}" 2> /dev/null)"
if [ 0 -ne $? ]; then
logError "Submission failed!"
exitError "Error message:\n${status}"
fi
local ticket="$(echo "${status}" | $GREP -oP "[a-f0-9-]+$")"
logInfo "Submission successful. Ticket ID: ${ticket}."
logInfo "Waiting for notarization to finish (this may take a while)..."
while true; do
echo -n "."
status="$(xcrun altool --notarization-info "${ticket}" \
--username "${ac_username}" \
--password "@keychain:${ac_keychain}" 2> /dev/null)"
if echo "$status" | $GREP -q "Status Code: 0"; then
logInfo "\nNotarization successful."
break
elif echo "$status" | $GREP -q "Status Code"; then
logError "\nNotarization failed!"
exitError "Error message:\n${status}"
fi
sleep 5
done
logInfo "Stapling ticket to disk image..."
xcrun stapler staple "${f}"
if [ 0 -ne $? ]; then
exitError "Stapling failed!"
fi
logInfo "Disk image successfully notarized."
done
}
# -----------------------------------------------------------------------
# parse global command line
# -----------------------------------------------------------------------
@@ -1331,7 +1417,8 @@ elif [ "help" == "$MODE" ]; then
printUsage "$1"
exit
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] \
|| [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ] || [ "appimage" == "$MODE" ]; then
|| [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ]|| [ "notarize" == "$MODE" ] \
|| [ "appimage" == "$MODE" ]; then
${MODE} "$@"
else
printUsage "$MODE"

BIN
share/empty3.kdbx Normal file

Binary file not shown.

BIN
share/empty4.kdbx Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M13.78 15.3L19.78 21.3L21.89 19.14L15.89 13.14L13.78 15.3M17.5 10.1C17.11 10.1 16.69 10.05 16.36 9.91L4.97 21.25L2.86 19.14L10.27 11.74L8.5 9.96L7.78 10.66L6.33 9.25V12.11L5.63 12.81L2.11 9.25L2.81 8.55H5.62L4.22 7.14L7.78 3.58C8.95 2.41 10.83 2.41 12 3.58L9.89 5.74L11.3 7.14L10.59 7.85L12.38 9.63L14.2 7.75C14.06 7.42 14 7 14 6.63C14 4.66 15.56 3.11 17.5 3.11C18.09 3.11 18.61 3.25 19.08 3.53L16.41 6.2L17.91 7.7L20.58 5.03C20.86 5.5 21 6 21 6.63C21 8.55 19.45 10.1 17.5 10.1Z" /></svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,1A5,5 0 0,0 7,6V8H6A2,2 0 0,0 4,10V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V10A2,2 0 0,0 18,8H17V6A5,5 0 0,0 12,1M12,2.9C13.71,2.9 15.1,4.29 15.1,6V8H8.9V6C8.9,4.29 10.29,2.9 12,2.9M12.19,10.5C13.13,10.5 13.88,10.71 14.42,11.12C14.96,11.54 15.23,12.1 15.23,12.8C15.23,13.24 15.08,13.63 14.79,14C14.5,14.36 14.12,14.64 13.66,14.85C13.4,15 13.23,15.15 13.14,15.32C13.05,15.5 13,15.72 13,16H11C11,15.5 11.1,15.16 11.29,14.92C11.5,14.68 11.84,14.4 12.36,14.08C12.62,13.94 12.83,13.76 13,13.54C13.14,13.33 13.22,13.08 13.22,12.8C13.22,12.5 13.13,12.28 12.95,12.11C12.77,11.93 12.5,11.85 12.19,11.85C11.92,11.85 11.7,11.92 11.5,12.06C11.34,12.2 11.24,12.41 11.24,12.69H9.27C9.22,12 9.5,11.4 10.05,11.04C10.59,10.68 11.3,10.5 12.19,10.5M11,17H13V19H11V17Z" /></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -42,10 +42,12 @@
<file>application/scalable/actions/group-edit.svg</file>
<file>application/scalable/actions/group-empty-trash.svg</file>
<file>application/scalable/actions/group-new.svg</file>
<file>application/scalable/actions/hammer-wrench.svg</file>
<file>application/scalable/actions/health.svg</file>
<file>application/scalable/actions/help-about.svg</file>
<file>application/scalable/actions/hibp.svg</file>
<file>application/scalable/actions/key-enter.svg</file>
<file>application/scalable/actions/lock-question.svg</file>
<file>application/scalable/actions/keyboard-shortcuts.svg</file>
<file>application/scalable/actions/message-close.svg</file>
<file>application/scalable/actions/move-down.svg</file>
@@ -58,7 +60,7 @@
<file>application/scalable/actions/password-generator.svg</file>
<file>application/scalable/actions/password-show-off.svg</file>
<file>application/scalable/actions/password-show-on.svg</file>
<file>application/scalable/actions/refresh.svg</file>
<file>application/scalable/actions/refresh.svg</file>
<file>application/scalable/actions/reports.svg</file>
<file>application/scalable/actions/reports-exclude.svg</file>
<file>application/scalable/actions/sort-alphabetical-ascending.svg</file>

View File

@@ -50,6 +50,23 @@
</screenshots>
<releases>
<release version="2.6.4" date="2021-01-31">
<description>
<ul>
<li>Automatically adapt to light/dark system theme changes (Windows/macOS only) [#6034]</li>
<li>Show window title as tooltip on system tray [#5948]</li>
<li>Compress Snap release as LZO for faster initial startup [#5877]</li>
<li>Password generator: Set maximum selectable password length to 999 [#5937]</li>
<li>Fix crash on app close when using SSH agent [#5935]</li>
<li>Fix KDF selection showing wrong item when using Argon2id [#5923]</li>
<li>Automatically close About dialog on database lock if it is still open [#5947]</li>
<li>Linux: Fix automatic launch at system startup with AppImages [#5901]</li>
<li>Linux: Fix click-to-move on empty area activating when using menus [#5971]</li>
<li>Linux: Try multiple times to show tray icon if tray is not ready yet [#5948]</li>
<li>macOS: Fix KeePassXC blocking clean shutdown [#6002]</li>
</ul>
</description>
</release>
<release version="2.6.3" date="2021-01-12">
<description>
<ul>

View File

@@ -4864,6 +4864,10 @@ Expect some bugs and minor issues, this version is not meant for production use.
<source>Show Toolbar</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Show Groups Panel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Show Preview Panel</source>
<translation type="unfinished"></translation>

View File

@@ -7477,7 +7477,7 @@ Kernel: %3 %4</translation>
</message>
<message>
<source>Do you want to trust %1 with the fingerprint of %2 from %3?</source>
<translation>Do you want to trust %1 with the fingerprint of %2 from %3? {1 ?} {2 ?}</translation>
<translation>Do you want to trust %1 with the fingerprint of %2 from %3?</translation>
</message>
<message>
<source>Not this time</source>

View File

@@ -85,8 +85,9 @@
</Directory>
</DirectoryRef>
<Property Id="AUTOSTARTPROGRAM" Value="1" />
<Property Id="LAUNCHAPPONEXIT" Value="1" />
<Property Id="AUTOSTARTPROGRAM" Value="1" Secure="yes" />
<Property Id="LAUNCHAPPONEXIT" Value="1" Secure="yes" />
<Property Id="INSTALLDESKTOPSHORTCUT" Secure="yes" />
<FeatureRef Id="ProductFeature">
<ComponentRef Id="ApplicationShortcut" />

View File

@@ -1,5 +1,5 @@
name: keepassxc
version: 2.6.3
version: 2.7.0
grade: stable
summary: Community-driven port of the Windows application “KeePass Password Safe”
description: |
@@ -8,6 +8,7 @@ description: |
published under the terms of the GNU General Public License.
confinement: strict
base: core18
compression: lzo
plugs: # plugs for theming, font settings, cursor and to use gtk3 file chooser
gtk-3-themes:

View File

@@ -33,7 +33,6 @@ endif(NOT ZXCVBN_LIBRARIES)
set(keepassx_SOURCES
core/Alloc.cpp
core/AutoTypeAssociations.cpp
core/AutoTypeMatch.cpp
core/Base32.cpp
core/Bootstrap.cpp
core/Clock.cpp
@@ -58,8 +57,6 @@ set(keepassx_SOURCES
core/PassphraseGenerator.cpp
core/Resources.cpp
core/SignalMultiplexer.cpp
core/ScreenLockListener.cpp
core/ScreenLockListenerPrivate.cpp
core/TimeDelta.cpp
core/TimeInfo.cpp
core/Tools.cpp
@@ -70,7 +67,6 @@ set(keepassx_SOURCES
crypto/CryptoHash.cpp
crypto/Random.cpp
crypto/SymmetricCipher.cpp
crypto/SymmetricCipherGcrypt.cpp
crypto/kdf/Kdf.cpp
crypto/kdf/AesKdf.cpp
crypto/kdf/Argon2Kdf.cpp
@@ -127,6 +123,7 @@ set(keepassx_SOURCES
gui/PasswordEdit.cpp
gui/PasswordGeneratorWidget.cpp
gui/ApplicationSettingsWidget.cpp
gui/Icons.cpp
gui/SearchWidget.cpp
gui/SortFilterHideProxyModel.cpp
gui/SquareSvgWidget.cpp
@@ -140,8 +137,6 @@ set(keepassx_SOURCES
gui/csvImport/CsvImportWizard.cpp
gui/csvImport/CsvParserModel.cpp
gui/entry/AutoTypeAssociationsModel.cpp
gui/entry/AutoTypeMatchModel.cpp
gui/entry/AutoTypeMatchView.cpp
gui/entry/EditEntryWidget.cpp
gui/entry/EntryAttachmentsModel.cpp
gui/entry/EntryAttachmentsWidget.cpp
@@ -159,6 +154,7 @@ set(keepassx_SOURCES
gui/dbsettings/DatabaseSettingsWidget.cpp
gui/dbsettings/DatabaseSettingsDialog.cpp
gui/dbsettings/DatabaseSettingsWidgetGeneral.cpp
gui/dbsettings/DatabaseSettingsWidgetMaintenance.cpp
gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp
gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp
gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
@@ -171,8 +167,11 @@ set(keepassx_SOURCES
gui/reports/ReportsWidgetStatistics.cpp
gui/reports/ReportsPageStatistics.cpp
gui/osutils/OSUtilsBase.cpp
gui/osutils/ScreenLockListener.cpp
gui/osutils/ScreenLockListenerPrivate.cpp
gui/settings/SettingsWidget.cpp
gui/widgets/ElidedLabel.cpp
gui/widgets/KPToolBar.cpp
gui/widgets/PopupHelpWidget.cpp
gui/wizard/NewDatabaseWizard.cpp
gui/wizard/NewDatabaseWizardPage.cpp
@@ -182,8 +181,7 @@ set(keepassx_SOURCES
keys/CompositeKey.cpp
keys/FileKey.cpp
keys/PasswordKey.cpp
keys/YkChallengeResponseKey.cpp
keys/YkChallengeResponseKeyCLI.cpp
keys/ChallengeResponseKey.cpp
streams/HashedBlockStream.cpp
streams/HmacBlockStream.cpp
streams/LayeredStream.cpp
@@ -194,30 +192,26 @@ set(keepassx_SOURCES
if(APPLE)
set(keepassx_SOURCES
${keepassx_SOURCES}
core/ScreenLockListenerMac.cpp
core/MacPasteboard.cpp
gui/osutils/macutils/MacUtils.cpp
gui/osutils/macutils/ScreenLockListenerMac.cpp
gui/osutils/macutils/AppKitImpl.mm
gui/osutils/macutils/AppKit.h)
endif()
if(UNIX AND NOT APPLE)
set(keepassx_SOURCES
${keepassx_SOURCES}
core/ScreenLockListenerDBus.cpp
gui/MainWindowAdaptor.cpp
gui/osutils/nixutils/NixUtils.cpp)
gui/osutils/nixutils/ScreenLockListenerDBus.cpp
gui/osutils/nixutils/NixUtils.cpp
gui/osutils/nixutils/X11Funcs.cpp)
endif()
if(MINGW)
set(keepassx_SOURCES
${keepassx_SOURCES}
core/ScreenLockListenerWin.cpp
gui/osutils/winutils/ScreenLockListenerWin.cpp
gui/osutils/winutils/WinUtils.cpp)
endif()
if(MINGW OR (UNIX AND NOT APPLE))
set(keepassx_SOURCES
${keepassx_SOURCES}
core/OSEventFilter.cpp)
endif()
set(keepassx_SOURCES ${keepassx_SOURCES}
../share/icons/icons.qrc
@@ -252,11 +246,6 @@ add_subdirectory(cli)
add_subdirectory(qrcode)
set(qrcode_LIB qrcode)
add_subdirectory(crypto/ssh)
if(WITH_XC_CRYPTO_SSH)
set(crypto_ssh_LIB crypto_ssh)
endif()
add_subdirectory(keeshare)
if(WITH_XC_KEESHARE)
set(keeshare_LIB keeshare)
@@ -276,11 +265,11 @@ set(autotype_SOURCES
core/Tools.cpp
autotype/AutoType.cpp
autotype/AutoTypeAction.cpp
autotype/AutoTypeFilterLineEdit.cpp
autotype/AutoTypeMatchModel.cpp
autotype/AutoTypeMatchView.cpp
autotype/AutoTypeSelectDialog.cpp
autotype/AutoTypeSelectView.cpp
autotype/PickcharsDialog.cpp
autotype/ShortcutWidget.cpp
autotype/WildcardMatcher.cpp
autotype/WindowSelectComboBox.cpp)
if(MINGW)
@@ -296,9 +285,9 @@ endif()
if(WITH_XC_NETWORKING)
list(APPEND keepassx_SOURCES
core/HibpDownloader.cpp
core/IconDownloader.cpp
core/NetworkManager.cpp
gui/UpdateCheckDialog.cpp
gui/IconDownloader.cpp
gui/IconDownloaderDialog.cpp
updatecheck/UpdateChecker.cpp)
endif()
@@ -327,10 +316,9 @@ target_link_libraries(keepassx_core
Qt5::Concurrent
Qt5::Network
Qt5::Widgets
${sodium_LIBRARY_RELEASE}
${BOTAN2_LIBRARIES}
${YUBIKEY_LIBRARIES}
${ZXCVBN_LIBRARIES}
${ARGON2_LIBRARIES}
${ZLIB_LIBRARIES}
)
@@ -342,7 +330,7 @@ if(WITH_XC_KEESHARE)
endif()
if(APPLE)
target_link_libraries(keepassx_core "-framework Foundation -framework AppKit")
target_link_libraries(keepassx_core "-framework Foundation -framework AppKit -framework Carbon")
if(Qt5MacExtras_FOUND)
target_link_libraries(keepassx_core Qt5::MacExtras)
endif()
@@ -355,7 +343,7 @@ if(HAIKU)
target_link_libraries(keepassx_core network)
endif()
if(UNIX AND NOT APPLE)
target_link_libraries(keepassx_core Qt5::DBus X11)
target_link_libraries(keepassx_core Qt5::DBus Qt5::X11Extras X11)
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
if(MINGW)
@@ -375,7 +363,6 @@ if(MINGW)
endif()
add_executable(${PROGNAME} WIN32 ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles})
target_link_libraries(keepassx_core ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES})
target_link_libraries(${PROGNAME} keepassx_core)
set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON)
@@ -409,7 +396,7 @@ if(APPLE AND WITH_APP_BUNDLE)
add_custom_command(TARGET ${PROGNAME}
POST_BUILD
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app 2> /dev/null
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
COMMENT "Deploying app bundle")
endif()
@@ -502,7 +489,7 @@ if(MINGW)
find_file(CRYPTO_DLL NAMES libcrypto-1_1.dll libcrypto-1_1-x64.dll)
if (NOT CRYPTO_DLL)
message(FATAL_ERROR "Cannot find libcrypto dll, ensure libgcrypt is properly installed.")
message(FATAL_ERROR "Cannot find libcrypto dll, ensure openssl is properly installed.")
endif()
install(FILES ${OPENSSL_DLL} ${CRYPTO_DLL} DESTINATION ".")

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@
#include <QStringList>
#include <QWidget>
#include "core/AutoTypeMatch.h"
#include "autotype/AutoTypeMatch.h"
class AutoTypeAction;
class AutoTypeExecutor;
@@ -39,17 +39,13 @@ class AutoType : public QObject
public:
QStringList windowTitles();
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers);
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, QString* error = nullptr);
void unregisterGlobalShortcut();
int callEventFilter(void* event);
static bool checkSyntax(const QString& string);
static bool checkHighRepetition(const QString& string);
static bool checkSlowKeypress(const QString& string);
static bool checkHighDelay(const QString& string);
static bool verifyAutoTypeSyntax(const QString& sequence);
void performAutoType(const Entry* entry, QWidget* hideWindow = nullptr);
void performAutoTypeWithSequence(const Entry* entry, const QString& sequence, QWidget* hideWindow = nullptr);
static bool verifyAutoTypeSyntax(const QString& sequence, const Entry* entry, QString& error);
inline bool isAvailable()
{
return m_plugin;
@@ -69,8 +65,6 @@ signals:
private slots:
void startGlobalAutoType();
void performAutoTypeFromGlobal(AutoTypeMatch match);
void autoTypeRejectedFromGlobal();
void unloadPlugin();
private:
@@ -88,19 +82,14 @@ private:
QWidget* hideWindow = nullptr,
const QString& customSequence = QString(),
WId window = 0);
bool parseActions(const QString& sequence, const Entry* entry, QList<AutoTypeAction*>& actions);
QList<AutoTypeAction*> createActionFromTemplate(const QString& tmpl, const Entry* entry);
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 resetAutoTypeState();
static QList<QSharedPointer<AutoTypeAction>>
parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly = false);
QMutex m_inAutoType;
QMutex m_inGlobalAutoTypeDialog;
int m_autoTypeDelay;
Qt::Key m_currentGlobalKey;
Qt::KeyboardModifiers m_currentGlobalModifiers;
QPluginLoader* m_pluginLoader;
AutoTypePlatformInterface* m_plugin;
AutoTypeExecutor* m_executor;

View File

@@ -1,4 +1,5 @@
/*
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@@ -19,77 +20,48 @@
#include "core/Tools.h"
AutoTypeChar::AutoTypeChar(const QChar& character)
: character(character)
{
}
AutoTypeAction* AutoTypeChar::clone()
{
return new AutoTypeChar(character);
}
void AutoTypeChar::accept(AutoTypeExecutor* executor)
{
executor->execChar(this);
}
AutoTypeKey::AutoTypeKey(Qt::Key key)
AutoTypeKey::AutoTypeKey(Qt::Key key, Qt::KeyboardModifiers modifiers)
: key(key)
, modifiers(modifiers)
{
}
AutoTypeAction* AutoTypeKey::clone()
AutoTypeKey::AutoTypeKey(const QChar& character, Qt::KeyboardModifiers modifiers)
: character(character)
, modifiers(modifiers)
{
return new AutoTypeKey(key);
}
void AutoTypeKey::accept(AutoTypeExecutor* executor)
AutoTypeAction::Result AutoTypeKey::exec(AutoTypeExecutor* executor) const
{
executor->execKey(this);
return executor->execType(this);
}
AutoTypeDelay::AutoTypeDelay(int delayMs)
AutoTypeDelay::AutoTypeDelay(int delayMs, bool setExecDelay)
: delayMs(delayMs)
, setExecDelay(setExecDelay)
{
}
AutoTypeAction* AutoTypeDelay::clone()
AutoTypeAction::Result 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);
}
return AutoTypeAction::Result::Ok();
}
void AutoTypeDelay::accept(AutoTypeExecutor* executor)
AutoTypeAction::Result AutoTypeClearField::exec(AutoTypeExecutor* executor) const
{
executor->execDelay(this);
return executor->execClearField(this);
}
AutoTypeClearField::AutoTypeClearField()
AutoTypeAction::Result AutoTypeBegin::exec(AutoTypeExecutor* executor) const
{
}
AutoTypeAction* AutoTypeClearField::clone()
{
return new AutoTypeClearField();
}
void AutoTypeClearField::accept(AutoTypeExecutor* executor)
{
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.
return executor->execBegin(this);
}

View File

@@ -1,4 +1,5 @@
/*
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@@ -19,69 +20,116 @@
#define KEEPASSX_AUTOTYPEACTION_H
#include <QChar>
#include <QObject>
#include <Qt>
#include "core/Global.h"
class AutoTypeExecutor;
class KEEPASSX_EXPORT AutoTypeAction
class KEEPASSXC_EXPORT AutoTypeAction
{
public:
virtual AutoTypeAction* clone() = 0;
virtual void accept(AutoTypeExecutor* executor) = 0;
virtual ~AutoTypeAction();
class Result
{
public:
Result()
: m_isOk(false)
, m_canRetry(false)
, m_error(QString())
{
}
static Result Ok()
{
return Result(true, false, QString());
}
static Result Retry(const QString& error)
{
return Result(false, true, error);
}
static Result Failed(const QString& error)
{
return Result(false, false, error);
}
bool isOk() const
{
return m_isOk;
}
bool canRetry() const
{
return m_canRetry;
}
const QString& errorString() const
{
return m_error;
}
private:
bool m_isOk;
bool m_canRetry;
QString m_error;
Result(bool isOk, bool canRetry, const QString& error)
: m_isOk(isOk)
, m_canRetry(canRetry)
, m_error(error)
{
}
};
AutoTypeAction() = default;
virtual Result exec(AutoTypeExecutor* executor) const = 0;
virtual ~AutoTypeAction() = default;
};
class KEEPASSX_EXPORT AutoTypeChar : public AutoTypeAction
class KEEPASSXC_EXPORT AutoTypeKey : public AutoTypeAction
{
public:
explicit AutoTypeChar(const QChar& character);
AutoTypeAction* clone() override;
void accept(AutoTypeExecutor* executor) override;
explicit AutoTypeKey(const QChar& character, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
explicit AutoTypeKey(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
Result exec(AutoTypeExecutor* executor) const override;
const QChar character;
const Qt::Key key = Qt::Key_unknown;
const Qt::KeyboardModifiers modifiers;
};
class KEEPASSX_EXPORT AutoTypeKey : public AutoTypeAction
class KEEPASSXC_EXPORT AutoTypeDelay : public AutoTypeAction
{
public:
explicit AutoTypeKey(Qt::Key key);
AutoTypeAction* clone() override;
void accept(AutoTypeExecutor* executor) override;
const Qt::Key key;
};
class KEEPASSX_EXPORT AutoTypeDelay : public AutoTypeAction
{
public:
explicit AutoTypeDelay(int delayMs);
AutoTypeAction* clone() override;
void accept(AutoTypeExecutor* executor) override;
explicit AutoTypeDelay(int delayMs, bool setExecDelay = false);
Result exec(AutoTypeExecutor* executor) const override;
const int delayMs;
const bool setExecDelay;
};
class KEEPASSX_EXPORT AutoTypeClearField : public AutoTypeAction
class KEEPASSXC_EXPORT AutoTypeClearField : public AutoTypeAction
{
public:
AutoTypeClearField();
AutoTypeAction* clone() override;
void accept(AutoTypeExecutor* executor) override;
Result exec(AutoTypeExecutor* executor) const override;
};
class KEEPASSX_EXPORT AutoTypeExecutor
class KEEPASSXC_EXPORT AutoTypeBegin : public AutoTypeAction
{
public:
virtual ~AutoTypeExecutor()
{
}
virtual void execChar(AutoTypeChar* action) = 0;
virtual void execKey(AutoTypeKey* action) = 0;
virtual void execDelay(AutoTypeDelay* action);
virtual void execClearField(AutoTypeClearField* action);
Result exec(AutoTypeExecutor* executor) const override;
};
class KEEPASSXC_EXPORT AutoTypeExecutor
{
public:
virtual ~AutoTypeExecutor() = default;
virtual AutoTypeAction::Result execBegin(const AutoTypeBegin* action) = 0;
virtual AutoTypeAction::Result execType(const AutoTypeKey* action) = 0;
virtual AutoTypeAction::Result execClearField(const AutoTypeClearField* action) = 0;
int execDelayMs = 25;
QString error;
};
#endif // KEEPASSX_AUTOTYPEACTION_H

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
* Copyright (C) 2021 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
@@ -15,4 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DBusReturn.h"
#ifndef KPXC_AUTOTYPEMATCH_H
#define KPXC_AUTOTYPEMATCH_H
#include <QPair>
#include <QPointer>
#include <QString>
class Entry;
typedef QPair<QPointer<Entry>, QString> AutoTypeMatch;
#endif // KPXC_AUTOTYPEMATCH_H

View File

@@ -56,7 +56,7 @@ void AutoTypeMatchModel::setMatchList(const QList<AutoTypeMatch>& matches)
QSet<Database*> databases;
for (AutoTypeMatch& match : m_matches) {
databases.insert(match.entry->group()->database());
databases.insert(match.first->group()->database());
}
for (Database* db : asConst(databases)) {
@@ -88,7 +88,6 @@ int AutoTypeMatchModel::rowCount(const QModelIndex& parent) const
int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return 4;
}
@@ -103,30 +102,30 @@ QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const
if (role == Qt::DisplayRole) {
switch (index.column()) {
case ParentGroup:
if (match.entry->group()) {
return match.entry->group()->name();
if (match.first->group()) {
return match.first->group()->name();
}
break;
case Title:
return match.entry->resolveMultiplePlaceholders(match.entry->title());
return match.first->resolveMultiplePlaceholders(match.first->title());
case Username:
return match.entry->resolveMultiplePlaceholders(match.entry->username());
return match.first->resolveMultiplePlaceholders(match.first->username());
case Sequence:
return match.sequence;
return match.second;
}
} else if (role == Qt::DecorationRole) {
switch (index.column()) {
case ParentGroup:
if (match.entry->group()) {
return match.entry->group()->iconPixmap();
if (match.first->group()) {
return match.first->group()->iconPixmap();
}
break;
case Title:
return match.entry->iconPixmap();
return match.first->iconPixmap();
}
} else if (role == Qt::FontRole) {
QFont font;
if (match.entry->isExpired()) {
if (match.first->isExpired()) {
font.setStrikeOut(true);
}
return font;
@@ -157,7 +156,7 @@ void AutoTypeMatchModel::entryDataChanged(Entry* entry)
{
for (int row = 0; row < m_matches.size(); ++row) {
AutoTypeMatch match = m_matches[row];
if (match.entry == entry) {
if (match.first == entry) {
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) {
AutoTypeMatch match = m_matches[row];
if (match.entry == entry) {
if (match.first == entry) {
beginRemoveRows(QModelIndex(), row, row);
m_matches.removeAt(row);
endRemoveRows();

View File

@@ -21,7 +21,7 @@
#include <QAbstractTableModel>
#include "core/AutoTypeMatch.h"
#include "autotype/AutoTypeMatch.h"
class Entry;
class Group;

View File

@@ -0,0 +1,137 @@
/*
* 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()));
} else if (event->key() == Qt::Key_PageUp) {
moveSelection(-5);
} else if (event->key() == Qt::Key_PageDown) {
moveSelection(5);
} else {
QTableView::keyPressEvent(event);
}
}
void AutoTypeMatchView::setMatchList(const QList<AutoTypeMatch>& matches, bool selectFirst)
{
m_model->setMatchList(matches);
m_sortModel->setFilterWildcard({});
horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
if (selectFirst) {
selectionModel()->setCurrentIndex(m_sortModel->index(0, 0),
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
} else {
selectionModel()->clear();
}
emit currentMatchChanged(currentMatch());
}
void AutoTypeMatchView::filterList(const QString& filter)
{
m_sortModel->setFilterWildcard(filter);
setCurrentIndex(m_sortModel->index(0, 0));
}
void AutoTypeMatchView::moveSelection(int offset)
{
auto index = currentIndex();
auto row = index.isValid() ? index.row() : -1;
selectRow(qBound(0, row + offset, model()->rowCount() - 1));
}
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);
}

View File

@@ -19,42 +19,38 @@
#ifndef 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 QTreeView
class AutoTypeMatchView : public QTableView
{
Q_OBJECT
public:
explicit AutoTypeMatchView(QWidget* parent = nullptr);
AutoTypeMatch currentMatch();
void setCurrentMatch(const AutoTypeMatch& match);
AutoTypeMatch matchFromIndex(const QModelIndex& index);
void setMatchList(const QList<AutoTypeMatch>& matches);
void setFirstMatchActive();
void setMatchList(const QList<AutoTypeMatch>& matches, bool selectFirst);
void filterList(const QString& filter);
void moveSelection(int offset);
signals:
void currentMatchChanged(AutoTypeMatch match);
void matchActivated(AutoTypeMatch match);
void matchSelectionChanged();
void matchTextCopied();
protected:
void keyPressEvent(QKeyEvent* event) override;
private slots:
void emitMatchActivated(const QModelIndex& index);
void userNameCopied();
void passwordCopied();
protected slots:
void currentChanged(const QModelIndex& current, const QModelIndex& previous) override;
private:
AutoTypeMatchModel* const m_model;
SortFilterHideProxyModel* const m_sortModel;
QSortFilterProxyModel* const m_sortModel;
};
#endif // KEEPASSX_AUTOTYPEMATCHVIEW_H

View File

@@ -32,9 +32,6 @@ public:
virtual QStringList windowTitles() = 0;
virtual WId activeWindow() = 0;
virtual QString activeWindowTitle() = 0;
virtual bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) = 0;
virtual void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) = 0;
virtual int platformEventFilter(void* event) = 0;
virtual bool raiseWindow(WId window) = 0;
virtual void unload()
{

View File

@@ -1,6 +1,6 @@
/*
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* 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
@@ -17,168 +17,322 @@
*/
#include "AutoTypeSelectDialog.h"
#include "ui_AutoTypeSelectDialog.h"
#include <QApplication>
#include <QCloseEvent>
#include <QMenu>
#include <QShortcut>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
#include <QScreen>
#else
#include <QDesktopWidget>
#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/Resources.h"
#include "gui/entry/AutoTypeMatchModel.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/EntrySearcher.h"
#include "gui/Clipboard.h"
#include "gui/Icons.h"
AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
: QDialog(parent)
, m_view(new AutoTypeSelectView(this))
, m_filterLineEdit(new AutoTypeFilterLineEdit(this))
, m_matchActivatedEmitted(false)
, m_rejected(false)
, m_ui(new Ui::AutoTypeSelectDialog())
{
setAttribute(Qt::WA_DeleteOnClose);
// Places the window on the active (virtual) desktop instead of where the main window is.
setAttribute(Qt::WA_X11BypassTransientForHint);
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
setWindowTitle(tr("Auto-Type - KeePassXC"));
setWindowIcon(resources()->applicationIcon());
setWindowFlags((windowFlags() | Qt::WindowStaysOnTopHint) & ~Qt::WindowContextHelpButtonHint);
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()));
m_ui->searchCheckBox->setShortcut(Qt::CTRL + Qt::Key_F);
connect(m_ui->searchCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
if (checked) {
performSearch();
m_ui->search->setFocus();
} else {
// Reset to original match list
m_ui->view->setMatchList(m_matches, true);
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, !m_matches.isEmpty() || !m_ui->search->text().isEmpty());
m_ui->searchCheckBox->setChecked(m_matches.isEmpty());
}
void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match)
{
if (match.first) {
m_accepted = true;
accept();
emit matchActivated(std::move(match));
}
}
void AutoTypeSelectDialog::performSearch()
{
if (!m_ui->searchCheckBox->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, !m_ui->search->text().isEmpty());
}
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:
m_ui->view->moveSelection(-1);
return true;
case Qt::Key_Down:
m_ui->view->moveSelection(1);
return true;
case Qt::Key_PageUp:
m_ui->view->moveSelection(-5);
return true;
case Qt::Key_PageDown:
m_ui->view->moveSelection(5);
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);
auto shortcut = new QShortcut(Qt::CTRL + Qt::Key_1, this);
connect(shortcut, &QShortcut::activated, typeUsernameAction, &QAction::trigger);
connect(typeUsernameAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch();
match.second = "{USERNAME}";
submitAutoTypeMatch(match);
});
shortcut = new QShortcut(Qt::CTRL + Qt::Key_2, this);
connect(shortcut, &QShortcut::activated, typePasswordAction, &QAction::trigger);
connect(typePasswordAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch();
match.second = "{PASSWORD}";
submitAutoTypeMatch(match);
});
shortcut = new QShortcut(Qt::CTRL + Qt::Key_3, this);
connect(shortcut, &QShortcut::activated, typeTotpAction, &QAction::trigger);
connect(typeTotpAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch();
match.second = "{TOTP}";
submitAutoTypeMatch(match);
});
connect(copyUsernameAction, &QAction::triggered, this, [&] {
auto entry = m_ui->view->currentMatch().first;
if (entry) {
clipboard()->setText(entry->resolvePlaceholder(entry->username()));
reject();
}
});
connect(copyPasswordAction, &QAction::triggered, this, [&] {
auto entry = m_ui->view->currentMatch().first;
if (entry) {
clipboard()->setText(entry->resolvePlaceholder(entry->password()));
reject();
}
});
connect(copyTotpAction, &QAction::triggered, this, [&] {
auto entry = m_ui->view->currentMatch().first;
if (entry) {
clipboard()->setText(entry->totp());
reject();
}
});
}
void AutoTypeSelectDialog::showEvent(QShowEvent* event)
{
QDialog::showEvent(event);
#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
QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos());
#endif
// Resize to last used size
QSize size = config()->get(Config::GUI_AutoTypeSelectDialogSize).toSize();
size.setWidth(qMin(size.width(), screenGeometry.width()));
size.setHeight(qMin(size.height(), screenGeometry.height()));
resize(size);
// move dialog to the center of the screen
QPoint screenCenter = screenGeometry.center();
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();
move(screenGeometry.center().x() - (size.width() / 2), screenGeometry.center().y() - (size.height() / 2));
}
void AutoTypeSelectDialog::setMatchList(const QList<AutoTypeMatch>& matchList)
{
m_view->setMatchList(matchList);
m_view->header()->resizeSections(QHeaderView::ResizeToContents);
}
void AutoTypeSelectDialog::done(int r)
void AutoTypeSelectDialog::hideEvent(QHideEvent* event)
{
config()->set(Config::GUI_AutoTypeSelectDialogSize, size());
QDialog::done(r);
}
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;
if (!m_accepted) {
emit rejected();
}
m_matchActivatedEmitted = true;
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());
QDialog::hideEvent(event);
}

View File

@@ -1,4 +1,5 @@
/*
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@@ -18,14 +19,17 @@
#ifndef KEEPASSX_AUTOTYPESELECTDIALOG_H
#define KEEPASSX_AUTOTYPESELECTDIALOG_H
#include <QAbstractItemModel>
#include "autotype/AutoTypeMatch.h"
#include <QDialog>
#include <QHash>
#include <QTimer>
#include "autotype/AutoTypeFilterLineEdit.h"
#include "core/AutoTypeMatch.h"
class Database;
class QMenu;
class AutoTypeSelectView;
namespace Ui
{
class AutoTypeSelectDialog;
}
class AutoTypeSelectDialog : public QDialog
{
@@ -33,28 +37,35 @@ class AutoTypeSelectDialog : public QDialog
public:
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:
void matchActivated(AutoTypeMatch match);
public slots:
void done(int r) override;
void reject() override;
protected:
bool eventFilter(QObject* obj, QEvent* event) override;
void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
private slots:
void emitMatchActivated(const QModelIndex& index);
void matchRemoved();
void filterList(QString filterString);
void moveSelectionUp();
void moveSelectionDown();
void activateCurrentIndex();
void submitAutoTypeMatch(AutoTypeMatch match);
void performSearch();
void activateCurrentMatch();
void updateActionMenu(const AutoTypeMatch& match);
private:
AutoTypeSelectView* const m_view;
AutoTypeFilterLineEdit* const m_filterLineEdit;
bool m_matchActivatedEmitted;
bool m_rejected;
void buildActionMenu();
QScopedPointer<Ui::AutoTypeSelectDialog> m_ui;
QList<QSharedPointer<Database>> m_dbs;
QList<AutoTypeMatch> m_matches;
QTimer m_searchTimer;
QPointer<QMenu> m_actionMenu;
bool m_accepted = false;
};
#endif // KEEPASSX_AUTOTYPESELECTDIALOG_H

View File

@@ -0,0 +1,243 @@
<?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>303</height>
</rect>
</property>
<property name="windowTitle">
<string>Auto-Type - KeePassXC</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="topMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<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>
<spacer name="horizontalSpacer_3">
<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="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>14</width>
<height>14</height>
</size>
</property>
<property name="toolTip">
<string>&lt;p&gt;You can use advanced search queries to find any entry in your open databases. The following shortcuts are useful:&lt;br/&gt;
Ctrl+F - Toggle database search&lt;br/&gt;
Ctrl+1 - Type username&lt;br/&gt;
Ctrl+2 - Type password&lt;br/&gt;
Ctrl+3 - Type TOTP&lt;/p&gt;</string>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../share/icons/icons.qrc">:/icons/application/scalable/actions/system-help.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</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="QCheckBox" name="searchCheckBox">
<property name="text">
<string>Search all open databases</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>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>searchCheckBox</tabstop>
<tabstop>search</tabstop>
</tabstops>
<resources>
<include location="../../share/icons/icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -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();
}
}

View 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));
}

View File

@@ -1,41 +1,52 @@
/*
* 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_AUTOTYPESELECTVIEW_H
#define KEEPASSX_AUTOTYPESELECTVIEW_H
#include "gui/entry/AutoTypeMatchView.h"
class AutoTypeSelectView : public AutoTypeMatchView
{
Q_OBJECT
public:
explicit AutoTypeSelectView(QWidget* parent = nullptr);
protected:
void mouseMoveEvent(QMouseEvent* event) override;
void keyReleaseEvent(QKeyEvent* e) override;
private slots:
void selectFirstMatch();
signals:
void rejected();
};
#endif // KEEPASSX_AUTOTYPESELECTVIEW_H
/*
* 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/>.
*/
#ifndef KEEPASSXC_PICKCHARSDIALOG_H
#define KEEPASSXC_PICKCHARSDIALOG_H
#include <QDialog>
#include <QPointer>
#include <QString>
namespace Ui
{
class PickcharsDialog;
}
class PickcharsDialog : public QDialog
{
Q_OBJECT
public:
explicit PickcharsDialog(const QString& string, QWidget* parent = nullptr);
QString selectedChars();
bool pressTab();
protected:
void showEvent(QShowEvent*) override;
private slots:
void charSelected();
void upPressed();
void downPressed();
private:
QSharedPointer<Ui::PickcharsDialog> m_ui;
int m_lastSelected;
};
#endif // KEEPASSXC_PICKCHARSDIALOG_H

View 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 &amp;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>

View File

@@ -18,6 +18,7 @@
#include "ShortcutWidget.h"
#include <QKeyEvent>
#include <QToolTip>
#include "autotype/AutoType.h"
@@ -48,9 +49,11 @@ void ShortcutWidget::setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
displayShortcut(m_key, m_modifiers);
if (autoType()->registerGlobalShortcut(m_key, m_modifiers)) {
QString error;
if (autoType()->registerGlobalShortcut(m_key, m_modifiers, &error)) {
setStyleSheet("");
} else {
QToolTip::showText(mapToGlobal(rect().bottomLeft()), error);
setStyleSheet("background-color: #FF9696;");
}
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -17,29 +17,18 @@
*/
#include "AutoTypeMac.h"
#include "core/Tools.h"
#include "gui/osutils/macutils/MacUtils.h"
#include "gui/MessageBox.h"
#include <ApplicationServices/ApplicationServices.h>
#define HOTKEY_ID 1
#define MAX_WINDOW_TITLE_LENGTH 1024
#define INVALID_KEYCODE 0xFFFF
namespace {
bool accessibilityChecked = false;
}
AutoTypePlatformMac::AutoTypePlatformMac()
: m_hotkeyRef(nullptr)
, m_hotkeyId({ 'kpx2', HOTKEY_ID })
{
EventTypeSpec eventSpec;
eventSpec.eventClass = kEventClassKeyboard;
eventSpec.eventKind = kEventHotKeyPressed;
MessageBox::initializeButtonDefs();
::InstallApplicationEventHandler(AutoTypePlatformMac::hotkeyHandler, 1, &eventSpec, this, nullptr);
}
/**
@@ -120,44 +109,6 @@ QString AutoTypePlatformMac::activeWindowTitle()
return title;
}
//
// Register global hotkey
//
bool AutoTypePlatformMac::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
uint16 nativeKeyCode = qtToNativeKeyCode(key);
if (nativeKeyCode == INVALID_KEYCODE) {
qWarning("Invalid key code");
return false;
}
CGEventFlags nativeModifiers = qtToNativeModifiers(modifiers, false);
if (::RegisterEventHotKey(nativeKeyCode, nativeModifiers, m_hotkeyId, GetApplicationEventTarget(), 0, &m_hotkeyRef) != noErr) {
qWarning("Register hotkey failed");
return false;
}
return true;
}
//
// Unregister global hotkey
//
void AutoTypePlatformMac::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(key);
Q_UNUSED(modifiers);
::UnregisterEventHotKey(m_hotkeyRef);
}
int AutoTypePlatformMac::platformEventFilter(void* event)
{
Q_UNUSED(event);
Q_ASSERT(false);
return -1;
}
AutoTypeExecutor* AutoTypePlatformMac::createExecutor()
{
return new AutoTypeExecutorMac(this);
@@ -208,13 +159,13 @@ void AutoTypePlatformMac::sendChar(const QChar& ch, bool isKeyDown)
//
void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers = 0)
{
uint16 keyCode = qtToNativeKeyCode(key);
uint16 keyCode = macUtils()->qtToNativeKeyCode(key);
if (keyCode == INVALID_KEYCODE) {
return;
}
CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown);
CGEventFlags nativeModifiers = qtToNativeModifiers(modifiers, true);
CGEventFlags nativeModifiers = macUtils()->qtToNativeModifiers(modifiers, true);
if (keyEvent != nullptr) {
::CGEventSetFlags(keyEvent, nativeModifiers);
::CGEventPost(kCGSessionEventTap, keyEvent);
@@ -222,223 +173,6 @@ void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModif
}
}
//
// Translate qt key code to mac os key code
// see: HIToolbox/Events.h
//
uint16 AutoTypePlatformMac::qtToNativeKeyCode(Qt::Key key)
{
switch (key) {
case Qt::Key_A:
return kVK_ANSI_A;
case Qt::Key_B:
return kVK_ANSI_B;
case Qt::Key_C:
return kVK_ANSI_C;
case Qt::Key_D:
return kVK_ANSI_D;
case Qt::Key_E:
return kVK_ANSI_E;
case Qt::Key_F:
return kVK_ANSI_F;
case Qt::Key_G:
return kVK_ANSI_G;
case Qt::Key_H:
return kVK_ANSI_H;
case Qt::Key_I:
return kVK_ANSI_I;
case Qt::Key_J:
return kVK_ANSI_J;
case Qt::Key_K:
return kVK_ANSI_K;
case Qt::Key_L:
return kVK_ANSI_L;
case Qt::Key_M:
return kVK_ANSI_M;
case Qt::Key_N:
return kVK_ANSI_N;
case Qt::Key_O:
return kVK_ANSI_O;
case Qt::Key_P:
return kVK_ANSI_P;
case Qt::Key_Q:
return kVK_ANSI_Q;
case Qt::Key_R:
return kVK_ANSI_R;
case Qt::Key_S:
return kVK_ANSI_S;
case Qt::Key_T:
return kVK_ANSI_T;
case Qt::Key_U:
return kVK_ANSI_U;
case Qt::Key_V:
return kVK_ANSI_V;
case Qt::Key_W:
return kVK_ANSI_W;
case Qt::Key_X:
return kVK_ANSI_X;
case Qt::Key_Y:
return kVK_ANSI_Y;
case Qt::Key_Z:
return kVK_ANSI_Z;
case Qt::Key_0:
return kVK_ANSI_0;
case Qt::Key_1:
return kVK_ANSI_1;
case Qt::Key_2:
return kVK_ANSI_2;
case Qt::Key_3:
return kVK_ANSI_3;
case Qt::Key_4:
return kVK_ANSI_4;
case Qt::Key_5:
return kVK_ANSI_5;
case Qt::Key_6:
return kVK_ANSI_6;
case Qt::Key_7:
return kVK_ANSI_7;
case Qt::Key_8:
return kVK_ANSI_8;
case Qt::Key_9:
return kVK_ANSI_9;
case Qt::Key_Equal:
return kVK_ANSI_Equal;
case Qt::Key_Minus:
return kVK_ANSI_Minus;
case Qt::Key_BracketRight:
return kVK_ANSI_RightBracket;
case Qt::Key_BracketLeft:
return kVK_ANSI_LeftBracket;
case Qt::Key_QuoteDbl:
return kVK_ANSI_Quote;
case Qt::Key_Semicolon:
return kVK_ANSI_Semicolon;
case Qt::Key_Backslash:
return kVK_ANSI_Backslash;
case Qt::Key_Comma:
return kVK_ANSI_Comma;
case Qt::Key_Slash:
return kVK_ANSI_Slash;
case Qt::Key_Period:
return kVK_ANSI_Period;
case Qt::Key_Shift:
return kVK_Shift;
case Qt::Key_Control:
return kVK_Command;
case Qt::Key_Backspace:
return kVK_Delete;
case Qt::Key_Tab:
case Qt::Key_Backtab:
return kVK_Tab;
case Qt::Key_Enter:
case Qt::Key_Return:
return kVK_Return;
case Qt::Key_CapsLock:
return kVK_CapsLock;
case Qt::Key_Escape:
return kVK_Escape;
case Qt::Key_Space:
return kVK_Space;
case Qt::Key_PageUp:
return kVK_PageUp;
case Qt::Key_PageDown:
return kVK_PageDown;
case Qt::Key_End:
return kVK_End;
case Qt::Key_Home:
return kVK_Home;
case Qt::Key_Left:
return kVK_LeftArrow;
case Qt::Key_Up:
return kVK_UpArrow;
case Qt::Key_Right:
return kVK_RightArrow;
case Qt::Key_Down:
return kVK_DownArrow;
case Qt::Key_Delete:
return kVK_ForwardDelete;
case Qt::Key_Help:
return kVK_Help;
case Qt::Key_F1:
return kVK_F1;
case Qt::Key_F2:
return kVK_F2;
case Qt::Key_F3:
return kVK_F3;
case Qt::Key_F4:
return kVK_F4;
case Qt::Key_F5:
return kVK_F5;
case Qt::Key_F6:
return kVK_F6;
case Qt::Key_F7:
return kVK_F7;
case Qt::Key_F8:
return kVK_F8;
case Qt::Key_F9:
return kVK_F9;
case Qt::Key_F10:
return kVK_F10;
case Qt::Key_F11:
return kVK_F11;
case Qt::Key_F12:
return kVK_F12;
case Qt::Key_F13:
return kVK_F13;
case Qt::Key_F14:
return kVK_F14;
case Qt::Key_F15:
return kVK_F15;
case Qt::Key_F16:
return kVK_F16;
default:
Q_ASSERT(false);
return INVALID_KEYCODE;
}
}
//
// Translate qt key modifiers to mac os modifiers
// see: https://doc.qt.io/qt-5/osx-issues.html#special-keys
//
CGEventFlags AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native)
{
CGEventFlags nativeModifiers = CGEventFlags(0);
CGEventFlags shiftMod = CGEventFlags(shiftKey);
CGEventFlags cmdMod = CGEventFlags(cmdKey);
CGEventFlags optionMod = CGEventFlags(optionKey);
CGEventFlags controlMod = CGEventFlags(controlKey);
if (native) {
shiftMod = kCGEventFlagMaskShift;
cmdMod = kCGEventFlagMaskCommand;
optionMod = kCGEventFlagMaskAlternate;
controlMod = kCGEventFlagMaskControl;
}
if (modifiers & Qt::ShiftModifier) {
nativeModifiers = CGEventFlags(nativeModifiers | shiftMod);
}
if (modifiers & Qt::ControlModifier) {
nativeModifiers = CGEventFlags(nativeModifiers | cmdMod);
}
if (modifiers & Qt::AltModifier) {
nativeModifiers = CGEventFlags(nativeModifiers | optionMod);
}
if (modifiers & Qt::MetaModifier) {
nativeModifiers = CGEventFlags(nativeModifiers | controlMod);
}
return nativeModifiers;
}
//
// Get window layer/level
//
@@ -472,39 +206,6 @@ QString AutoTypePlatformMac::windowTitle(CFDictionaryRef window)
return title;
}
//
// Carbon hotkey handler
//
OSStatus AutoTypePlatformMac::hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData)
{
Q_UNUSED(nextHandler);
// Determine if the user has given proper permissions to KeePassXC to perform Auto-Type
if (!accessibilityChecked) {
if (macUtils()->enableAccessibility() && macUtils()->enableScreenRecording()) {
accessibilityChecked = true;
} else {
// Does not have required permissions to Auto-Type, ignore the keypress
MessageBox::information(nullptr,
tr("Permission Required"),
tr("KeePassXC requires the Accessibility and Screen Recorder permission in order to perform global "
"Auto-Type. Screen Recording is necessary to use the window title to find entries. If you "
"already granted permission, you may have to restart KeePassXC."));
return noErr;
}
}
AutoTypePlatformMac* self = static_cast<AutoTypePlatformMac*>(userData);
EventHotKeyID hotkeyId;
if (::GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, nullptr, sizeof(hotkeyId), nullptr, &hotkeyId) == noErr
&& hotkeyId.id == HOTKEY_ID) {
emit self->globalShortcutTriggered();
}
return noErr;
}
//
// ------------------------------ AutoTypeExecutorMac ------------------------------
//
@@ -514,36 +215,51 @@ AutoTypeExecutorMac::AutoTypeExecutorMac(AutoTypePlatformMac* platform)
{
}
void AutoTypeExecutorMac::execChar(AutoTypeChar* action)
{
m_platform->sendChar(action->character, true);
m_platform->sendChar(action->character, false);
}
void AutoTypeExecutorMac::execKey(AutoTypeKey* action)
{
m_platform->sendKey(action->key, true);
m_platform->sendKey(action->key, false);
}
void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr)
AutoTypeAction::Result AutoTypeExecutorMac::execBegin(const AutoTypeBegin* action)
{
Q_UNUSED(action);
m_platform->sendKey(Qt::Key_Control, true, Qt::ControlModifier);
m_platform->sendKey(Qt::Key_Up, true, Qt::ControlModifier);
m_platform->sendKey(Qt::Key_Up, false, Qt::ControlModifier);
m_platform->sendKey(Qt::Key_Control, false);
usleep(25 * 1000);
m_platform->sendKey(Qt::Key_Shift, true, Qt::ShiftModifier);
m_platform->sendKey(Qt::Key_Control, true, Qt::ShiftModifier | Qt::ControlModifier);
m_platform->sendKey(Qt::Key_Down, true, Qt::ShiftModifier | Qt::ControlModifier);
m_platform->sendKey(Qt::Key_Down, false, Qt::ShiftModifier | Qt::ControlModifier);
m_platform->sendKey(Qt::Key_Control, false, Qt::ShiftModifier);
m_platform->sendKey(Qt::Key_Shift, false);
usleep(25 * 1000);
m_platform->sendKey(Qt::Key_Backspace, true);
m_platform->sendKey(Qt::Key_Backspace, false);
usleep(25 * 1000);
return AutoTypeAction::Result::Ok();
}
AutoTypeAction::Result AutoTypeExecutorMac::execType(const AutoTypeKey* action)
{
if (action->modifiers & Qt::ShiftModifier) {
m_platform->sendKey(Qt::Key_Shift, true);
}
if (action->modifiers & Qt::ControlModifier) {
m_platform->sendKey(Qt::Key_Control, true);
}
if (action->modifiers & Qt::AltModifier) {
m_platform->sendKey(Qt::Key_Alt, true);
}
if (action->key != Qt::Key_unknown) {
m_platform->sendKey(action->key, true);
m_platform->sendKey(action->key, false);
} else {
m_platform->sendChar(action->character, true);
m_platform->sendChar(action->character, false);
}
if (action->modifiers & Qt::ShiftModifier) {
m_platform->sendKey(Qt::Key_Shift, false);
}
if (action->modifiers & Qt::ControlModifier) {
m_platform->sendKey(Qt::Key_Control, false);
}
if (action->modifiers & Qt::AltModifier) {
m_platform->sendKey(Qt::Key_Alt, false);
}
Tools::sleep(execDelayMs);
return AutoTypeAction::Result::Ok();
}
AutoTypeAction::Result AutoTypeExecutorMac::execClearField(const AutoTypeClearField* action)
{
Q_UNUSED(action);
execType(new AutoTypeKey(Qt::Key_Up, Qt::ControlModifier));
execType(new AutoTypeKey(Qt::Key_Down, Qt::ControlModifier | Qt::ShiftModifier));
execType(new AutoTypeKey(Qt::Key_Backspace));
return AutoTypeAction::Result::Ok();
}

View File

@@ -38,9 +38,6 @@ public:
QStringList windowTitles() override;
WId activeWindow() override;
QString activeWindowTitle() override;
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
int platformEventFilter(void* event) override;
bool raiseWindow(WId pid) override;
AutoTypeExecutor* createExecutor() override;
@@ -50,18 +47,9 @@ public:
void sendChar(const QChar& ch, bool isKeyDown);
void sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers);
signals:
void globalShortcutTriggered();
private:
EventHotKeyRef m_hotkeyRef;
EventHotKeyID m_hotkeyId;
static uint16 qtToNativeKeyCode(Qt::Key key);
static CGEventFlags qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native);
static int windowLayer(CFDictionaryRef window);
static QString windowTitle(CFDictionaryRef window);
static OSStatus hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData);
};
class AutoTypeExecutorMac : public AutoTypeExecutor
@@ -69,9 +57,9 @@ class AutoTypeExecutorMac : public AutoTypeExecutor
public:
explicit AutoTypeExecutorMac(AutoTypePlatformMac* platform);
void execChar(AutoTypeChar* action) override;
void execKey(AutoTypeKey* action) override;
void execClearField(AutoTypeClearField* action) override;
AutoTypeAction::Result execBegin(const AutoTypeBegin* action) override;
AutoTypeAction::Result execType(const AutoTypeKey* action) override;
AutoTypeAction::Result execClearField(const AutoTypeClearField* action) override;
private:
AutoTypePlatformMac* const m_platform;

View File

@@ -1,18 +1,18 @@
set(autotype_mac_SOURCES AutoTypeMac.cpp)
add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES})
set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
add_library(keepassxc-autotype-cocoa MODULE ${autotype_mac_SOURCES})
set_target_properties(keepassxc-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
target_link_libraries(keepassxc-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
if(WITH_APP_BUNDLE)
add_custom_command(TARGET keepassx-autotype-cocoa
add_custom_command(TARGET keepassxc-autotype-cocoa
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR}
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassxc-autotype-cocoa.so ${PLUGIN_INSTALL_DIR}/libkeepassxc-autotype-cocoa.so
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassxc-autotype-cocoa.so -no-plugins 2> /dev/null
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
COMMENT "Deploying autotype plugin")
else()
install(TARGETS keepassx-autotype-cocoa
install(TARGETS keepassxc-autotype-cocoa
BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)
endif()

View File

@@ -42,37 +42,11 @@ QString AutoTypePlatformTest::activeWindowTitle()
return m_activeWindowTitle;
}
bool AutoTypePlatformTest::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(key);
Q_UNUSED(modifiers);
return true;
}
void AutoTypePlatformTest::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(key);
Q_UNUSED(modifiers);
}
int AutoTypePlatformTest::platformEventFilter(void* event)
{
Q_UNUSED(event);
return -1;
}
AutoTypeExecutor* AutoTypePlatformTest::createExecutor()
{
return new AutoTypeExecutorTest(this);
}
void AutoTypePlatformTest::triggerGlobalAutoType()
{
emit globalShortcutTriggered();
}
void AutoTypePlatformTest::setActiveWindowTitle(const QString& title)
{
m_activeWindowTitle = title;
@@ -85,27 +59,23 @@ QString AutoTypePlatformTest::actionChars()
int AutoTypePlatformTest::actionCount()
{
return m_actionList.size();
return m_actionCount;
}
void AutoTypePlatformTest::clearActions()
{
qDeleteAll(m_actionList);
m_actionList.clear();
m_actionChars.clear();
m_actionCount = 0;
}
void AutoTypePlatformTest::addActionChar(AutoTypeChar* action)
void AutoTypePlatformTest::addAction(const AutoTypeKey* action)
{
m_actionList.append(action->clone());
m_actionChars += action->character;
}
void AutoTypePlatformTest::addActionKey(AutoTypeKey* action)
{
m_actionList.append(action->clone());
m_actionChars.append(keyToString(action->key));
++m_actionCount;
if (action->key != Qt::Key_unknown) {
m_actionChars += keyToString(action->key);
} else {
m_actionChars += action->character;
}
}
bool AutoTypePlatformTest::raiseWindow(WId window)
@@ -132,12 +102,20 @@ AutoTypeExecutorTest::AutoTypeExecutorTest(AutoTypePlatformTest* platform)
{
}
void AutoTypeExecutorTest::execChar(AutoTypeChar* action)
AutoTypeAction::Result AutoTypeExecutorTest::execBegin(const AutoTypeBegin* action)
{
m_platform->addActionChar(action);
Q_UNUSED(action);
return AutoTypeAction::Result::Ok();
}
void AutoTypeExecutorTest::execKey(AutoTypeKey* action)
AutoTypeAction::Result AutoTypeExecutorTest::execType(const AutoTypeKey* action)
{
m_platform->addActionKey(action);
m_platform->addAction(action);
return AutoTypeAction::Result::Ok();
}
AutoTypeAction::Result AutoTypeExecutorTest::execClearField(const AutoTypeClearField* action)
{
Q_UNUSED(action);
return AutoTypeAction::Result::Ok();
}

View File

@@ -37,9 +37,6 @@ public:
QStringList windowTitles() override;
WId activeWindow() override;
QString activeWindowTitle() override;
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
int platformEventFilter(void* event) override;
bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override;
@@ -48,22 +45,17 @@ public:
bool raiseOwnWindow() override;
#endif
void triggerGlobalAutoType() override;
void setActiveWindowTitle(const QString& title) override;
QString actionChars() override;
int actionCount() override;
void clearActions() override;
void addActionChar(AutoTypeChar* action);
void addActionKey(AutoTypeKey* action);
signals:
void globalShortcutTriggered();
void addAction(const AutoTypeKey* action);
private:
QString m_activeWindowTitle;
QList<AutoTypeAction*> m_actionList;
int m_actionCount = 0;
QString m_actionChars;
};
@@ -72,8 +64,9 @@ class AutoTypeExecutorTest : public AutoTypeExecutor
public:
explicit AutoTypeExecutorTest(AutoTypePlatformTest* platform);
void execChar(AutoTypeChar* action) override;
void execKey(AutoTypeKey* action) override;
AutoTypeAction::Result execBegin(const AutoTypeBegin* action) override;
AutoTypeAction::Result execType(const AutoTypeKey* action) override;
AutoTypeAction::Result execClearField(const AutoTypeClearField* action) override;
private:
AutoTypePlatformTest* const m_platform;

View File

@@ -26,7 +26,6 @@ public:
virtual ~AutoTypeTestInterface()
{
}
virtual void triggerGlobalAutoType() = 0;
virtual void setActiveWindowTitle(const QString& title) = 0;
virtual QString actionChars() = 0;

View File

@@ -1,4 +1,4 @@
set(autotype_test_SOURCES AutoTypeTest.cpp)
add_library(keepassx-autotype-test MODULE ${autotype_test_SOURCES})
target_link_libraries(keepassx-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
add_library(keepassxc-autotype-test MODULE ${autotype_test_SOURCES})
target_link_libraries(keepassxc-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)

View File

@@ -17,6 +17,8 @@
*/
#include "AutoTypeWindows.h"
#include "core/Tools.h"
#include "gui/osutils/OSUtils.h"
#include <VersionHelpers.h>
@@ -61,49 +63,6 @@ QString AutoTypePlatformWin::activeWindowTitle()
return windowTitle(::GetForegroundWindow());
}
//
// Register global hotkey
//
bool AutoTypePlatformWin::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
DWORD nativeKeyCode = qtToNativeKeyCode(key);
if (nativeKeyCode < 1 || nativeKeyCode > 254) {
return false;
}
DWORD nativeModifiers = qtToNativeModifiers(modifiers);
if (!::RegisterHotKey(nullptr, HOTKEY_ID, nativeModifiers | MOD_NOREPEAT, nativeKeyCode)) {
return false;
}
return true;
}
//
// Unregister global hotkey
//
void AutoTypePlatformWin::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(key);
Q_UNUSED(modifiers);
::UnregisterHotKey(nullptr, HOTKEY_ID);
}
//
// Native event filter
//
int AutoTypePlatformWin::platformEventFilter(void* event)
{
MSG* msg = static_cast<MSG*>(event);
if (msg->message == WM_HOTKEY && msg->wParam == HOTKEY_ID) {
emit globalShortcutTriggered();
return 1;
}
return -1;
}
AutoTypeExecutor* AutoTypePlatformWin::createExecutor()
{
return new AutoTypeExecutorWin(this);
@@ -145,7 +104,7 @@ void AutoTypePlatformWin::sendChar(const QChar& ch, bool isKeyDown)
//
void AutoTypePlatformWin::sendKey(Qt::Key key, bool isKeyDown)
{
DWORD nativeKeyCode = qtToNativeKeyCode(key);
DWORD nativeKeyCode = winUtils()->qtToNativeKeyCode(key);
if (nativeKeyCode < 1 || nativeKeyCode > 254) {
return;
}
@@ -168,234 +127,12 @@ void AutoTypePlatformWin::sendKey(Qt::Key key, bool isKeyDown)
::SendInput(1, &in, sizeof(INPUT));
}
// clang-format off
//
// Translate qt key code to windows virtual key code
// see: https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
//
DWORD AutoTypePlatformWin::qtToNativeKeyCode(Qt::Key key)
{
switch (key) {
case Qt::Key_Backspace:
return VK_BACK; // 0x08
case Qt::Key_Tab:
case Qt::Key_Backtab:
return VK_TAB; // 0x09
case Qt::Key_Clear:
return VK_CLEAR; // 0x0C
case Qt::Key_Enter:
case Qt::Key_Return:
return VK_RETURN; // 0x0D
case Qt::Key_Shift:
return VK_SHIFT; // 0x10
case Qt::Key_Control:
return VK_CONTROL; // 0x11
case Qt::Key_Pause:
return VK_PAUSE; // 0x13
case Qt::Key_CapsLock:
return VK_CAPITAL; // 0x14
case Qt::Key_Escape:
return VK_ESCAPE; // 0x1B
case Qt::Key_Space:
return VK_SPACE; // 0x20
case Qt::Key_PageUp:
return VK_PRIOR; // 0x21
case Qt::Key_PageDown:
return VK_NEXT; // 0x22
case Qt::Key_End:
return VK_END; // 0x23
case Qt::Key_Home:
return VK_HOME; // 0x24
case Qt::Key_Left:
return VK_LEFT; // 0x25
case Qt::Key_Up:
return VK_UP; // 0x26
case Qt::Key_Right:
return VK_RIGHT; // 0x27
case Qt::Key_Down:
return VK_DOWN; // 0x28
case Qt::Key_Print:
return VK_SNAPSHOT; // 0x2C
case Qt::Key_Insert:
return VK_INSERT; // 0x2D
case Qt::Key_Delete:
return VK_DELETE; // 0x2E
case Qt::Key_Help:
return VK_HELP; // 0x2F
case Qt::Key_0:
return 0x30; // 0x30
case Qt::Key_1:
return 0x31; // 0x31
case Qt::Key_2:
return 0x32; // 0x32
case Qt::Key_3:
return 0x33; // 0x33
case Qt::Key_4:
return 0x34; // 0x34
case Qt::Key_5:
return 0x35; // 0x35
case Qt::Key_6:
return 0x36; // 0x36
case Qt::Key_7:
return 0x37; // 0x37
case Qt::Key_8:
return 0x38; // 0x38
case Qt::Key_9:
return 0x39; // 0x39
case Qt::Key_A:
return 0x41; // 0x41
case Qt::Key_B:
return 0x42; // 0x42
case Qt::Key_C:
return 0x43; // 0x43
case Qt::Key_D:
return 0x44; // 0x44
case Qt::Key_E:
return 0x45; // 0x45
case Qt::Key_F:
return 0x46; // 0x46
case Qt::Key_G:
return 0x47; // 0x47
case Qt::Key_H:
return 0x48; // 0x48
case Qt::Key_I:
return 0x49; // 0x49
case Qt::Key_J:
return 0x4A; // 0x4A
case Qt::Key_K:
return 0x4B; // 0x4B
case Qt::Key_L:
return 0x4C; // 0x4C
case Qt::Key_M:
return 0x4D; // 0x4D
case Qt::Key_N:
return 0x4E; // 0x4E
case Qt::Key_O:
return 0x4F; // 0x4F
case Qt::Key_P:
return 0x50; // 0x50
case Qt::Key_Q:
return 0x51; // 0x51
case Qt::Key_R:
return 0x52; // 0x52
case Qt::Key_S:
return 0x53; // 0x53
case Qt::Key_T:
return 0x54; // 0x54
case Qt::Key_U:
return 0x55; // 0x55
case Qt::Key_V:
return 0x56; // 0x56
case Qt::Key_W:
return 0x57; // 0x57
case Qt::Key_X:
return 0x58; // 0x58
case Qt::Key_Y:
return 0x59; // 0x59
case Qt::Key_Z:
return 0x5A; // 0x5A
case Qt::Key_F1:
return VK_F1; // 0x70
case Qt::Key_F2:
return VK_F2; // 0x71
case Qt::Key_F3:
return VK_F3; // 0x72
case Qt::Key_F4:
return VK_F4; // 0x73
case Qt::Key_F5:
return VK_F5; // 0x74
case Qt::Key_F6:
return VK_F6; // 0x75
case Qt::Key_F7:
return VK_F7; // 0x76
case Qt::Key_F8:
return VK_F8; // 0x77
case Qt::Key_F9:
return VK_F9; // 0x78
case Qt::Key_F10:
return VK_F10; // 0x79
case Qt::Key_F11:
return VK_F11; // 0x7A
case Qt::Key_F12:
return VK_F12; // 0x7B
case Qt::Key_F13:
return VK_F13; // 0x7C
case Qt::Key_F14:
return VK_F14; // 0x7D
case Qt::Key_F15:
return VK_F15; // 0x7E
case Qt::Key_F16:
return VK_F16; // 0x7F
case Qt::Key_F17:
return VK_F17; // 0x80
case Qt::Key_F18:
return VK_F18; // 0x81
case Qt::Key_F19:
return VK_F19; // 0x82
case Qt::Key_F20:
return VK_F20; // 0x83
case Qt::Key_F21:
return VK_F21; // 0x84
case Qt::Key_F22:
return VK_F22; // 0x85
case Qt::Key_F23:
return VK_F23; // 0x86
case Qt::Key_F24:
return VK_F24; // 0x87
case Qt::Key_NumLock:
return VK_NUMLOCK; // 0x90
case Qt::Key_ScrollLock:
return VK_SCROLL; // 0x91
case Qt::Key_Exclam: // !
case Qt::Key_QuoteDbl: // "
case Qt::Key_NumberSign: // #
case Qt::Key_Dollar: // $
case Qt::Key_Percent: // %
case Qt::Key_Ampersand: // &
case Qt::Key_Apostrophe: // '
case Qt::Key_ParenLeft: // (
case Qt::Key_ParenRight: // )
case Qt::Key_Asterisk: // *
case Qt::Key_Plus: // +
case Qt::Key_Comma: // ,
case Qt::Key_Minus: // -
case Qt::Key_Period: // .
case Qt::Key_Slash: // /
case Qt::Key_Colon: // :
case Qt::Key_Semicolon: // ;
case Qt::Key_Less: // <
case Qt::Key_Equal: // =
case Qt::Key_Greater: // >
case Qt::Key_Question: // ?
case Qt::Key_BracketLeft: // [
case Qt::Key_Backslash: // '\'
case Qt::Key_BracketRight: // ]
case Qt::Key_AsciiCircum: // ^
case Qt::Key_Underscore: // _
case Qt::Key_QuoteLeft: // `
case Qt::Key_BraceLeft: // {
case Qt::Key_Bar: // |
case Qt::Key_BraceRight: // }
case Qt::Key_AsciiTilde: // ~
return LOBYTE(::VkKeyScanExW(key, ::GetKeyboardLayout(0)));
default:
Q_ASSERT(false);
return 0;
}
}
//
// The extended-key flag indicates whether the keystroke message originated
// from one of the additional keys on the enhanced keyboard
// see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646267%28v=vs.85%29.aspx#EXTENDED_KEY_FLAG
//
BOOL AutoTypePlatformWin::isExtendedKey(DWORD nativeKeyCode)
bool AutoTypePlatformWin::isExtendedKey(DWORD nativeKeyCode)
{
switch (nativeKeyCode) {
case VK_RMENU:
@@ -417,44 +154,21 @@ BOOL AutoTypePlatformWin::isExtendedKey(DWORD nativeKeyCode)
case VK_LWIN:
case VK_RWIN:
case VK_APPS:
return TRUE;
return true;
default:
return FALSE;
return false;
}
}
// clang-format on
//
// Translate qt key modifiers to windows modifiers
//
DWORD AutoTypePlatformWin::qtToNativeModifiers(Qt::KeyboardModifiers modifiers)
{
DWORD nativeModifiers = 0;
if (modifiers & Qt::ShiftModifier) {
nativeModifiers |= MOD_SHIFT;
}
if (modifiers & Qt::ControlModifier) {
nativeModifiers |= MOD_CONTROL;
}
if (modifiers & Qt::AltModifier) {
nativeModifiers |= MOD_ALT;
}
if (modifiers & Qt::MetaModifier) {
nativeModifiers |= MOD_WIN;
}
return nativeModifiers;
}
//
// Test if window is in Alt+Tab list
// see: https://blogs.msdn.microsoft.com/oldnewthing/20071008-00/?p=24863
//
BOOL AutoTypePlatformWin::isAltTabWindow(HWND hwnd)
bool AutoTypePlatformWin::isAltTabWindow(HWND hwnd)
{
if (!::IsWindowVisible(hwnd)) {
return FALSE;
return false;
}
// Start at the root owner
@@ -512,36 +226,51 @@ AutoTypeExecutorWin::AutoTypeExecutorWin(AutoTypePlatformWin* platform)
{
}
void AutoTypeExecutorWin::execChar(AutoTypeChar* action)
{
m_platform->sendChar(action->character, true);
m_platform->sendChar(action->character, false);
}
void AutoTypeExecutorWin::execKey(AutoTypeKey* action)
{
m_platform->sendKey(action->key, true);
m_platform->sendKey(action->key, false);
}
void AutoTypeExecutorWin::execClearField(AutoTypeClearField* action = nullptr)
AutoTypeAction::Result AutoTypeExecutorWin::execBegin(const AutoTypeBegin* action)
{
Q_UNUSED(action);
m_platform->sendKey(Qt::Key_Control, true);
m_platform->sendKey(Qt::Key_Home, true);
m_platform->sendKey(Qt::Key_Home, false);
m_platform->sendKey(Qt::Key_Control, false);
::Sleep(25);
m_platform->sendKey(Qt::Key_Control, true);
m_platform->sendKey(Qt::Key_Shift, true);
m_platform->sendKey(Qt::Key_End, true);
m_platform->sendKey(Qt::Key_End, false);
m_platform->sendKey(Qt::Key_Shift, false);
m_platform->sendKey(Qt::Key_Control, false);
::Sleep(25);
m_platform->sendKey(Qt::Key_Backspace, true);
m_platform->sendKey(Qt::Key_Backspace, false);
::Sleep(25);
return AutoTypeAction::Result::Ok();
}
AutoTypeAction::Result AutoTypeExecutorWin::execType(const AutoTypeKey* action)
{
if (action->modifiers & Qt::ShiftModifier) {
m_platform->sendKey(Qt::Key_Shift, true);
}
if (action->modifiers & Qt::ControlModifier) {
m_platform->sendKey(Qt::Key_Control, true);
}
if (action->modifiers & Qt::AltModifier) {
m_platform->sendKey(Qt::Key_Alt, true);
}
if (action->key != Qt::Key_unknown) {
m_platform->sendKey(action->key, true);
m_platform->sendKey(action->key, false);
} else {
m_platform->sendChar(action->character, true);
m_platform->sendChar(action->character, false);
}
if (action->modifiers & Qt::ShiftModifier) {
m_platform->sendKey(Qt::Key_Shift, false);
}
if (action->modifiers & Qt::ControlModifier) {
m_platform->sendKey(Qt::Key_Control, false);
}
if (action->modifiers & Qt::AltModifier) {
m_platform->sendKey(Qt::Key_Alt, false);
}
Tools::sleep(execDelayMs);
return AutoTypeAction::Result::Ok();
}
AutoTypeAction::Result AutoTypeExecutorWin::execClearField(const AutoTypeClearField* action)
{
Q_UNUSED(action);
execType(new AutoTypeKey(Qt::Key_Home, Qt::ControlModifier));
execType(new AutoTypeKey(Qt::Key_End, Qt::ControlModifier | Qt::ShiftModifier));
execType(new AutoTypeKey(Qt::Key_Backspace));
return AutoTypeAction::Result::Ok();
}

View File

@@ -20,7 +20,7 @@
#define KEEPASSX_AUTOTYPEWINDOWS_H
#include <QtPlugin>
#include <Windows.h>
#include <windows.h>
#include "autotype/AutoTypeAction.h"
#include "autotype/AutoTypePlatformPlugin.h"
@@ -36,23 +36,15 @@ public:
QStringList windowTitles() override;
WId activeWindow() override;
QString activeWindowTitle() override;
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
int platformEventFilter(void* event) override;
bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override;
void sendChar(const QChar& ch, bool isKeyDown);
void sendKey(Qt::Key key, bool isKeyDown);
signals:
void globalShortcutTriggered();
private:
static DWORD qtToNativeKeyCode(Qt::Key key);
static DWORD qtToNativeModifiers(Qt::KeyboardModifiers modifiers);
static BOOL isExtendedKey(DWORD nativeKeyCode);
static BOOL isAltTabWindow(HWND hwnd);
static bool isExtendedKey(DWORD nativeKeyCode);
static bool isAltTabWindow(HWND hwnd);
static BOOL CALLBACK windowTitleEnumProc(_In_ HWND hwnd, _In_ LPARAM lParam);
static QString windowTitle(HWND hwnd);
};
@@ -62,9 +54,9 @@ class AutoTypeExecutorWin : public AutoTypeExecutor
public:
explicit AutoTypeExecutorWin(AutoTypePlatformWin* platform);
void execChar(AutoTypeChar* action) override;
void execKey(AutoTypeKey* action) override;
void execClearField(AutoTypeClearField* action) override;
AutoTypeAction::Result execBegin(const AutoTypeBegin* action) override;
AutoTypeAction::Result execType(const AutoTypeKey* action) override;
AutoTypeAction::Result execClearField(const AutoTypeClearField* action) override;
private:
AutoTypePlatformWin* const m_platform;

View File

@@ -1,7 +1,7 @@
set(autotype_win_SOURCES AutoTypeWindows.cpp)
add_library(keepassx-autotype-windows MODULE ${autotype_win_SOURCES})
target_link_libraries(keepassx-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
install(TARGETS keepassx-autotype-windows
add_library(keepassxc-autotype-windows MODULE ${autotype_win_SOURCES})
target_link_libraries(keepassxc-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets)
install(TARGETS keepassxc-autotype-windows
BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)

View File

@@ -18,19 +18,11 @@
*/
#include "AutoTypeXCB.h"
#include "KeySymMap.h"
#include "core/Tools.h"
#include <time.h>
#include <xcb/xcb.h>
bool AutoTypePlatformX11::m_catchXErrors = false;
bool AutoTypePlatformX11::m_xErrorOccurred = false;
int (*AutoTypePlatformX11::m_oldXErrorHandler)(Display*, XErrorEvent*) = nullptr;
AutoTypePlatformX11::AutoTypePlatformX11()
{
m_dpy = QX11Info::display();
// Qt handles XCB slightly differently so we open our own connection
m_dpy = XOpenDisplay(XDisplayString(QX11Info::display()));
m_rootWindow = QX11Info::appRootWindow();
m_atomWmState = XInternAtom(m_dpy, "WM_STATE", True);
@@ -39,6 +31,8 @@ AutoTypePlatformX11::AutoTypePlatformX11()
m_atomString = XInternAtom(m_dpy, "STRING", True);
m_atomUtf8String = XInternAtom(m_dpy, "UTF8_STRING", True);
m_atomNetActiveWindow = XInternAtom(m_dpy, "_NET_ACTIVE_WINDOW", True);
m_atomTransientFor = XInternAtom(m_dpy, "WM_TRANSIENT_FOR", True);
m_atomWindow = XInternAtom(m_dpy, "WINDOW", True);
m_classBlacklist << "desktop_window"
<< "gnome-panel"; // Gnome
@@ -49,18 +43,9 @@ AutoTypePlatformX11::AutoTypePlatformX11()
m_classBlacklist << "xfdesktop"
<< "xfce4-panel"; // Xfce 4
m_currentGlobalKey = static_cast<Qt::Key>(0);
m_currentGlobalModifiers = nullptr;
m_keysymTable = nullptr;
m_xkb = nullptr;
m_remapKeycode = 0;
m_currentRemapKeysym = NoSymbol;
m_modifierMask = ControlMask | ShiftMask | Mod1Mask | Mod4Mask;
m_loaded = true;
updateKeymap();
}
bool AutoTypePlatformX11::isAvailable()
@@ -75,34 +60,21 @@ bool AutoTypePlatformX11::isAvailable()
return false;
}
if (!m_xkb) {
XkbDescPtr kbd = getKeyboard();
if (!kbd) {
return false;
}
XkbFreeKeyboard(kbd, XkbAllComponentsMask, True);
}
return true;
}
void AutoTypePlatformX11::unload()
{
// Restore the KeyboardMapping to its original state.
if (m_currentRemapKeysym != NoSymbol) {
AddKeysym(NoSymbol);
}
if (m_keysymTable) {
XFree(m_keysymTable);
}
m_keymap.clear();
if (m_xkb) {
XkbFreeKeyboard(m_xkb, XkbAllComponentsMask, True);
m_xkb = nullptr;
}
XCloseDisplay(m_dpy);
m_dpy = nullptr;
m_loaded = false;
}
@@ -142,105 +114,6 @@ QString AutoTypePlatformX11::activeWindowTitle()
return windowTitle(activeWindow(), true);
}
bool AutoTypePlatformX11::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
int keycode = XKeysymToKeycode(m_dpy, charToKeySym(key));
uint nativeModifiers = qtToNativeModifiers(modifiers);
startCatchXErrors();
XGrabKey(m_dpy, keycode, nativeModifiers, m_rootWindow, True, GrabModeAsync, GrabModeAsync);
XGrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask, m_rootWindow, True, GrabModeAsync, GrabModeAsync);
XGrabKey(m_dpy, keycode, nativeModifiers | LockMask, m_rootWindow, True, GrabModeAsync, GrabModeAsync);
XGrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask | LockMask, m_rootWindow, True, GrabModeAsync, GrabModeAsync);
stopCatchXErrors();
if (!m_xErrorOccurred) {
m_currentGlobalKey = key;
m_currentGlobalModifiers = modifiers;
m_currentGlobalKeycode = keycode;
m_currentGlobalNativeModifiers = nativeModifiers;
return true;
} else {
unregisterGlobalShortcut(key, modifiers);
return false;
}
}
uint AutoTypePlatformX11::qtToNativeModifiers(Qt::KeyboardModifiers modifiers)
{
uint nativeModifiers = 0;
if (modifiers & Qt::ShiftModifier) {
nativeModifiers |= ShiftMask;
}
if (modifiers & Qt::ControlModifier) {
nativeModifiers |= ControlMask;
}
if (modifiers & Qt::AltModifier) {
nativeModifiers |= Mod1Mask;
}
if (modifiers & Qt::MetaModifier) {
nativeModifiers |= Mod4Mask;
}
return nativeModifiers;
}
void AutoTypePlatformX11::unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
KeyCode keycode = XKeysymToKeycode(m_dpy, charToKeySym(key));
uint nativeModifiers = qtToNativeModifiers(modifiers);
XUngrabKey(m_dpy, keycode, nativeModifiers, m_rootWindow);
XUngrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask, m_rootWindow);
XUngrabKey(m_dpy, keycode, nativeModifiers | LockMask, m_rootWindow);
XUngrabKey(m_dpy, keycode, nativeModifiers | Mod2Mask | LockMask, m_rootWindow);
m_currentGlobalKey = static_cast<Qt::Key>(0);
m_currentGlobalModifiers = nullptr;
m_currentGlobalKeycode = 0;
m_currentGlobalNativeModifiers = 0;
}
int AutoTypePlatformX11::platformEventFilter(void* event)
{
xcb_generic_event_t* genericEvent = static_cast<xcb_generic_event_t*>(event);
quint8 type = genericEvent->response_type & 0x7f;
if (type == XCB_KEY_PRESS || type == XCB_KEY_RELEASE) {
xcb_key_press_event_t* keyPressEvent = static_cast<xcb_key_press_event_t*>(event);
if (keyPressEvent->detail == m_currentGlobalKeycode
&& (keyPressEvent->state & m_modifierMask) == m_currentGlobalNativeModifiers
&& (!QApplication::activeWindow() || QApplication::activeWindow()->isMinimized()) && m_loaded) {
if (type == XCB_KEY_PRESS) {
emit globalShortcutTriggered();
}
return 1;
}
} else if (type == XCB_MAPPING_NOTIFY) {
xcb_mapping_notify_event_t* mappingNotifyEvent = static_cast<xcb_mapping_notify_event_t*>(event);
if (mappingNotifyEvent->request == XCB_MAPPING_KEYBOARD
|| mappingNotifyEvent->request == XCB_MAPPING_MODIFIER) {
XMappingEvent xMappingEvent;
memset(&xMappingEvent, 0, sizeof(xMappingEvent));
xMappingEvent.type = MappingNotify;
xMappingEvent.display = m_dpy;
if (mappingNotifyEvent->request == XCB_MAPPING_KEYBOARD) {
xMappingEvent.request = MappingKeyboard;
} else {
xMappingEvent.request = MappingModifier;
}
xMappingEvent.first_keycode = mappingNotifyEvent->first_keycode;
xMappingEvent.count = mappingNotifyEvent->count;
XRefreshKeyboardMapping(&xMappingEvent);
updateKeymap();
}
}
return -1;
}
AutoTypeExecutor* AutoTypePlatformX11::createExecutor()
{
return new AutoTypeExecutorX11(this);
@@ -373,111 +246,36 @@ QStringList AutoTypePlatformX11::windowTitlesRecursive(Window window)
bool AutoTypePlatformX11::isTopLevelWindow(Window window)
{
bool result = false;
Atom type = None;
int format;
unsigned long nitems;
unsigned long after;
unsigned char* data = Q_NULLPTR;
unsigned char* data = nullptr;
// Check if the window has WM_STATE atom and it is not Withdrawn
int retVal = XGetWindowProperty(
m_dpy, window, m_atomWmState, 0, 2, False, m_atomWmState, &type, &format, &nitems, &after, &data);
bool result = false;
if (retVal == 0 && data) {
if (type == m_atomWmState && format == 32 && nitems > 0) {
qint32 state = static_cast<qint32>(*data);
result = (state != WithdrawnState);
result = (static_cast<quint32>(*data) != WithdrawnState);
}
XFree(data);
} else {
// See if this is a transient window without WM_STATE
retVal = XGetWindowProperty(
m_dpy, window, m_atomTransientFor, 0, 1, False, m_atomWindow, &type, &format, &nitems, &after, &data);
if (retVal == 0 && data) {
result = true;
XFree(data);
}
}
return result;
}
KeySym AutoTypePlatformX11::charToKeySym(const QChar& ch)
{
ushort unicode = ch.unicode();
/* first check for Latin-1 characters (1:1 mapping) */
if ((unicode >= 0x0020 && unicode <= 0x007e) || (unicode >= 0x00a0 && unicode <= 0x00ff)) {
return unicode;
}
/* mapping table generated from keysymdef.h */
const uint* match = Tools::binaryFind(m_unicodeToKeysymKeys, m_unicodeToKeysymKeys + m_unicodeToKeysymLen, unicode);
int index = match - m_unicodeToKeysymKeys;
if (index != m_unicodeToKeysymLen) {
return m_unicodeToKeysymValues[index];
}
if (unicode >= 0x0100) {
return unicode | 0x01000000;
}
return NoSymbol;
}
KeySym AutoTypePlatformX11::keyToKeySym(Qt::Key key)
{
switch (key) {
case Qt::Key_Tab:
return XK_Tab;
case Qt::Key_Enter:
return XK_Return;
case Qt::Key_Space:
return XK_space;
case Qt::Key_Up:
return XK_Up;
case Qt::Key_Down:
return XK_Down;
case Qt::Key_Left:
return XK_Left;
case Qt::Key_Right:
return XK_Right;
case Qt::Key_Insert:
return XK_Insert;
case Qt::Key_Delete:
return XK_Delete;
case Qt::Key_Home:
return XK_Home;
case Qt::Key_End:
return XK_End;
case Qt::Key_PageUp:
return XK_Page_Up;
case Qt::Key_PageDown:
return XK_Page_Down;
case Qt::Key_Backspace:
return XK_BackSpace;
case Qt::Key_Pause:
return XK_Break;
case Qt::Key_CapsLock:
return XK_Caps_Lock;
case Qt::Key_Escape:
return XK_Escape;
case Qt::Key_Help:
return XK_Help;
case Qt::Key_NumLock:
return XK_Num_Lock;
case Qt::Key_Print:
return XK_Print;
case Qt::Key_ScrollLock:
return XK_Scroll_Lock;
case Qt::Key_Shift:
return XK_Shift_L;
case Qt::Key_Control:
return XK_Control_L;
case Qt::Key_Alt:
return XK_Alt_L;
default:
if (key >= Qt::Key_F1 && key <= Qt::Key_F16) {
return XK_F1 + (key - Qt::Key_F1);
} else {
return NoSymbol;
}
}
}
/*
* Update the keyboard and modifier mapping.
* We need the KeyboardMapping for AddKeysym.
@@ -488,21 +286,35 @@ void AutoTypePlatformX11::updateKeymap()
if (m_xkb) {
XkbFreeKeyboard(m_xkb, XkbAllComponentsMask, True);
}
m_xkb = getKeyboard();
m_xkb = XkbGetMap(m_dpy, XkbAllClientInfoMask, XkbUseCoreKbd);
XDisplayKeycodes(m_dpy, &m_minKeycode, &m_maxKeycode);
if (m_keysymTable != nullptr)
XFree(m_keysymTable);
m_keysymTable = XGetKeyboardMapping(m_dpy, m_minKeycode, m_maxKeycode - m_minKeycode + 1, &m_keysymPerKeycode);
/* Build updated keymap */
m_keymap.clear();
/* determine the keycode to use for remapped keys */
if (m_remapKeycode == 0 || !isRemapKeycodeValid()) {
for (int keycode = m_minKeycode; keycode <= m_maxKeycode; keycode++) {
int inx = (keycode - m_minKeycode) * m_keysymPerKeycode;
if (m_keysymTable[inx] == NoSymbol) {
m_remapKeycode = keycode;
m_currentRemapKeysym = NoSymbol;
break;
for (int ckeycode = m_xkb->min_key_code; ckeycode < m_xkb->max_key_code; ckeycode++) {
int groups = XkbKeyNumGroups(m_xkb, ckeycode);
for (int cgroup = 0; cgroup < groups; cgroup++) {
XkbKeyTypePtr type = XkbKeyKeyType(m_xkb, ckeycode, cgroup);
for (int clevel = 0; clevel < type->num_levels; clevel++) {
KeySym sym = XkbKeycodeToKeysym(m_dpy, ckeycode, cgroup, clevel);
int mask = 0;
for (int nmap = 0; nmap < type->map_count; nmap++) {
XkbKTMapEntryRec map = type->map[nmap];
if (map.active && map.level == clevel) {
mask = map.mods.mask;
break;
}
}
/* explicitly disallow requiring lock modifiers (Caps Lock and Num Lock) */
if (mask & (LockMask | Mod2Mask)) {
continue;
}
m_keymap.append(AutoTypePlatformX11::KeyDesc{sym, ckeycode, cgroup, mask});
}
}
}
@@ -520,104 +332,12 @@ void AutoTypePlatformX11::updateKeymap()
}
}
XFreeModifiermap(modifiers);
/* Xlib needs some time until the mapping is distributed to
all clients */
// TODO: we should probably only sleep while in the middle of typing something
timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 30 * 1000 * 1000;
nanosleep(&ts, nullptr);
}
bool AutoTypePlatformX11::isRemapKeycodeValid()
{
int baseKeycode = (m_remapKeycode - m_minKeycode) * m_keysymPerKeycode;
for (int i = 0; i < m_keysymPerKeycode; i++) {
if (m_keysymTable[baseKeycode + i] == m_currentRemapKeysym) {
return true;
}
}
return false;
}
void AutoTypePlatformX11::startCatchXErrors()
{
Q_ASSERT(!m_catchXErrors);
m_catchXErrors = true;
m_xErrorOccurred = false;
m_oldXErrorHandler = XSetErrorHandler(x11ErrorHandler);
}
void AutoTypePlatformX11::stopCatchXErrors()
{
Q_ASSERT(m_catchXErrors);
XSync(m_dpy, False);
XSetErrorHandler(m_oldXErrorHandler);
m_catchXErrors = false;
}
int AutoTypePlatformX11::x11ErrorHandler(Display* display, XErrorEvent* error)
{
Q_UNUSED(display)
Q_UNUSED(error)
if (m_catchXErrors) {
m_xErrorOccurred = true;
}
return 1;
}
XkbDescPtr AutoTypePlatformX11::getKeyboard()
{
int num_devices;
XID keyboard_id = XkbUseCoreKbd;
XDeviceInfo* devices = XListInputDevices(m_dpy, &num_devices);
if (!devices) {
return nullptr;
}
for (int i = 0; i < num_devices; i++) {
if (QString(devices[i].name) == "Virtual core XTEST keyboard") {
keyboard_id = devices[i].id;
break;
}
}
XFreeDeviceList(devices);
return XkbGetKeyboard(m_dpy, XkbCompatMapMask | XkbGeometryMask, keyboard_id);
}
// --------------------------------------------------------------------------
// The following code is taken from xvkbd 3.0 and has been slightly modified.
// --------------------------------------------------------------------------
/*
* Insert a specified keysym on the dedicated position in the keymap
* table.
*/
int AutoTypePlatformX11::AddKeysym(KeySym keysym)
{
if (m_remapKeycode == 0) {
return 0;
}
int inx = (m_remapKeycode - m_minKeycode) * m_keysymPerKeycode;
m_keysymTable[inx] = keysym;
m_currentRemapKeysym = keysym;
XChangeKeyboardMapping(m_dpy, m_remapKeycode, m_keysymPerKeycode, &m_keysymTable[inx], 1);
XFlush(m_dpy);
updateKeymap();
return m_remapKeycode;
}
/*
* Send event to the focused window.
* If input focus is specified explicitly, select the window
@@ -652,42 +372,26 @@ void AutoTypePlatformX11::SendModifiers(unsigned int mask, bool press)
* Determines the keycode and modifier mask for the given
* keysym.
*/
int AutoTypePlatformX11::GetKeycode(KeySym keysym, unsigned int* mask)
bool AutoTypePlatformX11::GetKeycode(KeySym keysym, int* keycode, int* group, unsigned int* mask)
{
int keycode = XKeysymToKeycode(m_dpy, keysym);
const KeyDesc* desc = nullptr;
if (keycode && keysymModifiers(keysym, keycode, mask)) {
return keycode;
}
/* no modifier matches => resort to remapping */
keycode = AddKeysym(keysym);
if (keycode && keysymModifiers(keysym, keycode, mask)) {
return keycode;
}
*mask = 0;
return 0;
}
bool AutoTypePlatformX11::keysymModifiers(KeySym keysym, int keycode, unsigned int* mask)
{
int shift, mod;
unsigned int mods_rtrn;
/* determine whether there is a combination of the modifiers
(Mod1-Mod5) with or without shift which returns keysym */
for (shift = 0; shift < 2; shift++) {
for (mod = ControlMapIndex; mod <= Mod5MapIndex; mod++) {
KeySym keysym_rtrn;
*mask = (mod == ControlMapIndex) ? shift : shift | (1 << mod);
XkbTranslateKeyCode(m_xkb, keycode, *mask, &mods_rtrn, &keysym_rtrn);
if (keysym_rtrn == keysym) {
return true;
for (const auto& key : m_keymap) {
if (key.sym == keysym) {
// pick this description if we don't have any for this sym or this matches the current group
if (desc == nullptr || key.group == *group) {
desc = &key;
}
}
}
if (desc) {
*keycode = desc->code;
*group = desc->group;
*mask = desc->mask;
return true;
}
return false;
}
@@ -696,22 +400,31 @@ bool AutoTypePlatformX11::keysymModifiers(KeySym keysym, int keycode, unsigned i
* window to simulate keyboard. If modifiers (shift, control, etc)
* are set ON, many events will be sent.
*/
void AutoTypePlatformX11::SendKey(KeySym keysym, unsigned int modifiers)
AutoTypeAction::Result AutoTypePlatformX11::sendKey(KeySym keysym, unsigned int modifiers)
{
if (keysym == NoSymbol) {
qWarning("No such key: keysym=0x%lX", keysym);
return;
return AutoTypeAction::Result::Failed(tr("Trying to send invalid keysym."));
}
int keycode;
int group;
int group_active;
unsigned int wanted_mask;
/* determine keycode and mask for the given keysym */
keycode = GetKeycode(keysym, &wanted_mask);
if (keycode < 8 || keycode > 255) {
qWarning("Unable to get valid keycode for key: keysym=0x%lX", keysym);
return;
/* pull current active layout group */
XkbStateRec state;
XkbGetState(m_dpy, XkbUseCoreKbd, &state);
group_active = state.group;
/* tell GetKeycode we would prefer a key from active group */
group = group_active;
/* determine keycode, group and mask for the given keysym */
if (!GetKeycode(keysym, &keycode, &group, &wanted_mask)) {
return AutoTypeAction::Result::Failed(tr("Unable to get valid keycode for key: ")
+ QString(XKeysymToString(keysym)));
}
wanted_mask |= modifiers;
Window root, child;
@@ -721,62 +434,40 @@ void AutoTypePlatformX11::SendKey(KeySym keysym, unsigned int modifiers)
XSync(m_dpy, False);
XQueryPointer(m_dpy, m_rootWindow, &root, &child, &root_x, &root_y, &x, &y, &original_mask);
// modifiers that need to be pressed but aren't
/* fail permanently if Caps Lock is on */
if (original_mask & LockMask) {
return AutoTypeAction::Result::Failed(tr("Sequence aborted: Caps Lock is on"));
}
/* retry if keysym affecting modifier is held except Num Lock (Mod2Mask) */
if (original_mask & (ShiftMask | ControlMask | Mod1Mask | Mod3Mask | Mod4Mask | Mod5Mask)) {
return AutoTypeAction::Result::Retry(tr("Sequence aborted: Modifier keys held by user"));
}
/* modifiers that need to be held but aren't */
unsigned int press_mask = wanted_mask & ~original_mask;
// modifiers that are pressed but maybe shouldn't
unsigned int release_check_mask = original_mask & ~wanted_mask;
// modifiers we need to release before sending the keycode
unsigned int release_mask = 0;
if (!modifiers) {
// check every release_check_mask individually if it affects the keysym we would generate
// if it doesn't we probably don't need to release it
for (int mod_index = ShiftMapIndex; mod_index <= Mod5MapIndex; mod_index++) {
if (release_check_mask & (1 << mod_index)) {
unsigned int mods_rtrn;
KeySym keysym_rtrn;
XkbTranslateKeyCode(m_xkb, keycode, wanted_mask | (1 << mod_index), &mods_rtrn, &keysym_rtrn);
if (keysym_rtrn != keysym) {
release_mask |= (1 << mod_index);
}
}
}
// finally check if the combination of pressed modifiers that we chose to ignore affects the keysym
unsigned int mods_rtrn;
KeySym keysym_rtrn;
XkbTranslateKeyCode(
m_xkb, keycode, wanted_mask | (release_check_mask & ~release_mask), &mods_rtrn, &keysym_rtrn);
if (keysym_rtrn != keysym) {
// oh well, release all the modifiers we don't want
release_mask = release_check_mask;
}
} else {
release_mask = release_check_mask;
/* change layout group if necessary */
if (group_active != group) {
XkbLockGroup(m_dpy, XkbUseCoreKbd, group);
XFlush(m_dpy);
}
/* set modifiers mask */
if ((release_mask | press_mask) & LockMask) {
SendModifiers(LockMask, true);
SendModifiers(LockMask, false);
}
SendModifiers(release_mask & ~LockMask, false);
SendModifiers(press_mask & ~LockMask, true);
/* press and release release key */
/* hold modifiers and press key */
SendModifiers(press_mask, true);
SendKeyEvent(keycode, true);
SendKeyEvent(keycode, false);
/* restore previous modifiers mask */
SendModifiers(press_mask & ~LockMask, false);
SendModifiers(release_mask & ~LockMask, true);
if ((release_mask | press_mask) & LockMask) {
SendModifiers(LockMask, true);
SendModifiers(LockMask, false);
/* release key and release modifiers */
SendKeyEvent(keycode, false);
SendModifiers(press_mask, false);
/* reset layout group if necessary */
if (group_active != group) {
XkbLockGroup(m_dpy, XkbUseCoreKbd, group_active);
XFlush(m_dpy);
}
return AutoTypeAction::Result::Ok();
}
int AutoTypePlatformX11::MyErrorHandler(Display* my_dpy, XErrorEvent* event)
@@ -796,32 +487,37 @@ AutoTypeExecutorX11::AutoTypeExecutorX11(AutoTypePlatformX11* platform)
{
}
void AutoTypeExecutorX11::execChar(AutoTypeChar* action)
{
m_platform->SendKey(m_platform->charToKeySym(action->character));
}
void AutoTypeExecutorX11::execKey(AutoTypeKey* action)
{
m_platform->SendKey(m_platform->keyToKeySym(action->key));
}
void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr)
AutoTypeAction::Result AutoTypeExecutorX11::execBegin(const AutoTypeBegin* action)
{
Q_UNUSED(action);
m_platform->updateKeymap();
return AutoTypeAction::Result::Ok();
}
timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 25 * 1000 * 1000;
AutoTypeAction::Result AutoTypeExecutorX11::execType(const AutoTypeKey* action)
{
AutoTypeAction::Result result;
m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Home), static_cast<unsigned int>(ControlMask));
nanosleep(&ts, nullptr);
if (action->key != Qt::Key_unknown) {
result = m_platform->sendKey(qtToNativeKeyCode(action->key), qtToNativeModifiers(action->modifiers));
} else {
result = m_platform->sendKey(qcharToNativeKeyCode(action->character), qtToNativeModifiers(action->modifiers));
}
m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_End), static_cast<unsigned int>(ControlMask | ShiftMask));
nanosleep(&ts, nullptr);
if (result.isOk()) {
Tools::sleep(execDelayMs);
}
m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Backspace));
nanosleep(&ts, nullptr);
return result;
}
AutoTypeAction::Result AutoTypeExecutorX11::execClearField(const AutoTypeClearField* action)
{
Q_UNUSED(action);
execType(new AutoTypeKey(Qt::Key_Home, Qt::ControlModifier));
execType(new AutoTypeKey(Qt::Key_End, Qt::ControlModifier | Qt::ShiftModifier));
execType(new AutoTypeKey(Qt::Key_Backspace));
return AutoTypeAction::Result::Ok();
}
bool AutoTypePlatformX11::raiseWindow(WId window)

View File

@@ -26,12 +26,16 @@
#include <QX11Info>
#include <QtPlugin>
#include "autotype/AutoTypeAction.h"
#include "autotype/AutoTypePlatformPlugin.h"
#include "core/Tools.h"
#include "gui/osutils/OSUtils.h"
#include "gui/osutils/nixutils/X11Funcs.h"
#include <X11/XKBlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include "autotype/AutoTypeAction.h"
#include "autotype/AutoTypePlatformPlugin.h"
#include <xcb/xcb.h>
#define N_MOD_INDICES (Mod5MapIndex + 1)
@@ -48,19 +52,11 @@ public:
QStringList windowTitles() override;
WId activeWindow() override;
QString activeWindowTitle() override;
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
int platformEventFilter(void* event) override;
bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override;
void updateKeymap();
KeySym charToKeySym(const QChar& ch);
KeySym keyToKeySym(Qt::Key key);
void SendKey(KeySym keysym, unsigned int modifiers = 0);
signals:
void globalShortcutTriggered();
AutoTypeAction::Result sendKey(KeySym keysym, unsigned int modifiers = 0);
private:
QString windowTitle(Window window, bool useBlacklist);
@@ -68,20 +64,12 @@ private:
QString windowClassName(Window window);
QList<Window> widgetsToX11Windows(const QWidgetList& widgetList);
bool isTopLevelWindow(Window window);
uint qtToNativeModifiers(Qt::KeyboardModifiers modifiers);
void startCatchXErrors();
void stopCatchXErrors();
static int x11ErrorHandler(Display* display, XErrorEvent* error);
XkbDescPtr getKeyboard();
void updateKeymap();
bool isRemapKeycodeValid();
int AddKeysym(KeySym keysym);
void AddModifier(KeySym keysym);
void SendKeyEvent(unsigned keycode, bool press);
void SendModifiers(unsigned int mask, bool press);
int GetKeycode(KeySym keysym, unsigned int* mask);
bool keysymModifiers(KeySym keysym, int keycode, unsigned int* mask);
bool GetKeycode(KeySym keysym, int* keycode, int* group, unsigned int* mask);
static int MyErrorHandler(Display* my_dpy, XErrorEvent* event);
@@ -93,28 +81,20 @@ private:
Atom m_atomString;
Atom m_atomUtf8String;
Atom m_atomNetActiveWindow;
Atom m_atomTransientFor;
Atom m_atomWindow;
QSet<QString> m_classBlacklist;
Qt::Key m_currentGlobalKey;
Qt::KeyboardModifiers m_currentGlobalModifiers;
uint m_currentGlobalKeycode;
uint m_currentGlobalNativeModifiers;
int m_modifierMask;
static bool m_catchXErrors;
static bool m_xErrorOccurred;
static int (*m_oldXErrorHandler)(Display*, XErrorEvent*);
static const int m_unicodeToKeysymLen;
static const uint m_unicodeToKeysymKeys[];
static const uint m_unicodeToKeysymValues[];
typedef struct
{
KeySym sym;
int code;
int group;
int mask;
} KeyDesc;
XkbDescPtr m_xkb;
KeySym* m_keysymTable;
int m_minKeycode;
int m_maxKeycode;
int m_keysymPerKeycode;
/* dedicated keycode for remapped keys */
unsigned int m_remapKeycode;
KeySym m_currentRemapKeysym;
QList<KeyDesc> m_keymap;
KeyCode m_modifier_keycode[N_MOD_INDICES];
bool m_loaded;
};
@@ -124,9 +104,9 @@ class AutoTypeExecutorX11 : public AutoTypeExecutor
public:
explicit AutoTypeExecutorX11(AutoTypePlatformX11* platform);
void execChar(AutoTypeChar* action) override;
void execKey(AutoTypeKey* action) override;
void execClearField(AutoTypeClearField* action) override;
AutoTypeAction::Result execBegin(const AutoTypeBegin* action) override;
AutoTypeAction::Result execType(const AutoTypeKey* action) override;
AutoTypeAction::Result execClearField(const AutoTypeClearField* action) override;
private:
AutoTypePlatformX11* const m_platform;

View File

@@ -2,8 +2,8 @@ include_directories(SYSTEM ${X11_X11_INCLUDE_PATH})
set(autotype_XCB_SOURCES AutoTypeXCB.cpp)
add_library(keepassx-autotype-xcb MODULE ${autotype_XCB_SOURCES})
target_link_libraries(keepassx-autotype-xcb keepassx_core Qt5::Core Qt5::Widgets Qt5::X11Extras ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_XTest_LIB})
install(TARGETS keepassx-autotype-xcb
add_library(keepassxc-autotype-xcb MODULE ${autotype_XCB_SOURCES})
target_link_libraries(keepassxc-autotype-xcb keepassx_core Qt5::Core Qt5::Widgets Qt5::X11Extras ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_XTest_LIB})
install(TARGETS keepassxc-autotype-xcb
BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)

View File

@@ -1,171 +0,0 @@
/*
* Automatically generated by keysymmap.py from parsing keysymdef.h.
*/
const int AutoTypePlatformX11::m_unicodeToKeysymLen = 632;
// clang-format off
const uint AutoTypePlatformX11::m_unicodeToKeysymKeys[] = {
0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107,
0x0108, 0x0109, 0x010a, 0x010b, 0x010c, 0x010d, 0x010e, 0x010f,
0x0110, 0x0111, 0x0112, 0x0113, 0x0116, 0x0117, 0x0118, 0x0119,
0x011a, 0x011b, 0x011c, 0x011d, 0x011e, 0x011f, 0x0120, 0x0121,
0x0122, 0x0123, 0x0124, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129,
0x012a, 0x012b, 0x012e, 0x012f, 0x0130, 0x0131, 0x0134, 0x0135,
0x0136, 0x0137, 0x0138, 0x0139, 0x013a, 0x013b, 0x013c, 0x013d,
0x013e, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0147,
0x0148, 0x014a, 0x014b, 0x014c, 0x014d, 0x0150, 0x0151, 0x0152,
0x0153, 0x0154, 0x0155, 0x0156, 0x0157, 0x0158, 0x0159, 0x015a,
0x015b, 0x015c, 0x015d, 0x015e, 0x015f, 0x0160, 0x0161, 0x0162,
0x0163, 0x0164, 0x0165, 0x0166, 0x0167, 0x0168, 0x0169, 0x016a,
0x016b, 0x016c, 0x016d, 0x016e, 0x016f, 0x0170, 0x0171, 0x0172,
0x0173, 0x0178, 0x0179, 0x017a, 0x017b, 0x017c, 0x017d, 0x017e,
0x0192, 0x02c7, 0x02d8, 0x02d9, 0x02db, 0x02dd, 0x0385, 0x0386,
0x0388, 0x0389, 0x038a, 0x038c, 0x038e, 0x038f, 0x0390, 0x0391,
0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399,
0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, 0x03a0, 0x03a1,
0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, 0x03a8, 0x03a9, 0x03aa,
0x03ab, 0x03ac, 0x03ad, 0x03ae, 0x03af, 0x03b0, 0x03b1, 0x03b2,
0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba,
0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03c0, 0x03c1, 0x03c2,
0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8, 0x03c9, 0x03ca,
0x03cb, 0x03cc, 0x03cd, 0x03ce, 0x0401, 0x0402, 0x0403, 0x0404,
0x0405, 0x0406, 0x0407, 0x0408, 0x0409, 0x040a, 0x040b, 0x040c,
0x040e, 0x040f, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415,
0x0416, 0x0417, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d,
0x041e, 0x041f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425,
0x0426, 0x0427, 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d,
0x042e, 0x042f, 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435,
0x0436, 0x0437, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d,
0x043e, 0x043f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445,
0x0446, 0x0447, 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d,
0x044e, 0x044f, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456,
0x0457, 0x0458, 0x0459, 0x045a, 0x045b, 0x045c, 0x045e, 0x045f,
0x0490, 0x0491, 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5,
0x05d6, 0x05d7, 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd,
0x05de, 0x05df, 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5,
0x05e6, 0x05e7, 0x05e8, 0x05e9, 0x05ea, 0x060c, 0x061b, 0x061f,
0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628,
0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f, 0x0630,
0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638,
0x0639, 0x063a, 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645,
0x0646, 0x0647, 0x0648, 0x0649, 0x064a, 0x064b, 0x064c, 0x064d,
0x064e, 0x064f, 0x0650, 0x0651, 0x0652, 0x0e01, 0x0e02, 0x0e03,
0x0e04, 0x0e05, 0x0e06, 0x0e07, 0x0e08, 0x0e09, 0x0e0a, 0x0e0b,
0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, 0x0e10, 0x0e11, 0x0e12, 0x0e13,
0x0e14, 0x0e15, 0x0e16, 0x0e17, 0x0e18, 0x0e19, 0x0e1a, 0x0e1b,
0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, 0x0e20, 0x0e21, 0x0e22, 0x0e23,
0x0e24, 0x0e25, 0x0e26, 0x0e27, 0x0e28, 0x0e29, 0x0e2a, 0x0e2b,
0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, 0x0e30, 0x0e31, 0x0e32, 0x0e33,
0x0e34, 0x0e35, 0x0e36, 0x0e37, 0x0e38, 0x0e39, 0x0e3a, 0x0e3f,
0x0e40, 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47,
0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x0e50, 0x0e51,
0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, 0x0e58, 0x0e59,
0x2002, 0x2003, 0x2004, 0x2005, 0x2007, 0x2008, 0x2009, 0x200a,
0x2012, 0x2013, 0x2014, 0x2015, 0x2017, 0x2018, 0x2019, 0x201a,
0x201c, 0x201d, 0x201e, 0x2020, 0x2021, 0x2025, 0x2026, 0x2030,
0x2032, 0x2033, 0x2038, 0x203e, 0x20ac, 0x2105, 0x2116, 0x2117,
0x211e, 0x2122, 0x2153, 0x2154, 0x2155, 0x2156, 0x2157, 0x2158,
0x2159, 0x215a, 0x215b, 0x215c, 0x215d, 0x215e, 0x2190, 0x2191,
0x2192, 0x2193, 0x21d2, 0x21d4, 0x2202, 0x2207, 0x2218, 0x221a,
0x221d, 0x221e, 0x2227, 0x2228, 0x2229, 0x222a, 0x222b, 0x2234,
0x223c, 0x2243, 0x2260, 0x2261, 0x2264, 0x2265, 0x2282, 0x2283,
0x22a2, 0x22a3, 0x22a4, 0x22a5, 0x2308, 0x230a, 0x2315, 0x2320,
0x2321, 0x2395, 0x239b, 0x239d, 0x239e, 0x23a0, 0x23a1, 0x23a3,
0x23a4, 0x23a6, 0x23a8, 0x23ac, 0x23b7, 0x23ba, 0x23bb, 0x23bc,
0x23bd, 0x2409, 0x240a, 0x240b, 0x240c, 0x240d, 0x2424, 0x2500,
0x2502, 0x250c, 0x2510, 0x2514, 0x2518, 0x251c, 0x2524, 0x252c,
0x2534, 0x253c, 0x2592, 0x25c6, 0x25cb, 0x260e, 0x2640, 0x2642,
0x2663, 0x2665, 0x2666, 0x266d, 0x266f, 0x2713, 0x2717, 0x271d,
0x2720, 0x3001, 0x3002, 0x300c, 0x300d, 0x309b, 0x309c, 0x30a1,
0x30a2, 0x30a3, 0x30a4, 0x30a5, 0x30a6, 0x30a7, 0x30a8, 0x30a9,
0x30aa, 0x30ab, 0x30ad, 0x30af, 0x30b1, 0x30b3, 0x30b5, 0x30b7,
0x30b9, 0x30bb, 0x30bd, 0x30bf, 0x30c1, 0x30c3, 0x30c4, 0x30c6,
0x30c8, 0x30ca, 0x30cb, 0x30cc, 0x30cd, 0x30ce, 0x30cf, 0x30d2,
0x30d5, 0x30d8, 0x30db, 0x30de, 0x30df, 0x30e0, 0x30e1, 0x30e2,
0x30e3, 0x30e4, 0x30e5, 0x30e6, 0x30e7, 0x30e8, 0x30e9, 0x30ea,
0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f2, 0x30f3, 0x30fb, 0x30fc
};
const uint AutoTypePlatformX11::m_unicodeToKeysymValues[] = {
0x03c0, 0x03e0, 0x01c3, 0x01e3, 0x01a1, 0x01b1, 0x01c6, 0x01e6,
0x02c6, 0x02e6, 0x02c5, 0x02e5, 0x01c8, 0x01e8, 0x01cf, 0x01ef,
0x01d0, 0x01f0, 0x03aa, 0x03ba, 0x03cc, 0x03ec, 0x01ca, 0x01ea,
0x01cc, 0x01ec, 0x02d8, 0x02f8, 0x02ab, 0x02bb, 0x02d5, 0x02f5,
0x03ab, 0x03bb, 0x02a6, 0x02b6, 0x02a1, 0x02b1, 0x03a5, 0x03b5,
0x03cf, 0x03ef, 0x03c7, 0x03e7, 0x02a9, 0x02b9, 0x02ac, 0x02bc,
0x03d3, 0x03f3, 0x03a2, 0x01c5, 0x01e5, 0x03a6, 0x03b6, 0x01a5,
0x01b5, 0x01a3, 0x01b3, 0x01d1, 0x01f1, 0x03d1, 0x03f1, 0x01d2,
0x01f2, 0x03bd, 0x03bf, 0x03d2, 0x03f2, 0x01d5, 0x01f5, 0x13bc,
0x13bd, 0x01c0, 0x01e0, 0x03a3, 0x03b3, 0x01d8, 0x01f8, 0x01a6,
0x01b6, 0x02de, 0x02fe, 0x01aa, 0x01ba, 0x01a9, 0x01b9, 0x01de,
0x01fe, 0x01ab, 0x01bb, 0x03ac, 0x03bc, 0x03dd, 0x03fd, 0x03de,
0x03fe, 0x02dd, 0x02fd, 0x01d9, 0x01f9, 0x01db, 0x01fb, 0x03d9,
0x03f9, 0x13be, 0x01ac, 0x01bc, 0x01af, 0x01bf, 0x01ae, 0x01be,
0x08f6, 0x01b7, 0x01a2, 0x01ff, 0x01b2, 0x01bd, 0x07ae, 0x07a1,
0x07a2, 0x07a3, 0x07a4, 0x07a7, 0x07a8, 0x07ab, 0x07b6, 0x07c1,
0x07c2, 0x07c3, 0x07c4, 0x07c5, 0x07c6, 0x07c7, 0x07c8, 0x07c9,
0x07ca, 0x07cb, 0x07cc, 0x07cd, 0x07ce, 0x07cf, 0x07d0, 0x07d1,
0x07d2, 0x07d4, 0x07d5, 0x07d6, 0x07d7, 0x07d8, 0x07d9, 0x07a5,
0x07a9, 0x07b1, 0x07b2, 0x07b3, 0x07b4, 0x07ba, 0x07e1, 0x07e2,
0x07e3, 0x07e4, 0x07e5, 0x07e6, 0x07e7, 0x07e8, 0x07e9, 0x07ea,
0x07eb, 0x07ec, 0x07ed, 0x07ee, 0x07ef, 0x07f0, 0x07f1, 0x07f3,
0x07f2, 0x07f4, 0x07f5, 0x07f6, 0x07f7, 0x07f8, 0x07f9, 0x07b5,
0x07b9, 0x07b7, 0x07b8, 0x07bb, 0x06b3, 0x06b1, 0x06b2, 0x06b4,
0x06b5, 0x06b6, 0x06b7, 0x06b8, 0x06b9, 0x06ba, 0x06bb, 0x06bc,
0x06be, 0x06bf, 0x06e1, 0x06e2, 0x06f7, 0x06e7, 0x06e4, 0x06e5,
0x06f6, 0x06fa, 0x06e9, 0x06ea, 0x06eb, 0x06ec, 0x06ed, 0x06ee,
0x06ef, 0x06f0, 0x06f2, 0x06f3, 0x06f4, 0x06f5, 0x06e6, 0x06e8,
0x06e3, 0x06fe, 0x06fb, 0x06fd, 0x06ff, 0x06f9, 0x06f8, 0x06fc,
0x06e0, 0x06f1, 0x06c1, 0x06c2, 0x06d7, 0x06c7, 0x06c4, 0x06c5,
0x06d6, 0x06da, 0x06c9, 0x06ca, 0x06cb, 0x06cc, 0x06cd, 0x06ce,
0x06cf, 0x06d0, 0x06d2, 0x06d3, 0x06d4, 0x06d5, 0x06c6, 0x06c8,
0x06c3, 0x06de, 0x06db, 0x06dd, 0x06df, 0x06d9, 0x06d8, 0x06dc,
0x06c0, 0x06d1, 0x06a3, 0x06a1, 0x06a2, 0x06a4, 0x06a5, 0x06a6,
0x06a7, 0x06a8, 0x06a9, 0x06aa, 0x06ab, 0x06ac, 0x06ae, 0x06af,
0x06bd, 0x06ad, 0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5,
0x0ce6, 0x0ce7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced,
0x0cee, 0x0cef, 0x0cf0, 0x0cf1, 0x0cf2, 0x0cf3, 0x0cf4, 0x0cf5,
0x0cf6, 0x0cf7, 0x0cf8, 0x0cf9, 0x0cfa, 0x05ac, 0x05bb, 0x05bf,
0x05c1, 0x05c2, 0x05c3, 0x05c4, 0x05c5, 0x05c6, 0x05c7, 0x05c8,
0x05c9, 0x05ca, 0x05cb, 0x05cc, 0x05cd, 0x05ce, 0x05cf, 0x05d0,
0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7, 0x05d8,
0x05d9, 0x05da, 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5,
0x05e6, 0x05e7, 0x05e8, 0x05e9, 0x05ea, 0x05eb, 0x05ec, 0x05ed,
0x05ee, 0x05ef, 0x05f0, 0x05f1, 0x05f2, 0x0da1, 0x0da2, 0x0da3,
0x0da4, 0x0da5, 0x0da6, 0x0da7, 0x0da8, 0x0da9, 0x0daa, 0x0dab,
0x0dac, 0x0dad, 0x0dae, 0x0daf, 0x0db0, 0x0db1, 0x0db2, 0x0db3,
0x0db4, 0x0db5, 0x0db6, 0x0db7, 0x0db8, 0x0db9, 0x0dba, 0x0dbb,
0x0dbc, 0x0dbd, 0x0dbe, 0x0dbf, 0x0dc0, 0x0dc1, 0x0dc2, 0x0dc3,
0x0dc4, 0x0dc5, 0x0dc6, 0x0dc7, 0x0dc8, 0x0dc9, 0x0dca, 0x0dcb,
0x0dcc, 0x0dcd, 0x0dce, 0x0dcf, 0x0dd0, 0x0dd1, 0x0dd2, 0x0dd3,
0x0dd4, 0x0dd5, 0x0dd6, 0x0dd7, 0x0dd8, 0x0dd9, 0x0dda, 0x0ddf,
0x0de0, 0x0de1, 0x0de2, 0x0de3, 0x0de4, 0x0de5, 0x0de6, 0x0de7,
0x0de8, 0x0de9, 0x0dea, 0x0deb, 0x0dec, 0x0ded, 0x0df0, 0x0df1,
0x0df2, 0x0df3, 0x0df4, 0x0df5, 0x0df6, 0x0df7, 0x0df8, 0x0df9,
0x0aa2, 0x0aa1, 0x0aa3, 0x0aa4, 0x0aa5, 0x0aa6, 0x0aa7, 0x0aa8,
0x0abb, 0x0aaa, 0x0aa9, 0x07af, 0x0cdf, 0x0ad0, 0x0ad1, 0x0afd,
0x0ad2, 0x0ad3, 0x0afe, 0x0af1, 0x0af2, 0x0aaf, 0x0aae, 0x0ad5,
0x0ad6, 0x0ad7, 0x0afc, 0x047e, 0x20ac, 0x0ab8, 0x06b0, 0x0afb,
0x0ad4, 0x0ac9, 0x0ab0, 0x0ab1, 0x0ab2, 0x0ab3, 0x0ab4, 0x0ab5,
0x0ab6, 0x0ab7, 0x0ac3, 0x0ac4, 0x0ac5, 0x0ac6, 0x08fb, 0x08fc,
0x08fd, 0x08fe, 0x08ce, 0x08cd, 0x08ef, 0x08c5, 0x0bca, 0x08d6,
0x08c1, 0x08c2, 0x08de, 0x08df, 0x08dc, 0x08dd, 0x08bf, 0x08c0,
0x08c8, 0x08c9, 0x08bd, 0x08cf, 0x08bc, 0x08be, 0x08da, 0x08db,
0x0bfc, 0x0bdc, 0x0bc2, 0x0bce, 0x0bd3, 0x0bc4, 0x0afa, 0x08a4,
0x08a5, 0x0bcc, 0x08ab, 0x08ac, 0x08ad, 0x08ae, 0x08a7, 0x08a8,
0x08a9, 0x08aa, 0x08af, 0x08b0, 0x08a1, 0x09ef, 0x09f0, 0x09f2,
0x09f3, 0x09e2, 0x09e5, 0x09e9, 0x09e3, 0x09e4, 0x09e8, 0x09f1,
0x09f8, 0x09ec, 0x09eb, 0x09ed, 0x09ea, 0x09f4, 0x09f5, 0x09f7,
0x09f6, 0x09ee, 0x09e1, 0x09e0, 0x0bcf, 0x0af9, 0x0af8, 0x0af7,
0x0aec, 0x0aee, 0x0aed, 0x0af6, 0x0af5, 0x0af3, 0x0af4, 0x0ad9,
0x0af0, 0x04a4, 0x04a1, 0x04a2, 0x04a3, 0x04de, 0x04df, 0x04a7,
0x04b1, 0x04a8, 0x04b2, 0x04a9, 0x04b3, 0x04aa, 0x04b4, 0x04ab,
0x04b5, 0x04b6, 0x04b7, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc,
0x04bd, 0x04be, 0x04bf, 0x04c0, 0x04c1, 0x04af, 0x04c2, 0x04c3,
0x04c4, 0x04c5, 0x04c6, 0x04c7, 0x04c8, 0x04c9, 0x04ca, 0x04cb,
0x04cc, 0x04cd, 0x04ce, 0x04cf, 0x04d0, 0x04d1, 0x04d2, 0x04d3,
0x04ac, 0x04d4, 0x04ad, 0x04d5, 0x04ae, 0x04d6, 0x04d7, 0x04d8,
0x04d9, 0x04da, 0x04db, 0x04dc, 0x04a6, 0x04dd, 0x04a5, 0x04b0
};
// clang-format on

View File

@@ -24,9 +24,9 @@
#include <QJsonDocument>
#include <QJsonParseError>
#include <sodium.h>
#include <sodium/crypto_box.h>
#include <sodium/randombytes.h>
#include <botan/sodium.h>
using namespace Botan::Sodium;
namespace
{
@@ -283,7 +283,7 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
const QString id = decrypted.value("id").toString();
const QString formUrl = decrypted.value("submitUrl").toString();
const QString auth = decrypted.value("httpAuth").toString();
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0 ? true : false;
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0;
const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth);
if (users.isEmpty()) {

View File

@@ -25,7 +25,6 @@
#include <QMutexLocker>
#include <QtNetwork>
#include "sodium.h"
#include <iostream>
#ifdef Q_OS_WIN
@@ -53,11 +52,6 @@ BrowserHost::~BrowserHost()
void BrowserHost::start()
{
if (sodium_init() == -1) {
qWarning() << "Failed to start browser service: libsodium failed to initialize!";
return;
}
if (!m_localServer->isListening()) {
m_localServer->listen(BrowserShared::localServerPath());
}

View File

@@ -55,6 +55,7 @@ static const QString KEEPASSHTTP_GROUP_NAME = QStringLiteral("KeePassHttp Passwo
const QString BrowserService::OPTION_SKIP_AUTO_SUBMIT = QStringLiteral("BrowserSkipAutoSubmit");
const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEntry");
const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnlyHttpAuth");
const QString BrowserService::OPTION_NOT_HTTP_AUTH = QStringLiteral("BrowserNotHttpAuth");
// Multiple URL's
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
@@ -69,6 +70,10 @@ BrowserService::BrowserService()
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
{
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());
}
@@ -397,6 +402,11 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
continue;
}
if (httpAuth && entry->customData()->contains(BrowserService::OPTION_NOT_HTTP_AUTH)
&& entry->customData()->value(BrowserService::OPTION_NOT_HTTP_AUTH) == TRUE_STR) {
continue;
}
// HTTP Basic Auth always needs a confirmation
if (!ignoreHttpAuth && httpAuth) {
pwEntriesToConfirm.append(entry);
@@ -598,7 +608,7 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
}
}
if (!handleURL(entry->url(), siteUrlStr, formUrlStr)) {
if (!handleEntry(entry, siteUrlStr, formUrlStr)) {
continue;
}
@@ -740,14 +750,9 @@ BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr,
std::sort(keys.begin(), keys.end(), [](int l, int r) { return l > r; });
QList<Entry*> results;
auto sortField = browserSettings()->sortByTitle() ? EntryAttributes::TitleKey : EntryAttributes::UserNameKey;
for (auto key : keys) {
// Sort same priority entries by Title or UserName
auto entries = priorities.values(key);
std::sort(entries.begin(), entries.end(), [&sortField](Entry* left, Entry* right) {
return QString::localeAwareCompare(left->attribute(sortField), right->attribute(sortField)) < 0;
});
results << entries;
results << priorities.values(key);
if (browserSettings()->bestMatchOnly() && !results.isEmpty()) {
// Early out once we find the highest batch of matches
break;
@@ -998,6 +1003,19 @@ bool BrowserService::removeFirstDomain(QString& hostname)
return false;
}
/* Test if a search URL matches a custom entry. If the URL has the schema "keepassxc", some special checks will be made.
* Otherwise, this simply delegates to handleURL(). */
bool BrowserService::handleEntry(Entry* entry, const QString& url, const QString& submitUrl)
{
// Use this special scheme to find entries by UUID
if (url.startsWith("keepassxc://by-uuid/")) {
return url.endsWith("by-uuid/" + entry->uuidToHex());
} else if (url.startsWith("keepassxc://by-path/")) {
return url.endsWith("by-path/" + entry->path());
}
return handleURL(entry->url(), url, submitUrl);
}
bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlStr, const QString& formUrlStr)
{
if (entryUrl.isEmpty()) {
@@ -1016,7 +1034,7 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& siteUrlSt
}
// Make a direct compare if a local file is used
if (siteUrlStr.contains("file://")) {
if (siteUrlStr.startsWith("file://")) {
return entryUrl == formUrlStr;
}

View File

@@ -90,6 +90,7 @@ public:
static const QString OPTION_SKIP_AUTO_SUBMIT;
static const QString OPTION_HIDE_ENTRY;
static const QString OPTION_ONLY_HTTP_AUTH;
static const QString OPTION_NOT_HTTP_AUTH;
static const QString ADDITIONAL_URL;
signals:
@@ -135,6 +136,7 @@ private:
int sortPriority(const QStringList& urls, const QString& siteUrlStr, const QString& formUrlStr);
bool schemeFound(const QString& url);
bool removeFirstDomain(QString& hostname);
bool handleEntry(Entry* entry, const QString& url, const QString& submitUrl);
bool handleURL(const QString& entryUrl, const QString& siteUrlStr, const QString& formUrlStr);
QString baseDomain(const QString& hostname) const;
QSharedPointer<Database> getDatabase();

View File

@@ -82,26 +82,6 @@ void BrowserSettings::setMatchUrlScheme(bool matchUrlScheme)
config()->set(Config::Browser_MatchUrlScheme, matchUrlScheme);
}
bool BrowserSettings::sortByUsername()
{
return config()->get(Config::Browser_SortByUsername).toBool();
}
void BrowserSettings::setSortByUsername(bool sortByUsername)
{
config()->set(Config::Browser_SortByUsername, sortByUsername);
}
bool BrowserSettings::sortByTitle()
{
return !sortByUsername();
}
void BrowserSettings::setSortByTitle(bool sortByUsertitle)
{
setSortByUsername(!sortByUsertitle);
}
bool BrowserSettings::alwaysAllowAccess()
{
return config()->get(Config::Browser_AlwaysAllowAccess).toBool();

View File

@@ -42,10 +42,6 @@ public:
void setUnlockDatabase(bool unlockDatabase);
bool matchUrlScheme();
void setMatchUrlScheme(bool matchUrlScheme);
bool sortByUsername();
void setSortByUsername(bool sortByUsername = true);
bool sortByTitle();
void setSortByTitle(bool sortByUsertitle = true);
bool alwaysAllowAccess();
void setAlwaysAllowAccess(bool alwaysAllowAccess);
bool alwaysAllowUpdate();

View File

@@ -20,7 +20,7 @@
#include "BrowserService.h"
#include "BrowserSettings.h"
#include "BrowserSettingsWidget.h"
#include "core/Resources.h"
#include "gui/Icons.h"
QString BrowserSettingsPage::name()
{
@@ -29,7 +29,7 @@ QString BrowserSettingsPage::name()
QIcon BrowserSettingsPage::icon()
{
return Resources::instance()->icon("internet-web-browser");
return icons()->icon("internet-web-browser");
}
QWidget* BrowserSettingsPage::createWidget()

View File

@@ -117,12 +117,6 @@ void BrowserSettingsWidget::loadSettings()
// TODO: fix this
m_ui->showNotification->hide();
if (settings->sortByUsername()) {
m_ui->sortByUsername->setChecked(true);
} else {
m_ui->sortByTitle->setChecked(true);
}
m_ui->alwaysAllowAccess->setChecked(settings->alwaysAllowAccess());
m_ui->alwaysAllowUpdate->setChecked(settings->alwaysAllowUpdate());
m_ui->httpAuthPermission->setChecked(settings->httpAuthPermission());
@@ -212,7 +206,6 @@ void BrowserSettingsWidget::saveSettings()
settings->setBestMatchOnly(m_ui->bestMatchOnly->isChecked());
settings->setUnlockDatabase(m_ui->unlockDatabase->isChecked());
settings->setMatchUrlScheme(m_ui->matchUrlScheme->isChecked());
settings->setSortByUsername(m_ui->sortByUsername->isChecked());
settings->setUseCustomProxy(m_ui->useCustomProxy->isChecked());
settings->setCustomProxyLocation(m_ui->customProxyLocation->text());

View File

@@ -209,10 +209,10 @@
<item>
<widget class="QCheckBox" name="matchUrlScheme">
<property name="toolTip">
<string>Only entries with the same scheme (http://, https://, ...) are returned.</string>
<string>Only entries with the same scheme (http://, https://, ) are returned.</string>
</property>
<property name="text">
<string>Match URL scheme (e.g., https://...)</string>
<string>Match URL scheme (e.g., https://example.com)</string>
</property>
</widget>
</item>
@@ -246,20 +246,6 @@
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="sortByTitle">
<property name="text">
<string extracomment="Credentials mean login data requested via browser extension">Sort matching credentials by title</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="sortByUsername">
<property name="text">
<string extracomment="Credentials mean login data requested via browser extension">Sort matching credentials by username</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
@@ -400,7 +386,7 @@
<string>Browser for custom proxy file</string>
</property>
<property name="text">
<string extracomment="Button for opening file dialog">Browse...</string>
<string extracomment="Button for opening file dialog">Browse</string>
</property>
</widget>
</item>
@@ -539,7 +525,7 @@
<string>Browse for custom browser path</string>
</property>
<property name="text">
<string extracomment="Button for opening file dialog">Browse...</string>
<string extracomment="Button for opening file dialog">Browse</string>
</property>
</widget>
</item>

View File

@@ -32,5 +32,5 @@ if(WITH_XC_BROWSER)
Variant.cpp)
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE})
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${BOTAN2_LIBRARIES})
endif()

View File

@@ -15,18 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <stdio.h>
#include "Add.h"
#include "cli/Generate.h"
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "Generate.h"
#include "Utils.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/PasswordGenerator.h"
const QCommandLineOption Add::UsernameOption = QCommandLineOption(QStringList() << "u"
<< "username",
@@ -36,6 +30,9 @@ const QCommandLineOption Add::UsernameOption = QCommandLineOption(QStringList()
const QCommandLineOption Add::UrlOption =
QCommandLineOption(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL"));
const QCommandLineOption Add::NotesOption =
QCommandLineOption(QStringList() << "notes", QObject::tr("Notes for the entry."), QObject::tr("Notes"));
const QCommandLineOption Add::PasswordPromptOption =
QCommandLineOption(QStringList() << "p"
<< "password-prompt",
@@ -51,6 +48,7 @@ Add::Add()
description = QObject::tr("Add a new entry to a database.");
options.append(Add::UsernameOption);
options.append(Add::UrlOption);
options.append(Add::NotesOption);
options.append(Add::PasswordPromptOption);
positionalArguments.append({QString("entry"), QObject::tr("Path of the entry to add."), QString("")});
@@ -77,7 +75,7 @@ int Add::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<Q
// Cannot use those 2 options at the same time!
if (parser->isSet(Add::GenerateOption) && parser->isSet(Add::PasswordPromptOption)) {
err << QObject::tr("Cannot generate a password and prompt at the same time!") << endl;
err << QObject::tr("Cannot generate a password and prompt at the same time.") << endl;
return EXIT_FAILURE;
}
@@ -105,6 +103,10 @@ int Add::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<Q
entry->setUrl(parser->value(Add::UrlOption));
}
if (!parser->value(Add::NotesOption).isEmpty()) {
entry->setNotes(parser->value(Add::NotesOption).replace("\\n", "\n"));
}
if (parser->isSet(Add::PasswordPromptOption)) {
if (!parser->isSet(Command::QuietOption)) {
out << QObject::tr("Enter password for new entry: ") << flush;

View File

@@ -29,9 +29,9 @@ public:
static const QCommandLineOption UsernameOption;
static const QCommandLineOption UrlOption;
static const QCommandLineOption NotesOption;
static const QCommandLineOption PasswordPromptOption;
static const QCommandLineOption GenerateOption;
static const QCommandLineOption PasswordLengthOption;
};
#endif // KEEPASSXC_ADD_H

View File

@@ -15,14 +15,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <stdio.h>
#include "AddGroup.h"
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "Utils.h"
#include "core/Entry.h"
#include "core/Group.h"

View File

@@ -16,16 +16,14 @@
*/
#include "Analyze.h"
#include "cli/Utils.h"
#include "Utils.h"
#include "core/Group.h"
#include "core/HibpOffline.h"
#include "core/Tools.h"
#include <QCommandLineParser>
#include <QFile>
#include <QString>
#include "cli/TextStream.h"
#include "core/Group.h"
#include "core/Tools.h"
const QCommandLineOption Analyze::HIBPDatabaseOption = QCommandLineOption(
{"H", "hibp"},
@@ -34,11 +32,17 @@ const QCommandLineOption Analyze::HIBPDatabaseOption = QCommandLineOption(
"https://haveibeenpwned.com/Passwords."),
QObject::tr("FILENAME"));
const QCommandLineOption Analyze::OkonOption =
QCommandLineOption("okon",
QObject::tr("Path to okon-cli to search a formatted HIBP file"),
QObject::tr("okon-cli"));
Analyze::Analyze()
{
name = QString("analyze");
description = QObject::tr("Analyze passwords for weaknesses and problems.");
options.append(Analyze::HIBPDatabaseOption);
options.append(Analyze::OkonOption);
}
int Analyze::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
@@ -46,35 +50,53 @@ int Analyze::executeWithDatabase(QSharedPointer<Database> database, QSharedPoint
auto& out = Utils::STDOUT;
auto& err = Utils::STDERR;
QString hibpDatabase = parser->value(Analyze::HIBPDatabaseOption);
QFile hibpFile(hibpDatabase);
if (!hibpFile.open(QFile::ReadOnly)) {
err << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) << endl;
return EXIT_FAILURE;
}
out << QObject::tr("Evaluating database entries against HIBP file, this will take a while...") << endl;
QList<QPair<const Entry*, int>> findings;
QString error;
if (!HibpOffline::report(database, hibpFile, findings, &error)) {
err << error << endl;
auto hibpDatabase = parser->value(Analyze::HIBPDatabaseOption);
if (!QFile::exists(hibpDatabase) || hibpDatabase.isEmpty()) {
err << QObject::tr("Cannot find HIBP file: %1").arg(hibpDatabase);
return EXIT_FAILURE;
}
for (auto& finding : findings) {
printHibpFinding(finding.first, finding.second, out);
auto okon = parser->value(Analyze::OkonOption);
if (!okon.isEmpty()) {
out << QObject::tr("Evaluating database entries using okon…") << endl;
if (!HibpOffline::okonReport(database, okon, hibpDatabase, findings, &error)) {
err << error << endl;
return EXIT_FAILURE;
}
} else {
QFile hibpFile(hibpDatabase);
if (!hibpFile.open(QFile::ReadOnly)) {
err << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) << endl;
return EXIT_FAILURE;
}
out << QObject::tr("Evaluating database entries against HIBP file, this will take a while…") << endl;
if (!HibpOffline::report(database, hibpFile, findings, &error)) {
err << error << endl;
return EXIT_FAILURE;
}
}
for (const auto& finding : findings) {
const auto entry = finding.first;
auto count = finding.second;
QString path = entry->title();
for (auto g = entry->group(); g && g != g->database()->rootGroup(); g = g->parentGroup()) {
path.prepend("/").prepend(g->name());
}
if (count > 0) {
out << QObject::tr("Password for '%1' has been leaked %2 time(s)!", "", count).arg(path).arg(count) << endl;
} else {
out << QObject::tr("Password for '%1' has been leaked!", "", count).arg(path) << endl;
}
}
return EXIT_SUCCESS;
}
void Analyze::printHibpFinding(const Entry* entry, int count, QTextStream& out)
{
QString path = entry->title();
for (auto g = entry->group(); g && g != g->database()->rootGroup(); g = g->parentGroup()) {
path.prepend("/").prepend(g->name());
}
out << QObject::tr("Password for '%1' has been leaked %2 time(s)!", "", count).arg(path).arg(count) << endl;
}

View File

@@ -27,9 +27,7 @@ public:
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override;
static const QCommandLineOption HIBPDatabaseOption;
private:
void printHibpFinding(const Entry* entry, int count, QTextStream& out);
static const QCommandLineOption OkonOption;
};
#endif // KEEPASSXC_HIBP_H

View File

@@ -54,13 +54,7 @@ add_executable(keepassxc-cli keepassxc-cli.cpp)
target_link_libraries(keepassxc-cli
${GPGERROR_LIBRARIES}
cli
keepassx_core
Qt5::Core
${GCRYPT_LIBRARIES}
${sodium_LIBRARY_RELEASE}
${ARGON2_LIBRARIES}
${ZLIB_LIBRARIES}
${ZXCVBN_LIBRARIES})
keepassx_core)
install(TARGETS keepassxc-cli
BUNDLE DESTINATION . COMPONENT Runtime
@@ -73,48 +67,11 @@ if(MINGW)
endif()
if(APPLE AND WITH_APP_BUNDLE)
add_custom_command(TARGET keepassxc-cli
POST_BUILD
COMMAND ${CMAKE_INSTALL_NAME_TOOL}
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore
"@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore"
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui
"@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui"
-change /usr/local/opt/qt/lib/QtMacExtras.framework/Versions/5/QtMacExtras
"@executable_path/../Frameworks/QtMacExtras.framework/Versions/5/QtMacExtras"
-change /usr/local/opt/qt/lib/QtConcurrent.framework/Versions/5/QtConcurrent
"@executable_path/../Frameworks/QtConcurrent.framework/Versions/5/QtConcurrent"
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore
"@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore"
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork
"@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork"
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets
"@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets"
-change /usr/local/opt/qt/lib/QtSvg.framework/Versions/5/QtSvg
"@executable_path/../Frameworks/QtSvg.framework/Versions/5/QtSvg"
-change /usr/local/opt/libgcrypt/lib/libgcrypt.20.dylib
"@executable_path/../Frameworks/libgcrypt.20.dylib"
-change /usr/local/opt/argon2/lib/libargon2.1.dylib
"@executable_path/../Frameworks/libargon2.1.dylib"
-change /usr/local/opt/libgpg-error/lib/libgpg-error.0.dylib
"@executable_path/../Frameworks/libgpg-error.0.dylib"
-change /usr/local/opt/libsodium/lib/libsodium.23.dylib
"@executable_path/../Frameworks/libsodium.23.dylib"
-change /usr/local/opt/qrencode/lib/libqrencode.4.dylib
"@executable_path/../Frameworks/libqrencode.4.dylib"
-change /usr/local/opt/libyubikey/lib/libyubikey.0.dylib
"@executable_path/../Frameworks/libyubikey.0.dylib"
-change /usr/local/opt/ykpers/lib/libykpers-1.1.dylib
"@executable_path/../Frameworks/libykpers-1.1.dylib"
-change /usr/local/opt/quazip/lib/libquazip.1.dylib
"@executable_path/../Frameworks/libquazip.1.dylib"
keepassxc-cli
COMMENT "Changing linking of keepassxc-cli")
# Copy app to staging directory for pre-install testing
set(CLI_APP_DIR "${CMAKE_BINARY_DIR}/src/${CLI_INSTALL_DIR}")
add_custom_command(TARGET keepassxc-cli
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy keepassxc-cli ${CLI_APP_DIR}/keepassxc-cli
COMMENT "Copying keepassxc-cli inside the application")
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/keepassxc-cli ${CLI_APP_DIR}/keepassxc-cli
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${CLI_APP_DIR}/keepassxc-cli -no-plugins 2> /dev/null
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
COMMENT "Deploying keepassxc-cli")
endif()

View File

@@ -15,22 +15,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <chrono>
#include <cstdlib>
#include <thread>
#include "Clip.h"
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "Utils.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/Tools.h"
#define CLI_DEFAULT_CLIP_TIMEOUT 10
const QCommandLineOption Clip::AttributeOption = QCommandLineOption(
QStringList() << "a"
<< "attribute",
QObject::tr("Copy the given attribute to the clipboard. Defaults to \"password\" if not specified."),
QObject::tr("Copy the given attribute to the clipboard. Defaults to \"password\" if not specified.",
"Don't translate \"password\", it refers to the attribute."),
"attr",
"password");
@@ -39,16 +37,25 @@ const QCommandLineOption Clip::TotpOption =
<< "totp",
QObject::tr("Copy the current TOTP to the clipboard (equivalent to \"-a totp\")."));
const QCommandLineOption Clip::BestMatchOption =
QCommandLineOption(QStringList() << "b"
<< "best-match",
QObject::tr("Must match only one entry, otherwise a list of possible matches is shown."));
Clip::Clip()
{
name = QString("clip");
description = QObject::tr("Copy an entry's attribute to the clipboard.");
options.append(Clip::AttributeOption);
options.append(Clip::TotpOption);
options.append(Clip::BestMatchOption);
positionalArguments.append(
{QString("entry"), QObject::tr("Path of the entry to clip.", "clip = copy to clipboard"), QString("")});
optionalArguments.append(
{QString("timeout"), QObject::tr("Timeout in seconds before clearing the clipboard."), QString("[timeout]")});
{QString("timeout"),
QObject::tr("Timeout before clearing the clipboard (default is %1 seconds, set to 0 for unlimited).")
.arg(CLI_DEFAULT_CLIP_TIMEOUT),
QString("[timeout]")});
}
int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
@@ -57,21 +64,35 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
auto& err = Utils::STDERR;
const QStringList args = parser->positionalArguments();
const QString& entryPath = args.at(1);
QString timeout;
auto timeout = CLI_DEFAULT_CLIP_TIMEOUT;
if (args.size() == 3) {
timeout = args.at(2);
bool ok;
timeout = args.at(2).toInt(&ok);
if (!ok) {
err << QObject::tr("Invalid timeout value %1.").arg(args.at(2)) << endl;
return EXIT_FAILURE;
}
}
int timeoutSeconds = 0;
if (!timeout.isEmpty() && timeout.toInt() <= 0) {
err << QObject::tr("Invalid timeout value %1.").arg(timeout) << endl;
return EXIT_FAILURE;
} else if (!timeout.isEmpty()) {
timeoutSeconds = timeout.toInt();
QString entryPath;
if (parser->isSet(Clip::BestMatchOption)) {
QStringList results = database->rootGroup()->locate(args.at(1));
if (results.count() > 1) {
err << QObject::tr("Multiple entries matching:") << endl;
for (const QString& result : asConst(results)) {
err << result << endl;
}
return EXIT_FAILURE;
} else {
entryPath = (results.isEmpty()) ? args.at(1) : results[0];
out << QObject::tr("Used matching entry: %1").arg(entryPath) << endl;
}
} else {
entryPath = args.at(1);
}
Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
auto* entry = database->rootGroup()->findEntryByPath(entryPath);
if (!entry) {
err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl;
return EXIT_FAILURE;
@@ -119,17 +140,17 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
out << QObject::tr("Entry's \"%1\" attribute copied to the clipboard!").arg(selectedAttribute) << endl;
if (!timeoutSeconds) {
if (timeout <= 0) {
return exitCode;
}
QString lastLine = "";
while (timeoutSeconds > 0) {
while (timeout > 0) {
out << '\r' << QString(lastLine.size(), ' ') << '\r';
lastLine = QObject::tr("Clearing the clipboard in %1 second(s)...", "", timeoutSeconds).arg(timeoutSeconds);
lastLine = QObject::tr("Clearing the clipboard in %1 second(s)...", "", timeout).arg(timeout);
out << lastLine << flush;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
--timeoutSeconds;
Tools::sleep(1000);
--timeout;
}
Utils::clipText("");
out << '\r' << QString(lastLine.size(), ' ') << '\r';

Some files were not shown because too many files have changed in this diff Show More