Compare commits
138 Commits
2.6.4
...
feature/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc92ec1814 | ||
|
|
c8f3018c4c | ||
|
|
a174ec4ee7 | ||
|
|
c669765256 | ||
|
|
626f592c98 | ||
|
|
9eae0b318e | ||
|
|
bcc81b069b | ||
|
|
fa785e1745 | ||
|
|
fd0bdaae80 | ||
|
|
60adcacaaa | ||
|
|
cc6f5c3226 | ||
|
|
8c61a73bb0 | ||
|
|
e1c8304c4b | ||
|
|
ee92b980bb | ||
|
|
17326dc3ec | ||
|
|
3c2abaaa82 | ||
|
|
64279bb881 | ||
|
|
8a7be101e4 | ||
|
|
be3e77d721 | ||
|
|
dc496fd1d9 | ||
|
|
01d86760e0 | ||
|
|
7b7f52c8af | ||
|
|
5c709f0da3 | ||
|
|
9fee79ea96 | ||
|
|
91e74944f3 | ||
|
|
c0ae130656 | ||
|
|
7fe0e2629c | ||
|
|
805574cac1 | ||
|
|
31aa5e12e5 | ||
|
|
9b8feed3ed | ||
|
|
ed0ece304d | ||
|
|
80809ace67 | ||
|
|
86ddd702fb | ||
|
|
871c4fffdd | ||
|
|
ca8abecc4b | ||
|
|
371bd2e51b | ||
|
|
c19efb5b19 | ||
|
|
439c155552 | ||
|
|
8c9530e3ec | ||
|
|
4d07507739 | ||
|
|
2423bede60 | ||
|
|
8b8fb9562f | ||
|
|
e29cf8bfef | ||
|
|
d6b69204a6 | ||
|
|
bc08913c61 | ||
|
|
57af7c131d | ||
|
|
6c7b04fee8 | ||
|
|
4e8b00da34 | ||
|
|
b9ea6fd2e7 | ||
|
|
022154462e | ||
|
|
c9c19d043f | ||
|
|
c5a2aa0a2a | ||
|
|
2d66786656 | ||
|
|
3ccd4f9b14 | ||
|
|
46f5596e59 | ||
|
|
c0d673b46f | ||
|
|
8d058cbd04 | ||
|
|
02446af743 | ||
|
|
813ab47e29 | ||
|
|
027ff9f2bf | ||
|
|
d9ae449f04 | ||
|
|
7ce35f81de | ||
|
|
f3d88fbd36 | ||
|
|
4f7460afbd | ||
|
|
75e4329c80 | ||
|
|
af55d1b1b3 | ||
|
|
a5094dd3ea | ||
|
|
9a8a5a0006 | ||
|
|
33e6da33ca | ||
|
|
1385929089 | ||
|
|
61b85183f9 | ||
|
|
4e90cb5818 | ||
|
|
b55f419386 | ||
|
|
c7323accf2 | ||
|
|
2e6c22d44d | ||
|
|
3b30855855 | ||
|
|
fff15d2d4d | ||
|
|
86278311d2 | ||
|
|
9a7b20cbfd | ||
|
|
80c1b9be6a | ||
|
|
37dab85df7 | ||
|
|
cd0084f21c | ||
|
|
404fd941e8 | ||
|
|
a6f01349e8 | ||
|
|
c9d1512748 | ||
|
|
de44764efa | ||
|
|
c9e7ffadad | ||
|
|
5ea46d4c12 | ||
|
|
748a6b5ce1 | ||
|
|
30c2e39e70 | ||
|
|
9a96124040 | ||
|
|
c6bd22aa12 | ||
|
|
3d10f31211 | ||
|
|
9f4118974d | ||
|
|
7f85eb77aa | ||
|
|
a651d7049d | ||
|
|
804a3b6706 | ||
|
|
f5caf3968f | ||
|
|
000e1823ac | ||
|
|
f8f2271f33 | ||
|
|
15dc6f062e | ||
|
|
91dea9cbc4 | ||
|
|
fb8423fdca | ||
|
|
cd519e1bf3 | ||
|
|
eb6f0eb346 | ||
|
|
0c5dd1556a | ||
|
|
fd3cc7e8c3 | ||
|
|
bf2cad28af | ||
|
|
fa546c440e | ||
|
|
34b44e7496 | ||
|
|
48d9fb3e79 | ||
|
|
fb87b1c794 | ||
|
|
af4ecb4aa1 | ||
|
|
ba8611cf4c | ||
|
|
9bffe05020 | ||
|
|
8f84675874 | ||
|
|
2e7a44de61 | ||
|
|
7426693f1d | ||
|
|
e1c2537084 | ||
|
|
ac5c1af829 | ||
|
|
d3747f40e2 | ||
|
|
55eb855267 | ||
|
|
8a4a804c8c | ||
|
|
6a35bbea2f | ||
|
|
f947c96462 | ||
|
|
f49f62d3be | ||
|
|
bbdfbe64da | ||
|
|
e53850627f | ||
|
|
6b96806914 | ||
|
|
656e6d289a | ||
|
|
745f1befe9 | ||
|
|
5c2a1a4284 | ||
|
|
71b05dbcf4 | ||
|
|
ff9dd43262 | ||
|
|
88a0a8d35a | ||
|
|
b6787d91a3 | ||
|
|
c03a734ebb | ||
|
|
6e99bb178d |
3
.gitattributes
vendored
@@ -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
@@ -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
|
||||
24
CHANGELOG.md
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
7
COPYING
@@ -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
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
[](https://codecov.io/gh/keepassxreboot/keepassxc)
|
||||
[](https://github.com/keepassxreboot/keepassxc/releases/)
|
||||
|
||||
[](https://matrix.to/#/!zUxwGnFkUyycpxeHeM:matrix.org?via=matrix.org)
|
||||
[](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).
|
||||
|
||||
@@ -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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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
@@ -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.
|
||||
@@ -1,6 +1,5 @@
|
||||
= KeePassXC: Getting Started Guide
|
||||
KeePassXC Team <team@keepassxc.org>
|
||||
v2.6.0
|
||||
:data-uri:
|
||||
:linkcss!:
|
||||
:homepage: https://keepassxc.org
|
||||
|
||||
@@ -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=*]
|
||||
|
||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 27 KiB |
BIN
docs/images/autotype_selection_dialog_search.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/images/autotype_selection_dialog_type_menu.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/enable_copy_dc.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
@@ -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.
|
||||
@@ -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[]
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
KeePassXC Team <team@keepassxc.org>
|
||||
v2.6.0
|
||||
:data-uri:
|
||||
:homepage: https://keepassxc.org
|
||||
:stylesheet: ../styles/dark.css
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[]
|
||||
@@ -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[]
|
||||
|
||||
@@ -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[]
|
||||
|
||||
94
docs/topics/Reference.adoc
Normal file
@@ -0,0 +1,94 @@
|
||||
= KeePassXC - Reference
|
||||
include::.sharedheader[]
|
||||
:imagesdir: ../images
|
||||
|
||||
// tag::content[]
|
||||
== Reference
|
||||
This section contains full details on advanced features available in KeePassXC.
|
||||
|
||||
=== Entry Placeholders
|
||||
[grid=rows, frame=none, width=90%]
|
||||
|===
|
||||
|Placeholder |Description
|
||||
|
||||
|{TITLE} |Entry Title
|
||||
|{USERNAME} |Username
|
||||
|{PASSWORD} |Password
|
||||
|{URL} |URL
|
||||
|{NOTES} |Notes
|
||||
|{TOTP} |Current TOTP value (if configured)
|
||||
|{S:<ATTRIBUTE_NAME>} |Value for the given attribute (case sensitive)
|
||||
|{URL:RMVSCM} |URL without scheme (e.g., https)
|
||||
|{URL:WITHOUTSCHEME} |URL without scheme
|
||||
|{URL:SCM} |URL Scheme
|
||||
|{URL:SCHEME} |URL Scheme
|
||||
|{URL:HOST} |URL Host (e.g., example.com)
|
||||
|{URL:PORT} |URL Port
|
||||
|{URL:PATH} |URL Path (e.g., /path/to/page.html)
|
||||
|{URL:QUERY} |URL Query String
|
||||
|{URL:FRAGMENT} |URL Fragment
|
||||
|{URL:USERINFO} |URL Username:Password
|
||||
|{URL:USERNAME} |URL Username
|
||||
|{URL:PASSWORD} |URL Password
|
||||
|{DT_SIMPLE} |Current Date-Time (yyyyMMddhhmmss)
|
||||
|{DT_YEAR} |Current Year (yyyy)
|
||||
|{DT_MONTH} |Current Month (MM)
|
||||
|{DT_DAY} |Current Day (dd)
|
||||
|{DT_HOUR} |Current Hour (hh)
|
||||
|{DT_MINUTE} |Current Minutes (mm)
|
||||
|{DT_SECOND} |Current Seconds (ss)
|
||||
|{DT_UTC_SIMPLE} |Current UTC Date-Time (yyyyMMddhhmmss)
|
||||
|{DT_UTC_YEAR} |Current UTC Year (yyyy)
|
||||
|{DT_UTC_MONTH} |Current UTC Month (MM)
|
||||
|{DT_UTC_DAY} |Current UTC Day (dd)
|
||||
|{DT_UTC_HOUR} |Current UTC Hour (hh)
|
||||
|{DT_UTC_MINUTE} |Current UTC Minutes (mm)
|
||||
|{DT_UTC_SECOND} |Current UTC Seconds (ss)
|
||||
|{DB_DIR} |Absolute directory path of database file
|
||||
|===
|
||||
|
||||
=== Entry Cross-Reference
|
||||
A reference to another entry's field is possible using the short-hand syntax:
|
||||
`{REF:<FIELD>@<SEARCH_IN>:<SEARCH_TEXT>}`
|
||||
|
||||
`<FIELD>` and `<SEARCH_IN>` can be one of following:
|
||||
|
||||
* T - Title
|
||||
* U - Username
|
||||
* P - Password
|
||||
* A - URL
|
||||
* N - Notes
|
||||
* I - UUID (found on entry properties page)
|
||||
* O - Custom Attribute _(SEARCH_IN only)_
|
||||
|
||||
Examples: +
|
||||
`{REF:U@I:033054D445C648C59092CC1D661B1B71}` +
|
||||
`{REF:P@T:Other Entry}` +
|
||||
`{REF:A@O:Attribute 1}`
|
||||
|
||||
=== Auto-Type Actions
|
||||
[grid=rows, frame=none, width=90%]
|
||||
|===
|
||||
|Action Code |Description
|
||||
|
||||
|{TAB}, {ENTER}, {SPACE}, {INSERT}, {DELETE}, {HOME}, {END}, {PGUP}, {PGDN}, {BACKSPACE}, {CAPSLOCK}, {ESC}
|
||||
|Press the corresponding keyboard key
|
||||
|
||||
|{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key
|
||||
|{F1}, {F2}, ..., {F16} |Press F1, F2, etc.
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively
|
||||
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|
||||
|{DELAY=X} |Set delay between key presses to X milliseconds
|
||||
|{DELAY X} |Pause typing for X milliseconds
|
||||
|{CLEARFIELD} |Clear the input field
|
||||
|{PICKCHARS} |Pick specific password characters from a dialog
|
||||
|===
|
||||
|
||||
*Text Conversions:*
|
||||
|
||||
*{T-CONV:/<PLACEHOLDER>/<METHOD>/}* +
|
||||
Convert resolved placeholder (e.g., {USERNAME}, {PASSWORD}, etc.) using the following methods: UPPER, LOWER, BASE64, HEX, URI, URI-DEC.
|
||||
|
||||
*{T-REPLACE-RX:/<PLACEHOLDER>/<SEARCH>/<REPLACE>/}* +
|
||||
Use regular expressions to find and replace data from a resolved placeholder. Refer to match groups using $1, $2, etc.
|
||||
// end::content[]
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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[]
|
||||
261
release-tool
@@ -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
BIN
share/empty4.kdbx
Normal 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 |
@@ -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 |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 ".")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AutoTypeFilterLineEdit.h"
|
||||
#include <QKeyEvent>
|
||||
|
||||
void AutoTypeFilterLineEdit::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Up) {
|
||||
emit keyUpPressed();
|
||||
} else if (event->key() == Qt::Key_Down) {
|
||||
emit keyDownPressed();
|
||||
} else {
|
||||
QLineEdit::keyPressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void AutoTypeFilterLineEdit::keyReleaseEvent(QKeyEvent* event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Escape) {
|
||||
emit escapeReleased();
|
||||
} else {
|
||||
QLineEdit::keyReleaseEvent(event);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_AUTOTYPEFILTERLINEEDIT_H
|
||||
#define KEEPASSX_AUTOTYPEFILTERLINEEDIT_H
|
||||
|
||||
#include <QLineEdit>
|
||||
|
||||
class AutoTypeFilterLineEdit : public QLineEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AutoTypeFilterLineEdit(QWidget* widget)
|
||||
: QLineEdit(widget)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent(QKeyEvent* event);
|
||||
virtual void keyReleaseEvent(QKeyEvent* event);
|
||||
signals:
|
||||
void keyUpPressed();
|
||||
void keyDownPressed();
|
||||
void escapeReleased();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AUTOTYPEFILTERLINEEDIT_H
|
||||
@@ -1,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
|
||||
@@ -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();
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
#include "core/AutoTypeMatch.h"
|
||||
#include "autotype/AutoTypeMatch.h"
|
||||
|
||||
class Entry;
|
||||
class Group;
|
||||
137
src/autotype/AutoTypeMatchView.cpp
Normal 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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
243
src/autotype/AutoTypeSelectDialog.ui
Normal 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><p>You can use advanced search queries to find any entry in your open databases. The following shortcuts are useful:<br/>
|
||||
Ctrl+F - Toggle database search<br/>
|
||||
Ctrl+1 - Type username<br/>
|
||||
Ctrl+2 - Type password<br/>
|
||||
Ctrl+3 - Type TOTP</p></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>
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AutoTypeSelectView.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
|
||||
AutoTypeSelectView::AutoTypeSelectView(QWidget* parent)
|
||||
: AutoTypeMatchView(parent)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
setAllColumnsShowFocus(true);
|
||||
|
||||
connect(model(), SIGNAL(modelReset()), SLOT(selectFirstMatch()));
|
||||
}
|
||||
|
||||
void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
QModelIndex index = indexAt(event->pos());
|
||||
|
||||
if (index.isValid()) {
|
||||
setCurrentIndex(index);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
} else {
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
AutoTypeMatchView::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void AutoTypeSelectView::selectFirstMatch()
|
||||
{
|
||||
QModelIndex index = model()->index(0, 0);
|
||||
|
||||
if (index.isValid()) {
|
||||
setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
void AutoTypeSelectView::keyReleaseEvent(QKeyEvent* e)
|
||||
{
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
emit rejected();
|
||||
} else {
|
||||
e->ignore();
|
||||
}
|
||||
}
|
||||
172
src/autotype/PickcharsDialog.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PickcharsDialog.h"
|
||||
#include "ui_PickcharsDialog.h"
|
||||
|
||||
#include "core/Entry.h"
|
||||
#include "gui/Icons.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QShortcut>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
#include <QScreen>
|
||||
#else
|
||||
#include <QDesktopWidget>
|
||||
#endif
|
||||
|
||||
PickcharsDialog::PickcharsDialog(const QString& string, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_ui(new Ui::PickcharsDialog())
|
||||
{
|
||||
if (string.isEmpty()) {
|
||||
reject();
|
||||
}
|
||||
|
||||
// Places the window on the active (virtual) desktop instead of where the main window is.
|
||||
setAttribute(Qt::WA_X11BypassTransientForHint);
|
||||
setWindowFlags((windowFlags() | Qt::WindowStaysOnTopHint | Qt::MSWindowsFixedSizeDialogHint)
|
||||
& ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowIcon(icons()->applicationIcon());
|
||||
|
||||
m_ui->setupUi(this);
|
||||
|
||||
// Increase max columns with longer passwords for better display
|
||||
int width = 10;
|
||||
if (string.length() >= 100) {
|
||||
width = 20;
|
||||
} else if (string.length() >= 60) {
|
||||
width = 15;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (const auto& ch : string) {
|
||||
auto btn = new QPushButton(QString::number(count + 1));
|
||||
btn->setProperty("char", ch);
|
||||
btn->setProperty("count", count);
|
||||
connect(btn, &QPushButton::clicked, this, &PickcharsDialog::charSelected);
|
||||
m_ui->charsGrid->addWidget(btn, count / width, count % width);
|
||||
m_lastSelected = count;
|
||||
++count;
|
||||
}
|
||||
// Prevent stretched buttons
|
||||
if (m_ui->charsGrid->rowCount() == 1 && m_ui->charsGrid->columnCount() < 5) {
|
||||
m_ui->charsGrid->addItem(new QSpacerItem(5, 5, QSizePolicy::MinimumExpanding), count / width, count % width);
|
||||
}
|
||||
m_ui->charsGrid->itemAtPosition(0, 0)->widget()->setFocus();
|
||||
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
// Navigate grid layout using up/down/left/right motion
|
||||
new QShortcut(Qt::Key_Up, this, SLOT(upPressed()));
|
||||
new QShortcut(Qt::Key_Down, this, SLOT(downPressed()));
|
||||
// Remove last selected character
|
||||
auto shortcut = new QShortcut(Qt::Key_Backspace, this);
|
||||
connect(shortcut, &QShortcut::activated, this, [this] {
|
||||
auto text = m_ui->selectedChars->text();
|
||||
m_ui->selectedChars->setText(text.left(text.size() - 1));
|
||||
});
|
||||
// Submit the form
|
||||
shortcut = new QShortcut(Qt::CTRL + Qt::Key_S, this);
|
||||
connect(shortcut, &QShortcut::activated, this, [this] { accept(); });
|
||||
}
|
||||
|
||||
void PickcharsDialog::upPressed()
|
||||
{
|
||||
auto focus = focusWidget();
|
||||
if (!focus) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto count = focus->property("count");
|
||||
if (count.isValid()) {
|
||||
// Lower bound not checked by QGridLayout::itemAt https://bugreports.qt.io/browse/QTBUG-91261
|
||||
auto upCount = count.toInt() - m_ui->charsGrid->columnCount();
|
||||
if (upCount >= 0) {
|
||||
m_ui->charsGrid->itemAt(upCount)->widget()->setFocus();
|
||||
}
|
||||
} else if (focus == m_ui->selectedChars) {
|
||||
// Move back to the last selected button
|
||||
auto item = m_ui->charsGrid->itemAt(m_lastSelected);
|
||||
if (item) {
|
||||
item->widget()->setFocus();
|
||||
}
|
||||
} else if (focus == m_ui->pressTab) {
|
||||
m_ui->selectedChars->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void PickcharsDialog::downPressed()
|
||||
{
|
||||
auto focus = focusWidget();
|
||||
if (!focus) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto count = focus->property("count");
|
||||
if (count.isValid()) {
|
||||
auto item = m_ui->charsGrid->itemAt(count.toInt() + m_ui->charsGrid->columnCount());
|
||||
if (item) {
|
||||
item->widget()->setFocus();
|
||||
} else {
|
||||
// Store the currently selected button and move to the line edit
|
||||
m_lastSelected = count.toInt();
|
||||
m_ui->selectedChars->setFocus();
|
||||
}
|
||||
} else if (focus == m_ui->selectedChars) {
|
||||
m_ui->pressTab->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
QString PickcharsDialog::selectedChars()
|
||||
{
|
||||
return m_ui->selectedChars->text();
|
||||
}
|
||||
|
||||
bool PickcharsDialog::pressTab()
|
||||
{
|
||||
return m_ui->pressTab->isChecked();
|
||||
}
|
||||
|
||||
void PickcharsDialog::charSelected()
|
||||
{
|
||||
auto btn = qobject_cast<QPushButton*>(sender());
|
||||
if (!btn) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->selectedChars->setText(m_ui->selectedChars->text() + btn->property("char").toChar());
|
||||
}
|
||||
|
||||
void PickcharsDialog::showEvent(QShowEvent* event)
|
||||
{
|
||||
QDialog::showEvent(event);
|
||||
|
||||
// Center on active screen
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
auto screen = QApplication::screenAt(QCursor::pos());
|
||||
if (!screen) {
|
||||
// screenAt can return a nullptr, default to the primary screen
|
||||
screen = QApplication::primaryScreen();
|
||||
}
|
||||
QRect screenGeometry = screen->availableGeometry();
|
||||
#else
|
||||
QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos());
|
||||
#endif
|
||||
move(screenGeometry.center().x() - (size().width() / 2), screenGeometry.center().y() - (size().height() / 2));
|
||||
}
|
||||
@@ -1,41 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* 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
|
||||
87
src/autotype/PickcharsDialog.ui
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PickcharsDialog</class>
|
||||
<widget class="QDialog" name="PickcharsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>418</width>
|
||||
<height>188</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>KeePassXC - Pick Characters</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select characters to type, navigate with arrow keys, Ctrl + S submits.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="charsGrid"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PasswordEdit" name="selectedChars">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="pressTab">
|
||||
<property name="text">
|
||||
<string>Press &Tab between characters</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>PasswordEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>gui/PasswordEdit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>selectedChars</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -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;");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "WildcardMatcher.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <utility>
|
||||
|
||||
const QChar WildcardMatcher::Wildcard = '*';
|
||||
const Qt::CaseSensitivity WildcardMatcher::Sensitivity = Qt::CaseInsensitive;
|
||||
|
||||
WildcardMatcher::WildcardMatcher(QString text)
|
||||
: m_text(std::move(text))
|
||||
{
|
||||
}
|
||||
|
||||
bool WildcardMatcher::match(const QString& pattern)
|
||||
{
|
||||
m_pattern = pattern;
|
||||
|
||||
if (patternContainsWildcard()) {
|
||||
return matchWithWildcards();
|
||||
} else {
|
||||
return patternEqualsText();
|
||||
}
|
||||
}
|
||||
|
||||
bool WildcardMatcher::patternContainsWildcard()
|
||||
{
|
||||
return m_pattern.contains(Wildcard);
|
||||
}
|
||||
|
||||
bool WildcardMatcher::patternEqualsText()
|
||||
{
|
||||
return m_text.compare(m_pattern, Sensitivity) == 0;
|
||||
}
|
||||
|
||||
bool WildcardMatcher::matchWithWildcards()
|
||||
{
|
||||
QStringList parts = m_pattern.split(Wildcard, QString::KeepEmptyParts);
|
||||
Q_ASSERT(parts.size() >= 2);
|
||||
|
||||
if (startOrEndDoesNotMatch(parts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return partsMatch(parts);
|
||||
}
|
||||
|
||||
bool WildcardMatcher::startOrEndDoesNotMatch(const QStringList& parts)
|
||||
{
|
||||
return !m_text.startsWith(parts.first(), Sensitivity) || !m_text.endsWith(parts.last(), Sensitivity);
|
||||
}
|
||||
|
||||
bool WildcardMatcher::partsMatch(const QStringList& parts)
|
||||
{
|
||||
int index = 0;
|
||||
for (const QString& part : parts) {
|
||||
int matchIndex = getMatchIndex(part, index);
|
||||
if (noMatchFound(matchIndex)) {
|
||||
return false;
|
||||
}
|
||||
index = calculateNewIndex(matchIndex, part.length());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int WildcardMatcher::getMatchIndex(const QString& part, int startIndex)
|
||||
{
|
||||
return m_text.indexOf(part, startIndex, Sensitivity);
|
||||
}
|
||||
|
||||
bool WildcardMatcher::noMatchFound(int index)
|
||||
{
|
||||
return index == -1;
|
||||
}
|
||||
|
||||
int WildcardMatcher::calculateNewIndex(int matchIndex, int partLength)
|
||||
{
|
||||
return matchIndex + partLength;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_WILDCARDMATCHER_H
|
||||
#define KEEPASSX_WILDCARDMATCHER_H
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
class WildcardMatcher
|
||||
{
|
||||
public:
|
||||
explicit WildcardMatcher(QString text);
|
||||
bool match(const QString& pattern);
|
||||
|
||||
static const QChar Wildcard;
|
||||
|
||||
private:
|
||||
bool patternEqualsText();
|
||||
bool patternContainsWildcard();
|
||||
bool matchWithWildcards();
|
||||
bool startOrEndDoesNotMatch(const QStringList& parts);
|
||||
bool partsMatch(const QStringList& parts);
|
||||
int getMatchIndex(const QString& part, int startIndex);
|
||||
bool noMatchFound(int index);
|
||||
int calculateNewIndex(int matchIndex, int partLength);
|
||||
|
||||
static const Qt::CaseSensitivity Sensitivity;
|
||||
const QString m_text;
|
||||
QString m_pattern;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_WILDCARDMATCHER_H
|
||||
@@ -17,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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -26,7 +26,6 @@ public:
|
||||
virtual ~AutoTypeTestInterface()
|
||||
{
|
||||
}
|
||||
virtual void triggerGlobalAutoType() = 0;
|
||||
virtual void setActiveWindowTitle(const QString& title) = 0;
|
||||
|
||||
virtual QString actionChars() = 0;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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';
|
||||
|
||||