mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merge branch 'release/2.4.2' into develop
This commit is contained in:
commit
bc891761b6
@ -20,9 +20,10 @@ project(KeePassXC)
|
|||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
|
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
|
||||||
"Choose the type of build, options are: None Debug Release RelWithDebInfo Debug DebugFull Profile MinSizeRel."
|
"Choose the type of build, options are: Debug Release RelWithDebInfo Profile"
|
||||||
FORCE)
|
FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
@ -40,22 +41,21 @@ option(WITH_ASAN "Enable address sanitizer checks (Linux / macOS only)" OFF)
|
|||||||
option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF)
|
option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF)
|
||||||
option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
|
option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
|
||||||
|
|
||||||
set(WITH_XC_ALL OFF CACHE BOOLEAN "Build in all available plugins")
|
set(WITH_XC_ALL OFF CACHE BOOL "Build in all available plugins")
|
||||||
|
|
||||||
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
|
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
|
||||||
option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website icons)." OFF)
|
option(WITH_XC_NETWORKING "Include networking code (e.g. for downloading website icons)." OFF)
|
||||||
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
|
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
|
||||||
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
|
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
|
||||||
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
||||||
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF)
|
option(WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)" OFF)
|
||||||
option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" OFF)
|
|
||||||
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
|
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
|
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WITH_XC_ALL)
|
if(WITH_XC_ALL)
|
||||||
# Enable all options
|
# Enable all options (except update check)
|
||||||
set(WITH_XC_AUTOTYPE ON)
|
set(WITH_XC_AUTOTYPE ON)
|
||||||
set(WITH_XC_NETWORKING ON)
|
set(WITH_XC_NETWORKING ON)
|
||||||
set(WITH_XC_BROWSER ON)
|
set(WITH_XC_BROWSER ON)
|
||||||
@ -67,18 +67,16 @@ if(WITH_XC_ALL)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WITH_XC_KEESHARE_SECURE)
|
|
||||||
set(WITH_XC_KEESHARE ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
|
if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
|
||||||
set(WITH_XC_CRYPTO_SSH ON)
|
set(WITH_XC_CRYPTO_SSH ON)
|
||||||
else()
|
else()
|
||||||
set(WITH_XC_CRYPTO_SSH OFF)
|
set(WITH_XC_CRYPTO_SSH OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WITH_XC_UPDATECHECK)
|
# Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK
|
||||||
set(WITH_XC_NETWORKING ON)
|
if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK)
|
||||||
|
message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled")
|
||||||
|
set(WITH_XC_UPDATECHECK OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(KEEPASSXC_VERSION_MAJOR "2")
|
set(KEEPASSXC_VERSION_MAJOR "2")
|
||||||
@ -162,11 +160,15 @@ if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
|
|||||||
set(IS_32BIT TRUE)
|
set(IS_32BIT TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
|
if("${CMAKE_C_COMPILER}" MATCHES "clang$"
|
||||||
|
OR "${CMAKE_EXTRA_GENERATOR_C_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__"
|
||||||
|
OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
|
||||||
set(CMAKE_COMPILER_IS_CLANG 1)
|
set(CMAKE_COMPILER_IS_CLANG 1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$"
|
||||||
|
OR "${CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__"
|
||||||
|
OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||||
set(CMAKE_COMPILER_IS_CLANGXX 1)
|
set(CMAKE_COMPILER_IS_CLANGXX 1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -199,7 +201,7 @@ add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
|
|||||||
add_gcc_compiler_flags("-fvisibility=hidden")
|
add_gcc_compiler_flags("-fvisibility=hidden")
|
||||||
add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden")
|
add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden")
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
|
||||||
add_gcc_compiler_flags("-Werror")
|
add_gcc_compiler_flags("-Werror")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -230,7 +232,6 @@ if(WITH_ASAN)
|
|||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
|
||||||
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
|
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
|
||||||
add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2")
|
add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2")
|
||||||
endif()
|
endif()
|
||||||
@ -264,6 +265,11 @@ endif()
|
|||||||
add_gcc_compiler_cflags("-std=c99")
|
add_gcc_compiler_cflags("-std=c99")
|
||||||
add_gcc_compiler_cxxflags("-std=c++11")
|
add_gcc_compiler_cxxflags("-std=c++11")
|
||||||
|
|
||||||
|
if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9.99) OR
|
||||||
|
(CMAKE_COMPILER_IS_CLANGXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6.99))
|
||||||
|
add_gcc_compiler_cxxflags("-fsized-deallocation")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
add_gcc_compiler_cxxflags("-stdlib=libc++")
|
add_gcc_compiler_cxxflags("-stdlib=libc++")
|
||||||
endif()
|
endif()
|
||||||
@ -276,7 +282,7 @@ if(MINGW)
|
|||||||
set(CMAKE_RC_COMPILER_INIT windres)
|
set(CMAKE_RC_COMPILER_INIT windres)
|
||||||
enable_language(RC)
|
enable_language(RC)
|
||||||
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
|
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
|
||||||
if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo"))
|
if(NOT (CMAKE_BUILD_TYPE_LOWER STREQUAL "debug" OR CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithdebinfo"))
|
||||||
# Enable DEP and ASLR
|
# Enable DEP and ASLR
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
||||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
||||||
@ -365,10 +371,17 @@ if(APPLE)
|
|||||||
set(CMAKE_MACOSX_RPATH TRUE)
|
set(CMAKE_MACOSX_RPATH TRUE)
|
||||||
find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
|
find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
|
||||||
if(NOT MACDEPLOYQT_EXE)
|
if(NOT MACDEPLOYQT_EXE)
|
||||||
message(FATAL_ERROR "macdeployqt is required to build in macOS")
|
message(FATAL_ERROR "macdeployqt is required to build on macOS")
|
||||||
else()
|
else()
|
||||||
message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}")
|
message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}")
|
||||||
endif()
|
endif()
|
||||||
|
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()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Debian sets the the build type to None for package builds.
|
# Debian sets the the build type to None for package builds.
|
||||||
@ -380,6 +393,7 @@ find_package(Gcrypt 1.7.0 REQUIRED)
|
|||||||
find_package(Argon2 REQUIRED)
|
find_package(Argon2 REQUIRED)
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
find_package(QREncode REQUIRED)
|
find_package(QREncode REQUIRED)
|
||||||
|
find_package(sodium 1.0.12 REQUIRED)
|
||||||
|
|
||||||
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
|
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
|
||||||
|
|
||||||
@ -387,20 +401,7 @@ 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")
|
message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR})
|
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR})
|
||||||
|
|
||||||
# Optional
|
|
||||||
if(WITH_XC_KEESHARE)
|
|
||||||
set(WITH_XC_KEESHARE_INSECURE ON)
|
|
||||||
if(WITH_XC_KEESHARE_SECURE)
|
|
||||||
# ZLIB is needed and already required
|
|
||||||
find_package(QuaZip REQUIRED)
|
|
||||||
include_directories(SYSTEM ${QUAZIP_INCLUDE_DIR})
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
set(WITH_XC_KEESHARE_INSECURE OFF)
|
|
||||||
set(WITH_XC_KEESHARE_SECURE OFF)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Optional
|
# Optional
|
||||||
if(WITH_XC_YUBIKEY)
|
if(WITH_XC_YUBIKEY)
|
||||||
|
18
INSTALL.md
18
INSTALL.md
@ -25,8 +25,8 @@ The following libraries are required:
|
|||||||
* zlib
|
* zlib
|
||||||
* libmicrohttpd
|
* libmicrohttpd
|
||||||
* libxi, libxtst, qtx11extras (optional for auto-type on X11)
|
* libxi, libxtst, qtx11extras (optional for auto-type on X11)
|
||||||
* libsodium (>= 1.0.12, optional for KeePassXC-Browser support)
|
* libsodium (>= 1.0.12)
|
||||||
* argon2
|
* libargon2
|
||||||
* qrencode
|
* qrencode
|
||||||
* yubikey ykpers (optional to support YubiKey)
|
* yubikey ykpers (optional to support YubiKey)
|
||||||
|
|
||||||
@ -99,18 +99,26 @@ These steps place the compiled KeePassXC binary inside the `./build/src/` direct
|
|||||||
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
|
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
|
||||||
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
|
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
|
||||||
-DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF)
|
-DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF)
|
||||||
-DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (favicon download) (default: OFF)
|
-DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (e.g., favicon downloading) (default: OFF)
|
||||||
-DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF)
|
-DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF)
|
||||||
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group syncronization extension (default: OFF)
|
|
||||||
-DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF)
|
-DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF)
|
||||||
|
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group synchronization extension (default: OFF)
|
||||||
|
-DWITH_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare signed containers, requires libquazip5 (default: OFF)
|
||||||
-DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF)
|
-DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF)
|
||||||
-DWITH_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare secure containers, requires libquazip5 (default: OFF)
|
|
||||||
|
-DWITH_XC_UPDATECHECK=[ON|OFF] Enable/Disable automatic updating checking (requires WITH_XC_NETWORKING) (default: ON)
|
||||||
|
|
||||||
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
|
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
|
||||||
-DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF)
|
-DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF)
|
||||||
-DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
|
-DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
|
||||||
-DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF)
|
-DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF)
|
||||||
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
|
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
|
||||||
-DWITH_APP_BUNDLE=[ON|OFF] Enable Application Bundle for macOS (default: ON)
|
-DWITH_APP_BUNDLE=[ON|OFF] Enable Application Bundle for macOS (default: ON)
|
||||||
|
|
||||||
|
-DKEEPASSXC_BUILD_TYPE=[Snapshot|PreRelease|Release] Set the build type to show/hide stability warnings (default: "Snapshot")
|
||||||
|
-DKEEPASSXC_DIST_TYPE=[Snap|AppImage|Other] Specify the distribution method (default: "Other")
|
||||||
|
-DOVERRIDE_VERSION=[X.X.X] Specify a version number when building. Used with snapshot builds (default: "")
|
||||||
|
-DGIT_HEAD_OVERRIDE=[XXXXXXX] Specify the 7 digit git commit ref for this build. Used with distribution builds (default: "")
|
||||||
```
|
```
|
||||||
|
|
||||||
* If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/`
|
* If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/`
|
||||||
|
@ -112,7 +112,7 @@ mark_as_advanced(
|
|||||||
CMAKE_EXE_LINKER_FLAGS_COVERAGE
|
CMAKE_EXE_LINKER_FLAGS_COVERAGE
|
||||||
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
|
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(NOT CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
|
||||||
message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
|
message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
|
||||||
endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
|
endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
|
||||||
|
|
||||||
|
@ -1,41 +1,24 @@
|
|||||||
# QUAZIP_FOUND - QuaZip library was found
|
# QUAZIP_FOUND - QuaZip library was found
|
||||||
# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir
|
# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir
|
||||||
# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR)
|
# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR)
|
||||||
# QUAZIP_LIBRARIES - List of QuaZip libraries
|
# QUAZIP_LIBRARIES - List of QuaZip libraries
|
||||||
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
|
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
|
||||||
|
|
||||||
IF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
|
if(MINGW)
|
||||||
# in cache already
|
find_library(QUAZIP_LIBRARIES libquazip5)
|
||||||
SET(QUAZIP_FOUND TRUE)
|
find_path(QUAZIP_INCLUDE_DIR quazip.h PATH_SUFFIXES quazip5)
|
||||||
ELSE(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
|
find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h)
|
||||||
IF(Qt5Core_FOUND)
|
else()
|
||||||
set(QUAZIP_LIB_VERSION_SUFFIX 5)
|
find_library(QUAZIP_LIBRARIES
|
||||||
ENDIF()
|
NAMES quazip5 quazip
|
||||||
IF(WIN32)
|
PATHS /usr/lib /usr/lib64 /usr/local/lib
|
||||||
FIND_PATH(QUAZIP_LIBRARY_DIR
|
|
||||||
WIN32_DEBUG_POSTFIX d
|
|
||||||
NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll
|
|
||||||
HINTS "C:/Programme/" "C:/Program Files"
|
|
||||||
PATH_SUFFIXES QuaZip/lib
|
|
||||||
)
|
)
|
||||||
FIND_LIBRARY(QUAZIP_LIBRARIES NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll HINTS ${QUAZIP_LIBRARY_DIR})
|
find_path(QUAZIP_INCLUDE_DIR quazip.h
|
||||||
FIND_PATH(QUAZIP_INCLUDE_DIR NAMES quazip.h HINTS ${QUAZIP_LIBRARY_DIR}/../ PATH_SUFFIXES include/quazip5)
|
PATHS /usr/include /usr/local/include
|
||||||
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR NAMES zlib.h)
|
PATH_SUFFIXES quazip5 quazip
|
||||||
ELSE(WIN32)
|
|
||||||
FIND_PACKAGE(PkgConfig)
|
|
||||||
pkg_check_modules(PC_QUAZIP quazip)
|
|
||||||
FIND_LIBRARY(QUAZIP_LIBRARIES
|
|
||||||
WIN32_DEBUG_POSTFIX d
|
|
||||||
NAMES quazip${QUAZIP_LIB_VERSION_SUFFIX}
|
|
||||||
HINTS /usr/lib /usr/lib64
|
|
||||||
)
|
)
|
||||||
FIND_PATH(QUAZIP_INCLUDE_DIR quazip.h
|
find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h PATHS /usr/include /usr/local/include)
|
||||||
HINTS /usr/include /usr/local/include
|
endif()
|
||||||
PATH_SUFFIXES quazip${QUAZIP_LIB_VERSION_SUFFIX}
|
include(FindPackageHandleStandardArgs)
|
||||||
)
|
set(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR})
|
||||||
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR zlib.h HINTS /usr/include /usr/local/include)
|
find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS)
|
||||||
ENDIF(WIN32)
|
|
||||||
INCLUDE(FindPackageHandleStandardArgs)
|
|
||||||
SET(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR})
|
|
||||||
find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS)
|
|
||||||
ENDIF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
|
|
||||||
|
11
snap/local/launchers/README.md
Normal file
11
snap/local/launchers/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# /snap/local/launchers
|
||||||
|
Here are the launchers, or wrapper programs to deal with some runtime-fixable problems for the snapped applications, like setting proper environmental variables in snap.
|
||||||
|
|
||||||
|
In convention launchers are named _something_-launch, for dealing certain problem with _something_, and usually can be called in a stacked manner to consolidate their modifications.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apps:
|
||||||
|
_app_name_:
|
||||||
|
command: foo-launch bar-launch _app_command_
|
||||||
|
```
|
||||||
|
|
14
snap/local/launchers/gtk3-env-launch
Executable file
14
snap/local/launchers/gtk3-env-launch
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# This is the maintainence launcher for the snap, make necessary runtime environment changes to make the snap work here. You may also insert security confinement/deprecation/obsoletion notice of the snap here.
|
||||||
|
|
||||||
|
set \
|
||||||
|
-o errexit \
|
||||||
|
-o errtrace \
|
||||||
|
-o nounset \
|
||||||
|
-o pipefail
|
||||||
|
|
||||||
|
# gtk-common-themes support
|
||||||
|
export QT_QPA_PLATFORMTHEME=gtk3
|
||||||
|
|
||||||
|
# Finally run the next part of the command chain
|
||||||
|
exec "${@}"
|
@ -9,16 +9,28 @@ description: |
|
|||||||
confinement: strict
|
confinement: strict
|
||||||
base: core18
|
base: core18
|
||||||
|
|
||||||
plugs:
|
plugs: # plugs for theming, font settings, cursor and to use gtk3 file chooser
|
||||||
icon-themes: # fix mouse cursor theme
|
gtk-3-themes:
|
||||||
|
interface: content
|
||||||
|
target: $SNAP/data-dir/themes
|
||||||
|
default-provider: gtk-common-themes:gtk-3-themes
|
||||||
|
icon-themes:
|
||||||
interface: content
|
interface: content
|
||||||
target: $SNAP/data-dir/icons
|
target: $SNAP/data-dir/icons
|
||||||
default-provider: gtk-common-themes
|
default-provider: gtk-common-themes:icon-themes
|
||||||
|
sound-themes:
|
||||||
|
interface: content
|
||||||
|
target: $SNAP/data-dir/sounds
|
||||||
|
default-provider: gtk-common-themes:sounds-themes
|
||||||
|
|
||||||
apps:
|
apps:
|
||||||
keepassxc:
|
keepassxc:
|
||||||
command: desktop-launch keepassxc
|
adapter: full
|
||||||
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy]
|
command: usr/bin/keepassxc -style fusion
|
||||||
|
command-chain:
|
||||||
|
- bin/desktop-launch
|
||||||
|
- bin/gtk3-env-launch
|
||||||
|
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy, desktop]
|
||||||
desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop
|
desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop
|
||||||
environment:
|
environment:
|
||||||
DISABLE_WAYLAND: 1
|
DISABLE_WAYLAND: 1
|
||||||
@ -73,7 +85,7 @@ parts:
|
|||||||
- libquazip5-1
|
- libquazip5-1
|
||||||
- libusb-1.0-0
|
- libusb-1.0-0
|
||||||
- qtwayland5
|
- qtwayland5
|
||||||
- qt5-style-plugins # for mouse cursor theme fix
|
- qt5-gtk-platformtheme # for theming, font settings, cursor and to use gtk3 file chooser
|
||||||
override-build: |
|
override-build: |
|
||||||
snapcraftctl build
|
snapcraftctl build
|
||||||
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop
|
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop
|
||||||
@ -83,6 +95,14 @@ parts:
|
|||||||
- -opt
|
- -opt
|
||||||
after: [desktop-qt5]
|
after: [desktop-qt5]
|
||||||
|
|
||||||
|
launchers: # custom launcher to set QT_QPA_PLATFORMTHEME=gtk3 correctly
|
||||||
|
source: snap/local/launchers
|
||||||
|
plugin: dump
|
||||||
|
organize:
|
||||||
|
'*': bin/
|
||||||
|
stage:
|
||||||
|
- -bin/README.*
|
||||||
|
|
||||||
desktop-qt5:
|
desktop-qt5:
|
||||||
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
|
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
|
||||||
source-subdir: qt
|
source-subdir: qt
|
@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
|
|
||||||
configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h)
|
|
||||||
|
|
||||||
find_library(ZXCVBN_LIBRARIES zxcvbn)
|
find_library(ZXCVBN_LIBRARIES zxcvbn)
|
||||||
if(NOT ZXCVBN_LIBRARIES)
|
if(NOT ZXCVBN_LIBRARIES)
|
||||||
add_library(zxcvbn STATIC zxcvbn/zxcvbn.c)
|
add_library(zxcvbn STATIC zxcvbn/zxcvbn.c)
|
||||||
@ -27,6 +24,7 @@ if(NOT ZXCVBN_LIBRARIES)
|
|||||||
endif(NOT ZXCVBN_LIBRARIES)
|
endif(NOT ZXCVBN_LIBRARIES)
|
||||||
|
|
||||||
set(keepassx_SOURCES
|
set(keepassx_SOURCES
|
||||||
|
core/Alloc.cpp
|
||||||
core/AutoTypeAssociations.cpp
|
core/AutoTypeAssociations.cpp
|
||||||
core/AutoTypeMatch.cpp
|
core/AutoTypeMatch.cpp
|
||||||
core/Compare.cpp
|
core/Compare.cpp
|
||||||
@ -167,7 +165,8 @@ if(APPLE)
|
|||||||
core/ScreenLockListenerMac.cpp
|
core/ScreenLockListenerMac.cpp
|
||||||
core/MacPasteboard.cpp
|
core/MacPasteboard.cpp
|
||||||
gui/macutils/MacUtils.cpp
|
gui/macutils/MacUtils.cpp
|
||||||
gui/macutils/AppKitImpl.mm)
|
gui/macutils/AppKitImpl.mm
|
||||||
|
gui/macutils/AppKit.h)
|
||||||
endif()
|
endif()
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
set(keepassx_SOURCES
|
set(keepassx_SOURCES
|
||||||
@ -192,8 +191,7 @@ add_feature_info(Auto-Type WITH_XC_AUTOTYPE "Automatic password typing")
|
|||||||
add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)")
|
add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)")
|
||||||
add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser")
|
add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser")
|
||||||
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
|
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
|
||||||
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare")
|
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)")
|
||||||
add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources")
|
|
||||||
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
||||||
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
|
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
@ -255,8 +253,13 @@ endif()
|
|||||||
|
|
||||||
if(WITH_XC_TOUCHID)
|
if(WITH_XC_TOUCHID)
|
||||||
list(APPEND keepassx_SOURCES touchid/TouchID.mm)
|
list(APPEND keepassx_SOURCES touchid/TouchID.mm)
|
||||||
|
# TODO: Remove -Wno-error once deprecation warnings have been resolved.
|
||||||
|
set_source_files_properties(touchid/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast -Wno-error")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
|
||||||
|
configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h)
|
||||||
|
|
||||||
add_library(autotype STATIC ${autotype_SOURCES})
|
add_library(autotype STATIC ${autotype_SOURCES})
|
||||||
target_link_libraries(autotype Qt5::Core Qt5::Widgets)
|
target_link_libraries(autotype Qt5::Core Qt5::Widgets)
|
||||||
|
|
||||||
@ -271,6 +274,7 @@ target_link_libraries(keepassx_core
|
|||||||
Qt5::Concurrent
|
Qt5::Concurrent
|
||||||
Qt5::Network
|
Qt5::Network
|
||||||
Qt5::Widgets
|
Qt5::Widgets
|
||||||
|
${sodium_LIBRARY_RELEASE}
|
||||||
${YUBIKEY_LIBRARIES}
|
${YUBIKEY_LIBRARIES}
|
||||||
${ZXCVBN_LIBRARIES}
|
${ZXCVBN_LIBRARIES}
|
||||||
${ARGON2_LIBRARIES}
|
${ARGON2_LIBRARIES}
|
||||||
@ -411,25 +415,19 @@ if(MINGW)
|
|||||||
|
|
||||||
install(CODE "set(gp_tool \"objdump\")" COMPONENT Runtime)
|
install(CODE "set(gp_tool \"objdump\")" COMPONENT Runtime)
|
||||||
|
|
||||||
include(DeployQt4)
|
# Deploy all 3rd party library dependencies first
|
||||||
install_qt4_executable(${PROGNAME}.exe)
|
install(CODE "include(BundleUtilities)
|
||||||
|
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${PROGNAME}.exe\" \"\" \"\")"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
# install Qt5 plugins
|
# Use windeployqt.exe to setup Qt dependencies
|
||||||
set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins)
|
set(WINDEPLOYQT_MODE "--release")
|
||||||
install(FILES
|
if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
|
||||||
${PLUGINS_DIR}/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
|
set(WINDEPLOYQT_MODE "--debug")
|
||||||
${PLUGINS_DIR}/platforms/qdirect2d$<$<CONFIG:Debug>:d>.dll
|
endif()
|
||||||
DESTINATION "platforms")
|
|
||||||
install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$<CONFIG:Debug>:d>.dll DESTINATION "styles")
|
install(CODE "execute_process(COMMAND ${WINDEPLOYQT_EXE} ${PROGNAME}.exe ${WINDEPLOYQT_MODE} WORKING_DIRECTORY \${CMAKE_INSTALL_PREFIX} OUTPUT_QUIET)"
|
||||||
install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$<CONFIG:Debug>:d>.dll DESTINATION "platforminputcontexts")
|
COMPONENT Runtime)
|
||||||
install(FILES ${PLUGINS_DIR}/iconengines/qsvgicon$<$<CONFIG:Debug>:d>.dll DESTINATION "iconengines")
|
|
||||||
install(FILES
|
|
||||||
${PLUGINS_DIR}/imageformats/qgif$<$<CONFIG:Debug>:d>.dll
|
|
||||||
${PLUGINS_DIR}/imageformats/qicns$<$<CONFIG:Debug>:d>.dll
|
|
||||||
${PLUGINS_DIR}/imageformats/qico$<$<CONFIG:Debug>:d>.dll
|
|
||||||
${PLUGINS_DIR}/imageformats/qjpeg$<$<CONFIG:Debug>:d>.dll
|
|
||||||
${PLUGINS_DIR}/imageformats/qwebp$<$<CONFIG:Debug>:d>.dll
|
|
||||||
DESTINATION "imageformats")
|
|
||||||
|
|
||||||
# install CA cert chains
|
# install CA cert chains
|
||||||
install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs")
|
install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs")
|
||||||
|
@ -296,6 +296,7 @@ QString BrowserService::storeKey(const QString& key)
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
QInputDialog keyDialog;
|
QInputDialog keyDialog;
|
||||||
|
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject()));
|
||||||
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
|
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
|
||||||
keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n"
|
keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n"
|
||||||
"If you would like to allow it access to your KeePassXC database,\n"
|
"If you would like to allow it access to your KeePassXC database,\n"
|
||||||
@ -310,7 +311,7 @@ QString BrowserService::storeKey(const QString& key)
|
|||||||
|
|
||||||
id = keyDialog.textValue();
|
id = keyDialog.textValue();
|
||||||
|
|
||||||
if (ok != QDialog::Accepted || id.isEmpty()) {
|
if (ok != QDialog::Accepted || id.isEmpty() || !isDatabaseOpened()) {
|
||||||
hideWindow();
|
hideWindow();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -406,6 +407,11 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
|||||||
return QJsonArray();
|
return QJsonArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that database is not locked when the popup was visible
|
||||||
|
if (!isDatabaseOpened()) {
|
||||||
|
return QJsonArray();
|
||||||
|
}
|
||||||
|
|
||||||
// Sort results
|
// Sort results
|
||||||
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
||||||
|
|
||||||
@ -760,6 +766,7 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
|||||||
|
|
||||||
m_dialogActive = true;
|
m_dialogActive = true;
|
||||||
BrowserAccessControlDialog accessControlDialog;
|
BrowserAccessControlDialog accessControlDialog;
|
||||||
|
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
|
||||||
accessControlDialog.setUrl(url);
|
accessControlDialog.setUrl(url);
|
||||||
accessControlDialog.setItems(pwEntriesToConfirm);
|
accessControlDialog.setItems(pwEntriesToConfirm);
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
if(WITH_XC_BROWSER)
|
if(WITH_XC_BROWSER)
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
find_package(sodium 1.0.12 REQUIRED)
|
|
||||||
|
|
||||||
set(keepassxcbrowser_SOURCES
|
set(keepassxcbrowser_SOURCES
|
||||||
BrowserAccessControlDialog.cpp
|
BrowserAccessControlDialog.cpp
|
||||||
@ -33,5 +32,5 @@ if(WITH_XC_BROWSER)
|
|||||||
Variant.cpp)
|
Variant.cpp)
|
||||||
|
|
||||||
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
|
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
|
||||||
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium)
|
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE})
|
||||||
endif()
|
endif()
|
||||||
|
@ -84,7 +84,7 @@ int Add::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli add");
|
errorTextStream << parser.helpText().replace("[options]", "add [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ target_link_libraries(keepassxc-cli
|
|||||||
keepassx_core
|
keepassx_core
|
||||||
Qt5::Core
|
Qt5::Core
|
||||||
${GCRYPT_LIBRARIES}
|
${GCRYPT_LIBRARIES}
|
||||||
|
${sodium_LIBRARY_RELEASE}
|
||||||
${ARGON2_LIBRARIES}
|
${ARGON2_LIBRARIES}
|
||||||
${GPGERROR_LIBRARIES}
|
${GPGERROR_LIBRARIES}
|
||||||
${ZLIB_LIBRARIES}
|
${ZLIB_LIBRARIES}
|
||||||
|
@ -63,7 +63,7 @@ int Clip::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() != 2 && args.size() != 3) {
|
if (args.size() != 2 && args.size() != 3) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli clip");
|
errorTextStream << parser.helpText().replace("[options]", "clip [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ int Create::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() < 1) {
|
if (args.size() < 1) {
|
||||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli create");
|
out << parser.helpText().replace("[options]", "create [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ int Diceware::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (!args.isEmpty()) {
|
if (!args.isEmpty()) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
errorTextStream << parser.helpText().replace("[options]", "diceware [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ int Diceware::execute(const QStringList& arguments)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!dicewareGenerator.isValid()) {
|
if (!dicewareGenerator.isValid()) {
|
||||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
outputTextStream << parser.helpText().replace("[options]", "diceware [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ int Edit::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit");
|
errorTextStream << parser.helpText().replace("[options]", "edit [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ int Estimate::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() > 1) {
|
if (args.size() > 1) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate");
|
errorTextStream << parser.helpText().replace("[options]", "estimate [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ int Extract::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() != 1) {
|
if (args.size() != 1) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli extract");
|
errorTextStream << parser.helpText().replace("[options]", "extract [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ int Generate::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (!args.isEmpty()) {
|
if (!args.isEmpty()) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
errorTextStream << parser.helpText().replace("[options]", "generate [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ int Generate::execute(const QStringList& arguments)
|
|||||||
passwordGenerator.setExcludedChars(parser.value(exclude));
|
passwordGenerator.setExcludedChars(parser.value(exclude));
|
||||||
|
|
||||||
if (!passwordGenerator.isValid()) {
|
if (!passwordGenerator.isValid()) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
errorTextStream << parser.helpText().replace("[options]", "generate [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ int List::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() != 1 && args.size() != 2) {
|
if (args.size() != 1 && args.size() != 2) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli ls");
|
errorTextStream << parser.helpText().replace("[options]", "ls [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ int Locate::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli locate");
|
errorTextStream << parser.helpText().replace("[options]", "locate [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ int Merge::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli merge");
|
errorTextStream << parser.helpText().replace("[options]", "merge [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ int Remove::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm");
|
errorTextStream << parser.helpText().replace("[options]", "rm [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ int Show::execute(const QStringList& arguments)
|
|||||||
|
|
||||||
const QStringList args = parser.positionalArguments();
|
const QStringList args = parser.positionalArguments();
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli show");
|
errorTextStream << parser.helpText().replace("[options]", "show [options]");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,9 @@
|
|||||||
|
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
#include <QTextCodec>
|
#include <QTextCodec>
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
TextStream::TextStream()
|
TextStream::TextStream()
|
||||||
{
|
{
|
||||||
@ -59,12 +62,26 @@ void TextStream::detectCodec()
|
|||||||
{
|
{
|
||||||
QString codecName = "UTF-8";
|
QString codecName = "UTF-8";
|
||||||
auto env = QProcessEnvironment::systemEnvironment();
|
auto env = QProcessEnvironment::systemEnvironment();
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
if (!env.contains("SHELL")) {
|
WINBOOL success = false;
|
||||||
// native shell (no Msys or cygwin)
|
#ifdef CP_UTF8
|
||||||
|
success = SetConsoleOutputCP(CP_UTF8);
|
||||||
|
#endif
|
||||||
|
if (!success && !env.contains("SHELL")) {
|
||||||
|
// Fall back to cp850 if this is Windows without CP_UTF8 and we
|
||||||
|
// are running in a native shell (i.e., no Msys or Cygwin).
|
||||||
codecName = "Windows-850";
|
codecName = "Windows-850";
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
if (env.contains("LANG") && !env.value("LANG").isEmpty() && env.value("LANG") != "C") {
|
||||||
|
// Only override codec if LANG is set, otherwise Qt will assume
|
||||||
|
// US-ASCII, which is almost always wrong and results in
|
||||||
|
// Unicode passwords being displayed as question marks.
|
||||||
|
codecName = QTextCodec::codecForLocale()->name();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
codecName = env.value("ENCODING_OVERRIDE", codecName);
|
codecName = env.value("ENCODING_OVERRIDE", codecName);
|
||||||
auto* codec = QTextCodec::codecForName(codecName.toLatin1());
|
auto* codec = QTextCodec::codecForName(codecName.toLatin1());
|
||||||
if (codec) {
|
if (codec) {
|
||||||
|
89
src/core/Alloc.cpp
Normal file
89
src/core/Alloc.cpp
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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 <QtGlobal>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <sodium.h>
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
#include <malloc/malloc.h>
|
||||||
|
#else
|
||||||
|
#include <malloc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(NDEBUG) && !defined(__cpp_sized_deallocation)
|
||||||
|
#warning "KeePassXC is being compiled without sized deallocation support. Deletes may be slow."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom sized delete operator which securely zeroes out allocated
|
||||||
|
* memory before freeing it (requires C++14 sized deallocation support).
|
||||||
|
*/
|
||||||
|
void operator delete(void* ptr, std::size_t size) noexcept
|
||||||
|
{
|
||||||
|
if (!ptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sodium_memzero(ptr, size);
|
||||||
|
std::free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator delete[](void* ptr, std::size_t size) noexcept
|
||||||
|
{
|
||||||
|
::operator delete(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom delete operator which securely zeroes out
|
||||||
|
* allocated memory before freeing it.
|
||||||
|
*/
|
||||||
|
void operator delete(void* ptr) noexcept
|
||||||
|
{
|
||||||
|
if (!ptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
::operator delete(ptr, _msize(ptr));
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
::operator delete(ptr, malloc_size(ptr));
|
||||||
|
#elif defined(Q_OS_UNIX)
|
||||||
|
::operator delete(ptr, malloc_usable_size(ptr));
|
||||||
|
#else
|
||||||
|
// whatever OS this is, give up and simply free stuff
|
||||||
|
std::free(ptr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator delete[](void* ptr) noexcept
|
||||||
|
{
|
||||||
|
::operator delete(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom insecure delete operator that does not zero out memory before
|
||||||
|
* freeing a buffer. Can be used for better performance.
|
||||||
|
*/
|
||||||
|
void operator delete(void* ptr, bool) noexcept
|
||||||
|
{
|
||||||
|
std::free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator delete[](void* ptr, bool) noexcept
|
||||||
|
{
|
||||||
|
::operator delete(ptr, false);
|
||||||
|
}
|
@ -85,6 +85,12 @@ namespace Bootstrap
|
|||||||
bootstrap();
|
bootstrap();
|
||||||
MessageBox::initializeButtonDefs();
|
MessageBox::initializeButtonDefs();
|
||||||
|
|
||||||
|
#ifdef KEEPASSXC_DIST_SNAP
|
||||||
|
// snap: force fallback theme to avoid using system theme (gtk integration)
|
||||||
|
// with missing actions just like on Windows and macOS
|
||||||
|
QIcon::setThemeSearchPaths(QStringList() << ":/icons");
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
// Don't show menu icons on OSX
|
// Don't show menu icons on OSX
|
||||||
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
|
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||||
|
@ -16,9 +16,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "CustomData.h"
|
#include "CustomData.h"
|
||||||
|
#include "Clock.h"
|
||||||
|
|
||||||
#include "core/Global.h"
|
#include "core/Global.h"
|
||||||
|
|
||||||
|
const QString CustomData::LastModified = "_LAST_MODIFIED";
|
||||||
|
|
||||||
CustomData::CustomData(QObject* parent)
|
CustomData::CustomData(QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
@ -60,6 +63,7 @@ void CustomData::set(const QString& key, const QString& value)
|
|||||||
|
|
||||||
if (addAttribute || changeValue) {
|
if (addAttribute || changeValue) {
|
||||||
m_data.insert(key, value);
|
m_data.insert(key, value);
|
||||||
|
updateLastModified();
|
||||||
emit customDataModified();
|
emit customDataModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +78,7 @@ void CustomData::remove(const QString& key)
|
|||||||
|
|
||||||
m_data.remove(key);
|
m_data.remove(key);
|
||||||
|
|
||||||
|
updateLastModified();
|
||||||
emit removed(key);
|
emit removed(key);
|
||||||
emit customDataModified();
|
emit customDataModified();
|
||||||
}
|
}
|
||||||
@ -94,6 +99,7 @@ void CustomData::rename(const QString& oldKey, const QString& newKey)
|
|||||||
m_data.remove(oldKey);
|
m_data.remove(oldKey);
|
||||||
m_data.insert(newKey, data);
|
m_data.insert(newKey, data);
|
||||||
|
|
||||||
|
updateLastModified();
|
||||||
emit customDataModified();
|
emit customDataModified();
|
||||||
emit renamed(oldKey, newKey);
|
emit renamed(oldKey, newKey);
|
||||||
}
|
}
|
||||||
@ -108,9 +114,19 @@ void CustomData::copyDataFrom(const CustomData* other)
|
|||||||
|
|
||||||
m_data = other->m_data;
|
m_data = other->m_data;
|
||||||
|
|
||||||
|
updateLastModified();
|
||||||
emit reset();
|
emit reset();
|
||||||
emit customDataModified();
|
emit customDataModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDateTime CustomData::getLastModified() const
|
||||||
|
{
|
||||||
|
if (m_data.contains(LastModified)) {
|
||||||
|
return Clock::parse(m_data.value(LastModified));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool CustomData::operator==(const CustomData& other) const
|
bool CustomData::operator==(const CustomData& other) const
|
||||||
{
|
{
|
||||||
return (m_data == other.m_data);
|
return (m_data == other.m_data);
|
||||||
@ -152,3 +168,13 @@ int CustomData::dataSize() const
|
|||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CustomData::updateLastModified()
|
||||||
|
{
|
||||||
|
if (m_data.size() == 1 && m_data.contains(LastModified)) {
|
||||||
|
m_data.remove(LastModified);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_data.insert(LastModified, Clock::currentDateTimeUtc().toString());
|
||||||
|
}
|
||||||
|
@ -42,9 +42,12 @@ public:
|
|||||||
int size() const;
|
int size() const;
|
||||||
int dataSize() const;
|
int dataSize() const;
|
||||||
void copyDataFrom(const CustomData* other);
|
void copyDataFrom(const CustomData* other);
|
||||||
|
QDateTime getLastModified() const;
|
||||||
bool operator==(const CustomData& other) const;
|
bool operator==(const CustomData& other) const;
|
||||||
bool operator!=(const CustomData& other) const;
|
bool operator!=(const CustomData& other) const;
|
||||||
|
|
||||||
|
static const QString LastModified;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void customDataModified();
|
void customDataModified();
|
||||||
void aboutToBeAdded(const QString& key);
|
void aboutToBeAdded(const QString& key);
|
||||||
@ -55,6 +58,10 @@ signals:
|
|||||||
void renamed(const QString& oldKey, const QString& newKey);
|
void renamed(const QString& oldKey, const QString& newKey);
|
||||||
void aboutToBeReset();
|
void aboutToBeReset();
|
||||||
void reset();
|
void reset();
|
||||||
|
void lastModified();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateLastModified();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHash<QString, QString> m_data;
|
QHash<QString, QString> m_data;
|
||||||
|
@ -609,9 +609,6 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
|
|||||||
// TODO HNH: missing handling of recycle bin, names, templates for groups and entries,
|
// TODO HNH: missing handling of recycle bin, names, templates for groups and entries,
|
||||||
// public data (entries of newer dict override keys of older dict - ignoring
|
// public data (entries of newer dict override keys of older dict - ignoring
|
||||||
// their own age - it is enough if one entry of the whole dict is newer) => possible lost update
|
// their own age - it is enough if one entry of the whole dict is newer) => possible lost update
|
||||||
// TODO HNH: CustomData is merged with entries of the new customData overwrite entries
|
|
||||||
// of the older CustomData - the dict with the newest entry is considered
|
|
||||||
// newer regardless of the age of the other entries => possible lost update
|
|
||||||
ChangeList changes;
|
ChangeList changes;
|
||||||
auto* sourceMetadata = context.m_sourceDb->metadata();
|
auto* sourceMetadata = context.m_sourceDb->metadata();
|
||||||
auto* targetMetadata = context.m_targetDb->metadata();
|
auto* targetMetadata = context.m_targetDb->metadata();
|
||||||
@ -624,5 +621,32 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
|
|||||||
changes << tr("Adding missing icon %1").arg(QString::fromLatin1(customIconId.toRfc4122().toHex()));
|
changes << tr("Adding missing icon %1").arg(QString::fromLatin1(customIconId.toRfc4122().toHex()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge Custom Data if source is newer
|
||||||
|
const auto targetCustomDataModificationTime = sourceMetadata->customData()->getLastModified();
|
||||||
|
const auto sourceCustomDataModificationTime = targetMetadata->customData()->getLastModified();
|
||||||
|
if (!targetMetadata->customData()->contains(CustomData::LastModified) ||
|
||||||
|
(targetCustomDataModificationTime.isValid() && sourceCustomDataModificationTime.isValid() &&
|
||||||
|
targetCustomDataModificationTime > sourceCustomDataModificationTime)) {
|
||||||
|
const auto sourceCustomDataKeys = sourceMetadata->customData()->keys();
|
||||||
|
const auto targetCustomDataKeys = targetMetadata->customData()->keys();
|
||||||
|
|
||||||
|
// Check missing keys from source. Remove those from target
|
||||||
|
for (const auto& key : targetCustomDataKeys) {
|
||||||
|
if (!sourceMetadata->customData()->contains(key)) {
|
||||||
|
auto value = targetMetadata->customData()->value(key);
|
||||||
|
targetMetadata->customData()->remove(key);
|
||||||
|
changes << tr("Removed custom data %1 [%2]").arg(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer new/existing keys
|
||||||
|
for (const auto& key : sourceCustomDataKeys) {
|
||||||
|
auto value = sourceMetadata->customData()->value(key);
|
||||||
|
targetMetadata->customData()->set(key, value);
|
||||||
|
changes << tr("Adding custom data %1 [%2]").arg(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ QPixmap Metadata::customIconScaledPixmap(const QUuid& uuid) const
|
|||||||
QPixmapCache::Key& cacheKey = m_customIconScaledCacheKeys[uuid];
|
QPixmapCache::Key& cacheKey = m_customIconScaledCacheKeys[uuid];
|
||||||
|
|
||||||
if (!QPixmapCache::find(cacheKey, &pixmap)) {
|
if (!QPixmapCache::find(cacheKey, &pixmap)) {
|
||||||
QImage image = m_customIcons.value(uuid).scaled(16, 16, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
QImage image = m_customIcons.value(uuid).scaled(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
pixmap = QPixmap::fromImage(image);
|
pixmap = QPixmap::fromImage(image);
|
||||||
cacheKey = QPixmapCache::insert(pixmap);
|
cacheKey = QPixmapCache::insert(pixmap);
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,14 @@
|
|||||||
*/
|
*/
|
||||||
void Translator::installTranslators()
|
void Translator::installTranslators()
|
||||||
{
|
{
|
||||||
|
QLocale locale;
|
||||||
QString language = config()->get("GUI/Language").toString();
|
QString language = config()->get("GUI/Language").toString();
|
||||||
if (language == "system" || language.isEmpty()) {
|
if (!language.isEmpty() && language != "system") {
|
||||||
language = QLocale::system().name();
|
|
||||||
}
|
|
||||||
if (language == "en") {
|
|
||||||
// use actual English translation instead of the English locale source language
|
// use actual English translation instead of the English locale source language
|
||||||
language = "en_US";
|
if (language == "en") {
|
||||||
|
language = "en_US";
|
||||||
|
}
|
||||||
|
locale = QLocale(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QStringList paths = {
|
const QStringList paths = {
|
||||||
@ -51,11 +52,12 @@ void Translator::installTranslators()
|
|||||||
|
|
||||||
bool translationsLoaded = false;
|
bool translationsLoaded = false;
|
||||||
for (const QString& path : paths) {
|
for (const QString& path : paths) {
|
||||||
translationsLoaded |= installTranslator(language, path) || installTranslator("en_US", path);
|
translationsLoaded |= installTranslator(locale, path) || installTranslator(QLocale("en_US"), path);
|
||||||
if (!installQtTranslator(language, path)) {
|
if (!installQtTranslator(language, path)) {
|
||||||
installQtTranslator("en", path);
|
installQtTranslator(QLocale("en"), path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!translationsLoaded) {
|
if (!translationsLoaded) {
|
||||||
// couldn't load configured language or fallback
|
// couldn't load configured language or fallback
|
||||||
qWarning("Couldn't load translations.");
|
qWarning("Couldn't load translations.");
|
||||||
@ -114,10 +116,10 @@ QList<QPair<QString, QString>> Translator::availableLanguages()
|
|||||||
* @param path local search path
|
* @param path local search path
|
||||||
* @return true on success
|
* @return true on success
|
||||||
*/
|
*/
|
||||||
bool Translator::installTranslator(const QString& language, const QString& path)
|
bool Translator::installTranslator(const QLocale& locale, const QString& path)
|
||||||
{
|
{
|
||||||
QScopedPointer<QTranslator> translator(new QTranslator(qApp));
|
QScopedPointer<QTranslator> translator(new QTranslator(qApp));
|
||||||
if (translator->load(QString("keepassx_%1").arg(language), path)) {
|
if (translator->load(locale, "keepassx_", "", path)) {
|
||||||
return QCoreApplication::installTranslator(translator.take());
|
return QCoreApplication::installTranslator(translator.take());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -131,13 +133,12 @@ bool Translator::installTranslator(const QString& language, const QString& path)
|
|||||||
* @param path local search path
|
* @param path local search path
|
||||||
* @return true on success
|
* @return true on success
|
||||||
*/
|
*/
|
||||||
bool Translator::installQtTranslator(const QString& language, const QString& path)
|
bool Translator::installQtTranslator(const QLocale& locale, const QString& path)
|
||||||
{
|
{
|
||||||
QScopedPointer<QTranslator> qtTranslator(new QTranslator(qApp));
|
QScopedPointer<QTranslator> qtTranslator(new QTranslator(qApp));
|
||||||
if (qtTranslator->load(QString("qtbase_%1").arg(language), path)) {
|
if (qtTranslator->load(locale, "qtbase_", "", path)) {
|
||||||
return QCoreApplication::installTranslator(qtTranslator.take());
|
return QCoreApplication::installTranslator(qtTranslator.take());
|
||||||
} else if (qtTranslator->load(QString("qtbase_%1").arg(language),
|
} else if (qtTranslator->load(locale, "qtbase_", "", QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
|
||||||
QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
|
|
||||||
return QCoreApplication::installTranslator(qtTranslator.take());
|
return QCoreApplication::installTranslator(qtTranslator.take());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QLocale>
|
||||||
|
|
||||||
class Translator
|
class Translator
|
||||||
{
|
{
|
||||||
@ -28,8 +29,8 @@ public:
|
|||||||
static QList<QPair<QString, QString>> availableLanguages();
|
static QList<QPair<QString, QString>> availableLanguages();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool installTranslator(const QString& language, const QString& path);
|
static bool installTranslator(const QLocale& locale, const QString& path);
|
||||||
static bool installQtTranslator(const QString& language, const QString& path);
|
static bool installQtTranslator(const QLocale& locale, const QString& path);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_TRANSLATOR_H
|
#endif // KEEPASSX_TRANSLATOR_H
|
||||||
|
@ -64,6 +64,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
|||||||
, m_globalAutoTypeModifiers(Qt::NoModifier)
|
, m_globalAutoTypeModifiers(Qt::NoModifier)
|
||||||
{
|
{
|
||||||
setHeadline(tr("Application Settings"));
|
setHeadline(tr("Application Settings"));
|
||||||
|
showApplyButton(false);
|
||||||
|
|
||||||
m_secUi->setupUi(m_secWidget);
|
m_secUi->setupUi(m_secWidget);
|
||||||
m_generalUi->setupUi(m_generalWidget);
|
m_generalUi->setupUi(m_generalWidget);
|
||||||
@ -75,7 +76,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
connect(this, SIGNAL(accepted()), SLOT(saveSettings()));
|
connect(this, SIGNAL(accepted()), SLOT(saveSettings()));
|
||||||
connect(this, SIGNAL(apply()), SLOT(saveSettings()));
|
|
||||||
connect(this, SIGNAL(rejected()), SLOT(reject()));
|
connect(this, SIGNAL(rejected()), SLOT(reject()));
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
@ -212,7 +212,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="fallbackToSearch">
|
<widget class="QCheckBox" name="fallbackToSearch">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Use DuckDuckGo as fallback for downloading website icons</string>
|
<string>Use DuckDuckGo service to download website icons</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -63,6 +63,10 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
|||||||
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
|
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
|
||||||
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
|
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
connect(macUtils(), SIGNAL(lockDatabases()), SLOT(lockDatabases()));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseTabWidget::~DatabaseTabWidget()
|
DatabaseTabWidget::~DatabaseTabWidget()
|
||||||
|
@ -1158,9 +1158,10 @@ void DatabaseWidget::onDatabaseModified()
|
|||||||
{
|
{
|
||||||
if (!m_blockAutoSave && config()->get("AutoSaveAfterEveryChange").toBool()) {
|
if (!m_blockAutoSave && config()->get("AutoSaveAfterEveryChange").toBool()) {
|
||||||
save();
|
save();
|
||||||
|
} else {
|
||||||
|
// Only block once, then reset
|
||||||
|
m_blockAutoSave = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_blockAutoSave = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DatabaseWidget::getCurrentSearch()
|
QString DatabaseWidget::getCurrentSearch()
|
||||||
@ -1258,11 +1259,13 @@ bool DatabaseWidget::lock()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_db->isModified()) {
|
if (m_db->isModified()) {
|
||||||
|
bool saved = false;
|
||||||
|
// Attempt to save on exit, but don't block locking if it fails
|
||||||
if (config()->get("AutoSaveOnExit").toBool()) {
|
if (config()->get("AutoSaveOnExit").toBool()) {
|
||||||
if (!save()) {
|
saved = save();
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
} else {
|
if (!saved) {
|
||||||
QString msg;
|
QString msg;
|
||||||
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
|
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
|
||||||
msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped());
|
msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped());
|
||||||
@ -1521,11 +1524,14 @@ bool DatabaseWidget::save()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read-only and new databases ask for filename
|
||||||
if (m_db->isReadOnly() || m_db->filePath().isEmpty()) {
|
if (m_db->isReadOnly() || m_db->filePath().isEmpty()) {
|
||||||
return saveAs();
|
return saveAs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent recursions and infinite save loops
|
||||||
blockAutoReload(true);
|
blockAutoReload(true);
|
||||||
|
m_blockAutoSave = true;
|
||||||
++m_saveAttempts;
|
++m_saveAttempts;
|
||||||
|
|
||||||
// TODO: Make this async, but lock out the database widget to prevent re-entrance
|
// TODO: Make this async, but lock out the database widget to prevent re-entrance
|
||||||
@ -1536,6 +1542,7 @@ bool DatabaseWidget::save()
|
|||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
m_saveAttempts = 0;
|
m_saveAttempts = 0;
|
||||||
|
m_blockAutoSave = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,8 @@ void DialogyWidget::keyPressEvent(QKeyEvent* e)
|
|||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
|
if (!e->modifiers() || e->modifiers() == Qt::ControlModifier
|
||||||
|
|| (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
|
||||||
switch (e->key()) {
|
switch (e->key()) {
|
||||||
case Qt::Key_Enter:
|
case Qt::Key_Enter:
|
||||||
case Qt::Key_Return:
|
case Qt::Key_Return:
|
||||||
|
@ -197,8 +197,6 @@ void EditWidgetIcons::downloadFavicon()
|
|||||||
|
|
||||||
QString fullyQualifiedDomain = m_url.host();
|
QString fullyQualifiedDomain = m_url.host();
|
||||||
|
|
||||||
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico"));
|
|
||||||
|
|
||||||
// Determine if host portion of URL is an IP address by resolving it and
|
// Determine if host portion of URL is an IP address by resolving it and
|
||||||
// searching for a match with the returned address(es).
|
// searching for a match with the returned address(es).
|
||||||
bool hostIsIp = false;
|
bool hostIsIp = false;
|
||||||
@ -209,32 +207,35 @@ void EditWidgetIcons::downloadFavicon()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the second-level domain, if available
|
||||||
|
QString secondLevelDomain;
|
||||||
if (!hostIsIp) {
|
if (!hostIsIp) {
|
||||||
QString secondLevelDomain = getSecondLevelDomain(m_url);
|
secondLevelDomain = getSecondLevelDomain(m_url);
|
||||||
|
|
||||||
// Attempt to simply load the favicon.ico file
|
|
||||||
if (fullyQualifiedDomain != secondLevelDomain) {
|
|
||||||
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to use alternative fallback URL, if enabled
|
// Start with the "fallback" url (if enabled) to try to get the best favicon
|
||||||
if (config()->get("security/IconDownloadFallback", false).toBool()) {
|
if (config()->get("security/IconDownloadFallback", false).toBool()) {
|
||||||
QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com");
|
QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com");
|
||||||
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico");
|
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico");
|
||||||
|
|
||||||
m_urlsToTry.append(fallbackUrl);
|
m_urlsToTry.append(fallbackUrl);
|
||||||
|
|
||||||
if (!hostIsIp) {
|
// Also try a direct pull of the second-level domain (if possible)
|
||||||
QString secondLevelDomain = getSecondLevelDomain(m_url);
|
if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) {
|
||||||
|
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico");
|
||||||
if (fullyQualifiedDomain != secondLevelDomain) {
|
m_urlsToTry.append(fallbackUrl);
|
||||||
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico");
|
|
||||||
m_urlsToTry.append(fallbackUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a direct pull of the website's own favicon.ico file
|
||||||
|
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico"));
|
||||||
|
|
||||||
|
// Also try a direct pull of the second-level domain (if possible)
|
||||||
|
if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) {
|
||||||
|
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the first URL to start the download process
|
||||||
|
// If a favicon is not found, the next URL will be tried
|
||||||
startFetchFavicon(m_urlsToTry.takeFirst());
|
startFetchFavicon(m_urlsToTry.takeFirst());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -277,7 +278,7 @@ void EditWidgetIcons::fetchFinished()
|
|||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
if (!addCustomIcon(image)) {
|
if (!addCustomIcon(image)) {
|
||||||
emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information);
|
emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information);
|
||||||
} else if (!this->isVisible()) {
|
} else if (!isVisible()) {
|
||||||
// Show confirmation message if triggered from Entry tab download button
|
// Show confirmation message if triggered from Entry tab download button
|
||||||
emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive);
|
emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive);
|
||||||
}
|
}
|
||||||
@ -289,7 +290,7 @@ void EditWidgetIcons::fetchFinished()
|
|||||||
if (!fallbackEnabled) {
|
if (!fallbackEnabled) {
|
||||||
emit messageEditEntry(
|
emit messageEditEntry(
|
||||||
tr("Unable to fetch favicon.") + "\n"
|
tr("Unable to fetch favicon.") + "\n"
|
||||||
+ tr("Hint: You can enable DuckDuckGo as a fallback under Tools>Settings>Security"),
|
+ tr("You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security"),
|
||||||
MessageWidget::Error);
|
MessageWidget::Error);
|
||||||
} else {
|
} else {
|
||||||
emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error);
|
emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error);
|
||||||
|
@ -41,6 +41,10 @@
|
|||||||
#include "keys/FileKey.h"
|
#include "keys/FileKey.h"
|
||||||
#include "keys/PasswordKey.h"
|
#include "keys/PasswordKey.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
#include "macutils/MacUtils.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_XC_UPDATECHECK
|
#ifdef WITH_XC_UPDATECHECK
|
||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
#include "gui/UpdateCheckDialog.h"
|
#include "gui/UpdateCheckDialog.h"
|
||||||
@ -135,6 +139,7 @@ MainWindow::MainWindow()
|
|||||||
, m_trayIcon(nullptr)
|
, m_trayIcon(nullptr)
|
||||||
, m_appExitCalled(false)
|
, m_appExitCalled(false)
|
||||||
, m_appExiting(false)
|
, m_appExiting(false)
|
||||||
|
, m_lastFocusOutTime(0)
|
||||||
{
|
{
|
||||||
g_MainWindow = this;
|
g_MainWindow = this;
|
||||||
|
|
||||||
@ -370,6 +375,9 @@ MainWindow::MainWindow()
|
|||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
setUnifiedTitleAndToolBarOnMac(true);
|
setUnifiedTitleAndToolBarOnMac(true);
|
||||||
|
if (macUtils()->isDarkMode()) {
|
||||||
|
setStyleSheet("QToolButton {color:white;}");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_XC_UPDATECHECK
|
#ifdef WITH_XC_UPDATECHECK
|
||||||
@ -396,6 +404,12 @@ MainWindow::MainWindow()
|
|||||||
connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock()));
|
connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock()));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Tray Icon setup
|
||||||
|
connect(Application::instance(), SIGNAL(focusWindowChanged(QWindow*)), SLOT(focusWindowChanged(QWindow*)));
|
||||||
|
m_trayIconTriggerReason = QSystemTrayIcon::Unknown;
|
||||||
|
m_trayIconTriggerTimer.setSingleShot(true);
|
||||||
|
connect(&m_trayIconTriggerTimer, SIGNAL(timeout()), SLOT(processTrayIconTrigger()));
|
||||||
|
|
||||||
updateTrayIcon();
|
updateTrayIcon();
|
||||||
|
|
||||||
if (config()->hasAccessError()) {
|
if (config()->hasAccessError()) {
|
||||||
@ -912,7 +926,7 @@ bool MainWindow::saveLastDatabases()
|
|||||||
}
|
}
|
||||||
|
|
||||||
QStringList openDatabases;
|
QStringList openDatabases;
|
||||||
for (int i=0; i < m_ui->tabWidget->count(); ++i) {
|
for (int i = 0; i < m_ui->tabWidget->count(); ++i) {
|
||||||
auto dbWidget = m_ui->tabWidget->databaseWidgetFromIndex(i);
|
auto dbWidget = m_ui->tabWidget->databaseWidgetFromIndex(i);
|
||||||
openDatabases.append(dbWidget->database()->filePath());
|
openDatabases.append(dbWidget->database()->filePath());
|
||||||
}
|
}
|
||||||
@ -1031,10 +1045,38 @@ void MainWindow::applySettingsChanges()
|
|||||||
updateTrayIcon();
|
updateTrayIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::focusWindowChanged(QWindow* focusWindow)
|
||||||
|
{
|
||||||
|
if (focusWindow != windowHandle()) {
|
||||||
|
m_lastFocusOutTime = Clock::currentSecondsSinceEpoch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
|
void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
|
||||||
{
|
{
|
||||||
if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::MiddleClick) {
|
if (!m_trayIconTriggerTimer.isActive()) {
|
||||||
|
m_trayIconTriggerTimer.start(150);
|
||||||
|
}
|
||||||
|
// Overcome Qt bug https://bugreports.qt.io/browse/QTBUG-69698
|
||||||
|
// Store last issued tray icon activation reason to properly
|
||||||
|
// capture doubleclick events
|
||||||
|
m_trayIconTriggerReason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::processTrayIconTrigger()
|
||||||
|
{
|
||||||
|
if (m_trayIconTriggerReason == QSystemTrayIcon::DoubleClick) {
|
||||||
|
// Always toggle window on double click
|
||||||
toggleWindow();
|
toggleWindow();
|
||||||
|
} else if (m_trayIconTriggerReason == QSystemTrayIcon::Trigger
|
||||||
|
|| m_trayIconTriggerReason == QSystemTrayIcon::MiddleClick) {
|
||||||
|
// On single/middle click focus the window if it is not hidden
|
||||||
|
// and did not have focus less than a second ago, otherwise toggle
|
||||||
|
if (isHidden() || (Clock::currentSecondsSinceEpoch() - m_lastFocusOutTime) <= 1) {
|
||||||
|
toggleWindow();
|
||||||
|
} else {
|
||||||
|
bringToFront();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +85,7 @@ private slots:
|
|||||||
void showAboutDialog();
|
void showAboutDialog();
|
||||||
void showUpdateCheckStartup();
|
void showUpdateCheckStartup();
|
||||||
void showUpdateCheckDialog();
|
void showUpdateCheckDialog();
|
||||||
|
void focusWindowChanged(QWindow* focusWindow);
|
||||||
void hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested);
|
void hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested);
|
||||||
void openDonateUrl();
|
void openDonateUrl();
|
||||||
void openBugReportUrl();
|
void openBugReportUrl();
|
||||||
@ -107,6 +108,7 @@ private slots:
|
|||||||
void showGroupContextMenu(const QPoint& globalPos);
|
void showGroupContextMenu(const QPoint& globalPos);
|
||||||
void applySettingsChanges();
|
void applySettingsChanges();
|
||||||
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
|
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
|
||||||
|
void processTrayIconTrigger();
|
||||||
void lockDatabasesAfterInactivity();
|
void lockDatabasesAfterInactivity();
|
||||||
void forgetTouchIDAfterInactivity();
|
void forgetTouchIDAfterInactivity();
|
||||||
void handleScreenLock();
|
void handleScreenLock();
|
||||||
@ -146,6 +148,9 @@ private:
|
|||||||
|
|
||||||
bool m_appExitCalled;
|
bool m_appExitCalled;
|
||||||
bool m_appExiting;
|
bool m_appExiting;
|
||||||
|
uint m_lastFocusOutTime;
|
||||||
|
QTimer m_trayIconTriggerTimer;
|
||||||
|
QSystemTrayIcon::ActivationReason m_trayIconTriggerReason;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,11 +152,6 @@ void EditEntryWidget::setupMain()
|
|||||||
m_mainUi->expirePresets->setMenu(createPresetsMenu());
|
m_mainUi->expirePresets->setMenu(createPresetsMenu());
|
||||||
connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
|
connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
|
||||||
|
|
||||||
QAction* action = new QAction(this);
|
|
||||||
action->setShortcut(Qt::CTRL | Qt::Key_Return);
|
|
||||||
connect(action, SIGNAL(triggered()), this, SLOT(commitEntry()));
|
|
||||||
this->addAction(action);
|
|
||||||
|
|
||||||
m_mainUi->passwordGenerator->hide();
|
m_mainUi->passwordGenerator->hide();
|
||||||
m_mainUi->passwordGenerator->reset();
|
m_mainUi->passwordGenerator->reset();
|
||||||
}
|
}
|
||||||
@ -285,7 +280,6 @@ void EditEntryWidget::setupEntryUpdate()
|
|||||||
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
|
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
|
||||||
#endif
|
#endif
|
||||||
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_mainUi->notesEnabled, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
|
||||||
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified()));
|
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified()));
|
||||||
connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
|
connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
|
||||||
|
|
||||||
@ -1111,8 +1105,9 @@ void EditEntryWidget::updateCurrentAttribute()
|
|||||||
|
|
||||||
void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
|
void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
|
||||||
{
|
{
|
||||||
// Block signals to prevent extra calls
|
// Block signals to prevent modified being set
|
||||||
m_advancedUi->protectAttributeButton->blockSignals(true);
|
m_advancedUi->protectAttributeButton->blockSignals(true);
|
||||||
|
m_advancedUi->attributesEdit->blockSignals(true);
|
||||||
|
|
||||||
if (index.isValid()) {
|
if (index.isValid()) {
|
||||||
QString key = m_attributesModel->keyByIndex(index);
|
QString key = m_attributesModel->keyByIndex(index);
|
||||||
@ -1143,6 +1138,7 @@ void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_advancedUi->protectAttributeButton->blockSignals(false);
|
m_advancedUi->protectAttributeButton->blockSignals(false);
|
||||||
|
m_advancedUi->attributesEdit->blockSignals(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditEntryWidget::protectCurrentAttribute(bool state)
|
void EditEntryWidget::protectCurrentAttribute(bool state)
|
||||||
|
@ -227,6 +227,9 @@ void EditGroupWidget::cancel()
|
|||||||
tr("Entry has unsaved changes"),
|
tr("Entry has unsaved changes"),
|
||||||
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
|
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
|
||||||
MessageBox::Cancel);
|
MessageBox::Cancel);
|
||||||
|
if (result == MessageBox::Cancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (result == MessageBox::Save) {
|
if (result == MessageBox::Save) {
|
||||||
apply();
|
apply();
|
||||||
setModified(false);
|
setModified(false);
|
||||||
|
@ -19,14 +19,15 @@
|
|||||||
#ifndef KEEPASSX_APPKIT_H
|
#ifndef KEEPASSX_APPKIT_H
|
||||||
#define KEEPASSX_APPKIT_H
|
#define KEEPASSX_APPKIT_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
extern "C" {
|
class AppKit : public QObject
|
||||||
|
|
||||||
class AppKit
|
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AppKit();
|
AppKit(QObject* parent = nullptr);
|
||||||
~AppKit();
|
~AppKit();
|
||||||
|
|
||||||
pid_t lastActiveProcessId();
|
pid_t lastActiveProcessId();
|
||||||
@ -37,10 +38,11 @@ public:
|
|||||||
bool isHidden(pid_t pid);
|
bool isHidden(pid_t pid);
|
||||||
bool isDarkMode();
|
bool isDarkMode();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void lockDatabases();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void *self;
|
void *self;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // extern "C"
|
|
||||||
|
|
||||||
#endif // KEEPASSX_APPKIT_H
|
#endif // KEEPASSX_APPKIT_H
|
||||||
|
@ -22,6 +22,10 @@
|
|||||||
#import <AppKit/NSRunningApplication.h>
|
#import <AppKit/NSRunningApplication.h>
|
||||||
|
|
||||||
@interface AppKitImpl : NSObject
|
@interface AppKitImpl : NSObject
|
||||||
|
{
|
||||||
|
AppKit *m_appkit;
|
||||||
|
}
|
||||||
|
- (id) initWithObject:(AppKit *)appkit;
|
||||||
|
|
||||||
@property (strong) NSRunningApplication *lastActiveApplication;
|
@property (strong) NSRunningApplication *lastActiveApplication;
|
||||||
|
|
||||||
@ -31,5 +35,6 @@
|
|||||||
- (bool) hideProcess:(pid_t) pid;
|
- (bool) hideProcess:(pid_t) pid;
|
||||||
- (bool) isHidden:(pid_t) pid;
|
- (bool) isHidden:(pid_t) pid;
|
||||||
- (bool) isDarkMode;
|
- (bool) isDarkMode;
|
||||||
|
- (void) userSwitchHandler:(NSNotification*) notification;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -22,19 +22,22 @@
|
|||||||
|
|
||||||
@implementation AppKitImpl
|
@implementation AppKitImpl
|
||||||
|
|
||||||
AppKit::AppKit()
|
- (id) initWithObject:(AppKit *)appkit
|
||||||
{
|
{
|
||||||
self = [[AppKitImpl alloc] init];
|
self = [super init];
|
||||||
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
|
if (self) {
|
||||||
|
m_appkit = appkit;
|
||||||
|
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
|
||||||
selector:@selector(didDeactivateApplicationObserver:)
|
selector:@selector(didDeactivateApplicationObserver:)
|
||||||
name:NSWorkspaceDidDeactivateApplicationNotification
|
name:NSWorkspaceDidDeactivateApplicationNotification
|
||||||
object:nil];
|
object:nil];
|
||||||
}
|
|
||||||
|
|
||||||
AppKit::~AppKit()
|
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
|
||||||
{
|
selector:@selector(userSwitchHandler:)
|
||||||
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)];
|
name:NSWorkspaceSessionDidResignActiveNotification
|
||||||
[static_cast<id>(self) dealloc];
|
object:nil];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -104,10 +107,34 @@ AppKit::~AppKit()
|
|||||||
&& NSOrderedSame == [style caseInsensitiveCompare:@"dark"] );
|
&& NSOrderedSame == [style caseInsensitiveCompare:@"dark"] );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Notification for user switch
|
||||||
|
//
|
||||||
|
- (void) userSwitchHandler:(NSNotification*) notification
|
||||||
|
{
|
||||||
|
if ([[notification name] isEqualToString:NSWorkspaceSessionDidResignActiveNotification] && m_appkit)
|
||||||
|
{
|
||||||
|
emit m_appkit->lockDatabases();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
//
|
//
|
||||||
// ------------------------- C++ Trampolines -------------------------
|
// ------------------------- C++ Trampolines -------------------------
|
||||||
//
|
//
|
||||||
|
|
||||||
|
AppKit::AppKit(QObject* parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
self = [[AppKitImpl alloc] initWithObject:this];
|
||||||
|
}
|
||||||
|
|
||||||
|
AppKit::~AppKit()
|
||||||
|
{
|
||||||
|
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)];
|
||||||
|
[static_cast<id>(self) dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
pid_t AppKit::lastActiveProcessId()
|
pid_t AppKit::lastActiveProcessId()
|
||||||
{
|
{
|
||||||
return [static_cast<id>(self) lastActiveApplication].processIdentifier;
|
return [static_cast<id>(self) lastActiveApplication].processIdentifier;
|
||||||
@ -142,5 +169,3 @@ bool AppKit::isDarkMode()
|
|||||||
{
|
{
|
||||||
return [static_cast<id>(self) isDarkMode];
|
return [static_cast<id>(self) isDarkMode];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
|
||||||
|
@ -24,7 +24,7 @@ MacUtils* MacUtils::m_instance = nullptr;
|
|||||||
MacUtils::MacUtils(QObject* parent) : QObject(parent)
|
MacUtils::MacUtils(QObject* parent) : QObject(parent)
|
||||||
, m_appkit(new AppKit())
|
, m_appkit(new AppKit())
|
||||||
{
|
{
|
||||||
|
connect(m_appkit.data(), SIGNAL(lockDatabases()), SIGNAL(lockDatabases()));
|
||||||
}
|
}
|
||||||
|
|
||||||
MacUtils::~MacUtils()
|
MacUtils::~MacUtils()
|
||||||
|
@ -39,14 +39,16 @@ public:
|
|||||||
bool isHidden();
|
bool isHidden();
|
||||||
bool isDarkMode();
|
bool isDarkMode();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void lockDatabases();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit MacUtils(QObject* parent = nullptr);
|
explicit MacUtils(QObject* parent = nullptr);
|
||||||
~MacUtils();
|
~MacUtils();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<AppKit> m_appkit;
|
QScopedPointer<AppKit> m_appkit;
|
||||||
static MacUtils* m_instance;
|
static MacUtils* m_instance;
|
||||||
void* self;
|
|
||||||
|
|
||||||
Q_DISABLE_COPY(MacUtils)
|
Q_DISABLE_COPY(MacUtils)
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
if(WITH_XC_KEESHARE)
|
if(WITH_XC_KEESHARE)
|
||||||
|
set(WITH_XC_KEESHARE_INSECURE ON PARENT_SCOPE)
|
||||||
|
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
set(keeshare_SOURCES
|
set(keeshare_SOURCES
|
||||||
@ -15,9 +17,19 @@ if(WITH_XC_KEESHARE)
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_library(keeshare STATIC ${keeshare_SOURCES})
|
add_library(keeshare STATIC ${keeshare_SOURCES})
|
||||||
if(WITH_XC_KEESHARE_SECURE)
|
target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB})
|
||||||
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB})
|
|
||||||
|
# Try to find libquazip5, if found, enable secure sharing
|
||||||
|
find_package(QuaZip)
|
||||||
|
if(QUAZIP_FOUND)
|
||||||
|
set(WITH_XC_KEESHARE_SECURE ON PARENT_SCOPE)
|
||||||
|
target_include_directories(keeshare SYSTEM PRIVATE ${QUAZIP_INCLUDE_DIR})
|
||||||
|
target_link_libraries(keeshare PRIVATE ${QUAZIP_LIBRARIES})
|
||||||
else()
|
else()
|
||||||
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB})
|
set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE)
|
||||||
|
message(STATUS "KeeShare: Secure container support is DISABLED; quazip library not found")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
else(WITH_XC_KEESHARE)
|
||||||
|
set(WITH_XC_KEESHARE_INSECURE OFF PARENT_SCOPE)
|
||||||
|
set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE)
|
||||||
|
endif(WITH_XC_KEESHARE)
|
||||||
|
@ -46,8 +46,8 @@
|
|||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
|
|
||||||
#if defined(WITH_XC_KEESHARE_SECURE)
|
#if defined(WITH_XC_KEESHARE_SECURE)
|
||||||
#include <quazip5/quazip.h>
|
#include <quazip.h>
|
||||||
#include <quazip5/quazipfile.h>
|
#include <quazipfile.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -18,19 +18,35 @@
|
|||||||
|
|
||||||
#include "FileKey.h"
|
#include "FileKey.h"
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
|
|
||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
#include "crypto/CryptoHash.h"
|
#include "crypto/CryptoHash.h"
|
||||||
#include "crypto/Random.h"
|
#include "crypto/Random.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
#include <gcrypt.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273");
|
QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273");
|
||||||
|
|
||||||
|
constexpr int FileKey::SHA256_SIZE;
|
||||||
|
|
||||||
FileKey::FileKey()
|
FileKey::FileKey()
|
||||||
: Key(UUID)
|
: Key(UUID)
|
||||||
|
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileKey::~FileKey()
|
||||||
|
{
|
||||||
|
if (m_key) {
|
||||||
|
gcry_free(m_key);
|
||||||
|
m_key = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read key file from device while trying to detect its file format.
|
* Read key file from device while trying to detect its file format.
|
||||||
*
|
*
|
||||||
@ -148,7 +164,10 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
|
|||||||
*/
|
*/
|
||||||
QByteArray FileKey::rawKey() const
|
QByteArray FileKey::rawKey() const
|
||||||
{
|
{
|
||||||
return m_key;
|
if (!m_key) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return QByteArray::fromRawData(m_key, SHA256_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,12 +242,15 @@ bool FileKey::loadXml(QIODevice* device)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
if (!xmlReader.error() && correctMeta && !data.isEmpty()) {
|
if (!xmlReader.error() && correctMeta && !data.isEmpty()) {
|
||||||
m_key = data;
|
std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
|
||||||
return true;
|
ok = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
|
||||||
|
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -293,7 +315,8 @@ bool FileKey::loadBinary(QIODevice* device)
|
|||||||
if (!Tools::readAllFromDevice(device, data) || data.size() != 32) {
|
if (!Tools::readAllFromDevice(device, data) || data.size() != 32) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
m_key = data;
|
std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
|
||||||
|
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,12 +344,15 @@ bool FileKey::loadHex(QIODevice* device)
|
|||||||
}
|
}
|
||||||
|
|
||||||
QByteArray key = QByteArray::fromHex(data);
|
QByteArray key = QByteArray::fromHex(data);
|
||||||
|
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
|
||||||
|
|
||||||
if (key.size() != 32) {
|
if (key.size() != 32) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_key = key;
|
std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size()));
|
||||||
|
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +374,9 @@ bool FileKey::loadHashed(QIODevice* device)
|
|||||||
cryptoHash.addData(buffer);
|
cryptoHash.addData(buffer);
|
||||||
} while (!buffer.isEmpty());
|
} while (!buffer.isEmpty());
|
||||||
|
|
||||||
m_key = cryptoHash.result();
|
auto result = cryptoHash.result();
|
||||||
|
std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size()));
|
||||||
|
sodium_memzero(result.data(), static_cast<std::size_t>(result.capacity()));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
FileKey();
|
FileKey();
|
||||||
|
~FileKey() override;
|
||||||
bool load(QIODevice* device);
|
bool load(QIODevice* device);
|
||||||
bool load(const QString& fileName, QString* errorMsg = nullptr);
|
bool load(const QString& fileName, QString* errorMsg = nullptr);
|
||||||
QByteArray rawKey() const override;
|
QByteArray rawKey() const override;
|
||||||
@ -48,6 +49,8 @@ public:
|
|||||||
static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128);
|
static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr int SHA256_SIZE = 32;
|
||||||
|
|
||||||
bool loadXml(QIODevice* device);
|
bool loadXml(QIODevice* device);
|
||||||
bool loadXmlMeta(QXmlStreamReader& xmlReader);
|
bool loadXmlMeta(QXmlStreamReader& xmlReader);
|
||||||
QByteArray loadXmlKey(QXmlStreamReader& xmlReader);
|
QByteArray loadXmlKey(QXmlStreamReader& xmlReader);
|
||||||
@ -55,7 +58,7 @@ private:
|
|||||||
bool loadHex(QIODevice* device);
|
bool loadHex(QIODevice* device);
|
||||||
bool loadHashed(QIODevice* device);
|
bool loadHashed(QIODevice* device);
|
||||||
|
|
||||||
QByteArray m_key;
|
char* m_key = nullptr;
|
||||||
Type m_type = None;
|
Type m_type = None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,35 +16,51 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PasswordKey.h"
|
#include "PasswordKey.h"
|
||||||
|
#include "core/Tools.h"
|
||||||
|
|
||||||
#include "crypto/CryptoHash.h"
|
#include "crypto/CryptoHash.h"
|
||||||
|
#include <gcrypt.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
|
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
|
||||||
|
|
||||||
|
constexpr int PasswordKey::SHA256_SIZE;
|
||||||
|
|
||||||
PasswordKey::PasswordKey()
|
PasswordKey::PasswordKey()
|
||||||
: Key(UUID)
|
: Key(UUID)
|
||||||
|
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordKey::PasswordKey(const QString& password)
|
PasswordKey::PasswordKey(const QString& password)
|
||||||
: Key(UUID)
|
: Key(UUID)
|
||||||
|
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
|
||||||
{
|
{
|
||||||
setPassword(password);
|
setPassword(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PasswordKey::~PasswordKey()
|
||||||
|
{
|
||||||
|
if (m_key) {
|
||||||
|
gcry_free(m_key);
|
||||||
|
m_key = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
|
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
|
||||||
{
|
{
|
||||||
auto result = QSharedPointer<PasswordKey>::create();
|
auto result = QSharedPointer<PasswordKey>::create();
|
||||||
result->m_key = rawKey;
|
std::memcpy(result->m_key, rawKey.data(), std::min(SHA256_SIZE, rawKey.size()));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray PasswordKey::rawKey() const
|
QByteArray PasswordKey::rawKey() const
|
||||||
{
|
{
|
||||||
return m_key;
|
return QByteArray::fromRawData(m_key, SHA256_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasswordKey::setPassword(const QString& password)
|
void PasswordKey::setPassword(const QString& password)
|
||||||
{
|
{
|
||||||
m_key = CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256);
|
std::memcpy(m_key, CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256).data(), SHA256_SIZE);
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,16 @@ public:
|
|||||||
|
|
||||||
PasswordKey();
|
PasswordKey();
|
||||||
explicit PasswordKey(const QString& password);
|
explicit PasswordKey(const QString& password);
|
||||||
|
~PasswordKey() override;
|
||||||
QByteArray rawKey() const override;
|
QByteArray rawKey() const override;
|
||||||
void setPassword(const QString& password);
|
void setPassword(const QString& password);
|
||||||
|
|
||||||
static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey);
|
static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QByteArray m_key;
|
static constexpr int SHA256_SIZE = 32;
|
||||||
|
|
||||||
|
char* m_key = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_PASSWORDKEY_H
|
#endif // KEEPASSX_PASSWORDKEY_H
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
||||||
* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
|
* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
|
||||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -32,6 +32,10 @@
|
|||||||
#include <QXmlStreamReader>
|
#include <QXmlStreamReader>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
#include <gcrypt.h>
|
||||||
|
#include <sodium.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
|
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
|
||||||
|
|
||||||
YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
|
YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
|
||||||
@ -45,9 +49,18 @@ YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
YkChallengeResponseKey::~YkChallengeResponseKey()
|
||||||
|
{
|
||||||
|
if (m_key) {
|
||||||
|
gcry_free(m_key);
|
||||||
|
m_keySize = 0;
|
||||||
|
m_key = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray YkChallengeResponseKey::rawKey() const
|
QByteArray YkChallengeResponseKey::rawKey() const
|
||||||
{
|
{
|
||||||
return m_key;
|
return QByteArray::fromRawData(m_key, static_cast<int>(m_keySize));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,14 +80,22 @@ bool YkChallengeResponseKey::challenge(const QByteArray& challenge, unsigned int
|
|||||||
emit userInteractionRequired();
|
emit userInteractionRequired();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray key;
|
||||||
auto result = AsyncTask::runAndWaitForFuture(
|
auto result = AsyncTask::runAndWaitForFuture(
|
||||||
[this, challenge]() { return YubiKey::instance()->challenge(m_slot, true, challenge, m_key); });
|
[this, challenge, &key]() { return YubiKey::instance()->challenge(m_slot, true, challenge, key); });
|
||||||
|
|
||||||
if (m_blocking) {
|
if (m_blocking) {
|
||||||
emit userConfirmed();
|
emit userConfirmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == YubiKey::SUCCESS) {
|
if (result == YubiKey::SUCCESS) {
|
||||||
|
if (m_key) {
|
||||||
|
gcry_free(m_key);
|
||||||
|
}
|
||||||
|
m_keySize = static_cast<std::size_t>(key.size());
|
||||||
|
m_key = static_cast<char*>(gcry_malloc_secure(m_keySize));
|
||||||
|
std::memcpy(m_key, key.data(), m_keySize);
|
||||||
|
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} while (retries > 0);
|
} while (retries > 0);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -32,6 +32,7 @@ public:
|
|||||||
static QUuid UUID;
|
static QUuid UUID;
|
||||||
|
|
||||||
explicit YkChallengeResponseKey(int slot = -1, bool blocking = false);
|
explicit YkChallengeResponseKey(int slot = -1, bool blocking = false);
|
||||||
|
~YkChallengeResponseKey() override;
|
||||||
|
|
||||||
QByteArray rawKey() const override;
|
QByteArray rawKey() const override;
|
||||||
bool challenge(const QByteArray& challenge) override;
|
bool challenge(const QByteArray& challenge) override;
|
||||||
@ -52,7 +53,8 @@ signals:
|
|||||||
void userConfirmed();
|
void userConfirmed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QByteArray m_key;
|
char* m_key = nullptr;
|
||||||
|
std::size_t m_keySize = 0;
|
||||||
int m_slot;
|
int m_slot;
|
||||||
bool m_blocking;
|
bool m_blocking;
|
||||||
};
|
};
|
||||||
|
@ -18,12 +18,13 @@ if(WITH_XC_BROWSER)
|
|||||||
include_directories(${BROWSER_SOURCE_DIR})
|
include_directories(${BROWSER_SOURCE_DIR})
|
||||||
|
|
||||||
set(proxy_SOURCES
|
set(proxy_SOURCES
|
||||||
|
../core/Alloc.cpp
|
||||||
keepassxc-proxy.cpp
|
keepassxc-proxy.cpp
|
||||||
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
|
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
|
||||||
NativeMessagingHost.cpp)
|
NativeMessagingHost.cpp)
|
||||||
|
|
||||||
add_library(proxy STATIC ${proxy_SOURCES})
|
add_library(proxy STATIC ${proxy_SOURCES})
|
||||||
target_link_libraries(proxy Qt5::Core Qt5::Network)
|
target_link_libraries(proxy Qt5::Core Qt5::Network ${sodium_LIBRARY_RELEASE})
|
||||||
add_executable(keepassxc-proxy keepassxc-proxy.cpp)
|
add_executable(keepassxc-proxy keepassxc-proxy.cpp)
|
||||||
target_link_libraries(keepassxc-proxy proxy)
|
target_link_libraries(keepassxc-proxy proxy)
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
inline void debug(const char* message, ...)
|
inline void debug(const char* message, ...)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(message);
|
||||||
// qWarning(...);
|
// qWarning(...);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +259,7 @@ bool TouchID::authenticate(const QString& message) const
|
|||||||
NSString* authMessage = msg.toNSString(); // autoreleased
|
NSString* authMessage = msg.toNSString(); // autoreleased
|
||||||
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
|
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
|
||||||
localizedReason:authMessage reply:^(BOOL success, NSError* error) {
|
localizedReason:authMessage reply:^(BOOL success, NSError* error) {
|
||||||
|
Q_UNUSED(error);
|
||||||
result = success ? kTouchIDResultAllowed : kTouchIDResultFailed;
|
result = success ? kTouchIDResultAllowed : kTouchIDResultFailed;
|
||||||
CFRunLoopWakeUp(CFRunLoopGetCurrent());
|
CFRunLoopWakeUp(CFRunLoopGetCurrent());
|
||||||
}];
|
}];
|
||||||
|
@ -186,8 +186,10 @@ void TestKdbx4::testFormat400Upgrade()
|
|||||||
|
|
||||||
QCOMPARE(reader.version(), expectedVersion);
|
QCOMPARE(reader.version(), expectedVersion);
|
||||||
QCOMPARE(targetDb->cipher(), cipherUuid);
|
QCOMPARE(targetDb->cipher(), cipherUuid);
|
||||||
QCOMPARE(*targetDb->metadata()->customData(), *sourceDb->metadata()->customData());
|
QCOMPARE(targetDb->metadata()->customData()->value("CustomPublicData"),
|
||||||
QCOMPARE(*targetDb->rootGroup()->customData(), *sourceDb->rootGroup()->customData());
|
sourceDb->metadata()->customData()->value("CustomPublicData"));
|
||||||
|
QCOMPARE(targetDb->rootGroup()->customData()->value("CustomGroupData"),
|
||||||
|
sourceDb->rootGroup()->customData()->value("CustomGroupData"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@ -346,20 +348,22 @@ void TestKdbx4::testCustomData()
|
|||||||
const QString customDataKey2 = "CD2";
|
const QString customDataKey2 = "CD2";
|
||||||
const QString customData1 = "abcäöü";
|
const QString customData1 = "abcäöü";
|
||||||
const QString customData2 = "Hello World";
|
const QString customData2 = "Hello World";
|
||||||
const int dataSize = customDataKey1.toUtf8().size() + customDataKey1.toUtf8().size() + customData1.toUtf8().size()
|
|
||||||
+ customData2.toUtf8().size();
|
|
||||||
|
|
||||||
// test custom database data
|
// test custom database data
|
||||||
db.metadata()->customData()->set(customDataKey1, customData1);
|
db.metadata()->customData()->set(customDataKey1, customData1);
|
||||||
db.metadata()->customData()->set(customDataKey2, customData2);
|
db.metadata()->customData()->set(customDataKey2, customData2);
|
||||||
QCOMPARE(db.metadata()->customData()->size(), 2);
|
auto lastModified = db.metadata()->customData()->value(CustomData::LastModified);
|
||||||
|
const int dataSize = customDataKey1.toUtf8().size() + customDataKey1.toUtf8().size() + customData1.toUtf8().size()
|
||||||
|
+ customData2.toUtf8().size() + lastModified.toUtf8().size()
|
||||||
|
+ CustomData::LastModified.toUtf8().size();
|
||||||
|
QCOMPARE(db.metadata()->customData()->size(), 3);
|
||||||
QCOMPARE(db.metadata()->customData()->dataSize(), dataSize);
|
QCOMPARE(db.metadata()->customData()->dataSize(), dataSize);
|
||||||
|
|
||||||
// test custom root group data
|
// test custom root group data
|
||||||
Group* root = db.rootGroup();
|
Group* root = db.rootGroup();
|
||||||
root->customData()->set(customDataKey1, customData1);
|
root->customData()->set(customDataKey1, customData1);
|
||||||
root->customData()->set(customDataKey2, customData2);
|
root->customData()->set(customDataKey2, customData2);
|
||||||
QCOMPARE(root->customData()->size(), 2);
|
QCOMPARE(root->customData()->size(), 3);
|
||||||
QCOMPARE(root->customData()->dataSize(), dataSize);
|
QCOMPARE(root->customData()->dataSize(), dataSize);
|
||||||
|
|
||||||
// test copied custom group data
|
// test copied custom group data
|
||||||
@ -378,9 +382,9 @@ void TestKdbx4::testCustomData()
|
|||||||
|
|
||||||
// test custom data deletion
|
// test custom data deletion
|
||||||
entry->customData()->set("additional item", "foobar");
|
entry->customData()->set("additional item", "foobar");
|
||||||
QCOMPARE(entry->customData()->size(), 3);
|
QCOMPARE(entry->customData()->size(), 4);
|
||||||
entry->customData()->remove("additional item");
|
entry->customData()->remove("additional item");
|
||||||
QCOMPARE(entry->customData()->size(), 2);
|
QCOMPARE(entry->customData()->size(), 3);
|
||||||
QCOMPARE(entry->customData()->dataSize(), dataSize);
|
QCOMPARE(entry->customData()->dataSize(), dataSize);
|
||||||
|
|
||||||
// test custom data on cloned groups
|
// test custom data on cloned groups
|
||||||
|
@ -1164,6 +1164,65 @@ void TestMerge::testMetadata()
|
|||||||
// will be used - exception is the target has no recycle bin activated
|
// will be used - exception is the target has no recycle bin activated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestMerge::testCustomdata()
|
||||||
|
{
|
||||||
|
QScopedPointer<Database> dbDestination(new Database());
|
||||||
|
QScopedPointer<Database> dbSource(createTestDatabase());
|
||||||
|
QScopedPointer<Database> dbDestination2(new Database());
|
||||||
|
QScopedPointer<Database> dbSource2(createTestDatabase());
|
||||||
|
|
||||||
|
m_clock->advanceSecond(1);
|
||||||
|
|
||||||
|
dbDestination->metadata()->customData()->set("toBeDeleted", "value");
|
||||||
|
dbDestination->metadata()->customData()->set("key3", "oldValue");
|
||||||
|
|
||||||
|
dbSource2->metadata()->customData()->set("key1", "value1");
|
||||||
|
dbSource2->metadata()->customData()->set("key2", "value2");
|
||||||
|
dbSource2->metadata()->customData()->set("key3", "newValue");
|
||||||
|
dbSource2->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
|
||||||
|
|
||||||
|
m_clock->advanceSecond(1);
|
||||||
|
|
||||||
|
dbSource->metadata()->customData()->set("key1", "value1");
|
||||||
|
dbSource->metadata()->customData()->set("key2", "value2");
|
||||||
|
dbSource->metadata()->customData()->set("key3", "newValue");
|
||||||
|
dbSource->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
|
||||||
|
|
||||||
|
dbDestination2->metadata()->customData()->set("notToBeDeleted", "value");
|
||||||
|
dbDestination2->metadata()->customData()->set("key3", "oldValue");
|
||||||
|
|
||||||
|
// Sanity check.
|
||||||
|
QVERIFY(!dbSource->metadata()->customData()->isEmpty());
|
||||||
|
QVERIFY(!dbSource2->metadata()->customData()->isEmpty());
|
||||||
|
|
||||||
|
m_clock->advanceSecond(1);
|
||||||
|
|
||||||
|
Merger merger(dbSource.data(), dbDestination.data());
|
||||||
|
merger.merge();
|
||||||
|
|
||||||
|
Merger merger2(dbSource2.data(), dbDestination2.data());
|
||||||
|
merger2.merge();
|
||||||
|
|
||||||
|
// Source is newer, data should be merged
|
||||||
|
QVERIFY(!dbDestination->metadata()->customData()->isEmpty());
|
||||||
|
QVERIFY(dbDestination->metadata()->customData()->contains("key1"));
|
||||||
|
QVERIFY(dbDestination->metadata()->customData()->contains("key2"));
|
||||||
|
QVERIFY(dbDestination->metadata()->customData()->contains("Browser"));
|
||||||
|
QVERIFY(!dbDestination->metadata()->customData()->contains("toBeDeleted"));
|
||||||
|
QCOMPARE(dbDestination->metadata()->customData()->value("key1"), QString("value1"));
|
||||||
|
QCOMPARE(dbDestination->metadata()->customData()->value("key2"), QString("value2"));
|
||||||
|
QCOMPARE(dbDestination->metadata()->customData()->value("Browser"), QString("n'8=3W@L^6d->d.]St_>]"));
|
||||||
|
QCOMPARE(dbDestination->metadata()->customData()->value("key3"), QString("newValue")); // Old value should be replaced
|
||||||
|
|
||||||
|
// Target is newer, no data is merged
|
||||||
|
QVERIFY(!dbDestination2->metadata()->customData()->isEmpty());
|
||||||
|
QVERIFY(!dbDestination2->metadata()->customData()->contains("key1"));
|
||||||
|
QVERIFY(!dbDestination2->metadata()->customData()->contains("key2"));
|
||||||
|
QVERIFY(!dbDestination2->metadata()->customData()->contains("Browser"));
|
||||||
|
QVERIFY(dbDestination2->metadata()->customData()->contains("notToBeDeleted"));
|
||||||
|
QCOMPARE(dbDestination2->metadata()->customData()->value("key3"), QString("oldValue")); // Old value should not be replaced
|
||||||
|
}
|
||||||
|
|
||||||
void TestMerge::testDeletedEntry()
|
void TestMerge::testDeletedEntry()
|
||||||
{
|
{
|
||||||
QScopedPointer<Database> dbDestination(createTestDatabase());
|
QScopedPointer<Database> dbDestination(createTestDatabase());
|
||||||
|
@ -59,6 +59,7 @@ private slots:
|
|||||||
void testMergeCustomIcons();
|
void testMergeCustomIcons();
|
||||||
void testMergeDuplicateCustomIcons();
|
void testMergeDuplicateCustomIcons();
|
||||||
void testMetadata();
|
void testMetadata();
|
||||||
|
void testCustomdata();
|
||||||
void testDeletedEntry();
|
void testDeletedEntry();
|
||||||
void testDeletedGroup();
|
void testDeletedGroup();
|
||||||
void testDeletedRevertedEntry();
|
void testDeletedRevertedEntry();
|
||||||
|
@ -1286,11 +1286,11 @@ void TestGui::testTrayRestoreHide()
|
|||||||
QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test...");
|
QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_mainWindow->hideWindow();
|
||||||
|
QVERIFY(!m_mainWindow->isVisible());
|
||||||
|
|
||||||
auto* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>();
|
auto* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>();
|
||||||
QVERIFY(m_mainWindow->isVisible());
|
QVERIFY(trayIcon);
|
||||||
|
|
||||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
|
||||||
QTRY_VERIFY(!m_mainWindow->isVisible());
|
|
||||||
|
|
||||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||||
QTRY_VERIFY(m_mainWindow->isVisible());
|
QTRY_VERIFY(m_mainWindow->isVisible());
|
||||||
@ -1298,8 +1298,17 @@ void TestGui::testTrayRestoreHide()
|
|||||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
trayIcon->activated(QSystemTrayIcon::Trigger);
|
||||||
QTRY_VERIFY(!m_mainWindow->isVisible());
|
QTRY_VERIFY(!m_mainWindow->isVisible());
|
||||||
|
|
||||||
trayIcon->activated(QSystemTrayIcon::Trigger);
|
trayIcon->activated(QSystemTrayIcon::MiddleClick);
|
||||||
QTRY_VERIFY(m_mainWindow->isVisible());
|
QTRY_VERIFY(m_mainWindow->isVisible());
|
||||||
|
|
||||||
|
trayIcon->activated(QSystemTrayIcon::MiddleClick);
|
||||||
|
QTRY_VERIFY(!m_mainWindow->isVisible());
|
||||||
|
|
||||||
|
trayIcon->activated(QSystemTrayIcon::DoubleClick);
|
||||||
|
QTRY_VERIFY(m_mainWindow->isVisible());
|
||||||
|
|
||||||
|
trayIcon->activated(QSystemTrayIcon::DoubleClick);
|
||||||
|
QTRY_VERIFY(!m_mainWindow->isVisible());
|
||||||
}
|
}
|
||||||
|
|
||||||
int TestGui::addCannedEntries()
|
int TestGui::addCannedEntries()
|
||||||
|
Loading…
Reference in New Issue
Block a user