mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-28 16:59:44 -05:00
Release 2.4.2
- Improve resilience against memory attacks - overwrite memory before free [#3020] - Prevent infinite save loop when location is unavailable [#3026] - Attempt to fix quitting application when shutdown or logout issued [#3199] - Support merging database custom data [#3002] - Fix opening URL's with non-http schemes [#3153] - Fix data loss due to not reading all database attachments if duplicates exist [#3180] - Fix entry context menu disabling when using keyboard navigation [#3199] - Fix behaviors when canceling an entry edit [#3199] - Fix processing of tray icon click and doubleclick [#3112] - Update group in preview widget when focused [#3199] - Prefer DuckDuckGo service over direct icon download (increases resolution) [#2996] - Remove apply button in application settings [#3019] - Use winqtdeploy on Windows to correct deployment issues [#3025] - Don't mark entry edit as modified when attribute selection changes [#3041] - Use console code page CP_UTF8 on Windows if supported [#3050] - Snap: Fix locking database with session lock [#3046] - Snap: Fix theming across Linux distributions [#3057] - Snap: Use SNAP_USER_COMMON and SNAP_USER_DATA directories [#3131] - KeeShare: Automatically enable WITH_XC_KEESHARE_SECURE if quazip is found [#3088] - macOS: Fix toolbar text when in dark mode [#2998] - macOS: Lock database on switching user [#3097] - macOS: Fix global Auto-Type when the database is locked [#3138] - Browser: Close popups when database is locked [#3093] - Browser: Add tests [#3016] - Browser: Don't create default group if custom group is enabled [#3127]
This commit is contained in:
commit
a775031fe9
29
CHANGELOG
29
CHANGELOG
@ -1,3 +1,32 @@
|
||||
2.4.2 (2019-05-31)
|
||||
=========================
|
||||
|
||||
- Improve resilience against memory attacks - overwrite memory before free [#3020]
|
||||
- Prevent infinite save loop when location is unavailable [#3026]
|
||||
- Attempt to fix quitting application when shutdown or logout issued [#3199]
|
||||
- Support merging database custom data [#3002]
|
||||
- Fix opening URL's with non-http schemes [#3153]
|
||||
- Fix data loss due to not reading all database attachments if duplicates exist [#3180]
|
||||
- Fix entry context menu disabling when using keyboard navigation [#3199]
|
||||
- Fix behaviors when canceling an entry edit [#3199]
|
||||
- Fix processing of tray icon click and doubleclick [#3112]
|
||||
- Update group in preview widget when focused [#3199]
|
||||
- Prefer DuckDuckGo service over direct icon download (increases resolution) [#2996]
|
||||
- Remove apply button in application settings [#3019]
|
||||
- Use winqtdeploy on Windows to correct deployment issues [#3025]
|
||||
- Don't mark entry edit as modified when attribute selection changes [#3041]
|
||||
- Use console code page CP_UTF8 on Windows if supported [#3050]
|
||||
- Snap: Fix locking database with session lock [#3046]
|
||||
- Snap: Fix theming across Linux distributions [#3057]
|
||||
- Snap: Use SNAP_USER_COMMON and SNAP_USER_DATA directories [#3131]
|
||||
- KeeShare: Automatically enable WITH_XC_KEESHARE_SECURE if quazip is found [#3088]
|
||||
- macOS: Fix toolbar text when in dark mode [#2998]
|
||||
- macOS: Lock database on switching user [#3097]
|
||||
- macOS: Fix global Auto-Type when the database is locked [#3138]
|
||||
- Browser: Close popups when database is locked [#3093]
|
||||
- Browser: Add tests [#3016]
|
||||
- Browser: Don't create default group if custom group is enabled [#3127]
|
||||
|
||||
2.4.1 (2019-04-12)
|
||||
=========================
|
||||
|
||||
|
@ -20,9 +20,10 @@ project(KeePassXC)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
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)
|
||||
endif()
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
||||
|
||||
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_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_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_YUBIKEY "Include YubiKey support." OFF)
|
||||
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
||||
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF)
|
||||
option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" OFF)
|
||||
option(WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)" OFF)
|
||||
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
|
||||
if(APPLE)
|
||||
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
|
||||
endif()
|
||||
|
||||
if(WITH_XC_ALL)
|
||||
# Enable all options
|
||||
# Enable all options (except update check)
|
||||
set(WITH_XC_AUTOTYPE ON)
|
||||
set(WITH_XC_NETWORKING ON)
|
||||
set(WITH_XC_BROWSER ON)
|
||||
@ -67,23 +67,21 @@ if(WITH_XC_ALL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_XC_KEESHARE_SECURE)
|
||||
set(WITH_XC_KEESHARE ON)
|
||||
endif()
|
||||
|
||||
if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
|
||||
set(WITH_XC_CRYPTO_SSH ON)
|
||||
else()
|
||||
set(WITH_XC_CRYPTO_SSH OFF)
|
||||
endif()
|
||||
|
||||
if(WITH_XC_UPDATECHECK)
|
||||
set(WITH_XC_NETWORKING ON)
|
||||
# Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK
|
||||
if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK)
|
||||
message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled")
|
||||
set(WITH_XC_UPDATECHECK OFF)
|
||||
endif()
|
||||
|
||||
set(KEEPASSXC_VERSION_MAJOR "2")
|
||||
set(KEEPASSXC_VERSION_MINOR "4")
|
||||
set(KEEPASSXC_VERSION_PATCH "1")
|
||||
set(KEEPASSXC_VERSION_PATCH "2")
|
||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
|
||||
set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds")
|
||||
|
||||
@ -162,11 +160,15 @@ if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
|
||||
set(IS_32BIT TRUE)
|
||||
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)
|
||||
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)
|
||||
endif()
|
||||
|
||||
@ -199,7 +201,7 @@ add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
|
||||
add_gcc_compiler_flags("-fvisibility=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")
|
||||
endif()
|
||||
|
||||
@ -230,7 +232,6 @@ if(WITH_ASAN)
|
||||
|
||||
endif()
|
||||
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
||||
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
|
||||
add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2")
|
||||
endif()
|
||||
@ -264,6 +265,11 @@ endif()
|
||||
add_gcc_compiler_cflags("-std=c99")
|
||||
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)
|
||||
add_gcc_compiler_cxxflags("-stdlib=libc++")
|
||||
endif()
|
||||
@ -276,7 +282,7 @@ if(MINGW)
|
||||
set(CMAKE_RC_COMPILER_INIT windres)
|
||||
enable_language(RC)
|
||||
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
|
||||
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")
|
||||
@ -365,10 +371,17 @@ if(APPLE)
|
||||
set(CMAKE_MACOSX_RPATH TRUE)
|
||||
find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
|
||||
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()
|
||||
message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}")
|
||||
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()
|
||||
|
||||
# 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(ZLIB REQUIRED)
|
||||
find_package(QREncode REQUIRED)
|
||||
find_package(sodium 1.0.12 REQUIRED)
|
||||
|
||||
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")
|
||||
endif()
|
||||
|
||||
include_directories(SYSTEM ${ARGON2_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()
|
||||
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR})
|
||||
|
||||
# Optional
|
||||
if(WITH_XC_YUBIKEY)
|
||||
|
16
INSTALL.md
16
INSTALL.md
@ -25,7 +25,7 @@ The following libraries are required:
|
||||
* zlib
|
||||
* libmicrohttpd
|
||||
* libxi, libxtst, qtx11extras (optional for auto-type on X11)
|
||||
* libsodium (>= 1.0.12, optional for KeePassXC-Browser support)
|
||||
* libsodium (>= 1.0.12)
|
||||
* libargon2
|
||||
|
||||
Prepare the Building Environment
|
||||
@ -97,18 +97,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_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_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_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_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_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_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_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_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/`
|
||||
|
@ -112,7 +112,7 @@ mark_as_advanced(
|
||||
CMAKE_EXE_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")
|
||||
endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
|
||||
|
||||
|
@ -1,41 +1,24 @@
|
||||
# QUAZIP_FOUND - QuaZip library was found
|
||||
# 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_LIBRARIES - List of QuaZip libraries
|
||||
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
|
||||
# QUAZIP_FOUND - QuaZip library was found
|
||||
# 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_LIBRARIES - List of QuaZip libraries
|
||||
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
|
||||
|
||||
IF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
|
||||
# in cache already
|
||||
SET(QUAZIP_FOUND TRUE)
|
||||
ELSE(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
|
||||
IF(Qt5Core_FOUND)
|
||||
set(QUAZIP_LIB_VERSION_SUFFIX 5)
|
||||
ENDIF()
|
||||
IF(WIN32)
|
||||
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
|
||||
if(MINGW)
|
||||
find_library(QUAZIP_LIBRARIES libquazip5)
|
||||
find_path(QUAZIP_INCLUDE_DIR quazip.h PATH_SUFFIXES quazip5)
|
||||
find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h)
|
||||
else()
|
||||
find_library(QUAZIP_LIBRARIES
|
||||
NAMES quazip5 quazip
|
||||
PATHS /usr/lib /usr/lib64 /usr/local/lib
|
||||
)
|
||||
FIND_LIBRARY(QUAZIP_LIBRARIES NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll HINTS ${QUAZIP_LIBRARY_DIR})
|
||||
FIND_PATH(QUAZIP_INCLUDE_DIR NAMES quazip.h HINTS ${QUAZIP_LIBRARY_DIR}/../ PATH_SUFFIXES include/quazip5)
|
||||
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR NAMES zlib.h)
|
||||
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
|
||||
PATHS /usr/include /usr/local/include
|
||||
PATH_SUFFIXES quazip5 quazip
|
||||
)
|
||||
FIND_PATH(QUAZIP_INCLUDE_DIR quazip.h
|
||||
HINTS /usr/include /usr/local/include
|
||||
PATH_SUFFIXES quazip${QUAZIP_LIB_VERSION_SUFFIX}
|
||||
)
|
||||
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR zlib.h HINTS /usr/include /usr/local/include)
|
||||
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)
|
||||
find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h PATHS /usr/include /usr/local/include)
|
||||
endif()
|
||||
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)
|
||||
|
@ -50,6 +50,37 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.4.2" date="2019-05-31">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Improve resilience against memory attacks - overwrite memory before free [#3020]</li>
|
||||
<li>Prevent infinite save loop when location is unavailable [#3026]</li>
|
||||
<li>Attempt to fix quitting application when shutdown or logout issued [#3199]</li>
|
||||
<li>Support merging database custom data [#3002]</li>
|
||||
<li>Fix opening URL's with non-http schemes [#3153]</li>
|
||||
<li>Fix data loss due to not reading all database attachments if duplicates exist [#3180]</li>
|
||||
<li>Fix entry context menu disabling when using keyboard navigation [#3199]</li>
|
||||
<li>Fix behaviors when canceling an entry edit [#3199]</li>
|
||||
<li>Fix processing of tray icon click and doubleclick [#3112]</li>
|
||||
<li>Update group in preview widget when focused [#3199]</li>
|
||||
<li>Prefer DuckDuckGo service over direct icon download (increases resolution) [#2996]</li>
|
||||
<li>Remove apply button in application settings [#3019]</li>
|
||||
<li>Use winqtdeploy on Windows to correct deployment issues [#3025]</li>
|
||||
<li>Don't mark entry edit as modified when attribute selection changes [#3041]</li>
|
||||
<li>Use console code page CP_UTF8 on Windows if supported [#3050]</li>
|
||||
<li>Snap: Fix locking database with session lock [#3046]</li>
|
||||
<li>Snap: Fix theming across Linux distributions [#3057]</li>
|
||||
<li>Snap: Use SNAP_USER_COMMON and SNAP_USER_DATA directories [#3131]</li>
|
||||
<li>KeeShare: Automatically enable WITH_XC_KEESHARE_SECURE if quazip is found [#3088]</li>
|
||||
<li>macOS: Fix toolbar text when in dark mode [#2998]</li>
|
||||
<li>macOS: Lock database on switching user [#3097]</li>
|
||||
<li>macOS: Fix global Auto-Type when the database is locked [#3138]</li>
|
||||
<li>Browser: Close popups when database is locked [#3093]</li>
|
||||
<li>Browser: Add tests [#3016]</li>
|
||||
<li>Browser: Don't create default group if custom group is enabled [#3127]</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="2.4.1" date="2019-04-12">
|
||||
<description>
|
||||
<ul>
|
||||
|
@ -1531,7 +1531,7 @@ Möchten Sie Ihre Änderungen zusammenführen?</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Do you really want to delete %n entry(s) for good?</source>
|
||||
<translation><numerusform>Sollen tatsächlich %1 Einträge gelöscht werden?</numerusform><numerusform>Sollen tatsächlich %1 Einträge gelöscht werden?</numerusform></translation>
|
||||
<translation><numerusform>Sollen tatsächlich %n Einträge gelöscht werden?</numerusform><numerusform>Sollen tatsächlich %n Einträge gelöscht werden?</numerusform></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Delete entry(s)?</source>
|
||||
|
@ -326,8 +326,8 @@
|
||||
<translation>Privacy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use DuckDuckGo as fallback for downloading website icons</source>
|
||||
<translation>Use DuckDuckGo as fallback for downloading website icons</translation>
|
||||
<source>Use DuckDuckGo service to download website icons</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -631,6 +631,14 @@ Please select the correct database for saving credentials.</translation>
|
||||
<source>&Brave</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Returns expired credentials. String [expired] is added to the title.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Allow returning expired credentials.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>BrowserService</name>
|
||||
@ -2244,10 +2252,6 @@ Supported extensions are: %1.</source>
|
||||
<source>Custom icon successfully downloaded</source>
|
||||
<translation>Custom icon successfully downloaded</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hint: You can enable DuckDuckGo as a fallback under Tools>Settings>Security</source>
|
||||
<translation>Hint: You can enable DuckDuckGo as a fallback under Tools>Settings>Security</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Image(s)</source>
|
||||
<translation>Select Image(s)</translation>
|
||||
@ -2284,6 +2288,10 @@ Supported extensions are: %1.</source>
|
||||
<numerusform>This icon is used by %n entry(s), and will be replaced by the default icon. Are you sure you want to delete it?</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditWidgetProperties</name>
|
||||
@ -3750,6 +3758,14 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
<source>Adding missing icon %1</source>
|
||||
<translation>Adding missing icon %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Removed custom data %1 [%2]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Adding custom data %1 [%2]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>NewDatabaseWizard</name>
|
||||
|
@ -200,27 +200,27 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type</source>
|
||||
<translation>Auto-Escritura</translation>
|
||||
<translation>Autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use entry title to match windows for global Auto-Type</source>
|
||||
<translation>Use título de entrada para acertar ventanas en Auto-Escritura global.</translation>
|
||||
<translation>Usar título de la entrada para emparejar ventanas en autoescritura global</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use entry URL to match windows for global Auto-Type</source>
|
||||
<translation>Use URL para acertar ventanas en Auto-Escritura global</translation>
|
||||
<translation>Usar URL de la entrada para emparejar ventanas en autoescritura global</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Always ask before performing Auto-Type</source>
|
||||
<translation>Siempre preguntar antes de hacer Auto-Escritura</translation>
|
||||
<translation>Siempre preguntar antes de hacer autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Global Auto-Type shortcut</source>
|
||||
<translation>Atajo global de Auto-Escritura</translation>
|
||||
<translation>Atajo global de autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type typing delay</source>
|
||||
<translation>Escribiendo retardo de la Auto-Escritura</translation>
|
||||
<translation>Escribiendo retardo de la autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> ms</source>
|
||||
@ -229,7 +229,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type start delay</source>
|
||||
<translation>Iniciar retardo de Auto-Escritura</translation>
|
||||
<translation>Iniciar retardo de autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Check for updates at application startup</source>
|
||||
@ -293,7 +293,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-lock previously locked database after performing Auto-Type</source>
|
||||
<translation>Volver a bloquear la base de datos tras realizar una Auto-Escritura </translation>
|
||||
<translation>Volver a bloquear la base de datos tras realizar una autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't require password repeat when it is visible</source>
|
||||
@ -332,27 +332,27 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type - KeePassXC</source>
|
||||
<translation>Auto-Escritura - KeePassXC</translation>
|
||||
<translation>Autoescritura - KeePassXC</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type</source>
|
||||
<translation>Auto-Escritura</translation>
|
||||
<translation>Autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The Syntax of your Auto-Type statement is incorrect!</source>
|
||||
<translation>¡La sintaxis de la declaración de su Auto-Escritura es incorrecta!</translation>
|
||||
<translation>¡La sintaxis de la declaración de su autoescritura es incorrecta!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>This Auto-Type command contains a very long delay. Do you really want to proceed?</source>
|
||||
<translation>Este comando de Auto-Escritura contiene un retraso muy largo. ¿Realmente desea continuar?</translation>
|
||||
<translation>Este comando de autoescritura contiene un retraso muy largo. ¿Realmente desea continuar?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>This Auto-Type command contains very slow key presses. Do you really want to proceed?</source>
|
||||
<translation>Este comando de Auto-Escritura contiene pulsaciones de teclas muy lentas. ¿Realmente desea continuar?</translation>
|
||||
<translation>Este comando de autoescritura contiene pulsaciones de teclas muy lentas. ¿Realmente desea continuar?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>This Auto-Type command contains arguments which are repeated very often. Do you really want to proceed?</source>
|
||||
<translation>Este comando de Auto-Escritura contiene argumentos que se repiten muy a menudo. ¿Realmente desea continuar?</translation>
|
||||
<translation>Este comando de autoescritura contiene argumentos que se repiten muy a menudo. ¿Realmente desea continuar?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -393,11 +393,11 @@
|
||||
<name>AutoTypeSelectDialog</name>
|
||||
<message>
|
||||
<source>Auto-Type - KeePassXC</source>
|
||||
<translation>Auto-Escritura - KeePassXC</translation>
|
||||
<translation>Autoescritura - KeePassXC</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select entry to Auto-Type:</source>
|
||||
<translation>Seleccionar entrada para Auto-Escritura:</translation>
|
||||
<translation>Seleccionar entrada para autoescritura:</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -884,7 +884,7 @@ Es necesario para mantener sus conexiones presentes del navegador.
|
||||
<name>DatabaseOpenDialog</name>
|
||||
<message>
|
||||
<source>Unlock Database - KeePassXC</source>
|
||||
<translation>Desbloquear Base de Datos - KeePassXC</translation>
|
||||
<translation>Desbloquear base de datos - KeePassXC</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1259,7 +1259,7 @@ Si conserva este número, ¡su base de datos puede ser muy fácil de descifrar!<
|
||||
<name>DatabaseSettingsWidgetGeneral</name>
|
||||
<message>
|
||||
<source>Database Meta Data</source>
|
||||
<translation>Metadatos de la Base de Datos</translation>
|
||||
<translation>Metadatos de la base de datos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database name:</source>
|
||||
@ -1295,7 +1295,7 @@ Si conserva este número, ¡su base de datos puede ser muy fácil de descifrar!<
|
||||
</message>
|
||||
<message>
|
||||
<source>Additional Database Settings</source>
|
||||
<translation>Configuraciones Adicionales de la Base de Datos</translation>
|
||||
<translation>Configuraciones adicionales de la base de datos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable &compression (recommended)</source>
|
||||
@ -1438,12 +1438,12 @@ Esto es definitivamente un error, por favor repórtelo a los desarrolladores.</t
|
||||
</message>
|
||||
<message>
|
||||
<source>New Database</source>
|
||||
<translation>Nueva Base de datos</translation>
|
||||
<translation>Nueva base de datos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 [New Database]</source>
|
||||
<comment>Database tab name modifier</comment>
|
||||
<translation>%1 [Nueva Base de Datos]</translation>
|
||||
<translation>%1 [Nueva base de datos]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 [Locked]</source>
|
||||
@ -1549,7 +1549,7 @@ Do you want to merge your changes?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Lock Database?</source>
|
||||
<translation>¿Bloquear la Base de datos?</translation>
|
||||
<translation>¿Bloquear la base de datos?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You are editing an entry. Discard changes and lock anyway?</source>
|
||||
@ -1654,7 +1654,7 @@ Disable safe saves and try again?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type</source>
|
||||
<translation>Auto-Escritura</translation>
|
||||
<translation>Autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Properties</source>
|
||||
@ -1800,15 +1800,15 @@ Disable safe saves and try again?</source>
|
||||
<name>EditEntryWidgetAutoType</name>
|
||||
<message>
|
||||
<source>Enable Auto-Type for this entry</source>
|
||||
<translation>Activar Auto-Escritura para esta entrada</translation>
|
||||
<translation>Activar autoescritura para esta entrada</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Inherit default Auto-Type sequence from the &group</source>
|
||||
<translation>Heredar la secuencia de Auto-Escritura por defecto del &grupo</translation>
|
||||
<translation>Heredar la secuencia de autoescritura por defecto del &grupo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Use custom Auto-Type sequence:</source>
|
||||
<translation>&Usar secuencia de Auto-Escritura personalizada:</translation>
|
||||
<translation>&Usar secuencia de autoescritura personalizada:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Window Associations</source>
|
||||
@ -2111,15 +2111,15 @@ Disable safe saves and try again?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type</source>
|
||||
<translation>Auto-Escritura</translation>
|
||||
<translation>Autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Use default Auto-Type sequence of parent group</source>
|
||||
<translation>&Usar por defecto la secuencia de Auto-Escritura del grupo padre</translation>
|
||||
<translation>&Usar por defecto la secuencia de autoescritura del grupo padre</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set default Auto-Type se&quence</source>
|
||||
<translation>Seleccionar se&cuencia de Auto-Escritura por defecto</translation>
|
||||
<translation>Seleccionar se&cuencia de autoescritura por defecto</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -2932,7 +2932,7 @@ Esta migración es en único sentido. No podrá abrir la base de datos importada
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-type association window or sequence missing</source>
|
||||
<translation>Falta de secuencia o ventana de asociación de Auto-Escritura</translation>
|
||||
<translation>Falta de secuencia o ventana de asociación de autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid bool value</source>
|
||||
@ -3522,7 +3522,7 @@ Le recomendamos que utilice la AppImage disponible en nuestra página de descarg
|
||||
</message>
|
||||
<message>
|
||||
<source>Perform &Auto-Type</source>
|
||||
<translation>Realizar &Auto-Escritura</translation>
|
||||
<translation>Realizar &autoescritura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open &URL</source>
|
||||
@ -4857,7 +4857,7 @@ Comandos disponibles:
|
||||
</message>
|
||||
<message>
|
||||
<source>Database password: </source>
|
||||
<translation>Contraseña de la Base de Datos:</translation>
|
||||
<translation>Contraseña de la base de datos:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot create new group</source>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -611,7 +611,7 @@ Veuillez sélectionner la base de donnée souhaitée pour enregistrer les identi
|
||||
</message>
|
||||
<message>
|
||||
<source>Due to Snap sandboxing, you must run a script to enable browser integration.<br />You can obtain this script from %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>À cause du mécanisme de sandboxing Snap, vous devez lancer un script pour activer l'intégration du navigateur.<br />Vous pouvez obtenir ce script depuis %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please see special instructions for browser extension use below</source>
|
||||
@ -710,7 +710,7 @@ Voulez-vous créer ce groupe ?
|
||||
<source>Your KeePassXC-Browser settings need to be moved into the database settings.
|
||||
This is necessary to maintain your current browser connections.
|
||||
Would you like to migrate your existing settings now?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Vos réglages pour KeePassXC-Browser doivent être intégrés dans les réglages de la base de données. Ceci est nécessaire pour maintenir vos connexions actuelles avec le navigateur ouvertes. Souhaitez-vous effectuer la migration de vos réglages maintenant ?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -874,7 +874,7 @@ Would you like to migrate your existing settings now?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Key not transformed. This is a bug, please report it to the developers!</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>La clé n'a pas été transformée. Ceci est un bogue, pouvez-vous s'il vous plaît le signaler aux développeurs ?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1071,7 +1071,7 @@ Cela peut empêcher la connexion avec l'extension de navigateur.</translati
|
||||
<message>
|
||||
<source>Do you really want forget all site-specific settings on every entry?
|
||||
Permissions to access entries will be revoked.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Êtes-vous sûr de vouloir effacer les préférences de site pour toutes les entrées ? Les permissions d'accès aux entrées seront révoquées.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Removing stored permissions…</source>
|
||||
@ -1099,12 +1099,13 @@ Permissions to access entries will be revoked.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Move KeePassHTTP attributes to custom data</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Déplacer les attributs KeePassHTTP vers les données personnalisées</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do you really want to move all legacy browser integration data to the latest standard?
|
||||
This is necessary to maintain compatibility with the browser plugin.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Voulez-vous convertir toutes les anciennes données d'intégration au navigateur en version plus récente ?
|
||||
Ceci est nécessaire pour assurer la compatibilité de l'extension.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1528,7 +1529,7 @@ Voulez-vous fusionner vos changements ?</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Do you really want to delete %n entry(s) for good?</source>
|
||||
<translation><numerusform>Voulez-vous vraiment supprimer définitivement %1 entrée ?</numerusform><numerusform>Voulez-vous vraiment supprimer définitivement %1 entrées ?</numerusform></translation>
|
||||
<translation><numerusform>Voulez-vous vraiment supprimer définitivement %n entrée ?</numerusform><numerusform>Voulez-vous vraiment supprimer définitivement %n entrées ?</numerusform></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Delete entry(s)?</source>
|
||||
@ -3463,7 +3464,7 @@ Nous recommandons l'utilisation de l'AppImage disponible sur notre pag
|
||||
</message>
|
||||
<message>
|
||||
<source>&New database...</source>
|
||||
<translation>&Ńouvelle base de données...</translation>
|
||||
<translation>&Nouvelle base de données...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Create a new database</source>
|
||||
@ -4513,7 +4514,7 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Multi-word extra bits %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Octets additionnels mots multiples %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Bruteforce</source>
|
||||
@ -4561,7 +4562,7 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Dict+Leet(Rep)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Type : Dictionnaire + Leet (rep)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: User Words(Rep)</source>
|
||||
@ -4569,7 +4570,7 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: User+Leet(Rep)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Type : Utilisateur + Leet (rep)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Repeated(Rep)</source>
|
||||
@ -4597,7 +4598,7 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>*** Password length (%1) != sum of length of parts (%2) ***</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>*** Longueur du mot de passe (%1) != longueurs additionnées des morceaux (%2) ***</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to load key file %1: %2</source>
|
||||
@ -4933,11 +4934,11 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Search terms are as follows: [modifiers][field:]["]term["]</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Les termes de recherche sont construits comme suit : [modificateurs][champ:]["]terme["]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Every search term must match (ie, logical AND)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Tous les termes doivent correspondre (ET logique)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modifiers</source>
|
||||
@ -4961,15 +4962,15 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Term Wildcards</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Caractères spéciaux</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>match anything</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>correspond à n'importe quel caractère</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>match one</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>correspond à un seul caractère</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>logical OR</source>
|
||||
@ -5105,7 +5106,7 @@ Commandes disponibles :
|
||||
<message>
|
||||
<source>key.share</source>
|
||||
<comment>Filetype for KeeShare key</comment>
|
||||
<translation type="unfinished"/>
|
||||
<translation>cle.share</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeeShare key file</source>
|
||||
@ -5121,15 +5122,15 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Exporting changed certificate</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Exportation des certificats modifiés</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The exported certificate is not the same as the one in use. Do you want to export the current certificate?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Le certificat exporté est différent de celui en cours d'utilisation. Voulez-vous exporter le certificat actuel ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Signer:</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Signataire :</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -5140,7 +5141,7 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>We cannot verify the source of the shared container because it is not signed. Do you really want to import from %1?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Nous ne pouvons vérifier la source du conteneur partagé car celui-ci n'est pas signé. Êtes-vous sûr de vouloir importer depuis %1 ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Import from container with certificate</source>
|
||||
@ -5176,7 +5177,7 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Signed share container are not supported - import prevented</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Conteneur de partage signé non pris en charge - importation annulée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>File is not readable</source>
|
||||
@ -5184,15 +5185,15 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid sharing container</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Conteneur de partage invalide</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Untrusted import prevented</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Importation non sécurisée annulée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Successful signed import</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Importation signée réussie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unexpected error</source>
|
||||
@ -5200,11 +5201,11 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Unsigned share container are not supported - import prevented</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Conteneur de partage non signé non pris en charge - importation annulée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Successful unsigned import</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Importation non signée réussie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>File does not exist</source>
|
||||
@ -5212,27 +5213,27 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown share container type</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Type de conteneur de partage non reconnu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Overwriting signed share container is not supported - export prevented</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Remplacement de conteneur de partage signé non pris en charge - exportation annulée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not write export container (%1)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Impossible d'exporter le conteneur (%1)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Overwriting unsigned share container is not supported - export prevented</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Remplacement de conteneur non signé non pris en charge - exportation annulée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not write export container</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Impossible d'exporter le conteneur</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unexpected export error occurred</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Une erreur inattendue est survenue lors de l'exportation</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Export to %1 failed (%2)</source>
|
||||
@ -5248,31 +5249,31 @@ Commandes disponibles :
|
||||
</message>
|
||||
<message>
|
||||
<source>Do you want to trust %1 with the fingerprint of %2 from %3?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Voulez-vous autoriser %1 avec l'empreinte de %2 à %3 ? {1 ?} {2 ?}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Multiple import source path to %1 in %2</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Chemin source d'importation multiple de %1 dans %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Conflicting export target path %1 in %2</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Conflit du chemin cible d'exportation %1 dans %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not embed signature: Could not open file to write (%1)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Impossible d'intégrer la signature : le fichier (%1) n'a pas pu être ouvert en écriture</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not embed signature: Could not write file (%1)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Impossible d'intégrer la signature : problème d'écriture dans le fichier (%1)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not embed database: Could not open file to write (%1)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Impossible d'intégrer la base de données : le fichier (%1) n'a pas pu être ouvert en écriture</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not embed database: Could not write file (%1)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Impossible d'intégrer la base de données : problème d'écriture dans le fichier (%1)</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -54,7 +54,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Use OpenSSH for Windows instead of Pageant</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Usa OpenSSH per Windows al posto di Pageant</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -442,7 +442,8 @@ Seleziona se vuoi consentire l'accesso.</translation>
|
||||
<message>
|
||||
<source>You have multiple databases open.
|
||||
Please select the correct database for saving credentials.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>C'è più di un database aperto
|
||||
Selezionare il database corretto dove salvare le credenziali</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1341,7 +1342,9 @@ Se continui con questo numero, il tuo database potrebbe essere decifrato molto f
|
||||
<source>WARNING! You have not set a password. Using a database without a password is strongly discouraged!
|
||||
|
||||
Are you sure you want to continue without a password?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>ATTENZIONE! Non è stata impostata una password. Utilizzare un database senza password è fortemente sconsigliato!
|
||||
|
||||
Siete sicuri di voler continuare senza password?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown error</source>
|
||||
@ -1547,7 +1550,8 @@ Vuoi salvare le modifiche?</translation>
|
||||
<message>
|
||||
<source>Database was modified.
|
||||
Save changes?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Il database è stato modificato.
|
||||
Salvare le modifiche?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save changes?</source>
|
||||
@ -3532,7 +3536,7 @@ Si consiglia di utilizzare l'AppImage disponibile sulla nostra pagina di do
|
||||
</message>
|
||||
<message>
|
||||
<source>Check for Updates...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Controllo aggiornamenti...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Share entry</source>
|
||||
@ -3545,15 +3549,15 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>Check for updates on startup?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Controllare gli aggiornamenti all'avvio?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Would you like KeePassXC to check for updates on startup?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Volete che KeePassXC controlli eventuali aggiornamenti all'avvio?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You can always check for updates manually from the application menu.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>È sempre possibile controllare gli aggiornamenti manualmente tramite i menu dell'applicazione.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -3619,7 +3623,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
<name>NewDatabaseWizard</name>
|
||||
<message>
|
||||
<source>Create a new KeePassXC database...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Creazione di un nuovo database KeePassXC...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Root</source>
|
||||
@ -3639,7 +3643,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>Here you can adjust the database encryption settings. Don't worry, you can change them later in the database settings.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Qui è possibile modificare le impostazioni di crittaggio del database. È sempre possibile modificarli dopo nelle impostazioni del database.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Advanced Settings</source>
|
||||
@ -3658,7 +3662,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>Here you can adjust the database encryption settings. Don't worry, you can change them later in the database settings.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Qui è possibile modificare le impostazioni di crittaggio del database. È sempre possibile modificarli dopo nelle impostazioni del database.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -3669,7 +3673,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>A master key known only to you protects your database.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Una password principale segreta protegge il vostro database.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -3798,7 +3802,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source><p>A password is the primary method for securing your database.</p><p>Good passwords are long and unique. KeePassXC can generate one for you.</p></source>
|
||||
<translation type="unfinished"/>
|
||||
<translation><p>Una password è il metodo principale per mantenere sicuro il vostro database.</p><p>Una buona password dev'essere lunga ed unica. KeePassXC può generarne una per voi.</p></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Passwords do not match.</source>
|
||||
@ -3958,7 +3962,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>{[(</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>{[(</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Punctuation</source>
|
||||
@ -3966,7 +3970,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>.,:;</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>.,:;</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quotes</source>
|
||||
@ -3974,7 +3978,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>" '</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>" '</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Math</source>
|
||||
@ -3982,7 +3986,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source><*+!?=</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation><*+!?=</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Dashes</source>
|
||||
@ -3990,7 +3994,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>\_|-/</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>\_|-/</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Logograms</source>
|
||||
@ -3998,7 +4002,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>#$%&&@^`~</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>#$%&&@^`~</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Switch to simple mode</source>
|
||||
@ -4041,21 +4045,21 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
<name>QApplication</name>
|
||||
<message>
|
||||
<source>KeeShare</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>KeeShare</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QFileDialog</name>
|
||||
<message>
|
||||
<source>Select</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Seleziona</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QMessageBox</name>
|
||||
<message>
|
||||
<source>Overwrite</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Sovrascrivi</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete</source>
|
||||
@ -4063,11 +4067,11 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>Move</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Sposta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Empty</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Vuoto</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove</source>
|
||||
@ -4075,7 +4079,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>Skip</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Salta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disable</source>
|
||||
@ -4083,7 +4087,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>Merge</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Incorpora</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -4743,19 +4747,19 @@ Comandi disponibili:
|
||||
</message>
|
||||
<message>
|
||||
<source>Create a new database.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Crea un nuovo database.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>File %1 already exists.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Il file %1 esiste già.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Loading the key file failed</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Caricamento del key-file fallito.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No key is set. Aborting database creation.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Chiave non impostata. Annullamento creazione database.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to save the database: %1.</source>
|
||||
@ -4763,7 +4767,7 @@ Comandi disponibili:
|
||||
</message>
|
||||
<message>
|
||||
<source>Successfully created new database.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Nuovo database creato con successo.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Insert password to encrypt database (Press enter to leave blank): </source>
|
||||
|
@ -491,7 +491,7 @@ Por favor, selecione o banco de dados correto para salvar as credenciais.</trans
|
||||
</message>
|
||||
<message>
|
||||
<source>Re&quest to unlock the database if it is locked</source>
|
||||
<translation>Pe&dir para desbloquear a base de dados se estiver bloqueada</translation>
|
||||
<translation>Pe&dir para desbloquear a banco de dados se estiver bloqueado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Only entries with the same scheme (http://, https://, ...) are returned.</source>
|
||||
@ -1063,7 +1063,7 @@ Isso pode impedir a conexão com o plugin do navegador.</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Successfully removed %n encryption key(s) from KeePassXC settings.</source>
|
||||
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
|
||||
<translation><numerusform>Removido com sucesso% n chave (s) criptográficas das configurações do KeePassXC.</numerusform><numerusform>Removido com sucesso% n chave (s) criptográficas das configurações do KeePassXC.</numerusform></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Forget all site-specific settings on entries</source>
|
||||
@ -1097,7 +1097,7 @@ Permissões para acessar entradas serão revogadas.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The active database does not contain an entry with permissions.</source>
|
||||
<translation>A base de dados ativa não contém uma entrada com permissões.</translation>
|
||||
<translation>O banco de dados ativo não contém uma entrada com permissões.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Move KeePassHTTP attributes to custom data</source>
|
||||
@ -1239,7 +1239,7 @@ Se você manter este número, seu banco de dados pode ser facilmente crackeado!<
|
||||
<message numerus="yes">
|
||||
<source> thread(s)</source>
|
||||
<comment>Threads for parallel execution (KDF settings)</comment>
|
||||
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
|
||||
<translation><numerusform>processo(s)</numerusform><numerusform>processo(s)</numerusform></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>%1 ms</source>
|
||||
@ -1489,11 +1489,11 @@ Este é definitivamente um bug, por favor denuncie para os desenvolvedores.</tra
|
||||
</message>
|
||||
<message>
|
||||
<source>No current database.</source>
|
||||
<translation>Nenhuma base de dados atual.</translation>
|
||||
<translation>Nenhuma banco de dados atual.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No source database, nothing to do.</source>
|
||||
<translation>Nenhuma base de dados de origem, nada a fazer.</translation>
|
||||
<translation>Nenhuma banco de dados de origem, nada a fazer.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search Results (%1)</source>
|
||||
@ -1509,7 +1509,7 @@ Este é definitivamente um bug, por favor denuncie para os desenvolvedores.</tra
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file has changed. Do you want to load the changes?</source>
|
||||
<translation>A base de dados foi alterada. Deseja carregar as alterações?</translation>
|
||||
<translation>O banco de dados foi alterado. Deseja carregar as alterações?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Merge Request</source>
|
||||
@ -1531,7 +1531,7 @@ Você deseja combinar suas alterações?</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Do you really want to delete %n entry(s) for good?</source>
|
||||
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
|
||||
<translation><numerusform>Você realmente quer apagar %n entrada(s) para o bem?</numerusform><numerusform>Você realmente quer apagar %n entrada(s) para o bem?</numerusform></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Delete entry(s)?</source>
|
||||
@ -1609,7 +1609,7 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>Entry "%1" has %2 reference(s). Do you want to overwrite references with values, skip this entry, or delete anyway?</source>
|
||||
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
|
||||
<translation><numerusform>A entrada "%1" tem %2 referência(s). Deseja substituir referências por valores, ignorar essa entrada ou excluir mesmo assim?</numerusform><numerusform>A entrada "%1" tem %2 referência(s). Deseja substituir referências por valores, ignorar essa entrada ou excluir mesmo assim?</numerusform></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete group</source>
|
||||
@ -2078,15 +2078,15 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The export container %1 is already referenced.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>O contêiner de exportado %1 já é referenciado.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The import container %1 is already imported.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>O contêiner de importado %1 já foi importado.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The container %1 imported and export by different groups.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>O contêiner %1 importado e exportado por diferentes grupos.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -2168,7 +2168,7 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hint: You can enable DuckDuckGo as a fallback under Tools>Settings>Security</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Dica: você pode habilitar o DuckDuckGo como um reserva em Ferramentas> Configurações> Segurança</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Image(s)</source>
|
||||
@ -2184,7 +2184,7 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>%n icon(s) already exist in the database</source>
|
||||
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
|
||||
<translation><numerusform>%n ícone(s) já existe no banco de dados</numerusform><numerusform>%n ícone(s) já existe no banco de dados</numerusform></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>The following icon(s) failed:</source>
|
||||
@ -2192,7 +2192,7 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>This icon is used by %n entry(s), and will be replaced by the default icon. Are you sure you want to delete it?</source>
|
||||
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
|
||||
<translation><numerusform>Este ícone é usado por %n entrada(s) e será substituído pelo ícone padrão. Tem certeza de que deseja excluí-lo?</numerusform><numerusform>Este ícone é usado por %n entrada(s) e será substituído pelo ícone padrão. Tem certeza de que deseja excluí-lo?</numerusform></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -2593,7 +2593,7 @@ Isto pode causar mal funcionamento dos plugins afetados.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wrong key or database file is corrupt.</source>
|
||||
<translation>Chave errada ou base de dados corrompida.</translation>
|
||||
<translation>Chave errada ou banco de dados corrompido.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>missing database headers</source>
|
||||
@ -2601,7 +2601,7 @@ Isto pode causar mal funcionamento dos plugins afetados.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Header doesn't match hash</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Cabeçalho não corresponde ao hash</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid header id size</source>
|
||||
@ -2833,7 +2833,7 @@ Isto é uma migração de caminho único. Você não poderá abrir o banco de da
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to parse UUID: %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Não é possível analisar o UUID: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to read database file.</source>
|
||||
@ -3036,7 +3036,7 @@ Linha %2, coluna %3</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wrong key or database file is corrupt.</source>
|
||||
<translation>Chave errada ou base de dados corrompida.</translation>
|
||||
<translation>Chave errada ou banco de dados corrompido.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Key transformation failed</source>
|
||||
@ -3225,7 +3225,7 @@ Linha %2, coluna %3</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><p>You can add a key file containing random bytes for additional security.</p><p>You must keep it secret and never lose it or you will be locked out!</p></source>
|
||||
<translation type="unfinished"/>
|
||||
<translation><p>Você pode adicionar um arquivo de chave contendo bytes aleatórios para segurança adicional.</p><p>Você deve mantê-lo em segredo e nunca perdê-lo ou você será bloqueado!</p></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Legacy key file format</source>
|
||||
@ -3236,12 +3236,16 @@ Linha %2, coluna %3</translation>
|
||||
unsupported in the future.
|
||||
|
||||
Please go to the master key settings and generate a new key file.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Você está usando um formato de arquivo de chave antigo que pode ficar
|
||||
sem suporte no futuro.
|
||||
|
||||
Por favor, vá para as configurações da chave mestra e gere um novo arquivo de chave.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error loading the key file '%1'
|
||||
Message: %2</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Erro ao carregar o arquivo de chave '%1'
|
||||
Mensagem: %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Key files</source>
|
||||
@ -3261,7 +3265,7 @@ Message: %2</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to create key file: %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Não foi possível criar arquivo de chave: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select a key file</source>
|
||||
@ -3308,11 +3312,11 @@ Message: %2</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Save database</source>
|
||||
<translation>&Salvar base de dados</translation>
|
||||
<translation>&Salvar banco de dados</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Close database</source>
|
||||
<translation>&Fechar base de dados</translation>
|
||||
<translation>&Fechar banco de dados</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Delete entry</source>
|
||||
@ -3360,7 +3364,7 @@ Message: %2</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Lock databases</source>
|
||||
<translation>&Trancar base de dados</translation>
|
||||
<translation>&Trancar banco de dados</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Title</source>
|
||||
@ -3591,11 +3595,11 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
|
||||
</message>
|
||||
<message>
|
||||
<source>Adding backup for older target %1 [%2]</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Adicionando backup para o alvo mais antigo %1 [%2]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Adding backup for older source %1 [%2]</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Adicionando backup para fonte mais antiga %1 [%2]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reapplying older target entry on top of newer source %1 [%2]</source>
|
||||
@ -3607,7 +3611,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
|
||||
</message>
|
||||
<message>
|
||||
<source>Synchronizing from newer source %1 [%2]</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Sincronizando de uma fonte mais nova %1 [%2]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Synchronizing from older source %1 [%2]</source>
|
||||
@ -3619,7 +3623,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
|
||||
</message>
|
||||
<message>
|
||||
<source>Deleting orphan %1 [%2]</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Excluindo órfã %1 [%2]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Changed deleted objects</source>
|
||||
@ -3937,7 +3941,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
|
||||
</message>
|
||||
<message>
|
||||
<source>ExtendedASCII</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>ASCIIEstendido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Switch to advanced mode</source>
|
||||
@ -3957,7 +3961,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
|
||||
</message>
|
||||
<message>
|
||||
<source>Lower Case Letters A to F</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Letras minúsculas de A a F</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>a-z</source>
|
||||
@ -4033,7 +4037,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
|
||||
</message>
|
||||
<message>
|
||||
<source>Add non-hex letters to "do not include" list</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Adicionar letras não hexadecimais à lista "não incluir"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hex</source>
|
||||
@ -4041,7 +4045,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
|
||||
</message>
|
||||
<message>
|
||||
<source>Excluded characters: "0", "1", "l", "I", "O", "|", "﹒"</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Caracteres excluídos: "0", "1", "l", "I", "O", "|", "﹒"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Word Co&unt:</source>
|
||||
@ -4415,11 +4419,11 @@ Comandos disponíveis:
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid value for password length %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Valor inválido para o tamanho da senha %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not create entry with path %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Não foi possível criar uma entrada com o caminho %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter password for new entry: </source>
|
||||
@ -4427,15 +4431,15 @@ Comandos disponíveis:
|
||||
</message>
|
||||
<message>
|
||||
<source>Writing the database failed %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Gravação do banco de dados falhou %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Successfully added entry %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Entrada adicionada com sucesso %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy the current TOTP to the clipboard.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Copie o TOTP atual para a área de transferência.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid timeout value %1.</source>
|
||||
@ -4516,11 +4520,11 @@ Comandos disponíveis:
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Bruteforce</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Tipo: Força Bruta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Dictionary</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Tipo: Dicionário</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Dict+Leet</source>
|
||||
@ -4540,15 +4544,15 @@ Comandos disponíveis:
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Sequence</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Tipo: Sequência</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Spatial</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Tipo: Espacial</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Date</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Tipo: Data</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Type: Bruteforce(Rep)</source>
|
||||
@ -4596,11 +4600,11 @@ Comandos disponíveis:
|
||||
</message>
|
||||
<message>
|
||||
<source>*** Password length (%1) != sum of length of parts (%2) ***</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>*** Comprimento da senha (%1) != soma do comprimento das partes (%2) ***</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to load key file %1: %2</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Falha ao carregar o arquivo de chave %1: %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>File %1 does not exist.</source>
|
||||
@ -4613,16 +4617,18 @@ Comandos disponíveis:
|
||||
<message>
|
||||
<source>Error while reading the database:
|
||||
%1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Erro ao ler o banco de dados:
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error while parsing the database:
|
||||
%1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Erro ao analisar o banco de dados:
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Length of the generated password</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Comprimento da senha gerada</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use lowercase characters</source>
|
||||
@ -4646,11 +4652,11 @@ Comandos disponíveis:
|
||||
</message>
|
||||
<message>
|
||||
<source>Exclude character set</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Excluir conjunto de caracteres</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>chars</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>caracteres</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Exclude similar looking characters</source>
|
||||
@ -4666,7 +4672,7 @@ Comandos disponíveis:
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot find group %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Não foi possível encontrar o grupo %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error reading merge file:
|
||||
@ -4691,19 +4697,19 @@ Comandos disponíveis:
|
||||
</message>
|
||||
<message>
|
||||
<source>Show the entry's current TOTP.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Mostrar o TOTP atual da entrada.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ERROR: unknown attribute %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>ERRO: atributo desconhecido %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No program defined for clipboard manipulation</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Nenhum programa definido para manipulação da área de transferência</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to start program %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Não é possível iniciar o programa %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file empty</source>
|
||||
@ -5268,7 +5274,7 @@ Comandos disponíveis:
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not embed database: Could not write file (%1)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Não foi possível incorporar o banco de dados: não foi possível gravar o arquivo (%1)</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -39,7 +39,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Project Maintainers:</source>
|
||||
<translation>Projekt ansvariga:</translation>
|
||||
<translation>Projektansvariga:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Special thanks from the KeePassXC team go to debfx for creating the original KeePassX.</source>
|
||||
@ -220,7 +220,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type typing delay</source>
|
||||
<translation>Auto-skriv fördröjning</translation>
|
||||
<translation>Fördröjning för auto-skriv</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> ms</source>
|
||||
@ -765,7 +765,7 @@ Would you like to migrate your existing settings now?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Number of headers line to discard</source>
|
||||
<translation>Antal av rubrik rader att kasta bort</translation>
|
||||
<translation>Antal rubrikrader att kasta bort</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Consider '\' an escape character</source>
|
||||
@ -1258,7 +1258,7 @@ If you keep this number, your database may be too easy to crack!</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Max. history items:</source>
|
||||
<translation>Maxantal historik poster:</translation>
|
||||
<translation>Maxantal historikposter:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Max. history size:</source>
|
||||
@ -3856,7 +3856,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>Word Separator:</source>
|
||||
<translation>Ord separerare:</translation>
|
||||
<translation>Ordseparerare:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy</source>
|
||||
@ -4008,11 +4008,11 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>Word Co&unt:</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>&Antal ord:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Regenerate</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Regenerera</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -73,7 +73,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Access error for config file %1</source>
|
||||
<translation>%1 yapılandırma dosyası için erişim hatası</translation>
|
||||
<translation>Yapılandırma dosyası erişim hatası %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Icon only</source>
|
||||
|
@ -27,11 +27,11 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Debug Info</source>
|
||||
<translation>Інформація щодо зневадження</translation>
|
||||
<translation>Зневаджувальна інформація</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Include the following information whenever you report a bug:</source>
|
||||
<translation>Коли Ви повідомляєте про ваду, завжди долучайте таку інформацію:</translation>
|
||||
<translation>Повідомляючи про проблему, завжди долучайте наступну інформацію:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy to clipboard</source>
|
||||
@ -61,7 +61,7 @@
|
||||
<name>ApplicationSettingsWidget</name>
|
||||
<message>
|
||||
<source>Application Settings</source>
|
||||
<translation>Налаштування застосунку</translation>
|
||||
<translation>Налаштування програми</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>General</source>
|
||||
@ -100,7 +100,7 @@
|
||||
<name>ApplicationSettingsWidgetGeneral</name>
|
||||
<message>
|
||||
<source>Basic Settings</source>
|
||||
<translation>Базове налаштування</translation>
|
||||
<translation>Основні налаштування</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Startup</source>
|
||||
@ -116,7 +116,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Remember last key files and security dongles</source>
|
||||
<translation>Пам'ятати останні файли ключів і механізми захисту</translation>
|
||||
<translation>Пам'ятати останні файли ключів та апаратні ключі</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Load previous databases on startup</source>
|
||||
@ -172,7 +172,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>General</source>
|
||||
<translation>Загальне</translation>
|
||||
<translation>Загальні</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide toolbar (icons)</source>
|
||||
@ -1425,7 +1425,7 @@ Are you sure you want to continue without a password?</source>
|
||||
<source>The created database has no key or KDF, refusing to save it.
|
||||
This is definitely a bug, please report it to the developers.</source>
|
||||
<translation>Створене сховище не має ані ключа, ані ФОК, і тому не може бути збереженим.
|
||||
Це напевно вада у програмі. Будь ласка, повідомте про це розробникам.</translation>
|
||||
Це певно є вадою програми, будь ласка, повідомте про це розробникам.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file does not exist or is not accessible.</source>
|
||||
@ -3564,8 +3564,8 @@ We recommend you use the AppImage available on our downloads page.</source>
|
||||
<message>
|
||||
<source>NOTE: You are using a pre-release version of KeePassXC!
|
||||
Expect some bugs and minor issues, this version is not meant for production use.</source>
|
||||
<translation><b>Примітка</b>: Ви використовуєте попередню версію KeePassXC!
|
||||
Зважайте на можливість деяких вади та незначних проблем, ця версія не призначена для повсякденного користування.</translation>
|
||||
<translation><b>Примітка</b>: Ви використовуєте попередній випуск KeePassXC!
|
||||
Зважайте на ймовірні помилки та незначні проблеми, ця версія не призначена для повсякденного користування.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Check for updates on startup?</source>
|
||||
|
@ -4672,7 +4672,7 @@ Available commands:
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot find group %1.</source>
|
||||
<translation>找不到组%1。</translation>
|
||||
<translation>找不到群组%1。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error reading merge file:
|
||||
@ -4760,7 +4760,7 @@ Available commands:
|
||||
</message>
|
||||
<message>
|
||||
<source>No groups found</source>
|
||||
<translation>未找到组</translation>
|
||||
<translation>未找到群组</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Create a new database.</source>
|
||||
|
@ -54,7 +54,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Use OpenSSH for Windows instead of Pageant</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>使用 OpenSSH for Windows 而不是 Pageant</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -93,7 +93,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow style</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>遵照系統樣式</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -132,11 +132,11 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Safely save database files (may be incompatible with Dropbox, etc)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>安全儲存資料庫檔案 (可能與 Dropbox 等服務不相容)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Backup database file before saving</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>儲存資料庫檔案前先備份</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Automatically save after every change</source>
|
||||
@ -168,7 +168,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide the entry preview panel</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>隱藏預覽項目區域</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>General</source>
|
||||
@ -176,11 +176,11 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide toolbar (icons)</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>隱藏工具列 (圖示)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Minimize instead of app exit</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>以最小化取代關閉程式</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show a system tray icon</source>
|
||||
@ -220,7 +220,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type typing delay</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>自動輸入按鍵延遲</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> ms</source>
|
||||
@ -229,23 +229,23 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type start delay</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>自動輸入啟動延遲</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Check for updates at application startup</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>程式啟動時檢查更新</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Include pre-releases when checking for updates</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>檢查更新時包括預先發行版本</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Movable toolbar</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>可移動的工具列</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Button style</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>按鈕樣式</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -269,11 +269,11 @@
|
||||
</message>
|
||||
<message>
|
||||
<source> min</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>分鐘</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Forget TouchID after inactivity of</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>遺忘 TouchID 當閒置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Convenience</source>
|
||||
@ -285,7 +285,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Forget TouchID when session is locked or lid is closed</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>當工作階段鎖定或蓋上螢幕時遺忘 TouchID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Lock databases after minimizing the window</source>
|
||||
@ -293,7 +293,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-lock previously locked database after performing Auto-Type</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>自動輸入後,將原本鎖定的資料庫重新鎖定</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't require password repeat when it is visible</source>
|
||||
@ -301,15 +301,15 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't hide passwords when editing them</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>編輯時不隱藏密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't use placeholder for empty password fields</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>不於空白密碼欄位處填入替代字符</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide passwords in the entry preview panel</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>在項目預覽區內隱藏密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide entry notes by default</source>
|
||||
@ -429,11 +429,11 @@ Please select whether you want to allow access.</source>
|
||||
<name>BrowserEntrySaveDialog</name>
|
||||
<message>
|
||||
<source>KeePassXC-Browser Save Entry</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>KeePassXC-Browser 瀏覽器擴充功能儲存項目</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ok</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>確定</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
@ -442,7 +442,8 @@ Please select whether you want to allow access.</source>
|
||||
<message>
|
||||
<source>You have multiple databases open.
|
||||
Please select the correct database for saving credentials.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>您開啟了多個資料庫。
|
||||
請選擇您想要儲存憑證的資料庫。</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -589,11 +590,11 @@ Please select the correct database for saving credentials.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Tor Browser</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>&Tor 瀏覽器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>Warning</b>, the keepassxc-proxy application was not found!<br />Please check the KeePassXC installation directory or confirm the custom path in advanced options.<br />Browser integration WILL NOT WORK without the proxy application.<br />Expected Path: </source>
|
||||
<translation type="unfinished"/>
|
||||
<translation><b>警告</b>,找不到 keepassxc-proxy 應用程式!<br />請檢查 KeePassXC 安裝目錄,或在進階選項中確認自定路徑。<br />缺少此應用程式瀏覽器整合將無法運作。<br />預期的路徑: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Executable Files</source>
|
||||
@ -606,7 +607,7 @@ Please select the correct database for saving credentials.</source>
|
||||
<message>
|
||||
<source>Do not ask permission for HTTP &Basic Auth</source>
|
||||
<extracomment>An extra HTTP Basic Auth setting</extracomment>
|
||||
<translation type="unfinished"/>
|
||||
<translation>不確認 HTTP 權限</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Due to Snap sandboxing, you must run a script to enable browser integration.<br />You can obtain this script from %1</source>
|
||||
@ -618,7 +619,7 @@ Please select the correct database for saving credentials.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC-Browser is needed for the browser integration to work. <br />Download it for %1 and %2. %3</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>需要 KeePassXC-Browser 瀏覽器擴充功能才能使用瀏覽器整合。為 %1 及 %2 下載。%3</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -883,7 +884,7 @@ Would you like to migrate your existing settings now?</source>
|
||||
<name>DatabaseOpenWidget</name>
|
||||
<message>
|
||||
<source>Enter master key</source>
|
||||
<translation>輸入主金鑰</translation>
|
||||
<translation>輸入主密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Key File:</source>
|
||||
@ -972,7 +973,7 @@ Please consider generating a new key file.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Master Key</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>主密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Encryption Settings</source>
|
||||
@ -987,7 +988,7 @@ Please consider generating a new key file.</source>
|
||||
<name>DatabaseSettingsWidgetBrowser</name>
|
||||
<message>
|
||||
<source>KeePassXC-Browser settings</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>KeePassXC-Browser 瀏覽器擴充功能設定</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Disconnect all browsers</source>
|
||||
@ -995,7 +996,7 @@ Please consider generating a new key file.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Forg&et all site-specific settings on entries</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>遺忘目前項目中所有站台相關的設定 (&e)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Move KeePassHTTP attributes to KeePassXC-Browser &custom data</source>
|
||||
@ -1032,7 +1033,7 @@ This may prevent connection to the browser plugin.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnect all browsers</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>與所有瀏覽器中斷連線</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do you really want to disconnect all browsers?
|
||||
@ -1291,7 +1292,7 @@ If you keep this number, your database may be too easy to crack!</source>
|
||||
<name>DatabaseSettingsWidgetKeeShare</name>
|
||||
<message>
|
||||
<source>Sharing</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>分享</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Breadcrumb</source>
|
||||
@ -1299,11 +1300,11 @@ If you keep this number, your database may be too easy to crack!</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Type</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>種類</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Path</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>路徑</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Last Signer</source>
|
||||
@ -1311,12 +1312,12 @@ If you keep this number, your database may be too easy to crack!</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Certificates</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>憑證</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> > </source>
|
||||
<comment>Breadcrumb separator</comment>
|
||||
<translation type="unfinished"/>
|
||||
<translation>></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1335,13 +1336,15 @@ If you keep this number, your database may be too easy to crack!</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>No password set</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>沒有設定密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>WARNING! You have not set a password. Using a database without a password is strongly discouraged!
|
||||
|
||||
Are you sure you want to continue without a password?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>警告!您尚未設定密碼。誠心建議不要使用不含密碼的資料庫檔案!
|
||||
|
||||
您確定還是要在無密碼的情形下繼續?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown error</source>
|
||||
@ -1349,7 +1352,7 @@ Are you sure you want to continue without a password?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to change master key</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>更改主密碼失敗</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1403,7 +1406,7 @@ Are you sure you want to continue without a password?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database creation error</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>資料庫建立錯誤</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The created database has no key or KDF, refusing to save it.
|
||||
@ -1425,17 +1428,17 @@ This is definitely a bug, please report it to the developers.</source>
|
||||
<message>
|
||||
<source>%1 [New Database]</source>
|
||||
<comment>Database tab name modifier</comment>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 [新的資料庫]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 [Locked]</source>
|
||||
<comment>Database tab name modifier</comment>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 [已鎖定]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 [Read-only]</source>
|
||||
<comment>Database tab name modifier</comment>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 [唯讀]</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1532,7 +1535,7 @@ Do you want to merge your changes?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Lock Database?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>鎖定資料庫?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You are editing an entry. Discard changes and lock anyway?</source>
|
||||
@ -1547,7 +1550,8 @@ Save changes?</source>
|
||||
<message>
|
||||
<source>Database was modified.
|
||||
Save changes?</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>資料庫已修改。
|
||||
儲存變更?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save changes?</source>
|
||||
@ -1560,7 +1564,7 @@ Error: %1</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disable safe saves?</source>
|
||||
<translation>關閉安全存檔?</translation>
|
||||
<translation>關閉安全存檔?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC has failed to save the database multiple times. This is likely caused by file sync services holding a lock on the save file.
|
||||
@ -2565,7 +2569,7 @@ This may cause the affected plugins to malfunction.</source>
|
||||
<name>Kdbx3Reader</name>
|
||||
<message>
|
||||
<source>Unable to calculate master key</source>
|
||||
<translation>無法計算主金鑰</translation>
|
||||
<translation>無法計算主密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to issue challenge-response.</source>
|
||||
@ -2604,7 +2608,7 @@ This may cause the affected plugins to malfunction.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to calculate master key</source>
|
||||
<translation>無法計算主金鑰</translation>
|
||||
<translation>無法計算主密碼</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -2615,7 +2619,7 @@ This may cause the affected plugins to malfunction.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to calculate master key</source>
|
||||
<translation>無法計算主金鑰</translation>
|
||||
<translation>無法計算主密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid header checksum size</source>
|
||||
@ -2743,7 +2747,7 @@ This may cause the affected plugins to malfunction.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to calculate master key</source>
|
||||
<translation>無法計算主金鑰</translation>
|
||||
<translation>無法計算主密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to serialize KDF parameters variant map</source>
|
||||
@ -3010,7 +3014,7 @@ Line %2, column %3</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to calculate master key</source>
|
||||
<translation>無法計算主金鑰</translation>
|
||||
<translation>無法計算主密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wrong key or database file is corrupt.</source>
|
||||
@ -3174,17 +3178,17 @@ Line %2, column %3</source>
|
||||
<message>
|
||||
<source>Change %1</source>
|
||||
<comment>Change a key component</comment>
|
||||
<translation type="unfinished"/>
|
||||
<translation>更改%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove %1</source>
|
||||
<comment>Remove a key component</comment>
|
||||
<translation type="unfinished"/>
|
||||
<translation>移除%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 set, click to change or remove</source>
|
||||
<comment>Change or remove a key component</comment>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1已設定,點選以更改或移除</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -3214,7 +3218,9 @@ Line %2, column %3</source>
|
||||
unsupported in the future.
|
||||
|
||||
Please go to the master key settings and generate a new key file.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>你正在使用未來將不再支援的舊式金鑰檔案格式。
|
||||
|
||||
請至主密碼設定產生新的金鑰。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error loading the key file '%1'
|
||||
@ -3414,11 +3420,11 @@ This version is not meant for production use.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Donate</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>捐贈 (&D)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Report a &bug</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>回報錯誤 (&b)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>WARNING: Your Qt version may cause KeePassXC to crash with an On-Screen Keyboard!
|
||||
@ -3427,75 +3433,75 @@ We recommend you use the AppImage available on our downloads page.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Import</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>匯入 (&I)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy att&ribute...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>複製屬性 (&r)…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TOTP...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>基於時間的一次性密碼算法…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&New database...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>新增資料庫(&N)…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Create a new database</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>建立新資料庫</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Merge from database...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>與資料庫合併(&M)…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Merge from another KDBX database</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>與其他 KDBX 資料庫合併</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&New entry</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>新增項目(&N)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add a new entry</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>添加新項目</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Edit entry</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>編輯項目(&E)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View or edit entry</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>檢視或編輯項目</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&New group</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>新增群組 (&N)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add a new group</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>添加新群組</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Change master &key...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>更改主密碼(&k)…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Database settings...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>資料庫設定(&D)…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy &password</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>複製密碼(&p)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Perform &Auto-Type</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>進行自動輸入 (&A)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open &URL</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>開啟網址(&U)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePass 1 database...</source>
|
||||
@ -3515,7 +3521,7 @@ We recommend you use the AppImage available on our downloads page.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show TOTP...</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>顯示 TOTP…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show TOTP QR Code...</source>
|
||||
@ -3626,11 +3632,11 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>En&cryption Settings</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>加密設定 (&c)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Here you can adjust the database encryption settings. Don't worry, you can change them later in the database settings.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>你可以在這裡調整資料庫加密設定。別擔心,你之後還能在資料庫設定中變更。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Advanced Settings</source>
|
||||
@ -3649,18 +3655,18 @@ Expect some bugs and minor issues, this version is not meant for production use.
|
||||
</message>
|
||||
<message>
|
||||
<source>Here you can adjust the database encryption settings. Don't worry, you can change them later in the database settings.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>你可以在這裡調整資料庫加密設定。別擔心,你之後還能在資料庫設定中變更。</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>NewDatabaseWizardPageMasterKey</name>
|
||||
<message>
|
||||
<source>Database Master Key</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>資料庫主密碼</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>A master key known only to you protects your database.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>只有你知道的密碼才能保護你的資料庫。</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -5047,7 +5053,7 @@ Available commands:
|
||||
</message>
|
||||
<message>
|
||||
<source>Path</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>路徑</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Status</source>
|
||||
@ -5361,11 +5367,11 @@ Available commands:
|
||||
</message>
|
||||
<message>
|
||||
<source>Please try again later.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>請稍後再試。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Software Update</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>軟體更新</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>A new version of KeePassXC is available!</source>
|
||||
@ -5377,7 +5383,7 @@ Available commands:
|
||||
</message>
|
||||
<message>
|
||||
<source>Download it at keepassxc.org</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>在 keepassxc.org 下載</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You're up-to-date!</source>
|
||||
|
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 "${@}"
|
@ -1,5 +1,5 @@
|
||||
name: keepassxc
|
||||
version: 2.4.1
|
||||
version: 2.4.2
|
||||
grade: stable
|
||||
summary: Community-driven port of the Windows application “KeePass Password Safe”
|
||||
description: |
|
||||
@ -9,16 +9,28 @@ description: |
|
||||
confinement: strict
|
||||
base: core18
|
||||
|
||||
plugs:
|
||||
icon-themes: # fix mouse cursor theme
|
||||
plugs: # plugs for theming, font settings, cursor and to use gtk3 file chooser
|
||||
gtk-3-themes:
|
||||
interface: content
|
||||
target: $SNAP/data-dir/themes
|
||||
default-provider: gtk-common-themes:gtk-3-themes
|
||||
icon-themes:
|
||||
interface: content
|
||||
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:
|
||||
keepassxc:
|
||||
command: desktop-launch keepassxc
|
||||
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy]
|
||||
adapter: full
|
||||
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
|
||||
environment:
|
||||
DISABLE_WAYLAND: 1
|
||||
@ -68,12 +80,12 @@ parts:
|
||||
- libxtst6
|
||||
- libqt5x11extras5
|
||||
- libqt5svg5
|
||||
- libqrencode3
|
||||
- try: [libqrencode3, libqrencode4]
|
||||
- libqt5concurrent5
|
||||
- libquazip5-1
|
||||
- libusb-1.0-0
|
||||
- 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: |
|
||||
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
|
||||
@ -82,7 +94,15 @@ parts:
|
||||
stage:
|
||||
- -opt
|
||||
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:
|
||||
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
|
||||
source-subdir: qt
|
@ -16,9 +16,6 @@
|
||||
|
||||
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)
|
||||
if(NOT ZXCVBN_LIBRARIES)
|
||||
add_library(zxcvbn STATIC zxcvbn/zxcvbn.c)
|
||||
@ -27,6 +24,7 @@ if(NOT ZXCVBN_LIBRARIES)
|
||||
endif(NOT ZXCVBN_LIBRARIES)
|
||||
|
||||
set(keepassx_SOURCES
|
||||
core/Alloc.cpp
|
||||
core/AutoTypeAssociations.cpp
|
||||
core/AutoTypeMatch.cpp
|
||||
core/Compare.cpp
|
||||
@ -167,7 +165,8 @@ if(APPLE)
|
||||
core/ScreenLockListenerMac.cpp
|
||||
core/MacPasteboard.cpp
|
||||
gui/macutils/MacUtils.cpp
|
||||
gui/macutils/AppKitImpl.mm)
|
||||
gui/macutils/AppKitImpl.mm
|
||||
gui/macutils/AppKit.h)
|
||||
endif()
|
||||
if(UNIX AND NOT APPLE)
|
||||
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(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(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare")
|
||||
add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources")
|
||||
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)")
|
||||
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
||||
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
|
||||
if(APPLE)
|
||||
@ -254,8 +252,13 @@ endif()
|
||||
|
||||
if(WITH_XC_TOUCHID)
|
||||
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()
|
||||
|
||||
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})
|
||||
target_link_libraries(autotype Qt5::Core Qt5::Widgets)
|
||||
|
||||
@ -270,6 +273,7 @@ target_link_libraries(keepassx_core
|
||||
Qt5::Concurrent
|
||||
Qt5::Network
|
||||
Qt5::Widgets
|
||||
${sodium_LIBRARY_RELEASE}
|
||||
${YUBIKEY_LIBRARIES}
|
||||
${ZXCVBN_LIBRARIES}
|
||||
${ARGON2_LIBRARIES}
|
||||
@ -410,25 +414,19 @@ if(MINGW)
|
||||
|
||||
install(CODE "set(gp_tool \"objdump\")" COMPONENT Runtime)
|
||||
|
||||
include(DeployQt4)
|
||||
install_qt4_executable(${PROGNAME}.exe)
|
||||
# Deploy all 3rd party library dependencies first
|
||||
install(CODE "include(BundleUtilities)
|
||||
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${PROGNAME}.exe\" \"\" \"\")"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# install Qt5 plugins
|
||||
set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins)
|
||||
install(FILES
|
||||
${PLUGINS_DIR}/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
|
||||
${PLUGINS_DIR}/platforms/qdirect2d$<$<CONFIG:Debug>:d>.dll
|
||||
DESTINATION "platforms")
|
||||
install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$<CONFIG:Debug>:d>.dll DESTINATION "styles")
|
||||
install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$<CONFIG:Debug>:d>.dll DESTINATION "platforminputcontexts")
|
||||
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")
|
||||
# Use windeployqt.exe to setup Qt dependencies
|
||||
set(WINDEPLOYQT_MODE "--release")
|
||||
if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
|
||||
set(WINDEPLOYQT_MODE "--debug")
|
||||
endif()
|
||||
|
||||
install(CODE "execute_process(COMMAND ${WINDEPLOYQT_EXE} ${PROGNAME}.exe ${WINDEPLOYQT_MODE} WORKING_DIRECTORY \${CMAKE_INSTALL_PREFIX} OUTPUT_QUIET)"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# install CA cert chains
|
||||
install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs")
|
||||
|
@ -47,7 +47,7 @@ AutoType::AutoType(QObject* parent, bool test)
|
||||
, m_pluginLoader(new QPluginLoader(this))
|
||||
, m_plugin(nullptr)
|
||||
, m_executor(nullptr)
|
||||
, m_windowFromGlobal(0)
|
||||
, m_windowForGlobal(0)
|
||||
{
|
||||
// prevent crash when the plugin has unresolved symbols
|
||||
m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
||||
@ -90,7 +90,7 @@ void AutoType::loadPlugin(const QString& pluginPath)
|
||||
if (m_plugin) {
|
||||
if (m_plugin->isAvailable()) {
|
||||
m_executor = m_plugin->createExecutor();
|
||||
connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SIGNAL(globalShortcutTriggered()));
|
||||
connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SLOT(startGlobalAutoType()));
|
||||
} else {
|
||||
unloadPlugin();
|
||||
}
|
||||
@ -222,6 +222,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
|
||||
|
||||
Tools::wait(qMax(100, config()->get("AutoTypeStartDelay", 500).toInt()));
|
||||
|
||||
// Used only for selected entry auto-type
|
||||
if (!window) {
|
||||
window = m_plugin->activeWindow();
|
||||
}
|
||||
@ -240,6 +241,9 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||
}
|
||||
|
||||
m_windowForGlobal = 0;
|
||||
m_windowTitleForGlobal.clear();
|
||||
|
||||
// emit signal only if autotype performed correctly
|
||||
emit autotypePerformed();
|
||||
|
||||
@ -264,6 +268,13 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
|
||||
executeAutoTypeActions(entry, hideWindow, sequences.first());
|
||||
}
|
||||
|
||||
void AutoType::startGlobalAutoType()
|
||||
{
|
||||
m_windowForGlobal = m_plugin->activeWindow();
|
||||
m_windowTitleForGlobal = m_plugin->activeWindowTitle();
|
||||
emit globalAutoTypeTriggered();
|
||||
}
|
||||
|
||||
/**
|
||||
* Global Autotype entry-point function
|
||||
* Perform global Auto-Type on the active window
|
||||
@ -278,9 +289,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
||||
return;
|
||||
}
|
||||
|
||||
QString windowTitle = m_plugin->activeWindowTitle();
|
||||
|
||||
if (windowTitle.isEmpty()) {
|
||||
if (m_windowTitleForGlobal.isEmpty()) {
|
||||
m_inGlobalAutoTypeDialog.unlock();
|
||||
return;
|
||||
}
|
||||
@ -290,7 +299,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
||||
for (const auto& db : dbList) {
|
||||
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
|
||||
for (Entry* entry : dbEntries) {
|
||||
const QSet<QString> sequences = autoTypeSequences(entry, windowTitle).toSet();
|
||||
const QSet<QString> sequences = autoTypeSequences(entry, m_windowTitleForGlobal).toSet();
|
||||
for (const QString& sequence : sequences) {
|
||||
if (!sequence.isEmpty()) {
|
||||
matchList << AutoTypeMatch(entry, sequence);
|
||||
@ -304,8 +313,9 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
||||
auto* msgBox = new QMessageBox();
|
||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
msgBox->setWindowTitle(tr("Auto-Type - KeePassXC"));
|
||||
msgBox->setText(
|
||||
tr("Couldn't find an entry that matches the window title:").append("\n\n").append(windowTitle));
|
||||
msgBox->setText(tr("Couldn't find an entry that matches the window title:")
|
||||
.append("\n\n")
|
||||
.append(m_windowTitleForGlobal));
|
||||
msgBox->setIcon(QMessageBox::Information);
|
||||
msgBox->setStandardButtons(QMessageBox::Ok);
|
||||
msgBox->show();
|
||||
@ -316,10 +326,9 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
||||
m_inGlobalAutoTypeDialog.unlock();
|
||||
emit autotypeRejected();
|
||||
} else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) {
|
||||
executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence);
|
||||
executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence, m_windowForGlobal);
|
||||
m_inGlobalAutoTypeDialog.unlock();
|
||||
} else {
|
||||
m_windowFromGlobal = m_plugin->activeWindow();
|
||||
auto* selectDialog = new AutoTypeSelectDialog();
|
||||
|
||||
// connect slots, both of which must unlock the m_inGlobalAutoTypeDialog mutex
|
||||
@ -327,11 +336,12 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
||||
connect(selectDialog, SIGNAL(rejected()), SLOT(autoTypeRejectedFromGlobal()));
|
||||
|
||||
selectDialog->setMatchList(matchList);
|
||||
#if defined(Q_OS_MACOS)
|
||||
#ifdef Q_OS_MACOS
|
||||
m_plugin->raiseOwnWindow();
|
||||
Tools::wait(500);
|
||||
Tools::wait(200);
|
||||
#endif
|
||||
selectDialog->show();
|
||||
selectDialog->raise();
|
||||
// necessary when the main window is minimized
|
||||
selectDialog->activateWindow();
|
||||
}
|
||||
@ -339,8 +349,8 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
|
||||
|
||||
void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match)
|
||||
{
|
||||
m_plugin->raiseWindow(m_windowFromGlobal);
|
||||
executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal);
|
||||
m_plugin->raiseWindow(m_windowForGlobal);
|
||||
executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowForGlobal);
|
||||
|
||||
// make sure the mutex is definitely locked before we unlock it
|
||||
Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock());
|
||||
@ -353,6 +363,8 @@ void AutoType::autoTypeRejectedFromGlobal()
|
||||
// so make sure the mutex is locked before we try unlocking it
|
||||
Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock());
|
||||
m_inGlobalAutoTypeDialog.unlock();
|
||||
m_windowForGlobal = 0;
|
||||
m_windowTitleForGlobal.clear();
|
||||
|
||||
emit autotypeRejected();
|
||||
}
|
||||
|
@ -62,18 +62,19 @@ public slots:
|
||||
void raiseWindow();
|
||||
|
||||
signals:
|
||||
void globalShortcutTriggered();
|
||||
void globalAutoTypeTriggered();
|
||||
void autotypePerformed();
|
||||
void autotypeRejected();
|
||||
|
||||
private slots:
|
||||
void startGlobalAutoType();
|
||||
void performAutoTypeFromGlobal(AutoTypeMatch match);
|
||||
void autoTypeRejectedFromGlobal();
|
||||
void unloadPlugin();
|
||||
|
||||
private:
|
||||
explicit AutoType(QObject* parent = nullptr, bool test = false);
|
||||
~AutoType();
|
||||
~AutoType() override;
|
||||
void loadPlugin(const QString& pluginPath);
|
||||
void executeAutoTypeActions(const Entry* entry,
|
||||
QWidget* hideWindow = nullptr,
|
||||
@ -94,9 +95,11 @@ private:
|
||||
QPluginLoader* m_pluginLoader;
|
||||
AutoTypePlatformInterface* m_plugin;
|
||||
AutoTypeExecutor* m_executor;
|
||||
WId m_windowFromGlobal;
|
||||
static AutoType* m_instance;
|
||||
|
||||
QString m_windowTitleForGlobal;
|
||||
WId m_windowForGlobal;
|
||||
|
||||
Q_DISABLE_COPY(AutoType)
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,6 @@
|
||||
set(autotype_mac_SOURCES AutoTypeMac.cpp)
|
||||
|
||||
set(autotype_mac_mm_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/gui/macutils/AppKitImpl.mm
|
||||
${CMAKE_SOURCE_DIR}/src/gui/macutils/MacUtils.cpp)
|
||||
|
||||
add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES} ${autotype_mac_mm_SOURCES})
|
||||
add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES})
|
||||
set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
|
||||
target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
|
||||
|
||||
|
@ -68,6 +68,11 @@ AutoTypeExecutor* AutoTypePlatformTest::createExecutor()
|
||||
return new AutoTypeExecutorTest(this);
|
||||
}
|
||||
|
||||
void AutoTypePlatformTest::triggerGlobalAutoType()
|
||||
{
|
||||
emit globalShortcutTriggered();
|
||||
}
|
||||
|
||||
void AutoTypePlatformTest::setActiveWindowTitle(const QString& title)
|
||||
{
|
||||
m_activeWindowTitle = title;
|
||||
|
@ -48,6 +48,7 @@ public:
|
||||
bool raiseOwnWindow() override;
|
||||
#endif
|
||||
|
||||
void triggerGlobalAutoType() override;
|
||||
void setActiveWindowTitle(const QString& title) override;
|
||||
|
||||
QString actionChars() override;
|
||||
|
@ -26,6 +26,7 @@ public:
|
||||
virtual ~AutoTypeTestInterface()
|
||||
{
|
||||
}
|
||||
virtual void triggerGlobalAutoType() = 0;
|
||||
virtual void setActiveWindowTitle(const QString& title) = 0;
|
||||
|
||||
virtual QString actionChars() = 0;
|
||||
|
@ -94,6 +94,8 @@ private:
|
||||
QString m_publicKey;
|
||||
QString m_secretKey;
|
||||
bool m_associated;
|
||||
|
||||
friend class TestBrowser;
|
||||
};
|
||||
|
||||
#endif // BROWSERACTION_H
|
||||
|
@ -120,6 +120,7 @@ void BrowserOptionDialog::loadSettings()
|
||||
m_ui->useCustomProxy->setChecked(settings->useCustomProxy());
|
||||
m_ui->customProxyLocation->setText(settings->customProxyLocation());
|
||||
m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath());
|
||||
m_ui->allowExpiredCredentials->setChecked(settings->allowExpiredCredentials());
|
||||
m_ui->chromeSupport->setChecked(settings->chromeSupport());
|
||||
m_ui->chromiumSupport->setChecked(settings->chromiumSupport());
|
||||
m_ui->firefoxSupport->setChecked(settings->firefoxSupport());
|
||||
@ -176,6 +177,7 @@ void BrowserOptionDialog::saveSettings()
|
||||
settings->setCustomProxyLocation(m_ui->customProxyLocation->text());
|
||||
|
||||
settings->setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked());
|
||||
settings->setAllowExpiredCredentials(m_ui->allowExpiredCredentials->isChecked());
|
||||
settings->setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked());
|
||||
settings->setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked());
|
||||
settings->setHttpAuthPermission(m_ui->httpAuthPermission->isChecked());
|
||||
|
@ -219,6 +219,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="allowExpiredCredentials">
|
||||
<property name="toolTip">
|
||||
<string>Returns expired credentials. String [expired] is added to the title.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Allow returning expired credentials.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByTitle">
|
||||
<property name="text">
|
||||
|
@ -172,9 +172,9 @@ QJsonArray BrowserService::getChildrenFromGroup(Group* group)
|
||||
return groupList;
|
||||
}
|
||||
|
||||
QJsonObject BrowserService::getDatabaseGroups()
|
||||
QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer<Database>& selectedDb)
|
||||
{
|
||||
auto db = getDatabase();
|
||||
auto db = selectedDb ? selectedDb : getDatabase();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
@ -296,6 +296,7 @@ QString BrowserService::storeKey(const QString& key)
|
||||
|
||||
do {
|
||||
QInputDialog keyDialog;
|
||||
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject()));
|
||||
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
|
||||
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"
|
||||
@ -310,7 +311,7 @@ QString BrowserService::storeKey(const QString& key)
|
||||
|
||||
id = keyDialog.textValue();
|
||||
|
||||
if (ok != QDialog::Accepted || id.isEmpty()) {
|
||||
if (ok != QDialog::Accepted || id.isEmpty() || !isDatabaseOpened()) {
|
||||
hideWindow();
|
||||
return {};
|
||||
}
|
||||
@ -406,6 +407,11 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
// Ensure that database is not locked when the popup was visible
|
||||
if (!isDatabaseOpened()) {
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
// Sort results
|
||||
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
||||
|
||||
@ -447,11 +453,6 @@ void BrowserService::addEntry(const QString& id,
|
||||
return;
|
||||
}
|
||||
|
||||
auto* addEntryGroup = findCreateAddEntryGroup(db);
|
||||
if (!addEntryGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* entry = new Entry();
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle(QUrl(url).host());
|
||||
@ -459,16 +460,19 @@ void BrowserService::addEntry(const QString& id,
|
||||
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||
entry->setUsername(login);
|
||||
entry->setPassword(password);
|
||||
entry->setGroup(addEntryGroup);
|
||||
|
||||
// Select a group for the entry
|
||||
if (!group.isEmpty()) {
|
||||
if (db->rootGroup()) {
|
||||
auto selectedGroup = db->rootGroup()->findGroupByUuid(Tools::hexToUuid(groupUuid));
|
||||
if (selectedGroup && selectedGroup->name() == group) {
|
||||
if (selectedGroup) {
|
||||
entry->setGroup(selectedGroup);
|
||||
} else {
|
||||
entry->setGroup(getDefaultEntryGroup(db));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entry->setGroup(getDefaultEntryGroup(db));
|
||||
}
|
||||
|
||||
const QString host = QUrl(url).host();
|
||||
@ -760,6 +764,7 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||
|
||||
m_dialogActive = true;
|
||||
BrowserAccessControlDialog accessControlDialog;
|
||||
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
|
||||
accessControlDialog.setUrl(url);
|
||||
accessControlDialog.setItems(pwEntriesToConfirm);
|
||||
|
||||
@ -811,6 +816,10 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
||||
res["totp"] = entry->totp();
|
||||
}
|
||||
|
||||
if (entry->isExpired()) {
|
||||
res["expired"] = "true";
|
||||
}
|
||||
|
||||
if (browserSettings()->supportKphFields()) {
|
||||
const EntryAttributes* attr = entry->attributes();
|
||||
QJsonArray stringFields;
|
||||
@ -834,7 +843,7 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
|
||||
return Unknown;
|
||||
}
|
||||
if (entry->isExpired()) {
|
||||
return Denied;
|
||||
return browserSettings()->allowExpiredCredentials() ? Allowed : Denied;
|
||||
}
|
||||
if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
|
||||
return Allowed;
|
||||
@ -848,7 +857,7 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
Group* BrowserService::findCreateAddEntryGroup(const QSharedPointer<Database>& selectedDb)
|
||||
Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb)
|
||||
{
|
||||
auto db = selectedDb ? selectedDb : getDatabase();
|
||||
if (!db) {
|
||||
@ -861,7 +870,7 @@ Group* BrowserService::findCreateAddEntryGroup(const QSharedPointer<Database>& s
|
||||
}
|
||||
|
||||
const QString groupName =
|
||||
QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); // TODO: setting to decide where new keys are created
|
||||
QLatin1String(KEEPASSXCBROWSER_GROUP_NAME);
|
||||
|
||||
for (auto* g : rootGroup->groupsRecursive(true)) {
|
||||
if (g->name() == groupName && !g->isRecycled()) {
|
||||
|
@ -44,7 +44,7 @@ public:
|
||||
bool openDatabase(bool triggerUnlock);
|
||||
QString getDatabaseRootUuid();
|
||||
QString getDatabaseRecycleBinUuid();
|
||||
QJsonObject getDatabaseGroups();
|
||||
QJsonObject getDatabaseGroups(const QSharedPointer<Database>& selectedDb = {});
|
||||
QJsonObject createNewGroup(const QString& groupName);
|
||||
QString getKey(const QString& id);
|
||||
void addEntry(const QString& id,
|
||||
@ -114,7 +114,7 @@ private:
|
||||
const QString& realm);
|
||||
QJsonObject prepareEntry(const Entry* entry);
|
||||
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
|
||||
Group* findCreateAddEntryGroup(const QSharedPointer<Database>& selectedDb = {});
|
||||
Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
|
||||
int
|
||||
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const;
|
||||
bool matchUrlScheme(const QString& url);
|
||||
@ -135,6 +135,8 @@ private:
|
||||
bool m_bringToFrontRequested;
|
||||
WindowState m_prevWindowState;
|
||||
QUuid m_keepassBrowserUUID;
|
||||
|
||||
friend class TestBrowser;
|
||||
};
|
||||
|
||||
#endif // BROWSERSERVICE_H
|
||||
|
@ -194,6 +194,16 @@ void BrowserSettings::setUpdateBinaryPath(bool enabled)
|
||||
config()->set("Browser/UpdateBinaryPath", enabled);
|
||||
}
|
||||
|
||||
bool BrowserSettings::allowExpiredCredentials()
|
||||
{
|
||||
return config()->get("Browser/AllowExpiredCredentials", false).toBool();
|
||||
}
|
||||
|
||||
void BrowserSettings::setAllowExpiredCredentials(bool enabled)
|
||||
{
|
||||
config()->set("Browser/AllowExpiredCredentials", enabled);
|
||||
}
|
||||
|
||||
bool BrowserSettings::chromeSupport()
|
||||
{
|
||||
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME);
|
||||
|
@ -64,6 +64,8 @@ public:
|
||||
void setCustomProxyLocation(const QString& location);
|
||||
bool updateBinaryPath();
|
||||
void setUpdateBinaryPath(bool enabled);
|
||||
bool allowExpiredCredentials();
|
||||
void setAllowExpiredCredentials(bool enabled);
|
||||
bool chromeSupport();
|
||||
void setChromeSupport(bool enabled);
|
||||
bool chromiumSupport();
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
if(WITH_XC_BROWSER)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
find_package(sodium 1.0.12 REQUIRED)
|
||||
|
||||
set(keepassxcbrowser_SOURCES
|
||||
BrowserAccessControlDialog.cpp
|
||||
@ -33,5 +32,5 @@ if(WITH_XC_BROWSER)
|
||||
Variant.cpp)
|
||||
|
||||
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
|
||||
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium)
|
||||
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE})
|
||||
endif()
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include "NativeMessagingBase.h"
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
@ -138,7 +140,7 @@ QString NativeMessagingBase::getLocalServerPath() const
|
||||
{
|
||||
const QString serverPath = "/kpxc_server";
|
||||
#if defined(KEEPASSXC_DIST_SNAP)
|
||||
return QProcessEnvironment::systemEnvironment().value("SNAP_COMMON") + serverPath;
|
||||
return QProcessEnvironment::systemEnvironment().value("SNAP_USER_COMMON") + serverPath;
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
// Use XDG_RUNTIME_DIR instead of /tmp if it's available
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
|
||||
|
@ -84,7 +84,7 @@ int Add::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli add");
|
||||
errorTextStream << parser.helpText().replace("[options]", "add [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,7 @@ target_link_libraries(keepassxc-cli
|
||||
keepassx_core
|
||||
Qt5::Core
|
||||
${GCRYPT_LIBRARIES}
|
||||
${sodium_LIBRARY_RELEASE}
|
||||
${ARGON2_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
|
@ -63,7 +63,7 @@ int Clip::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ int Create::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() < 1) {
|
||||
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli create");
|
||||
out << parser.helpText().replace("[options]", "create [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ int Diceware::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (!args.isEmpty()) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
||||
errorTextStream << parser.helpText().replace("[options]", "diceware [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ int Diceware::execute(const QStringList& arguments)
|
||||
}
|
||||
|
||||
if (!dicewareGenerator.isValid()) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
||||
outputTextStream << parser.helpText().replace("[options]", "diceware [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ int Edit::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit");
|
||||
errorTextStream << parser.helpText().replace("[options]", "edit [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ int Estimate::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() > 1) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate");
|
||||
errorTextStream << parser.helpText().replace("[options]", "estimate [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ int Extract::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 1) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli extract");
|
||||
errorTextStream << parser.helpText().replace("[options]", "extract [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ int Generate::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (!args.isEmpty()) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
||||
errorTextStream << parser.helpText().replace("[options]", "generate [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ int Generate::execute(const QStringList& arguments)
|
||||
passwordGenerator.setExcludedChars(parser.value(exclude));
|
||||
|
||||
if (!passwordGenerator.isValid()) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
||||
errorTextStream << parser.helpText().replace("[options]", "generate [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ int List::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ int Locate::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli locate");
|
||||
errorTextStream << parser.helpText().replace("[options]", "locate [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ int Merge::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli merge");
|
||||
errorTextStream << parser.helpText().replace("[options]", "merge [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ int Remove::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm");
|
||||
errorTextStream << parser.helpText().replace("[options]", "rm [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ int Show::execute(const QStringList& arguments)
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli show");
|
||||
errorTextStream << parser.helpText().replace("[options]", "show [options]");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,9 @@
|
||||
|
||||
#include <QProcessEnvironment>
|
||||
#include <QTextCodec>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
TextStream::TextStream()
|
||||
{
|
||||
@ -59,12 +62,26 @@ void TextStream::detectCodec()
|
||||
{
|
||||
QString codecName = "UTF-8";
|
||||
auto env = QProcessEnvironment::systemEnvironment();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (!env.contains("SHELL")) {
|
||||
// native shell (no Msys or cygwin)
|
||||
WINBOOL success = false;
|
||||
#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";
|
||||
}
|
||||
#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
|
||||
|
||||
codecName = env.value("ENCODING_OVERRIDE", codecName);
|
||||
auto* codec = QTextCodec::codecForName(codecName.toLatin1());
|
||||
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();
|
||||
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
|
||||
// Don't show menu icons on OSX
|
||||
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||
|
@ -16,9 +16,12 @@
|
||||
*/
|
||||
|
||||
#include "CustomData.h"
|
||||
#include "Clock.h"
|
||||
|
||||
#include "core/Global.h"
|
||||
|
||||
const QString CustomData::LastModified = "_LAST_MODIFIED";
|
||||
|
||||
CustomData::CustomData(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
@ -60,6 +63,7 @@ void CustomData::set(const QString& key, const QString& value)
|
||||
|
||||
if (addAttribute || changeValue) {
|
||||
m_data.insert(key, value);
|
||||
updateLastModified();
|
||||
emit customDataModified();
|
||||
}
|
||||
|
||||
@ -74,6 +78,7 @@ void CustomData::remove(const QString& key)
|
||||
|
||||
m_data.remove(key);
|
||||
|
||||
updateLastModified();
|
||||
emit removed(key);
|
||||
emit customDataModified();
|
||||
}
|
||||
@ -94,6 +99,7 @@ void CustomData::rename(const QString& oldKey, const QString& newKey)
|
||||
m_data.remove(oldKey);
|
||||
m_data.insert(newKey, data);
|
||||
|
||||
updateLastModified();
|
||||
emit customDataModified();
|
||||
emit renamed(oldKey, newKey);
|
||||
}
|
||||
@ -108,9 +114,19 @@ void CustomData::copyDataFrom(const CustomData* other)
|
||||
|
||||
m_data = other->m_data;
|
||||
|
||||
updateLastModified();
|
||||
emit reset();
|
||||
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
|
||||
{
|
||||
return (m_data == other.m_data);
|
||||
@ -152,3 +168,13 @@ int CustomData::dataSize() const
|
||||
}
|
||||
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 dataSize() const;
|
||||
void copyDataFrom(const CustomData* other);
|
||||
QDateTime getLastModified() const;
|
||||
bool operator==(const CustomData& other) const;
|
||||
bool operator!=(const CustomData& other) const;
|
||||
|
||||
static const QString LastModified;
|
||||
|
||||
signals:
|
||||
void customDataModified();
|
||||
void aboutToBeAdded(const QString& key);
|
||||
@ -55,6 +58,10 @@ signals:
|
||||
void renamed(const QString& oldKey, const QString& newKey);
|
||||
void aboutToBeReset();
|
||||
void reset();
|
||||
void lastModified();
|
||||
|
||||
private slots:
|
||||
void updateLastModified();
|
||||
|
||||
private:
|
||||
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,
|
||||
// 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
|
||||
// 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;
|
||||
auto* sourceMetadata = context.m_sourceDb->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()));
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ QPixmap Metadata::customIconScaledPixmap(const QUuid& uuid) const
|
||||
QPixmapCache::Key& cacheKey = m_customIconScaledCacheKeys[uuid];
|
||||
|
||||
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);
|
||||
cacheKey = QPixmapCache::insert(pixmap);
|
||||
}
|
||||
|
@ -34,13 +34,14 @@
|
||||
*/
|
||||
void Translator::installTranslators()
|
||||
{
|
||||
QLocale locale;
|
||||
QString language = config()->get("GUI/Language").toString();
|
||||
if (language == "system" || language.isEmpty()) {
|
||||
language = QLocale::system().name();
|
||||
}
|
||||
if (language == "en") {
|
||||
if (!language.isEmpty() && language != "system") {
|
||||
// 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 = {
|
||||
@ -51,11 +52,12 @@ void Translator::installTranslators()
|
||||
|
||||
bool translationsLoaded = false;
|
||||
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)) {
|
||||
installQtTranslator("en", path);
|
||||
installQtTranslator(QLocale("en"), path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!translationsLoaded) {
|
||||
// couldn't load configured language or fallback
|
||||
qWarning("Couldn't load translations.");
|
||||
@ -114,10 +116,10 @@ QList<QPair<QString, QString>> Translator::availableLanguages()
|
||||
* @param path local search path
|
||||
* @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));
|
||||
if (translator->load(QString("keepassx_%1").arg(language), path)) {
|
||||
if (translator->load(locale, "keepassx_", "", path)) {
|
||||
return QCoreApplication::installTranslator(translator.take());
|
||||
}
|
||||
return false;
|
||||
@ -131,13 +133,12 @@ bool Translator::installTranslator(const QString& language, const QString& path)
|
||||
* @param path local search path
|
||||
* @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));
|
||||
if (qtTranslator->load(QString("qtbase_%1").arg(language), path)) {
|
||||
if (qtTranslator->load(locale, "qtbase_", "", path)) {
|
||||
return QCoreApplication::installTranslator(qtTranslator.take());
|
||||
} else if (qtTranslator->load(QString("qtbase_%1").arg(language),
|
||||
QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
|
||||
} else if (qtTranslator->load(locale, "qtbase_", "", QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
|
||||
return QCoreApplication::installTranslator(qtTranslator.take());
|
||||
}
|
||||
return false;
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QLocale>
|
||||
|
||||
class Translator
|
||||
{
|
||||
@ -28,8 +29,8 @@ public:
|
||||
static QList<QPair<QString, QString>> availableLanguages();
|
||||
|
||||
private:
|
||||
static bool installTranslator(const QString& language, const QString& path);
|
||||
static bool installQtTranslator(const QString& language, const QString& path);
|
||||
static bool installTranslator(const QLocale& locale, const QString& path);
|
||||
static bool installQtTranslator(const QLocale& locale, const QString& path);
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TRANSLATOR_H
|
||||
|
@ -35,7 +35,7 @@ Argon2Kdf::Argon2Kdf()
|
||||
, m_memory(1 << 16)
|
||||
, m_parallelism(static_cast<quint32>(QThread::idealThreadCount()))
|
||||
{
|
||||
m_rounds = 1;
|
||||
m_rounds = 10;
|
||||
}
|
||||
|
||||
quint32 Argon2Kdf::version() const
|
||||
|
@ -35,7 +35,7 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
{
|
||||
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
|
||||
|
||||
m_binaryPoolInverse.clear();
|
||||
m_binaryPool.clear();
|
||||
|
||||
if (hasError()) {
|
||||
return false;
|
||||
@ -273,11 +273,7 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
|
||||
return false;
|
||||
}
|
||||
auto data = fieldData.mid(1);
|
||||
if (m_binaryPoolInverse.contains(data)) {
|
||||
qWarning("Skipping duplicate binary record");
|
||||
break;
|
||||
}
|
||||
m_binaryPoolInverse.insert(data, QString::number(m_binaryPoolInverse.size()));
|
||||
m_binaryPool.insert(QString::number(m_binaryPool.size()), data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -422,17 +418,5 @@ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
|
||||
*/
|
||||
QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const
|
||||
{
|
||||
QHash<QString, QByteArray> binaryPool;
|
||||
for (auto it = m_binaryPoolInverse.cbegin(); it != m_binaryPoolInverse.cend(); ++it) {
|
||||
binaryPool.insert(it.value(), it.key());
|
||||
}
|
||||
return binaryPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mapping from binary data to attachment keys
|
||||
*/
|
||||
QHash<QByteArray, QString> Kdbx4Reader::binaryPoolInverse() const
|
||||
{
|
||||
return m_binaryPoolInverse;
|
||||
return m_binaryPool;
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ public:
|
||||
const QByteArray& headerData,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
Database* db) override;
|
||||
QHash<QByteArray, QString> binaryPoolInverse() const;
|
||||
QHash<QString, QByteArray> binaryPool() const;
|
||||
|
||||
protected:
|
||||
@ -44,7 +43,7 @@ private:
|
||||
bool readInnerHeaderField(QIODevice* device);
|
||||
QVariantMap readVariantMap(QIODevice* device);
|
||||
|
||||
QHash<QByteArray, QString> m_binaryPoolInverse;
|
||||
QHash<QString, QByteArray> m_binaryPool;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDBX4READER_H
|
||||
|
@ -64,6 +64,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
, m_globalAutoTypeModifiers(Qt::NoModifier)
|
||||
{
|
||||
setHeadline(tr("Application Settings"));
|
||||
showApplyButton(false);
|
||||
|
||||
m_secUi->setupUi(m_secWidget);
|
||||
m_generalUi->setupUi(m_generalWidget);
|
||||
@ -75,7 +76,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
}
|
||||
|
||||
connect(this, SIGNAL(accepted()), SLOT(saveSettings()));
|
||||
connect(this, SIGNAL(apply()), SLOT(saveSettings()));
|
||||
connect(this, SIGNAL(rejected()), SLOT(reject()));
|
||||
|
||||
// clang-format off
|
||||
|
@ -212,7 +212,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fallbackToSearch">
|
||||
<property name="text">
|
||||
<string>Use DuckDuckGo as fallback for downloading website icons</string>
|
||||
<string>Use DuckDuckGo service to download website icons</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -59,10 +59,14 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged()));
|
||||
connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
|
||||
m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*)));
|
||||
connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType()));
|
||||
connect(autoType(), SIGNAL(globalAutoTypeTriggered()), SLOT(performGlobalAutoType()));
|
||||
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
|
||||
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
|
||||
// clang-format on
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
connect(macUtils(), SIGNAL(lockDatabases()), SLOT(lockDatabases()));
|
||||
#endif
|
||||
}
|
||||
|
||||
DatabaseTabWidget::~DatabaseTabWidget()
|
||||
@ -558,7 +562,7 @@ void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget,
|
||||
#ifdef Q_OS_MACOS
|
||||
if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) {
|
||||
macUtils()->raiseOwnWindow();
|
||||
Tools::wait(500);
|
||||
Tools::wait(200);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -174,7 +174,8 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
|
||||
connect(m_previewSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(previewSplitterSizesChanged()));
|
||||
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode)));
|
||||
connect(m_previewView, SIGNAL(errorOccurred(QString)), this, SLOT(showErrorMessage(QString)));
|
||||
connect(m_previewView, SIGNAL(errorOccurred(QString)), SLOT(showErrorMessage(QString)));
|
||||
connect(m_previewView, SIGNAL(entryUrlActivated(Entry*)), SLOT(openUrlForEntry(Entry*)));
|
||||
connect(m_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged()));
|
||||
connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SLOT(onGroupChanged(Group*)));
|
||||
connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SIGNAL(groupChanged()));
|
||||
@ -190,7 +191,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
|
||||
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
|
||||
connect(m_fileWatcher.data(), SIGNAL(fileChanged()), this, SLOT(reloadDatabaseFile()));
|
||||
connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged()));
|
||||
connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged()));
|
||||
// clang-format on
|
||||
|
||||
connectDatabaseSignals();
|
||||
@ -652,6 +653,10 @@ void DatabaseWidget::openUrl()
|
||||
void DatabaseWidget::openUrlForEntry(Entry* entry)
|
||||
{
|
||||
Q_ASSERT(entry);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString cmdString = entry->resolveMultiplePlaceholders(entry->url());
|
||||
if (cmdString.startsWith("cmd://")) {
|
||||
// check if decision to execute command was stored
|
||||
@ -695,9 +700,9 @@ void DatabaseWidget::openUrlForEntry(Entry* entry)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QString urlString = entry->webUrl();
|
||||
if (!urlString.isEmpty()) {
|
||||
QDesktopServices::openUrl(urlString);
|
||||
QUrl url = QUrl(entry->url());
|
||||
if (!url.isEmpty()) {
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -782,6 +787,9 @@ void DatabaseWidget::switchToMainView(bool previousDialogAccepted)
|
||||
}
|
||||
|
||||
m_newParent = nullptr;
|
||||
} else {
|
||||
// Workaround: ensure entries are focused so search doesn't reset
|
||||
m_entryView->setFocus();
|
||||
}
|
||||
|
||||
setCurrentWidget(m_mainWidget);
|
||||
@ -1158,9 +1166,10 @@ void DatabaseWidget::onDatabaseModified()
|
||||
{
|
||||
if (!m_blockAutoSave && config()->get("AutoSaveAfterEveryChange").toBool()) {
|
||||
save();
|
||||
} else {
|
||||
// Only block once, then reset
|
||||
m_blockAutoSave = false;
|
||||
}
|
||||
|
||||
m_blockAutoSave = false;
|
||||
}
|
||||
|
||||
QString DatabaseWidget::getCurrentSearch()
|
||||
@ -1258,11 +1267,13 @@ bool DatabaseWidget::lock()
|
||||
}
|
||||
|
||||
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 (!save()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
saved = save();
|
||||
}
|
||||
|
||||
if (!saved) {
|
||||
QString msg;
|
||||
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
|
||||
msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped());
|
||||
@ -1521,11 +1532,14 @@ bool DatabaseWidget::save()
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read-only and new databases ask for filename
|
||||
if (m_db->isReadOnly() || m_db->filePath().isEmpty()) {
|
||||
return saveAs();
|
||||
}
|
||||
|
||||
// Prevent recursions and infinite save loops
|
||||
blockAutoReload(true);
|
||||
m_blockAutoSave = true;
|
||||
++m_saveAttempts;
|
||||
|
||||
// TODO: Make this async, but lock out the database widget to prevent re-entrance
|
||||
@ -1536,6 +1550,7 @@ bool DatabaseWidget::save()
|
||||
|
||||
if (ok) {
|
||||
m_saveAttempts = 0;
|
||||
m_blockAutoSave = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,8 @@ void DialogyWidget::keyPressEvent(QKeyEvent* e)
|
||||
}
|
||||
} else
|
||||
#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()) {
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
|
@ -197,8 +197,6 @@ void EditWidgetIcons::downloadFavicon()
|
||||
|
||||
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
|
||||
// searching for a match with the returned address(es).
|
||||
bool hostIsIp = false;
|
||||
@ -209,32 +207,35 @@ void EditWidgetIcons::downloadFavicon()
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the second-level domain, if available
|
||||
QString secondLevelDomain;
|
||||
if (!hostIsIp) {
|
||||
QString 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"));
|
||||
}
|
||||
secondLevelDomain = getSecondLevelDomain(m_url);
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com");
|
||||
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico");
|
||||
|
||||
m_urlsToTry.append(fallbackUrl);
|
||||
|
||||
if (!hostIsIp) {
|
||||
QString secondLevelDomain = getSecondLevelDomain(m_url);
|
||||
|
||||
if (fullyQualifiedDomain != secondLevelDomain) {
|
||||
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico");
|
||||
m_urlsToTry.append(fallbackUrl);
|
||||
}
|
||||
// Also try a direct pull of the second-level domain (if possible)
|
||||
if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) {
|
||||
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());
|
||||
#endif
|
||||
}
|
||||
@ -277,7 +278,7 @@ void EditWidgetIcons::fetchFinished()
|
||||
if (!image.isNull()) {
|
||||
if (!addCustomIcon(image)) {
|
||||
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
|
||||
emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive);
|
||||
}
|
||||
@ -289,7 +290,7 @@ void EditWidgetIcons::fetchFinished()
|
||||
if (!fallbackEnabled) {
|
||||
emit messageEditEntry(
|
||||
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);
|
||||
} else {
|
||||
emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error);
|
||||
|
@ -54,11 +54,13 @@ EntryPreviewWidget::EntryPreviewWidget(QWidget* parent)
|
||||
m_ui->entryAttachmentsWidget->setReadOnly(true);
|
||||
m_ui->entryAttachmentsWidget->setButtonsVisible(false);
|
||||
|
||||
connect(m_ui->entryUrlLabel, SIGNAL(linkActivated(QString)), SLOT(openEntryUrl()));
|
||||
|
||||
connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpWidget, SLOT(setVisible(bool)));
|
||||
connect(m_ui->entryCloseButton, SIGNAL(clicked()), SLOT(hide()));
|
||||
connect(m_ui->togglePasswordButton, SIGNAL(clicked(bool)), SLOT(setPasswordVisible(bool)));
|
||||
connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection);
|
||||
connect(&m_totpTimer, SIGNAL(timeout()), this, SLOT(updateTotpLabel()));
|
||||
connect(&m_totpTimer, SIGNAL(timeout()), SLOT(updateTotpLabel()));
|
||||
|
||||
// Group
|
||||
m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close"));
|
||||
@ -197,11 +199,12 @@ void EntryPreviewWidget::updateEntryGeneralTab()
|
||||
}
|
||||
|
||||
m_ui->entryUrlLabel->setRawText(m_currentEntry->displayUrl());
|
||||
const QString url = m_currentEntry->webUrl();
|
||||
const QString url = m_currentEntry->url();
|
||||
if (!url.isEmpty()) {
|
||||
// URL is well formed and can be opened in a browser
|
||||
m_ui->entryUrlLabel->setUrl(url);
|
||||
m_ui->entryUrlLabel->setCursor(Qt::PointingHandCursor);
|
||||
m_ui->entryUrlLabel->setOpenExternalLinks(false);
|
||||
} else {
|
||||
m_ui->entryUrlLabel->setUrl({});
|
||||
m_ui->entryUrlLabel->setCursor(Qt::ArrowCursor);
|
||||
@ -327,6 +330,13 @@ void EntryPreviewWidget::updateTabIndexes()
|
||||
m_selectedTabGroup = m_ui->groupTabWidget->currentIndex();
|
||||
}
|
||||
|
||||
void EntryPreviewWidget::openEntryUrl()
|
||||
{
|
||||
if (m_currentEntry) {
|
||||
emit entryUrlActivated(m_currentEntry);
|
||||
}
|
||||
}
|
||||
|
||||
void EntryPreviewWidget::setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled)
|
||||
{
|
||||
const int tabIndex = tabWidget->indexOf(widget);
|
||||
|
@ -43,6 +43,7 @@ public slots:
|
||||
|
||||
signals:
|
||||
void errorOccurred(const QString& error);
|
||||
void entryUrlActivated(Entry* entry);
|
||||
|
||||
private slots:
|
||||
void updateEntryHeaderLine();
|
||||
@ -63,6 +64,7 @@ private slots:
|
||||
|
||||
void updateTotpLabel();
|
||||
void updateTabIndexes();
|
||||
void openEntryUrl();
|
||||
|
||||
private:
|
||||
void setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled);
|
||||
|
@ -41,6 +41,10 @@
|
||||
#include "keys/FileKey.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "macutils/MacUtils.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_UPDATECHECK
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/UpdateCheckDialog.h"
|
||||
@ -135,6 +139,7 @@ MainWindow::MainWindow()
|
||||
, m_trayIcon(nullptr)
|
||||
, m_appExitCalled(false)
|
||||
, m_appExiting(false)
|
||||
, m_lastFocusOutTime(0)
|
||||
{
|
||||
g_MainWindow = this;
|
||||
|
||||
@ -248,6 +253,9 @@ MainWindow::MainWindow()
|
||||
m_ui->actionEntryCopyURL->setShortcutVisibleInContextMenu(true);
|
||||
#endif
|
||||
|
||||
connect(m_ui->menuEntries, SIGNAL(aboutToHide()), SLOT(releaseContextFocusLock()));
|
||||
connect(m_ui->menuGroups, SIGNAL(aboutToHide()), SLOT(releaseContextFocusLock()));
|
||||
|
||||
// Control window state
|
||||
new QShortcut(Qt::CTRL + Qt::Key_M, this, SLOT(showMinimized()));
|
||||
new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_M, this, SLOT(hideWindow()));
|
||||
@ -370,6 +378,9 @@ MainWindow::MainWindow()
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
setUnifiedTitleAndToolBarOnMac(true);
|
||||
if (macUtils()->isDarkMode()) {
|
||||
setStyleSheet("QToolButton {color:white;}");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_UPDATECHECK
|
||||
@ -396,6 +407,12 @@ MainWindow::MainWindow()
|
||||
connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock()));
|
||||
#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();
|
||||
|
||||
if (config()->hasAccessError()) {
|
||||
@ -529,8 +546,8 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
switch (mode) {
|
||||
case DatabaseWidget::Mode::ViewMode: {
|
||||
// bool inSearch = dbWidget->isInSearchMode();
|
||||
bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1 && dbWidget->currentEntryHasFocus();
|
||||
bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && dbWidget->currentEntryHasFocus();
|
||||
bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1 && (m_contextMenuFocusLock || dbWidget->currentEntryHasFocus());
|
||||
bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && (m_contextMenuFocusLock || dbWidget->currentEntryHasFocus());
|
||||
bool groupSelected = dbWidget->isGroupSelected();
|
||||
bool recycleBinSelected = dbWidget->isRecycleBinSelected();
|
||||
|
||||
@ -857,7 +874,9 @@ void MainWindow::closeEvent(QCloseEvent* event)
|
||||
return;
|
||||
}
|
||||
|
||||
if (config()->get("GUI/MinimizeOnClose").toBool() && !m_appExitCalled) {
|
||||
// Don't ignore close event when the app is hidden to tray.
|
||||
// This can occur when the OS issues close events on shutdown.
|
||||
if (config()->get("GUI/MinimizeOnClose").toBool() && !isHidden() && !m_appExitCalled) {
|
||||
event->ignore();
|
||||
hideWindow();
|
||||
return;
|
||||
@ -912,7 +931,7 @@ bool MainWindow::saveLastDatabases()
|
||||
}
|
||||
|
||||
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);
|
||||
openDatabases.append(dbWidget->database()->filePath());
|
||||
}
|
||||
@ -936,6 +955,8 @@ void MainWindow::updateTrayIcon()
|
||||
QAction* actionToggle = new QAction(tr("Toggle window"), menu);
|
||||
menu->addAction(actionToggle);
|
||||
|
||||
menu->addAction(m_ui->actionLockDatabases);
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
QAction* actionQuit = new QAction(tr("Quit KeePassXC"), menu);
|
||||
menu->addAction(actionQuit);
|
||||
@ -969,13 +990,20 @@ void MainWindow::updateTrayIcon()
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::releaseContextFocusLock()
|
||||
{
|
||||
m_contextMenuFocusLock = false;
|
||||
}
|
||||
|
||||
void MainWindow::showEntryContextMenu(const QPoint& globalPos)
|
||||
{
|
||||
m_contextMenuFocusLock = true;
|
||||
m_ui->menuEntries->popup(globalPos);
|
||||
}
|
||||
|
||||
void MainWindow::showGroupContextMenu(const QPoint& globalPos)
|
||||
{
|
||||
m_contextMenuFocusLock = true;
|
||||
m_ui->menuGroups->popup(globalPos);
|
||||
}
|
||||
|
||||
@ -1029,10 +1057,38 @@ void MainWindow::applySettingsChanges()
|
||||
updateTrayIcon();
|
||||
}
|
||||
|
||||
void MainWindow::focusWindowChanged(QWindow* focusWindow)
|
||||
{
|
||||
if (focusWindow != windowHandle()) {
|
||||
m_lastFocusOutTime = Clock::currentSecondsSinceEpoch();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
} 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 showUpdateCheckStartup();
|
||||
void showUpdateCheckDialog();
|
||||
void focusWindowChanged(QWindow* focusWindow);
|
||||
void hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested);
|
||||
void openDonateUrl();
|
||||
void openBugReportUrl();
|
||||
@ -107,6 +108,7 @@ private slots:
|
||||
void showGroupContextMenu(const QPoint& globalPos);
|
||||
void applySettingsChanges();
|
||||
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
|
||||
void processTrayIconTrigger();
|
||||
void lockDatabasesAfterInactivity();
|
||||
void forgetTouchIDAfterInactivity();
|
||||
void handleScreenLock();
|
||||
@ -115,6 +117,7 @@ private slots:
|
||||
void selectPreviousDatabaseTab();
|
||||
void togglePasswordsHidden();
|
||||
void toggleUsernamesHidden();
|
||||
void releaseContextFocusLock();
|
||||
|
||||
private:
|
||||
static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0);
|
||||
@ -146,6 +149,10 @@ private:
|
||||
|
||||
bool m_appExitCalled;
|
||||
bool m_appExiting;
|
||||
bool m_contextMenuFocusLock;
|
||||
uint m_lastFocusOutTime;
|
||||
QTimer m_trayIconTriggerTimer;
|
||||
QSystemTrayIcon::ActivationReason m_trayIconTriggerReason;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -16,9 +16,6 @@
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">#SearchHelpWidget { background-color: #ffffff }</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Box</enum>
|
||||
</property>
|
||||
|
@ -152,11 +152,6 @@ void EditEntryWidget::setupMain()
|
||||
m_mainUi->expirePresets->setMenu(createPresetsMenu());
|
||||
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->reset();
|
||||
}
|
||||
@ -285,7 +280,6 @@ void EditEntryWidget::setupEntryUpdate()
|
||||
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
|
||||
#endif
|
||||
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->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
|
||||
|
||||
@ -968,6 +962,7 @@ void EditEntryWidget::cancel()
|
||||
m_entry->setIcon(Entry::DefaultIconNumber);
|
||||
}
|
||||
|
||||
bool accepted = false;
|
||||
if (isModified()) {
|
||||
auto result = MessageBox::question(this,
|
||||
QString(),
|
||||
@ -975,18 +970,17 @@ void EditEntryWidget::cancel()
|
||||
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
|
||||
MessageBox::Cancel);
|
||||
if (result == MessageBox::Cancel) {
|
||||
m_mainUi->passwordGenerator->reset();
|
||||
return;
|
||||
}
|
||||
if (result == MessageBox::Save) {
|
||||
commitEntry();
|
||||
setModified(false);
|
||||
} else if (result == MessageBox::Save) {
|
||||
accepted = true;
|
||||
if (!commitEntry()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
emit editFinished(!isModified());
|
||||
emit editFinished(accepted);
|
||||
}
|
||||
|
||||
void EditEntryWidget::clear()
|
||||
@ -1111,8 +1105,9 @@ void EditEntryWidget::updateCurrentAttribute()
|
||||
|
||||
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->attributesEdit->blockSignals(true);
|
||||
|
||||
if (index.isValid()) {
|
||||
QString key = m_attributesModel->keyByIndex(index);
|
||||
@ -1143,6 +1138,7 @@ void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
|
||||
}
|
||||
|
||||
m_advancedUi->protectAttributeButton->blockSignals(false);
|
||||
m_advancedUi->attributesEdit->blockSignals(false);
|
||||
}
|
||||
|
||||
void EditEntryWidget::protectCurrentAttribute(bool state)
|
||||
|
@ -7,9 +7,11 @@
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMimeData>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "EntryAttachmentsModel.h"
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/EntryAttachments.h"
|
||||
#include "core/Tools.h"
|
||||
@ -324,7 +326,12 @@ bool EntryAttachmentsWidget::openAttachment(const QModelIndex& index, QString& e
|
||||
const QByteArray attachmentData = m_entryAttachments->value(filename);
|
||||
|
||||
// tmp file will be removed once the database (or the application) has been closed
|
||||
#ifdef KEEPASSXC_DIST_SNAP
|
||||
const QString tmpFileTemplate =
|
||||
QString("%1/XXXXXX.%2").arg(QProcessEnvironment::systemEnvironment().value("SNAP_USER_DATA"), filename);
|
||||
#else
|
||||
const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename));
|
||||
#endif
|
||||
|
||||
QScopedPointer<QTemporaryFile> tmpFile(new QTemporaryFile(tmpFileTemplate, this));
|
||||
|
||||
|
@ -139,17 +139,18 @@ void EntryView::keyPressEvent(QKeyEvent* event)
|
||||
}
|
||||
|
||||
int last = m_model->rowCount() - 1;
|
||||
if (last > 0) {
|
||||
if (event->key() == Qt::Key_Up && currentIndex().row() == 0) {
|
||||
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(last, 0));
|
||||
setCurrentEntry(m_model->entryFromIndex(index));
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->key() == Qt::Key_Up && currentIndex().row() == 0) {
|
||||
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(last, 0));
|
||||
setCurrentEntry(m_model->entryFromIndex(index));
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->key() == Qt::Key_Down && currentIndex().row() == last) {
|
||||
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0));
|
||||
setCurrentEntry(m_model->entryFromIndex(index));
|
||||
return;
|
||||
if (event->key() == Qt::Key_Down && currentIndex().row() == last) {
|
||||
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0));
|
||||
setCurrentEntry(m_model->entryFromIndex(index));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QTreeView::keyPressEvent(event);
|
||||
|
@ -227,6 +227,9 @@ void EditGroupWidget::cancel()
|
||||
tr("Entry has unsaved changes"),
|
||||
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
|
||||
MessageBox::Cancel);
|
||||
if (result == MessageBox::Cancel) {
|
||||
return;
|
||||
}
|
||||
if (result == MessageBox::Save) {
|
||||
apply();
|
||||
setModified(false);
|
||||
|
@ -72,6 +72,12 @@ void GroupView::dragMoveEvent(QDragMoveEvent* event)
|
||||
}
|
||||
}
|
||||
|
||||
void GroupView::focusInEvent(QFocusEvent* event)
|
||||
{
|
||||
emitGroupChanged();
|
||||
QTreeView::focusInEvent(event);
|
||||
}
|
||||
|
||||
Group* GroupView::currentGroup()
|
||||
{
|
||||
if (currentIndex() == QModelIndex()) {
|
||||
|
@ -47,6 +47,7 @@ private slots:
|
||||
|
||||
protected:
|
||||
void dragMoveEvent(QDragMoveEvent* event) override;
|
||||
void focusInEvent(QFocusEvent* event) override;
|
||||
|
||||
private:
|
||||
void recInitExpanded(Group* group);
|
||||
|
@ -19,14 +19,15 @@
|
||||
#ifndef KEEPASSX_APPKIT_H
|
||||
#define KEEPASSX_APPKIT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <unistd.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
class AppKit
|
||||
class AppKit : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AppKit();
|
||||
AppKit(QObject* parent = nullptr);
|
||||
~AppKit();
|
||||
|
||||
pid_t lastActiveProcessId();
|
||||
@ -37,10 +38,11 @@ public:
|
||||
bool isHidden(pid_t pid);
|
||||
bool isDarkMode();
|
||||
|
||||
signals:
|
||||
void lockDatabases();
|
||||
|
||||
private:
|
||||
void *self;
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // KEEPASSX_APPKIT_H
|
||||
|
@ -22,6 +22,10 @@
|
||||
#import <AppKit/NSRunningApplication.h>
|
||||
|
||||
@interface AppKitImpl : NSObject
|
||||
{
|
||||
AppKit *m_appkit;
|
||||
}
|
||||
- (id) initWithObject:(AppKit *)appkit;
|
||||
|
||||
@property (strong) NSRunningApplication *lastActiveApplication;
|
||||
|
||||
@ -31,5 +35,6 @@
|
||||
- (bool) hideProcess:(pid_t) pid;
|
||||
- (bool) isHidden:(pid_t) pid;
|
||||
- (bool) isDarkMode;
|
||||
- (void) userSwitchHandler:(NSNotification*) notification;
|
||||
|
||||
@end
|
||||
|
@ -22,19 +22,22 @@
|
||||
|
||||
@implementation AppKitImpl
|
||||
|
||||
AppKit::AppKit()
|
||||
- (id) initWithObject:(AppKit *)appkit
|
||||
{
|
||||
self = [[AppKitImpl alloc] init];
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
|
||||
self = [super init];
|
||||
if (self) {
|
||||
m_appkit = appkit;
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
|
||||
selector:@selector(didDeactivateApplicationObserver:)
|
||||
name:NSWorkspaceDidDeactivateApplicationNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
AppKit::~AppKit()
|
||||
{
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)];
|
||||
[static_cast<id>(self) dealloc];
|
||||
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
|
||||
selector:@selector(userSwitchHandler:)
|
||||
name:NSWorkspaceSessionDidResignActiveNotification
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
//
|
||||
@ -104,10 +107,34 @@ AppKit::~AppKit()
|
||||
&& 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 -------------------------
|
||||
//
|
||||
|
||||
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()
|
||||
{
|
||||
return [static_cast<id>(self) lastActiveApplication].processIdentifier;
|
||||
@ -142,5 +169,3 @@ bool AppKit::isDarkMode()
|
||||
{
|
||||
return [static_cast<id>(self) isDarkMode];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -24,7 +24,7 @@ MacUtils* MacUtils::m_instance = nullptr;
|
||||
MacUtils::MacUtils(QObject* parent) : QObject(parent)
|
||||
, m_appkit(new AppKit())
|
||||
{
|
||||
|
||||
connect(m_appkit.data(), SIGNAL(lockDatabases()), SIGNAL(lockDatabases()));
|
||||
}
|
||||
|
||||
MacUtils::~MacUtils()
|
||||
|
@ -39,14 +39,16 @@ public:
|
||||
bool isHidden();
|
||||
bool isDarkMode();
|
||||
|
||||
signals:
|
||||
void lockDatabases();
|
||||
|
||||
private:
|
||||
explicit MacUtils(QObject* parent = nullptr);
|
||||
~MacUtils();
|
||||
|
||||
private:
|
||||
std::unique_ptr<AppKit> m_appkit;
|
||||
QScopedPointer<AppKit> m_appkit;
|
||||
static MacUtils* m_instance;
|
||||
void* self;
|
||||
|
||||
Q_DISABLE_COPY(MacUtils)
|
||||
};
|
||||
|
@ -1,4 +1,6 @@
|
||||
if(WITH_XC_KEESHARE)
|
||||
set(WITH_XC_KEESHARE_INSECURE ON PARENT_SCOPE)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(keeshare_SOURCES
|
||||
@ -15,9 +17,19 @@ if(WITH_XC_KEESHARE)
|
||||
)
|
||||
|
||||
add_library(keeshare STATIC ${keeshare_SOURCES})
|
||||
if(WITH_XC_KEESHARE_SECURE)
|
||||
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB})
|
||||
target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${GCRYPT_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()
|
||||
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()
|
||||
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>
|
||||
|
||||
#if defined(WITH_XC_KEESHARE_SECURE)
|
||||
#include <quazip5/quazip.h>
|
||||
#include <quazip5/quazipfile.h>
|
||||
#include <quazip.h>
|
||||
#include <quazipfile.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
|
@ -18,19 +18,35 @@
|
||||
|
||||
#include "FileKey.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/CryptoHash.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");
|
||||
|
||||
constexpr int FileKey::SHA256_SIZE;
|
||||
|
||||
FileKey::FileKey()
|
||||
: 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.
|
||||
*
|
||||
@ -148,7 +164,10 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
|
||||
*/
|
||||
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()) {
|
||||
m_key = data;
|
||||
return true;
|
||||
std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
|
||||
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) {
|
||||
return false;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@ -321,12 +344,15 @@ bool FileKey::loadHex(QIODevice* device)
|
||||
}
|
||||
|
||||
QByteArray key = QByteArray::fromHex(data);
|
||||
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
|
||||
|
||||
if (key.size() != 32) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -348,7 +374,9 @@ bool FileKey::loadHashed(QIODevice* device)
|
||||
cryptoHash.addData(buffer);
|
||||
} 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;
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
};
|
||||
|
||||
FileKey();
|
||||
~FileKey() override;
|
||||
bool load(QIODevice* device);
|
||||
bool load(const QString& fileName, QString* errorMsg = nullptr);
|
||||
QByteArray rawKey() const override;
|
||||
@ -48,6 +49,8 @@ public:
|
||||
static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128);
|
||||
|
||||
private:
|
||||
static constexpr int SHA256_SIZE = 32;
|
||||
|
||||
bool loadXml(QIODevice* device);
|
||||
bool loadXmlMeta(QXmlStreamReader& xmlReader);
|
||||
QByteArray loadXmlKey(QXmlStreamReader& xmlReader);
|
||||
@ -55,7 +58,7 @@ private:
|
||||
bool loadHex(QIODevice* device);
|
||||
bool loadHashed(QIODevice* device);
|
||||
|
||||
QByteArray m_key;
|
||||
char* m_key = nullptr;
|
||||
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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -16,35 +16,51 @@
|
||||
*/
|
||||
|
||||
#include "PasswordKey.h"
|
||||
#include "core/Tools.h"
|
||||
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include <gcrypt.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
|
||||
|
||||
constexpr int PasswordKey::SHA256_SIZE;
|
||||
|
||||
PasswordKey::PasswordKey()
|
||||
: Key(UUID)
|
||||
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
|
||||
{
|
||||
}
|
||||
|
||||
PasswordKey::PasswordKey(const QString& password)
|
||||
: Key(UUID)
|
||||
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
|
||||
{
|
||||
setPassword(password);
|
||||
}
|
||||
|
||||
PasswordKey::~PasswordKey()
|
||||
{
|
||||
if (m_key) {
|
||||
gcry_free(m_key);
|
||||
m_key = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
|
||||
{
|
||||
auto result = QSharedPointer<PasswordKey>::create();
|
||||
result->m_key = rawKey;
|
||||
std::memcpy(result->m_key, rawKey.data(), std::min(SHA256_SIZE, rawKey.size()));
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray PasswordKey::rawKey() const
|
||||
{
|
||||
return m_key;
|
||||
return QByteArray::fromRawData(m_key, SHA256_SIZE);
|
||||
}
|
||||
|
||||
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();
|
||||
explicit PasswordKey(const QString& password);
|
||||
~PasswordKey() override;
|
||||
QByteArray rawKey() const override;
|
||||
void setPassword(const QString& password);
|
||||
|
||||
static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey);
|
||||
|
||||
private:
|
||||
QByteArray m_key;
|
||||
static constexpr int SHA256_SIZE = 32;
|
||||
|
||||
char* m_key = nullptr;
|
||||
};
|
||||
|
||||
#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) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -32,6 +32,10 @@
|
||||
#include <QXmlStreamReader>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <gcrypt.h>
|
||||
#include <sodium.h>
|
||||
#include <cstring>
|
||||
|
||||
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
|
||||
|
||||
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
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
QByteArray key;
|
||||
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) {
|
||||
emit userConfirmed();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -32,6 +32,7 @@ public:
|
||||
static QUuid UUID;
|
||||
|
||||
explicit YkChallengeResponseKey(int slot = -1, bool blocking = false);
|
||||
~YkChallengeResponseKey() override;
|
||||
|
||||
QByteArray rawKey() const override;
|
||||
bool challenge(const QByteArray& challenge) override;
|
||||
@ -52,7 +53,8 @@ signals:
|
||||
void userConfirmed();
|
||||
|
||||
private:
|
||||
QByteArray m_key;
|
||||
char* m_key = nullptr;
|
||||
std::size_t m_keySize = 0;
|
||||
int m_slot;
|
||||
bool m_blocking;
|
||||
};
|
||||
|
@ -18,12 +18,13 @@ if(WITH_XC_BROWSER)
|
||||
include_directories(${BROWSER_SOURCE_DIR})
|
||||
|
||||
set(proxy_SOURCES
|
||||
../core/Alloc.cpp
|
||||
keepassxc-proxy.cpp
|
||||
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
|
||||
NativeMessagingHost.cpp)
|
||||
|
||||
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)
|
||||
target_link_libraries(keepassxc-proxy proxy)
|
||||
|
||||
|
@ -128,9 +128,9 @@ QString Totp::writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
||||
auto urlstring = QString("otpauth://totp/%1:%2?secret=%3&period=%4&digits=%5&issuer=%1")
|
||||
.arg(title.isEmpty() ? "KeePassXC" : QString(QUrl::toPercentEncoding(title)),
|
||||
username.isEmpty() ? "none" : QString(QUrl::toPercentEncoding(username)),
|
||||
QString(Base32::sanitizeInput(settings->key.toLatin1())))
|
||||
.arg(settings->step)
|
||||
.arg(settings->digits);
|
||||
QString(Base32::sanitizeInput(settings->key.toLatin1())),
|
||||
QString::number(settings->step),
|
||||
QString::number(settings->digits));
|
||||
|
||||
if (!settings->encoder.name.isEmpty()) {
|
||||
urlstring.append("&encoder=").append(settings->encoder.name);
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
inline void debug(const char* message, ...)
|
||||
{
|
||||
Q_UNUSED(message);
|
||||
// qWarning(...);
|
||||
}
|
||||
|
||||
@ -258,6 +259,7 @@ bool TouchID::authenticate(const QString& message) const
|
||||
NSString* authMessage = msg.toNSString(); // autoreleased
|
||||
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
|
||||
localizedReason:authMessage reply:^(BOOL success, NSError* error) {
|
||||
Q_UNUSED(error);
|
||||
result = success ? kTouchIDResultAllowed : kTouchIDResultFailed;
|
||||
CFRunLoopWakeUp(CFRunLoopGetCurrent());
|
||||
}];
|
||||
|
@ -220,6 +220,11 @@ add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
|
||||
add_unit_test(NAME testtools SOURCES TestTools.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
if(WITH_XC_BROWSER)
|
||||
add_unit_test(NAME testbrowser SOURCES TestBrowser.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
endif()
|
||||
|
||||
|
||||
if(WITH_GUI_TESTS)
|
||||
# CLI clip tests need X environment on Linux
|
||||
|
@ -157,6 +157,7 @@ void TestAutoType::testGlobalAutoTypeWithNoMatch()
|
||||
void TestAutoType::testGlobalAutoTypeWithOneMatch()
|
||||
{
|
||||
m_test->setActiveWindowTitle("custom window");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
|
||||
QCOMPARE(m_test->actionChars(), QString("%1association%2").arg(m_entry1->username()).arg(m_entry1->password()));
|
||||
@ -167,6 +168,7 @@ void TestAutoType::testGlobalAutoTypeTitleMatch()
|
||||
config()->set("AutoTypeEntryTitleMatch", true);
|
||||
|
||||
m_test->setActiveWindowTitle("An Entry Title!");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
|
||||
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter)));
|
||||
@ -177,6 +179,7 @@ void TestAutoType::testGlobalAutoTypeUrlMatch()
|
||||
config()->set("AutoTypeEntryTitleMatch", true);
|
||||
|
||||
m_test->setActiveWindowTitle("Dummy - http://example.org/ - <My Browser>");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
|
||||
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter)));
|
||||
@ -187,6 +190,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch()
|
||||
config()->set("AutoTypeEntryTitleMatch", true);
|
||||
|
||||
m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - <My Browser>");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
|
||||
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter)));
|
||||
@ -195,6 +199,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch()
|
||||
void TestAutoType::testGlobalAutoTypeTitleMatchDisabled()
|
||||
{
|
||||
m_test->setActiveWindowTitle("An Entry Title!");
|
||||
m_test->triggerGlobalAutoType();
|
||||
MessageBox::setNextAnswer(MessageBox::Ok);
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
|
||||
@ -205,58 +210,68 @@ void TestAutoType::testGlobalAutoTypeRegExp()
|
||||
{
|
||||
// substring matches are ok
|
||||
m_test->setActiveWindowTitle("lorem REGEX1 ipsum");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("regex1"));
|
||||
m_test->clearActions();
|
||||
|
||||
// should be case-insensitive
|
||||
m_test->setActiveWindowTitle("lorem regex1 ipsum");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("regex1"));
|
||||
m_test->clearActions();
|
||||
|
||||
// exact match
|
||||
m_test->setActiveWindowTitle("REGEX2");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("regex2"));
|
||||
m_test->clearActions();
|
||||
|
||||
// a bit more complicated regex
|
||||
m_test->setActiveWindowTitle("REGEX3-R2D2");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("regex3"));
|
||||
m_test->clearActions();
|
||||
|
||||
// with custom attributes
|
||||
m_test->setActiveWindowTitle("CustomAttr1");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute"));
|
||||
m_test->clearActions();
|
||||
|
||||
// with (non uppercase) undefined custom attributes
|
||||
m_test->setActiveWindowTitle("CustomAttr2");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString(""));
|
||||
m_test->clearActions();
|
||||
|
||||
// with mixedcase default attributes
|
||||
m_test->setActiveWindowTitle("CustomAttr3");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("custom_attr"));
|
||||
m_test->clearActions();
|
||||
|
||||
// with resolve placeholders in window association title
|
||||
m_test->setActiveWindowTitle("AttrValueFirst");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("custom_attr_first"));
|
||||
m_test->clearActions();
|
||||
|
||||
m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second"));
|
||||
m_test->clearActions();
|
||||
|
||||
m_test->setActiveWindowTitle("lorem AttrValueThird ipsum");
|
||||
m_test->triggerGlobalAutoType();
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("custom_attr_third"));
|
||||
m_test->clearActions();
|
||||
|
344
tests/TestBrowser.cpp
Normal file
344
tests/TestBrowser.cpp
Normal file
@ -0,0 +1,344 @@
|
||||
/*
|
||||
* 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 "TestBrowser.h"
|
||||
#include "TestGlobal.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "sodium/crypto_box.h"
|
||||
#include "browser/BrowserSettings.h"
|
||||
#include <QString>
|
||||
|
||||
QTEST_GUILESS_MAIN(TestBrowser)
|
||||
|
||||
const QString PUBLICKEY = "UIIPObeoya1G8g1M5omgyoPR/j1mR1HlYHu0wHCgMhA=";
|
||||
const QString SECRETKEY = "B8ei4ZjQJkWzZU2SK/tBsrYRwp+6ztEMf5GFQV+i0yI=";
|
||||
const QString SERVERPUBLICKEY = "lKnbLhrVCOqzEjuNoUz1xj9EZlz8xeO4miZBvLrUPVQ=";
|
||||
const QString SERVERSECRETKEY = "tbPQcghxfOgbmsnEqG2qMIj1W2+nh+lOJcNsHncaz1Q=";
|
||||
const QString NONCE = "zBKdvTjL5bgWaKMCTut/8soM/uoMrFoZ";
|
||||
const QString CLIENTID = "testClient";
|
||||
|
||||
void TestBrowser::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
m_browserService.reset(new BrowserService(nullptr));
|
||||
m_browserAction.reset(new BrowserAction(*m_browserService.data()));
|
||||
}
|
||||
|
||||
void TestBrowser::cleanupTestCase()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for BrowserAction
|
||||
*/
|
||||
|
||||
void TestBrowser::testChangePublicKeys()
|
||||
{
|
||||
QJsonObject json;
|
||||
json["action"] = "change-public-keys";
|
||||
json["publicKey"] = PUBLICKEY;
|
||||
json["nonce"] = NONCE;
|
||||
|
||||
auto response = m_browserAction->handleAction(json);
|
||||
QCOMPARE(response["action"].toString(), QString("change-public-keys"));
|
||||
QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
|
||||
QCOMPARE(response["success"].toString(), QString("true"));
|
||||
}
|
||||
|
||||
void TestBrowser::testEncryptMessage()
|
||||
{
|
||||
QJsonObject message;
|
||||
message["action"] = "test-action";
|
||||
|
||||
m_browserAction->m_publicKey = SERVERPUBLICKEY;
|
||||
m_browserAction->m_secretKey = SERVERSECRETKEY;
|
||||
m_browserAction->m_clientPublicKey = PUBLICKEY;
|
||||
auto encrypted = m_browserAction->encryptMessage(message, NONCE);
|
||||
|
||||
QCOMPARE(encrypted, QString("+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP"));
|
||||
}
|
||||
|
||||
void TestBrowser::testDecryptMessage()
|
||||
{
|
||||
QString message = "+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP";
|
||||
m_browserAction->m_publicKey = SERVERPUBLICKEY;
|
||||
m_browserAction->m_secretKey = SERVERSECRETKEY;
|
||||
m_browserAction->m_clientPublicKey = PUBLICKEY;
|
||||
auto decrypted = m_browserAction->decryptMessage(message, NONCE);
|
||||
|
||||
QCOMPARE(decrypted["action"].toString(), QString("test-action"));
|
||||
}
|
||||
|
||||
void TestBrowser::testGetBase64FromKey()
|
||||
{
|
||||
unsigned char pk[crypto_box_PUBLICKEYBYTES];
|
||||
|
||||
for (unsigned int i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) {
|
||||
pk[i] = i;
|
||||
}
|
||||
|
||||
auto response = m_browserAction->getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
|
||||
QCOMPARE(response, QString("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="));
|
||||
}
|
||||
|
||||
void TestBrowser::testIncrementNonce()
|
||||
{
|
||||
auto result = m_browserAction->incrementNonce(NONCE);
|
||||
QCOMPARE(result, QString("zRKdvTjL5bgWaKMCTut/8soM/uoMrFoZ"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for BrowserService
|
||||
*/
|
||||
void TestBrowser::testBaseDomain()
|
||||
{
|
||||
QString url1 = "https://another.example.co.uk";
|
||||
QString url2 = "https://www.example.com";
|
||||
QString url3 = "http://test.net";
|
||||
QString url4 = "http://so.many.subdomains.co.jp";
|
||||
|
||||
QString res1 = m_browserService->baseDomain(url1);
|
||||
QString res2 = m_browserService->baseDomain(url2);
|
||||
QString res3 = m_browserService->baseDomain(url3);
|
||||
QString res4 = m_browserService->baseDomain(url4);
|
||||
|
||||
QCOMPARE(res1, QString("example.co.uk"));
|
||||
QCOMPARE(res2, QString("example.com"));
|
||||
QCOMPARE(res3, QString("test.net"));
|
||||
QCOMPARE(res4, QString("subdomains.co.jp"));
|
||||
}
|
||||
|
||||
void TestBrowser::testSortPriority()
|
||||
{
|
||||
QString host = "github.com";
|
||||
QString submitUrl = "https://github.com/session";
|
||||
QString baseSubmitUrl = "https://github.com";
|
||||
|
||||
QScopedPointer<Entry> entry1(new Entry());
|
||||
QScopedPointer<Entry> entry2(new Entry());
|
||||
QScopedPointer<Entry> entry3(new Entry());
|
||||
QScopedPointer<Entry> entry4(new Entry());
|
||||
QScopedPointer<Entry> entry5(new Entry());
|
||||
QScopedPointer<Entry> entry6(new Entry());
|
||||
QScopedPointer<Entry> entry7(new Entry());
|
||||
QScopedPointer<Entry> entry8(new Entry());
|
||||
QScopedPointer<Entry> entry9(new Entry());
|
||||
QScopedPointer<Entry> entry10(new Entry());
|
||||
|
||||
entry1->setUrl("https://github.com/login");
|
||||
entry2->setUrl("https://github.com/login");
|
||||
entry3->setUrl("https://github.com/");
|
||||
entry4->setUrl("github.com/login");
|
||||
entry5->setUrl("http://github.com");
|
||||
entry6->setUrl("http://github.com/login");
|
||||
entry7->setUrl("github.com");
|
||||
entry8->setUrl("github.com/login");
|
||||
entry9->setUrl("https://github");
|
||||
entry10->setUrl("github.com");
|
||||
|
||||
// The extension uses the submitUrl as default for comparison
|
||||
auto res1 = m_browserService->sortPriority(entry1.data(), host, "https://github.com/login", baseSubmitUrl);
|
||||
auto res2 = m_browserService->sortPriority(entry2.data(), host, submitUrl, baseSubmitUrl);
|
||||
auto res3 = m_browserService->sortPriority(entry3.data(), host, submitUrl, baseSubmitUrl);
|
||||
auto res4 = m_browserService->sortPriority(entry4.data(), host, submitUrl, baseSubmitUrl);
|
||||
auto res5 = m_browserService->sortPriority(entry5.data(), host, submitUrl, baseSubmitUrl);
|
||||
auto res6 = m_browserService->sortPriority(entry6.data(), host, submitUrl, baseSubmitUrl);
|
||||
auto res7 = m_browserService->sortPriority(entry7.data(), host, submitUrl, baseSubmitUrl);
|
||||
auto res8 = m_browserService->sortPriority(entry8.data(), host, submitUrl, baseSubmitUrl);
|
||||
auto res9 = m_browserService->sortPriority(entry9.data(), host, submitUrl, baseSubmitUrl);
|
||||
auto res10 = m_browserService->sortPriority(entry10.data(), host, submitUrl, baseSubmitUrl);
|
||||
|
||||
QCOMPARE(res1, 100);
|
||||
QCOMPARE(res2, 40);
|
||||
QCOMPARE(res3, 90);
|
||||
QCOMPARE(res4, 0);
|
||||
QCOMPARE(res5, 0);
|
||||
QCOMPARE(res6, 0);
|
||||
QCOMPARE(res7, 0);
|
||||
QCOMPARE(res8, 0);
|
||||
QCOMPARE(res9, 90);
|
||||
QCOMPARE(res10, 0);
|
||||
}
|
||||
|
||||
void TestBrowser::testSearchEntries()
|
||||
{
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
auto* root = db->rootGroup();
|
||||
|
||||
QList<QString> urls;
|
||||
urls.push_back("https://github.com/login_page");
|
||||
urls.push_back("https://github.com/login");
|
||||
urls.push_back("https://github.com/");
|
||||
urls.push_back("github.com/login");
|
||||
urls.push_back("http://github.com");
|
||||
urls.push_back("http://github.com/login");
|
||||
urls.push_back("github.com");
|
||||
urls.push_back("github.com/login");
|
||||
urls.push_back("https://github");
|
||||
urls.push_back("github.com");
|
||||
|
||||
for (int i = 0; i < urls.length(); ++i) {
|
||||
auto entry = new Entry();
|
||||
entry->setGroup(root);
|
||||
entry->beginUpdate();
|
||||
entry->setUrl(urls[i]);
|
||||
entry->setUsername(QString("User %1").arg(i));
|
||||
entry->endUpdate();
|
||||
}
|
||||
|
||||
browserSettings()->setMatchUrlScheme(false);
|
||||
auto result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
|
||||
QCOMPARE(result.length(), 7);
|
||||
QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
|
||||
QCOMPARE(result[1]->url(), QString("https://github.com/login"));
|
||||
QCOMPARE(result[2]->url(), QString("https://github.com/"));
|
||||
QCOMPARE(result[3]->url(), QString("http://github.com"));
|
||||
QCOMPARE(result[4]->url(), QString("http://github.com/login"));
|
||||
QCOMPARE(result[5]->url(), QString("github.com"));
|
||||
QCOMPARE(result[6]->url(), QString("github.com")) ;
|
||||
|
||||
// With matching there should be only 5 results
|
||||
browserSettings()->setMatchUrlScheme(true);
|
||||
result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
|
||||
QCOMPARE(result.length(), 5);
|
||||
QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
|
||||
QCOMPARE(result[1]->url(), QString("https://github.com/login"));
|
||||
QCOMPARE(result[2]->url(), QString("https://github.com/"));
|
||||
QCOMPARE(result[3]->url(), QString("github.com"));
|
||||
QCOMPARE(result[4]->url(), QString("github.com"));
|
||||
}
|
||||
|
||||
void TestBrowser::testSearchEntriesWithPort()
|
||||
{
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
auto* root = db->rootGroup();
|
||||
|
||||
QList<QString> urls;
|
||||
urls.push_back("http://127.0.0.1:443");
|
||||
urls.push_back("http://127.0.0.1:80");
|
||||
|
||||
for (int i = 0; i < urls.length(); ++i) {
|
||||
auto entry = new Entry();
|
||||
entry->setGroup(root);
|
||||
entry->beginUpdate();
|
||||
entry->setUrl(urls[i]);
|
||||
entry->setUsername(QString("User %1").arg(i));
|
||||
entry->endUpdate();
|
||||
}
|
||||
|
||||
auto result = m_browserService->searchEntries(db, "127.0.0.1", "http://127.0.0.1:443"); // db, hostname, url
|
||||
QCOMPARE(result.length(), 1);
|
||||
QCOMPARE(result[0]->url(), QString("http://127.0.0.1:443"));
|
||||
}
|
||||
|
||||
void TestBrowser::testSortEntries()
|
||||
{
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
auto* root = db->rootGroup();
|
||||
|
||||
QList<QString> urls;
|
||||
urls.push_back("https://github.com/login_page");
|
||||
urls.push_back("https://github.com/login");
|
||||
urls.push_back("https://github.com/");
|
||||
urls.push_back("github.com/login");
|
||||
urls.push_back("http://github.com");
|
||||
urls.push_back("http://github.com/login");
|
||||
urls.push_back("github.com");
|
||||
urls.push_back("github.com/login");
|
||||
urls.push_back("https://github");
|
||||
urls.push_back("github.com");
|
||||
|
||||
QList<Entry*> entries;
|
||||
for (int i = 0; i < urls.length(); ++i) {
|
||||
auto entry = new Entry();
|
||||
entry->setGroup(root);
|
||||
entry->beginUpdate();
|
||||
entry->setUrl(urls[i]);
|
||||
entry->setUsername(QString("User %1").arg(i));
|
||||
entry->endUpdate();
|
||||
entries.push_back(entry);
|
||||
}
|
||||
|
||||
browserSettings()->setBestMatchOnly(false);
|
||||
auto result = m_browserService->sortEntries(entries, "github.com", "https://github.com/session"); // entries, host, submitUrl
|
||||
QCOMPARE(result.size(), 10);
|
||||
QCOMPARE(result[0]->username(), QString("User 2"));
|
||||
QCOMPARE(result[0]->url(), QString("https://github.com/"));
|
||||
QCOMPARE(result[1]->username(), QString("User 8"));
|
||||
QCOMPARE(result[1]->url(), QString("https://github"));
|
||||
QCOMPARE(result[2]->username(), QString("User 0"));
|
||||
QCOMPARE(result[2]->url(), QString("https://github.com/login_page"));
|
||||
QCOMPARE(result[3]->username(), QString("User 1"));
|
||||
QCOMPARE(result[3]->url(), QString("https://github.com/login"));
|
||||
}
|
||||
|
||||
void TestBrowser::testGetDatabaseGroups()
|
||||
{
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
auto* root = db->rootGroup();
|
||||
|
||||
QScopedPointer<Group> group1(new Group());
|
||||
group1->setParent(root);
|
||||
group1->setName("group1");
|
||||
|
||||
QScopedPointer<Group> group2(new Group());
|
||||
group2->setParent(root);
|
||||
group2->setName("group2");
|
||||
|
||||
QScopedPointer<Group> group3(new Group());
|
||||
group3->setParent(root);
|
||||
group3->setName("group3");
|
||||
|
||||
QScopedPointer<Group> group2_1(new Group());
|
||||
group2_1->setParent(group2.data());
|
||||
group2_1->setName("group2_1");
|
||||
|
||||
QScopedPointer<Group> group2_2(new Group());
|
||||
group2_2->setParent(group2.data());
|
||||
group2_2->setName("group2_2");
|
||||
|
||||
QScopedPointer<Group> group2_1_1(new Group());
|
||||
group2_1_1->setParent(group2_1.data());
|
||||
group2_1_1->setName("group2_1_1");
|
||||
|
||||
auto result = m_browserService->getDatabaseGroups(db);
|
||||
QCOMPARE(result.length(), 1);
|
||||
|
||||
auto groups = result["groups"].toArray();
|
||||
auto first = groups.at(0);
|
||||
auto children = first.toObject()["children"].toArray();
|
||||
QCOMPARE(first.toObject()["name"].toString(), QString("Root"));
|
||||
QCOMPARE(children.size(), 3);
|
||||
|
||||
auto firstChild = children.at(0);
|
||||
auto secondChild = children.at(1);
|
||||
auto thirdChild = children.at(2);
|
||||
QCOMPARE(firstChild.toObject()["name"].toString(), QString("group1"));
|
||||
QCOMPARE(secondChild.toObject()["name"].toString(), QString("group2"));
|
||||
QCOMPARE(thirdChild.toObject()["name"].toString(), QString("group3"));
|
||||
|
||||
auto childrenOfSecond = secondChild.toObject()["children"].toArray();
|
||||
auto firstOfCOS = childrenOfSecond.at(0);
|
||||
auto secondOfCOS = childrenOfSecond.at(1);
|
||||
QCOMPARE(firstOfCOS.toObject()["name"].toString(), QString("group2_1"));
|
||||
QCOMPARE(secondOfCOS.toObject()["name"].toString(), QString("group2_2"));
|
||||
|
||||
auto lastChildren = firstOfCOS.toObject()["children"].toArray();
|
||||
auto lastChild = lastChildren.at(0);
|
||||
QCOMPARE(lastChild.toObject()["name"].toString(), QString("group2_1_1"));
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user