mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-23 13:11:12 -05:00
Merge branch 'develop' into issue-1148-msi-package
This commit is contained in:
commit
470ddb4704
@ -15,8 +15,8 @@ compiler:
|
||||
- gcc
|
||||
|
||||
env:
|
||||
- CONFIG=Release ASAN_OPTIONS=detect_odr_violation=1:leak_check_at_exit=0
|
||||
- CONFIG=Debug ASAN_OPTIONS=detect_odr_violation=1:leak_check_at_exit=0
|
||||
- CONFIG=Release ASAN_OPTIONS=detect_odr_violation=1
|
||||
- CONFIG=Debug ASAN_OPTIONS=detect_odr_violation=1
|
||||
|
||||
git:
|
||||
depth: 3
|
||||
@ -37,7 +37,7 @@ script:
|
||||
- cmake -DCMAKE_BUILD_TYPE=${CONFIG} -DWITH_GUI_TESTS=ON -DWITH_ASAN=ON -DWITH_XC_HTTP=ON -DWITH_XC_AUTOTYPE=ON -DWITH_XC_YUBIKEY=ON $CMAKE_ARGS ..
|
||||
- make -j2
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then ASAN_OPTIONS=${ASAN_OPTIONS}:leak_check_at_exit=0 xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test ARGS+="--output-on-failure"; fi
|
||||
|
||||
# Generate snapcraft build when merging into master/develop branches
|
||||
|
@ -87,14 +87,15 @@ else
|
||||
fi
|
||||
EOF
|
||||
chmod +x ./usr/bin/keepassxc_env
|
||||
sed -i 's/Exec=keepassxc/Exec=keepassxc_env/' org.${LOWERAPP}.desktop
|
||||
get_desktopintegration "org.${LOWERAPP}"
|
||||
|
||||
GLIBC_NEEDED=$(glibc_needed)
|
||||
sed -i 's/Exec=keepassxc/Exec=keepassxc_env/' org.${LOWERAPP}.${APP}.desktop
|
||||
get_desktopintegration "org.${LOWERAPP}.${APP}"
|
||||
|
||||
cd ..
|
||||
|
||||
generate_type2_appimage
|
||||
GLIBC_NEEDED=$(glibc_needed)
|
||||
NO_GLIBC_VERSION=true
|
||||
|
||||
mv ../out/*.AppImage ../KeePassXC-${VERSION}-${ARCH}.AppImage
|
||||
rmdir ../out > /dev/null 2>&1
|
||||
generate_type2_appimage -u "gh-releases-zsync|keepassxreboot|keepassxc|latest|KeePassXC-*-${ARCH}.AppImage.zsync"
|
||||
|
||||
mv ../out/*.AppImage* ../
|
||||
rm -rf ../out
|
||||
|
10
CHANGELOG
10
CHANGELOG
@ -1,3 +1,13 @@
|
||||
2.2.4 (2017-12-13)
|
||||
=========================
|
||||
|
||||
- Prevent database corruption when locked [#1219]
|
||||
- Fixes apply button not saving new entries [#1141]
|
||||
- Switch to Consolas font on Windows for password edit [#1229]
|
||||
- Multiple fixes to AppImage deployment [#1115, #1228]
|
||||
- Fixes multiple memory leaks [#1213]
|
||||
- Resize message close to 16x16 pixels [#1253]
|
||||
|
||||
2.2.2 (2017-10-22)
|
||||
=========================
|
||||
|
||||
|
@ -14,16 +14,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
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."
|
||||
FORCE)
|
||||
endif()
|
||||
|
||||
project(KeePassXC)
|
||||
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||
|
||||
# Support Visual Studio Code
|
||||
@ -43,13 +43,14 @@ option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
|
||||
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
|
||||
option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF)
|
||||
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
|
||||
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
||||
|
||||
# Process ui files automatically from source files
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
set(KEEPASSXC_VERSION_MAJOR "2")
|
||||
set(KEEPASSXC_VERSION_MINOR "2")
|
||||
set(KEEPASSXC_VERSION_PATCH "2")
|
||||
set(KEEPASSXC_VERSION_PATCH "4")
|
||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
|
||||
|
||||
# Distribution info
|
||||
@ -96,7 +97,7 @@ if(WITH_APP_BUNDLE)
|
||||
endif()
|
||||
|
||||
add_gcc_compiler_flags("-fno-common")
|
||||
add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long")
|
||||
add_gcc_compiler_flags("-Wall -Werror -Wextra -Wundef -Wpointer-arith -Wno-long-long")
|
||||
add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
|
||||
add_gcc_compiler_flags("-fvisibility=hidden")
|
||||
add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden")
|
||||
|
5
COPYING
5
COPYING
@ -55,10 +55,6 @@ Files: cmake/GenerateProductVersion.cmake
|
||||
Copyright: 2015 halex2005 <akharlov@gmail.com>
|
||||
License: MIT
|
||||
|
||||
Files: cmake/CodeCoverage.cmake
|
||||
Copyright: 2012 - 2015, Lars Bilke
|
||||
License: BSD-3-clause
|
||||
|
||||
Files: share/icons/application/*/apps/keepassxc.png
|
||||
share/icons/application/scalable/apps/keepassxc.svgz
|
||||
share/icons/application/*/apps/keepassxc-dark.png
|
||||
@ -179,6 +175,7 @@ Files: share/icons/application/*/actions/application-exit.png
|
||||
share/icons/application/*/actions/view-history.png
|
||||
share/icons/application/*/apps/internet-web-browser.png
|
||||
share/icons/application/*/apps/preferences-desktop-icons.png
|
||||
share/icons/application/*/apps/utilities-terminal.png
|
||||
share/icons/application/*/categories/preferences-other.png
|
||||
share/icons/application/*/status/dialog-error.png
|
||||
share/icons/application/*/status/dialog-information.png
|
||||
|
37
Dockerfile
37
Dockerfile
@ -24,7 +24,8 @@ RUN set -x \
|
||||
&& apt-get -y install software-properties-common
|
||||
|
||||
RUN set -x \
|
||||
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty
|
||||
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
|
||||
&& add-apt-repository ppa:phoerious/keepassxc
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get update -y \
|
||||
@ -42,7 +43,9 @@ RUN set -x \
|
||||
zlib1g-dev \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
mesa-common-dev
|
||||
mesa-common-dev \
|
||||
libyubikey-dev \
|
||||
libykpers-1-dev
|
||||
|
||||
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
|
||||
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
|
||||
@ -52,34 +55,8 @@ RUN set -x \
|
||||
# AppImage dependencies
|
||||
RUN set -x \
|
||||
&& apt-get install -y \
|
||||
wget \
|
||||
libfuse2
|
||||
|
||||
# build libyubikey
|
||||
ENV YUBIKEY_VERSION=1.13
|
||||
RUN set -x \
|
||||
&& wget "https://developers.yubico.com/yubico-c/Releases/libyubikey-${YUBIKEY_VERSION}.tar.gz" \
|
||||
&& tar xf libyubikey-${YUBIKEY_VERSION}.tar.gz \
|
||||
&& cd libyubikey-${YUBIKEY_VERSION} \
|
||||
&& ./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu \
|
||||
&& make \
|
||||
&& make install \
|
||||
&& cd .. \
|
||||
&& rm -Rf libyubikey-${YUBIKEY_VERSION}*
|
||||
|
||||
# build libykpers-1
|
||||
ENV YKPERS_VERSION=1.18.0
|
||||
RUN set -x \
|
||||
&& apt-get install -y libusb-dev
|
||||
RUN set -x \
|
||||
&& wget "https://developers.yubico.com/yubikey-personalization/Releases/ykpers-${YKPERS_VERSION}.tar.gz" \
|
||||
&& tar xf ykpers-${YKPERS_VERSION}.tar.gz \
|
||||
&& cd ykpers-${YKPERS_VERSION} \
|
||||
&& ./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu \
|
||||
&& make \
|
||||
&& make install \
|
||||
&& cd .. \
|
||||
&& rm -Rf ykpers-${YKPERS_VERSION}*
|
||||
libfuse2 \
|
||||
wget
|
||||
|
||||
VOLUME /keepassxc/src
|
||||
VOLUME /keepassxc/out
|
||||
|
75
INSTALL.md
75
INSTALL.md
@ -1,11 +1,13 @@
|
||||
Install KeePassXC
|
||||
Build and Install KeePassXC
|
||||
=================
|
||||
|
||||
This document will guide you across the steps to install KeePassXC.
|
||||
You can visit the online version of this document at the following link
|
||||
This document will guide you through the steps to build and install KeePassXC from source.
|
||||
You can visit the online version of this document at the following link:
|
||||
|
||||
https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source
|
||||
|
||||
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using KeePassXC on your
|
||||
Windows, Mac, or Linux computer using the pre-built binaries.
|
||||
|
||||
Build Dependencies
|
||||
==================
|
||||
@ -28,10 +30,9 @@ The following libraries are required:
|
||||
Prepare the Building Environment
|
||||
================================
|
||||
|
||||
* Building Environment on Linux ==> https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Linux
|
||||
* Building Environment on Windows ==> https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Windows
|
||||
* Building Environment on MacOS ==> https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-OS-X
|
||||
|
||||
* [Building Environment on Linux](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Linux)
|
||||
* [Building Environment on Windows](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Windows)
|
||||
* [Building Environment on MacOS](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-OS-X)
|
||||
|
||||
Build Steps
|
||||
===========
|
||||
@ -39,32 +40,68 @@ Build Steps
|
||||
To compile from source, open a **Terminal (on Linux/MacOS)** or a **MSYS2-MinGW shell (on Windows)**<br/>
|
||||
**Note:** on Windows make sure you are using a **MINGW shell** by checking the label before the current path
|
||||
|
||||
Navigate to the path you have downloaded KeePassXC and type these commands:
|
||||
First, download the KeePassXC [source tarball](https://keepassxc.org/download#source)
|
||||
or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
|
||||
|
||||
To clone the project from Git, `cd` to a suitable location and run
|
||||
|
||||
```bash
|
||||
git clone https://github.com/keepassxreboot/keepassxc.git
|
||||
```
|
||||
|
||||
This will clone the entire contents of the repository and check out the current `develop` branch.
|
||||
|
||||
To update the project from within the project's folder, you can run the following command:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
Navigate to the directory where you have downloaded KeePassXC and type these commands:
|
||||
|
||||
```
|
||||
cd directory-where-sources-live
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DWITH_TESTS=OFF
|
||||
cmake -DWITH_TESTS=OFF ...and other options - see below...
|
||||
make
|
||||
```
|
||||
These steps place the compiled KeePassXC binary inside the `./build/src/` directory.
|
||||
(Note the cmake notes/options below.)
|
||||
|
||||
**Note:** 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/`
|
||||
**Cmake Notes:**
|
||||
|
||||
You will have the compiled KeePassXC binary inside the `./build/src/` directory.
|
||||
* Common cmake parameters
|
||||
|
||||
Common cmake parameters
|
||||
```
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local
|
||||
-DCMAKE_VERBOSE_MAKEFILE=ON
|
||||
-DCMAKE_BUILD_TYPE=<RelWithDebInfo/Debug/Release>
|
||||
-DWITH_GUI_TESTS=ON
|
||||
```
|
||||
```
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local
|
||||
-DCMAKE_VERBOSE_MAKEFILE=ON
|
||||
-DCMAKE_BUILD_TYPE=<RelWithDebInfo/Debug/Release>
|
||||
-DWITH_GUI_TESTS=ON
|
||||
```
|
||||
|
||||
* cmake accepts the following options:
|
||||
|
||||
```
|
||||
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
|
||||
-DWITH_XC_HTTP=[ON|OFF] Enable/Disable KeePassHTTP and custom icon downloads (default: OFF)
|
||||
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
|
||||
|
||||
-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)
|
||||
```
|
||||
|
||||
* 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/`
|
||||
|
||||
:exclamation: When building with ASan support on macOS, you need to use `export ASAN_OPTIONS=detect_leaks=0` before running the tests (no LSan support in macOS).
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To install this binary execute the following:
|
||||
After you have successfully built KeePassXC, install the binary by executing the following:
|
||||
|
||||
```bash
|
||||
sudo make install
|
||||
|
98
README.md
98
README.md
@ -1,10 +1,20 @@
|
||||
# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC [![Travis Build Status](https://travis-ci.org/keepassxreboot/keepassxc.svg?branch=develop)](https://travis-ci.org/keepassxreboot/keepassxc) [![Coverage Status](https://coveralls.io/repos/github/keepassxreboot/keepassxc/badge.svg)](https://coveralls.io/github/keepassxreboot/keepassxc)
|
||||
# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC
|
||||
[![Travis Build Status](https://travis-ci.org/keepassxreboot/keepassxc.svg?branch=develop)](https://travis-ci.org/keepassxreboot/keepassxc) [![Coverage Status](https://coveralls.io/repos/github/keepassxreboot/keepassxc/badge.svg)](https://coveralls.io/github/keepassxreboot/keepassxc)
|
||||
|
||||
KeePass Cross-platform Community Edition
|
||||
|
||||
## About
|
||||
[KeePassXC](https://keepassxc.org) is a community fork of [KeePassX](https://www.keepassx.org/) with the goal to extend and improve it with new features and bugfixes to provide a feature-rich, fully cross-platform and modern open-source password manager.
|
||||
## About KeePassXC
|
||||
[KeePassXC](https://keepassxc.org) is a cross-platform community fork of
|
||||
[KeePassX](https://www.keepassx.org/).
|
||||
Our goal is to extend and improve it with new features and bugfixes
|
||||
to provide a feature-rich, fully cross-platform and modern
|
||||
open-source password manager.
|
||||
|
||||
## Installation
|
||||
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using
|
||||
KeePassXC on your Windows, Mac, or Linux computer using pre-compiled binaries
|
||||
from the [downloads page](https://keepassxc.org/download).
|
||||
|
||||
Additionally, individual Linux distributions may ship their own versions,
|
||||
so please check out your distribution's package list to see if KeePassXC is available.
|
||||
|
||||
## Additional features compared to KeePassX
|
||||
- Auto-Type on all three major platforms (Linux, Windows, macOS)
|
||||
@ -19,67 +29,39 @@ KeePass Cross-platform Community Edition
|
||||
- Using website favicons as entry icons
|
||||
- Merging of databases
|
||||
- Automatic reload when the database changed on disk
|
||||
- KeePassHTTP support for use with KeePassHTTP-Connector for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and [passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari.
|
||||
- Browser integration with KeePassHTTP-Connector for
|
||||
[Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and
|
||||
[Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and
|
||||
[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#Note_about_KeePassHTTP)
|
||||
- Many bug fixes
|
||||
|
||||
For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document.
|
||||
|
||||
### Note about KeePassHTTP
|
||||
KeePassHTTP is not a highly secure protocol and has certain flaw which allow an attacker to decrypt your passwords when they manage to intercept communication between a KeePassHTTP server and PassIFox/chromeIPass over a network connection (see [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)). KeePassXC therefore strictly limits communication between itself and the browser plugin to your local computer. As long as your computer is not compromised, your passwords are fairly safe that way, but use it at your own risk!
|
||||
## Building KeePassXC
|
||||
|
||||
### Installation
|
||||
Pre-compiled binaries can be found on the [downloads page](https://keepassxc.org/download). Additionally, individual Linux distributions may ship their own versions, so please check out your distribution's package list to see if KeePassXC is available.
|
||||
Detailed instructions are available in the [Build and Install](./INSTALL.md)
|
||||
page or on the [Wiki page](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).
|
||||
|
||||
### Building KeePassXC
|
||||
## Contributing
|
||||
|
||||
*More detailed instructions are available in the INSTALL file or on the [Wiki page](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).*
|
||||
|
||||
First, you must download the KeePassXC [source tarball](https://keepassxc.org/download#source) or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
|
||||
|
||||
To clone the project from Git, `cd` to a suitable location and run
|
||||
|
||||
```bash
|
||||
git clone https://github.com/keepassxreboot/keepassxc.git
|
||||
```
|
||||
|
||||
This will clone the entire contents of the repository and check out the current `develop` branch.
|
||||
|
||||
To update the project from within the project's folder, you can run the following command:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
Once you have downloaded the source code, you can `cd` into the source code directory, build and install KeePassXC:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DWITH_TESTS=OFF ..
|
||||
make -j8
|
||||
sudo make install
|
||||
```
|
||||
|
||||
cmake accepts the following options:
|
||||
|
||||
```
|
||||
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
|
||||
-DWITH_XC_HTTP=[ON|OFF] Enable/Disable KeePassHTTP and custom icon downloads (default: OFF)
|
||||
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
|
||||
|
||||
-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)
|
||||
```
|
||||
|
||||
:exclamation: When building with ASan support on macOS, you need to use `export ASAN_OPTIONS=detect_leaks=0` before running the tests (no LSan support in macOS).
|
||||
|
||||
### Contributing
|
||||
|
||||
We are always looking for suggestions how to improve our application. If you find any bugs or have an idea for a new feature, please let us know by opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues) on GitHub or join us on IRC on freenode channels #keepassxc or #keepassxc-dev.
|
||||
We are always looking for suggestions how to improve our application.
|
||||
If you find any bugs or have an idea for a new feature, please let us know by
|
||||
opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues)
|
||||
on GitHub or join us on IRC on freenode channels #keepassxc or #keepassxc-dev.
|
||||
|
||||
You can of course also directly contribute your own code. We are happy to accept your pull requests.
|
||||
|
||||
Please read the [CONTRIBUTING document](.github/CONTRIBUTING.md) for further information.
|
||||
|
||||
### Note about KeePassHTTP
|
||||
The KeePassHTTP protocol is not a highly secure protocol.
|
||||
It has a certain flaw which could allow an attacker to decrypt your passwords
|
||||
should they manage to impersonate the web browser extension from a remote address.
|
||||
<!--intercept communication between a KeePassHTTP server
|
||||
and PassIFox/chromeIPass over a network connection -->
|
||||
(See [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)).
|
||||
|
||||
To minimize the risk, KeePassXC strictly limits communication between itself
|
||||
and the browser plugin to your local computer (localhost).
|
||||
This makes your passwords quite safe,
|
||||
but as with all open source software, use it at your own risk!
|
||||
|
BIN
docs/KeePassXC-Accept-Button.png
Normal file
BIN
docs/KeePassXC-Accept-Button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
BIN
docs/KeePassXC-Confirm.png
Normal file
BIN
docs/KeePassXC-Confirm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
docs/KeePassXC-Connect.png
Normal file
BIN
docs/KeePassXC-Connect.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
47
docs/QUICKSTART.md
Normal file
47
docs/QUICKSTART.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Quick Start for KeePassXC
|
||||
|
||||
This procedure gets KeePassXC running on your computer with browser integration,
|
||||
using the pre-built binaries available for [download](https://keepassxc.org/download)
|
||||
from [KeePassXC site](https://keepassxc.org).
|
||||
|
||||
**TL;DR** KeePassXC saves your passwords securely.
|
||||
When you double-click a URL in KeePassXC, it launches your default browser to that URL.
|
||||
With browser integration configured, KeePassXC automatically enters
|
||||
username/password credentials into web page fields.
|
||||
|
||||
## Installing and Starting KeePassXC
|
||||
|
||||
* [Download the native installer](https://keepassxc.org/download) and install
|
||||
KeePassXC for your Windows, macOS, or Linux computer in the usual way for your platform.
|
||||
* Open the KeePassXC application.
|
||||
* Create a new database and give it a master key that's used to unlock the database file.
|
||||
This database holds entries (usernames, passwords, account numbers, notes)
|
||||
for all your websites, programs, etc.
|
||||
* Create a few entries - enter the username, password, URL, and optionally notes about the entry.
|
||||
* KeePassXC securely stores those entries in the database.
|
||||
|
||||
|
||||
## Setting up Browser Integration with KeePassXC
|
||||
|
||||
* *Within KeePassXC*, go to **Tools->Settings** (on macOS, go to **KeePassXC->Preferences**.)
|
||||
* In **Browser Integration**, check **Enable KeePassHTTP server**
|
||||
Leave the other options at their defaults.
|
||||
* *In your default web browser,* install the KeePassHTTP-Connector extension/add-on. Instructions for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/?src=api) or [Chrome](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb?utm_source=chrome-app-launcher-info-dialog)
|
||||
* Click the KeePassXC icon in the upper-right corner. You'll see the dialog below.
|
||||
* Click the blue Connect button to make the browser extension connect to the KeePassXC application.
|
||||
<img src="./KeePassXC-Connect.png" height="200" alt="KeePassXC Connect dialog">
|
||||
|
||||
* *Switch back to KeePassXC.* You'll see a dialog (below) indicating that a request to connect has arrived.
|
||||
* Give the connection a name (perhaps *Keepass-Browsername*, any unique name will suffice) and click OK to accept it.
|
||||
* This one-time operation connects KeePassXC and your browser.
|
||||
<img src="./KeePassXC-Accept-Button.png" height="200" alt="KeePassXC accept connection dialog">
|
||||
|
||||
## Using Browser Integration
|
||||
|
||||
* *Within KeePassXC,* double-click the URL of an entry,
|
||||
or select it and type Ctrl+U (Cmd+U on macOS).
|
||||
* Your browser opens to that URL.
|
||||
* If there are username/password fields on that page, you will see the dialog below.
|
||||
Click *Allow* to confirm that KeePassXC may access the credentials to auto-fill the fields.
|
||||
* Check *Remember this decision* to allow this each time you visit the page.
|
||||
<img src="./KeePassXC-Confirm.png" height="200" alt="KeePassCX Confirm Access dialog">
|
251
release-tool
251
release-tool
@ -50,14 +50,14 @@ printUsage() {
|
||||
local cmd
|
||||
if [ "" == "$1" ] || [ "help" == "$1" ]; then
|
||||
cmd="COMMAND"
|
||||
elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "sign" == "$1" ]; then
|
||||
elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "gpgsign" == "$1" ] || [ "appsign" == "$1" ]; then
|
||||
cmd="$1"
|
||||
else
|
||||
logError "Unknown command: '$1'\n"
|
||||
cmd="COMMAND"
|
||||
fi
|
||||
|
||||
printf "\e[1mUsage:\e[0m $(basename $0) $cmd [--version x.y.z] [options]\n"
|
||||
|
||||
printf "\e[1mUsage:\e[0m $(basename $0) $cmd [options]\n"
|
||||
|
||||
if [ "COMMAND" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
@ -66,7 +66,8 @@ Commands:
|
||||
check Perform a dry-run check, nothing is changed
|
||||
merge Merge release branch into main branch and create release tags
|
||||
build Build and package binary release from sources
|
||||
sign Sign previously compiled release packages
|
||||
gpgsign Sign previously compiled release packages with GPG
|
||||
appsign Sign binaries with code signing certificates on Windows and macOS
|
||||
help Show help for the given command
|
||||
EOF
|
||||
elif [ "merge" == "$cmd" ]; then
|
||||
@ -113,16 +114,25 @@ Options:
|
||||
-n, --no-source-tarball Don't build source tarball
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
elif [ "sign" == "$cmd" ]; then
|
||||
elif [ "gpgsign" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Sign previously compiled release packages
|
||||
Sign previously compiled release packages with GPG
|
||||
|
||||
Options:
|
||||
-f, --files Files to sign (required)
|
||||
-g, --gpg-key GPG key used to sign the files (default: '${GPG_KEY}')
|
||||
--signtool Specify the signtool executable (default: 'signtool')
|
||||
--signtool-key Provide a key to be used with signtool (for Windows EXE)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
elif [ "appsign" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Sign binaries with code signing certificates on Windows and macOS
|
||||
|
||||
Options:
|
||||
-f, --files Files to sign (required)
|
||||
-k, --signtool-key Key to be used with signtool (required for Windows EXE)
|
||||
-i, --identity Apple Developer ID to be used with codesign (required for macOS APP and DMG)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
fi
|
||||
@ -264,13 +274,13 @@ checkChangeLog() {
|
||||
}
|
||||
|
||||
checkAppStreamInfo() {
|
||||
if [ ! -f share/linux/org.keepassxc.appdata.xml ]; then
|
||||
if [ ! -f share/linux/org.keepassxc.KeePassXC.appdata.xml ]; then
|
||||
exitError "No AppStream info file found!"
|
||||
fi
|
||||
|
||||
grep -qPzo "<release version=\"${RELEASE_NAME}\" date=\"\d{4}-\d{2}-\d{2}\">" share/linux/org.keepassxc.appdata.xml
|
||||
grep -qPzo "<release version=\"${RELEASE_NAME}\" date=\"\d{4}-\d{2}-\d{2}\">" share/linux/org.keepassxc.KeePassXC.appdata.xml
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "'share/linux/org.keepassxc.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!"
|
||||
exitError "'share/linux/org.keepassxc.KeePassXC.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!"
|
||||
fi
|
||||
}
|
||||
|
||||
@ -289,7 +299,28 @@ checkSnapcraft() {
|
||||
checkTransifexCommandExists() {
|
||||
command -v tx > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'"
|
||||
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'."
|
||||
fi
|
||||
}
|
||||
|
||||
checkOsslsigncodeCommandExists() {
|
||||
command -v osslsigncode > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "osslsigncode command not found on the PATH! Please install it using 'pacman -S mingw-w64-osslsigncode'."
|
||||
fi
|
||||
}
|
||||
|
||||
checkCodesignCommandExists() {
|
||||
command -v codesign > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "codesign command not found on the PATH! Please check that you have correctly installed Xcode."
|
||||
fi
|
||||
}
|
||||
|
||||
checkCreateDMGCommandExists() {
|
||||
command -v create-dmg > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "create-dmg command not found on the PATH! Please install it using 'npm install --global create-dmg'."
|
||||
fi
|
||||
}
|
||||
|
||||
@ -665,81 +696,54 @@ build() {
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# sign command
|
||||
# gpgsign command
|
||||
# -----------------------------------------------------------------------
|
||||
sign() {
|
||||
SIGN_FILES=()
|
||||
SIGNTOOL="signtool"
|
||||
SIGNTOOL_KEY=""
|
||||
|
||||
gpgsign() {
|
||||
local sign_files=()
|
||||
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-f|--files)
|
||||
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
||||
SIGN_FILES+=("$2")
|
||||
sign_files+=("$2")
|
||||
shift
|
||||
done ;;
|
||||
|
||||
|
||||
-g|--gpg-key)
|
||||
GPG_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
--signtool)
|
||||
SIGNTOOL="$2"
|
||||
shift ;;
|
||||
|
||||
--signtool-key)
|
||||
SIGNTOOL_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "sign"
|
||||
printUsage "gpgsign"
|
||||
exit ;;
|
||||
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "sign"
|
||||
printUsage "gpgsign"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "$SIGN_FILES" ]; then
|
||||
if [ -z "${sign_files}" ]; then
|
||||
logError "Missing arguments, --files is required!\n"
|
||||
printUsage "sign"
|
||||
printUsage "gpgsign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$SIGNTOOL_KEY" && ! -f "$SIGNTOOL_KEY" ]]; then
|
||||
exitError "Signtool Key was not found!"
|
||||
elif [[ -f "$SIGNTOOL_KEY" && ! -x $(command -v "${SIGNTOOL}") ]]; then
|
||||
exitError "signtool program not found on PATH!"
|
||||
fi
|
||||
|
||||
for f in "${SIGN_FILES[@]}"; do
|
||||
for f in "${sign_files[@]}"; do
|
||||
if [ ! -f "$f" ]; then
|
||||
exitError "File '${f}' does not exist!"
|
||||
exitError "File '${f}' does not exist or is not a file!"
|
||||
fi
|
||||
|
||||
if [[ -n "$SIGNTOOL_KEY" && ${f: -4} == '.exe' ]]; then
|
||||
logInfo "Signing file '${f}' using signtool...\n"
|
||||
read -s -p "Signtool Key Password: " password
|
||||
echo
|
||||
"${SIGNTOOL}" sign -f "${SIGNTOOL_KEY}" -p ${password} -v -t http://timestamp.comodoca.com/authenticode ${f}
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
fi
|
||||
|
||||
logInfo "Signing file '${f}' using release key..."
|
||||
gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
|
||||
|
||||
logInfo "Creating digest for file '${f}'..."
|
||||
local rp="$(realpath "$f")"
|
||||
local bname="$(basename "$f")"
|
||||
@ -750,6 +754,143 @@ sign() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# appsign command
|
||||
# -----------------------------------------------------------------------
|
||||
appsign() {
|
||||
local sign_files=()
|
||||
local signtool_key
|
||||
local codesign_identity
|
||||
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-f|--files)
|
||||
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
||||
sign_files+=("$2")
|
||||
shift
|
||||
done ;;
|
||||
|
||||
-k|--signtool-key)
|
||||
signtool_key="$2"
|
||||
shift ;;
|
||||
|
||||
-i|--identity)
|
||||
codesign_identity="$2"
|
||||
shift ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "appsign"
|
||||
exit ;;
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "appsign"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "${sign_files}" ]; then
|
||||
logError "Missing arguments, --files is required!\n"
|
||||
printUsage "appsign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for f in "${sign_files[@]}"; do
|
||||
if [ ! -f "${f}" ]; then
|
||||
exitError "File '${f}' does not exist or is not a file!"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$(uname -s)" == "Darwin" ]; then
|
||||
if [ -z "${codesign_identity}" ]; then
|
||||
logError "Missing arguments, --identity is required on macOS!\n"
|
||||
printUsage "appsign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
checkCodesignCommandExists
|
||||
checkCreateDMGCommandExists
|
||||
|
||||
local orig_dir="$(pwd)"
|
||||
for f in "${sign_files[@]}"; do
|
||||
if [[ ${f: -4} == '.dmg' ]]; then
|
||||
logInfo "Unpacking disk image '${f}'..."
|
||||
local tmp_dir="/tmp/KeePassXC_${RANDOM}"
|
||||
mkdir -p ${tmp_dir}/{mnt,app}
|
||||
hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}"
|
||||
cd ${tmp_dir}
|
||||
cp -a ./mnt/KeePassXC.app ./app
|
||||
hdiutil detach -quiet ${tmp_dir}/mnt
|
||||
|
||||
if [ ! -d ./app/KeePassXC.app ]; then
|
||||
cd "${orig_dir}"
|
||||
exitError "Unpacking failed!"
|
||||
fi
|
||||
|
||||
logInfo "Signing app using codesign..."
|
||||
codesign --sign "${codesign_identity}" --verbose --deep ./app/KeePassXC.app
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
cd "${orig_dir}"
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
|
||||
logInfo "Repacking and signing disk image..."
|
||||
create-dmg ./app/KeePassXC.app
|
||||
cd "${orig_dir}"
|
||||
cp -f ${tmp_dir}/KeePassXC-*.dmg "${f}"
|
||||
rm -Rf ${tmp_dir}
|
||||
else
|
||||
logInfo "Skipping non-DMG file '${f}'..."
|
||||
fi
|
||||
done
|
||||
|
||||
elif [ "$(uname -o)" == "Msys" ]; then
|
||||
if [ -z "${signtool_key}" ]; then
|
||||
logError "Missing arguments, --signtool-key is required on Windows!\n"
|
||||
printUsage "appsign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
checkOsslsigncodeCommandExists
|
||||
|
||||
if [[ ! -f "${signtool_key}" ]]; then
|
||||
exitError "Key file was not found!"
|
||||
fi
|
||||
|
||||
read -s -p "Key password: " password
|
||||
echo
|
||||
|
||||
for f in "${sign_files[@]}"; do
|
||||
if [[ ${f: -4} == '.exe' ]]; then
|
||||
logInfo "Signing file '${f}' using osslsigncode..."
|
||||
# output a signed exe; we have to use a different name due to osslsigntool limitations
|
||||
osslsigncode sign -pkcs12 "${signtool_key}" -pass "${password}" \
|
||||
-t "http://timestamp.comodoca.com/authenticode" -in "${f}" -out "${f}.signed"
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
rm -f "${f}.signed"
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
|
||||
# overwrite the original exe with the signed exe
|
||||
mv -f "${f}.signed" "${f}"
|
||||
else
|
||||
logInfo "Skipping non-EXE file '${f}'..."
|
||||
fi
|
||||
done
|
||||
|
||||
else
|
||||
exitError "Unsupported platform for code signing!\n"
|
||||
fi
|
||||
|
||||
logInfo "All done!"
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# parse global command line
|
||||
# -----------------------------------------------------------------------
|
||||
@ -762,8 +903,8 @@ if [ "" == "$MODE" ]; then
|
||||
elif [ "help" == "$MODE" ]; then
|
||||
printUsage "$1"
|
||||
exit
|
||||
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "sign" == "$MODE" ]; then
|
||||
$MODE "$@"
|
||||
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ]; then
|
||||
${MODE} "$@"
|
||||
else
|
||||
printUsage "$MODE"
|
||||
fi
|
||||
|
@ -30,8 +30,8 @@ if(UNIX AND NOT APPLE)
|
||||
install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor
|
||||
FILES_MATCHING PATTERN "application-x-keepassxc.png" PATTERN "application-x-keepassxc.svgz"
|
||||
PATTERN "status" EXCLUDE PATTERN "actions" EXCLUDE PATTERN "categories" EXCLUDE)
|
||||
install(FILES linux/org.keepassxc.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||
install(FILES linux/org.keepassxc.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
|
||||
install(FILES linux/org.keepassxc.KeePassXC.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||
install(FILES linux/org.keepassxc.KeePassXC.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
|
||||
install(FILES linux/keepassxc.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages)
|
||||
endif(UNIX AND NOT APPLE)
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 360 B |
BIN
share/icons/application/32x32/apps/utilities-terminal.png
Normal file
BIN
share/icons/application/32x32/apps/utilities-terminal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 958 B |
BIN
share/icons/svg/utilities-terminal.svgz
Normal file
BIN
share/icons/svg/utilities-terminal.svgz
Normal file
Binary file not shown.
@ -1,5 +1,4 @@
|
||||
[General]
|
||||
ShowToolbar=true
|
||||
RememberLastDatabases=true
|
||||
RememberLastKeyFiles=true
|
||||
OpenPreviousDatabasesOnStartup=true
|
||||
|
@ -1,16 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2017 KeePassXC Team <team@keepassxc.org> -->
|
||||
<!--Copyright 2017 KeePassXC Team <team@keepassxc.org> -->
|
||||
<component type="desktop-application">
|
||||
<id>org.keepassxc</id>
|
||||
<id>org.keepassxc.KeePassXC.desktop</id>
|
||||
<name>KeePassXC</name>
|
||||
<metadata_license>CC-BY-3.0</metadata_license>
|
||||
<project_license>GPL-3.0+</project_license>
|
||||
<icon type="stock">keepassxc</icon>
|
||||
<url type="homepage">https://keepassxc.org</url>
|
||||
<mimetypes>
|
||||
<mimetype>application/x-keepass2</mimetype>
|
||||
</mimetypes>
|
||||
<summary>Community-driven port of the Windows application “KeePass Password Safe”</summary>
|
||||
<developer_name>KeePassXC Team</developer_name>
|
||||
<description>
|
||||
<p>
|
||||
KeePassXC is an application for people with extremely high demands on secure
|
||||
@ -19,56 +18,53 @@
|
||||
</p>
|
||||
</description>
|
||||
|
||||
<launchable type="desktop-id">org.keepassxc.desktop</launchable>
|
||||
<launchable type="desktop-id">org.keepassxc.KeePassXC.desktop</launchable>
|
||||
|
||||
<url type="homepage">https://keepassxc.org</url>
|
||||
<url type="bugtracker">https://github.com/keepassxreboot/keepassxc/issues</url>
|
||||
<url type="faq">https://keepassxc.org/docs#faq</url>
|
||||
<url type="help">https://keepassxc.org/docs</url>
|
||||
<url type="translate">https://www.transifex.com/keepassxc/keepassxc</url>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_001.png</image>
|
||||
<caption>Create, Import or Open Databases</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_002.png</image>
|
||||
<caption>Organize with Groups and Entries</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_003.png</image>
|
||||
<caption>Database Entry</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_004.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_005.png</image>
|
||||
<caption>Icon Selection for Entry</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_006.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_007.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_008.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_009.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_010.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_011.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_012.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_013.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_014.png</image>
|
||||
<caption>Password Generator</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.2.4" date="2017-12-13">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Prevent database corruption when locked [#1219]</li>
|
||||
<li>Fixes apply button not saving new entries [#1141]</li>
|
||||
<li>Switch to Consolas font on Windows for password edit [#1229]</li>
|
||||
<li>Multiple fixes to AppImage deployment [#1115, #1228]</li>
|
||||
<li>Fixes multiple memory leaks [#1213]</li>
|
||||
<li>Resize message close to 16x16 pixels [#1253]</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="2.2.2" date="2017-10-22">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Fixed entries with empty URLs being reported to KeePassHTTP clients [#1031]</li>
|
||||
<li>Fixed YubiKey detection and enabled CLI tool for AppImage binary [#1100]</li>
|
||||
@ -85,8 +81,10 @@
|
||||
<li>Fixed screen lock and Google fallback settings [#1029]</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release><release version="2.2.1" date="2017-10-01">
|
||||
</release>
|
||||
<release version="2.2.1" date="2017-10-01">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Corrected multiple snap issues [#934, #1011]</li>
|
||||
<li>Corrected multiple custom icon issues [#708, #719, #994]</li>
|
||||
@ -103,6 +101,7 @@
|
||||
</release>
|
||||
<release version="2.2.0" date="2017-06-23">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Added YubiKey 2FA integration for unlocking databases [#127]</li>
|
||||
<li>Added TOTP support [#519]</li>
|
||||
@ -138,6 +137,7 @@
|
||||
</release>
|
||||
<release version="2.1.4" date="2017-04-09">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Bumped KeePassHTTP version to 1.8.4.2</li>
|
||||
<li>KeePassHTTP confirmation window comes to foreground [#466]</li>
|
||||
@ -146,6 +146,7 @@
|
||||
</release>
|
||||
<release version="2.1.3" date="2017-03-03">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Fix possible overflow in zxcvbn library [#363]</li>
|
||||
<li>Revert HiDPI setting to avoid problems on laptop screens [#332]</li>
|
||||
@ -160,6 +161,7 @@
|
||||
</release>
|
||||
<release version="2.1.2" date="2017-02-17">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Ask for save location when creating a new database [#302]</li>
|
||||
<li>Remove Libmicrohttpd dependency to clean up the code and ensure better OS X compatibility [#317, #265]</li>
|
||||
@ -177,6 +179,7 @@
|
||||
</release>
|
||||
<release version="2.1.1" date="2017-02-06">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Enabled HTTP plugin build; plugin is disabled by default and limited to localhost [#147]</li>
|
||||
<li>Escape HTML in dialog boxes [#247]</li>
|
||||
@ -189,6 +192,7 @@
|
||||
</release>
|
||||
<release version="2.1.0" date="2017-01-22">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Show unlock dialog when using autotype on a closed database [#10, #89]</li>
|
||||
<li>Show different tray icon when database is locked [#37, #46]</li>
|
@ -9,6 +9,7 @@ Comment=Community-driven port of the Windows application “KeePass Password Saf
|
||||
Exec=keepassxc %f
|
||||
TryExec=keepassxc
|
||||
Icon=keepassxc
|
||||
StartupWMClass=keepassxc
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Version=1.0
|
2395
share/translations/keepassx_ca.ts
Normal file
2395
share/translations/keepassx_ca.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -571,7 +571,7 @@ Sie können sie speichern oder die Sperre freigeben.</translation>
|
||||
Do you want to save the database before locking it?
|
||||
Otherwise your changes are lost.</source>
|
||||
<translation>Dieses Datenbank wurde geändert.
|
||||
Soll sie gespeichert werden bevor sie gesperrt wirt?
|
||||
Soll sie gespeichert werden bevor sie gesperrt wird?
|
||||
Anderenfalls gehen Ihre Änderungen verloren.</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
@ -69,7 +69,7 @@ Núcleo: %3 %4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Distribution: %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Distribución: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -250,7 +250,7 @@ Por favor seleccione si desea autorizar su acceso.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec</source>
|
||||
<translation>Codificación</translation>
|
||||
<translation>Códec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Text is qualified by</source>
|
||||
@ -302,11 +302,11 @@ Por favor seleccione si desea autorizar su acceso.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Original data: </source>
|
||||
<translation>Datos originales:</translation>
|
||||
<translation>Dato original:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error(s) detected in CSV file !</source>
|
||||
<translation>¡Se detectaron errores en el archivo CSV!</translation>
|
||||
<translation>¡Error(es) detectado(s) en el archivo CSV!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> more messages skipped]</source>
|
||||
@ -728,7 +728,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Merge Request</source>
|
||||
<translation>Solicitud de unión</translation>
|
||||
<translation>Solicitud de Unión</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file has changed and you have unsaved changes.Do you want to merge your changes?</source>
|
||||
@ -744,7 +744,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Are you sure you want to permanently delete everything from your recycle bin?</source>
|
||||
<translation>¿Está seguro(a) que quiere permanentemente eliminar todo de su papelera de reciclaje?</translation>
|
||||
<translation>¿Está seguro que quiere eliminar permanentemente todo de su papelera de reciclaje?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -795,7 +795,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select file</source>
|
||||
<translation>Seleccionar archivo llave</translation>
|
||||
<translation>Seleccionar archivo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to open file</source>
|
||||
@ -817,11 +817,11 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>%n week(s)</source>
|
||||
<translation><numerusform>%n semana</numerusform><numerusform>%n semanas</numerusform></translation>
|
||||
<translation><numerusform>%n semana</numerusform><numerusform>%n semana(s)</numerusform></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>%n month(s)</source>
|
||||
<translation><numerusform>%n mes</numerusform><numerusform>%n meses</numerusform></translation>
|
||||
<translation><numerusform>%n mes</numerusform><numerusform>%n mes(es)</numerusform></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>1 year</source>
|
||||
@ -969,7 +969,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Presets</source>
|
||||
<translation>Predeterminado</translation>
|
||||
<translation>Programar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Notes:</source>
|
||||
@ -1098,7 +1098,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom icon already exists</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>El icono personalizado ya existe</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1352,7 +1352,7 @@ Esta migración es en único sentido. No podrá abrir la base de datos importada
|
||||
</message>
|
||||
<message>
|
||||
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>El archivo de bloqueo de instancia única existente no es válido. Lanzando nueva instancia.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1483,7 +1483,7 @@ Esta migración es en único sentido. No podrá abrir la base de datos importada
|
||||
</message>
|
||||
<message>
|
||||
<source>Timed one-time password</source>
|
||||
<translation>Contraseña programada de único uso (TOTP)</translation>
|
||||
<translation>Contraseña programada de único uso</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy &TOTP</source>
|
||||
|
@ -29,27 +29,27 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Revision: %1</source>
|
||||
<translation>Revision: %1</translation>
|
||||
<translation>Révision : %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Libraries:</source>
|
||||
<translation>Bibliothèques:</translation>
|
||||
<translation>Bibliothèques :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Operating system: %1
|
||||
CPU architecture: %2
|
||||
Kernel: %3 %4</source>
|
||||
<translation>Système d'exploitation: %1
|
||||
Architecture CPU: %2
|
||||
Kernel: %3 %4</translation>
|
||||
<translation>Système d'exploitation : %1
|
||||
Architecture CPU : %2
|
||||
Kernel : %3 %4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enabled extensions:</source>
|
||||
<translation>Extensions activées:</translation>
|
||||
<translation>Extensions activées :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues" style="text-decoration: underline;">https://github.com</a></source>
|
||||
<translation>Signaler les bugs sur: <a href="https://github.com/keepassxreboot/keepassxc/issues" style="text-decoration: underline;">https://github.com</a></translation>
|
||||
<translation>Signaler les bugs sur : <a href="https://github.com/keepassxreboot/keepassxc/issues" style="text-decoration: underline;">https://github.com</a></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</source>
|
||||
@ -57,7 +57,7 @@ Kernel: %3 %4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Project Maintainers:</source>
|
||||
<translation>Mainteneurs du projet:</translation>
|
||||
<translation>Mainteneurs du projet :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors">See Contributions on GitHub</a></source>
|
||||
@ -65,11 +65,11 @@ Kernel: %3 %4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Include the following information whenever you report a bug:</source>
|
||||
<translation>Inclure l'information suivante lorsque vous signaler un bug:</translation>
|
||||
<translation>Inclure l'information suivante lorsque vous signaler un bug :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Distribution: %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Distribution : %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -302,7 +302,7 @@ Veuillez sélectionner si vous souhaitez autoriser l’accès.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Original data: </source>
|
||||
<translation>Données originales:</translation>
|
||||
<translation>Données originales :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error(s) detected in CSV file !</source>
|
||||
@ -397,7 +397,7 @@ Veuillez sélectionner si vous souhaitez autoriser l’accès.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Challenge Response:</source>
|
||||
<translation>Challenge-réponse:</translation>
|
||||
<translation>Challenge-réponse :</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -477,15 +477,15 @@ Vous pouvez maintenant la sauvegarder.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>AES: 256 Bit (default)</source>
|
||||
<translation>AES: 256 Bits (par défault)</translation>
|
||||
<translation>AES : 256 Bits (par défaut)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Twofish: 256 Bit</source>
|
||||
<translation>Twofish: 256 bits</translation>
|
||||
<translation>Twofish : 256 bits</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Algorithm:</source>
|
||||
<translation>Algorithme:</translation>
|
||||
<translation>Algorithme :</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -737,7 +737,7 @@ Voulez vous l'ouvrir quand même ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not open the new database file while attempting to autoreload this database.</source>
|
||||
<translation>La nouvelle base de données ne peux être ouverte pendant qu'un rafraîchissement automatique de l'actuelle est en cours.</translation>
|
||||
<translation>La nouvelle base de données ne peut être ouverte pendant qu'un rafraîchissement automatique de l'actuelle est en cours.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Empty recycle bin?</source>
|
||||
@ -1095,11 +1095,11 @@ Voulez vous l'ouvrir quand même ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hint: You can enable Google as a fallback under Tools>Settings>Security</source>
|
||||
<translation>Astuce: Vous pouvez activer Google en tant que repli sous Outils>Paramètres>Sécurité</translation>
|
||||
<translation>Astuce : Vous pouvez activer Google en tant que repli sous Outils>Paramètres>Sécurité</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom icon already exists</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>L'icône personnalisée existe déjà</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1175,7 +1175,7 @@ Voulez vous l'ouvrir quand même ?</translation>
|
||||
<message>
|
||||
<source>Ref: </source>
|
||||
<comment>Reference abbreviation</comment>
|
||||
<translation>Réf:</translation>
|
||||
<translation>Réf : </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1353,7 +1353,7 @@ Il s'agit d'une migration à sens unique. Vous ne pourrez pas ouvrir l
|
||||
</message>
|
||||
<message>
|
||||
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Le fichier de verrouillage de l’instance unique existant n’est pas valide. Lancement d'une nouvelle instance.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1647,11 +1647,11 @@ Il s'agit d'une migration à sens unique. Vous ne pourrez pas ouvrir l
|
||||
</message>
|
||||
<message>
|
||||
<source>HTTP Port:</source>
|
||||
<translation>Port HTTP:</translation>
|
||||
<translation>Port HTTP :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Default port: 19455</source>
|
||||
<translation>Port par défaut: 19455</translation>
|
||||
<translation>Port par défaut : 19455</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re&quest to unlock the database if it is locked</source>
|
||||
@ -1681,7 +1681,7 @@ Restauration du port 19455 par défaut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Return advanced string fields which start with "KPH: "</source>
|
||||
<translation>& Retourne les champs avancés de type chaîne qui commencent par "KPH:"</translation>
|
||||
<translation>& Retourne les champs avancés de type chaîne qui commencent par "KPH :"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Automatically creating or updating string fields is not supported.</source>
|
||||
@ -1774,7 +1774,7 @@ Ne les changez que si vous savez ce que vous faites.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Length:</source>
|
||||
<translation>&Longueur:</translation>
|
||||
<translation>&Longueur :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick characters from every group</source>
|
||||
@ -1794,11 +1794,11 @@ Ne les changez que si vous savez ce que vous faites.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Entropy: %1 bit</source>
|
||||
<translation>Entropie: %1 bit</translation>
|
||||
<translation>Entropie : %1 bit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password Quality: %1</source>
|
||||
<translation>Qualité du mot de passe: %1</translation>
|
||||
<translation>Qualité du mot de passe : %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Poor</source>
|
||||
@ -1830,15 +1830,15 @@ Ne les changez que si vous savez ce que vous faites.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wordlist:</source>
|
||||
<translation>Liste de mots:</translation>
|
||||
<translation>Liste de mots :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Word Count:</source>
|
||||
<translation>Nombre de mots:</translation>
|
||||
<translation>Nombre de mots :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Word Separator:</source>
|
||||
<translation>Séparateur de mot:</translation>
|
||||
<translation>Séparateur de mot :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy</source>
|
||||
@ -2019,7 +2019,7 @@ Veuillez déverrouiller la base de données sélectionnée ou en choisir une qui
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: New key association request</source>
|
||||
<translation>KeePassXC: nouvelle demande d'association</translation>
|
||||
<translation>KeePassXC : nouvelle demande d'association</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You have received an association request for the above key.
|
||||
@ -2031,23 +2031,23 @@ attribuez lui un nom unique pour l'identifier et acceptez la.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: Overwrite existing key?</source>
|
||||
<translation>KeePassXC: Écraser la clé existante ?</translation>
|
||||
<translation>KeePassXC : Écraser la clé existante ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: Update Entry</source>
|
||||
<translation>KeePassXC: Mettre à jour l'entrée</translation>
|
||||
<translation>KeePassXC : Mettre à jour l'entrée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: Database locked!</source>
|
||||
<translation>KeePassXC: Base de données verrouillée !</translation>
|
||||
<translation>KeePassXC : Base de données verrouillée !</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: Removed keys from database</source>
|
||||
<translation>KeePassXC: Les clés ont été effacées de la base de donnée</translation>
|
||||
<translation>KeePassXC : Les clés ont été effacées de la base de donnée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: No keys found</source>
|
||||
<translation>KeePassXC: Aucune clé trouvée</translation>
|
||||
<translation>KeePassXC : Aucune clé trouvée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: Settings not available!</source>
|
||||
@ -2055,11 +2055,11 @@ attribuez lui un nom unique pour l'identifier et acceptez la.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: Removed permissions</source>
|
||||
<translation>KeePassXC: Permissions retirées</translation>
|
||||
<translation>KeePassXC : Permissions retirées</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: No entry with permissions found!</source>
|
||||
<translation>KeePassXC: Aucune entrée avec permissions trouvée !</translation>
|
||||
<translation>KeePassXC : Aucune entrée avec permissions trouvée !</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -2145,7 +2145,7 @@ attribuez lui un nom unique pour l'identifier et acceptez la.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't mark database as modified for non-data changes (e.g., expanding groups)</source>
|
||||
<translation>Ne pas indiquer la base de données comme modifiée pour les changements hors-données (par exemple: groupes développés)</translation>
|
||||
<translation>Ne pas indiquer la base de données comme modifiée pour les changements hors-données (par exemple : groupes développés)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type</source>
|
||||
@ -2227,7 +2227,7 @@ attribuez lui un nom unique pour l'identifier et acceptez la.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Key:</source>
|
||||
<translation>Clé:</translation>
|
||||
<translation>Clé :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use custom settings</source>
|
||||
@ -2235,11 +2235,11 @@ attribuez lui un nom unique pour l'identifier et acceptez la.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Note: Change these settings only if you know what you are doing.</source>
|
||||
<translation>Attention: modifiez ces paramètres seulement si vous savez ce que vous faites.</translation>
|
||||
<translation>Attention : modifiez ces paramètres seulement si vous savez ce que vous faites.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Time step:</source>
|
||||
<translation>Période de temps:</translation>
|
||||
<translation>Période de temps :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>8 digits</source>
|
||||
@ -2251,7 +2251,7 @@ attribuez lui un nom unique pour l'identifier et acceptez la.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Code size:</source>
|
||||
<translation>Taille du code:</translation>
|
||||
<translation>Taille du code :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> sec</source>
|
||||
@ -2375,7 +2375,7 @@ attribuez lui un nom unique pour l'identifier et acceptez la.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Path of the group to list. Default is /</source>
|
||||
<translation>Chemin du groupe à lister. Par défaut: /</translation>
|
||||
<translation>Chemin du groupe à lister. Par défaut : /</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Print the UUIDs of the entries and groups.</source>
|
||||
|
@ -69,7 +69,7 @@ Kernel: %3 %4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Distribution: %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Distribusi: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -131,7 +131,7 @@ Seleziona se vuoi consentire l'accesso.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type - KeePassXC</source>
|
||||
<translation> KeePassXC - Auto completamento</translation>
|
||||
<translation>KeePassXC - Auto completamento</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -170,7 +170,7 @@ Seleziona se vuoi consentire l'accesso.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to create Key File : </source>
|
||||
<translation>Impossibile creare file chiave:</translation>
|
||||
<translation>Impossibile creare file chiave: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select a key file</source>
|
||||
@ -290,11 +290,11 @@ Seleziona se vuoi consentire l'accesso.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Empty fieldname </source>
|
||||
<translation>Nome campo vuoto</translation>
|
||||
<translation>Nome campo vuoto </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>column </source>
|
||||
<translation>colonna</translation>
|
||||
<translation>colonna </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Imported from CSV file</source>
|
||||
@ -302,7 +302,7 @@ Seleziona se vuoi consentire l'accesso.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Original data: </source>
|
||||
<translation>Dati originali:</translation>
|
||||
<translation>Dati originali: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error(s) detected in CSV file !</source>
|
||||
@ -1917,23 +1917,23 @@ Modificale solo se sai quello che stai facendo.</translation>
|
||||
<name>QtIOCompressor</name>
|
||||
<message>
|
||||
<source>Internal zlib error when compressing: </source>
|
||||
<translation>Errore interno di zlib durante la compressione:</translation>
|
||||
<translation>Errore interno di zlib durante la compressione: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error writing to underlying device: </source>
|
||||
<translation>Errore durante la scrittura nel dispositivo:</translation>
|
||||
<translation>Errore durante la scrittura nel dispositivo: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error opening underlying device: </source>
|
||||
<translation>Errore durante l'apertura dal dispositivo:</translation>
|
||||
<translation>Errore durante l'apertura dal dispositivo: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error reading data from underlying device: </source>
|
||||
<translation>Errore durante la lettura dal dispositivo:</translation>
|
||||
<translation>Errore durante la lettura dal dispositivo: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Internal zlib error when decompressing: </source>
|
||||
<translation>Errore interno di zlib durante la decompressione:</translation>
|
||||
<translation>Errore interno di zlib durante la decompressione: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1944,7 +1944,7 @@ Modificale solo se sai quello che stai facendo.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Internal zlib error: </source>
|
||||
<translation>Errore interno di zlib:</translation>
|
||||
<translation>Errore interno di zlib: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -2018,7 +2018,7 @@ Sblocca il database selezionato o scegli un altro database sbloccato.</translati
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC: New key association request</source>
|
||||
<translation>KeePassXC: Nuova richiesta di associazione chiave </translation>
|
||||
<translation>KeePassXC: Nuova richiesta di associazione chiave</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You have received an association request for the above key.
|
||||
@ -2164,7 +2164,7 @@ imposta un nome unico per identificarla ed accettarla.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> ms</source>
|
||||
<translation>ms</translation>
|
||||
<translation> ms</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Start only a single instance of KeePassXC</source>
|
||||
@ -2179,7 +2179,7 @@ imposta un nome unico per identificarla ed accettarla.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> sec</source>
|
||||
<translation>sec</translation>
|
||||
<translation> sec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Lock databases after inactivity of</source>
|
||||
@ -2254,7 +2254,7 @@ imposta un nome unico per identificarla ed accettarla.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> sec</source>
|
||||
<translation>sec</translation>
|
||||
<translation> sec</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -69,7 +69,7 @@ CPU アーキテクチャ: %2
|
||||
</message>
|
||||
<message>
|
||||
<source>Distribution: %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>配布形式: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -69,7 +69,7 @@ CPU 아키텍처: %2
|
||||
</message>
|
||||
<message>
|
||||
<source>Distribution: %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>배포판: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1096,7 +1096,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom icon already exists</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>사용자 정의 아이콘이 이미 존재함</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1350,7 +1350,7 @@ This is a one-way migration. You won't be able to open the imported databas
|
||||
</message>
|
||||
<message>
|
||||
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>존재하는 단일 인스턴스 잠금 파일이 잘못되었습니다. 새 인스턴스를 실행합니다.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -69,7 +69,7 @@ Kernel: %3 %4</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Distribution: %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Дистрибутив: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -219,7 +219,7 @@ Please select whether you want to allow access.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Replace username and password with references</source>
|
||||
<translation>Заменить имя пользователя и пароль к ссылкам</translation>
|
||||
<translation>Использовать ссылки для имени пользователя и пароля</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy history</source>
|
||||
@ -227,7 +227,7 @@ Please select whether you want to allow access.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Append ' - Clone' to title</source>
|
||||
<translation>Добавить' - Клонировать' в заголовок</translation>
|
||||
<translation>Добавить к названию « - клон»</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -579,7 +579,7 @@ Otherwise your changes are lost.</source>
|
||||
<message>
|
||||
<source>"%1" is in edit mode.
|
||||
Discard changes and close anyway?</source>
|
||||
<translation>«%1» в режиме редактирования.
|
||||
<translation>«%1» в режиме правки.
|
||||
Отменить изменения и всё равно закрыть?</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -741,7 +741,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Empty recycle bin?</source>
|
||||
<translation>Корзина пустая?</translation>
|
||||
<translation>Очистить корзину?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Are you sure you want to permanently delete everything from your recycle bin?</source>
|
||||
@ -784,7 +784,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Edit entry</source>
|
||||
<translation>Редактировать запись</translation>
|
||||
<translation>Править запись</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Different passwords supplied.</source>
|
||||
@ -838,7 +838,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>[PROTECTED] Press reveal to view or edit</source>
|
||||
<translation>[Защищён] Нажмите для открытия просмотра или редактирования</translation>
|
||||
<translation>[Защищён] Нажмите для открытия просмотра или правки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Are you sure you want to remove this attachment?</source>
|
||||
@ -853,7 +853,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add</source>
|
||||
<translation>Добавить</translation>
|
||||
<translation>Создать</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove</source>
|
||||
@ -946,7 +946,7 @@ Do you want to open it anyway?</source>
|
||||
<name>EditEntryWidgetMain</name>
|
||||
<message>
|
||||
<source>Title:</source>
|
||||
<translation>Заголовок:</translation>
|
||||
<translation>Название:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Username:</source>
|
||||
@ -997,7 +997,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Edit group</source>
|
||||
<translation>Редактировать группу</translation>
|
||||
<translation>Править группу</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable</source>
|
||||
@ -1095,22 +1095,22 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hint: You can enable Google as a fallback under Tools>Settings>Security</source>
|
||||
<translation>Подсказка: вы можете включить Google в качестве резервного копирования в меню «Инструменты»> «Настройки»> «Безопасность»</translation>
|
||||
<translation>Подсказка: в качестве резервного варианта для получения значков сайтов возможно использовать Google. Включите этот параметр в меню «Инструменты» -> «Настройки» -> «Безопасность»</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom icon already exists</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Пользовательский значок уже существует</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditWidgetProperties</name>
|
||||
<message>
|
||||
<source>Created:</source>
|
||||
<translation>Создано:</translation>
|
||||
<translation>Создание:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Modified:</source>
|
||||
<translation>Изменено:</translation>
|
||||
<translation>Изменение:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Accessed:</source>
|
||||
@ -1125,7 +1125,7 @@ Do you want to open it anyway?</source>
|
||||
<name>Entry</name>
|
||||
<message>
|
||||
<source> - Clone</source>
|
||||
<translation> - Клонировать</translation>
|
||||
<translation> - клон</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1143,7 +1143,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Title</source>
|
||||
<translation>Заголовок</translation>
|
||||
<translation>Имя записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Username</source>
|
||||
@ -1162,7 +1162,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Title</source>
|
||||
<translation>Заголовок</translation>
|
||||
<translation>Имя записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Username</source>
|
||||
@ -1353,7 +1353,7 @@ This is a one-way migration. You won't be able to open the imported databas
|
||||
</message>
|
||||
<message>
|
||||
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Запускается новый экземпляр программы, т.к. файл блокировки запуска повреждён.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1452,11 +1452,11 @@ This is a one-way migration. You won't be able to open the imported databas
|
||||
</message>
|
||||
<message>
|
||||
<source>&Add new entry</source>
|
||||
<translation>&Добавить новую запись</translation>
|
||||
<translation>&Создать запись</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&View/Edit entry</source>
|
||||
<translation>&Посмотреть/редактировать запись</translation>
|
||||
<translation>&Открыть/править запись</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Delete entry</source>
|
||||
@ -1464,11 +1464,11 @@ This is a one-way migration. You won't be able to open the imported databas
|
||||
</message>
|
||||
<message>
|
||||
<source>&Add new group</source>
|
||||
<translation>&Добавить новую группу</translation>
|
||||
<translation>&Создать группу</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Edit group</source>
|
||||
<translation>&Редактировать группу</translation>
|
||||
<translation>&Править группу</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Delete group</source>
|
||||
@ -1524,7 +1524,7 @@ This is a one-way migration. You won't be able to open the imported databas
|
||||
</message>
|
||||
<message>
|
||||
<source>&Title</source>
|
||||
<translation>&Заголовок</translation>
|
||||
<translation>&Имя записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&URL</source>
|
||||
@ -1556,7 +1556,7 @@ This is a one-way migration. You won't be able to open the imported databas
|
||||
</message>
|
||||
<message>
|
||||
<source>Empty recycle bin</source>
|
||||
<translation>Корзина пустая</translation>
|
||||
<translation>Очистить корзину</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Access error for config file %1</source>
|
||||
@ -1701,15 +1701,15 @@ Using default port 19455.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Return only best matching entries</source>
|
||||
<translation>&Возврат только наиболее совпадающих записей</translation>
|
||||
<translation>&Показывать только лучшие совпадения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Only entries with the same scheme (http://, https://, ftp://, ...) are returned.</source>
|
||||
<translation>Возвращаются только записи с той же схемой (http: //, https: //, ftp: //, ...).</translation>
|
||||
<translation>Будут отобраны только записи с совпадающим протоколом (http://, https://, ftp://, ...).</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Match URL schemes</source>
|
||||
<translation>&Совпадения схем адресов</translation>
|
||||
<translation>&Проверять протокол</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password Generator</source>
|
||||
@ -1879,7 +1879,7 @@ Change them only if you know what you are doing.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Title</source>
|
||||
<translation>Заголовок</translation>
|
||||
<translation>Имя записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Username</source>
|
||||
@ -2085,7 +2085,7 @@ give it a unique name to identify and accept it.</source>
|
||||
<name>SettingsWidgetGeneral</name>
|
||||
<message>
|
||||
<source>Remember last databases</source>
|
||||
<translation>Помнить последнюю базу данных</translation>
|
||||
<translation>Запоминать последнюю базу данных</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Automatically save on exit</source>
|
||||
@ -2117,7 +2117,7 @@ give it a unique name to identify and accept it.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide window to system tray when minimized</source>
|
||||
<translation>При сворачивании прятать окно в системный лоток</translation>
|
||||
<translation>При сворачивании скрывать окно в системный лоток</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Load previous databases on startup</source>
|
||||
@ -2129,7 +2129,7 @@ give it a unique name to identify and accept it.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide window to system tray instead of app exit</source>
|
||||
<translation>Прятать окно в системный лоток вместо выхода</translation>
|
||||
<translation>Скрывать окно в системный лоток вместо выхода</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Minimize window at application startup</source>
|
||||
@ -2141,11 +2141,11 @@ give it a unique name to identify and accept it.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remember last key files and security dongles</source>
|
||||
<translation>Помнить последние ключевые файлы и ключи безопасности</translation>
|
||||
<translation>Запоминать последние использованные файлы ключей и устройства</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't mark database as modified for non-data changes (e.g., expanding groups)</source>
|
||||
<translation>Не помечать базу данных как измененную без изменения данных (например, для расширения групп)</translation>
|
||||
<translation>Не помечать базу данных изменённой при действиях, не связанных с изменением данных (например, при распахивании групп)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-Type</source>
|
||||
@ -2153,7 +2153,7 @@ give it a unique name to identify and accept it.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use entry title and URL to match windows for global Auto-Type</source>
|
||||
<translation>Использовать URL и заголовок записи при сопоставлении окон для глобального автоввода</translation>
|
||||
<translation>Использовать для поиска URL и название записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Always ask before performing Auto-Type</source>
|
||||
@ -2184,7 +2184,7 @@ give it a unique name to identify and accept it.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Lock databases after inactivity of</source>
|
||||
<translation>Заблокировать базу данных после неактивности длительностью</translation>
|
||||
<translation>Блокировать базу данных при отсутствии активности длительностью</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show passwords in cleartext by default</source>
|
||||
@ -2192,7 +2192,7 @@ give it a unique name to identify and accept it.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Lock databases after minimizing the window</source>
|
||||
<translation>Блокировать базу данных после сворачивания окна</translation>
|
||||
<translation>Блокировать базу данных при сворачивания окна</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't require password repeat when it is visible</source>
|
||||
@ -2216,7 +2216,7 @@ give it a unique name to identify and accept it.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Google as fallback for downloading website icons</source>
|
||||
<translation>Использовать Google как резерв для загрузки значков веб-сайтов</translation>
|
||||
<translation>Использовать Google в качестве резервного варианта для получения значков веб-сайтов</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -69,7 +69,7 @@ Kernel: %3 %4</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Distribution: %1</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>散佈:%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -196,7 +196,7 @@ Please select whether you want to allow access.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cha&llenge Response</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>挑戰回應</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Refresh</source>
|
||||
@ -208,7 +208,7 @@ Please select whether you want to allow access.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Changing master key failed: no YubiKey inserted.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>挑戰主金鑰失敗:沒有插入 YubiKey</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -397,7 +397,7 @@ Please select whether you want to allow access.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Challenge Response:</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>挑戰驗證:</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1098,7 +1098,7 @@ Do you want to open it anyway?</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom icon already exists</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>自訂圖示已經存在</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -2140,7 +2140,7 @@ give it a unique name to identify and accept it.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remember last key files and security dongles</source>
|
||||
<translation>記住最近的金鑰檔案與安全加密狗</translation>
|
||||
<translation>記住最近的金鑰檔案與安全鎖</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't mark database as modified for non-data changes (e.g., expanding groups)</source>
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: keepassxc
|
||||
version: 2.2.2
|
||||
version: 2.2.4
|
||||
grade: stable
|
||||
summary: Community-driven port of the Windows application “KeePass Password Safe”
|
||||
description: |
|
||||
@ -12,7 +12,7 @@ apps:
|
||||
keepassxc:
|
||||
command: desktop-launch keepassxc
|
||||
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb]
|
||||
desktop: usr/share/applications/org.keepassxc.desktop
|
||||
desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop
|
||||
cli:
|
||||
command: keepassxc-cli
|
||||
plugs: [gsettings, home, removable-media, raw-usb]
|
||||
@ -41,8 +41,10 @@ parts:
|
||||
- libxtst-dev
|
||||
- libyubikey-dev
|
||||
- libykpers-1-dev
|
||||
stage-packages:
|
||||
- dbus
|
||||
install: |
|
||||
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.desktop
|
||||
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop
|
||||
after: [desktop-qt5]
|
||||
|
||||
# Redefine desktop-qt5 stage packages to work with Ubuntu 17.04
|
||||
@ -52,19 +54,12 @@ parts:
|
||||
- ttf-ubuntu-font-family
|
||||
- dmz-cursor-theme
|
||||
- light-themes
|
||||
- adwaita-icon-theme
|
||||
- gnome-themes-standard
|
||||
- shared-mime-info
|
||||
- libqt5gui5
|
||||
- libgdk-pixbuf2.0-0
|
||||
- libqt5svg5 # for loading icon themes which are svg
|
||||
- locales-all
|
||||
- xdg-user-dirs
|
||||
|
||||
# Overcome limitation in snapd to support URL loading (CTRL+U)
|
||||
# client needs to install "snapd-xdg-open" on their system
|
||||
snapd-xdg-open:
|
||||
source: https://github.com/ubuntu-core/snapd-xdg-open.git
|
||||
source-depth: 1
|
||||
plugin: nil
|
||||
install: |
|
||||
install -D -t $SNAPCRAFT_PART_INSTALL/usr/bin/ data/xdg-open
|
||||
stage-packages:
|
||||
- dbus
|
||||
|
@ -105,6 +105,7 @@ set(keepassx_SOURCES
|
||||
gui/EditWidgetIcons.cpp
|
||||
gui/EditWidgetProperties.cpp
|
||||
gui/FileDialog.cpp
|
||||
gui/Font.cpp
|
||||
gui/IconModels.cpp
|
||||
gui/KeePass1OpenWidget.cpp
|
||||
gui/KMessageWidget.cpp
|
||||
@ -155,6 +156,8 @@ if(APPLE)
|
||||
set(keepassx_SOURCES ${keepassx_SOURCES}
|
||||
core/ScreenLockListenerMac.h
|
||||
core/ScreenLockListenerMac.cpp
|
||||
core/MacPasteboard.h
|
||||
core/MacPasteboard.cpp
|
||||
)
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
@ -177,6 +180,7 @@ set(keepassx_SOURCES_MAINEXE
|
||||
add_feature_info(AutoType WITH_XC_AUTOTYPE "Automatic password typing")
|
||||
add_feature_info(KeePassHTTP WITH_XC_HTTP "Browser integration compatible with ChromeIPass and PassIFox")
|
||||
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
||||
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
|
||||
|
||||
add_subdirectory(http)
|
||||
if(WITH_XC_HTTP)
|
||||
@ -186,6 +190,11 @@ endif()
|
||||
add_subdirectory(autotype)
|
||||
add_subdirectory(cli)
|
||||
|
||||
add_subdirectory(sshagent)
|
||||
if(WITH_XC_SSHAGENT)
|
||||
set(sshagent_LIB sshagent)
|
||||
endif()
|
||||
|
||||
set(autotype_SOURCES
|
||||
core/Tools.cpp
|
||||
autotype/AutoType.cpp
|
||||
@ -222,6 +231,7 @@ set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUIL
|
||||
target_link_libraries(keepassx_core
|
||||
${keepasshttp_LIB}
|
||||
${autotype_LIB}
|
||||
${sshagent_LIB}
|
||||
${YUBIKEY_LIBRARIES}
|
||||
${ZXCVBN_LIBRARIES}
|
||||
Qt5::Core
|
||||
|
@ -256,6 +256,13 @@ void AutoType::resetInAutoType()
|
||||
m_inAutoType = false;
|
||||
}
|
||||
|
||||
void AutoType::raiseWindow()
|
||||
{
|
||||
#if defined(Q_OS_MAC)
|
||||
m_plugin->raiseOwnWindow();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutoType::unloadPlugin()
|
||||
{
|
||||
if (m_executor) {
|
||||
|
@ -51,6 +51,7 @@ public:
|
||||
|
||||
public slots:
|
||||
void performGlobalAutoType(const QList<Database*>& dbList);
|
||||
void raiseWindow();
|
||||
|
||||
signals:
|
||||
void globalShortcutTriggered();
|
||||
|
@ -22,8 +22,8 @@ set(cli_SOURCES
|
||||
Command.h
|
||||
Edit.cpp
|
||||
Edit.h
|
||||
EntropyMeter.cpp
|
||||
EntropyMeter.h
|
||||
Estimate.cpp
|
||||
Estimate.h
|
||||
Extract.cpp
|
||||
Extract.h
|
||||
List.cpp
|
||||
|
@ -55,9 +55,7 @@ int Clip::execute(QStringList arguments)
|
||||
parser.addOption(keyFile);
|
||||
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to clip."));
|
||||
parser.addPositionalArgument(
|
||||
"timeout",
|
||||
QObject::tr("Timeout in seconds before clearing the clipboard."),
|
||||
QString("[timeout]"));
|
||||
"timeout", QObject::tr("Timeout in seconds before clearing the clipboard."), QString("[timeout]"));
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
|
@ -23,9 +23,9 @@
|
||||
#include "Command.h"
|
||||
|
||||
#include "Add.h"
|
||||
#include "Edit.h"
|
||||
#include "Clip.h"
|
||||
#include "EntropyMeter.h"
|
||||
#include "Edit.h"
|
||||
#include "Estimate.h"
|
||||
#include "Extract.h"
|
||||
#include "List.h"
|
||||
#include "Locate.h"
|
||||
@ -62,7 +62,7 @@ void populateCommands()
|
||||
commands.insert(QString("add"), new Add());
|
||||
commands.insert(QString("clip"), new Clip());
|
||||
commands.insert(QString("edit"), new Edit());
|
||||
commands.insert(QString("entropy-meter"), new EntropyMeter());
|
||||
commands.insert(QString("estimate"), new Estimate());
|
||||
commands.insert(QString("extract"), new Extract());
|
||||
commands.insert(QString("locate"), new Locate());
|
||||
commands.insert(QString("ls"), new List());
|
||||
|
@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "EntropyMeter.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <zxcvbn.h>
|
||||
|
||||
/* For pre-compiled headers under windows */
|
||||
#ifdef _WIN32
|
||||
#ifndef __MINGW32__
|
||||
#include "stdafx.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
EntropyMeter::EntropyMeter()
|
||||
{
|
||||
this->name = QString("entropy-meter");
|
||||
this->description = QObject::tr("Calculate password entropy.");
|
||||
}
|
||||
|
||||
EntropyMeter::~EntropyMeter()
|
||||
{
|
||||
}
|
||||
|
||||
static void calculate(const char *pwd, int advanced)
|
||||
{
|
||||
double e;
|
||||
int len = strlen(pwd);
|
||||
if (advanced == 0){
|
||||
e = ZxcvbnMatch(pwd, 0, 0);
|
||||
printf("Pass '%s' \tLength %d\tEntropy %.3f\tLog10 %.3f\n", pwd, len, e, e * 0.301029996);
|
||||
} else {
|
||||
int ChkLen;
|
||||
ZxcMatch_t *info, *p;
|
||||
double m = 0.0;
|
||||
e = ZxcvbnMatch(pwd, 0, &info);
|
||||
for(p = info; p; p = p->Next) {
|
||||
m += p->Entrpy;
|
||||
}
|
||||
m = e - m;
|
||||
printf("Pass '%s' \tLength %d\tEntropy %.3f\tLog10 %.3f\n Multi-word extra bits %.1f\n", pwd, len, e, e * 0.301029996, m);
|
||||
p = info;
|
||||
ChkLen = 0;
|
||||
while(p) {
|
||||
int n;
|
||||
switch(static_cast<int>(p->Type))
|
||||
{
|
||||
case BRUTE_MATCH: printf(" Type: Bruteforce "); break;
|
||||
case DICTIONARY_MATCH: printf(" Type: Dictionary "); break;
|
||||
case DICT_LEET_MATCH: printf(" Type: Dict+Leet "); break;
|
||||
case USER_MATCH: printf(" Type: User Words "); break;
|
||||
case USER_LEET_MATCH: printf(" Type: User+Leet "); break;
|
||||
case REPEATS_MATCH: printf(" Type: Repeated "); break;
|
||||
case SEQUENCE_MATCH: printf(" Type: Sequence "); break;
|
||||
case SPATIAL_MATCH: printf(" Type: Spatial "); break;
|
||||
case DATE_MATCH: printf(" Type: Date "); break;
|
||||
case BRUTE_MATCH+MULTIPLE_MATCH: printf(" Type: Bruteforce(Rep)"); break;
|
||||
case DICTIONARY_MATCH+MULTIPLE_MATCH: printf(" Type: Dictionary(Rep)"); break;
|
||||
case DICT_LEET_MATCH+MULTIPLE_MATCH: printf(" Type: Dict+Leet(Rep) "); break;
|
||||
case USER_MATCH+MULTIPLE_MATCH: printf(" Type: User Words(Rep)"); break;
|
||||
case USER_LEET_MATCH+MULTIPLE_MATCH: printf(" Type: User+Leet(Rep) "); break;
|
||||
case REPEATS_MATCH+MULTIPLE_MATCH: printf(" Type: Repeated(Rep) "); break;
|
||||
case SEQUENCE_MATCH+MULTIPLE_MATCH: printf(" Type: Sequence(Rep) "); break;
|
||||
case SPATIAL_MATCH+MULTIPLE_MATCH: printf(" Type: Spatial(Rep) "); break;
|
||||
case DATE_MATCH+MULTIPLE_MATCH: printf(" Type: Date(Rep) "); break;
|
||||
|
||||
default: printf(" Type: Unknown%d ", p->Type); break;
|
||||
}
|
||||
ChkLen += p->Length;
|
||||
printf(" Length %d Entropy %6.3f (%.2f) ", p->Length, p->Entrpy, p->Entrpy * 0.301029996);
|
||||
for(n = 0; n < p->Length; ++n, ++pwd) {
|
||||
printf("%c", *pwd);
|
||||
}
|
||||
printf("\n");
|
||||
p = p->Next;
|
||||
}
|
||||
ZxcvbnFreeInfo(info);
|
||||
if (ChkLen != len)
|
||||
printf("*** Password length (%d) != sum of length of parts (%d) ***\n", len, ChkLen);
|
||||
}
|
||||
}
|
||||
|
||||
int EntropyMeter::execute(QStringList arguments)
|
||||
{
|
||||
printf("KeePassXC Entropy Meter, based on zxcvbn-c.\nEnter your password below or pass it as argv\n");
|
||||
printf(" Usage: entropy-meter [-a] [pwd1 pwd2 ...]\n> ");
|
||||
int i, advanced = 0;
|
||||
if (arguments.size() > 1 && arguments.at(1) == "-a")
|
||||
{
|
||||
advanced = 1;
|
||||
arguments.removeAt(1);
|
||||
}
|
||||
i = 1;
|
||||
if (i >= arguments.size())
|
||||
{
|
||||
/* No test passwords on command line, so get them from stdin */
|
||||
char line[500];
|
||||
while(fgets(line, sizeof line, stdin))
|
||||
{
|
||||
/* Drop the trailing newline character */
|
||||
for(i = 0; i < static_cast<int>(sizeof line - 1); ++i)
|
||||
{
|
||||
if (line[i] < ' ')
|
||||
{
|
||||
line[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (line[0]) {
|
||||
calculate(line,advanced);
|
||||
printf("> ");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Do the test passwords on the command line */
|
||||
for(; i < arguments.size(); ++i)
|
||||
{
|
||||
calculate(arguments.at(i).toLatin1(), advanced);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
170
src/cli/Estimate.cpp
Normal file
170
src/cli/Estimate.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Estimate.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <zxcvbn.h>
|
||||
|
||||
/* For pre-compiled headers under windows */
|
||||
#ifdef _WIN32
|
||||
#ifndef __MINGW32__
|
||||
#include "stdafx.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Estimate::Estimate()
|
||||
{
|
||||
this->name = QString("estimate");
|
||||
this->description = QObject::tr("Estimate the entropy of a password.");
|
||||
}
|
||||
|
||||
Estimate::~Estimate()
|
||||
{
|
||||
}
|
||||
|
||||
static void estimate(const char* pwd, bool advanced)
|
||||
{
|
||||
double e;
|
||||
int len = strlen(pwd);
|
||||
if (!advanced) {
|
||||
e = ZxcvbnMatch(pwd, 0, 0);
|
||||
printf("Length %d\tEntropy %.3f\tLog10 %.3f\n", len, e, e * 0.301029996);
|
||||
} else {
|
||||
int ChkLen;
|
||||
ZxcMatch_t *info, *p;
|
||||
double m = 0.0;
|
||||
e = ZxcvbnMatch(pwd, 0, &info);
|
||||
for (p = info; p; p = p->Next) {
|
||||
m += p->Entrpy;
|
||||
}
|
||||
m = e - m;
|
||||
printf("Length %d\tEntropy %.3f\tLog10 %.3f\n Multi-word extra bits %.1f\n", len, e, e * 0.301029996, m);
|
||||
p = info;
|
||||
ChkLen = 0;
|
||||
while (p) {
|
||||
int n;
|
||||
switch (static_cast<int>(p->Type)) {
|
||||
case BRUTE_MATCH:
|
||||
printf(" Type: Bruteforce ");
|
||||
break;
|
||||
case DICTIONARY_MATCH:
|
||||
printf(" Type: Dictionary ");
|
||||
break;
|
||||
case DICT_LEET_MATCH:
|
||||
printf(" Type: Dict+Leet ");
|
||||
break;
|
||||
case USER_MATCH:
|
||||
printf(" Type: User Words ");
|
||||
break;
|
||||
case USER_LEET_MATCH:
|
||||
printf(" Type: User+Leet ");
|
||||
break;
|
||||
case REPEATS_MATCH:
|
||||
printf(" Type: Repeated ");
|
||||
break;
|
||||
case SEQUENCE_MATCH:
|
||||
printf(" Type: Sequence ");
|
||||
break;
|
||||
case SPATIAL_MATCH:
|
||||
printf(" Type: Spatial ");
|
||||
break;
|
||||
case DATE_MATCH:
|
||||
printf(" Type: Date ");
|
||||
break;
|
||||
case BRUTE_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Bruteforce(Rep)");
|
||||
break;
|
||||
case DICTIONARY_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Dictionary(Rep)");
|
||||
break;
|
||||
case DICT_LEET_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Dict+Leet(Rep) ");
|
||||
break;
|
||||
case USER_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: User Words(Rep)");
|
||||
break;
|
||||
case USER_LEET_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: User+Leet(Rep) ");
|
||||
break;
|
||||
case REPEATS_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Repeated(Rep) ");
|
||||
break;
|
||||
case SEQUENCE_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Sequence(Rep) ");
|
||||
break;
|
||||
case SPATIAL_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Spatial(Rep) ");
|
||||
break;
|
||||
case DATE_MATCH + MULTIPLE_MATCH:
|
||||
printf(" Type: Date(Rep) ");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf(" Type: Unknown%d ", p->Type);
|
||||
break;
|
||||
}
|
||||
ChkLen += p->Length;
|
||||
printf(" Length %d Entropy %6.3f (%.2f) ", p->Length, p->Entrpy, p->Entrpy * 0.301029996);
|
||||
for (n = 0; n < p->Length; ++n, ++pwd) {
|
||||
printf("%c", *pwd);
|
||||
}
|
||||
printf("\n");
|
||||
p = p->Next;
|
||||
}
|
||||
ZxcvbnFreeInfo(info);
|
||||
if (ChkLen != len) {
|
||||
printf("*** Password length (%d) != sum of length of parts (%d) ***\n", len, ChkLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Estimate::execute(QStringList arguments)
|
||||
{
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.addPositionalArgument("password", QObject::tr("Password for which to estimate the entropy."), "[password]");
|
||||
QCommandLineOption advancedOption(QStringList() << "a"
|
||||
<< "advanced",
|
||||
QObject::tr("Perform advanced analysis on the password."));
|
||||
parser.addOption(advancedOption);
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() > 1) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString password;
|
||||
if (args.size() == 1) {
|
||||
password = args.at(0);
|
||||
} else {
|
||||
password = inputTextStream.readLine();
|
||||
}
|
||||
|
||||
estimate(password.toLatin1(), parser.isSet(advancedOption));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -15,17 +15,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_ENTROPYMETER_H
|
||||
#define KEEPASSXC_ENTROPYMETER_H
|
||||
#ifndef KEEPASSXC_ESTIMATE_H
|
||||
#define KEEPASSXC_ESTIMATE_H
|
||||
|
||||
#include "Command.h"
|
||||
|
||||
class EntropyMeter : public Command
|
||||
class Estimate : public Command
|
||||
{
|
||||
public:
|
||||
EntropyMeter();
|
||||
~EntropyMeter();
|
||||
Estimate();
|
||||
~Estimate();
|
||||
int execute(QStringList arguments);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_ENTROPYMETER_H
|
||||
#endif // KEEPASSXC_ESTIMATE_H
|
@ -85,7 +85,6 @@ int Extract::execute(QStringList arguments)
|
||||
compositeKey.addKey(fileKey);
|
||||
}
|
||||
|
||||
|
||||
QString databaseFilename = args.at(0);
|
||||
QFile dbFile(databaseFilename);
|
||||
if (!dbFile.exists()) {
|
||||
|
@ -44,8 +44,7 @@ int List::execute(QStringList arguments)
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(this->description);
|
||||
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
|
||||
parser.addPositionalArgument(
|
||||
"group", QObject::tr("Path of the group to list. Default is /"), QString("[group]"));
|
||||
parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), QString("[group]"));
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
QObject::tr("Key file of the database."),
|
||||
|
@ -83,5 +83,4 @@ int Locate::locateEntry(Database* database, QString searchTerm)
|
||||
outputTextStream << result << endl;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
}
|
||||
|
@ -43,10 +43,9 @@ int Merge::execute(QStringList arguments)
|
||||
parser.addPositionalArgument("database1", QObject::tr("Path of the database to merge into."));
|
||||
parser.addPositionalArgument("database2", QObject::tr("Path of the database to merge from."));
|
||||
|
||||
QCommandLineOption samePasswordOption(
|
||||
QStringList() << "s"
|
||||
<< "same-credentials",
|
||||
QObject::tr("Use the same credentials for both database files."));
|
||||
QCommandLineOption samePasswordOption(QStringList() << "s"
|
||||
<< "same-credentials",
|
||||
QObject::tr("Use the same credentials for both database files."));
|
||||
|
||||
QCommandLineOption keyFile(QStringList() << "k"
|
||||
<< "key-file",
|
||||
@ -55,8 +54,8 @@ int Merge::execute(QStringList arguments)
|
||||
parser.addOption(keyFile);
|
||||
QCommandLineOption keyFileFrom(QStringList() << "f"
|
||||
<< "key-file-from",
|
||||
QObject::tr("Key file of the database to merge from."),
|
||||
QObject::tr("path"));
|
||||
QObject::tr("Key file of the database to merge from."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFileFrom);
|
||||
|
||||
parser.addOption(samePasswordOption);
|
||||
@ -68,7 +67,6 @@ int Merge::execute(QStringList arguments)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
Database* db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
|
||||
if (db1 == nullptr) {
|
||||
return EXIT_FAILURE;
|
||||
|
@ -49,6 +49,13 @@ int Show::execute(QStringList arguments)
|
||||
QObject::tr("Key file of the database."),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(keyFile);
|
||||
QCommandLineOption attributes(QStringList() << "a"
|
||||
<< "attributes",
|
||||
QObject::tr("Names of the attributes to show. "
|
||||
"This option can be specified more than once, with each attribute shown one-per-line in the given order. "
|
||||
"If no attributes are specified, a summary of the default attributes is given."),
|
||||
QObject::tr("attribute"));
|
||||
parser.addOption(attributes);
|
||||
parser.addPositionalArgument("entry", QObject::tr("Name of the entry to show."));
|
||||
parser.process(arguments);
|
||||
|
||||
@ -63,10 +70,10 @@ int Show::execute(QStringList arguments)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return this->showEntry(db, args.at(1));
|
||||
return this->showEntry(db, parser.values(attributes), args.at(1));
|
||||
}
|
||||
|
||||
int Show::showEntry(Database* database, QString entryPath)
|
||||
int Show::showEntry(Database* database, QStringList attributes, QString entryPath)
|
||||
{
|
||||
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
@ -78,10 +85,24 @@ int Show::showEntry(Database* database, QString entryPath)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
outputTextStream << " title: " << entry->title() << endl;
|
||||
outputTextStream << "username: " << entry->username() << endl;
|
||||
outputTextStream << "password: " << entry->password() << endl;
|
||||
outputTextStream << " URL: " << entry->url() << endl;
|
||||
outputTextStream << " Notes: " << entry->notes() << endl;
|
||||
return EXIT_SUCCESS;
|
||||
// If no attributes specified, output the default attribute set.
|
||||
bool showAttributeNames = attributes.isEmpty();
|
||||
if (attributes.isEmpty()) {
|
||||
attributes = EntryAttributes::DefaultAttributes;
|
||||
}
|
||||
|
||||
// Iterate over the attributes and output them line-by-line.
|
||||
bool sawUnknownAttribute = false;
|
||||
for (QString attribute : attributes) {
|
||||
if (!entry->attributes()->contains(attribute)) {
|
||||
sawUnknownAttribute = true;
|
||||
qCritical("ERROR: unknown attribute '%s'.", qPrintable(attribute));
|
||||
continue;
|
||||
}
|
||||
if (showAttributeNames) {
|
||||
outputTextStream << attribute << ": ";
|
||||
}
|
||||
outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(attribute)) << endl;
|
||||
}
|
||||
return sawUnknownAttribute ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
Show();
|
||||
~Show();
|
||||
int execute(QStringList arguments);
|
||||
int showEntry(Database* database, QString entryPath);
|
||||
int showEntry(Database* database, QStringList attributes, QString entryPath);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_SHOW_H
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include <QProcess>
|
||||
#include <QTextStream>
|
||||
|
||||
|
||||
void Utils::setStdinEcho(bool enable = true)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -22,8 +22,8 @@ Copies the password of a database entry to the clipboard. If multiple entries wi
|
||||
.IP "edit [options] <database> <entry>"
|
||||
Edits a database entry. A password can be generated (\fI-g\fP option), or a prompt can be displayed to input the password (\fI-p\fP option).
|
||||
|
||||
.IP "entropy-meter [-a pwd1 pwd2 ...]"
|
||||
Calculates the entropy of a single, or multiple passwords specified using the \fI-a\fP option. If no passwords are specified, the program will run in interactive mode and prompt the user to enter a password.
|
||||
.IP "estimate [options] [password]"
|
||||
Estimates the entropy of a password. The password to estimate can be provided as a positional argument, or using the standard input.
|
||||
|
||||
.IP "extract [options] <database>"
|
||||
Extracts and prints the contents of a database to standard output in XML format.
|
||||
@ -90,6 +90,20 @@ Specify the length of the password to generate.
|
||||
Specify the title of the entry.
|
||||
|
||||
|
||||
.SS "Estimate options"
|
||||
|
||||
.IP "-a, --advanced"
|
||||
Perform advanced analysis on the password.
|
||||
|
||||
|
||||
.SS "Show options"
|
||||
|
||||
.IP "-a, --attributes <attribute>..."
|
||||
Names of the attributes to show. This option can be specified more than once,
|
||||
with each attribute shown one-per-line in the given order. If no attributes are
|
||||
specified, a summary of the default attributes is given.
|
||||
|
||||
|
||||
.SH REPORTING BUGS
|
||||
Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues.
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
#cmakedefine WITH_XC_HTTP
|
||||
#cmakedefine WITH_XC_AUTOTYPE
|
||||
#cmakedefine WITH_XC_YUBIKEY
|
||||
#cmakedefine WITH_XC_SSHAGENT
|
||||
|
||||
#cmakedefine KEEPASSXC_DIST
|
||||
#cmakedefine KEEPASSXC_DIST_TYPE "@KEEPASSXC_DIST_TYPE@"
|
||||
|
@ -52,8 +52,9 @@ QVariant Base32::decode(const QByteArray& encodedData)
|
||||
|
||||
int nPads = 0;
|
||||
for (int i = -1; i > -7; --i) {
|
||||
if ('=' == encodedData[encodedData.size() + i])
|
||||
if ('=' == encodedData[encodedData.size() + i]) {
|
||||
++nPads;
|
||||
}
|
||||
}
|
||||
|
||||
int specialOffset;
|
||||
@ -95,11 +96,12 @@ QVariant Base32::decode(const QByteArray& encodedData)
|
||||
int nQuantumBytes = 5;
|
||||
|
||||
for (int n = 0; n < 8; ++n) {
|
||||
quint8 ch = static_cast<quint8>(encodedData[i++]);
|
||||
auto ch = static_cast<quint8>(encodedData[i++]);
|
||||
if ((ASCII_A <= ch && ch <= ASCII_Z) || (ASCII_a <= ch && ch <= ASCII_z)) {
|
||||
ch -= ASCII_A;
|
||||
if (ch >= ALPH_POS_2)
|
||||
if (ch >= ALPH_POS_2) {
|
||||
ch -= ASCII_a - ASCII_A;
|
||||
}
|
||||
} else {
|
||||
if (ASCII_2 <= ch && ch <= ASCII_7) {
|
||||
ch -= ASCII_2;
|
||||
@ -126,8 +128,8 @@ QVariant Base32::decode(const QByteArray& encodedData)
|
||||
const int offset = (nQuantumBytes - 1) * 8;
|
||||
quint64 mask = quint64(0xFF) << offset;
|
||||
for (int n = offset; n >= 0 && o < nBytes; n -= 8) {
|
||||
data[o++] = static_cast<char>((quantum & mask) >> n);
|
||||
mask >>= 8;
|
||||
data[o++] = static_cast<char>((quantum & mask) >> n);
|
||||
mask >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +149,7 @@ QByteArray Base32::encode(const QByteArray& data)
|
||||
const int rBits = nBits % 40; // in {0, 8, 16, 24, 32}
|
||||
const int nQuanta = nBits / 40 + (rBits > 0 ? 1 : 0);
|
||||
const int nBytes = nQuanta * 8;
|
||||
QByteArray encodedData(nQuanta * 8, Qt::Uninitialized);
|
||||
QByteArray encodedData(nBytes, Qt::Uninitialized);
|
||||
|
||||
int i = 0;
|
||||
int o = 0;
|
||||
@ -166,6 +168,7 @@ QByteArray Base32::encode(const QByteArray& data)
|
||||
int index;
|
||||
for (n = 35; n >= 0; n -= 5) {
|
||||
index = (quantum & mask) >> n;
|
||||
Q_ASSERT(0 <= index && index <= 31);
|
||||
encodedData[o++] = alphabet[index];
|
||||
mask >>= 5;
|
||||
}
|
||||
@ -173,10 +176,11 @@ QByteArray Base32::encode(const QByteArray& data)
|
||||
|
||||
// < 40-bits of input at final input group
|
||||
if (i < data.size()) {
|
||||
Q_ASSERT(rBits > 0);
|
||||
Q_ASSERT(8 <= rBits && rBits <= 32);
|
||||
quantum = 0;
|
||||
for (n = rBits - 8; n >= 0; n -= 8)
|
||||
for (n = rBits - 8; n >= 0; n -= 8) {
|
||||
quantum |= static_cast<quint64>(data[i++]) << n;
|
||||
}
|
||||
|
||||
switch (rBits) {
|
||||
case 8: // expand to 10 bits
|
||||
@ -195,7 +199,7 @@ QByteArray Base32::encode(const QByteArray& data)
|
||||
n = 20;
|
||||
break;
|
||||
default: // expand to 35 bits
|
||||
Q_ASSERT(rBits == 32);
|
||||
Q_ASSERT(32 == rBits);
|
||||
quantum <<= 3;
|
||||
mask = MASK_35BIT;
|
||||
n = 30;
|
||||
@ -203,6 +207,7 @@ QByteArray Base32::encode(const QByteArray& data)
|
||||
|
||||
while (n >= 0) {
|
||||
int index = (quantum & mask) >> n;
|
||||
Q_ASSERT(0 <= index && index <= 31);
|
||||
encodedData[o++] = alphabet[index];
|
||||
mask >>= 5;
|
||||
n -= 5;
|
||||
|
@ -114,7 +114,6 @@ void Config::init(const QString& fileName)
|
||||
m_defaults.insert("AutoSaveAfterEveryChange", false);
|
||||
m_defaults.insert("AutoReloadOnChange", true);
|
||||
m_defaults.insert("AutoSaveOnExit", false);
|
||||
m_defaults.insert("ShowToolbar", true);
|
||||
m_defaults.insert("SearchLimitGroup", false);
|
||||
m_defaults.insert("MinimizeOnCopy", false);
|
||||
m_defaults.insert("UseGroupIconOnEntryCreation", false);
|
||||
|
@ -95,10 +95,15 @@ const Metadata* Database::metadata() const
|
||||
|
||||
Entry* Database::resolveEntry(const Uuid& uuid)
|
||||
{
|
||||
return recFindEntry(uuid, m_rootGroup);
|
||||
return findEntryRecursive(uuid, m_rootGroup);
|
||||
}
|
||||
|
||||
Entry* Database::recFindEntry(const Uuid& uuid, Group* group)
|
||||
Entry* Database::resolveEntry(const QString& text, EntryReferenceType referenceType)
|
||||
{
|
||||
return findEntryRecursive(text, referenceType, m_rootGroup);
|
||||
}
|
||||
|
||||
Entry* Database::findEntryRecursive(const Uuid& uuid, Group* group)
|
||||
{
|
||||
const QList<Entry*> entryList = group->entries();
|
||||
for (Entry* entry : entryList) {
|
||||
@ -109,7 +114,57 @@ Entry* Database::recFindEntry(const Uuid& uuid, Group* group)
|
||||
|
||||
const QList<Group*> children = group->children();
|
||||
for (Group* child : children) {
|
||||
Entry* result = recFindEntry(uuid, child);
|
||||
Entry* result = findEntryRecursive(uuid, child);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Entry* Database::findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group)
|
||||
{
|
||||
Q_ASSERT_X(referenceType != EntryReferenceType::Unknown, "Database::findEntryRecursive",
|
||||
"Can't search entry with \"referenceType\" parameter equal to \"Unknown\"");
|
||||
|
||||
bool found = false;
|
||||
const QList<Entry*> entryList = group->entries();
|
||||
for (Entry* entry : entryList) {
|
||||
switch (referenceType) {
|
||||
case EntryReferenceType::Unknown:
|
||||
return nullptr;
|
||||
case EntryReferenceType::Title:
|
||||
found = entry->title() == text;
|
||||
break;
|
||||
case EntryReferenceType::UserName:
|
||||
found = entry->username() == text;
|
||||
break;
|
||||
case EntryReferenceType::Password:
|
||||
found = entry->password() == text;
|
||||
break;
|
||||
case EntryReferenceType::Url:
|
||||
found = entry->url() == text;
|
||||
break;
|
||||
case EntryReferenceType::Notes:
|
||||
found = entry->notes() == text;
|
||||
break;
|
||||
case EntryReferenceType::Uuid:
|
||||
found = entry->uuid() == Uuid::fromHex(text);
|
||||
break;
|
||||
case EntryReferenceType::CustomAttributes:
|
||||
found = entry->attributes()->containsValue(text);
|
||||
break;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
const QList<Group*> children = group->children();
|
||||
for (Group* child : children) {
|
||||
Entry* result = findEntryRecursive(text, referenceType, child);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
@ -120,10 +175,10 @@ Entry* Database::recFindEntry(const Uuid& uuid, Group* group)
|
||||
|
||||
Group* Database::resolveGroup(const Uuid& uuid)
|
||||
{
|
||||
return recFindGroup(uuid, m_rootGroup);
|
||||
return findGroupRecursive(uuid, m_rootGroup);
|
||||
}
|
||||
|
||||
Group* Database::recFindGroup(const Uuid& uuid, Group* group)
|
||||
Group* Database::findGroupRecursive(const Uuid& uuid, Group* group)
|
||||
{
|
||||
if (group->uuid() == uuid) {
|
||||
return group;
|
||||
@ -131,7 +186,7 @@ Group* Database::recFindGroup(const Uuid& uuid, Group* group)
|
||||
|
||||
const QList<Group*> children = group->children();
|
||||
for (Group* child : children) {
|
||||
Group* result = recFindGroup(uuid, child);
|
||||
Group* result = findGroupRecursive(uuid, child);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "keys/CompositeKey.h"
|
||||
|
||||
class Entry;
|
||||
enum class EntryReferenceType;
|
||||
class Group;
|
||||
class Metadata;
|
||||
class QTimer;
|
||||
@ -81,6 +82,7 @@ public:
|
||||
Metadata* metadata();
|
||||
const Metadata* metadata() const;
|
||||
Entry* resolveEntry(const Uuid& uuid);
|
||||
Entry* resolveEntry(const QString& text, EntryReferenceType referenceType);
|
||||
Group* resolveGroup(const Uuid& uuid);
|
||||
QList<DeletedObject> deletedObjects();
|
||||
void addDeletedObject(const DeletedObject& delObj);
|
||||
@ -141,8 +143,9 @@ private slots:
|
||||
void startModifiedTimer();
|
||||
|
||||
private:
|
||||
Entry* recFindEntry(const Uuid& uuid, Group* group);
|
||||
Group* recFindGroup(const Uuid& uuid, Group* group);
|
||||
Entry* findEntryRecursive(const Uuid& uuid, Group* group);
|
||||
Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group);
|
||||
Group* findGroupRecursive(const Uuid& uuid, Group* group);
|
||||
|
||||
void createRecycleBin();
|
||||
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include "core/Metadata.h"
|
||||
#include "totp/totp.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
const int Entry::DefaultIconNumber = 0;
|
||||
const int Entry::ResolveMaximumDepth = 10;
|
||||
|
||||
@ -40,8 +42,8 @@ Entry::Entry()
|
||||
m_data.iconNumber = DefaultIconNumber;
|
||||
m_data.autoTypeEnabled = true;
|
||||
m_data.autoTypeObfuscation = 0;
|
||||
m_data.totpStep = QTotp::defaultStep;
|
||||
m_data.totpDigits = QTotp::defaultDigits;
|
||||
m_data.totpStep = Totp::defaultStep;
|
||||
m_data.totpDigits = Totp::defaultDigits;
|
||||
|
||||
connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified()));
|
||||
connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged()));
|
||||
@ -90,6 +92,29 @@ void Entry::setUpdateTimeinfo(bool value)
|
||||
m_updateTimeinfo = value;
|
||||
}
|
||||
|
||||
EntryReferenceType Entry::referenceType(const QString& referenceStr)
|
||||
{
|
||||
const QString referenceLowerStr = referenceStr.toLower();
|
||||
EntryReferenceType result = EntryReferenceType::Unknown;
|
||||
if (referenceLowerStr == QLatin1String("t")) {
|
||||
result = EntryReferenceType::Title;
|
||||
} else if (referenceLowerStr == QLatin1String("u")) {
|
||||
result = EntryReferenceType::UserName;
|
||||
} else if (referenceLowerStr == QLatin1String("p")) {
|
||||
result = EntryReferenceType::Password;
|
||||
} else if (referenceLowerStr == QLatin1String("a")) {
|
||||
result = EntryReferenceType::Url;
|
||||
} else if (referenceLowerStr == QLatin1String("n")) {
|
||||
result = EntryReferenceType::Notes;
|
||||
} else if (referenceLowerStr == QLatin1String("i")) {
|
||||
result = EntryReferenceType::Uuid;
|
||||
} else if (referenceLowerStr == QLatin1String("o")) {
|
||||
result = EntryReferenceType::CustomAttributes;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Uuid Entry::uuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
@ -315,7 +340,7 @@ QString Entry::totp() const
|
||||
if (hasTotp()) {
|
||||
QString seed = totpSeed();
|
||||
quint64 time = QDateTime::currentDateTime().toTime_t();
|
||||
QString output = QTotp::generateTotp(seed.toLatin1(), time, m_data.totpDigits, m_data.totpStep);
|
||||
QString output = Totp::generateTotp(seed.toLatin1(), time, m_data.totpDigits, m_data.totpStep);
|
||||
|
||||
return QString(output);
|
||||
} else {
|
||||
@ -326,18 +351,30 @@ QString Entry::totp() const
|
||||
void Entry::setTotp(const QString& seed, quint8& step, quint8& digits)
|
||||
{
|
||||
if (step == 0) {
|
||||
step = QTotp::defaultStep;
|
||||
step = Totp::defaultStep;
|
||||
}
|
||||
|
||||
if (digits == 0) {
|
||||
digits = QTotp::defaultDigits;
|
||||
digits = Totp::defaultDigits;
|
||||
}
|
||||
QString data;
|
||||
|
||||
const Totp::Encoder & enc = Totp::encoders.value(digits, Totp::defaultEncoder);
|
||||
|
||||
if (m_attributes->hasKey("otp")) {
|
||||
m_attributes->set("otp", QString("key=%1&step=%2&size=%3").arg(seed).arg(step).arg(digits), true);
|
||||
data = QString("key=%1&step=%2&size=%3").arg(seed).arg(step).arg(enc.digits == 0 ? digits : enc.digits);
|
||||
if (!enc.name.isEmpty()) {
|
||||
data.append("&enocder=").append(enc.name);
|
||||
}
|
||||
m_attributes->set("otp", data, true);
|
||||
} else {
|
||||
m_attributes->set("TOTP Seed", seed, true);
|
||||
m_attributes->set("TOTP Settings", QString("%1;%2").arg(step).arg(digits));
|
||||
if (!enc.shortName.isEmpty()) {
|
||||
data = QString("%1;%2").arg(step).arg(enc.shortName);
|
||||
} else {
|
||||
data = QString("%1;%2").arg(step).arg(digits);
|
||||
}
|
||||
m_attributes->set("TOTP Settings", data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,19 +388,24 @@ QString Entry::totpSeed() const
|
||||
secret = m_attributes->value("TOTP Seed");
|
||||
}
|
||||
|
||||
m_data.totpDigits = QTotp::defaultDigits;
|
||||
m_data.totpStep = QTotp::defaultStep;
|
||||
m_data.totpDigits = Totp::defaultDigits;
|
||||
m_data.totpStep = Totp::defaultStep;
|
||||
|
||||
if (m_attributes->hasKey("TOTP Settings")) {
|
||||
QRegExp rx("(\\d+);(\\d)", Qt::CaseInsensitive, QRegExp::RegExp);
|
||||
int pos = rx.indexIn(m_attributes->value("TOTP Settings"));
|
||||
if (pos > -1) {
|
||||
m_data.totpStep = rx.cap(1).toUInt();
|
||||
m_data.totpDigits = rx.cap(2).toUInt();
|
||||
// this regex must be kept in sync with the set of allowed short names Totp::shortNameToEncoder
|
||||
QRegularExpression rx(QString("(\\d+);((?:\\d+)|S)"));
|
||||
QRegularExpressionMatch m = rx.match(m_attributes->value("TOTP Settings"));
|
||||
if (m.hasMatch()) {
|
||||
m_data.totpStep = m.captured(1).toUInt();
|
||||
if (Totp::shortNameToEncoder.contains(m.captured(2))) {
|
||||
m_data.totpDigits = Totp::shortNameToEncoder[m.captured(2)];
|
||||
} else {
|
||||
m_data.totpDigits = m.captured(2).toUInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QTotp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep);
|
||||
return Totp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep);
|
||||
}
|
||||
|
||||
quint8 Entry::totpStep() const
|
||||
@ -680,7 +722,7 @@ void Entry::updateModifiedSinceBegin()
|
||||
m_modifiedSinceBegin = true;
|
||||
}
|
||||
|
||||
QString Entry::resolveMultiplePlaceholdersRecursive(const QString &str, int maxDepth) const
|
||||
QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const
|
||||
{
|
||||
if (maxDepth <= 0) {
|
||||
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", qPrintable(uuid().toHex()));
|
||||
@ -704,15 +746,12 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString &str, int maxD
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Entry::resolvePlaceholderRecursive(const QString &placeholder, int maxDepth) const
|
||||
QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDepth) const
|
||||
{
|
||||
const PlaceholderType typeOfPlaceholder = placeholderType(placeholder);
|
||||
switch (typeOfPlaceholder) {
|
||||
case PlaceholderType::NotPlaceholder:
|
||||
return placeholder;
|
||||
case PlaceholderType::Unknown:
|
||||
qWarning("Can't resolve placeholder %s for entry with uuid %s", qPrintable(placeholder),
|
||||
qPrintable(uuid().toHex()));
|
||||
return placeholder;
|
||||
case PlaceholderType::Title:
|
||||
return title();
|
||||
@ -743,45 +782,64 @@ QString Entry::resolvePlaceholderRecursive(const QString &placeholder, int maxDe
|
||||
const QString key = placeholder.mid(3, placeholder.length() - 4); // {S:attr} => mid(3, len - 4)
|
||||
return attributes()->hasKey(key) ? attributes()->value(key) : QString();
|
||||
}
|
||||
case PlaceholderType::Reference: {
|
||||
// resolving references in format: {REF:<WantedField>@I:<uuid of referenced entry>}
|
||||
// using format from http://keepass.info/help/base/fieldrefs.html at the time of writing,
|
||||
// but supporting lookups of standard fields and references by UUID only
|
||||
|
||||
QString result;
|
||||
QRegExp* referenceRegExp = m_attributes->referenceRegExp();
|
||||
if (referenceRegExp->indexIn(placeholder) != -1) {
|
||||
constexpr int wantedFieldIndex = 1;
|
||||
constexpr int referencedUuidIndex = 2;
|
||||
const Uuid referencedUuid(QByteArray::fromHex(referenceRegExp->cap(referencedUuidIndex).toLatin1()));
|
||||
const Entry* refEntry = m_group->database()->resolveEntry(referencedUuid);
|
||||
if (refEntry) {
|
||||
const QString wantedField = referenceRegExp->cap(wantedFieldIndex).toLower();
|
||||
if (wantedField == "t") {
|
||||
result = refEntry->title();
|
||||
} else if (wantedField == "u") {
|
||||
result = refEntry->username();
|
||||
} else if (wantedField == "p") {
|
||||
result = refEntry->password();
|
||||
} else if (wantedField == "a") {
|
||||
result = refEntry->url();
|
||||
} else if (wantedField == "n") {
|
||||
result = refEntry->notes();
|
||||
}
|
||||
|
||||
// Referencing fields of other entries only works with standard fields, not with custom user strings.
|
||||
// If you want to reference a custom user string, you need to place a redirection in a standard field
|
||||
// of the entry with the custom string, using {S:<Name>}, and reference the standard field.
|
||||
result = refEntry->resolveMultiplePlaceholdersRecursive(result, maxDepth - 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case PlaceholderType::Reference:
|
||||
return resolveReferencePlaceholderRecursive(placeholder, maxDepth);
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const
|
||||
{
|
||||
// resolving references in format: {REF:<WantedField>@<SearchIn>:<SearchText>}
|
||||
// using format from http://keepass.info/help/base/fieldrefs.html at the time of writing
|
||||
|
||||
QRegularExpressionMatch match = EntryAttributes::matchReference(placeholder);
|
||||
if (!match.hasMatch()) {
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
QString result;
|
||||
const QString searchIn = match.captured(EntryAttributes::SearchInGroupName);
|
||||
const QString searchText = match.captured(EntryAttributes::SearchTextGroupName);
|
||||
|
||||
const EntryReferenceType searchInType = Entry::referenceType(searchIn);
|
||||
const Entry* refEntry = m_group->database()->resolveEntry(searchText, searchInType);
|
||||
|
||||
if (refEntry) {
|
||||
const QString wantedField = match.captured(EntryAttributes::WantedFieldGroupName);
|
||||
result = refEntry->referenceFieldValue(Entry::referenceType(wantedField));
|
||||
|
||||
// Referencing fields of other entries only works with standard fields, not with custom user strings.
|
||||
// If you want to reference a custom user string, you need to place a redirection in a standard field
|
||||
// of the entry with the custom string, using {S:<Name>}, and reference the standard field.
|
||||
result = refEntry->resolveMultiplePlaceholdersRecursive(result, maxDepth - 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Entry::referenceFieldValue(EntryReferenceType referenceType) const
|
||||
{
|
||||
switch (referenceType) {
|
||||
case EntryReferenceType::Title:
|
||||
return title();
|
||||
case EntryReferenceType::UserName:
|
||||
return username();
|
||||
case EntryReferenceType::Password:
|
||||
return password();
|
||||
case EntryReferenceType::Url:
|
||||
return url();
|
||||
case EntryReferenceType::Notes:
|
||||
return notes();
|
||||
case EntryReferenceType::Uuid:
|
||||
return uuid().toHex();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
Group* Entry::group()
|
||||
{
|
||||
return m_group;
|
||||
@ -839,7 +897,7 @@ const Database* Entry::database() const
|
||||
}
|
||||
}
|
||||
|
||||
QString Entry::maskPasswordPlaceholders(const QString &str) const
|
||||
QString Entry::maskPasswordPlaceholders(const QString& str) const
|
||||
{
|
||||
QString result = str;
|
||||
result.replace(QRegExp("(\\{PASSWORD\\})", Qt::CaseInsensitive, QRegExp::RegExp2), "******");
|
||||
@ -856,7 +914,7 @@ QString Entry::resolvePlaceholder(const QString& placeholder) const
|
||||
return resolvePlaceholderRecursive(placeholder, ResolveMaximumDepth);
|
||||
}
|
||||
|
||||
QString Entry::resolveUrlPlaceholder(const QString &str, Entry::PlaceholderType placeholderType) const
|
||||
QString Entry::resolveUrlPlaceholder(const QString& str, Entry::PlaceholderType placeholderType) const
|
||||
{
|
||||
if (str.isEmpty())
|
||||
return QString();
|
||||
@ -892,7 +950,7 @@ QString Entry::resolveUrlPlaceholder(const QString &str, Entry::PlaceholderType
|
||||
return QString();
|
||||
}
|
||||
|
||||
Entry::PlaceholderType Entry::placeholderType(const QString &placeholder) const
|
||||
Entry::PlaceholderType Entry::placeholderType(const QString& placeholder) const
|
||||
{
|
||||
if (!placeholder.startsWith(QLatin1Char('{')) || !placeholder.endsWith(QLatin1Char('}'))) {
|
||||
return PlaceholderType::NotPlaceholder;
|
||||
|
@ -36,6 +36,17 @@
|
||||
class Database;
|
||||
class Group;
|
||||
|
||||
enum class EntryReferenceType {
|
||||
Unknown,
|
||||
Title,
|
||||
UserName,
|
||||
Password,
|
||||
Url,
|
||||
Notes,
|
||||
Uuid,
|
||||
CustomAttributes
|
||||
};
|
||||
|
||||
struct EntryData
|
||||
{
|
||||
int iconNumber;
|
||||
@ -170,7 +181,7 @@ public:
|
||||
QString maskPasswordPlaceholders(const QString& str) const;
|
||||
QString resolveMultiplePlaceholders(const QString& str) const;
|
||||
QString resolvePlaceholder(const QString& str) const;
|
||||
QString resolveUrlPlaceholder(const QString &str, PlaceholderType placeholderType) const;
|
||||
QString resolveUrlPlaceholder(const QString& str, PlaceholderType placeholderType) const;
|
||||
PlaceholderType placeholderType(const QString& placeholder) const;
|
||||
QString resolveUrl(const QString& url) const;
|
||||
|
||||
@ -203,6 +214,10 @@ private slots:
|
||||
private:
|
||||
QString resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const;
|
||||
QString resolvePlaceholderRecursive(const QString& placeholder, int maxDepth) const;
|
||||
QString resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const;
|
||||
QString referenceFieldValue(EntryReferenceType referenceType) const;
|
||||
|
||||
static EntryReferenceType referenceType(const QString& referenceStr);
|
||||
|
||||
const Database* database() const;
|
||||
template <class T> bool set(T& property, const T& value);
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
#include "EntryAttachments.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
EntryAttachments::EntryAttachments(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
@ -71,7 +73,8 @@ void EntryAttachments::set(const QString& key, const QByteArray& value)
|
||||
void EntryAttachments::remove(const QString& key)
|
||||
{
|
||||
if (!m_attachments.contains(key)) {
|
||||
Q_ASSERT(false);
|
||||
Q_ASSERT_X(false, "EntryAttachments::remove",
|
||||
qPrintable(QString("Can't find attachment for key %1").arg(key)));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -83,6 +86,31 @@ void EntryAttachments::remove(const QString& key)
|
||||
emit modified();
|
||||
}
|
||||
|
||||
void EntryAttachments::remove(const QStringList& keys)
|
||||
{
|
||||
if (keys.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool isModified = false;
|
||||
for (const QString &key: keys) {
|
||||
if (!m_attachments.contains(key)) {
|
||||
Q_ASSERT_X(false, "EntryAttachments::remove",
|
||||
qPrintable(QString("Can't find attachment for key %1").arg(key)));
|
||||
continue;
|
||||
}
|
||||
|
||||
isModified = true;
|
||||
emit aboutToBeRemoved(key);
|
||||
m_attachments.remove(key);
|
||||
emit removed(key);
|
||||
}
|
||||
|
||||
if (isModified) {
|
||||
emit modified();
|
||||
}
|
||||
}
|
||||
|
||||
void EntryAttachments::clear()
|
||||
{
|
||||
if (m_attachments.isEmpty()) {
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
||||
class QStringList;
|
||||
|
||||
class EntryAttachments : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -33,6 +35,7 @@ public:
|
||||
QByteArray value(const QString& key) const;
|
||||
void set(const QString& key, const QByteArray& value);
|
||||
void remove(const QString& key);
|
||||
void remove(const QStringList& keys);
|
||||
void clear();
|
||||
void copyDataFrom(const EntryAttachments* other);
|
||||
bool operator==(const EntryAttachments& other) const;
|
||||
|
@ -25,11 +25,15 @@ const QString EntryAttributes::URLKey = "URL";
|
||||
const QString EntryAttributes::NotesKey = "Notes";
|
||||
const QStringList EntryAttributes::DefaultAttributes(QStringList() << TitleKey << UserNameKey
|
||||
<< PasswordKey << URLKey << NotesKey);
|
||||
|
||||
const QString EntryAttributes::WantedFieldGroupName = "WantedField";
|
||||
const QString EntryAttributes::SearchInGroupName = "SearchIn";
|
||||
const QString EntryAttributes::SearchTextGroupName = "SearchText";
|
||||
|
||||
const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
|
||||
|
||||
EntryAttributes::EntryAttributes(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_referenceRegExp("\\{REF:([TUPAN])@I:([^}]+)\\}", Qt::CaseInsensitive, QRegExp::RegExp2)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
@ -61,11 +65,16 @@ QString EntryAttributes::value(const QString& key) const
|
||||
return m_attributes.value(key);
|
||||
}
|
||||
|
||||
bool EntryAttributes::contains(const QString &key) const
|
||||
bool EntryAttributes::contains(const QString& key) const
|
||||
{
|
||||
return m_attributes.contains(key);
|
||||
}
|
||||
|
||||
bool EntryAttributes::containsValue(const QString& value) const
|
||||
{
|
||||
return m_attributes.values().contains(value);
|
||||
}
|
||||
|
||||
bool EntryAttributes::isProtected(const QString& key) const
|
||||
{
|
||||
return m_protectedAttributes.contains(key);
|
||||
@ -78,16 +87,8 @@ bool EntryAttributes::isReference(const QString& key) const
|
||||
return false;
|
||||
}
|
||||
|
||||
QString data = value(key);
|
||||
if (m_referenceRegExp.indexIn(data) != -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QRegExp* EntryAttributes::referenceRegExp()
|
||||
{
|
||||
return &m_referenceRegExp;
|
||||
const QString data = value(key);
|
||||
return matchReference(data).hasMatch();
|
||||
}
|
||||
|
||||
void EntryAttributes::set(const QString& key, const QString& value, bool protect)
|
||||
@ -258,6 +259,15 @@ bool EntryAttributes::operator!=(const EntryAttributes& other) const
|
||||
|| m_protectedAttributes != other.m_protectedAttributes);
|
||||
}
|
||||
|
||||
QRegularExpressionMatch EntryAttributes::matchReference(const QString& text)
|
||||
{
|
||||
static QRegularExpression referenceRegExp(
|
||||
"\\{REF:(?<WantedField>[TUPANI])@(?<SearchIn>[TUPANIO]):(?<SearchText>[^}]+)\\}",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
return referenceRegExp.match(text);
|
||||
}
|
||||
|
||||
void EntryAttributes::clear()
|
||||
{
|
||||
emit aboutToBeReset();
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
#include <QStringList>
|
||||
|
||||
@ -35,9 +36,9 @@ public:
|
||||
QList<QString> customKeys();
|
||||
QString value(const QString& key) const;
|
||||
bool contains(const QString& key) const;
|
||||
bool containsValue(const QString& value) const;
|
||||
bool isProtected(const QString& key) const;
|
||||
bool isReference(const QString& key) const;
|
||||
QRegExp* referenceRegExp();
|
||||
void set(const QString& key, const QString& value, bool protect = false);
|
||||
void remove(const QString& key);
|
||||
void rename(const QString& oldKey, const QString& newKey);
|
||||
@ -49,6 +50,8 @@ public:
|
||||
bool operator==(const EntryAttributes& other) const;
|
||||
bool operator!=(const EntryAttributes& other) const;
|
||||
|
||||
static QRegularExpressionMatch matchReference(const QString& text);
|
||||
|
||||
static const QString TitleKey;
|
||||
static const QString UserNameKey;
|
||||
static const QString PasswordKey;
|
||||
@ -58,6 +61,10 @@ public:
|
||||
static const QString RememberCmdExecAttr;
|
||||
static bool isDefaultAttribute(const QString& key);
|
||||
|
||||
static const QString WantedFieldGroupName;
|
||||
static const QString SearchInGroupName;
|
||||
static const QString SearchTextGroupName;
|
||||
|
||||
signals:
|
||||
void modified();
|
||||
void defaultKeyModified();
|
||||
@ -74,7 +81,6 @@ signals:
|
||||
private:
|
||||
QMap<QString, QString> m_attributes;
|
||||
QSet<QString> m_protectedAttributes;
|
||||
QRegExp m_referenceRegExp;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_ENTRYATTRIBUTES_H
|
||||
|
@ -26,6 +26,11 @@
|
||||
const int Group::DefaultIconNumber = 48;
|
||||
const int Group::RecycleBinIconNumber = 43;
|
||||
|
||||
Group::CloneFlags Group::DefaultCloneFlags = static_cast<Group::CloneFlags>(
|
||||
Group::CloneNewUuid | Group::CloneResetTimeInfo | Group::CloneIncludeEntries);
|
||||
Entry::CloneFlags Group::DefaultEntryCloneFlags = static_cast<Entry::CloneFlags>(
|
||||
Entry::CloneNewUuid | Entry::CloneResetTimeInfo);
|
||||
|
||||
Group::Group()
|
||||
: m_updateTimeinfo(true)
|
||||
{
|
||||
@ -77,8 +82,7 @@ template <class P, class V> inline bool Group::set(P& property, const V& value)
|
||||
updateTimeinfo();
|
||||
emit modified();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -682,38 +686,59 @@ void Group::merge(const Group* other)
|
||||
|
||||
Entry* existingEntry = rootGroup->findEntryByUuid(entry->uuid());
|
||||
|
||||
// This entry does not exist at all. Create it.
|
||||
if (!existingEntry) {
|
||||
// This entry does not exist at all. Create it.
|
||||
qDebug("New entry %s detected. Creating it.", qPrintable(entry->title()));
|
||||
entry->clone(Entry::CloneIncludeHistory)->setGroup(this);
|
||||
// Entry is already present in the database. Update it.
|
||||
} else {
|
||||
// Entry is already present in the database. Update it.
|
||||
bool locationChanged = existingEntry->timeInfo().locationChanged() < entry->timeInfo().locationChanged();
|
||||
if (locationChanged && existingEntry->group() != this) {
|
||||
existingEntry->setGroup(this);
|
||||
qDebug("Location changed for entry %s. Updating it", qPrintable(existingEntry->title()));
|
||||
}
|
||||
resolveConflict(existingEntry, entry);
|
||||
resolveEntryConflict(existingEntry, entry);
|
||||
}
|
||||
}
|
||||
|
||||
// merge groups recursively
|
||||
const QList<Group*> dbChildren = other->children();
|
||||
for (Group* group : dbChildren) {
|
||||
// groups are searched by name instead of uuid
|
||||
if (findChildByName(group->name())) {
|
||||
findChildByName(group->name())->merge(group);
|
||||
} else {
|
||||
|
||||
Group* existingGroup = rootGroup->findChildByUuid(group->uuid());
|
||||
|
||||
if (!existingGroup) {
|
||||
qDebug("New group %s detected. Creating it.", qPrintable(group->name()));
|
||||
Group* newGroup = group->clone(Entry::CloneNoFlags, true);
|
||||
Group* newGroup = group->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
|
||||
newGroup->setParent(this);
|
||||
newGroup->merge(group);
|
||||
} else {
|
||||
bool locationChanged = existingGroup->timeInfo().locationChanged() < group->timeInfo().locationChanged();
|
||||
if (locationChanged && existingGroup->parent() != this) {
|
||||
existingGroup->setParent(this);
|
||||
qDebug("Location changed for group %s. Updating it", qPrintable(existingGroup->name()));
|
||||
}
|
||||
resolveGroupConflict(existingGroup, group);
|
||||
existingGroup->merge(group);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
emit modified();
|
||||
}
|
||||
|
||||
Group* Group::findChildByUuid(const Uuid& uuid)
|
||||
{
|
||||
Q_ASSERT(!uuid.isNull());
|
||||
for (Group* group : groupsRecursive(true)) {
|
||||
if (group->uuid() == uuid) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Group* Group::findChildByName(const QString& name)
|
||||
{
|
||||
for (Group* group : asConst(m_children)) {
|
||||
@ -725,16 +750,21 @@ Group* Group::findChildByName(const QString& name)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Group* Group::clone(Entry::CloneFlags entryFlags, bool shallow) const
|
||||
Group* Group::clone(Entry::CloneFlags entryFlags, Group::CloneFlags groupFlags) const
|
||||
{
|
||||
Group* clonedGroup = new Group();
|
||||
|
||||
clonedGroup->setUpdateTimeinfo(false);
|
||||
|
||||
clonedGroup->setUuid(Uuid::random());
|
||||
if (groupFlags & Group::CloneNewUuid) {
|
||||
clonedGroup->setUuid(Uuid::random());
|
||||
} else {
|
||||
clonedGroup->setUuid(this->uuid());
|
||||
}
|
||||
|
||||
clonedGroup->m_data = m_data;
|
||||
|
||||
if (!shallow) {
|
||||
if (groupFlags & Group::CloneIncludeEntries) {
|
||||
const QList<Entry*> entryList = entries();
|
||||
for (Entry* entry : entryList) {
|
||||
Entry* clonedEntry = entry->clone(entryFlags);
|
||||
@ -743,18 +773,20 @@ Group* Group::clone(Entry::CloneFlags entryFlags, bool shallow) const
|
||||
|
||||
const QList<Group*> childrenGroups = children();
|
||||
for (Group* groupChild : childrenGroups) {
|
||||
Group* clonedGroupChild = groupChild->clone(entryFlags);
|
||||
Group* clonedGroupChild = groupChild->clone(entryFlags, groupFlags);
|
||||
clonedGroupChild->setParent(clonedGroup);
|
||||
}
|
||||
}
|
||||
|
||||
clonedGroup->setUpdateTimeinfo(true);
|
||||
if (groupFlags & Group::CloneResetTimeInfo) {
|
||||
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
clonedGroup->m_data.timeInfo.setCreationTime(now);
|
||||
clonedGroup->m_data.timeInfo.setLastModificationTime(now);
|
||||
clonedGroup->m_data.timeInfo.setLastAccessTime(now);
|
||||
clonedGroup->m_data.timeInfo.setLocationChanged(now);
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
clonedGroup->m_data.timeInfo.setCreationTime(now);
|
||||
clonedGroup->m_data.timeInfo.setLastModificationTime(now);
|
||||
clonedGroup->m_data.timeInfo.setLastAccessTime(now);
|
||||
clonedGroup->m_data.timeInfo.setLocationChanged(now);
|
||||
}
|
||||
|
||||
return clonedGroup;
|
||||
}
|
||||
@ -908,7 +940,7 @@ bool Group::resolveAutoTypeEnabled() const
|
||||
}
|
||||
}
|
||||
|
||||
void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)
|
||||
void Group::resolveEntryConflict(Entry* existingEntry, Entry* otherEntry)
|
||||
{
|
||||
const QDateTime timeExisting = existingEntry->timeInfo().lastModificationTime();
|
||||
const QDateTime timeOther = otherEntry->timeInfo().lastModificationTime();
|
||||
@ -946,6 +978,26 @@ void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)
|
||||
}
|
||||
}
|
||||
|
||||
void Group::resolveGroupConflict(Group* existingGroup, Group* otherGroup)
|
||||
{
|
||||
const QDateTime timeExisting = existingGroup->timeInfo().lastModificationTime();
|
||||
const QDateTime timeOther = otherGroup->timeInfo().lastModificationTime();
|
||||
|
||||
// only if the other group is newer, update the existing one.
|
||||
if (timeExisting < timeOther) {
|
||||
qDebug("Updating group %s.", qPrintable(existingGroup->name()));
|
||||
existingGroup->setName(otherGroup->name());
|
||||
existingGroup->setNotes(otherGroup->notes());
|
||||
if (otherGroup->iconNumber() == 0) {
|
||||
existingGroup->setIcon(otherGroup->iconUuid());
|
||||
} else {
|
||||
existingGroup->setIcon(otherGroup->iconNumber());
|
||||
}
|
||||
existingGroup->setExpiryTime(otherGroup->timeInfo().expiryTime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QStringList Group::locate(QString locateTerm, QString currentPath)
|
||||
{
|
||||
Q_ASSERT(!locateTerm.isNull());
|
||||
|
@ -37,6 +37,14 @@ public:
|
||||
enum TriState { Inherit, Enable, Disable };
|
||||
enum MergeMode { ModeInherit, KeepBoth, KeepNewer, KeepExisting };
|
||||
|
||||
enum CloneFlag {
|
||||
CloneNoFlags = 0,
|
||||
CloneNewUuid = 1, // generate a random uuid for the clone
|
||||
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
|
||||
CloneIncludeEntries = 4, // clone the group entries
|
||||
};
|
||||
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)
|
||||
|
||||
struct GroupData
|
||||
{
|
||||
QString name;
|
||||
@ -78,8 +86,11 @@ public:
|
||||
|
||||
static const int DefaultIconNumber;
|
||||
static const int RecycleBinIconNumber;
|
||||
static CloneFlags DefaultCloneFlags;
|
||||
static Entry::CloneFlags DefaultEntryCloneFlags;
|
||||
|
||||
Group* findChildByName(const QString& name);
|
||||
Group* findChildByUuid(const Uuid& uuid);
|
||||
Entry* findEntry(QString entryId);
|
||||
Entry* findEntryByUuid(const Uuid& uuid);
|
||||
Entry* findEntryByPath(QString entryPath, QString basePath = QString(""));
|
||||
@ -119,14 +130,13 @@ public:
|
||||
QList<Group*> groupsRecursive(bool includeSelf);
|
||||
QSet<Uuid> customIconsRecursive() const;
|
||||
/**
|
||||
* Creates a duplicate of this group including all child entries and groups (if not shallow).
|
||||
* The exceptions are that the returned group doesn't have a parent group
|
||||
* and all TimeInfo attributes are set to the current time.
|
||||
* Creates a duplicate of this group.
|
||||
* Note that you need to copy the custom icons manually when inserting the
|
||||
* new group into another database.
|
||||
*/
|
||||
Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo,
|
||||
bool shallow = false) const;
|
||||
Group* clone(Entry::CloneFlags entryFlags = DefaultEntryCloneFlags,
|
||||
CloneFlags groupFlags = DefaultCloneFlags) const;
|
||||
|
||||
void copyDataFrom(const Group* other);
|
||||
void merge(const Group* other);
|
||||
QString print(bool recursive = false, int depth = 0);
|
||||
@ -160,7 +170,8 @@ private:
|
||||
void removeEntry(Entry* entry);
|
||||
void setParent(Database* db);
|
||||
void markOlderEntry(Entry* entry);
|
||||
void resolveConflict(Entry* existingEntry, Entry* otherEntry);
|
||||
void resolveEntryConflict(Entry* existingEntry, Entry* otherEntry);
|
||||
void resolveGroupConflict(Group* existingGroup, Group* otherGroup);
|
||||
|
||||
void recSetDatabase(Database* db);
|
||||
void cleanupParent();
|
||||
@ -183,4 +194,6 @@ private:
|
||||
friend void Entry::setGroup(Group* group);
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Group::CloneFlags)
|
||||
|
||||
#endif // KEEPASSX_GROUP_H
|
||||
|
94
src/core/MacPasteboard.cpp
Normal file
94
src/core/MacPasteboard.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "MacPasteboard.h"
|
||||
|
||||
QString MacPasteboard::convertorName() { return QLatin1String("MacPasteboard"); }
|
||||
|
||||
QString MacPasteboard::flavorFor(const QString& mimetype) {
|
||||
if (mimetype == QLatin1String("text/plain")) {
|
||||
return QLatin1String("public.utf8-plain-text");
|
||||
} else if (mimetype == QLatin1String("application/x-nspasteboard-concealed-type")) {
|
||||
return QLatin1String("org.nspasteboard.ConcealedType");
|
||||
}
|
||||
|
||||
int i = mimetype.indexOf(QLatin1String("charset="));
|
||||
|
||||
if (i >= 0) {
|
||||
QString cs(mimetype.mid(i + 8).toLower());
|
||||
i = cs.indexOf(QLatin1Char(';'));
|
||||
|
||||
if (i >= 0) {
|
||||
cs = cs.left(i);
|
||||
}
|
||||
|
||||
if (cs == QLatin1String("system")) {
|
||||
return QLatin1String("public.utf8-plain-text");
|
||||
} else if (cs == QLatin1String("iso-10646-ucs-2") ||
|
||||
cs == QLatin1String("utf16")) {
|
||||
return QLatin1String("public.utf16-plain-text");
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString MacPasteboard::mimeFor(QString flavor) {
|
||||
if (flavor == QLatin1String("public.utf8-plain-text"))
|
||||
return QLatin1String("text/plain");
|
||||
if (flavor == QLatin1String("org.nspasteboard.ConcealedType"))
|
||||
return QLatin1String("application/x-nspasteboard-concealed-type");
|
||||
if (flavor == QLatin1String("public.utf16-plain-text"))
|
||||
return QLatin1String("text/plain;charset=utf16");
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool MacPasteboard::canConvert(const QString& mimetype, QString flavor) {
|
||||
Q_UNUSED(mimetype);
|
||||
Q_UNUSED(flavor);
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant MacPasteboard::convertToMime(const QString& mimetype, QList<QByteArray> data, QString flavor) {
|
||||
if (data.count() > 1)
|
||||
qWarning("QMime::convertToMime: Cannot handle multiple member data");
|
||||
const QByteArray& firstData = data.first();
|
||||
QVariant ret;
|
||||
if (flavor == QLatin1String("public.utf8-plain-text")) {
|
||||
ret = QString::fromUtf8(firstData);
|
||||
} else if (flavor == QLatin1String("org.nspasteboard.ConcealedType")) {
|
||||
ret = QString::fromUtf8(firstData);
|
||||
} else if (flavor == QLatin1String("public.utf16-plain-text")) {
|
||||
ret = QTextCodec::codecForName("UTF-16")->toUnicode(firstData);
|
||||
} else {
|
||||
qWarning("QMime::convertToMime: unhandled mimetype: %s",
|
||||
qPrintable(mimetype));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<QByteArray> MacPasteboard::convertFromMime(const QString&, QVariant data, QString flavor) {
|
||||
QList<QByteArray> ret;
|
||||
QString string = data.toString();
|
||||
if (flavor == QLatin1String("public.utf8-plain-text"))
|
||||
ret.append(string.toUtf8());
|
||||
else if (flavor == QLatin1String("org.nspasteboard.ConcealedType"))
|
||||
ret.append(string.toUtf8());
|
||||
else if (flavor == QLatin1String("public.utf16-plain-text"))
|
||||
ret.append(QTextCodec::codecForName("UTF-16")->fromUnicode(string));
|
||||
return ret;
|
||||
}
|
||||
|
37
src/core/MacPasteboard.h
Normal file
37
src/core/MacPasteboard.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_MACPASTEBOARD_H
|
||||
#define KEEPASSXC_MACPASTEBOARD_H
|
||||
|
||||
#include <QMacPasteboardMime>
|
||||
#include <QTextCodec>
|
||||
|
||||
class MacPasteboard : public QMacPasteboardMime
|
||||
{
|
||||
public:
|
||||
explicit MacPasteboard() : QMacPasteboardMime(MIME_ALL) {}
|
||||
|
||||
QString convertorName() override;
|
||||
bool canConvert(const QString &mime, QString flav) override;
|
||||
QString mimeFor(QString flav) override;
|
||||
QString flavorFor(const QString &mime) override;
|
||||
QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav) override;
|
||||
QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_MACPASTEBOARD_H
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OPTIONAL_H
|
||||
#define OPTIONAL_H
|
||||
|
||||
/*
|
||||
* This utility class is for providing basic support for an option type.
|
||||
* It can be replaced by std::optional (C++17) or
|
||||
* std::experimental::optional (C++11) when they become fully supported
|
||||
* by all the compilers.
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
class Optional
|
||||
{
|
||||
public:
|
||||
|
||||
// None
|
||||
Optional() :
|
||||
m_hasValue(false),
|
||||
m_value()
|
||||
{ };
|
||||
|
||||
// Some T
|
||||
Optional(const T& value) :
|
||||
m_hasValue(true),
|
||||
m_value(value)
|
||||
{ };
|
||||
|
||||
// Copy
|
||||
Optional(const Optional& other) :
|
||||
m_hasValue(other.m_hasValue),
|
||||
m_value(other.m_value)
|
||||
{ };
|
||||
|
||||
const Optional& operator=(const Optional& other)
|
||||
{
|
||||
m_hasValue = other.m_hasValue;
|
||||
m_value = other.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Optional& other) const
|
||||
{
|
||||
if(m_hasValue)
|
||||
return other.m_hasValue && m_value == other.m_value;
|
||||
else
|
||||
return !other.m_hasValue;
|
||||
}
|
||||
|
||||
bool operator!=(const Optional& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool hasValue() const
|
||||
{
|
||||
return m_hasValue;
|
||||
}
|
||||
|
||||
T valueOr(const T& other) const
|
||||
{
|
||||
return m_hasValue ? m_value : other;
|
||||
}
|
||||
|
||||
Optional static makeOptional(const T& value)
|
||||
{
|
||||
return Optional(value);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
bool m_hasValue;
|
||||
T m_value;
|
||||
};
|
||||
|
||||
#endif // OPTIONAL_H
|
@ -36,6 +36,14 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent):
|
||||
this, //receiver
|
||||
SLOT(freedesktopScreenSaver(bool)));
|
||||
|
||||
sessionBus.connect(
|
||||
"org.gnome.ScreenSaver", // service
|
||||
"/org/gnome/ScreenSaver", // path
|
||||
"org.gnome.ScreenSaver", // interface
|
||||
"ActiveChanged", // signal name
|
||||
this, //receiver
|
||||
SLOT(freedesktopScreenSaver(bool)));
|
||||
|
||||
sessionBus.connect(
|
||||
"org.gnome.SessionManager", // service
|
||||
"/org/gnome/SessionManager/Presence", // path
|
||||
|
@ -74,6 +74,11 @@ bool SymmetricCipher::reset()
|
||||
return m_backend->reset();
|
||||
}
|
||||
|
||||
int SymmetricCipher::keySize() const
|
||||
{
|
||||
return m_backend->keySize();
|
||||
}
|
||||
|
||||
int SymmetricCipher::blockSize() const
|
||||
{
|
||||
return m_backend->blockSize();
|
||||
|
@ -38,6 +38,7 @@ public:
|
||||
enum Mode
|
||||
{
|
||||
Cbc,
|
||||
Ctr,
|
||||
Ecb,
|
||||
Stream
|
||||
};
|
||||
@ -69,6 +70,7 @@ public:
|
||||
}
|
||||
|
||||
bool reset();
|
||||
int keySize() const;
|
||||
int blockSize() const;
|
||||
QString errorString() const;
|
||||
|
||||
|
@ -33,6 +33,7 @@ public:
|
||||
Q_REQUIRED_RESULT virtual bool processInPlace(QByteArray& data, quint64 rounds) = 0;
|
||||
|
||||
virtual bool reset() = 0;
|
||||
virtual int keySize() const = 0;
|
||||
virtual int blockSize() const = 0;
|
||||
|
||||
virtual QString errorString() const = 0;
|
||||
|
@ -26,7 +26,6 @@ SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, Sy
|
||||
, m_algo(gcryptAlgo(algo))
|
||||
, m_mode(gcryptMode(mode))
|
||||
, m_direction(direction)
|
||||
, m_blockSize(-1)
|
||||
{
|
||||
}
|
||||
|
||||
@ -62,6 +61,9 @@ int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
|
||||
case SymmetricCipher::Cbc:
|
||||
return GCRY_CIPHER_MODE_CBC;
|
||||
|
||||
case SymmetricCipher::Ctr:
|
||||
return GCRY_CIPHER_MODE_CTR;
|
||||
|
||||
case SymmetricCipher::Stream:
|
||||
return GCRY_CIPHER_MODE_STREAM;
|
||||
|
||||
@ -86,20 +88,14 @@ bool SymmetricCipherGcrypt::init()
|
||||
|
||||
gcry_error_t error;
|
||||
|
||||
if(m_ctx != nullptr)
|
||||
gcry_cipher_close(m_ctx);
|
||||
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t blockSizeT;
|
||||
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, nullptr, &blockSizeT);
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_blockSize = blockSizeT;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -119,7 +115,13 @@ bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
|
||||
bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
|
||||
{
|
||||
m_iv = iv;
|
||||
gcry_error_t error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
|
||||
gcry_error_t error;
|
||||
|
||||
if (m_mode == GCRY_CIPHER_MODE_CTR) {
|
||||
error = gcry_cipher_setctr(m_ctx, m_iv.constData(), m_iv.size());
|
||||
} else {
|
||||
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
|
||||
}
|
||||
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
@ -228,9 +230,28 @@ bool SymmetricCipherGcrypt::reset()
|
||||
return true;
|
||||
}
|
||||
|
||||
int SymmetricCipherGcrypt::keySize() const
|
||||
{
|
||||
gcry_error_t error;
|
||||
size_t keySizeT;
|
||||
|
||||
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_KEYLEN, nullptr, &keySizeT);
|
||||
if (error != 0)
|
||||
return -1;
|
||||
|
||||
return keySizeT;
|
||||
}
|
||||
|
||||
int SymmetricCipherGcrypt::blockSize() const
|
||||
{
|
||||
return m_blockSize;
|
||||
gcry_error_t error;
|
||||
size_t blockSizeT;
|
||||
|
||||
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, nullptr, &blockSizeT);
|
||||
if (error != 0)
|
||||
return -1;
|
||||
|
||||
return blockSizeT;
|
||||
}
|
||||
|
||||
QString SymmetricCipherGcrypt::errorString() const
|
||||
|
@ -39,6 +39,7 @@ public:
|
||||
Q_REQUIRED_RESULT bool processInPlace(QByteArray& data, quint64 rounds);
|
||||
|
||||
bool reset();
|
||||
int keySize() const;
|
||||
int blockSize() const;
|
||||
|
||||
QString errorString() const;
|
||||
@ -54,7 +55,6 @@ private:
|
||||
const SymmetricCipher::Direction m_direction;
|
||||
QByteArray m_key;
|
||||
QByteArray m_iv;
|
||||
int m_blockSize;
|
||||
QString m_errorString;
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -18,36 +19,29 @@
|
||||
#include "KeePass2Repair.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QScopedPointer>
|
||||
#include <QRegExp>
|
||||
|
||||
#include "format/KeePass2RandomStream.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "format/KeePass2XmlReader.h"
|
||||
|
||||
KeePass2Repair::KeePass2Repair()
|
||||
: m_db(nullptr)
|
||||
KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key)
|
||||
{
|
||||
}
|
||||
|
||||
KeePass2Repair::RepairResult KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key)
|
||||
{
|
||||
m_db = nullptr;
|
||||
m_errorStr.clear();
|
||||
|
||||
KeePass2Reader reader;
|
||||
reader.setSaveXml(true);
|
||||
|
||||
Database* db = reader.readDatabase(device, key, true);
|
||||
QScopedPointer<Database> db(reader.readDatabase(device, key, true));
|
||||
if (!reader.hasError()) {
|
||||
delete db;
|
||||
return NothingTodo;
|
||||
return qMakePair(NothingTodo, nullptr);
|
||||
}
|
||||
|
||||
QByteArray xmlData = reader.xmlData();
|
||||
if (!db || xmlData.isEmpty()) {
|
||||
delete db;
|
||||
m_errorStr = reader.errorString();
|
||||
return UnableToOpen;
|
||||
return qMakePair(UnableToOpen, nullptr);
|
||||
}
|
||||
|
||||
bool repairAction = false;
|
||||
@ -59,8 +53,7 @@ KeePass2Repair::RepairResult KeePass2Repair::repairDatabase(QIODevice* device, c
|
||||
&& encodingRegExp.cap(1).compare("utf8", Qt::CaseInsensitive) != 0)
|
||||
{
|
||||
// database is not utf-8 encoded, we don't support repairing that
|
||||
delete db;
|
||||
return RepairFailed;
|
||||
return qMakePair(RepairFailed, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,8 +68,7 @@ KeePass2Repair::RepairResult KeePass2Repair::repairDatabase(QIODevice* device, c
|
||||
|
||||
if (!repairAction) {
|
||||
// we were unable to find the problem
|
||||
delete db;
|
||||
return RepairFailed;
|
||||
return qMakePair(RepairFailed, nullptr);
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream;
|
||||
@ -84,23 +76,16 @@ KeePass2Repair::RepairResult KeePass2Repair::repairDatabase(QIODevice* device, c
|
||||
KeePass2XmlReader xmlReader;
|
||||
QBuffer buffer(&xmlData);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
xmlReader.readDatabase(&buffer, db, &randomStream);
|
||||
xmlReader.readDatabase(&buffer, db.data(), &randomStream);
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
delete db;
|
||||
return RepairFailed;
|
||||
return qMakePair(RepairFailed, nullptr);
|
||||
}
|
||||
else {
|
||||
m_db = db;
|
||||
return RepairSuccess;
|
||||
return qMakePair(RepairSuccess, db.take());
|
||||
}
|
||||
}
|
||||
|
||||
Database* KeePass2Repair::database() const
|
||||
{
|
||||
return m_db;
|
||||
}
|
||||
|
||||
QString KeePass2Repair::errorString() const
|
||||
{
|
||||
return m_errorStr;
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -20,6 +21,7 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QIODevice>
|
||||
#include <QPair>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
@ -36,14 +38,12 @@ public:
|
||||
RepairSuccess,
|
||||
RepairFailed
|
||||
};
|
||||
using RepairOutcome = QPair<RepairResult, Database*>;
|
||||
|
||||
KeePass2Repair();
|
||||
RepairResult repairDatabase(QIODevice* device, const CompositeKey& key);
|
||||
Database* database() const;
|
||||
RepairOutcome repairDatabase(QIODevice* device, const CompositeKey& key);
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
Database* m_db;
|
||||
QString m_errorStr;
|
||||
};
|
||||
|
||||
|
@ -87,6 +87,9 @@ AboutDialog::AboutDialog(QWidget* parent)
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
extensions += "\n- YubiKey";
|
||||
#endif
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
extensions += "\n- SSH Agent";
|
||||
#endif
|
||||
|
||||
if (extensions.isEmpty())
|
||||
extensions = " None";
|
||||
|
@ -28,6 +28,9 @@ Clipboard* Clipboard::m_instance(nullptr);
|
||||
Clipboard::Clipboard(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_timer(new QTimer(this))
|
||||
#ifdef Q_OS_MAC
|
||||
, m_pasteboard(new MacPasteboard)
|
||||
#endif
|
||||
{
|
||||
m_timer->setSingleShot(true);
|
||||
connect(m_timer, SIGNAL(timeout()), SLOT(clearClipboard()));
|
||||
@ -38,10 +41,17 @@ void Clipboard::setText(const QString& text)
|
||||
{
|
||||
QClipboard* clipboard = QApplication::clipboard();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
QMimeData* mime = new QMimeData;
|
||||
mime->setText(text);
|
||||
mime->setData("application/x-nspasteboard-concealed-type", text.toUtf8());
|
||||
clipboard->setMimeData(mime, QClipboard::Clipboard);
|
||||
#else
|
||||
clipboard->setText(text, QClipboard::Clipboard);
|
||||
if (clipboard->supportsSelection()) {
|
||||
clipboard->setText(text, QClipboard::Selection);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (config()->get("security/clearclipboard").toBool()) {
|
||||
int timeout = config()->get("security/clearclipboardtimeout").toInt();
|
||||
|
@ -19,6 +19,9 @@
|
||||
#define KEEPASSX_CLIPBOARD_H
|
||||
|
||||
#include <QObject>
|
||||
#ifdef Q_OS_MAC
|
||||
#include "core/MacPasteboard.h"
|
||||
#endif
|
||||
|
||||
class QTimer;
|
||||
|
||||
@ -43,6 +46,9 @@ private:
|
||||
static Clipboard* m_instance;
|
||||
|
||||
QTimer* m_timer;
|
||||
#ifdef Q_OS_MAC
|
||||
QScopedPointer<MacPasteboard> m_pasteboard;
|
||||
#endif
|
||||
QString m_lastCopied;
|
||||
};
|
||||
|
||||
|
@ -277,6 +277,9 @@ void DatabaseOpenWidget::activateChallengeResponse()
|
||||
void DatabaseOpenWidget::browseKeyFile()
|
||||
{
|
||||
QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files"));
|
||||
if (!config()->get("RememberLastKeyFiles").toBool()) {
|
||||
fileDialog()->setNextForgetDialog();
|
||||
}
|
||||
QString filename = fileDialog()->getOpenFileName(this, tr("Select key file"), QString(), filters);
|
||||
|
||||
if (!filename.isEmpty()) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -69,7 +70,8 @@ void DatabaseRepairWidget::openDatabase()
|
||||
delete m_db;
|
||||
}
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
KeePass2Repair::RepairResult repairResult = repair.repairDatabase(&file, masterKey);
|
||||
auto repairOutcome = repair.repairDatabase(&file, masterKey);
|
||||
KeePass2Repair::RepairResult repairResult = repairOutcome.first;
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
switch (repairResult) {
|
||||
@ -83,7 +85,7 @@ void DatabaseRepairWidget::openDatabase()
|
||||
emit editFinished(false);
|
||||
return;
|
||||
case KeePass2Repair::RepairSuccess:
|
||||
m_db = repair.database();
|
||||
m_db = repairOutcome.second;
|
||||
MessageBox::warning(this, tr("Success"), tr("The database has been successfully repaired\nYou can now save it."));
|
||||
emit editFinished(true);
|
||||
return;
|
||||
|
@ -90,12 +90,12 @@ void DatabaseTabWidget::newDatabase()
|
||||
Database* db = new Database();
|
||||
db->rootGroup()->setName(tr("Root"));
|
||||
dbStruct.dbWidget = new DatabaseWidget(db, this);
|
||||
|
||||
|
||||
CompositeKey emptyKey;
|
||||
db->setKey(emptyKey);
|
||||
|
||||
insertDatabase(db, dbStruct);
|
||||
|
||||
|
||||
if (!saveDatabaseAs(db)) {
|
||||
closeDatabase(db);
|
||||
return;
|
||||
@ -298,8 +298,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
|
||||
if (!saveDatabase(db)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else if (dbStruct.dbWidget->currentMode() != DatabaseWidget::LockedMode) {
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Save changes?"),
|
||||
@ -307,10 +306,9 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
|
||||
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
|
||||
if (result == QMessageBox::Yes) {
|
||||
if (!saveDatabase(db)) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (result == QMessageBox::Cancel) {
|
||||
} else if (result == QMessageBox::Cancel) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -355,8 +353,13 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
|
||||
{
|
||||
DatabaseManagerStruct& dbStruct = m_dbList[db];
|
||||
|
||||
if (dbStruct.saveToFilename) {
|
||||
if (dbStruct.dbWidget->currentMode() == DatabaseWidget::LockedMode) {
|
||||
// Never allow saving a locked database; it causes corruption
|
||||
// We return true since a save is not required
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dbStruct.saveToFilename) {
|
||||
dbStruct.dbWidget->blockAutoReload(true);
|
||||
QString errorMessage = db->saveToFile(dbStruct.canonicalFilePath);
|
||||
dbStruct.dbWidget->blockAutoReload(false);
|
||||
@ -375,7 +378,6 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
|
||||
MessageWidget::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
return saveDatabaseAs(db);
|
||||
}
|
||||
@ -622,6 +624,36 @@ void DatabaseTabWidget::updateTabNameFromDbWidgetSender()
|
||||
|
||||
DatabaseWidget* dbWidget = static_cast<DatabaseWidget*>(sender());
|
||||
updateTabName(databaseFromDatabaseWidget(dbWidget));
|
||||
|
||||
Database* db = dbWidget->database();
|
||||
Group *autoload = db->rootGroup()->findChildByName("AutoOpen");
|
||||
if (autoload) {
|
||||
const DatabaseManagerStruct& dbStruct = m_dbList.value(db);
|
||||
QFileInfo dbpath(dbStruct.canonicalFilePath);
|
||||
QDir dbFolder(dbpath.canonicalPath());
|
||||
for (auto entry : autoload->entries()) {
|
||||
if (entry->url().isEmpty() || entry->password().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QFileInfo filepath;
|
||||
if (entry->url().startsWith("file://")) {
|
||||
QUrl url(entry->url());
|
||||
filepath.setFile(url.toLocalFile());
|
||||
}
|
||||
else {
|
||||
filepath.setFile(entry->url());
|
||||
if (filepath.isRelative()) {
|
||||
filepath.setFile(dbFolder, entry->url());
|
||||
}
|
||||
}
|
||||
|
||||
if (!filepath.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
openDatabase(filepath.canonicalFilePath(), entry->password(), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DatabaseTabWidget::databaseIndex(Database* db)
|
||||
|
@ -57,12 +57,19 @@
|
||||
#include "gui/group/EditGroupWidget.h"
|
||||
#include "gui/group/GroupView.h"
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#include "sshagent/SSHAgent.h"
|
||||
#endif
|
||||
|
||||
DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
: QStackedWidget(parent)
|
||||
, m_db(db)
|
||||
, m_newGroup(nullptr)
|
||||
, m_newEntry(nullptr)
|
||||
, m_newParent(nullptr)
|
||||
, m_importingCsv(false)
|
||||
{
|
||||
m_mainWidget = new QWidget(this);
|
||||
|
||||
@ -73,15 +80,15 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
QLayout* layout = new QHBoxLayout();
|
||||
mainLayout->addWidget(m_messageWidget);
|
||||
mainLayout->addLayout(layout);
|
||||
m_splitter = new QSplitter(m_mainWidget);
|
||||
m_splitter->setChildrenCollapsible(false);
|
||||
m_mainSplitter = new QSplitter(m_mainWidget);
|
||||
m_mainSplitter->setChildrenCollapsible(false);
|
||||
m_detailSplitter = new QSplitter(m_mainWidget);
|
||||
m_detailSplitter->setOrientation(Qt::Vertical);
|
||||
m_detailSplitter->setChildrenCollapsible(true);
|
||||
|
||||
QWidget* rightHandSideWidget = new QWidget(m_splitter);
|
||||
QWidget* rightHandSideWidget = new QWidget(m_mainSplitter);
|
||||
|
||||
m_groupView = new GroupView(db, m_splitter);
|
||||
m_groupView = new GroupView(db, m_mainSplitter);
|
||||
m_groupView->setObjectName("groupView");
|
||||
m_groupView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)),
|
||||
@ -122,13 +129,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
|
||||
setTabOrder(m_entryView, m_groupView);
|
||||
|
||||
m_splitter->addWidget(m_groupView);
|
||||
m_splitter->addWidget(rightHandSideWidget);
|
||||
m_mainSplitter->addWidget(m_groupView);
|
||||
m_mainSplitter->addWidget(rightHandSideWidget);
|
||||
|
||||
m_splitter->setStretchFactor(0, 30);
|
||||
m_splitter->setStretchFactor(1, 70);
|
||||
m_mainSplitter->setStretchFactor(0, 30);
|
||||
m_mainSplitter->setStretchFactor(1, 70);
|
||||
|
||||
layout->addWidget(m_splitter);
|
||||
layout->addWidget(m_mainSplitter);
|
||||
m_mainWidget->setLayout(mainLayout);
|
||||
|
||||
m_editEntryWidget = new EditEntryWidget();
|
||||
@ -137,13 +144,14 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
m_editGroupWidget = new EditGroupWidget();
|
||||
m_editGroupWidget->setObjectName("editGroupWidget");
|
||||
m_changeMasterKeyWidget = new ChangeMasterKeyWidget();
|
||||
m_changeMasterKeyWidget->setObjectName("changeMasterKeyWidget");
|
||||
m_changeMasterKeyWidget->headlineLabel()->setText(tr("Change master key"));
|
||||
m_csvImportWizard = new CsvImportWizard();
|
||||
m_csvImportWizard->setObjectName("csvImportWizard");
|
||||
QFont headlineLabelFont = m_changeMasterKeyWidget->headlineLabel()->font();
|
||||
headlineLabelFont.setBold(true);
|
||||
headlineLabelFont.setPointSize(headlineLabelFont.pointSize() + 2);
|
||||
m_changeMasterKeyWidget->headlineLabel()->setFont(headlineLabelFont);
|
||||
m_csvImportWizard = new CsvImportWizard();
|
||||
m_csvImportWizard->setObjectName("csvImportWizard");
|
||||
m_databaseSettingsWidget = new DatabaseSettingsWidget();
|
||||
m_databaseSettingsWidget->setObjectName("databaseSettingsWidget");
|
||||
m_databaseOpenWidget = new DatabaseOpenWidget();
|
||||
@ -168,7 +176,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
addWidget(m_keepass1OpenWidget);
|
||||
addWidget(m_unlockDatabaseWidget);
|
||||
|
||||
connect(m_splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterSizesChanged()));
|
||||
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
|
||||
connect(m_detailSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(detailSplitterSizesChanged()));
|
||||
connect(m_entryView->header(), SIGNAL(sectionResized(int,int,int)), SIGNAL(entryColumnSizesChanged()));
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*)));
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged()));
|
||||
@ -207,6 +216,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
m_searchCaseSensitive = false;
|
||||
m_searchLimitGroup = config()->get("SearchLimitGroup", false).toBool();
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
if (config()->get("SSHAgent", false).toBool()) {
|
||||
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), SSHAgent::instance(), SLOT(databaseModeChanged(DatabaseWidget::Mode)));
|
||||
connect(this, SIGNAL(closeRequest()), SSHAgent::instance(), SLOT(databaseModeChanged()));
|
||||
}
|
||||
#endif
|
||||
|
||||
setCurrentWidget(m_mainWidget);
|
||||
}
|
||||
|
||||
@ -251,14 +267,24 @@ bool DatabaseWidget::isEditWidgetModified() const
|
||||
}
|
||||
}
|
||||
|
||||
QList<int> DatabaseWidget::splitterSizes() const
|
||||
QList<int> DatabaseWidget::mainSplitterSizes() const
|
||||
{
|
||||
return m_splitter->sizes();
|
||||
return m_mainSplitter->sizes();
|
||||
}
|
||||
|
||||
void DatabaseWidget::setSplitterSizes(const QList<int>& sizes)
|
||||
void DatabaseWidget::setMainSplitterSizes(const QList<int>& sizes)
|
||||
{
|
||||
m_splitter->setSizes(sizes);
|
||||
m_mainSplitter->setSizes(sizes);
|
||||
}
|
||||
|
||||
QList<int> DatabaseWidget::detailSplitterSizes() const
|
||||
{
|
||||
return m_detailSplitter->sizes();
|
||||
}
|
||||
|
||||
void DatabaseWidget::setDetailSplitterSizes(const QList<int> &sizes)
|
||||
{
|
||||
m_detailSplitter->setSizes(sizes);
|
||||
}
|
||||
|
||||
QList<int> DatabaseWidget::entryHeaderViewSizes() const
|
||||
@ -391,6 +417,8 @@ void DatabaseWidget::setupTotp()
|
||||
setupTotpDialog->setSeed(currentEntry->totpSeed());
|
||||
setupTotpDialog->setStep(currentEntry->totpStep());
|
||||
setupTotpDialog->setDigits(currentEntry->totpDigits());
|
||||
// now that all settings are set, decide whether it's default, steam or custom
|
||||
setupTotpDialog->setSettings(currentEntry->totpDigits());
|
||||
}
|
||||
|
||||
setupTotpDialog->open();
|
||||
@ -771,6 +799,12 @@ void DatabaseWidget::switchToGroupEdit(Group* group, bool create)
|
||||
|
||||
void DatabaseWidget::updateMasterKey(bool accepted)
|
||||
{
|
||||
if (m_importingCsv) {
|
||||
setCurrentWidget(m_csvImportWizard);
|
||||
m_csvImportWizard->keyFinished(accepted, m_changeMasterKeyWidget->newMasterKey());
|
||||
return;
|
||||
}
|
||||
|
||||
if (accepted) {
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
|
||||
@ -904,6 +938,7 @@ void DatabaseWidget::switchToMasterKeyChange(bool disableCancel)
|
||||
m_changeMasterKeyWidget->clearForms();
|
||||
m_changeMasterKeyWidget->setCancelEnabled(!disableCancel);
|
||||
setCurrentWidget(m_changeMasterKeyWidget);
|
||||
m_importingCsv = false;
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToDatabaseSettings()
|
||||
@ -915,8 +950,13 @@ void DatabaseWidget::switchToDatabaseSettings()
|
||||
void DatabaseWidget::switchToOpenDatabase(const QString& fileName)
|
||||
{
|
||||
updateFilename(fileName);
|
||||
m_databaseOpenWidget->load(fileName);
|
||||
setCurrentWidget(m_databaseOpenWidget);
|
||||
if (m_databaseOpenWidget) {
|
||||
m_databaseOpenWidget->load(fileName);
|
||||
setCurrentWidget(m_databaseOpenWidget);
|
||||
} else if (m_unlockDatabaseWidget) {
|
||||
m_unlockDatabaseWidget->load(fileName);
|
||||
setCurrentWidget(m_unlockDatabaseWidget);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString& password,
|
||||
@ -924,15 +964,21 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString
|
||||
{
|
||||
updateFilename(fileName);
|
||||
switchToOpenDatabase(fileName);
|
||||
m_databaseOpenWidget->enterKey(password, keyFile);
|
||||
if (m_databaseOpenWidget) {
|
||||
m_databaseOpenWidget->enterKey(password, keyFile);
|
||||
} else if (m_unlockDatabaseWidget) {
|
||||
m_unlockDatabaseWidget->enterKey(password, keyFile);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToImportCsv(const QString& fileName)
|
||||
{
|
||||
updateFilename(fileName);
|
||||
switchToMasterKeyChange();
|
||||
m_csvImportWizard->load(fileName, m_db);
|
||||
setCurrentWidget(m_csvImportWizard);
|
||||
m_changeMasterKeyWidget->clearForms();
|
||||
m_changeMasterKeyWidget->setCancelEnabled(false);
|
||||
setCurrentWidget(m_changeMasterKeyWidget);
|
||||
m_importingCsv = true;
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName)
|
||||
@ -1169,7 +1215,7 @@ void DatabaseWidget::onWatchedFileChanged()
|
||||
|
||||
void DatabaseWidget::reloadDatabaseFile()
|
||||
{
|
||||
if (m_db == nullptr) {
|
||||
if (!m_db || currentMode() == DatabaseWidget::LockedMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1201,7 +1247,7 @@ void DatabaseWidget::reloadDatabaseFile()
|
||||
if (m_databaseModified) {
|
||||
// Ask if we want to merge changes into new database
|
||||
QMessageBox::StandardButton mb = MessageBox::question(this, tr("Merge Request"),
|
||||
tr("The database file has changed and you have unsaved changes."
|
||||
tr("The database file has changed and you have unsaved changes.\n"
|
||||
"Do you want to merge your changes?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
@ -1365,6 +1411,12 @@ void DatabaseWidget::showUnlockDialog()
|
||||
{
|
||||
m_unlockDatabaseDialog->clearForms();
|
||||
m_unlockDatabaseDialog->setDBFilename(m_filename);
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
autoType()->raiseWindow();
|
||||
Tools::wait(500);
|
||||
#endif
|
||||
|
||||
m_unlockDatabaseDialog->show();
|
||||
m_unlockDatabaseDialog->activateWindow();
|
||||
}
|
||||
|
@ -88,8 +88,10 @@ public:
|
||||
bool isGroupSelected() const;
|
||||
bool isInEditMode() const;
|
||||
bool isEditWidgetModified() const;
|
||||
QList<int> splitterSizes() const;
|
||||
void setSplitterSizes(const QList<int>& sizes);
|
||||
QList<int> mainSplitterSizes() const;
|
||||
void setMainSplitterSizes(const QList<int>& sizes);
|
||||
QList<int> detailSplitterSizes() const;
|
||||
void setDetailSplitterSizes(const QList<int>& sizes);
|
||||
QList<int> entryHeaderViewSizes() const;
|
||||
void setEntryViewHeaderSizes(const QList<int>& sizes);
|
||||
void clearAllWidgets();
|
||||
@ -123,7 +125,8 @@ signals:
|
||||
void listModeActivated();
|
||||
void searchModeAboutToActivate();
|
||||
void searchModeActivated();
|
||||
void splitterSizesChanged();
|
||||
void mainSplitterSizesChanged();
|
||||
void detailSplitterSizesChanged();
|
||||
void entryColumnSizesChanged();
|
||||
void updateSearch(QString text);
|
||||
|
||||
@ -214,7 +217,7 @@ private:
|
||||
KeePass1OpenWidget* m_keepass1OpenWidget;
|
||||
UnlockDatabaseWidget* m_unlockDatabaseWidget;
|
||||
UnlockDatabaseDialog* m_unlockDatabaseDialog;
|
||||
QSplitter* m_splitter;
|
||||
QSplitter* m_mainSplitter;
|
||||
QSplitter* m_detailSplitter;
|
||||
GroupView* m_groupView;
|
||||
EntryView* m_entryView;
|
||||
@ -233,6 +236,9 @@ private:
|
||||
bool m_searchCaseSensitive;
|
||||
bool m_searchLimitGroup;
|
||||
|
||||
// CSV import state
|
||||
bool m_importingCsv;
|
||||
|
||||
// Autoreload
|
||||
QFileSystemWatcher m_fileWatcher;
|
||||
QTimer m_fileWatchTimer;
|
||||
|
@ -25,14 +25,16 @@ DatabaseWidgetStateSync::DatabaseWidgetStateSync(QObject* parent)
|
||||
, m_activeDbWidget(nullptr)
|
||||
, m_blockUpdates(false)
|
||||
{
|
||||
m_splitterSizes = variantToIntList(config()->get("GUI/SplitterState"));
|
||||
m_mainSplitterSizes = variantToIntList(config()->get("GUI/SplitterState"));
|
||||
m_detailSplitterSizes = variantToIntList(config()->get("GUI/DetailSplitterState"));
|
||||
m_columnSizesList = variantToIntList(config()->get("GUI/EntryListColumnSizes"));
|
||||
m_columnSizesSearch = variantToIntList(config()->get("GUI/EntrySearchColumnSizes"));
|
||||
}
|
||||
|
||||
DatabaseWidgetStateSync::~DatabaseWidgetStateSync()
|
||||
{
|
||||
config()->set("GUI/SplitterState", intListToVariant(m_splitterSizes));
|
||||
config()->set("GUI/SplitterState", intListToVariant(m_mainSplitterSizes));
|
||||
config()->set("GUI/DetailSplitterState", intListToVariant(m_detailSplitterSizes));
|
||||
config()->set("GUI/EntryListColumnSizes", intListToVariant(m_columnSizesList));
|
||||
config()->set("GUI/EntrySearchColumnSizes", intListToVariant(m_columnSizesSearch));
|
||||
}
|
||||
@ -48,17 +50,25 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
|
||||
if (m_activeDbWidget) {
|
||||
m_blockUpdates = true;
|
||||
|
||||
if (!m_splitterSizes.isEmpty())
|
||||
m_activeDbWidget->setSplitterSizes(m_splitterSizes);
|
||||
if (!m_mainSplitterSizes.isEmpty()) {
|
||||
m_activeDbWidget->setMainSplitterSizes(m_mainSplitterSizes);
|
||||
}
|
||||
|
||||
if (m_activeDbWidget->isInSearchMode())
|
||||
if (!m_detailSplitterSizes.isEmpty()) {
|
||||
m_activeDbWidget->setDetailSplitterSizes(m_detailSplitterSizes);
|
||||
}
|
||||
|
||||
if (m_activeDbWidget->isInSearchMode()) {
|
||||
restoreSearchView();
|
||||
else
|
||||
} else {
|
||||
restoreListView();
|
||||
}
|
||||
|
||||
m_blockUpdates = false;
|
||||
|
||||
connect(m_activeDbWidget, SIGNAL(splitterSizesChanged()),
|
||||
connect(m_activeDbWidget, SIGNAL(mainSplitterSizesChanged()),
|
||||
SLOT(updateSplitterSizes()));
|
||||
connect(m_activeDbWidget, SIGNAL(detailSplitterSizesChanged()),
|
||||
SLOT(updateSplitterSizes()));
|
||||
connect(m_activeDbWidget, SIGNAL(entryColumnSizesChanged()),
|
||||
SLOT(updateColumnSizes()));
|
||||
@ -102,7 +112,8 @@ void DatabaseWidgetStateSync::updateSplitterSizes()
|
||||
return;
|
||||
}
|
||||
|
||||
m_splitterSizes = m_activeDbWidget->splitterSizes();
|
||||
m_mainSplitterSizes = m_activeDbWidget->mainSplitterSizes();
|
||||
m_detailSplitterSizes = m_activeDbWidget->detailSplitterSizes();
|
||||
}
|
||||
|
||||
void DatabaseWidgetStateSync::updateColumnSizes()
|
||||
|
@ -46,7 +46,8 @@ private:
|
||||
DatabaseWidget* m_activeDbWidget;
|
||||
|
||||
bool m_blockUpdates;
|
||||
QList<int> m_splitterSizes;
|
||||
QList<int> m_mainSplitterSizes;
|
||||
QList<int> m_detailSplitterSizes;
|
||||
QList<int> m_columnSizesList;
|
||||
QList<int> m_columnSizesSearch;
|
||||
};
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "ui_DetailsWidget.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/Config.h"
|
||||
#include "core/FilePath.h"
|
||||
@ -33,6 +34,7 @@ DetailsWidget::DetailsWidget(QWidget* parent)
|
||||
, m_locked(false)
|
||||
, m_currentEntry(nullptr)
|
||||
, m_currentGroup(nullptr)
|
||||
, m_timer(nullptr)
|
||||
, m_attributesWidget(nullptr)
|
||||
, m_autotypeWidget(nullptr)
|
||||
, m_selectedTabEntry(0)
|
||||
@ -58,7 +60,7 @@ DetailsWidget::~DetailsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
|
||||
void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
|
||||
{
|
||||
if (!selectedEntry) {
|
||||
hideDetails();
|
||||
@ -108,7 +110,8 @@ void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
|
||||
m_ui->usernameLabel->setText(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->username()));
|
||||
|
||||
if (!config()->get("security/hidepassworddetails").toBool()) {
|
||||
m_ui->passwordLabel->setText(shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())));
|
||||
m_ui->passwordLabel->setText(
|
||||
shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())));
|
||||
m_ui->passwordLabel->setToolTip(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()));
|
||||
} else {
|
||||
m_ui->passwordLabel->setText("****");
|
||||
@ -136,14 +139,16 @@ void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
|
||||
}
|
||||
|
||||
if (m_currentEntry->hasTotp()) {
|
||||
m_ui->totpButton->show();
|
||||
updateTotp();
|
||||
|
||||
m_step = m_currentEntry->totpStep();
|
||||
|
||||
if (nullptr != m_timer) {
|
||||
m_timer->stop();
|
||||
}
|
||||
m_timer = new QTimer(this);
|
||||
connect(m_timer, SIGNAL(timeout()), this, SLOT(updateTotp()));
|
||||
updateTotp();
|
||||
m_timer->start(m_step * 10);
|
||||
m_ui->totpButton->show();
|
||||
}
|
||||
|
||||
QString notes = m_currentEntry->notes();
|
||||
@ -188,7 +193,7 @@ void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
|
||||
}
|
||||
}
|
||||
|
||||
void DetailsWidget::getSelectedGroup(Group* selectedGroup)
|
||||
void DetailsWidget::getSelectedGroup(Group* selectedGroup)
|
||||
{
|
||||
if (!selectedGroup) {
|
||||
hideDetails();
|
||||
@ -212,7 +217,6 @@ void DetailsWidget::getSelectedGroup(Group* selectedGroup)
|
||||
|
||||
m_ui->tabWidget->setTabEnabled(GroupNotesTab, false);
|
||||
|
||||
|
||||
m_ui->totpButton->hide();
|
||||
m_ui->totpWidget->hide();
|
||||
|
||||
@ -248,7 +252,7 @@ void DetailsWidget::getSelectedGroup(Group* selectedGroup)
|
||||
autotype = tr("Enabled");
|
||||
}
|
||||
m_ui->autotypeLabel->setText(autotype);
|
||||
|
||||
|
||||
TimeInfo groupTime = m_currentGroup->timeInfo();
|
||||
if (groupTime.expires()) {
|
||||
m_ui->groupExpirationLabel->setText(groupTime.expiryTime().toString(Qt::DefaultLocaleShortDate));
|
||||
@ -263,23 +267,23 @@ void DetailsWidget::getSelectedGroup(Group* selectedGroup)
|
||||
|
||||
void DetailsWidget::updateTotp()
|
||||
{
|
||||
if (m_locked) {
|
||||
if (!m_locked) {
|
||||
QString totpCode = m_currentEntry->totp();
|
||||
QString firstHalf = totpCode.left(totpCode.size() / 2);
|
||||
QString secondHalf = totpCode.mid(totpCode.size() / 2);
|
||||
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
|
||||
} else if (nullptr != m_timer) {
|
||||
m_timer->stop();
|
||||
return;
|
||||
}
|
||||
QString totpCode = m_currentEntry->totp();
|
||||
QString firstHalf = totpCode.left(totpCode.size()/2);
|
||||
QString secondHalf = totpCode.right(totpCode.size()/2);
|
||||
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
|
||||
}
|
||||
|
||||
void DetailsWidget::showTotp(bool visible)
|
||||
{
|
||||
if (visible){
|
||||
{
|
||||
if (visible) {
|
||||
m_ui->totpWidget->show();
|
||||
} else {
|
||||
m_ui->totpWidget->hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString DetailsWidget::shortUrl(QString url)
|
||||
@ -326,7 +330,8 @@ void DetailsWidget::setDatabaseMode(DatabaseWidget::Mode mode)
|
||||
}
|
||||
}
|
||||
|
||||
void DetailsWidget::updateTabIndex(int index) {
|
||||
void DetailsWidget::updateTabIndex(int index)
|
||||
{
|
||||
if (m_ui->stackedWidget->currentIndex() == GroupPreview) {
|
||||
m_selectedTabGroup = index;
|
||||
} else {
|
||||
|
@ -18,9 +18,8 @@
|
||||
#ifndef KEEPASSX_DETAILSWIDGET_H
|
||||
#define KEEPASSX_DETAILSWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class DetailsWidget;
|
||||
|
@ -6,22 +6,10 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>630</width>
|
||||
<width>600</width>
|
||||
<height>200</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,0">
|
||||
@ -36,12 +24,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
@ -123,7 +105,7 @@
|
||||
<item>
|
||||
<widget class="QToolButton" name="closeButton">
|
||||
<property name="toolTip">
|
||||
<string>Generate TOTP Token</string>
|
||||
<string>Close</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
@ -166,85 +148,19 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="entryPage">
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="1,3">
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,1">
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="usernameLabel">
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="passwordLabel"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="urlLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="expirationLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Expiration</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="urlLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="expirationLabel"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="passwordLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
@ -267,6 +183,102 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QLabel" name="expirationLabel"/>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="urlLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="urlLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="expirationLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Expiration</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="passwordLabel"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="usernameLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
@ -291,41 +303,11 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="usernameLabel">
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="groupPage">
|
||||
<layout class="QGridLayout" name="gridLayout_4" columnstretch="1,3">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="groupExpirationLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,1">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="autotypeLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
@ -347,7 +329,30 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="groupExpirationLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="searchingLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
@ -357,7 +362,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="searchingLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
@ -379,7 +384,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="autotypeLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
@ -389,7 +394,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="groupExpirationLabel_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
@ -411,15 +416,18 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<item row="0" column="0">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
@ -434,27 +442,9 @@
|
||||
<attribute name="title">
|
||||
<string>Attributes</string>
|
||||
</attribute>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="attributesEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
@ -469,27 +459,9 @@
|
||||
<attribute name="title">
|
||||
<string>Notes</string>
|
||||
</attribute>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="notesEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
@ -504,8 +476,8 @@
|
||||
<attribute name="title">
|
||||
<string>Autotype</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="autotypeTree">
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
|
@ -27,7 +27,7 @@ QString FileDialog::getOpenFileName(QWidget* parent, const QString& caption, QSt
|
||||
{
|
||||
if (!m_nextFileName.isEmpty()) {
|
||||
QString result = m_nextFileName;
|
||||
m_nextFileName = "";
|
||||
m_nextFileName.clear();
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
@ -43,11 +43,37 @@ QString FileDialog::getOpenFileName(QWidget* parent, const QString& caption, QSt
|
||||
parent->activateWindow();
|
||||
}
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
config()->set("LastDir", QFileInfo(result).absolutePath());
|
||||
saveLastDir(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
QStringList FileDialog::getOpenFileNames(QWidget *parent, const QString &caption, QString dir,
|
||||
const QString &filter, QString *selectedFilter,
|
||||
QFileDialog::Options options)
|
||||
{
|
||||
if (!m_nextFileNames.isEmpty()) {
|
||||
QStringList results = m_nextFileNames;
|
||||
m_nextFileNames.clear();
|
||||
return results;
|
||||
}
|
||||
else {
|
||||
if (dir.isEmpty()) {
|
||||
dir = config()->get("LastDir").toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
QStringList results = QFileDialog::getOpenFileNames(parent, caption, dir, filter,
|
||||
selectedFilter, options);
|
||||
|
||||
// on Mac OS X the focus is lost after closing the native dialog
|
||||
if (parent) {
|
||||
parent->activateWindow();
|
||||
}
|
||||
|
||||
if (!results.isEmpty()) {
|
||||
saveLastDir(results[0]);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +83,7 @@ QString FileDialog::getSaveFileName(QWidget* parent, const QString& caption, QSt
|
||||
{
|
||||
if (!m_nextFileName.isEmpty()) {
|
||||
QString result = m_nextFileName;
|
||||
m_nextFileName = "";
|
||||
m_nextFileName.clear();
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
@ -95,11 +121,33 @@ QString FileDialog::getSaveFileName(QWidget* parent, const QString& caption, QSt
|
||||
parent->activateWindow();
|
||||
}
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
config()->set("LastDir", QFileInfo(result).absolutePath());
|
||||
saveLastDir(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
QString FileDialog::getExistingDirectory(QWidget *parent, const QString &caption, QString dir,
|
||||
QFileDialog::Options options)
|
||||
{
|
||||
if (!m_nextDirName.isEmpty()) {
|
||||
QString result = m_nextDirName;
|
||||
m_nextDirName.clear();
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
if (dir.isEmpty()) {
|
||||
dir = config()->get("LastDir").toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
dir = QFileDialog::getExistingDirectory(parent, caption, dir, options);
|
||||
|
||||
// on Mac OS X the focus is lost after closing the native dialog
|
||||
if (parent) {
|
||||
parent->activateWindow();
|
||||
}
|
||||
|
||||
saveLastDir(dir);
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,10 +156,33 @@ void FileDialog::setNextFileName(const QString& fileName)
|
||||
m_nextFileName = fileName;
|
||||
}
|
||||
|
||||
void FileDialog::setNextFileNames(const QStringList &fileNames)
|
||||
{
|
||||
m_nextFileNames = fileNames;
|
||||
}
|
||||
|
||||
void FileDialog::setNextDirName(const QString &dirName)
|
||||
{
|
||||
m_nextDirName = dirName;
|
||||
}
|
||||
|
||||
void FileDialog::setNextForgetDialog()
|
||||
{
|
||||
m_forgetLastDir = true;
|
||||
}
|
||||
|
||||
FileDialog::FileDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void FileDialog::saveLastDir(QString dir) {
|
||||
if (!dir.isEmpty() && !m_forgetLastDir) {
|
||||
config()->set("LastDir", QFileInfo(dir).absolutePath());
|
||||
}
|
||||
|
||||
m_forgetLastDir = false;
|
||||
}
|
||||
|
||||
FileDialog* FileDialog::instance()
|
||||
{
|
||||
if (!m_instance) {
|
||||
|
@ -26,22 +26,35 @@ public:
|
||||
QString getOpenFileName(QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
QString dir = QString(), const QString& filter = QString(),
|
||||
QString* selectedFilter = nullptr, QFileDialog::Options options = 0);
|
||||
QStringList getOpenFileNames(QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
QString dir = QString(), const QString& filter = QString(),
|
||||
QString* selectedFilter = nullptr, QFileDialog::Options options = 0);
|
||||
QString getSaveFileName(QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
QString dir = QString(), const QString& filter = QString(),
|
||||
QString* selectedFilter = nullptr, QFileDialog::Options options = 0,
|
||||
const QString& defaultExtension = QString());
|
||||
QString getExistingDirectory(QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
QString dir = QString(), QFileDialog::Options options = QFileDialog::ShowDirsOnly);
|
||||
|
||||
void setNextForgetDialog();
|
||||
/**
|
||||
* Sets the result of the next get* method call.
|
||||
* Use only for testing.
|
||||
*/
|
||||
void setNextFileName(const QString& fileName);
|
||||
void setNextFileNames(const QStringList& fileNames);
|
||||
void setNextDirName(const QString& dirName);
|
||||
|
||||
static FileDialog* instance();
|
||||
|
||||
private:
|
||||
FileDialog();
|
||||
QString m_nextFileName;
|
||||
QStringList m_nextFileNames;
|
||||
QString m_nextDirName;
|
||||
bool m_forgetLastDir = false;
|
||||
|
||||
void saveLastDir(QString);
|
||||
|
||||
static FileDialog* m_instance;
|
||||
|
||||
|
36
src/gui/Font.cpp
Normal file
36
src/gui/Font.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Font.h"
|
||||
|
||||
#include <QFontDatabase>
|
||||
|
||||
QFont Font::fixedFont()
|
||||
{
|
||||
QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// try to use Consolas on Windows, because the default Courier New has too many similar characters
|
||||
QFont consolasFont = QFontDatabase().font("Consolas", fixedFont.styleName(), fixedFont.pointSize());
|
||||
const QFont defaultFont;
|
||||
if (fixedFont != defaultFont) {
|
||||
fixedFont = consolasFont;
|
||||
}
|
||||
#endif
|
||||
|
||||
return fixedFont;
|
||||
}
|
31
src/gui/Font.h
Normal file
31
src/gui/Font.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_FONT_H
|
||||
#define KEEPASSX_FONT_H
|
||||
|
||||
#include <QFont>
|
||||
|
||||
class Font
|
||||
{
|
||||
public:
|
||||
static QFont fixedFont();
|
||||
private:
|
||||
Font() {}
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_FONT_H
|
@ -49,6 +49,11 @@
|
||||
#include "http/OptionDialog.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#include "sshagent/AgentSettingsPage.h"
|
||||
#include "sshagent/SSHAgent.h"
|
||||
#endif
|
||||
|
||||
#include "gui/SettingsWidget.h"
|
||||
#include "gui/PasswordGeneratorWidget.h"
|
||||
|
||||
@ -109,6 +114,8 @@ MainWindow::MainWindow()
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_ui->toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
|
||||
|
||||
// Setup the search widget in the toolbar
|
||||
SearchWidget *search = new SearchWidget();
|
||||
search->connectSignals(m_actionMultiplexer);
|
||||
@ -121,6 +128,10 @@ MainWindow::MainWindow()
|
||||
#ifdef WITH_XC_HTTP
|
||||
m_ui->settingsWidget->addSettingsPage(new HttpPlugin(m_ui->tabWidget));
|
||||
#endif
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
SSHAgent::init(this);
|
||||
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
|
||||
#endif
|
||||
|
||||
setWindowIcon(filePath()->applicationIcon());
|
||||
m_ui->globalMessageWidget->setHidden(true);
|
||||
@ -727,7 +738,9 @@ void MainWindow::changeEvent(QEvent* event)
|
||||
|
||||
void MainWindow::saveWindowInformation()
|
||||
{
|
||||
config()->set("GUI/MainWindowGeometry", saveGeometry());
|
||||
if (isVisible()) {
|
||||
config()->set("GUI/MainWindowGeometry", saveGeometry());
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::saveLastDatabases()
|
||||
@ -811,11 +824,6 @@ void MainWindow::showGroupContextMenu(const QPoint& globalPos)
|
||||
m_ui->menuGroups->popup(globalPos);
|
||||
}
|
||||
|
||||
void MainWindow::saveToolbarState(bool value)
|
||||
{
|
||||
config()->set("ShowToolbar", value);
|
||||
}
|
||||
|
||||
void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback)
|
||||
{
|
||||
if (!QKeySequence::keyBindings(standard).isEmpty()) {
|
||||
@ -858,6 +866,7 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
|
||||
|
||||
void MainWindow::hideWindow()
|
||||
{
|
||||
saveWindowInformation();
|
||||
#ifndef Q_OS_MAC
|
||||
setWindowState(windowState() | Qt::WindowMinimized);
|
||||
#endif
|
||||
|
@ -87,7 +87,6 @@ private slots:
|
||||
void updateCopyAttributesMenu();
|
||||
void showEntryContextMenu(const QPoint& globalPos);
|
||||
void showGroupContextMenu(const QPoint& globalPos);
|
||||
void saveToolbarState(bool value);
|
||||
void rememberOpenDatabases(const QString& filePath);
|
||||
void applySettingsChanges();
|
||||
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
|
||||
|
@ -557,7 +557,7 @@
|
||||
</action>
|
||||
<action name="actionGroupEmptyRecycleBin">
|
||||
<property name="text">
|
||||
<string>Empty recycle bin</string>
|
||||
<string>E&mpty recycle bin</string>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
|
@ -19,8 +19,7 @@
|
||||
#include "PasswordEdit.h"
|
||||
|
||||
#include "core/Config.h"
|
||||
|
||||
#include <QFontDatabase>
|
||||
#include "gui/Font.h"
|
||||
|
||||
const QColor PasswordEdit::CorrectSoFarColor = QColor(255, 205, 15);
|
||||
const QColor PasswordEdit::ErrorColor = QColor(255, 125, 125);
|
||||
@ -31,9 +30,9 @@ PasswordEdit::PasswordEdit(QWidget* parent)
|
||||
{
|
||||
setEchoMode(QLineEdit::Password);
|
||||
updateStylesheet();
|
||||
|
||||
// set font to system monospace font and increase letter spacing
|
||||
QFont passwordFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
|
||||
// use a monospace font for the password field
|
||||
QFont passwordFont = Font::fixedFont();
|
||||
passwordFont.setLetterSpacing(QFont::PercentageSpacing, 110);
|
||||
setFont(passwordFont);
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QDir>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "core/Config.h"
|
||||
#include "core/PasswordGenerator.h"
|
||||
@ -149,6 +150,19 @@ void PasswordGeneratorWidget::setStandaloneMode(bool standalone)
|
||||
}
|
||||
}
|
||||
|
||||
void PasswordGeneratorWidget::keyPressEvent(QKeyEvent* e)
|
||||
{
|
||||
if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
|
||||
if (e->key() == Qt::Key_Escape && m_standalone == true) {
|
||||
emit dialogTerminated();
|
||||
} else {
|
||||
e->ignore();
|
||||
}
|
||||
} else {
|
||||
e->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void PasswordGeneratorWidget::regeneratePassword()
|
||||
{
|
||||
if (m_ui->tabWidget->currentIndex() == Password) {
|
||||
|
@ -81,6 +81,9 @@ private:
|
||||
const QScopedPointer<PasswordGenerator> m_passwordGenerator;
|
||||
const QScopedPointer<PassphraseGenerator> m_dicewareGenerator;
|
||||
const QScopedPointer<Ui::PasswordGeneratorWidget> m_ui;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* e) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_PASSWORDGENERATORWIDGET_H
|
||||
|
@ -318,7 +318,7 @@ QProgressBar::chunk {
|
||||
<string>Extended ASCII</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">Extended ASCII</string>
|
||||
<string>Extended ASCII</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
@ -241,6 +241,7 @@ void SettingsWidget::saveSettings()
|
||||
|
||||
if (!config()->get("RememberLastKeyFiles").toBool()) {
|
||||
config()->set("LastKeyFiles", QVariant());
|
||||
config()->set("LastDir", "");
|
||||
}
|
||||
|
||||
for (const ExtraPage& page: asConst(m_extraPages)) {
|
||||
|
@ -35,7 +35,9 @@ SetupTotpDialog::SetupTotpDialog(DatabaseWidget* parent, Entry* entry)
|
||||
|
||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close()));
|
||||
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(setupTotp()));
|
||||
connect(m_ui->customSettingsCheckBox, SIGNAL(toggled(bool)), SLOT(toggleCustom(bool)));
|
||||
connect(m_ui->radioDefault, SIGNAL(toggled(bool)), SLOT(toggleDefault(bool)));
|
||||
connect(m_ui->radioSteam, SIGNAL(toggled(bool)), SLOT(toggleSteam(bool)));
|
||||
connect(m_ui->radioCustom, SIGNAL(toggled(bool)), SLOT(toggleCustom(bool)));
|
||||
}
|
||||
|
||||
|
||||
@ -43,19 +45,37 @@ void SetupTotpDialog::setupTotp()
|
||||
{
|
||||
quint8 digits;
|
||||
|
||||
if (m_ui->radio8Digits->isChecked()) {
|
||||
if (m_ui->radioSteam->isChecked()) {
|
||||
digits = Totp::ENCODER_STEAM;
|
||||
} else if (m_ui->radio8Digits->isChecked()) {
|
||||
digits = 8;
|
||||
} else {
|
||||
digits = 6;
|
||||
}
|
||||
|
||||
quint8 step = m_ui->stepSpinBox->value();
|
||||
QString seed = QTotp::parseOtpString(m_ui->seedEdit->text(), digits, step);
|
||||
QString seed = Totp::parseOtpString(m_ui->seedEdit->text(), digits, step);
|
||||
m_entry->setTotp(seed, step, digits);
|
||||
emit m_parent->entrySelectionChanged();
|
||||
close();
|
||||
}
|
||||
|
||||
void SetupTotpDialog::toggleDefault(bool status)
|
||||
{
|
||||
if (status) {
|
||||
setStep(Totp::defaultStep);
|
||||
setDigits(Totp::defaultDigits);
|
||||
}
|
||||
}
|
||||
|
||||
void SetupTotpDialog::toggleSteam(bool status)
|
||||
{
|
||||
if (status) {
|
||||
setStep(Totp::defaultStep);
|
||||
setDigits(Totp::ENCODER_STEAM);
|
||||
}
|
||||
}
|
||||
|
||||
void SetupTotpDialog::toggleCustom(bool status)
|
||||
{
|
||||
m_ui->digitsLabel->setEnabled(status);
|
||||
@ -72,13 +92,25 @@ void SetupTotpDialog::setSeed(QString value)
|
||||
m_ui->seedEdit->setText(value);
|
||||
}
|
||||
|
||||
void SetupTotpDialog::setSettings(quint8 digits) {
|
||||
quint8 step = m_ui->stepSpinBox->value();
|
||||
|
||||
bool isDefault = ((step == Totp::defaultStep) &&
|
||||
(digits == Totp::defaultDigits));
|
||||
bool isSteam = (digits == Totp::ENCODER_STEAM);
|
||||
|
||||
if (isSteam) {
|
||||
m_ui->radioSteam->setChecked(true);
|
||||
} else if (isDefault) {
|
||||
m_ui->radioDefault->setChecked(true);
|
||||
} else {
|
||||
m_ui->radioCustom->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
void SetupTotpDialog::setStep(quint8 step)
|
||||
{
|
||||
m_ui->stepSpinBox->setValue(step);
|
||||
|
||||
if (step != QTotp::defaultStep) {
|
||||
m_ui->customSettingsCheckBox->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
void SetupTotpDialog::setDigits(quint8 digits)
|
||||
@ -90,13 +122,8 @@ void SetupTotpDialog::setDigits(quint8 digits)
|
||||
m_ui->radio6Digits->setChecked(true);
|
||||
m_ui->radio8Digits->setChecked(false);
|
||||
}
|
||||
|
||||
if (digits != QTotp::defaultDigits) {
|
||||
m_ui->customSettingsCheckBox->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SetupTotpDialog::~SetupTotpDialog()
|
||||
{
|
||||
}
|
||||
|
@ -39,8 +39,11 @@ public:
|
||||
void setSeed(QString value);
|
||||
void setStep(quint8 step);
|
||||
void setDigits(quint8 digits);
|
||||
void setSettings(quint8 digits);
|
||||
|
||||
private Q_SLOTS:
|
||||
void toggleDefault(bool status);
|
||||
void toggleSteam(bool status);
|
||||
void toggleCustom(bool status);
|
||||
void setupTotp();
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>282</width>
|
||||
<height>257</height>
|
||||
<height>364</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -29,11 +29,38 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="customSettingsCheckBox">
|
||||
<property name="text">
|
||||
<string>Use custom settings</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioDefault">
|
||||
<property name="text">
|
||||
<string>Default RFC 6238 token settings</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">settingsButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioSteam">
|
||||
<property name="text">
|
||||
<string>Steam token settings</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">settingsButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioCustom">
|
||||
<property name="text">
|
||||
<string>Use custom settings</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">settingsButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
@ -134,4 +161,7 @@
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="settingsButtonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user