From 9bdb41a72716edfe0a3113227a3b9b63b43b9e56 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Mon, 26 May 2014 00:14:39 -0700 Subject: [PATCH 001/333] keys: Add ChallengeResponseKey header * Add initial header file for forthcoming challenge response support. * A ChallengeResponseKey operates by submitting some challenge data and getting a deterministic result. * In the case of the forthcoming YubiKey integration, the master seed is submitted as the challenge to the YubiKey hardware and the YubiKey returns a HMAC-SHA1 response. Signed-off-by: Kyle Manna --- src/keys/ChallengeResponseKey.h | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/keys/ChallengeResponseKey.h diff --git a/src/keys/ChallengeResponseKey.h b/src/keys/ChallengeResponseKey.h new file mode 100644 index 000000000..e03a2f9f9 --- /dev/null +++ b/src/keys/ChallengeResponseKey.h @@ -0,0 +1,32 @@ +/* +* Copyright (C) 2014 Kyle Manna +* +* 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 . +*/ + +#ifndef KEEPASSX_CHALLENGE_RESPONSE_KEY_H +#define KEEPASSX_CHALLENGE_RESPONSE_KEY_H + +#include + +class ChallengeResponseKey +{ +public: + virtual ~ChallengeResponseKey() {} + virtual QByteArray rawKey() const = 0; + virtual ChallengeResponseKey* clone() const = 0; + virtual bool challenge(const QByteArray& challenge) = 0; +}; + +#endif // KEEPASSX_CHALLENGE_RESPONSE_KEY_H From ccd6704b8f2b0fac4101f15baeb18c01cdf09192 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Mon, 26 May 2014 01:38:07 -0700 Subject: [PATCH 002/333] keys: CompositeKey: Add ChallengeResponseKey support * Each Challenge Response Key consists of a list of regular keys and now challenge response keys. * Copy ChallengeResponseKeys when copying the object. * Challenge consists of challenging each driver in the list and hashing the concatenated data result using SHA256. Signed-off-by: Kyle Manna --- src/keys/CompositeKey.cpp | 30 +++++++++++++++++++++++++++++- src/keys/CompositeKey.h | 5 +++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 16b48592e..2270d96eb 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -17,6 +17,7 @@ #include "CompositeKey.h" #include "CompositeKey_p.h" +#include "ChallengeResponseKey.h" #include #include @@ -47,7 +48,7 @@ void CompositeKey::clear() bool CompositeKey::isEmpty() const { - return m_keys.isEmpty(); + return m_keys.isEmpty() && m_challengeResponseKeys.isEmpty(); } CompositeKey* CompositeKey::clone() const @@ -67,6 +68,9 @@ CompositeKey& CompositeKey::operator=(const CompositeKey& key) for (const Key* subKey : asConst(key.m_keys)) { addKey(*subKey); } + Q_FOREACH (const ChallengeResponseKey* subKey, key.m_challengeResponseKeys) { + addChallengeResponseKey(*subKey); + } return *this; } @@ -142,11 +146,35 @@ QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray return result; } +QByteArray CompositeKey::challenge(const QByteArray& seed) const +{ + /* If no challenge response was requested, return nothing to + * maintain backwards compatability with regular databases. + */ + if (m_challengeResponseKeys.length() == 0) { + return QByteArray(); + } + + CryptoHash cryptoHash(CryptoHash::Sha256); + + Q_FOREACH (ChallengeResponseKey* key, m_challengeResponseKeys) { + key->challenge(seed); + cryptoHash.addData(key->rawKey()); + } + + return cryptoHash.result(); +} + void CompositeKey::addKey(const Key& key) { m_keys.append(key.clone()); } +void CompositeKey::addChallengeResponseKey(const ChallengeResponseKey& key) +{ + m_challengeResponseKeys.append(key.clone()); +} + int CompositeKey::transformKeyBenchmark(int msec) { TransformKeyBenchmarkThread thread1(msec); diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index 3290d3671..66ad91ad9 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -21,6 +21,7 @@ #include #include "keys/Key.h" +#include "keys/ChallengeResponseKey.h" class CompositeKey : public Key { @@ -36,7 +37,10 @@ public: QByteArray rawKey() const; QByteArray transform(const QByteArray& seed, quint64 rounds, bool* ok, QString* errorString) const; + QByteArray challenge(const QByteArray& seed) const; + void addKey(const Key& key); + void addChallengeResponseKey(const ChallengeResponseKey& key); static int transformKeyBenchmark(int msec); @@ -45,6 +49,7 @@ private: quint64 rounds, bool* ok, QString* errorString); QList m_keys; + QList m_challengeResponseKeys; }; #endif // KEEPASSX_COMPOSITEKEY_H From e354a0ee0eb01bedf18931fb0f65bdfb50f0d66f Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Mon, 26 May 2014 01:40:38 -0700 Subject: [PATCH 003/333] database: Pass master seed to challenge response keys * Pass the master seed from the database to CompositeKey::challenge() function which will in turn issue challenges to all selected drivers. Signed-off-by: Kyle Manna --- src/core/Database.cpp | 5 +++++ src/core/Database.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 336820381..db13e2499 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -176,6 +176,11 @@ QByteArray Database::transformedMasterKey() const return m_data.transformedMasterKey; } +QByteArray Database::challengeMasterSeed(const QByteArray& masterSeed) const +{ + return m_data.key.challenge(masterSeed); +} + void Database::setCipher(const Uuid& cipher) { Q_ASSERT(!cipher.isNull()); diff --git a/src/core/Database.h b/src/core/Database.h index 3cd5ed1b1..607792332 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -89,6 +89,7 @@ public: quint64 transformRounds() const; QByteArray transformedMasterKey() const; const CompositeKey & key() const; + QByteArray challengeMasterSeed(const QByteArray& masterSeed) const; void setCipher(const Uuid& cipher); void setCompressionAlgo(Database::CompressionAlgorithm algo); From add4846d799315d4149b96ca09c65db0dd7675eb Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Mon, 26 May 2014 00:29:41 -0700 Subject: [PATCH 004/333] format: Add challenge response result to final key hash * The challengeMasterSeed() function return empty if not present maintaining backwards compatability. * This commit is where the challenge response result is computed into the final key used to encrypt or decrypt the database. Signed-off-by: Kyle Manna --- src/format/KeePass2Reader.cpp | 1 + src/format/KeePass2Writer.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index b45cefa6c..17e007d76 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -115,6 +115,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke CryptoHash hash(CryptoHash::Sha256); hash.addData(m_masterSeed); + hash.addData(m_db->challengeMasterSeed(m_masterSeed)); hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index dfbbf3532..3a3195a0b 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -53,6 +53,7 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) CryptoHash hash(CryptoHash::Sha256); hash.addData(masterSeed); + hash.addData(db->challengeMasterSeed(masterSeed)); Q_ASSERT(!db->transformedMasterKey().isEmpty()); hash.addData(db->transformedMasterKey()); QByteArray finalKey = hash.result(); From 82aed2caabf94836a13dfb476601b5640e60d4c2 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Mon, 26 May 2014 00:41:54 -0700 Subject: [PATCH 005/333] keys: yk: Add YubiKey hardware driver support * Use compile time detection of the YubiKey libraries and link against the libraries if present. Can be disabled with: $ cmake -DCMAKE_DISABLE_FIND_PACKAGE_YubiKey=FALSE * A stub file provides empty calls for all the function calls integrated in to the UI to support this. In the future a more modular approach maybe better, but opting for simplicity initially. Signed-off-by: Kyle Manna --- CMakeLists.txt | 8 + cmake/FindYubiKey.cmake | 29 ++++ src/CMakeLists.txt | 11 ++ src/keys/drivers/YubiKey.cpp | 245 +++++++++++++++++++++++++++++++ src/keys/drivers/YubiKey.h | 70 +++++++++ src/keys/drivers/YubiKeyStub.cpp | 71 +++++++++ 6 files changed, 434 insertions(+) create mode 100644 cmake/FindYubiKey.cmake create mode 100644 src/keys/drivers/YubiKey.cpp create mode 100644 src/keys/drivers/YubiKey.h create mode 100644 src/keys/drivers/YubiKeyStub.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 883f462ef..ff7b05e5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,14 @@ if(NOT ZLIB_SUPPORTS_GZIP) message(FATAL_ERROR "zlib 1.2.x or higher is required to use the gzip format") endif() +# Optional +find_package(YubiKey) + +if(YUBIKEY_FOUND) + include_directories(SYSTEM ${YUBIKEY_INCLUDE_DIRS}) +endif() + + if(UNIX) check_cxx_source_compiles("#include int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }" diff --git a/cmake/FindYubiKey.cmake b/cmake/FindYubiKey.cmake new file mode 100644 index 000000000..297b68387 --- /dev/null +++ b/cmake/FindYubiKey.cmake @@ -0,0 +1,29 @@ +# Copyright (C) 2014 Kyle Manna +# +# 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 . + +find_path(YUBIKEY_CORE_INCLUDE_DIR yubikey.h) +find_path(YUBIKEY_PERS_INCLUDE_DIR ykcore.h PATH_SUFFIXES ykpers-1) +set(YUBIKEY_INCLUDE_DIRS ${YUBIKEY_CORE_INCLUDE_DIR} ${YUBIKEY_PERS_INCLUDE_DIR}) + +find_library(YUBIKEY_CORE_LIBRARY yubikey) +find_library(YUBIKEY_PERS_LIBRARY ykpers-1) +set(YUBIKEY_LIBRARIES ${YUBIKEY_CORE_LIBRARY} ${YUBIKEY_PERS_LIBRARY}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(YubiKey DEFAULT_MSG YUBIKEY_LIBRARIES YUBIKEY_INCLUDE_DIRS) + +# TODO: Is mark_as_advanced() necessary? It's used in many examples with +# little explanation. Disable for now in favor of simplicity. +#mark_as_advanced(YUBIKEY_LIBRARIES YUBIKEY_INCLUDE_DIRS) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 30332c71e..5e5e7d58a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -111,6 +111,7 @@ set(keepassx_SOURCES gui/group/GroupView.cpp keys/CompositeKey.cpp keys/CompositeKey_p.h + keys/drivers/YubiKey.h keys/FileKey.cpp keys/Key.h keys/PasswordKey.cpp @@ -190,6 +191,12 @@ if(MINGW) ${CMAKE_SOURCE_DIR}/share/windows/icon.rc) endif() +if(YUBIKEY_FOUND) + set(keepassx_SOURCES ${keepassx_SOURCES} keys/drivers/YubiKey.cpp) +else() + set(keepassx_SOURCES ${keepassx_SOURCES} keys/drivers/YubiKeyStub.cpp) +endif() + qt5_wrap_ui(keepassx_SOURCES ${keepassx_FORMS}) add_library(zxcvbn STATIC zxcvbn/zxcvbn.cpp) @@ -220,6 +227,10 @@ target_link_libraries(${PROGNAME} ${GCRYPT_LIBRARIES} ${ZLIB_LIBRARIES}) +if(YUBIKEY_FOUND) + target_link_libraries(keepassx_core ${YUBIKEY_LIBRARIES}) +endif() + set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON) if(APPLE) diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp new file mode 100644 index 000000000..1d69bb5b9 --- /dev/null +++ b/src/keys/drivers/YubiKey.cpp @@ -0,0 +1,245 @@ +/* +* Copyright (C) 2014 Kyle Manna +* +* 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 . +*/ + +#include + +#include + +#include +#include +#include +#include + +#include "core/Global.h" +#include "crypto/Random.h" + +#include "YubiKey.h" + +/* Cast the void pointer from the generalized class definition + * to the proper pointer type from the now included system headers + */ +#define m_yk (static_cast(m_yk_void)) +#define m_ykds (static_cast(m_ykds_void)) + +YubiKey::YubiKey() : m_yk_void(NULL), m_ykds_void(NULL) +{ +} + +YubiKey* YubiKey::m_instance(Q_NULLPTR); + +/** + * @brief YubiKey::instance - get instance of singleton + * @return + */ +YubiKey* YubiKey::instance() +{ + if (!m_instance) { + m_instance = new YubiKey(); + } + + return m_instance; +} + +/** + * @brief YubiKey::init - initialize yubikey library and hardware + * @return + */ +bool YubiKey::init() +{ + /* Previously initalized */ + if (m_yk != NULL && m_ykds != NULL) { + + if (yk_get_status(m_yk, m_ykds)) { + /* Still connected */ + return true; + } else { + /* Initialized but not connected anymore, re-init */ + deinit(); + } + } + + if (!yk_init()) { + return false; + } + + /* TODO: handle multiple attached hardware devices, currently own one */ + m_yk_void = static_cast(yk_open_first_key()); + if (m_yk == NULL) { + return false; + } + + m_ykds_void = static_cast(ykds_alloc()); + if (m_ykds == NULL) { + yk_close_key(m_yk); + m_yk_void = NULL; + return false; + } + + return true; +} + +/** + * @brief YubiKey::deinit - cleanup after init + * @return true on success + */ +bool YubiKey::deinit() +{ + if (m_yk) { + yk_close_key(m_yk); + m_yk_void = NULL; + } + + if (m_ykds) { + ykds_free(m_ykds); + m_ykds_void = NULL; + } + + return true; +} + +/** + * @brief YubiKey::detect - probe for attached YubiKeys + */ +void YubiKey::detect() +{ + if (init()) { + + for (int i = 1; i < 3; i++) { + YubiKey::ChallengeResult result; + QByteArray rand = randomGen()->randomArray(1); + QByteArray resp; + + result = challenge(i, false, rand, resp); + + if (result != YubiKey::ERROR) { + Q_EMIT detected(i, result == YubiKey::WOULDBLOCK ? true : false); + } + } + } +} + +/** + * @brief YubiKey::getSerial - serial number of yubikey + * @param serial + * @return + */ +bool YubiKey::getSerial(unsigned int& serial) const +{ + if (!yk_get_serial(m_yk, 1, 0, &serial)) { + return false; + } + + return true; +} + +#ifdef QT_DEBUG +/** + * @brief printByteArray - debug raw data + * @param a array input + * @return string representation of array + */ +static inline QString printByteArray(const QByteArray& a) +{ + QString s; + for (int i = 0; i < a.size(); i++) + s.append(QString::number(a[i] & 0xff, 16).rightJustified(2, '0')); + return s; +} +#endif + +/** + * @brief YubiKey::challenge - issue a challenge + * + * This operation could block if the YubiKey requires a touch to trigger. + * + * TODO: Signal to the UI that the system is waiting for challenge response + * touch. + * + * @param slot YubiKey configuration slot + * @param mayBlock operation is allowed to block + * @param chal challenge input to YubiKey + * @param resp response output from YubiKey + * @return SUCCESS when successful + */ +YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, + const QByteArray& chal, + QByteArray& resp) const +{ + int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2; + QByteArray paddedChal = chal; + + /* yk_challenge_response() insists on 64 byte response buffer */ + resp.resize(64); + + /* The challenge sent to the yubikey should always be 64 bytes for + * compatibility with all configurations. Follow PKCS7 padding. + * + * There is some question whether or not 64 byte fixed length + * configurations even work, some docs say avoid it. + */ + const int padLen = 64 - paddedChal.size(); + if (padLen > 0) { + paddedChal.append(QByteArray(padLen, padLen)); + } + + const unsigned char *c; + unsigned char *r; + c = reinterpret_cast(paddedChal.constData()); + r = reinterpret_cast(resp.data()); + +#ifdef QT_DEBUG + qDebug().nospace() << __func__ << "(" << slot << ") c = " + << printByteArray(paddedChal); +#endif + + int ret = yk_challenge_response(m_yk, yk_cmd, mayBlock, + paddedChal.size(), c, + resp.size(), r); + + if(!ret) { + if (yk_errno == YK_EWOULDBLOCK) { + return WOULDBLOCK; + } else if (yk_errno == YK_ETIMEOUT) { + return ERROR; + } else if (yk_errno) { + + /* Something went wrong, close the key, so that the next call to + * can try to re-open. + * + * Likely caused by the YubiKey being unplugged. + */ + + if (yk_errno == YK_EUSBERR) { + qWarning() << "USB error:" << yk_usb_strerror(); + } else { + qWarning() << "YubiKey core error:" << yk_strerror(yk_errno); + } + + return ERROR; + } + } + + /* Actual HMAC-SHA1 response is only 20 bytes */ + resp.resize(20); + +#ifdef QT_DEBUG + qDebug().nospace() << __func__ << "(" << slot << ") r = " + << printByteArray(resp) << ", ret = " << ret; +#endif + + return SUCCESS; +} diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h new file mode 100644 index 000000000..492fba01d --- /dev/null +++ b/src/keys/drivers/YubiKey.h @@ -0,0 +1,70 @@ +/* +* Copyright (C) 2014 Kyle Manna +* +* 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 . +*/ + +#ifndef KEEPASSX_YUBIKEY_H +#define KEEPASSX_YUBIKEY_H + +#include + +/** + * Singleton class to manage the interface to the hardware + */ +class YubiKey : public QObject +{ + Q_OBJECT + +public: + enum ChallengeResult { ERROR = -1, SUCCESS = 0, WOULDBLOCK }; + + static YubiKey* instance(); + + /** Initialize the underlying yubico libraries */ + bool init(); + bool deinit(); + + /** Issue a challenge to the hardware */ + ChallengeResult challenge(int slot, bool mayBlock, + const QByteArray& chal, + QByteArray& resp) const; + + /** Read the serial number from the hardware */ + bool getSerial(unsigned int& serial) const; + + /** Start looking for attached hardware devices */ + void detect(); + +Q_SIGNALS: + /** Emitted in response to detect() when a device is found + * + * @slot is the slot number detected + * @blocking signifies if the YK is setup in passive mode or if requires + * the user to touch it for a response + */ + void detected(int slot, bool blocking); + +private: + explicit YubiKey(); + static YubiKey* m_instance; + + /* Create void ptr here to avoid ifdef header include mess */ + void *m_yk_void; + void *m_ykds_void; + + Q_DISABLE_COPY(YubiKey) +}; + +#endif // KEEPASSX_YUBIKEY_H diff --git a/src/keys/drivers/YubiKeyStub.cpp b/src/keys/drivers/YubiKeyStub.cpp new file mode 100644 index 000000000..c00790f38 --- /dev/null +++ b/src/keys/drivers/YubiKeyStub.cpp @@ -0,0 +1,71 @@ +/* +* Copyright (C) 2014 Kyle Manna +* +* 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 . +*/ + +#include + +#include "core/Global.h" +#include "crypto/Random.h" + +#include "YubiKey.h" + +YubiKey::YubiKey() : m_yk_void(NULL), m_ykds_void(NULL) +{ +} + +YubiKey* YubiKey::m_instance(Q_NULLPTR); + +YubiKey* YubiKey::instance() +{ + if (!m_instance) { + m_instance = new YubiKey(); + } + + return m_instance; +} + +bool YubiKey::init() +{ + return false; +} + +bool YubiKey::deinit() +{ + return false; +} + +void YubiKey::detect() +{ +} + +bool YubiKey::getSerial(unsigned int& serial) const +{ + Q_UNUSED(serial); + + return false; +} + +YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, + const QByteArray& chal, + QByteArray& resp) const +{ + Q_UNUSED(slot); + Q_UNUSED(mayBlock); + Q_UNUSED(chal); + Q_UNUSED(resp); + + return ERROR; +} From 5b8b4c8c7b877227cb7b6602450aaaa3e859a14e Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Mon, 26 May 2014 02:06:26 -0700 Subject: [PATCH 006/333] keys: yk: Implement ChallengeResponseKey for YubiKey * Implement a YubiKey challenge response class. One object will be created for each challenge response key available. Signed-off-by: Kyle Manna --- src/CMakeLists.txt | 1 + src/keys/YkChallengeResponseKey.cpp | 72 +++++++++++++++++++++++++++++ src/keys/YkChallengeResponseKey.h | 44 ++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 src/keys/YkChallengeResponseKey.cpp create mode 100644 src/keys/YkChallengeResponseKey.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e5e7d58a..cf4d593ab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -115,6 +115,7 @@ set(keepassx_SOURCES keys/FileKey.cpp keys/Key.h keys/PasswordKey.cpp + keys/YkChallengeResponseKey.cpp streams/HashedBlockStream.cpp streams/LayeredStream.cpp streams/qtiocompressor.cpp diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp new file mode 100644 index 000000000..5f495a340 --- /dev/null +++ b/src/keys/YkChallengeResponseKey.cpp @@ -0,0 +1,72 @@ +/* +* Copyright (C) 2014 Kyle Manna +* +* 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 . +*/ + + +#include +#include + +#include "core/Tools.h" +#include "crypto/CryptoHash.h" +#include "crypto/Random.h" + +#include "keys/YkChallengeResponseKey.h" +#include "keys/drivers/YubiKey.h" + +YkChallengeResponseKey::YkChallengeResponseKey(int slot, + bool blocking) + : m_slot(slot), + m_blocking(blocking) +{ +} + +QByteArray YkChallengeResponseKey::rawKey() const +{ + return m_key; +} + +YkChallengeResponseKey* YkChallengeResponseKey::clone() const +{ + return new YkChallengeResponseKey(*this); +} + + +/** Assumes yubikey()->init() was called */ +bool YkChallengeResponseKey::challenge(const QByteArray& chal) +{ + if (YubiKey::instance()->challenge(m_slot, true, chal, m_key) != YubiKey::ERROR) { + return true; + } + + return false; +} + +QString YkChallengeResponseKey::getName() const +{ + unsigned int serial; + QString fmt("YubiKey[%1] Challenge Response - Slot %2 - %3"); + + YubiKey::instance()->getSerial(serial); + + return fmt.arg(QString::number(serial), + QString::number(m_slot), + (m_blocking) ? "Press" : "Passive"); +} + +bool YkChallengeResponseKey::isBlocking() const +{ + return m_blocking; +} diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h new file mode 100644 index 000000000..a52367486 --- /dev/null +++ b/src/keys/YkChallengeResponseKey.h @@ -0,0 +1,44 @@ +/* +* Copyright (C) 2011 Felix Geyer +* +* 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 . +*/ + +#ifndef KEEPASSX_YK_CHALLENGERESPONSEKEY_H +#define KEEPASSX_YK_CHALLENGERESPONSEKEY_H + +#include "core/Global.h" +#include "keys/ChallengeResponseKey.h" +#include "keys/drivers/YubiKey.h" + +class YkChallengeResponseKey : public ChallengeResponseKey +{ +public: + + YkChallengeResponseKey(int slot = -1, + bool blocking = false); + + QByteArray rawKey() const; + YkChallengeResponseKey* clone() const; + bool challenge(const QByteArray& challenge); + QString getName() const; + bool isBlocking() const; + +private: + QByteArray m_key; + int m_slot; + bool m_blocking; +}; + +#endif // KEEPASSX_YK_CHALLENGERESPONSEKEY_H From 9556d8e6dad2143a8af0bbd9620ae06f8329c953 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Mon, 26 May 2014 00:46:41 -0700 Subject: [PATCH 007/333] tests: Add YubiKey Tests * Basic testing for YubiKey code. Signed-off-by: Kyle Manna --- tests/CMakeLists.txt | 8 ++ tests/TestYkChallengeResponseKey.cpp | 108 +++++++++++++++++++++++++++ tests/TestYkChallengeResponseKey.h | 54 ++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 tests/TestYkChallengeResponseKey.cpp create mode 100644 tests/TestYkChallengeResponseKey.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0ea73b2fe..7edf89395 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -99,6 +99,10 @@ set(testsupport_SOURCES modeltest.cpp FailDevice.cpp) add_library(testsupport STATIC ${testsupport_SOURCES}) target_link_libraries(testsupport ${MHD_LIBRARIES} Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test) +if(YUBIKEY_FOUND) + set(TEST_LIBRARIES ${TEST_LIBRARIES} ${YUBIKEY_LIBRARIES}) +endif() + add_unit_test(NAME testgroup SOURCES TestGroup.cpp LIBS ${TEST_LIBRARIES}) @@ -165,6 +169,10 @@ add_unit_test(NAME testexporter SOURCES TestExporter.cpp add_unit_test(NAME testcsvexporter SOURCES TestCsvExporter.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testykchallengeresponsekey + SOURCES TestYkChallengeResponseKey.cpp TestYkChallengeResponseKey.h + LIBS ${TEST_LIBRARIES}) + if(WITH_GUI_TESTS) add_subdirectory(gui) endif(WITH_GUI_TESTS) diff --git a/tests/TestYkChallengeResponseKey.cpp b/tests/TestYkChallengeResponseKey.cpp new file mode 100644 index 000000000..91fba83cb --- /dev/null +++ b/tests/TestYkChallengeResponseKey.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 Kyle Manna + * + * + * 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 . + */ + +#include "TestYkChallengeResponseKey.h" + +#include +#include + +#include "keys/YkChallengeResponseKey.h" + +QTEST_GUILESS_MAIN(TestYubiKeyChalResp) + +void TestYubiKeyChalResp::initTestCase() +{ + m_detected = 0; + m_key = NULL; +} + +void TestYubiKeyChalResp::cleanupTestCase() +{ + if (m_key) + delete m_key; +} + +void TestYubiKeyChalResp::init() +{ + bool result = YubiKey::instance()->init(); + + if (!result) { + QSKIP("Unable to connect to YubiKey", SkipAll); + } +} + +void TestYubiKeyChalResp::detectDevices() +{ + connect(YubiKey::instance(), SIGNAL(detected(int,bool)), + SLOT(ykDetected(int,bool)), + Qt::QueuedConnection); + QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); + + /* Need to wait for the hardware (that's hopefully plugged in)... */ + QTest::qWait(2000); + QVERIFY2(m_detected > 0, "Is a YubiKey attached?"); +} + +void TestYubiKeyChalResp::getSerial() +{ + unsigned int serial; + QVERIFY(YubiKey::instance()->getSerial(serial)); +} + +void TestYubiKeyChalResp::keyGetName() +{ + QVERIFY(m_key); + QVERIFY(m_key->getName().length() > 0); +} + +void TestYubiKeyChalResp::keyIssueChallenge() +{ + QVERIFY(m_key); + if (m_key->isBlocking()) { + /* Testing active mode in unit tests is unreasonable */ + QSKIP("YubiKey not in passive mode", SkipSingle); + } + + QByteArray ba("UnitTest"); + QVERIFY(m_key->challenge(ba)); + + /* TODO Determine if it's reasonable to provide a fixed secret key for + * verification testing. Obviously simple technically, but annoying + * if devs need to re-program their yubikeys or have a spare test key + * for unit tests to past. + * + * Might be worth it for integrity verification though. + */ +} + +void TestYubiKeyChalResp::ykDetected(int slot, bool blocking) +{ + Q_UNUSED(blocking); + + if (slot > 0) + m_detected++; + + /* Key used for later testing */ + if (!m_key) + m_key = new YkChallengeResponseKey(slot, blocking); +} + +void TestYubiKeyChalResp::deinit() +{ + QVERIFY(YubiKey::instance()->deinit()); +} diff --git a/tests/TestYkChallengeResponseKey.h b/tests/TestYkChallengeResponseKey.h new file mode 100644 index 000000000..4699b9101 --- /dev/null +++ b/tests/TestYkChallengeResponseKey.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Kyle Manna + * + * 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 . + */ + +#ifndef KEEPASSX_TESTYUBIKEYCHALRESP_H +#define KEEPASSX_TESTYUBIKEYCHALRESP_H + +#include + +#include "keys/YkChallengeResponseKey.h" + +class TestYubiKeyChalResp: public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void init(); + + /* Order is important! + * Need to init and detectDevices() before proceeding + */ + void detectDevices(); + + void getSerial(); + void keyGetName(); + void keyIssueChallenge(); + + void deinit(); + + /* Callback for detectDevices() */ + void ykDetected(int slot, bool blocking); + +private: + int m_detected; + YkChallengeResponseKey *m_key; +}; + +#endif // KEEPASSX_TESTYUBIKEYCHALRESP_H From ba8fd256045ae1ec83b8aa00b1dce2fd44771102 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Mon, 26 May 2014 00:49:28 -0700 Subject: [PATCH 008/333] gui: Add YubiKey support to widgets * Add YubiKey support to the GUI widgets. Signed-off-by: Kyle Manna --- src/gui/ChangeMasterKeyWidget.cpp | 29 ++++++++++++++++++ src/gui/ChangeMasterKeyWidget.h | 1 + src/gui/ChangeMasterKeyWidget.ui | 25 ++++++++++++++++ src/gui/DatabaseOpenWidget.cpp | 49 +++++++++++++++++++++++++++++++ src/gui/DatabaseOpenWidget.h | 3 ++ src/gui/DatabaseOpenWidget.ui | 17 +++++++++++ 6 files changed, 124 insertions(+) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 3e346bc10..c69cf2dcc 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -15,14 +15,18 @@ * along with this program. If not, see . */ +#include + #include "ChangeMasterKeyWidget.h" #include "ui_ChangeMasterKeyWidget.h" #include "core/FilePath.h" #include "keys/FileKey.h" #include "keys/PasswordKey.h" +#include "keys/YkChallengeResponseKey.h" #include "gui/FileDialog.h" #include "gui/MessageBox.h" +#include "crypto/Random.h" ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) : DialogyWidget(parent) @@ -81,6 +85,15 @@ void ChangeMasterKeyWidget::clearForms() m_ui->togglePasswordButton->setChecked(false); // TODO: clear m_ui->keyFileCombo + m_ui->challengeResponseGroup->setChecked(false); + m_ui->challengeResponseCombo->clear(); + + /* YubiKey init is slow */ + connect(YubiKey::instance(), SIGNAL(detected(int,bool)), + SLOT(ykDetected(int,bool)), + Qt::QueuedConnection); + QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); + m_ui->enterPasswordEdit->setFocus(); } @@ -128,6 +141,14 @@ void ChangeMasterKeyWidget::generateKey() m_key.addKey(fileKey); } + if (m_ui->challengeResponseGroup->isChecked()) { + int i = m_ui->challengeResponseCombo->currentIndex(); + i = m_ui->challengeResponseCombo->itemData(i).toInt(); + YkChallengeResponseKey key(i); + + m_key.addChallengeResponseKey(key); + } + Q_EMIT editFinished(true); } @@ -136,3 +157,11 @@ void ChangeMasterKeyWidget::reject() { Q_EMIT editFinished(false); } + + +void ChangeMasterKeyWidget::ykDetected(int slot, bool blocking) +{ + YkChallengeResponseKey yk(slot, blocking); + m_ui->challengeResponseCombo->addItem(yk.getName(), QVariant(slot)); + m_ui->challengeResponseGroup->setEnabled(true); +} diff --git a/src/gui/ChangeMasterKeyWidget.h b/src/gui/ChangeMasterKeyWidget.h index 8985ff7a8..9b765c205 100644 --- a/src/gui/ChangeMasterKeyWidget.h +++ b/src/gui/ChangeMasterKeyWidget.h @@ -47,6 +47,7 @@ private Q_SLOTS: void reject(); void createKeyFile(); void browseKeyFile(); + void ykDetected(int slot, bool blocking); private: const QScopedPointer m_ui; diff --git a/src/gui/ChangeMasterKeyWidget.ui b/src/gui/ChangeMasterKeyWidget.ui index d14941ccc..335a67c92 100644 --- a/src/gui/ChangeMasterKeyWidget.ui +++ b/src/gui/ChangeMasterKeyWidget.ui @@ -123,6 +123,31 @@ + + + + false + + + Challenge Response + + + true + + + + + + + + + Challenge Response: + + + + + + diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 781b836fd..5d3815963 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +#include + #include "DatabaseOpenWidget.h" #include "ui_DatabaseOpenWidget.h" @@ -27,6 +29,9 @@ #include "format/KeePass2Reader.h" #include "keys/FileKey.h" #include "keys/PasswordKey.h" +#include "keys/YkChallengeResponseKey.h" +#include "crypto/Random.h" + DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) : DialogyWidget(parent) @@ -49,6 +54,13 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) connect(m_ui->editPassword, SIGNAL(textChanged(QString)), SLOT(activatePassword())); connect(m_ui->comboKeyFile, SIGNAL(editTextChanged(QString)), SLOT(activateKeyFile())); + connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(activateChallengeResponse())); + + connect(m_ui->checkPassword, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); + connect(m_ui->checkKeyFile, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); + connect(m_ui->comboKeyFile, SIGNAL(editTextChanged(QString)), SLOT(setOkButtonEnabled())); + connect(m_ui->checkChallengeResponse, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); + connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(setOkButtonEnabled())); connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase())); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); @@ -79,6 +91,13 @@ void DatabaseOpenWidget::load(const QString& filename) } m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + + /* YubiKey init is slow */ + connect(YubiKey::instance(), SIGNAL(detected(int,bool)), + SLOT(ykDetected(int,bool)), + Qt::QueuedConnection); + QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); + m_ui->editPassword->setFocus(); } @@ -156,6 +175,15 @@ CompositeKey DatabaseOpenWidget::databaseKey() config()->set("LastKeyFiles", lastKeyFiles); } + + if (m_ui->checkChallengeResponse->isChecked()) { + int i = m_ui->comboChallengeResponse->currentIndex(); + i = m_ui->comboChallengeResponse->itemData(i).toInt(); + YkChallengeResponseKey key(i); + + masterKey.addChallengeResponseKey(key); + } + return masterKey; } @@ -174,6 +202,19 @@ void DatabaseOpenWidget::activateKeyFile() m_ui->checkKeyFile->setChecked(true); } +void DatabaseOpenWidget::activateChallengeResponse() +{ + m_ui->checkChallengeResponse->setChecked(true); +} + +void DatabaseOpenWidget::setOkButtonEnabled() +{ + bool enable = m_ui->checkPassword->isChecked() || m_ui->checkChallengeResponse->isChecked() + || (m_ui->checkKeyFile->isChecked() && !m_ui->comboKeyFile->currentText().isEmpty()); + + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable); +} + void DatabaseOpenWidget::browseKeyFile() { QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files")); @@ -183,3 +224,11 @@ void DatabaseOpenWidget::browseKeyFile() m_ui->comboKeyFile->lineEdit()->setText(filename); } } + +void DatabaseOpenWidget::ykDetected(int slot, bool blocking) +{ + YkChallengeResponseKey yk(slot, blocking); + m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant(slot)); + m_ui->comboChallengeResponse->setEnabled(true); + m_ui->checkChallengeResponse->setEnabled(true); +} diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index 34f401a09..fadb5ee70 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -55,7 +55,10 @@ protected Q_SLOTS: private Q_SLOTS: void activatePassword(); void activateKeyFile(); + void activateChallengeResponse(); + void setOkButtonEnabled(); void browseKeyFile(); + void ykDetected(int slot, bool blocking); protected: const QScopedPointer m_ui; diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui index 4aae5faf2..3d651ee8d 100644 --- a/src/gui/DatabaseOpenWidget.ui +++ b/src/gui/DatabaseOpenWidget.ui @@ -111,6 +111,23 @@ + + + + false + + + Challenge Response: + + + + + + + false + + + From faa055010f247d37c73154f1ecbad00e6b632e9e Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sat, 6 Sep 2014 17:49:39 -0700 Subject: [PATCH 009/333] challenge: Propagate failed challenge to caller * If a removed Yubikey is to blame, re-inserting the Yubikey won't resolve the issue. Hot plug isn't supported at this point. * The caller should detect the error and cancel the database write. Signed-off-by: Kyle Manna --- src/core/Database.cpp | 4 ++-- src/core/Database.h | 2 +- src/format/KeePass2Reader.cpp | 8 +++++++- src/format/KeePass2Writer.cpp | 8 +++++++- src/keys/CompositeKey.cpp | 12 ++++++++---- src/keys/CompositeKey.h | 2 +- 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index db13e2499..fd9e02dd1 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -176,9 +176,9 @@ QByteArray Database::transformedMasterKey() const return m_data.transformedMasterKey; } -QByteArray Database::challengeMasterSeed(const QByteArray& masterSeed) const +bool Database::challengeMasterSeed(const QByteArray& masterSeed, QByteArray& result) const { - return m_data.key.challenge(masterSeed); + return m_data.key.challenge(masterSeed, result); } void Database::setCipher(const Uuid& cipher) diff --git a/src/core/Database.h b/src/core/Database.h index 607792332..e2eb0fb14 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -89,7 +89,7 @@ public: quint64 transformRounds() const; QByteArray transformedMasterKey() const; const CompositeKey & key() const; - QByteArray challengeMasterSeed(const QByteArray& masterSeed) const; + bool challengeMasterSeed(const QByteArray& masterSeed, QByteArray& result) const; void setCipher(const Uuid& cipher); void setCompressionAlgo(Database::CompressionAlgorithm algo); diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 17e007d76..8ac983276 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -113,9 +113,15 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke return nullptr; } + QByteArray challengeResult; + if (m_db->challengeMasterSeed(m_masterSeed, challengeResult) == false) { + raiseError(tr("Unable to issue challenge-response.")); + return Q_NULLPTR; + } + CryptoHash hash(CryptoHash::Sha256); hash.addData(m_masterSeed); - hash.addData(m_db->challengeMasterSeed(m_masterSeed)); + hash.addData(challengeResult); hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 3a3195a0b..6c0bf2e1b 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -51,9 +51,15 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) QByteArray startBytes = randomGen()->randomArray(32); QByteArray endOfHeader = "\r\n\r\n"; + QByteArray challengeResult; + if (db->challengeMasterSeed(masterSeed, challengeResult) == false) { + raiseError("Unable to issue challenge-response."); + return; + } + CryptoHash hash(CryptoHash::Sha256); hash.addData(masterSeed); - hash.addData(db->challengeMasterSeed(masterSeed)); + hash.addData(challengeResult); Q_ASSERT(!db->transformedMasterKey().isEmpty()); hash.addData(db->transformedMasterKey()); QByteArray finalKey = hash.result(); diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 2270d96eb..ed64b8ef4 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -146,23 +146,27 @@ QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray return result; } -QByteArray CompositeKey::challenge(const QByteArray& seed) const +bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const { /* If no challenge response was requested, return nothing to * maintain backwards compatability with regular databases. */ if (m_challengeResponseKeys.length() == 0) { - return QByteArray(); + return true; } CryptoHash cryptoHash(CryptoHash::Sha256); Q_FOREACH (ChallengeResponseKey* key, m_challengeResponseKeys) { - key->challenge(seed); + /* If the device isn't present or fails, return an error */ + if (key->challenge(seed) == false) { + return false; + } cryptoHash.addData(key->rawKey()); } - return cryptoHash.result(); + result = cryptoHash.result(); + return true; } void CompositeKey::addKey(const Key& key) diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index 66ad91ad9..b5f973d20 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -37,7 +37,7 @@ public: QByteArray rawKey() const; QByteArray transform(const QByteArray& seed, quint64 rounds, bool* ok, QString* errorString) const; - QByteArray challenge(const QByteArray& seed) const; + bool challenge(const QByteArray& seed, QByteArray &result) const; void addKey(const Key& key); void addChallengeResponseKey(const ChallengeResponseKey& key); From f7ee528d41325d335b5cada4b4dfe514d76cc9e8 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sat, 6 Sep 2014 22:09:06 -0700 Subject: [PATCH 010/333] YubiKey: Retry to recover hotplugging * Attempt one retry in the event the event the device was removed and re-inserted. Signed-off-by: Kyle Manna --- src/keys/YkChallengeResponseKey.cpp | 22 ++++++++++++++++++++++ src/keys/YkChallengeResponseKey.h | 3 ++- src/keys/drivers/YubiKey.cpp | 5 +++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index 5f495a340..17982ab62 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -26,6 +26,8 @@ #include "keys/YkChallengeResponseKey.h" #include "keys/drivers/YubiKey.h" +#include + YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) : m_slot(slot), @@ -46,11 +48,31 @@ YkChallengeResponseKey* YkChallengeResponseKey::clone() const /** Assumes yubikey()->init() was called */ bool YkChallengeResponseKey::challenge(const QByteArray& chal) +{ + return challenge(chal, 1); +} + +bool YkChallengeResponseKey::challenge(const QByteArray& chal, int retries) { if (YubiKey::instance()->challenge(m_slot, true, chal, m_key) != YubiKey::ERROR) { return true; } + /* If challenge failed, retry to detect YubiKeys int the event the YubiKey + * was un-plugged and re-plugged */ + while (retries > 0) { + qDebug() << "Attempt" << retries << "to re-detect YubiKey(s)"; + retries--; + + if (YubiKey::instance()->init() != true) { + continue; + } + + if (YubiKey::instance()->challenge(m_slot, true, chal, m_key) != YubiKey::ERROR) { + return true; + } + } + return false; } diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h index a52367486..8acb0f9e9 100644 --- a/src/keys/YkChallengeResponseKey.h +++ b/src/keys/YkChallengeResponseKey.h @@ -31,7 +31,8 @@ public: QByteArray rawKey() const; YkChallengeResponseKey* clone() const; - bool challenge(const QByteArray& challenge); + bool challenge(const QByteArray& chal); + bool challenge(const QByteArray& chal, int retries); QString getName() const; bool isBlocking() const; diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index 1d69bb5b9..9c87ec154 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -182,6 +182,11 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2; QByteArray paddedChal = chal; + /* Ensure that YubiKey::init() succeeded */ + if (m_yk == NULL) { + return ERROR; + } + /* yk_challenge_response() insists on 64 byte response buffer */ resp.resize(64); From 62190d79be49771d003ca1a507a1a371c7f27334 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sat, 6 Sep 2014 22:19:05 -0700 Subject: [PATCH 011/333] YubiKey: Whitespace clean-up * This was bugging me. Oops. * No functional changes. Signed-off-by: Kyle Manna --- src/keys/drivers/YubiKey.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index 9c87ec154..5ca175836 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -104,8 +104,8 @@ bool YubiKey::deinit() } if (m_ykds) { - ykds_free(m_ykds); - m_ykds_void = NULL; + ykds_free(m_ykds); + m_ykds_void = NULL; } return true; @@ -215,7 +215,7 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, paddedChal.size(), c, resp.size(), r); - if(!ret) { + if (!ret) { if (yk_errno == YK_EWOULDBLOCK) { return WOULDBLOCK; } else if (yk_errno == YK_ETIMEOUT) { From 77cc99acd3de0ff1e3df24403eeb136e5faaeb0a Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sun, 7 Sep 2014 16:37:46 -0700 Subject: [PATCH 012/333] YubiKey: Clean-up master seed challenge * Tweak the logic so it more closely resembles other code (i.e. trasnformKey()). Matches existing style better. * Save the challengeResponseKey in the database structure so that it can be referred to later (i.e. database unlocking). Signed-off-by: Kyle Manna --- src/core/Database.cpp | 9 +++++++-- src/core/Database.h | 4 +++- src/format/KeePass2Reader.cpp | 5 ++--- src/format/KeePass2Writer.cpp | 5 ++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index fd9e02dd1..5297c2ad3 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -176,9 +176,14 @@ QByteArray Database::transformedMasterKey() const return m_data.transformedMasterKey; } -bool Database::challengeMasterSeed(const QByteArray& masterSeed, QByteArray& result) const +QByteArray Database::challengeResponseKey() const { - return m_data.key.challenge(masterSeed, result); + return m_data.challengeResponseKey; +} + +bool Database::challengeMasterSeed(const QByteArray& masterSeed) +{ + return m_data.key.challenge(masterSeed, m_data.challengeResponseKey); } void Database::setCipher(const Uuid& cipher) diff --git a/src/core/Database.h b/src/core/Database.h index e2eb0fb14..be022ae39 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -59,6 +59,7 @@ public: QByteArray transformedMasterKey; CompositeKey key; bool hasKey; + QByteArray challengeResponseKey; }; Database(); @@ -89,7 +90,8 @@ public: quint64 transformRounds() const; QByteArray transformedMasterKey() const; const CompositeKey & key() const; - bool challengeMasterSeed(const QByteArray& masterSeed, QByteArray& result) const; + QByteArray challengeResponseKey() const; + bool challengeMasterSeed(const QByteArray& masterSeed); void setCipher(const Uuid& cipher); void setCompressionAlgo(Database::CompressionAlgorithm algo); diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 8ac983276..73960a7a1 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -113,15 +113,14 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke return nullptr; } - QByteArray challengeResult; - if (m_db->challengeMasterSeed(m_masterSeed, challengeResult) == false) { + if (m_db->challengeMasterSeed(m_masterSeed) == false) { raiseError(tr("Unable to issue challenge-response.")); return Q_NULLPTR; } CryptoHash hash(CryptoHash::Sha256); hash.addData(m_masterSeed); - hash.addData(challengeResult); + hash.addData(m_db->challengeResponseKey()); hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 6c0bf2e1b..6bb316bac 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -51,15 +51,14 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) QByteArray startBytes = randomGen()->randomArray(32); QByteArray endOfHeader = "\r\n\r\n"; - QByteArray challengeResult; - if (db->challengeMasterSeed(masterSeed, challengeResult) == false) { + if (db->challengeMasterSeed(masterSeed) == false) { raiseError("Unable to issue challenge-response."); return; } CryptoHash hash(CryptoHash::Sha256); hash.addData(masterSeed); - hash.addData(challengeResult); + hash.addData(db->challengeResponseKey()); Q_ASSERT(!db->transformedMasterKey().isEmpty()); hash.addData(db->transformedMasterKey()); QByteArray finalKey = hash.result(); From 951fa968480a861718371e25d7ca3f8106821100 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sun, 7 Sep 2014 16:43:04 -0700 Subject: [PATCH 013/333] YubiKey: Fix database locking * Save the master seed upon first challenge so it can be used as a challenge at a later point. * When verifyKey() is called, verify that the challenge is successful. * Uncheck YubiKey box to not leak information about how the database is protected. Signed-off-by: Kyle Manna --- src/core/Database.cpp | 17 +++++++++++++++++ src/core/Database.h | 1 + src/gui/UnlockDatabaseWidget.cpp | 1 + 3 files changed, 19 insertions(+) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 5297c2ad3..22fc07230 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -183,6 +183,7 @@ QByteArray Database::challengeResponseKey() const bool Database::challengeMasterSeed(const QByteArray& masterSeed) { + m_data.masterSeed = masterSeed; return m_data.key.challenge(masterSeed, m_data.challengeResponseKey); } @@ -256,6 +257,22 @@ bool Database::verifyKey(const CompositeKey& key) const { Q_ASSERT(hasKey()); + /* If the database has challenge response keys, then the the verification + * key better as well */ + if (!m_data.challengeResponseKey.isEmpty()) { + QByteArray result; + + if (!key.challenge(m_data.masterSeed, result)) { + /* Challenge failed, (YubiKey?) removed? */ + return false; + } + + if (m_data.challengeResponseKey != result) { + /* Wrong response from challenged device(s) */ + return false; + } + } + return (m_data.key.rawKey() == key.rawKey()); } diff --git a/src/core/Database.h b/src/core/Database.h index be022ae39..3f946a1cf 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -59,6 +59,7 @@ public: QByteArray transformedMasterKey; CompositeKey key; bool hasKey; + QByteArray masterSeed; QByteArray challengeResponseKey; }; diff --git a/src/gui/UnlockDatabaseWidget.cpp b/src/gui/UnlockDatabaseWidget.cpp index a005d0e60..d6beb1339 100644 --- a/src/gui/UnlockDatabaseWidget.cpp +++ b/src/gui/UnlockDatabaseWidget.cpp @@ -33,6 +33,7 @@ void UnlockDatabaseWidget::clearForms() m_ui->comboKeyFile->clear(); m_ui->checkPassword->setChecked(false); m_ui->checkKeyFile->setChecked(false); + m_ui->checkChallengeResponse->setChecked(false); m_ui->buttonTogglePassword->setChecked(false); m_db = nullptr; } From d398d367c1e2b6fe9534f42769cc6e05cd4ceafe Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Tue, 27 Jan 2015 17:38:26 +0000 Subject: [PATCH 014/333] Allow a previously yubikey protected database to be saved without the yubikey challenge-response code. --- src/keys/CompositeKey.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index ed64b8ef4..d2334e3c2 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -43,7 +43,9 @@ CompositeKey::~CompositeKey() void CompositeKey::clear() { qDeleteAll(m_keys); + qDeleteAll(m_challengeResponseKeys); m_keys.clear(); + m_challengeResponseKeys.clear(); } bool CompositeKey::isEmpty() const @@ -152,6 +154,7 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const * maintain backwards compatability with regular databases. */ if (m_challengeResponseKeys.length() == 0) { + result.clear(); return true; } From ef06165ea2f4998e8c04c6c62b04a2fe504e5656 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sun, 8 Jan 2017 18:45:02 -0800 Subject: [PATCH 015/333] keys: CompositeKey: Change Q_FOREACH to C++11 for() * Use the C++11 range based loop as recommended from https://github.com/keepassxreboot/keepassxc/pull/119 Signed-off-by: Kyle Manna --- src/keys/CompositeKey.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index d2334e3c2..ae654eb74 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -70,7 +70,7 @@ CompositeKey& CompositeKey::operator=(const CompositeKey& key) for (const Key* subKey : asConst(key.m_keys)) { addKey(*subKey); } - Q_FOREACH (const ChallengeResponseKey* subKey, key.m_challengeResponseKeys) { + for (const ChallengeResponseKey* subKey : asConst(key.m_challengeResponseKeys)) { addChallengeResponseKey(*subKey); } @@ -160,7 +160,7 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const CryptoHash cryptoHash(CryptoHash::Sha256); - Q_FOREACH (ChallengeResponseKey* key, m_challengeResponseKeys) { + for (ChallengeResponseKey* key : m_challengeResponseKeys) { /* If the device isn't present or fails, return an error */ if (key->challenge(seed) == false) { return false; From 05774854efb4e32280aac7d7bcd420779c4ec375 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sat, 14 Jan 2017 16:07:02 -0800 Subject: [PATCH 016/333] travis-ci: Add YubiKey development libraries * With these packages cmake will detect the YubiKey headers and libraries and build in YubiKey support. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a7807b612..adb8afdd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ git: before_install: - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libmicrohttpd10 libmicrohttpd-dev libxi-dev qtbase5-dev libqt5x11extras5-dev qttools5-dev qttools5-dev-tools libgcrypt20-dev zlib1g-dev libxtst-dev xvfb; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libmicrohttpd10 libmicrohttpd-dev libxi-dev qtbase5-dev libqt5x11extras5-dev qttools5-dev qttools5-dev-tools libgcrypt20-dev zlib1g-dev libxtst-dev xvfb libyubikey-dev libykpers-1-dev; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq cmake || brew install cmake; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq qt5 || brew install qt5; fi From f33cd1541941a088b90f463d457f4a9f86aa712a Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sat, 14 Jan 2017 17:08:48 -0800 Subject: [PATCH 017/333] gui: Clear YubiKeys detected on each load * Clear the YubiKey detected list on each load. * In the event the YubiKey was removed, it will no longer be displayed. * If it's still present it won't be duplicated. --- src/gui/DatabaseOpenWidget.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 5d3815963..0b63bc0fc 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -64,6 +64,10 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase())); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); + + connect(YubiKey::instance(), SIGNAL(detected(int,bool)), + SLOT(ykDetected(int,bool)), + Qt::QueuedConnection); } DatabaseOpenWidget::~DatabaseOpenWidget() @@ -92,10 +96,8 @@ void DatabaseOpenWidget::load(const QString& filename) m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - /* YubiKey init is slow */ - connect(YubiKey::instance(), SIGNAL(detected(int,bool)), - SLOT(ykDetected(int,bool)), - Qt::QueuedConnection); + /* YubiKey init is slow, detect asynchronously to not block the UI */ + m_ui->comboChallengeResponse->clear(); QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); m_ui->editPassword->setFocus(); From a7cf39c7cd07fd27eb3d521403ad92d69ef4cf74 Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sat, 14 Jan 2017 17:36:14 -0800 Subject: [PATCH 018/333] gui: ChangeMasterKeyWidget: Clear YubiKeys detected * Clear the YubiKey detected list on each load. * In the event the YubiKey was removed, it will no longer be displayed. * If it's still present it won't be duplicated. --- src/gui/ChangeMasterKeyWidget.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index c69cf2dcc..f2cf3b8d9 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -41,6 +41,10 @@ ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) m_ui->repeatPasswordEdit->enableVerifyMode(m_ui->enterPasswordEdit); connect(m_ui->createKeyFileButton, SIGNAL(clicked()), SLOT(createKeyFile())); connect(m_ui->browseKeyFileButton, SIGNAL(clicked()), SLOT(browseKeyFile())); + + connect(YubiKey::instance(), SIGNAL(detected(int,bool)), + SLOT(ykDetected(int,bool)), + Qt::QueuedConnection); } ChangeMasterKeyWidget::~ChangeMasterKeyWidget() @@ -88,10 +92,8 @@ void ChangeMasterKeyWidget::clearForms() m_ui->challengeResponseGroup->setChecked(false); m_ui->challengeResponseCombo->clear(); - /* YubiKey init is slow */ - connect(YubiKey::instance(), SIGNAL(detected(int,bool)), - SLOT(ykDetected(int,bool)), - Qt::QueuedConnection); + /* YubiKey init is slow, detect asynchronously to not block the UI */ + m_ui->challengeResponseCombo->clear(); QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); m_ui->enterPasswordEdit->setFocus(); From 5a3ed27fedd5668bab0b9ffab7a815d1c112884e Mon Sep 17 00:00:00 2001 From: Kyle Manna Date: Sat, 14 Jan 2017 17:58:05 -0800 Subject: [PATCH 019/333] tests: yk: Initialize Crypto class Initialize the Crypto class before running the tests as YubiKey detection code depends on it for random data. --- tests/TestYkChallengeResponseKey.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/TestYkChallengeResponseKey.cpp b/tests/TestYkChallengeResponseKey.cpp index 91fba83cb..bd58ac018 100644 --- a/tests/TestYkChallengeResponseKey.cpp +++ b/tests/TestYkChallengeResponseKey.cpp @@ -21,6 +21,7 @@ #include #include +#include "crypto/Crypto.h" #include "keys/YkChallengeResponseKey.h" QTEST_GUILESS_MAIN(TestYubiKeyChalResp) @@ -44,6 +45,9 @@ void TestYubiKeyChalResp::init() if (!result) { QSKIP("Unable to connect to YubiKey", SkipAll); } + + /* Crypto subsystem needs to be initalized for YubiKey testing */ + QVERIFY(Crypto::init()); } void TestYubiKeyChalResp::detectDevices() From a01607e869f78d7f8b9ad99f8d9604a0b28b0cf8 Mon Sep 17 00:00:00 2001 From: Timothy Redaelli Date: Tue, 4 Aug 2015 15:18:41 +0200 Subject: [PATCH 020/333] Add support for Twofish in KeePass2 code --- src/crypto/SymmetricCipher.cpp | 20 ++++++++++++++++++++ src/crypto/SymmetricCipher.h | 4 ++++ src/format/KeePass2.h | 1 + src/format/KeePass2Reader.cpp | 4 ++-- src/format/KeePass2Writer.cpp | 4 ++-- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index 12ec264f5..98d481969 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -83,3 +83,23 @@ QString SymmetricCipher::errorString() const { return m_backend->errorString(); } + +SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(Uuid cipher) +{ + if (cipher == KeePass2::CIPHER_AES) { + return SymmetricCipher::Aes256; + } + else { + return SymmetricCipher::Twofish; + } +} + +Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo) +{ + switch (algo) { + case SymmetricCipher::Aes256: + return KeePass2::CIPHER_AES; + default: + return KeePass2::CIPHER_TWOFISH; + } +} diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index 4fc06b7de..0070ed7de 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -23,6 +23,7 @@ #include #include "crypto/SymmetricCipherBackend.h" +#include "format/KeePass2.h" class SymmetricCipher { @@ -71,6 +72,9 @@ public: int blockSize() const; QString errorString() const; + static SymmetricCipher::Algorithm cipherToAlgorithm(Uuid cipher); + static Uuid algorithmToCipher(SymmetricCipher::Algorithm algo); + private: static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, SymmetricCipher::Direction direction); diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index b49ae4f6a..91ee48293 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -33,6 +33,7 @@ namespace KeePass2 const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian; const Uuid CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); + const Uuid CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); const QByteArray INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 1371aaa6a..668165c5f 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -118,7 +118,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); - SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, + SymmetricCipherStream cipherStream(m_device, SymmetricCipher::cipherToAlgorithm(m_db->cipher()), SymmetricCipher::Cbc, SymmetricCipher::Decrypt); if (!cipherStream.init(finalKey, m_encryptionIV)) { raiseError(cipherStream.errorString()); @@ -330,7 +330,7 @@ void KeePass2Reader::setCipher(const QByteArray& data) else { Uuid uuid(data); - if (uuid != KeePass2::CIPHER_AES) { + if (uuid != KeePass2::CIPHER_AES && uuid != KeePass2::CIPHER_TWOFISH) { raiseError("Unsupported cipher"); } else { diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index dfbbf3532..e6ec5f600 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -87,8 +87,8 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256); CHECK_RETURN(writeData(header.data())); - SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, - SymmetricCipher::Encrypt); + SymmetricCipherStream cipherStream(device, SymmetricCipher::cipherToAlgorithm(db->cipher()), + SymmetricCipher::Cbc, SymmetricCipher::Encrypt); cipherStream.init(finalKey, encryptionIV); if (!cipherStream.open(QIODevice::WriteOnly)) { raiseError(cipherStream.errorString()); From b0a4d7278ac9d6a49cc8f669db98f1456d8fa783 Mon Sep 17 00:00:00 2001 From: Timothy Redaelli Date: Wed, 5 Aug 2015 15:23:49 +0200 Subject: [PATCH 021/333] Add Algorithm label / ComboBox in Database settings form --- src/gui/DatabaseSettingsWidget.cpp | 5 ++ src/gui/DatabaseSettingsWidget.ui | 89 ++++++++++++++++++------------ 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index b0759bf3a..59a3c5c3a 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -21,6 +21,8 @@ #include "core/Database.h" #include "core/Group.h" #include "core/Metadata.h" +#include "crypto/SymmetricCipher.h" +#include "format/KeePass2.h" #include "keys/CompositeKey.h" DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) @@ -53,6 +55,7 @@ void DatabaseSettingsWidget::load(Database* db) m_ui->dbDescriptionEdit->setText(meta->description()); m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled()); m_ui->defaultUsernameEdit->setText(meta->defaultUserName()); + m_ui->AlgorithmComboBox->setCurrentIndex(SymmetricCipher::cipherToAlgorithm(m_db->cipher())); m_ui->transformRoundsSpinBox->setValue(m_db->transformRounds()); if (meta->historyMaxItems() > -1) { m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); @@ -82,6 +85,8 @@ void DatabaseSettingsWidget::save() meta->setName(m_ui->dbNameEdit->text()); meta->setDescription(m_ui->dbDescriptionEdit->text()); meta->setDefaultUserName(m_ui->defaultUsernameEdit->text()); + m_db->setCipher(SymmetricCipher::algorithmToCipher(static_cast + (m_ui->AlgorithmComboBox->currentIndex()))); meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked()); if (static_cast(m_ui->transformRoundsSpinBox->value()) != m_db->transformRounds()) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); diff --git a/src/gui/DatabaseSettingsWidget.ui b/src/gui/DatabaseSettingsWidget.ui index 5d1f3d9f1..1c233bdd4 100644 --- a/src/gui/DatabaseSettingsWidget.ui +++ b/src/gui/DatabaseSettingsWidget.ui @@ -49,35 +49,7 @@ - - - - Transform rounds: - - - - - - - Default username: - - - - - - - true - - - - - - - Use recycle bin: - - - - + @@ -100,7 +72,7 @@ - + @@ -117,7 +89,7 @@ - + @@ -144,23 +116,72 @@ - + Max. history items: - + Max. history size: - + + + + Transform rounds: + + + + + + + + Default username: + + + + + + + Use recycle bin: + + + + + + + true + + + + + + + Algorithm: + + + + + + + + AES: 256 Bit (default) + + + + + Twofish: 256 Bit + + + + From 75dc21c66d496bdc1e167a2a5ceb334dbab09fc6 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 15 Jan 2017 19:12:23 +0100 Subject: [PATCH 022/333] Implement Twofish CBC encryption and decryption tests --- tests/TestSymmetricCipher.cpp | 121 ++++++++++++++++++++++++++++++++++ tests/TestSymmetricCipher.h | 2 + 2 files changed, 123 insertions(+) diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index 698ecb204..737778479 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -123,6 +123,127 @@ void TestSymmetricCipher::testAes256CbcDecryption() plainText); } +void TestSymmetricCipher::testTwofish256CbcEncryption() +{ + // NIST MCT Known-Answer Tests (cbc_e_m.txt) + // https://www.schneier.com/code/twofish-kat.zip + + QVector keys { + QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + QByteArray::fromHex("D0A260EB41755B19374BABF259A79DB3EA7162E65490B03B1AE4871FB35EF23B"), + QByteArray::fromHex("8D55E4849A4DED08D89881E6708EDD26BEEE942073DFB3790B2798B240ACD74A"), + QByteArray::fromHex("606EFDC2066A837AF0430EBE4CF1F21071CCB236C33B4B9D82404FDB05C74621"), + QByteArray::fromHex("B119AA9485CEEEB4CC778AF21121E54DE4BDBA3498C61C8FD9004AA0C71909C3") + }; + QVector ivs { + QByteArray::fromHex("00000000000000000000000000000000"), + QByteArray::fromHex("EA7162E65490B03B1AE4871FB35EF23B"), + QByteArray::fromHex("549FF6C6274F034211C31FADF3F22571"), + QByteArray::fromHex("CF222616B0E4F8E48967D769456B916B"), + QByteArray::fromHex("957108025BFD57125B40057BC2DE4FE2") + }; + QVector plainTexts { + QByteArray::fromHex("00000000000000000000000000000000"), + QByteArray::fromHex("D0A260EB41755B19374BABF259A79DB3"), + QByteArray::fromHex("5DF7846FDB38B611EFD32A1429294095"), + QByteArray::fromHex("ED3B19469C276E7228DB8F583C7F2F36"), + QByteArray::fromHex("D177575683A46DCE3C34844C5DD0175D") + }; + QVector cipherTexts { + QByteArray::fromHex("EA7162E65490B03B1AE4871FB35EF23B"), + QByteArray::fromHex("549FF6C6274F034211C31FADF3F22571"), + QByteArray::fromHex("CF222616B0E4F8E48967D769456B916B"), + QByteArray::fromHex("957108025BFD57125B40057BC2DE4FE2"), + QByteArray::fromHex("6F725C5950133F82EF021A94CADC8508") + }; + + SymmetricCipher cipher(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); + bool ok; + + for (int i = 0; i < keys.size(); ++i) { + cipher.init(keys[i], ivs[i]); + QByteArray ptNext = plainTexts[i]; + QByteArray ctPrev = ivs[i]; + QByteArray ctCur; + QCOMPARE(cipher.blockSize(), 16); + for (int j = 0; j < 5000; ++j) { + ctCur = cipher.process(ptNext, &ok); + if (!ok) + return; + ptNext = ctPrev; + ctPrev = ctCur; + + ctCur = cipher.process(ptNext, &ok); + if (!ok) + return; + ptNext = ctPrev; + ctPrev = ctCur; + } + + QVERIFY(ok); + QCOMPARE(ctCur, cipherTexts[i]); + } +} + +void TestSymmetricCipher::testTwofish256CbcDecryption() +{ + // NIST MCT Known-Answer Tests (cbc_d_m.txt) + // https://www.schneier.com/code/twofish-kat.zip + + QVector keys { + QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + QByteArray::fromHex("1B1FE8F5A911CD4C0D800EDCE8ED0A942CBA6271A1044F90C30BA8FE91E1C163"), + QByteArray::fromHex("EBA31FF8D2A24FDD769A937353E23257294A33394E4D17A668060AD8230811A1"), + QByteArray::fromHex("1DCF1915C389AB273F80F897BF008F058ED89F58A95C1BE523C4B11295ED2D0F"), + QByteArray::fromHex("491B9A66D3ED4EF19F02180289D5B1A1C2596AE568540A95DC5244198A9B8869") + }; + QVector ivs { + QByteArray::fromHex("00000000000000000000000000000000"), + QByteArray::fromHex("1B1FE8F5A911CD4C0D800EDCE8ED0A94"), + QByteArray::fromHex("F0BCF70D7BB382917B1A9DAFBB0F38C3"), + QByteArray::fromHex("F66C06ED112BE4FA491A6BE4ECE2BD52"), + QByteArray::fromHex("54D483731064E5D6A082E09536D53EA4") + }; + QVector plainTexts { + QByteArray::fromHex("2CBA6271A1044F90C30BA8FE91E1C163"), + QByteArray::fromHex("05F05148EF495836AB0DA226B2E9D0C2"), + QByteArray::fromHex("A792AC61E7110C434BC2BBCAB6E53CAE"), + QByteArray::fromHex("4C81F5BDC1081170FF96F50B1F76A566"), + QByteArray::fromHex("BD959F5B787037631A37051EA5F369F8") + }; + QVector cipherTexts { + QByteArray::fromHex("00000000000000000000000000000000"), + QByteArray::fromHex("2CBA6271A1044F90C30BA8FE91E1C163"), + QByteArray::fromHex("05F05148EF495836AB0DA226B2E9D0C2"), + QByteArray::fromHex("A792AC61E7110C434BC2BBCAB6E53CAE"), + QByteArray::fromHex("4C81F5BDC1081170FF96F50B1F76A566") + }; + + SymmetricCipher cipher(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + bool ok; + + for (int i = 0; i < keys.size(); ++i) { + cipher.init(keys[i], ivs[i]); + QByteArray ctNext = cipherTexts[i]; + QByteArray ptCur; + QCOMPARE(cipher.blockSize(), 16); + for (int j = 0; j < 5000; ++j) { + ptCur = cipher.process(ctNext, &ok); + if (!ok) + return; + ctNext = ptCur; + + ptCur = cipher.process(ctNext, &ok); + if (!ok) + return; + ctNext = ptCur; + } + + QVERIFY(ok); + QCOMPARE(ptCur, plainTexts[i]); + } +} + void TestSymmetricCipher::testSalsa20() { // http://www.ecrypt.eu.org/stream/svn/viewcvs.cgi/ecrypt/trunk/submissions/salsa20/full/verified.test-vectors?logsort=rev&rev=210&view=markup diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index 1ac45793f..17fa77a49 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -28,6 +28,8 @@ private Q_SLOTS: void initTestCase(); void testAes256CbcEncryption(); void testAes256CbcDecryption(); + void testTwofish256CbcEncryption(); + void testTwofish256CbcDecryption(); void testSalsa20(); void testPadding(); void testStreamReset(); From 4dab30ce6fd494e6b5b61cf6942ae440b364ee2b Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 15 Jan 2017 19:36:00 +0100 Subject: [PATCH 023/333] Break instead of returning --- tests/TestSymmetricCipher.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index 737778479..3edf735b8 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -169,13 +169,13 @@ void TestSymmetricCipher::testTwofish256CbcEncryption() for (int j = 0; j < 5000; ++j) { ctCur = cipher.process(ptNext, &ok); if (!ok) - return; + break; ptNext = ctPrev; ctPrev = ctCur; ctCur = cipher.process(ptNext, &ok); if (!ok) - return; + break; ptNext = ctPrev; ctPrev = ctCur; } @@ -230,12 +230,12 @@ void TestSymmetricCipher::testTwofish256CbcDecryption() for (int j = 0; j < 5000; ++j) { ptCur = cipher.process(ctNext, &ok); if (!ok) - return; + break; ctNext = ptCur; ptCur = cipher.process(ctNext, &ok); if (!ok) - return; + break; ctNext = ptCur; } From 0091cb3f23203d1ed3d3ab26ec61c32829ab2088 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 28 Jan 2017 22:02:35 -0500 Subject: [PATCH 024/333] Fixes database modified after discard --- src/gui/DatabaseTabWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index af6907001..a227cdbeb 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -716,6 +716,7 @@ void DatabaseTabWidget::lockDatabases() } else if (result == QMessageBox::Discard) { m_dbList[db].modified = false; + m_dbList[db].dbWidget->databaseSaved(); } else if (result == QMessageBox::Cancel) { continue; From 26fdb193441bc313830b36463a6253809f9a6dc9 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 5 Feb 2017 12:47:27 -0500 Subject: [PATCH 025/333] Typo is snap description. --- snapcraft.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snapcraft.yaml b/snapcraft.yaml index 5a97a38a1..6e8ef7985 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -3,9 +3,9 @@ version: 2.1.0 grade: stable summary: community driven port of the windows application “Keepass Password Safe” description: | - KeePassXC is an application for people with extremly high demands on secure + KeePassXC is an application for people with extremely high demands on secure personal data management. It has a light interface, is cross platform and - published under the terms of the GNU General Public License. + is published under the terms of the GNU General Public License. confinement: strict apps: From 343a1e2a76bdf04a97580a68e4d438231586865c Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 7 Feb 2017 01:16:40 +0100 Subject: [PATCH 026/333] Also build http --- release-tool | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-tool b/release-tool index 7bc54cda0..af05481b0 100755 --- a/release-tool +++ b/release-tool @@ -37,7 +37,7 @@ DOCKER_CONTAINER_NAME="keepassxc-build-container" CMAKE_OPTIONS="" COMPILER="g++" MAKE_OPTIONS="-j8" -BUILD_PLUGINS="autotype" +BUILD_PLUGINS="autotype http" INSTALL_PREFIX="/usr/local" BUILD_SOURCE_TARBALL=true ORIG_BRANCH="" From 0ac05dbb0fe1d5512d390b17c5d545469f962870 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 8 Feb 2017 00:11:16 +0100 Subject: [PATCH 027/333] fix #256 space in autotype sequence --- src/autotype/AutoType.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 0c942e728..9d9629c47 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -396,6 +396,9 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c else if (tmplName.compare("enter",Qt::CaseInsensitive)==0) { list.append(new AutoTypeKey(Qt::Key_Enter)); } + else if (tmplName.compare("space",Qt::CaseInsensitive)==0) { + list.append(new AutoTypeChar(' ')); + } else if (tmplName.compare("up",Qt::CaseInsensitive)==0) { list.append(new AutoTypeKey(Qt::Key_Up)); } From 2975eb315f12f91fe664e54043ba60758569dd6c Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 8 Feb 2017 00:32:22 +0100 Subject: [PATCH 028/333] use space from XCB KeySym --- src/autotype/AutoType.cpp | 2 +- src/autotype/xcb/AutoTypeXCB.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 9d9629c47..40ece6e13 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -397,7 +397,7 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c list.append(new AutoTypeKey(Qt::Key_Enter)); } else if (tmplName.compare("space",Qt::CaseInsensitive)==0) { - list.append(new AutoTypeChar(' ')); + list.append(new AutoTypeKey(Qt::Key_Space)); } else if (tmplName.compare("up",Qt::CaseInsensitive)==0) { list.append(new AutoTypeKey(Qt::Key_Up)); diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index f419875dc..a07a916c4 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -435,6 +435,8 @@ KeySym AutoTypePlatformX11::keyToKeySym(Qt::Key key) return XK_Tab; case Qt::Key_Enter: return XK_Return; + case Qt::Key_Space: + return XK_space; case Qt::Key_Up: return XK_Up; case Qt::Key_Down: From 8408e7fdb42cacdbe4be1c0b9df19fec14aeb361 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 8 Feb 2017 01:04:05 +0100 Subject: [PATCH 029/333] fix #218 custom attribute like KeePass interface --- src/core/Entry.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 46e2670ac..162d3f089 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -646,12 +646,17 @@ QString Entry::resolvePlaceholder(const QString& str) const const QList keyList = attributes()->keys(); for (const QString& key : keyList) { Qt::CaseSensitivity cs = Qt::CaseInsensitive; + QString k = key; + if (!EntryAttributes::isDefaultAttribute(key)) { cs = Qt::CaseSensitive; + k.prepend("{S:"); + } else { + k.prepend("{"); } - QString k = key; - k.prepend("{").append("}"); + + k.append("}"); if (result.compare(k,cs)==0) { result.replace(result,attributes()->value(key)); break; From 51c668a0fd9219e5b0d6fa69660216bae56236d0 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 8 Feb 2017 01:14:52 +0100 Subject: [PATCH 030/333] fix tests with new CustomAttribute format --- tests/TestAutoType.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index c5c1a5933..a73298866 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -96,10 +96,10 @@ void TestAutoType::init() m_entry4->setPassword("custom_attr"); m_entry4->attributes()->set("CUSTOM","Attribute",false); association.window = "//^CustomAttr1$//"; - association.sequence = "{PASSWORD}:{CUSTOM}"; + association.sequence = "{PASSWORD}:{S:CUSTOM}"; m_entry4->autoTypeAssociations()->add(association); association.window = "//^CustomAttr2$//"; - association.sequence = "{CuStOm}"; + association.sequence = "{S:CuStOm}"; m_entry4->autoTypeAssociations()->add(association); association.window = "//^CustomAttr3$//"; association.sequence = "{PaSSworD}"; From c6ad476cb707bf452aa7d538255f72e157c55be7 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Mon, 19 Jan 2015 20:16:21 +0000 Subject: [PATCH 031/333] Added kmessagewidget from subsurface project (https://github.com/torvalds/subsurface, commit: 82a946152b7f1da177344937acbc9d3cb5b0ccbf). Added MessageWidget class. --- src/CMakeLists.txt | 2 + src/gui/MessageWidget.cpp | 49 +++++ src/gui/MessageWidget.h | 21 ++ src/gui/kmessagewidget.cpp | 425 +++++++++++++++++++++++++++++++++++++ src/gui/kmessagewidget.h | 239 +++++++++++++++++++++ 5 files changed, 736 insertions(+) create mode 100644 src/gui/MessageWidget.cpp create mode 100644 src/gui/MessageWidget.h create mode 100644 src/gui/kmessagewidget.cpp create mode 100644 src/gui/kmessagewidget.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c3948842..3de167544 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -86,9 +86,11 @@ set(keepassx_SOURCES gui/FileDialog.cpp gui/IconModels.cpp gui/KeePass1OpenWidget.cpp + gui/kmessagewidget.cpp gui/LineEdit.cpp gui/MainWindow.cpp gui/MessageBox.cpp + gui/MessageWidget.cpp gui/PasswordEdit.cpp gui/PasswordGeneratorWidget.cpp gui/PasswordComboBox.cpp diff --git a/src/gui/MessageWidget.cpp b/src/gui/MessageWidget.cpp new file mode 100644 index 000000000..1e6d06044 --- /dev/null +++ b/src/gui/MessageWidget.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 Felix Geyer + * + * 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 . + */ + +#include "MessageWidget.h" + +MessageWidget::MessageWidget(QWidget* parent) + :KMessageWidget(parent) +{ + +} + +void MessageWidget::showMessageError(const QString& text) +{ + showMessage(text, MessageType::Error); +} + +void MessageWidget::showMessageWarning(const QString& text) +{ + showMessage(text, MessageType::Warning); +} + +void MessageWidget::showMessagePositive(const QString& text) +{ + showMessage(text, MessageType::Positive); +} + +void MessageWidget::showMessage(const QString& text, MessageType type) +{ + setMessageType(type); + setText(text); + animatedShow(); +} + + + diff --git a/src/gui/MessageWidget.h b/src/gui/MessageWidget.h new file mode 100644 index 000000000..6e8366fae --- /dev/null +++ b/src/gui/MessageWidget.h @@ -0,0 +1,21 @@ + + +#ifndef MESSAGEWIDGET_H +#define MESSAGEWIDGET_H + +#include "gui/kmessagewidget.h" + +class MessageWidget : public KMessageWidget +{ +public: + explicit MessageWidget(QWidget* parent = 0); + void showMessageError(const QString& text); + void showMessageWarning(const QString& text); + void showMessageInformation(const QString& text); + void showMessagePositive(const QString& text); + +private: + void showMessage(const QString& text, MessageType type); +}; + +#endif // MESSAGEWIDGET_H diff --git a/src/gui/kmessagewidget.cpp b/src/gui/kmessagewidget.cpp new file mode 100644 index 000000000..e5aafd61a --- /dev/null +++ b/src/gui/kmessagewidget.cpp @@ -0,0 +1,425 @@ +/* This file is part of the KDE libraries + * + * Copyright (c) 2011 Aurélien Gâteau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include "kmessagewidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void KMessageWidgetPrivate::init(KMessageWidget *q_ptr) +{ + q = q_ptr; + + q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + timeLine = new QTimeLine(500, q); + QObject::connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(slotTimeLineChanged(qreal))); + QObject::connect(timeLine, SIGNAL(finished()), q, SLOT(slotTimeLineFinished())); + + content = new QFrame(q); + content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + wordWrap = false; + + iconLabel = new QLabel(content); + iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + iconLabel->hide(); + + textLabel = new QLabel(content); + textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + QObject::connect(textLabel, SIGNAL(linkActivated(const QString &)), q, SIGNAL(linkActivated(const QString &))); + QObject::connect(textLabel, SIGNAL(linkHovered(const QString &)), q, SIGNAL(linkHovered(const QString &))); + + QAction *closeAction = new QAction(QObject::tr("Close"), q); + q->connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(animatedHide())); + + closeButton = new QToolButton(content); + closeButton->setAutoRaise(true); + closeButton->setDefaultAction(closeAction); + closeButton->setVisible(true); + q->setMessageType(KMessageWidget::Information); +} + +void KMessageWidgetPrivate::createLayout() +{ + delete content->layout(); + + content->resize(q->size()); + + qDeleteAll(buttons); + buttons.clear(); + + Q_FOREACH (QAction *action, q->actions()) { + QToolButton *button = new QToolButton(content); + button->setDefaultAction(action); + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + buttons.append(button); + } + + // AutoRaise reduces visual clutter, but we don't want to turn it on if + // there are other buttons, otherwise the close button will look different + // from the others. + closeButton->setAutoRaise(buttons.isEmpty()); + + if (wordWrap) { + QGridLayout *layout = new QGridLayout(content); + // Set alignment to make sure icon does not move down if text wraps + layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop); + layout->addWidget(textLabel, 0, 1); + + QDialogButtonBox *buttonLayout = new QDialogButtonBox(); + //buttonLayout->addStretch(); + Q_FOREACH (QToolButton *button, buttons) { + // For some reason, calling show() is necessary if wordwrap is true, + // otherwise the buttons do not show up. It is not needed if + // wordwrap is false. + button->show(); + buttonLayout->addButton(button, QDialogButtonBox::QDialogButtonBox::AcceptRole); + } + buttonLayout->addButton(closeButton, QDialogButtonBox::RejectRole); + layout->addWidget(buttonLayout, 1, 0, 1, 2, Qt::AlignHCenter | Qt::AlignTop); + } else { + bool closeButtonVisible = closeButton->isVisible(); + QHBoxLayout *layout = new QHBoxLayout(content); + layout->addWidget(iconLabel); + layout->addWidget(textLabel); + + QDialogButtonBox *buttonLayout = new QDialogButtonBox(); + Q_FOREACH (QToolButton *button, buttons) { + buttonLayout->addButton(button, QDialogButtonBox::QDialogButtonBox::AcceptRole); + } + + buttonLayout->addButton(closeButton, QDialogButtonBox::RejectRole); + // Something gets changed when added to the buttonLayout + closeButton->setVisible(closeButtonVisible); + layout->addWidget(buttonLayout); + }; + + if (q->isVisible()) { + q->setFixedHeight(content->sizeHint().height()); + } + + q->updateGeometry(); +} + +void KMessageWidgetPrivate::updateLayout() +{ + if (content->layout()) { + createLayout(); + } +} + +void KMessageWidgetPrivate::updateSnapShot() +{ + // Attention: updateSnapShot calls QWidget::render(), which causes the whole + // window layouts to be activated. Calling this method from resizeEvent() + // can lead to infinite recursion, see: + // https://bugs.kde.org/show_bug.cgi?id=311336 + contentSnapShot = QPixmap(content->size()); + contentSnapShot.fill(Qt::transparent); + content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren); +} + +void KMessageWidgetPrivate::slotTimeLineChanged(qreal value) +{ + q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height()); + q->update(); +} + +void KMessageWidgetPrivate::slotTimeLineFinished() +{ + if (timeLine->direction() == QTimeLine::Forward) { + // Show + // We set the whole geometry here, because it may be wrong if a + // KMessageWidget is shown right when the toplevel window is created. + content->setGeometry(0, 0, q->width(), bestContentHeight()); + } else { + // Hide + q->hide(); + } +} + +int KMessageWidgetPrivate::bestContentHeight() const +{ + int height = content->heightForWidth(q->width()); + + if (height == -1) { + height = content->sizeHint().height(); + } + + return height; +} + + +//--------------------------------------------------------------------- +// KMessageWidget +//--------------------------------------------------------------------- +KMessageWidget::KMessageWidget(QWidget *parent) : QFrame(parent), d(new KMessageWidgetPrivate) +{ + d->init(this); +} + +KMessageWidget::KMessageWidget(const QString &text, QWidget *parent) : QFrame(parent), d(new KMessageWidgetPrivate) +{ + d->init(this); + setText(text); +} + +KMessageWidget::~KMessageWidget() +{ + delete d; +} + +QString KMessageWidget::text() const +{ + return d->textLabel->text(); +} + +void KMessageWidget::setText(const QString &text) +{ + d->textLabel->setText(text); + updateGeometry(); +} + +int KMessageWidget::bestContentHeight() const +{ + return d->bestContentHeight(); +} + + +KMessageWidget::MessageType KMessageWidget::messageType() const +{ + return d->messageType; +} + +void KMessageWidget::setMessageType(KMessageWidget::MessageType type) +{ + d->messageType = type; + QColor bg0, bg1, bg2, border, fg; + + switch (type) { + case Positive: + bg1 = QColor("#72D594"); // nice green + fg = QColor(Qt::white); + break; + + case Information: + bg1 = QColor("#41A8E3"); // nice blue + fg = QColor(Qt::black); + break; + + case Warning: + bg1 = QColor("#FFCD0F"); // nice yellow + fg = QColor(Qt::black); + break; + + case Error: + bg1 = QColor("#FF7D7D"); // nice red. + fg = QColor(Qt::black); + break; + } + + // Colors + bg0 = bg1.lighter(110); + bg2 = bg1.darker(110); + border = bg2.darker(110); + d->content->setStyleSheet( + QString(".QFrame {" + "background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1," + " stop: 0 %1," + " stop: 0.1 %2," + " stop: 1.0 %3);" + "border-radius: 5px;" + "border: 1px solid %4;" + "margin: %5px;" + "}" + ".QLabel { color: %6; }").arg(bg0.name()) + .arg(bg1.name()) + .arg(bg2.name()) + .arg(border.name()) + /* + DefaultFrameWidth returns the size of the external margin + border width. + We know our border is 1px, so we subtract this from the frame + normal QStyle FrameWidth to get our margin + */ + .arg(style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this) - 1) + .arg(fg.name())); +} + +QSize KMessageWidget::sizeHint() const +{ + ensurePolished(); + return d->content->sizeHint(); +} + +QSize KMessageWidget::minimumSizeHint() const +{ + ensurePolished(); + return d->content->minimumSizeHint(); +} + +bool KMessageWidget::event(QEvent *event) +{ + if (event->type() == QEvent::Polish && !d->content->layout()) { + d->createLayout(); + } + + return QFrame::event(event); +} + +void KMessageWidget::resizeEvent(QResizeEvent *event) +{ + QFrame::resizeEvent(event); + + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->content->resize(width(), d->bestContentHeight()); + } +} + +int KMessageWidget::heightForWidth(int width) const +{ + ensurePolished(); + return d->content->heightForWidth(width); +} + +void KMessageWidget::paintEvent(QPaintEvent *event) +{ + QFrame::paintEvent(event); + + if (d->timeLine->state() == QTimeLine::Running) { + QPainter painter(this); + painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue()); + painter.drawPixmap(0, 0, d->contentSnapShot); + } +} + +void KMessageWidget::showEvent(QShowEvent *event) +{ + // Keep this method here to avoid breaking binary compatibility: + // QFrame::showEvent() used to be reimplemented. + QFrame::showEvent(event); +} + +bool KMessageWidget::wordWrap() const +{ + return d->wordWrap; +} + +void KMessageWidget::setWordWrap(bool wordWrap) +{ + d->wordWrap = wordWrap; + d->textLabel->setWordWrap(wordWrap); + QSizePolicy policy = sizePolicy(); + policy.setHeightForWidth(wordWrap); + setSizePolicy(policy); + d->updateLayout(); + + // Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum + // height is set, causing the widget to be too high. + // Mostly visible in test programs. + if (wordWrap) { + setMinimumHeight(0); + } +} + +bool KMessageWidget::isCloseButtonVisible() const +{ + return d->closeButton->isVisible(); +} + +void KMessageWidget::setCloseButtonVisible(bool show) +{ + d->closeButton->setVisible(show); + updateGeometry(); +} + +void KMessageWidget::addAction(QAction *action) +{ + QFrame::addAction(action); + d->updateLayout(); +} + +void KMessageWidget::removeAction(QAction *action) +{ + QFrame::removeAction(action); + d->updateLayout(); +} + +void KMessageWidget::animatedShow() +{ + if (isVisible()) { + return; + } + + QFrame::show(); + setFixedHeight(0); + int wantedHeight = d->bestContentHeight(); + d->content->setGeometry(0, -wantedHeight, width(), wantedHeight); + + d->updateSnapShot(); + + d->timeLine->setDirection(QTimeLine::Forward); + + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->timeLine->start(); + } +} + +void KMessageWidget::animatedHide() +{ + if (!isVisible()) { + hide(); + return; + } + + d->content->move(0, -d->content->height()); + d->updateSnapShot(); + + d->timeLine->setDirection(QTimeLine::Backward); + + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->timeLine->start(); + } +} + +QIcon KMessageWidget::icon() const +{ + return d->icon; +} + +void KMessageWidget::setIcon(const QIcon &icon) +{ + d->icon = icon; + + if (d->icon.isNull()) { + d->iconLabel->hide(); + } else { + d->iconLabel->setPixmap(d->icon.pixmap(QSize(16, 16))); + d->iconLabel->show(); + } +} diff --git a/src/gui/kmessagewidget.h b/src/gui/kmessagewidget.h new file mode 100644 index 000000000..dbc694afa --- /dev/null +++ b/src/gui/kmessagewidget.h @@ -0,0 +1,239 @@ +/* This file is part of the KDE libraries + * + * Copyright (c) 2011 Aurélien Gâteau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef KMESSAGEWIDGET_H +#define KMESSAGEWIDGET_H + +#include + +class KMessageWidgetPrivate; + +/** + * @short A widget to provide feedback or propose opportunistic interactions. + * + * KMessageWidget can be used to provide inline positive or negative + * feedback, or to implement opportunistic interactions. + * + * As a feedback widget, KMessageWidget provides a less intrusive alternative + * to "OK Only" message boxes. If you do not need the modalness of KMessageBox, + * consider using KMessageWidget instead. + * + * Negative feedback + * + * The KMessageWidget can be used as a secondary indicator of failure: the + * first indicator is usually the fact the action the user expected to happen + * did not happen. + * + * Example: User fills a form, clicks "Submit". + * + * @li Expected feedback: form closes + * @li First indicator of failure: form stays there + * @li Second indicator of failure: a KMessageWidget appears on top of the + * form, explaining the error condition + * + * When used to provide negative feedback, KMessageWidget should be placed + * close to its context. In the case of a form, it should appear on top of the + * form entries. + * + * KMessageWidget should get inserted in the existing layout. Space should not + * be reserved for it, otherwise it becomes "dead space", ignored by the user. + * KMessageWidget should also not appear as an overlay to prevent blocking + * access to elements the user needs to interact with to fix the failure. + * + * Positive feedback + * + * KMessageWidget can be used for positive feedback but it shouldn't be + * overused. It is often enough to provide feedback by simply showing the + * results of an action. + * + * Examples of acceptable uses: + * + * @li Confirm success of "critical" transactions + * @li Indicate completion of background tasks + * + * Example of inadapted uses: + * + * @li Indicate successful saving of a file + * @li Indicate a file has been successfully removed + * + * Opportunistic interaction + * + * Opportunistic interaction is the situation where the application suggests to + * the user an action he could be interested in perform, either based on an + * action the user just triggered or an event which the application noticed. + * + * Example of acceptable uses: + * + * @li A browser can propose remembering a recently entered password + * @li A music collection can propose ripping a CD which just got inserted + * @li A chat application may notify the user a "special friend" just connected + * + * @author Aurélien Gâteau + * @since 4.7 + */ +class KMessageWidget : public QFrame { + Q_OBJECT + Q_ENUMS(MessageType) + + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(bool closeButtonVisible READ isCloseButtonVisible WRITE setCloseButtonVisible) + Q_PROPERTY(MessageType messageType READ messageType WRITE setMessageType) + Q_PROPERTY(QIcon icon READ icon WRITE setIcon) +public: + enum MessageType { + Positive, + Information, + Warning, + Error + }; + + /** + * Constructs a KMessageWidget with the specified parent. + */ + explicit KMessageWidget(QWidget *parent = 0); + + explicit KMessageWidget(const QString &text, QWidget *parent = 0); + + ~KMessageWidget(); + + QString text() const; + + bool wordWrap() const; + + bool isCloseButtonVisible() const; + + MessageType messageType() const; + + void addAction(QAction *action); + + void removeAction(QAction *action); + + QSize sizeHint() const; + + QSize minimumSizeHint() const; + + int heightForWidth(int width) const; + + /** + * The icon shown on the left of the text. By default, no icon is shown. + * @since 4.11 + */ + QIcon icon() const; + +public +Q_SLOTS: + void setText(const QString &text); + + void setWordWrap(bool wordWrap); + + void setCloseButtonVisible(bool visible); + + void setMessageType(KMessageWidget::MessageType type); + + /** + * Show the widget using an animation, unless + * KGlobalSettings::graphicsEffectLevel() does not allow simple effects. + */ + void animatedShow(); + + /** + * Hide the widget using an animation, unless + * KGlobalSettings::graphicsEffectLevel() does not allow simple effects. + */ + void animatedHide(); + + /** + * Define an icon to be shown on the left of the text + * @since 4.11 + */ + void setIcon(const QIcon &icon); + + int bestContentHeight() const; +Q_SIGNALS: + /** + * This signal is emitted when the user clicks a link in the text label. + * The URL referred to by the href anchor is passed in contents. + * @param contents text of the href anchor + * @see QLabel::linkActivated() + * @since 4.10 + */ + void linkActivated(const QString &contents); + + /** + * This signal is emitted when the user hovers over a link in the text label. + * The URL referred to by the href anchor is passed in contents. + * @param contents text of the href anchor + * @see QLabel::linkHovered() + * @since 4.11 + */ + void linkHovered(const QString &contents); + +protected: + void paintEvent(QPaintEvent *event); + + bool event(QEvent *event); + + void resizeEvent(QResizeEvent *event); + + void showEvent(QShowEvent *event); + +private: + KMessageWidgetPrivate *const d; + friend class KMessageWidgetPrivate; + + Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal)) + Q_PRIVATE_SLOT(d, void slotTimeLineFinished()) +}; + +//--------------------------------------------------------------------- +// KMessageWidgetPrivate +//--------------------------------------------------------------------- +class QLabel; +class QToolButton; +class QTimeLine; +#include + +class KMessageWidgetPrivate { +public: + void init(KMessageWidget *); + + KMessageWidget *q; + QFrame *content; + QLabel *iconLabel; + QLabel *textLabel; + QToolButton *closeButton; + QTimeLine *timeLine; + QIcon icon; + + KMessageWidget::MessageType messageType; + bool wordWrap; + QList buttons; + QPixmap contentSnapShot; + + void createLayout(); + void updateSnapShot(); + void updateLayout(); + void slotTimeLineChanged(qreal); + void slotTimeLineFinished(); + + int bestContentHeight() const; +}; + +#endif // KMESSAGEWIDGET_H From a710181388f7525b703c88aaa69fcd4c6d1c0d37 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Mon, 19 Jan 2015 23:14:59 +0000 Subject: [PATCH 032/333] Replaced MessageBox dialogs with inline MessageWidget in DatabaseOpenWidget. --- src/gui/DatabaseOpenWidget.cpp | 15 ++++++++++----- src/gui/DatabaseOpenWidget.ui | 11 ++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 781b836fd..fcc029ab6 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -35,6 +35,8 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) { m_ui->setupUi(this); + m_ui->messageWidget->setHidden(true); + QFont font = m_ui->labelHeadline->font(); font.setBold(true); font.setPointSize(font.pointSize() + 2); @@ -106,8 +108,8 @@ void DatabaseOpenWidget::openDatabase() QFile file(m_filename); if (!file.open(QIODevice::ReadOnly)) { - MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n") - .append(file.errorString())); + m_ui->messageWidget->showMessageError( + tr("Unable to open the database.").append("\n").append(file.errorString())); return; } if (m_db) { @@ -118,11 +120,14 @@ void DatabaseOpenWidget::openDatabase() QApplication::restoreOverrideCursor(); if (m_db) { + if (m_ui->messageWidget->isVisible()) { + m_ui->messageWidget->animatedHide(); + } Q_EMIT editFinished(true); } else { - MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n") - .append(reader.errorString())); + m_ui->messageWidget->showMessageError(tr("Unable to open the database.") + .append("\n").append(reader.errorString())); m_ui->editPassword->clear(); } } @@ -142,7 +147,7 @@ CompositeKey DatabaseOpenWidget::databaseKey() QString keyFilename = m_ui->comboKeyFile->currentText(); QString errorMsg; if (!key.load(keyFilename, &errorMsg)) { - MessageBox::warning(this, tr("Error"), tr("Can't open key file").append(":\n").append(errorMsg)); + m_ui->messageWidget->showMessageError(tr("Can't open key file").append(":\n").append(errorMsg)); return CompositeKey(); } masterKey.addKey(key); diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui index 4aae5faf2..f658d568a 100644 --- a/src/gui/DatabaseOpenWidget.ui +++ b/src/gui/DatabaseOpenWidget.ui @@ -10,10 +10,13 @@ 250 - + 8 + + + @@ -144,6 +147,12 @@ QLineEdit
gui/PasswordEdit.h
+ + MessageWidget + QWidget +
gui/MessageWidget.h
+ 1 +
checkPassword From 3c4af1a1945e079e205f50164f97198930bee52f Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Mon, 19 Jan 2015 23:27:57 +0000 Subject: [PATCH 033/333] Replace MessageBox dialogs that don't require user interaction with inline MessageWidget in ChangeMasterKeyWidget. --- src/gui/ChangeMasterKeyWidget.cpp | 6 ++++-- src/gui/ChangeMasterKeyWidget.ui | 11 ++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 3e346bc10..185ffc26f 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -30,6 +30,8 @@ ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) { m_ui->setupUi(this); + m_ui->messageWidget->setHidden(true); + connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(generateKey())); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); @@ -52,7 +54,7 @@ void ChangeMasterKeyWidget::createKeyFile() QString errorMsg; bool created = FileKey::create(fileName, &errorMsg); if (!created) { - MessageBox::warning(this, tr("Error"), tr("Unable to create Key File : ") + errorMsg); + m_ui->messageWidget->showMessageError(tr("Unable to create Key File : ").append(errorMsg)); } else { m_ui->keyFileCombo->setEditText(fileName); @@ -110,7 +112,7 @@ void ChangeMasterKeyWidget::generateKey() m_key.addKey(PasswordKey(m_ui->enterPasswordEdit->text())); } else { - MessageBox::warning(this, tr("Error"), tr("Different passwords supplied.")); + m_ui->messageWidget->showMessageError(tr("Different passwords supplied.")); m_ui->enterPasswordEdit->setText(""); m_ui->repeatPasswordEdit->setText(""); return; diff --git a/src/gui/ChangeMasterKeyWidget.ui b/src/gui/ChangeMasterKeyWidget.ui index d14941ccc..4af497a53 100644 --- a/src/gui/ChangeMasterKeyWidget.ui +++ b/src/gui/ChangeMasterKeyWidget.ui @@ -7,10 +7,13 @@ 0 0 438 - 256 + 342
+ + + @@ -151,6 +154,12 @@ QLineEdit
gui/PasswordEdit.h
+ + MessageWidget + QWidget +
gui/MessageWidget.h
+ 1 +
passwordGroup From 8fa070f01cb707550393115cb2399f9ab6540de7 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Mon, 19 Jan 2015 23:47:57 +0000 Subject: [PATCH 034/333] Show inline message when unable to load the Key File. --- src/gui/ChangeMasterKeyWidget.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 185ffc26f..ba241875a 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -121,10 +121,10 @@ void ChangeMasterKeyWidget::generateKey() if (m_ui->keyFileGroup->isChecked()) { FileKey fileKey; QString errorMsg; - if (!fileKey.load(m_ui->keyFileCombo->currentText(), &errorMsg)) { - MessageBox::critical(this, tr("Failed to set key file"), - tr("Failed to set %1 as the Key file:\n%2") - .arg(m_ui->keyFileCombo->currentText(), errorMsg)); + QString fileKeyName = m_ui->keyFileCombo->currentText(); + if (!fileKey.load(fileKeyName, &errorMsg)) { + m_ui->messageWidget->showMessageError( + tr("Failed to set %1 as the Key file:\n%2: ").arg(fileKeyName, errorMsg)); return; } m_key.addKey(fileKey); From c2826bb1afdffe81e5234d28819a6eb9068d0b7a Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Tue, 20 Jan 2015 01:27:56 +0000 Subject: [PATCH 035/333] Replace MessageBox Dialog with inline MessageWidget in EditWidget and in UnlockDatabaseWidget. Add missing method to show Information Message. --- src/gui/EditWidget.cpp | 24 ++++++++++++++++++++++++ src/gui/EditWidget.h | 6 ++++++ src/gui/EditWidget.ui | 9 +++++++++ src/gui/MessageWidget.cpp | 5 +++++ src/gui/entry/EditEntryWidget.cpp | 23 ++++++++++------------- 5 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index b3d9842be..c2f9551c4 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -25,6 +25,8 @@ EditWidget::EditWidget(QWidget* parent) m_ui->setupUi(this); setReadOnly(false); + m_ui->messageWidget->setHidden(true); + QFont headerLabelFont = m_ui->headerLabel->font(); headerLabelFont.setBold(true); headerLabelFont.setPointSize(headerLabelFont.pointSize() + 2); @@ -86,3 +88,25 @@ bool EditWidget::readOnly() const { return m_readOnly; } + +void EditWidget::showMessageError(const QString& text) +{ + m_ui->messageWidget->showMessageError(text); +} + +void EditWidget::showMessageWarning(const QString& text) +{ + m_ui->messageWidget->showMessageWarning(text); +} + +void EditWidget::showMessageInformation(const QString& text) +{ + m_ui->messageWidget->showMessageInformation(text); +} + +void EditWidget::hideMessage() +{ + if (m_ui->messageWidget->isVisible()) { + m_ui->messageWidget->animatedHide(); + } +} diff --git a/src/gui/EditWidget.h b/src/gui/EditWidget.h index c5f507ac9..97dfda880 100644 --- a/src/gui/EditWidget.h +++ b/src/gui/EditWidget.h @@ -48,6 +48,12 @@ Q_SIGNALS: void accepted(); void rejected(); +protected: + void showMessageError(const QString& text); + void showMessageWarning(const QString& text); + void showMessageInformation(const QString& text); + void hideMessage(); + private: const QScopedPointer m_ui; bool m_readOnly; diff --git a/src/gui/EditWidget.ui b/src/gui/EditWidget.ui index 3891007a1..05b06b909 100644 --- a/src/gui/EditWidget.ui +++ b/src/gui/EditWidget.ui @@ -11,6 +11,9 @@ + + + @@ -58,6 +61,12 @@ + + MessageWidget + QWidget +
gui/MessageWidget.h
+ 1 +
CategoryListWidget QListWidget diff --git a/src/gui/MessageWidget.cpp b/src/gui/MessageWidget.cpp index 1e6d06044..19badfea8 100644 --- a/src/gui/MessageWidget.cpp +++ b/src/gui/MessageWidget.cpp @@ -33,6 +33,11 @@ void MessageWidget::showMessageWarning(const QString& text) showMessage(text, MessageType::Warning); } +void MessageWidget::showMessageInformation(const QString& text) +{ + showMessage(text, MessageType::Information); +} + void MessageWidget::showMessagePositive(const QString& text) { showMessage(text, MessageType::Positive); diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index f2372a0d5..c56f70232 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -395,12 +395,13 @@ void EditEntryWidget::saveEntry() { if (m_history) { clear(); + hideMessage(); Q_EMIT editFinished(false); return; } if (!passwordsEqual()) { - MessageBox::warning(this, tr("Error"), tr("Different passwords supplied.")); + showMessageError(tr("Different passwords supplied.")); return; } @@ -474,6 +475,7 @@ void EditEntryWidget::cancel() { if (m_history) { clear(); + hideMessage(); Q_EMIT editFinished(false); return; } @@ -497,6 +499,7 @@ void EditEntryWidget::clear() m_autoTypeAssoc->clear(); m_historyModel->clear(); m_iconsWidget->reset(); + hideMessage(); } bool EditEntryWidget::hasBeenModified() const @@ -630,15 +633,13 @@ void EditEntryWidget::insertAttachment() QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { - MessageBox::warning(this, tr("Error"), - tr("Unable to open file").append(":\n").append(file.errorString())); + showMessageError(tr("Unable to open file").append(":\n").append(file.errorString())); return; } QByteArray data; if (!Tools::readAllFromDevice(&file, data)) { - MessageBox::warning(this, tr("Error"), - tr("Unable to open file").append(":\n").append(file.errorString())); + showMessageError(tr("Unable to open file").append(":\n").append(file.errorString())); return; } @@ -665,13 +666,11 @@ void EditEntryWidget::saveCurrentAttachment() QFile file(savePath); if (!file.open(QIODevice::WriteOnly)) { - MessageBox::warning(this, tr("Error"), - tr("Unable to save the attachment:\n").append(file.errorString())); + showMessageError(tr("Unable to save the attachment:\n").append(file.errorString())); return; } if (file.write(attachmentData) != attachmentData.size()) { - MessageBox::warning(this, tr("Error"), - tr("Unable to save the attachment:\n").append(file.errorString())); + showMessageError(tr("Unable to save the attachment:\n").append(file.errorString())); return; } } @@ -692,14 +691,12 @@ void EditEntryWidget::openAttachment(const QModelIndex& index) QTemporaryFile* file = new QTemporaryFile(tmpFileTemplate, this); if (!file->open()) { - MessageBox::warning(this, tr("Error"), - tr("Unable to save the attachment:\n").append(file->errorString())); + showMessageError(tr("Unable to save the attachment:\n").append(file->errorString())); return; } if (file->write(attachmentData) != attachmentData.size()) { - MessageBox::warning(this, tr("Error"), - tr("Unable to save the attachment:\n").append(file->errorString())); + showMessageError(tr("Unable to save the attachment:\n").append(file->errorString())); return; } From 13c85cdfcb507380b9e399229114cf6a3b9e8007 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Wed, 21 Jan 2015 16:19:05 +0000 Subject: [PATCH 036/333] Replace MessageBox with MessageWidget in remaining classes. Chnage to one method to set MessageWidget text passing type as parameter. Only messages with questions requiring user input reamin using MessageBox dialog. Use signal/slots to set message in MessageWidget and hide message, signal/slots only used when required.Maybe need to change all calls to signals/slots in the future. --- src/gui/ChangeMasterKeyWidget.cpp | 9 +++++---- src/gui/DatabaseOpenWidget.cpp | 11 +++++----- src/gui/DatabaseTabWidget.cpp | 22 +++++++++++++------- src/gui/DatabaseTabWidget.h | 6 ++++++ src/gui/DatabaseWidget.cpp | 23 +++++++++++++++++++-- src/gui/DatabaseWidget.h | 9 +++++++++ src/gui/DatabaseWidgetStateSync.cpp | 1 + src/gui/EditWidget.cpp | 14 ++----------- src/gui/EditWidget.h | 7 +++---- src/gui/EditWidgetIcons.cpp | 7 +++---- src/gui/EditWidgetIcons.h | 5 +++++ src/gui/KeePass1OpenWidget.cpp | 5 +++-- src/gui/MainWindow.cpp | 31 +++++++++++++++++++++++++++++ src/gui/MainWindow.h | 4 ++++ src/gui/MainWindow.ui | 30 +++++++++++++++++++++++++++- src/gui/MessageWidget.cpp | 28 +++++--------------------- src/gui/MessageWidget.h | 12 +++++------ src/gui/entry/EditEntryWidget.cpp | 17 +++++++++------- src/gui/group/EditGroupWidget.cpp | 3 +++ src/gui/group/EditGroupWidget.h | 2 ++ 20 files changed, 169 insertions(+), 77 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index ba241875a..37fe9b0d4 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -54,7 +54,7 @@ void ChangeMasterKeyWidget::createKeyFile() QString errorMsg; bool created = FileKey::create(fileName, &errorMsg); if (!created) { - m_ui->messageWidget->showMessageError(tr("Unable to create Key File : ").append(errorMsg)); + m_ui->messageWidget->showMessage(tr("Unable to create Key File : ").append(errorMsg), MessageWidget::Error); } else { m_ui->keyFileCombo->setEditText(fileName); @@ -112,7 +112,7 @@ void ChangeMasterKeyWidget::generateKey() m_key.addKey(PasswordKey(m_ui->enterPasswordEdit->text())); } else { - m_ui->messageWidget->showMessageError(tr("Different passwords supplied.")); + m_ui->messageWidget->showMessage(tr("Different passwords supplied."), MessageWidget::Error); m_ui->enterPasswordEdit->setText(""); m_ui->repeatPasswordEdit->setText(""); return; @@ -123,13 +123,14 @@ void ChangeMasterKeyWidget::generateKey() QString errorMsg; QString fileKeyName = m_ui->keyFileCombo->currentText(); if (!fileKey.load(fileKeyName, &errorMsg)) { - m_ui->messageWidget->showMessageError( - tr("Failed to set %1 as the Key file:\n%2: ").arg(fileKeyName, errorMsg)); + m_ui->messageWidget->showMessage( + tr("Failed to set %1 as the Key file:\n%2").arg(fileKeyName, errorMsg), MessageWidget::Error); return; } m_key.addKey(fileKey); } + m_ui->messageWidget->hideMessage(); Q_EMIT editFinished(true); } diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index fcc029ab6..c9d266fae 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -108,8 +108,8 @@ void DatabaseOpenWidget::openDatabase() QFile file(m_filename); if (!file.open(QIODevice::ReadOnly)) { - m_ui->messageWidget->showMessageError( - tr("Unable to open the database.").append("\n").append(file.errorString())); + m_ui->messageWidget->showMessage( + tr("Unable to open the database.").append("\n").append(file.errorString()), MessageWidget::Error); return; } if (m_db) { @@ -126,8 +126,8 @@ void DatabaseOpenWidget::openDatabase() Q_EMIT editFinished(true); } else { - m_ui->messageWidget->showMessageError(tr("Unable to open the database.") - .append("\n").append(reader.errorString())); + m_ui->messageWidget->showMessage(tr("Unable to open the database.") + .append("\n").append(reader.errorString()), MessageWidget::Error); m_ui->editPassword->clear(); } } @@ -147,7 +147,8 @@ CompositeKey DatabaseOpenWidget::databaseKey() QString keyFilename = m_ui->comboKeyFile->currentText(); QString errorMsg; if (!key.load(keyFilename, &errorMsg)) { - m_ui->messageWidget->showMessageError(tr("Can't open key file").append(":\n").append(errorMsg)); + m_ui->messageWidget->showMessage(tr("Can't open key file").append(":\n") + .append(errorMsg), MessageWidget::Error); return CompositeKey(); } masterKey.addKey(key); diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 0c784eb2f..b1eb9a0a5 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -116,7 +116,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, QFileInfo fileInfo(fileName); QString canonicalFilePath = fileInfo.canonicalFilePath(); if (canonicalFilePath.isEmpty()) { - MessageBox::warning(this, tr("Warning"), tr("File not found!")); + Q_EMIT messageGlobal(tr("File not found!"), MessageWidget::Error); return; } @@ -136,8 +136,9 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, QFile file(fileName); if (!file.open(QIODevice::ReadWrite)) { if (!file.open(QIODevice::ReadOnly)) { - MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n") - .append(file.errorString())); + // can't open + Q_EMIT messageGlobal( + tr("Unable to open the database.").append("\n").append(file.errorString()), MessageWidget::Error); return; } else { @@ -184,6 +185,10 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, insertDatabase(db, dbStruct); + if (dbStruct.readOnly) { + Q_EMIT messageTab(tr("File opened in read only mode."), MessageWidget::Warning); + } + updateLastDatabases(dbStruct.filePath); if (!pw.isNull() || !keyFile.isEmpty()) { @@ -192,6 +197,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, else { dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath); } + Q_EMIT messageDismissGlobal(); } void DatabaseTabWidget::mergeDatabase() @@ -332,6 +338,7 @@ bool DatabaseTabWidget::saveDatabase(Database* db) dbStruct.modified = false; dbStruct.dbWidget->databaseSaved(); updateTabName(db); + Q_EMIT messageDismissTab(); return true; } else { @@ -341,8 +348,8 @@ bool DatabaseTabWidget::saveDatabase(Database* db) } } else { - MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n" - + saveFile.errorString()); + Q_EMIT messageTab(tr("Writing the database failed.\n") + .append("\n").append(saveFile.errorString()), MessageWidget::Error); return false; } } @@ -486,8 +493,9 @@ void DatabaseTabWidget::exportToCsv() CsvExporter csvExporter; if (!csvExporter.exportDatabase(fileName, db)) { - MessageBox::critical(this, tr("Error"), tr("Writing the CSV file failed.") + "\n\n" - + csvExporter.errorString()); + Q_EMIT messageGlobal( + tr("Writing the CSV file failed.").append("\n\n") + .append(csvExporter.errorString()), MessageWidget::Error); } } diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 24bdbde2f..8f01a987d 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -23,12 +23,14 @@ #include "format/KeePass2Writer.h" #include "gui/DatabaseWidget.h" +#include "gui/MessageWidget.h" class DatabaseWidget; class DatabaseWidgetStateSync; class DatabaseOpenWidget; class QFile; class QLockFile; +class MessageWidget; struct DatabaseManagerStruct { @@ -84,6 +86,10 @@ Q_SIGNALS: void activateDatabaseChanged(DatabaseWidget* dbWidget); void databaseLocked(DatabaseWidget* dbWidget); void databaseUnlocked(DatabaseWidget* dbWidget); + void messageGlobal(const QString&, MessageWidget::MessageType type); + void messageTab(const QString&, MessageWidget::MessageType type); + void messageDismissGlobal(); + void messageDismissTab(); private Q_SLOTS: void updateTabName(Database* db); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 4a1298deb..74fc2d7cc 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -60,7 +60,14 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) , m_newParent(nullptr) { m_mainWidget = new QWidget(this); - QLayout* layout = new QHBoxLayout(m_mainWidget); + + m_messageWidget = new MessageWidget(this); + m_messageWidget->setHidden(true); + + QVBoxLayout* mainLayout = new QVBoxLayout(); + QLayout* layout = new QHBoxLayout(); + mainLayout->addWidget(m_messageWidget); + mainLayout->addLayout(layout); m_splitter = new QSplitter(m_mainWidget); m_splitter->setChildrenCollapsible(false); @@ -105,7 +112,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) m_splitter->setStretchFactor(1, 70); layout->addWidget(m_splitter); - m_mainWidget->setLayout(layout); + m_mainWidget->setLayout(mainLayout); m_editEntryWidget = new EditEntryWidget(); m_editEntryWidget->setObjectName("editEntryWidget"); @@ -1220,3 +1227,15 @@ void DatabaseWidget::closeUnlockDialog() { m_unlockDatabaseDialog->close(); } + +void DatabaseWidget::showMessage(const QString& text, MessageWidget::MessageType type) +{ + m_messageWidget->showMessage(text, type); +} + +void DatabaseWidget::hideMessage() +{ + if (m_messageWidget->isVisible()) { + m_messageWidget->animatedHide(); + } +} diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 79e58cecf..4133e52b4 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -26,6 +26,7 @@ #include "core/Uuid.h" #include "gui/entry/EntryModel.h" +#include "gui/MessageWidget.h" class ChangeMasterKeyWidget; class DatabaseOpenWidget; @@ -43,9 +44,14 @@ class QMenu; class QSplitter; class QLabel; class UnlockDatabaseWidget; +class MessageWidget; class UnlockDatabaseDialog; class QFileSystemWatcher; +namespace Ui { + class SearchWidget; +} + class DatabaseWidget : public QStackedWidget { Q_OBJECT @@ -145,6 +151,8 @@ public Q_SLOTS: void search(const QString& searchtext); void setSearchCaseSensitive(bool state); void endSearch(); + void showMessage(const QString& text, MessageWidget::MessageType type); + void hideMessage(); private Q_SLOTS: void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column); @@ -192,6 +200,7 @@ private: QString m_filename; Uuid m_groupBeforeLock; Uuid m_entryBeforeLock; + MessageWidget* m_messageWidget; // Search state QString m_lastSearchText; diff --git a/src/gui/DatabaseWidgetStateSync.cpp b/src/gui/DatabaseWidgetStateSync.cpp index fd5719f5a..1510d8440 100644 --- a/src/gui/DatabaseWidgetStateSync.cpp +++ b/src/gui/DatabaseWidgetStateSync.cpp @@ -149,3 +149,4 @@ QVariant DatabaseWidgetStateSync::intListToVariant(const QList& list) return result; } + diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index c2f9551c4..ef29f0132 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -89,19 +89,9 @@ bool EditWidget::readOnly() const return m_readOnly; } -void EditWidget::showMessageError(const QString& text) +void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type) { - m_ui->messageWidget->showMessageError(text); -} - -void EditWidget::showMessageWarning(const QString& text) -{ - m_ui->messageWidget->showMessageWarning(text); -} - -void EditWidget::showMessageInformation(const QString& text) -{ - m_ui->messageWidget->showMessageInformation(text); + m_ui->messageWidget->showMessage(text, type); } void EditWidget::hideMessage() diff --git a/src/gui/EditWidget.h b/src/gui/EditWidget.h index 97dfda880..6f2c8f2dc 100644 --- a/src/gui/EditWidget.h +++ b/src/gui/EditWidget.h @@ -21,6 +21,7 @@ #include #include "gui/DialogyWidget.h" +#include "gui/MessageWidget.h" class QLabel; @@ -48,10 +49,8 @@ Q_SIGNALS: void accepted(); void rejected(); -protected: - void showMessageError(const QString& text); - void showMessageWarning(const QString& text); - void showMessageInformation(const QString& text); +protected Q_SLOTS: + void showMessage(const QString& text, MessageWidget::MessageType type); void hideMessage(); private: diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 145957ab9..fcfff5ca3 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -248,7 +248,7 @@ void EditWidgetIcons::addCustomIcon() m_ui->customIconsView->setCurrentIndex(index); } else { - MessageBox::critical(this, tr("Error"), tr("Can't read icon")); + Q_EMIT messageEditEntry(tr("Can't read icon"), MessageWidget::Error); } } } @@ -302,9 +302,8 @@ void EditWidgetIcons::removeCustomIcon() } } else { - MessageBox::information(this, tr("Can't delete icon!"), - tr("Can't delete icon. Still used by %1 items.") - .arg(iconUsedCount)); + Q_EMIT messageEditEntry( + tr("Can't delete icon. Still used by %1 items.").arg(iconUsedCount), MessageWidget::Error); } } } diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index 508c56864..ae5f86951 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -26,6 +26,7 @@ #include "core/Global.h" #include "core/Uuid.h" +#include "gui/MessageWidget.h" class Database; class DefaultIconModel; @@ -58,6 +59,10 @@ public: public Q_SLOTS: void setUrl(const QString &url); +Q_SIGNALS: + void messageEditEntry(QString, MessageWidget::MessageType); + void messageEditEntryDismiss(); + private Q_SLOTS: void downloadFavicon(); void fetchFavicon(QUrl url); diff --git a/src/gui/KeePass1OpenWidget.cpp b/src/gui/KeePass1OpenWidget.cpp index 4f70a9787..c5159aaba 100644 --- a/src/gui/KeePass1OpenWidget.cpp +++ b/src/gui/KeePass1OpenWidget.cpp @@ -65,8 +65,9 @@ void KeePass1OpenWidget::openDatabase() Q_EMIT editFinished(true); } else { - MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n") - .append(reader.errorString())); + m_ui->messageWidget->showMessage(tr("Unable to open the database.").append("\n") + .append(reader.errorString()), MessageWidget::Error); + m_ui->editPassword->clear(); } } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 819bda5dc..92a9cbdd4 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -104,6 +104,7 @@ MainWindow::MainWindow() #endif setWindowIcon(filePath()->applicationIcon()); + m_ui->globalMessageWidget->setHidden(true); QAction* toggleViewAction = m_ui->toolBar->toggleViewAction(); toggleViewAction->setText(tr("Show toolbar")); m_ui->menuView->addAction(toggleViewAction); @@ -277,6 +278,11 @@ MainWindow::MainWindow() connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog())); + connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)), this, SLOT(displayGlobalMessage(QString, MessageWidget::MessageType))); + connect(m_ui->tabWidget, SIGNAL(messageDismissGlobal()), this, SLOT(hideGlobalMessage())); + connect(m_ui->tabWidget, SIGNAL(messageTab(QString,MessageWidget::MessageType)), this, SLOT(displayTabMessage(QString, MessageWidget::MessageType))); + connect(m_ui->tabWidget, SIGNAL(messageDismissTab()), this, SLOT(hideTabMessage())); + updateTrayIcon(); } @@ -787,3 +793,28 @@ bool MainWindow::isTrayIconEnabled() const && QSystemTrayIcon::isSystemTrayAvailable(); #endif } + +void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type) +{ + m_ui->globalMessageWidget->showMessage(text, type); +} + +void MainWindow::displayTabMessage(const QString& text, MessageWidget::MessageType type) +{ + //if (m_ui->stackedWidget->currentIndex() == 0) { + m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type); + //} +} + +void MainWindow::hideGlobalMessage() +{ + m_ui->globalMessageWidget->hideMessage(); +} + +void MainWindow::hideTabMessage() +{ + if (m_ui->stackedWidget->currentIndex() == 0) { + m_ui->tabWidget->currentDatabaseWidget()->hideMessage(); + } +} + diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index ab9924a75..4182140c7 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -71,6 +71,10 @@ private Q_SLOTS: void toggleWindow(); void lockDatabasesAfterInactivity(); void repairDatabase(); + void displayGlobalMessage(const QString& text, MessageWidget::MessageType type); + void displayTabMessage(const QString& text, MessageWidget::MessageType type); + void hideGlobalMessage(); + void hideTabMessage(); private: static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 188ef1586..bb7ff779c 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -2,6 +2,9 @@ MainWindow + + true + 0 @@ -14,6 +17,9 @@ KeePassXC + + true + 0 @@ -27,8 +33,24 @@ 0 + + + + + 0 + 0 + + + + + + + 0 + 0 + + 2 @@ -104,7 +126,7 @@ 0 0 800 - 26 + 29 @@ -452,6 +474,12 @@ + + MessageWidget + QWidget +
gui/MessageWidget.h
+ 1 +
DatabaseTabWidget QTabWidget diff --git a/src/gui/MessageWidget.cpp b/src/gui/MessageWidget.cpp index 19badfea8..15e6b2562 100644 --- a/src/gui/MessageWidget.cpp +++ b/src/gui/MessageWidget.cpp @@ -23,32 +23,14 @@ MessageWidget::MessageWidget(QWidget* parent) } -void MessageWidget::showMessageError(const QString& text) -{ - showMessage(text, MessageType::Error); -} - -void MessageWidget::showMessageWarning(const QString& text) -{ - showMessage(text, MessageType::Warning); -} - -void MessageWidget::showMessageInformation(const QString& text) -{ - showMessage(text, MessageType::Information); -} - -void MessageWidget::showMessagePositive(const QString& text) -{ - showMessage(text, MessageType::Positive); -} - -void MessageWidget::showMessage(const QString& text, MessageType type) +void MessageWidget::showMessage(const QString& text, MessageWidget::MessageType type) { setMessageType(type); setText(text); animatedShow(); } - - +void MessageWidget::hideMessage() +{ + animatedHide(); +} diff --git a/src/gui/MessageWidget.h b/src/gui/MessageWidget.h index 6e8366fae..af97c6f4c 100644 --- a/src/gui/MessageWidget.h +++ b/src/gui/MessageWidget.h @@ -7,15 +7,15 @@ class MessageWidget : public KMessageWidget { + Q_OBJECT + public: explicit MessageWidget(QWidget* parent = 0); - void showMessageError(const QString& text); - void showMessageWarning(const QString& text); - void showMessageInformation(const QString& text); - void showMessagePositive(const QString& text); -private: - void showMessage(const QString& text, MessageType type); +public Q_SLOTS: + void showMessage(const QString& text, MessageWidget::MessageType type); + void hideMessage(); + }; #endif // MESSAGEWIDGET_H diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index c56f70232..d9b235f3b 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -77,6 +77,9 @@ EditEntryWidget::EditEntryWidget(QWidget* parent) connect(this, SIGNAL(accepted()), SLOT(saveEntry())); connect(this, SIGNAL(rejected()), SLOT(cancel())); + + connect(m_iconsWidget, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType))); + connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage())); } EditEntryWidget::~EditEntryWidget() @@ -401,7 +404,7 @@ void EditEntryWidget::saveEntry() } if (!passwordsEqual()) { - showMessageError(tr("Different passwords supplied.")); + showMessage(tr("Different passwords supplied."), MessageWidget::Error); return; } @@ -633,13 +636,13 @@ void EditEntryWidget::insertAttachment() QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { - showMessageError(tr("Unable to open file").append(":\n").append(file.errorString())); + showMessage(tr("Unable to open file").append(":\n").append(file.errorString()), MessageWidget::Error); return; } QByteArray data; if (!Tools::readAllFromDevice(&file, data)) { - showMessageError(tr("Unable to open file").append(":\n").append(file.errorString())); + showMessage(tr("Unable to open file").append(":\n").append(file.errorString()), MessageWidget::Error); return; } @@ -666,11 +669,11 @@ void EditEntryWidget::saveCurrentAttachment() QFile file(savePath); if (!file.open(QIODevice::WriteOnly)) { - showMessageError(tr("Unable to save the attachment:\n").append(file.errorString())); + showMessage(tr("Unable to save the attachment:\n").append(file.errorString()), MessageWidget::Error); return; } if (file.write(attachmentData) != attachmentData.size()) { - showMessageError(tr("Unable to save the attachment:\n").append(file.errorString())); + showMessage(tr("Unable to save the attachment:\n").append(file.errorString()), MessageWidget::Error); return; } } @@ -691,12 +694,12 @@ void EditEntryWidget::openAttachment(const QModelIndex& index) QTemporaryFile* file = new QTemporaryFile(tmpFileTemplate, this); if (!file->open()) { - showMessageError(tr("Unable to save the attachment:\n").append(file->errorString())); + showMessage(tr("Unable to save the attachment:\n").append(file->errorString()), MessageWidget::Error); return; } if (file->write(attachmentData) != attachmentData.size()) { - showMessageError(tr("Unable to save the attachment:\n").append(file->errorString())); + showMessage(tr("Unable to save the attachment:\n").append(file->errorString()), MessageWidget::Error); return; } diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp index 177c62bb0..5b9dfcbc8 100644 --- a/src/gui/group/EditGroupWidget.cpp +++ b/src/gui/group/EditGroupWidget.cpp @@ -43,6 +43,9 @@ EditGroupWidget::EditGroupWidget(QWidget* parent) connect(this, SIGNAL(accepted()), SLOT(save())); connect(this, SIGNAL(rejected()), SLOT(cancel())); + + connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType))); + connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage())); } EditGroupWidget::~EditGroupWidget() diff --git a/src/gui/group/EditGroupWidget.h b/src/gui/group/EditGroupWidget.h index 94ad891db..606cc77b0 100644 --- a/src/gui/group/EditGroupWidget.h +++ b/src/gui/group/EditGroupWidget.h @@ -45,6 +45,8 @@ public: Q_SIGNALS: void editFinished(bool accepted); + void messageEditEntry(QString, MessageWidget::MessageType); + void messageEditEntryDismiss(); private Q_SLOTS: void save(); From 6e7f826e19620565d6b6757dd6164af74a95fd4f Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Wed, 21 Jan 2015 17:01:59 +0000 Subject: [PATCH 037/333] Added licence header. --- src/gui/MessageWidget.cpp | 2 +- src/gui/MessageWidget.h | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/gui/MessageWidget.cpp b/src/gui/MessageWidget.cpp index 15e6b2562..9360a6e62 100644 --- a/src/gui/MessageWidget.cpp +++ b/src/gui/MessageWidget.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Felix Geyer + * Copyright (C) 2015 Pedro Alves * * 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 diff --git a/src/gui/MessageWidget.h b/src/gui/MessageWidget.h index af97c6f4c..0e7d8a307 100644 --- a/src/gui/MessageWidget.h +++ b/src/gui/MessageWidget.h @@ -1,4 +1,19 @@ - +/* + * Copyright (C) 2015 Pedro Alves + * + * 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 . + */ #ifndef MESSAGEWIDGET_H #define MESSAGEWIDGET_H From 84222e80787cf90358984a449df3846e156692cb Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 15 Jan 2017 01:10:26 +0100 Subject: [PATCH 038/333] Add license notice for KMessageWidget --- COPYING | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/COPYING b/COPYING index 9322a0e37..0495b912f 100644 --- a/COPYING +++ b/COPYING @@ -198,3 +198,9 @@ Files: src/zxcvbn/zxcvbn.* Copyright: 2015, Tony Evans 2016, KeePassXC Team License: BSD 3-clause + +Files: src/gui/kmessagewidget.h + src/gui/kmessagewidget.cpp +Copyright: 2011 Aurélien Gâteau + 2014 Dominik Haumann +License: LGPL-2.1 From b6ea06ba245e44923fe136f4734b81111d93a155 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 15 Jan 2017 01:10:46 +0100 Subject: [PATCH 039/333] Update KMessageWidget --- src/gui/kmessagewidget.cpp | 603 ++++++++++++++++++++----------------- src/gui/kmessagewidget.h | 397 +++++++++++++++--------- 2 files changed, 579 insertions(+), 421 deletions(-) diff --git a/src/gui/kmessagewidget.cpp b/src/gui/kmessagewidget.cpp index e5aafd61a..ee3c1b69c 100644 --- a/src/gui/kmessagewidget.cpp +++ b/src/gui/kmessagewidget.cpp @@ -1,6 +1,7 @@ /* This file is part of the KDE libraries - * + * * Copyright (c) 2011 Aurélien Gâteau + * Copyright (c) 2014 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,407 +20,461 @@ */ #include "kmessagewidget.h" +#include #include #include -#include +#include #include #include #include #include #include #include -#include + +//--------------------------------------------------------------------- +// KMessageWidgetPrivate +//--------------------------------------------------------------------- +class KMessageWidgetPrivate +{ +public: + void init(KMessageWidget *); + + KMessageWidget *q; + QFrame *content; + QLabel *iconLabel; + QLabel *textLabel; + QToolButton *closeButton; + QTimeLine *timeLine; + QIcon icon; + + KMessageWidget::MessageType messageType; + bool wordWrap; + QList buttons; + QPixmap contentSnapShot; + + void createLayout(); + void updateSnapShot(); + void updateLayout(); + void slotTimeLineChanged(qreal); + void slotTimeLineFinished(); + + int bestContentHeight() const; +}; void KMessageWidgetPrivate::init(KMessageWidget *q_ptr) { - q = q_ptr; - - q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); - - timeLine = new QTimeLine(500, q); - QObject::connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(slotTimeLineChanged(qreal))); - QObject::connect(timeLine, SIGNAL(finished()), q, SLOT(slotTimeLineFinished())); - - content = new QFrame(q); - content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - wordWrap = false; - - iconLabel = new QLabel(content); - iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - iconLabel->hide(); - - textLabel = new QLabel(content); - textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - QObject::connect(textLabel, SIGNAL(linkActivated(const QString &)), q, SIGNAL(linkActivated(const QString &))); - QObject::connect(textLabel, SIGNAL(linkHovered(const QString &)), q, SIGNAL(linkHovered(const QString &))); - - QAction *closeAction = new QAction(QObject::tr("Close"), q); - q->connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(animatedHide())); - - closeButton = new QToolButton(content); - closeButton->setAutoRaise(true); - closeButton->setDefaultAction(closeAction); - closeButton->setVisible(true); - q->setMessageType(KMessageWidget::Information); + q = q_ptr; + + q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + timeLine = new QTimeLine(500, q); + QObject::connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(slotTimeLineChanged(qreal))); + QObject::connect(timeLine, SIGNAL(finished()), q, SLOT(slotTimeLineFinished())); + + content = new QFrame(q); + content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + wordWrap = false; + + iconLabel = new QLabel(content); + iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + iconLabel->hide(); + + textLabel = new QLabel(content); + textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + QObject::connect(textLabel, SIGNAL(linkActivated(QString)), q, SIGNAL(linkActivated(QString))); + QObject::connect(textLabel, SIGNAL(linkHovered(QString)), q, SIGNAL(linkHovered(QString))); + + QAction *closeAction = new QAction(q); + closeAction->setText(KMessageWidget::tr("&Close")); + closeAction->setToolTip(KMessageWidget::tr("Close message")); + closeAction->setIcon(q->style()->standardIcon(QStyle::SP_DialogCloseButton)); + + QObject::connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(animatedHide())); + + closeButton = new QToolButton(content); + closeButton->setAutoRaise(true); + closeButton->setDefaultAction(closeAction); + + q->setMessageType(KMessageWidget::Information); } void KMessageWidgetPrivate::createLayout() { - delete content->layout(); - - content->resize(q->size()); - - qDeleteAll(buttons); - buttons.clear(); - - Q_FOREACH (QAction *action, q->actions()) { - QToolButton *button = new QToolButton(content); - button->setDefaultAction(action); - button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - buttons.append(button); - } - - // AutoRaise reduces visual clutter, but we don't want to turn it on if - // there are other buttons, otherwise the close button will look different - // from the others. - closeButton->setAutoRaise(buttons.isEmpty()); - - if (wordWrap) { - QGridLayout *layout = new QGridLayout(content); - // Set alignment to make sure icon does not move down if text wraps - layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop); - layout->addWidget(textLabel, 0, 1); - - QDialogButtonBox *buttonLayout = new QDialogButtonBox(); - //buttonLayout->addStretch(); - Q_FOREACH (QToolButton *button, buttons) { - // For some reason, calling show() is necessary if wordwrap is true, - // otherwise the buttons do not show up. It is not needed if - // wordwrap is false. - button->show(); - buttonLayout->addButton(button, QDialogButtonBox::QDialogButtonBox::AcceptRole); - } - buttonLayout->addButton(closeButton, QDialogButtonBox::RejectRole); - layout->addWidget(buttonLayout, 1, 0, 1, 2, Qt::AlignHCenter | Qt::AlignTop); - } else { - bool closeButtonVisible = closeButton->isVisible(); - QHBoxLayout *layout = new QHBoxLayout(content); - layout->addWidget(iconLabel); - layout->addWidget(textLabel); - - QDialogButtonBox *buttonLayout = new QDialogButtonBox(); - Q_FOREACH (QToolButton *button, buttons) { - buttonLayout->addButton(button, QDialogButtonBox::QDialogButtonBox::AcceptRole); - } - - buttonLayout->addButton(closeButton, QDialogButtonBox::RejectRole); - // Something gets changed when added to the buttonLayout - closeButton->setVisible(closeButtonVisible); - layout->addWidget(buttonLayout); - }; - - if (q->isVisible()) { - q->setFixedHeight(content->sizeHint().height()); - } - - q->updateGeometry(); + delete content->layout(); + + content->resize(q->size()); + + qDeleteAll(buttons); + buttons.clear(); + + Q_FOREACH (QAction *action, q->actions()) { + QToolButton *button = new QToolButton(content); + button->setDefaultAction(action); + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + buttons.append(button); + } + + // AutoRaise reduces visual clutter, but we don't want to turn it on if + // there are other buttons, otherwise the close button will look different + // from the others. + closeButton->setAutoRaise(buttons.isEmpty()); + + if (wordWrap) { + QGridLayout *layout = new QGridLayout(content); + // Set alignment to make sure icon does not move down if text wraps + layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop); + layout->addWidget(textLabel, 0, 1); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + Q_FOREACH (QToolButton *button, buttons) { + // For some reason, calling show() is necessary if wordwrap is true, + // otherwise the buttons do not show up. It is not needed if + // wordwrap is false. + button->show(); + buttonLayout->addWidget(button); + } + buttonLayout->addWidget(closeButton); + layout->addItem(buttonLayout, 1, 0, 1, 2); + } else { + QHBoxLayout *layout = new QHBoxLayout(content); + layout->addWidget(iconLabel); + layout->addWidget(textLabel); + + Q_FOREACH (QToolButton *button, buttons) { + layout->addWidget(button); + } + + layout->addWidget(closeButton); + }; + + if (q->isVisible()) { + q->setFixedHeight(content->sizeHint().height()); + } + q->updateGeometry(); } void KMessageWidgetPrivate::updateLayout() { - if (content->layout()) { - createLayout(); - } + if (content->layout()) { + createLayout(); + } } void KMessageWidgetPrivate::updateSnapShot() { - // Attention: updateSnapShot calls QWidget::render(), which causes the whole - // window layouts to be activated. Calling this method from resizeEvent() - // can lead to infinite recursion, see: - // https://bugs.kde.org/show_bug.cgi?id=311336 - contentSnapShot = QPixmap(content->size()); - contentSnapShot.fill(Qt::transparent); - content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren); + // Attention: updateSnapShot calls QWidget::render(), which causes the whole + // window layouts to be activated. Calling this method from resizeEvent() + // can lead to infinite recursion, see: + // https://bugs.kde.org/show_bug.cgi?id=311336 + contentSnapShot = QPixmap(content->size() * q->devicePixelRatio()); + contentSnapShot.setDevicePixelRatio(q->devicePixelRatio()); + contentSnapShot.fill(Qt::transparent); + content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren); } void KMessageWidgetPrivate::slotTimeLineChanged(qreal value) { - q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height()); - q->update(); + q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height()); + q->update(); } void KMessageWidgetPrivate::slotTimeLineFinished() { - if (timeLine->direction() == QTimeLine::Forward) { - // Show - // We set the whole geometry here, because it may be wrong if a - // KMessageWidget is shown right when the toplevel window is created. - content->setGeometry(0, 0, q->width(), bestContentHeight()); - } else { - // Hide - q->hide(); - } + if (timeLine->direction() == QTimeLine::Forward) { + // Show + // We set the whole geometry here, because it may be wrong if a + // KMessageWidget is shown right when the toplevel window is created. + content->setGeometry(0, 0, q->width(), bestContentHeight()); + + // notify about finished animation + emit q->showAnimationFinished(); + } else { + // hide and notify about finished animation + q->hide(); + emit q->hideAnimationFinished(); + } } int KMessageWidgetPrivate::bestContentHeight() const { - int height = content->heightForWidth(q->width()); - - if (height == -1) { - height = content->sizeHint().height(); - } - - return height; + int height = content->heightForWidth(q->width()); + if (height == -1) { + height = content->sizeHint().height(); + } + return height; } - //--------------------------------------------------------------------- // KMessageWidget //--------------------------------------------------------------------- -KMessageWidget::KMessageWidget(QWidget *parent) : QFrame(parent), d(new KMessageWidgetPrivate) +KMessageWidget::KMessageWidget(QWidget *parent) +: QFrame(parent) +, d(new KMessageWidgetPrivate) { - d->init(this); + d->init(this); } -KMessageWidget::KMessageWidget(const QString &text, QWidget *parent) : QFrame(parent), d(new KMessageWidgetPrivate) +KMessageWidget::KMessageWidget(const QString &text, QWidget *parent) +: QFrame(parent) +, d(new KMessageWidgetPrivate) { - d->init(this); - setText(text); + d->init(this); + setText(text); } KMessageWidget::~KMessageWidget() { - delete d; + delete d; } QString KMessageWidget::text() const { - return d->textLabel->text(); + return d->textLabel->text(); } void KMessageWidget::setText(const QString &text) { - d->textLabel->setText(text); - updateGeometry(); + d->textLabel->setText(text); + updateGeometry(); } -int KMessageWidget::bestContentHeight() const -{ - return d->bestContentHeight(); -} - - KMessageWidget::MessageType KMessageWidget::messageType() const { - return d->messageType; + return d->messageType; +} + +static QColor darkShade(QColor c) +{ + qreal contrast = 0.7; // taken from kcolorscheme for the dark shade + + qreal darkAmount; + if (c.lightnessF() < 0.006) { /* too dark */ + darkAmount = 0.02 + 0.40 * contrast; + } else if (c.lightnessF() > 0.93) { /* too bright */ + darkAmount = -0.06 - 0.60 * contrast; + } else { + darkAmount = (-c.lightnessF()) * (0.55 + contrast * 0.35); + } + + qreal v = c.lightnessF() + darkAmount; + v = v > 0.0 ? (v < 1.0 ? v : 1.0) : 0.0; + c.setHsvF(c.hslHueF(), c.hslSaturationF(), v); + return c; } void KMessageWidget::setMessageType(KMessageWidget::MessageType type) { - d->messageType = type; - QColor bg0, bg1, bg2, border, fg; - - switch (type) { - case Positive: - bg1 = QColor("#72D594"); // nice green - fg = QColor(Qt::white); - break; - - case Information: - bg1 = QColor("#41A8E3"); // nice blue - fg = QColor(Qt::black); - break; - - case Warning: - bg1 = QColor("#FFCD0F"); // nice yellow - fg = QColor(Qt::black); - break; - - case Error: - bg1 = QColor("#FF7D7D"); // nice red. - fg = QColor(Qt::black); - break; - } - - // Colors - bg0 = bg1.lighter(110); - bg2 = bg1.darker(110); - border = bg2.darker(110); - d->content->setStyleSheet( - QString(".QFrame {" - "background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1," - " stop: 0 %1," - " stop: 0.1 %2," - " stop: 1.0 %3);" - "border-radius: 5px;" - "border: 1px solid %4;" - "margin: %5px;" - "}" - ".QLabel { color: %6; }").arg(bg0.name()) - .arg(bg1.name()) - .arg(bg2.name()) - .arg(border.name()) - /* - DefaultFrameWidth returns the size of the external margin + border width. - We know our border is 1px, so we subtract this from the frame - normal QStyle FrameWidth to get our margin - */ - .arg(style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this) - 1) - .arg(fg.name())); + d->messageType = type; + QColor bg0, bg1, bg2, border, fg; + switch (type) { + case Positive: + bg1.setRgb(0, 110, 40); // values taken from kcolorscheme.cpp (Positive) + break; + case Information: + bg1 = palette().highlight().color(); + break; + case Warning: + bg1.setRgb(176, 128, 0); // values taken from kcolorscheme.cpp (Neutral) + break; + case Error: + bg1.setRgb(191, 3, 3); // values taken from kcolorscheme.cpp (Negative) + break; + } + + // Colors + fg = palette().highlightedText().color(); + bg0 = bg1.lighter(110); + bg2 = bg1.darker(110); + border = darkShade(bg1); + + d->content->setStyleSheet( + QString(QLatin1String(".QFrame {" + "background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1," + " stop: 0 %1," + " stop: 0.1 %2," + " stop: 1.0 %3);" + "border-radius: 5px;" + "border: 1px solid %4;" + "margin: %5px;" + "}" + ".QLabel { color: %6; }" + )) + .arg(bg0.name()) + .arg(bg1.name()) + .arg(bg2.name()) + .arg(border.name()) + // DefaultFrameWidth returns the size of the external margin + border width. We know our border is 1px, so we subtract this from the frame normal QStyle FrameWidth to get our margin + .arg(style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this) - 1) + .arg(fg.name()) + ); } QSize KMessageWidget::sizeHint() const { - ensurePolished(); - return d->content->sizeHint(); + ensurePolished(); + return d->content->sizeHint(); } QSize KMessageWidget::minimumSizeHint() const { - ensurePolished(); - return d->content->minimumSizeHint(); + ensurePolished(); + return d->content->minimumSizeHint(); } bool KMessageWidget::event(QEvent *event) { - if (event->type() == QEvent::Polish && !d->content->layout()) { - d->createLayout(); - } - - return QFrame::event(event); + if (event->type() == QEvent::Polish && !d->content->layout()) { + d->createLayout(); + } + return QFrame::event(event); } void KMessageWidget::resizeEvent(QResizeEvent *event) { - QFrame::resizeEvent(event); - - if (d->timeLine->state() == QTimeLine::NotRunning) { - d->content->resize(width(), d->bestContentHeight()); - } + QFrame::resizeEvent(event); + + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->content->resize(width(), d->bestContentHeight()); + } } int KMessageWidget::heightForWidth(int width) const { - ensurePolished(); - return d->content->heightForWidth(width); + ensurePolished(); + return d->content->heightForWidth(width); } void KMessageWidget::paintEvent(QPaintEvent *event) { - QFrame::paintEvent(event); - - if (d->timeLine->state() == QTimeLine::Running) { - QPainter painter(this); - painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue()); - painter.drawPixmap(0, 0, d->contentSnapShot); - } -} - -void KMessageWidget::showEvent(QShowEvent *event) -{ - // Keep this method here to avoid breaking binary compatibility: - // QFrame::showEvent() used to be reimplemented. - QFrame::showEvent(event); + QFrame::paintEvent(event); + if (d->timeLine->state() == QTimeLine::Running) { + QPainter painter(this); + painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue()); + painter.drawPixmap(0, 0, d->contentSnapShot); + } } bool KMessageWidget::wordWrap() const { - return d->wordWrap; + return d->wordWrap; } void KMessageWidget::setWordWrap(bool wordWrap) { - d->wordWrap = wordWrap; - d->textLabel->setWordWrap(wordWrap); - QSizePolicy policy = sizePolicy(); - policy.setHeightForWidth(wordWrap); - setSizePolicy(policy); - d->updateLayout(); - - // Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum - // height is set, causing the widget to be too high. - // Mostly visible in test programs. - if (wordWrap) { - setMinimumHeight(0); - } + d->wordWrap = wordWrap; + d->textLabel->setWordWrap(wordWrap); + QSizePolicy policy = sizePolicy(); + policy.setHeightForWidth(wordWrap); + setSizePolicy(policy); + d->updateLayout(); + // Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum + // height is set, causing the widget to be too high. + // Mostly visible in test programs. + if (wordWrap) { + setMinimumHeight(0); + } } bool KMessageWidget::isCloseButtonVisible() const { - return d->closeButton->isVisible(); + return d->closeButton->isVisible(); } void KMessageWidget::setCloseButtonVisible(bool show) { - d->closeButton->setVisible(show); - updateGeometry(); + d->closeButton->setVisible(show); + updateGeometry(); } void KMessageWidget::addAction(QAction *action) { - QFrame::addAction(action); - d->updateLayout(); + QFrame::addAction(action); + d->updateLayout(); } void KMessageWidget::removeAction(QAction *action) { - QFrame::removeAction(action); - d->updateLayout(); + QFrame::removeAction(action); + d->updateLayout(); } void KMessageWidget::animatedShow() { - if (isVisible()) { - return; - } - - QFrame::show(); - setFixedHeight(0); - int wantedHeight = d->bestContentHeight(); - d->content->setGeometry(0, -wantedHeight, width(), wantedHeight); - - d->updateSnapShot(); - - d->timeLine->setDirection(QTimeLine::Forward); - - if (d->timeLine->state() == QTimeLine::NotRunning) { - d->timeLine->start(); - } + if (!style()->styleHint(QStyle::SH_Widget_Animate, 0, this)) { + show(); + emit showAnimationFinished(); + return; + } + + if (isVisible()) { + return; + } + + QFrame::show(); + setFixedHeight(0); + int wantedHeight = d->bestContentHeight(); + d->content->setGeometry(0, -wantedHeight, width(), wantedHeight); + + d->updateSnapShot(); + + d->timeLine->setDirection(QTimeLine::Forward); + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->timeLine->start(); + } } void KMessageWidget::animatedHide() { - if (!isVisible()) { - hide(); - return; - } + if (!style()->styleHint(QStyle::SH_Widget_Animate, 0, this)) { + hide(); + emit hideAnimationFinished(); + return; + } + + if (!isVisible()) { + hide(); + return; + } + + d->content->move(0, -d->content->height()); + d->updateSnapShot(); + + d->timeLine->setDirection(QTimeLine::Backward); + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->timeLine->start(); + } +} - d->content->move(0, -d->content->height()); - d->updateSnapShot(); +bool KMessageWidget::isHideAnimationRunning() const +{ + return (d->timeLine->direction() == QTimeLine::Backward) + && (d->timeLine->state() == QTimeLine::Running); +} - d->timeLine->setDirection(QTimeLine::Backward); - - if (d->timeLine->state() == QTimeLine::NotRunning) { - d->timeLine->start(); - } +bool KMessageWidget::isShowAnimationRunning() const +{ + return (d->timeLine->direction() == QTimeLine::Forward) + && (d->timeLine->state() == QTimeLine::Running); } QIcon KMessageWidget::icon() const { - return d->icon; + return d->icon; } void KMessageWidget::setIcon(const QIcon &icon) { - d->icon = icon; - - if (d->icon.isNull()) { - d->iconLabel->hide(); - } else { - d->iconLabel->setPixmap(d->icon.pixmap(QSize(16, 16))); - d->iconLabel->show(); - } + d->icon = icon; + if (d->icon.isNull()) { + d->iconLabel->hide(); + } else { + const int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize); + d->iconLabel->setPixmap(d->icon.pixmap(size)); + d->iconLabel->show(); + } } + +#include "moc_kmessagewidget.cpp" diff --git a/src/gui/kmessagewidget.h b/src/gui/kmessagewidget.h index dbc694afa..4398e0f99 100644 --- a/src/gui/kmessagewidget.h +++ b/src/gui/kmessagewidget.h @@ -1,6 +1,7 @@ /* This file is part of the KDE libraries - * + * * Copyright (c) 2011 Aurélien Gâteau + * Copyright (c) 2014 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -31,9 +32,14 @@ class KMessageWidgetPrivate; * feedback, or to implement opportunistic interactions. * * As a feedback widget, KMessageWidget provides a less intrusive alternative - * to "OK Only" message boxes. If you do not need the modalness of KMessageBox, + * to "OK Only" message boxes. If you want to avoid a modal KMessageBox, * consider using KMessageWidget instead. * + * Examples of KMessageWidget look as follows, all of them having an icon set + * with setIcon(), and the first three show a close button: + * + * \image html kmessagewidget.png "KMessageWidget with different message types" + * * Negative feedback * * The KMessageWidget can be used as a secondary indicator of failure: the @@ -67,7 +73,7 @@ class KMessageWidgetPrivate; * @li Confirm success of "critical" transactions * @li Indicate completion of background tasks * - * Example of inadapted uses: + * Example of unadapted uses: * * @li Indicate successful saving of a file * @li Indicate a file has been successfully removed @@ -87,153 +93,250 @@ class KMessageWidgetPrivate; * @author Aurélien Gâteau * @since 4.7 */ -class KMessageWidget : public QFrame { - Q_OBJECT - Q_ENUMS(MessageType) - - Q_PROPERTY(QString text READ text WRITE setText) - Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) - Q_PROPERTY(bool closeButtonVisible READ isCloseButtonVisible WRITE setCloseButtonVisible) - Q_PROPERTY(MessageType messageType READ messageType WRITE setMessageType) - Q_PROPERTY(QIcon icon READ icon WRITE setIcon) +class KMessageWidget : public QFrame +{ + Q_OBJECT + Q_ENUMS(MessageType) + + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(bool closeButtonVisible READ isCloseButtonVisible WRITE setCloseButtonVisible) + Q_PROPERTY(MessageType messageType READ messageType WRITE setMessageType) + Q_PROPERTY(QIcon icon READ icon WRITE setIcon) public: - enum MessageType { - Positive, - Information, - Warning, - Error - }; - - /** - * Constructs a KMessageWidget with the specified parent. - */ - explicit KMessageWidget(QWidget *parent = 0); - - explicit KMessageWidget(const QString &text, QWidget *parent = 0); - - ~KMessageWidget(); - - QString text() const; - - bool wordWrap() const; - - bool isCloseButtonVisible() const; - - MessageType messageType() const; - - void addAction(QAction *action); - - void removeAction(QAction *action); - - QSize sizeHint() const; - - QSize minimumSizeHint() const; - - int heightForWidth(int width) const; - - /** - * The icon shown on the left of the text. By default, no icon is shown. - * @since 4.11 - */ - QIcon icon() const; - -public -Q_SLOTS: - void setText(const QString &text); - - void setWordWrap(bool wordWrap); - - void setCloseButtonVisible(bool visible); - - void setMessageType(KMessageWidget::MessageType type); - - /** - * Show the widget using an animation, unless - * KGlobalSettings::graphicsEffectLevel() does not allow simple effects. - */ - void animatedShow(); - - /** - * Hide the widget using an animation, unless - * KGlobalSettings::graphicsEffectLevel() does not allow simple effects. - */ - void animatedHide(); - - /** - * Define an icon to be shown on the left of the text - * @since 4.11 - */ - void setIcon(const QIcon &icon); - - int bestContentHeight() const; + + /** + * Available message types. + * The background colors are chosen depending on the message type. + */ + enum MessageType { + Positive, + Information, + Warning, + Error + }; + + /** + * Constructs a KMessageWidget with the specified @p parent. + */ + explicit KMessageWidget(QWidget *parent = 0); + + /** + * Constructs a KMessageWidget with the specified @p parent and + * contents @p text. + */ + explicit KMessageWidget(const QString &text, QWidget *parent = 0); + + /** + * Destructor. + */ + ~KMessageWidget(); + + /** + * Get the text of this message widget. + * @see setText() + */ + QString text() const; + + /** + * Check whether word wrap is enabled. + * + * If word wrap is enabled, the message widget wraps the displayed text + * as required to the available width of the widget. This is useful to + * avoid breaking widget layouts. + * + * @see setWordWrap() + */ + bool wordWrap() const; + + /** + * Check whether the close button is visible. + * + * @see setCloseButtonVisible() + */ + bool isCloseButtonVisible() const; + + /** + * Get the type of this message. + * By default, the type is set to KMessageWidget::Information. + * + * @see KMessageWidget::MessageType, setMessageType() + */ + MessageType messageType() const; + + /** + * Add @p action to the message widget. + * For each action a button is added to the message widget in the + * order the actions were added. + * + * @param action the action to add + * @see removeAction(), QWidget::actions() + */ + void addAction(QAction *action); + + /** + * Remove @p action from the message widget. + * + * @param action the action to remove + * @see KMessageWidget::MessageType, addAction(), setMessageType() + */ + void removeAction(QAction *action); + + /** + * Returns the preferred size of the message widget. + */ + QSize sizeHint() const Q_DECL_OVERRIDE; + + /** + * Returns the minimum size of the message widget. + */ + QSize minimumSizeHint() const Q_DECL_OVERRIDE; + + /** + * Returns the required height for @p width. + * @param width the width in pixels + */ + int heightForWidth(int width) const Q_DECL_OVERRIDE; + + /** + * The icon shown on the left of the text. By default, no icon is shown. + * @since 4.11 + */ + QIcon icon() const; + + /** + * Check whether the hide animation started by calling animatedHide() + * is still running. If animations are disabled, this function always + * returns @e false. + * + * @see animatedHide(), hideAnimationFinished() + * @since 5.0 + */ + bool isHideAnimationRunning() const; + + /** + * Check whether the show animation started by calling animatedShow() + * is still running. If animations are disabled, this function always + * returns @e false. + * + * @see animatedShow(), showAnimationFinished() + * @since 5.0 + */ + bool isShowAnimationRunning() const; + +public Q_SLOTS: + /** + * Set the text of the message widget to @p text. + * If the message widget is already visible, the text changes on the fly. + * + * @param text the text to display, rich text is allowed + * @see text() + */ + void setText(const QString &text); + + /** + * Set word wrap to @p wordWrap. If word wrap is enabled, the text() + * of the message widget is wrapped to fit the available width. + * If word wrap is disabled, the message widget's minimum size is + * such that the entire text fits. + * + * @param wordWrap disable/enable word wrap + * @see wordWrap() + */ + void setWordWrap(bool wordWrap); + + /** + * Set the visibility of the close button. If @p visible is @e true, + * a close button is shown that calls animatedHide() if clicked. + * + * @see closeButtonVisible(), animatedHide() + */ + void setCloseButtonVisible(bool visible); + + /** + * Set the message type to @p type. + * By default, the message type is set to KMessageWidget::Information. + * + * @see messageType(), KMessageWidget::MessageType + */ + void setMessageType(KMessageWidget::MessageType type); + + /** + * Show the widget using an animation. + */ + void animatedShow(); + + /** + * Hide the widget using an animation. + */ + void animatedHide(); + + /** + * Define an icon to be shown on the left of the text + * @since 4.11 + */ + void setIcon(const QIcon &icon); + Q_SIGNALS: - /** - * This signal is emitted when the user clicks a link in the text label. - * The URL referred to by the href anchor is passed in contents. - * @param contents text of the href anchor - * @see QLabel::linkActivated() - * @since 4.10 - */ - void linkActivated(const QString &contents); - - /** - * This signal is emitted when the user hovers over a link in the text label. - * The URL referred to by the href anchor is passed in contents. - * @param contents text of the href anchor - * @see QLabel::linkHovered() - * @since 4.11 - */ - void linkHovered(const QString &contents); - + /** + * This signal is emitted when the user clicks a link in the text label. + * The URL referred to by the href anchor is passed in contents. + * @param contents text of the href anchor + * @see QLabel::linkActivated() + * @since 4.10 + */ + void linkActivated(const QString &contents); + + /** + * This signal is emitted when the user hovers over a link in the text label. + * The URL referred to by the href anchor is passed in contents. + * @param contents text of the href anchor + * @see QLabel::linkHovered() + * @since 4.11 + */ + void linkHovered(const QString &contents); + + /** + * This signal is emitted when the hide animation is finished, started by + * calling animatedHide(). If animations are disabled, this signal is + * emitted immediately after the message widget got hidden. + * + * @note This signal is @e not emitted if the widget was hidden by + * calling hide(), so this signal is only useful in conjunction + * with animatedHide(). + * + * @see animatedHide() + * @since 5.0 + */ + void hideAnimationFinished(); + + /** + * This signal is emitted when the show animation is finished, started by + * calling animatedShow(). If animations are disabled, this signal is + * emitted immediately after the message widget got shown. + * + * @note This signal is @e not emitted if the widget was shown by + * calling show(), so this signal is only useful in conjunction + * with animatedShow(). + * + * @see animatedShow() + * @since 5.0 + */ + void showAnimationFinished(); + protected: - void paintEvent(QPaintEvent *event); - - bool event(QEvent *event); - - void resizeEvent(QResizeEvent *event); - - void showEvent(QShowEvent *event); - + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + + bool event(QEvent *event) Q_DECL_OVERRIDE; + + void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; + private: - KMessageWidgetPrivate *const d; - friend class KMessageWidgetPrivate; - - Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal)) - Q_PRIVATE_SLOT(d, void slotTimeLineFinished()) + KMessageWidgetPrivate *const d; + friend class KMessageWidgetPrivate; + + Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal)) + Q_PRIVATE_SLOT(d, void slotTimeLineFinished()) }; -//--------------------------------------------------------------------- -// KMessageWidgetPrivate -//--------------------------------------------------------------------- -class QLabel; -class QToolButton; -class QTimeLine; -#include - -class KMessageWidgetPrivate { -public: - void init(KMessageWidget *); - - KMessageWidget *q; - QFrame *content; - QLabel *iconLabel; - QLabel *textLabel; - QToolButton *closeButton; - QTimeLine *timeLine; - QIcon icon; - - KMessageWidget::MessageType messageType; - bool wordWrap; - QList buttons; - QPixmap contentSnapShot; - - void createLayout(); - void updateSnapShot(); - void updateLayout(); - void slotTimeLineChanged(qreal); - void slotTimeLineFinished(); - - int bestContentHeight() const; -}; - -#endif // KMESSAGEWIDGET_H +#endif /* KMESSAGEWIDGET_H */ From 41d51116847a478725d509debadd5f5622ff061d Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 15 Jan 2017 01:44:48 +0100 Subject: [PATCH 040/333] Use KMessageWidget also for new error messages --- src/gui/DatabaseTabWidget.cpp | 14 +++++++------- src/gui/DatabaseWidget.cpp | 18 +++++++++--------- src/gui/KeePass1OpenWidget.cpp | 4 ++-- src/gui/MainWindow.cpp | 7 ++++--- src/gui/entry/EditEntryWidget.cpp | 3 +-- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index b1eb9a0a5..8b29cfd03 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -328,8 +328,8 @@ bool DatabaseTabWidget::saveDatabase(Database* db) // write the database to the file m_writer.writeDatabase(&saveFile, db); if (m_writer.hasError()) { - MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n" - + m_writer.errorString()); + Q_EMIT messageTab(tr("Writing the database failed.").append("\n") + .append(m_writer.errorString()), MessageWidget::Error); return false; } @@ -342,14 +342,14 @@ bool DatabaseTabWidget::saveDatabase(Database* db) return true; } else { - MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n" - + saveFile.errorString()); + Q_EMIT messageTab(tr("Writing the database failed.").append("\n") + .append(saveFile.errorString()), MessageWidget::Error); return false; } } else { - Q_EMIT messageTab(tr("Writing the database failed.\n") - .append("\n").append(saveFile.errorString()), MessageWidget::Error); + Q_EMIT messageTab(tr("Writing the database failed.").append("\n") + .append(saveFile.errorString()), MessageWidget::Error); return false; } } @@ -494,7 +494,7 @@ void DatabaseTabWidget::exportToCsv() CsvExporter csvExporter; if (!csvExporter.exportDatabase(fileName, db)) { Q_EMIT messageGlobal( - tr("Writing the CSV file failed.").append("\n\n") + tr("Writing the CSV file failed.").append("\n") .append(csvExporter.errorString()), MessageWidget::Error); } } diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 74fc2d7cc..2d65352f9 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -696,7 +696,7 @@ void DatabaseWidget::updateMasterKey(bool accepted) QApplication::restoreOverrideCursor(); if (!result) { - MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key")); + m_messageWidget->showMessage(tr("Unable to calculate master key"), MessageWidget::Error); return; } } @@ -736,14 +736,14 @@ void DatabaseWidget::mergeDatabase(bool accepted) { if (accepted) { if (!m_db) { - MessageBox::critical(this, tr("Error"), tr("No current database.")); + m_messageWidget->showMessage(tr("No current database."), MessageWidget::Error); return; } Database* srcDb = static_cast(sender())->database(); if (!srcDb) { - MessageBox::critical(this, tr("Error"), tr("No source database, nothing to do.")); + m_messageWidget->showMessage(tr("No source database, nothing to do."), MessageWidget::Error); return; } @@ -1093,15 +1093,15 @@ void DatabaseWidget::reloadDatabaseFile() } else { - MessageBox::critical(this, tr("Autoreload Failed"), - tr("Could not parse or unlock the new database file while attempting" - " to autoreload this database.")); + m_messageWidget->showMessage( + tr("Could not parse or unlock the new database file while attempting" + " to autoreload this database."), MessageWidget::Error); } } else { - MessageBox::critical(this, tr("Autoreload Failed"), - tr("Could not open the new database file while attempting to autoreload" - " this database.")); + m_messageWidget->showMessage( + tr("Could not open the new database file while attempting to autoreload this database."), + MessageWidget::Error); } // Rewatch the database file diff --git a/src/gui/KeePass1OpenWidget.cpp b/src/gui/KeePass1OpenWidget.cpp index c5159aaba..b63bbc485 100644 --- a/src/gui/KeePass1OpenWidget.cpp +++ b/src/gui/KeePass1OpenWidget.cpp @@ -49,8 +49,8 @@ void KeePass1OpenWidget::openDatabase() QFile file(m_filename); if (!file.open(QIODevice::ReadOnly)) { - MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n") - .append(file.errorString())); + m_ui->messageWidget->showMessage( tr("Unable to open the database.").append("\n") + .append(file.errorString()), MessageWidget::Error); return; } if (m_db) { diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 92a9cbdd4..63bc1ba87 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -761,7 +761,7 @@ void MainWindow::repairDatabase() if (fileName.isEmpty()) { return; } - + QScopedPointer dialog(new QDialog(this)); DatabaseRepairWidget* dbRepairWidget = new DatabaseRepairWidget(dialog.data()); connect(dbRepairWidget, SIGNAL(success()), dialog.data(), SLOT(accept())); @@ -776,8 +776,9 @@ void MainWindow::repairDatabase() KeePass2Writer writer; writer.writeDatabase(saveFileName, dbRepairWidget->database()); if (writer.hasError()) { - QMessageBox::critical(this, tr("Error"), - tr("Writing the database failed.").append("\n\n").append(writer.errorString())); + displayGlobalMessage( + tr("Writing the database failed.").append("\n").append(writer.errorString()), + MessageWidget::Error); } } } diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index d9b235f3b..58e2a7707 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -704,8 +704,7 @@ void EditEntryWidget::openAttachment(const QModelIndex& index) } if (!file->flush()) { - MessageBox::warning(this, tr("Error"), - tr("Unable to save the attachment:\n").append(file->errorString())); + showMessage(tr("Unable to save the attachment:\n").append(file->errorString()), MessageWidget::Error); return; } From 94f8650ca4f3ec3998b48d3b88c9462d97635d46 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Feb 2017 02:11:43 +0100 Subject: [PATCH 041/333] Rename KMessageWidget files to match our coding style --- src/CMakeLists.txt | 2 +- src/gui/EditWidgetIcons.cpp | 5 ++--- src/gui/{kmessagewidget.cpp => KMessageWidget.cpp} | 4 ++-- src/gui/{kmessagewidget.h => KMessageWidget.h} | 0 src/gui/MainWindow.cpp | 4 +--- src/gui/MessageWidget.h | 2 +- 6 files changed, 7 insertions(+), 10 deletions(-) rename src/gui/{kmessagewidget.cpp => KMessageWidget.cpp} (99%) rename src/gui/{kmessagewidget.h => KMessageWidget.h} (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3de167544..c199cbb9d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -86,7 +86,7 @@ set(keepassx_SOURCES gui/FileDialog.cpp gui/IconModels.cpp gui/KeePass1OpenWidget.cpp - gui/kmessagewidget.cpp + gui/KMessageWidget.cpp gui/LineEdit.cpp gui/MainWindow.cpp gui/MessageBox.cpp diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index fcfff5ca3..ba395b592 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -158,12 +158,11 @@ void EditWidgetIcons::fetchFavicon(QUrl url) void EditWidgetIcons::fetchFaviconFromGoogle(QString domain) { - if (m_fallbackToGoogle) { + if (m_fallbackToGoogle) { abortFaviconDownload(); m_fallbackToGoogle = false; fetchFavicon(QUrl("http://www.google.com/s2/favicons?domain=" + domain)); - } - else { + } else { abortFaviconDownload(); MessageBox::warning(this, tr("Error"), tr("Unable to fetch favicon.")); } diff --git a/src/gui/kmessagewidget.cpp b/src/gui/KMessageWidget.cpp similarity index 99% rename from src/gui/kmessagewidget.cpp rename to src/gui/KMessageWidget.cpp index ee3c1b69c..f2c48c253 100644 --- a/src/gui/kmessagewidget.cpp +++ b/src/gui/KMessageWidget.cpp @@ -18,7 +18,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ -#include "kmessagewidget.h" +#include "KMessageWidget.h" #include #include @@ -477,4 +477,4 @@ void KMessageWidget::setIcon(const QIcon &icon) } } -#include "moc_kmessagewidget.cpp" +#include "moc_KMessageWidget.cpp" diff --git a/src/gui/kmessagewidget.h b/src/gui/KMessageWidget.h similarity index 100% rename from src/gui/kmessagewidget.h rename to src/gui/KMessageWidget.h diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 63bc1ba87..b9da4e19e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -802,9 +802,7 @@ void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::Messag void MainWindow::displayTabMessage(const QString& text, MessageWidget::MessageType type) { - //if (m_ui->stackedWidget->currentIndex() == 0) { - m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type); - //} + m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type); } void MainWindow::hideGlobalMessage() diff --git a/src/gui/MessageWidget.h b/src/gui/MessageWidget.h index 0e7d8a307..34c06743c 100644 --- a/src/gui/MessageWidget.h +++ b/src/gui/MessageWidget.h @@ -18,7 +18,7 @@ #ifndef MESSAGEWIDGET_H #define MESSAGEWIDGET_H -#include "gui/kmessagewidget.h" +#include "gui/KMessageWidget.h" class MessageWidget : public KMessageWidget { From bf419d18a98027e5f1ddbeee65fda191286de3dc Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Feb 2017 02:50:46 +0100 Subject: [PATCH 042/333] Update KMessageWidget files in COPYING also [skip ci] --- COPYING | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/COPYING b/COPYING index 0495b912f..4dbaece33 100644 --- a/COPYING +++ b/COPYING @@ -199,8 +199,8 @@ Copyright: 2015, Tony Evans 2016, KeePassXC Team License: BSD 3-clause -Files: src/gui/kmessagewidget.h - src/gui/kmessagewidget.cpp +Files: src/gui/KMessageWidget.h + src/gui/KMessageWidget.cpp Copyright: 2011 Aurélien Gâteau 2014 Dominik Haumann License: LGPL-2.1 From 3821ea2c74c5ca51c5b361ec025996f06bc5fc14 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Fri, 10 Feb 2017 17:06:26 -0500 Subject: [PATCH 043/333] Reorder items. --- src/gui/SettingsWidgetGeneral.ui | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index eb9209777..9e6ea72df 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -103,6 +103,13 @@ + + + + Show a system tray icon + + + @@ -183,13 +190,6 @@
- - - - Show a system tray icon - - -
From 145f64bdfcd2e0ca633f7d05cdf60292d9ef28c7 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Fri, 10 Feb 2017 20:49:53 -0500 Subject: [PATCH 044/333] Regroup minimize window. --- src/gui/SettingsWidgetGeneral.ui | 37 +++++++++++++------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 9e6ea72df..fd5dd0514 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -70,47 +70,54 @@
+ + + Minimize window at application startup + + + + Use group icon on entry creation - + Global Auto-Type shortcut - + - + Use entry title to match windows for global auto-type - + Language - + - + Show a system tray icon - + QLayout::SetMaximumSize @@ -146,7 +153,7 @@ - + QLayout::SetMaximumSize @@ -176,20 +183,6 @@ - - - - QLayout::SetMaximumSize - - - - - Minimize window at application startup - - - - - From f1e7167c65f716dc7500ef509c61405d9f24fbf4 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Fri, 10 Feb 2017 21:33:36 -0500 Subject: [PATCH 045/333] Add spacers to align left. --- src/gui/SettingsWidgetGeneral.ui | 70 +++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index fd5dd0514..400a6ce02 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -84,14 +84,43 @@ - - - Global Auto-Type shortcut - - + + + + + Global Auto-Type shortcut + + + Qt::AlignLeft + + + + + + + + + + Qt::Horizontal + + + + 400 + + + + + + + + - + + + + + @@ -101,11 +130,30 @@ - - - Language - - + + + + + Language + + + + + + + Qt::Horizontal + + + + 400 + + + + + + + + From 7e8679b6c36461be33c40e540cb774b173380578 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Fri, 10 Feb 2017 21:59:33 -0500 Subject: [PATCH 046/333] Hiding tray settings for mac. --- src/gui/SettingsWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 49d9a6968..5696ff121 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -65,6 +65,7 @@ SettingsWidget::SettingsWidget(QWidget* parent) #ifdef Q_OS_MAC // systray not useful on OS X m_generalUi->systrayShowCheckBox->setVisible(false); + m_generalUi->systrayMinimizeOnCloseCheckBox->setVisible(false); m_generalUi->systrayMinimizeToTrayCheckBox->setVisible(false); #endif From a70bf1ffb1869b0f5262b22652f19f8bd2fdeb05 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 11 Feb 2017 11:12:49 +0100 Subject: [PATCH 047/333] fix #203: insert newDatabase icon in ToolBar --- src/gui/MainWindow.ui | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index bb7ff779c..44afe68ac 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -225,6 +225,7 @@ false + From aef17b414c1df685823baede61ff8cddec5025a0 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 11 Feb 2017 12:47:41 +0100 Subject: [PATCH 048/333] add document-new 22x22 icon --- .../application/22x22/actions/document-new.png | Bin 0 -> 1220 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 share/icons/application/22x22/actions/document-new.png diff --git a/share/icons/application/22x22/actions/document-new.png b/share/icons/application/22x22/actions/document-new.png new file mode 100644 index 0000000000000000000000000000000000000000..9ff24e2b287fcdc40d268bd3dad0d10721bb7a7c GIT binary patch literal 1220 zcmV;#1UvhQP)h09_RD}L2TtQoB$#bgMD(3)f>){Hip%p??DozPnG(yuM-{7jjEr<6Uw%%*q2fqa1QWiJ= z>Bq&Po=jpsaS(Y7n2U^^J9gF|KmPIC8QX?oFo>uum>JReD9+qIgLk|3qBGcxRAx%v zsMvNflVM=Dwa7JN8izI2bk#9$&~BB8<%VkRbnk~tbjE*SOgQ37c9gKA`&jxAj}NMwUOi`Rys9uVC4~X z)UOk(vJy?T&Df$2Ntb;9^&m8BD-iZGKq91Emc+(t>3U7=iWT` z==5)=rvZq$L@fv?ga><7wyG?2z1D?|n>Ip3z+5h+eXH=0RV0;kJQ3~v$($8SduqcOaL`$=uvLrlw+I z7$z*M5|L=+W?%2c69c#UBLH9+2KZHvx%bvni7f(*cr;@T!h2nJLi8m|LL`>*Z%~7)-|n| zdOB5JJkv19FbuHwUMz)rMw(rfy|lGT728=CPh-!<`mbO4YJ7Ym1_15tYs;WziWYyST5EX~bP=qyMml5b@X#X{ i02&(`Ff$Whw)Q`I>JGI;k@nI60000 Date: Sat, 11 Feb 2017 13:47:31 +0100 Subject: [PATCH 049/333] fix/add new database KeyShortcut --- src/gui/MainWindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index b9da4e19e..993e28c8f 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -138,13 +138,14 @@ MainWindow::MainWindow() this, SLOT(lockDatabasesAfterInactivity())); applySettingsChanges(); + setShortcut(m_ui->actionDatabaseNew, QKeySequence::New, Qt::CTRL + Qt::Key_N); setShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O); setShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S); setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs); setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W); m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L); setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q); - m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::Key_N); + m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N); m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E); m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D); m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K); From 7ffbcebe4ec1609358276196dd6ed48d601dead0 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 11 Feb 2017 17:05:28 +0100 Subject: [PATCH 050/333] Replace echo -e with printf to achieve better cross-platform compatibility --- release-tool | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/release-tool b/release-tool index af05481b0..3a69691cc 100755 --- a/release-tool +++ b/release-tool @@ -16,8 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -echo -e "\e[1m\e[32mKeePassXC\e[0m Release Preparation Helper" -echo -e "Copyright (C) 2017 KeePassXC Team \n" +printf "\e[1m\e[32mKeePassXC\e[0m Release Preparation Helper\n" +printf "Copyright (C) 2017 KeePassXC Team \n\n" # ----------------------------------------------------------------------- @@ -57,7 +57,7 @@ printUsage() { cmd="COMMAND" fi - echo -e "\e[1mUsage:\e[0m $(basename $0) $cmd [options]" + printf "\e[1mUsage:\e[0m $(basename $0) $cmd [options]\n" if [ "COMMAND" == "$cmd" ]; then cat << EOF @@ -126,11 +126,11 @@ EOF } logInfo() { - echo -e "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1" + printf "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1\n" } logError() { - echo -e "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1" >&2 + printf "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1\n" >&2 } init() { From b367e105fab5d085ffca52c1ad565caf9f8c2599 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 11 Feb 2017 17:08:53 +0100 Subject: [PATCH 051/333] re-implement realpath when it does NOT exist --- release-tool | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-tool b/release-tool index 3a69691cc..7290d9621 100755 --- a/release-tool +++ b/release-tool @@ -246,7 +246,7 @@ checkTransifexCommandExists() { # re-implement realpath for OS X (thanks mschrag) # https://superuser.com/questions/205127/ -if $(command -v realpath > /dev/null); then +if ! $(command -v realpath > /dev/null); then realpath() { pushd . > /dev/null if [ -d "$1" ]; then From 12f62df49a771e64a5d292a18692fed58efec9fe Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 11 Feb 2017 17:19:46 +0100 Subject: [PATCH 052/333] Parse CHANGELOG before checking out target branch --- release-tool | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/release-tool b/release-tool index 7290d9621..0082758e7 100755 --- a/release-tool +++ b/release-tool @@ -347,15 +347,15 @@ merge() { checkReleaseDoesNotExist checkWorkingTreeClean checkSourceBranchExists - checkTargetBranchExists + + logInfo "Checking out source branch '${SOURCE_BRANCH}'..." + git checkout "$SOURCE_BRANCH" + checkVersionInCMake checkChangeLog logInfo "All checks pass, getting our hands dirty now!" - logInfo "Checking out source branch..." - git checkout "$SOURCE_BRANCH" - logInfo "Updating language files..." ./share/translations/update.sh if [ 0 -ne $? ]; then @@ -372,15 +372,15 @@ merge() { fi fi + CHANGELOG=$(grep -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n)=+\n\n?(?:.|\n)+?\n(?=\n)" \ + CHANGELOG | grep -Pzo '(?<=\n\n)(.|\n)+' | tr -d \\0) + COMMIT_MSG="Release ${RELEASE_NAME}" + logInfo "Checking out target branch '${TARGET_BRANCH}'..." git checkout "$TARGET_BRANCH" logInfo "Merging '${SOURCE_BRANCH}' into '${TARGET_BRANCH}'..." - CHANGELOG=$(grep -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n)=+\n\n?(?:.|\n)+?\n(?=\n)" \ - CHANGELOG | grep -Pzo '(?<=\n\n)(.|\n)+' | tr -d \\0) - COMMIT_MSG="Release ${RELEASE_NAME}" - git merge "$SOURCE_BRANCH" --no-ff -m "$COMMIT_MSG" -m "${CHANGELOG}" "$SOURCE_BRANCH" -S"$GPG_GIT_KEY" logInfo "Creating tag '${TAG_NAME}'..." From c33ce8e0e4354d1001c3275870f629c98fe23ecf Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 11 Feb 2017 18:29:50 +0100 Subject: [PATCH 053/333] Add missing target branch check --- release-tool | 1 + 1 file changed, 1 insertion(+) diff --git a/release-tool b/release-tool index 0082758e7..3259e6396 100755 --- a/release-tool +++ b/release-tool @@ -347,6 +347,7 @@ merge() { checkReleaseDoesNotExist checkWorkingTreeClean checkSourceBranchExists + checkTargetBranchExists logInfo "Checking out source branch '${SOURCE_BRANCH}'..." git checkout "$SOURCE_BRANCH" From ee981c4c19a7e9a1fd8ab363c1aed24382047142 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 11 Feb 2017 19:04:37 +0100 Subject: [PATCH 054/333] closes #204. Welcome screen redesign --- src/gui/MainWindow.cpp | 29 ++++++ src/gui/MainWindow.h | 4 + src/gui/MainWindow.ui | 46 ++++++++- src/gui/WelcomeWidget.cpp | 37 +++++++ src/gui/WelcomeWidget.h | 10 ++ src/gui/WelcomeWidget.ui | 198 +++++++++++++++++++++++++++++++++++++- 6 files changed, 318 insertions(+), 6 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 993e28c8f..2f3d7083b 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -277,6 +277,11 @@ MainWindow::MainWindow() connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(switchToPasswordGen(bool))); connect(m_ui->passwordGeneratorWidget, SIGNAL(dialogTerminated()), SLOT(closePasswordGen())); + connect(m_ui->welcomeWidget, SIGNAL(newDatabase()), SLOT(switchToNewDatabase())); + connect(m_ui->welcomeWidget, SIGNAL(openDatabase()), SLOT(switchToOpenDatabase())); + connect(m_ui->welcomeWidget, SIGNAL(openDatabaseFile(QString)), SLOT(switchToDatabaseFile(QString))); + connect(m_ui->welcomeWidget, SIGNAL(importKeePass1Database()), SLOT(switchToKeePass1Database())); + connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog())); connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)), this, SLOT(displayGlobalMessage(QString, MessageWidget::MessageType))); @@ -537,6 +542,30 @@ void MainWindow::closePasswordGen() switchToPasswordGen(false); } +void MainWindow::switchToNewDatabase() +{ + m_ui->tabWidget->newDatabase(); + switchToDatabases(); +} + +void MainWindow::switchToOpenDatabase() +{ + m_ui->tabWidget->openDatabase(); + switchToDatabases(); +} + +void MainWindow::switchToDatabaseFile(QString file) +{ + m_ui->tabWidget->openDatabase(file); + switchToDatabases(); +} + +void MainWindow::switchToKeePass1Database() +{ + m_ui->tabWidget->importKeePass1Database(); + switchToDatabases(); +} + void MainWindow::databaseStatusChanged(DatabaseWidget *) { updateTrayIcon(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 4182140c7..694b38e7a 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -55,6 +55,10 @@ private Q_SLOTS: void switchToDatabases(); void switchToSettings(); void switchToPasswordGen(bool enabled); + void switchToNewDatabase(); + void switchToOpenDatabase(); + void switchToDatabaseFile(QString file); + void switchToKeePass1Database(); void closePasswordGen(); void databaseStatusChanged(DatabaseWidget *dbWidget); void databaseTabChanged(int tabIndex); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 44afe68ac..6e3ecf684 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -105,7 +105,45 @@ - + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 50 + 20 + + + + + + + horizontalSpacer_2 + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 50 + 20 + + + + + @@ -277,9 +315,9 @@ - - Merge from KeePassX database - + + Merge from KeePassX database + diff --git a/src/gui/WelcomeWidget.cpp b/src/gui/WelcomeWidget.cpp index 842546ecc..12a1b6db0 100644 --- a/src/gui/WelcomeWidget.cpp +++ b/src/gui/WelcomeWidget.cpp @@ -18,13 +18,50 @@ #include "WelcomeWidget.h" #include "ui_WelcomeWidget.h" +#include "config-keepassx.h" +#include "core/FilePath.h" +#include "core/Config.h" + WelcomeWidget::WelcomeWidget(QWidget* parent) : QWidget(parent) , m_ui(new Ui::WelcomeWidget()) { m_ui->setupUi(this); + + m_ui->welcomeLabel->setText(m_ui->welcomeLabel->text() + " " + KEEPASSX_VERSION); + QFont welcomeLabelFont = m_ui->welcomeLabel->font(); + welcomeLabelFont.setBold(true); + welcomeLabelFont.setPointSize(welcomeLabelFont.pointSize() + 4); + m_ui->welcomeLabel->setFont(welcomeLabelFont); + + m_ui->iconLabel->setPixmap(filePath()->applicationIcon().pixmap(64)); + + // waiting for CSV PR + m_ui->buttonImportCSV->hide(); + + m_ui->recentListWidget->clear(); + const QStringList lastDatabases = config()->get("LastDatabases", QVariant()).toStringList(); + for (const QString& database : lastDatabases) { + QListWidgetItem *itm = new QListWidgetItem; + itm->setText(database); + m_ui->recentListWidget->addItem(itm); + } + + connect(m_ui->buttonNewDatabase, SIGNAL(clicked()), SIGNAL(newDatabase())); + connect(m_ui->buttonOpenDatabase, SIGNAL(clicked()), SIGNAL(openDatabase())); + connect(m_ui->buttonImportKeePass1, SIGNAL(clicked()), SIGNAL(importKeePass1Database())); + connect(m_ui->recentListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, + SLOT(openDatabaseFromFile(QListWidgetItem*))); } WelcomeWidget::~WelcomeWidget() { } + +void WelcomeWidget::openDatabaseFromFile(QListWidgetItem* item) +{ + if (item->text().isEmpty()) { + return; + } + Q_EMIT openDatabaseFile(item->text()); +} \ No newline at end of file diff --git a/src/gui/WelcomeWidget.h b/src/gui/WelcomeWidget.h index 80a0dde1c..dbd0d2e27 100644 --- a/src/gui/WelcomeWidget.h +++ b/src/gui/WelcomeWidget.h @@ -19,6 +19,7 @@ #define KEEPASSX_WELCOMEWIDGET_H #include +#include namespace Ui { class WelcomeWidget; @@ -32,6 +33,15 @@ public: explicit WelcomeWidget(QWidget* parent = nullptr); ~WelcomeWidget(); +Q_SIGNALS: + void newDatabase(); + void openDatabase(); + void openDatabaseFile(QString); + void importKeePass1Database(); + +private Q_SLOTS: + void openDatabaseFromFile(QListWidgetItem* item); + private: const QScopedPointer m_ui; }; diff --git a/src/gui/WelcomeWidget.ui b/src/gui/WelcomeWidget.ui index 4382e7c77..7591a6038 100644 --- a/src/gui/WelcomeWidget.ui +++ b/src/gui/WelcomeWidget.ui @@ -2,17 +2,211 @@ WelcomeWidget + + + 0 + 0 + 450 + 401 + + + + + 0 + 0 + + + + + 450 + 0 + + - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + - Welcome! + Welcome to KeePassXC Qt::AlignCenter + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + + + 0 + 255 + 0 + + + + + + + + + 0 + 255 + 0 + + + + + + + + + 0 + 255 + 0 + + + + + + + + Create new database + + + + + + + Open existing database + + + + + + + + + Import from KeePass1 + + + + + + + true + + + Import from CSV + + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 0 + 5 + + + + + + + + Recent databases + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + From 5c80c31a702365b3f763ba451cc5dcaea3e3a706 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 11 Feb 2017 23:56:26 +0100 Subject: [PATCH 055/333] hide recent database; fix wording --- src/gui/WelcomeWidget.cpp | 7 ++-- src/gui/WelcomeWidget.ui | 71 +++++++++------------------------------ 2 files changed, 19 insertions(+), 59 deletions(-) diff --git a/src/gui/WelcomeWidget.cpp b/src/gui/WelcomeWidget.cpp index 12a1b6db0..cb7a1de2e 100644 --- a/src/gui/WelcomeWidget.cpp +++ b/src/gui/WelcomeWidget.cpp @@ -36,9 +36,6 @@ WelcomeWidget::WelcomeWidget(QWidget* parent) m_ui->iconLabel->setPixmap(filePath()->applicationIcon().pixmap(64)); - // waiting for CSV PR - m_ui->buttonImportCSV->hide(); - m_ui->recentListWidget->clear(); const QStringList lastDatabases = config()->get("LastDatabases", QVariant()).toStringList(); for (const QString& database : lastDatabases) { @@ -46,6 +43,10 @@ WelcomeWidget::WelcomeWidget(QWidget* parent) itm->setText(database); m_ui->recentListWidget->addItem(itm); } + bool recent_visibility = (m_ui->recentListWidget->count() > 0); + m_ui->startLabel->setVisible(!recent_visibility); + m_ui->recentListWidget->setVisible(recent_visibility); + m_ui->recentLabel->setVisible(recent_visibility); connect(m_ui->buttonNewDatabase, SIGNAL(clicked()), SIGNAL(newDatabase())); connect(m_ui->buttonOpenDatabase, SIGNAL(clicked()), SIGNAL(openDatabase())); diff --git a/src/gui/WelcomeWidget.ui b/src/gui/WelcomeWidget.ui index 7591a6038..8d6a7c49a 100644 --- a/src/gui/WelcomeWidget.ui +++ b/src/gui/WelcomeWidget.ui @@ -80,6 +80,16 @@ + + + + Start storing your passwords securely in a KeePassXC database + + + Qt::AlignCenter + + + @@ -98,43 +108,6 @@ - - - - - - - 0 - 255 - 0 - - - - - - - - - 0 - 255 - 0 - - - - - - - - - 0 - 255 - 0 - - - - - - Create new database @@ -148,25 +121,11 @@ - - - - - Import from KeePass1 - - - - - - - true - - - Import from CSV - - - - + + + Import from KeePass 1 + + From d530c21cd7ca2faa55af07aed8b7a73340e6a29b Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sun, 12 Feb 2017 00:27:33 +0100 Subject: [PATCH 056/333] fix recentListWidget size --- src/gui/WelcomeWidget.ui | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gui/WelcomeWidget.ui b/src/gui/WelcomeWidget.ui index 8d6a7c49a..b08ec917e 100644 --- a/src/gui/WelcomeWidget.ui +++ b/src/gui/WelcomeWidget.ui @@ -7,7 +7,7 @@ 0 0 450 - 401 + 419 @@ -153,7 +153,7 @@ - + 0 0 @@ -164,6 +164,12 @@ 0 + + + 16777215 + 110 + + From 68f033fbbeab9c75c37fca723b9f87eb637a26af Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 15 Feb 2017 16:36:02 -0500 Subject: [PATCH 057/333] Add github-linguist language hints. --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitattributes b/.gitattributes index 9d1ecabf4..9df1af791 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,3 +7,7 @@ src/version.h.cmake export-subst snapcraft.yaml export-ignore make_release.sh export-ignore AppImage-Recipe.sh export-ignore + +# github-linguist language hints +*.h linguist-language=C++ +*.cpp linguist-language=C++ From b10b713e4826c4fb1c694d1289a5849ce1f28714 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 17 Feb 2017 01:03:39 +0100 Subject: [PATCH 058/333] fix/revert new entry shortcut --- src/gui/MainWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 2f3d7083b..779fd6303 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -138,14 +138,14 @@ MainWindow::MainWindow() this, SLOT(lockDatabasesAfterInactivity())); applySettingsChanges(); - setShortcut(m_ui->actionDatabaseNew, QKeySequence::New, Qt::CTRL + Qt::Key_N); + setShortcut(m_ui->actionDatabaseNew, QKeySequence::New, Qt::CTRL + Qt::SHIFT + Qt::Key_N); setShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O); setShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S); setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs); setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W); m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L); setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q); - m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N); + m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::Key_N); m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E); m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D); m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K); From 2ad5e6f06a07f880bdb2f6048e0eacf9c3f78378 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 18 Feb 2017 13:49:55 +0100 Subject: [PATCH 059/333] load a different config with debug option, close #290 --- src/core/Config.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 03b5e4755..d5365d7c1 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -75,7 +75,11 @@ Config::Config(QObject* parent) userPath += "/"; #endif +#ifdef QT_DEBUG + userPath += "keepassxc_debug.ini"; +#else userPath += "keepassxc.ini"; +#endif init(userPath); } From 0c47c2016dea415e3e25dd58e5de97bd2765a75d Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 17 Feb 2017 17:21:27 +0100 Subject: [PATCH 060/333] Only use relative path in DIGEST file --- release-tool | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/release-tool b/release-tool index 3259e6396..a508d79f7 100755 --- a/release-tool +++ b/release-tool @@ -657,7 +657,9 @@ sign() { fi logInfo "Creating digest for file '${f}'..." - sha256sum "$f" > "${f}.DIGEST" + local rp="$(realpath "$f")" + local bname="$(basename "$f")" + (cd "$(dirname "$rp")"; sha256sum "$bname" > "${bname}.DIGEST") done logInfo "All done!" From 7174549441d79f6f7de17caf9253d1fac5eafbb0 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Mon, 20 Feb 2017 20:35:28 +0100 Subject: [PATCH 061/333] Align YubiKey combobox with rest of interface --- src/gui/DatabaseOpenWidget.ui | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui index b3abbb287..01b82d407 100644 --- a/src/gui/DatabaseOpenWidget.ui +++ b/src/gui/DatabaseOpenWidget.ui @@ -7,7 +7,7 @@ 0 0 596 - 250 + 262 @@ -85,7 +85,7 @@ - + 5 @@ -118,7 +118,7 @@ - + 5 @@ -142,7 +142,7 @@ - + false @@ -152,12 +152,22 @@ - - - - false + + + + 5 - + + 5 + + + + + false + + + + From eb23dda99b187871745b274c34f1d1cd15d4ded9 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Mon, 20 Feb 2017 22:07:01 +0100 Subject: [PATCH 062/333] Remember if challenge-response was used for each database and allow to re-detect Yubikeys without closing the database first --- src/gui/DatabaseOpenWidget.cpp | 47 ++++++++++++++++++++++++++++------ src/gui/DatabaseOpenWidget.h | 5 +++- src/gui/DatabaseOpenWidget.ui | 21 ++++++++++++++- src/keys/drivers/YubiKey.cpp | 5 ++-- src/keys/drivers/YubiKey.h | 5 ++++ 5 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index f5d582c60..83488317f 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -67,9 +67,10 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase())); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); - connect(YubiKey::instance(), SIGNAL(detected(int,bool)), - SLOT(ykDetected(int,bool)), - Qt::QueuedConnection); + connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey())); + + connect(YubiKey::instance(), SIGNAL(detected(int,bool)), SLOT(yubikeyDetected(int,bool)), Qt::QueuedConnection); + connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection); #ifdef Q_OS_MACOS // add random padding to layouts to align widgets properly @@ -105,9 +106,8 @@ void DatabaseOpenWidget::load(const QString& filename) m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - /* YubiKey init is slow, detect asynchronously to not block the UI */ m_ui->comboChallengeResponse->clear(); - QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); + pollYubikey(); m_ui->editPassword->setFocus(); } @@ -169,6 +169,7 @@ CompositeKey DatabaseOpenWidget::databaseKey() } QHash lastKeyFiles = config()->get("LastKeyFiles").toHash(); + QHash lastChallengeResponse = config()->get("LastChallengeResponse").toHash(); if (m_ui->checkKeyFile->isChecked()) { FileKey key; @@ -181,13 +182,19 @@ CompositeKey DatabaseOpenWidget::databaseKey() } masterKey.addKey(key); lastKeyFiles[m_filename] = keyFilename; - } - else { + } else { lastKeyFiles.remove(m_filename); } + if (m_ui->checkChallengeResponse->isChecked()) { + lastChallengeResponse[m_filename] = true; + } else { + lastChallengeResponse.remove(m_filename); + } + if (config()->get("RememberLastKeyFiles").toBool()) { config()->set("LastKeyFiles", lastKeyFiles); + config()->set("LastChallengeResponse", lastChallengeResponse); } @@ -240,10 +247,34 @@ void DatabaseOpenWidget::browseKeyFile() } } -void DatabaseOpenWidget::ykDetected(int slot, bool blocking) +void DatabaseOpenWidget::pollYubikey() +{ + // YubiKey init is slow, detect asynchronously to not block the UI + m_ui->buttonRedetectYubikey->setEnabled(false); + m_ui->checkChallengeResponse->setEnabled(false); + m_ui->checkChallengeResponse->setChecked(false); + m_ui->comboChallengeResponse->setEnabled(false); + m_ui->comboChallengeResponse->clear(); + QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); +} + +void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking) { YkChallengeResponseKey yk(slot, blocking); m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant(slot)); m_ui->comboChallengeResponse->setEnabled(true); m_ui->checkChallengeResponse->setEnabled(true); + m_ui->buttonRedetectYubikey->setEnabled(true); + + if (config()->get("RememberLastKeyFiles").toBool()) { + QHash lastChallengeResponse = config()->get("LastChallengeResponse").toHash(); + if (lastChallengeResponse.contains(m_filename)) { + m_ui->checkChallengeResponse->setChecked(true); + } + } +} + +void DatabaseOpenWidget::noYubikeyFound() +{ + m_ui->buttonRedetectYubikey->setEnabled(true); } diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index fadb5ee70..d02346055 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -58,9 +58,12 @@ private Q_SLOTS: void activateChallengeResponse(); void setOkButtonEnabled(); void browseKeyFile(); - void ykDetected(int slot, bool blocking); + void yubikeyDetected(int slot, bool blocking); + void noYubikeyFound(); + void pollYubikey(); protected: + const QScopedPointer m_ui; Database* m_db; QString m_filename; diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui index 01b82d407..f835fc2f6 100644 --- a/src/gui/DatabaseOpenWidget.ui +++ b/src/gui/DatabaseOpenWidget.ui @@ -7,7 +7,7 @@ 0 0 596 - 262 + 264 @@ -165,6 +165,25 @@ false + + + 0 + 0 + + + + false + + + + + + + false + + + Refresh + diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index 5ca175836..20e91237d 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -117,7 +117,6 @@ bool YubiKey::deinit() void YubiKey::detect() { if (init()) { - for (int i = 1; i < 3; i++) { YubiKey::ChallengeResult result; QByteArray rand = randomGen()->randomArray(1); @@ -126,10 +125,12 @@ void YubiKey::detect() result = challenge(i, false, rand, resp); if (result != YubiKey::ERROR) { - Q_EMIT detected(i, result == YubiKey::WOULDBLOCK ? true : false); + emit detected(i, result == YubiKey::WOULDBLOCK ? true : false); + return; } } } + emit notFound(); } /** diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h index 492fba01d..0441e69a7 100644 --- a/src/keys/drivers/YubiKey.h +++ b/src/keys/drivers/YubiKey.h @@ -56,6 +56,11 @@ Q_SIGNALS: */ void detected(int slot, bool blocking); + /** + * Emitted when no Yubikey could be found. + */ + void notFound(); + private: explicit YubiKey(); static YubiKey* m_instance; From c7defdc06f698118569ad687c0c0ae34fa607542 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Mon, 20 Feb 2017 22:41:39 +0100 Subject: [PATCH 063/333] Add redetect button to ChangeMasterKeyWidget and only poll for Yubikeys when the challenge response group is enabled --- src/gui/ChangeMasterKeyWidget.cpp | 50 +++++++++++++++++++++---------- src/gui/ChangeMasterKeyWidget.h | 5 +++- src/gui/ChangeMasterKeyWidget.ui | 30 ++++++++++++------- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 7f0fb0663..bb2b201dc 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -44,9 +44,11 @@ ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) connect(m_ui->createKeyFileButton, SIGNAL(clicked()), SLOT(createKeyFile())); connect(m_ui->browseKeyFileButton, SIGNAL(clicked()), SLOT(browseKeyFile())); - connect(YubiKey::instance(), SIGNAL(detected(int,bool)), - SLOT(ykDetected(int,bool)), - Qt::QueuedConnection); + connect(m_ui->challengeResponseGroup, SIGNAL(clicked(bool)), SLOT(challengeResponseGroupToggled(bool))); + connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey())); + + connect(YubiKey::instance(), SIGNAL(detected(int,bool)), SLOT(yubikeyDetected(int,bool)), Qt::QueuedConnection); + connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection); } ChangeMasterKeyWidget::~ChangeMasterKeyWidget() @@ -89,14 +91,9 @@ void ChangeMasterKeyWidget::clearForms() m_ui->repeatPasswordEdit->setText(""); m_ui->keyFileGroup->setChecked(false); m_ui->togglePasswordButton->setChecked(false); - // TODO: clear m_ui->keyFileCombo m_ui->challengeResponseGroup->setChecked(false); - m_ui->challengeResponseCombo->clear(); - - /* YubiKey init is slow, detect asynchronously to not block the UI */ - m_ui->challengeResponseCombo->clear(); - QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); + m_ui->comboChallengeResponse->clear(); m_ui->enterPasswordEdit->setFocus(); } @@ -146,29 +143,50 @@ void ChangeMasterKeyWidget::generateKey() } if (m_ui->challengeResponseGroup->isChecked()) { - int i = m_ui->challengeResponseCombo->currentIndex(); - i = m_ui->challengeResponseCombo->itemData(i).toInt(); + int i = m_ui->comboChallengeResponse->currentIndex(); + i = m_ui->comboChallengeResponse->itemData(i).toInt(); YkChallengeResponseKey key(i); m_key.addChallengeResponseKey(key); } m_ui->messageWidget->hideMessage(); - Q_EMIT editFinished(true); + emit editFinished(true); } void ChangeMasterKeyWidget::reject() { - Q_EMIT editFinished(false); + emit editFinished(false); } +void ChangeMasterKeyWidget::challengeResponseGroupToggled(bool checked) +{ + if (checked) + pollYubikey(); +} -void ChangeMasterKeyWidget::ykDetected(int slot, bool blocking) +void ChangeMasterKeyWidget::pollYubikey() +{ + m_ui->buttonRedetectYubikey->setEnabled(false); + m_ui->comboChallengeResponse->setEnabled(false); + m_ui->comboChallengeResponse->clear(); + + // YubiKey init is slow, detect asynchronously to not block the UI + QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); +} + +void ChangeMasterKeyWidget::yubikeyDetected(int slot, bool blocking) { YkChallengeResponseKey yk(slot, blocking); - m_ui->challengeResponseCombo->addItem(yk.getName(), QVariant(slot)); - m_ui->challengeResponseGroup->setEnabled(true); + m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant(slot)); + m_ui->comboChallengeResponse->setEnabled(m_ui->challengeResponseGroup->isChecked()); + m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); +} + +void ChangeMasterKeyWidget::noYubikeyFound() +{ + m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); } void ChangeMasterKeyWidget::setCancelEnabled(bool enabled) diff --git a/src/gui/ChangeMasterKeyWidget.h b/src/gui/ChangeMasterKeyWidget.h index cbf67c1f6..0a99037f0 100644 --- a/src/gui/ChangeMasterKeyWidget.h +++ b/src/gui/ChangeMasterKeyWidget.h @@ -48,7 +48,10 @@ private Q_SLOTS: void reject(); void createKeyFile(); void browseKeyFile(); - void ykDetected(int slot, bool blocking); + void yubikeyDetected(int slot, bool blocking); + void noYubikeyFound(); + void challengeResponseGroupToggled(bool checked); + void pollYubikey(); private: const QScopedPointer m_ui; diff --git a/src/gui/ChangeMasterKeyWidget.ui b/src/gui/ChangeMasterKeyWidget.ui index c867e1971..5640364ed 100644 --- a/src/gui/ChangeMasterKeyWidget.ui +++ b/src/gui/ChangeMasterKeyWidget.ui @@ -7,7 +7,7 @@ 0 0 818 - 397 + 424 @@ -90,7 +90,7 @@ - Key file + &Key file true @@ -129,22 +129,32 @@ - false + true - Challenge Response + Cha&llenge Response true - - - - + + true + + - + + + + 0 + 0 + + + + + + - Challenge Response: + Refresh From c49aa6beef446d04966d9b6bddf975722e732065 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Mon, 20 Feb 2017 22:50:12 +0100 Subject: [PATCH 064/333] Show error message when trying to use challenge response without YubiKey --- src/gui/ChangeMasterKeyWidget.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index bb2b201dc..b5a465af5 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -145,6 +145,13 @@ void ChangeMasterKeyWidget::generateKey() if (m_ui->challengeResponseGroup->isChecked()) { int i = m_ui->comboChallengeResponse->currentIndex(); i = m_ui->comboChallengeResponse->itemData(i).toInt(); + + if (0 == i) { + m_ui->messageWidget->showMessage(tr("Changing master key failed: no YubiKey inserted."), + MessageWidget::Error); + return; + } + YkChallengeResponseKey key(i); m_key.addChallengeResponseKey(key); From 5d068dfb2359fa8cecc750aaf792cd65e366e039 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Mon, 20 Feb 2017 23:20:32 +0100 Subject: [PATCH 065/333] Show busy indicator while scanning for YubiKeys --- src/gui/ChangeMasterKeyWidget.cpp | 13 +++++- src/gui/ChangeMasterKeyWidget.ui | 56 +++++++++++++++++++------- src/gui/DatabaseOpenWidget.cpp | 8 ++++ src/gui/DatabaseOpenWidget.ui | 67 +++++++++++++++++++++++-------- 4 files changed, 111 insertions(+), 33 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index b5a465af5..8f9839c97 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -36,11 +36,17 @@ ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) m_ui->messageWidget->setHidden(true); + m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); + m_ui->repeatPasswordEdit->enableVerifyMode(m_ui->enterPasswordEdit); + + m_ui->yubikeyProgress->setVisible(false); + QSizePolicy sp = m_ui->yubikeyProgress->sizePolicy(); + sp.setRetainSizeWhenHidden(true); + m_ui->yubikeyProgress->setSizePolicy(sp); + connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(generateKey())); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); - m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), m_ui->enterPasswordEdit, SLOT(setShowPassword(bool))); - m_ui->repeatPasswordEdit->enableVerifyMode(m_ui->enterPasswordEdit); connect(m_ui->createKeyFileButton, SIGNAL(clicked()), SLOT(createKeyFile())); connect(m_ui->browseKeyFileButton, SIGNAL(clicked()), SLOT(browseKeyFile())); @@ -178,6 +184,7 @@ void ChangeMasterKeyWidget::pollYubikey() m_ui->buttonRedetectYubikey->setEnabled(false); m_ui->comboChallengeResponse->setEnabled(false); m_ui->comboChallengeResponse->clear(); + m_ui->yubikeyProgress->setVisible(true); // YubiKey init is slow, detect asynchronously to not block the UI QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); @@ -189,11 +196,13 @@ void ChangeMasterKeyWidget::yubikeyDetected(int slot, bool blocking) m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant(slot)); m_ui->comboChallengeResponse->setEnabled(m_ui->challengeResponseGroup->isChecked()); m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); + m_ui->yubikeyProgress->setVisible(false); } void ChangeMasterKeyWidget::noYubikeyFound() { m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); + m_ui->yubikeyProgress->setVisible(false); } void ChangeMasterKeyWidget::setCancelEnabled(bool enabled) diff --git a/src/gui/ChangeMasterKeyWidget.ui b/src/gui/ChangeMasterKeyWidget.ui index 5640364ed..693d8ac1d 100644 --- a/src/gui/ChangeMasterKeyWidget.ui +++ b/src/gui/ChangeMasterKeyWidget.ui @@ -7,7 +7,7 @@ 0 0 818 - 424 + 471 @@ -142,21 +142,47 @@ - - - - 0 - 0 - + + + 0 - - - - - - Refresh - - + + + + Refresh + + + + + + + + 0 + 0 + + + + + + + + + 16777215 + 2 + + + + 0 + + + -1 + + + false + + + + diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 83488317f..d9dd78f0a 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -49,6 +49,11 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + m_ui->yubikeyProgress->setVisible(false); + QSizePolicy sp = m_ui->yubikeyProgress->sizePolicy(); + sp.setRetainSizeWhenHidden(true); + m_ui->yubikeyProgress->setSizePolicy(sp); + m_ui->buttonTogglePassword->setIcon(filePath()->onOffIcon("actions", "password-show")); connect(m_ui->buttonTogglePassword, SIGNAL(toggled(bool)), m_ui->editPassword, SLOT(setShowPassword(bool))); @@ -255,6 +260,7 @@ void DatabaseOpenWidget::pollYubikey() m_ui->checkChallengeResponse->setChecked(false); m_ui->comboChallengeResponse->setEnabled(false); m_ui->comboChallengeResponse->clear(); + m_ui->yubikeyProgress->setVisible(true); QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); } @@ -265,6 +271,7 @@ void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking) m_ui->comboChallengeResponse->setEnabled(true); m_ui->checkChallengeResponse->setEnabled(true); m_ui->buttonRedetectYubikey->setEnabled(true); + m_ui->yubikeyProgress->setVisible(false); if (config()->get("RememberLastKeyFiles").toBool()) { QHash lastChallengeResponse = config()->get("LastChallengeResponse").toHash(); @@ -277,4 +284,5 @@ void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking) void DatabaseOpenWidget::noYubikeyFound() { m_ui->buttonRedetectYubikey->setEnabled(true); + m_ui->yubikeyProgress->setVisible(false); } diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui index f835fc2f6..7a97d0083 100644 --- a/src/gui/DatabaseOpenWidget.ui +++ b/src/gui/DatabaseOpenWidget.ui @@ -7,7 +7,7 @@ 0 0 596 - 264 + 302 @@ -142,25 +142,28 @@ - - - - false - - - Challenge Response: - - - - - + + 5 5 - + + 0 + + + + + false + + + Refresh + + + + false @@ -176,13 +179,45 @@ + + + + + 16777215 + 2 + + + + 0 + + + 0 + + + -1 + + + false + + + + + + + + + 0 + + + 2 + - + false - Refresh + Challenge Response: From 2d4e08e060ce18d0040ffcc0c9a2af17e28a81ff Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Mon, 20 Feb 2017 23:35:03 +0100 Subject: [PATCH 066/333] Warn user when no authentication factor was chosen --- src/gui/ChangeMasterKeyWidget.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 8f9839c97..051ff76ae 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -118,12 +118,24 @@ void ChangeMasterKeyWidget::generateKey() { m_key.clear(); + bool anyChecked = m_ui->passwordGroup->isChecked(); + anyChecked |= m_ui->keyFileGroup->isChecked(); + anyChecked |= m_ui->challengeResponseGroup->isChecked(); + + if (!anyChecked) { + if (MessageBox::warning(this, tr("No authentication factor chosen"), + tr("Your database will be completely unprotected!
Do you really want to continue?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + if (m_ui->passwordGroup->isChecked()) { if (m_ui->enterPasswordEdit->text() == m_ui->repeatPasswordEdit->text()) { if (m_ui->enterPasswordEdit->text().isEmpty()) { - if (MessageBox::question(this, tr("Question"), - tr("Do you really want to use an empty string as password?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + if (MessageBox::warning(this, tr("Empty password"), + tr("Do you really want to use an empty string as password?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } } From 8d3e0687a0e54a5fab5866929ae38005277280c2 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 21 Feb 2017 00:28:01 +0100 Subject: [PATCH 067/333] Restructure doc comments and make hard-coded strings translatable --- src/keys/YkChallengeResponseKey.cpp | 9 +++-- src/keys/drivers/YubiKey.cpp | 55 +++++------------------------ src/keys/drivers/YubiKey.h | 45 +++++++++++++++++++---- 3 files changed, 53 insertions(+), 56 deletions(-) diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index 17982ab62..51520e3d4 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -27,6 +27,7 @@ #include "keys/drivers/YubiKey.h" #include +#include YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) @@ -58,10 +59,12 @@ bool YkChallengeResponseKey::challenge(const QByteArray& chal, int retries) return true; } - /* If challenge failed, retry to detect YubiKeys int the event the YubiKey + /* If challenge failed, retry to detect YubiKeys in the event the YubiKey * was un-plugged and re-plugged */ while (retries > 0) { +#ifdef QT_DEBUG qDebug() << "Attempt" << retries << "to re-detect YubiKey(s)"; +#endif retries--; if (YubiKey::instance()->init() != true) { @@ -79,13 +82,13 @@ bool YkChallengeResponseKey::challenge(const QByteArray& chal, int retries) QString YkChallengeResponseKey::getName() const { unsigned int serial; - QString fmt("YubiKey[%1] Challenge Response - Slot %2 - %3"); + QString fmt(QObject::tr("YubiKey[%1] Challenge Response - Slot %2 - %3")); YubiKey::instance()->getSerial(serial); return fmt.arg(QString::number(serial), QString::number(m_slot), - (m_blocking) ? "Press" : "Passive"); + (m_blocking) ? QObject::tr("Press") : QObject::tr("Passive")); } bool YkChallengeResponseKey::isBlocking() const diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index 20e91237d..b287c13b2 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -29,9 +29,8 @@ #include "YubiKey.h" -/* Cast the void pointer from the generalized class definition - * to the proper pointer type from the now included system headers - */ +// Cast the void pointer from the generalized class definition +// to the proper pointer type from the now included system headers #define m_yk (static_cast(m_yk_void)) #define m_ykds (static_cast(m_ykds_void)) @@ -41,10 +40,6 @@ YubiKey::YubiKey() : m_yk_void(NULL), m_ykds_void(NULL) YubiKey* YubiKey::m_instance(Q_NULLPTR); -/** - * @brief YubiKey::instance - get instance of singleton - * @return - */ YubiKey* YubiKey::instance() { if (!m_instance) { @@ -54,20 +49,16 @@ YubiKey* YubiKey::instance() return m_instance; } -/** - * @brief YubiKey::init - initialize yubikey library and hardware - * @return - */ bool YubiKey::init() { - /* Previously initalized */ + // previously initialized if (m_yk != NULL && m_ykds != NULL) { if (yk_get_status(m_yk, m_ykds)) { - /* Still connected */ + // Still connected return true; } else { - /* Initialized but not connected anymore, re-init */ + // Initialized but not connected anymore, re-init deinit(); } } @@ -76,7 +67,7 @@ bool YubiKey::init() return false; } - /* TODO: handle multiple attached hardware devices, currently own one */ + // TODO: handle multiple attached hardware devices m_yk_void = static_cast(yk_open_first_key()); if (m_yk == NULL) { return false; @@ -92,10 +83,6 @@ bool YubiKey::init() return true; } -/** - * @brief YubiKey::deinit - cleanup after init - * @return true on success - */ bool YubiKey::deinit() { if (m_yk) { @@ -111,9 +98,6 @@ bool YubiKey::deinit() return true; } -/** - * @brief YubiKey::detect - probe for attached YubiKeys - */ void YubiKey::detect() { if (init()) { @@ -133,11 +117,6 @@ void YubiKey::detect() emit notFound(); } -/** - * @brief YubiKey::getSerial - serial number of yubikey - * @param serial - * @return - */ bool YubiKey::getSerial(unsigned int& serial) const { if (!yk_get_serial(m_yk, 1, 0, &serial)) { @@ -162,23 +141,7 @@ static inline QString printByteArray(const QByteArray& a) } #endif -/** - * @brief YubiKey::challenge - issue a challenge - * - * This operation could block if the YubiKey requires a touch to trigger. - * - * TODO: Signal to the UI that the system is waiting for challenge response - * touch. - * - * @param slot YubiKey configuration slot - * @param mayBlock operation is allowed to block - * @param chal challenge input to YubiKey - * @param resp response output from YubiKey - * @return SUCCESS when successful - */ -YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, - const QByteArray& chal, - QByteArray& resp) const +YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& chal, QByteArray& resp) const { int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2; QByteArray paddedChal = chal; @@ -188,7 +151,7 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, return ERROR; } - /* yk_challenge_response() insists on 64 byte response buffer */ + // yk_challenge_response() insists on 64 byte response buffer */ resp.resize(64); /* The challenge sent to the yubikey should always be 64 bytes for @@ -239,7 +202,7 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, } } - /* Actual HMAC-SHA1 response is only 20 bytes */ + // actual HMAC-SHA1 response is only 20 bytes resp.resize(20); #ifdef QT_DEBUG diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h index 0441e69a7..acf4feb72 100644 --- a/src/keys/drivers/YubiKey.h +++ b/src/keys/drivers/YubiKey.h @@ -30,21 +30,52 @@ class YubiKey : public QObject public: enum ChallengeResult { ERROR = -1, SUCCESS = 0, WOULDBLOCK }; + /** + * @brief YubiKey::instance - get instance of singleton + * @return instance + */ static YubiKey* instance(); - /** Initialize the underlying yubico libraries */ + /** + * @brief YubiKey::init - initialize yubikey library and hardware + * @return true on success + */ bool init(); + + /** + * @brief YubiKey::deinit - cleanup after init + * @return true on success + */ bool deinit(); - /** Issue a challenge to the hardware */ + /** + * @brief YubiKey::challenge - issue a challenge + * + * This operation could block if the YubiKey requires a touch to trigger. + * + * TODO: Signal to the UI that the system is waiting for challenge response + * touch. + * + * @param slot YubiKey configuration slot + * @param mayBlock operation is allowed to block + * @param chal challenge input to YubiKey + * @param resp response output from YubiKey + * @return true on success + */ ChallengeResult challenge(int slot, bool mayBlock, const QByteArray& chal, QByteArray& resp) const; - /** Read the serial number from the hardware */ + /** + * @brief YubiKey::getSerial - serial number of YubiKey + * @param serial serial number + * @return true on success + */ bool getSerial(unsigned int& serial) const; - /** Start looking for attached hardware devices */ + /** + * @brief YubiKey::detect - probe for attached YubiKeys + */ void detect(); Q_SIGNALS: @@ -65,9 +96,9 @@ private: explicit YubiKey(); static YubiKey* m_instance; - /* Create void ptr here to avoid ifdef header include mess */ - void *m_yk_void; - void *m_ykds_void; + // Create void ptr here to avoid ifdef header include mess + void* m_yk_void; + void* m_ykds_void; Q_DISABLE_COPY(YubiKey) }; From b2650c5a965a9da902dd294a0133f21ca8707ce5 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 21 Feb 2017 01:06:32 +0100 Subject: [PATCH 068/333] Hide UI elements when KeePassXC was compiled without -DWITH_XC_YUBIKEY --- CMakeLists.txt | 5 ++-- src/CMakeLists.txt | 9 +++---- src/gui/ChangeMasterKeyWidget.cpp | 17 +++++++++--- src/gui/DatabaseOpenWidget.cpp | 45 ++++++++++++++++++++----------- src/keys/drivers/YubiKeyStub.cpp | 4 +-- 5 files changed, 50 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c3bf58cc..ecce4f0ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,13 +195,12 @@ if(NOT ZLIB_SUPPORTS_GZIP) endif() # Optional -find_package(YubiKey) +if(WITH_XC_YUBIKEY) + find_package(YubiKey REQUIRED) -if(YUBIKEY_FOUND) include_directories(SYSTEM ${YUBIKEY_INCLUDE_DIRS}) endif() - if(UNIX) check_cxx_source_compiles("#include int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 45c711b73..e27c24928 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -179,7 +179,7 @@ if(MINGW) ${CMAKE_SOURCE_DIR}/share/windows/icon.rc) endif() -if(YUBIKEY_FOUND) +if(WITH_XC_YUBIKEY) set(keepassx_SOURCES ${keepassx_SOURCES} keys/drivers/YubiKey.cpp) else() set(keepassx_SOURCES ${keepassx_SOURCES} keys/drivers/YubiKeyStub.cpp) @@ -209,7 +209,8 @@ target_link_libraries(keepassx_core Qt5::Network ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES} - ${ZLIB_LIBRARIES}) + ${ZLIB_LIBRARIES} + ${YUBIKEY_LIBRARIES}) if (UNIX AND NOT APPLE) target_link_libraries(keepassx_core Qt5::DBus) endif() @@ -234,10 +235,6 @@ endif() add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles}) target_link_libraries(${PROGNAME} keepassx_core) -if(YUBIKEY_FOUND) - target_link_libraries(keepassx_core ${YUBIKEY_LIBRARIES}) -endif() - set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON) if(APPLE) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 051ff76ae..32048e342 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -28,6 +28,8 @@ #include "gui/MessageBox.h" #include "crypto/Random.h" +#include "config-keepassx.h" + ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) : DialogyWidget(parent) , m_ui(new Ui::ChangeMasterKeyWidget()) @@ -50,11 +52,15 @@ ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) connect(m_ui->createKeyFileButton, SIGNAL(clicked()), SLOT(createKeyFile())); connect(m_ui->browseKeyFileButton, SIGNAL(clicked()), SLOT(browseKeyFile())); +#ifdef WITH_XC_YUBIKEY connect(m_ui->challengeResponseGroup, SIGNAL(clicked(bool)), SLOT(challengeResponseGroupToggled(bool))); connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey())); connect(YubiKey::instance(), SIGNAL(detected(int,bool)), SLOT(yubikeyDetected(int,bool)), Qt::QueuedConnection); connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection); +#else + m_ui->challengeResponseGroup->setVisible(false); +#endif } ChangeMasterKeyWidget::~ChangeMasterKeyWidget() @@ -98,8 +104,10 @@ void ChangeMasterKeyWidget::clearForms() m_ui->keyFileGroup->setChecked(false); m_ui->togglePasswordButton->setChecked(false); +#ifdef WITH_XC_YUBIKEY m_ui->challengeResponseGroup->setChecked(false); m_ui->comboChallengeResponse->clear(); +#endif m_ui->enterPasswordEdit->setFocus(); } @@ -118,9 +126,10 @@ void ChangeMasterKeyWidget::generateKey() { m_key.clear(); - bool anyChecked = m_ui->passwordGroup->isChecked(); - anyChecked |= m_ui->keyFileGroup->isChecked(); - anyChecked |= m_ui->challengeResponseGroup->isChecked(); + bool anyChecked = m_ui->passwordGroup->isChecked() | m_ui->keyFileGroup->isChecked(); +#ifdef WITH_XC_YUBIKEY + anyChecked |= m_ui->challengeResponseGroup->isChecked(); +#endif if (!anyChecked) { if (MessageBox::warning(this, tr("No authentication factor chosen"), @@ -160,6 +169,7 @@ void ChangeMasterKeyWidget::generateKey() m_key.addKey(fileKey); } +#ifdef WITH_XC_YUBIKEY if (m_ui->challengeResponseGroup->isChecked()) { int i = m_ui->comboChallengeResponse->currentIndex(); i = m_ui->comboChallengeResponse->itemData(i).toInt(); @@ -174,6 +184,7 @@ void ChangeMasterKeyWidget::generateKey() m_key.addChallengeResponseKey(key); } +#endif m_ui->messageWidget->hideMessage(); emit editFinished(true); diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index d9dd78f0a..bac987c0e 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -29,14 +29,16 @@ #include "format/KeePass2Reader.h" #include "keys/FileKey.h" #include "keys/PasswordKey.h" -#include "keys/YkChallengeResponseKey.h" #include "crypto/Random.h" +#include "keys/YkChallengeResponseKey.h" + +#include "config-keepassx.h" DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) - : DialogyWidget(parent) - , m_ui(new Ui::DatabaseOpenWidget()) - , m_db(nullptr) + : DialogyWidget(parent), + m_ui(new Ui::DatabaseOpenWidget()), + m_db(nullptr) { m_ui->setupUi(this); @@ -49,11 +51,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - m_ui->yubikeyProgress->setVisible(false); - QSizePolicy sp = m_ui->yubikeyProgress->sizePolicy(); - sp.setRetainSizeWhenHidden(true); - m_ui->yubikeyProgress->setSizePolicy(sp); - m_ui->buttonTogglePassword->setIcon(filePath()->onOffIcon("actions", "password-show")); connect(m_ui->buttonTogglePassword, SIGNAL(toggled(bool)), m_ui->editPassword, SLOT(setShowPassword(bool))); @@ -61,21 +58,33 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) connect(m_ui->editPassword, SIGNAL(textChanged(QString)), SLOT(activatePassword())); connect(m_ui->comboKeyFile, SIGNAL(editTextChanged(QString)), SLOT(activateKeyFile())); - connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(activateChallengeResponse())); connect(m_ui->checkPassword, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); connect(m_ui->checkKeyFile, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); connect(m_ui->comboKeyFile, SIGNAL(editTextChanged(QString)), SLOT(setOkButtonEnabled())); - connect(m_ui->checkChallengeResponse, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); - connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(setOkButtonEnabled())); connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase())); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); +#ifdef WITH_XC_YUBIKEY + m_ui->yubikeyProgress->setVisible(false); + QSizePolicy sp = m_ui->yubikeyProgress->sizePolicy(); + sp.setRetainSizeWhenHidden(true); + m_ui->yubikeyProgress->setSizePolicy(sp); + connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey())); + connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(activateChallengeResponse())); + connect(m_ui->checkChallengeResponse, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); + connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(setOkButtonEnabled())); connect(YubiKey::instance(), SIGNAL(detected(int,bool)), SLOT(yubikeyDetected(int,bool)), Qt::QueuedConnection); connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection); +#else + m_ui->checkChallengeResponse->setVisible(false); + m_ui->buttonRedetectYubikey->setVisible(false); + m_ui->comboChallengeResponse->setVisible(false); + m_ui->yubikeyProgress->setVisible(false); +#endif #ifdef Q_OS_MACOS // add random padding to layouts to align widgets properly @@ -109,10 +118,12 @@ void DatabaseOpenWidget::load(const QString& filename) } } - m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - +#ifdef WITH_XC_YUBIKEY m_ui->comboChallengeResponse->clear(); pollYubikey(); +#endif + + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); m_ui->editPassword->setFocus(); } @@ -199,9 +210,12 @@ CompositeKey DatabaseOpenWidget::databaseKey() if (config()->get("RememberLastKeyFiles").toBool()) { config()->set("LastKeyFiles", lastKeyFiles); - config()->set("LastChallengeResponse", lastChallengeResponse); } +#ifdef WITH_XC_YUBIKEY + if (config()->get("RememberLastKeyFiles").toBool()) { + config()->set("LastChallengeResponse", lastChallengeResponse); + } if (m_ui->checkChallengeResponse->isChecked()) { int i = m_ui->comboChallengeResponse->currentIndex(); @@ -210,6 +224,7 @@ CompositeKey DatabaseOpenWidget::databaseKey() masterKey.addChallengeResponseKey(key); } +#endif return masterKey; } diff --git a/src/keys/drivers/YubiKeyStub.cpp b/src/keys/drivers/YubiKeyStub.cpp index c00790f38..e93099bf4 100644 --- a/src/keys/drivers/YubiKeyStub.cpp +++ b/src/keys/drivers/YubiKeyStub.cpp @@ -58,9 +58,7 @@ bool YubiKey::getSerial(unsigned int& serial) const return false; } -YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, - const QByteArray& chal, - QByteArray& resp) const +YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& chal, QByteArray& resp) const { Q_UNUSED(slot); Q_UNUSED(mayBlock); From 8e91a89a3735b3e14597a2537542bacb9f05508d Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 21 Feb 2017 01:28:50 +0100 Subject: [PATCH 069/333] Force at least one encryption key (no more unencrypted databases) --- src/gui/ChangeMasterKeyWidget.cpp | 45 +++++++++++++++++-------------- src/gui/ChangeMasterKeyWidget.h | 7 +++-- src/gui/DatabaseOpenWidget.cpp | 2 -- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 32048e342..8c17ff575 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -41,19 +41,25 @@ ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); m_ui->repeatPasswordEdit->enableVerifyMode(m_ui->enterPasswordEdit); + connect(m_ui->passwordGroup, SIGNAL(clicked(bool)), SLOT(setOkEnabled())); + connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), m_ui->enterPasswordEdit, SLOT(setShowPassword(bool))); + + connect(m_ui->keyFileGroup, SIGNAL(clicked(bool)), SLOT(setOkEnabled())); + connect(m_ui->createKeyFileButton, SIGNAL(clicked()), SLOT(createKeyFile())); + connect(m_ui->browseKeyFileButton, SIGNAL(clicked()), SLOT(browseKeyFile())); + connect(m_ui->keyFileCombo, SIGNAL(editTextChanged(QString)), SLOT(setOkEnabled())); + + connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(generateKey())); + connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); + +#ifdef WITH_XC_YUBIKEY m_ui->yubikeyProgress->setVisible(false); QSizePolicy sp = m_ui->yubikeyProgress->sizePolicy(); sp.setRetainSizeWhenHidden(true); m_ui->yubikeyProgress->setSizePolicy(sp); - connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(generateKey())); - connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); - connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), m_ui->enterPasswordEdit, SLOT(setShowPassword(bool))); - connect(m_ui->createKeyFileButton, SIGNAL(clicked()), SLOT(createKeyFile())); - connect(m_ui->browseKeyFileButton, SIGNAL(clicked()), SLOT(browseKeyFile())); - -#ifdef WITH_XC_YUBIKEY connect(m_ui->challengeResponseGroup, SIGNAL(clicked(bool)), SLOT(challengeResponseGroupToggled(bool))); + connect(m_ui->challengeResponseGroup, SIGNAL(clicked(bool)), SLOT(setOkEnabled())); connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey())); connect(YubiKey::instance(), SIGNAL(detected(int,bool)), SLOT(yubikeyDetected(int,bool)), Qt::QueuedConnection); @@ -126,19 +132,6 @@ void ChangeMasterKeyWidget::generateKey() { m_key.clear(); - bool anyChecked = m_ui->passwordGroup->isChecked() | m_ui->keyFileGroup->isChecked(); -#ifdef WITH_XC_YUBIKEY - anyChecked |= m_ui->challengeResponseGroup->isChecked(); -#endif - - if (!anyChecked) { - if (MessageBox::warning(this, tr("No authentication factor chosen"), - tr("Your database will be completely unprotected!
Do you really want to continue?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - if (m_ui->passwordGroup->isChecked()) { if (m_ui->enterPasswordEdit->text() == m_ui->repeatPasswordEdit->text()) { if (m_ui->enterPasswordEdit->text().isEmpty()) { @@ -208,6 +201,7 @@ void ChangeMasterKeyWidget::pollYubikey() m_ui->comboChallengeResponse->setEnabled(false); m_ui->comboChallengeResponse->clear(); m_ui->yubikeyProgress->setVisible(true); + setOkEnabled(); // YubiKey init is slow, detect asynchronously to not block the UI QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); @@ -220,12 +214,23 @@ void ChangeMasterKeyWidget::yubikeyDetected(int slot, bool blocking) m_ui->comboChallengeResponse->setEnabled(m_ui->challengeResponseGroup->isChecked()); m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); m_ui->yubikeyProgress->setVisible(false); + setOkEnabled(); } void ChangeMasterKeyWidget::noYubikeyFound() { m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); m_ui->yubikeyProgress->setVisible(false); + setOkEnabled(); +} + +void ChangeMasterKeyWidget::setOkEnabled() +{ + bool ok = m_ui->passwordGroup->isChecked() || + (m_ui->challengeResponseGroup->isChecked() && !m_ui->comboChallengeResponse->currentText().isEmpty()) || + (m_ui->keyFileGroup->isChecked() && !m_ui->keyFileCombo->currentText().isEmpty()); + + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ok); } void ChangeMasterKeyWidget::setCancelEnabled(bool enabled) diff --git a/src/gui/ChangeMasterKeyWidget.h b/src/gui/ChangeMasterKeyWidget.h index 0a99037f0..b3e097276 100644 --- a/src/gui/ChangeMasterKeyWidget.h +++ b/src/gui/ChangeMasterKeyWidget.h @@ -38,12 +38,15 @@ public: void clearForms(); CompositeKey newMasterKey(); QLabel* headlineLabel(); + +public slots: + void setOkEnabled(); void setCancelEnabled(bool enabled); -Q_SIGNALS: +signals: void editFinished(bool accepted); -private Q_SLOTS: +private slots: void generateKey(); void reject(); void createKeyFile(); diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index bac987c0e..107304266 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -123,8 +123,6 @@ void DatabaseOpenWidget::load(const QString& filename) pollYubikey(); #endif - m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - m_ui->editPassword->setFocus(); } From 91761a2beab80b408e43b92cecf8e1e167950d27 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 21 Feb 2017 02:19:11 +0100 Subject: [PATCH 070/333] Only poll YubiKey for currently visible tab --- src/gui/DatabaseOpenWidget.cpp | 11 +++++------ src/gui/DatabaseOpenWidget.h | 11 ++++++----- src/gui/DatabaseOpenWidget.ui | 2 +- src/gui/DatabaseTabWidget.cpp | 4 ++-- src/gui/DatabaseTabWidget.h | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 107304266..475d00c72 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -102,6 +102,10 @@ void DatabaseOpenWidget::showEvent(QShowEvent* event) { DialogyWidget::showEvent(event); m_ui->editPassword->setFocus(); + +#ifdef WITH_XC_YUBIKEY + pollYubikey(); +#endif } void DatabaseOpenWidget::load(const QString& filename) @@ -118,11 +122,6 @@ void DatabaseOpenWidget::load(const QString& filename) } } -#ifdef WITH_XC_YUBIKEY - m_ui->comboChallengeResponse->clear(); - pollYubikey(); -#endif - m_ui->editPassword->setFocus(); } @@ -229,7 +228,7 @@ CompositeKey DatabaseOpenWidget::databaseKey() void DatabaseOpenWidget::reject() { - Q_EMIT editFinished(false); + emit editFinished(false); } void DatabaseOpenWidget::activatePassword() diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index d02346055..975a530c4 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -41,18 +41,21 @@ public: void enterKey(const QString& pw, const QString& keyFile); Database* database(); -Q_SIGNALS: +public slots: + void pollYubikey(); + +signals: void editFinished(bool accepted); protected: void showEvent(QShowEvent* event) override; CompositeKey databaseKey(); -protected Q_SLOTS: +protected slots: virtual void openDatabase(); void reject(); -private Q_SLOTS: +private slots: void activatePassword(); void activateKeyFile(); void activateChallengeResponse(); @@ -60,10 +63,8 @@ private Q_SLOTS: void browseKeyFile(); void yubikeyDetected(int slot, bool blocking); void noYubikeyFound(); - void pollYubikey(); protected: - const QScopedPointer m_ui; Database* m_db; QString m_filename; diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui index 7a97d0083..dba71d0fa 100644 --- a/src/gui/DatabaseOpenWidget.ui +++ b/src/gui/DatabaseOpenWidget.ui @@ -156,7 +156,7 @@ - false + true Refresh diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 3a48098dd..734dc3f2e 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -53,7 +53,7 @@ const int DatabaseTabWidget::LastDatabasesCount = 5; DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) : QTabWidget(parent) - , m_dbWidgetSateSync(new DatabaseWidgetStateSync(this)) + , m_dbWidgetStateSync(new DatabaseWidgetStateSync(this)) { DragTabBar* tabBar = new DragTabBar(this); setTabBar(tabBar); @@ -61,7 +61,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabase(int))); connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged())); - connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), m_dbWidgetSateSync, SLOT(setActive(DatabaseWidget*))); + connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*))); connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType())); } diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 8f01a987d..6eabcd49c 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -116,7 +116,7 @@ private: KeePass2Writer m_writer; QHash m_dbList; - DatabaseWidgetStateSync* m_dbWidgetSateSync; + DatabaseWidgetStateSync* m_dbWidgetStateSync; }; #endif // KEEPASSX_DATABASETABWIDGET_H From e93e4a99315cb011bd8794a94ce2d79928c42a42 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 21 Feb 2017 02:40:23 +0100 Subject: [PATCH 071/333] Allow opening of unprotected databases (but don't allow creating them) --- src/gui/DatabaseOpenWidget.cpp | 16 ---------------- src/gui/DatabaseOpenWidget.h | 1 - 2 files changed, 17 deletions(-) diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 475d00c72..1543dc43c 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -49,8 +49,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) font.setPointSize(font.pointSize() + 2); m_ui->labelHeadline->setFont(font); - m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - m_ui->buttonTogglePassword->setIcon(filePath()->onOffIcon("actions", "password-show")); connect(m_ui->buttonTogglePassword, SIGNAL(toggled(bool)), m_ui->editPassword, SLOT(setShowPassword(bool))); @@ -59,10 +57,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) connect(m_ui->editPassword, SIGNAL(textChanged(QString)), SLOT(activatePassword())); connect(m_ui->comboKeyFile, SIGNAL(editTextChanged(QString)), SLOT(activateKeyFile())); - connect(m_ui->checkPassword, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); - connect(m_ui->checkKeyFile, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); - connect(m_ui->comboKeyFile, SIGNAL(editTextChanged(QString)), SLOT(setOkButtonEnabled())); - connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase())); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); @@ -74,8 +68,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey())); connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(activateChallengeResponse())); - connect(m_ui->checkChallengeResponse, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled())); - connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(setOkButtonEnabled())); connect(YubiKey::instance(), SIGNAL(detected(int,bool)), SLOT(yubikeyDetected(int,bool)), Qt::QueuedConnection); connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection); @@ -246,14 +238,6 @@ void DatabaseOpenWidget::activateChallengeResponse() m_ui->checkChallengeResponse->setChecked(true); } -void DatabaseOpenWidget::setOkButtonEnabled() -{ - bool enable = m_ui->checkPassword->isChecked() || m_ui->checkChallengeResponse->isChecked() - || (m_ui->checkKeyFile->isChecked() && !m_ui->comboKeyFile->currentText().isEmpty()); - - m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable); -} - void DatabaseOpenWidget::browseKeyFile() { QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files")); diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index 975a530c4..caba70a61 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -59,7 +59,6 @@ private slots: void activatePassword(); void activateKeyFile(); void activateChallengeResponse(); - void setOkButtonEnabled(); void browseKeyFile(); void yubikeyDetected(int slot, bool blocking); void noYubikeyFound(); From 851c7b891e5824f5e420f9a4052d335b7edf682b Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 21 Feb 2017 21:34:13 +0100 Subject: [PATCH 072/333] Show icons in vertical tab bar TODO: use the correct icons, move vertical tab bar into separate widget --- src/gui/EditWidget.cpp | 39 +++++++++++++++++ src/gui/EditWidget.h | 12 +++++- src/gui/EditWidget.ui | 57 ++++++++++++++++++++++++- src/gui/MainWindow.cpp | 2 +- src/gui/entry/EditEntryWidget_p.h | 69 +++++++++++++++++++++++++------ src/http/OptionDialog.ui | 2 +- 6 files changed, 162 insertions(+), 19 deletions(-) diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index ef29f0132..157848eb9 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -18,6 +18,8 @@ #include "EditWidget.h" #include "ui_EditWidget.h" +#include "core/FilePath.h" + EditWidget::EditWidget(QWidget* parent) : DialogyWidget(parent) , m_ui(new Ui::EditWidget()) @@ -37,6 +39,11 @@ EditWidget::EditWidget(QWidget* parent) connect(m_ui->buttonBox, SIGNAL(accepted()), SIGNAL(accepted())); connect(m_ui->buttonBox, SIGNAL(rejected()), SIGNAL(rejected())); + + connect(m_ui->scrollUp, SIGNAL(clicked()), SLOT(scrollCategoriesUp())); + connect(m_ui->scrollDown, SIGNAL(clicked()), SLOT(scrollCategoriesDown())); + connect(m_ui->categoryList->verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(updateCategoryScrollButtons())); + connect(m_ui->categoryList->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), SLOT(updateCategoryScrollButtons())); } EditWidget::~EditWidget() @@ -46,6 +53,8 @@ EditWidget::~EditWidget() void EditWidget::add(const QString& labelText, QWidget* widget) { m_ui->categoryList->addItem(labelText); + QListWidgetItem* item = m_ui->categoryList->item(m_ui->categoryList->count() - 1); + item->setIcon(FilePath::instance()->icon("apps", "keepassxc")); m_ui->stackedWidget->addWidget(widget); } @@ -100,3 +109,33 @@ void EditWidget::hideMessage() m_ui->messageWidget->animatedHide(); } } + +void EditWidget::updateCategoryScrollButtons() +{ + m_ui->scrollUp->setEnabled(m_ui->categoryList->verticalScrollBar()->value() != 0); + m_ui->scrollDown->setEnabled(m_ui->categoryList->verticalScrollBar()->value() + != m_ui->categoryList->verticalScrollBar()->maximum()); + + m_ui->scrollUp->setVisible(m_ui->categoryList->verticalScrollBar()->maximum() > 0); + m_ui->scrollDown->setVisible(m_ui->scrollUp->isVisible()); +} + +void EditWidget::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + updateCategoryScrollButtons(); +} + +void EditWidget::scrollCategoriesUp() +{ + m_ui->categoryList->verticalScrollBar()->setValue( + m_ui->categoryList->verticalScrollBar()->value() - m_ui->categoryList->verticalScrollBar()->pageStep() + ); +} + +void EditWidget::scrollCategoriesDown() +{ + m_ui->categoryList->verticalScrollBar()->setValue( + m_ui->categoryList->verticalScrollBar()->value() + m_ui->categoryList->verticalScrollBar()->pageStep() + ); +} diff --git a/src/gui/EditWidget.h b/src/gui/EditWidget.h index 6f2c8f2dc..398a04600 100644 --- a/src/gui/EditWidget.h +++ b/src/gui/EditWidget.h @@ -19,6 +19,8 @@ #define KEEPASSX_EDITWIDGET_H #include +#include +#include #include "gui/DialogyWidget.h" #include "gui/MessageWidget.h" @@ -45,11 +47,17 @@ public: void setReadOnly(bool readOnly); bool readOnly() const; -Q_SIGNALS: +protected: + void showEvent(QShowEvent* event) override; + +signals: void accepted(); void rejected(); -protected Q_SLOTS: +protected slots: + void updateCategoryScrollButtons(); + void scrollCategoriesDown(); + void scrollCategoriesUp(); void showMessage(const QString& text, MessageWidget::MessageType type); void hideMessage(); diff --git a/src/gui/EditWidget.ui b/src/gui/EditWidget.ui index 64baf7032..7ab9b143f 100644 --- a/src/gui/EditWidget.ui +++ b/src/gui/EditWidget.ui @@ -40,7 +40,58 @@ - + + + 0 + + + + + + 0 + 0 + + + + + 16777215 + 15 + + + + + + + Qt::UpArrow + + + + + + + + + + + 0 + 0 + + + + + 16777215 + 15 + + + + + + + Qt::DownArrow + + + + @@ -52,7 +103,7 @@ - + 5 @@ -82,6 +133,8 @@ categoryList + scrollUp + scrollDown diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index e9a05e5d1..7225c4c5a 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -62,7 +62,7 @@ class HttpPlugin: public ISettingsPage //delete m_service; } virtual QString name() { - return QObject::tr("Http"); + return QObject::tr("Browser Integration"); } virtual QWidget * createWidget() { OptionDialog * dlg = new OptionDialog(); diff --git a/src/gui/entry/EditEntryWidget_p.h b/src/gui/entry/EditEntryWidget_p.h index cdae8bd3d..34232d25e 100644 --- a/src/gui/entry/EditEntryWidget_p.h +++ b/src/gui/entry/EditEntryWidget_p.h @@ -22,34 +22,73 @@ #include #include #include +#include class CategoryListViewDelegate : public QStyledItemDelegate { public: - explicit CategoryListViewDelegate(QObject* parent) : QStyledItemDelegate(parent) {} + explicit CategoryListViewDelegate(QListView* parent = nullptr) + : QStyledItemDelegate(parent), m_size(96, 96) + {} - QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +protected: + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { - QSize size = QStyledItemDelegate::sizeHint(option, index); - size.setHeight(qMax(size.height(), 22)); - return size; + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + painter->save(); + + QIcon icon = opt.icon; + QSize iconSize = opt.icon.actualSize(QSize(32, 32)); + opt.icon = QIcon(); + opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignVCenter; + opt.decorationPosition = QStyleOptionViewItem::Top; + + QStyle* style = opt.widget ? opt.widget->style() : QApplication::style(); + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); + + QRect fontRect = painter->fontMetrics().boundingRect( + QRect(0, 0, m_size.width(), m_size.height()), Qt::AlignHCenter | Qt::AlignBottom | Qt::TextWordWrap, opt.text); + + int paddingTop = fontRect.height() < 30 ? 15 : 10; + int left = opt.rect.left() + opt.rect.width() / 2 - iconSize.width() / 2; + painter->drawPixmap(left, opt.rect.top() + paddingTop, icon.pixmap(iconSize)); + + painter->restore(); } + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + Q_UNUSED(option); + Q_UNUSED(index); + return m_size; + } + +private: + QSize m_size; }; class CategoryListWidget : public QListWidget { public: - explicit CategoryListWidget(QWidget* parent = 0) : QListWidget(parent) + explicit CategoryListWidget(QWidget* parent = 0) + : QListWidget(parent) { - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); setItemDelegate(new CategoryListViewDelegate(this)); + setMovement(QListView::Static); + setViewMode(QListWidget::IconMode); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setWordWrap(true); } - - virtual QSize sizeHint() const +protected: + QSize sizeHint() const override { QSize sizeHint = QListWidget::sizeHint(); - int width = sizeHintForColumn(0) + frameWidth() * 2 + 5; + int width = sizeHintForColumn(0) + frameWidth() * 2; if (verticalScrollBar()->isVisible()) { width += verticalScrollBar()->width(); } @@ -57,6 +96,11 @@ public: return sizeHint; } + + QSize minimumSizeHint() const override + { + return QSize(sizeHint().width(), sizeHintForRow(0) * 2); + } }; class AttributesListView : public QListView @@ -65,14 +109,13 @@ public: explicit AttributesListView(QWidget* parent = 0) : QListView(parent) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - setItemDelegate(new CategoryListViewDelegate(this)); } - virtual QSize sizeHint() const + QSize sizeHint() const override { QSize sizeHint = QListView::sizeHint(); - int width = sizeHintForColumn(0) + frameWidth() * 2 + 5; + int width = sizeHintForColumn(0) + frameWidth() * 2; if (verticalScrollBar()->isVisible()) { width += verticalScrollBar()->width(); } diff --git a/src/http/OptionDialog.ui b/src/http/OptionDialog.ui index ab8047db4..d55ceecdf 100644 --- a/src/http/OptionDialog.ui +++ b/src/http/OptionDialog.ui @@ -148,7 +148,7 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< color: rgb(255, 0, 0); - Activate the following only, if you know what you are doing! + Activate the following only if you know what you are doing! From cee297b218c9fd6ab1ed4560e31f3cc52ef33705 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 01:05:24 +0100 Subject: [PATCH 073/333] Move category tab widgets to separate widget and hide history category when there is no history --- src/CMakeLists.txt | 2 + src/gui/CategoryListWidget.cpp | 173 ++++++++++++++++++++++++++++++ src/gui/CategoryListWidget.h | 79 ++++++++++++++ src/gui/CategoryListWidget.ui | 116 ++++++++++++++++++++ src/gui/EditWidget.cpp | 64 ++++------- src/gui/EditWidget.h | 12 +-- src/gui/EditWidget.ui | 60 +---------- src/gui/SettingsWidget.cpp | 9 +- src/gui/entry/EditEntryWidget.cpp | 16 +-- src/gui/entry/EditEntryWidget_p.h | 84 +-------------- src/gui/group/EditGroupWidget.cpp | 9 +- 11 files changed, 412 insertions(+), 212 deletions(-) create mode 100644 src/gui/CategoryListWidget.cpp create mode 100644 src/gui/CategoryListWidget.h create mode 100644 src/gui/CategoryListWidget.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ca9be92af..0c539b50d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,6 +70,7 @@ set(keepassx_SOURCES format/KeePass2XmlWriter.cpp gui/AboutDialog.cpp gui/Application.cpp + gui/CategoryListWidget.cpp gui/ChangeMasterKeyWidget.cpp gui/Clipboard.cpp gui/DatabaseOpenWidget.cpp @@ -132,6 +133,7 @@ set(keepassx_FORMS gui/ChangeMasterKeyWidget.ui gui/DatabaseOpenWidget.ui gui/DatabaseSettingsWidget.ui + gui/CategoryListWidget.ui gui/EditWidget.ui gui/EditWidgetIcons.ui gui/EditWidgetProperties.ui diff --git a/src/gui/CategoryListWidget.cpp b/src/gui/CategoryListWidget.cpp new file mode 100644 index 000000000..758709c0b --- /dev/null +++ b/src/gui/CategoryListWidget.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CategoryListWidget.h" +#include "ui_CategoryListWidget.h" + +#include +#include +#include +#include +#include + +CategoryListWidget::CategoryListWidget(QWidget* parent) + : QWidget(parent), + m_ui(new Ui::CategoryListWidget()) +{ + m_ui->setupUi(this); + m_ui->categoryList->setItemDelegate(new CategoryListWidgetDelegate(this)); + + connect(m_ui->categoryList, SIGNAL(currentRowChanged(int)), SLOT(emitCategoryChanged(int))); + + connect(m_ui->scrollUp, SIGNAL(clicked()), SLOT(scrollCategoriesUp())); + connect(m_ui->scrollDown, SIGNAL(clicked()), SLOT(scrollCategoriesDown())); + connect(m_ui->categoryList->verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(updateCategoryScrollButtons())); + connect(m_ui->categoryList->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), SLOT(updateCategoryScrollButtons())); +} + +CategoryListWidget::~CategoryListWidget() +{ +} + +QSize CategoryListWidget::sizeHint() const +{ + QSize sizeHint = QWidget::sizeHint(); + + int width = m_ui->categoryList->sizeHintForColumn(0) + m_ui->categoryList->frameWidth() * 2; + if (m_ui->categoryList->verticalScrollBar()->isVisible()) { + width += m_ui->categoryList->verticalScrollBar()->width(); + } + sizeHint.setWidth(width); + + return sizeHint; +} + +QSize CategoryListWidget::minimumSizeHint() const +{ + return QSize(sizeHint().width(), m_ui->categoryList->sizeHintForRow(0) * 2); +} + +int CategoryListWidget::addCategory(const QString& labelText, const QIcon& icon) +{ + QListWidgetItem* item = new QListWidgetItem(m_ui->categoryList); + item->setText(labelText); + item->setIcon(icon); + m_ui->categoryList->addItem(item); + return m_ui->categoryList->count() - 1; +} + +void CategoryListWidget::removeCategory(int index) +{ + m_ui->categoryList->removeItemWidget(m_ui->categoryList->item(index)); +} + +int CategoryListWidget::currentCategory() +{ + return m_ui->categoryList->currentRow(); +} + +void CategoryListWidget::setCurrentCategory(int index) +{ + m_ui->categoryList->setCurrentRow(index); +} + +void CategoryListWidget::setCategoryHidden(int index, bool hidden) +{ + m_ui->categoryList->item(index)->setHidden(hidden); +} + +bool CategoryListWidget::isCategoryHidden(int index) +{ + return m_ui->categoryList->item(index)->isHidden(); +} + +void CategoryListWidget::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + updateCategoryScrollButtons(); +} + +void CategoryListWidget::updateCategoryScrollButtons() +{ + m_ui->scrollUp->setEnabled(m_ui->categoryList->verticalScrollBar()->value() != 0); + m_ui->scrollDown->setEnabled(m_ui->categoryList->verticalScrollBar()->value() + != m_ui->categoryList->verticalScrollBar()->maximum()); + + m_ui->scrollUp->setVisible(m_ui->categoryList->verticalScrollBar()->maximum() > 0); + m_ui->scrollDown->setVisible(m_ui->scrollUp->isVisible()); +} + +void CategoryListWidget::scrollCategoriesUp() +{ + m_ui->categoryList->verticalScrollBar()->setValue( + m_ui->categoryList->verticalScrollBar()->value() - m_ui->categoryList->verticalScrollBar()->pageStep() + ); +} + +void CategoryListWidget::scrollCategoriesDown() +{ + m_ui->categoryList->verticalScrollBar()->setValue( + m_ui->categoryList->verticalScrollBar()->value() + m_ui->categoryList->verticalScrollBar()->pageStep() + ); +} + +void CategoryListWidget::emitCategoryChanged(int index) +{ + emit categoryChanged(index); +} + + +/* =============================================================================================== */ + + +CategoryListWidgetDelegate::CategoryListWidgetDelegate(QWidget* parent) + : QStyledItemDelegate(parent), + m_size(96, 96) +{} + +void CategoryListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + painter->save(); + + QIcon icon = opt.icon; + QSize iconSize = opt.icon.actualSize(QSize(32, 32)); + opt.icon = QIcon(); + opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignVCenter; + opt.decorationPosition = QStyleOptionViewItem::Top; + + QStyle* style = opt.widget ? opt.widget->style() : QApplication::style(); + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); + + QRect fontRect = painter->fontMetrics().boundingRect( + QRect(0, 0, m_size.width(), m_size.height()), Qt::AlignHCenter | Qt::AlignBottom | Qt::TextWordWrap, opt.text); + + int paddingTop = fontRect.height() < 30 ? 15 : 10; + int left = opt.rect.left() + opt.rect.width() / 2 - iconSize.width() / 2; + painter->drawPixmap(left, opt.rect.top() + paddingTop, icon.pixmap(iconSize)); + + painter->restore(); +} + +QSize CategoryListWidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(option); + Q_UNUSED(index); + return m_size; +} diff --git a/src/gui/CategoryListWidget.h b/src/gui/CategoryListWidget.h new file mode 100644 index 000000000..de910fdf3 --- /dev/null +++ b/src/gui/CategoryListWidget.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +namespace Ui { + class CategoryListWidget; +} + +class CategoryListWidget : public QWidget +{ + Q_OBJECT + +public: + CategoryListWidget(QWidget* parent = 0); + ~CategoryListWidget(); + + int currentCategory(); + void setCurrentCategory(int index); + int addCategory(const QString& labelText, const QIcon& icon); + void setCategoryHidden(int index, bool hidden); + bool isCategoryHidden(int index); + void removeCategory(int index); + +signals: + void categoryChanged(int index); + +protected: + void showEvent(QShowEvent* event) override; + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + +protected slots: + void updateCategoryScrollButtons(); + void scrollCategoriesDown(); + void scrollCategoriesUp(); + void emitCategoryChanged(int index); + +private: + const QScopedPointer m_ui; + + Q_DISABLE_COPY(CategoryListWidget) +}; + + +/* =============================================================================================== */ + + +class CategoryListWidgetDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit CategoryListWidgetDelegate(QWidget* parent = nullptr); + +protected: + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +private: + QSize m_size; + + Q_DISABLE_COPY(CategoryListWidgetDelegate) +}; diff --git a/src/gui/CategoryListWidget.ui b/src/gui/CategoryListWidget.ui new file mode 100644 index 000000000..307a039b6 --- /dev/null +++ b/src/gui/CategoryListWidget.ui @@ -0,0 +1,116 @@ + + + CategoryListWidget + + + + 0 + 0 + 182 + 418 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 16777215 + 15 + + + + + + + Qt::UpArrow + + + + + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QListView::Static + + + QListView::TopToBottom + + + false + + + QListView::IconMode + + + true + + + true + + + + + + + + 0 + 0 + + + + + 16777215 + 15 + + + + + + + Qt::DownArrow + + + + + + + categoryList + scrollUp + scrollDown + + + + diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index 157848eb9..24cc64fec 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -34,41 +34,43 @@ EditWidget::EditWidget(QWidget* parent) headerLabelFont.setPointSize(headerLabelFont.pointSize() + 2); headlineLabel()->setFont(headerLabelFont); - connect(m_ui->categoryList, SIGNAL(currentRowChanged(int)), + connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int))); connect(m_ui->buttonBox, SIGNAL(accepted()), SIGNAL(accepted())); connect(m_ui->buttonBox, SIGNAL(rejected()), SIGNAL(rejected())); - - connect(m_ui->scrollUp, SIGNAL(clicked()), SLOT(scrollCategoriesUp())); - connect(m_ui->scrollDown, SIGNAL(clicked()), SLOT(scrollCategoriesDown())); - connect(m_ui->categoryList->verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(updateCategoryScrollButtons())); - connect(m_ui->categoryList->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), SLOT(updateCategoryScrollButtons())); } EditWidget::~EditWidget() { } -void EditWidget::add(const QString& labelText, QWidget* widget) +void EditWidget::addPage(const QString& labelText, const QIcon& icon, QWidget* widget) { - m_ui->categoryList->addItem(labelText); - QListWidgetItem* item = m_ui->categoryList->item(m_ui->categoryList->count() - 1); - item->setIcon(FilePath::instance()->icon("apps", "keepassxc")); m_ui->stackedWidget->addWidget(widget); + m_ui->categoryList->addCategory(labelText, icon); } -void EditWidget::setRowHidden(QWidget* widget, bool hide) +void EditWidget::setPageHidden(QWidget* widget, bool hidden) { - int row = m_ui->stackedWidget->indexOf(widget); - if (row != -1) { - m_ui->categoryList->item(row)->setHidden(hide); + int index = m_ui->stackedWidget->indexOf(widget); + if (index != -1) { + m_ui->categoryList->setCategoryHidden(index, hidden); + } + + if (index == m_ui->stackedWidget->currentIndex()) { + int newIndex = m_ui->stackedWidget->currentIndex() - 1; + if (newIndex < 0) { + newIndex = m_ui->stackedWidget->count() - 1; + } + m_ui->stackedWidget->setCurrentIndex(newIndex); } } -void EditWidget::setCurrentRow(int index) +void EditWidget::setCurrentPage(int index) { - m_ui->categoryList->setCurrentRow(index); + m_ui->categoryList->setCurrentCategory(index); + m_ui->stackedWidget->setCurrentIndex(index); } void EditWidget::setHeadline(const QString& text) @@ -109,33 +111,3 @@ void EditWidget::hideMessage() m_ui->messageWidget->animatedHide(); } } - -void EditWidget::updateCategoryScrollButtons() -{ - m_ui->scrollUp->setEnabled(m_ui->categoryList->verticalScrollBar()->value() != 0); - m_ui->scrollDown->setEnabled(m_ui->categoryList->verticalScrollBar()->value() - != m_ui->categoryList->verticalScrollBar()->maximum()); - - m_ui->scrollUp->setVisible(m_ui->categoryList->verticalScrollBar()->maximum() > 0); - m_ui->scrollDown->setVisible(m_ui->scrollUp->isVisible()); -} - -void EditWidget::showEvent(QShowEvent* event) -{ - QWidget::showEvent(event); - updateCategoryScrollButtons(); -} - -void EditWidget::scrollCategoriesUp() -{ - m_ui->categoryList->verticalScrollBar()->setValue( - m_ui->categoryList->verticalScrollBar()->value() - m_ui->categoryList->verticalScrollBar()->pageStep() - ); -} - -void EditWidget::scrollCategoriesDown() -{ - m_ui->categoryList->verticalScrollBar()->setValue( - m_ui->categoryList->verticalScrollBar()->value() + m_ui->categoryList->verticalScrollBar()->pageStep() - ); -} diff --git a/src/gui/EditWidget.h b/src/gui/EditWidget.h index 398a04600..46951de41 100644 --- a/src/gui/EditWidget.h +++ b/src/gui/EditWidget.h @@ -39,25 +39,19 @@ public: explicit EditWidget(QWidget* parent = nullptr); ~EditWidget(); - void add(const QString& labelText, QWidget* widget); - void setRowHidden(QWidget* widget, bool hide); - void setCurrentRow(int index); + void addPage(const QString& labelText, const QIcon& icon, QWidget* widget); + void setPageHidden(QWidget* widget, bool hidden); + void setCurrentPage(int index); void setHeadline(const QString& text); QLabel* headlineLabel(); void setReadOnly(bool readOnly); bool readOnly() const; -protected: - void showEvent(QShowEvent* event) override; - signals: void accepted(); void rejected(); protected slots: - void updateCategoryScrollButtons(); - void scrollCategoriesDown(); - void scrollCategoriesUp(); void showMessage(const QString& text, MessageWidget::MessageType type); void hideMessage(); diff --git a/src/gui/EditWidget.ui b/src/gui/EditWidget.ui index 7ab9b143f..239c3d9f8 100644 --- a/src/gui/EditWidget.ui +++ b/src/gui/EditWidget.ui @@ -40,58 +40,7 @@ - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 15 - - - - - - - Qt::UpArrow - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 15 - - - - - - - Qt::DownArrow - - - - + @@ -128,14 +77,9 @@ CategoryListWidget QListWidget -
gui/entry/EditEntryWidget_p.h
+
gui/CategoryListWidget.h
- - categoryList - scrollUp - scrollDown - diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 5696ff121..ef153c123 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -22,6 +22,7 @@ #include "autotype/AutoType.h" #include "core/Config.h" #include "core/Translator.h" +#include "core/FilePath.h" class SettingsWidget::ExtraPage { @@ -57,8 +58,8 @@ SettingsWidget::SettingsWidget(QWidget* parent) m_secUi->setupUi(m_secWidget); m_generalUi->setupUi(m_generalWidget); - add(tr("General"), m_generalWidget); - add(tr("Security"), m_secWidget); + addPage(tr("General"), FilePath::instance()->icon("apps", "keepassxc"), m_generalWidget); + addPage(tr("Security"), FilePath::instance()->icon("apps", "keepassxc"), m_secWidget); m_generalUi->autoTypeShortcutWidget->setVisible(autoType()->isAvailable()); m_generalUi->autoTypeShortcutLabel->setVisible(autoType()->isAvailable()); @@ -92,7 +93,7 @@ void SettingsWidget::addSettingsPage(ISettingsPage *page) QWidget * widget = page->createWidget(); widget->setParent(this); m_extraPages.append(ExtraPage(page, widget)); - add(page->name(), widget); + addPage(page->name(), FilePath::instance()->icon("apps", "keepassxc"), widget); } void SettingsWidget::loadSettings() @@ -146,7 +147,7 @@ void SettingsWidget::loadSettings() Q_FOREACH (const ExtraPage& page, m_extraPages) page.loadSettings(); - setCurrentRow(0); + setCurrentPage(0); } void SettingsWidget::saveSettings() diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 4acfd8765..ff75540af 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -90,7 +90,7 @@ EditEntryWidget::~EditEntryWidget() void EditEntryWidget::setupMain() { m_mainUi->setupUi(m_mainWidget); - add(tr("Entry"), m_mainWidget); + addPage(tr("Entry"), FilePath::instance()->icon("apps", "keepassxc"), m_mainWidget); m_mainUi->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); m_mainUi->togglePasswordGeneratorButton->setIcon(filePath()->icon("actions", "password-generator", false)); @@ -115,7 +115,7 @@ void EditEntryWidget::setupMain() void EditEntryWidget::setupAdvanced() { m_advancedUi->setupUi(m_advancedWidget); - add(tr("Advanced"), m_advancedWidget); + addPage(tr("Advanced"), FilePath::instance()->icon("apps", "keepassxc"), m_advancedWidget); m_attachmentsModel->setEntryAttachments(m_entryAttachments); m_advancedUi->attachmentsView->setModel(m_attachmentsModel); @@ -139,13 +139,13 @@ void EditEntryWidget::setupAdvanced() void EditEntryWidget::setupIcon() { - add(tr("Icon"), m_iconsWidget); + addPage(tr("Icon"), FilePath::instance()->icon("apps", "keepassxc"), m_iconsWidget); } void EditEntryWidget::setupAutoType() { m_autoTypeUi->setupUi(m_autoTypeWidget); - add(tr("Auto-Type"), m_autoTypeWidget); + addPage(tr("Auto-Type"), FilePath::instance()->icon("apps", "keepassxc"), m_autoTypeWidget); m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->inheritSequenceButton); m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->customSequenceButton); @@ -177,13 +177,13 @@ void EditEntryWidget::setupAutoType() void EditEntryWidget::setupProperties() { - add(tr("Properties"), m_editWidgetProperties); + addPage(tr("Properties"), FilePath::instance()->icon("apps", "keepassxc"), m_editWidgetProperties); } void EditEntryWidget::setupHistory() { m_historyUi->setupUi(m_historyWidget); - add(tr("History"), m_historyWidget); + addPage(tr("History"), FilePath::instance()->icon("apps", "keepassxc"), m_historyWidget); m_sortModel->setSourceModel(m_historyModel); m_sortModel->setDynamicSortFilter(true); @@ -291,8 +291,8 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q setForms(entry); setReadOnly(m_history); - setCurrentRow(0); - setRowHidden(m_historyWidget, m_history); + setCurrentPage(0); + setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1); } void EditEntryWidget::setForms(const Entry* entry, bool restore) diff --git a/src/gui/entry/EditEntryWidget_p.h b/src/gui/entry/EditEntryWidget_p.h index 34232d25e..0e37c1fe8 100644 --- a/src/gui/entry/EditEntryWidget_p.h +++ b/src/gui/entry/EditEntryWidget_p.h @@ -18,90 +18,8 @@ #ifndef KEEPASSX_EDITENTRYWIDGET_P_H #define KEEPASSX_EDITENTRYWIDGET_P_H -#include +#include #include -#include -#include -#include - -class CategoryListViewDelegate : public QStyledItemDelegate -{ -public: - explicit CategoryListViewDelegate(QListView* parent = nullptr) - : QStyledItemDelegate(parent), m_size(96, 96) - {} - -protected: - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override - { - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - - painter->save(); - - QIcon icon = opt.icon; - QSize iconSize = opt.icon.actualSize(QSize(32, 32)); - opt.icon = QIcon(); - opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignVCenter; - opt.decorationPosition = QStyleOptionViewItem::Top; - - QStyle* style = opt.widget ? opt.widget->style() : QApplication::style(); - style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); - - QRect fontRect = painter->fontMetrics().boundingRect( - QRect(0, 0, m_size.width(), m_size.height()), Qt::AlignHCenter | Qt::AlignBottom | Qt::TextWordWrap, opt.text); - - int paddingTop = fontRect.height() < 30 ? 15 : 10; - int left = opt.rect.left() + opt.rect.width() / 2 - iconSize.width() / 2; - painter->drawPixmap(left, opt.rect.top() + paddingTop, icon.pixmap(iconSize)); - - painter->restore(); - } - - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override - { - Q_UNUSED(option); - Q_UNUSED(index); - return m_size; - } - -private: - QSize m_size; -}; - -class CategoryListWidget : public QListWidget -{ -public: - explicit CategoryListWidget(QWidget* parent = 0) - : QListWidget(parent) - { - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - setItemDelegate(new CategoryListViewDelegate(this)); - setMovement(QListView::Static); - setViewMode(QListWidget::IconMode); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setWordWrap(true); - } -protected: - QSize sizeHint() const override - { - QSize sizeHint = QListWidget::sizeHint(); - - int width = sizeHintForColumn(0) + frameWidth() * 2; - if (verticalScrollBar()->isVisible()) { - width += verticalScrollBar()->width(); - } - sizeHint.setWidth(width); - - return sizeHint; - } - - QSize minimumSizeHint() const override - { - return QSize(sizeHint().width(), sizeHintForRow(0) * 2); - } -}; class AttributesListView : public QListView { diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp index 5b9dfcbc8..23e2108b5 100644 --- a/src/gui/group/EditGroupWidget.cpp +++ b/src/gui/group/EditGroupWidget.cpp @@ -19,6 +19,7 @@ #include "ui_EditGroupWidgetMain.h" #include "core/Metadata.h" +#include "core/FilePath.h" #include "gui/EditWidgetIcons.h" #include "gui/EditWidgetProperties.h" @@ -33,9 +34,9 @@ EditGroupWidget::EditGroupWidget(QWidget* parent) { m_mainUi->setupUi(m_editGroupWidgetMain); - add(tr("Group"), m_editGroupWidgetMain); - add(tr("Icon"), m_editGroupWidgetIcons); - add(tr("Properties"), m_editWidgetProperties); + addPage(tr("Group"), FilePath::instance()->icon("apps", "keepassxc"), m_editGroupWidgetMain); + addPage(tr("Icon"), FilePath::instance()->icon("apps", "keepassxc"), m_editGroupWidgetIcons); + addPage(tr("Properties"), FilePath::instance()->icon("apps", "keepassxc"), m_editWidgetProperties); connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool))); connect(m_mainUi->autoTypeSequenceCustomRadio, SIGNAL(toggled(bool)), @@ -94,7 +95,7 @@ void EditGroupWidget::loadGroup(Group* group, bool create, Database* database) m_editWidgetProperties->setFields(group->timeInfo(), group->uuid()); - setCurrentRow(0); + setCurrentPage(0); m_mainUi->editName->setFocus(); } From 74afd8e81965c0cc692f26550086c1fddc382e86 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 03:39:13 +0100 Subject: [PATCH 074/333] Make widget scalable and set minimum width based on the widget text --- src/gui/CategoryListWidget.cpp | 66 +++++++++++++++++++++++++++++----- src/gui/CategoryListWidget.h | 13 ++++++- src/gui/CategoryListWidget.ui | 12 +++++-- src/gui/EditWidget.ui | 14 ++++++-- src/http/OptionDialog.ui | 3 -- 5 files changed, 89 insertions(+), 19 deletions(-) diff --git a/src/gui/CategoryListWidget.cpp b/src/gui/CategoryListWidget.cpp index 758709c0b..1c3c7dcce 100644 --- a/src/gui/CategoryListWidget.cpp +++ b/src/gui/CategoryListWidget.cpp @@ -26,10 +26,12 @@ CategoryListWidget::CategoryListWidget(QWidget* parent) : QWidget(parent), + m_itemDelegate(nullptr), m_ui(new Ui::CategoryListWidget()) { m_ui->setupUi(this); - m_ui->categoryList->setItemDelegate(new CategoryListWidgetDelegate(this)); + m_itemDelegate = new CategoryListWidgetDelegate(m_ui->categoryList); + m_ui->categoryList->setItemDelegate(m_itemDelegate); connect(m_ui->categoryList, SIGNAL(currentRowChanged(int)), SLOT(emitCategoryChanged(int))); @@ -47,10 +49,16 @@ QSize CategoryListWidget::sizeHint() const { QSize sizeHint = QWidget::sizeHint(); - int width = m_ui->categoryList->sizeHintForColumn(0) + m_ui->categoryList->frameWidth() * 2; + int width = m_ui->categoryList->sizeHintForColumn(0); if (m_ui->categoryList->verticalScrollBar()->isVisible()) { width += m_ui->categoryList->verticalScrollBar()->width(); } + + QSize min = minimumSizeHint(); + if (width < min.width()) { + width = min.width(); + } + width += m_ui->categoryList->frameWidth() * 2; sizeHint.setWidth(width); return sizeHint; @@ -58,7 +66,8 @@ QSize CategoryListWidget::sizeHint() const QSize CategoryListWidget::minimumSizeHint() const { - return QSize(sizeHint().width(), m_ui->categoryList->sizeHintForRow(0) * 2); + return QSize(m_ui->categoryList->sizeHintForColumn(0) + m_ui->categoryList->frameWidth() * 2, + m_ui->categoryList->sizeHintForRow(0) * 2); } int CategoryListWidget::addCategory(const QString& labelText, const QIcon& icon) @@ -101,6 +110,16 @@ void CategoryListWidget::showEvent(QShowEvent* event) updateCategoryScrollButtons(); } +void CategoryListWidget::resizeEvent(QResizeEvent* event) +{ + auto newDelegate = new CategoryListWidgetDelegate(m_ui->categoryList); + m_ui->categoryList->setItemDelegate(newDelegate); + m_itemDelegate->deleteLater(); + m_itemDelegate = newDelegate; + + QWidget::resizeEvent(event); +} + void CategoryListWidget::updateCategoryScrollButtons() { m_ui->scrollUp->setEnabled(m_ui->categoryList->verticalScrollBar()->value() != 0); @@ -134,10 +153,15 @@ void CategoryListWidget::emitCategoryChanged(int index) /* =============================================================================================== */ -CategoryListWidgetDelegate::CategoryListWidgetDelegate(QWidget* parent) +CategoryListWidgetDelegate::CategoryListWidgetDelegate(QListWidget* parent) : QStyledItemDelegate(parent), - m_size(96, 96) -{} + m_listWidget(parent), + m_size(minWidth(), 96) +{ + if (m_listWidget && m_listWidget->width() > m_size.width()) { + m_size.setWidth(m_listWidget->width()); + } +} void CategoryListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { @@ -147,7 +171,7 @@ void CategoryListWidgetDelegate::paint(QPainter* painter, const QStyleOptionView painter->save(); QIcon icon = opt.icon; - QSize iconSize = opt.icon.actualSize(QSize(32, 32)); + QSize iconSize = opt.icon.actualSize(QSize(ICON_SIZE, ICON_SIZE)); opt.icon = QIcon(); opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignVCenter; opt.decorationPosition = QStyleOptionViewItem::Top; @@ -156,7 +180,7 @@ void CategoryListWidgetDelegate::paint(QPainter* painter, const QStyleOptionView style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); QRect fontRect = painter->fontMetrics().boundingRect( - QRect(0, 0, m_size.width(), m_size.height()), Qt::AlignHCenter | Qt::AlignBottom | Qt::TextWordWrap, opt.text); + QRect(0, 0, minWidth(), m_size.height()), Qt::AlignHCenter | Qt::AlignBottom | Qt::TextWordWrap, opt.text); int paddingTop = fontRect.height() < 30 ? 15 : 10; int left = opt.rect.left() + opt.rect.width() / 2 - iconSize.width() / 2; @@ -165,9 +189,33 @@ void CategoryListWidgetDelegate::paint(QPainter* painter, const QStyleOptionView painter->restore(); } +int CategoryListWidgetDelegate::minWidth() const +{ + int c = m_listWidget->count(); + int maxWidth = 0; + + for (int i = 0; i < c; ++i) { + QFontMetrics fm(m_listWidget->font()); + QRect fontRect = fm.boundingRect( + QRect(0, 0, 0, 0), Qt::TextWordWrap | Qt::ElideNone, m_listWidget->item(i)->text()); + + if (fontRect.width() > maxWidth) { + maxWidth = fontRect.width(); + } + } + + return maxWidth > m_size.height() ? maxWidth + 5 : m_size.height(); +} + QSize CategoryListWidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); Q_UNUSED(index); - return m_size; + + int w = minWidth(); + if (m_listWidget->width() > w) { + w = m_listWidget->width(); + } + + return QSize(w, m_size.height()); } diff --git a/src/gui/CategoryListWidget.h b/src/gui/CategoryListWidget.h index de910fdf3..23860f322 100644 --- a/src/gui/CategoryListWidget.h +++ b/src/gui/CategoryListWidget.h @@ -17,6 +17,10 @@ #include #include +#include + +class CategoryListWidgetDelegate; +class QListWidget; namespace Ui { class CategoryListWidget; @@ -42,6 +46,7 @@ signals: protected: void showEvent(QShowEvent* event) override; + void resizeEvent(QResizeEvent * event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; @@ -52,6 +57,7 @@ protected slots: void emitCategoryChanged(int index); private: + QPointer m_itemDelegate; const QScopedPointer m_ui; Q_DISABLE_COPY(CategoryListWidget) @@ -66,13 +72,18 @@ class CategoryListWidgetDelegate : public QStyledItemDelegate Q_OBJECT public: - explicit CategoryListWidgetDelegate(QWidget* parent = nullptr); + explicit CategoryListWidgetDelegate(QListWidget* parent = nullptr); protected: void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: + int minWidth() const; + + const int ICON_SIZE = 32; + + QPointer m_listWidget; QSize m_size; Q_DISABLE_COPY(CategoryListWidgetDelegate) diff --git a/src/gui/CategoryListWidget.ui b/src/gui/CategoryListWidget.ui index 307a039b6..f16165cdb 100644 --- a/src/gui/CategoryListWidget.ui +++ b/src/gui/CategoryListWidget.ui @@ -10,6 +10,12 @@ 418 + + + 0 + 0 + + 0 @@ -29,7 +35,7 @@ - + 0 0 @@ -51,7 +57,7 @@ - + 0 0 @@ -85,7 +91,7 @@ - + 0 0 diff --git a/src/gui/EditWidget.ui b/src/gui/EditWidget.ui index 239c3d9f8..9be882121 100644 --- a/src/gui/EditWidget.ui +++ b/src/gui/EditWidget.ui @@ -38,9 +38,16 @@ - + - + + + + 0 + 0 + + + @@ -76,8 +83,9 @@ CategoryListWidget - QListWidget + QWidget
gui/CategoryListWidget.h
+ 1
diff --git a/src/http/OptionDialog.ui b/src/http/OptionDialog.ui index d55ceecdf..3c5714e1d 100644 --- a/src/http/OptionDialog.ui +++ b/src/http/OptionDialog.ui @@ -24,9 +24,6 @@ This is required for accessing your databases from ChromeIPass or PassIFox - - QTabWidget::Rounded - 0 From ad2ccae5db49a2c0f6ba223733aebde83a17d261 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 04:24:07 +0100 Subject: [PATCH 075/333] Fix funny resizing bug and increase min-padding --- src/gui/CategoryListWidget.cpp | 19 +++++++++---------- src/gui/CategoryListWidget.h | 2 +- src/gui/EditWidget.ui | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/gui/CategoryListWidget.cpp b/src/gui/CategoryListWidget.cpp index 1c3c7dcce..ddafd6949 100644 --- a/src/gui/CategoryListWidget.cpp +++ b/src/gui/CategoryListWidget.cpp @@ -49,16 +49,12 @@ QSize CategoryListWidget::sizeHint() const { QSize sizeHint = QWidget::sizeHint(); - int width = m_ui->categoryList->sizeHintForColumn(0); - if (m_ui->categoryList->verticalScrollBar()->isVisible()) { - width += m_ui->categoryList->verticalScrollBar()->width(); - } + int width = m_ui->categoryList->width(); - QSize min = minimumSizeHint(); - if (width < min.width()) { - width = min.width(); + int min = minimumSizeHint().width(); + if (width < min) { + width = min; } - width += m_ui->categoryList->frameWidth() * 2; sizeHint.setWidth(width); return sizeHint; @@ -66,7 +62,7 @@ QSize CategoryListWidget::sizeHint() const QSize CategoryListWidget::minimumSizeHint() const { - return QSize(m_ui->categoryList->sizeHintForColumn(0) + m_ui->categoryList->frameWidth() * 2, + return QSize(m_itemDelegate->minWidth() + m_ui->categoryList->frameWidth() * 2, m_ui->categoryList->sizeHintForRow(0) * 2); } @@ -204,7 +200,10 @@ int CategoryListWidgetDelegate::minWidth() const } } - return maxWidth > m_size.height() ? maxWidth + 5 : m_size.height(); + // add some padding + maxWidth += 10; + + return maxWidth < m_size.height() ? m_size.height() : maxWidth; } QSize CategoryListWidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const diff --git a/src/gui/CategoryListWidget.h b/src/gui/CategoryListWidget.h index 23860f322..3cf82fd1b 100644 --- a/src/gui/CategoryListWidget.h +++ b/src/gui/CategoryListWidget.h @@ -73,13 +73,13 @@ class CategoryListWidgetDelegate : public QStyledItemDelegate public: explicit CategoryListWidgetDelegate(QListWidget* parent = nullptr); + int minWidth() const; protected: void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: - int minWidth() const; const int ICON_SIZE = 32; diff --git a/src/gui/EditWidget.ui b/src/gui/EditWidget.ui index 9be882121..6afce9f3e 100644 --- a/src/gui/EditWidget.ui +++ b/src/gui/EditWidget.ui @@ -38,11 +38,11 @@ - + - + 0 0 From d24c9322cc1b1bcbaf98c26916f865cad8972e56 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 14:05:59 +0100 Subject: [PATCH 076/333] Use dedicated icon for each category --- share/CMakeLists.txt | 4 +- .../32x32/actions/document-edit.png | Bin 0 -> 1859 bytes .../32x32/actions/document-properties.png | Bin 0 -> 1385 bytes .../application/32x32/actions/key-enter.png | Bin 0 -> 1095 bytes .../32x32/actions/view-history.png | Bin 0 -> 1614 bytes .../32x32/apps/internet-web-browser.png | Bin 0 -> 1568 bytes .../32x32/apps/preferences-desktop-icons.png | Bin 0 -> 2472 bytes .../32x32/categories/preferences-other.png | Bin 0 -> 2319 bytes .../32x32/status/security-high.png | Bin 0 -> 1732 bytes share/icons/svg/document-properties.svgz | Bin 0 -> 9614 bytes share/icons/svg/internet-web-browser.svgz | Bin 0 -> 206090 bytes share/icons/svg/key-enter.svgz | Bin 0 -> 9345 bytes .../icons/svg/preferences-desktop-icons.svgz | Bin 0 -> 17837 bytes share/icons/svg/preferences-other.svgz | Bin 0 -> 12294 bytes share/icons/svg/security-high.svgz | Bin 0 -> 3854 bytes share/icons/svg/view-history.svgz | Bin 0 -> 6519 bytes src/gui/MainWindow.cpp | 69 +++++++++++------- src/gui/SettingsWidget.cpp | 10 +-- src/gui/SettingsWidget.h | 1 + src/gui/entry/EditEntryWidget.cpp | 12 +-- 20 files changed, 55 insertions(+), 41 deletions(-) create mode 100644 share/icons/application/32x32/actions/document-edit.png create mode 100644 share/icons/application/32x32/actions/document-properties.png create mode 100644 share/icons/application/32x32/actions/key-enter.png create mode 100644 share/icons/application/32x32/actions/view-history.png create mode 100644 share/icons/application/32x32/apps/internet-web-browser.png create mode 100644 share/icons/application/32x32/apps/preferences-desktop-icons.png create mode 100644 share/icons/application/32x32/categories/preferences-other.png create mode 100644 share/icons/application/32x32/status/security-high.png create mode 100644 share/icons/svg/document-properties.svgz create mode 100644 share/icons/svg/internet-web-browser.svgz create mode 100644 share/icons/svg/key-enter.svgz create mode 100644 share/icons/svg/preferences-desktop-icons.svgz create mode 100644 share/icons/svg/preferences-other.svgz create mode 100644 share/icons/svg/security-high.svgz create mode 100644 share/icons/svg/view-history.svgz diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index 37e4bdd68..e57cdb3c7 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -22,10 +22,10 @@ install(FILES ${DATABASE_ICONS} DESTINATION ${DATA_INSTALL_DIR}/icons/database) if(UNIX AND NOT APPLE) install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor FILES_MATCHING PATTERN "keepassx*.png" PATTERN "keepassx*.svgz" - PATTERN "status" EXCLUDE PATTERN "actions" EXCLUDE) + PATTERN "status" EXCLUDE PATTERN "actions" EXCLUDE PATTERN "categories" EXCLUDE) 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 "status" EXCLUDE PATTERN "actions" EXCLUDE PATTERN "categories" EXCLUDE) install(FILES linux/keepassxc.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) install(FILES linux/keepassxc.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages) endif(UNIX AND NOT APPLE) diff --git a/share/icons/application/32x32/actions/document-edit.png b/share/icons/application/32x32/actions/document-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..eb327b0a10fabf9cf9d82f96aee4a1bc3522e1fa GIT binary patch literal 1859 zcmV-J2fX-+P)J8+jCYM-Gv7I%o$!&;-x!QF(X2$;NbAuSq1-gvUeBG~ZUriG=_~sT3DNpbXCy zWn@%J)}?c^kiv6rKA{>9zdaP?&HW}t;J)63dU>L!H)&ZU;HC-yM5S;wSE{Xt#2rh= z`FH{KwQ%CXRMzh51)!s&gJ+g-`KEPNxbZ&M(Z9lXDkA_WiZbZs!lip9r~k}|L_hsQ zBEc-}-7yxfK7*8yOeR_Rg9hf@E~0+F@5%@Do)xdal`1J1`=q1o4g1Aos~G3GKu^lW zf>oWEwjiI+bMLKQ9=bD5(C;h$;gGWI>6Z%~faxAH1w%li?v>5Y9)E>t+WTa3Hjh4C zAd?#`#winWtekIB8}hOH&*rA5cIHn4MZl_>f)THF;wlBF2tn7m_xO@$4~C_%d^221 zN&pnAi`FM#T+ksC{t-{@)Z2!{i-1#EgjLe;d8tTspJYOAGm7G4&8u$y@h=?!Q}5-~ zl^W4ngKY3&2K;jY3_u?mWz}17tv5jkK|XzvV0( z9$mYGxaTo^J|9EkdB6s!o`O*z%A;WF{fN#d@rUYY&wPtbyV|fU3q`=Qi{Zw}X&iM1 z=jC!#RYTJ>hQ!Tby)pa1SP;^&Fwg&jaP0&V%wq9R_fRMl09ZUb%bhp&p=eE<(x1TN z3!-Tnp-^Zz0?w!arNuW4Q2_!J&aNR`8zG*H^3c*9q|+Gy?ztt$!(UIM`p0o1yBwFV z4o%Ythr@)!VTMCLdITuD31uk(h=2GTHL5`>)yJYI_t70sA*JM&7M-8X>n9lSb0oDC z(=`cIRSAVcL?RJ99#4hVwG|;(U%QK^-zFN2GmyQ)kJp{!>?a8TZkX)w%))-gg*_Zj z%qQctjEKi#F*HpZIl!0zV-;c7x&1V{4`As1tlHkg2mkB>AQo|0zo?f9;{?azv+2^m zf~u+ngFzY^8VCl1C`#2}kDY?yhzKD_CqAa$*aHZ*z0uEswjLbEA)rb&E=|)Em3-7Y zlhdi&P*s(hni?iennWZL!R>Zmt*L7PC~v*}iB8-XR^So_yWaVfH~!dxX_~kc$+{(3 zW;7dgrl)i8(kxU}B@hS@jYg@juSW=hWm#C3g>5?(QWbB($PbPZkWBJC=+|-VP&+UF zW&p!55CT>$$aC9_0)538d)sFds8P{0jr#g}VzC%f%8{!ngg|ju7GdRMR2qSyrBO)Z zs=1q^=hGO*M*uuNr@(i=Hh^x|^7_$x@%RJyd_L;x>S%0i1R#}4Q8WysM`qy- z-9S@Q6OE0Hj2}N9MR9TA;zd$dt`GJfByh2 zO<9aW62+~8cOr>wfWKURj5}uEPAnF~>-FL|4g&)@dU|?D_9pq>gY&Q~i=LitHf-Da z+V-tmSCos74lpF%pGxxbTZfs}1oLM%;nt?oJYz0jjZMMl^WpV+ak*UBwoNXVBbUq3 z*WZs{^N)yc+}irP?VFzeJ|L?}jadVKdiOQb=@h=H*K_^M)ih0+j?3j5iH;Bg$8m5R z2ivyE7xLr=aI4*sH<uA+rLOC;SS(7zFr;ak(lkwJS(dacOWL+A9mkQ5<47r` zv~61!3I!RD$K{cuN9FNTAKH&EU)~C%{#hCuz_#rQ!BQU<9|3S2sTU2IO!diw$4{Dz xmMvTJMa0WL91l$RPsoQ%2RitIVgJ9{zX47j^9HRIUeo{p002ovPDHLkV1h-edrANR literal 0 HcmV?d00001 diff --git a/share/icons/application/32x32/actions/document-properties.png b/share/icons/application/32x32/actions/document-properties.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d13863d8d9a39759b84da475167b0ac01c3419 GIT binary patch literal 1385 zcmV-v1(y1WP)P|F+xR zot-ns3%bkhbhcekPOg3?=51ASW^V+giR8~z)D==1AOCYh%C*$lhZ^Ge$l<3g`en0UxyAMZre5xDGS%k0VReU}ld3kwg+MS>H zR~(2pAwWzI({^$tdyn@sX~O+Xos^9zUV`86$K&zfa5z$$?_NOCmOzLYmrk$ZXj4DF zA`eeJ0=Kf4P#p;1^?GqSohjXax58-w0Hi5sJN_o8&v&C_-N)?d4%!Bvqo%5g;^Jal zF4w5y|HuI;C0EX@;n&t6T|p;{=lb}i{V{55t0^rlO?17TVDc87Jnq~#vRSPF%H^{k z(0b`lTCbVBx2%?vtpO%Zs-mo{EMa@f0~T@9G%-yR!!Qs+AcRO0hGF>ywHC0 z28Yh}v3|`Ajx-liF`<(3@^aj6ccSC1qVZ!~FdB`b>pG!Oh^Ve3O_QwbY+|Mi14Fg| zKWt!0PjteQ4SUFw*o<#Z*) z>ptChr16U{=h}$_s6)cLU|de$wJn?Lm;QL-(gm;A%L5+IsN%`5U>raQ!BBXJ&|ru_ zMc}shhKwtPMdFh_Gx1#*CK!2ICAfDDB~G7^bMUDstS7L&19Od5tE4a1N^2x*$8 zG)+@VDWwoXhQndm(a|B#ookZc?LQ!|Ub`w6FJJyGc7ORSp$*f;hAoOX91f&u-u1Ss zC<>?w{>n<@qq_R9cQrOH8Y^?Vcvc88BID!{&pzRRY05}cmz_Pmvgtz0(ENo9H)oQ0 r0^Blx2$*uW)Jf3~oa7!d|KH+oj}Ib`Kj*nd00000NkvXXu0mjft%i=Z literal 0 HcmV?d00001 diff --git a/share/icons/application/32x32/actions/key-enter.png b/share/icons/application/32x32/actions/key-enter.png new file mode 100644 index 0000000000000000000000000000000000000000..60d11e2f11f37bf848f81a0fbb019dd301b8af86 GIT binary patch literal 1095 zcmV-N1i1T&P)H;pD!_grN}$z*;M$l(FxZ{R`@}f1`Z%@jWKVh01-)!0k-J_yWp&lG3GxJ7!;(!81pir zN5J*oCF7D9dJD?sa%FdS_vhK!*)NGGh@wbDQ6!@%62=%AMUf<;cdl_CuvV+#@bECU z$tqcEGZhFA4i0`>$)y6Q|c8@H9|& z?0e6I{q0I65ClOS-LHYW#$F@kmZ8#6YfW0~OmO!Y_8%c>s#t5Gl*%Tcl!Dg!Rd80w zy$5^}X{z`Nq#iVUbq)ge+jhH+)6-KNA0KDN#ZW<*cE_i^bc7JNySu~w{yy^gJhr#D zQLEJe0FRH41Bqu6aPRLla993Psf6|QbzEOxV{vg2g+c+=T5N7^CJ3<2p@NVf6obIs z{Lkm}I6FH-qtQU4(ZJEs(W`MVR1gXw5Cj3b-EP8TAo;}NxkQB3)m5yltn{ho$G}hu zluD(sv9U22W6*B56KUtl?`HtF{+?^C-8VrdfoObuT!mo>B7)XBAvt?JsCW?%BEs|Y zGdi6Plv23AzyBwTz{ktW%is6*_P*_QyFnC1sm2F^4*G74L8H<5^Wx&-S9i`JR|-o@ zOS=mT3!fu-R*0Tqk+PPYtCk!BB!r-chlfvBS69CQxCIdBA{&u-Q9}bzy*2<4_QuNl zr+RCcob6QuAo5zS-Cm8{e`yLx2UIy@1{4l3uV>F~ukRA#);Qz1{0qlSe^=DucFzC+ N002ovPDHLkV1k9D@<9Lq literal 0 HcmV?d00001 diff --git a/share/icons/application/32x32/actions/view-history.png b/share/icons/application/32x32/actions/view-history.png new file mode 100644 index 0000000000000000000000000000000000000000..a67c689ac5b5d9a501907391f37ea3b6be37c350 GIT binary patch literal 1614 zcmV-U2C?~xP)zEjwjKD5ZZmaL;$n{eQpvoeTGV4C_?P73Z0z8S!`m@mL)3cmnBk z2H9*D*<3-hEfbta5P~BN1Gv5mujRq@S_py=zVD*dYN;Ryph!WHf|LqMNhqZR5j}Q) zK4U!q0R;x=_s4_~a9s~dtLEv(=rP$ zKEJnSnPyE&SsFIbXm7?C* zwXRfg*Frrk2V-Mn>0B%_AofU{@Ma@Tb~xOHoCACdct>o(0sqhZ{fGaetG7=5{3um7pPzrWduXWK_` zPgky7nTw+6{b!ze>UW8R{qD-j{IoQ?dj9y~`8S7#hCc7qucHdw)9~={jWcK7xfqM3 z;)!JPr|-WvFf=qY^p}lzZ3qAWq?7}-T78~Jm|mEfJ+o1tj*sA;Ow%lnkB@&Ei>VzE z$zA}n8}Zpt1uB(_UM`o9mrA7@$z;k&CNqaBm5TL01IXv|zd3pGWYp8s(?}*${;P+6 z@-N@_e_gFsJNE1Nh9gIgyn5=?slA-@kz%nJ+IC$0@COC=@ZrN_BO@cPe5(L^_Uzey z`t<2zhGE>cENjZP6H3>0D&>Usqn$e&`}glhrBZqJTLGw6tJ=|{M}NC@>sBKOf{#mij)<8E^Si_HZrF3op09p_iJZU27L0WMy=7)H@c zAMXC~j-TIMa*8|vmYo2mX`|&jXt~Q+ahKJ^`0e2j1}-dZDnL4&-eOy^e@@?;DXchw zRmkO(AfbhU4?pnXx-EoZK#hhYT+chg7~4TaFKrrt$;>A~c!GenR!4&p46T+2$7y0^ zWf@TvGA@J#ki{7L84>AtL?^UfY<-}R+p?f(Oiw0L%rs4;l4(k(GFm(l&nA;;7`hIj zIHE8@5P0x>7oN9T1@T-Pf} z5@w`yyJaj}_qG~qyM+J%u{z-Z2Zs>iktOCF5g@|Vow##<^8X9{2RMump_RM6#{d8T M07*qoM6N<$f-cwvnE(I) literal 0 HcmV?d00001 diff --git a/share/icons/application/32x32/apps/internet-web-browser.png b/share/icons/application/32x32/apps/internet-web-browser.png new file mode 100644 index 0000000000000000000000000000000000000000..b4106a58b3875b20d865f158524f39f3e627718e GIT binary patch literal 1568 zcmV+*2H*LKP)kl|6c9)+}|!GKqLS)IXO9Q zS=PYqOxIdp92y$htpF*67^qgOx5W6uOqr>s#eq(R)`qv6ihfZi?b|#tWpn4bbA08Y z5q#et0Hgvi#t_HxEdeO4Hdt*4ijG3Z2`j~fj1yB|i+JP2GR1nGIF2#K0H6TkIKE{7 zBfHwkueQi$T>#pHCYg+bl!7Nd16^Hx>+-HOkk99_ENffyLZQyDo_+=0wD5eJmX0Zw zo6OA@$%Yp9e|CgVeRB7@cl!G`-p6sw^78U}fcExwlu}!U|LA9brdBoFbI%ZG&n~c3 zsBv++h#%PazDr`;96PbV)6e~jCx7t;1_wH}^wL_hxVQ*_yh^l{j4^!u8_$ypQ+(vm z0J*+Sgy%A~SS1bvdiU+5DJ_fyc$miP#Q;J+Zg9l@|~Z&z{JGTwgbB^z%$RjMrSTd zsTz^WhVdY;aIrZiu(@Pbmm#UnfU*r6InXaA;kAL@%HwLiP z6g+=y49iiVB%i)_50zF-Er$Jf_i^ap0EZ3_F?qRyopNX*(UzpNR%7;3nYYg`QC9)S zj-T1o`?^!``s*_!(jpd;j*gJX2oj*F4Vg?py3OarnK|mxqK0HOP7t1hRu+k6vHzpH zu`?mh{{FRX8&CjAl5F?_o_gkG<`=6lv5X8{4gxw6q(TRw3}-GZ5~e*04UHAJm?$QZ z@`||>1ffer!rWYQL%33kBuU-{*h~vEMZCaeY%`yfqHCM zsK@L`JFF!F88{@ZE2B@00U;>XG)O^`7@{b~w%3DP&Ahe-q9|JTKsxIan*^j_zEJ0P zFP>+3s0+)n7+a`up%U@8={1T5r0*a-8{ygr+agIcwMN9H>B~s;hVUqgHm-s9YiECk zRAAGTf?7-Cx{6=_b%N319B$gjw=99RdzU=Z!^|4 zuo=LYA3nlbv4$fBooyban&!^#G-np7JTThJ7mxJu$dP_VKiq{EdPv8{^Az1#kCoLb zGS>LYWzVjzZ2`7yptB=nxIe|&a*Nhtg*L~bqo{wg@~$=-J$rOR>K< z!y6Y@(Ty0>j1bD=$boM1`RvUD3=Zz#r$6`vomPT!ZD?uUyjWm-se*4qZ>ZRtwYeiu zy!B3zwL%49zzID1M1!Y(`QWC$@6FtD3a;wm&yNyHo8SIroM4~>FB_s;nye%6lw_h% zXYz6xR~Wpsk8CvA(Ol*DpT33=Tkmsk7y#ht=k8`?ILD)p|ACpdEPDvdBA`*IOWTCW2zg+k%<$&)7!-mt?hP^nZp1(*-=Xl4L?n=}8fjrtEI3#4y| SB_x^v00006U2i=0`U7<|$+&AhJbE4r@F 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 E0K-Jt4FCWD literal 0 HcmV?d00001 diff --git a/share/icons/svg/internet-web-browser.svgz b/share/icons/svg/internet-web-browser.svgz new file mode 100644 index 0000000000000000000000000000000000000000..f48f1415c2e2f606c79e489fd157b5713cb2e385 GIT binary patch literal 206090 zcmce-Wo#Wy&@Pym8DnN<$4oIZGqZin%nUIzGcz+YGcz-@9pf?N*qcvxb@%8Gc&EKt{$Q&m~a2<*k0GO-x|k&+W!DyR68vhGC0jq!=x!^jY6f2<5b|E<09O(PJZB zT;)h$@9fany%PVM(;`O`552}ml!f!3{mr~P&iK-rJ0I^}>|XkTPpczZGd^AI&efkd zZykTWH*vRGP!bdm@YQ8I{nV3ZPJ1)81ahA9K&E$BBTz#*T3vEOCq^r3N2d^e zTs=oejSo>Es+|zK#@!?7-Qx**^WWLkOb71lUXXvU=f}Gt%f^?+k{|3&Xr8CH-;e#UqWP-y>##Er z{hdLY`CCLDQFY#svqO9}BZBXHRRuD7wcNt{h~)+j)5vjkHnJ4|lc`!y-W+da;u`eC zv;JV&%1h!&?~w3kyN=aqUXW-QU+Cl2pcoqX)Jatu~VO>$G{$z(07yfWN_!xyY6WU@JY8r;#mD*vhucl5SWEQ%_LW$NNc z8yf}tyOrr6*B4`+t6s)hpL)}aS+aHlO})WQ%S@$+b2qiYiE)KAs%>Ll0Vge?)h2h8=%sb5?npJl zM7%f8o?!3L+BRm(gKh` zK38`9LxNDA5^yV!ZE`*?f18onS|;yKH2W6Tt&NqZ>;I{m=IhRt>DmiIS-t-%7jX4! z-aP_jt?m3x!QXxI+)GXK&&lIn^`>2w9WqeA*KfaIT)Eg?FBx)f;xtQ4M|_+z>O6V_ zFJ{I1Wvt&EEFty_5ATSDv$=V|1fZSUKEX6xp^2~8h9IJgooJ~&a@qIw`SHP-_k4B& z+N6#|O4`vnZ5x_fC5TGQVQuL2rj&r(%PWoQYVzbHeC3UN^Gd!RX4UB`@xGp-l)g>h zw7n;MYt-`BB+`nNt=c)1y$Xwp;8JMv9_|`_OO89oq~c&yxo+N#{H-b8=AMB>QjcfY zdg(PMo?BlV`-(@%D)I2AkghJ0&G#H|I&PP8lv|P4h`;G4!` zoAr4qg-mYZ`bQZ2#nS0C=!nq&a#8iFev;t#HEP_(A7^i}3eAz(Uav%dMuacVX8+Ci zqoIYU6?i+KEvgvX`)3+Yf)e>gE}XwzZ$i3eGj&_qNMw!r%ZYIin?H9I7r9m{qur;P z1)5mHEcpy(j?1~p5Q4b%Bxa5ZXIpbt<4 zQT%5}JZg6MfhZVNqXTKXi30?cF;_x=1bsFP15PyQf7As3pB_e>|9|X@{udc>AUbol z>ncX!4i2ce+CRQ2N_PijK-u#?5wYc z`8q59@T~D>Pt-^R#sb&uU6bg4sL`uqb!C>GSk7v$Sg2yo3Xvv2l7D0FfnG35EjW2K zxSQMcYxe4w#l%~;3p%Gq3sQsSrQI&7#l^E7>I-yz^s)B!%4+KXgg)+4 za9J~fDwg#t89n=Yeb>T>hfLHh`MZf4kMzSsuK$F3?qB(+%i|XDKwa6yPk3OH;FwcF zP9`U5NW@h{3~wf+jQE}w`ofd}wtN9)!W@puQe&IFQYxEK1*q%AFtjn-phprSmVVx( z`>rm%(VPY|9W-&);U%gW+Frb492myo-OM04=(1uKk5E3SR1|i*pMjqm2Pcb#Qw%2a zyc&j*o`tOeej1;NvHF`LIOT`__bwmM>CwoCr(g~>gfKd6H`-TToq_LPOQ0;}lVYz| zKR=O`R{rsdh<~RC$X>wp5T-uzM^l*pvjg&oxt9H8WrH% zpZgVetS{j6#@O23t{(^?fLXFm=j55pPUcy0cj)usa-mAyX4qsz{7{exm1P4kFC?6d zs%&6;BFHFNvs(SFN74mx529dxgF`2fy!GsAb7ypT{nkqvsVlCZwr;o5Du6;=tf?ye zcV$M8-U`HI_&DCwP$UZ9!4c z8}F5jw#t&PScI#Kfe2S5m70OlrD|neIe)=>B&NhKG8lO)Gfw#k^rWRwd{Xup&S=FF zoNZkjvwSBuTQ`Gm@N$4ZokIIICEI@$!5@h$!dA+HPkEkkNV73`v+3^pR171VITjH zmA8mhCo(;VC^X#+)%lz`+;R0(ZyeI#m1-mtGJ59N_Z|JM%sq%tXO+G`nOgVgXPK3T ztOT}ttf$N%me1(VG+ML}g+G%6T!(6+bh18~`h07PO-jsWS}kenZX6mu4#+cq@RzC{ z=l-PW`?WR;DITLD2I}V4C{;VobmSm3%whMiL#dYXZv|%GvLkBl{w+baB{+?{!y|T~P6x2u!sO9IPO6HeJyn-W_QYt=0lp>Tq z#{0=69L*<{x~~Qv3j-P1;L(ptr|Qj$YktU=0GWOfC!I(nx0pJ>5IOup?`pukB2_~c z2F?<1TVw@=CdJAND@<_@;t#g*-yG>+ZuIrz=koT&%bRCBMn{evLmQy@0$L zeK8RtdI~_DstJuuv6>SNMjruM+X2C9Cthk~pgRB;3c79PuI%P|dAqW79HA4@wp7sE zIS~WC`hME|qOYO5h7#EId7k><^e41bn|K7()$Cn&E&%U7j|x41f7ES(BwsXq9$l%* zmX7Ln`#w%?s*Dc%`n*9eZkKxo?%+S|_1(XGN;oa+X8HfRynKQ->I`~(fiK4CUMqV3 zV!v^e)I32);}M^KZdb1Iy1v}e^!0rHKA%O&=gr#lYY=?I5b|k@@S3Qd6hbwtSLtox z!cIPY*thHoVW4SM#~eWmr51V{T&Gq%BL(h9d=^hvqwqCm~Y{F-ehgb+O~pz>|?BpW2U$@e4T_ zmP>ifc`5G3XQarrSEXli*YE4oEB=(Ho;2U=w6xPNjpEysYL`7dT<=E!jA+m{v=rQ6 zu3nqKw0OU|MVBZ$%Iq>Cvk+>SOGRO`%!tut6r)-|QZrOVAyRI_t`q|k=cA4#QmXt; zlC~AyQdH{tB)^9&BAjwg_;<)2StS_c|ln&D0DS2{nSnUF)oPd zsA_h|Q##?ZtUsGZ?3LWiJ#u)UG62R$F^pZiC&%4Om{5Mby=cEKdWJr5PkiSEpUepqDJ@Yt~C zqy>25)ew3-;%Ei*6P&93snnmt*pA&%sDoYDB)sXUP;e^deMZG-3_Jv=2#Ur@a^I9` zlKA}N0W5d7-e7dv_u?4w!Q6R3%4)&&ti59}ZPOV9@$WM1cU7k8G*)v@U!PkBU(a1c z7h~q`F>!mWP=pULK14;|x4XtRoVDdA+n$1AM*F3tse)1EWjsmCuqJU zS$f(%4*~b$j&Y1-r@GvYM?qmk{0Z*+CdYsA1~3O=I#dkk6y2(Cl~HvQi)dC>6@M@5 zs20(9Yzo%l!*W&kOtsKMTd$scxZ4ZJTVn2ML_FxlNDf-|T5GG$b9#okS$4`!ELmFq zCGGr*DHb!8Po4E_=^G=aGXV9Y|1W3B39VlL)&Urgm3o0=P zdQ@$9`Z0&FZBnoSlh`ekg9ZVa2uXimMhbYyf%TcMHL?wxnm(fHj~41F>}8rMx)CZP zR1-AuI!NId;q}?-Ys=!*7VO3g-?)#_U1u^$)$bOVeQ}&#tc;)Cp9pQt93H6;xYTZ|L!#@t#fzopp_);ndxEAb?e8Z0p z=V@tYLfwQeLR~tRkiG$nt6E%#P9;6(HaLgXi^h)3kpg%rj?$Kp;CBuqYSg#pK#V+v z0c=(_J1j9wa?3eP;atDbK)3tGhn38m!9??-wM0Ucddmq|w3auqsDD?`TO2!f)RqlNaVw2! zQdI#8RhI87!6$Xu*z@smuWG((C68|#u@fhttNQfv!*Qw*wUUGkm0C``;mU8Fa#$uG z{fNz;J;DkrGVbPia!KNu026#F5S9V8ts5*smdYz;ItZt_3#6)b`)CuyHpTh0Nt?6C zI4GEh+o6+?mC{n`xoL*2>563Xs~D2B^v#?1WFw-z8^`Vr3msy!g5R411WZ?A+Zm-NDs|g;)+kxx>^hCv{V=~CLt?vY8Tp$=-4VLTFZI~ zh0-UZf|{BmKU){s7!Z`vGqOsV$!Qzd-^&N2>6(5V^2{WH+~~O>N+VwA zOMcfwY5-sd3ZpB`X~4&qou1EGo4s9V)Ggw&jnq0x^5Yd3LAVyKU8GYP5Gfo|`xlq3 z2fyi8ERsSZ`4=>?T5aZQW!DJWwaQ_b}z={L$~s?>f|VJBr7MQ`V6;i6a|7p{Z)Gn}GWC(z{Vr^`XKkKjDDZIx9!DU^+D(VtJyf zt*s=4s8*q;jOce7ts2;dKwNdiK?r8SX$#*pWt+?b_UfiECL=qT=*D7~GK)~n(M1X` zN2}9NG~Q`4&Mg8jPG{?4%Ru$awpb#P2!xhyb2)TjuJ-4@2#1*0Rd^N{8D_Qw-~@qe zO!KG{I~Y`6wlMQ6jfbhOj8JA}0kEu)g~}5L9Q2qerbF`4(bQ|LhSAKH1A*${i>4CA z$<~ov(Q4Lxn3HfA?rBicloQ~6UEd;TGxuip3j!DZ-fnp^N_g@FiJ?@>Q^ZYXR?p+l z%%6m^O`;fYG6k@v9GlTWTukER!J4;1nQN;T$FR-ilq$j27v2IzAke4gml|3-ua1-W zOM`zpq4$hJl5hCmy3tv^t) z1qWEtdMXU{-Uo9Mt>P^Akb7gY5w|a%qe2_oEG&4ZqRB?zo^HSrjsN zd1j+Sb;bxuLfT?b2 zY33_5f@#GrHZVA&YDR|NQPG}hnw;?WDgnMf6VM-|O>oIo%IzV9D8et*Nku#p=Kt6! zF%{5dq{dG#l<@5OiQ|^X+Yw#q1sd zrb;5#h6bw}T?D&6mC}ny8MMVN6TFXsn#Wu7dw9Fu4i-wnaYQEqs@z1@^~zFI5r5hf zu{uc#Eb}5I=6C99x>Xy8y!%Nq9>@WI(td^2N4RP{b=9hSXBt-H0WZ2#BjBhT&fizDV@#Rmv5LePe!v4t+Hu4r10_aVG3VYIi|Qj z;n|xyVz5W^kc)C~2&=61cp2FC$ZD6-iTV_`s&5h-yM@IuuD;mr|5x!>MHjfs>f}}1 zd*Zy*)y=g#YkZk+A30`gq<99b{Ay`A3_pjig8v>gRh#x4$Et_K`i!@Rr^*vDEALPd zQnV-QAl4Xf)2bSetKV4=Z1SYptpNJ-oU~ysY%6hBie8sC&D~JN?U@;@d(R6hKbX2O zvGiCm>ke<~SSX&>q8pb&h7ge2JL@&iyniI$i#QNu~DLk~zns^Js%99Vjq zWlbd;6+eK#v|LwOLG3RMm00gv$$yQPlnt6c+Jog7-I4R#xR?Iwa-;S2>-wT}*!Itz z?UnN`b?Xm_1WMU@B-Em9av8t54)V{wkJ4U)8QA-I?14xX6DkQgE&Grb&9Wo4D72;Y zs8W~}7ROJaw@Uie*f_b?HNgUs`D{>#2iNyK;0 zrSVzbws))$1=moRNg;`*W*+-UCgu22E@T&WEk(SyPSWo4ky7#jUCcVC&w@h-pSd(a<3{6>f{MB?$9#j>scsoEuF z-qQAT$^5@%v=3~7t~HI1e5P>}ra+;MmK474s2v!)|Gd>~A4??Q@`JOUoA9p(Lp(4j zp&0YL?t*I7QM-@mVq8U}uEFW}idUcd*i**y?w%HB=m>-}`*=g#k=jWPGTK5~e^50uHM_Qn|Z z>f}c^4kLs&HNwM9Jz_qfmANmsH-Vpw61q20e~zwRj)}UWHGn>z&PzxeX>G-BG+&Lv zS?M-t!)P5&5-l$*vICj4J<5lPuAK$((E-dB8!#PI1v!Q(g0d^o)>BXI^Or!_8g8GZ zSws4VSg=6a=8dD@vR2}r$t5zV5Nd_ZBE}Q!iW5IuZ9~S3mK)TDi&h<3U$55gZ97a- zY|XXZsFw-?u)tJBx(F%pPCD>UF1!MQAIOt7UVk&yfQ${P>7+W>A6Aq#n|+B9+DXD} zVMxgC=eYusv=3dP*clwX*>N$v58t#E{-jXsSmd;&x@6&pT`hL@x{E!g-E!+K706`v z{76UP)aYafz5wPrQ58dT^bJIwA>Q9DQcx}xy3kU=E0e%8?__)WlZ!cY*z>0&&&vGf zm{!>_t(-ke@1I{xX-lvP;J!O@&d#i&Eys&(^4K0iz*6B%A2YRk@pgH?Si8)f?B}ZV ziQrxpVzE#Xi=9EqBM5uuG38$2?l^IGoBH8e6Y1A;&ae5$ExWKWomqOuUkWgMkYp!~ zy;r>1pZkVa3hVAaQUp zPswQ!AvQ7J3&&K1B4Ey`F5ocDaPi}LV>K2YJcpq~%303ZdwN?rDVbW7%j=MDxoC1& zSs1%lSUB*=?t;+pE+)USdgJ=i zpgQ~;Dzah>L5=%+hhWZrM^YiK_oSD|Oeb~O>U9aVXsSpk%>G=4R4(mTXQY!T#05D#oHpiyCb1Xw=Oyg< z2nSerY=FGVyfX!nLDlO;559GZd>{VLhGDV}Kh^7$2n9`;2uMt@nm@Bh1k+z^qv-)3%-Wf;<{9*EA|0?8ZLKj=RpfT@5Ca7m;8k?XZKtO*G$79@Hbq~ zc)(BR>xkIy6|z->%9DZJ=Zmlu%-;TeGZ`511WXA8vWkg`u!LZM-S+XxhDc~a@RjNj zC8^7_rKPfS_?&^h{_30Yu=Hd2&Mb|Ekz{u{KDeeWCX+!ouXhK&!!$UOSBO(@dt?hC zKO3d-(;H$;M0z#%zth&!`SkFSOc4O7I^93v>$K-M9YRdZR*uXSs+J)W z%TG~o(K765_@;o&jt-a<7sq=lYzeFknCFw9UE%s4g_9)>O5@K zs$-{BjhbzMsi|0%&Ue*>0g@y_6y+0ebXY5O55bk(UgU9)L*ITMSJf({0L7OB9z}3e zP6D=;;6${F-u7dZ&@iIZ*Ml=4mU-Q$+x>l+`v<6!(OpqpLYE_={W?7n=|))-ARREo zf^2-reAQ;va+JLUyJC7*1ja4pmX?2*%pbmMf{B83n2V1duF8UA(H*2yGz!KQ8B!Ku z;5{V2l9^j-_0xIacQiMQwi~n(dv|Mj5pP-%?_C!}HUVn``B^ifkVo*C9l>ns09{Ut z+a3xF8J#B?#7OcbUXU^^y)WEdV5I-DyU@tH&9Ao;4)uxE$iWQrXH<}Ki4VCok1h0C zLsQcdFMz^I`z4vuC!HeC0^<21;RAI%Z}Zr695pFT0Sk-uh{jG= zb$t04(GpxlXN_IRX4T^G)$(~tRZpTEFk51svUO>rw4&gfl|b`AP#Ed{lR&wj@@3~c zH{rK0=s5%`l@CzBvj$}tLQLIW6n=yUT-?w8M>d`0C|=_zFKGtaaE0)0IRFRP>c#ce z_2oUj<~>pK3xf^caTx)NCvlz1IJ^0!kKR<*a;jY1Ci$U1H0waiLE z+C{k9*ah4n^D*KunBo?Ms1B3k?qp`mkl{Kgx8K8Ye1ko-P;OCw^EnkpqEmgF8_}B6 zaAefaY&FKHTJ2eVvEx}jci?gO;!Z|l$5~*e^U94K%QPJiO1+`i@9AEg9=RY3QGT6Z zBolbK-WC4!biI48)DyzYTbbsdva^v7S-GOOV&=aBlIaT4vKC3+nfve`h^}KrTe+1l zyktRTff?nO5vQ`vGtM^i#Vxq5Q>*SP-M_sBac=LCuOaX4*>!Ii*wzv@RCpudfr@Y8 ztv%MyU*qS{ZAn)Nzn(Zyv-VhRn&+R-JBIE_BPUR^^9SVj42Mh|V`)}|k+Q^q-Zt zGHN1wI@Ro9aPm+l@3pv+JWL7eIJ<+_!B?NZ2^(%Ela4NqJbEwTpI_+&QDcd%4Ru@eG>=H{z(ltt?G|m{?{{uVlXq* zO6H|NJ*XIAQOF!DeL7_=d^DpLYrGt1vZzD<2=$Ey3Ie(oD|Sxwlwh==h<$FYAqY9} z{m%fj&{q!KN$J%?~xplIi7 z!O@$sYa6>*?A`IBtIc_v=$oY6Hvk&%Q(6U^3i$9lfj1AJ)pymlm0prgO~Or6O&N zyuP^DC`h#~hn)Ln0wQf5VIU`*$hzO9qb$jeb@EBV*91U+Z^9&I<+&ior5|75oEJB3u5aof!sZ zJah)23DS4ZzWlce{NW;1H<1a-gj$0|Ugb1i?eW9;OC0k+Iok2OUfT39n3bRWJ40f1 zE0XPB+y0UU6xU;smM${l`p=KXd1`}Tq(@gu&of>A?`g2L60|2*dwn0L27SK3GjoIc zxxLSWETI4A<>%u7v+ZkHt3e<8cF7#4|JU=~E@z}4WU-N0mNRYX`X5JZX?``^6 z%lpil#*TqOQc3UER8yYXF2!8mUSE&Dfa4g9rD@|Cp8G=~_~7)zLg}*+0l_eSUiI~} z{`-?_I#Bym0{ZV#7&5w&fiLhG`R;TIRA>M7yyL#t^Cgihe$_Bnxx3^i%VBGE|5Knk zF^|^_E9Rl$*hTG%$gl04?y)B?1@vdB(f!|s8rs|||G`_+eo&kLYuMNO!Kcjn)eUi4 zNQn|1-Y!~#x!O$yGltcRtktKX4}U*S*l~gGF8#c${k3SWyLj)n&XuxMf6xIa@AJ=$0{qw9hyB-|?kC{h z=lxFDU2o18X!F)|&xg-_J`CC5%RykH=&2dxEM7T%Tl+COHl~~B73)S8j z=lCe;KxcPH6`ES6lQ(+bY4ejc`6u3AgPs{&A50eGojKu1xC_z0yw?bY70LqQ=HYt) zPLfX;8!_{nZx4bOFgda_)TAhJn#kO0ISU7MQT~di11}H%3hpzvc6<2<0Cu_%oEJ8-Mr1a7 z0$)m_X&|feyLbP?*}(td%U5h3G>64rL+Y5hI(CeHh14XGl0zjx*G+#|Bu9nG8WEXi z&tn^cDdFbZd=VDMnPbU_!6<`sCqSgjd%mi;`ITbNB1y{_7@X3@)ff zcl^#eD@e=7%)K~avfFECTJhQVYlUpt?{px#fMR7X_L)E4T;FYZ^7 z0?<~LF0hEm|MOpvST*r44CK)((9mw~V}a9~UzYt6Kv2Nm{D~5J4O`*bOJ2&xzL)9s+yPzu9qrX13lk;`T7gr5zD;&g zQ5B_o*!-|{6g||(Ng;XZ_k}u--j6BZoxtDwsy?4bP}`D)3b7cMK*t>N9PkbD{Xzf0 zfIlCL{6^rjjTMUTfJ*t3<*z2Zm|h-?K&xnKA3`r?IV)^mc<))3{A&aYSNZh?&wlU{ zpsj4IiY}ktP=Dip*(UVCrNs9Oyes&vrKQBj%xgSZ>oRwD>k1U;{k#$QvNI_4|C^VD zaqC@Dk0R8zj--Br^yp&Xeq9GxuVj)!rb!*4n^MhZY%JO3iu-_Gh2eRQO!(L>JTVTH3yd44z`AYwAJ)o%|EEEjz zns!+~zXmar4_GPCMq}#HKNb`g+AIIT=Z-w2L^LI!Tzo2pm>dx|VOZ@}!4V=8Pe;P2 z^;8LXD;zS;)5J*Rqkx_1tq2ecMYJ@~&9Mf+Y{(IuFsm}njS6Zb5ez#niM|@=d&;Z* z!NrI?ZAZlNQ>G%n=Wjx#caDV6cVZ3ILPo^&VhylF0CtwEz_O6= z2E71O@f%4L111>@z0)uU4TXtgfra?LB2zK+hD0k7399+*L=nN2I|LA*$%@P9jkWQoHeVQz9an~T^M6HMi zIgW`T#V4P9xh{lyByIzJP55V$DXUOa8yN#_2&x2;K=so^XnxT#pJFtc8V`%blm5IM z50B)Wi=d{n1 zmbcru4O`M{cWD%)LfFR3JpQ^s9G|~II0K2(c(RbHNW4hIfQZ5CXA_ogNI0iWyUY1p z!~h8L;ML|qa?rimHx5uHsv+m>yj%!{E21G}sHXlmAAd%Y?c}x0Cx$ml;4#MRdo6zy$~mp zfYhUq#ZiGseI0=^p`p{A9YS(p-|c%khBm?=Z`wVHsal(1wyB)>7F1P+&l|HiHJ!@G zyCH>wB#=1!AOUm*ylASI_3n+t2v@Rl)Gaz()8b%xaskcQe7USj`QtRO@mCDE;=HoQ z*&MKz?AHX;H2bjM$U^58jZ#E}e&W(vTl{&^fhzXEj(05=bwNh=<NbcXQfB$c7)vG14ISd%8z7QZkjoaIeT>VV|kSFJj#K*xN& zQP?wpCBZK?>uZ?U2I1+^&{R_>XS=C)S>5x?hCzvd$pGS`;mUdvUo6xsMU+iIsHX3Q z!%p~H%rFZ^xvfEQG5)Vi?<}3M$ZkgE;250TmYG4$+t7wQc+4r-dtR(+pKHTL+ncL{ zjldV@g+?n`>RH=ZM~p%r8;2Fge!a0sT0%ToTOF=3uoLG6U0+$0O6DTMi88RHYnD`e zXp6h1Ah&9Z+oZGzk|MUSkWxoB7L%-%u?P!1s%6h>7(Jxd*W>jc`d56h%lNFi);1PA zmfTburQ?o+iDPgZTLh;(nB}XQM%sW-?l{kEK;Yg7V3p}6=LdX@ldBtO{P8bn7z7M{b^U@M%< z+pX4qhS1P@9lT)IV~aYSUzJ$sXpXsFuywA@fpvVi%G@@|7cmj;n;{wQv~(q7U~)Kk z^iI_;XW3Ux;Pw597Vvtk#w5rbA;qeVYGwyyJF4*VuBt=zY zA=jpS1B@<#zYx*bF*PsN3tV@Wj1{M7;5DbXi1Ckt(_-Ldc^O6NIXlB*wn4h$uGxQ` zE;N-~PSQ-zB_|51-a#rz)a#HHP@lh#0CNq)R&nm7Z2xrvdcEpeUqhQ_TRqNRF-4pUkz;UP^Ig!3GX!dbhkS zzOari^o#s@BatRMoIjU2%!~^uOla?w_mg_`^%-*h=RJS)IP2xXeAHediC#4hpu zOeF)#ocm4iPH9mJ8W%b9F#*A)eq}s=nCu20ayXULM|B_fDMQ}u;GZKR0rr6JIV!o} zTZlDN)xTie7F-;IcESR8rbKq7P8`J-mt9QuNJt}WtO}3B*}=Z-kzB7yJEuqitd01R4YiyZoYBAH!w$Ni zhHZsGt?AP@9AqdPg^3}@4eqk`b&nktx<{x4u?V;?~3hbW=FbkRG>PLAzh!n)dbB{Okgtk zejM{6lp`Z1bI>b}$q`IP#Vlq)t>$bX0DCQkWy9bmjD%9cLhU*%huA{R%??k<9-hZB z{C-7*Cc#Q3BLX=EOo-r>t$#UZ7;cPh|5_kE+%jT!b2)B>j>DMfwY}9S3@~z>Ex`TP zv%JLu*G!b~ydKYspc-@Rb=OM{2*lqkq~LMYksggz-Jiwk!!`LZeu2+9t{nbbNgk!ZU0O|Cc2VNJhCPHX{`9^MSPVd^>mt)Gf|Lq%GcBM zux_b;S|DgmO`KFv_-5kidd$QvrH==x{Yvg>ty!-`Od}D~SzCR^-ORlFcKbedSv9#J|g$>j>A{G9+S0~OCX+*FVzTxlHSPB zC*qbpB=@q#9_RJkCB!KIIQGQC8y)(cd_(g$Ip<97u;BMGm8X6NX^3*JtUVH9t|Lni zk4y69#M3*I&jK`DcgHL?bP(VHk)sZM&!-C1R!7<@`EiXAmgU&R={|k9^|Btm;R2=Le z$1dW%JX6H8rc&ZjplA#vJ+r*zX}Y1+5!Y5l{93*4HN=coTGI$;T0X*&4sM)*XY+0Hkp2CRkjH>^K^$EO%q80YG(n zRa4*G)MK+Ya+Ex1H&X#59+^-rvlvArByCw9H4hj7BC2sXw>`dbcQ);(KWG~7;E}y` zC`OUWu;Hb>s{tG8HOz6o)6@sn_eTw?vFNotl*KtJp+h}xsOHfayPbLGyX`0To6HK5kE?vSA5t_|njP@rb^$PAxN@`u*8%`bOT zzZhNw%qmGCbx!BkeiU=prblYxVXB?gA<2o(VkcnCIKr%36!Pc~QF4|59D8X73BiUglZrVj&o3XHYJ5FRS9wG=z$0p#AvjN}e{naT!)`t-ffo-rknU8q-T2 z+O80yPul9R0ki3p>{)+Oak=9Ei=af<+hjbe9aeAJjnlJGSGuMjcwOk6b(F^-BLNTC z(B%J~3l1*5(^AoB;P|?FB6ERuYsA$(_9;Pm=rKOkvW-sHDALcJ()xx%6Rm`6!1ogS zs^|3lNq|rtsAxZDi_mcvCm>Q~l;5TPIX>ZEFluQd^N^k}2@p<*gDq~TIn0B-Yh%eZ z3f#is8_o^2ad=oqWjqp1h($EwMoOR3L|p_xtHeO=vZ$LdvijWpWHO-C)GH82aN7}q zD(ZGE>JEiG`74{#8^ib zheoU8Q3hU(%shLU^c95I-TFZVkpsfqZQCw1QBRLT#bR)jT6Sk<(?x> z#5K2k=?y_UV^A}uAz}JG#rzGqtpu2Wz*Q$fpX-q8O~y1w;nz#f7w(c@!p3Q?&+CGv zM}bv(&TPDPisR{uuGbab-9>D78en3v=rlbyrAObo;&4PYo-zky+%vS|Fos+8aE{b+ z0RhQ(QrWaSvDJ=3A!Be(c+f01TbiRt1?%#Cg&NNYfy;vIKbS5`S6K#nTFJ&1iBoUf z-ej5jQXD5Af3nGIZmyFo10<~NvQE|wO|}}S%b#YXiCCAt@TutAm6W#%HKwK^OPb7+T#Kl>ZFdqz$LXn~r&xSc<&2$IQ{tC>@v4A5F^Z+Y#bTUbb6R z&p#cVGZ$6Fs*OT4ZP#AoxffK$(bOd<4L)~Mb&P$t+Ce&PAlYuM34=S>RwZI-^i0Yc zgNRwDX zI20I;2rk8r{bnD3c}sc0x99R<_qtxLqF zlTF1LVBwIwN;fw7_jrq766m#L*u?pX z)S!PFT;6hU$Lg+Oo=3Z(?DhYpa&sq1YCHxGp6&We#GWhu9B1j;q{mDUrSw6TvJfHC zDH|0yy|BZa=-O<+c&W+5w0Z;Z?!k}t=+I}?J-4)d^wvr zsC4oBgr)@M4cF3DY(C;xv|BhOL;7ON$~-h#t9-+by9?viu^c(BZ)8J1a{+vc%ub$U z^6)0@g6=-$7!^yX+cdLR6xJ&DF{8FjpE={$d>LnH;@7LcMc)~#-P44) zgtzVLlOg4~uq_WG*QaKrYIZ5w0uk=)@jHG=jvdcF5tKaLg#{gt<1M+F?4AnbC$;VP z;wL_4YWy-}rw~bk%W9z2E<*d~gZ^DgqKCB_suu7$w~Zh{O<( z9xM8dQEhVIUZZK{pSE~(xK26cqx!V*4MKae zrpoCr2OC5@hKjk6jFsfJSW(g@Kcq@1U`zT?SYiV#?`ZY$!>@S`y3#gL7c=c4P$y); z#Ny--tcSXER$^26jh&qya0VXd$PE1U)I?Zh=J?rIG#<0lseciSm8Z{cWV5Ig$R(sT zI(-*9+WQX(Noc&7DKK`NG~o9^tJ{qI zERNzOVh*X;-Zf*5ooA^%m5zJC_y;DO5#+rzaF?w5+*U^a%q{p-a@|45yPui)0ET~E z3-hvDmjoazSI3Y6`Uf(H>3xu$#Hcv#dt$GyYACr)RhA9sVvaGq@7()KtA& zrKca7ywdbUmKE(i!`8N7>EdDadI;o-7X@&fHx7v{{1x7)F;zdFvq6W5chP5iYSm1p z;!m;2X~rX;MaDPx4gH$UHZ}t~Y(aLbAJ^JOUoz@PNA7*MKX109L3~(U?HQ`-@O>_X z%E$(gGS76wG{aryRWeph&a}d&MXyuuDR6;Gy6P7^OJ{X5s564>dpFPl;@0H{jy$vB z?d7g<4cS0uJ|I~lkV2t zwgYoc**ndx(a$@jPj$nl(UK2&$R7F8E_i;emYIKeFcL?X*RY+DeSe&Mx+8kmZ&hKf z=gcZcr~@`|^SUYVjY2(n@~f{bXI86}XEOWY-iqgKgSxz%gPnuA3{@^$jPO3~X`n6* z=jH}~Gf@${l#xJwH2v1?jcQ|;Nvo!Eq!8C9Uv~S0cgX-DUvW*xj#J1qHMwKo*KFt)0;h%!dB}vjYM#<=D|~m)rt7e?E!LC_ z*!(CXcb9{9P5cvE*e_cX@PpOw$;M$rwzOEnNkVpD1l&<4KZ6E%x52X+6^$BrsWv@r zEZ|RwXueq!nB(qV>w6}&ZSYj60`8};%I~+Fr;3R$b|2#;e)5@X&;S*_JrVpOe0t_V z@2l`kX-zIc$1gD{Xq0oU0K9GXR45~vB$i;w^{3aAc}gJj+;n+f2*w;UegUGnagVkxUZ(s zU@lm@Rzl$c)l>&vlQ`HFyPT9z&^lm^eJF5>CSIw*?TQ^r$S_yX0L-pU(O|-t>h;D> z5dK4ed>wt~ISypO>&pr@q-|{;NkyjYkfaiQxBDm*t~@+tphmMkY_9O+VtahlvjrU! z@4EqzEOZ=RIDV_3v1TrFy2;9!Z46N(mqxnT3Ff=WnI~uiz`1yi>1TWQ&0^eUGLBHx2%$VU`w&Fk8x>C5IC}7X z4hz#%C9NRZ7N%TiO92Q61oe#`n4J#Eq3xuaQLURR0iVYz7Lz%*bvY(Xoi)DFaI8b6l;gSF8StAT5Txn zL-u!5f1=m?uXgBZc&epnR;*04FVbCS<}MJi&YVz!qCdvmYPlUWD>Iv8tO9~+=whEM ziODM6nb@XsQ{&}~kl)P$&K08=cc|PHX_oN|$KR`8-=u9V9hzJDyIw+L zp^L~RLg!B*!Yi-uCj*qLTJ(K(wyT9Kb7%`{!;Q`!T}U_&BY_cvdg)SKKqjvzgOL}` zX|#4GsCj*lqYT{$PD90{oWKmoxB5unzf-7om3@rY@bz$TIXXHq0QT{sbwF$`sa2rvXu3a7Em0bh-XRx0 zxg%Lj2h_^HK#Kfg0UzW4Il$R*L^UH!__!8>;r`Cal!AlTWm><+`@$0*UTi^H!o||3 z%ht|pz2RlsZ|Ipy!bs8jbuFO!A5AqP#mi^dwv#zIEE+akMT{t^nOvN4&awO)AP6x5 ziRv=d`^|vh?C~|?zeAjaI~N&60X7YFBS-7+JEnL;jE(q!5GU+I{KjArtwV#vHxB1Y zE=`(MzO9OO5hukd+6GPRztxRoEr8v29A1t0SNf{1M7@Hs&U#=}+b{^@6z=mKcB?tA zGe|t-O9|PYe#7q&FBAZAxkJ9qcF-31x_({sWK%ohsSLjum1_lukdY}H1Tqyfz+Jk;rM`1#Wm3fw4;E4ah zYIcnT|Ki3Sz2wlG0Yr>(2_rDE7|A6zJ*C7O;Frq^?>qXK;Ogg;bh0|)IE;{M5EU;6 z`oV!}JSuEycju(0Hfjn0v~~*>ZpV`bF3|~8sdZBUjt%7dab`>wQNxYxq~xpGu*=Sh zD_e;h$<2)$Npp~a5uTUs-j)UoEQ*G68cp%5NmlLH{>^*sZ?aE%j(!pY^LxvX)`xq( zCYsZ)!aHAEYqve{Wb0F-t_q)>y9sO=LQLHlfJM!a#ghbo-d1b*S?I_=&Lq36b#mdBM_DLWa?meDPTiPGfnA3W)0*fTfe@~7$O?M z;urBkg%_GQ;mzxcT+|tl)+;AhPjx$8JDKjVu%HkvlVI-?%NK{XC&8LX zPV6*~k_>;4yol2y+s-;cclLcp0hoFLaN~EumIatvPZFqs5PSnKEStiz(eQXceFZqv zYOG8y;D`6@QTGz2l_d=A7}v~#1rS?HNShM2baZg?(cci^ihk_&QD?BKT;X{8^7%0M zt&=jiS~ZK>Xmp@`;&^$QHhGvP{=BW9Vh>~`sg*QRo!n%yQ##F59>qH#f* zflu7ue;M-_)*HW_oj^&aEFhb!H~0d2*9n`%gyd3!^z9{(8E8?;rs#p@0kt#nY=%*> zCyY=n$=(>f5+=Fbk66)JxctGAZKx`$_q!^kR*Er10`Xzd%4KLr1A1*KNDO|B2@%b* zO85e>SLRo`KulGl;)_oVYS{ovgS}sQXvehx*ue<%rq(~F|Cc<`+4-vIGdyd2l4@Nlg3i!4?+pqj8{&6}valQ!%Wp6i=$ z$b#%yTav3P1)6E`s>M(v5iB2CfzprO^ki5cJ2v?R8=DHq=|4L!=(`T|zP5F^`Leva zb2Prl-ZsSz!_T!I3t|mky2z>Vg4X2wQrvV-L6Xt*EDepCU1kl6dU(eS%&o5jL`8-P z*XrEER|CW!IrxpERfVX#>U0u=r%@41I3$*L`o!5Yr;HA& z3Q&SW)8L)+49NR(QK?>=r4ZPD!RG5nTL#R@?&nttvY{CXlvCGxmi7`2C%4Kv%6L&s z_4;3tia)1{)BybodOmj$-=XBr__+lq9XD4=NnwD6lFa4uyjhRJ>Jd!S<4=;K8`%>O z$D3yL`xX^#LUvzRGhRXmi!KV(-N=~03rx)lRM02+xUN+TF|zCOKC0Y|THOCdy0!e= zN>Zj--O5{P_>9GYd7V^81^|lEM(5B!$%or4u(3SvGeMR(+*Bmqe%I@&5b#z@06f29 zbkIaqM3=YkMQ4cB-)bg40A8M#bE7TftFOF-iYPOiOCQcWnZ)f9GA|~2>Nv=+kG1@S zSplgPcd@81aM{qEQCmnTPNwB)iCZ>L{d=+I&$M&9gqy!TO7WLz!=3+}nkgCNKK%a9 zV^MSab3vRiwgAD5cnL&}BMVXmqb8uVYBcfdCd_p}47J+f)(d)Mk%Jqr7?$y7-AIkM zciBt4nAzB)Jr~MT1pngg1FEC}=eBbeNp+)nC;X-oU*pO??EEPWWq#l@3=eUrX6;KtyVqdKoF ztXZa>gTI*pFTM*j}C$mtk4zP6mZ7HtmFO*jfg<4Qtbv)zT!`c=VnEgWNI(BZl*vGK~Rc9ykXKMM5i7-07G9 zS3^5aN4II6HZ*Tnn**3Fb`b35sn0wbjeD7lKd)_eSsft%+5r_O21nHVbe3e~`CsEZ zl?k+U)Gag6%rnoft+&lk9=W?oN58SvYAth3nMe*pNVqg()E|-w;qyut9yvHZK68gy z%x;6P$y8?U-S;9rpC1>!MlStRF9s&ARU6$>-I;0T>ZeNo%SFX_P7cJ?PK&{!oxhJ~ zOR#+REJ5AMy`J%bP&_1Qp+n`H0^MGLk}8*!Wlm{w2|Tn9n}(mY865mA&%f|`H-*(= zMy{%FaNw%XW~Y?lg`Cq1N;D2>S?4*o2~b%T(5!!M5}Z?D+CfRuZ(V(f;}^$%KPE*Y zg9t7rzVewJB{f;BD@TV$zhf)6;P7RpNus8_LHiEMlKSu9!5rT~W8SHY?*CpufnOKr znfd)5Oj=p=Dbds)NzfIOzjgPIN5^<|3;l-lbHN$a(J{lg!MGP;G}Va#-Z`ruQgIaV)Q~y1 zk8Hduv3e;Y1M6N{#gswksH8X)t}H&MQ+>=P?^*{ikX&WX)>yaRd@qk$Xx9mgnnWs< zL4GEIy{QoF50M~U@WE)^An3{QZ`VLY83Mv?xK$pkH{B47D93ol_ ze5l5jv;RGr>~ln3VX`pDT0%jOT974AQL;C}Smi=ax`1Z=ufKa@`|ZZNLbVW(sMyK! zCu$ZaEc>2l_n&souS=%Sc~Hq4*Rge!5Oi>T0VQcE#FXJos%^?HwK!RztczYD1>>bL2^&zX>%S|>mmU8ad_SC-tPoD$mdEUz$z ztp};zgP%6p3KMfYN%)|jyq+nhyzts*mJsH3Y3qLT<-ffyb|+OW40(PL&`n7)9+88p z_3TH*Hlsq5wP@6AfTKGJ1tEB*;4&)T>wsB5(Z{>HD=ejyJW7r-l#d3P#MEAkMhP)DU011h> zb<_0oiR3Sesq>c4tQkqsTr9mij{Mbn%oEoyo0m*Rwk{kxJ+i$r)^l;lUe4{=GMcK5 z(XF-dw|+oNcrSv)xH1-Y8$O_Y)8CqmBZi{Rvu3m7)n_RoJQ^M2xOPtwgl zsPVqrr_Uj+#t@XF5asCE5OJ_|4V<%|5ZX$<*1SFpNb+?VC0xJppjYDkvow{1zLi`? zhEZld?^6+>bMw<4B{YcdrRB`9chTiMF5GaS*Xh2A+7|8GE!bh;+x)!ZlYo^Pg>%E}Qsh(Um z?=dTKbnBS`Dd9uJt+Y>?LmK9cG4X#&KVQH7&_s!9juBT+nU85`%phG4n^+2G_q=LY z)OqZ=YMfJjG#Kw&IjWLa-Ms876sSF#BOiq}_h^!L4ssqH{bAuY!Z-(+CNZY`oi3+x zbCq=Fi^M^q*3AUhYTIT)EOp6aV8tV>D7bmowc5FPH&&uwd{IbFCgx88Fsn;`5#GxW z3HBSm@G7K{z4-`SPDc*hT+I0jXxEUzfKt*CpT2j_J*@#x>*wJ1|ozkgTa z%??hOWR5Um321fJ7_%oud`>7~)Z zyG4LGRpxJarg9qcaYX^0F!{v2?@YXv5hEsXw7F4x!2Ky{GwE-zXxgtCRn7g+oPtkH z!jo5isN8hL>?UMhX^RbX&wp#~{q2}&-`pGR2It?ZNIRL56gCGC=Nb{+tc~tre+}ty=e6smWiXzezd_@r3FR zMF*8_zj30e&27;Vu`exNd?k3fKS(JkonPcBy_sL<`L5`PU*~CiC`?iiZV7lRg}%4t zrB7WN`TZ_9QI0S70>eG0!O(`5hI7crUsadU)*$BFOUTX=sg(lTvMvzR*nvoanjc zz!Dqeiedp9SR>#6a#H`#SD$y@TUZVdh;J4q{vvk4EHKepR~yyZj?gg|WRH;3z}Jf@ zdsGZallNCDea5r7!BO23+k+0TuS$}=n&JPweGnI9*>-q$;vwikHByYin8l>-Q0y%A` zS0E`pwP~bj;-{hY-N-hB9-Ep2J}6}8sAW84DuE0vLl`elL)#UC(k(Or{E>PZQqYQ8 zZ?9tQ1|Gg;rwEIm9c(0?%MUr7O;Fgk<*}2k z7%e@vuc#ard$G`)-+CMs3Rvx=eiP;mhaJAqnzU+<|M?5)Y<^;|x#+niRZ zTH7}TFe+2TIF2k9W=(YJim-<5&$Sk^@P3KS?XtX;N<;g*4yE;1Kin;_Sji|Uw9l7% zl5bxV>}dO%$|3Brlt_$_KBM77*HUAj07$zP_<&stl*K^w(xGC^;Gkt4B z@dAXRYGDFH4&ypk$93c3e_G;n0H77zZ#`z79ElXFYN;{W4VVI@3xM+q3IQ>R9d8jz zXtUEhe;ilvjCD(oXPb`=0sq1a#_UkyoQ?w<0YyODx zM}h9oR^WZen|JeL!D>|_ImQ~(OB`4D-mdA>WT_dNrKMj=iXhw8 zZ~NHm6grFwg&01#wwQ6sn@;v%j&6kANsWMGs0)uT-eku@)OFF}$x6(6fVsOb!SA^b z-+#-qc4&5l@^VvpWEsJwauW}`)4V9AScJ6zBzVs*;GI)!Wr8X zNt6)kT1sboG3!*dO+ixiCTD3|Fe+XwRtgOI6lAv%_9X^YUrTHURgkCJdwL%c| zoCIr6>rJ7S;;Q;g_o=|xn8@|998 zw$jG;cJJjjPRWppG)5+1WF@X*7si>zrze%zT9|TcGjl)K9eiBKDKSeh9eBJ|>mT`% z$?#8>Qu~xW8yn=Q+!*qobH#ewYxF(h`OcGZnh|JsMJtS=%$dPEnJaM^1_N}85|1Ue zq`dq|aIyCszKoXE-9Ou{WmTb5$nm)i3^tUPx`M;03aG7;)IQua9Y+Q~Ywgr!PzsRS z==ptF-mFT4adSzgko#z!Enzg)+?Y#kdI5!kMCF=WiE4R1+2)(Cbg!Ma9duD=5|Yrn zNiYwNQ*h1PAFp>`Ogj$!-zwNi)-z?m$yh1 z-!T%abBI%a3F4qd)(OKE=bQ@TwmZL>-&cub{mXdXz8wwB-~lEsA%EyN^$|Oo+vl_X z^`nkulTLa>3xTA$%pr>!!nBp~1XL)7wCN{?l%N3?b_!%HvQ394Tmbd(MwQZAyA=$I__e-9z6ohy&4Lrpt zgMYv~DX(NJd4E+gz|0kW?9Kd=W`6DiNMFwGmAoLU&CCdFS|i$fa#dQ&(r)Y9EZ1(5 z{Q;~lJ)DENhYijm%r=_G%llltD_>4V!IeUywJ$d9GUOMFMZL)i%c*+E2d(EEB}${H zJ8V{tcYsoOq++;E$=~|RrHI*+ZD3Z_{cjFtXYV>(fhZ%eDA$I2@zn*8mY?l_&^XFT zDW^oQl9T73jch~&R_{+&4JOddT`4o)#8;focR!e`6~jJU+4xgL3s$0eI@oI@qMXIL z+}}2I@@K<&>_P2gXktLVTUd}*ymaICeNYk+`6|Y>rCJs$(&^`Jp-15mW|T9IECfwu zX#t8{fNM)3BVOpKKc-9-HNWyRpYUpg`h1DmHp?d8;ydt+LPZnI9Hr-WYdr;W0c8oi z;l>2Cx6TE;{#bexyt?!QS?w7s48OoS6BI{mX=ZMXJTeoZa=LyV5`Qw6QtjDb9A zt4cGwRwqlAbvz!BeJa$t_XMesE@etil(GMFf9)wFpZFt2J)TwCZWbP8iKl$S0WWG1 z4Ghw)%07E%n6P{f@}GgVA+zfAMFhN|cVc`)!~LW@yH^n`Eq5|6!bWI}BB~ryKlYqC zVLboBuxU14uMSeishShWBy?%OVG=15-sUkybn4;8VdRMSUyy~_`1YV~oFOW@=G?_R z##)Gd8*UwsxszQ2_@+mR*%^I&9&0^?3Zbs&DAx!L$awCFS=dH$4d=bid6ijP_Af~| zhlWN?!4+~uZT;t?--8->9qT;wSqyaxX*#Y!a`1F_3L*;v#yI99@Ek*K-Bh}g@r35$ zT)n6j9A*i1+JsD|_=_%-vO0s59688l6Kd(ICwB2Qsi(P2@Y z);ts#5*q>`O8(hxr>P3rL}+r;1XMWQs(f6s?tc0~itfn5FDCjal?Ow!`wacqr<`o- z!Lax*P2&*_|B_2!k&eavT?^s)FQ0gedFTIlWIVrYNO3~r;(f}8E0w-k>%6Avycnt< zqOyJMqX|Yizyf@a88)-62ukNv&lLf^uumLRZU?&0RVg>6tvW>291F@M`%vWQBC>jF zmd0!w$j_)iTOBk2alQOxOD%qmU72@La}}0__VPK46G!%nO{IEYqVdm*Cm*CAJ#C%a zj}tGkwsXrVBu7YObSJ5KBzkaBnMKvu&!BF>T7A{bn*2=9>T2J?^3YE-4oAe{3o3Y7 zWf`99tfHQnqNTVOhl*&)(F#d1S2;#F=VJLq+?no0`_EC&BV>CIGc?f{`@tEa*gQ{# zCjj}&X&2#C6Kig}Y7A=2B+AQqGxKjmfSnepMA}u+Mn{MlE`aP_2|4u`6w@Akq~5*Yft#&wAS-I^BF z9OIH-$L}Szkwh#FK}Bs@?~CRuYCwcEm=Q0fkY57`$Y*t+!BkH=C4bh?^+um-OU<+G z!M?Yl$vq!@i5}CtRW(oW`?oNo>CW~>=1F3R=4^!?0J)*&i7l9s{5F0&E-3l@{=^E8 zXIsHBk0*%GqM6%tGUk6*2!%R|ltbl2K7=c(-{`IbpQmO|IEAX-%qRO{VYqiqAKu+y z-$@jTd3%_F-a|_>4PR2U69QwwS zBw*&;DMP^(tG2i6;YzX)#ry;^o4uXw`T-7`|C#u(K4+0;oYUUwnh2oXcN5+aCv3L2 z3eHt*b?Sg|usYz}lMjZow^-x4)VDK>p5gJ+fd368>wT$^><+a>9BV zxY*uT<*lqsvi%}qj$|98Pk+-1zCjJ`eU6Zkcl_}RA|&GzqEjI;qw)IDlHTa36k5r6 ztEkxTw2{g<{f#U$m0ix`lxM%4liNNHP=CXdYk6;&()Rbed!^le7kNlk;(&4%>!OrV zlpS>JDkhIVu5ZwYFyLR_w3_fC%-%{v&_2nm!!p{Y z=*vd9>I-wz9TV>=3q{;)^LhVyaM|{X4I%Hz(k9(1V6w%W!mDuK=TF(9q1Z<;1yR#q zX=jB$;DaG$&+qj4Pqq(-fA1dmMpIG<1uLk) z9$BDD+!4{G!4CF{a(o^>(pQ`tgBjg%`NbryL1)<2PL=1#WJBc~LZ9)}qT&&lLlj;h zIDJq^8r_w($3r?O+tawb)vnmfh@T?S;k600Sy~O2AIGeaJwZ&RL-St%++8ppN$0^Z zAQz>B-oamG04UVWM|!J@QZDK)I6tQd;G{=c6+$0i1>SB+zJ27>;hOORN-B|Kz>Ty` zxnJ>wF~bX+I0awuLB-H&N-j$w{%!2sX-B_LY|AuRiH3bsqw@F}OA5ZSO=#i)}@9Y!Z4E0KFQ&av#E?ST^guS12k7B)8tt-$gB zjvaGr>&kXOHZgw?-`M*V15#PNyI6%1=|P2#Q=VB~EHA-@AY?(c0$$Lh;Z4Q7y6g2< zI#~~kDfla#Q=~I0LcFoY_OK^ve6J%pBMxI(PM&-BHRxlTQ{k0g){+}T(=4d=x&wgI_fsCG~evyI$4t0#WRqow;P|!WN%gwn+Jp}{)`rPM?6zew>I*z=hMqQ z4bTT)2u5IiA{&0~-+GN|g}BQAuIe(lLMAg1Ni7BmRYIG-6-e zS_s|--i4G{Rm#=n5r5>u6JmxNhC`7FEPo$@sM5GwwT06zUbk9? zrC0YZ1qc^_cNH9MmdsdI*pmQgX!&K#7=$1e6qnD2KQe4RIfz&YoQG(f$Zzm&iW zi$N>B4Y}8MlwDZo?KF7frvjImX+}_T^8p$Z{L8N;CKU57Bl-#`S+8^>?SAM9m1j$6 zJQdPl1LNTkj1N6c2P67v(qsuoo0ED}5Etjy=Ly*liDX}48nKQ*yvJqsqflz3+^7x~ zZ=E_4bMf*v%y+zK^hZU+kUqJ=#g6KEWZAXuhH~0wRoA0(@0KVDQ&|ZY&(9Z`~m z9-Me(;(x;amSp9pE&jNSjKc$$E#dRKdu#4?^W`}Hb#sE>wxWBoQ^9>!^_7*pF`J=y!3p** zRuLF9Zo{fm6GN36Bl*lQBGEMMAOQrv%rD5ehqk1DyE`b!?ND4cpJ)nr6eMPIPw6Ty zsx(6!rX(1d>(^&Zt|jrk^Q@L)@mQ_jS!F)#Z%$N#tt9)G+xceu(-m}YuFCqne6H_B z0yFmQk4Ae=W&*p>wE*=ap1uVIar}EJ3Ikt{g(vlEapBp_czr46NW82R)tKdZ*VE&w z%ERt=MM02RtA@FE+fBkYPoC9(Q1jsk?IP<_=_u}1(M}GS_ zKkWOi3;ES9T$0cbR^=`0GxkPLzApa$#qJs7?@Wc_9?Wje%7csvZx+)#mTc693grh^ zsGaA}qj#Tn3$#-%AIjX-aZq64k$VeSlewdFJS=lr8SxyU9yH0Z^H-!XS?PLhsgmuS zClKw*KGWPNW>TX?u{hD@d8{hrQM|&ON`a}@6CgSBc#N%@0G72~0^uh07px7n+~>y@ z@Djt0tBNDN2A%i0dRWSVO=yUI`4qSAwAZFLb1}>q%Q}}8>1qi|-k>j~Y0K8rfZF}p zx356oOR)4N-_mDpD-c62R#b5vWl~5TP+uS4cM0b!XLi;D47&gsk&4S76VQM;Ji97Y0;YU_W8NIsJy+P#vSN-J5&IW4T)IEhWl zb&OF77jF1MUBWGddV+meKDDLCEEs_wEu|AOnxI{j@W5XF8gT4gmenFraPjQ~S2va8VzY?exWSz>e}7FES&g%cK?lyXiOApAJBeNEb+ zZKb9HqxL=5R`X;06vPqY`q8qRg=CqH!XDVfv;+Z&VxDEQ>@7Yuw)XLGi2pmg!~``B zuA9sM^N4w=$cm_vxt+vh#bYr@7*M<89{5Bc+v-uvb?2raY5(Zkwxm%? zYgo5!i?r?_{-`Ti;i;*1s$V5zv$}XH$9s$F<%FOdTd7Xv>HJt`_9XI zjmYtPCg#}!Tl!-+c}sy1=*8RWt$1;F864UAnO76I z!|%1fv$Hwl_Qr6~r>i=Jx5<9*`(}d+Q4O;Cf!#em2q~le%3z_XzOq60J;tEDN|Dld z%sYmiY{ah(DcVFQyIB7MIJhfLRY;Ya&i%!>s;i@(NS=JD=V{C(>_wO9*t#QQ4h|+I zPDLE?7u}m@^9l#S}y9ShL8Ij;YS~V2`;p)|E1G7KxXvB+a4%d4T0h8@TTHaiT*E+>T?vf*+0aaLKQG% zCNnvDZSSR#J)Oi)(Qad;aW4v69x|ro`Ia-f<_WviHl#BVHhv9`Dl_W8|`z z&NyXbM9md{o$E3NDpE*`Z<5n?iu^fiAu;^7L_c{XrhsAUsa&E>xd{OP7w@Iy?^5Q6ip*jWtWl4vO*=Dng-+iMhz9KZ*o+^^Dx=KO-C542E*Uw#>y*)2 z?ybGR==vr@0BZW>P{|mkUVi}@{%`96DgCOlSv;9~SqxObi7Zra68K`Q@n_Casr+x# zzjYjRIHHh5}-lq%+RfWE07K(!VJ;={*R$TG0Uv;;AV?EncJQy~Z zxc^SFvNID?(klcBZ$VKSq3ZHhKen=~P zTd+FD|3cd1;};{N-whPi5gI}Cg{jqaB#k@pnTE@%=#{G~`Q(Pa^C|_GLchEfN2uLJ z!+8LegPlb>YR7lPd&}$}cXq>A!(W$?nSVv|+>Vj+bme((*FI@0d>&2vFgEhM{vTKN z)@2Y(MfUTEtImjHb!NgB->EFp05S${*s#pFPFN_0lM}q3=>O}V5x&yYaWs)IxmAS6vK0t{BEr zaPur>a=$>X3_q+$krMyUxt=d_??k%xqU9))rZto^6L&4LP+K*We zO0C1YGM%hI@WYY1YxnMqZXL))wugK`PMHz{{oz0afH_-LaSg1XTyghE=vlZf?G@{K zLC8fY=*H6>tY!o-Laz7?z*h8_wIQ{R{>Zh3mxOD=-~Z}XwaMA6n0i;W)s2D}H9m{b zHm(mT#Rbg}GT9Ipy%;Ob=4->{Rm)}IK{~D3L4Ds_D`Sn-Y3YoYA`D`iE1z_4VzXaF zT?|pm?R&n!9Ib>Mb!u47~BtChz;dylWC}$bfC+oeUIRkAC=bPiqc=$o?Otg zt~1V;@bz?xY6b@PeGWF8V|xy4w~pX<5n$o`r;6ODtJAA=cB8I$rq_JT2IuyQc3t=F zf>uybB6M44r9W<>k7r$MX^IWBy9G1G(xbW0OS$~JUYO$2V_xpBD1E+SVL!WjRc|fO zEm##JLY8YKz0Vyw{h7l2s`M4-bx?#uk^)`Z+Roz8Ey|!7b|DeUw7<5QYLOi|gVE{0 z1%&|`#&F%vj)Da%Z{-O~>S4lAL`^d}-Py~0;!(pGf7r#)t&_l+_g_UQk?R{Qgg@z& zi~H{HCoea}skARv!kx}QOiNepHYJu`hlfKk+kU?2WAQ5K}B#j1&$Y8JRRpUB3^mpoFb>{{TaeBNqmhk@d zbUI;}NXGi~q<&*MEhlf;>ul>XD|-0Mv^3vXI^{EwI~|+$p-x?biS_vwzus$hrHAlSJ}Y_iN%C2L2-#C*-wE&DEnnLHwct*KUDZ zRME|3$$RyM>rh1_1e9!pfzt5YLrQeOuDhyYH=Ut()diZ-k!w0G(#?z0q{J-cm(~Y= z?s%6Nh!))@bJpIc+>NIsgT{_uAk9McXxDHoTs))eUdOE}U)xqa6 zaYK116>U&!?c&y}w;h|i$LUmtXY~bKmX>$FvLVjzKRsAc67iK@5EB8U{TYgQ>~F>) zCn92Wxmi!zB6lxrv>Kg0ryvq)MW|NyN(bL*Kj-u=+NkUSGfnGd@YbJ?__lyE)T3 z)^@#4efJumeU2S}tSjkGClp186I;8?!-QuQP-3zryx8WoL88gXqAhpfQilDUpVrwi zvjZm~)JV|CaSikaRaYiyLLrnEv2l>k%*N6*4lfgM^to;?GT&(3{cK# z3V?PF{OcJj31RAP+5wyTE?^WV(uP>(I6rXWZXPSBGIu31v5fE$X}Xf`NXOaeeiz72 zRn*2up1^VC#4xKA3CtNw8~Uj3UqG|PR%{Rxo*WR#nUkQS*e)FPYP#D6TSLN2-U+VCjOsIDttP3RtAxs`HPp7QbvFE znZgRU=N{MF7d_<+F@jS^XX4?V-bMj@uJn&=TNw?e<0eMWQC1e+sK}= zh!9|(<4J8kUsp2?Oqikm{S5CH;-}wbeD?Y$GHJQ=8WzT_%_O5kYz$LxA1$jKc6ysS zspePrbrJoVsHcJBmwT?V9J<^cEB!CFY6-L`q>^V-W2~j4$icl0|Fv3ZJ0pdm+OCD`w8ySqQ{V3p z2WZxPpeZY^KE)UrIJ}lf+3F{5z7y*0byBgrezBYDy3u64zVBSVa!n&dh`#uj>o)D& z_MV62sPaL%ixNbdt@LFI7Op!L# zDPTz?e|@tZlh7mR>-95vbt2>>GR|50VzK${(dOrJO49eHf5L{w5PM9~Q5`2gVKpnu zO2(L5G%~nFrvbro$|h$VC*hTFDV5MuHo5F9O@XJY+Xe>fjwY8)$hr1K1^N=1+$XJq zm4r|KTP6B={e+1ygKIJl{a^Onp<8=7nUUph-r?&1B=^CytgS4hvP%;}y81nHE&l51=^*_JLZcw<}q{i^Fh`)l<|bi_&>bieLZ)inZf^N3ViddSjtVl&HwlzQS^r}I!@4RA(Y{i7v$eMs% zajmEjt(0G_!#3ul7WCc|_UzxbF9dD;g%nQAP}GwDqYG`an7TY6PVwJ$JwNbAvklcgpkE8UDeH}V9;$*U?5Q04y zzZ1=o*N0|y2CDY3HY;@#y|Lh_ zxe|vlrg-5~*-BZlic8_!NdjFiDwx_1)1~Rm?48@RtpNfimIIz%V>c@~sB0~~7N+_| zd3Ip)XNiN+KT4-2xup01P9zNt z=zxkpxkVe&zfva?@KDyY}I{>h1TW_DaaJkq1XKvtDmGZ8dF;( zdR?7y@hbak)=xScf#tj3C*uu6WjzUTC}P=@GlxLKpOiWlmg;8MeI-ad3Aq7B2~ zb)85&>AYX7!~gll{Uv7YFU9Q1TQ{xez7R{d)q^gS&9M#LpPp z3$k8ga5a|78KddI_~j_04PAbDH7#hd?`t3j6@Fe5bfM1D+%7g#LiEK9G7xp!jW$HzfqGt;A&8mR3w4mHuNQ71BmR9M{}S2phay`~5uQ8z zLQkPFx)E)s&=_4vsZ(f-PGsa=Xp9Whl2d4mb~gVkG^WeY%TA#&TG1(|&=@Tk&AZZA z(hD;|7kugVDl+yi3YmcfX^2R&q4u#Lc{+7@crcx=_+|AoM9TMmzFDLv@jS z`cr3&R&>%mXdPOf3oqLI?ouGqce$~m7c`7Ew6cpH*d#4zNxBjK)Ec7;Idf`_(TUDe zYHV}q$A+A^y6k}Le?*Wzw8N_JT4O~UBK_1FqZJi-c8Jn~iJ%R>^q1KFBYB^ALc{KB zmt{N7&CI&l^fWw!EJ)72);VU=$M&7iS(~kX-3XoJHMWdGq&GvzwKJrv^G28N>!xMM zfl>wUODXqd)%+KWg|+UhtaH=GuZyl@vnH=wu#*(%s_Twy*-S=4HKOL~I_^`~1@SGP z>kjVN8uROhZYK@a*TiSVX8YVHxJZWyxgc?|rmma8lMHCz_lDcubD(Oji^YRXXzO+L zxNg??zLJ)lDl(^i;~D1*v%|<=ytln_ERa3F4&46rSjAgYEP*=Iw1LR zqKb5^$x|wiHY_`p@<_oHsaWCU!pNzUcYpP&t4^Lb&F6|!B=1h&3l((=<OLa5UAjM^%LSbkT@6T0C&r%E1O@S?4e zI&{)TCpte4TIutZ$!R+k@@Rv%(1Nh~e#99$Rq|*-tfC8J|Gc1oSyvAEJL}5Ur(}P5~6mBiP4T3J;lUmJ~K}-F`7?%6%#8yMNTm>dcoW8LSkpVb70Y@m>AtqGrFLi zbU{|1Vq$b)+D|nx+RsIC8v(q^U)^8PBAfB;QbU6qZh2d#l+MH+D|btx*;{E zm>6AHZKseJ9hi<&NbKs<9X)o6iTyjE{SwH3BPslMzO*eVw7`s9u#%^knA9NVY_G_>nwaxFJh1XpO^j~j)F~&ny>w`UM9>Axrq`42 zN+wGDE-SXVvQJGhI^a6ABSdLO&77iQ+fqRk`?}(Ol{_htKfE$0_nRP_xW#?yH|rRf2qLzip9IRKd!2 z(2PGS$mq>lb`?EWa<1fP&Igk#lJA=7(wh}_RaF;KuCfk^wjmV~cSUyJ>>Ltn z^R9a55LeP+5?8JFAOp+$ssx{R-+`K|RJ>A&u)COikOdvtrRFyqD~yDUZnnx*ratar zCa)s)agRcEmAF4+RFG%VwVNid5+m_T=>9KF`u;QN6GWUMQF*)QLg?)hIuLD_$7w@) zZ>JV5DAmhJ`=D2BmkeFIwB6~mYVJaBx!vBhL)PWdq!p?`E0#FDV4>S0SDm@;O_ih* zX})cyw4-|NE)^bi*R%8O=%EFovt6clJv`g)4!RJlX~XiQ10}kx^08sGw{3z}$jR>5 z7HwD6zMk!BxvTo+_H@&U9-|$)j1EZ3wrkM__sx6?3vSMeyYApgq5~3n*CzCBm!cDy zy`5cmZA06h=cA4My8`e_bpMg8@ZUGh73Ci*D*8^7$%QE2w9324wA?v1nb1|6CQ3V& z%AKHpENIJZ?YqYGE5Wb3#B^+HE4`nzqvn*D_HC1<7eVE)tYux3*G1vT9I-&#OMW&O-a%k+8-BMKTe2k`kY;Qo03Hf`Z@r7uT9xqWLmbv zi!Ox5wgwrn-#&Eb-$)97zJw@6rCgP;j5I_J38^9-ws$ombjW}%QqfanVwsT8w#`nz z%5x=IsI@8U9=y8|K391!BL~-wantvzZIke#1fsgDhQ(~o2no+56`gg}8>0;6O(9Il zYo&r*BUir@2<6Y;L)j!;gM_A%1=G1|p*h)5D-yAc97rh1hukaTc2!x6&6y_QbJo*` zgghUV6rFL^Z_8i8=Z~afKmR)1uK}YC>X);F-oN&37r$$S#t`S;uzYK#;Xyta*&<-ixjuu+6?w_~0i-h)Nm5&RvzAxbAb{7ft z`=ze5p<=U)5@eq#L0vUMrMEqtE<~Pb1sMp*O%QP1Qm=2Qud%Q>{cmt2mZ1#bDz1n{@u z{wv9tEQAY_1H&jIXALSyL7R|`$+=FHS#HZTT}T_c;7$g@sYH&E2DhOD6HN=ua$D_A zbnPCgmLt^ebhl_jZwHpjwwTk35v3I#O)vBcT2Nzq7LsGz;$$K^$pxRG9bUS-oVnhw zt*|r_8cwdglsk>|t5A=)kXVsq?weSr;1#4{81|ExRivO-k=K^?oP7n}1lUT}QzMB@ zTFIsWLv|b$9~rDDXb`fijg6tg_e4aZNN5>Zdw=ODdVS~^9}?z8vYG#*V3tw~6>{|} z@cou+WGIOhR3f5&iQ>PMmwD|gZ)Q2Y;OcZ<-+OFet7*qf(hXfs8|^c+#^i*9lqK{R_-LPAsW^$FpBTd;ZcwaP8v>tDIhl3i`SOR%)Q@V?#;Mg|?;@ zOUZ=SkP9YB2SjOY?@i=s?u#aclxjDEb>POpJ1J*g>O&BN-N zhM1%SWgiR5^-leKZ)>(0bSrJpHoCFoW5p_=6Jkj(bO!BE?<(pac+k{L7kT>O*l`CZ0FaO*ON0l;xM69}3OQ&^II@s8#lU zJ6+VbRM{dQM%Xf_9MtlqQJ()Yb1elmiq4Z!$xx(x81muSv{-8AGRR}j*hyTnA;W_< zJE(J(r^#uVjW%s_{U|t=MJB%7s4RoD^FtY38fn&t9Jfo`_uuJwnWW4t&&WhZlbm~3}}B?>bd7=(vqcNGdYT+{bq_z z=f$WZdh^7@0gmuDjSMFiSr%?GLuuOIL^On^^vzR)m+_{t*w?U|<;sWr>^7TX?s^kf zEbW|HEfRkdX-`tUY4)zvWD;)@j@svwqhkX;!fpy**npq789okH-Uh?>=Gbw<^p~0Y z9hu+HUm`fDj=C49y(|hgFjqm5{;mOKlxAT1QmA;|a+4mG)1Ipl<}5$gs-=;{oGY0w zgB&cBbP}ErQ6DJ@AqC|u_505%iQg+$m@Ro7H=NA**B^5$#QW* zf?R#+$knYWJ#UU2o8wZq-!$5n8-22D#VKgMIrwRf$=fgSrs+;IcSASlLz8}!d}J(s zYXJTsbKEk){-;Xz+kIPqMN;x}R;@f|DWoDF4xjf2hsP-)=d!5h)|XdF>UpZqx^zm> zr#6l2<=nb8F0J?e`PQ|unNV|z-mz?w2Agl6h3F}ouY%a~qv2c{`RJ{;FNG$aVmyla zGEb>~nug^JJZQ<7BAfWA>vPCr-hF^^a_MK6V3fuoWKJXG^X@PV6MMWH5Z#pG%QJbq zNXGxRh0#IP{qAzKla0TdAdM9K-6QFnt#Fasd=6ab=)1pCO%u@MswmZ#L$#|&CD|_H z|8aNo%Y2P*dgRTg7tl#Y;22Odmr4=|n-yH@u=B@8I(l3IcM2Rf>y4Yj$xpvDx_eG6 z9ga%4RMtrnhn3(nWSgvubmq7f?h1I`XLuY|y)ELbvoY*&!O4A``aC8C7Cx@Lwpk0A zUzfw94J*f5DR_>b3RD?Ap>#0Ij=P_yp}-~rxAn2`0Vdzr-S$7yhM9arBGi=_0# zdfePFzpZmE=dih!Ml88@vT#^r;9-GPpAmyRPRvx+NzBJC4M#W} zVr!hnfRh8x^y7IRngv!LRt?-acgP{LMr^|2z6%1|sTX?+EMh;GxL+prm!v6%JF-N% z+OuFYhouviI_2wOgIN?#{kW(-U~`AF4CXxciFH#omNUdm60s0abU?L_lg2(Snh3bu z;cPHGLFa(Y6&4d%EY?XKeMbx$Rm7qbE_T=?VDDr0V~t)1RUNYy99(Os8iUorh*nj-frlS5X0OmBIuxqV@BpPl{*ix{`p;>UG!#M&MEF=m}8WDXoL6Z$z>l(MSa74l*2A9@7DB5rj<~d@G|BWF|T@P7+ss*+gY$NxvvI0jK z+;UvM0d`FZjFWn$YGJ+$3s%b%>{eUdWa+5^RLZ~n9kg%mkAs$ut&qy4p*-YU))+q z)L>1AJp~RnxY^;dexFk&K8Q^P94jH^hB_Q}o3)XOz*!1MDLi~UR%<2ESvwggZ0A!V zmeq(w%Q}c7;O2ncJ~m=4xTRtBkCT)G_6#`YINRYFuxH+r&DMogN<`r5hE09lB%^Rx zz{vua`_EPGm)ZR#X`}_yoGtv~I+TW8{FskL4et54k<-B~aM&Ej!0LT`&-Bjy+7V+# zEYso2g01vekGa9@RZ`873Fy;sRKm)0EFTArBk~7}V{VvzVAWYWRWvMK zu;;)W1LJODXfl>YJqmX=e7lqptNY(ENjPI*)`kN^D3vShCa_1r#sfpBwP)$378gZrtFX(V;Js15oaz(hb;HZQn?%Yx( z_IhqC;E;fo6P^ayRz~j6J&VA@3TG{yEDH2axJ6*&SO;ms&nX?mitFB4Et4b^EC*xW z+p={Lo4^(hdj>4ga8KSlKUp`KI$UXY#HR8_EK=7)QlQ0v(G5%NhcG5)yQ}suGy6;O z$e$G-Z6+SSPB<&CbuFYRuua14e%!N0+sj*&QWRkI`25Kci^&u7UOP$6v4NF(D6$SR zcUZgO(L7FHCq4pfu`c4i=D0%^o!C?cJ2J97g%zVdmF1TZ0 zvyY2h6V^<4<{xV$#@~@g{!ENvj)9YX%~T0m9C{2ad@QkK>R8%PyTZu@hZbz7_J)}9 zASP1lrtUz;UYEfU1E&eBdTg^S>IqN+jQFr=L}3+&vvQr(S+HXPM<^r_QZ86h*F{sY z4r)%g++lZvLyoyY?N~aM2+T3CS+A4Yo*|7U6|odB`p|k{nSmvHt<)mxpz(oS!7>3^ z6AtOMQ0ln+^{ZYmPiXgIB4Z-p%zE_PU;QC%su<}EH798s{j!rB6dJA5`Z5h{d)$_ySG*khjZ zCoHY7iLaT41NI3xwc#v-Ex`5wxo&-S+kepH|K4;SpsJaY#nfAz|WYS zy$)(ocrxHndD1)hwrC?Jo@k~Y4_I~Jj5mdZEw5W(u7*86HmXiIBw?THa#%UvF%zFt zk;=jirUqDAu$dm`F}uLcx)!RqHiJFhR2`Nq4Ed(>!qgSV({QxI!^b**s}{`kk7B|v zllwa|$RGDs%f-%53T6v9>#zMSVhm#&Lgd{tHDO|#yQ2z5CfMO?As&NE15ODz=3^zXf;|n+2>7J-MpB^Q z{sWQhM2InhlT1CE%5d><<@_ur{4%${^SPF=PQYV%>-@GJ;y%`KSS{~fLBckmv4G*e zW@;X|PC*JWlN0N_yA|hL2dl#74NK?U>`2%`Ve5iJ`fiwj*@tcgcl_~=JN!?e<^}r< ztXwdg!`5~;bQF$MSme0e?xs+|qX{e9T1n&%X;g7SIsqFe>}_{fO5v=4T?OvUyQ^hu zBR&joP$OWGV~=M@rtCp11Jn$#L+;L-t(`awb`4mg{_}Xz|1z`XJ2J>0ucE%~GI?FF zxg3k!ZkxVtSW1u8Z^up_Tfoe5?3ib_=|@j2{cQ?@TVaiag`cJ(Kh}VG%x$T>73YDuk{WY#GJ51A6MQkIEBUgU%x}i#J=g4 zMDGW3*tOpejB-&5%$2Z?9do`Vr~3zmB@*tkTe6Ddv!o%$;_vr1M(zUU=&g#?u@$Ui zaJ1gaS|6Li;s$%$EzG@B4?~{fT|Ltib~%hyQ`?aUsG+cyKXpU!i06oFnSc9fp)T3S z=huvwd7hZYt!?@-J1i|Qx8Bld9jC)Ae(blrYI1CG%ddH?NNNz%v}YB%CEUIin8%N~ z{_XqrP2!t&+4YwF0-i`X+3zm9!xqO81q&Z{+Ee(uyX?pL?y~ovg+Iia6ETsvyX;Th z5A;YlP2lQdo4dy@pakeKPu&l0RyfP=_Im$O_e03%gp>pBkf-jqwv7*5u5c6>;^0`q z+Htqo9p)G~EB~zf-7R)QECqBsv>v!6VadKb?Do|C;F#kwfu|b|$-BKCu(7>c?$wDH z&)s2v>V9BC`G2?*sjqse$1RQwoPDc72DU>Xx)fqIxJF>9TWf;M6|?|D2235epgA`P zh;-*Ib@^I}>)2ypEwC&=&$-1kFv~z~gS!QesI?J~!L1FO+(O>_x}_Vj0xJqk8+iJ7 zxYtgq`cxBPPl4SPE)KZ3;0cANo*{A6{;6leo}f>IrC`N?)45J!T_$Nbs5NkC!C?hA z2VCRpBJNrj@wpL4%*qin&Y!JAzf9=)=J)K{@x1TZg{>Mk8`xvkP1RtyfvN(h&eKy2 zmtYz{eaDD3*VC5_55%lT6Hiz3dgJ3y=QAuLu!X}q{&Y^mHW(2xO+zd=p5|*fMPX^M z)SlLDxb1E4h9_jr6glf6&VktymZz~09psLfiQESBV>gH03y!?~=?Z2yY%_4n$4Olc zj}MFyo4DhS7}RcvsTyoLu*K74UvRL(#to;(zkZ6mseDtnNSqHexBfF=dcZQr!FuaQ z4O}XVUcfm62R9tjaCN}u`W)_nB?mS?W>z@W?wCw%gV?aa*?JF{ z1RPrb*yCsVC28Z&HUV^g4X9C|6v$!Arg{sbpsB+dxACk%4}m=c)(DtQ;Ae~O&4*CD zg(Z%&;&#Rj>}{~7!aDVI*TEv7#k}R^+nWd1UCeI(-czE!-F=|7fguGe7tB5|o81P& zeyZfKaKPEUMm_L?)9_2V7sD!T)uyq*!4YcoaXUoFZH_MnTCb^*@*=u!Dv4=gS+fF{N3z@ z``rtEMpRM6%IDbo?kC%EADcOBW^ig8_wh)=ZvAd)!*lGD@0gMHY>UHb`fQuS#Q|I9 zaXXxG+}ZH>@kFj~2FOF~4aZp{5fjg6`yW=Hb&^090a+L9S#XxY(GE`xTpUk>0^A0M zL6cBRyh}7Ah7Jf=EnsVdtp?7_A2o#EX7-olkUy_M_XES_4XgRnrw;Q3Y?AkH3Ldlp z-2oQ+T1j%?XoEfC{r)}kX|;zf;%&vh^8q$-SUln6cTT}Qdgol+(*R&+gMRP2@3cj% zB9A%lfrEn61U5@J(%{sBeH#Arr*#iKz}5!)^gE{6iB%7*-Os5C&=0NjSQO55SQ?y& z!|;2qBahACuz5!m$@gps%>JGo`Ev<#@oWF*b`krI6!J%#Iq2eGxxuXphdo`91)DmY zIP$S0%lDnfC?(B0NT z_{tG!c0}@RTm{_*4oO(K;m&xDnBCr3h07X_2sl{b9)mUUk)Q5c#8}_%+HkQVZ?f<0Y>{D%a4Nm!g$(6Gs(nSX*%FZhof{YR9dh+M|x4;*bw#-mqw}sn;1N`Wa^$JihHof z$3gyH(rcaPye*1326qZ9;djKM$a57g@pY4?!&(AQ#JZ@}*F$LoTkWSDCKFpHjXFFn zu;;9W(gWr@a}13wgB`>OgU1c#o;8;BwNq-Wom7q`5^l@2QFg7dSp}=uI*IdRrLwwX zCS?p_nzAm^(6vz2z-mu)uoz!xj&6fvJzCrNn0HFp02`+uL1+iEDK zK+bhBdG@*}N7uN7!6v;{lJIrXxWLm1M}BP7qOeq+5z!cVPq6o_gWL2;Du!p!mW z3jWK?wr}dlR=m}w%hpZSab4>ojqBERl4u@Jtes{G9$6Z7$)SC$RBp%dS9^3yo><44 z$-CF}K1tHjj$_tMY6T1Xx=6&1L)SqwpmHpoI_o(7?Nu%h#7upyl_bz&XF`jid~R&!XoVK=?i z2Zd7w=5n}ZZt>y{88kV!z9H)*F@c>8)_&?E3cDt(ZE)mMVga+?5)6zHQK7+*hz&bp zY_l$6*0;(7_dHb~Sh8UYh1K${B*Am68nAiaLKMb4v7V<`1-r^y%z{1MViz0(JNt3k zKj)HOUv-ow!l}7VQ@Ztatc5C%YkIBJ6USX^CssW!SqDiR#tIdPL;bkOIpKU(xNZuQ z6N_07W%RM)=s>lBQMtzH$zz?h5u3nXv38R1W9fB}R>1b$`wm)}5Yz5;QAi%k$4zMl zi_3bbLSRls2U2IOjizEPG_7!Vt&MmbuCcd8N;weAv2~D%!!gSwR|UOgjnmPv)yMcJ z!5X$6DhpUl&^gxTFmt`lXM_JArLI^Tu?}1=YbQ~IlkM4HhyAy@w$C>;{JmGA`+)=I zUSjGjo5tn-y$Wuw`!{VkEAQWY;E4N`1nBx#^Ie!BW+L}%O<1PSx2_*XsB8ldz`W-&3bISdR$Fa&?2Ku>kTjSbt9Ca5FJ2t_XXQ7D`le-Qz*z>GYbKH8? zF_+`2ANQxf+?0fT>zgo@DrTLOm20EOgtcw$WW5aPk!6swz`?#wVr^Jt;LfE}*@s01 zS;`;pa*vG}P4{}prs0&j?{g1WV|fp?no{?`)5oE;-gp`;C2ON{UkkO&8FHM`6frMd z2ZcH;7z1nQnyK2?MWzY6$GS=FSVY!CsId%H-#5Dp=wa|o!t(T$2#tP1efvsq&=>Z} zHR`RfbgZ4qXI*C!a!|3rQMyjbC|JwZMJXRAP1RrhGi``v=Xxl@VVnH-HIn#_O!8+r zb3GI;I5X2*dG`OI?p||q$8j_OZ)iY>cXIz58~PRPn#y1@{Wk#`bslwf`y`(%ix)ZP z&}o6y6b{XCQCW?dI1N@8I1i7L3KkN%*71B{ELG6*Q`Tc8(S+3uP8~Xx))4yySmio8 z#t6>FP8`>I6b`BFfvUY^&?&~4B*QK?7D`vR0~D1Z_N`;4jk@dB8jFT*{#cLd7=I{|7qq;nn@9juvI4RlG zw2hV299EODke+LnZIOu~Q}gJMFn_smMLnot>lmZq2$l^X(6np4>GvL^SAEm-J~nDm zI622kr@1yx4Q(291JPFp{0!aTO0p!nY59|#~#Yz1re*Si2) z!ABUDj<5CMy{2fq?GZ3X!FqYBx4ELOkHB*LO{#5!hQiqmp8})a?her7ZTo7#N)a2j@b`*Z^4(=d0A>-~j~+v{T( zllppr#z~dEy+Q_CI()q0llIHp{u}Y*&u?f;uk|KIwlv$Yly2!ZHZ!7I-Zj{dZTYug z7KYhn}P$StA! zml{Edy%}clEy3%X8rc$k!)7^t#u;YTt@6LQ5!o&P_&sHTu_L?*5*a6*=FN`iHX0ag zo!f|T%@y0MP-D!@ZDt7A)5by?3`^=>lS#WbL(W>iTm0TfB9+(zm#)TPJu6zy`DO7Kj6uUB*yE{z?&BV-DDgw*+ml_g1JC zjMlAS8=P>`(S)mVi{Jux`d+a~w(4}gI=aJbymfYkg=_2Za@|{|S5Q5+l<%-{jUBf% zeDiGE4ZvGhdbLb^Ufb#ARX)*^UUkWA%1S z@O9UAR9x5bABDSLX7=9*A%9k%HfFxo(_`$VVfNUV+a7N`?(=7+(l=%|I8rvG2mQ!K z`~s`Qcj&fUF~$~~H5j5bc9RO%nZ8K}hrR4gOu$;TiHm}<*y-7qvNm<@qODBgfw4^9 z)QH0FwW*V9wau(R%(=-I8y8h?Zbre_)J-&KVw>6#m@D`Cw2Xl@W807&LsGm!KaZU<>rD#4QUaIR*eE+T zsc@}aaD|VR%IZx_z*+%U%U)BcRc~(N6l~RSOT)+C+(?em(hqkuEGORl%J@*W-~0Rd*Glc*f+o9uxM}oC&xvV3P&>R{21j|n8V;S9VeCOCZHI$X80>c8N%|- zVGV=I4eTSZTWto+VR89?Bgo`GIkDk5GDd}Z%*0u+R=^p$5u}WT)UQph+ifGr$4VaZ zuLzREh%?^^5-!N)k&`LI$8#KXqTl!tP7au*#z~qRL5_-!GAPV&bpI=Yp_*9RPB-#ja@2va&%Qu1?J03e5LBd%9Yvee{ z)i#2(agtQQEuRc5_z|jwFgZcR_33<)-N;L|4`FNY}a`Yg)(@%(sO%88n*2iF(|*5k;g$?!=R%A zdI((MaGQ?Nqcl!Z`KM>ZF%oUVCfl}r--eNfO?k}3qHwmu-Lj4Uz8xb4``a+mU-e^d*q-jFaZm(kr-w-)4CnFf6bZOH_L8`H_kfB2T-tt_(SIXq z{CQv*cHcIF0&AVcyKC!hLJ-(m;fm`L8wc5u-5Cblik;u?9^X!gf-7y?BxKwq#;}{i z!MSY~iQViu49B_F_HEU;&Q3V?-~o2XU%N-cAps}b?yzm!MFO@4(=S_nn@xN-@eOA) zT)zD(d1C+pi?MWXn6ND`x>1CKWnk8}%f!YY3YUY~z8yEVQH;Um4wvb6?AQiD4cDy@ zZJ?yv_45W&0hi|PM`OE&{FjOSry|Gi8-n8)Tx??|u00gmV1~n4y!-3eL!}D0C!AWl zzxF+t>i_Jo3qw%_)_Kpi_HM8p4%rQM?%CK@IJseW-wn3z`C8j8w!wCnM2sb_fgHka zv1`21W*#~ra7n=FvS)y0%w!HG`);vgj}Zr4(Xijb{cf{!k0RG@u?@E1-C`$P4Q9Ou zoqc!M0jK6TNt*Yp^y8xA3VY&`7%O3nb;a(mW6xvP?ywWCmp_z;EQv67a+-8nWo>c4yh`j^^={l8EsIx<#L6Zo7M8>K!L3Vw{L zKP<}QB-b1>mCLxO?b@=jo6~TZzwH;}Rzk7KYOEw2u(pnk!WlmHIOz1)n_{p6soKz>_eKDD|4TCDNo61Vi z_dK(dzL*<+>pUdUf?{1)TXABl0AvHn^rG515u%PO#s4~=4$iWbsGP9`>b z-xQ7zwv{9UZ0CpRJxod-8j0JmzNgs?;oonZlx`;}s7oFd^KIMFxfHUkLnbSE*t{S#z7G^W@7Vkl0n%yDq`ue zkcGnKHbxWOxXCl%yZRoI-ptsseSI*~VH-P%Hk?v$hm3_xdmI#9VNwJS3FEluyd}JbaM}~#B@KqCA44cG2KZ5?UBmA(E!JNH05*spP^0bFx zF%tv>Uo3Jx~rm(lY$Fo<8hJNuBoH`icnZ| z1w+L=yvLvLlSnMukC>$N(eWAz9r3%|Cz#QA7@Qs|Iq#!FhlWTF50a68FD=3ezQj<5 z;VI-b403rS&Z9)+kB3+ci;jYl24?RA(1%UFPejFo z$3=bYmzn*i-q-R(GKW-coYY+F8V9NHwe{F2C9cE9N-TX{Ic9Qo*mxXGZI`~4m?%=K zJJvQ1N>|uT$4(q_-8Duv0*>G@Qx}*zI;0KN33QRw(d)`_lCZ=7bd@>Y8>ZM7<=E(0 z!~9UlB0wm4$3YxmCJ)h~4)>l@5z{*}k*)PHlexlj3bm@L zhBabbq!O^Jj)O89&SPT~tKljhD@6B%&Y_ZafG2v)R4nhU zF+L;`a8>Rb1oHaC80YKYZNnAx_vd?Sj+H9bBO%G}#vadT35*-Rnoh;F%NRwNYtFG# zb--Hr9sk#(@A@;w2XbvaW@5h7`nkOQGOhneFo?dP;0+OcT%?g3MHrm%1{uR9Hs28B z+S%YE3|I69FmpmCQ6=5@1|KrU+-yT2g=-J-4VXBtm&buvEX%GpuPFTnB6x8+wC&E>8%qh%x_S_v`vXdpM45+`sHMEAY$2{wqi9 zwCMYDJ zPu<+zP)NBzw#Q1xZBu{Plwd3~H$CWMBWv*R2p`fW7!|I36Ob|va`#P8K28z=Spux& zagz4qBIh^z>H8OGV&lz_3Y=arW4Sz}A!8%+>-HQgCHEM+-A&NOScs3oA`M4uc9-uZ zLrIQr0vJqewlceJ|Ni3yhr{7O{_v-1d~X1$?;1PN_Ub3((jwS?{4UA1g^IY#C$Ozadj6?-lWk z$C#z=scah`>KYbFF=)->q{G+e)Yx%KT%*aA$3|uiAMddeyN!$d-ElTk?HEgsu~iu7 zJ#SG>A`Oc?PU_?1piCbJdD>%AwXu_^kDaP(tW>SAMSm|KcWsQFJsim; zu#Jn%bgWeGD_7&%j{P#b|45|s^Bck}%!2X3VsjE(!hMY6g|MiMQG41Fu8j|!ytcw2 z#c%_eH`IwFQN0yh=dn|rYwdlLZ-U1j3bota-kFow)^BsD zl~tp;-{tkGj7?cNQ^TTiH8=63(3t@c|_@Ecp?TTa-} z!`^ZNe5B!HdBd0Hx16xijg6h6d4nJLEhlVzwB-c9;gj-PP1x|N;3K->S8O%G;UhA} z)@NLFy5JMTBo5nZ!bY?jB-~q4;IWg0!mN*tq+<&TF;-H33kp6KQUyh8$V1zDf{)P< zx%Gsd%!2g&))ZunJuc$_3=Fo%zWxF7EgBu;EwQShPBNl;UUGu*xf1>Gy6uV{-3 zH9l0VTT~cxQ=Wp4@wmu)3ku0Uwx+-AM)+?;kw5QM*iE-Im@qTLr#CjKtt~1nr?>RD z@y%P93{bq_qxjG_M6xYyCM+IX_~Z?--nwYOa;^{C25GW|)oVVnb=TM+ug9{Lsf`2g z(2;;OZ0ovyQHL@NwVD`C}oKp-{Y${8x6LuZ{KtW84fMi??*Y$%5F@e*RqHewo*QBZmB0 z`5OyGF{}@jO1=q{m^_9v4~38$phnBEj>Xdc=v!4Iy<8(=ryQ{E7ATqInurS3I*{=6Y%Hk!{<9k%AN&^g!V)Of?x zXmaVXktcXOhfWo`A>=S=hCr4aW6o^!_#L&(+5nOpecsHn0i=$F%nVkQu~L@20p!?; zC1Klhk(LdgJr`M88$9}V?op0z0IBH8_RKec{Mj=5%hdi$oswU-#`Y!c=Bplx@77Fi zOB2)D__JHkSkfTrP+Y=5fdAE%JG+F60g7kVNmDTP!Kx>C^1aYFVYx>_7{u{J-%24JhhR`Ba1K20lz2+V*Y}p|J6VE+jRXafnf#C zQ>zw5*x1QJ$4SLCwy}+aI(v)~H_T>ZA(I+ADf5^~duU{3!$YbuR*E++Y?9;|5-Q_y z5jS|mhe@X&GYRL|Z|4o>@6f1nC^+XeR!ZY>ZMjOwJK^(?$+U(^qWqQMB%ZM#vqk`Q zO2G;34{phfL*fjK-8)7~|99LuNTX_y5BC zc4=@^NRN{$YFt#EV-%y|-JposFv+aPO3geDO5K3Lg*-8Y+9Z-TkDX==Ta0oY8wI~x z3?D1C=r0rdPjyT3enT>5Y6V3fDyj5X$o;WV+K{PIhd~?+%XyqP(m--L$Bo}sr>CiI zJD4>j@-}=iwcCspdG=VzbeI5`KxeUtkUtU>{=8N3c&dU$W9$@_@6;X}N#@S&91E2jEXT)8 zCOfx{Z?>X#YH#wP$mN~iJx)3yJGUP@dDTwt$3}eko!n!gII)x4#!L(yN1DOQcWOVb z8IGOWyO;^0&z;&mHmtFj+QMTc6Yxk4vGnfD*6|@J-kEL48$>%S&Eq6Kx-&b+P7ZOz z_b$`woz^xKk|_8@jSq#^PV4b0>aKTMkCW5|w&<}_^gFG`sM9lSn&0d5)$2~{eItKf zn##UywElx>{YPR#=W{bwao?for>VBQ<*Klxj-9-RMUYXrf_%e@WFIDZ`&cN8;d5-u zGQ%M49v6w#zj)mIB*e4r&VyINiLnihf3$@w!keI_y3Z$BpbI2sZ<04HRBe!#} zScw@BWXmk!X((rC$5~p25t7+jv}5tYasM7$=p3o;eJv^1YFh90$qKF;1zDokSYul(CVxz~VK=QAJpFC?wsuB8DoFz*AA!-ruLr+8!0+>t=5O!JuEWBY?K_s#?3wyYvi?*pRDxPSs?*x@KA2N=hXI4 ziBlfB#?Xi*H?ko%r1KEon^C3?gN}j5K1@0XJgFdl^Pp0`c}JCprZH@)j)!cF@`F`&X<7ou}}tRCx(aG9X^tHT|8TjlfrA9qzX#g(8>96P-~BsqF^-WZ9f;hxev5j z;eT@XJ%|kw^p+tGd%==6HnQlkQHPF&;uIV)Je203hNFwB^7?G0R4vi(FQt^-V-#dy z7auE$1uP+BCcQl8w|T99X4jE-H_cB_L&Ao-hPXdWldl`cXS8xC#2OFF9x9!D7B(`G zq0xDkTA{-Vw${Vo9y-;=|E?r?G^sI!@xyF;l6Jn@%$P76wHRbvedNcr4VANR9rRx;sshT*W-N>wU4zK$`jIBeP zR)qT6%4mm2u2u7%8%*3Ec{<33DSu=QCg?cOjCGsP3O%n&l6HvCI#1DwGq#R1bmREa z<^IMPOxLcN*?acVdacp4q3?C+$APwatwR@TWPPgXLAkx&=Mz)kys&P>ebYw2 z@%T5Z{6_4b#4~P8PZp#fS;Wm+^I@oc^V*v%Y|?x5;VcEG{7p3Urp)=!!fr;LpF|~Z z%DvY+{buCxra3oX*P8@=Qu>=B^-cKoO`!bN18#a%w_MN-TMES0%7OhB+_36h4b-zX@=+mg|b(_+OR$Z4Q z?dX$yG;3w;L*f*MFB?MV8+(u@CP|Nf0c48YT8FuFm;`R7C zT4+U9t))MY7I%2|n__p<3C&#lF}iW?^`k)#x`uvhRIwy3KTNpPI%DXDwyi%O50wK6yQy~2ilcth z?4%8IUWXbTsLr+KzuD6-yF32ox3eIo$$cbz9i;2q$m7Kv|@skh=H6EOg{@ z7%*gFmWGx`=k^!xe#?N&Z9y_-NryR=aNhLN;8WioW-2n9Z0LX7;>>M-QYT_H{Aak0 z)q{STIfJ-e)!TS&l*etn=EHNGw4H2-f>x6b=LU^LWRU?IGSDTr=_?PVhpdfd!V&7X zWt?x(;P#WVzEuUQ+j7>o?vQU&TKeG;O3+RTTq#A>y!DK@?QVOh{m_xO6hgco5K2YJ zyrt-Wx;MXV*uRnE-Cn)if{d-JicVC|ZTI)B==hxm&|C1ab&RjidAv75+HkVgr9*!U zo}Y1qTX3>9zap?p2p{xzY_u(s5+S!l_wn6&;w# zbx6{Vkyw`vZJ%!2uFd^$mOC8~6IzhbYoiXLbmj5RM0ndeI_ZW@uImV0u+y(+=!D9w zyUFdZQ% z=|pO*wTcde>-~*)%c{d;?v8faVBZZ-Ds*No*0eyCRI~_rARcSaH|jo_YoQ7=N(oAu z&O3p$OSVga_NEW!DD9}KwHIlqGs`1ME7JKBZ4f@RAbNRH$%Bxh8xcFh_jxP&t{p3` z-A{-We1ODOu5_R+=>r#| z{SBFO;6obX7}a7MEN`B)njy^J?Tb9(~emeT$U${Oo%6KkbLau zA{E+{A*xA%AH7pu3N7K+4?m))k*tF+vI5LeAb7C_^)b1!l%gdc zoiftU#`JAq^3eB4t?q}H3^dacEwnqCxO@W4kd=?ii?iXXKB_LpehV zPLK}YtGb^iHS`@@7ORJ9LHahiRgwNMp!ZM*mrm*ZLbhMF?O$omh;F{98n8C{plfJH zJAE2+Z9*1K7HOz#ZE$a&stOIf~U}l zj-mr@y5>4P|Z*nhr?oI*-!^ky`6s-!?35nET%P zv}KIYiLO~|k&cyOV&mxh2$|fq45trXS$CJgAtFPt+{-%@4$I z^ivOcS#q_HryoSRuK zwVX7x=|i8sxmi6dD{}vbGx=t9pDN)N-frIK4$>X7ClJdR-WP3y3c%BT;8}8Sw$3ol$K@r&80}6 z`lMQSZ;^!(xjXNhsO`4YZyraM{G@q5+xq5#bvOPuRU}RHCX#wTG-TkFnB5%oe%z=+ zEHsHm40V&*e?GhX&o5i{Z=^l{=5cGQ8(1V6sG1vh#9HbTmJIC(|3ocD8|LH5mWn95VNub7Zn;sDt)=9~RZ1VY2OWE^AaqbiZd5jOV_a^~)@#Y66@AlZEm-Ld z`hreG-k5Nd4wTgm8I5-I_>C%SeJtprgEmN#c`qyU6Rs*Lu)M*n-G6`s-!((;0Pnyj zXhkd82%8%zKQ2V(4W@%mM9q!0oenx~C|ULfS@ce1(}K3!xz$3O@0yCz zgEXZNV)!$-zQOch`k_H%*49P`P6yqnArqr7^(fsp{4b>1JyCp+n!9#FXoZi`3GbpE zmQ4r7gidHIt$*9o^>^MOvH8I%gz3(Lkm)>BOz{9)sil7X~YOF3QW^IH3+?`JR-yXGf$ z?nm6#VNN^j#5zmqglDZ=hUU7;dg{JHpqd_tlZvsG$<2W@a9VcF$LWG(ivIW4Gjb zdkKX(`7OJ~Ks3nMDnlOFx9A#A`#4W652ffF%JyNWTof--cLp>AZnxz6AN$jTIEXt^ zmyMHt%dJnd+-9iW(_?i|UgTRzDuswFnctPcJE`24p_QYnZk?6nYTpX$I|Mbizf z?;ZJ<{#X64CVT&Pw(LJQ7qTH9w>pVgSMtrRibYVJa<9h9Y(8<@)O)DgHt$C;7k3;>IsDezG-pIT=dZJ5ZlkJ1S_zBVBn8odH2WrpyzY(Yy2)YEiJn}azQ57~GI|rlrVakd4UOE}$NFT^2S?@RhO{-b z>gI+{CqiW{XK3fT(F(t9T~kcuO%IzENcURB-^T^d%?z6?M9WPL9|tOZZC$isEN^P4 zwBpRH^~62Ia-i|a4`ZL(W7F#sj}xJDbHkw>8F_O<`?tOH%ijLqkQ^$q-6SRd55IeZ zNs7e4Yv_UVYgF=VrNXx=^*YsPgHF)`JxvExUOy^`4yCXedT`c;^J(VW!Km+As?mx* zK|9ogc4YOstLQ}7(1DPk8_R^G>^@X|-Qu+3q|gPgyj$DQ`y(6TP6x7m-AWd`hTNY1 z_hGqG2QqXBj@7zHXhk2P<$f9+5swviem=lD&zj-r(2XOxt_XTiX0*Q>Ed_2t2UH&$ zL~A`&w4=}CM48YDFQW@Sw{e~g(coG`lN?kQIXFg_z$Vu%k8K|sBz=%4Xh%xW0-O0| zZ_gju+q{17@RLuY6>h!(!Z_J5qXe^)1nNrZd+<6TIi!(0Ng!gRV-Bx6PB|GEUNoE_ zlE_;a!0|V}ohJe3omx;1|C>CUKd7DLERHKLTTc{jN`9qo5JK>4m-c38H z`%(UBj&jMM_RDv2%iq|y76%Li#REA_Zgq7%6LdhU=!8)P_gtkoJ;>ARvA5G+S#&{* zG>r5zg-o2x<=owy4c%YYTbs5zFkfaTxwTz-rvYh4%+fu{M4ei<4c+@UFL9td&u}o; z8IKKTEx5bbIv_`P$*1Xpbgjpn9%u}$+d8xnm@V6kG*}mH(D}??%(~L)g16HSb^VTT zgvzd!&W?G6Hyuzl+7Kn{(xwB^N+;AF7oF>h$3qKU(9V4$Rb;KdPaqUScyO&@nRFn; z*CX@qo%xq-ZGU20|9gL-w`EK#^ax$38rpD9(*tu~x7t4Wx5sEl`eTJZ40s7?m{2-l z=iU*YYbjhSh#Q@lsP%Nw4Y#Hn?c`vNlY<;y9)UeOZ4M%ZR@5{ta3dY6Mk?$RU``Mj#L)6O~2Y&)O8S9viBMZo~+^<9PHSBdo|3#PAU9ydJo-kIx{tsQ@3YwPLnS9DHnIYejar%8oZu0@A-WDf1TlQN^B^@Z&^)aIh(?Sd8eHw=Lsw!!Pm#j187C#&%vuhDS zCnjs{(eyy?b*uN)<6cW0B@LzVT8$1&^;+`r#w@Qzzr_#l-nCPtqP$*O(}8pJwIp58 ze0k(k`k)VHe%si8BR%~0lS&Vw^#;+j9%SbYqe($kkcv@K(f%ogTE4?XCy8&JPvAZ>a>>ZS!1pP>4&B2#F``Ha|` zAwq6wJ+#BsZd~1TKx=Mb?KIcrHlbbDW1rC4=t8;N*t+OO=qI?2`#T3ZaU<+}&tmw{ zhA>%I>y59A7R1pTU^lIh89GtE?3Np0Cv6D#8)L`!aEgrHKs(n>y5KwtxZOCr?ztVm zL3aGIv;RhN`0pQRos`40sDi}ffa@~nRsbdGhDy_17hjhe-6#oqpeN};UuV(r^K{@i zZwovWM{^yKv_Z_*lSMn!jov~G9kB3qS&-X>Ttv z55`_w2(37=O!=G*nRRE-4jrW(XOa%M_}b3UTd=vm(cf*Fg-l&%HQlK0b(_+I5nE5u zwe!Jr;Ed4<$z02)$2-2(dhGBwu--Lu;aVW#We=Iisx~mXu_jJ`LdkswnuXj*GxqSY z<_$zi!>GKm>Unk(REumxlZl8M==#%L@2(pM?-6UYzESc$*u6EY8%X=pVV`dD{T5Gy zwca3{R~ShL+P`OdO?ve70hi6}DmIINS0K|W^tJCA^OQ*cy*BVNpINt7N`~ZmP%XE;-o@me%=(LH-GA1Htruu2>*?i zv;|f6iKb}Bxs)Um+Wp#wZiL(GlATNH^9|={!wG+#x1BZt6ZwRMbRkk-kJ5vd_(qgF zp_;z-7}{Zy>xiTik+RNIy3wcCRkxlYf)h9HJwk6`;>N{62f}16Rdk_p)@D11bdD^2 zNQGJ~Glh0|+Pa-gWX}!k`}-88ksJHx&LN5E+RbQ#d92f$4n*|2%jiO7-t433!IZ8o z-YKM#Wu9A4c}MDYtyAel#LxjTp&R9~cKN5^^~=Wof0PdX4{q##3zDSEC$?ZgQ_#_5 zaJe3uJj{I(q8!xJjV$z`&J8TcL7%!|g;VAmQ+%T3M$e5XobZ?%PY889Dc^7JLWbN} zLMbh$a*MlL$i8s|5fwLx2uj0#;s;FJ;9-CJmU_R@18i>WP=t7W0|&=Q-LN64I{672 zsE|*%;FMY3U|}e!xuHT($K@L+?5@M8+)$yYmbrmKP-S_;1f#O_1_=5{gGyu*F@1xB z`DH8qjRfxBdqyYDN%CxKzYEEW^+u^%SADeU`fRg{Y_85T3mu+h!6}EzZANhv`u;Bi za}QHaAzF7y8BWhYJ8!cqxvBRH{x-vuR+P^AXwnYPx(&6W6RNl0=Hx9T)Is;qgNj_s z+{tD^_}kGV3}!_&T8a*c`&wM-#H8Pr;p1%}-ldy3jK8z}?r{ydFZ!t=b1|w6FCut{wr8 zzLkHcx2?QZ6?zaSZ|C5ht_#<-+R%pRzTF2hkgiJ|vQUJgSA)t~TixGZf_A^n51|dC zvGxf%fBGj`bVGzZdoPzhP_ebzV}+^S_Ds?aw^_T}?c{x8n!$;o8##}AC#Pf%T6=90 zv|=*o!bH>CE?ws-x}aHW&Bp^*yB(^y<8y-OzFjP#4HdrDDLOFPw+ANw7F_?ETRV>Y ztpU1j_#K~Hh}vg*t_vMM!*gVeeP`!L^PkZ<^32cVTyKH-J2Xe;9cuf`&2h=~J2gkC z{0z;JM}B7JI?(zvGDoI=C*~;6`VPyHc7A5%NV(%bpK-a4@1$HiI)4V`$g-a~IWpyE zOpc7B-w8R=&(DAyY3FA?j?`Np_>9PLNj$$3a^&gHfZR%d=HuEhWuMWwrF|#kmgi?M zj_mr*#Vwh{{$xCNNq^b3_P0*k%Mq0LXIzR_Xxe9-YTYuQaVy$j?VoY1^+bL~wrEFK zd`7zHfZV^nJNC6}CG{B#qZ1~5r^c3}-=Q+P5bmF$GrA%9ol9HS%x7+m9{Bx?d}iB3 z4o>@L_HA8OpV>HCV3T)rj#Qkq&t%=YtUl9sw83RQ(|NRiM>*~U->&u1&x9Wxh-04# zKsurFoe*4)_Gf^QF64K{kZf4>X9kfT$URPS=M;CXBffKt8~z;s%sJBA6}@ASq=HL# zO0w47{mbV5TTeidIPiWCm-yZk!{#0?Ascz_;gTc^nSBqJkcslo;Sw^?@;O{WI(p`w zE+Gxm@pHN)cIi~z(e5yo6Ll-Qy(>1!evoFQE?R+~XxLPyMIkC87h3 zd%T2dxV^_qbjU)g-}5D8qUCeGWO?+SFQEdedC!-S4)=S$gfe9QoG-bXwtm^P^`{<4 zb$6}N0;jgr`8mydBh1Hk=UxnE{y7m$Z>CNMq8^g11| zQ=b#=bi!QMa~>D$9zXw_l9y_@8Xb^wdf++hs@Y^d*#Ct6{_wZEF2Wd$QCVDL*-@{Y?g}Qr7Zd}QM0J@`$!W-EC#wJmu)E#|e{O^w$ z#VJ|Vb&Dc_BAGRVn9sdau{>?xTc@TGE#JGQ$kX}sMJY(*>kE0l_f0KDjeJtYCy}9K zA$%o6JeQYEpe)~8rgW(zq}{}yada3N2(hKBy-i>t^bVb)vYW+~#Rd~cK*LHhSTsil7Y<}tMM1qWl_JEfM8{Ni{9Exg`r zfqLt*H(MYtMZVbrZ)sPbYyrBE{#Uj@%Uc}=9dM0nl{Z_!-r{oIs8HHbGf%dFJ(lyy z7Vx)#{bUQoTj0i@Yyp33;BU0RTOvE%n3=bP{zMD-^=bV?3%q6cCt9H2GW`=RP}h?A zceKD;>VKmJ-d5lfE%4ann=SBm68>fj^tDLS`?`9&5uqDZ`gSY&SGd5u9Sy1Q@weeg z+F>Qv8uRZy?1#IwgDG(}A{wXj%F@bM^kvAr=hIG=GqEt}%CnvDq z;^LDNNEZ3#1TOn|=YtMvAYQ9e`W6;%PT=x(s83GdTTy&s0^fq-ub9BMr1-=Hz7>VP zF@a>luH;)zJRyPk780M3Kyr3?sTOY;VQ)y_TSojH68KgUKkbWuNoV(OtNMm{zHNbh z!am=oL1{%;ygkJ?F!b#>Ad}pd?HQXmdL-6r|UWT ziA8<;srtmGzU@|hVpVDTUI_C>ufF}-H=30Wh<%e=moq{uw2V%4l7^^Huc)-6-%e6K z!Le_j>2$&6Z9?sN`)TC2PmOOh?R$Obn_T-gx%~~cT~DtK{2%R@BeWyy?V`WYxo?kc z-)oV7m+qf*UV0GzZ?E*jcKth=zkk1{DhNr^kohLbHR&kzrALv0>Q9m! znP~T=XOe|E^(M)Yd@Nap$Gv3cSpP|q6AlUF1a{JT@hPh|YNS9u&hbYZ@U^G!GAoe119uV>%lS9A2;uM{n?r9!GNOXl6N z49)Q)@18}kJ?-7M)OCxzJC~n6A^Tn7UTx_7W_yujl6(ailUYGSv z^?FZ)eFMFgE&I;jP_J8=d^5e4s^3VjrJW`Xe&t8LfnH=B`{sF(De;Z+BFnx>Udzr~ z3V*}AI&jW6%WEnAjq+OB`6hWSJ%?0`tT7pM^I{F-avPWu-$s>+F;u)ycUt!*wtjcT&NJ`ewF?K7mR($aK=+ zwmPcRb7fjKtcy&z@YzDevM|z1u^{KRsF{d6S8(oqDH)hkq{63{cHXx($JsY?)Cn>W zGfNRA3&KNN8bXU~l=Ig;tHRu}T61X?q+o=v74#W$Ut~)SDsG>c2l>g`^+d%TlA!u- zTro&_W=%C#r*=vzD32fmAL}1j)zbvj>`UIcaw}GeXfRf0i?Q z=K3mid5)2e%zT$KBLh+XEN7Nv!kdimNobo|N)OpEv1d6mBL`~w!^ZI|3jKdKc2e#% zZUn-LG`RKh#E@}k$HNg_WTNX*)X2V<@`WKY-{l))sj^fY)}EoLvTx>EYwnO)k%4Hw zKrpAtf_X2!iyVlqPT9L|BVT%unWQ3HmwM@Y;Pye6Q_0a@dVdjZb!F#*JmX2%y)T4aVj$kD^0$zygNb;yTPGEjD<7d85wYwlGGX4& zcVftf=qxjv9LymFr}B2cS-te34DPxb(LVtf)AzgTRAPKqRYf+kNrTEZzz43q%v>rY z{Mk(;Y3Pb&jF5roy1STEvY?~O?@}d=>3;T5@xxa8Th2as!z-RDa>zl4EsMRQhumzc zO87BS?>+8Skkh1L#Fj0~xDzDR2n#Y1$sZnQBw6U}<$R_$V<7yAKDXERJn-#s1Rh2{ ztFg60CP>9pEys;~k|WxXlVrf->5jt5L_Q6RN|J@{<@fp^&%tTFK5uF2b)fk)6PBdF zjck}Ssfg{w26BQll*_WGNJp3LJW)=Nff-WZlVsfs@!(fkSM)#s9ze*s zmk+q1jZ}e0E@PTB*h9-81Nu;G$b#%^6{A89POXZtgXvhs={Ltg(K9<_;-sT>C7M*; zX3GWRO*S%fS@m4h8)M59Cl#T-^cd3lm`f8S6E-U;ecss{rnN)I<%xhu{%84gLItR*-?Vxs+w0MwXIB4$AFv ze_aW3a(UWFLxx^PkbyjLY0E;SUaI|d)o(sSKWw?bqX+u;AmZmB>oTNCg#}qSQl#UU zEJKwHE)3+CGxl^oZmnr_8Vb$!XlQq)Nv#KXQ>FI%$D9Ev8%d(ME zPygJ{WydYc2_Xd?aeHs046aXK{t1nAOx4r57nul^r?(HXaUz#$n76X3VIsNq9CM?v4KS6h80Zz@v@M(Q3$_v+Q7?-7kxkrzkKeA zGw8X~0G)7^l{`1=Ma^?m=W~s6s%|23561MV2Xe1966JI8EmlTh)*QLJyD{9mD{g^4 zelKipySv#*oGlLYrmNfnvRAUtHR3IB_|z@6p6bW$9ev_&eMBide5Db8t!`pZd1Yik zjz7g$kqP%)%1N?NGf&~B))e;w!ljtIcac>OA9h!EtipG z!OSn)$N`6z_x5*zT3@A+1{qtb#P?=+@%SDSr*OuX=1vZd+vk{S=U_&cag0=q)#U`~ z=(c5ECR%;j$H+!ylixd)O%Ce#Qwk`lNROqIk&dWbnk*9`D^H;sZoN#&JH2l?%c>z2 zI_rZYuT+dSC751yIAw^OWp$7NNxjrh+mooU9m;I7 zVcwTAMAUc<_bd=`Od&q>TT$YlBk-kj*tLyPQ6!XJ&`&-(cDx4d@ zh0r1!?w4cK+_)aiXJ)bm(z>jm^jYAfAUUL=$Cfch2K25Hh@;CylrR0W)@>Kc| zIXCfK2j+2S2*Z6@ljJi;Y6lud8suR~kPg2~`a{~tK$$Nyf-D?v)us@$?~I}L9ZE@Z z?wgrGj^Ey|rj{{HD$Xu2?Ko4ULEDx|_}v^VAZN%xnJrU|e6A(1g!tQnoQA}XWCk^N z%e8`x-_|h9mZe1sO2~%hQqe8TK0`idUTt7eWI*O}?z0KAymby=`zI7%ZgHYNm6*Sx z`}z0pAsaEij0tj{vncu;i1nbwmgPzc<~&7?E)_GeoKvLZY`N<2Nirb(#qnoV2~Ehv z4rLelT!!We9qMjetDYnWCS)P@ciVH)+Z&=n3e0?2PARqX^Oi;#OnN!*`>UL(nb$}D?=d``ZHBCN^2QF>vJ{$Z*ye#^NXJ2>(jaGerFtY(ZdoSS6=JwrND zX6YtmLOhq5R3Xb(JweX>XyuvRFu$$f(%QZY)Tzb}r6p-7yX;v8bQ8`XsZ^p|mTpcq z)N)y(s;U z;iSOZm)=DxMrYX?q~m7nL-Oq}^0OuvLWV4;U3LZ8$k-rGj^;fHbxN0coD_IpPBp2w zf>DP>kq+%#mJ9i`9~$>oga00*{@<4C5x=CvkQPine@;*a^ID1y=_n)jVVRJJ7ETtz zpMS}q8WXzAqc_6DvX^R33TpKEjO=^ko8FO-q8u5qlw2mx__GZdSqRsqx08(-SxV?V zZ|y$M))k~+WEE#+u5(sjtdxGwb+ z`7XHf+<5TcuHc6)_jhzT|5m@+0+Fj!N>L{+Yw56Kmpx>lCzl~g7JTf|mxI&!E!yWG zTbG}@dlXuqlAknm*0R*efXBm5vT%Hs9ZojVYB~DM`Kad8i3llZ(aRaqV3SLkcei^S zQ*WQ+WFk_Rew1v8uR@X(b}RP^@Fij3yPDzHFo3d4xNmNCvEADWha!bY*Q+!!wrrJ^wkl{;=Ktk}gN|V5FYf z$4NnlK9y0Dikf=rCMOLQzSLMcN}rO82;|f)$-2ozwl6J}EK+YV_ql;Yn?v)Id(@y# zJ+)xm%PY(LsT7xjDqU(fsc0&Yos^-gmZyt!jN4P92AQ|#cl(}M7Awf9r^FSqq311t zmjnBh$9%mXxY|=ZJIP0{<@xc5`#V~NfBqg)ku6Vi6r`b5pVp~K$5bpWf=r0z({>eE zuoFv7CmY`Wv}{QZvUDk4>tWG!^K~r@>aTAlXX|s^L{n zZ|>wi>e5`l`EDZl^!74)pZWZ0n)( zB=^U?P~+d~>W4NbtF=(6jb`9@H4AI66zKAkRI;9)sEM$e=4$5kfX@v1ZnANXEKQwEjJ#3y zj>+Ospna7hl%pb+noE0bJT;6Isff9Z3Q`cVQfunKH~3%Q$-RNOE~AQUXuR*N&`=K5 zUEK3Yf+Q2Cdzq4C;2dAtandkSH>YZvRJ7?b$4NnXEp3wIaIWO+-enN=PQndZltT^g zUS7|?^19eXqVX`mPblGO4-}B)AUETeM{geHsJ6?9) zaUjR9rGjpxJ~6g^`dUJ--Gwe>_4TQ`)=x@@2%RXkYxBHT|D1L&OgqN{m35se*V^mE zP!7|%k`~%vS#-cNX@wu99Ti3k-1_>s(+8gOJ<)HwOX`77(~7j98=*-H#+oif*tI&D zhzjx`yy!rTJ(n3Y=}5VhQqt}}*)+U|RHWM{dANlmf{a*+8Tnk>K8gJYszKIGSs9@k z*(=eeisDZ?JcCS<&l>tP8RRS_f^_6tyo@5w4jGa^Am~U+k?l(rCxz6FR*o|AAnUny zgFl;tOkLV>vNyP|`Tu29A+46jlgw}GZx>vhLvg2&I?*RdM|3S^C`0UT3q&my;eLbD z%1J@!rR9+4esXQtt?gDzwvr06;r^zLE25P{1=o9t%NUigo~5550~~rTiCH>KeW{_O zK}VjY5Qm$f?3jynW7W!a;?YdK={xFKTSI_l}@C{+X5J>b}Z1RXhnBjpQG!2q#e>j z3q;ojtD+Co*NS+W49g@1~FeXq3=jLWr+ zUwh8=QKN5VNDDIbIyA1e<@$us2P03_68&!x^TR&wmrQv5yL!^H$i}E&rjv#4hm~Za zJugSdK%ZHrDCwxOODob)ew8alo%}>t)!CN|vf|Q)JV>|8yN1yqMVD< z&5Crmu6$N}v|45wsqn~U+fv}wcag1ATamJvx7qA$ybOr-d7+{w6UejX4{(vfXT3ndMuvWyf`QTcXofvP9_tWPXw zmIuAR?p+Ei20glTFS0Q%%d8;_GpmujIyV#x5=|z~)Y6ZU!QuCxyO8lNv@Eu)7Sd4e z%Q=0imD}wdD^k&?mXZ(6WvSDopevScSAJ#{spP@%7yl=g?uVV!@0gYSw-qKMjWp!B z^hYOirPHJ$G?tP@3Z`Xw?BVw_pblnHEf*H~_a~@1Xo;ntWutVK5=|D;^W8qkL|kPJ z74Wj9dy)?8UaAafuru#gNh(zB@9kDoAU4ZLB@a%nEU0KqeZCZAp(mcNOpu9|T$T?R zD63^ek&cW@%O(v`{rA^ZL*X;;*O?T!zTb7~z}%LZqz^P_>C1(20Ew#DITVm=uZO8W4H)j*P6H>G`l)2tK2Cu-JW{si>^3 zFFUL1%jzxXU=adNVjDJ89Qut&@#3Fgd6pla*9rGfXE7N&05( z{PLt`F&%U2$i11Bt2RS)auK_4R>b5aW}9r(B9l*B8zXF@Q|m@~o*iA;la)&Swg{!N zT=VeBLfJQSO=L>Q#9^KWqA7KwiBBv-oy^pV$w_eA9Ojcj;My$Kx5WqwjB!&MCOd`6 zX0wTRxVg`Cm*>7?*&K|?Law=)t0ps9$K)gc_b%qB&1!i2jHZu)ieqw;wrw`lWRPwf zL9IPGNHsTm>0~Eq+sr1DjRYqYPnqZVJ&?&l-1XLh>pi#DN3+idKUnaX?e{q|wi zrZU~EEt88%WO7m_CMRXV$6MeWR2B9VzrDCHvCM%^(bON7Z zA#`nK#Aa!kJY~B-<6jY|8O3Es&1b*eZ=l=L;3e4>!cNx%dqaKEOZ zg|}Cphz%^b)7K{!sm6&BCnmY&o%x;wLg?42@f-@Ol$|@5-pK7IhMWQ?(>v{MlZ?Xq z#CSe2mZi^>{YI8PF)Sv^4zW3U67M(IfBejE{lw@Lf1@8^=Dr_c?mP)9C#lz+w%E@w z2m495pJ7heo$uJh8Ykt1vDQ0LGRY|*uSt|PD)ZN*i>n}blPbv}U+0@-KjfTpC#38oE`KJgD#%%1 z6ItbyYA1E-N$mNW0xKg+Jkw*Plw2ni$4Tt7lKXN?$ZFevO}-WHY9}G}Bu3v^yZyQt zmrXr=GmA3qneIDD>`nzvNb=T`D)t*{D)P=D`bnyJCmAQj>#@qOS;%zi>}QJdB!k@f zOPr7Nr@!VlPZGP+p0Qt`7&>)1IGZZe8YdOkNv60Hs*{*f`zPKre;dEt{dkJ9S)LqO zlR=2utR0&rd^3wq4no-E-65MLHoEIXCpb3C>oia96SusYDrK?}o*WgEbuu(rL+n@_oo6^3UK_^y?_R1X)u7s{Ev+EbZmGRTf>86*W>LJZ_w)I7uIrrhJm-Gy`$wNoe&rx^v!v(E zV;S+A5j~oV;PG3FnMA?yoBq{w`rQ1S1e>L#P+d)E^+vGYqs8R5 z7(ZF*X>UZB(Rt^S1N&XA+ewwk59DP&X;#{Xa23z>Qv^Qj~q?Dbo?>065Gcv*6(v(dLI7yFYV0vcIIH)dBE9j z+*!4G;PGyo-|@er5%an#uhV(wz?0>fe}X_)>sH*~x{MlPP4@|U^W6AzVCzfYz_&H; zv4z*CJqIh!s1Ie=xgxBpXao1t%>Oyebo30(oDr8xgLLy1WX_1CmafWDfndk;3aWWo zy4i_!%FHQ#s}(yh>(6@>+m>_$e}cgtoR96uHlDRP?aKO{?NtPxQQ2kBXCKO*d@p8~ z2|RZW9JIPO>rL1`l6R_f>n@aA?RMMaW*kWUcXWWE>aj}<&ipys_4z!rVV&}mo!@W0 zu6L;HkCwUQ+$v|TOd$2k&^fzDr^Cq+xpkZNnbGtAC?|ca?`_rsSrzG2t(_IOZ?6W2 zwb!;hR8V^mJmn$S`rGusVA^FCT6?-DM*}b;WTB9Z!+)9dzsGzT0gJD0a-{6FgkN#k zbT-}&JE8AadG;ri`Xnp9%o%Yy$A5h(Yuw?`%DA4^id)J`UyIy`{&n(%lo(x|6qG$Rf%%2XXe*6A+isT>b%fqRe&lQuhrtmt2&rt>MAf7wM)us+*35Qab>{4(Aaa zm?xOCaWD*_`~&X5N)S4Nu$4yWvFTY_8Vuy6iL5EKdqxh7l7rsu*ozZgbSSXHB0+BU zU5ef-8#X{()kn(10b+&$X4v=TA|0CiVAH4KU-nVD&ds&jY?Tj9j{K#FA64(tP2jah zpg~pH?&MAHD9SY4jTr*P&mdISnk58g^Twz zk`-M%Tn;V$7A%laeB-&-_=PNe!Swx@N6vcioARO9MxGTM$(ynEL?Mb@kMFRA_PWPF z=@~S;3@oeFT2TE?z!RYZ7)ip|8ar&L@ayX^PLw&HfE3T+; z4D@vVpq{wtjmh{{>S|V?28y%r`gM-$B3E%OOJtgt*?5?cRDQ(g;?IA zt9{6b24@_DF$mi3pf(+Ewj=uPZiqJN8?`>DnC!ovn?#LXHPT>OEd zR~h)>32bTvGt}~J)&zGq2E<8|24kq&z|Hxzd^;#F%K?-u+H-c2byX|gD8PY@MI;mc zz0w2AZj3OQ{((6>TYDZzex*Sll;_%Nw6jBrrvzvQ5nq%xj>~-=#8fq`W?Me zm>kJL8Wjq@<#t8;&|>9rJ>bvNXi3gSt8!C>ZG>;I_bsKVb`5;+v|ZP)s1(cUFnt0{ zDVmu#ZRxi5nlU0-y9%Pl%jQO(asZ}n)9*5pe{yejdXCMV217RnysGkc4Cdp5<-FW- z-Fl=aMkL-&E4vOc z{A+K^U(VS`77}oz3P61wgsXfai@vrP_GBT@!-v0k?AQ>O46uyC8{Xq{-AXCV$SPDl zy)GO7FP~_jWL@1Cd2z3p9HW-hQhKrpXE*X$mvxz`CCm(XYpdz!!Ll)ZzrY+aZ4!pg zuWQV*_Nu=XQ9RD~)|dCKaB9B1A5B;?ZbR8WQh2QVpfZ*>1M1Xgf78ewQU|`$TQ|n@ zT)?13PjIQT-x|Ri_V?YQS8fX?LGU{v2s`EnY!UB%J#GzNdj2c5OjDP36XG~x7W`op zSD8%Py($^dhS90K1lLHkZh$&@1Ba#Y5@R;sAM!U5f(-S2cqjWwB3E0v^w0=fu!Bp2 zbzNMLet)I$NUxB-FJ!BUd6*8aGLAl|l^U<`iCBJN1jW}ke^8O@BOuYz?onk6L>-#0{l}K!ZCEzHx;LQi}8_s)V!>f=q6Pf_(L`gra@6rh@^EF8VQvM&G zf0^!lXSl^)SZ4Amw*IRJtr9EF9EqHjHnp>c1PJ z`0JGi8}`3)aAL_{io_MJ650E~jdcg697+BfpBiZyMDcYX zjta-Z&`v{FOiE;pBktM@gbh|+m8awd6-S1NlQFOK`wf~n%)0D^Y@VaB2mzkvaklMW zmB%(JsNYORS1x8)9E@`0HHWdr*f%{Damyb#+@g6UT?8V^dJjx`L=wpKEyRuoPd%%Q z?FUOVh7mM2v+y^{ye$0mwfhg)Jq)|h4b{;{2a=}pa_;>+u)7GL?uEBCdjm9K)r&eT2_kT%QnI+&KnvTeNL`&e-o9(VN~9qHQLtR;E%-*dcXz>PFr zJLh2O>xpyaRnOFx5Swe#qKw(I#@U52&E~?Ue7p#=A))J$se4s5k3(Z0WqfzUTqNpv zV3@}ousz1WgK|M$<+7r;y@m+~XR)rhvIZqL!t;%?D@ZKpI=%Y}GW=?H(6dGqmH0y1 zHhDF+NXT=BAo?C`K94;Ku@g1$1yf;lgIcejvBFh|87Fs5>^7yCtY&Mvv+R53jH$`v zy1u#52w~D)up}^}uz3&JL;}y&8F<{>d172WW0TCeht;l`DhEs1h!@EzO<8K&TaNa$ z|7e=EC>pFO8$E(OTf1L-%vEPW&-^w)ZEF4-5p8N^RVs!hq64^ zfyBd3uJrm1&&yo*x~K2ZIPb&dTmrs4)&KfgUe%I*WKYL1+?<$L2ftp|7-mng2Qx}Z z4}WUZ5CiywTRLMIId-b7QrXuRCr`?5JvzfNIBwzdJ&Erh;?w&;Zs1m%XmQuM^c-e%3~ zJ2_w<{ap5#k9c!_hEY)c+U~KTeVoV71wMd*MvzAtb`h!cm2hxP%GX4Mp_n@TMzNeL zWY;jXYqxcNXAFE@l8g&$%B>6Sko@DLq`wNHx;8M)<;YG9)m`C=2*(PmG=>h*YK4XUJv+pxX|8+2bZU#q5ueaVoPzf{ zMG-d#WB3c-WZKvFtuqb9vkvoqEBdv;?mN)*@)yG|tw~}_!JSAGsU3S;twFg&K4Vrj zzSV8oZvVmaX-55E9|2%<1t;Q%UDy4NtvW|9$z#(Y{pclxQ&_f)`{&zG zo*=)GFq1b{=#)jRy5;O6o@+6PEo#r_ATuN6ancGF0W%7~jO#B9xzA8z*Qyay)cpeA ze`aq)j2@Al=th5DBchcr-k8g`T9{mWTwi8o_DKJN%Wd;XkuOcp79{GwmxilCvLv`3 zl&}g}R_sa3I;mvLL0p_!fNROAAXzSYVG@3E;XEe;0**5 zmwcv7SG+q5M3w0W&NH^EMmnz`!hPRprN|GwO5i3_s}bU}fl2 zCt6ztM&)*{9jSOa>?n)YyXKo=oNdp+uPL?W=&F9LjNIWBqgpzPP*1Jq_{Afum!+$Z z7S;`lPzZ_p+&ReW^dKVTcY4x#i_lGV56{Pt+XgdRQnhkD)Iqym5A|0uan~}6opCx( zFDs>(WK^kn!r0(^9-RS~?&>-&&;5K}kC))l11d#T*D??$fYusPrXH8W9{2G=$95714@7P7|OX3Td~!^e=}C2>b)085M;*{q-20w9=$(zw>_!Wsu0FgMy>^3*u zBwtM!^~YrEzmtJ2h6H6U{ev3!9I68L;3nE>gWmu`*gVZ-K1cgYXxs+XT#nL z#NVcX3ysAOUggHuPx4xj(=D2rp0a6qV7ejt-Fl{L;#|qsI$@yNZcn~(4@}gv@2;b~ z37oG;x^wV1!_AIj3YlIOh2NfnKUutMHXm83Zt0u()@+RSq!i_8PnX3S|bY7 zbby{b>rU(}Ecbrs17uk#hDkRoMnzt6O?$xD2L16^&2_#5d0olTL$`R*u~^}9ja357 z(|mr<><;rBG&90!-^dqHt~gpZ@|=l;fEF1BKd>h=WAPQ>q>_}lpl1cQ2~r{zkM!ei z*@bN*`9<)s60CFu7e73=k;T?j9=V1@RAM1ZIYrpIKmIW$iJ#%2n#Ot+9q9>j0B2ER z!A*msN-JDdg^@}xtS+D2k$nQ8*jFL%(y}5#`wZzgQV3bWSlz1Hcw8a}eQ=2Z=$ViY=Z~)eP!lgPGFL})v1rxxxrk?IGTEF`X=hn(m&%RwCSHrSMq-Si|8C!C<(jQ`^YuuB(5 zY*7B!9jgITLZZ6b;i#t39@E42y8#cdeD&r}OC8`EJqa^o*(B@nH7^>fG8V$S3{xS2 zIIZnicUQ{H6wrG*1GnD+vmtTf2791r3`Kp(ANzz6=o*=fSC z)De^=0vQc>Bz6U{At;_oUt;XoE8%#=Li6Bzds(<^S zQ{H|*wJq(|qezO+C~V#~Njhj9{B8a55Gvd*6?eNd60SkN30HlbxZhrlz|0VpS$&*i z?nbaq$m33s;l5?or~^z*ty^qRVY{JQFdIVkian+QxHpi~1k|`dkk}1*2DT*>1z?pK zAHn6gC{@HKA}oq2W4_OS{Oj3{r7I4xno+%}wP^o^1NsPMn8ZVM#23;&0yl1_pgaZD zq}oh>N|>hQOL%Bp9klFD!F5S+LK=IYJ%h_Zx?H{F$bh&vbJk3fPbl*B(i3Zh(RcfX zc{|L)Y3A@hqmsnpPArgV5iyXU*z@QAl4LvL6w)@`bK}V^gQKm>b+# z`?Adkd~dqkYlm!|wK-7K5;yAaJ^xjt#y%hJT;0uGPj)IYSQ#L$@33rh@NFWpr<=q$oz~*z>MgN%LLG#BE@#JCC)NU?$RXs^ok z5Z5#nBH7?5cw+y}a-=7<5!w#e(Z5&9%J|FKaD=K8^0D8fDUTlRBf=n&stY7f?7P{W zZIwK{Q}C+tMS#eORlBb{9{T>J+XId6!@IWGM}gMQ%R><#qM0DhCo?>WL3o>-d-!J5 zO>;P-%cOkmTA>^4Q8T3-`^~RNduk5ZRBzgMkV)z-{cFkpQi4YuYEh&$G`9lmoLmGj zbQrs5?4umTj;P3m3-M_?lL^v9;G{oGKCP+;Opn7qySAh*u|6Xn=LibpoL)t>t`#>I zkn|-8b6=|c)E|`%x~%?EA>8Wm8|Jl@J4Yn6>UtYgl zq1tsAXRP6qQU-u$0KChZn*)tl8M&oEzfeq8F#bI4j5x{N8$WMrYEf6-d0D|se_>GN% zk_8kClcAecAR>pii^%u@|qfKvAdv05}3ni~pO3 zBCKg*#ccpN#%_8ZXF|>nO*3dhexnmmxPzpxP=7u2-3T;3Ft9kY$mfJyW;scd+=Fb; zBgqw3lIXIk4dj~-vXF|)=?Pqj&$7vj|ody4Q= zT+{I^u7G-9Bjj}!QkAXukv9uw>b&k`T03jVeigwgWc-~j<+*uSO3YtD9o{J}UXp~O z-TJ4{WI5fvaox*=Irpb^4oNY3|#{Zs2@w0=KwQI zMG$Ss@uoA&EW>LUh|NzBO_dpY#i-q6^s zf40TLK9!Iw8Bsnh7E%PgnA?Mo0Do)1+l*aPUR))V48Vdp-3Rg}5L_>+?IahSs`!c~ zSnsIGINuCkde2fAOqf(KgsmhX+58LIWIY0N82ZgE{{aUJ$Ve{Cj_I%JoDCsDSt0#OstR6dlf zqMBOO8r`=uFSC z@v6cBI7;^nvg@cbdsV|do6@nxWooxHBFc57*aT(Ea9l zrb$k5JHEGv^*n`|5_{=|6t~FAA62UMWf@W3(`v(PjVMb=nb_2~T{%h7>9x7{oXd?= zEPz9g5w)?rz9OBaTH_cCCE<`kThf;@t@^rVN5s8|etcL~bn+y?vvjw@Y;xK`mM0zC zw~Jz74A~{sPWXwD07M+>H!{a_cr){j5MsnN`imhhUcWq})+9;D31II!f=te=OPa=F zE)}yrUUh9S7$}$v-ydqNLFd9Y`YG4-`c`lPZ+0s@odZi&ZgqLwJSf;L^FULwyeQ%n zH+8yD5UU|~HqMqU$n zo*mf=2IVpYS~FS&I)|cODmoX}Dtte4xGBn zfy|=7C~obx|2R!r=^t_e>RGFnzCFu%v)-m~dvN~21w$}e3iFhxH|Y7rwUqU@Vd~r1 zMIFRp+NraLW{A1cu%y|0zsaT*e1}3P;+Ho2ovKRXT&6ik{M_`TKkLdI|NOgW24equ zDzOzRI!O|7vL<3YrfB`>$&cUof+*qCiH$T zF6VAp>Zm|^ScnWyHY^FOgUBc|jJo$YYQON|dJ+7ZF2VM48yD~~9BAd=_j7ZSh4C0T zo;xX>)g`;`KM7|ySbC*B))zob-ld*YDVHl$`U(`Rz+bsBcLM<4BLR2ouOMrqTw-+U zZV2hfSJmXQx>4D34QfE}P&~83RVXFgO?nx+(*ta1;c>D8AzFO73eE2X8ga) z46&}qAvZ8h_WJ72m%rJ`rI$gxOx)J=y7sb7v5D~-Uhm@(bar?N_GH9bzC5%@>mw{% z`lhOAnn{ru3z163c9N%7b023nVD(AmlnWjT_&Gh{PYeiQZQH(nxn!aaYVza3{ z<6-x|bRU-y5<2NIO;z=fYq;A0xQyVL=lyd`z1(}Et-4eMJB)5vx@?5W^7;RMFS^97 zO0pt925iMT9Rg^sL?eo38~J}w*2u$^ci>O${?I51=N2i|Nm+FGrU5}3Gx_}APpy;6 z3n)x4+2E*q4H#mPVuwxG1f8zfl#=9krEJIoENnkMef2^=n?!I`YfM^Lf%_^6u9-BF02m0R{+Bt{{p)+nX^;1Um~p{aXsz?W=_jN#Q})73H{LGPj6E#D zq(IMAJs(r<@%;q$k>bu;?`{Ga7m0iQRT!?hzJVBlv43wnda{(*xFOJdMZ0{6(;-?g z0x>dyNUhB*FPQbGLz$?Cz3Z>_EsMJrv>TK7tHlV~1j~&}X6rPG4R>&bGlJ`It!!I?)BQ|rG3%GaE za1_iwNdh%IIM5$*ylMPNEGFqbMT53ycq{2v4aT(_NRZlY;E(Q9b=2+QRcp4(=-jH`kW{wp6&LOAAvSRB5=j%}!3c zz)X7VgETDmsuoqfVN&36<~5 zB3b#r!3X5jbGnu7V;cB2CLPcd@AN~Y2IZ4G0nHn*w|hv&&sz+Nf(QPp5bicvIdH7C zoW(~s z0d!;QzLQwRQ2!^_G8hM8qUjvR+3)E~Ht$P$ZQJiq&dFj@V>yA?b@k=e*tNzxTqMgj z7U+7fWmVPc9g)-P;FQCcpGZklgBMJ7u1xr>bzN9pGR{c_Yxi~^)qt;{IC?~6xl@^x zD~#++HWt&u13Dk#hCB1GHu^#AmkV>K-EA zz@o_2KM-LWhc1TQ0jAeVXNW8YVk`<_5@g$FtZol0K+?~cd^>JN+@cL5orY*rFO$^) z$9_k!83q8lA&Kr&gB|6KutPFr(Ije2gjPGbV#rlMIC#eWW1=LBDET+i@KN`(w=r6W z+K&ANhBKPM?d9mB)b9+uVK@{4EZoOBDS3{X*S~HlGO0wkWX?B`s%AM{H~aIvt+AA} zq4W0Y7Epz$Vf8^0V%EA<&rM#l%OD#n3RAs7u4M$J@=pBQ^{8p;Z#u~e^%&i3Y;$LM zu)lGuV1zt%jT~fF2c7$@p|vP)iR;lX7q4~c3t!aWr$Kxg6Dzah!l;Wa55jaW{AZ+p z{Da>EskB2HQdY$i`CTY9}vId*EE`-*`0=w z`A+GCT0TV%wG_mYMEF}e@?AS==)EXsm_VNKVSa+_+Lw5qGaag<2FZn5ck&-+EB`}` zBA@5$UoU12d8VZZ(!7Kp?rL;dD9mNq9LFWE{%4ZE`fTbM&@tiyC9$Y&uOq$}Y z>ef^<_<9j2Irk)s{kvVYf0}DwQXHoM!Ni??7_*q~rE0R&y!b_f#VZEM993W&IC&0j z;rye^83&`+)4a(e>wwJ5NDAPM6Z(qJ(#3=C2+!wITTgWe{Aj#F4(PBOjk-LNFE_(P zZM2v`jDHlcCA~bE;WOMnQPzuK)wA1TM-^|#6w89A97d!l)*vpn&xi=s4LM+5l3B`T zxlY+$@l02XqfPJ{-=Dp3KKmtXHjSo_$kq0Y8v5(YNY9YiTXNQS30^Ie)K}xKBc6N$ zH#oB;W0RoscITb`tp?yq06MRaN|I+$Mb?hQFiG-Cx%HE3)-<9ro zB`gJ8YB;+wKFbF8MzhE#*5+*IjbqPXT0>pp@k(7kKyRO1H1@pQ4)m?F3JgY^LZJxK z^+_Lj=bnK4A2?0A20PYTtJ52j5L5>OFuHmxLb++FC}o3Y~pha{M@O#2$382dD zt?_y{O=IHTq(UHT!xjTZ93En8rni=!%g1o$woXbKV#pqh5P^)x$=y#h-HO0iN#2$^pUyr z`Z+93Ls?R*cTRYo9syx}FeT}Qi&EY`*HGB4i3yep?~w}}iT?LHcK^xC_IGfVZBX$6 zC#lUR{7w~YrBRZ#X!~f*L1ZpbE$`r@&3&r|Qp9=SCbW~f!5v!EM2A{)rE#pglI!!9 zB63fjLGpvp>Y%3m+&H($M>ApGjZ2wqky~6&s(UC|Qaj>4`(?;RnCb53j5p2$9+Y@&fC~RT_SH@a+$1HlmVYM+|b^Qh6J|beTIr-qKJw_N8+i_Oga8I7s zF@NA4Q&p6vJ>$B4zuf8YxPummzGo+Sr4tNQ8@QrtAMe~`s_Lk(&#)C(a9wJ^^1O0N z^~3PXvI4Uy*j(~k+D8T6J;Xhzl7EvQB)?wE;r)ze%@xd#Os*URGoz^frJTwvCIctT z_v+jefp!2wdk0zQp_N~I0pm1TwdxjRet{TUI@@7eJ9 zP3|&+s`X1I)=Hnk?= zq9$9bIHCqe&GYGYUqBp}C$Y4|ebfw}9SJ|4>c;4>9I%s2Ibm2nCy*!`hOW;IE>92Y z-Oq}tWoDN=aO+nu$e}vy(T9u%-K;detnlBkRLgMxJeLy_{gl4{9>dwgM~sJI#o;^J zkbgvKRugZ`i28n0UKDf=ZihtpzisyDK-Fe#^ZeQ#y7Ws+W$;&3;fY)4jxr0zUY&?> zZ;E+fcr{cUX{3fjE{(v$H=P`8B0qY^Q#ryySdO}UQ3f( z&R9;{<}mGuD%+~T<(;5Uv1remWc{kGGRf>8^>zjWW;M6HapQ?`a!;ET(Xo)sZet4k zv1abe`X(*N0Gq6(Y)!P2kuVKA=hzWirJzX>2uVbknaQBg2hoNM5+SYp!w-q829MOs zzcx9l50{ejm$2dNaW-wkMP^_=J4Y?<$>MoWWvv$I(@i*?WwV{E9E1h3oDG#s8KXvP z{a3VgC+V~4=9HJ~)%<<`}7)zHZUOirDqN=v1P&aA6stdSLn)%xlKV$AYe0j0fO#FQ^{U~GRy*0( zp_?G@k@4c=O(qw>ILJ=i<$ZLqWM0zaf$F*;9wjXm$;zx6Ax*?n!rZT`%y(?0J$6H2 zD<#I()2WhUq9}St@|SJar2B&gZ7k`Gf&rb+ICBiLhxt~g)mp^%oevT8=q|ay3%=s|w>zEW_!>WR&(x68i`?cPG z37oJ};xC&EPaMA=t6)EOgWs8|A=~Jo^hlhOn*5)gctPbw3!~BSTgJNaaU4^Dm_ZW~@YIC>P=)9Xi z3<6?V^&Z=J2v&MEvw@-+75gU?W53w#TdL7 zy`LX^aWkrZaa*I)YRBpJff*tzLT8kQ3uaGHwU71-DIzT?gFRn=;ywtG?>{{&q_7zh zl>r11ZT|uF?3sg;eH*Pmd#1k%b7vYTWzm#FbItYV!Y~UmGzEmpxie8j1wdQ0c$&#mR!R-zEKy#YpOMua4up(O8uOiq8|F zLK%lf1OOrE;oplRpj?8AT;odDIzSlpad&P`GOT&x;?tnFQnuB{M6dtgEI+btO-a|g z53SUC@EiK4(>^hWm7~8N{KgnQpn47Qg7dg=h__BP={gz9S*N57G8z#yDTv=Mj48#n zJP>jivfe-1bF%mQL zbMvQ9_Ui+-0-?rcXtx@^Oz~q&>KCOYWT|h3QP_T_22yEKeg=1VCh4nDJ>0M;CE@uq zh;F~I>~6KalX@(pF=F7qP7)gwg;<^b2-6Ith}tnEZ3grGg%v5e^g@PCGfXeUIZ;&u zaFLS=-!;VRy4^{g$)Wz>p!S3A#NV~67@=pua9w2M^@{rWA$`WCnQ-49!jBh~v0;$} zVCh67s`kDUu$*_R&Vz-x8=g3eZdr2NV;9IZSMn#z(ED{uL%x&y?Z^|j`lXfTRS8=+ zV;YcNxt$c}2)2H;5OBC`BG8fB2!7hS<}~5Q(gTv`Y-8Jf|6?CsSn& zRxU|h9FjjKUo(!RMpTlT#Z>MenvC4r2Xp@-o8XlSOo0uz!{(1e8wZ*^=Cw|H>IlZWwDfah>E06)}X&^~{W>=`AA-P;pOreSkz;_^{oPs#noEy4D$>WLR%;*gPvL9~-K>!D!R&C7?Fs7l2po z>%N=P;XK*0+h=FBXd3rsrVD~8U4gvpTQYnbRz7f*2oTOjkm%0}YeuR7@%LjH-};w5 zxR{k6a#1Q6-B(dkTSFHax@ug;p%xkc)h!MVJ~0hhPy9)Sa27OeF%)m_ow&sdnz_}M zXMEj9W%E_=OEvm1hRh^dI8~v3E&<*xl2?w6@ENoAo;L_`*T}y#m&JgAlFNMbBbxrH zk>$t>#%@jz3;9DGG*eR6LRE(2YZ%CJ zpq1$wRjdhqUAc7?SrQDFL+(zFPuE=XkoYuKWrg#9(UCT_`n2S?u1NKgxP>QHdN6I8 zYSf#Pa)#1owM+d;SCh$_q&M6G;V*N)xPC(4Kz&?k$Mi9{AGQILn`9#mx@>~a^{?CB zPVpzh@48k?M&HK+wn5fCfY`Uu`nocMcSHI)6r|fn=a4rj_*9$jfz;rWSGu3J$qPuM zPTRaylg#c9{qijTP~(%Ckx83PrXpU{)2!8kufFGzuO5$44ZZIV!2@o0;tm2bt~!p0 z+a(Y;R{t{B9+R%n9-vzKtp+CiBt#;A{ck>L_E@YNi)wp zW=DQ*LaK$iYVdCeYglg-#4{yjlJZ;z&>W=awdL&;%MnlJ{KL;Q1;kfSz6B)yfs8;b zDncgiZq&%ukft@(_00X`$In(0(j+N9S~@>(Aw|;wL(H#r=(!1X;&0i>xN!?zuHfDy z4A<8(DpFW9ZKht{yxD*mN6!CrVCC^YmgeI%#R$~7#xf^7C25k4NjVgB zE7~UAHCmMf=&U8H%I~{%`;G*>1vbDEQds5baHNT^<^uO}OdDI#pi&}>@%MxU(OxQm*7s+Cz-^D&99y?*zv%+R= z-vj?u+j9JbBiDWDz_zxFXn^bao!*pIX4Kex!3PV@-I7UmY5wN3b z{A`shd%hoc|L@FM?or$M#R4@g@ZWyi`LU$K6&XP7*5gaULIn79dBi}_AWv7d1rW8DsrUxgx ztFhd%hL&G@Tyt>0WwsVts-vMo1h0m~{C8~^1WjH{e_JnON3St0MzVhQyKtW< zk<$*f{cxYcTh&R46kHc1eg@U!E!C&^y}t+bFSV9{W+!JXSrkVL1-$SxZ;NJ5^O}0M z%-ZQ5N7ZMNe6n{6*IKLP{yR3P@l;Q1L)Ss>g5C)w zC8t=2OWIdltS_HHPg7N8dyOkgiQmLh%v6(lToP8C82ebB!;j~+^1w6&!qXk%EpQEo zd=tkkA5K7~Wx8q9#KVyh0mGNu^?}82mJ-+gwZKe#zj3aB6&ov^s|DC3)`r7O)+z<+&D^Uy@esIM3SZj$1+ogSu!DS1^wm#}f!GsJT`c~G zJ^Xeu&_^;%6hU<#2u^XfNwM8cXt?zR6!y&ztB-Stj^8Yv-3Wq@LiqhPX2Bs$wJ)v# zn3L)b0y-1|>st9=##H?Xvc#G^LMW1%Re${!6R?0W)93+6odx^Ibm~SLi$hFImS-I5 zMzvCnL<`~O1vs(><0x=B=fEcO!2WaFs$rx-w1>U26CmH;H@ht0`x`-5{dtH-y46v- z|GU5GQQ$u|nWDFL!V}S7E#PZ&kIiYA%A{Al7=sJCe^oY8H=E}u%~WV!jzHlv*A&12 zh>l@CY*I(UctNaV_Yup26`bs(LW7c&A{g3V07oByn97E?>g>q6%bA-Df@L7dT5UWc zkJfv_O!M(*B!qJezO-OdmdGd%%O7&Cvj|Ngk`q@Fjn(o-!$z+(&I;=Eu!w@LD3$U~ z!+Y&<2tb3KA@toyo)4v#dDv}KBc9z(lA5yuf^2;uOu^N8cry3+><@vMd*LzGQSakI zJ@W}XFtxQuVJ!cn=)511~U)C?)97R%?%lJ>J$TF=`|v_DXQk+MBmE+L~1a zv1*SbR>U5qwIymKDK$#%tyWQdegA{}aP!>fKIfdz@zHrbq500HQ{qvscZ@*cVd^MS zYAk37$J%nO(e+ZEcTY2~9RH+9eU0?Ah4<*^w(O3&1C?|0%aKU`bI-|irx#MsFFywzL=I@L3X5El{ zIj{!rwT(rV$kK(03pzX!h*KZ}bHcKuNzoWx5(Px6fdpEG57e{pG5aEiE)19h;F zh`h-hC&%vF^S*89FjA!9c)h4>4sNHdUKVj>8yhIX#XQT5L)FEVGF-HK9=@9@TYxdf zZPGc?TS(hUKO@X1ImB-y**<^Gw<=le#$Mj30|S3k&om zipEEQAn!f^xgj#g_lKyRRm1;D7*rIF;!0L22Cr8-(fS0i!j>mb$Ctvd#cmNFuI4t2QF@Tly5{kO<2({E?>-siLI zupcw^$8bKVM&a*JTLGRt6aIz&I$m%af*vb8XC1h%e{-;fV*(JS_H%0(cJV>BN=Jjb z6HzrcMHeOVYUw`tkHixVpJFEiehe#WT>NhViBjKi8Zwx@^WhqkoYc($MFm}-Uk{=e zcOZ;6CLEqcedC5dx9tawZW+h+Rv6qGRfaQ{SZs{0RgK)gU->XMg055F5YN$0qy=fm zTqdaAT`89EOx@CYD+Pb8y~Al8V8REBJO{we=zJ4ejZOs)hou4MfR1nX4S%@yAWhHy z5K>qEzAzN}(Zx4z(WTQilPaX0UHv6Ysgx?%qJgw!dLCKE1r5+K&-EMDkZBgGzE~oU zv9R$?gTC3Hx$&=Ux1*?>O%_M-;l$l@*V03w+Kdyvgve^TX(Rk7mGM~5hxheAw$xz= zO}MxPwKkYuMrqQ-k8y9nSkQ6K(fRsg<2mEW%ugcX{JDlFT~<&P;3+Q=`A9>-W;fG8 zWw_{v$1WgO&b22nyz5K4sSYorrX1~G^tjg-@VW$S`1e_j!3lg9H>IUkI;41YvmHaLpF;MyM>$h%j{ z48C#p#YN?O-8oHk^SNl%2eWrzs)R)Y7OXss8Z)RL+6iH9&3F0c&cP@W`N`HgjT2s3 zg+7Mb*@303gPfV`>hT6QbL|pDNlVsdqgcUPGQ1e9+_JzudOO5UH*wl7>C!e?S~qGF zXk(RN5-4C%jV;}jcpv_%O4inieL(aozchp7Sl9ECb&2Ff8f0y=!D+YRtD{$rcmJ50 zUB7miGRM53>Xph>Zi@CedesTe8((j&i!+wt8O#KnXmw7}A65t7XfI)t8{-@#xb4>Y z#_R@N=5^nOGiWNQ2E02biiq4oMyj`tMh?rrli|%X6)9n?&;R71bM+Uws``7_V(t|F zN-~gzqF?C=BI>5_?dZ}kxU9GEW>uIDzp)WkpV%oR4ihyAJTj|<1l8x)mspm{CdBgD z*i-R$F7}FpVBbs=BRf)IxE0+aBjJ(Wtf=8k9*4+XFz#6!mhYk!c_LML$#+7-r2!`u ztaD!1g*qm{116^SotCB~m%$$aIvp@q^3(cEtzKOmd{6&Wyj#%o?kxzvrn~&;YpR!x z3K?(R@#GRhsc=5Q97>qs#IAyjjBF;QjCCZOUuR}4X%y_I%JT*5n2P$WNNoP2af- ziS6uRK%nI4Q|SKh5*;Sa6xWy#5!WkIhnj(WD>0Bn;4%oiaZUbzcak<246UjtP4Xv5l#0*B9%!YuM> z_N_E{YfhI!5q$()JP@4}2R4>ncb(mA^^Z3jvAx&ptr1Oum9BU5CacEmfzLmk!S+&=Z`Q-uhpWKJCByh4`#2mJrB}I(WKpsh%0U z1uG4K=D$~*`5v9=)scpSiN#&`UNoqeN%CL9TV&uM0(kaoTO|?#`_!CkqTDv7&dV~fp!GS0_bExdmvgk?->5W9$r0>hm z{zqyE*Ii*=t0OHFHiM}>8d7Mh(K<33ngtme@;h@d*9`uR)dB8cRAHJ9PUtmSB3i9- z3X&^5(;;K|wX{*#mfYNagg0-JkCd{QV96?neZhYA14SRs5jWw|XoS$FbvaY)u zj1%Up*7o+oci8vP`I?DzDGZt9b#H+>u@p?gtJW{Hy56)zvrRzXU3V&3{BHLdR~k;hBa+@wMgsVBBVHewxLx&F)*@d1} z&-9gvtk@ijjw+Rc4~!?+?=d_I6lr^1)9J=uh@wvX647e^6>}#N8D^%5Ps?qza=lX; zsN^%WT-R%CJp3(w5ZR~Kfe9K*%tw>_!09TJ)6$n(I)%GFUl#!)iOO_zf%XV zZV&V(&ms35=k4VDNSuqI)s^tg5*%RfM^l~WBJ7j|8itT=-l>#MI+MrQwgFhhPukSj zoTs}<(*Dqn*DTkcgBw%ds{$&kdaJJ;8fR-+oOt_L1Cb>ytIKI0I#T#gMc5}@hS*w- z;lJwFZfitTrtk~Y#b_=0EkM+!ZmSk9`Hoa|-I~x<(p))M zSg7+)Y} zOAeC#u@)qbi?aw^Kt-fBx~cr`GrUQ9A2ZGgG(fEASB#eMh^3JqMdWRFtUsVSKeh3M zTTB9Lg@b8kF*Fpzrpbc~8{N&exBh?+z>nsslX)azv7gkdxaksyZTQWAhm$gTqfh^W zG7ai?Ya<82w6SN>z^|`UdMPp+eFgtM&o+@`*sjjN60tj`s{BNXWDn9)YgvDd8iFm} zGcohYg8YaDR{vY42xEFFCPqx|$-EYdL@f%kikNcmk$&O*7LH4k9)edim!Al;t0n{Z ztvInp*?&iZmXK9S0}Od+Yj-C;rA=G?n7QDz7Uxfp*hd5Hg)Bfp>L#RGa-K?!2#9j( zKo(uDME!b);O<)lSG)+=?UwWs{5CKBhn8gqrkJEpA$bvo#jIfRpFy^Y*}4U2U7 zoq=^zZ{y89ffp<-;S3O{@xW|a&I9G!dS)bg^9`nI+300oJz-XJcYZ%(NsJ}H>`;c1 zo(9e}S8y!Zn7qAFH+wmoUV1a@?W+}YA%-e?ciP{|!~L6$Eg_XRHZKS~+nrH+dpx^y24Jo5tAkA|+NfYdGkAPyq8P$q9*lWy@^*qmc&jRArGpWsDUR8e z;Egq%P&uR;+udlh&N}o|`=~p7auX8Ib4X2qkPi+8R9iZWuu;&mdOEp zmgbXlB#_0j`N|9D68rw>M-0b(qLj2}!AnT5&-5|ls8fl0m$xT_IqH9~tex_V7cr%` z+->JR16{+#RQM$7w9NC}@7jEszdzQIV&JJmv+4EV2m0Y3fbb0Sm1p)rqh=1n87D&! z30bs_o2PW~=QNEB6B!$>qG7g|^RG+f+3cKSb?2yym|tXbN3Swukt9tJ&9GnRz-hH2 zkwZ^{r=2R@NNW7$05*5HHGf||$5iLBFBYd0FAp1d)8}zrUao}niOpNO_e@P0&7oe+ zJ4;1fA8)cLE$DXY%k#RWBGEg*4+{}^V+pujU%(1pB|N&2Sbv9x<*QJ&>;58nK(Cv?vBRa7&FgOjOh9Xe<< zRIjUWtMTDugn6H~Dc=9lD{rpH3)BROCEOaVlayp$FI_l|e{Y=a&9Z2M>%3VmT5E~J zU(dSg>M2b{0ICJAcBLo$P5Wsf zw|~2t@z~k;LG?Aehm`i7yqHR)3e?U`k64s&Ef*i>=T%T%DvgH9OxxEL+6DTJb9JLGn6R zeQJ*J8A;_HZH%XrNGon#Ju7UWY7%?!A*$(4mrDcI&&h(XH_coUm$)Db(Aq z3|5^_%Hmlfth;TL_>yc#{!l^OW&Zt+G)_nq1jDr)tHeI%>6}Ud58gumBvqhBqT=OL z9@yp2JL~kI$#VHL!=T~ocHHrbpXnNX%{O8T&7mgzbbAO}EA2z;-oS6m_i}*UgWdt@HpW5{9n4+Q?m%s{Isq2SytA`M9g3H|lAkzqqI6(7!a@x_{f|3Ry2ztx zTdc=$7ioe}{`mFiyxHe|8RdcsZAK3IGmU-7T`qtOM4m;3?pcy=PJV>tletX$(MG?0 zuT01`^TU%)D4!)*aV%W3NW(apIPa@~O;tb1Cwtu_FjBW%PhF?n;0|>IxXphEQjGTN zds^uG(EZ^`QDL80q?Xr@cz5>FxT0hKRN)LP z4kDnD%h8#asi&q>#NFtGemQ63BSY+d5JMJI$Y_Vv;lPEi(`Ea& z*&JOJollUNvFWzVY0onqU`^lKzsiNv6+Wq0z~qPhcFReD9oLWZ@?4gf2=#&Akq1fT zq<;#;8j+&Vkfjz$Zn()&zB_GJSXe8gKX#)k@Hq|!p#s7M&V* z;`rf2w;$6qVCa2-ElN_@ny4Q9P>EbmfQl{;zAD7DcV_wu&4_cTxPG&PKRBD=tV(G> zA{(BFZOR_sk-92I8~HRjV60KydfBd%j$9Y>(bP~>ePk8e+o?&ouyHi^tA5r%;zX4r zbJg{9b4^S(s3g(P$R*r`R@#Fn35It>_ZdzWG4+`nJ;Kp)l^Rs~NC%)KhvSmI*L9G> z`~xINU}Bk)PshFwJFN1(YQ^<=co-sJkLJxY*A`@imJSBlZoJE)RzDelRLfx6FUW0x zRAB=t$7s^`iQ~rnBwmw7ml)tO9jBGDZN$iDLbx1Fe5J1-}V04?2vp~Yd32o& zeiA)jH~h4sBVJRktAG`A^PW3uM%z!*uIH{DV_LR6+nPo> zj}kPtvxH@K{|wh;lyAu5NfKMY6+Iel3cK21!ZkE1R@g`4V2JUre9Uax5*qe2D>-c!G(K1^68+(NW7jdBzyMS`;z)Z5 zWY9>$R*fBpMxd&u+8p9`59xzcQ^40a2uxs?EAZZkbF4yTp}p+XvId&q0PxdzCb-Ju zmOxg?wZER++ciWy@E|{uqE$6Q#e6t#6`_=&Nr)rE{FRRW#dV?{(NDsz*OICJZ;WuL zD%&XX%hx0tzxA?-s`{kN?)^UVs(aB;gY6o(FpuGnwz(@r-i(hZ?_BVa-K&iEat&kF zMuA=Dg^Yj4x;&9JnVuX4rX{2SuO)=1ZS_1#s%Zhsw4&;-F2XLM2t~`#JBi0Sj~GLg zizD_-dCzwcjm-$Vmr?;tMrmVgcs!yx$4v5TBRIliQns+i(vzvV)<-rK;8iY_@o@}T zFTBg4J&etGQEt$_iEmP*kH60_G4VgbcYeOMk)?5^kK_d(7^~n3_C5p7ej8#17NdTC z1fd!@4(hLuO1?AYCP`a5unljZgnMEjckMf|s_zaVUI&yT6GB=EDJG_uyQ>OE@3r6n zAO;5ISyJ0ZTF3P@#fF1`#PBY39sIR(%GuSmg+sWnzNk=R6{3D?8KZjdH`M_`9`@pI z5gkHqFQ4#FvGEKOx8gsZqoXvoBd>eSABCn2yQAuuhn#CnqaUd`JKZ!a7mzP$42cuA z$Z&FKkxuGoLmEES$3v zK{E}QGvi4zKL+G;|I+xl+f2gZx?xg12%>~7ttnuYDxH8f$R$thMy(s*-uxTNA!#MF zJ!l@A@t)+4qoL=AlaDR)xr!m}*GwOXyXQOpQrQ{1^5w5EW}YVVg=5KdTkwWeAIHVB z;D~owRl7KF=OHPw_=?p^{&asTuu2HbbRavYD+&kM~e* zSnKBp$*l^n?_-9#!l2J%EzQ{2n!BxQXbqWo$c^+(BI_7^Nb5J1pk;1hO&+>w6JOf+ zn*!Gu{4GqDw)Ezchf^=7E2yJiwH}q6&-j_9=1ji)=T2BE_TTmU;`#Ct+J3bWIAN}HqRg(F505ZWhW-ow-UvP? zlWz2O-m3i$RBN7<(Y}nD+C-}=bdl~kn3V~hzMkmkUFQ@iLCp;wx7)=9r&d;>ozHofk%-3s9JWlAmRPyZ$FV2@bx$%~8MMX+6E*Yz*y|vR`u9QfFb{+qE2X zX3$R{AtIG1JvM6mz%JG((PW$V(S_St&lxbrJ(;m1?R%?Kk?+H0U|e&qSRq97HoJWY zJnMIDjzBfs!PPwaYrZ9Na7?V4GD4JMt4cGWlCq zYRVlH%;iw3M&Ko@&hk*@JdlR+eEZc}Fa`9wNxT-3TS@%QkiZ3;lYp|9Uo1-xuL+Jt zNt32Ci;QB-*?HG|;pugmgOkE<4C%3O8B8QBN8fklPrsN;LUJWddyxtLQ!)A#UVYy0 zWdQr9^7$)akVnlaPxL%XN|{N8T2fZB4cbUvPbZoS>tg1G(XinyQ9F1Z>DBsM%UiM$ z9XxRZ$V0aV{%q^A^TK^3pSKlDbSlAi15vPmj0__&^narLWST=FRZoT@DWz1~3o`d! z6b^8`E-Y7<(oV^0O6V_@>6Rxy3?PH-{56F3t#Hpjrk}vE!?t8%VhEgx2Kq+bbx64m zjQ({?gYe@^1bG4VD`*k^ts!k#`?IH|%^KZiaXUU5SHt{QwWRn0ubp1q411t0uzN zhL=wf>c~g>gz0Umn5?mSveLV?OL_vooC>{15GX~y1*I&-35cl>k^3SZ;~S8P(%nVI zIa882)1f9h!jOgqBS~$gx4H{jULT=$kzX~Ev8iBhot8Jm{#k|n#+(L>HPg<=^t8FI zZ{C>cFU;BUx9_j)*M+rPXtEEV0zL<3cRmHh%U+zysvMi#9J%UDGKy7>Uf+T+GeTY2 zJ42VML{fGKd@dqs8?AwRMw{h=AG}csbxPLVr9}Aq1BdcO7vE#IlB-&+rv|Ne4-Fig)yk(E)#~wv#Vt*s9-8CT~NL`IY;9`jO^}I~lC#3;%0s?xin21^~ zEWZ3cH6^RcUuIb8YX-5n?f9I;=t!Eq5p=C=`F2^Hq~Qcw2)F4%kTLd^x~l=s%_IhmC{va@9jy>{BE^xE_Z;E z5=?O}FY*45qtM{Z04LyfWiRWPFNw2exEju`g|AN`7hIe?K%_nTZjmBg^ahwr>?b%( z6^D}f2TdOcKd>BY&qtCS^L-|A;9wNxG6*}Vks%&5~%Ub2~@E4>|9y_HU+l+Y@ zUY=$rZz-SWVxZpU6+z#vy@c@y8J4Dof{#ndNu*Utz zsXAqnecFJWQOJ^D;g-&ClZ_{+rdJ;3rfNlkl-^tvS@(ICdc=H)ZC?DQ)|Xt;m>vbM zPCWcD!6a6~EnvGthQ@Xf*k`BAV|R+H08sG|57N+)Xdh?Mhcr#c@(ckQ^374onoWH(!+b@$ZPGCp)=l z&3&w+gembUn1(#R)Hivqq>5_Qfcf`mZLM41yFnNRSc}ga`P@<#n~P6t)pq;apVSQ= z{Y;uq>4_GhzvmHu`H>sy>645dNYE!vU#KGPA1&Y<#Q zuugEHH5F;R`!((I7rG>~oS4Pa8aSggWYjLW++K1oi{IIUj99J@HGMKJSN!BFt63i7QLsKx7Re0*5;kiK-g51_j{jQI z@NVsmnr7_;327~SWf?eo&$BiF8s znzznf6i(i~rEGCuH4!&yvyPrqBOnq?mqGh7L__pwWO>rMY=P=e*Ch!AfI$Rv2=YxD zdtKU%+IzKO$sF*2-mmZv`dxS9pLhRujaD?P*lkL0Nfp?Zb;NXmy`F4TJtn4a`3DXC zDNB<)L*4356EVNq5y+JN&@?rdL6y?qt08uq@Ss9(;+a=sF=+PJ_OeBmy%KYBxxpbi zbL&F~@_avXn^RjX(w^;lL{I8}>XJ=!0psFvQ6MqW- zg!4&(uZOk$!41*-c`Dn^;*Mv7M6K-pk5i?VzBQ?Dor>E`tzJ=|Sq^eQ+Znfgia@wA zB%RA*l|+f#M8_m=Y3x15!G8`na@Fyhe4?cmT7v#rHMsnheialsSX5mj7cHs`{9EUR z9j9M1Sz@11^UH>-0lLGJ=uDjrD{e1JVXK!IGOYtb)ax^eGp^n5KZYrK{Gs1Zs(ja~ zJP{<~;SoeO^!`V0Kzw1b*@S5Niw#Z5V()JHi^L=h0+Wa^aBZnR4aL+l_Gp=5dEz+* ztC3L8A8noaesp(0{kbha^?U=|yTCNDmwtZHN;b>hAw7h-vTWq3u_Lx%Wz;#V`{>NRUq?Z zoGiDGW#w4hCw~x28FKi)2ndQXr3UXWEqU2$utFy=%&F}kbX9jjwf(Mve=}JY)_r@A z%W;zDpXZ@AotNlX2XjGH-SL!bj5(HdB}wo^-FV`^#C3?boeWKKf*JVe-wDklSm1t% zSdMvq51D!gmO2JLoFCh%omcykjd0F-w1TCbFAGoOEN}J{Cc-CTcfcqP6;mki6|Sq|y`R?ngA5$co}69hck{Wb;KpM(He7CsoNw0rG&#fpn3unn7e!LD#5 z^d+=R-PzdWn&!#Db<+crJ@GfDBKzmdsNbR7OFn?#a?=yX{Kdq$|;>q{@#S zDb3P${`A%ch7H^D(WFXIC6C|UzpwpFo|qRTXovaRUsTEc(C{inGD}@XMg5=QyA9~O z-mjZA3Vr_~H^~-9ILl2e*lT{F%6YUNMa?U?13M_H{~_y-{k2NOUZ9&=evJ@mPkA;J zc_Z;Tp@F%9@-1L$l;82>#EL%Kx-$m!wz@pJI0WnX}Gg z{!~FzIPp^=??FxK=?$pAL%iqc`S1P~txES9z4CK<-%I4vC2G9YFIVp~JKfKwUfCWg zC9%&4fMrwmU#J~JUN|0Bx3!NYebAh%_>()4sQi6ZMw*dUYsL4|oK16pr@Av`!t%0` zU5gh%q=??F!*ibUK&>{kog(oo-F=koMU&fs*vxO-4KU8ywcjpatJ;DBrH3)X@eoil z`g}<}#znPBYRp(Zf;@t_FoO*Lumw2YE!RDLV)|>tc!Q6|bPKJEk_`2n#r4oPcplvMgQo|R!A>RE_+bqHn}+DUGn^%m?$Q*$}`CtiW>dNiO5t(l)W= zjo}LTz830d$b6&^;nB^p9VHnBDH2WuE`ZT1G42`@AyHAf@V?p+#?oe!9XU-OqQStj-9g?7a3f8}aWpbsq`YsC*bSK5vp7xybr zMvmi55{r}=!rTA}XPcE#rwC#br)wg5`AnOhAFu-asr!Eqa4_w|QcbTdrV`|vD2XYP z9F;(uON4D-Xu12Q@XZ1%PhGg{p=O(4)85u9&QCwkgSk}FDzE8im}@6#*A_L178znZ z`-<1L3PTwK$`V^zUlfA%pe(Y0@$^u;efgsgi2#BYkph>}z*$`BABE3=$mn+|EtXq- zqP*`NkKL{gtSWD7EnuT{t-ybH^NJ+i)uLHgh^TNAJjuKbjB_rFHGps6vsTj-b&EU* zm%kMCh+g z6Joqi=}uLzmBZc~BFF7r84|60mh4dn8<^y_RWOGUTCkWVIGlSzkRxe}3sc@uHCb5W zhP;h;fc3Yx>ac7WC_7tV+gMwNY>}T1AaGF zJF?cc`2*QZfa9e>9IU@deS6liIpyNy0Bw=i42+fmb8k=dZLSt@X77L_^GQh- z(V2P$7Ijr~P4c~(L=DY_G)1K9lq@u5A^Wc;hvMv0ijy z+ePKeJyE3}4c}~|@6_MQa?XHsBXQU3t^Bmp#1u0#{nf$4ewth(!5l9(Px98qk#_gK zriezgM#&WB4U8oPV`x}Xmd8lS1dH3p@lz^^bETUIFDj>gH}|W$xz%pL`%>6x^L@dR zG$ldTe~N27a$3RdXt_B`+`GKjAp-Is9Zj{|5&Fv34x-BKMxl!Q%|V%1qAi&#{WUW2 zaxqELd!_)^iKm_$^_*9p0oh}TQCD$djZ(MTUe37$J1_6~R4=4Gp`{5|KZqUXLYG&4 zT8738*Ta|F3WRhEiJTNhP4tzws5GRigb54n&ogSY-1d5CT?P2&|;h!#$JoRIAZ*_r|*&6 zM5vD;##;`zIXF7up}CfKZ*(gc)@Hd{I6UQ(6ED&G%QDE2+|R}O1CHb2I~;jYN_vy# z5qpLRA*qq-mR?50BJk&L1(AOy)X&@4m)Ad;DTu9zgVu(JycDhZ1V*0+E7>Xo-JnTQ2e#pVkzP$rHqUsrAEzz0zHC6!ouV)if4P377v$}%tLE~DEJ)2 z#T7Hr6&Bh-V|J|3bDn55*|N*)hRC*Q>~2C3OWprH3NHiuZRfnM&7n|Lamk*gG}n>d@1)>PMN zP*~1hlqhC_Xj9#HQ4!?sKsw_+bnjCT;Yojl8kp;smjx9QVJgudtj0KHXS|M+`x8Qq z$)=3S0kjXiLm=aN*3p?J{9_e%<3u4;#R!t?$dbn-EO=07LZSRjAUtsXCNy;v`oSdo zz;vcLl^z3quk#dmeHvcHH*qdsT)s*&FXWa@5OU|4Ay; zwrN>yrlwHZH!#!9JEtxau2r+LxJy4p&Zj-Bc-wfoJAMu}U`+mTDxdY@PEXYjD+jHk zT#?S&>(QyxhObFs#U_luQ(@|np(Bz^B0lURjhfZxvGBKhDIcmGwYOw8hnPjr_ z(PThYg0Gr*B#4R7J-epf)j zmelU)J|jwRo`6-}2NpJmwHvD?>N5-GO}*BG$7UA!NEt?cOVa)J1rT@G2c+hF#OmaeSRJ#~l89^*`dQ z9mfBrWt^kuVmnFsubUaFspvi3_B7c{L2Hpq+=PVQ9Y{)h>)KG^`QOmABQ^P064Ujp z%X}WX?l3VQYh<*=#XS(Tr$M2qScec%AXhW4cCb=?bhWKPEECi45d-sqUo#%ozU_`U{+nZTdHDQ4NKNC^c!*+|TKlxw2{DrNh zX^N=dDY(CbneBXdLSjBWz--h$Oxh`MzR;S#l@=S4uKq{+f+Z=5W6~5CBT;4`9hJh{ zbD_v)n_mIL9V`&}nUx@yvWwmETgd9qz{A*f3;iPL_crWbf(t{7Dl8K(ri6GZ@2w{V zKP~NU^cJT0tdM(=!8gX@O7|7T2XmNjZzMNQf%9Ilc~3=~&IfYe;(+_z^^||Ru^`u! zW^hynZWe1R4+J&bm?JL)=OczKJ-_fRJ(|4DN^w7p0d|*z?sgQ>3-?t@)I#8-p7$a$ z%s!!`1)7l~FM9;TTfGCBN(+ny0(3ulw~AM@6W%z@0e4g_#s&u~Q9qCwPU9=V1uEDW z`Ah?6!u>t4#6=+ar}~#35Of8JS^hDhw9f$U zr_+->nl)D{Sl5I0?B5RGj@x@zgiD3-6!WM*Rjt*B;G=W@B7}$Mj&?kphr40l8tYLa z^9XS!-|J4*`sD}WdxlL|RNfa(P|A~U3;m15klmy?Ec4UQ>HCD^>hpgPnw>0UaJ+5l z@6OK*kb`z5u0fph>`0o~tM_M4MZqz@OfEqco55Z?$TiHdOOrc4I18rvhs*I*lhXPp#kjh2tUH)8K!iRdnALOC!Wn!A_agc$E!y4#=i#uW#z((p(^mBEB`o9*y^?whB z%EFbXAL>z=uQCkQwrep{9L@h+=xSR5>j@4Z%d56vdj8^{{~Y0w;|=9Fzd3}%MwFUEVg2|V9_3aJk!xo+ zu@&qxr0B-WcRTrw9TWMCZA~6*(#QT$OUuZe9XvZEH$dPS3hk5`Uo|wN9*mq33c|ms3slD*>>8xKwIpdf<0QGfM;dN3(Wl7L zZiWYt1A~;0uu6$7*Amg?$qw3pPMs}*IXgHr%VKNDPH(wdUIpiQ?}PcuyVL?T-%j3G zgwCm=6Zk2Ok#p@=nV2DwOwK!>1-z}2>66=`DN%I!yDn|N+2Feh#< z8(r|X3JP7vPkn^I+lYmTa#HR%y#a43rj#pZg3g^7kZjt(q8=7Q8_&z9mo2A=rrYD? zqtXC5gD>FAe}oBL10QMILph%^kv$80uOk{TJk05>nLWxyE**6X%x@RIPfBEi#rvhtILXt=CPYfrdEU-2;1@#bI1IP>HrAE-ll}HAiJRGIh@vf0y z7FTBdk~MLmK*{2dvB(?rTwzG86b&lV18#TR8ghJiJ7?=Ft1cph_Ie&9IpS@jThscg z`H)vaegjEXlwqAeyypRcUSj>4doe$`wxh# zDm;ABxEk?5=~!ncWK>*ldfIOmn|I5-bCYJ39-I!AL6{x}cBjP+2<^qBA(Jjk(w(gp zzxdUZ$HwQAc)I%0^NKaQw#>Tv&Z{+@KjZ$`CmdGc>MuVGfx?Ae>GJ4Fp3qhYXkVH5 zF>`g7CG{LI%~M$*@aKF_CJkz?l_>k7)^}O99*~E z&T<*Dm#H)2O1~VNCa3MSx0@Y7n4YAmj3>AB?xC{gstNmtzNd{Hv%s- z=VPl(aYOcJp6vD%3(hLXQxUHFzcRR(w`wD&7^vK&;&c)pVm>>kyd*|OzNf$P>#EP+ z#;QH+c=tgS<89(atFev2-btvmb|x^%F%1|rIGE^pHq>7;26_~8kk^d${}zlkLaf+s z*bjlo6Dnlk#Q{V4=y!w~=>S=Gb8G0^CH)>p zl_=?~5x>pywHBpYfpla8+-DngX5UU4*H=vpnO9;59J02B(Z7Y?@@@@P{8!hDs;1={ zwQ~&#QGz7B;Z@x!dwAorLbe^S(J;OE&2D*4(13j_=QBQf%w!MoyJ*u!dBWPjt=jn= z=V756XdTm1o}mG3RJ{5?Mq9%{d+EB&rfBZBvn(+?FgWi?llnGb+j}tP%A9WL{RB)q z=hG~1dZ4<*o0)$6>?t_JI7UwBTQi-r?JNc%-uLhr)4x?igwsc`oz&V>rq4eM6q;AP zYA^dmQdExAF@S^7EY|+-@T-H7KhDFY)tD#$&aoR z;R;KLrtgRtNBm^IxFodu3}J9W&5cf1j-DLV&J=0dbx; z%o2*orSpCEvyp#r7j9$A?6Hk@ocoJ~Y0ea6TRtFmqD4xJ5kPzuxcm}8kTeo=>G>8x zg@MuEyAk;?^Y5=`kZ_HridwtBH#Y+yW zVSr=0T>4k~&Fe4C$IQW|j9!1twbTXZ89kQHoM*D(L%YiUe!gHg3bJ`qYT5X}n(`3#rpC&P}v-NCAWdJsy&Hy-8hH>jar5R7#g=l_l6I18Ym{ zVgx3ALhqMil5PJ_Vb{)9eR4ww(-E=!TrGm>2AB{rn0|Vl!7{85fA;9dI0r3Zn-*ZI z0ke2^-LJ?CS6aM;KAFNEGHj^4O?M&)e?YIle~%tF5PA!SoxHe5bG)2M()xzPNRgxs zru~+3V;X*?!}3T@@g)$=Ic}^oSXhOT{WL~15BzGm_#MQ03@yS$m0fwd2jbT`9t!WV zg^ZG+XzR0s?w~9;^$(XBMr=YNs(wcbT_Rp>9&mT1wKVNSFOJP3yW+;%<|ue$d0KcI z(hZn*X>jKsyQJdeXPj|;u^#p4GF>}4dpLkyo<3#>FYi-$u~cL`pMi+o>d*c4(j>mI z)Fl~)BHpPua@$VIOvYKx$vOwEGt0Xofn=_Jk`!W}u@*15{*4n-Ej@odD?;2Gjtctc zys}C_Q@wF}J!tM$*=&r8U5XT1Lc%n$_VHsIfrQ7sE5hmhq@2V*;f-K- z`FSdO4%8xKAJOdf{UNQ1pWmvXcSd~HQKjl(HiUTmk~c(Q29x=&l&;-~ZdAW_!PEJF zjJ;)C(|`E({j)Ft6_FML1f)b5LmHJ*M@VdJqog+2KqaNI=n~0ILO?*+#(*(tP&zh7 zZ4(fX?vO_6+W&Q35AJ$!KmR^Hzvt&TkK=t@MN)$%_90IKG&2r9(XhsLg>rQ}SJqyK zL8LqwpM(bl?>EwfPC1HNXn&l>#FjAv1N2js6ejVqxi*5QCP8O&%6Uh4>t--9qO-H+ z>TA7S{?%#_wG$h_XbuMU_j+BHK#3vR;P z&w?rPyc5W&cmPx`-_ah6%J|75y9~?N7m^x1s3!#uWrPAbZ|KKnmUS_soNB$OJ>>kD zc~PBiiS%*#=tlmHJ1*;_XAXtp{S)8B3AR4wO>kMcOV%W}m_P($7kHkv1iUm9syjmP zfzhpL0|IAI)Vw)AZ@^qH?k&IUt;E9IyGDeTFADXpO+uvy#t&JtzMsYCti0DQ)TAaq zyWQQ-7Pnj`L9GTC?g`oa_wLVy{X(aNNhfmJ3)Thv6@+-Ju%avikG0D6-VGdW zso|TwdhfX`N>QDZvm=#O^Rj;TFfyX|f)p|*vMQhrvplvYnNG`@vAA-jdRct); zQ6~fD>%#f9i`KK-zrJ*O-@G%O?2$qhcr-rdLpt$-s>N9U1~vP7ru$@Ksv+)GrkM0B zs~fwcKi>fTw)qc7_X^OM$Di=!QGNMaVDz9_qQ0<_dTtDR=5>wO=)Mc7H)GmlE8 zmaDT66L7^k9 z8B9Rhq14$X_rgFF5m&Z0&*JQuMijRI3hjnyCdJ;jj~!=dob|#Nw}wJ1`=Xc z#a8h!BQ|WSFMan;*kfDJW(1Pc=?H%LwcbB4vM|0iC(b}4E~NX|)@yLxd=bR_>L>ah z8Fg@}YUJf$!w}aQTeRvGG?^Fu{u~oq4bO_s|6 z$(DVOlosH>{xBy+T{{sy!ZT;#eY;21X?9ca+6oEc_5byZ>Pt#6UeP^uVk&AwpB(;c?&+bss68yVSu5UigwxXuLll3aZq>Nn6PY=Nq27z zqXGgOQ?i$fn>H(qljlpPxR$FuxMYgYbbThqrKRzSyX9L%rbPi@Dp^|`G<;M&?^+=r zn<|}%N|ifLDHMAvUjiCK+q2fzdcjR`Ba)OuEMg60 zaXA}k5aBM4Z3E@1tkadgE%tqULzdLFlO?2iS2*H1YP~KJN}>iFi7pRj1+(pn$-}?$ zjpM?_X3$A; zKpBeB(>en$aM}j&?5Y^~=WKYPR5eq=kPdr&(~`xdXAcKa(QF8yyb9D4lwMwD6qB&w z`b?NOLntasxN?WyS9y8n7x z58NmtTV7TA{wBOCZ=uh6d8?1wQw!szPS-z zJ@ngNRzcja^;}k}k7Mh8v{@zzaRy=i=nA_q*&c21j79Tf`X3=p<2+7G5Q0;2dH1_yYOQMlqEou648^BjY4xOY0aZr zrWSl@6!`GB!^5lN>riPR7c#0?YyJ+O#mL+rnBZ^PGYu#@tN5VIxZoBc7-R%3XBBEt z$uD98Ikg+LTrTlj&?n{5z-a(lyK;|(1l?&yrSMB&#p#o_l=Ok0% zwBNKyT$Dmrzf35@)I$rmj)x|S1$E=|4->_u1Hre4 zd!=Gy56D?^7sUHockdxLa`cCvU+)-~%QP91$!Vi9YOWI%MJ)yGezAznva>#R~1NsW(pQcxb30^K}7j_WhdSLH5<>zn1tu;p6g# zuN1fF(3}zt+hC3^cLVp;9q2tX%IwMGVd#@hP;JA60rkSsh?z4oFQB-GUi~GcMl=F~PP;EOE}dIQH68ucV2Ux%qzhP?GY9 z)`jI$jxI9U0X|E}BkL7RP_p^1GhdC0zb;z#6zxw@Uwknk6FzMbeFkW|J>S>FxzeKrVX5v0u%lQLlQs5$#MqLQ>_K1%#RBkX%rY@_lbW=!>X$>pSPgtjgF_K zAD%ih(lUOeS}U9NZdjT=EAM_Meh8ntm}tA{^0zT`#jo{bH~d4$u#gu^4wAE$4)u%; zJ)xul6F_30t^YN?m?C#(!QU!*j?4f;^SSxNu?*(=?GE=hJN{88n5R0ZzYmNrZ{2P!sdk?6EQ&;Yt4tuL=B!r4O`RHG)bOjm<8XYfUG7Fa3OV=4&d#fp_aXztoB8L|*_WWpI~J8<83yUT++ z8Of`+s@&FPfzm#gcKh6Fs_d!q3YmU_aoGQkzH~iB&FQNcz6-jVQLF`%sIlFh2cFez zWS(S}!Ty3bm~^TIw*?P<&-2Vf{Ue`@kXYRxR~D9~N;Jl7roRp@2Wt!2Q@e(OWc$@p z6>WZe^6(G8R3mNi2K~S&&@0S^FJmB{i${4={*ZTnJQ0=F_hSzc@wpMPh z_t&I2`RTbVz-|Y~nl+^sw|Atb+?q^6!HIrNACN8@4a2N|(?-2y?&}XbaT-Siz3)fq~r&+oH2ECiTFq0KRiyNq&t5W_!jh@EQ z7TQxXyox3vwTgATX(hWZ=_Pp15FBTiI?iMt5Q~;|87ZBy89(Y1eFmrUhlb>{eXIEN zU}{P*@uC)KXXa<&>;s(A6;IBT$65}ayTkM&pt^xWV3iD-uUdIV9v*w1q;81^l6C8% z|Cp-K-l1N=UpEQ_N4ZQ2j*F%zf2s>xF~#Re%4*HaELzEaWqu;7pbAHZCV&!pLA)dV z#BmKerT?LMx^7CtE6CI;Yu%s;hIC4w{xBJ8jaUcY zmzt0{J%_(Ak96LECF=XuD+&;7Vj<&5vy1^{GMVD9 z&Za+F+CD0z-kpZu(K~Qit&7$wXnW_mpPXlCN2m!`MN8Ss3N84~WE}O~+W2V(T*PUh z?;g9=XiZ-6-TJ_Q6$QjHgP`zC+xTMV;TBxVc&SwCC1!eT2XK|QJZxg1 zz{=@Rkbb3m0CgH()%tZJRF)8zzrdrd7t>Ipol%E6AKUQBZnacG!0(DpdtTg9TB_I_ z7zcbX7jv4`kfu+QN+9=6JscCj*RsLQ_dS5T~+#=82( z_r}3Au$AglF51ljP<&Cev7|8BA0poJBSGP0R^|hm1p2L&PH!*|{vomVeS~H5S8iJ$ zTjfU6gFX_f&MSY3BaEikb9b(PX@x%nk*`g3+?f)fXt1L7wDieri`S8By4sIDud4J2 zo;q*!%Vd*WHbY@2IbFxSzq0}8nbBC7or)aD02vi-NvkE01$!McLc{lN?A&I?eBE_= znMe9PamgAd#nIj9RK;kIvXM^OJ+J?PYA2+O)Z1hBPma(80;cd2?ubBQ(hNp5Am8hxqA+{sQWns_Z2qp!}7hD24g)XTwrJ`=6{c zAjlE9a!hd_BH`uP_m!yjQmQ=b@zXyriK-8ocVs)blZjA^9d?V^f@Gsa2Li@zxB61u zyvr`1^{;vRkokB2>~KU6^Sr2AQ(;vgUv!8Rilt zi@wfHyq{$=pRcw*5^-)Cjr6~WzTl`L{j*z|5>(C*C|k*5OoR^ zfDx--+JYsA*KZPAc^?DesSJ8&Wli*95E{VpW0PnjNW$?aoRsdNmr? zh9;WlYlwpMC+9aJUpQ33%Vg3!+BWcP?&4-CtSfTzElSo?A6S<6Wgzk=FH`ShLrLnW z1=lAF*<=nLbn*DCX>~rJVd-L&{K96Im}j4#xNa0$@IeGHEvnohT7nblU> zn`xgWHH))AOXoMUa4V19jfUIo2~yLx1>VSSSw8bIyz8Kl;tAQm6}ZDa^r8p9ZGX+~ zVBXok+he8c3{w>|IkdFFN$ApkFuyj(h3Xl?Sxq5F`c~73xWtQi6hrDcuGxhkd{YnF#S~==U-6WEP_) zi$dXGolCR)W0$tC+yK5M7ZtX$bA#p5i|-ZZKP>`c5nhbE72u-nw1{WeOA7%#Jz}RI zSL*iM40g-+?wM9)(3dBF3#*6H3v3*qXno7{pK4io;3=#VW)>9hH1)aqmGauouoB-P zY?o;^)zv++t>G0_o(j92`BMdbJ&8qcWb?VeyW3~fUUZ+rvZh+sY*#D3|_C=a-`+8 zqB017M)gu<@2oMyI&d(4bO)@v0py2zE0t<{j`__0tf+G{oTAV^ncQ>dG#ae+;F4Pw z@0UG2cKdjHg4?!&cC zJ~tuLg2cBLeM88?xme0ATmAG;;==7Na35 zoOO1`Id(@Xh^?yHvH|bOv-)qIZf-{u_w3lj|9;h)xs8^v_OwK{6ldK3W6Q92vY0l} z-Aqre*};J8SAc#2wfBUzC&^btIUhUj9f?a=l{!-CMDsNr2gVZMwk!uTQK2^ zbbE|IR=EIc75}d)P|Za&6pB8Y3KhzV z#VJLnQeVxNmCrl{KRuh!dGK%^i#23TLSR*rSuFGXxzDiZ2>EnQgRMq5IIQZpeA_eFYtmS^X3=0lb5UlJWPgWsHgRc{vpgTlq)ksNOB`8wsu=#=`;>G* z(TgE|e+hrFo5QZ}G<1+{i(4oE7{hNC0B#yDdIBab@3>wo{bjn#1&hq{FzAM{x_p5n zav;AcrzW!uq%gPd%!-w#A=2g-NyKJIV!YtY?HN1Sh@H^8PB&yQ(oKO#WdOQWrlb`=B4{1|{kyi6Ix|H~$5xLGK0%xC4DOS;eQ1^(xU zr3J1&SAOhV^maaI%@e1qYFE#{jy+Oe$Y?*oe2?v<-MZn>4)&uB2yn(B!CNbdsLSfP zR}ZeO|8&;URR}QTYRQezyZ+Yfuk}_aNGHR`X&*yclF4X7mant=)aVj!)<4b4fy^)W zR>^n1FR+#<#6m`$)E6WsVo!%8OVzU~mHS(Dw%}}2L5!7d z@>*sl+3wLspH1PoONCu`pA+pVt6HMwm^OA8-f%E2cf5HizjCIo|(+$J@wmz-^01{?xw{z?lw+q zFM?Be{19eqz%S$QuDESD>D=GM?%LM}`!@D>nsJ|E;`NJp z*k;i3_ZN0V)Ok&#XY&;R-up}>Ul*wp`^7$L2pMib#mo-b-$XOP91y+hv{rnC=%}GeTxG1HG(`!%^ zFIwweQ)VCR{#VVHeln%`glFVMO6@CWJrb&SzAgnnBw!mbwviqQ$jxRHHFX%XEQ$qR zlF(;-WyPw`f*kJN)phzgmj5n&-K?{`*<&iI*OlX0deWrIP!=5e9 zFsrZXLUJXpQb1_gwMCeX2~Tvj1^qIp51Uo|08X%06&(QaH%*y5e*+Y{L6!Jjn4t~X zf!uYFEqaJ2r@7Bawwm4&_P1k4IsnryTHOze!&RpY;)6pX+d|v=mpa?1skI)KP&8gNyvI@uM zpE$yZCgLr|@7rD}9TH|P`*WUJWv&!TU^|#EP72GhFGi>uvht>w&$FTWbCp#FTt&uX zUo-)jvR41n+s&p*bx6rjBYaEG%!`d2$GuY z2V3oo6Z^B`+rC0cisdi=U9C|YVK;t9q^F@6@66K3irZY^abLSzmEjkc z;{W^Y85_x8?1XX9oC)TA{};Vm7-}?5jBV$?{zR0;^J!hUG`tqz#J;P|>}#5&a5_LY zj9RWl1e*Bj{{Gq{y30HKR=*vYeC}>CSs?!La4O0ox6VWUb0OT`cm{(T37|e&sNG9P zKl8nx#gmr?=8h$`%EQOL7M1*{A#ea}&eR{aERN}53KT#I&%1*bq==WLls7e@6?g7ZoEM3g= z+f1nEHrGVm{WGWIhYB1x?P9FVu3QhPD>!F!bUuJGH#2DB9<$l^rokqdGoj zo!>7SFkFgTw^{sagx4MVB{32eXXBmblqnE8D*2pS>>u1vr3BH;$+MnR{<#(sb3Q|* z1R%@UpI(89D>x9G=`6RjUk2N=LV%Hu=0^P9WmR0+n+C-Z3IE0K28&kOIu|N9Z+6v} z1A{c#YD@G>cG(4x4hZBZgG#!X&14Df#??gvIPKOona+JY65-D|o@wQ&G$7}OSgzFg zArsl~o9HmljZS#CBYpN80}3(ADzARoWfU-V4U;+zFT#27{Sw#|%b)F9sG~t@xY!Zt zNO{i`LrC2{sVd%r><^|Z7eCFG|>WlQJqv-#AQN=B!4V#Ip zZt$j^PC3msK{uGL2Yvm$tds}1)MEH-SZ6Ktyfl9Ai?}8@S2~a`Tl`}d%#gWdzeeXn&$r_3oPX5+{-#9Y; zzEiOsIU9Kz(@PBt)Kx>x0DU<|nZ^}*TarBm}|{^5=CAMZcOQ9C!LB|&Ya81mc^ zVuchm;E8#`hQ0RBZW@8@)BSFH&*Y|ocFgOZf=< z9(pQv4IsT6NW+y!%^$>IuM~9y^(JD|CcD4ElB4MHhC&#cfgqGdqRfUH$YZdz*PUtCd;v)zRXi zch>Zd2BA)w z!1LvQ;ex4BO#D#nYSUNU@CDiHLTND1GI8ePegKsEhr_JULR5Ol0zLJfoOUlg^cjU5 zr6}tM+pMjmue{tEJ7*XqL#5q3!Dm6;gz3jLs21Uo;EUX}M;Iu&w2i4iy@v)~+s1WW zXrV8a!E$P}qKf~JioflZMYGyRZL9Uzg>S{GzvCWbZ=(JdLhg#E5! z-rBW1gz8<^WS5i6{+q2lZ0REtZ^mt8ctK*wBz zS)Pj9#MPCm`I2uZ8>wl$oJqJl%Rq&Ba4I?$dm|pdoH-|Itb$kmgxoegf2&wG+>$U2 z)vr~?Si@rl7Gct#HvtBU4zfZfevm0jLEO841L1!c%G<@_AycN79i@nj_raw@+3MbD z4H9x4w0Nz17aH;v!z|QdJmRYy(7T?bknjAS@^>vyklb!$BW&WeM{Ns*#AVe$YO2|fL$x_Sa(jNg9USTtCo-E@}!_>;!2v06Rm%qX? zTHdfsQnd}Pmo%J4fB&;IF`746AIB(S(0S|aq&bSex5a_5mxR5CA;gZvMho7UPuC%Y zCZEb;IaA7OGLUbKiB5V!203(?Us3}cjM(rB^mlW{TU02#k~rr2iA=1Q`)Jmy3hBy6 zp1*fepFhC;zn|*tQHK+*ujdqb{$OOV$P4zxGgpHMbsAb^V|jb;AKNzULf*l|=DycI zuq@=M+U2-0S*MRQSU7-zHE9-n=NN)=e1o%_u|~!U;*-yMk%!Ey7%$p!JM_=jeL=zH z_$=@)^K2e&d~j@g8w)+Ev2(f%GSQgo`(R!LQGnJa*frXPllH$6mJIB$s_*Z6Mc)qP zbpIQhS|#|f%PM+~Bg6Z$#q58rxMYDX6z`X+Qt)5c<+DH1J94_1fxjp9+;4oTs6XRQ zsk$JQj8>t?knzu4VjaZr;7VpKyr_q+C^kk=nG^_8l9ct6>d4dEog=M8fs2f~x+W>e zH++`+IHzQ!*x(dSW8^cguyX|;$WX4JAf?SPiD{igE32|qJgN;rstgfy&Vq}`{!+&~xGNJJJN9{Q=zq|A_ zSUzpibTGI-N|lsxCS05RJ+$-fo(7I9;U(yYDDAS#DEfLn1?ce9pRBuX^UCz+zi;$R zw1!&(I(rFAmO}iCc4B3H9`INRp)a7-1gs1rE8+d;>#NJUMsIhu6&od6;tR15bly0n z4Rt`*sXj8KVw0P>Nv4A-shwgyjhUQ(@cLv)}auSz|Es2|Red`1FtM`=6uMZBdJnL)+}6{x5k? zq|=iN`VYQ|q)F!3fKaaX zm-50S34V$ijNb)*xduATv)&r*owVjBt<1v^WFdnX`sgIl+aY`C&jVIFc_A*kcYz)Axt(u`rHb>&5;hyFX-&9n z>1A{z!adeT1J3RQ&nUzmE}XluNPjU&5v--ShrLLLeOX+PN@0%GTt`)6ZD4=vB7PI{ zTp$f8noWcdFmN1w;X74O77PDe!UFuGdg@v_%y1~lLm9KJ6yE&TRs{LFv#$A#>j0ku zyb-3!24|mYD=fJ|R(yxFN!chgp%;NaYLXBR6ZjU>td{Z#Sn~ntTGj_Y_<~KBI}uO{ zte>%dTif5WqSG++&aNKb8mS7x6_Ku;Vm$xk87oli@7?^dZm7Fl1?}x@Xm>3$blOS- zu<{p0+kCx}sbX*pXa%IuaN14e1?L@$RLe_7gb%~o#kWew#Q2leY_)IJuzCd)b*8a0 zE+nNB$NK|3jluHvD^G$mXEZg9)qbcf7PbS4kG5buJ?Mv@{tfg-^{+!}hn{+iI}ERYWs)QX{xDBWf)ziUYK|6Od8z%qKmMDmE$Vg+qldebg=ZHY z-N-|q(angxl%*mFO5~ZBi(4<<_wTG}t7+=4l`BPm5(n^PN3wu(Gk!8QPYoTKb28hyKdWII_`T`vSOBdf*Cs5Wda_4+ZNPS?zg4$q)Ko! zvi9|_?ijJu_oUOP{BBC}Ue$*oc<;`md*Rct@1sn6SX}Z0l{jqJ4BC)IP?P%UKo#z- zPvLQ~K*l&o4msF#ZO5o?-lM}s>&n?E1MyqMB_;cde8c_P$jx4uz)xu6c}?^a(hnRM zGl&}&>}GopaCY?P6|SG@OlP$tx><9I}WzYROqh2po)L3o{4!u{!ZCVtJ@Dzu?pM%OM$}&Zqss7pNAmvOUrTVPVK^Yq z^9Q)}>PAXHXJBEddcE_Dw|Ao58hS(@-CuI-Lvwcj$M~Neczc_9E?3T4^onN+uM%=H zgE(_}1ND=z?xnbwpP@N|)G;DHd~UsbrD?2r-h*CL2(4lOMMrE6HzI+ty)-BTTocCj zlDzU$sx0Vwbtfj8=ZBFe56@u1fXrz^E1!AV1YG1#kdjOb$SiMa;p@*zNZ6|eznaq2 zGChbs&**ORsJl{297uz!j+gg3)PXM@Jw0EF86u zhYN0BRfR9DlK+}B>Q1N?9L9X|9hQtDk$v_(mk0k+5Sc8+u<5&0IiV^QGU?eY&w6|8kVh;fSm z%Ei)QH*FTf;_}jp8J~D+DpT%y9<)^tSN*Lh<8CE?sP!O-_L23BPoQ;84^dnpAqJ4a zW%~syN+*N+*9Rh^=u)8#)hbzg{796nVsS-A^)KRGT6uxYd9;q|D`1dF zx3~z+Ji8Ic14!OK!L4|QAqbpCJH1#Sw{xtL-1`fB;%FkL{$5ABT!AnCG#~(fP{M9X)^xqS z2n5IWN6>QZXa`$Kwl}7wT4&H>cSrh6T%bZ(f#{Ft5|;zs-VixTF)EDcgyS7cnIa0T z6Yh8z!2KKRFYa(*qed)N;%i!6U*1m`>}X@=g6hD1UH{saL8(XzIY$9SFuToE1WpZ) zm^%NnCZuV&1KZfp34nB<<0Vf{x0%D%%d%;>}(s@+NT{% z)PRj31)T6qJ4B%2*R{HLwjOdVk7z>v*eAAI>k&2%xEw~KlYjqp#uPk<=V*cBR7s}- zqS()}gr+zRCz*0EIJCL*4UnALiX)RLRv{G9DHH6!3D@&$6YQ$d1m^qy zqdMo5A}FOWZnnr_jW>oNgUGs~x54*sCq$55d_EvY7IpJ!9jc!-C$U+GFyB0s#h7G^ zr9xNV+%M=`s<~W#L9Yb zF1Ymt0-OMHIWvXUIMs-9N0lHtyRC9z4Ro4=%>Fs^*0JTvQ=iSk8i=+Rs0?g1;d(-| z4R~b*!I$-}{sKsm@hL2V%rEFK>(54l^j-`BemeTT#OB(4E!JO&>Wl2*%mwDKcJ?#Z z$^wC;D^Tfs2eA&Qe{ewp%8*O6PF2dST$QCpoD#KM?PeFXr{)SCefJd9>bm(Uzo4}& zkP)S>?O|ih@Z0fFzSATmwB8BR4fsmb_1ZGMVi)B21rdvpJ#OYSAav#|I;TT1=TwMP z`^{8qApk}0@ZZJqZL0$B5y= zD{K}9bNCVz*=&ADAPX23`|Ol6dhj-;OQCJHjZS)8JN&}Ccs4*?k`mXin$C}5tp$cU-6)3FG`uPw<%YTv#4sBg9>|H=RT}P$hwTERJZC-RqG!LeYD%x-3cR{!(XXf^f=Q*lZ^0~PLMxd ziOw?*Um4*7W#7>Z;$BMD5%Nd%)! z9?rsjYXYgS<6f#=v9TP^I3EGsdTLy3m)48s)Set(;RDx}Z&ipp7Xoq(&@x8bSa+5` zcKW{%3qrvb-C?wTU{Ymg{cP6{Km-^KY+h@*9pv_)nG75))Ii;Omj=-5qxttuka?zK z=@0y9FfJI{zycln;u$`+vBe^KL1S*q+Wk*OfI2y(GvmX)mF!C;HjN3z(Oe!qL-k~E z@W`JMaqT^#-{{yu^qDPiw*+E%la_PGB1*OZ~D zLh?=PddMfN+{OFIOl*tHTSQbI{<$#tsj4Yv8lBL;4Mz?{zm2=EaFAeZt9NG zTVpcj0tTf#aF7fT`4<;EYG=jDDX%Ik8+I>zjXILQq$P^-C07})emcb%4ZUCP7x0`e zY7XyeFNJA;e5u(u^-7lV{OCIBreSzYw+(9d5B}r=?DL%#fzK(swI=;MRjOVE-BD#W z{a+_37y5%1A2+9M3BkYm)#ph<-;?EyTx0_;TZD(_GLqyE!;Io)7zZVe0u26FD9*1m zQd}JCmcgv8R=Y~VPBM;C6xUfbo)6FrHsNC2^kze-6F!hi$3Sy_$1wea?mqnf)VG+k zCD!8B?wo9k={KwBzeQH;1uXIo&SCZtCvNUeC@WiREj68m>-TsNB-HllED+>V)2yzj z>NI$lz?PgY<;VRaNMc82iZiZCKR>)j5HP&|ePfd)Pi&nl;CUQmokiv)+=x2#m=x9v z;d#|IM0qvZGhajt@sVEuc=BrTl|(AL`tzo?CJqRiovEMR>ZOpS(9yAn8*Fvh@iHrI zL{hixLLj_tP7k);g7=DUcBDof2l?y+ecKHH+2T>JYV$?@3>NGq1jzFu({` z2;DNS9pcI)FPxm9JFLN?8NYCDW!+`xKUdHcxu$VmsuQq2dxC73Cz)wbYzU3c!o*i^ zX67YB-&xkk{R~=?I2aE0*ZfBte#hoJk~5uY5f$rR}{$IU5*Sc?Bw?B|2P z7d{V0d~1-ldX^SwT-wpg^e$pT+CpTYw^<6Ax#DKZi}>5j3xpW5Lg-d%UE+JfxgeL3 zs^bpT#33>pvD^y%S-hfKO`8S5@uv=JDlW ztMA0ZGisi{O0rt$-&MfKe`cPtcH*)rh}HpguV?tk#R9+aP!cr*mwZu%D|r$m2+9Jg z_~&uG5A3zmWi0$qU8h{jlU1LC6hgbM{6Xt(?0pUTIE4z=ccy;3XhUsEe_CACB*V&< zHcMC|*1-3sO2ZWAWEvCU?E3TV>`HQ0Tw6I{a=X{= z@z^1Wv>VYE>jTTp?Bonm9hs_YuTB(vDR<9|l){)H%{;rq?6hMQ5OB#EBAHE+8)9cX z{5iq-DvA0ucr~ov_3SSzN3og5C269%D@YXqo&T zufE|z$2OiNjsXdSUaOxpl5h`C1p(hx8o4`ue3Gildo1f} zJT}5-vaCuPs@P~LpP9>fN1=5l?kOpTGK%oA=!CQZP}lIVMVo}0+=xJ~EDrnWEfd4B zBY z+{3*~r3#|;>KR2JG+S#o?=3clmx}sL%N4S>VTn#ysxa4kNm<}Y`Bmj7l32^6`w&18cTUGlU4KJH}zOi4o1?ZO^z}(uSjVCE8ZJ8J}2ekO`-FBfAXc`WZBt*$+}or zEThqnu4Hk>s2g>$4JjK`VV!7vkNF=U0Z;ebCPo5v*PNs-nf7zy>S8=n!EJGBoGuf8?ksr9+%zK?@csafs)~ z1Ha(*Sx`Gh+1`i(+5l<>R%Jw8u4LGI*uY&Fp2=lu&cVDqg~0np9;%;2SW5iA8sd z(0wZqAAJ~7_m1{xQhic>D9)?`yUn1Hs&Gk^B|I;t0K+grV*W;?zt&#&Vm>EOUT~Gb zN9Vo$*=xzy=;@O3N~y(ZP?Xnw+parX*Ocle?x=Q<<+bfePYo#;RofaEVx6%6;#;v8 z|Eanu1Y%F#E5m8ms-~b@xBcJ9F}rusl|C*bP|T-jT}A|hF3)=r`c{}yM36eDcFyj`+Qsb zIWOEBV=+O82= zI+ix{>NCbFS>zFV>RFIdhE0Rg_zvYLwR2RX45|&yCpslGZnd4vm#fL_db8V@l^tf#q478derVE~PClW<8QyDz?pZ+= zGw@<(24H{yqUC!X3cK-O?c&^Z`)=64pZaHMt_ucbg?T;}TkIAsIz)wZ9^}(bS}iy7 zc{4f(enbmla6H{aJrpLv11RycQh$11Hk3%)ghVK9!+XG03^PmFqx_zZK<1Wkm1j>; z+m9;MsGkS@uO*)h^}aoG5Fz%uIMi>|!xM-z@Gf%g#y*iPHpv^;+Si-Yh#)7>gQZ{u zY@~D+2k%w%Gd+7kSzYN&2CI7R$6_AAx>;&5b9z3&J~p3R^q58Ca=k0OX7s{e>xvgM zk6IW3T_j^<0TDNd!z@vl?;2{4lx}`SWya5|`hd_=u#@_-Zqg_35z|`=45HfT+ zXWIEyc~jRuTf=#G!k-qv>k`fJoIlk<{|C-afc#(J?B6XEs{S{e{ivq&-!+L=Y@ZHGggv#0$3M6>(-ztHTj%?>$pp!vnItBo5|yQza35G;cH3QdI;JtM-wE-q1tM|3%qdzO~&3*n_VZZz+Z16ev(!gG--Mpe48ytPmhTaCchVDaC`e zI3&S?OL2EdaEiOTZ=Tt?W_RW{`(porTzQe?%Kg31`J7xrt+uEl)aQ$t(YDAd!Mzq& zV4aa!hdnMhDj~;d3CD&X@+YtvQ8CIzm5xo!g3c+~!Y%+Y0=(BILdNY3vX4YhzoRvM50(`9Er5KPcgXIlgHySGz_#-C@fRKSnLB`q^M3L^Py<<&0`t2OqC=+#>z zX5yKXm?H7|1-u5*Opq=g0>5$I@)0Upnuy$ImSLaZd4rqasy*tAo02R1hj`x9!uXr?Y%73`mr4` z58ZoxSWn+pkNUr1_Myme*Z+Xoy&j(j?5kiB2xi02NNdc`PR_4Y6ow? zjCkT-3E^Oh)S+b*9exA5e=)s;5r6oE=1Kc#Z2!;NDiFma+_ytfFY| zdlcsHZfJ$^I>2k<6)c~4u?Rq@qXVw8x9o#K7_oU)!ajjHPo0C76b?sLPF)`jLpy#Y z(xV`sXW`n@4>q~`lmCJ$B7tTFVb?0g^yBzK38Pl!n+{|aXo(Elola0*y|E(Skh^6i z3hL6(0rVo$JpV2%J=Xk;qI8pPAS_s_Vq6@ECPY5g$K~gpcVE|8gLeF%F#GhIKJgw# zopD_vuaHQ$Huj;0o8n0XTh!ip)_=(Cp#LtjhYIfswt+s!>Vtb5&~K%OKo{M&xr*i5 zJs&ovk__s%RrDUI~Zlnj-UX>KL)VrIjch;SB_Xzc{f5SMZMwGD; zuEi4Ggnry=HRyFGo4XO9QSjTy@EL!4Mwv2y2yM|WJJW9b3xI6&56u*rt-~~95NfS1h9|hOF@P!5+kKs&I_(zEDXjFrc;sH$%Ipo5|1X)H;@9wrWk3*$l|H7|_)w;v#iw-2&YBEi*Zk>08FO@o zh(#24eIuS{CsshvQ!0r~5q^{=7ixPSm=5mLhWwYzK16p?AimwR24pc^PC{#(Qs~k` z1=xljsX1{@1-o0`-0t9~7FIlIN8!*fV}>OSQ}0wuI~NM)J9PO~(f}zvH=3Q=adzzz zim~u170#5lHJtw8plsMGgh`{xUS#rCz$UTWU=JwSxdV8hw!r<)vR~3Tf~apo7QDXY zl^5cYcaT?NNwC4cHXsfUY*9|-N4<95#(y&q0s;I&|6sfd)?VG0`&nm`hxr=NLJ3wb z*3K$>W98iUONJLWU5&yeu>b)`pjv8u_QJZ2OMN*PQdJEP(^%CFK@F0BMigdWMQ~9U zJ!{rzs%`@rut5GFn0-#zi=CohiS+*m%zphpVD|mDSeU(7OJcDF0CMA`XIzk+_v7k7 z)cNy6Kj~1L{qp`YbsP#VPO{1UksnuNn7VU<>2+65`(5wE88x4ILXIS3&zp;+p3pE@lD>RgLTtf_JTtYt?wcHye#B`|1`|wQw*t4cmsneowoJ6&P zN-4v6i~?Gn!g&0pav~jU&+t==^>Iztyfyp(W!c?2ZYJ26d?py{mIw-$C^aH4ugOl# zl4OTP{i;tWpAS7!y#inws7(Dx5v^1-LyPO>Fc!=&wqrnVy%hgH%DUX09JUHE`c4Hg z67M$awer^wd5_kvnT%lwe4lSv63oj?-P-lThzD~M+1*VweFSB78%aFNAx8q~y(lsQ zr_HiqRZ~rF%Rd=!(A~oDG(IVR&BOPM0Z`M#iInM+^2$NccxgeX$m4AQpYG6KPc27h zY@^H|zvtN%0iMroYtIw`GdA1Z(|@$U$sRBT;m8}*P(fTSN=O( z#JE<2_t2Asm&gc+&y8paiG1@+1feN(JOYbxu^1pvtOyQGVR|vu&%)u6(K?3@Q6R#! zQm>@&>O+2q`dapx+5@ED?xXKAhMp+=(SY!)mjxfi`^|45BJ-K^jgj#m<|*Hm^FS6U zd=jnCu*00$O3;H*!IUGERn>mQBLrK0>GdojKHMJdWrv-RnA` zv`)@%ZXJ z9rl+d9YV{SVssX)z_axKrLtF|kFAaVRoTV!yevj0_` z(1ef47cFRd-XTgwu2BGsU(QW%po>yvPNy}ly1rH2CnuC$jw+Yfs$%c7lGi|G;dX*K zI%yD*`fBaRgV8av4pAgK`hB!fC1E0={11-5z!{)gjLf1&b=Zg=B>>u;%w3C!Ltk-1 zz8{F=Yf-zL;pY>EDU)CC3rKMa$iLyNh3wjH#>!-Bp3i75e$+3YF{zaGJkVuxXRfwt z=7=NXMpp44naRnLWJSC<+1mM~x5t7qir}gB0etr1u4kYqzV-fIMvSBU5vtpA6&T_l zn~$Xd@KZ(Aqk_=k2@+7#goFG&L5m6O04RdvibB`d66g=Xnz¬X*SUE0pY86ORFK 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 literal 0 HcmV?d00001 diff --git a/share/icons/svg/key-enter.svgz b/share/icons/svg/key-enter.svgz new file mode 100644 index 0000000000000000000000000000000000000000..7176b5acb722936825246721ec6ca93ff0578f4e GIT binary patch literal 9345 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 v-4i%GNBJm+JfdoMCyMO!m?)>1F`PKE= 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 literal 0 HcmV?d00001 diff --git a/share/icons/svg/preferences-other.svgz b/share/icons/svg/preferences-other.svgz new file mode 100644 index 0000000000000000000000000000000000000000..4abeca3763b099d5870f723bfe41a52e1bb8b3ac GIT binary patch literal 12294 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 QN)LbiKL~y|t^ig508PrE{Qv*} literal 0 HcmV?d00001 diff --git a/share/icons/svg/view-history.svgz b/share/icons/svg/view-history.svgz new file mode 100644 index 0000000000000000000000000000000000000000..fff230f6660e7b290704c79d29bb4eb55833e466 GIT binary patch literal 6519 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 literal 0 HcmV?d00001 diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 7225c4c5a..cf6ae0401 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -54,34 +54,47 @@ #ifdef WITH_XC_HTTP class HttpPlugin: public ISettingsPage { - public: - HttpPlugin(DatabaseTabWidget * tabWidget) { - m_service = new Service(tabWidget); - } - virtual ~HttpPlugin() { - //delete m_service; - } - virtual QString name() { - return QObject::tr("Browser Integration"); - } - virtual QWidget * createWidget() { - OptionDialog * dlg = new OptionDialog(); - QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service, SLOT(removeSharedEncryptionKeys())); - QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service, SLOT(removeStoredPermissions())); - return dlg; - } - virtual void loadSettings(QWidget * widget) { - qobject_cast(widget)->loadSettings(); - } - virtual void saveSettings(QWidget * widget) { - qobject_cast(widget)->saveSettings(); - if (HttpSettings::isEnabled()) - m_service->start(); - else - m_service->stop(); - } - private: - Service *m_service; +public: + HttpPlugin(DatabaseTabWidget * tabWidget) + { + m_service = new Service(tabWidget); + } + + ~HttpPlugin() = default; + + QString name() override + { + return QObject::tr("Browser Integration"); + } + + QIcon icon() override + { + return FilePath::instance()->icon("apps", "internet-web-browser"); + } + + QWidget * createWidget() override + { + OptionDialog * dlg = new OptionDialog(); + QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service, SLOT(removeSharedEncryptionKeys())); + QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service, SLOT(removeStoredPermissions())); + return dlg; + } + + void loadSettings(QWidget * widget) override + { + qobject_cast(widget)->loadSettings(); + } + + void saveSettings(QWidget * widget) override + { + qobject_cast(widget)->saveSettings(); + if (HttpSettings::isEnabled()) + m_service->start(); + else + m_service->stop(); + } +private: + Service *m_service; }; #endif diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index ef153c123..660d5a864 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -58,8 +58,8 @@ SettingsWidget::SettingsWidget(QWidget* parent) m_secUi->setupUi(m_secWidget); m_generalUi->setupUi(m_generalWidget); - addPage(tr("General"), FilePath::instance()->icon("apps", "keepassxc"), m_generalWidget); - addPage(tr("Security"), FilePath::instance()->icon("apps", "keepassxc"), m_secWidget); + addPage(tr("General"), FilePath::instance()->icon("categories", "preferences-other"), m_generalWidget); + addPage(tr("Security"), FilePath::instance()->icon("status", "security-high"), m_secWidget); m_generalUi->autoTypeShortcutWidget->setVisible(autoType()->isAvailable()); m_generalUi->autoTypeShortcutLabel->setVisible(autoType()->isAvailable()); @@ -88,12 +88,12 @@ SettingsWidget::~SettingsWidget() { } -void SettingsWidget::addSettingsPage(ISettingsPage *page) +void SettingsWidget::addSettingsPage(ISettingsPage* page) { - QWidget * widget = page->createWidget(); + QWidget* widget = page->createWidget(); widget->setParent(this); m_extraPages.append(ExtraPage(page, widget)); - addPage(page->name(), FilePath::instance()->icon("apps", "keepassxc"), widget); + addPage(page->name(), page->icon(), widget); } void SettingsWidget::loadSettings() diff --git a/src/gui/SettingsWidget.h b/src/gui/SettingsWidget.h index 236206299..e94f48767 100644 --- a/src/gui/SettingsWidget.h +++ b/src/gui/SettingsWidget.h @@ -29,6 +29,7 @@ class ISettingsPage { public: virtual ~ISettingsPage() {} virtual QString name() = 0; + virtual QIcon icon() = 0; virtual QWidget * createWidget() = 0; virtual void loadSettings(QWidget * widget) = 0; virtual void saveSettings(QWidget * widget) = 0; diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index ff75540af..51e0bb735 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -90,7 +90,7 @@ EditEntryWidget::~EditEntryWidget() void EditEntryWidget::setupMain() { m_mainUi->setupUi(m_mainWidget); - addPage(tr("Entry"), FilePath::instance()->icon("apps", "keepassxc"), m_mainWidget); + addPage(tr("Entry"), FilePath::instance()->icon("actions", "document-edit"), m_mainWidget); m_mainUi->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); m_mainUi->togglePasswordGeneratorButton->setIcon(filePath()->icon("actions", "password-generator", false)); @@ -115,7 +115,7 @@ void EditEntryWidget::setupMain() void EditEntryWidget::setupAdvanced() { m_advancedUi->setupUi(m_advancedWidget); - addPage(tr("Advanced"), FilePath::instance()->icon("apps", "keepassxc"), m_advancedWidget); + addPage(tr("Advanced"), FilePath::instance()->icon("categories", "preferences-other"), m_advancedWidget); m_attachmentsModel->setEntryAttachments(m_entryAttachments); m_advancedUi->attachmentsView->setModel(m_attachmentsModel); @@ -139,13 +139,13 @@ void EditEntryWidget::setupAdvanced() void EditEntryWidget::setupIcon() { - addPage(tr("Icon"), FilePath::instance()->icon("apps", "keepassxc"), m_iconsWidget); + addPage(tr("Icon"), FilePath::instance()->icon("apps", "preferences-desktop-icons"), m_iconsWidget); } void EditEntryWidget::setupAutoType() { m_autoTypeUi->setupUi(m_autoTypeWidget); - addPage(tr("Auto-Type"), FilePath::instance()->icon("apps", "keepassxc"), m_autoTypeWidget); + addPage(tr("Auto-Type"), FilePath::instance()->icon("actions", "key-enter"), m_autoTypeWidget); m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->inheritSequenceButton); m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->customSequenceButton); @@ -177,13 +177,13 @@ void EditEntryWidget::setupAutoType() void EditEntryWidget::setupProperties() { - addPage(tr("Properties"), FilePath::instance()->icon("apps", "keepassxc"), m_editWidgetProperties); + addPage(tr("Properties"), FilePath::instance()->icon("actions", "document-properties"), m_editWidgetProperties); } void EditEntryWidget::setupHistory() { m_historyUi->setupUi(m_historyWidget); - addPage(tr("History"), FilePath::instance()->icon("apps", "keepassxc"), m_historyWidget); + addPage(tr("History"), FilePath::instance()->icon("actions", "view-history"), m_historyWidget); m_sortModel->setSourceModel(m_historyModel); m_sortModel->setDynamicSortFilter(true); From 7abebabf4fa1155601d517aa85b9edfa8354b36e Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 15:30:27 +0100 Subject: [PATCH 077/333] Rework general settings page --- src/gui/DatabaseWidget.cpp | 2 +- src/gui/SettingsWidget.cpp | 12 +- src/gui/SettingsWidgetGeneral.ui | 562 ++++++++++++++++++------------- 3 files changed, 343 insertions(+), 233 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index a67646ef1..dffbe69d0 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -287,7 +287,7 @@ void DatabaseWidget::createEntry() void DatabaseWidget::setIconFromParent() { - if (!config()->get("UseGroupIconOnEntryCreation").toBool()) { + if (!config()->get("UseGroupIconOnEntryCreation", true).toBool()) { return; } diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 660d5a864..d8f50a3b0 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -61,13 +61,13 @@ SettingsWidget::SettingsWidget(QWidget* parent) addPage(tr("General"), FilePath::instance()->icon("categories", "preferences-other"), m_generalWidget); addPage(tr("Security"), FilePath::instance()->icon("status", "security-high"), m_secWidget); - m_generalUi->autoTypeShortcutWidget->setVisible(autoType()->isAvailable()); - m_generalUi->autoTypeShortcutLabel->setVisible(autoType()->isAvailable()); + if (!autoType()->isAvailable()) { + m_generalUi->generalSettingsTabWidget->removeTab(1); + } + #ifdef Q_OS_MAC // systray not useful on OS X - m_generalUi->systrayShowCheckBox->setVisible(false); - m_generalUi->systrayMinimizeOnCloseCheckBox->setVisible(false); - m_generalUi->systrayMinimizeToTrayCheckBox->setVisible(false); + m_generalUi->systraySettings->setVisible(false); #endif connect(this, SIGNAL(accepted()), SLOT(saveSettings())); @@ -106,7 +106,7 @@ void SettingsWidget::loadSettings() m_generalUi->autoSaveOnExitCheckBox->setChecked(config()->get("AutoSaveOnExit").toBool()); m_generalUi->autoReloadOnChangeCheckBox->setChecked(config()->get("AutoReloadOnChange").toBool()); m_generalUi->minimizeOnCopyCheckBox->setChecked(config()->get("MinimizeOnCopy").toBool()); - m_generalUi->useGroupIconOnEntryCreationCheckBox->setChecked(config()->get("UseGroupIconOnEntryCreation").toBool()); + m_generalUi->useGroupIconOnEntryCreationCheckBox->setChecked(config()->get("UseGroupIconOnEntryCreation", true).toBool()); m_generalUi->autoTypeEntryTitleMatchCheckBox->setChecked(config()->get("AutoTypeEntryTitleMatch").toBool()); m_generalUi->languageComboBox->clear(); diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 400a6ce02..b58f31e80 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -7,230 +7,349 @@ 0 0 684 - 452 + 732 - - - QFormLayout::AllNonFixedFieldsGrow + + + 0 - - - - Remember last databases + + 0 + + + 0 + + + 0 + + + + + 0 - - true - - - - - - - Remember last key files - - - true - - - - - - - Load previous databases on startup - - - - - - - Automatically save on exit - - - - - - - Automatically save after every change - - - - - - - Automatically reload the database when modified externally - - - - - - - Minimize when copying to clipboard - - - - - - - Minimize window at application startup - - - - - - - Use group icon on entry creation - - - - - - - - - Global Auto-Type shortcut - - - Qt::AlignLeft - - - - - - - - - - Qt::Horizontal - - - - 400 - - - - - - - - - - - - - - + + + Basic Settings + + + + + + Remember last databases + + + true + + + + + + + Remember last key files and security dongles + + + true + + + + + + + Load previous databases on startup + + + + + + + Automatically save on exit + + + + + + + Automatically save after every change + + + + + + + Automatically reload the database when modified externally + + + + + + + Minimize when copying to clipboard + + + + + + + Minimize window at application startup + + + + + + + Use group icon on entry creation + + + true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + Show a system tray icon + + + + + + + 0 + + + QLayout::SetMaximumSize + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + false + + + + 0 + 0 + + + + Hide window to system tray when minimized + + + + + + + + + 0 + + + QLayout::SetMaximumSize + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + false + + + Hide window to system tray instead of app exit + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + 15 + + + + + + 0 + 0 + + + + Language + + + + + + + + 0 + 0 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 40 + + + + + + + + + Auto-Type + + + + + + Use entry title to match windows for global Auto-Type + + + + + + + 15 + + + + + + 0 + 0 + + + + Global Auto-Type shortcut + + + Qt::AlignLeading + + + + + + + + 0 + 0 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + - - - - - Use entry title to match windows for global auto-type - - - - - - - Language - - - - - - - Qt::Horizontal - - - - 400 - - - - - - - - - - - - - - - - Show a system tray icon - - - - - - - QLayout::SetMaximumSize - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - - 0 - 0 - - - - Hide window to system tray when minimized - - - - - - - - - QLayout::SetMaximumSize - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - Hide window to system tray instead of app exit - - - - - @@ -240,15 +359,6 @@
autotype/ShortcutWidget.h
- - rememberLastDatabasesCheckBox - rememberLastKeyFilesCheckBox - openPreviousDatabasesOnStartupCheckBox - autoSaveOnExitCheckBox - autoSaveAfterEveryChangeCheckBox - minimizeOnCopyCheckBox - autoTypeShortcutWidget - From 9715ba0c8a0833b488b3955bb3645da690e07b2c Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 16:08:06 +0100 Subject: [PATCH 078/333] Rework security settings, set sane default timeout value --- src/core/Config.cpp | 3 +- src/gui/SettingsWidget.cpp | 8 +- src/gui/SettingsWidgetGeneral.ui | 10 ++ src/gui/SettingsWidgetSecurity.ui | 195 +++++++++++++++++++----------- 4 files changed, 140 insertions(+), 76 deletions(-) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index d5365d7c1..28b17536f 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -102,10 +102,11 @@ void Config::init(const QString& fileName) m_defaults.insert("MinimizeOnCopy", false); m_defaults.insert("UseGroupIconOnEntryCreation", false); m_defaults.insert("AutoTypeEntryTitleMatch", true); + m_defaults.insert("UseGroupIconOnEntryCreation", true); m_defaults.insert("security/clearclipboard", true); m_defaults.insert("security/clearclipboardtimeout", 10); m_defaults.insert("security/lockdatabaseidle", false); - m_defaults.insert("security/lockdatabaseidlesec", 10); + m_defaults.insert("security/lockdatabaseidlesec", 240); m_defaults.insert("security/lockdatabaseminimize", false); m_defaults.insert("security/passwordsrepeat", false); m_defaults.insert("security/passwordscleartext", false); diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index d8f50a3b0..3ec9674fe 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -106,7 +106,7 @@ void SettingsWidget::loadSettings() m_generalUi->autoSaveOnExitCheckBox->setChecked(config()->get("AutoSaveOnExit").toBool()); m_generalUi->autoReloadOnChangeCheckBox->setChecked(config()->get("AutoReloadOnChange").toBool()); m_generalUi->minimizeOnCopyCheckBox->setChecked(config()->get("MinimizeOnCopy").toBool()); - m_generalUi->useGroupIconOnEntryCreationCheckBox->setChecked(config()->get("UseGroupIconOnEntryCreation", true).toBool()); + m_generalUi->useGroupIconOnEntryCreationCheckBox->setChecked(config()->get("UseGroupIconOnEntryCreation").toBool()); m_generalUi->autoTypeEntryTitleMatchCheckBox->setChecked(config()->get("AutoTypeEntryTitleMatch").toBool()); m_generalUi->languageComboBox->clear(); @@ -123,6 +123,7 @@ void SettingsWidget::loadSettings() m_generalUi->systrayMinimizeToTrayCheckBox->setChecked(config()->get("GUI/MinimizeToTray").toBool()); m_generalUi->systrayMinimizeOnCloseCheckBox->setChecked(config()->get("GUI/MinimizeOnClose").toBool()); m_generalUi->systrayMinimizeOnStartup->setChecked(config()->get("GUI/MinimizeOnStartup").toBool()); + m_generalUi->autoTypeAskCheckBox->setChecked(config()->get("security/autotypeask").toBool()); if (autoType()->isAvailable()) { m_globalAutoTypeKey = static_cast(config()->get("GlobalAutoTypeKey").toInt()); @@ -142,7 +143,6 @@ void SettingsWidget::loadSettings() m_secUi->passwordCleartextCheckBox->setChecked(config()->get("security/passwordscleartext").toBool()); m_secUi->passwordRepeatCheckBox->setChecked(config()->get("security/passwordsrepeat").toBool()); - m_secUi->autoTypeAskCheckBox->setChecked(config()->get("security/autotypeask").toBool()); Q_FOREACH (const ExtraPage& page, m_extraPages) page.loadSettings(); @@ -173,6 +173,8 @@ void SettingsWidget::saveSettings() config()->set("GUI/MinimizeOnClose", m_generalUi->systrayMinimizeOnCloseCheckBox->isChecked()); config()->set("GUI/MinimizeOnStartup", m_generalUi->systrayMinimizeOnStartup->isChecked()); + config()->set("security/autotypeask", m_generalUi->autoTypeAskCheckBox->isChecked()); + if (autoType()->isAvailable()) { config()->set("GlobalAutoTypeKey", m_generalUi->autoTypeShortcutWidget->key()); config()->set("GlobalAutoTypeModifiers", @@ -188,8 +190,6 @@ void SettingsWidget::saveSettings() config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked()); config()->set("security/passwordsrepeat", m_secUi->passwordRepeatCheckBox->isChecked()); - config()->set("security/autotypeask", m_secUi->autoTypeAskCheckBox->isChecked()); - Q_FOREACH (const ExtraPage& page, m_extraPages) page.saveSettings(); diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index b58f31e80..88d7cad45 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -300,6 +300,16 @@
+ + + + Always ask before performing Auto-Type + + + true + + + diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index d664736ae..08fa6f4ea 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/SettingsWidgetSecurity.ui @@ -6,91 +6,144 @@ 0 0 - 374 - 303 + 595 + 443 - - - - - Clear clipboard after + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Timeouts + + + + + false + + + + 0 + 0 + + + + sec + + + 1 + + + 999 + + + 10 + + + + + + + false + + + + 0 + 0 + + + + sec + + + 10 + + + 9999 + + + 240 + + + + + + + Clear clipboard after + + + + + + + Lock databases after inactivity of + + + + - - - - false - - - sec - - - 1 - - - 999 + + + + Convenience + + + + + Don't require password repeat when it is visible + + + + + + + Show passwords in cleartext by default + + + + + + + Lock databases after minimizing the window + + + + - - - - Lock databases after inactivity of + + + + Qt::Vertical - - - - - - false + + QSizePolicy::Expanding - - sec + + + 20 + 30 + - - 10 - - - 9999 - - - - - - - Lock databases after minimizing the window - - - - - - - Show passwords in cleartext by default - - - - - - - Don't require password repeat when it is visible - - - - - - - Always ask before performing auto-type - - + - - clearClipboardCheckBox - clearClipboardSpinBox - From d98249814d99ea1429972e03f44ac64975adbc82 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 17:46:41 +0100 Subject: [PATCH 079/333] Rework HTTP settings --- src/http/HttpPasswordGeneratorWidget.cpp | 3 - src/http/HttpPasswordGeneratorWidget.h | 2 +- src/http/HttpPasswordGeneratorWidget.ui | 51 +++------ src/http/OptionDialog.cpp | 81 +++++++-------- src/http/OptionDialog.h | 2 +- src/http/OptionDialog.ui | 126 ++++++++++++----------- 6 files changed, 118 insertions(+), 147 deletions(-) diff --git a/src/http/HttpPasswordGeneratorWidget.cpp b/src/http/HttpPasswordGeneratorWidget.cpp index 30e4f71e7..031ff30ad 100644 --- a/src/http/HttpPasswordGeneratorWidget.cpp +++ b/src/http/HttpPasswordGeneratorWidget.cpp @@ -32,13 +32,10 @@ HttpPasswordGeneratorWidget::HttpPasswordGeneratorWidget(QWidget* parent) { m_ui->setupUi(this); - connect(m_ui->buttonApply, SIGNAL(clicked()), SLOT(saveSettings())); - connect(m_ui->sliderLength, SIGNAL(valueChanged(int)), SLOT(sliderMoved())); connect(m_ui->spinBoxLength, SIGNAL(valueChanged(int)), SLOT(spinBoxChanged())); connect(m_ui->optionButtons, SIGNAL(buttonClicked(int)), SLOT(updateGenerator())); - m_ui->buttonApply->setEnabled(true); loadSettings(); reset(); diff --git a/src/http/HttpPasswordGeneratorWidget.h b/src/http/HttpPasswordGeneratorWidget.h index 2b2ace39b..f8e35c232 100644 --- a/src/http/HttpPasswordGeneratorWidget.h +++ b/src/http/HttpPasswordGeneratorWidget.h @@ -35,10 +35,10 @@ public: explicit HttpPasswordGeneratorWidget(QWidget* parent = nullptr); ~HttpPasswordGeneratorWidget(); void loadSettings(); + void saveSettings(); void reset(); private Q_SLOTS: - void saveSettings(); void sliderMoved(); void spinBoxChanged(); diff --git a/src/http/HttpPasswordGeneratorWidget.ui b/src/http/HttpPasswordGeneratorWidget.ui index 29399bcd6..066b9c512 100644 --- a/src/http/HttpPasswordGeneratorWidget.ui +++ b/src/http/HttpPasswordGeneratorWidget.ui @@ -6,14 +6,26 @@ 0 0 - 434 - 250 + 500 + 181
+ + 0 + + + 0 + + + 0 + + + 0 + @@ -168,42 +180,8 @@
- - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - Accept - - - - -
- - - PasswordComboBox - QComboBox -
gui/PasswordComboBox.h
-
-
sliderLength spinBoxLength @@ -213,7 +191,6 @@ checkBoxSpecialChars checkBoxExcludeAlike checkBoxEnsureEvery - buttonApply diff --git a/src/http/OptionDialog.cpp b/src/http/OptionDialog.cpp index 5245d333b..a82e2c77e 100644 --- a/src/http/OptionDialog.cpp +++ b/src/http/OptionDialog.cpp @@ -15,15 +15,21 @@ #include "ui_OptionDialog.h" #include "HttpSettings.h" +#include "core/FilePath.h" + #include OptionDialog::OptionDialog(QWidget *parent) : QWidget(parent), - ui(new Ui::OptionDialog()) + m_ui(new Ui::OptionDialog()) { - ui->setupUi(this); - connect(ui->removeSharedEncryptionKeys, SIGNAL(clicked()), this, SIGNAL(removeSharedEncryptionKeys())); - connect(ui->removeStoredPermissions, SIGNAL(clicked()), this, SIGNAL(removeStoredPermissions())); + m_ui->setupUi(this); + connect(m_ui->removeSharedEncryptionKeys, SIGNAL(clicked()), this, SIGNAL(removeSharedEncryptionKeys())); + connect(m_ui->removeStoredPermissions, SIGNAL(clicked()), this, SIGNAL(removeStoredPermissions())); + + m_ui->warningWidget->showMessage(tr("The following options can be dangerous!\nChange them only if you know what you are doing."), MessageWidget::Warning); + m_ui->warningWidget->setIcon(FilePath::instance()->icon("status", "dialog-warning")); + m_ui->warningWidget->setCloseButtonVisible(false); } OptionDialog::~OptionDialog() @@ -33,65 +39,48 @@ OptionDialog::~OptionDialog() void OptionDialog::loadSettings() { HttpSettings settings; - ui->enableHttpServer->setChecked(settings.isEnabled()); + m_ui->enableHttpServer->setChecked(settings.isEnabled()); - ui->showNotification->setChecked(settings.showNotification()); - ui->bestMatchOnly->setChecked(settings.bestMatchOnly()); - ui->unlockDatabase->setChecked(settings.unlockDatabase()); - ui->matchUrlScheme->setChecked(settings.matchUrlScheme()); + m_ui->showNotification->setChecked(settings.showNotification()); + m_ui->bestMatchOnly->setChecked(settings.bestMatchOnly()); + m_ui->unlockDatabase->setChecked(settings.unlockDatabase()); + m_ui->matchUrlScheme->setChecked(settings.matchUrlScheme()); if (settings.sortByUsername()) - ui->sortByUsername->setChecked(true); + m_ui->sortByUsername->setChecked(true); else - ui->sortByTitle->setChecked(true); - ui->httpPort->setText(QString::number(settings.httpPort())); + m_ui->sortByTitle->setChecked(true); + m_ui->httpPort->setText(QString::number(settings.httpPort())); -/* - ui->checkBoxLower->setChecked(settings.passwordUseLowercase()); - ui->checkBoxNumbers->setChecked(settings.passwordUseNumbers()); - ui->checkBoxUpper->setChecked(settings.passwordUseUppercase()); - ui->checkBoxSpecialChars->setChecked(settings.passwordUseSpecial()); - ui->checkBoxEnsureEvery->setChecked(settings.passwordEveryGroup()); - ui->checkBoxExcludeAlike->setChecked(settings.passwordExcludeAlike()); - ui->spinBoxLength->setValue(settings.passwordLength()); -*/ + m_ui->alwaysAllowAccess->setChecked(settings.alwaysAllowAccess()); + m_ui->alwaysAllowUpdate->setChecked(settings.alwaysAllowUpdate()); + m_ui->searchInAllDatabases->setChecked(settings.searchInAllDatabases()); + m_ui->supportKphFields->setChecked(settings.supportKphFields()); - ui->alwaysAllowAccess->setChecked(settings.alwaysAllowAccess()); - ui->alwaysAllowUpdate->setChecked(settings.alwaysAllowUpdate()); - ui->searchInAllDatabases->setChecked(settings.searchInAllDatabases()); - ui->supportKphFields->setChecked(settings.supportKphFields()); + m_ui->passwordGenerator->loadSettings(); } void OptionDialog::saveSettings() { HttpSettings settings; - settings.setEnabled(ui->enableHttpServer->isChecked()); + settings.setEnabled(m_ui->enableHttpServer->isChecked()); - settings.setShowNotification(ui->showNotification->isChecked()); - settings.setBestMatchOnly(ui->bestMatchOnly->isChecked()); - settings.setUnlockDatabase(ui->unlockDatabase->isChecked()); - settings.setMatchUrlScheme(ui->matchUrlScheme->isChecked()); - settings.setSortByUsername(ui->sortByUsername->isChecked()); + settings.setShowNotification(m_ui->showNotification->isChecked()); + settings.setBestMatchOnly(m_ui->bestMatchOnly->isChecked()); + settings.setUnlockDatabase(m_ui->unlockDatabase->isChecked()); + settings.setMatchUrlScheme(m_ui->matchUrlScheme->isChecked()); + settings.setSortByUsername(m_ui->sortByUsername->isChecked()); - int port = ui->httpPort->text().toInt(); + int port = m_ui->httpPort->text().toInt(); if (port < 1024) { QMessageBox::warning(this, tr("Cannot bind to privileged ports"), tr("Cannot bind to privileged ports below 1024!\nUsing default port 19455.")); port = 19455; } settings.setHttpPort(port); + settings.setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked()); + settings.setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked()); + settings.setSearchInAllDatabases(m_ui->searchInAllDatabases->isChecked()); + settings.setSupportKphFields(m_ui->supportKphFields->isChecked()); -/* - settings.setPasswordUseLowercase(ui->checkBoxLower->isChecked()); - settings.setPasswordUseNumbers(ui->checkBoxNumbers->isChecked()); - settings.setPasswordUseUppercase(ui->checkBoxUpper->isChecked()); - settings.setPasswordUseSpecial(ui->checkBoxSpecialChars->isChecked()); - settings.setPasswordEveryGroup(ui->checkBoxEnsureEvery->isChecked()); - settings.setPasswordExcludeAlike(ui->checkBoxExcludeAlike->isChecked()); - settings.setPasswordLength(ui->spinBoxLength->value()); -*/ - - settings.setAlwaysAllowAccess(ui->alwaysAllowAccess->isChecked()); - settings.setAlwaysAllowUpdate(ui->alwaysAllowUpdate->isChecked()); - settings.setSearchInAllDatabases(ui->searchInAllDatabases->isChecked()); - settings.setSupportKphFields(ui->supportKphFields->isChecked()); + m_ui->passwordGenerator->saveSettings(); } diff --git a/src/http/OptionDialog.h b/src/http/OptionDialog.h index adec9f695..1b1819159 100644 --- a/src/http/OptionDialog.h +++ b/src/http/OptionDialog.h @@ -38,7 +38,7 @@ Q_SIGNALS: void removeStoredPermissions(); private: - QScopedPointer ui; + QScopedPointer m_ui; }; #endif // OPTIONDIALOG_H diff --git a/src/http/OptionDialog.ui b/src/http/OptionDialog.ui index 9bb168ee8..f3171733b 100644 --- a/src/http/OptionDialog.ui +++ b/src/http/OptionDialog.ui @@ -6,14 +6,26 @@ 0 0 - 605 - 429 + 577 + 404 Dialog + + 0 + + + 0 + + + 0 + + + 0 + @@ -37,13 +49,18 @@ This is required for accessing your databases from ChromeIPass or PassIFox Sh&ow a notification when credentials are requested + + true + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + - &Return only best matching entries for a URL instead -of all entries for the whole domain + &Return only best matching entries @@ -52,13 +69,18 @@ of all entries for the whole domain Re&quest to unlock the database if it is locked + + true +
+ + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned + &Match URL schemes @@ -107,7 +129,7 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< - Password generator + Password Generator @@ -132,20 +154,14 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Advanced - + - - - - 75 - true - - - - color: rgb(255, 0, 0); - - - The following options can be dangerous. Change them only if you know what you are doing. + + + + 0 + 0 + @@ -165,35 +181,21 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< + + Only the selected database has to be connected with a client. + Searc&h in all opened databases for matching entries - - - - Only the selected database has to be connected with a client! - - - 30 - - - - - &Return advanced string fields which start with "KPH: " - - - - - - + Automatically creating or updating string fields is not supported. - - 30 + + &Return advanced string fields which start with "KPH: " @@ -214,25 +216,8 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
- - - - - d0000 - - - Default port: 19455 - - - - - - - KeePassXC will listen to this port on 127.0.0.1 - - - - + + @@ -248,6 +233,23 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< + + + + d0000 + + + Default port: 19455 + + + + + + + KeePassXC will listen to this port on 127.0.0.1 + + + @@ -276,6 +278,12 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
http/HttpPasswordGeneratorWidget.h
1 + + MessageWidget + QWidget +
gui/MessageWidget.h
+ 1 +
From 08e50ac72bc6e5ee6a06fdef0f505e92f382ee63 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 18:02:44 +0100 Subject: [PATCH 080/333] Use correct icons for group edit --- src/gui/group/EditGroupWidget.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp index 23e2108b5..ac4e4ce99 100644 --- a/src/gui/group/EditGroupWidget.cpp +++ b/src/gui/group/EditGroupWidget.cpp @@ -34,9 +34,9 @@ EditGroupWidget::EditGroupWidget(QWidget* parent) { m_mainUi->setupUi(m_editGroupWidgetMain); - addPage(tr("Group"), FilePath::instance()->icon("apps", "keepassxc"), m_editGroupWidgetMain); - addPage(tr("Icon"), FilePath::instance()->icon("apps", "keepassxc"), m_editGroupWidgetIcons); - addPage(tr("Properties"), FilePath::instance()->icon("apps", "keepassxc"), m_editWidgetProperties); + addPage(tr("Group"), FilePath::instance()->icon("actions", "document-edit"), m_editGroupWidgetMain); + addPage(tr("Icon"), FilePath::instance()->icon("apps", "preferences-desktop-icons"), m_editGroupWidgetIcons); + addPage(tr("Properties"), FilePath::instance()->icon("actions", "document-properties"), m_editWidgetProperties); connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool))); connect(m_mainUi->autoTypeSequenceCustomRadio, SIGNAL(toggled(bool)), From 852d194b39776943b3b2eb701de280a1baa1e6bc Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 18:29:15 +0100 Subject: [PATCH 081/333] Adjust message widget color to look less dirty --- src/gui/KMessageWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/KMessageWidget.cpp b/src/gui/KMessageWidget.cpp index f2c48c253..e2c22574c 100644 --- a/src/gui/KMessageWidget.cpp +++ b/src/gui/KMessageWidget.cpp @@ -277,7 +277,7 @@ void KMessageWidget::setMessageType(KMessageWidget::MessageType type) bg1 = palette().highlight().color(); break; case Warning: - bg1.setRgb(176, 128, 0); // values taken from kcolorscheme.cpp (Neutral) + bg1.setRgb(181, 102, 0); // values taken from kcolorscheme.cpp (Neutral) break; case Error: bg1.setRgb(191, 3, 3); // values taken from kcolorscheme.cpp (Negative) From fcadee550ef026fdc6a2c5a6fa307802f87408b4 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 18:36:30 +0100 Subject: [PATCH 082/333] Fix layout of group edit widget --- src/gui/group/EditGroupWidgetMain.ui | 223 +++++++++++++++------------ 1 file changed, 127 insertions(+), 96 deletions(-) diff --git a/src/gui/group/EditGroupWidgetMain.ui b/src/gui/group/EditGroupWidgetMain.ui index b8abf762c..0e1b08a44 100644 --- a/src/gui/group/EditGroupWidgetMain.ui +++ b/src/gui/group/EditGroupWidgetMain.ui @@ -6,116 +6,147 @@ 0 0 - 676 - 356 + 579 + 407 - - - - - QFormLayout::ExpandingFieldsGrow + + + 10 + + + 0 + + + 0 + + + 0 + + + + + Name - - - - Name + + + + + + + + + Notes + + + + + + + + 0 + 0 + + + + + 16777215 + 120 + + + + + + + + Expires + + + + + + + + + + false + + + true + + + + + + + Search + + + + + + + + + + Auto-Type + + + + + + + &Use default auto-type sequence of parent group + + + + + + + Set default Auto-Type se&quence + + + + + + + + + Qt::Horizontal - - - - - - - - - Notes + + QSizePolicy::Fixed - - - - - - - - - Expires + + + 13 + 1 + - + - - + + false - - true - - - - - Search - - - - - - - - - - Auto-type - - - - - - - - - - Use default auto-type sequence of parent group - - - - - - - Set default auto-type sequence - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 1 - - - - - - - - false - - - - - + + + + Qt::Vertical + + + + 20 + 40 + + + + From 79ab7a1062d93f1cb7d454d50cd48692fe330d82 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 19:10:13 +0100 Subject: [PATCH 083/333] Use better default row width for history view and enable alternating row colors --- src/gui/entry/EditEntryWidgetHistory.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/entry/EditEntryWidgetHistory.ui b/src/gui/entry/EditEntryWidgetHistory.ui index 2ee8e08f3..8390f22fa 100644 --- a/src/gui/entry/EditEntryWidgetHistory.ui +++ b/src/gui/entry/EditEntryWidgetHistory.ui @@ -25,9 +25,15 @@ + + true + true + + 160 + From a58700c78f099a889c63b635b071f517bdf65e87 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 22 Feb 2017 23:23:14 +0100 Subject: [PATCH 084/333] Fix selection background on Windows --- src/gui/CategoryListWidget.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/gui/CategoryListWidget.cpp b/src/gui/CategoryListWidget.cpp index ddafd6949..c93ac46e7 100644 --- a/src/gui/CategoryListWidget.cpp +++ b/src/gui/CategoryListWidget.cpp @@ -159,6 +159,33 @@ CategoryListWidgetDelegate::CategoryListWidgetDelegate(QListWidget* parent) } } +#ifdef Q_OS_WIN +#include +class WindowsCorrectedStyle : public QProxyStyle +{ +public: + void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override + { + painter->save(); + + if (PE_PanelItemViewItem == element) { + // Qt on Windows draws selection backgrounds only for the actual text/icon + // bounding box, not over the full width of a list item. + // We therefore need to translate and stretch the painter before we can + // tell Qt to draw its native styles. + // Since we are scaling horizontally, we also need to move the right and left + // edge pixels outside the drawing area to avoid thick border lines. + QRect itemRect = subElementRect(QStyle::SE_ItemViewItemFocusRect, option, widget).adjusted(1, 0, 1, 0); + painter->scale(static_cast(option->rect.width()) / itemRect.width(), 1.0); + painter->translate(option->rect.left() - itemRect.left() + 1, 0); + } + QProxyStyle::drawPrimitive(element, option, painter, widget); + + painter->restore(); + } +}; +#endif + void CategoryListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; @@ -172,7 +199,12 @@ void CategoryListWidgetDelegate::paint(QPainter* painter, const QStyleOptionView opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignVCenter; opt.decorationPosition = QStyleOptionViewItem::Top; +#ifdef Q_OS_WIN + QScopedPointer style(new WindowsCorrectedStyle()); +#else QStyle* style = opt.widget ? opt.widget->style() : QApplication::style(); +#endif + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); QRect fontRect = painter->fontMetrics().boundingRect( From 1d79d342c0af7436255d56e845def02ec8854ba0 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 23 Feb 2017 00:45:57 +0100 Subject: [PATCH 085/333] Rework About dialog --- src/gui/AboutDialog.cpp | 50 +++++++--- src/gui/AboutDialog.h | 3 + src/gui/AboutDialog.ui | 198 ++++++++++++++++++++++++++-------------- 3 files changed, 169 insertions(+), 82 deletions(-) diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index a4f180b05..1f4cda9a5 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -23,15 +23,17 @@ #include "core/FilePath.h" #include "crypto/Crypto.h" +#include +#include + AboutDialog::AboutDialog(QWidget* parent) - : QDialog(parent) - , m_ui(new Ui::AboutDialog()) + : QDialog(parent), + m_ui(new Ui::AboutDialog()) { m_ui->setupUi(this); m_ui->nameLabel->setText(m_ui->nameLabel->text() + " " + KEEPASSX_VERSION); QFont nameLabelFont = m_ui->nameLabel->font(); - nameLabelFont.setBold(true); nameLabelFont.setPointSize(nameLabelFont.pointSize() + 4); m_ui->nameLabel->setFont(nameLabelFont); @@ -45,37 +47,55 @@ AboutDialog::AboutDialog(QWidget* parent) commitHash = DIST_HASH; } + QString debugInfo = "KeePassXC - "; + debugInfo.append(tr("Version %1\n").arg(KEEPASSX_VERSION)); if (!commitHash.isEmpty()) { - QString labelText = tr("Revision").append(": ").append(commitHash); - m_ui->label_git->setText(labelText); + debugInfo.append(tr("Revision: %1").arg(commitHash).append("\n\n")); } - QString libs = QString("%1\n- Qt %2\n- %3") - .arg(m_ui->label_libs->text()) - .arg(QString::fromLocal8Bit(qVersion())) - .arg(Crypto::backendVersion()); - m_ui->label_libs->setText(libs); + debugInfo.append(QString("%1\n- Qt %2\n- %3\n\n") + .arg(tr("Libraries:")) + .arg(QString::fromLocal8Bit(qVersion())) + .arg(Crypto::backendVersion())); + + debugInfo.append(tr("Operating system: %1 (version: %2)\nCPU architecture: %3\nKernel: %4 %5") + .arg(QSysInfo::prettyProductName()) + .arg(QSysInfo::productVersion()) + .arg(QSysInfo::currentCpuArchitecture()) + .arg(QSysInfo::kernelType()) + .arg(QSysInfo::kernelVersion())); + + debugInfo.append("\n\n"); QString extensions; #ifdef WITH_XC_HTTP - extensions += "- KeePassHTTP\n"; + extensions += "\n- KeePassHTTP"; #endif #ifdef WITH_XC_AUTOTYPE - extensions += "- Autotype\n"; + extensions += "\n- Auto-Type"; #endif #ifdef WITH_XC_YUBIKEY - extensions += "- Yubikey\n"; + extensions += "\n- YubiKey"; #endif if (extensions.isEmpty()) - extensions = "None"; + extensions = " None"; - m_ui->label_features->setText(m_ui->label_features->text() + extensions); + debugInfo.append(tr("Enabled extensions:").append(extensions)); + + m_ui->debugInfo->setPlainText(debugInfo); setAttribute(Qt::WA_DeleteOnClose); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close())); + connect(m_ui->copyToClipboard, SIGNAL(clicked()), SLOT(copyToClipboard())); } AboutDialog::~AboutDialog() { } + +void AboutDialog::copyToClipboard() +{ + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(m_ui->debugInfo->toPlainText()); +} diff --git a/src/gui/AboutDialog.h b/src/gui/AboutDialog.h index 08db6c887..b69a14dbb 100644 --- a/src/gui/AboutDialog.h +++ b/src/gui/AboutDialog.h @@ -33,6 +33,9 @@ public: explicit AboutDialog(QWidget* parent = nullptr); ~AboutDialog(); +protected slots: + void copyToClipboard(); + private: QScopedPointer m_ui; }; diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index 28f4dd0c4..baabb52e3 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -6,20 +6,23 @@ 0 0 - 455 - 266 + 479 + 488 About KeePassXC - - - QLayout::SetFixedSize - + - + + 15 + + + 20 + + @@ -27,9 +30,21 @@ 0 + + + 48 + 48 + + + + + 48 + 48 + + - + @@ -37,6 +52,12 @@ 0 + + + 75 + true + + KeePassXC @@ -45,66 +66,109 @@ - - - - 0 - 0 - - - - <a href="https://keepassxc.org/">https://keepassxc.org/</a> - - - true - - - - - - - - 0 - 0 - - - - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - - - true - - - - - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Using: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - Extensions: - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + 0 + + + About + + + + + + <html><head/><body><p>Website: <a href="https://keepassxc.org/"><span style=" text-decoration: underline; color:#007af4;">https://keepassxc.org/</span></a></p></body> +Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues">https://github.com/</a></html> + + + true + + + + + + + KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 10 + + + + + + + + <html><head/><body><div>Main contributors: +<ul> +<li>debfx (KeePassX)</li> +<li>droidmonkey</li> +<li>louib</li> +<li>phoerious<li> +<li>thezero</li> +</div></body></html> + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + Debug Info + + + + + + Include the following information whenever you report a bug: + + + + + + + true + + + + + + + Copy to clipboard + + + + + From 1cef08e92b43dcbc2b1726ba7e033fadebb992d0 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 23 Feb 2017 01:08:26 +0100 Subject: [PATCH 086/333] Only query operating system stats on Qt >= 5.4 --- src/gui/AboutDialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index 1f4cda9a5..5c459c8d4 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -58,6 +58,7 @@ AboutDialog::AboutDialog(QWidget* parent) .arg(QString::fromLocal8Bit(qVersion())) .arg(Crypto::backendVersion())); +#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) debugInfo.append(tr("Operating system: %1 (version: %2)\nCPU architecture: %3\nKernel: %4 %5") .arg(QSysInfo::prettyProductName()) .arg(QSysInfo::productVersion()) @@ -66,6 +67,7 @@ AboutDialog::AboutDialog(QWidget* parent) .arg(QSysInfo::kernelVersion())); debugInfo.append("\n\n"); +#endif QString extensions; #ifdef WITH_XC_HTTP From 8a75acb985481a78a316976ae0077cfeffc58479 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 23 Feb 2017 01:16:56 +0100 Subject: [PATCH 087/333] Remove productVersion() which is included in prettyProductName() --- src/gui/AboutDialog.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index 5c459c8d4..ebb18d2a8 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -59,9 +59,8 @@ AboutDialog::AboutDialog(QWidget* parent) .arg(Crypto::backendVersion())); #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) - debugInfo.append(tr("Operating system: %1 (version: %2)\nCPU architecture: %3\nKernel: %4 %5") + debugInfo.append(tr("Operating system: %1\nCPU architecture: %2\nKernel: %3 %4") .arg(QSysInfo::prettyProductName()) - .arg(QSysInfo::productVersion()) .arg(QSysInfo::currentCpuArchitecture()) .arg(QSysInfo::kernelType()) .arg(QSysInfo::kernelVersion())); From 0f8c12aaf4108263ae91947983dcc750d1de6620 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 23 Feb 2017 01:58:29 +0100 Subject: [PATCH 088/333] Fix Qt Designer HTML lapses --- src/gui/AboutDialog.ui | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index baabb52e3..4eb9ee56b 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -78,8 +78,8 @@ - <html><head/><body><p>Website: <a href="https://keepassxc.org/"><span style=" text-decoration: underline; color:#007af4;">https://keepassxc.org/</span></a></p></body> -Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues">https://github.com/</a></html> + <p>Website: <a href="https://keepassxc.org/">https://keepassxc.org/</a></p> +<p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues">https://github.com/</a></p> true @@ -115,14 +115,15 @@ Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/iss - <html><head/><body><div>Main contributors: + <div>Main contributors: <ul> <li>debfx (KeePassX)</li> <li>droidmonkey</li> <li>louib</li> <li>phoerious<li> <li>thezero</li> -</div></body></html> +</ul> +</div> From 5e664e3a06cf99ce7180abe5655fecd40f6cdcba Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 23 Feb 2017 02:11:48 +0100 Subject: [PATCH 089/333] Use consistent terminology --- src/gui/group/EditGroupWidgetMain.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/group/EditGroupWidgetMain.ui b/src/gui/group/EditGroupWidgetMain.ui index 0e1b08a44..20ce2f414 100644 --- a/src/gui/group/EditGroupWidgetMain.ui +++ b/src/gui/group/EditGroupWidgetMain.ui @@ -96,7 +96,7 @@ - &Use default auto-type sequence of parent group + &Use default Auto-Type sequence of parent group From 093fe5c7ef9a513f18b7f258ffcf6711e7346ba8 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 23 Feb 2017 23:52:36 +0100 Subject: [PATCH 090/333] Use QSharedPointer instead of cloning YkChallengeResponseKey and make it a QObject to allow emitting signals --- src/core/Database.cpp | 6 ++-- src/format/KeePass2Reader.cpp | 2 +- src/gui/ChangeMasterKeyWidget.cpp | 13 +++---- src/gui/DatabaseOpenWidget.cpp | 16 +++++---- src/keys/ChallengeResponseKey.h | 1 - src/keys/CompositeKey.cpp | 21 ++++++----- src/keys/CompositeKey.h | 5 +-- src/keys/YkChallengeResponseKey.cpp | 56 ++++++++++++++--------------- src/keys/YkChallengeResponseKey.h | 24 ++++++++++--- 9 files changed, 78 insertions(+), 66 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 22fc07230..a441910d1 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -257,18 +257,16 @@ bool Database::verifyKey(const CompositeKey& key) const { Q_ASSERT(hasKey()); - /* If the database has challenge response keys, then the the verification - * key better as well */ if (!m_data.challengeResponseKey.isEmpty()) { QByteArray result; if (!key.challenge(m_data.masterSeed, result)) { - /* Challenge failed, (YubiKey?) removed? */ + // challenge failed, (YubiKey?) removed? return false; } if (m_data.challengeResponseKey != result) { - /* Wrong response from challenged device(s) */ + // wrong response from challenged device(s) return false; } } diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 33bea620c..ffe4e94fc 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -115,7 +115,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke if (m_db->challengeMasterSeed(m_masterSeed) == false) { raiseError(tr("Unable to issue challenge-response.")); - return Q_NULLPTR; + return nullptr; } CryptoHash hash(CryptoHash::Sha256); diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 8c17ff575..453498a17 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -15,8 +15,6 @@ * along with this program. If not, see . */ -#include - #include "ChangeMasterKeyWidget.h" #include "ui_ChangeMasterKeyWidget.h" @@ -30,6 +28,9 @@ #include "config-keepassx.h" +#include +#include + ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent) : DialogyWidget(parent) , m_ui(new Ui::ChangeMasterKeyWidget()) @@ -172,9 +173,9 @@ void ChangeMasterKeyWidget::generateKey() MessageWidget::Error); return; } - - YkChallengeResponseKey key(i); - + bool blocking = i & true; + int slot = i >> 1; + auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); m_key.addChallengeResponseKey(key); } #endif @@ -210,7 +211,7 @@ void ChangeMasterKeyWidget::pollYubikey() void ChangeMasterKeyWidget::yubikeyDetected(int slot, bool blocking) { YkChallengeResponseKey yk(slot, blocking); - m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant(slot)); + m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant((slot << 1) | blocking)); m_ui->comboChallengeResponse->setEnabled(m_ui->challengeResponseGroup->isChecked()); m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); m_ui->yubikeyProgress->setVisible(false); diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 1543dc43c..2eebcae3c 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -15,8 +15,6 @@ * along with this program. If not, see . */ -#include - #include "DatabaseOpenWidget.h" #include "ui_DatabaseOpenWidget.h" @@ -34,6 +32,9 @@ #include "config-keepassx.h" +#include +#include + DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) : DialogyWidget(parent), @@ -156,7 +157,7 @@ void DatabaseOpenWidget::openDatabase() if (m_ui->messageWidget->isVisible()) { m_ui->messageWidget->animatedHide(); } - Q_EMIT editFinished(true); + emit editFinished(true); } else { m_ui->messageWidget->showMessage(tr("Unable to open the database.") @@ -209,8 +210,10 @@ CompositeKey DatabaseOpenWidget::databaseKey() if (m_ui->checkChallengeResponse->isChecked()) { int i = m_ui->comboChallengeResponse->currentIndex(); i = m_ui->comboChallengeResponse->itemData(i).toInt(); - YkChallengeResponseKey key(i); + bool blocking = i & true; + int slot = i >> 1; + auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); masterKey.addChallengeResponseKey(key); } #endif @@ -250,20 +253,21 @@ void DatabaseOpenWidget::browseKeyFile() void DatabaseOpenWidget::pollYubikey() { - // YubiKey init is slow, detect asynchronously to not block the UI m_ui->buttonRedetectYubikey->setEnabled(false); m_ui->checkChallengeResponse->setEnabled(false); m_ui->checkChallengeResponse->setChecked(false); m_ui->comboChallengeResponse->setEnabled(false); m_ui->comboChallengeResponse->clear(); m_ui->yubikeyProgress->setVisible(true); + + // YubiKey init is slow, detect asynchronously to not block the UI QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); } void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking) { YkChallengeResponseKey yk(slot, blocking); - m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant(slot)); + m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant((slot << 1) | blocking)); m_ui->comboChallengeResponse->setEnabled(true); m_ui->checkChallengeResponse->setEnabled(true); m_ui->buttonRedetectYubikey->setEnabled(true); diff --git a/src/keys/ChallengeResponseKey.h b/src/keys/ChallengeResponseKey.h index e03a2f9f9..ac8c81650 100644 --- a/src/keys/ChallengeResponseKey.h +++ b/src/keys/ChallengeResponseKey.h @@ -25,7 +25,6 @@ class ChallengeResponseKey public: virtual ~ChallengeResponseKey() {} virtual QByteArray rawKey() const = 0; - virtual ChallengeResponseKey* clone() const = 0; virtual bool challenge(const QByteArray& challenge) = 0; }; diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 4e79cd05c..6114fd366 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -46,7 +46,6 @@ CompositeKey::~CompositeKey() void CompositeKey::clear() { qDeleteAll(m_keys); - qDeleteAll(m_challengeResponseKeys); m_keys.clear(); m_challengeResponseKeys.clear(); } @@ -73,8 +72,8 @@ CompositeKey& CompositeKey::operator=(const CompositeKey& key) for (const Key* subKey : asConst(key.m_keys)) { addKey(*subKey); } - for (const ChallengeResponseKey* subKey : asConst(key.m_challengeResponseKeys)) { - addChallengeResponseKey(*subKey); + for (const auto subKey : asConst(key.m_challengeResponseKeys)) { + addChallengeResponseKey(subKey); } return *this; @@ -176,9 +175,8 @@ QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const { - /* If no challenge response was requested, return nothing to - * maintain backwards compatability with regular databases. - */ + // if no challenge response was requested, return nothing to + // maintain backwards compatibility with regular databases. if (m_challengeResponseKeys.length() == 0) { result.clear(); return true; @@ -186,9 +184,9 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const CryptoHash cryptoHash(CryptoHash::Sha256); - for (ChallengeResponseKey* key : m_challengeResponseKeys) { - /* If the device isn't present or fails, return an error */ - if (key->challenge(seed) == false) { + for (const auto key : m_challengeResponseKeys) { + // if the device isn't present or fails, return an error + if (!key->challenge(seed)) { return false; } cryptoHash.addData(key->rawKey()); @@ -203,11 +201,12 @@ void CompositeKey::addKey(const Key& key) m_keys.append(key.clone()); } -void CompositeKey::addChallengeResponseKey(const ChallengeResponseKey& key) +void CompositeKey::addChallengeResponseKey(QSharedPointer key) { - m_challengeResponseKeys.append(key.clone()); + m_challengeResponseKeys.append(key); } + int CompositeKey::transformKeyBenchmark(int msec) { TransformKeyBenchmarkThread thread1(msec); diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index 531c2d9b2..50b2f699a 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -20,6 +20,7 @@ #include #include +#include #include "keys/Key.h" #include "keys/ChallengeResponseKey.h" @@ -41,7 +42,7 @@ public: bool challenge(const QByteArray& seed, QByteArray &result) const; void addKey(const Key& key); - void addChallengeResponseKey(const ChallengeResponseKey& key); + void addChallengeResponseKey(QSharedPointer key); static int transformKeyBenchmark(int msec); static CompositeKey readFromLine(QString line); @@ -51,7 +52,7 @@ private: quint64 rounds, bool* ok, QString* errorString); QList m_keys; - QList m_challengeResponseKeys; + QList> m_challengeResponseKeys; }; #endif // KEEPASSX_COMPOSITEKEY_H diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index 51520e3d4..a6e5cdbcf 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -15,7 +15,6 @@ * along with this program. If not, see . */ - #include #include @@ -26,11 +25,7 @@ #include "keys/YkChallengeResponseKey.h" #include "keys/drivers/YubiKey.h" -#include -#include - -YkChallengeResponseKey::YkChallengeResponseKey(int slot, - bool blocking) +YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) : m_slot(slot), m_blocking(blocking) { @@ -41,40 +36,41 @@ QByteArray YkChallengeResponseKey::rawKey() const return m_key; } -YkChallengeResponseKey* YkChallengeResponseKey::clone() const -{ - return new YkChallengeResponseKey(*this); -} - - -/** Assumes yubikey()->init() was called */ +/** + * Assumes yubikey()->init() was called + */ bool YkChallengeResponseKey::challenge(const QByteArray& chal) { return challenge(chal, 1); } - -bool YkChallengeResponseKey::challenge(const QByteArray& chal, int retries) +#include +bool YkChallengeResponseKey::challenge(const QByteArray& chal, unsigned retries) { - if (YubiKey::instance()->challenge(m_slot, true, chal, m_key) != YubiKey::ERROR) { - return true; - } + Q_ASSERT(retries > 0); - /* If challenge failed, retry to detect YubiKeys in the event the YubiKey - * was un-plugged and re-plugged */ - while (retries > 0) { -#ifdef QT_DEBUG - qDebug() << "Attempt" << retries << "to re-detect YubiKey(s)"; -#endif - retries--; + do { + --retries; - if (YubiKey::instance()->init() != true) { + if (m_blocking) { + emit userInteractionRequired(); + } + + auto result = YubiKey::instance()->challenge(m_slot, true, chal, m_key); + + if (m_blocking) { + emit userConfirmed(); + } + + if (result != YubiKey::ERROR) { + return true; + } + + // if challenge failed, retry to detect YubiKeys in the event the YubiKey was un-plugged and re-plugged + if (retries > 0 && YubiKey::instance()->init() != true) { continue; } - if (YubiKey::instance()->challenge(m_slot, true, chal, m_key) != YubiKey::ERROR) { - return true; - } - } + } while (retries > 0); return false; } diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h index 8acb0f9e9..96e44220d 100644 --- a/src/keys/YkChallengeResponseKey.h +++ b/src/keys/YkChallengeResponseKey.h @@ -22,20 +22,34 @@ #include "keys/ChallengeResponseKey.h" #include "keys/drivers/YubiKey.h" -class YkChallengeResponseKey : public ChallengeResponseKey +#include + +class YkChallengeResponseKey : public QObject, public ChallengeResponseKey { + Q_OBJECT + public: - YkChallengeResponseKey(int slot = -1, - bool blocking = false); + YkChallengeResponseKey(int slot = -1, bool blocking = false); QByteArray rawKey() const; - YkChallengeResponseKey* clone() const; bool challenge(const QByteArray& chal); - bool challenge(const QByteArray& chal, int retries); + bool challenge(const QByteArray& chal, unsigned retries); QString getName() const; bool isBlocking() const; +signals: + /** + * Emitted whenever user interaction is required to proceed with the challenge-response protocol. + * You can use this to show a helpful dialog informing the user that his assistance is required. + */ + void userInteractionRequired(); + + /** + * Emitted when the user has provided their required input. + */ + void userConfirmed(); + private: QByteArray m_key; int m_slot; From 70816f90b2c4488c9636ca60124eb897c7da6371 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 00:15:57 +0100 Subject: [PATCH 091/333] Make challenge() member thread-safe --- src/keys/drivers/YubiKey.cpp | 27 ++++++++++++++++----------- src/keys/drivers/YubiKey.h | 12 ++++++++++-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index b287c13b2..1d4445cd6 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -107,8 +107,10 @@ void YubiKey::detect() QByteArray resp; result = challenge(i, false, rand, resp); - - if (result != YubiKey::ERROR) { + if (result == YubiKey::ALREADY_RUNNING) { + emit alreadyRunning(); + return; + } else if (result != YubiKey::ERROR) { emit detected(i, result == YubiKey::WOULDBLOCK ? true : false); return; } @@ -141,13 +143,18 @@ static inline QString printByteArray(const QByteArray& a) } #endif -YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& chal, QByteArray& resp) const +YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& chal, QByteArray& resp) { + if (!m_mutex.tryLock()) { + return ALREADY_RUNNING; + } + int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2; QByteArray paddedChal = chal; - /* Ensure that YubiKey::init() succeeded */ + // ensure that YubiKey::init() succeeded if (m_yk == NULL) { + m_mutex.unlock(); return ERROR; } @@ -171,13 +178,12 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte r = reinterpret_cast(resp.data()); #ifdef QT_DEBUG - qDebug().nospace() << __func__ << "(" << slot << ") c = " - << printByteArray(paddedChal); + qDebug().nospace() << __func__ << "(" << slot << ") c = " << printByteArray(paddedChal); #endif - int ret = yk_challenge_response(m_yk, yk_cmd, mayBlock, - paddedChal.size(), c, - resp.size(), r); + int ret = yk_challenge_response(m_yk, yk_cmd, mayBlock, paddedChal.size(), c, resp.size(), r); + + m_mutex.unlock(); if (!ret) { if (yk_errno == YK_EWOULDBLOCK) { @@ -206,8 +212,7 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte resp.resize(20); #ifdef QT_DEBUG - qDebug().nospace() << __func__ << "(" << slot << ") r = " - << printByteArray(resp) << ", ret = " << ret; + qDebug().nospace() << __func__ << "(" << slot << ") r = " << printByteArray(resp) << ", ret = " << ret; #endif return SUCCESS; diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h index acf4feb72..6c3504ed1 100644 --- a/src/keys/drivers/YubiKey.h +++ b/src/keys/drivers/YubiKey.h @@ -19,6 +19,7 @@ #define KEEPASSX_YUBIKEY_H #include +#include /** * Singleton class to manage the interface to the hardware @@ -28,7 +29,7 @@ class YubiKey : public QObject Q_OBJECT public: - enum ChallengeResult { ERROR = -1, SUCCESS = 0, WOULDBLOCK }; + enum ChallengeResult { ERROR = -1, SUCCESS = 0, WOULDBLOCK, ALREADY_RUNNING }; /** * @brief YubiKey::instance - get instance of singleton @@ -64,7 +65,7 @@ public: */ ChallengeResult challenge(int slot, bool mayBlock, const QByteArray& chal, - QByteArray& resp) const; + QByteArray& resp); /** * @brief YubiKey::getSerial - serial number of YubiKey @@ -92,6 +93,11 @@ Q_SIGNALS: */ void notFound(); + /** + * Emitted when detection is already running. + */ + void alreadyRunning(); + private: explicit YubiKey(); static YubiKey* m_instance; @@ -100,6 +106,8 @@ private: void* m_yk_void; void* m_ykds_void; + QMutex m_mutex; + Q_DISABLE_COPY(YubiKey) }; From 44ac7d152b4024c2355cb83c09ce3bf41491f0bf Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 01:06:09 +0100 Subject: [PATCH 092/333] Use better variable names --- src/keys/drivers/YubiKey.cpp | 24 ++++++++++++------------ src/keys/drivers/YubiKey.h | 8 +++----- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index 1d4445cd6..e68949394 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -111,7 +111,7 @@ void YubiKey::detect() emit alreadyRunning(); return; } else if (result != YubiKey::ERROR) { - emit detected(i, result == YubiKey::WOULDBLOCK ? true : false); + emit detected(i, result == YubiKey::WOULDBLOCK); return; } } @@ -143,14 +143,14 @@ static inline QString printByteArray(const QByteArray& a) } #endif -YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& chal, QByteArray& resp) +YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response) { if (!m_mutex.tryLock()) { return ALREADY_RUNNING; } int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2; - QByteArray paddedChal = chal; + QByteArray paddedChallenge = challenge; // ensure that YubiKey::init() succeeded if (m_yk == NULL) { @@ -159,7 +159,7 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte } // yk_challenge_response() insists on 64 byte response buffer */ - resp.resize(64); + response.resize(64); /* The challenge sent to the yubikey should always be 64 bytes for * compatibility with all configurations. Follow PKCS7 padding. @@ -167,21 +167,21 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte * There is some question whether or not 64 byte fixed length * configurations even work, some docs say avoid it. */ - const int padLen = 64 - paddedChal.size(); + const int padLen = 64 - paddedChallenge.size(); if (padLen > 0) { - paddedChal.append(QByteArray(padLen, padLen)); + paddedChallenge.append(QByteArray(padLen, padLen)); } const unsigned char *c; unsigned char *r; - c = reinterpret_cast(paddedChal.constData()); - r = reinterpret_cast(resp.data()); + c = reinterpret_cast(paddedChallenge.constData()); + r = reinterpret_cast(response.data()); #ifdef QT_DEBUG - qDebug().nospace() << __func__ << "(" << slot << ") c = " << printByteArray(paddedChal); + qDebug().nospace() << __func__ << "(" << slot << ") c = " << printByteArray(paddedChallenge); #endif - int ret = yk_challenge_response(m_yk, yk_cmd, mayBlock, paddedChal.size(), c, resp.size(), r); + int ret = yk_challenge_response(m_yk, yk_cmd, mayBlock, paddedChallenge.size(), c, response.size(), r); m_mutex.unlock(); @@ -209,10 +209,10 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte } // actual HMAC-SHA1 response is only 20 bytes - resp.resize(20); + response.resize(20); #ifdef QT_DEBUG - qDebug().nospace() << __func__ << "(" << slot << ") r = " << printByteArray(resp) << ", ret = " << ret; + qDebug().nospace() << __func__ << "(" << slot << ") r = " << printByteArray(response) << ", ret = " << ret; #endif return SUCCESS; diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h index 6c3504ed1..2382b69cf 100644 --- a/src/keys/drivers/YubiKey.h +++ b/src/keys/drivers/YubiKey.h @@ -59,13 +59,11 @@ public: * * @param slot YubiKey configuration slot * @param mayBlock operation is allowed to block - * @param chal challenge input to YubiKey - * @param resp response output from YubiKey + * @param challenge challenge input to YubiKey + * @param response response output from YubiKey * @return true on success */ - ChallengeResult challenge(int slot, bool mayBlock, - const QByteArray& chal, - QByteArray& resp); + ChallengeResult challenge(int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response); /** * @brief YubiKey::getSerial - serial number of YubiKey From d6c48a5cf1144e6d67a76fdf95708d2443451a36 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 03:25:08 +0100 Subject: [PATCH 093/333] Show message when user needs to touch their YubiKey (still buggy when using multiple databases) --- src/gui/Application.cpp | 5 +++++ src/gui/Application.h | 1 + src/gui/ChangeMasterKeyWidget.cpp | 15 +++++++++++++ src/gui/ChangeMasterKeyWidget.h | 2 ++ src/gui/DatabaseOpenWidget.cpp | 14 ++++++++++++ src/gui/DatabaseOpenWidget.h | 2 ++ src/gui/MainWindow.h | 9 +++++--- src/keys/YkChallengeResponseKey.cpp | 33 +++++++++++++++++++---------- src/keys/YkChallengeResponseKey.h | 4 ++-- src/keys/drivers/YubiKey.cpp | 1 + src/keys/drivers/YubiKey.h | 5 +++++ 11 files changed, 75 insertions(+), 16 deletions(-) diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 26d9d2283..4c84fda86 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -87,6 +87,11 @@ Application::Application(int& argc, char** argv) #endif } +QWidget* Application::mainWindow() const +{ + return m_mainWindow; +} + void Application::setMainWindow(QWidget* mainWindow) { m_mainWindow = mainWindow; diff --git a/src/gui/Application.h b/src/gui/Application.h index 9bfe4d549..c6f4aae66 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -29,6 +29,7 @@ class Application : public QApplication public: Application(int& argc, char** argv); + QWidget* mainWindow() const; void setMainWindow(QWidget* mainWindow); bool event(QEvent* event) override; diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 453498a17..b223d3922 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -25,6 +25,7 @@ #include "gui/FileDialog.h" #include "gui/MessageBox.h" #include "crypto/Random.h" +#include "MainWindow.h" #include "config-keepassx.h" @@ -176,6 +177,8 @@ void ChangeMasterKeyWidget::generateKey() bool blocking = i & true; int slot = i >> 1; auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); + connect(key.data(), SIGNAL(userInteractionRequired()), SLOT(showYubiKeyPopup())); + connect(key.data(), SIGNAL(userConfirmed()), SLOT(hideYubiKeyPopup())); m_key.addChallengeResponseKey(key); } #endif @@ -238,3 +241,15 @@ void ChangeMasterKeyWidget::setCancelEnabled(bool enabled) { m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(enabled); } + +void ChangeMasterKeyWidget::showYubiKeyPopup() +{ + KEEPASSXC_MAIN_WINDOW->displayGlobalMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information); + KEEPASSXC_MAIN_WINDOW->setEnabled(false); +} + +void ChangeMasterKeyWidget::hideYubiKeyPopup() +{ + KEEPASSXC_MAIN_WINDOW->hideGlobalMessage(); + KEEPASSXC_MAIN_WINDOW->setEnabled(true); +} diff --git a/src/gui/ChangeMasterKeyWidget.h b/src/gui/ChangeMasterKeyWidget.h index b3e097276..fdc7f9529 100644 --- a/src/gui/ChangeMasterKeyWidget.h +++ b/src/gui/ChangeMasterKeyWidget.h @@ -55,6 +55,8 @@ private slots: void noYubikeyFound(); void challengeResponseGroupToggled(bool checked); void pollYubikey(); + void showYubiKeyPopup(); + void hideYubiKeyPopup(); private: const QScopedPointer m_ui; diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 2eebcae3c..03a560c2f 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -214,6 +214,8 @@ CompositeKey DatabaseOpenWidget::databaseKey() bool blocking = i & true; int slot = i >> 1; auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); + connect(key.data(), SIGNAL(userInteractionRequired()), SLOT(showYubiKeyPopup())); + connect(key.data(), SIGNAL(userConfirmed()), SLOT(hideYubiKeyPopup())); masterKey.addChallengeResponseKey(key); } #endif @@ -264,6 +266,18 @@ void DatabaseOpenWidget::pollYubikey() QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); } +void DatabaseOpenWidget::showYubiKeyPopup() +{ + m_ui->messageWidget->showMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information); + KEEPASSXC_MAIN_WINDOW->setEnabled(false); +} + +void DatabaseOpenWidget::hideYubiKeyPopup() +{ + m_ui->messageWidget->hideMessage(); + KEEPASSXC_MAIN_WINDOW->setEnabled(true); +} + void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking) { YkChallengeResponseKey yk(slot, blocking); diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index caba70a61..eee705a79 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -53,6 +53,8 @@ protected: protected slots: virtual void openDatabase(); + void showYubiKeyPopup(); + void hideYubiKeyPopup(); void reject(); private slots: diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 694b38e7a..c3262a3cc 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -24,6 +24,7 @@ #include "core/SignalMultiplexer.h" #include "gui/DatabaseWidget.h" +#include "gui/Application.h" namespace Ui { class MainWindow; @@ -43,6 +44,9 @@ public Q_SLOTS: void openDatabase(const QString& fileName, const QString& pw = QString(), const QString& keyFile = QString()); void appExit(); + void displayGlobalMessage(const QString& text, MessageWidget::MessageType type); + void displayTabMessage(const QString& text, MessageWidget::MessageType type); + void hideGlobalMessage(); protected: void closeEvent(QCloseEvent* event) override; @@ -75,9 +79,6 @@ private Q_SLOTS: void toggleWindow(); void lockDatabasesAfterInactivity(); void repairDatabase(); - void displayGlobalMessage(const QString& text, MessageWidget::MessageType type); - void displayTabMessage(const QString& text, MessageWidget::MessageType type); - void hideGlobalMessage(); void hideTabMessage(); private: @@ -106,4 +107,6 @@ private: bool appExitCalled; }; +#define KEEPASSXC_MAIN_WINDOW qobject_cast(qobject_cast(qApp)->mainWindow()) + #endif // KEEPASSX_MAINWINDOW_H diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index a6e5cdbcf..039d7a1e6 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -14,16 +14,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - -#include -#include +#include "keys/YkChallengeResponseKey.h" +#include "keys/drivers/YubiKey.h" #include "core/Tools.h" #include "crypto/CryptoHash.h" #include "crypto/Random.h" -#include "keys/YkChallengeResponseKey.h" -#include "keys/drivers/YubiKey.h" +#include +#include +#include +#include +#include +#include YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) : m_slot(slot), @@ -39,12 +42,12 @@ QByteArray YkChallengeResponseKey::rawKey() const /** * Assumes yubikey()->init() was called */ -bool YkChallengeResponseKey::challenge(const QByteArray& chal) +bool YkChallengeResponseKey::challenge(const QByteArray& challenge) { - return challenge(chal, 1); + return this->challenge(challenge, 1); } -#include -bool YkChallengeResponseKey::challenge(const QByteArray& chal, unsigned retries) + +bool YkChallengeResponseKey::challenge(const QByteArray& challenge, unsigned retries) { Q_ASSERT(retries > 0); @@ -55,13 +58,21 @@ bool YkChallengeResponseKey::challenge(const QByteArray& chal, unsigned retries) emit userInteractionRequired(); } - auto result = YubiKey::instance()->challenge(m_slot, true, chal, m_key); + QFuture future = QtConcurrent::run([this, challenge]() { + return YubiKey::instance()->challenge(m_slot, true, challenge, m_key); + }); + + QEventLoop loop; + QFutureWatcher watcher; + watcher.setFuture(future); + connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); if (m_blocking) { emit userConfirmed(); } - if (result != YubiKey::ERROR) { + if (future.result() != YubiKey::ERROR) { return true; } diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h index 96e44220d..8c566ca41 100644 --- a/src/keys/YkChallengeResponseKey.h +++ b/src/keys/YkChallengeResponseKey.h @@ -33,8 +33,8 @@ public: YkChallengeResponseKey(int slot = -1, bool blocking = false); QByteArray rawKey() const; - bool challenge(const QByteArray& chal); - bool challenge(const QByteArray& chal, unsigned retries); + bool challenge(const QByteArray& challenge); + bool challenge(const QByteArray& challenge, unsigned retries); QString getName() const; bool isBlocking() const; diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index e68949394..bab0d7215 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -182,6 +182,7 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte #endif int ret = yk_challenge_response(m_yk, yk_cmd, mayBlock, paddedChallenge.size(), c, response.size(), r); + emit challenged(); m_mutex.unlock(); diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h index 2382b69cf..b938ff86b 100644 --- a/src/keys/drivers/YubiKey.h +++ b/src/keys/drivers/YubiKey.h @@ -86,6 +86,11 @@ Q_SIGNALS: */ void detected(int slot, bool blocking); + /** + * Emitted when the YubiKey was challenged and has returned a response. + */ + void challenged(); + /** * Emitted when no Yubikey could be found. */ From 492e32062878ad1ab05e049b7e952f42487d3f39 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 16:22:40 +0100 Subject: [PATCH 094/333] Shorten KeePassHTTP checkbox description and only enable settings when it's checked --- src/http/OptionDialog.cpp | 3 +++ src/http/OptionDialog.ui | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/http/OptionDialog.cpp b/src/http/OptionDialog.cpp index a82e2c77e..fd30f8745 100644 --- a/src/http/OptionDialog.cpp +++ b/src/http/OptionDialog.cpp @@ -30,6 +30,9 @@ OptionDialog::OptionDialog(QWidget *parent) : m_ui->warningWidget->showMessage(tr("The following options can be dangerous!\nChange them only if you know what you are doing."), MessageWidget::Warning); m_ui->warningWidget->setIcon(FilePath::instance()->icon("status", "dialog-warning")); m_ui->warningWidget->setCloseButtonVisible(false); + + m_ui->tabWidget->setEnabled(m_ui->enableHttpServer->isChecked()); + connect(m_ui->enableHttpServer, SIGNAL(toggled(bool)), m_ui->tabWidget, SLOT(setEnabled(bool))); } OptionDialog::~OptionDialog() diff --git a/src/http/OptionDialog.ui b/src/http/OptionDialog.ui index f3171733b..e78199c00 100644 --- a/src/http/OptionDialog.ui +++ b/src/http/OptionDialog.ui @@ -28,9 +28,11 @@ + + This is required for accessing your databases from ChromeIPass or PassIFox + - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox + Enable KeePassHTTP server From ed085776c7cad991cc0446c9c7d3231770df374f Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 16:27:21 +0100 Subject: [PATCH 095/333] Make HTTP port input field smaller --- src/http/OptionDialog.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/http/OptionDialog.ui b/src/http/OptionDialog.ui index e78199c00..abe994772 100644 --- a/src/http/OptionDialog.ui +++ b/src/http/OptionDialog.ui @@ -237,6 +237,12 @@ + + + 0 + 0 + + d0000 From b10cb1c83c1b8ac1b94becf0c8af94ca68af04f6 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 17:27:27 +0100 Subject: [PATCH 096/333] Show YubiKey message from MainWindow to ensure it's always shown when a challenge is generated --- src/gui/ChangeMasterKeyWidget.cpp | 14 -------------- src/gui/ChangeMasterKeyWidget.h | 2 -- src/gui/DatabaseOpenWidget.cpp | 14 -------------- src/gui/DatabaseOpenWidget.h | 2 -- src/gui/MainWindow.cpp | 11 +++++++++++ src/gui/MainWindow.h | 2 ++ src/keys/YkChallengeResponseKey.cpp | 4 ++++ 7 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index b223d3922..bb963d3cd 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -177,8 +177,6 @@ void ChangeMasterKeyWidget::generateKey() bool blocking = i & true; int slot = i >> 1; auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); - connect(key.data(), SIGNAL(userInteractionRequired()), SLOT(showYubiKeyPopup())); - connect(key.data(), SIGNAL(userConfirmed()), SLOT(hideYubiKeyPopup())); m_key.addChallengeResponseKey(key); } #endif @@ -241,15 +239,3 @@ void ChangeMasterKeyWidget::setCancelEnabled(bool enabled) { m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(enabled); } - -void ChangeMasterKeyWidget::showYubiKeyPopup() -{ - KEEPASSXC_MAIN_WINDOW->displayGlobalMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information); - KEEPASSXC_MAIN_WINDOW->setEnabled(false); -} - -void ChangeMasterKeyWidget::hideYubiKeyPopup() -{ - KEEPASSXC_MAIN_WINDOW->hideGlobalMessage(); - KEEPASSXC_MAIN_WINDOW->setEnabled(true); -} diff --git a/src/gui/ChangeMasterKeyWidget.h b/src/gui/ChangeMasterKeyWidget.h index fdc7f9529..b3e097276 100644 --- a/src/gui/ChangeMasterKeyWidget.h +++ b/src/gui/ChangeMasterKeyWidget.h @@ -55,8 +55,6 @@ private slots: void noYubikeyFound(); void challengeResponseGroupToggled(bool checked); void pollYubikey(); - void showYubiKeyPopup(); - void hideYubiKeyPopup(); private: const QScopedPointer m_ui; diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 03a560c2f..2eebcae3c 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -214,8 +214,6 @@ CompositeKey DatabaseOpenWidget::databaseKey() bool blocking = i & true; int slot = i >> 1; auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); - connect(key.data(), SIGNAL(userInteractionRequired()), SLOT(showYubiKeyPopup())); - connect(key.data(), SIGNAL(userConfirmed()), SLOT(hideYubiKeyPopup())); masterKey.addChallengeResponseKey(key); } #endif @@ -266,18 +264,6 @@ void DatabaseOpenWidget::pollYubikey() QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); } -void DatabaseOpenWidget::showYubiKeyPopup() -{ - m_ui->messageWidget->showMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information); - KEEPASSXC_MAIN_WINDOW->setEnabled(false); -} - -void DatabaseOpenWidget::hideYubiKeyPopup() -{ - m_ui->messageWidget->hideMessage(); - KEEPASSXC_MAIN_WINDOW->setEnabled(true); -} - void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking) { YkChallengeResponseKey yk(slot, blocking); diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index eee705a79..caba70a61 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -53,8 +53,6 @@ protected: protected slots: virtual void openDatabase(); - void showYubiKeyPopup(); - void hideYubiKeyPopup(); void reject(); private slots: diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index e9a05e5d1..9a9ef3dcf 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -871,3 +871,14 @@ void MainWindow::hideTabMessage() } } +void MainWindow::showYubiKeyPopup() +{ + displayGlobalMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information); + setEnabled(false); +} + +void MainWindow::hideYubiKeyPopup() +{ + hideGlobalMessage(); + setEnabled(true); +} diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index c3262a3cc..eb677fbbb 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -47,6 +47,8 @@ public Q_SLOTS: void displayGlobalMessage(const QString& text, MessageWidget::MessageType type); void displayTabMessage(const QString& text, MessageWidget::MessageType type); void hideGlobalMessage(); + void showYubiKeyPopup(); + void hideYubiKeyPopup(); protected: void closeEvent(QCloseEvent* event) override; diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index 039d7a1e6..31ad1d860 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -20,6 +20,7 @@ #include "core/Tools.h" #include "crypto/CryptoHash.h" #include "crypto/Random.h" +#include "gui/MainWindow.h" #include #include @@ -32,6 +33,9 @@ YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) : m_slot(slot), m_blocking(blocking) { + + connect(this, SIGNAL(userInteractionRequired()), KEEPASSXC_MAIN_WINDOW, SLOT(showYubiKeyPopup())); + connect(this, SIGNAL(userConfirmed()), KEEPASSXC_MAIN_WINDOW, SLOT(hideYubiKeyPopup())); } QByteArray YkChallengeResponseKey::rawKey() const From 18844d096a4e0348120040368d53542fad5cca6e Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 17:50:19 +0100 Subject: [PATCH 097/333] Make other YubiKey driver methods thread-safe --- src/keys/YkChallengeResponseKey.cpp | 1 - src/keys/drivers/YubiKey.cpp | 21 ++++++++++++++++++--- src/keys/drivers/YubiKey.h | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index 31ad1d860..60db42823 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -33,7 +33,6 @@ YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) : m_slot(slot), m_blocking(blocking) { - connect(this, SIGNAL(userInteractionRequired()), KEEPASSXC_MAIN_WINDOW, SLOT(showYubiKeyPopup())); connect(this, SIGNAL(userConfirmed()), KEEPASSXC_MAIN_WINDOW, SLOT(hideYubiKeyPopup())); } diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index bab0d7215..a490f0699 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -34,7 +34,7 @@ #define m_yk (static_cast(m_yk_void)) #define m_ykds (static_cast(m_ykds_void)) -YubiKey::YubiKey() : m_yk_void(NULL), m_ykds_void(NULL) +YubiKey::YubiKey() : m_yk_void(NULL), m_ykds_void(NULL), m_mutex(QMutex::Recursive) { } @@ -51,11 +51,14 @@ YubiKey* YubiKey::instance() bool YubiKey::init() { + m_mutex.lock(); + // previously initialized if (m_yk != NULL && m_ykds != NULL) { if (yk_get_status(m_yk, m_ykds)) { // Still connected + m_mutex.unlock(); return true; } else { // Initialized but not connected anymore, re-init @@ -64,12 +67,14 @@ bool YubiKey::init() } if (!yk_init()) { + m_mutex.unlock(); return false; } // TODO: handle multiple attached hardware devices m_yk_void = static_cast(yk_open_first_key()); if (m_yk == NULL) { + m_mutex.unlock(); return false; } @@ -77,14 +82,18 @@ bool YubiKey::init() if (m_ykds == NULL) { yk_close_key(m_yk); m_yk_void = NULL; + m_mutex.unlock(); return false; } + m_mutex.unlock(); return true; } bool YubiKey::deinit() { + m_mutex.lock(); + if (m_yk) { yk_close_key(m_yk); m_yk_void = NULL; @@ -95,6 +104,8 @@ bool YubiKey::deinit() m_ykds_void = NULL; } + m_mutex.unlock(); + return true; } @@ -119,9 +130,13 @@ void YubiKey::detect() emit notFound(); } -bool YubiKey::getSerial(unsigned int& serial) const +bool YubiKey::getSerial(unsigned int& serial) { - if (!yk_get_serial(m_yk, 1, 0, &serial)) { + m_mutex.lock(); + int result = yk_get_serial(m_yk, 1, 0, &serial); + m_mutex.unlock(); + + if (!result) { return false; } diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h index b938ff86b..47341f9a2 100644 --- a/src/keys/drivers/YubiKey.h +++ b/src/keys/drivers/YubiKey.h @@ -70,7 +70,7 @@ public: * @param serial serial number * @return true on success */ - bool getSerial(unsigned int& serial) const; + bool getSerial(unsigned int& serial); /** * @brief YubiKey::detect - probe for attached YubiKeys From 2721317fc3187f66f2007f11ab8439deaeffeb64 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 18:43:15 +0100 Subject: [PATCH 098/333] Block and unblock autoreload in timed mutex style to prevent a double challenge when saving the database and the YubiKey requires user interaction --- src/gui/DatabaseTabWidget.cpp | 28 ++++++++++++------------- src/gui/DatabaseWidget.cpp | 39 ++++++++++++++++++++--------------- src/gui/DatabaseWidget.h | 13 ++++++------ 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 734dc3f2e..0503531ab 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -322,17 +322,18 @@ bool DatabaseTabWidget::closeAllDatabases() bool DatabaseTabWidget::saveDatabase(Database* db) { DatabaseManagerStruct& dbStruct = m_dbList[db]; - // temporarily disable autoreload - dbStruct.dbWidget->ignoreNextAutoreload(); if (dbStruct.saveToFilename) { QSaveFile saveFile(dbStruct.canonicalFilePath); if (saveFile.open(QIODevice::WriteOnly)) { // write the database to the file + dbStruct.dbWidget->blockAutoReload(true); m_writer.writeDatabase(&saveFile, db); + dbStruct.dbWidget->blockAutoReload(false); + if (m_writer.hasError()) { - Q_EMIT messageTab(tr("Writing the database failed.").append("\n") - .append(m_writer.errorString()), MessageWidget::Error); + emit messageTab(tr("Writing the database failed.").append("\n") + .append(m_writer.errorString()), MessageWidget::Error); return false; } @@ -341,22 +342,19 @@ bool DatabaseTabWidget::saveDatabase(Database* db) dbStruct.modified = false; dbStruct.dbWidget->databaseSaved(); updateTabName(db); - Q_EMIT messageDismissTab(); + emit messageDismissTab(); return true; - } - else { - Q_EMIT messageTab(tr("Writing the database failed.").append("\n") - .append(saveFile.errorString()), MessageWidget::Error); + } else { + emit messageTab(tr("Writing the database failed.").append("\n") + .append(saveFile.errorString()), MessageWidget::Error); return false; } - } - else { - Q_EMIT messageTab(tr("Writing the database failed.").append("\n") - .append(saveFile.errorString()), MessageWidget::Error); + } else { + emit messageTab(tr("Writing the database failed.").append("\n") + .append(saveFile.errorString()), MessageWidget::Error); return false; } - } - else { + } else { return saveDatabaseAs(db); } } diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index a67646ef1..9af79f777 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -168,14 +168,14 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool))); connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged())); connect(&m_fileWatchTimer, SIGNAL(timeout()), this, SLOT(reloadDatabaseFile())); - connect(&m_ignoreWatchTimer, SIGNAL(timeout()), this, SLOT(onWatchedFileChanged())); + connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(unblockAutoReload())); connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged())); m_databaseModified = false; m_fileWatchTimer.setSingleShot(true); - m_ignoreWatchTimer.setSingleShot(true); - m_ignoreNextAutoreload = false; + m_fileWatchUnblockTimer.setSingleShot(true); + m_ignoreAutoReload = false; m_searchCaseSensitive = false; @@ -1001,7 +1001,7 @@ void DatabaseWidget::lock() void DatabaseWidget::updateFilename(const QString& fileName) { - if (! m_filename.isEmpty()) { + if (!m_filename.isEmpty()) { m_fileWatcher.removePath(m_filename); } @@ -1009,26 +1009,31 @@ void DatabaseWidget::updateFilename(const QString& fileName) m_filename = fileName; } -void DatabaseWidget::ignoreNextAutoreload() +void DatabaseWidget::blockAutoReload(bool block) { - m_ignoreNextAutoreload = true; - m_ignoreWatchTimer.start(100); + if (block) { + m_ignoreAutoReload = true; + m_fileWatchTimer.stop(); + } else { + m_fileWatchUnblockTimer.start(500); + } +} + +void DatabaseWidget::unblockAutoReload() +{ + m_ignoreAutoReload = false; + updateFilename(m_filename); } void DatabaseWidget::onWatchedFileChanged() { - if (m_ignoreNextAutoreload) { - // Reset the watch - m_ignoreNextAutoreload = false; - m_ignoreWatchTimer.stop(); - m_fileWatcher.addPath(m_filename); + if (m_ignoreAutoReload) { + return; } - else { - if (m_fileWatchTimer.isActive()) - return; + if (m_fileWatchTimer.isActive()) + return; - m_fileWatchTimer.start(500); - } + m_fileWatchTimer.start(500); } void DatabaseWidget::reloadDatabaseFile() diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 78b6131de..6e9462508 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -98,9 +98,9 @@ public: EntryView* entryView(); void showUnlockDialog(); void closeUnlockDialog(); - void ignoreNextAutoreload(); + void blockAutoReload(bool block = true); -Q_SIGNALS: +signals: void closeRequest(); void currentModeChanged(DatabaseWidget::Mode mode); void groupChanged(); @@ -118,7 +118,7 @@ Q_SIGNALS: void entryColumnSizesChanged(); void updateSearch(QString text); -public Q_SLOTS: +public slots: void createEntry(); void cloneEntry(); void deleteEntries(); @@ -154,7 +154,7 @@ public Q_SLOTS: void showMessage(const QString& text, MessageWidget::MessageType type); void hideMessage(); -private Q_SLOTS: +private slots: void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column); void switchBackToEntryEdit(); void switchToHistoryView(Entry* entry); @@ -172,6 +172,7 @@ private Q_SLOTS: void onWatchedFileChanged(); void reloadDatabaseFile(); void restoreGroupEntryFocus(Uuid groupUuid, Uuid EntryUuid); + void unblockAutoReload(); private: void setClipboardTextAndMinimize(const QString& text); @@ -209,8 +210,8 @@ private: // Autoreload QFileSystemWatcher m_fileWatcher; QTimer m_fileWatchTimer; - bool m_ignoreNextAutoreload; - QTimer m_ignoreWatchTimer; + QTimer m_fileWatchUnblockTimer; + bool m_ignoreAutoReload; bool m_databaseModified; }; From 46942413db9a9b41e86180e19b9f13fd01c5be3e Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 19:47:03 +0100 Subject: [PATCH 099/333] Fix unit test crash --- src/gui/MainWindow.h | 3 ++- src/keys/YkChallengeResponseKey.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index eb677fbbb..4435c13ba 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -109,6 +109,7 @@ private: bool appExitCalled; }; -#define KEEPASSXC_MAIN_WINDOW qobject_cast(qobject_cast(qApp)->mainWindow()) +#define KEEPASSXC_MAIN_WINDOW (qobject_cast(qApp) ? \ + qobject_cast(qobject_cast(qApp)->mainWindow()) : nullptr) #endif // KEEPASSX_MAINWINDOW_H diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index 60db42823..dcd583358 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -33,8 +33,10 @@ YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) : m_slot(slot), m_blocking(blocking) { - connect(this, SIGNAL(userInteractionRequired()), KEEPASSXC_MAIN_WINDOW, SLOT(showYubiKeyPopup())); - connect(this, SIGNAL(userConfirmed()), KEEPASSXC_MAIN_WINDOW, SLOT(hideYubiKeyPopup())); + if (KEEPASSXC_MAIN_WINDOW) { + connect(this, SIGNAL(userInteractionRequired()), KEEPASSXC_MAIN_WINDOW, SLOT(showYubiKeyPopup())); + connect(this, SIGNAL(userConfirmed()), KEEPASSXC_MAIN_WINDOW, SLOT(hideYubiKeyPopup())); + } } QByteArray YkChallengeResponseKey::rawKey() const From 9a94c6d85e9d3c3dc9b3ef0acb7715c2b6a6129d Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 20:44:06 +0100 Subject: [PATCH 100/333] Remove debug output to reduce console spam when running in debug mode --- src/keys/drivers/YubiKey.cpp | 8 -------- tests/TestYkChallengeResponseKey.cpp | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index a490f0699..ffb48fc74 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -192,10 +192,6 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte c = reinterpret_cast(paddedChallenge.constData()); r = reinterpret_cast(response.data()); -#ifdef QT_DEBUG - qDebug().nospace() << __func__ << "(" << slot << ") c = " << printByteArray(paddedChallenge); -#endif - int ret = yk_challenge_response(m_yk, yk_cmd, mayBlock, paddedChallenge.size(), c, response.size(), r); emit challenged(); @@ -227,9 +223,5 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte // actual HMAC-SHA1 response is only 20 bytes response.resize(20); -#ifdef QT_DEBUG - qDebug().nospace() << __func__ << "(" << slot << ") r = " << printByteArray(response) << ", ret = " << ret; -#endif - return SUCCESS; } diff --git a/tests/TestYkChallengeResponseKey.cpp b/tests/TestYkChallengeResponseKey.cpp index bd58ac018..046c06660 100644 --- a/tests/TestYkChallengeResponseKey.cpp +++ b/tests/TestYkChallengeResponseKey.cpp @@ -46,7 +46,7 @@ void TestYubiKeyChalResp::init() QSKIP("Unable to connect to YubiKey", SkipAll); } - /* Crypto subsystem needs to be initalized for YubiKey testing */ + // crypto subsystem needs to be initialized for YubiKey testing QVERIFY(Crypto::init()); } @@ -57,7 +57,7 @@ void TestYubiKeyChalResp::detectDevices() Qt::QueuedConnection); QtConcurrent::run(YubiKey::instance(), &YubiKey::detect); - /* Need to wait for the hardware (that's hopefully plugged in)... */ + // need to wait for the hardware (that's hopefully plugged in)... QTest::qWait(2000); QVERIFY2(m_detected > 0, "Is a YubiKey attached?"); } From a001553c5ecdd7540109c02ce7b0c4b7bddb1314 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 24 Feb 2017 21:00:48 +0100 Subject: [PATCH 101/333] Fix warnings about Crypto already having been initialized --- tests/TestYkChallengeResponseKey.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/TestYkChallengeResponseKey.cpp b/tests/TestYkChallengeResponseKey.cpp index 046c06660..40eda3bf9 100644 --- a/tests/TestYkChallengeResponseKey.cpp +++ b/tests/TestYkChallengeResponseKey.cpp @@ -30,6 +30,9 @@ void TestYubiKeyChalResp::initTestCase() { m_detected = 0; m_key = NULL; + + // crypto subsystem needs to be initialized for YubiKey testing + QVERIFY(Crypto::init()); } void TestYubiKeyChalResp::cleanupTestCase() @@ -45,9 +48,6 @@ void TestYubiKeyChalResp::init() if (!result) { QSKIP("Unable to connect to YubiKey", SkipAll); } - - // crypto subsystem needs to be initialized for YubiKey testing - QVERIFY(Crypto::init()); } void TestYubiKeyChalResp::detectDevices() From f25ad83a02bd0d22bff605ac18c16d194672ed17 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Fri, 24 Feb 2017 16:59:16 -0500 Subject: [PATCH 102/333] Removed header from autotype window listview; replaced with groupbox --- src/gui/entry/EditEntryWidgetAutoType.ui | 338 ++++++++++++----------- 1 file changed, 177 insertions(+), 161 deletions(-) diff --git a/src/gui/entry/EditEntryWidgetAutoType.ui b/src/gui/entry/EditEntryWidgetAutoType.ui index c1f243680..21e102bfe 100644 --- a/src/gui/entry/EditEntryWidgetAutoType.ui +++ b/src/gui/entry/EditEntryWidgetAutoType.ui @@ -88,167 +88,183 @@ - - - - - - - false - - - true - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - - 0 - 0 - - - - - 0 - 25 - - - - + - - - - - - - false - - - - 0 - 0 - - - - - 0 - 25 - - - - - - - - - - - - - - - - - - Window title: - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 1 - 10 - - - - - - - - Use default se&quence - - - - - - - Set custo&m sequence: - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 1 - - - - - - - - false - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - + + + Window Associations + + + + + + + + false + + + true + + + false + + + false + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 0 + 0 + + + + + 0 + 25 + + + + + + + + + + + + false + + + + 0 + 0 + + + + + 0 + 25 + + + + - + + + + + + + + + + + + + Window title: + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 1 + 10 + + + + + + + + Use default se&quence + + + + + + + Set custo&m sequence: + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 1 + + + + + + + + false + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + +
From 204bf81dd1111fc1c89b7bb60b6838a00a00a4b3 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 25 Feb 2017 00:27:51 +0100 Subject: [PATCH 103/333] Resize About dialog to minimum size, even out layout spacings and open it as a dialog --- src/gui/AboutDialog.cpp | 3 ++ src/gui/AboutDialog.ui | 65 ++++++++++++++++++++++++++++++++++++----- src/gui/MainWindow.cpp | 2 +- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index ebb18d2a8..cbafb745b 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -32,6 +32,9 @@ AboutDialog::AboutDialog(QWidget* parent) { m_ui->setupUi(this); + resize(minimumSize()); + setWindowFlags(Qt::Sheet); + m_ui->nameLabel->setText(m_ui->nameLabel->text() + " " + KEEPASSX_VERSION); QFont nameLabelFont = m_ui->nameLabel->font(); nameLabelFont.setPointSize(nameLabelFont.pointSize() + 4); diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index 4eb9ee56b..e1e706808 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -7,7 +7,7 @@ 0 0 479 - 488 + 478 @@ -77,17 +77,51 @@ + + + 0 + 0 + + - <p>Website: <a href="https://keepassxc.org/">https://keepassxc.org/</a></p> -<p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues">https://github.com/</a></p> + <p>Website: <a href="https://keepassxc.org/">https://keepassxc.org/</a></p> true + + + + <p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues">https://github.com/</a></p> + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + 0 + 0 + + KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. @@ -107,23 +141,28 @@ 0 - 10 + 5 + + + 0 + 0 + + - <div>Main contributors: + <p>Main contributors:</p> <ul> <li>debfx (KeePassX)</li> <li>droidmonkey</li> <li>louib</li> <li>phoerious<li> <li>thezero</li> -</ul> -</div> +</ul> @@ -149,6 +188,12 @@ + + + 0 + 0 + + Include the following information whenever you report a bug: @@ -156,6 +201,12 @@ + + + 0 + 0 + + true diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index cf6ae0401..36e66c127 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -523,7 +523,7 @@ void MainWindow::updateWindowTitle() void MainWindow::showAboutDialog() { AboutDialog* aboutDialog = new AboutDialog(this); - aboutDialog->show(); + aboutDialog->open(); } void MainWindow::switchToDatabases() From c248944dc13fbcf0e260324abfc1e8d75d16cb63 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 25 Feb 2017 00:52:51 +0100 Subject: [PATCH 104/333] Remove window title bar context help button --- src/gui/AboutDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index cbafb745b..636b284f9 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -34,6 +34,7 @@ AboutDialog::AboutDialog(QWidget* parent) resize(minimumSize()); setWindowFlags(Qt::Sheet); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); m_ui->nameLabel->setText(m_ui->nameLabel->text() + " " + KEEPASSX_VERSION); QFont nameLabelFont = m_ui->nameLabel->font(); From d0ebaff7b4aeedbe1898fd38a9921ddc64dda99b Mon Sep 17 00:00:00 2001 From: rockihack Date: Sat, 25 Feb 2017 01:12:01 +0100 Subject: [PATCH 105/333] Prevent memory dumps on windows. --- src/core/Tools.cpp | 105 +++++++++++++++++++++++++++++++++++++++++++++ src/core/Tools.h | 1 + 2 files changed, 106 insertions(+) diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index bc63bf139..57b77878c 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 Lennart Glauer * * 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 @@ -27,6 +28,7 @@ #ifdef Q_OS_WIN #include // for Sleep(), SetDllDirectoryA() and SetSearchPathMode() +#include #endif #ifdef Q_OS_UNIX @@ -226,6 +228,10 @@ void disableCoreDumps() success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0); #endif +#ifdef Q_OS_WIN + success = success && createWindowsDACL(); +#endif + if (!success) { qWarning("Unable to disable core dumps."); } @@ -240,4 +246,103 @@ void setupSearchPaths() #endif } +// +// Prevent memory dumps without admin privileges. +// MiniDumpWriteDump function requires +// PROCESS_QUERY_INFORMATION and PROCESS_VM_READ +// see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680360%28v=vs.85%29.aspx +// +bool createWindowsDACL() +{ + bool bSuccess = false; + + // Access control list + PACL pACL = nullptr; + DWORD cbACL = 0; + + // Security identifiers + PSID pSIDAdmin = nullptr; + PSID pSIDSystem = nullptr; + SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; + + // Create a SID for the BUILTIN\Administrators group + if (!AllocateAndInitializeSid( + &SIDAuthNT, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &pSIDAdmin + )) { + goto Cleanup; + } + + // Create a SID for the System group + if (!AllocateAndInitializeSid( + &SIDAuthNT, + 1, + SECURITY_LOCAL_SYSTEM_RID, + 0, 0, 0, 0, 0, 0, 0, + &pSIDSystem + )) { + goto Cleanup; + } + + cbACL = sizeof(ACL) + + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pSIDAdmin) + + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pSIDSystem); + + pACL = static_cast(HeapAlloc(GetProcessHeap(), 0, cbACL)); + if (pACL == nullptr) { + goto Cleanup; + } + + // Initialize access control list + if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) { + goto Cleanup; + } + + // Add allowed access control entries, everything else is denied + if (!AddAccessAllowedAce( + pACL, + ACL_REVISION, + SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // protected process + pSIDAdmin + )) { + goto Cleanup; + } + if (!AddAccessAllowedAce( + pACL, + ACL_REVISION, + PROCESS_ALL_ACCESS, + pSIDSystem + )) { + goto Cleanup; + } + + // Update discretionary access control list + bSuccess = ERROR_SUCCESS == SetSecurityInfo( + GetCurrentProcess(), // object handle + SE_KERNEL_OBJECT, // type of object + DACL_SECURITY_INFORMATION, // change only the objects DACL + nullptr, nullptr, // do not change owner or group + pACL, // DACL specified + nullptr // do not change SACL + ); + +Cleanup: + + if (pSIDAdmin != nullptr) { + FreeSid(pSIDAdmin); + } + if (pSIDSystem != nullptr) { + FreeSid(pSIDSystem); + } + if (pACL != nullptr) { + HeapFree(GetProcessHeap(), 0, pACL); + } + + return bSuccess; +} + } // namespace Tools diff --git a/src/core/Tools.h b/src/core/Tools.h index 65df1ea4e..ba55054a8 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -41,6 +41,7 @@ void sleep(int ms); void wait(int ms); void disableCoreDumps(); void setupSearchPaths(); +bool createWindowsDACL(); template RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value) From 153dc620c8d32e35c6ea0a46a8a84c59a0d0a69e Mon Sep 17 00:00:00 2001 From: rockihack Date: Sat, 25 Feb 2017 01:35:47 +0100 Subject: [PATCH 106/333] Add #ifdef Q_OS_WIN guard. --- src/core/Tools.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 57b77878c..a6bff32f3 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -256,6 +256,7 @@ bool createWindowsDACL() { bool bSuccess = false; +#ifdef Q_OS_WIN // Access control list PACL pACL = nullptr; DWORD cbACL = 0; @@ -341,6 +342,7 @@ Cleanup: if (pACL != nullptr) { HeapFree(GetProcessHeap(), 0, pACL); } +#endif return bSuccess; } From 6e2254c13d631f28feb5c34cba755b3019331f67 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 25 Feb 2017 16:09:26 +0100 Subject: [PATCH 107/333] Don't fall back to ugly Qt close icon --- src/gui/KMessageWidget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gui/KMessageWidget.cpp b/src/gui/KMessageWidget.cpp index e2c22574c..522a72f54 100644 --- a/src/gui/KMessageWidget.cpp +++ b/src/gui/KMessageWidget.cpp @@ -20,6 +20,8 @@ */ #include "KMessageWidget.h" +#include "core/FilePath.h" + #include #include #include @@ -89,7 +91,7 @@ void KMessageWidgetPrivate::init(KMessageWidget *q_ptr) QAction *closeAction = new QAction(q); closeAction->setText(KMessageWidget::tr("&Close")); closeAction->setToolTip(KMessageWidget::tr("Close message")); - closeAction->setIcon(q->style()->standardIcon(QStyle::SP_DialogCloseButton)); + closeAction->setIcon(FilePath::instance()->icon("actions", "dialog-close")); QObject::connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(animatedHide())); From 84f2520924ce1f91f60d1c82bf865c9ed0ff5172 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 25 Feb 2017 16:18:36 +0100 Subject: [PATCH 108/333] Fix message widget style on OS X --- src/gui/KMessageWidget.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/gui/KMessageWidget.cpp b/src/gui/KMessageWidget.cpp index 522a72f54..a6bfa04bc 100644 --- a/src/gui/KMessageWidget.cpp +++ b/src/gui/KMessageWidget.cpp @@ -98,6 +98,12 @@ void KMessageWidgetPrivate::init(KMessageWidget *q_ptr) closeButton = new QToolButton(content); closeButton->setAutoRaise(true); closeButton->setDefaultAction(closeAction); +#ifdef Q_OS_MAC + closeButton->setStyleSheet("QToolButton { background: transparent;" + "border-radius: 2px; padding: 3px; }" + "QToolButton::hover, QToolButton::focus {" + "border: 1px solid rgb(90, 200, 250); }"); +#endif q->setMessageType(KMessageWidget::Information); } @@ -287,7 +293,11 @@ void KMessageWidget::setMessageType(KMessageWidget::MessageType type) } // Colors +#ifdef Q_OS_MAC + fg = palette().light().color(); +#else fg = palette().highlightedText().color(); +#endif bg0 = bg1.lighter(110); bg2 = bg1.darker(110); border = darkShade(bg1); From 44206cf088fb0b7d600a8a819e6d384a785e8741 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 25 Feb 2017 17:04:00 +0100 Subject: [PATCH 109/333] Fix stub compilation --- src/keys/drivers/YubiKeyStub.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/keys/drivers/YubiKeyStub.cpp b/src/keys/drivers/YubiKeyStub.cpp index e93099bf4..15eef27ad 100644 --- a/src/keys/drivers/YubiKeyStub.cpp +++ b/src/keys/drivers/YubiKeyStub.cpp @@ -51,14 +51,14 @@ void YubiKey::detect() { } -bool YubiKey::getSerial(unsigned int& serial) const +bool YubiKey::getSerial(unsigned int& serial) { Q_UNUSED(serial); return false; } -YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& chal, QByteArray& resp) const +YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& chal, QByteArray& resp) { Q_UNUSED(slot); Q_UNUSED(mayBlock); From 48366d245cd0972fa0ae3c7726afaaa1c5fd0fce Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 25 Feb 2017 17:11:02 +0100 Subject: [PATCH 110/333] Add CMake feature description --- CMakeLists.txt | 6 +++--- src/CMakeLists.txt | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ecce4f0ad..0c2fa267b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,9 +34,9 @@ option(WITH_GUI_TESTS "Enable building of GUI tests" OFF) option(WITH_DEV_BUILD "Use only for development. Disables/warns about deprecated methods." OFF) option(WITH_COVERAGE "Use to build with coverage tests. (GCC ONLY)." OFF) -option(WITH_XC_AUTOTYPE "Include Autotype." OFF) -option(WITH_XC_HTTP "Include KeePassHTTP." OFF) -option(WITH_XC_YUBIKEY "Include Yubikey support." OFF) +option(WITH_XC_AUTOTYPE "Include Auto-Type." OFF) +option(WITH_XC_HTTP "Include KeePassHTTP support." OFF) +option(WITH_XC_YUBIKEY "Include YubiKey support." OFF) set(KEEPASSXC_VERSION "2.1.2") set(KEEPASSXC_VERSION_NUM "2.1.2") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 380a6c2dd..418f1e798 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -152,8 +152,9 @@ set(keepassx_FORMS gui/group/EditGroupWidgetMain.ui ) -add_feature_info(KeePassHTTP WITH_XC_HTTP "KeePassHTTP support for ChromeIPass and PassIFox") -add_feature_info(Autotype WITH_XC_AUTOTYPE "Auto-type passwords in Input fields") +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_subdirectory(http) if(WITH_XC_HTTP) From 9ba88838ba99ab104dc0149006089045327e2737 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 25 Feb 2017 21:42:55 +0100 Subject: [PATCH 111/333] Use white close icon and add more padding to message widget --- .../16x16/actions/message-close.png | Bin 0 -> 457 bytes .../22x22/actions/message-close.png | Bin 0 -> 457 bytes share/icons/svg/message-close.svg | 65 ++++++++++++++++++ src/gui/KMessageWidget.cpp | 7 +- 4 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 share/icons/application/16x16/actions/message-close.png create mode 100644 share/icons/application/22x22/actions/message-close.png create mode 100644 share/icons/svg/message-close.svg diff --git a/share/icons/application/16x16/actions/message-close.png b/share/icons/application/16x16/actions/message-close.png new file mode 100644 index 0000000000000000000000000000000000000000..4b2f9ca4d7840c5c8a181bddcf779c88f1bee2b7 GIT binary patch literal 457 zcmV;)0XF`LP)T(Rc?!;7lrb z3nc1N-70%V!L6SQddQ@yo*9Ptl3C2WuCITptHvoJU|-UOq%%o}k~-d+N_v&_AZh5F zo5dI*unr7>FN&1_eV|?hZ2^xZfT3Q1gBY{}Oe;WBeFAN3`%hayQ%!(c7CeXyTI4_$ zeoUy|D*LP_Y63KX;|P4=y@ml#fTn;OBk%_}^%EZf-+_0M@#aSSm4KmPtXAa#>B_Bd zmhJ?+M@$P|1`6<|$X2pi9ZSLXvzqUeDo2Te_fWt8dKC91p_?S{R=~Ak29`H)FOj6)tGD(^6)mx)>l;@K+C?Y%SJ!00DUIb=!0YF zv7S&%GoTCXh8&kOK~vJXf2^PBNYcnTmss(Cf&p^D#659M00000NkvXXu0mjfp<%a# literal 0 HcmV?d00001 diff --git a/share/icons/application/22x22/actions/message-close.png b/share/icons/application/22x22/actions/message-close.png new file mode 100644 index 0000000000000000000000000000000000000000..4b2f9ca4d7840c5c8a181bddcf779c88f1bee2b7 GIT binary patch literal 457 zcmV;)0XF`LP)T(Rc?!;7lrb z3nc1N-70%V!L6SQddQ@yo*9Ptl3C2WuCITptHvoJU|-UOq%%o}k~-d+N_v&_AZh5F zo5dI*unr7>FN&1_eV|?hZ2^xZfT3Q1gBY{}Oe;WBeFAN3`%hayQ%!(c7CeXyTI4_$ zeoUy|D*LP_Y63KX;|P4=y@ml#fTn;OBk%_}^%EZf-+_0M@#aSSm4KmPtXAa#>B_Bd zmhJ?+M@$P|1`6<|$X2pi9ZSLXvzqUeDo2Te_fWt8dKC91p_?S{R=~Ak29`H)FOj6)tGD(^6)mx)>l;@K+C?Y%SJ!00DUIb=!0YF zv7S&%GoTCXh8&kOK~vJXf2^PBNYcnTmss(Cf&p^D#659M00000NkvXXu0mjfp<%a# literal 0 HcmV?d00001 diff --git a/share/icons/svg/message-close.svg b/share/icons/svg/message-close.svg new file mode 100644 index 000000000..44b643072 --- /dev/null +++ b/share/icons/svg/message-close.svg @@ -0,0 +1,65 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/gui/KMessageWidget.cpp b/src/gui/KMessageWidget.cpp index a6bfa04bc..b88c3bc12 100644 --- a/src/gui/KMessageWidget.cpp +++ b/src/gui/KMessageWidget.cpp @@ -91,7 +91,7 @@ void KMessageWidgetPrivate::init(KMessageWidget *q_ptr) QAction *closeAction = new QAction(q); closeAction->setText(KMessageWidget::tr("&Close")); closeAction->setToolTip(KMessageWidget::tr("Close message")); - closeAction->setIcon(FilePath::instance()->icon("actions", "dialog-close")); + closeAction->setIcon(FilePath::instance()->icon("actions", "message-close", false)); QObject::connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(animatedHide())); @@ -293,11 +293,7 @@ void KMessageWidget::setMessageType(KMessageWidget::MessageType type) } // Colors -#ifdef Q_OS_MAC fg = palette().light().color(); -#else - fg = palette().highlightedText().color(); -#endif bg0 = bg1.lighter(110); bg2 = bg1.darker(110); border = darkShade(bg1); @@ -311,6 +307,7 @@ void KMessageWidget::setMessageType(KMessageWidget::MessageType type) "border-radius: 5px;" "border: 1px solid %4;" "margin: %5px;" + "padding: 5px;" "}" ".QLabel { color: %6; }" )) From 3715286eba54970802b85eb4f759b6e1992a7f7c Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 25 Feb 2017 22:09:55 +0100 Subject: [PATCH 112/333] Hide close button on YubiKey user interaction message --- src/gui/MainWindow.cpp | 8 +++++--- src/gui/MainWindow.h | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 1258d9f6e..ff63d7b3f 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -862,13 +862,15 @@ bool MainWindow::isTrayIconEnabled() const #endif } -void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type) +void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton) { + m_ui->globalMessageWidget->setCloseButtonVisible(showClosebutton); m_ui->globalMessageWidget->showMessage(text, type); } -void MainWindow::displayTabMessage(const QString& text, MessageWidget::MessageType type) +void MainWindow::displayTabMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton) { + m_ui->globalMessageWidget->setCloseButtonVisible(showClosebutton); m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type); } @@ -886,7 +888,7 @@ void MainWindow::hideTabMessage() void MainWindow::showYubiKeyPopup() { - displayGlobalMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information); + displayGlobalMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information, false); setEnabled(false); } diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 4435c13ba..131ccc225 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -44,8 +44,8 @@ public Q_SLOTS: void openDatabase(const QString& fileName, const QString& pw = QString(), const QString& keyFile = QString()); void appExit(); - void displayGlobalMessage(const QString& text, MessageWidget::MessageType type); - void displayTabMessage(const QString& text, MessageWidget::MessageType type); + void displayGlobalMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton = true); + void displayTabMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton = true); void hideGlobalMessage(); void showYubiKeyPopup(); void hideYubiKeyPopup(); From 0a85279bcb2e3d4beff9cdca210e3d12ee1f6885 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 26 Feb 2017 00:08:48 +0100 Subject: [PATCH 113/333] Enable Yubikey in release-tool by default --- release-tool | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-tool b/release-tool index a508d79f7..e5a49b05e 100755 --- a/release-tool +++ b/release-tool @@ -37,7 +37,7 @@ DOCKER_CONTAINER_NAME="keepassxc-build-container" CMAKE_OPTIONS="" COMPILER="g++" MAKE_OPTIONS="-j8" -BUILD_PLUGINS="autotype http" +BUILD_PLUGINS="autotype http yubikey" INSTALL_PREFIX="/usr/local" BUILD_SOURCE_TARBALL=true ORIG_BRANCH="" From 6125988f35032bdfb88cc5eaf30a3d76371aa879 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 26 Feb 2017 18:39:03 +0100 Subject: [PATCH 114/333] Mark CMake library variables as advanced --- cmake/FindYubiKey.cmake | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmake/FindYubiKey.cmake b/cmake/FindYubiKey.cmake index 297b68387..e5e0bb681 100644 --- a/cmake/FindYubiKey.cmake +++ b/cmake/FindYubiKey.cmake @@ -24,6 +24,4 @@ set(YUBIKEY_LIBRARIES ${YUBIKEY_CORE_LIBRARY} ${YUBIKEY_PERS_LIBRARY}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(YubiKey DEFAULT_MSG YUBIKEY_LIBRARIES YUBIKEY_INCLUDE_DIRS) -# TODO: Is mark_as_advanced() necessary? It's used in many examples with -# little explanation. Disable for now in favor of simplicity. -#mark_as_advanced(YUBIKEY_LIBRARIES YUBIKEY_INCLUDE_DIRS) +mark_as_advanced(YUBIKEY_LIBRARIES YUBIKEY_INCLUDE_DIRS) From 6d69f0b547a77676c80e9b51ac2bcd3f05665b36 Mon Sep 17 00:00:00 2001 From: rockihack Date: Sun, 26 Feb 2017 22:59:21 +0100 Subject: [PATCH 115/333] Grant minimal access rights to the user associated with the process token. --- src/core/Tools.cpp | 89 ++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index a6bff32f3..54cca29cd 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -248,8 +248,7 @@ void setupSearchPaths() // // Prevent memory dumps without admin privileges. -// MiniDumpWriteDump function requires -// PROCESS_QUERY_INFORMATION and PROCESS_VM_READ +// MiniDumpWriteDump function requires PROCESS_QUERY_INFORMATION and PROCESS_VM_READ // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680360%28v=vs.85%29.aspx // bool createWindowsDACL() @@ -257,48 +256,62 @@ bool createWindowsDACL() bool bSuccess = false; #ifdef Q_OS_WIN + // Process token and user + HANDLE hToken = nullptr; + PTOKEN_USER pTokenUser = nullptr; + DWORD cbBufferSize = 0; + // Access control list PACL pACL = nullptr; DWORD cbACL = 0; - // Security identifiers - PSID pSIDAdmin = nullptr; - PSID pSIDSystem = nullptr; - SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; - - // Create a SID for the BUILTIN\Administrators group - if (!AllocateAndInitializeSid( - &SIDAuthNT, - 2, - SECURITY_BUILTIN_DOMAIN_RID, - DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, - &pSIDAdmin + // Open the access token associated with the calling process + if (!OpenProcessToken( + GetCurrentProcess(), + TOKEN_QUERY, + &hToken )) { goto Cleanup; } - // Create a SID for the System group - if (!AllocateAndInitializeSid( - &SIDAuthNT, - 1, - SECURITY_LOCAL_SYSTEM_RID, - 0, 0, 0, 0, 0, 0, 0, - &pSIDSystem + // Retrieve the token information in a TOKEN_USER structure + GetTokenInformation( + hToken, + TokenUser, // request for a TOKEN_USER structure + nullptr, + 0, + &cbBufferSize + ); + + pTokenUser = static_cast(HeapAlloc(GetProcessHeap(), 0, cbBufferSize)); + if (pTokenUser == nullptr) { + goto Cleanup; + } + + if (!GetTokenInformation( + hToken, + TokenUser, + pTokenUser, + cbBufferSize, + &cbBufferSize )) { goto Cleanup; } + if (!IsValidSid(pTokenUser->User.Sid)) { + goto Cleanup; + } + + // Calculate the amount of memory that must be allocated for the DACL cbACL = sizeof(ACL) - + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pSIDAdmin) - + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pSIDSystem); + + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid); + // Create and initialize an ACL pACL = static_cast(HeapAlloc(GetProcessHeap(), 0, cbACL)); if (pACL == nullptr) { goto Cleanup; } - // Initialize access control list if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) { goto Cleanup; } @@ -307,21 +320,13 @@ bool createWindowsDACL() if (!AddAccessAllowedAce( pACL, ACL_REVISION, - SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // protected process - pSIDAdmin - )) { - goto Cleanup; - } - if (!AddAccessAllowedAce( - pACL, - ACL_REVISION, - PROCESS_ALL_ACCESS, - pSIDSystem + SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process + pTokenUser->User.Sid // pointer to the trustee's SID )) { goto Cleanup; } - // Update discretionary access control list + // Set discretionary access control list bSuccess = ERROR_SUCCESS == SetSecurityInfo( GetCurrentProcess(), // object handle SE_KERNEL_OBJECT, // type of object @@ -333,15 +338,15 @@ bool createWindowsDACL() Cleanup: - if (pSIDAdmin != nullptr) { - FreeSid(pSIDAdmin); - } - if (pSIDSystem != nullptr) { - FreeSid(pSIDSystem); - } if (pACL != nullptr) { HeapFree(GetProcessHeap(), 0, pACL); } + if (pTokenUser != nullptr) { + HeapFree(GetProcessHeap(), 0, pTokenUser); + } + if (hToken != nullptr) { + CloseHandle(hToken); + } #endif return bSuccess; From 5654dc99076286be726865d8ac5b8afcd94b52dd Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 28 Feb 2017 14:30:12 +0100 Subject: [PATCH 116/333] Update README to reflect current text from our website --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e892f1b01..ab5df61aa 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,23 @@ [![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) ## About -KeePassXC is a fork of [KeePassX](https://www.keepassx.org/) that [aims to incorporate stalled pull requests, features, and bug fixes that have never made it into the main KeePassX repository](https://github.com/keepassxreboot/keepassx/issues/43). +KeePassXC 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. ## Additional features compared to KeePassX -- Autotype on all three major platforms (Linux, Windows, OS X) +- Auto-Type on all three major platforms (Linux, Windows, OS X) - Stand-alone password generator - Password strength meter -- Use website's favicons as entry icons +- Using website favicons as entry icons - Merging of databases - Automatic reload when the database changed on disk - KeePassHTTP support for use with [PassIFox](https://addons.mozilla.org/en-us/firefox/addon/passifox/) in Mozilla Firefox and [chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae) in Google Chrome or Chromium. +- 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 still use it at your own risk! +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! ### 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. From 4061fc7cf879a85c98fdd738c94e1ac88ac025a3 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 28 Feb 2017 22:45:40 -0500 Subject: [PATCH 117/333] Delete a custom icon with multiple entries using it (#357) * Made it possible to delete a custom icon with multiple entries using it --- src/gui/EditWidgetIcons.cpp | 74 ++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index dd5c933a2..8cd9837d8 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -273,50 +273,74 @@ void EditWidgetIcons::removeCustomIcon() QModelIndex index = m_ui->customIconsView->currentIndex(); if (index.isValid()) { Uuid iconUuid = m_customIconModel->uuidFromIndex(index); - int iconUsedCount = 0; const QList allEntries = m_database->rootGroup()->entriesRecursive(true); + QList entriesWithSameIcon; QList historyEntriesWithSameIcon; for (Entry* entry : allEntries) { - bool isHistoryEntry = !entry->group(); if (iconUuid == entry->iconUuid()) { - if (isHistoryEntry) { + // Check if this is a history entry (no assigned group) + if (!entry->group()) { historyEntriesWithSameIcon << entry; - } - else if (m_currentUuid != entry->uuid()) { - iconUsedCount++; + } else if (m_currentUuid != entry->uuid()) { + entriesWithSameIcon << entry; } } } const QList allGroups = m_database->rootGroup()->groupsRecursive(true); - for (const Group* group : allGroups) { + QList groupsWithSameIcon; + + for (Group* group : allGroups) { if (iconUuid == group->iconUuid() && m_currentUuid != group->uuid()) { - iconUsedCount++; + groupsWithSameIcon << group; } } - if (iconUsedCount == 0) { - for (Entry* entry : asConst(historyEntriesWithSameIcon)) { - entry->setUpdateTimeinfo(false); - entry->setIcon(0); - entry->setUpdateTimeinfo(true); - } + int iconUseCount = entriesWithSameIcon.size() + groupsWithSameIcon.size(); + if (iconUseCount > 0) { + QMessageBox::StandardButton ans = MessageBox::question(this, tr("Confirm Delete"), + tr("This icon is used by %1 entries, and will be replaced " + "by the default icon. Are you sure you want to delete it?") + .arg(iconUseCount), QMessageBox::Yes | QMessageBox::No); - m_database->metadata()->removeCustomIcon(iconUuid); - m_customIconModel->setIcons(m_database->metadata()->customIconsScaledPixmaps(), - m_database->metadata()->customIconsOrder()); - if (m_customIconModel->rowCount() > 0) { - m_ui->customIconsView->setCurrentIndex(m_customIconModel->index(0, 0)); - } - else { - updateRadioButtonDefaultIcons(); + if (ans == QMessageBox::No) { + // Early out, nothing is changed + return; + } else { + // Revert matched entries to the default entry icon + for (Entry* entry : asConst(entriesWithSameIcon)) { + entry->setIcon(Entry::DefaultIconNumber); + } + + // Revert matched groups to the default group icon + for (Group* group : asConst(groupsWithSameIcon)) { + group->setIcon(Group::DefaultIconNumber); + } } } - else { - Q_EMIT messageEditEntry( - tr("Can't delete icon. Still used by %1 items.").arg(iconUsedCount), MessageWidget::Error); + + + // Remove the icon from history entries + for (Entry* entry : asConst(historyEntriesWithSameIcon)) { + entry->setUpdateTimeinfo(false); + entry->setIcon(0); + entry->setUpdateTimeinfo(true); + } + + // Remove the icon from the database + m_database->metadata()->removeCustomIcon(iconUuid); + m_customIconModel->setIcons(m_database->metadata()->customIconsScaledPixmaps(), + m_database->metadata()->customIconsOrder()); + + // Reset the current icon view + updateRadioButtonDefaultIcons(); + + if (m_database->resolveEntry(m_currentUuid) != nullptr) { + m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(Entry::DefaultIconNumber)); + } else { + m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(Group::DefaultIconNumber)); } } } From ac0178d2c7a397cfb0d5be66fecec94ed8b1085d Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Wed, 1 Mar 2017 21:08:10 -0500 Subject: [PATCH 118/333] closeEvent() should always hide the window, never raise it. This fixes an issue on X11 where Alt-F4 would not close the window, due to toggleWindow() believing the window is inactive and trying to raise it. Avoid the problem by closing the window unconditionally. --- src/gui/MainWindow.cpp | 25 +++++++++++++++---------- src/gui/MainWindow.h | 1 + 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index f263a0014..763ada1ae 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -301,7 +301,7 @@ MainWindow::MainWindow() connect(m_ui->welcomeWidget, SIGNAL(importKeePass1Database()), SLOT(switchToKeePass1Database())); connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog())); - + #ifdef Q_OS_MAC setUnifiedTitleAndToolBarOnMac(true); #endif @@ -612,7 +612,7 @@ void MainWindow::closeEvent(QCloseEvent* event) if (minimizeOnClose && !appExitCalled) { event->ignore(); - toggleWindow(); + hideWindow(); if (config()->get("security/lockdatabaseminimize").toBool()) { m_ui->tabWidget->lockDatabases(); @@ -777,22 +777,27 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason) } } +void MainWindow::hideWindow() +{ + setWindowState(windowState() | Qt::WindowMinimized); + QTimer::singleShot(0, this, SLOT(hide())); + + if (config()->get("security/lockdatabaseminimize").toBool()) { + m_ui->tabWidget->lockDatabases(); + } +} + void MainWindow::toggleWindow() { if ((QApplication::activeWindow() == this) && isVisible() && !isMinimized()) { - setWindowState(windowState() | Qt::WindowMinimized); - QTimer::singleShot(0, this, SLOT(hide())); - - if (config()->get("security/lockdatabaseminimize").toBool()) { - m_ui->tabWidget->lockDatabases(); - } + hideWindow(); } else { ensurePolished(); setWindowState(windowState() & ~Qt::WindowMinimized); show(); raise(); activateWindow(); - + #if defined(Q_OS_LINUX) && ! defined(QT_NO_DBUS) // re-register global D-Bus menu (needed on Ubuntu with Unity) // see https://github.com/keepassxreboot/keepassxc/issues/271 @@ -832,7 +837,7 @@ void MainWindow::repairDatabase() if (fileName.isEmpty()) { return; } - + QScopedPointer dialog(new QDialog(this)); DatabaseRepairWidget* dbRepairWidget = new DatabaseRepairWidget(dialog.data()); connect(dbRepairWidget, SIGNAL(success()), dialog.data(), SLOT(accept())); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 694b38e7a..5c7ccad8e 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -72,6 +72,7 @@ private Q_SLOTS: void rememberOpenDatabases(const QString& filePath); void applySettingsChanges(); void trayIconTriggered(QSystemTrayIcon::ActivationReason reason); + void hideWindow(); void toggleWindow(); void lockDatabasesAfterInactivity(); void repairDatabase(); From cdf54b07c5bbedb26cb8097eaecda8f7ce3fc0c7 Mon Sep 17 00:00:00 2001 From: rockihack Date: Thu, 2 Mar 2017 19:24:31 +0100 Subject: [PATCH 119/333] Add more detailed comment. --- src/core/Tools.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 54cca29cd..a1bfcb0c0 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -27,8 +27,8 @@ #include #ifdef Q_OS_WIN -#include // for Sleep(), SetDllDirectoryA() and SetSearchPathMode() -#include +#include // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ... +#include // for SetSecurityInfo() #endif #ifdef Q_OS_UNIX @@ -247,9 +247,13 @@ void setupSearchPaths() } // -// Prevent memory dumps without admin privileges. -// MiniDumpWriteDump function requires PROCESS_QUERY_INFORMATION and PROCESS_VM_READ -// see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680360%28v=vs.85%29.aspx +// This function grants the user associated with the process token minimal access rights and +// denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and +// PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory(). +// We do this using a discretionary access control list (DACL). Effectively this prevents +// crash dumps and disallows other processes from accessing our memory. This works as long +// as you do not have admin privileges, since then you are able to grant yourself the +// SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL. // bool createWindowsDACL() { @@ -277,7 +281,7 @@ bool createWindowsDACL() // Retrieve the token information in a TOKEN_USER structure GetTokenInformation( hToken, - TokenUser, // request for a TOKEN_USER structure + TokenUser, nullptr, 0, &cbBufferSize From 3139ae152841e07ba1a69764f2602f50f6f3bbcd Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Thu, 2 Mar 2017 18:44:01 -0500 Subject: [PATCH 120/333] WITH_XC_AUTOTYPE defaults to ON and WITH_XC_HTTP includes ALL networking --- CMakeLists.txt | 2 +- src/CMakeLists.txt | 4 +--- src/gui/EditWidgetIcons.cpp | 15 +++++++++++++++ src/gui/EditWidgetIcons.h | 12 ++++++++++-- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 347c52768..4d1ed7f57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ option(WITH_GUI_TESTS "Enable building of GUI tests" OFF) option(WITH_DEV_BUILD "Use only for development. Disables/warns about deprecated methods." OFF) option(WITH_COVERAGE "Use to build with coverage tests. (GCC ONLY)." OFF) -option(WITH_XC_AUTOTYPE "Include Autotype." OFF) +option(WITH_XC_AUTOTYPE "Include Autotype." ON) option(WITH_XC_HTTP "Include KeePassHTTP." OFF) option(WITH_XC_YUBIKEY "Include Yubikey support." OFF) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c539b50d..53b62ae75 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -155,7 +155,7 @@ add_feature_info(Autotype WITH_XC_AUTOTYPE "Auto-type passwords in Input fields" add_subdirectory(http) if(WITH_XC_HTTP) - set(keepasshttp_LIB keepasshttp) + set(keepasshttp_LIB keepasshttp qhttp Qt5::Network) endif() add_subdirectory(autotype) @@ -196,11 +196,9 @@ target_link_libraries(keepassx_core ${keepasshttp_LIB} ${autotype_LIB} zxcvbn - qhttp Qt5::Core Qt5::Concurrent Qt5::Widgets - Qt5::Network ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES}) diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 8cd9837d8..74e57cdd4 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -28,10 +28,12 @@ #include "gui/IconModels.h" #include "gui/MessageBox.h" +#ifdef WITH_XC_HTTP #include "http/qhttp/qhttpclient.hpp" #include "http/qhttp/qhttpclientresponse.hpp" using namespace qhttp::client; +#endif IconStruct::IconStruct() : uuid(Uuid()) @@ -45,7 +47,9 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) , m_database(nullptr) , m_defaultIconModel(new DefaultIconModel(this)) , m_customIconModel(new CustomIconModel(this)) + #ifdef WITH_XC_HTTP , m_httpClient(nullptr) + #endif { m_ui->setupUi(this); @@ -65,6 +69,9 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon())); m_ui->faviconButton->setVisible(false); + + m_fallbackToGoogle = true; + m_redirectCount = 0; } EditWidgetIcons::~EditWidgetIcons() @@ -138,18 +145,25 @@ void EditWidgetIcons::load(const Uuid& currentUuid, Database* database, const Ic void EditWidgetIcons::setUrl(const QString& url) { +#ifdef WITH_XC_HTTP m_url = url; m_ui->faviconButton->setVisible(!url.isEmpty()); resetFaviconDownload(); +#else + m_ui->faviconButton->setVisible(false); +#endif } void EditWidgetIcons::downloadFavicon() { +#ifdef WITH_XC_HTTP QUrl url = QUrl(m_url); url.setPath("/favicon.ico"); fetchFavicon(url); +#endif } +#ifdef WITH_XC_HTTP void EditWidgetIcons::fetchFavicon(const QUrl& url) { if (nullptr == m_httpClient) { @@ -241,6 +255,7 @@ void EditWidgetIcons::resetFaviconDownload(bool clearRedirect) m_fallbackToGoogle = true; m_ui->faviconButton->setDisabled(false); } +#endif void EditWidgetIcons::addCustomIcon() { diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index 829a5d2db..467796266 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -22,6 +22,7 @@ #include #include +#include "config-keepassx.h" #include "core/Global.h" #include "core/Uuid.h" #include "gui/MessageWidget.h" @@ -30,11 +31,14 @@ class Database; class DefaultIconModel; class CustomIconModel; +#ifdef WITH_XC_HTTP namespace qhttp { namespace client { class QHttpClient; } } +#endif + namespace Ui { class EditWidgetIcons; } @@ -68,9 +72,11 @@ Q_SIGNALS: private Q_SLOTS: void downloadFavicon(); +#ifdef WITH_XC_HTTP void fetchFavicon(const QUrl& url); void fetchFaviconFromGoogle(const QString& domain); void resetFaviconDownload(bool clearRedirect = true); +#endif void addCustomIcon(); void removeCustomIcon(); void updateWidgetsDefaultIcons(bool checked); @@ -84,11 +90,13 @@ private: Uuid m_currentUuid; QString m_url; QUrl m_redirectUrl; - bool m_fallbackToGoogle = true; - unsigned short m_redirectCount = 0; + bool m_fallbackToGoogle; + unsigned short m_redirectCount; DefaultIconModel* const m_defaultIconModel; CustomIconModel* const m_customIconModel; +#ifdef WITH_XC_HTTP qhttp::client::QHttpClient* m_httpClient; +#endif Q_DISABLE_COPY(EditWidgetIcons) }; From e81564387cbba57eec583ad608cf693ad8dfb2ec Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Thu, 2 Mar 2017 19:49:32 -0500 Subject: [PATCH 121/333] Cleanup --- src/gui/EditWidgetIcons.cpp | 5 ++--- src/gui/EditWidgetIcons.h | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 74e57cdd4..1e1f5db29 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -49,6 +49,8 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) , m_customIconModel(new CustomIconModel(this)) #ifdef WITH_XC_HTTP , m_httpClient(nullptr) + , m_fallbackToGoogle(true) + , m_redirectCount(0) #endif { m_ui->setupUi(this); @@ -69,9 +71,6 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon())); m_ui->faviconButton->setVisible(false); - - m_fallbackToGoogle = true; - m_redirectCount = 0; } EditWidgetIcons::~EditWidgetIcons() diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index 467796266..b0ff6c6c9 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -89,12 +89,12 @@ private: Database* m_database; Uuid m_currentUuid; QString m_url; - QUrl m_redirectUrl; - bool m_fallbackToGoogle; - unsigned short m_redirectCount; DefaultIconModel* const m_defaultIconModel; CustomIconModel* const m_customIconModel; #ifdef WITH_XC_HTTP + QUrl m_redirectUrl; + bool m_fallbackToGoogle; + unsigned short m_redirectCount; qhttp::client::QHttpClient* m_httpClient; #endif From 7ec8d4c3f68e2c1a4741a87d2b7287ed2795c5e8 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Thu, 2 Mar 2017 22:07:42 -0500 Subject: [PATCH 122/333] Fixed WITH_XC_AUTOTYPE and WITH_XC_HTTP descriptions --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d1ed7f57..08a42d1ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,8 +34,8 @@ option(WITH_GUI_TESTS "Enable building of GUI tests" OFF) option(WITH_DEV_BUILD "Use only for development. Disables/warns about deprecated methods." OFF) option(WITH_COVERAGE "Use to build with coverage tests. (GCC ONLY)." OFF) -option(WITH_XC_AUTOTYPE "Include Autotype." ON) -option(WITH_XC_HTTP "Include KeePassHTTP." OFF) +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) set(KEEPASSXC_VERSION "2.1.3") From b2fa6fca82348456508ba1f8c83b083f2f8a2ae8 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 4 Mar 2017 12:19:18 -0500 Subject: [PATCH 123/333] Fixes crash when deleting in search mode. --- src/gui/DatabaseWidget.cpp | 14 +++++++++----- src/gui/DatabaseWidget.h | 3 +++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index aa66c4615..3da461cad 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -322,8 +322,7 @@ void DatabaseWidget::cloneEntry() Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo | Entry::CloneRenameTitle); entry->setGroup(currentEntry->group()); - if (isInSearchMode()) - search(m_lastSearchText); + refreshSearch(); m_entryView->setFocus(); m_entryView->setCurrentEntry(entry); } @@ -366,6 +365,7 @@ void DatabaseWidget::deleteEntries() for (Entry* entry : asConst(selectedEntries)) { delete entry; } + refreshSearch(); } } else { @@ -875,6 +875,12 @@ void DatabaseWidget::databaseSaved() m_databaseModified = false; } +void DatabaseWidget::refreshSearch() { + if (isInSearchMode()) { + search(m_lastSearchText); + } +} + void DatabaseWidget::search(const QString& searchtext) { if (searchtext.isEmpty()) @@ -908,9 +914,7 @@ void DatabaseWidget::search(const QString& searchtext) void DatabaseWidget::setSearchCaseSensitive(bool state) { m_searchCaseSensitive = state; - - if (isInSearchMode()) - search(m_lastSearchText); + refreshSearch(); } void DatabaseWidget::onGroupChanged(Group* group) diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 78b6131de..7bd4b6b49 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -147,10 +147,12 @@ public Q_SLOTS: void switchToImportKeepass1(const QString& fileName); void databaseModified(); void databaseSaved(); + // Search related slots void search(const QString& searchtext); void setSearchCaseSensitive(bool state); void endSearch(); + void showMessage(const QString& text, MessageWidget::MessageType type); void hideMessage(); @@ -177,6 +179,7 @@ private: void setClipboardTextAndMinimize(const QString& text); void setIconFromParent(); void replaceDatabase(Database* db); + void refreshSearch(); Database* m_db; QWidget* m_mainWidget; From 1fe75f94209a69acf60879a5a7ea2c1cf4a72610 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Mon, 27 Feb 2017 20:25:56 -0500 Subject: [PATCH 124/333] Add feature to handle references, resolves #75 - Create popup for clone options - Add ability to resolve references for autotype/http/copying --- src/CMakeLists.txt | 2 + src/autotype/AutoType.cpp | 10 ++--- src/core/Entry.cpp | 33 ++++++++++++++++ src/core/Entry.h | 10 +++-- src/core/EntrySearcher.cpp | 8 ++-- src/gui/CloneDialog.cpp | 71 +++++++++++++++++++++++++++++++++++ src/gui/CloneDialog.h | 51 +++++++++++++++++++++++++ src/gui/CloneDialog.ui | 77 ++++++++++++++++++++++++++++++++++++++ src/gui/DatabaseWidget.cpp | 27 +++++++------ src/gui/DatabaseWidget.h | 2 +- src/http/Service.cpp | 2 +- 11 files changed, 264 insertions(+), 29 deletions(-) create mode 100644 src/gui/CloneDialog.cpp create mode 100644 src/gui/CloneDialog.h create mode 100644 src/gui/CloneDialog.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 53b62ae75..94a685737 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,6 +73,7 @@ set(keepassx_SOURCES gui/CategoryListWidget.cpp gui/ChangeMasterKeyWidget.cpp gui/Clipboard.cpp + gui/CloneDialog.cpp gui/DatabaseOpenWidget.cpp gui/DatabaseRepairWidget.cpp gui/DatabaseSettingsWidget.cpp @@ -131,6 +132,7 @@ set(keepassx_SOURCES_MAINEXE set(keepassx_FORMS gui/AboutDialog.ui gui/ChangeMasterKeyWidget.ui + gui/CloneDialog.ui gui/DatabaseOpenWidget.ui gui/DatabaseSettingsWidget.ui gui/CategoryListWidget.ui diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 40ece6e13..25afccfe9 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -565,8 +565,8 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl } } - if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->title().isEmpty() - && windowTitle.contains(entry->title(), Qt::CaseInsensitive)) { + if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->resolvePlaceholder(entry->title()).isEmpty() + && windowTitle.contains(entry->resolvePlaceholder(entry->title()), Qt::CaseInsensitive)) { sequence = entry->defaultAutoTypeSequence(); match = true; } @@ -597,11 +597,11 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl group = group->parentGroup(); } while (group && (!enableSet || sequence.isEmpty())); - if (sequence.isEmpty() && (!entry->username().isEmpty() || !entry->password().isEmpty())) { - if (entry->username().isEmpty()) { + if (sequence.isEmpty() && (!entry->resolvePlaceholder(entry->username()).isEmpty() || !entry->resolvePlaceholder(entry->password()).isEmpty())) { + if (entry->resolvePlaceholder(entry->username()).isEmpty()) { sequence = "{PASSWORD}{ENTER}"; } - else if (entry->password().isEmpty()) { + else if (entry->resolvePlaceholder(entry->password()).isEmpty()) { sequence = "{USERNAME}{ENTER}"; } else { diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 162d3f089..99f119f66 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -494,6 +494,18 @@ Entry* Entry::clone(CloneFlags flags) const entry->m_data = m_data; entry->m_attributes->copyDataFrom(m_attributes); entry->m_attachments->copyDataFrom(m_attachments); + + if (flags & CloneUserAsRef) { + // Build the username refrence + QString username = "{REF:U@I:" + m_uuid.toHex() + "}"; + entry->m_attributes->set(EntryAttributes::UserNameKey, username.toUpper(), m_attributes->isProtected(EntryAttributes::UserNameKey)); + } + + if (flags & ClonePassAsRef) { + QString password = "{REF:P@I:" + m_uuid.toHex() + "}"; + entry->m_attributes->set(EntryAttributes::PasswordKey, password.toUpper(), m_attributes->isProtected(EntryAttributes::PasswordKey)); + } + entry->m_autoTypeAssociations->copyDataFrom(this->m_autoTypeAssociations); if (flags & CloneIncludeHistory) { for (Entry* historyItem : m_history) { @@ -663,5 +675,26 @@ QString Entry::resolvePlaceholder(const QString& str) const } } + // resolving references in format: {REF:@I:} + // 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 + + QRegExp tmpRegExp("\\{REF:([TUPAN])@I:([^}]+)\\}", Qt::CaseInsensitive, QRegExp::RegExp2); + if (tmpRegExp.indexIn(result) != -1) { + // cap(0) contains the whole reference + // cap(1) contains which field is wanted + // cap(2) contains the uuid of the referenced entry + Entry* tmpRefEntry = m_group->database()->resolveEntry(Uuid(QByteArray::fromHex(tmpRegExp.cap(2).toLatin1()))); + if (tmpRefEntry) { + // entry found, get the relevant field + QString tmpRefField = tmpRegExp.cap(1).toLower(); + if (tmpRefField == "t") result.replace(tmpRegExp.cap(0), tmpRefEntry->title(), Qt::CaseInsensitive); + else if (tmpRefField == "u") result.replace(tmpRegExp.cap(0), tmpRefEntry->username(), Qt::CaseInsensitive); + else if (tmpRefField == "p") result.replace(tmpRegExp.cap(0), tmpRefEntry->password(), Qt::CaseInsensitive); + else if (tmpRefField == "a") result.replace(tmpRegExp.cap(0), tmpRefEntry->url(), Qt::CaseInsensitive); + else if (tmpRefField == "n") result.replace(tmpRegExp.cap(0), tmpRefEntry->notes(), Qt::CaseInsensitive); + } + } + return result; } diff --git a/src/core/Entry.h b/src/core/Entry.h index ae60b596c..d08c7217c 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -113,10 +113,12 @@ public: enum CloneFlag { CloneNoFlags = 0, - CloneNewUuid = 1, // generate a random uuid for the clone - CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time - CloneIncludeHistory = 4, // clone the history items - CloneRenameTitle = 8 // add "-Clone" after the original title + CloneNewUuid = 1, // generate a random uuid for the clone + CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time + CloneIncludeHistory = 4, // clone the history items + CloneRenameTitle = 8, // add "-Clone" after the original title + CloneUserAsRef = 16, // Add the user as a refrence to the origional entry + ClonePassAsRef = 32, // Add the password as a refrence to the origional entry }; Q_DECLARE_FLAGS(CloneFlags, CloneFlag) diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp index 01e152e2a..df05711ac 100644 --- a/src/core/EntrySearcher.cpp +++ b/src/core/EntrySearcher.cpp @@ -68,10 +68,10 @@ QList EntrySearcher::matchEntry(const QString& searchTerm, Entry* entry, bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensitivity caseSensitivity) { - return entry->title().contains(word, caseSensitivity) || - entry->username().contains(word, caseSensitivity) || - entry->url().contains(word, caseSensitivity) || - entry->notes().contains(word, caseSensitivity); + return entry->resolvePlaceholder(entry->title()).contains(word, caseSensitivity) || + entry->resolvePlaceholder(entry->username()).contains(word, caseSensitivity) || + entry->resolvePlaceholder(entry->url()).contains(word, caseSensitivity) || + entry->resolvePlaceholder(entry->notes()).contains(word, caseSensitivity); } bool EntrySearcher::matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity) diff --git a/src/gui/CloneDialog.cpp b/src/gui/CloneDialog.cpp new file mode 100644 index 000000000..6c8f83117 --- /dev/null +++ b/src/gui/CloneDialog.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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 . + */ + +#include "CloneDialog.h" +#include "ui_CloneDialog.h" + +#include "config-keepassx.h" +#include "version.h" +#include "core/Database.h" +#include "core/Entry.h" +#include "core/FilePath.h" +#include "crypto/Crypto.h" +#include "gui/DatabaseWidget.h" + +CloneDialog::CloneDialog(DatabaseWidget* parent, Database* db, Entry* entry) + : QDialog(parent) + , m_ui(new Ui::CloneDialog()) +{ + m_db = db; + m_entry = entry; + m_parent = parent; + + m_ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose); + + connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close())); + connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(cloneEntry())); +} + +void CloneDialog::cloneEntry() +{ + Entry::CloneFlags flags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo; + + if (m_ui->titleClone->isChecked()) { + flags |= Entry::CloneRenameTitle; + } + + if (m_ui->referencesClone->isChecked()) { + flags |= Entry::CloneUserAsRef; + flags |= Entry::ClonePassAsRef; + } + + if (m_ui->historyClone->isChecked()) { + flags |= Entry::CloneIncludeHistory; + } + + Entry* entry = m_entry->clone(flags); + entry->setGroup(m_entry->group()); + + emit m_parent->refreshSearch(); + close(); +} + +CloneDialog::~CloneDialog() +{ +} diff --git a/src/gui/CloneDialog.h b/src/gui/CloneDialog.h new file mode 100644 index 000000000..277da4a82 --- /dev/null +++ b/src/gui/CloneDialog.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2012 Felix Geyer + * + * 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 . + */ + +#ifndef KEEPASSX_CLONEDIALOG_H +#define KEEPASSX_CLONEDIALOG_H + +#include +#include +#include "core/Entry.h" +#include "core/Database.h" +#include "gui/DatabaseWidget.h" + +namespace Ui { + class CloneDialog; +} + +class CloneDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CloneDialog(DatabaseWidget* parent = nullptr, Database* db = nullptr, Entry* entry = nullptr); + ~CloneDialog(); + +private: + QScopedPointer m_ui; + +private Q_SLOTS: + void cloneEntry(); + +protected: + Database* m_db; + Entry* m_entry; + DatabaseWidget* m_parent; +}; + +#endif // KEEPASSX_CLONEDIALOG_H diff --git a/src/gui/CloneDialog.ui b/src/gui/CloneDialog.ui new file mode 100644 index 000000000..3fef5222d --- /dev/null +++ b/src/gui/CloneDialog.ui @@ -0,0 +1,77 @@ + + + CloneDialog + + + + 0 + 0 + 338 + 120 + + + + Clone Options + + + + + 12 + 12 + 323 + 62 + + + + + + + + 170 + 16777215 + + + + Append ' - Copy' to title + + + true + + + + + + + Replace username and password with references + + + + + + + Copy history + + + true + + + + + + + + + 160 + 90 + 164 + 32 + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 3da461cad..1cb1882d3 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -41,6 +41,7 @@ #include "format/KeePass2Reader.h" #include "gui/ChangeMasterKeyWidget.h" #include "gui/Clipboard.h" +#include "gui/CloneDialog.h" #include "gui/DatabaseOpenWidget.h" #include "gui/DatabaseSettingsWidget.h" #include "gui/KeePass1OpenWidget.h" @@ -320,11 +321,9 @@ void DatabaseWidget::cloneEntry() return; } - Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo | Entry::CloneRenameTitle); - entry->setGroup(currentEntry->group()); - refreshSearch(); - m_entryView->setFocus(); - m_entryView->setCurrentEntry(entry); + CloneDialog* cloneDialog = new CloneDialog(this, m_db, currentEntry); + cloneDialog->show(); + return; } void DatabaseWidget::deleteEntries() @@ -408,7 +407,7 @@ void DatabaseWidget::copyTitle() return; } - setClipboardTextAndMinimize(currentEntry->title()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->title())); } void DatabaseWidget::copyUsername() @@ -419,7 +418,7 @@ void DatabaseWidget::copyUsername() return; } - setClipboardTextAndMinimize(currentEntry->username()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->username())); } void DatabaseWidget::copyPassword() @@ -430,7 +429,7 @@ void DatabaseWidget::copyPassword() return; } - setClipboardTextAndMinimize(currentEntry->password()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->password())); } void DatabaseWidget::copyURL() @@ -441,7 +440,7 @@ void DatabaseWidget::copyURL() return; } - setClipboardTextAndMinimize(currentEntry->url()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->url())); } void DatabaseWidget::copyNotes() @@ -452,7 +451,7 @@ void DatabaseWidget::copyNotes() return; } - setClipboardTextAndMinimize(currentEntry->notes()); + setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->notes())); } void DatabaseWidget::copyAttribute(QAction* action) @@ -1172,7 +1171,7 @@ bool DatabaseWidget::currentEntryHasUsername() Q_ASSERT(false); return false; } - return !currentEntry->username().isEmpty(); + return !currentEntry->resolvePlaceholder(currentEntry->username()).isEmpty(); } bool DatabaseWidget::currentEntryHasPassword() @@ -1182,7 +1181,7 @@ bool DatabaseWidget::currentEntryHasPassword() Q_ASSERT(false); return false; } - return !currentEntry->password().isEmpty(); + return !currentEntry->resolvePlaceholder(currentEntry->password()).isEmpty(); } bool DatabaseWidget::currentEntryHasUrl() @@ -1192,7 +1191,7 @@ bool DatabaseWidget::currentEntryHasUrl() Q_ASSERT(false); return false; } - return !currentEntry->url().isEmpty(); + return !currentEntry->resolvePlaceholder(currentEntry->url()).isEmpty(); } bool DatabaseWidget::currentEntryHasNotes() @@ -1202,7 +1201,7 @@ bool DatabaseWidget::currentEntryHasNotes() Q_ASSERT(false); return false; } - return !currentEntry->notes().isEmpty(); + return !currentEntry->resolvePlaceholder(currentEntry->notes()).isEmpty(); } GroupView* DatabaseWidget::groupView() { diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 7bd4b6b49..66ece0537 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -99,6 +99,7 @@ public: void showUnlockDialog(); void closeUnlockDialog(); void ignoreNextAutoreload(); + void refreshSearch(); Q_SIGNALS: void closeRequest(); @@ -179,7 +180,6 @@ private: void setClipboardTextAndMinimize(const QString& text); void setIconFromParent(); void replaceDatabase(Database* db); - void refreshSearch(); Database* m_db; QWidget* m_mainWidget; diff --git a/src/http/Service.cpp b/src/http/Service.cpp index aac5d6b0a..1a89b7328 100644 --- a/src/http/Service.cpp +++ b/src/http/Service.cpp @@ -244,7 +244,7 @@ Service::Access Service::checkAccess(const Entry *entry, const QString & host, c KeepassHttpProtocol::Entry Service::prepareEntry(const Entry* entry) { - KeepassHttpProtocol::Entry res(entry->title(), entry->username(), entry->password(), entry->uuid().toHex()); + KeepassHttpProtocol::Entry res(entry->resolvePlaceholder(entry->title()), entry->resolvePlaceholder(entry->username()), entry->resolvePlaceholder(entry->password()), entry->uuid().toHex()); if (HttpSettings::supportKphFields()) { const EntryAttributes * attr = entry->attributes(); Q_FOREACH (const QString& key, attr->keys()) From 97150034bcc1207eab0b9c198c81e79b86412ee6 Mon Sep 17 00:00:00 2001 From: Ryan Matthews Date: Sat, 4 Mar 2017 19:42:21 -0500 Subject: [PATCH 125/333] Fix clone entry gui test --- tests/gui/TestGui.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 0c776e021..3893038ba 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -45,6 +45,7 @@ #include "format/KeePass2Reader.h" #include "gui/DatabaseTabWidget.h" #include "gui/DatabaseWidget.h" +#include "gui/CloneDialog.h" #include "gui/FileDialog.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" @@ -563,6 +564,10 @@ void TestGui::testCloneEntry() triggerAction("actionEntryClone"); + CloneDialog* cloneDialog = m_dbWidget->findChild("CloneDialog"); + QDialogButtonBox* cloneButtonBox = cloneDialog->findChild("buttonBox"); + QTest::mouseClick(cloneButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); + QCOMPARE(entryView->model()->rowCount(), 2); Entry* entryClone = entryView->entryFromIndex(entryView->model()->index(1, 1)); QVERIFY(entryOrg->uuid() != entryClone->uuid()); From b6b2e812c127fb4ce8d3e4fcd575afd95f7b95a5 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 5 Mar 2017 09:52:13 -0500 Subject: [PATCH 126/333] Removed Google Groups Link and Added IRC info --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab5df61aa..4cb956bf2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ To enable autotype, add `-DWITH_XC_AUTOTYPE=ON` to the `cmake` command. KeePassH ### 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 write to our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum. +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. From afdf02b4bea88d001a74048cd065143530b28c3a Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Mon, 9 Jan 2017 01:33:21 +0100 Subject: [PATCH 127/333] Implement import of databases in CSV (Comma Separated Values) format (i.e. from other password managers) --- src/CMakeLists.txt | 5 + src/core/CsvParser.cpp | 411 ++++++++++++++++++++ src/core/CsvParser.h | 101 +++++ src/gui/DatabaseTabWidget.cpp | 17 + src/gui/DatabaseTabWidget.h | 1 + src/gui/DatabaseWidget.cpp | 25 +- src/gui/DatabaseWidget.h | 6 +- src/gui/MainWindow.cpp | 4 +- src/gui/MainWindow.ui | 24 +- src/gui/csvImport/CsvImportWidget.cpp | 289 ++++++++++++++ src/gui/csvImport/CsvImportWidget.h | 78 ++++ src/gui/csvImport/CsvImportWidget.ui | 524 ++++++++++++++++++++++++++ src/gui/csvImport/CsvImportWizard.cpp | 72 ++++ src/gui/csvImport/CsvImportWizard.h | 55 +++ src/gui/csvImport/CsvParserModel.cpp | 139 +++++++ src/gui/csvImport/CsvParserModel.h | 59 +++ src/main.cpp | 1 + tests/CMakeLists.txt | 3 + tests/TestCsvParser.cpp | 335 ++++++++++++++++ tests/TestCsvParser.h | 69 ++++ 20 files changed, 2209 insertions(+), 9 deletions(-) create mode 100644 src/core/CsvParser.cpp create mode 100644 src/core/CsvParser.h create mode 100644 src/gui/csvImport/CsvImportWidget.cpp create mode 100644 src/gui/csvImport/CsvImportWidget.h create mode 100644 src/gui/csvImport/CsvImportWidget.ui create mode 100644 src/gui/csvImport/CsvImportWizard.cpp create mode 100644 src/gui/csvImport/CsvImportWizard.h create mode 100644 src/gui/csvImport/CsvParserModel.cpp create mode 100644 src/gui/csvImport/CsvParserModel.h create mode 100644 tests/TestCsvParser.cpp create mode 100644 tests/TestCsvParser.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94a685737..f0b293a06 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,6 +31,7 @@ configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) set(keepassx_SOURCES core/AutoTypeAssociations.cpp core/Config.cpp + core/CsvParser.cpp core/Database.cpp core/DatabaseIcons.cpp core/Endian.cpp @@ -102,6 +103,9 @@ set(keepassx_SOURCES gui/UnlockDatabaseWidget.cpp gui/UnlockDatabaseDialog.cpp gui/WelcomeWidget.cpp + gui/csvImport/CsvImportWidget.cpp + gui/csvImport/CsvImportWizard.cpp + gui/csvImport/CsvParserModel.cpp gui/entry/AutoTypeAssociationsModel.cpp gui/entry/EditEntryWidget.cpp gui/entry/EditEntryWidget_p.h @@ -133,6 +137,7 @@ set(keepassx_FORMS gui/AboutDialog.ui gui/ChangeMasterKeyWidget.ui gui/CloneDialog.ui + gui/csvImport/CsvImportWidget.ui gui/DatabaseOpenWidget.ui gui/DatabaseSettingsWidget.ui gui/CategoryListWidget.ui diff --git a/src/core/CsvParser.cpp b/src/core/CsvParser.cpp new file mode 100644 index 000000000..87b3f3403 --- /dev/null +++ b/src/core/CsvParser.cpp @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * 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 . + */ + +#include +#include +#include "core/Tools.h" +#include "CsvParser.h" + +CsvParser::CsvParser() + : m_ch(0) + , m_comment('#') + , m_currCol(1) + , m_currRow(1) + , m_isBackslashSyntax(false) + , m_isEof(false) + , m_isFileLoaded(false) + , m_isGood(true) + , m_lastPos(-1) + , m_maxCols(0) + , m_qualifier('"') + , m_separator(',') + , m_statusMsg("") +{ + m_csv.setBuffer(&m_array); + m_ts.setDevice(&m_csv); + m_csv.open(QIODevice::ReadOnly); + m_ts.setCodec("UTF-8"); +} + +CsvParser::~CsvParser() { + m_csv.close(); +} + +bool CsvParser::isFileLoaded() { + return m_isFileLoaded; +} + +bool CsvParser::reparse() { + reset(); + return parseFile(); +} + + +bool CsvParser::parse(QFile *device) { + clear(); + if (nullptr == device) { + m_statusMsg += QObject::tr("NULL device\n"); + return false; + } + if (!readFile(device)) { + return false; + } + return parseFile(); +} + +bool CsvParser::readFile(QFile *device) { + if (device->isOpen()) { + device->close(); + } + + device->open(QIODevice::ReadOnly); + if (!Tools::readAllFromDevice(device, m_array)) { + m_statusMsg += QObject::tr("Error reading from device\n"); + m_isFileLoaded = false; + } + else { + device->close(); + + m_array.replace("\r\n", "\n"); + m_array.replace("\r", "\n"); + if (0 == m_array.size()) { + m_statusMsg += QObject::tr("File empty\n"); + } + m_isFileLoaded = true; + } + return m_isFileLoaded; +} + +void CsvParser::reset() { + m_ch = 0; + m_currCol = 1; + m_currRow = 1; + m_isEof = false; + m_isGood = true; + m_lastPos = -1; + m_maxCols = 0; + m_statusMsg = ""; + m_ts.seek(0); + m_table.clear(); + //the following are users' concern :) + //m_comment = '#'; + //m_backslashSyntax = false; + //m_comment = '#'; + //m_qualifier = '"'; + //m_separator = ','; +} + +void CsvParser::clear() { + reset(); + m_isFileLoaded = false; + m_array.clear(); +} + +bool CsvParser::parseFile() { + parseRecord(); + while (!m_isEof) + { + if (!skipEndline()) { + appendStatusMsg(QObject::tr("malformed string")); + } + m_currRow++; + m_currCol = 1; + parseRecord(); + } + fillColumns(); + return m_isGood; +} + +void CsvParser::parseRecord() { + csvrow row; + if (isComment()) { + skipLine(); + return; + } + else { + do { + parseField(row); + getChar(m_ch); + } while (isSeparator(m_ch) && !m_isEof); + + if (!m_isEof) { + ungetChar(); + } + if (isEmptyRow(row)) { + row.clear(); + return; + } + m_table.push_back(row); + if (m_maxCols < row.size()) { + m_maxCols = row.size(); + } + m_currCol++; + } +} + +void CsvParser::parseField(csvrow& row) { + QString field; + peek(m_ch); + if (!isTerminator(m_ch)) + { + if (isQualifier(m_ch)) { + parseQuoted(field); + } + else { + parseSimple(field); + } + } + row.push_back(field); +} + +void CsvParser::parseSimple(QString &s) { + QChar c; + getChar(c); + while ((isText(c)) && (!m_isEof)) + { + s.append(c); + getChar(c); + } + if (!m_isEof) { + ungetChar(); + } +} + +void CsvParser::parseQuoted(QString &s) { + //read and discard initial qualifier (e.g. quote) + getChar(m_ch); + parseEscaped(s); + //getChar(m_ch); + if (!isQualifier(m_ch)) { + appendStatusMsg(QObject::tr("missing closing quote")); + } +} + +void CsvParser::parseEscaped(QString &s) { + parseEscapedText(s); + while (processEscapeMark(s, m_ch)) { + parseEscapedText(s); + } + if (!m_isEof) { + ungetChar(); + } +} + +void CsvParser::parseEscapedText(QString &s) { + getChar(m_ch); + while ((!isQualifier(m_ch)) && !m_isEof) + { + s.append(m_ch); + getChar(m_ch); + } +} + +bool CsvParser::processEscapeMark(QString &s, QChar c) { + QChar buf; + peek(buf); + QChar c2; + //escape-character syntax, e.g. \" + if (true == m_isBackslashSyntax) + { + if (c != '\\') { + return false; + } + //consume (and append) second qualifier + getChar(c2); + if (m_isEof){ + c2='\\'; + s.append('\\'); + return false; + } + else { + s.append(c2); + return true; + } + } + //double quote syntax, e.g. "" + else + { + if (!isQualifier(c)) { + return false; + } + peek(c2); + if (!m_isEof) { //not EOF, can read one char + if (isQualifier(c2)) { + s.append(c2); + getChar(c2); + return true; + } + } + return false; + } +} + +void CsvParser::fillColumns() { + //fill the rows with lesser columns with empty fields + + for (int i=0; i 0) { + csvrow r = m_table.at(i); + for (int j=0; j> c; + } +} + +void CsvParser::ungetChar() { + if (!m_ts.seek(m_lastPos)) + m_statusMsg += QObject::tr("Internal: unget lower bound exceeded"); +} + +void CsvParser::peek(QChar& c) { + getChar(c); + if (!m_isEof) { + ungetChar(); + } +} + +bool CsvParser::isQualifier(const QChar c) const { + if (true == m_isBackslashSyntax && (c != m_qualifier)) { + return (c == '\\'); + } + else { + return (c == m_qualifier); + } +} + +bool CsvParser::isComment() { + bool result = false; + QChar c2; + qint64 pos = m_ts.pos(); + + do { + getChar(c2); + } while ((isSpace(c2) || isTab(c2)) && (!m_isEof)); + + if (c2 == m_comment) { + result = true; + } + m_ts.seek(pos); + return result; +} + +bool CsvParser::isText(QChar c) const { + return !( (isCRLF(c)) || (isSeparator(c)) ); +} + +bool CsvParser::isEmptyRow(csvrow row) const { + csvrow::const_iterator it = row.constBegin(); + for (; it != row.constEnd(); ++it) { + if ( ((*it) != "\n") && ((*it) != "") ) + return false; + } + return true; +} + +bool CsvParser::isCRLF(const QChar c) const { + return (c == '\n'); +} + +bool CsvParser::isSpace(const QChar c) const { + return (c == 0x20); +} + +bool CsvParser::isTab(const QChar c) const { + return (c == '\t'); +} + +bool CsvParser::isSeparator(const QChar c) const { + return (c == m_separator); +} + +bool CsvParser::isTerminator(const QChar c) const { + return (isSeparator(c) || (c == '\n') || (c == '\r')); +} + +void CsvParser::setBackslashSyntax(bool set) { + m_isBackslashSyntax = set; +} + +void CsvParser::setComment(const QChar c) { + m_comment = c.unicode(); +} + +void CsvParser::setCodec(const QString s) { + m_ts.setCodec(QTextCodec::codecForName(s.toLocal8Bit())); +} + +void CsvParser::setFieldSeparator(const QChar c) { + m_separator = c.unicode(); +} + +void CsvParser::setTextQualifier(const QChar c) { + m_qualifier = c.unicode(); +} + +int CsvParser::getFileSize() const { + return m_csv.size(); +} + +const csvtable CsvParser::getCsvTable() const { + return m_table; +} + +QString CsvParser::getStatus() const { + return m_statusMsg; +} + +int CsvParser::getCsvCols() const { + if ((m_table.size() > 0) && (m_table.at(0).size() > 0)) + return m_table.at(0).size(); + else return 0; +} + +int CsvParser::getCsvRows() const { + return m_table.size(); +} + + +void CsvParser::appendStatusMsg(QString s) { + m_statusMsg += s + .append(" @" + QString::number(m_currRow)) + .append(",") + .append(QString::number(m_currCol)) + .append("\n"); + m_isGood = false; +} diff --git a/src/core/CsvParser.h b/src/core/CsvParser.h new file mode 100644 index 000000000..77c6d36e1 --- /dev/null +++ b/src/core/CsvParser.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * 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 . + */ + +#ifndef KEEPASSX_CSVPARSER_H +#define KEEPASSX_CSVPARSER_H + +#include +#include +#include +#include + +typedef QStringList csvrow; +typedef QList csvtable; + +class CsvParser { + +public: + CsvParser(); + ~CsvParser(); + //read data from device and parse it + bool parse(QFile *device); + bool isFileLoaded(); + //reparse the same buffer (device is not opened again) + bool reparse(); + void setCodec(const QString s); + void setComment(const QChar c); + void setFieldSeparator(const QChar c); + void setTextQualifier(const QChar c); + void setBackslashSyntax(bool set); + int getFileSize() const; + int getCsvRows() const; + int getCsvCols() const; + QString getStatus() const; + const csvtable getCsvTable() const; + +protected: + csvtable m_table; + +private: + QByteArray m_array; + QBuffer m_csv; + QChar m_ch; + QChar m_comment; + unsigned int m_currCol; + unsigned int m_currRow; + bool m_isBackslashSyntax; + bool m_isEof; + bool m_isFileLoaded; + bool m_isGood; + qint64 m_lastPos; + int m_maxCols; + QChar m_qualifier; + QChar m_separator; + QString m_statusMsg; + QTextStream m_ts; + + void getChar(QChar &c); + void ungetChar(); + void peek(QChar &c); + void fillColumns(); + bool isTerminator(const QChar c) const; + bool isSeparator(const QChar c) const; + bool isQualifier(const QChar c) const; + bool processEscapeMark(QString &s, QChar c); + bool isText(QChar c) const; + bool isComment(); + bool isCRLF(const QChar c) const; + bool isSpace(const QChar c) const; + bool isTab(const QChar c) const; + bool isEmptyRow(csvrow row) const; + bool parseFile(); + void parseRecord(); + void parseField(csvrow& row); + void parseSimple(QString& s); + void parseQuoted(QString& s); + void parseEscaped(QString& s); + void parseEscapedText(QString &s); + bool readFile(QFile *device); + void reset(); + void clear(); + bool skipEndline(); + void skipLine(); + void appendStatusMsg(QString s); +}; + +#endif //CSVPARSER_H + diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 910e94899..e3f4bc188 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -212,6 +212,23 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, Q_EMIT messageDismissGlobal(); } +void DatabaseTabWidget::importCsv() +{ + QString fileName = fileDialog()->getOpenFileName(this, tr("Open CSV file"), QString(), + tr("CSV file") + " (*.csv);;" + tr("All files (*)")); + + if (fileName.isEmpty()) { + return; + } + + Database* db = new Database(); + DatabaseManagerStruct dbStruct; + dbStruct.dbWidget = new DatabaseWidget(db, this); + + insertDatabase(db, dbStruct); + dbStruct.dbWidget->switchToImportCsv(fileName); +} + void DatabaseTabWidget::mergeDatabase() { QString filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files")); diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 8f01a987d..d48e0e9d0 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -66,6 +66,7 @@ public: public Q_SLOTS: void newDatabase(); void openDatabase(); + void importCsv(); void mergeDatabase(); void importKeePass1Database(); bool saveDatabase(int index = -1); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 1cb1882d3..2a88d8a1a 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -122,6 +122,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) m_editGroupWidget->setObjectName("editGroupWidget"); m_changeMasterKeyWidget = new 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); @@ -145,6 +147,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) addWidget(m_databaseSettingsWidget); addWidget(m_historyEditEntryWidget); addWidget(m_databaseOpenWidget); + addWidget(m_csvImportWizard); addWidget(m_databaseOpenMergeWidget); addWidget(m_keepass1OpenWidget); addWidget(m_unlockDatabaseWidget); @@ -165,6 +168,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_databaseOpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool))); connect(m_databaseOpenMergeWidget, SIGNAL(editFinished(bool)), SLOT(mergeDatabase(bool))); connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool))); + connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool))); connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool))); connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool))); connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged())); @@ -624,6 +628,16 @@ void DatabaseWidget::setCurrentWidget(QWidget* widget) adjustSize(); } +void DatabaseWidget::csvImportFinished(bool accepted) +{ + if (!accepted) { + Q_EMIT closeRequest(); + } + else { + setCurrentWidget(m_mainWidget); + } +} + void DatabaseWidget::switchToView(bool accepted) { if (m_newGroup) { @@ -844,12 +858,21 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString m_databaseOpenWidget->enterKey(password, keyFile); } +void DatabaseWidget::switchToImportCsv(const QString& fileName) +{ + updateFilename(fileName); + switchToMasterKeyChange(); + m_csvImportWizard->load(fileName, m_db); + setCurrentWidget(m_csvImportWizard); +} + void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName) { m_databaseOpenMergeWidget->load(fileName); setCurrentWidget(m_databaseOpenMergeWidget); } + void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName, const QString& password, const QString& keyFile) { diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 66ece0537..87d14a182 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include "gui/entry/EntryModel.h" #include "gui/MessageWidget.h" +#include "gui/csvImport/CsvImportWizard.h" class ChangeMasterKeyWidget; class DatabaseOpenWidget; @@ -143,6 +144,8 @@ public Q_SLOTS: void switchToDatabaseSettings(); void switchToOpenDatabase(const QString& fileName); void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile); + void switchToImportCsv(const QString& fileName); + void csvImportFinished(bool accepted); void switchToOpenMergeDatabase(const QString& fileName); void switchToOpenMergeDatabase(const QString& fileName, const QString& password, const QString& keyFile); void switchToImportKeepass1(const QString& fileName); @@ -187,6 +190,7 @@ private: EditEntryWidget* m_historyEditEntryWidget; EditGroupWidget* m_editGroupWidget; ChangeMasterKeyWidget* m_changeMasterKeyWidget; + CsvImportWizard* m_csvImportWizard; DatabaseSettingsWidget* m_databaseSettingsWidget; DatabaseOpenWidget* m_databaseOpenWidget; DatabaseOpenWidget* m_databaseOpenMergeWidget; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 763ada1ae..aa862465d 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -250,6 +250,8 @@ MainWindow::MainWindow() SLOT(changeMasterKey())); connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget, SLOT(changeDatabaseSettings())); + connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget, + SLOT(importCsv())); connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget, SLOT(importKeePass1Database())); connect(m_ui->actionRepairDatabase, SIGNAL(triggered()), this, @@ -487,7 +489,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); - m_ui->actionImportKeePass1->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); + m_ui->menuImport->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); m_ui->actionDatabaseMerge->setEnabled(inDatabaseTabWidget); m_ui->actionRepairDatabase->setEnabled(inDatabaseTabWidgetOrWelcomeWidget); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 6e3ecf684..7a029eeef 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -176,6 +176,13 @@ &Recent databases + + + Import + + + + @@ -187,7 +194,7 @@ - + @@ -394,11 +401,6 @@ Database settings - - - &Import KeePass 1 database - - false @@ -506,6 +508,16 @@ &Export to CSV file + + + Import KeePass 1 database + + + + + Import CSV file + + Re&pair database diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp new file mode 100644 index 000000000..9e53d5af3 --- /dev/null +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * 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 . + */ + +#include "CsvImportWidget.h" +#include "ui_CsvImportWidget.h" +#include "gui/MessageBox.h" + +#include +#include + +//I wanted to make the CSV import GUI future-proof, so if one day you need entries +//to have a new field, all you have to do is uncomment a row or two here, and the GUI will follow: +//dynamic generation of comboBoxes, labels, placement and so on. Try it for immense fun! +const QStringList CsvImportWidget::m_columnheader = QStringList() + << QObject::tr("Group") + << QObject::tr("Title") + << QObject::tr("Username") + << QObject::tr("Password") + << QObject::tr("URL") + << QObject::tr("Notes") +// << QObject::tr("Future field1") +// << QObject::tr("Future field2") +// << QObject::tr("Future field3") + ; + +CsvImportWidget::CsvImportWidget(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::CsvImportWidget()) + , m_parserModel(new CsvParserModel(this)) + , m_comboModel(new QStringListModel(this)) + , m_comboMapper(new QSignalMapper(this)) +{ + m_ui->setupUi(this); + + QFont font = m_ui->labelHeadline->font(); + font.setBold(true); + font.setPointSize(font.pointSize() + 2); + m_ui->labelHeadline->setFont(font); + + m_ui->comboBoxCodec->addItems(QStringList() <<"UTF-8" <<"Windows-1252" <<"UTF-16" <<"UTF-16LE"); + m_ui->comboBoxFieldSeparator->addItems(QStringList() <<"," <<";" <<"-" <<":" <<"."); + m_ui->comboBoxTextQualifier->addItems(QStringList() <<"\"" <<"'" <<":" <<"." <<"|"); + m_ui->comboBoxComment->addItems(QStringList() <<"#" <<";" <<":" <<"@"); + + m_ui->tableViewFields->setSelectionMode(QAbstractItemView::NoSelection); + m_ui->tableViewFields->setFocusPolicy(Qt::NoFocus); + + for (int i=0; isetFixedWidth(label->minimumSizeHint().width()); + font = label->font(); + font.setBold(false); + label->setFont(font); + + QComboBox* combo = new QComboBox(this); + font = combo->font(); + font.setBold(false); + combo->setFont(font); + m_combos.append(combo); + combo->setModel(m_comboModel); + m_comboMapper->setMapping(combo, i); + connect(combo, SIGNAL(currentIndexChanged(int)), m_comboMapper, SLOT(map())); + + //layout labels and combo fields in column-first order + int combo_rows = 1+(m_columnheader.count()-1)/2; + int x=i%combo_rows; + int y= 2*(i/combo_rows); + m_ui->gridLayout_combos->addWidget(label, x, y); + m_ui->gridLayout_combos->addWidget(combo, x, y+1); + } + + m_parserModel->setHeaderLabels(m_columnheader); + m_ui->tableViewFields->setModel(m_parserModel); + + connect(m_ui->spinBoxSkip, SIGNAL(valueChanged(int)), SLOT(skippedChanged(int))); + connect(m_ui->comboBoxCodec, SIGNAL(currentIndexChanged(int)), SLOT(parse())); + connect(m_ui->comboBoxTextQualifier, SIGNAL(currentIndexChanged(int)), SLOT(parse())); + connect(m_ui->comboBoxComment, SIGNAL(currentIndexChanged(int)), SLOT(parse())); + connect(m_ui->comboBoxFieldSeparator, SIGNAL(currentIndexChanged(int)), SLOT(parse())); + connect(m_ui->checkBoxBackslash, SIGNAL(toggled(bool)), SLOT(parse())); + connect(m_ui->pushButtonWarnings, SIGNAL(clicked()), this, SLOT(showReport())); + connect(m_comboMapper, SIGNAL(mapped(int)), this, SLOT(comboChanged(int))); + + connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(writeDatabase())); + connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +void CsvImportWidget::comboChanged(int comboId) { + QComboBox* currentSender = qobject_cast(m_comboMapper->mapping(comboId)); + if (currentSender->currentIndex() != -1) { + //here is the line that actually updates the GUI table + m_parserModel->mapColumns(currentSender->currentIndex(), comboId); + } + updateTableview(); +} + +void CsvImportWidget::skippedChanged(int rows) { + m_parserModel->setSkippedRows(rows); + updateTableview(); +} + +CsvImportWidget::~CsvImportWidget() {} + +void CsvImportWidget::configParser() { + m_parserModel->setBackslashSyntax(m_ui->checkBoxBackslash->isChecked()); + m_parserModel->setComment(m_ui->comboBoxComment->currentText().at(0)); + m_parserModel->setTextQualifier(m_ui->comboBoxTextQualifier->currentText().at(0)); + m_parserModel->setCodec(m_ui->comboBoxCodec->currentText()); + m_parserModel->setFieldSeparator(m_ui->comboBoxFieldSeparator->currentText().at(0)); +} + +void CsvImportWidget::updateTableview() { + m_ui->tableViewFields->resizeRowsToContents(); + m_ui->tableViewFields->resizeColumnsToContents(); + + for (int c=0; ctableViewFields->horizontalHeader()->count(); ++c) { + m_ui->tableViewFields->horizontalHeader()->setSectionResizeMode( + c, QHeaderView::Stretch); + } +} + +void CsvImportWidget::updatePreview() { + + m_ui->labelSizeRowsCols->setText(m_parserModel->getFileInfo()); + m_ui->spinBoxSkip->setValue(0); + m_ui->spinBoxSkip->setMaximum(m_parserModel->rowCount()-1); + + int i; + QStringList list(tr("Not present in CSV file")); + + for (i=1; igetCsvCols(); i++) { + QString s = QString(tr("Column ")) + QString::number(i); + list << s; + } + m_comboModel->setStringList(list); + + i=1; + Q_FOREACH (QComboBox* b, m_combos) { + if (i < m_parserModel->getCsvCols()) { + b->setCurrentIndex(i); + } + else { + b->setCurrentIndex(0); + } + ++i; + } +} + +void CsvImportWidget::load(const QString& filename, Database* const db) { + m_db = db; + m_parserModel->setFilename(filename); + m_ui->labelFilename->setText(filename); + Group* group = m_db->rootGroup(); + group->setUuid(Uuid::random()); + group->setNotes(tr("Imported from CSV file\nOriginal data: ") + filename); + + parse(); +} + +void CsvImportWidget::parse() { + configParser(); + QApplication::setOverrideCursor(Qt::WaitCursor); + bool good = m_parserModel->parse(); + QApplication::restoreOverrideCursor(); + updatePreview(); + m_ui->pushButtonWarnings->setEnabled(!good); +} + +void CsvImportWidget::showReport() { + MessageBox::warning(this, tr("Syntax error"), tr("While parsing file...\n") + .append(m_parserModel->getStatus()), QMessageBox::Ok, QMessageBox::Ok); +} + +void CsvImportWidget::writeDatabase() { + + checkGroupNames(); + for (int r=0; rrowCount(); r++) { + //use the validity of second column as a GO/NOGO hint for all others fields + if (m_parserModel->data(m_parserModel->index(r, 1)).isValid()) { + Entry* entry = new Entry(); + entry->setUuid(Uuid::random()); + entry->setGroup(splitGroups(m_parserModel->data(m_parserModel->index(r, 0)).toString())); + entry->setTitle( m_parserModel->data(m_parserModel->index(r, 1)).toString()); + entry->setUsername( m_parserModel->data(m_parserModel->index(r, 2)).toString()); + entry->setPassword( m_parserModel->data(m_parserModel->index(r, 3)).toString()); + entry->setUrl( m_parserModel->data(m_parserModel->index(r, 4)).toString()); + entry->setNotes( m_parserModel->data(m_parserModel->index(r, 5)).toString()); + } + } + + QBuffer buffer; + buffer.open(QBuffer::ReadWrite); + + KeePass2Writer writer; + writer.writeDatabase(&buffer, m_db); + if (writer.hasError()) { + MessageBox::warning(this, tr("Error"), tr("CSV import: writer has errors:\n") + .append((writer.errorString())), QMessageBox::Ok, QMessageBox::Ok); + } + Q_EMIT editFinished(true); +} + + +void CsvImportWidget::checkGroupNames() { + QString groupLabel; + QStringList groupList; + bool is_root = false + , is_empty = false + , is_label = false; + for (int r=0; rrowCount(); r++) { + groupLabel = m_parserModel->data(m_parserModel->index(r, 0)).toString(); + //check if group name is either "root", "" (empty) or some other label + groupList = groupLabel.split("/", QString::SkipEmptyParts); + if (not groupList.first().compare("Root", Qt::CaseSensitive)) + is_root = true; + else if (not groupLabel.compare("")) + is_empty = true; + else + is_label = true; + groupList.clear(); + } + + if ((not is_label and (is_empty xor is_root)) + or (is_label and not is_root)) { + m_db->rootGroup()->setName("Root"); + } + else if ((is_empty and is_root) + or (is_label and not is_empty and is_root)) { + m_db->rootGroup()->setName("CSV IMPORTED"); + } + else { + //SHOULD NEVER GET HERE + m_db->rootGroup()->setName("ROOT_FALLBACK"); + } +} + +Group *CsvImportWidget::splitGroups(QString label) { + //extract group names from nested path provided in "label" + Group *current = m_db->rootGroup(); + QStringList groupList = label.split("/", QString::SkipEmptyParts); + + //skip the creation of a subgroup of Root with the same name + if (m_db->rootGroup()->name() == "Root" && groupList.first() == "Root") { + groupList.removeFirst(); + } + + for (const QString& groupName : groupList) { + Group *children = hasChildren(current, groupName); + if (children == nullptr) { + Group *brandNew = new Group(); + brandNew->setParent(current); + brandNew->setName(groupName); + current = brandNew; + } + else { + Q_ASSERT(children != nullptr); + current = children; + } + } + return current; +} + +Group* CsvImportWidget::hasChildren(Group* current, QString groupName) { + //returns the group whose name is "groupName" and is child of "current" group + for (Group * group : current->children()) { + if (group->name() == groupName) { + return group; + } + } + return nullptr; +} + +void CsvImportWidget::reject() { + Q_EMIT editFinished(false); +} diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h new file mode 100644 index 000000000..f8798b560 --- /dev/null +++ b/src/gui/csvImport/CsvImportWidget.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * 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 . + */ + +#ifndef KEEPASSX_CSVIMPORTWIDGET_H +#define KEEPASSX_CSVIMPORTWIDGET_H + +#include +#include +#include +#include +#include +#include +#include + +#include "format/KeePass2Writer.h" +#include "gui/csvImport/CsvParserModel.h" +#include "keys/PasswordKey.h" +#include "core/Metadata.h" + + +namespace Ui { + class CsvImportWidget; +} + +class CsvImportWidget : public QWidget +{ + Q_OBJECT + +public: + explicit CsvImportWidget(QWidget *parent = nullptr); + virtual ~CsvImportWidget(); + void load(const QString& filename, Database* const db); + +Q_SIGNALS: + void editFinished(bool accepted); + +private Q_SLOTS: + void parse(); + void showReport(); + void comboChanged(int comboId); + void skippedChanged(int rows); + void writeDatabase(); + void checkGroupNames(); + void reject(); + +private: + Q_DISABLE_COPY(CsvImportWidget) + const QScopedPointer m_ui; + CsvParserModel* const m_parserModel; + QStringListModel* const m_comboModel; + QSignalMapper* m_comboMapper; + QList m_combos; + Database *m_db; + + KeePass2Writer m_writer; + static const QStringList m_columnheader; + void configParser(); + void updatePreview(); + void updateTableview(); + Group* splitGroups(QString label); + Group* hasChildren(Group* current, QString groupName); +}; + +#endif // KEEPASSX_CSVIMPORTWIDGET_H diff --git a/src/gui/csvImport/CsvImportWidget.ui b/src/gui/csvImport/CsvImportWidget.ui new file mode 100644 index 000000000..5df2aa1af --- /dev/null +++ b/src/gui/csvImport/CsvImportWidget.ui @@ -0,0 +1,524 @@ + + + CsvImportWidget + + + + 0 + 0 + 779 + 691 + + + + + + + + + + + 0 + 137 + + + + + 75 + true + + + + Encoding + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Text is qualified by + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + + 50 + false + + + + Show parser warnings + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Codec + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + + 50 + false + + + + Fields are separated by + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + false + + + + Comments start with + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Treat '\' as escape character + + + + + + + + + + 50 + false + + + + Skip first + + + + + + + + 50 + false + + + + + + + + + 50 + false + + + + rows + + + + + + + + + + 50 + false + true + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + 75 + true + + + + Column layout + + + + + + 0 + + + + + + + + + + + + + 0 + 0 + + + + Import CSV fields + + + + + + + + 0 + 0 + + + + filename + + + + + + + + 0 + 0 + + + + size, rows, columns + + + + + + + + + + 0 + 0 + + + + + 0 + 200 + + + + + 75 + true + + + + Preview + + + false + + + + + + + 0 + 0 + + + + + 50 + false + + + + true + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 27 + + + + + + + + + diff --git a/src/gui/csvImport/CsvImportWizard.cpp b/src/gui/csvImport/CsvImportWizard.cpp new file mode 100644 index 000000000..f84a22959 --- /dev/null +++ b/src/gui/csvImport/CsvImportWizard.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * 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 . + */ + +#include "CsvImportWizard.h" +#include +#include +#include "gui/MessageBox.h" + + +CsvImportWizard::CsvImportWizard(QWidget *parent) + : DialogyWidget(parent) +{ + m_layout = new QGridLayout(this); + m_pages = new QStackedWidget(parent); + m_layout->addWidget(m_pages, 0, 0); + + m_pages->addWidget(key = new ChangeMasterKeyWidget(m_pages)); + m_pages->addWidget(parse = new CsvImportWidget(m_pages)); + key->headlineLabel()->setText(tr("Import CSV file")); + + connect(key, SIGNAL(editFinished(bool)), this, SLOT(keyFinished(bool))); + connect(parse, SIGNAL(editFinished(bool)), this, SLOT(parseFinished(bool))); +} + +CsvImportWizard::~CsvImportWizard() +{} + +void CsvImportWizard::load(const QString& filename, Database* database) +{ + m_db = database; + parse->load(filename, database); + key->clearForms(); +} + +void CsvImportWizard::keyFinished(bool accepted) +{ + if (!accepted) { + Q_EMIT(importFinished(false)); + return; + } + + m_pages->setCurrentIndex(m_pages->currentIndex()+1); + + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + bool result = m_db->setKey(key->newMasterKey()); + QApplication::restoreOverrideCursor(); + + if (!result) { + MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key")); + Q_EMIT importFinished(false); + return; + } +} + +void CsvImportWizard::parseFinished(bool accepted) +{ + Q_EMIT(importFinished(accepted)); +} diff --git a/src/gui/csvImport/CsvImportWizard.h b/src/gui/csvImport/CsvImportWizard.h new file mode 100644 index 000000000..5a83a19d8 --- /dev/null +++ b/src/gui/csvImport/CsvImportWizard.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * 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 . + */ + +#ifndef KEEPASSX_CSVIMPORTWIZARD_H +#define KEEPASSX_CSVIMPORTWIZARD_H + +#include +#include + +#include "CsvImportWidget.h" +#include "core/Database.h" +#include "gui/ChangeMasterKeyWidget.h" +#include "gui/DialogyWidget.h" + +class CsvImportWidget; + +class CsvImportWizard : public DialogyWidget +{ + Q_OBJECT + +public: + explicit CsvImportWizard(QWidget *parent = nullptr); + virtual ~CsvImportWizard(); + void load(const QString& filename, Database *database); + +Q_SIGNALS: + void importFinished(bool accepted); + +private Q_SLOTS: + void keyFinished(bool accepted); + void parseFinished(bool accepted); + +private: + Database* m_db; + CsvImportWidget* parse; + ChangeMasterKeyWidget* key; + QStackedWidget *m_pages; + QGridLayout *m_layout; +}; + +#endif //KEEPASSX_CSVIMPORTWIZARD_H diff --git a/src/gui/csvImport/CsvParserModel.cpp b/src/gui/csvImport/CsvParserModel.cpp new file mode 100644 index 000000000..3bc6c834c --- /dev/null +++ b/src/gui/csvImport/CsvParserModel.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * 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 . + */ + +#include "CsvParserModel.h" + +CsvParserModel::CsvParserModel(QObject *parent) + : QAbstractTableModel(parent) + , m_skipped(0) +{} + +CsvParserModel::~CsvParserModel() +{} + +void CsvParserModel::setFilename(const QString& filename) { + m_filename = filename; +} + +QString CsvParserModel::getFileInfo(){ + QString a(QString::number(getFileSize()).append(tr(" byte, "))); + a.append(QString::number(getCsvRows())).append(tr(" rows, ")); + a.append(QString::number((getCsvCols()-1))).append(tr(" columns")); + return a; +} + +bool CsvParserModel::parse() { + bool r; + beginResetModel(); + m_columnMap.clear(); + if (CsvParser::isFileLoaded()) { + r = CsvParser::reparse(); + } + else { + QFile csv(m_filename); + r = CsvParser::parse(&csv); + } + for (int i=0; i= getCsvCols()) { + m_columnMap[dbColumn] = 0; //map to the empty column + } + else { + m_columnMap[dbColumn] = csvColumn; + } + endResetModel(); +} + +void CsvParserModel::setSkippedRows(int skipped) { + m_skipped = skipped; + QModelIndex topLeft = createIndex(skipped,0); + QModelIndex bottomRight = createIndex(m_skipped+rowCount(), columnCount()); + Q_EMIT dataChanged(topLeft, bottomRight); + Q_EMIT layoutChanged(); +} + +void CsvParserModel::setHeaderLabels(QStringList l) { + m_columnHeader = l; +} + +int CsvParserModel::rowCount(const QModelIndex &parent) const { + if (parent.isValid()) { + return 0; + } + return getCsvRows(); +} + +int CsvParserModel::columnCount(const QModelIndex &parent) const { + if (parent.isValid()) { + return 0; + } + return m_columnHeader.size(); +} + +QVariant CsvParserModel::data(const QModelIndex &index, int role) const { + if ( (index.column() >= m_columnHeader.size()) + || (index.row()+m_skipped >= rowCount()) + || !index.isValid() ) + { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return m_table.at(index.row()+m_skipped).at(m_columnMap[index.column()]); + } + return QVariant(); +} + +QVariant CsvParserModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role == Qt::DisplayRole) { + if (orientation == Qt::Horizontal) { + if ( (section < 0) || (section >= m_columnHeader.size())) { + return QVariant(); + } + return m_columnHeader.at(section); + } + else if (orientation == Qt::Vertical) { + if (section+m_skipped >= rowCount()) { + return QVariant(); + } + return QString::number(section+1); + } + } + return QVariant(); +} + + diff --git a/src/gui/csvImport/CsvParserModel.h b/src/gui/csvImport/CsvParserModel.h new file mode 100644 index 000000000..ae7bf6e20 --- /dev/null +++ b/src/gui/csvImport/CsvParserModel.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * 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 . + */ + +#ifndef KEEPASSX_CSVPARSERMODEL_H +#define KEEPASSX_CSVPARSERMODEL_H + +#include +#include +#include "core/Group.h" +#include "core/CsvParser.h" + +class CsvParserModel : public QAbstractTableModel, public CsvParser +{ + Q_OBJECT + +public: + explicit CsvParserModel(QObject *parent = nullptr); + virtual ~CsvParserModel(); + void setFilename(const QString& filename); + QString getFileInfo(); + bool parse(); + + void setHeaderLabels(QStringList l); + void mapColumns(int csvColumn, int dbColumn); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + +public Q_SLOTS: + void setSkippedRows(int skipped); + +private: + int m_skipped; + QString m_filename; + QStringList m_columnHeader; + //first column of model must be empty (aka combobox row "Not present in CSV file") + void addEmptyColumn(); + //mapping CSV columns to keepassx columns + QMap m_columnMap; +}; + +#endif //KEEPASSX_CSVPARSERMODEL_H + diff --git a/src/main.cpp b/src/main.cpp index 0618cefae..ab8177f74 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,7 @@ #include "crypto/Crypto.h" #include "gui/Application.h" #include "gui/MainWindow.h" +#include "gui/csvImport/CsvImportWizard.h" #include "gui/MessageBox.h" #ifdef QT_STATIC diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5840a5b4b..4f64f4c65 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -154,6 +154,9 @@ endif() add_unit_test(NAME testentry SOURCES TestEntry.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testcsvparser SOURCES TestCsvParser.cpp + LIBS ${TEST_LIBRARIES}) + add_unit_test(NAME testrandom SOURCES TestRandom.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestCsvParser.cpp b/tests/TestCsvParser.cpp new file mode 100644 index 000000000..9ff93f025 --- /dev/null +++ b/tests/TestCsvParser.cpp @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2015 Enrico Mariotti + * + * 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 . + */ + +#include "TestCsvParser.h" +#include + +QTEST_GUILESS_MAIN(TestCsvParser) + +void TestCsvParser::initTestCase() +{ + parser = new CsvParser(); +} + +void TestCsvParser::cleanupTestCase() +{ + delete parser; +} + +void TestCsvParser::init() +{ + file.setFileName("/tmp/keepassXn94do1x.csv"); + if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) + QFAIL("Cannot open file!"); + parser->setBackslashSyntax(false); + parser->setComment('#'); + parser->setFieldSeparator(','); + parser->setTextQualifier(QChar('"')); +} + +void TestCsvParser::cleanup() +{ + file.close(); +} + +/****************** TEST CASES ******************/ +void TestCsvParser::testMissingQuote() { + parser->setTextQualifier(':'); + QTextStream out(&file); + out << "A,B\n:BM,1"; + QEXPECT_FAIL("", "Bad format", Continue); + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QWARN(parser->getStatus().toLatin1()); +} + +void TestCsvParser::testMalformed() { + parser->setTextQualifier(':'); + QTextStream out(&file); + out << "A,B,C\n:BM::,1,:2:"; + QEXPECT_FAIL("", "Bad format", Continue); + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QWARN(parser->getStatus().toLatin1()); +} + +void TestCsvParser::testBackslashSyntax() { + parser->setBackslashSyntax(true); + parser->setTextQualifier(QChar('X')); + QTextStream out(&file); + //attended result: one"\t\"wo + out << "Xone\\\"\\\\t\\\\\\\"w\noX\n" + << "X13X,X2\\X,X,\"\"3\"X\r" + << "3,X\"4\"X,,\n" + << "XX\n" + << "\\"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.at(0).at(0) == "one\"\\t\\\"w\no"); + QVERIFY(t.at(1).at(0) == "13"); + QVERIFY(t.at(1).at(1) == "2X,"); + QVERIFY(t.at(1).at(2) == "\"\"3\"X"); + QVERIFY(t.at(2).at(0) == "3"); + QVERIFY(t.at(2).at(1) == "\"4\""); + QVERIFY(t.at(2).at(2) == ""); + QVERIFY(t.at(2).at(3) == ""); + QVERIFY(t.at(3).at(0) == "\\"); + QVERIFY(t.size() == 4); +} + +void TestCsvParser::testQuoted() { + QTextStream out(&file); + out << "ro,w,\"end, of \"\"\"\"\"\"row\"\"\"\"\"\n" + << "2\n"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.at(0).at(0) == "ro"); + QVERIFY(t.at(0).at(1) == "w"); + QVERIFY(t.at(0).at(2) == "end, of \"\"\"row\"\""); + QVERIFY(t.at(1).at(0) == "2"); + QVERIFY(t.size() == 2); +} + +void TestCsvParser::testEmptySimple() { + QTextStream out(&file); + out <<""; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 0); +} + +void TestCsvParser::testEmptyQuoted() { + QTextStream out(&file); + out <<"\"\""; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 0); +} + +void TestCsvParser::testEmptyNewline() { + QTextStream out(&file); + out <<"\"\n\""; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 0); +} + +void TestCsvParser::testEmptyFile() +{ + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 0); +} + +void TestCsvParser::testNewline() +{ + QTextStream out(&file); + out << "1,2\n\n\n"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 1); + QVERIFY(t.at(0).at(0) == "1"); + QVERIFY(t.at(0).at(1) == "2"); +} + +void TestCsvParser::testCR() +{ + QTextStream out(&file); + out << "1,2\r3,4"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 2); + QVERIFY(t.at(0).at(0) == "1"); + QVERIFY(t.at(0).at(1) == "2"); + QVERIFY(t.at(1).at(0) == "3"); + QVERIFY(t.at(1).at(1) == "4"); +} + +void TestCsvParser::testLF() +{ + QTextStream out(&file); + out << "1,2\n3,4"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 2); + QVERIFY(t.at(0).at(0) == "1"); + QVERIFY(t.at(0).at(1) == "2"); + QVERIFY(t.at(1).at(0) == "3"); + QVERIFY(t.at(1).at(1) == "4"); +} + +void TestCsvParser::testCRLF() +{ + QTextStream out(&file); + out << "1,2\r\n3,4"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 2); + QVERIFY(t.at(0).at(0) == "1"); + QVERIFY(t.at(0).at(1) == "2"); + QVERIFY(t.at(1).at(0) == "3"); + QVERIFY(t.at(1).at(1) == "4"); +} + +void TestCsvParser::testComments() +{ + QTextStream out(&file); + out << " #one\n" + << " \t # two, three \r\n" + << " #, sing\t with\r" + << " #\t me!\n" + << "useful,text #1!"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 1); + QVERIFY(t.at(0).at(0) == "useful"); + QVERIFY(t.at(0).at(1) == "text #1!"); +} + +void TestCsvParser::testColumns() { + QTextStream out(&file); + out << "1,2\n" + << ",,,,,,,,,a\n" + << "a,b,c,d\n"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(parser->getCsvCols() == 10); +} + +void TestCsvParser::testSimple() { + QTextStream out(&file); + out << ",,2\r,2,3\n" + << "A,,B\"\n" + << " ,,\n"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 4); + QVERIFY(t.at(0).at(0) == ""); + QVERIFY(t.at(0).at(1) == ""); + QVERIFY(t.at(0).at(2) == "2"); + QVERIFY(t.at(1).at(0) == ""); + QVERIFY(t.at(1).at(1) == "2"); + QVERIFY(t.at(1).at(2) == "3"); + QVERIFY(t.at(2).at(0) == "A"); + QVERIFY(t.at(2).at(1) == ""); + QVERIFY(t.at(2).at(2) == "B\""); + QVERIFY(t.at(3).at(0) == " "); + QVERIFY(t.at(3).at(1) == ""); + QVERIFY(t.at(3).at(2) == ""); +} + +void TestCsvParser::testSeparator() { + parser->setFieldSeparator('\t'); + QTextStream out(&file); + out << "\t\t2\r\t2\t3\n" + << "A\t\tB\"\n" + << " \t\t\n"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 4); + QVERIFY(t.at(0).at(0) == ""); + QVERIFY(t.at(0).at(1) == ""); + QVERIFY(t.at(0).at(2) == "2"); + QVERIFY(t.at(1).at(0) == ""); + QVERIFY(t.at(1).at(1) == "2"); + QVERIFY(t.at(1).at(2) == "3"); + QVERIFY(t.at(2).at(0) == "A"); + QVERIFY(t.at(2).at(1) == ""); + QVERIFY(t.at(2).at(2) == "B\""); + QVERIFY(t.at(3).at(0) == " "); + QVERIFY(t.at(3).at(1) == ""); + QVERIFY(t.at(3).at(2) == ""); +} + +void TestCsvParser::testMultiline() +{ + parser->setTextQualifier(QChar(':')); + QTextStream out(&file); + out << ":1\r\n2a::b:,:3\r4:\n" + << "2\n"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.at(0).at(0) == "1\n2a:b"); + QVERIFY(t.at(0).at(1) == "3\n4"); + QVERIFY(t.at(1).at(0) == "2"); + QVERIFY(t.size() == 2); +} + +void TestCsvParser::testEmptyReparsing() +{ + parser->parse(nullptr); + QVERIFY(parser->reparse()); + t = parser->getCsvTable(); + QVERIFY(t.size() == 0); +} + +void TestCsvParser::testReparsing() +{ + QTextStream out(&file); + out << ":te\r\nxt1:,:te\rxt2:,:end of \"this\n string\":\n" + << "2\n"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + + QEXPECT_FAIL("", "Wrong qualifier", Continue); + QVERIFY(t.at(0).at(0) == "te\nxt1"); + + parser->setTextQualifier(QChar(':')); + + QVERIFY(parser->reparse()); + t = parser->getCsvTable(); + QVERIFY(t.at(0).at(0) == "te\nxt1"); + QVERIFY(t.at(0).at(1) == "te\nxt2"); + QVERIFY(t.at(0).at(2) == "end of \"this\n string\""); + QVERIFY(t.at(1).at(0) == "2"); + QVERIFY(t.size() == 2); +} + +void TestCsvParser::testQualifier() { + parser->setTextQualifier(QChar('X')); + QTextStream out(&file); + out << "X1X,X2XX,X,\"\"3\"\"\"X\r" + << "3,X\"4\"X,,\n"; + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 2); + QVERIFY(t.at(0).at(0) == "1"); + QVERIFY(t.at(0).at(1) == "2X,"); + QVERIFY(t.at(0).at(2) == "\"\"3\"\"\"X"); + QVERIFY(t.at(1).at(0) == "3"); + QVERIFY(t.at(1).at(1) == "\"4\""); + QVERIFY(t.at(1).at(2) == ""); + QVERIFY(t.at(1).at(3) == ""); +} + +void TestCsvParser::testUnicode() { + //QString m("Texte en fran\u00e7ais"); + //CORRECT QString g("\u20AC"); + //CORRECT QChar g(0x20AC); + //ERROR QChar g("\u20AC"); + parser->setFieldSeparator(QChar('A')); + QTextStream out(&file); + out << QString("€1A2śA\"3śAż\"Ażac"); + + QVERIFY(parser->parse(&file)); + t = parser->getCsvTable(); + QVERIFY(t.size() == 1); + QVERIFY(t.at(0).at(0) == "€1"); + QVERIFY(t.at(0).at(1) == "2ś"); + QVERIFY(t.at(0).at(2) == "3śAż"); + QVERIFY(t.at(0).at(3) == "żac"); +} diff --git a/tests/TestCsvParser.h b/tests/TestCsvParser.h new file mode 100644 index 000000000..efdd96ab0 --- /dev/null +++ b/tests/TestCsvParser.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 Enrico Mariotti + * + * 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 . + */ + +#ifndef KEEPASSX_TESTCSVPARSER_H +#define KEEPASSX_TESTCSVPARSER_H + +#include +#include + +#include "core/CsvParser.h" + +class CsvParser; + +class TestCsvParser : public QObject +{ + Q_OBJECT + +public: + +private Q_SLOTS: + void init(); + void cleanup(); + void initTestCase(); + void cleanupTestCase(); + + void testUnicode(); + void testLF(); + void testEmptyReparsing(); + void testSimple(); + void testEmptyQuoted(); + void testEmptyNewline(); + void testSeparator(); + void testCR(); + void testCRLF(); + void testMalformed(); + void testQualifier(); + void testNewline(); + void testEmptySimple(); + void testMissingQuote(); + void testComments(); + void testBackslashSyntax(); + void testReparsing(); + void testEmptyFile(); + void testQuoted(); + void testMultiline(); + void testColumns(); + +private: + QFile file; + CsvParser* parser; + csvtable t; + void dumpRow(csvtable table, int row); +}; + +#endif // KEEPASSX_TESTCSVPARSER_H From a7e358c27dedb31ccf01e53df9d4277583f6a4a7 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Sat, 18 Feb 2017 01:51:31 +0100 Subject: [PATCH 128/333] Syntax style, spaces and pretty indentation --- src/core/CsvParser.cpp | 174 +++++++++++--------------- src/core/CsvParser.h | 44 +++---- src/gui/DatabaseWidget.cpp | 3 + src/gui/DatabaseWidget.h | 1 + src/gui/MainWindow.cpp | 1 + src/gui/csvImport/CsvImportWidget.cpp | 99 +++++++-------- src/gui/csvImport/CsvImportWidget.h | 8 +- src/gui/csvImport/CsvImportWidget.ui | 160 ++++++++++++----------- src/gui/csvImport/CsvImportWizard.cpp | 8 +- src/gui/csvImport/CsvImportWizard.h | 5 +- src/gui/csvImport/CsvParserModel.cpp | 48 +++---- src/gui/csvImport/CsvParserModel.h | 13 +- tests/TestCsvParser.h | 4 +- 13 files changed, 273 insertions(+), 295 deletions(-) diff --git a/src/core/CsvParser.cpp b/src/core/CsvParser.cpp index 87b3f3403..288cd0404 100644 --- a/src/core/CsvParser.cpp +++ b/src/core/CsvParser.cpp @@ -15,10 +15,12 @@ * along with this program. If not, see . */ +#include "CsvParser.h" + #include #include + #include "core/Tools.h" -#include "CsvParser.h" CsvParser::CsvParser() : m_ch(0) @@ -61,16 +63,14 @@ bool CsvParser::parse(QFile *device) { m_statusMsg += QObject::tr("NULL device\n"); return false; } - if (!readFile(device)) { + if (!readFile(device)) return false; - } return parseFile(); } bool CsvParser::readFile(QFile *device) { - if (device->isOpen()) { + if (device->isOpen()) device->close(); - } device->open(QIODevice::ReadOnly); if (!Tools::readAllFromDevice(device, m_array)) { @@ -78,14 +78,14 @@ bool CsvParser::readFile(QFile *device) { m_isFileLoaded = false; } else { - device->close(); + device->close(); - m_array.replace("\r\n", "\n"); - m_array.replace("\r", "\n"); - if (0 == m_array.size()) { - m_statusMsg += QObject::tr("File empty\n"); - } - m_isFileLoaded = true; + m_array.replace("\r\n", "\n"); + m_array.replace("\r", "\n"); + if (0 == m_array.size()) { + m_statusMsg += QObject::tr("File empty\n"); + } + m_isFileLoaded = true; } return m_isFileLoaded; } @@ -117,11 +117,9 @@ void CsvParser::clear() { bool CsvParser::parseFile() { parseRecord(); - while (!m_isEof) - { - if (!skipEndline()) { + while (!m_isEof) { + if (!skipEndline()) appendStatusMsg(QObject::tr("malformed string")); - } m_currRow++; m_currCol = 1; parseRecord(); @@ -131,43 +129,36 @@ bool CsvParser::parseFile() { } void CsvParser::parseRecord() { - csvrow row; + CsvRow row; if (isComment()) { skipLine(); return; } - else { - do { - parseField(row); - getChar(m_ch); - } while (isSeparator(m_ch) && !m_isEof); + do { + parseField(row); + getChar(m_ch); + } while (isSeparator(m_ch) && !m_isEof); - if (!m_isEof) { - ungetChar(); - } - if (isEmptyRow(row)) { - row.clear(); - return; - } - m_table.push_back(row); - if (m_maxCols < row.size()) { - m_maxCols = row.size(); - } - m_currCol++; + if (!m_isEof) + ungetChar(); + if (isEmptyRow(row)) { + row.clear(); + return; } + m_table.push_back(row); + if (m_maxCols < row.size()) + m_maxCols = row.size(); + m_currCol++; } -void CsvParser::parseField(csvrow& row) { +void CsvParser::parseField(CsvRow& row) { QString field; peek(m_ch); - if (!isTerminator(m_ch)) - { - if (isQualifier(m_ch)) { + if (!isTerminator(m_ch)) { + if (isQualifier(m_ch)) parseQuoted(field); - } - else { - parseSimple(field); - } + else + parseSimple(field); } row.push_back(field); } @@ -175,14 +166,12 @@ void CsvParser::parseField(csvrow& row) { void CsvParser::parseSimple(QString &s) { QChar c; getChar(c); - while ((isText(c)) && (!m_isEof)) - { + while ((isText(c)) && (!m_isEof)) { s.append(c); getChar(c); } - if (!m_isEof) { + if (!m_isEof) ungetChar(); - } } void CsvParser::parseQuoted(QString &s) { @@ -190,25 +179,21 @@ void CsvParser::parseQuoted(QString &s) { getChar(m_ch); parseEscaped(s); //getChar(m_ch); - if (!isQualifier(m_ch)) { + if (!isQualifier(m_ch)) appendStatusMsg(QObject::tr("missing closing quote")); - } } void CsvParser::parseEscaped(QString &s) { parseEscapedText(s); - while (processEscapeMark(s, m_ch)) { + while (processEscapeMark(s, m_ch)) parseEscapedText(s); - } - if (!m_isEof) { + if (!m_isEof) ungetChar(); - } } void CsvParser::parseEscapedText(QString &s) { getChar(m_ch); - while ((!isQualifier(m_ch)) && !m_isEof) - { + while ((!isQualifier(m_ch)) && !m_isEof) { s.append(m_ch); getChar(m_ch); } @@ -218,30 +203,25 @@ bool CsvParser::processEscapeMark(QString &s, QChar c) { QChar buf; peek(buf); QChar c2; - //escape-character syntax, e.g. \" - if (true == m_isBackslashSyntax) - { + if (true == m_isBackslashSyntax) { + //escape-character syntax, e.g. \" if (c != '\\') { return false; } //consume (and append) second qualifier getChar(c2); - if (m_isEof){ + if (m_isEof) { c2='\\'; s.append('\\'); return false; - } - else { + } else { s.append(c2); return true; } - } - //double quote syntax, e.g. "" - else - { - if (!isQualifier(c)) { + } else { + //double quote syntax, e.g. "" + if (!isQualifier(c)) return false; - } peek(c2); if (!m_isEof) { //not EOF, can read one char if (isQualifier(c2)) { @@ -255,13 +235,12 @@ bool CsvParser::processEscapeMark(QString &s, QChar c) { } void CsvParser::fillColumns() { - //fill the rows with lesser columns with empty fields - - for (int i=0; i 0) { - csvrow r = m_table.at(i); - for (int j=0; j 100) + return m_statusMsg.section('\n', 0, 4) + .append("\n[...]\n").append(QObject::tr("More messages, skipped!")); return m_statusMsg; } diff --git a/src/core/CsvParser.h b/src/core/CsvParser.h index 77c6d36e1..f7c043a30 100644 --- a/src/core/CsvParser.h +++ b/src/core/CsvParser.h @@ -23,8 +23,8 @@ #include #include -typedef QStringList csvrow; -typedef QList csvtable; +typedef QStringList CsvRow; +typedef QList CsvTable; class CsvParser { @@ -36,19 +36,19 @@ public: bool isFileLoaded(); //reparse the same buffer (device is not opened again) bool reparse(); - void setCodec(const QString s); - void setComment(const QChar c); - void setFieldSeparator(const QChar c); - void setTextQualifier(const QChar c); + void setCodec(const QString &s); + void setComment(const QChar &c); + void setFieldSeparator(const QChar &c); + void setTextQualifier(const QChar &c); void setBackslashSyntax(bool set); - int getFileSize() const; - int getCsvRows() const; - int getCsvCols() const; + int getFileSize() const; + int getCsvRows() const; + int getCsvCols() const; QString getStatus() const; - const csvtable getCsvTable() const; + const CsvTable getCsvTable() const; protected: - csvtable m_table; + CsvTable m_table; private: QByteArray m_array; @@ -72,22 +72,22 @@ private: void ungetChar(); void peek(QChar &c); void fillColumns(); - bool isTerminator(const QChar c) const; - bool isSeparator(const QChar c) const; - bool isQualifier(const QChar c) const; + bool isTerminator(const QChar &c) const; + bool isSeparator(const QChar &c) const; + bool isQualifier(const QChar &c) const; bool processEscapeMark(QString &s, QChar c); bool isText(QChar c) const; bool isComment(); - bool isCRLF(const QChar c) const; - bool isSpace(const QChar c) const; - bool isTab(const QChar c) const; - bool isEmptyRow(csvrow row) const; + bool isCRLF(const QChar &c) const; + bool isSpace(const QChar &c) const; + bool isTab(const QChar &c) const; + bool isEmptyRow(CsvRow row) const; bool parseFile(); void parseRecord(); - void parseField(csvrow& row); - void parseSimple(QString& s); - void parseQuoted(QString& s); - void parseEscaped(QString& s); + void parseField(CsvRow &row); + void parseSimple(QString &s); + void parseQuoted(QString &s); + void parseEscaped(QString &s); void parseEscapedText(QString &s); bool readFile(QFile *device); void reset(); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 2a88d8a1a..86319e910 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -196,6 +196,9 @@ DatabaseWidget::Mode DatabaseWidget::currentMode() const if (currentWidget() == nullptr) { return DatabaseWidget::None; } + else if (currentWidget() == m_csvImportWizard) { + return DatabaseWidget::ImportMode; + } else if (currentWidget() == m_mainWidget) { return DatabaseWidget::ViewMode; } diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 87d14a182..651b9d34f 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -61,6 +61,7 @@ public: enum Mode { None, + ImportMode, ViewMode, EditMode, LockedMode diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index aa862465d..47edaf482 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -425,6 +425,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) break; } case DatabaseWidget::EditMode: + case DatabaseWidget::ImportMode: case DatabaseWidget::LockedMode: { const QList entryActions = m_ui->menuEntries->actions(); for (QAction* action : entryActions) { diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 9e53d5af3..1e3c81a86 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -17,15 +17,17 @@ #include "CsvImportWidget.h" #include "ui_CsvImportWidget.h" -#include "gui/MessageBox.h" #include #include +#include "gui/MessageBox.h" +#include "gui/MessageWidget.h" + //I wanted to make the CSV import GUI future-proof, so if one day you need entries //to have a new field, all you have to do is uncomment a row or two here, and the GUI will follow: //dynamic generation of comboBoxes, labels, placement and so on. Try it for immense fun! -const QStringList CsvImportWidget::m_columnheader = QStringList() +const QStringList CsvImportWidget::m_columnHeader = QStringList() << QObject::tr("Group") << QObject::tr("Title") << QObject::tr("Username") @@ -46,10 +48,7 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) { m_ui->setupUi(this); - QFont font = m_ui->labelHeadline->font(); - font.setBold(true); - font.setPointSize(font.pointSize() + 2); - m_ui->labelHeadline->setFont(font); + m_ui->messageWidget->setHidden(true); m_ui->comboBoxCodec->addItems(QStringList() <<"UTF-8" <<"Windows-1252" <<"UTF-16" <<"UTF-16LE"); m_ui->comboBoxFieldSeparator->addItems(QStringList() <<"," <<";" <<"-" <<":" <<"."); @@ -59,10 +58,10 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) m_ui->tableViewFields->setSelectionMode(QAbstractItemView::NoSelection); m_ui->tableViewFields->setFocusPolicy(Qt::NoFocus); - for (int i=0; isetFixedWidth(label->minimumSizeHint().width()); - font = label->font(); + QFont font = label->font(); font.setBold(false); label->setFont(font); @@ -76,14 +75,14 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) connect(combo, SIGNAL(currentIndexChanged(int)), m_comboMapper, SLOT(map())); //layout labels and combo fields in column-first order - int combo_rows = 1+(m_columnheader.count()-1)/2; - int x=i%combo_rows; - int y= 2*(i/combo_rows); + int combo_rows = 1+(m_columnHeader.count()-1)/2; + int x = i%combo_rows; + int y = 2*(i/combo_rows); m_ui->gridLayout_combos->addWidget(label, x, y); m_ui->gridLayout_combos->addWidget(combo, x, y+1); } - m_parserModel->setHeaderLabels(m_columnheader); + m_parserModel->setHeaderLabels(m_columnHeader); m_ui->tableViewFields->setModel(m_parserModel); connect(m_ui->spinBoxSkip, SIGNAL(valueChanged(int)), SLOT(skippedChanged(int))); @@ -101,10 +100,9 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) void CsvImportWidget::comboChanged(int comboId) { QComboBox* currentSender = qobject_cast(m_comboMapper->mapping(comboId)); - if (currentSender->currentIndex() != -1) { - //here is the line that actually updates the GUI table + if (currentSender->currentIndex() != -1) + //this line is the one that actually updates GUI table m_parserModel->mapColumns(currentSender->currentIndex(), comboId); - } updateTableview(); } @@ -127,7 +125,7 @@ void CsvImportWidget::updateTableview() { m_ui->tableViewFields->resizeRowsToContents(); m_ui->tableViewFields->resizeColumnsToContents(); - for (int c=0; ctableViewFields->horizontalHeader()->count(); ++c) { + for (int c = 0; c < m_ui->tableViewFields->horizontalHeader()->count(); ++c) { m_ui->tableViewFields->horizontalHeader()->setSectionResizeMode( c, QHeaderView::Stretch); } @@ -137,12 +135,12 @@ void CsvImportWidget::updatePreview() { m_ui->labelSizeRowsCols->setText(m_parserModel->getFileInfo()); m_ui->spinBoxSkip->setValue(0); - m_ui->spinBoxSkip->setMaximum(m_parserModel->rowCount()-1); + m_ui->spinBoxSkip->setMaximum(m_parserModel->rowCount() - 1); int i; QStringList list(tr("Not present in CSV file")); - for (i=1; igetCsvCols(); i++) { + for (i = 1; i < m_parserModel->getCsvCols(); i++) { QString s = QString(tr("Column ")) + QString::number(i); list << s; } @@ -150,12 +148,10 @@ void CsvImportWidget::updatePreview() { i=1; Q_FOREACH (QComboBox* b, m_combos) { - if (i < m_parserModel->getCsvCols()) { + if (i < m_parserModel->getCsvCols()) b->setCurrentIndex(i); - } - else { + else b->setCurrentIndex(0); - } ++i; } } @@ -166,7 +162,7 @@ void CsvImportWidget::load(const QString& filename, Database* const db) { m_ui->labelFilename->setText(filename); Group* group = m_db->rootGroup(); group->setUuid(Uuid::random()); - group->setNotes(tr("Imported from CSV file\nOriginal data: ") + filename); + group->setNotes(tr("Imported from CSV file").append("\n").append(tr("Original data: ")) + filename); parse(); } @@ -181,47 +177,48 @@ void CsvImportWidget::parse() { } void CsvImportWidget::showReport() { - MessageBox::warning(this, tr("Syntax error"), tr("While parsing file...\n") - .append(m_parserModel->getStatus()), QMessageBox::Ok, QMessageBox::Ok); +// MessageBox::warning(this, tr("Syntax error"), tr("While parsing file...\n") +// .append(m_parserModel->getStatus()), QMessageBox::Ok, QMessageBox::Ok); + m_ui->messageWidget->showMessage(tr("Syntax error while parsing file.").append("\n") + .append(m_parserModel->getStatus()), MessageWidget::Warning); } void CsvImportWidget::writeDatabase() { - checkGroupNames(); - for (int r=0; rrowCount(); r++) { + setRootGroup(); + for (int r = 0; r < m_parserModel->rowCount(); r++) //use the validity of second column as a GO/NOGO hint for all others fields if (m_parserModel->data(m_parserModel->index(r, 1)).isValid()) { Entry* entry = new Entry(); entry->setUuid(Uuid::random()); entry->setGroup(splitGroups(m_parserModel->data(m_parserModel->index(r, 0)).toString())); - entry->setTitle( m_parserModel->data(m_parserModel->index(r, 1)).toString()); - entry->setUsername( m_parserModel->data(m_parserModel->index(r, 2)).toString()); - entry->setPassword( m_parserModel->data(m_parserModel->index(r, 3)).toString()); - entry->setUrl( m_parserModel->data(m_parserModel->index(r, 4)).toString()); - entry->setNotes( m_parserModel->data(m_parserModel->index(r, 5)).toString()); + entry->setTitle(m_parserModel->data(m_parserModel->index(r, 1)).toString()); + entry->setUsername(m_parserModel->data(m_parserModel->index(r, 2)).toString()); + entry->setPassword(m_parserModel->data(m_parserModel->index(r, 3)).toString()); + entry->setUrl(m_parserModel->data(m_parserModel->index(r, 4)).toString()); + entry->setNotes(m_parserModel->data(m_parserModel->index(r, 5)).toString()); } - } QBuffer buffer; buffer.open(QBuffer::ReadWrite); KeePass2Writer writer; writer.writeDatabase(&buffer, m_db); - if (writer.hasError()) { + if (writer.hasError()) MessageBox::warning(this, tr("Error"), tr("CSV import: writer has errors:\n") .append((writer.errorString())), QMessageBox::Ok, QMessageBox::Ok); - } Q_EMIT editFinished(true); } -void CsvImportWidget::checkGroupNames() { +void CsvImportWidget::setRootGroup() { QString groupLabel; QStringList groupList; - bool is_root = false - , is_empty = false - , is_label = false; - for (int r=0; rrowCount(); r++) { + bool is_root = false; + bool is_empty = false; + bool is_label = false; + + for (int r = 0; r < m_parserModel->rowCount(); r++) { groupLabel = m_parserModel->data(m_parserModel->index(r, 0)).toString(); //check if group name is either "root", "" (empty) or some other label groupList = groupLabel.split("/", QString::SkipEmptyParts); @@ -234,18 +231,13 @@ void CsvImportWidget::checkGroupNames() { groupList.clear(); } - if ((not is_label and (is_empty xor is_root)) - or (is_label and not is_root)) { + if ((not is_label and (is_empty xor is_root)) or (is_label and not is_root)) m_db->rootGroup()->setName("Root"); - } - else if ((is_empty and is_root) - or (is_label and not is_empty and is_root)) { + else if ((is_empty and is_root) or (is_label and not is_empty and is_root)) m_db->rootGroup()->setName("CSV IMPORTED"); - } - else { + else //SHOULD NEVER GET HERE m_db->rootGroup()->setName("ROOT_FALLBACK"); - } } Group *CsvImportWidget::splitGroups(QString label) { @@ -254,9 +246,8 @@ Group *CsvImportWidget::splitGroups(QString label) { QStringList groupList = label.split("/", QString::SkipEmptyParts); //skip the creation of a subgroup of Root with the same name - if (m_db->rootGroup()->name() == "Root" && groupList.first() == "Root") { + if (m_db->rootGroup()->name() == "Root" && groupList.first() == "Root") groupList.removeFirst(); - } for (const QString& groupName : groupList) { Group *children = hasChildren(current, groupName); @@ -265,8 +256,7 @@ Group *CsvImportWidget::splitGroups(QString label) { brandNew->setParent(current); brandNew->setName(groupName); current = brandNew; - } - else { + } else { Q_ASSERT(children != nullptr); current = children; } @@ -277,9 +267,8 @@ Group *CsvImportWidget::splitGroups(QString label) { Group* CsvImportWidget::hasChildren(Group* current, QString groupName) { //returns the group whose name is "groupName" and is child of "current" group for (Group * group : current->children()) { - if (group->name() == groupName) { + if (group->name() == groupName) return group; - } } return nullptr; } diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index f8798b560..1a71924ea 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -26,10 +26,10 @@ #include #include +#include "core/Metadata.h" #include "format/KeePass2Writer.h" #include "gui/csvImport/CsvParserModel.h" #include "keys/PasswordKey.h" -#include "core/Metadata.h" namespace Ui { @@ -42,7 +42,7 @@ class CsvImportWidget : public QWidget public: explicit CsvImportWidget(QWidget *parent = nullptr); - virtual ~CsvImportWidget(); + ~CsvImportWidget(); void load(const QString& filename, Database* const db); Q_SIGNALS: @@ -54,7 +54,7 @@ private Q_SLOTS: void comboChanged(int comboId); void skippedChanged(int rows); void writeDatabase(); - void checkGroupNames(); + void setRootGroup(); void reject(); private: @@ -67,7 +67,7 @@ private: Database *m_db; KeePass2Writer m_writer; - static const QStringList m_columnheader; + static const QStringList m_columnHeader; void configParser(); void updatePreview(); void updateTableview(); diff --git a/src/gui/csvImport/CsvImportWidget.ui b/src/gui/csvImport/CsvImportWidget.ui index 5df2aa1af..8816c577c 100644 --- a/src/gui/csvImport/CsvImportWidget.ui +++ b/src/gui/csvImport/CsvImportWidget.ui @@ -14,7 +14,33 @@ - + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 758 + 24 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + @@ -377,17 +403,7 @@
- - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - + @@ -409,50 +425,7 @@
- - - - - - - 0 - 0 - - - - Import CSV fields - - - - - - - - 0 - 0 - - - - filename - - - - - - - - 0 - 0 - - - - size, rows, columns - - - - - - + @@ -501,24 +474,69 @@
- - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 27 - - - + + + + + + + + + + 0 + 0 + + + + + 11 + 75 + true + + + + Import CSV fields + + + + + + + + 0 + 0 + + + + filename + + + + + + + + 0 + 0 + + + + size, rows, columns + + + + + + + MessageWidget + QWidget +
gui/MessageWidget.h
+ 1 +
+
diff --git a/src/gui/csvImport/CsvImportWizard.cpp b/src/gui/csvImport/CsvImportWizard.cpp index f84a22959..0114fcf32 100644 --- a/src/gui/csvImport/CsvImportWizard.cpp +++ b/src/gui/csvImport/CsvImportWizard.cpp @@ -16,8 +16,10 @@ */ #include "CsvImportWizard.h" + #include #include + #include "gui/MessageBox.h" @@ -49,7 +51,7 @@ void CsvImportWizard::load(const QString& filename, Database* database) void CsvImportWizard::keyFinished(bool accepted) { if (!accepted) { - Q_EMIT(importFinished(false)); + emit(importFinished(false)); return; } @@ -61,12 +63,12 @@ void CsvImportWizard::keyFinished(bool accepted) if (!result) { MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key")); - Q_EMIT importFinished(false); + emit(importFinished(false)); return; } } void CsvImportWizard::parseFinished(bool accepted) { - Q_EMIT(importFinished(accepted)); + emit(importFinished(accepted)); } diff --git a/src/gui/csvImport/CsvImportWizard.h b/src/gui/csvImport/CsvImportWizard.h index 5a83a19d8..75d10bb9d 100644 --- a/src/gui/csvImport/CsvImportWizard.h +++ b/src/gui/csvImport/CsvImportWizard.h @@ -18,10 +18,11 @@ #ifndef KEEPASSX_CSVIMPORTWIZARD_H #define KEEPASSX_CSVIMPORTWIZARD_H +#include "CsvImportWidget.h" + #include #include -#include "CsvImportWidget.h" #include "core/Database.h" #include "gui/ChangeMasterKeyWidget.h" #include "gui/DialogyWidget.h" @@ -34,7 +35,7 @@ class CsvImportWizard : public DialogyWidget public: explicit CsvImportWizard(QWidget *parent = nullptr); - virtual ~CsvImportWizard(); + ~CsvImportWizard(); void load(const QString& filename, Database *database); Q_SIGNALS: diff --git a/src/gui/csvImport/CsvParserModel.cpp b/src/gui/csvImport/CsvParserModel.cpp index 3bc6c834c..ba5d20d92 100644 --- a/src/gui/csvImport/CsvParserModel.cpp +++ b/src/gui/csvImport/CsvParserModel.cpp @@ -42,39 +42,33 @@ bool CsvParserModel::parse() { m_columnMap.clear(); if (CsvParser::isFileLoaded()) { r = CsvParser::reparse(); - } - else { + } else { QFile csv(m_filename); r = CsvParser::parse(&csv); } - for (int i=0; i= getCsvCols()) { + if (csvColumn >= getCsvCols()) m_columnMap[dbColumn] = 0; //map to the empty column - } - else { + else m_columnMap[dbColumn] = csvColumn; - } endResetModel(); } @@ -82,8 +76,8 @@ void CsvParserModel::setSkippedRows(int skipped) { m_skipped = skipped; QModelIndex topLeft = createIndex(skipped,0); QModelIndex bottomRight = createIndex(m_skipped+rowCount(), columnCount()); - Q_EMIT dataChanged(topLeft, bottomRight); - Q_EMIT layoutChanged(); + emit dataChanged(topLeft, bottomRight); + emit layoutChanged(); } void CsvParserModel::setHeaderLabels(QStringList l) { @@ -91,45 +85,37 @@ void CsvParserModel::setHeaderLabels(QStringList l) { } int CsvParserModel::rowCount(const QModelIndex &parent) const { - if (parent.isValid()) { + if (parent.isValid()) return 0; - } return getCsvRows(); } int CsvParserModel::columnCount(const QModelIndex &parent) const { - if (parent.isValid()) { + if (parent.isValid()) return 0; - } return m_columnHeader.size(); } QVariant CsvParserModel::data(const QModelIndex &index, int role) const { - if ( (index.column() >= m_columnHeader.size()) + if ((index.column() >= m_columnHeader.size()) || (index.row()+m_skipped >= rowCount()) - || !index.isValid() ) - { + || !index.isValid()) { return QVariant(); } - - if (role == Qt::DisplayRole) { + if (role == Qt::DisplayRole) return m_table.at(index.row()+m_skipped).at(m_columnMap[index.column()]); - } return QVariant(); } QVariant CsvParserModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole) { if (orientation == Qt::Horizontal) { - if ( (section < 0) || (section >= m_columnHeader.size())) { + if ((section < 0) || (section >= m_columnHeader.size())) return QVariant(); - } return m_columnHeader.at(section); - } - else if (orientation == Qt::Vertical) { - if (section+m_skipped >= rowCount()) { + } else if (orientation == Qt::Vertical) { + if (section+m_skipped >= rowCount()) return QVariant(); - } return QString::number(section+1); } } diff --git a/src/gui/csvImport/CsvParserModel.h b/src/gui/csvImport/CsvParserModel.h index ae7bf6e20..ff4f410d7 100644 --- a/src/gui/csvImport/CsvParserModel.h +++ b/src/gui/csvImport/CsvParserModel.h @@ -20,8 +20,9 @@ #include #include -#include "core/Group.h" + #include "core/CsvParser.h" +#include "core/Group.h" class CsvParserModel : public QAbstractTableModel, public CsvParser { @@ -29,7 +30,7 @@ class CsvParserModel : public QAbstractTableModel, public CsvParser public: explicit CsvParserModel(QObject *parent = nullptr); - virtual ~CsvParserModel(); + ~CsvParserModel(); void setFilename(const QString& filename); QString getFileInfo(); bool parse(); @@ -37,10 +38,10 @@ public: void setHeaderLabels(QStringList l); void mapColumns(int csvColumn, int dbColumn); - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; public Q_SLOTS: void setSkippedRows(int skipped); diff --git a/tests/TestCsvParser.h b/tests/TestCsvParser.h index efdd96ab0..975d86a92 100644 --- a/tests/TestCsvParser.h +++ b/tests/TestCsvParser.h @@ -62,8 +62,8 @@ private Q_SLOTS: private: QFile file; CsvParser* parser; - csvtable t; - void dumpRow(csvtable table, int row); + CsvTable t; + void dumpRow(CsvTable table, int row); }; #endif // KEEPASSX_TESTCSVPARSER_H From 41f9c3d2a1dac4c009dee2c53bcdb5bb7ccbd76e Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Wed, 22 Feb 2017 01:03:22 +0100 Subject: [PATCH 129/333] Better handle of parser status messages (critical/not critical) Use of messageWidget for displaying parser status messages setRootGroup assigns the right label to the root db folder test uses portable QTemporaryFile instead of hardcoded file --- src/core/CsvParser.cpp | 24 +++--- src/core/CsvParser.h | 2 +- src/gui/AboutDialog.ui | 3 +- src/gui/csvImport/CsvImportWidget.cpp | 106 +++++++++++++++----------- src/gui/csvImport/CsvImportWidget.h | 2 +- src/gui/csvImport/CsvImportWidget.ui | 42 +++++----- src/gui/csvImport/CsvImportWizard.cpp | 5 +- src/gui/csvImport/CsvParserModel.cpp | 2 +- tests/TestCsvParser.cpp | 85 +++++++++++---------- tests/TestCsvParser.h | 3 +- 10 files changed, 150 insertions(+), 124 deletions(-) diff --git a/src/core/CsvParser.cpp b/src/core/CsvParser.cpp index 288cd0404..0ab7f28cc 100644 --- a/src/core/CsvParser.cpp +++ b/src/core/CsvParser.cpp @@ -60,7 +60,7 @@ bool CsvParser::reparse() { bool CsvParser::parse(QFile *device) { clear(); if (nullptr == device) { - m_statusMsg += QObject::tr("NULL device\n"); + appendStatusMsg(QObject::tr("NULL device"), true); return false; } if (!readFile(device)) @@ -74,7 +74,7 @@ bool CsvParser::readFile(QFile *device) { device->open(QIODevice::ReadOnly); if (!Tools::readAllFromDevice(device, m_array)) { - m_statusMsg += QObject::tr("Error reading from device\n"); + appendStatusMsg(QObject::tr("error reading from device"), true); m_isFileLoaded = false; } else { @@ -82,9 +82,8 @@ bool CsvParser::readFile(QFile *device) { m_array.replace("\r\n", "\n"); m_array.replace("\r", "\n"); - if (0 == m_array.size()) { - m_statusMsg += QObject::tr("File empty\n"); - } + if (0 == m_array.size()) + appendStatusMsg(QObject::tr("file empty !\n")); m_isFileLoaded = true; } return m_isFileLoaded; @@ -119,7 +118,7 @@ bool CsvParser::parseFile() { parseRecord(); while (!m_isEof) { if (!skipEndline()) - appendStatusMsg(QObject::tr("malformed string")); + appendStatusMsg(QObject::tr("malformed string"), true); m_currRow++; m_currCol = 1; parseRecord(); @@ -180,7 +179,7 @@ void CsvParser::parseQuoted(QString &s) { parseEscaped(s); //getChar(m_ch); if (!isQualifier(m_ch)) - appendStatusMsg(QObject::tr("missing closing quote")); + appendStatusMsg(QObject::tr("missing closing quote"), true); } void CsvParser::parseEscaped(QString &s) { @@ -269,7 +268,7 @@ void CsvParser::getChar(QChar& c) { void CsvParser::ungetChar() { if (!m_ts.seek(m_lastPos)) - m_statusMsg += QObject::tr("Internal: unget lower bound exceeded"); + appendStatusMsg(QObject::tr("INTERNAL - unget lower bound exceeded"), true); } void CsvParser::peek(QChar& c) { @@ -360,9 +359,6 @@ const CsvTable CsvParser::getCsvTable() const { } QString CsvParser::getStatus() const { - if (m_statusMsg.size() > 100) - return m_statusMsg.section('\n', 0, 4) - .append("\n[...]\n").append(QObject::tr("More messages, skipped!")); return m_statusMsg; } @@ -377,11 +373,11 @@ int CsvParser::getCsvRows() const { } -void CsvParser::appendStatusMsg(QString s) { +void CsvParser::appendStatusMsg(QString s, bool isCritical) { m_statusMsg += s - .append(" @" + QString::number(m_currRow)) + .append(": (row,col) " + QString::number(m_currRow)) .append(",") .append(QString::number(m_currCol)) .append("\n"); - m_isGood = false; + m_isGood = not isCritical; } diff --git a/src/core/CsvParser.h b/src/core/CsvParser.h index f7c043a30..48be05584 100644 --- a/src/core/CsvParser.h +++ b/src/core/CsvParser.h @@ -94,7 +94,7 @@ private: void clear(); bool skipEndline(); void skipLine(); - void appendStatusMsg(QString s); + void appendStatusMsg(QString s, bool isCritical = false); }; #endif //CSVPARSER_H diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index e1e706808..bfa27689a 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -160,8 +160,9 @@ <li>debfx (KeePassX)</li> <li>droidmonkey</li> <li>louib</li> -<li>phoerious<li> +<li>phoerious</li> <li>thezero</li> +<li>seatedscribe</li> </ul> diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 1e3c81a86..2c782b89f 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -48,8 +48,6 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) { m_ui->setupUi(this); - m_ui->messageWidget->setHidden(true); - m_ui->comboBoxCodec->addItems(QStringList() <<"UTF-8" <<"Windows-1252" <<"UTF-16" <<"UTF-16LE"); m_ui->comboBoxFieldSeparator->addItems(QStringList() <<"," <<";" <<"-" <<":" <<"."); m_ui->comboBoxTextQualifier->addItems(QStringList() <<"\"" <<"'" <<":" <<"." <<"|"); @@ -75,9 +73,9 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) connect(combo, SIGNAL(currentIndexChanged(int)), m_comboMapper, SLOT(map())); //layout labels and combo fields in column-first order - int combo_rows = 1+(m_columnHeader.count()-1)/2; - int x = i%combo_rows; - int y = 2*(i/combo_rows); + int combo_rows = 1 + (m_columnHeader.count() - 1) / 2; + int x = i % combo_rows; + int y = 2 * (i / combo_rows); m_ui->gridLayout_combos->addWidget(label, x, y); m_ui->gridLayout_combos->addWidget(combo, x, y+1); } @@ -91,7 +89,6 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) connect(m_ui->comboBoxComment, SIGNAL(currentIndexChanged(int)), SLOT(parse())); connect(m_ui->comboBoxFieldSeparator, SIGNAL(currentIndexChanged(int)), SLOT(parse())); connect(m_ui->checkBoxBackslash, SIGNAL(toggled(bool)), SLOT(parse())); - connect(m_ui->pushButtonWarnings, SIGNAL(clicked()), this, SLOT(showReport())); connect(m_comboMapper, SIGNAL(mapped(int)), this, SLOT(comboChanged(int))); connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(writeDatabase())); @@ -125,17 +122,15 @@ void CsvImportWidget::updateTableview() { m_ui->tableViewFields->resizeRowsToContents(); m_ui->tableViewFields->resizeColumnsToContents(); - for (int c = 0; c < m_ui->tableViewFields->horizontalHeader()->count(); ++c) { - m_ui->tableViewFields->horizontalHeader()->setSectionResizeMode( - c, QHeaderView::Stretch); - } + for (int c = 0; c < m_ui->tableViewFields->horizontalHeader()->count(); ++c) + m_ui->tableViewFields->horizontalHeader()->setSectionResizeMode(c, QHeaderView::Stretch); } void CsvImportWidget::updatePreview() { m_ui->labelSizeRowsCols->setText(m_parserModel->getFileInfo()); m_ui->spinBoxSkip->setValue(0); - m_ui->spinBoxSkip->setMaximum(m_parserModel->rowCount() - 1); + m_ui->spinBoxSkip->setMaximum(qMax(0, m_parserModel->rowCount() - 1)); int i; QStringList list(tr("Not present in CSV file")); @@ -157,48 +152,63 @@ void CsvImportWidget::updatePreview() { } void CsvImportWidget::load(const QString& filename, Database* const db) { + //QApplication::processEvents(); m_db = db; m_parserModel->setFilename(filename); m_ui->labelFilename->setText(filename); Group* group = m_db->rootGroup(); group->setUuid(Uuid::random()); group->setNotes(tr("Imported from CSV file").append("\n").append(tr("Original data: ")) + filename); - parse(); } void CsvImportWidget::parse() { configParser(); QApplication::setOverrideCursor(Qt::WaitCursor); + //QApplication::processEvents(); bool good = m_parserModel->parse(); - QApplication::restoreOverrideCursor(); updatePreview(); - m_ui->pushButtonWarnings->setEnabled(!good); + QApplication::restoreOverrideCursor(); + if (good) + //four newline are provided to avoid resizing at every Positive/Warning switch + m_ui->messageWidget->showMessage(QString("\n\n").append(tr("CSV syntax seems in good shape !")) + .append("\n\n"), MessageWidget::Positive); + else + m_ui->messageWidget->showMessage(tr("Error(s) detected in CSV file !").append("\n") + .append(formatStatusText()), MessageWidget::Warning); + QWidget::adjustSize(); } -void CsvImportWidget::showReport() { -// MessageBox::warning(this, tr("Syntax error"), tr("While parsing file...\n") -// .append(m_parserModel->getStatus()), QMessageBox::Ok, QMessageBox::Ok); - m_ui->messageWidget->showMessage(tr("Syntax error while parsing file.").append("\n") - .append(m_parserModel->getStatus()), MessageWidget::Warning); + +QString CsvImportWidget::formatStatusText() const { + QString text = m_parserModel->getStatus(); + int items = text.count('\n'); + if (items > 3) + return text.section('\n', 0, 2) + .append("\n[").append(QString::number(items - 3)) + .append(tr(" more messages skipped]")); + else + for (int i = 0; i < 3 - items; i++) + text.append(QString("\n")); + return text; } void CsvImportWidget::writeDatabase() { setRootGroup(); - for (int r = 0; r < m_parserModel->rowCount(); r++) - //use the validity of second column as a GO/NOGO hint for all others fields - if (m_parserModel->data(m_parserModel->index(r, 1)).isValid()) { - Entry* entry = new Entry(); - entry->setUuid(Uuid::random()); - entry->setGroup(splitGroups(m_parserModel->data(m_parserModel->index(r, 0)).toString())); - entry->setTitle(m_parserModel->data(m_parserModel->index(r, 1)).toString()); - entry->setUsername(m_parserModel->data(m_parserModel->index(r, 2)).toString()); - entry->setPassword(m_parserModel->data(m_parserModel->index(r, 3)).toString()); - entry->setUrl(m_parserModel->data(m_parserModel->index(r, 4)).toString()); - entry->setNotes(m_parserModel->data(m_parserModel->index(r, 5)).toString()); - } - + for (int r = 0; r < m_parserModel->rowCount(); r++) { + //use validity of second column as a GO/NOGO for all others fields + if (not m_parserModel->data(m_parserModel->index(r, 1)).isValid()) + continue; + Entry* entry = new Entry(); + entry->setUuid(Uuid::random()); + entry->setGroup(splitGroups(m_parserModel->data(m_parserModel->index(r, 0)).toString())); + entry->setTitle(m_parserModel->data(m_parserModel->index(r, 1)).toString()); + entry->setUsername(m_parserModel->data(m_parserModel->index(r, 2)).toString()); + entry->setPassword(m_parserModel->data(m_parserModel->index(r, 3)).toString()); + entry->setUrl(m_parserModel->data(m_parserModel->index(r, 4)).toString()); + entry->setNotes(m_parserModel->data(m_parserModel->index(r, 5)).toString()); + } QBuffer buffer; buffer.open(QBuffer::ReadWrite); @@ -207,7 +217,7 @@ void CsvImportWidget::writeDatabase() { if (writer.hasError()) MessageBox::warning(this, tr("Error"), tr("CSV import: writer has errors:\n") .append((writer.errorString())), QMessageBox::Ok, QMessageBox::Ok); - Q_EMIT editFinished(true); + emit editFinished(true); } @@ -219,33 +229,39 @@ void CsvImportWidget::setRootGroup() { bool is_label = false; for (int r = 0; r < m_parserModel->rowCount(); r++) { + //use validity of second column as a GO/NOGO for all others fields + if (not m_parserModel->data(m_parserModel->index(r, 1)).isValid()) + continue; groupLabel = m_parserModel->data(m_parserModel->index(r, 0)).toString(); //check if group name is either "root", "" (empty) or some other label groupList = groupLabel.split("/", QString::SkipEmptyParts); - if (not groupList.first().compare("Root", Qt::CaseSensitive)) - is_root = true; - else if (not groupLabel.compare("")) + if (groupList.isEmpty()) is_empty = true; else - is_label = true; + if (not groupList.first().compare("Root", Qt::CaseSensitive)) + is_root = true; + else if (not groupLabel.compare("")) + is_empty = true; + else + is_label = true; + groupList.clear(); } - if ((not is_label and (is_empty xor is_root)) or (is_label and not is_root)) - m_db->rootGroup()->setName("Root"); - else if ((is_empty and is_root) or (is_label and not is_empty and is_root)) + if ((is_empty and is_root) or (is_label and not is_empty and is_root)) m_db->rootGroup()->setName("CSV IMPORTED"); else - //SHOULD NEVER GET HERE - m_db->rootGroup()->setName("ROOT_FALLBACK"); + m_db->rootGroup()->setName("Root"); } Group *CsvImportWidget::splitGroups(QString label) { //extract group names from nested path provided in "label" Group *current = m_db->rootGroup(); - QStringList groupList = label.split("/", QString::SkipEmptyParts); + if (label.isEmpty()) + return current; - //skip the creation of a subgroup of Root with the same name + QStringList groupList = label.split("/", QString::SkipEmptyParts); + //avoid the creation of a subgroup with the same name as Root if (m_db->rootGroup()->name() == "Root" && groupList.first() == "Root") groupList.removeFirst(); @@ -274,5 +290,5 @@ Group* CsvImportWidget::hasChildren(Group* current, QString groupName) { } void CsvImportWidget::reject() { - Q_EMIT editFinished(false); + emit editFinished(false); } diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index 1a71924ea..2079e9f96 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -50,7 +50,6 @@ Q_SIGNALS: private Q_SLOTS: void parse(); - void showReport(); void comboChanged(int comboId); void skippedChanged(int rows); void writeDatabase(); @@ -73,6 +72,7 @@ private: void updateTableview(); Group* splitGroups(QString label); Group* hasChildren(Group* current, QString groupName); + QString formatStatusText() const; }; #endif // KEEPASSX_CSVIMPORTWIDGET_H diff --git a/src/gui/csvImport/CsvImportWidget.ui b/src/gui/csvImport/CsvImportWidget.ui index 8816c577c..82ee68dd2 100644 --- a/src/gui/csvImport/CsvImportWidget.ui +++ b/src/gui/csvImport/CsvImportWidget.ui @@ -122,22 +122,6 @@
- - - - false - - - - 50 - false - - - - Show parser warnings - - - @@ -162,6 +146,12 @@ 0 + + + 0 + 25 + + 50 @@ -239,6 +229,12 @@ 0 + + + 0 + 25 + + 50 @@ -290,6 +286,12 @@ 0 + + + 0 + 25 + + 50 @@ -309,6 +311,12 @@ 0 + + + 0 + 25 + + 50 @@ -342,7 +350,7 @@ - Treat '\' as escape character + Consider '\' as escape character diff --git a/src/gui/csvImport/CsvImportWizard.cpp b/src/gui/csvImport/CsvImportWizard.cpp index 0114fcf32..a1e1757bb 100644 --- a/src/gui/csvImport/CsvImportWizard.cpp +++ b/src/gui/csvImport/CsvImportWizard.cpp @@ -33,6 +33,10 @@ CsvImportWizard::CsvImportWizard(QWidget *parent) m_pages->addWidget(key = new ChangeMasterKeyWidget(m_pages)); m_pages->addWidget(parse = new CsvImportWidget(m_pages)); key->headlineLabel()->setText(tr("Import CSV file")); + QFont headLineFont = key->headlineLabel()->font(); + headLineFont.setBold(true); + headLineFont.setPointSize(headLineFont.pointSize() + 2); + key->headlineLabel()->setFont(headLineFont); connect(key, SIGNAL(editFinished(bool)), this, SLOT(keyFinished(bool))); connect(parse, SIGNAL(editFinished(bool)), this, SLOT(parseFinished(bool))); @@ -64,7 +68,6 @@ void CsvImportWizard::keyFinished(bool accepted) if (!result) { MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key")); emit(importFinished(false)); - return; } } diff --git a/src/gui/csvImport/CsvParserModel.cpp b/src/gui/csvImport/CsvParserModel.cpp index ba5d20d92..d315f1ad2 100644 --- a/src/gui/csvImport/CsvParserModel.cpp +++ b/src/gui/csvImport/CsvParserModel.cpp @@ -32,7 +32,7 @@ void CsvParserModel::setFilename(const QString& filename) { QString CsvParserModel::getFileInfo(){ QString a(QString::number(getFileSize()).append(tr(" byte, "))); a.append(QString::number(getCsvRows())).append(tr(" rows, ")); - a.append(QString::number((getCsvCols()-1))).append(tr(" columns")); + a.append(QString::number(qMax(0, getCsvCols()-1))).append(tr(" columns")); return a; } diff --git a/tests/TestCsvParser.cpp b/tests/TestCsvParser.cpp index 9ff93f025..93bcf0060 100644 --- a/tests/TestCsvParser.cpp +++ b/tests/TestCsvParser.cpp @@ -16,6 +16,7 @@ */ #include "TestCsvParser.h" + #include QTEST_GUILESS_MAIN(TestCsvParser) @@ -32,8 +33,8 @@ void TestCsvParser::cleanupTestCase() void TestCsvParser::init() { - file.setFileName("/tmp/keepassXn94do1x.csv"); - if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) + file = new QTemporaryFile(); + if (not file->open()) QFAIL("Cannot open file!"); parser->setBackslashSyntax(false); parser->setComment('#'); @@ -43,26 +44,26 @@ void TestCsvParser::init() void TestCsvParser::cleanup() { - file.close(); + file->remove(); } /****************** TEST CASES ******************/ void TestCsvParser::testMissingQuote() { parser->setTextQualifier(':'); - QTextStream out(&file); + QTextStream out(file); out << "A,B\n:BM,1"; QEXPECT_FAIL("", "Bad format", Continue); - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QWARN(parser->getStatus().toLatin1()); } void TestCsvParser::testMalformed() { parser->setTextQualifier(':'); - QTextStream out(&file); + QTextStream out(file); out << "A,B,C\n:BM::,1,:2:"; QEXPECT_FAIL("", "Bad format", Continue); - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QWARN(parser->getStatus().toLatin1()); } @@ -70,14 +71,14 @@ void TestCsvParser::testMalformed() { void TestCsvParser::testBackslashSyntax() { parser->setBackslashSyntax(true); parser->setTextQualifier(QChar('X')); - QTextStream out(&file); + QTextStream out(file); //attended result: one"\t\"wo out << "Xone\\\"\\\\t\\\\\\\"w\noX\n" << "X13X,X2\\X,X,\"\"3\"X\r" << "3,X\"4\"X,,\n" << "XX\n" << "\\"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.at(0).at(0) == "one\"\\t\\\"w\no"); QVERIFY(t.at(1).at(0) == "13"); @@ -92,10 +93,10 @@ void TestCsvParser::testBackslashSyntax() { } void TestCsvParser::testQuoted() { - QTextStream out(&file); + QTextStream out(file); out << "ro,w,\"end, of \"\"\"\"\"\"row\"\"\"\"\"\n" << "2\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.at(0).at(0) == "ro"); QVERIFY(t.at(0).at(1) == "w"); @@ -105,41 +106,41 @@ void TestCsvParser::testQuoted() { } void TestCsvParser::testEmptySimple() { - QTextStream out(&file); + QTextStream out(file); out <<""; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 0); } void TestCsvParser::testEmptyQuoted() { - QTextStream out(&file); + QTextStream out(file); out <<"\"\""; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 0); } void TestCsvParser::testEmptyNewline() { - QTextStream out(&file); + QTextStream out(file); out <<"\"\n\""; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 0); } void TestCsvParser::testEmptyFile() { - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 0); } void TestCsvParser::testNewline() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\n\n\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 1); QVERIFY(t.at(0).at(0) == "1"); @@ -148,9 +149,9 @@ void TestCsvParser::testNewline() void TestCsvParser::testCR() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\r3,4"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 2); QVERIFY(t.at(0).at(0) == "1"); @@ -161,9 +162,9 @@ void TestCsvParser::testCR() void TestCsvParser::testLF() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\n3,4"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 2); QVERIFY(t.at(0).at(0) == "1"); @@ -174,9 +175,9 @@ void TestCsvParser::testLF() void TestCsvParser::testCRLF() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\r\n3,4"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 2); QVERIFY(t.at(0).at(0) == "1"); @@ -187,13 +188,13 @@ void TestCsvParser::testCRLF() void TestCsvParser::testComments() { - QTextStream out(&file); + QTextStream out(file); out << " #one\n" << " \t # two, three \r\n" << " #, sing\t with\r" << " #\t me!\n" << "useful,text #1!"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 1); QVERIFY(t.at(0).at(0) == "useful"); @@ -201,21 +202,21 @@ void TestCsvParser::testComments() } void TestCsvParser::testColumns() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\n" << ",,,,,,,,,a\n" << "a,b,c,d\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(parser->getCsvCols() == 10); } void TestCsvParser::testSimple() { - QTextStream out(&file); + QTextStream out(file); out << ",,2\r,2,3\n" << "A,,B\"\n" << " ,,\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 4); QVERIFY(t.at(0).at(0) == ""); @@ -234,11 +235,11 @@ void TestCsvParser::testSimple() { void TestCsvParser::testSeparator() { parser->setFieldSeparator('\t'); - QTextStream out(&file); + QTextStream out(file); out << "\t\t2\r\t2\t3\n" << "A\t\tB\"\n" << " \t\t\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 4); QVERIFY(t.at(0).at(0) == ""); @@ -258,10 +259,10 @@ void TestCsvParser::testSeparator() { void TestCsvParser::testMultiline() { parser->setTextQualifier(QChar(':')); - QTextStream out(&file); + QTextStream out(file); out << ":1\r\n2a::b:,:3\r4:\n" << "2\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.at(0).at(0) == "1\n2a:b"); QVERIFY(t.at(0).at(1) == "3\n4"); @@ -279,10 +280,10 @@ void TestCsvParser::testEmptyReparsing() void TestCsvParser::testReparsing() { - QTextStream out(&file); + QTextStream out(file); out << ":te\r\nxt1:,:te\rxt2:,:end of \"this\n string\":\n" << "2\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QEXPECT_FAIL("", "Wrong qualifier", Continue); @@ -301,10 +302,10 @@ void TestCsvParser::testReparsing() void TestCsvParser::testQualifier() { parser->setTextQualifier(QChar('X')); - QTextStream out(&file); + QTextStream out(file); out << "X1X,X2XX,X,\"\"3\"\"\"X\r" << "3,X\"4\"X,,\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 2); QVERIFY(t.at(0).at(0) == "1"); @@ -322,10 +323,10 @@ void TestCsvParser::testUnicode() { //CORRECT QChar g(0x20AC); //ERROR QChar g("\u20AC"); parser->setFieldSeparator(QChar('A')); - QTextStream out(&file); + QTextStream out(file); out << QString("€1A2śA\"3śAż\"Ażac"); - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 1); QVERIFY(t.at(0).at(0) == "€1"); diff --git a/tests/TestCsvParser.h b/tests/TestCsvParser.h index 975d86a92..928ac4201 100644 --- a/tests/TestCsvParser.h +++ b/tests/TestCsvParser.h @@ -20,6 +20,7 @@ #include #include +#include #include "core/CsvParser.h" @@ -60,7 +61,7 @@ private Q_SLOTS: void testColumns(); private: - QFile file; + QTemporaryFile* file; CsvParser* parser; CsvTable t; void dumpRow(CsvTable table, int row); From 39057a6aa085e603fcd0f24063bc101f7a77f0b4 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Mon, 6 Mar 2017 00:47:49 +0100 Subject: [PATCH 130/333] Better widget positions, removed futile message when no errors shows up --- src/core/CsvParser.cpp | 2 +- src/gui/AboutDialog.ui | 1 - src/gui/csvImport/CsvImportWidget.cpp | 23 +- src/gui/csvImport/CsvImportWidget.ui | 970 +++++++++++++------------- 4 files changed, 515 insertions(+), 481 deletions(-) diff --git a/src/core/CsvParser.cpp b/src/core/CsvParser.cpp index 0ab7f28cc..7f0443aac 100644 --- a/src/core/CsvParser.cpp +++ b/src/core/CsvParser.cpp @@ -315,7 +315,7 @@ bool CsvParser::isCRLF(const QChar &c) const { } bool CsvParser::isSpace(const QChar &c) const { - return (c == 0x20); + return (c == ' '); } bool CsvParser::isTab(const QChar &c) const { diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index bfa27689a..a853c0413 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -162,7 +162,6 @@ <li>louib</li> <li>phoerious</li> <li>thezero</li> -<li>seatedscribe</li> </ul> diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 2c782b89f..38eb09b93 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "gui/MessageBox.h" #include "gui/MessageWidget.h" @@ -52,13 +53,13 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) m_ui->comboBoxFieldSeparator->addItems(QStringList() <<"," <<";" <<"-" <<":" <<"."); m_ui->comboBoxTextQualifier->addItems(QStringList() <<"\"" <<"'" <<":" <<"." <<"|"); m_ui->comboBoxComment->addItems(QStringList() <<"#" <<";" <<":" <<"@"); - m_ui->tableViewFields->setSelectionMode(QAbstractItemView::NoSelection); m_ui->tableViewFields->setFocusPolicy(Qt::NoFocus); + m_ui->messageWidget->setHidden(true); for (int i = 0; i < m_columnHeader.count(); ++i) { QLabel* label = new QLabel(m_columnHeader.at(i), this); - label->setFixedWidth(label->minimumSizeHint().width()); + label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); QFont font = label->font(); font.setBold(false); label->setFont(font); @@ -78,6 +79,8 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) int y = 2 * (i / combo_rows); m_ui->gridLayout_combos->addWidget(label, x, y); m_ui->gridLayout_combos->addWidget(combo, x, y+1); + QSpacerItem *item = new QSpacerItem(1,1, QSizePolicy::Expanding, QSizePolicy::Fixed); + m_ui->gridLayout_combos->addItem(item, x, y+2); } m_parserModel->setHeaderLabels(m_columnHeader); @@ -169,13 +172,11 @@ void CsvImportWidget::parse() { bool good = m_parserModel->parse(); updatePreview(); QApplication::restoreOverrideCursor(); - if (good) - //four newline are provided to avoid resizing at every Positive/Warning switch - m_ui->messageWidget->showMessage(QString("\n\n").append(tr("CSV syntax seems in good shape !")) - .append("\n\n"), MessageWidget::Positive); - else + if (!good) m_ui->messageWidget->showMessage(tr("Error(s) detected in CSV file !").append("\n") .append(formatStatusText()), MessageWidget::Warning); + else + m_ui->messageWidget->setHidden(true); QWidget::adjustSize(); } @@ -183,12 +184,12 @@ void CsvImportWidget::parse() { QString CsvImportWidget::formatStatusText() const { QString text = m_parserModel->getStatus(); int items = text.count('\n'); - if (items > 3) - return text.section('\n', 0, 2) - .append("\n[").append(QString::number(items - 3)) + if (items > 2) + return text.section('\n', 0, 1) + .append("\n[").append(QString::number(items - 2)) .append(tr(" more messages skipped]")); else - for (int i = 0; i < 3 - items; i++) + for (int i = 0; i < 2 - items; i++) text.append(QString("\n")); return text; } diff --git a/src/gui/csvImport/CsvImportWidget.ui b/src/gui/csvImport/CsvImportWidget.ui index 82ee68dd2..1b4bed729 100644 --- a/src/gui/csvImport/CsvImportWidget.ui +++ b/src/gui/csvImport/CsvImportWidget.ui @@ -14,474 +14,6 @@ - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 758 - 24 - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - - - - - 0 - 137 - - - - - 75 - true - - - - Encoding - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - - 50 - false - - - - Text is qualified by - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 50 - false - - - - false - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 114 - 20 - - - - - - - - Qt::Horizontal - - - - 114 - 20 - - - - - - - - - 50 - false - - - - Codec - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - - 114 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 50 - false - - - - false - - - - - - - - 50 - false - - - - Fields are separated by - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 50 - false - - - - Comments start with - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 50 - false - - - - false - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 50 - false - - - - false - - - - - - - Qt::Horizontal - - - - 114 - 20 - - - - - - - - - 50 - false - - - - Consider '\' as escape character - - - - - - - - - - 50 - false - - - - Skip first - - - - - - - - 50 - false - - - - - - - - - 50 - false - - - - rows - - - - - - - - - - 50 - false - true - - - - - - - - - - - - - - - 75 - true - - - - Column layout - - - - - - 0 - - - - - - - - - - - 0 - 0 - - - - - 0 - 200 - - - - - 75 - true - - - - Preview - - - false - - - - - - - 0 - 0 - - - - - 50 - false - - - - true - - - - - - @@ -535,6 +67,508 @@ + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 75 + true + + + + Encoding + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Consider '\' an escape character + + + + + + + + 50 + false + + + + Codec + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + false + true + + + + + + + + + + + + 50 + false + + + + Fields are separated by + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + + 50 + false + + + + Comments start with + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Text is qualified by + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + + + 50 + false + + + + Skip first + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + false + + + + + + + + + 50 + false + + + + rows + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Column layout + + + + + + 6 + + + 6 + + + 0 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 758 + 24 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + 0 + 0 + + + + + 0 + 200 + + + + + 75 + true + + + + Preview + + + false + + + + + + + 0 + 0 + + + + + 50 + false + + + + true + + + + + + From 2ec500f926246531d9b3d80a4cf70058462b2761 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Mon, 6 Mar 2017 13:51:52 +0100 Subject: [PATCH 131/333] Reorder link dependencies --- src/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 677d2380c..5e221b916 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -206,14 +206,14 @@ set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUIL target_link_libraries(keepassx_core ${keepasshttp_LIB} ${autotype_LIB} + ${YUBIKEY_LIBRARIES} zxcvbn Qt5::Core Qt5::Concurrent Qt5::Widgets ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES} - ${ZLIB_LIBRARIES} - ${YUBIKEY_LIBRARIES}) + ${ZLIB_LIBRARIES}) if (UNIX AND NOT APPLE) target_link_libraries(keepassx_core Qt5::DBus) endif() From f4791c19e18b219809197512a8a7a2cca68bb444 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Mon, 6 Mar 2017 23:05:06 +0100 Subject: [PATCH 132/333] Assign uuid to newborn groups --- src/gui/csvImport/CsvImportWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 38eb09b93..4d64296e9 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -272,6 +272,7 @@ Group *CsvImportWidget::splitGroups(QString label) { Group *brandNew = new Group(); brandNew->setParent(current); brandNew->setName(groupName); + brandNew->setUuid(Uuid::random()); current = brandNew; } else { Q_ASSERT(children != nullptr); From e91a41401c966008f8e3fcf98ba11f2521afad4d Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Mon, 6 Mar 2017 22:08:43 -0500 Subject: [PATCH 133/333] Added current pledgie campaign --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4cb956bf2..2b6fd0315 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # KeePassXC - KeePass Cross-platform Community Edition [![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) +
KeePassXC Authenticode Certificate Campaign! ## About KeePassXC 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. From a03e35450415283a9f9101d19d71acb83202b129 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sun, 5 Mar 2017 23:47:08 +0100 Subject: [PATCH 134/333] highlight reference field in Database view --- src/core/Entry.cpp | 11 +++++++++++ src/core/Entry.h | 1 + src/core/EntryAttributes.cpp | 15 +++++++++++++++ src/core/EntryAttributes.h | 1 + src/gui/entry/EntryModel.cpp | 27 ++++++++++++++++++++++++--- 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 99f119f66..208a1dec9 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -254,6 +254,17 @@ bool Entry::isExpired() const return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < QDateTime::currentDateTimeUtc(); } +bool Entry::hasReferences() const +{ + const QList keyList = EntryAttributes::DefaultAttributes; + for (const QString& key : keyList) { + if (m_attributes->isReference(key)) { + return true; + } + } + return false; +} + EntryAttributes* Entry::attributes() { return m_attributes; diff --git a/src/core/Entry.h b/src/core/Entry.h index d08c7217c..38ec42d4e 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -79,6 +79,7 @@ public: QString password() const; QString notes() const; bool isExpired() const; + bool hasReferences() const; EntryAttributes* attributes(); const EntryAttributes* attributes() const; EntryAttachments* attachments(); diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp index b633cae32..6f3c4fd6f 100644 --- a/src/core/EntryAttributes.cpp +++ b/src/core/EntryAttributes.cpp @@ -69,6 +69,21 @@ bool EntryAttributes::isProtected(const QString& key) const return m_protectedAttributes.contains(key); } +bool EntryAttributes::isReference(const QString& key) const +{ + if (!m_attributes.contains(key)) { + Q_ASSERT(false); + return false; + } + + QString data = value(key); + QRegExp referenceRegExp("\\{REF:([TUPAN])@I:([^}]+)\\}", Qt::CaseInsensitive, QRegExp::RegExp2); + if (referenceRegExp.indexIn(data) != -1) { + return true; + } + return false; +} + void EntryAttributes::set(const QString& key, const QString& value, bool protect) { bool emitModified = false; diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h index 211b6d483..be33ab167 100644 --- a/src/core/EntryAttributes.h +++ b/src/core/EntryAttributes.h @@ -35,6 +35,7 @@ public: QString value(const QString& key) const; bool contains(const QString& key) const; bool isProtected(const QString& key) const; + bool isReference(const QString& key) const; void set(const QString& key, const QString& value, bool protect = false); void remove(const QString& key); void rename(const QString& oldKey, const QString& newKey); diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index d606a777e..323a55c82 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -19,6 +19,7 @@ #include #include +#include #include "core/DatabaseIcons.h" #include "core/Entry.h" @@ -127,8 +128,10 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const } Entry* entry = entryFromIndex(index); + EntryAttributes* attr = entry->attributes(); if (role == Qt::DisplayRole) { + QString result; switch (index.column()) { case ParentGroup: if (entry->group()) { @@ -136,11 +139,23 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const } break; case Title: - return entry->title(); + result = entry->resolvePlaceholder(entry->title()); + if (attr->isReference(EntryAttributes::TitleKey)) { + result.prepend(tr("Ref: ","Reference abbreviation")); + } + return result; case Username: - return entry->username(); + result = entry->resolvePlaceholder(entry->username()); + if (attr->isReference(EntryAttributes::UserNameKey)) { + result.prepend(tr("Ref: ","Reference abbreviation")); + } + return result; case Url: - return entry->url(); + result = entry->resolvePlaceholder(entry->url()); + if (attr->isReference(EntryAttributes::URLKey)) { + result.prepend(tr("Ref: ","Reference abbreviation")); + } + return result; } } else if (role == Qt::DecorationRole) { @@ -166,6 +181,12 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const } return font; } + else if (role == Qt::TextColorRole) { + if (entry->hasReferences()) { + QPalette p; + return QVariant(p.color(QPalette::Active, QPalette::Mid)); + } + } return QVariant(); } From 40851409fb90004b4aefda2775ff4ae3282c0da6 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Tue, 7 Mar 2017 17:19:41 +0100 Subject: [PATCH 135/333] reuse referenceRegExp --- src/core/Entry.cpp | 2 +- src/core/EntryAttributes.cpp | 9 +++++++-- src/core/EntryAttributes.h | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 208a1dec9..235dd3a7e 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -690,7 +690,7 @@ QString Entry::resolvePlaceholder(const QString& str) const // 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 - QRegExp tmpRegExp("\\{REF:([TUPAN])@I:([^}]+)\\}", Qt::CaseInsensitive, QRegExp::RegExp2); + QRegExp tmpRegExp = m_attributes->referenceRegExp(); if (tmpRegExp.indexIn(result) != -1) { // cap(0) contains the whole reference // cap(1) contains which field is wanted diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp index 6f3c4fd6f..94e5f96b6 100644 --- a/src/core/EntryAttributes.cpp +++ b/src/core/EntryAttributes.cpp @@ -28,6 +28,7 @@ const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD"; EntryAttributes::EntryAttributes(QObject* parent) : QObject(parent) + , m_referenceRegExp("\\{REF:([TUPAN])@I:([^}]+)\\}", Qt::CaseInsensitive, QRegExp::RegExp2) { clear(); } @@ -77,13 +78,17 @@ bool EntryAttributes::isReference(const QString& key) const } QString data = value(key); - QRegExp referenceRegExp("\\{REF:([TUPAN])@I:([^}]+)\\}", Qt::CaseInsensitive, QRegExp::RegExp2); - if (referenceRegExp.indexIn(data) != -1) { + if (m_referenceRegExp.indexIn(data) != -1) { return true; } return false; } +QRegExp EntryAttributes::referenceRegExp() const +{ + return m_referenceRegExp; +} + void EntryAttributes::set(const QString& key, const QString& value, bool protect) { bool emitModified = false; diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h index be33ab167..42abed1ee 100644 --- a/src/core/EntryAttributes.h +++ b/src/core/EntryAttributes.h @@ -36,6 +36,7 @@ public: bool contains(const QString& key) const; bool isProtected(const QString& key) const; bool isReference(const QString& key) const; + QRegExp referenceRegExp() const; void set(const QString& key, const QString& value, bool protect = false); void remove(const QString& key); void rename(const QString& oldKey, const QString& newKey); @@ -72,6 +73,7 @@ Q_SIGNALS: private: QMap m_attributes; QSet m_protectedAttributes; + QRegExp m_referenceRegExp; }; #endif // KEEPASSX_ENTRYATTRIBUTES_H From 78acdf9095f61f359d444e46d2cb2f4c30f27746 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Tue, 7 Mar 2017 20:16:51 +0100 Subject: [PATCH 136/333] pointer to referenceRegExp --- src/core/Entry.cpp | 18 +++++++++--------- src/core/EntryAttributes.cpp | 4 ++-- src/core/EntryAttributes.h | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 235dd3a7e..a2e72f7fd 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -690,20 +690,20 @@ QString Entry::resolvePlaceholder(const QString& str) const // 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 - QRegExp tmpRegExp = m_attributes->referenceRegExp(); - if (tmpRegExp.indexIn(result) != -1) { + QRegExp* tmpRegExp = m_attributes->referenceRegExp(); + if (tmpRegExp->indexIn(result) != -1) { // cap(0) contains the whole reference // cap(1) contains which field is wanted // cap(2) contains the uuid of the referenced entry - Entry* tmpRefEntry = m_group->database()->resolveEntry(Uuid(QByteArray::fromHex(tmpRegExp.cap(2).toLatin1()))); + Entry* tmpRefEntry = m_group->database()->resolveEntry(Uuid(QByteArray::fromHex(tmpRegExp->cap(2).toLatin1()))); if (tmpRefEntry) { // entry found, get the relevant field - QString tmpRefField = tmpRegExp.cap(1).toLower(); - if (tmpRefField == "t") result.replace(tmpRegExp.cap(0), tmpRefEntry->title(), Qt::CaseInsensitive); - else if (tmpRefField == "u") result.replace(tmpRegExp.cap(0), tmpRefEntry->username(), Qt::CaseInsensitive); - else if (tmpRefField == "p") result.replace(tmpRegExp.cap(0), tmpRefEntry->password(), Qt::CaseInsensitive); - else if (tmpRefField == "a") result.replace(tmpRegExp.cap(0), tmpRefEntry->url(), Qt::CaseInsensitive); - else if (tmpRefField == "n") result.replace(tmpRegExp.cap(0), tmpRefEntry->notes(), Qt::CaseInsensitive); + QString tmpRefField = tmpRegExp->cap(1).toLower(); + if (tmpRefField == "t") result.replace(tmpRegExp->cap(0), tmpRefEntry->title(), Qt::CaseInsensitive); + else if (tmpRefField == "u") result.replace(tmpRegExp->cap(0), tmpRefEntry->username(), Qt::CaseInsensitive); + else if (tmpRefField == "p") result.replace(tmpRegExp->cap(0), tmpRefEntry->password(), Qt::CaseInsensitive); + else if (tmpRefField == "a") result.replace(tmpRegExp->cap(0), tmpRefEntry->url(), Qt::CaseInsensitive); + else if (tmpRefField == "n") result.replace(tmpRegExp->cap(0), tmpRefEntry->notes(), Qt::CaseInsensitive); } } diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp index 94e5f96b6..865e853f2 100644 --- a/src/core/EntryAttributes.cpp +++ b/src/core/EntryAttributes.cpp @@ -84,9 +84,9 @@ bool EntryAttributes::isReference(const QString& key) const return false; } -QRegExp EntryAttributes::referenceRegExp() const +QRegExp* EntryAttributes::referenceRegExp() { - return m_referenceRegExp; + return &m_referenceRegExp; } void EntryAttributes::set(const QString& key, const QString& value, bool protect) diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h index 42abed1ee..78afe5efa 100644 --- a/src/core/EntryAttributes.h +++ b/src/core/EntryAttributes.h @@ -36,7 +36,7 @@ public: bool contains(const QString& key) const; bool isProtected(const QString& key) const; bool isReference(const QString& key) const; - QRegExp referenceRegExp() 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); From 31494ec32796ed723cd5b772bae014cc6e8afe6c Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 7 Mar 2017 22:38:18 -0500 Subject: [PATCH 137/333] Enhance attribute entry and add protected attributes (#220) * Allow protected attributes to be hidden * Entry area is resizable * Added test cases for protected attributes --- src/gui/entry/EditEntryWidget.cpp | 120 +++++++++++++++++------ src/gui/entry/EditEntryWidget.h | 4 + src/gui/entry/EditEntryWidgetAdvanced.ui | 99 +++++++++++++++---- tests/TestEntryModel.cpp | 6 ++ tests/gui/TestGui.cpp | 14 +++ 5 files changed, 195 insertions(+), 48 deletions(-) diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 51e0bb735..a30325057 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -132,6 +132,8 @@ void EditEntryWidget::setupAdvanced() connect(m_advancedUi->addAttributeButton, SIGNAL(clicked()), SLOT(insertAttribute())); connect(m_advancedUi->editAttributeButton, SIGNAL(clicked()), SLOT(editCurrentAttribute())); connect(m_advancedUi->removeAttributeButton, SIGNAL(clicked()), SLOT(removeCurrentAttribute())); + connect(m_advancedUi->protectAttributeButton, SIGNAL(toggled(bool)), SLOT(protectCurrentAttribute(bool))); + connect(m_advancedUi->revealAttributeButton, SIGNAL(clicked(bool)), SLOT(revealCurrentAttribute())); connect(m_advancedUi->attributesView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(updateCurrentAttribute())); @@ -351,6 +353,11 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore) m_advancedUi->attributesEdit->setEnabled(false); } + QList sizes = m_advancedUi->attributesSplitter->sizes(); + sizes.replace(0, m_advancedUi->attributesSplitter->width() * 0.3); + sizes.replace(1, m_advancedUi->attributesSplitter->width() * 0.7); + m_advancedUi->attributesSplitter->setSizes(sizes); + IconStruct iconStruct; iconStruct.uuid = entry->iconUuid(); iconStruct.number = entry->iconNumber(); @@ -409,7 +416,7 @@ void EditEntryWidget::saveEntry() return; } - if (m_advancedUi->attributesView->currentIndex().isValid()) { + if (m_advancedUi->attributesView->currentIndex().isValid() && m_advancedUi->attributesEdit->isEnabled()) { QString key = m_attributesModel->keyByIndex(m_advancedUi->attributesView->currentIndex()); m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), m_entryAttributes->isProtected(key)); @@ -578,46 +585,96 @@ void EditEntryWidget::removeCurrentAttribute() QModelIndex index = m_advancedUi->attributesView->currentIndex(); if (index.isValid()) { - m_entryAttributes->remove(m_attributesModel->keyByIndex(index)); + if (MessageBox::question(this, tr("Confirm Remove"), tr("Are you sure you want to remove this attribute?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_entryAttributes->remove(m_attributesModel->keyByIndex(index)); + } } } void EditEntryWidget::updateCurrentAttribute() { QModelIndex newIndex = m_advancedUi->attributesView->currentIndex(); + QString newKey = m_attributesModel->keyByIndex(newIndex); - if (m_history) { - if (newIndex.isValid()) { - QString key = m_attributesModel->keyByIndex(newIndex); - m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key)); - m_advancedUi->attributesEdit->setEnabled(true); + if (!m_history && m_currentAttribute != newIndex) { + // Save changes to the currently selected attribute if editing is enabled + if (m_currentAttribute.isValid() && m_advancedUi->attributesEdit->isEnabled()) { + QString currKey = m_attributesModel->keyByIndex(m_currentAttribute); + m_entryAttributes->set(currKey, m_advancedUi->attributesEdit->toPlainText(), + m_entryAttributes->isProtected(currKey)); + } + } + + displayAttribute(newIndex, m_entryAttributes->isProtected(newKey)); + + m_currentAttribute = newIndex; +} + +void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected) +{ + // Block signals to prevent extra calls + m_advancedUi->protectAttributeButton->blockSignals(true); + + if (index.isValid()) { + QString key = m_attributesModel->keyByIndex(index); + if (showProtected) { + m_advancedUi->attributesEdit->setPlainText(tr("[PROTECTED] Press reveal to view or edit")); + m_advancedUi->attributesEdit->setEnabled(false); + m_advancedUi->revealAttributeButton->setEnabled(true); + m_advancedUi->protectAttributeButton->setChecked(true); } else { - m_advancedUi->attributesEdit->setPlainText(""); - m_advancedUi->attributesEdit->setEnabled(false); + m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key)); + m_advancedUi->attributesEdit->setEnabled(true); + m_advancedUi->revealAttributeButton->setEnabled(false); + m_advancedUi->protectAttributeButton->setChecked(false); } + + // Don't allow editing in history view + m_advancedUi->protectAttributeButton->setEnabled(!m_history); + m_advancedUi->editAttributeButton->setEnabled(!m_history); + m_advancedUi->removeAttributeButton->setEnabled(!m_history); } else { - if (m_currentAttribute != newIndex) { - if (m_currentAttribute.isValid()) { - QString key = m_attributesModel->keyByIndex(m_currentAttribute); - m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), - m_entryAttributes->isProtected(key)); - } + m_advancedUi->attributesEdit->setPlainText(""); + m_advancedUi->attributesEdit->setEnabled(false); + m_advancedUi->revealAttributeButton->setEnabled(false); + m_advancedUi->protectAttributeButton->setChecked(false); + m_advancedUi->protectAttributeButton->setEnabled(false); + m_advancedUi->editAttributeButton->setEnabled(false); + m_advancedUi->removeAttributeButton->setEnabled(false); + } - if (newIndex.isValid()) { - QString key = m_attributesModel->keyByIndex(newIndex); - m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key)); - m_advancedUi->attributesEdit->setEnabled(true); - } - else { - m_advancedUi->attributesEdit->setPlainText(""); - m_advancedUi->attributesEdit->setEnabled(false); - } + m_advancedUi->protectAttributeButton->blockSignals(false); +} - m_advancedUi->editAttributeButton->setEnabled(newIndex.isValid()); - m_advancedUi->removeAttributeButton->setEnabled(newIndex.isValid()); - m_currentAttribute = newIndex; +void EditEntryWidget::protectCurrentAttribute(bool state) +{ + QModelIndex index = m_advancedUi->attributesView->currentIndex(); + if (!m_history && index.isValid()) { + QString key = m_attributesModel->keyByIndex(index); + if (state) { + // Save the current text and protect the attribute + m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(), true); + } else { + // Unprotect the current attribute value (don't save text as it is obscured) + m_entryAttributes->set(key, m_entryAttributes->value(key), false); + } + + // Display the attribute + displayAttribute(index, state); + } +} + +void EditEntryWidget::revealCurrentAttribute() +{ + if (! m_advancedUi->attributesEdit->isEnabled()) { + QModelIndex index = m_advancedUi->attributesView->currentIndex(); + if (index.isValid()) { + QString key = m_attributesModel->keyByIndex(index); + m_advancedUi->attributesEdit->setPlainText(m_entryAttributes->value(key)); + m_advancedUi->attributesEdit->setEnabled(true); } } } @@ -730,8 +787,13 @@ void EditEntryWidget::removeCurrentAttachment() return; } - QString key = m_attachmentsModel->keyByIndex(index); - m_entryAttachments->remove(key); + QMessageBox::StandardButton ans = MessageBox::question(this, tr("Confirm Remove"), + tr("Are you sure you want to remove this attachment?"), + QMessageBox::Yes | QMessageBox::No); + if (ans == QMessageBox::Yes) { + QString key = m_attachmentsModel->keyByIndex(index); + m_entryAttachments->remove(key); + } } void EditEntryWidget::updateAutoTypeEnabled() diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h index c8045d93c..270542e8c 100644 --- a/src/gui/entry/EditEntryWidget.h +++ b/src/gui/entry/EditEntryWidget.h @@ -76,6 +76,8 @@ private Q_SLOTS: void editCurrentAttribute(); void removeCurrentAttribute(); void updateCurrentAttribute(); + void protectCurrentAttribute(bool state); + void revealCurrentAttribute(); void insertAttachment(); void saveCurrentAttachment(); void openAttachment(const QModelIndex& index); @@ -110,6 +112,8 @@ private: QMenu* createPresetsMenu(); void updateEntryData(Entry* entry) const; + void displayAttribute(QModelIndex index, bool showProtected); + Entry* m_entry; Database* m_database; diff --git a/src/gui/entry/EditEntryWidgetAdvanced.ui b/src/gui/entry/EditEntryWidgetAdvanced.ui index 61380bb90..2c7f95dde 100644 --- a/src/gui/entry/EditEntryWidgetAdvanced.ui +++ b/src/gui/entry/EditEntryWidgetAdvanced.ui @@ -28,19 +28,50 @@ Additional attributes - + - - - - - + + + Qt::Horizontal + + false + + + + 0 + 0 + + + + QAbstractScrollArea::AdjustToContents + + + QListView::Adjust + + + + + false + + + + 0 + 0 + + + + + 170 + 0 + + + - + @@ -48,16 +79,6 @@ - - - - false - - - Edit - - - @@ -68,6 +89,16 @@ + + + + false + + + Edit Name + + + @@ -81,6 +112,35 @@ + + + + true + + + margin-left:50%;margin-right:50% + + + Protect + + + true + + + + + + + false + + + Reveal + + + false + + + @@ -103,7 +163,7 @@
- + @@ -172,11 +232,12 @@ attributesView attributesEdit addAttributeButton - editAttributeButton removeAttributeButton + editAttributeButton attachmentsView addAttachmentButton removeAttachmentButton + openAttachmentButton saveAttachmentButton diff --git a/tests/TestEntryModel.cpp b/tests/TestEntryModel.cpp index d5a16ebab..e0c8bb490 100644 --- a/tests/TestEntryModel.cpp +++ b/tests/TestEntryModel.cpp @@ -181,6 +181,12 @@ void TestEntryModel::testAttributesModel() QCOMPARE(spyAboutToRemove.count(), 1); QCOMPARE(spyRemoved.count(), 1); + // test attribute protection + QString value = entryAttributes->value("2nd"); + entryAttributes->set("2nd", value, true); + QVERIFY(entryAttributes->isProtected("2nd")); + QCOMPARE(entryAttributes->value("2nd"), value); + QSignalSpy spyReset(model, SIGNAL(modelReset())); entryAttributes->clear(); model->setEntryAttributes(0); diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 3893038ba..7df5942e8 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -242,6 +243,19 @@ void TestGui::testEditEntry() QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "_test"); + // Test protected attributes + editEntryWidget->setCurrentPage(1); + QPlainTextEdit* attrTextEdit = editEntryWidget->findChild("attributesEdit"); + QTest::mouseClick(editEntryWidget->findChild("addAttributeButton"), Qt::LeftButton); + QString attrText = "TEST TEXT"; + QTest::keyClicks(attrTextEdit, attrText); + QCOMPARE(attrTextEdit->toPlainText(), attrText); + QTest::mouseClick(editEntryWidget->findChild("protectAttributeButton"), Qt::LeftButton); + QVERIFY(attrTextEdit->toPlainText().contains("PROTECTED")); + QTest::mouseClick(editEntryWidget->findChild("revealAttributeButton"), Qt::LeftButton); + QCOMPARE(attrTextEdit->toPlainText(), attrText); + editEntryWidget->setCurrentPage(0); + // Save the edit QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); From 984602b7a0b6b79cb2c74f1e0b47f9e069945cd4 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Wed, 8 Mar 2017 22:58:46 +0100 Subject: [PATCH 138/333] Enhance FormatStatusText(), other minor cosmetics --- src/gui/csvImport/CsvImportWidget.cpp | 19 ++++++++++--------- src/gui/csvImport/CsvParserModel.cpp | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 4d64296e9..ed00790f5 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -138,14 +138,14 @@ void CsvImportWidget::updatePreview() { int i; QStringList list(tr("Not present in CSV file")); - for (i = 1; i < m_parserModel->getCsvCols(); i++) { + for (i = 1; i < m_parserModel->getCsvCols(); ++i) { QString s = QString(tr("Column ")) + QString::number(i); list << s; } m_comboModel->setStringList(list); i=1; - Q_FOREACH (QComboBox* b, m_combos) { + for (QComboBox* b : m_combos) { if (i < m_parserModel->getCsvCols()) b->setCurrentIndex(i); else @@ -184,20 +184,21 @@ void CsvImportWidget::parse() { QString CsvImportWidget::formatStatusText() const { QString text = m_parserModel->getStatus(); int items = text.count('\n'); - if (items > 2) + if (items > 2) { return text.section('\n', 0, 1) .append("\n[").append(QString::number(items - 2)) .append(tr(" more messages skipped]")); - else - for (int i = 0; i < 2 - items; i++) - text.append(QString("\n")); - return text; + } + if (items == 1) { + text.append(QString("\n")); + } + return text; } void CsvImportWidget::writeDatabase() { setRootGroup(); - for (int r = 0; r < m_parserModel->rowCount(); r++) { + for (int r = 0; r < m_parserModel->rowCount(); ++r) { //use validity of second column as a GO/NOGO for all others fields if (not m_parserModel->data(m_parserModel->index(r, 1)).isValid()) continue; @@ -229,7 +230,7 @@ void CsvImportWidget::setRootGroup() { bool is_empty = false; bool is_label = false; - for (int r = 0; r < m_parserModel->rowCount(); r++) { + for (int r = 0; r < m_parserModel->rowCount(); ++r) { //use validity of second column as a GO/NOGO for all others fields if (not m_parserModel->data(m_parserModel->index(r, 1)).isValid()) continue; diff --git a/src/gui/csvImport/CsvParserModel.cpp b/src/gui/csvImport/CsvParserModel.cpp index d315f1ad2..efffda552 100644 --- a/src/gui/csvImport/CsvParserModel.cpp +++ b/src/gui/csvImport/CsvParserModel.cpp @@ -46,7 +46,7 @@ bool CsvParserModel::parse() { QFile csv(m_filename); r = CsvParser::parse(&csv); } - for (int i = 0; i < columnCount(); i++) + for (int i = 0; i < columnCount(); ++i) m_columnMap.insert(i,0); addEmptyColumn(); endResetModel(); From da0afd39395ed5aaae5ebf35d1905ccc9f94d186 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Mar 2017 14:28:26 +0100 Subject: [PATCH 139/333] Fix compiler warnings --- src/gui/Application.cpp | 4 ++-- src/gui/EditWidgetIcons.cpp | 3 ++- src/gui/MainWindow.ui | 4 +--- src/gui/entry/EditEntryWidgetAutoType.ui | 5 ----- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 26d9d2283..34d1b8680 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -148,7 +148,7 @@ void Application::handleUnixSignal(int sig) case SIGTERM: { char buf = 0; - ::write(unixSignalSocket[0], &buf, sizeof(buf)); + Q_UNUSED(::write(unixSignalSocket[0], &buf, sizeof(buf))); return; } case SIGHUP: @@ -160,7 +160,7 @@ void Application::quitBySignal() { m_unixSignalNotifier->setEnabled(false); char buf; - ::read(unixSignalSocket[1], &buf, sizeof(buf)); + Q_UNUSED(::read(unixSignalSocket[1], &buf, sizeof(buf))); if (nullptr != m_mainWindow) static_cast(m_mainWindow)->appExit(); diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 1e1f5db29..d789eb498 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -48,9 +48,9 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) , m_defaultIconModel(new DefaultIconModel(this)) , m_customIconModel(new CustomIconModel(this)) #ifdef WITH_XC_HTTP - , m_httpClient(nullptr) , m_fallbackToGoogle(true) , m_redirectCount(0) + , m_httpClient(nullptr) #endif { m_ui->setupUi(this); @@ -149,6 +149,7 @@ void EditWidgetIcons::setUrl(const QString& url) m_ui->faviconButton->setVisible(!url.isEmpty()); resetFaviconDownload(); #else + Q_UNUSED(url); m_ui->faviconButton->setVisible(false); #endif } diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 6e3ecf684..1d57d557c 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -123,9 +123,7 @@ - - horizontalSpacer_2 - + diff --git a/src/gui/entry/EditEntryWidgetAutoType.ui b/src/gui/entry/EditEntryWidgetAutoType.ui index 21e102bfe..a8090f768 100644 --- a/src/gui/entry/EditEntryWidgetAutoType.ui +++ b/src/gui/entry/EditEntryWidgetAutoType.ui @@ -259,11 +259,6 @@ - - - - -
From 2872f1706c08b89709ab80821da40a4f53f1c336 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Mar 2017 15:05:02 +0100 Subject: [PATCH 140/333] Fix Qt deprecation warnings --- src/http/Service.cpp | 4 +++- tests/TestGroupModel.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/http/Service.cpp b/src/http/Service.cpp index 1a89b7328..676bb8921 100644 --- a/src/http/Service.cpp +++ b/src/http/Service.cpp @@ -29,6 +29,8 @@ #include "core/Uuid.h" #include "core/PasswordGenerator.h" +#include + static const unsigned char KEEPASSHTTP_UUID_DATA[] = { 0x34, 0x69, 0x7a, 0x40, 0x8a, 0x5b, 0x41, 0xc0, 0x9f, 0x36, 0x89, 0x7d, 0x62, 0x3e, 0xcb, 0x31 @@ -387,7 +389,7 @@ QList Service::findMatchingEntries(const QString& /* priorities.insert(entry, sortPriority(entry, host, submitUrl, baseSubmitURL)); //Sort by priorities - qSort(pwEntries.begin(), pwEntries.end(), SortEntries(priorities, HttpSettings::sortByTitle() ? "Title" : "UserName")); + std::sort(pwEntries.begin(), pwEntries.end(), SortEntries(priorities, HttpSettings::sortByTitle() ? "Title" : "UserName")); } //if (pwEntries.count() > 0) diff --git a/tests/TestGroupModel.cpp b/tests/TestGroupModel.cpp index 3608cc475..1faf82aa2 100644 --- a/tests/TestGroupModel.cpp +++ b/tests/TestGroupModel.cpp @@ -131,7 +131,7 @@ void TestGroupModel::test() QCOMPARE(spyMoved.count(), 3); QVERIFY(index12.isValid()); QCOMPARE(model->data(index12).toString(), QString("group12")); - QCOMPARE(model->data(index12.child(0, 0)).toString(), QString("group121")); + QCOMPARE(model->data(index12.model()->index(0, 0, index12)).toString(), QString("group121")); delete group12; QCOMPARE(spyAboutToAdd.count(), 1); From cb51ec61f739b16710071e8c022f5c7418fdab6e Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Mar 2017 15:45:00 +0100 Subject: [PATCH 141/333] Replace remaining instances of Q_FOREACH with C++11 range-based for loops --- src/core/PasswordGenerator.cpp | 4 +- src/gui/KMessageWidget.cpp | 8 ++-- src/gui/SettingsWidget.cpp | 7 ++- src/http/AccessControlDialog.cpp | 5 +- src/http/Protocol.cpp | 13 ++++-- src/http/Service.cpp | 79 +++++++++++++++++++------------- 6 files changed, 72 insertions(+), 44 deletions(-) diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index aea237a2e..cee1c55be 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -97,11 +97,11 @@ QString PasswordGenerator::generatePassword() const int PasswordGenerator::getbits() const { - QVector groups = passwordGroups(); + const QVector groups = passwordGroups(); int bits = 0; QVector passwordChars; - Q_FOREACH (const PasswordGroup& group, groups) { + for (const PasswordGroup& group: groups) { bits += group.size(); } diff --git a/src/gui/KMessageWidget.cpp b/src/gui/KMessageWidget.cpp index b88c3bc12..7f4cb94f4 100644 --- a/src/gui/KMessageWidget.cpp +++ b/src/gui/KMessageWidget.cpp @@ -21,6 +21,7 @@ #include "KMessageWidget.h" #include "core/FilePath.h" +#include "core/Global.h" #include #include @@ -117,7 +118,8 @@ void KMessageWidgetPrivate::createLayout() qDeleteAll(buttons); buttons.clear(); - Q_FOREACH (QAction *action, q->actions()) { + const auto actions = q->actions(); + for (QAction *action: actions) { QToolButton *button = new QToolButton(content); button->setDefaultAction(action); button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); @@ -137,7 +139,7 @@ void KMessageWidgetPrivate::createLayout() QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); - Q_FOREACH (QToolButton *button, buttons) { + for (QToolButton* button: asConst(buttons)) { // For some reason, calling show() is necessary if wordwrap is true, // otherwise the buttons do not show up. It is not needed if // wordwrap is false. @@ -151,7 +153,7 @@ void KMessageWidgetPrivate::createLayout() layout->addWidget(iconLabel); layout->addWidget(textLabel); - Q_FOREACH (QToolButton *button, buttons) { + for (QToolButton* button: asConst(buttons)) { layout->addWidget(button); } diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 3ec9674fe..8aa6982d7 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -23,6 +23,7 @@ #include "core/Config.h" #include "core/Translator.h" #include "core/FilePath.h" +#include "core/Global.h" class SettingsWidget::ExtraPage { @@ -144,8 +145,9 @@ void SettingsWidget::loadSettings() m_secUi->passwordRepeatCheckBox->setChecked(config()->get("security/passwordsrepeat").toBool()); - Q_FOREACH (const ExtraPage& page, m_extraPages) + for (const ExtraPage& page: asConst(m_extraPages)) { page.loadSettings(); + } setCurrentPage(0); } @@ -190,8 +192,9 @@ void SettingsWidget::saveSettings() config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked()); config()->set("security/passwordsrepeat", m_secUi->passwordRepeatCheckBox->isChecked()); - Q_FOREACH (const ExtraPage& page, m_extraPages) + for (const ExtraPage& page: asConst(m_extraPages)) { page.saveSettings(); + } Q_EMIT editFinished(true); } diff --git a/src/http/AccessControlDialog.cpp b/src/http/AccessControlDialog.cpp index 60422be4f..4d21aa95c 100644 --- a/src/http/AccessControlDialog.cpp +++ b/src/http/AccessControlDialog.cpp @@ -34,10 +34,11 @@ void AccessControlDialog::setUrl(const QString &url) "Please select whether you want to allow access.")).arg(QUrl(url).host())); } -void AccessControlDialog::setItems(const QList &items) +void AccessControlDialog::setItems(const QList &items) { - Q_FOREACH (Entry * entry, items) + for (Entry* entry: items) { ui->itemsList->addItem(entry->title() + " - " + entry->username()); + } } bool AccessControlDialog::remember() const diff --git a/src/http/Protocol.cpp b/src/http/Protocol.cpp index bcb30f0b1..7b37013f8 100644 --- a/src/http/Protocol.cpp +++ b/src/http/Protocol.cpp @@ -17,6 +17,7 @@ #include "crypto/Random.h" #include "crypto/SymmetricCipher.h" #include "crypto/SymmetricCipherGcrypt.h" +#include "core/Global.h" namespace KeepassHttpProtocol { @@ -370,8 +371,9 @@ QVariant Response::getEntries() const QList res; res.reserve(m_entries.size()); - Q_FOREACH (const Entry &entry, m_entries) + for (const Entry& entry: asConst(m_entries)) { res.append(qobject2qvariant(&entry)); + } return res; } @@ -383,14 +385,16 @@ void Response::setEntries(const QList &entries) QList encryptedEntries; encryptedEntries.reserve(m_count); - Q_FOREACH (const Entry &entry, entries) { + for (const Entry& entry: entries) { Entry encryptedEntry(encrypt(entry.name(), m_cipher), encrypt(entry.login(), m_cipher), entry.password().isNull() ? QString() : encrypt(entry.password(), m_cipher), encrypt(entry.uuid(), m_cipher)); - Q_FOREACH (const StringField & field, entry.stringFields()) + const auto stringFields = entry.stringFields(); + for (const StringField& field: stringFields) { encryptedEntry.addStringField(encrypt(field.key(), m_cipher), encrypt(field.value(), m_cipher)); + } encryptedEntries << encryptedEntry; } m_entries = encryptedEntries; @@ -508,8 +512,9 @@ QVariant Entry::getStringFields() const QList res; res.reserve(m_stringFields.size()); - Q_FOREACH (const StringField &stringfield, m_stringFields) + for (const StringField& stringfield: asConst(m_stringFields)) { res.append(qobject2qvariant(&stringfield)); + } return res; } diff --git a/src/http/Service.cpp b/src/http/Service.cpp index 676bb8921..25c69f614 100644 --- a/src/http/Service.cpp +++ b/src/http/Service.cpp @@ -23,6 +23,7 @@ #include "core/Database.h" #include "core/Entry.h" +#include "core/Global.h" #include "core/Group.h" #include "core/EntrySearcher.h" #include "core/Metadata.h" @@ -189,8 +190,9 @@ bool Service::removeFirstDomain(QString & hostname) QList Service::searchEntries(Database* db, const QString& hostname) { QList entries; - if (Group* rootGroup = db->rootGroup()) - Q_FOREACH (Entry* entry, EntrySearcher().search(hostname, rootGroup, Qt::CaseInsensitive)) { + if (Group* rootGroup = db->rootGroup()) { + const auto results = EntrySearcher().search(hostname, rootGroup, Qt::CaseInsensitive); + for (Entry* entry: results) { QString title = entry->title(); QString url = entry->url(); @@ -201,6 +203,7 @@ QList Service::searchEntries(Database* db, const QString& hostname) || (matchUrlScheme(url) && hostname.endsWith(QUrl(url).host())) ) entries.append(entry); } + } return entries; } @@ -223,8 +226,9 @@ QList Service::searchEntries(const QString& text) QString hostname = QUrl(text).host(); QList entries; do { - Q_FOREACH (Database* db, databases) + for (Database* db: asConst(databases)) { entries << searchEntries(db, hostname); + } } while(entries.isEmpty() && removeFirstDomain(hostname)); return entries; @@ -249,9 +253,12 @@ KeepassHttpProtocol::Entry Service::prepareEntry(const Entry* entry) KeepassHttpProtocol::Entry res(entry->resolvePlaceholder(entry->title()), entry->resolvePlaceholder(entry->username()), entry->resolvePlaceholder(entry->password()), entry->uuid().toHex()); if (HttpSettings::supportKphFields()) { const EntryAttributes * attr = entry->attributes(); - Q_FOREACH (const QString& key, attr->keys()) - if (key.startsWith(QLatin1String("KPH: "))) + const auto keys = attr->keys(); + for (const QString& key: keys) { + if (key.startsWith(QLatin1String("KPH: "))) { res.addStringField(key, attr->value(key)); + } + } } return res; } @@ -318,7 +325,8 @@ QList Service::findMatchingEntries(const QString& /* //Check entries for authorization QList pwEntriesToConfirm; QList pwEntries; - Q_FOREACH (Entry * entry, searchEntries(url)) { + const auto entries = searchEntries(url); + for (Entry* entry: entries) { switch(checkAccess(entry, host, submitHost, realm)) { case Denied: continue; @@ -352,7 +360,7 @@ QList Service::findMatchingEntries(const QString& /* int res = dlg.exec(); if (dlg.remember()) { - Q_FOREACH (Entry * entry, pwEntriesToConfirm) { + for (Entry* entry: asConst(pwEntriesToConfirm)) { EntryConfig config; config.load(entry); if (res == QDialog::Accepted) { @@ -383,28 +391,22 @@ QList Service::findMatchingEntries(const QString& /* const QString baseSubmitURL = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment); //Cache priorities - QHash priorities; + QHash priorities; priorities.reserve(pwEntries.size()); - Q_FOREACH (const Entry * entry, pwEntries) + for (const Entry* entry: asConst(pwEntries)) { priorities.insert(entry, sortPriority(entry, host, submitUrl, baseSubmitURL)); + } //Sort by priorities std::sort(pwEntries.begin(), pwEntries.end(), SortEntries(priorities, HttpSettings::sortByTitle() ? "Title" : "UserName")); } - //if (pwEntries.count() > 0) - //{ - // var names = (from e in resp.Entries select e.Name).Distinct(); - // var n = String.Join("\n ", names.ToArray()); - // if (HttpSettings::receiveCredentialNotification()) - // ShowNotification(QString("%0: %1 is receiving credentials for:\n%2").arg(Id).arg(host).arg(n))); - //} - //Fill the list QList result; result.reserve(pwEntries.count()); - Q_FOREACH (Entry * entry, pwEntries) + for (Entry* entry: asConst(pwEntries)) { result << prepareEntry(entry); + } return result; } @@ -416,12 +418,19 @@ int Service::countMatchingEntries(const QString &, const QString &url, const QSt QList Service::searchAllEntries(const QString &) { QList result; - if (DatabaseWidget * dbWidget = m_dbTabWidget->currentDatabaseWidget()) - if (Database * db = dbWidget->database()) - if (Group * rootGroup = db->rootGroup()) - Q_FOREACH (Entry * entry, rootGroup->entriesRecursive()) - if (!entry->url().isEmpty() || QUrl(entry->title()).isValid()) - result << KeepassHttpProtocol::Entry(entry->title(), entry->username(), QString(), entry->uuid().toHex()); + if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) { + if (Database* db = dbWidget->database()) { + if (Group* rootGroup = db->rootGroup()) { + const auto entries = rootGroup->entriesRecursive(); + for (Entry* entry: entries) { + if (!entry->url().isEmpty() || QUrl(entry->title()).isValid()) { + result << KeepassHttpProtocol::Entry(entry->title(), entry->username(), + QString(), entry->uuid().toHex()); + } + } + } + } + } return result; } @@ -430,11 +439,15 @@ Group * Service::findCreateAddEntryGroup() if (DatabaseWidget * dbWidget = m_dbTabWidget->currentDatabaseWidget()) if (Database * db = dbWidget->database()) if (Group * rootGroup = db->rootGroup()) { - const QString groupName = QLatin1String(KEEPASSHTTP_GROUP_NAME);//TODO: setting to decide where new keys are created + //TODO: setting to decide where new keys are created + const QString groupName = QLatin1String(KEEPASSHTTP_GROUP_NAME); - Q_FOREACH (const Group * g, rootGroup->groupsRecursive(true)) - if (g->name() == groupName) + const auto groups = rootGroup->groupsRecursive(true); + for (const Group * g: groups) { + if (g->name() == groupName) { return db->resolveGroup(g->uuid()); + } + } Group * group; group = new Group(); @@ -507,14 +520,18 @@ void Service::removeSharedEncryptionKeys() QMessageBox::Ok); } else if (Entry* entry = getConfigEntry()) { QStringList keysToRemove; - Q_FOREACH (const QString& key, entry->attributes()->keys()) - if (key.startsWith(ASSOCIATE_KEY_PREFIX)) + const auto keys = entry->attributes()->keys(); + for (const QString& key: keys) { + if (key.startsWith(ASSOCIATE_KEY_PREFIX)) { keysToRemove << key; + } + } if(keysToRemove.count()) { entry->beginUpdate(); - Q_FOREACH (const QString& key, keysToRemove) + for (const QString& key: asConst(keysToRemove)) { entry->attributes()->remove(key); + } entry->endUpdate(); const int count = keysToRemove.count(); @@ -548,7 +565,7 @@ void Service::removeStoredPermissions() progress.setWindowModality(Qt::WindowModal); uint counter = 0; - Q_FOREACH (Entry* entry, entries) { + for (Entry* entry: asConst(entries)) { if (progress.wasCanceled()) return; if (entry->attributes()->contains(KEEPASSHTTP_NAME)) { From 4c0e2af6e3912142a021e773f51cd62fb12aed53 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Mar 2017 15:56:24 +0100 Subject: [PATCH 142/333] Remove UTF-8 BOM --- src/zxcvbn/zxcvbn.cpp | 2 +- src/zxcvbn/zxcvbn.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zxcvbn/zxcvbn.cpp b/src/zxcvbn/zxcvbn.cpp index c999adfae..52c0bb1f3 100644 --- a/src/zxcvbn/zxcvbn.cpp +++ b/src/zxcvbn/zxcvbn.cpp @@ -1,4 +1,4 @@ -/********************************************************************************** +/********************************************************************************** * C implementation of the zxcvbn password strength estimation method. * Copyright (c) 2015, Tony Evans * All rights reserved. diff --git a/src/zxcvbn/zxcvbn.h b/src/zxcvbn/zxcvbn.h index 2d3ec52c1..796d6b47b 100644 --- a/src/zxcvbn/zxcvbn.h +++ b/src/zxcvbn/zxcvbn.h @@ -1,4 +1,4 @@ -#ifndef ZXCVBN_H_F98183CE2A01_INCLUDED +#ifndef ZXCVBN_H_F98183CE2A01_INCLUDED #define ZXCVBN_H_F98183CE2A01_INCLUDED /********************************************************************************** * C implementation of the zxcvbn password strength estimation method. From 8d487d31a4c757a7b1f4faad3af85737d32961d5 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Mar 2017 15:58:42 +0100 Subject: [PATCH 143/333] Replace Q_EMIT, Q_SIGNALS and Q_SLOTS macros with MOC keywords --- src/autotype/AutoType.h | 6 +-- src/autotype/AutoTypeSelectDialog.cpp | 2 +- src/autotype/AutoTypeSelectDialog.h | 6 +-- src/autotype/AutoTypeSelectView.h | 2 +- src/autotype/mac/AutoTypeMac.cpp | 2 +- src/autotype/mac/AutoTypeMac.h | 2 +- src/autotype/test/AutoTypeTest.h | 2 +- src/autotype/windows/AutoTypeWindows.cpp | 2 +- src/autotype/windows/AutoTypeWindows.h | 2 +- src/autotype/xcb/AutoTypeXCB.cpp | 2 +- src/autotype/xcb/AutoTypeXCB.h | 2 +- src/core/AutoTypeAssociations.cpp | 22 +++++----- src/core/AutoTypeAssociations.h | 2 +- src/core/Database.cpp | 4 +- src/core/Database.h | 4 +- src/core/Entry.cpp | 16 +++---- src/core/Entry.h | 4 +- src/core/EntryAttachments.cpp | 26 +++++------ src/core/EntryAttachments.h | 2 +- src/core/EntryAttributes.cpp | 40 ++++++++--------- src/core/EntryAttributes.h | 2 +- src/core/Group.cpp | 48 ++++++++++----------- src/core/Group.h | 2 +- src/core/InactivityTimer.cpp | 2 +- src/core/InactivityTimer.h | 4 +- src/core/Metadata.cpp | 14 +++--- src/core/Metadata.h | 2 +- src/gui/Application.cpp | 2 +- src/gui/Application.h | 4 +- src/gui/ChangeMasterKeyWidget.cpp | 4 +- src/gui/ChangeMasterKeyWidget.h | 4 +- src/gui/Clipboard.h | 4 +- src/gui/CloneDialog.h | 2 +- src/gui/DatabaseOpenWidget.cpp | 4 +- src/gui/DatabaseOpenWidget.h | 6 +-- src/gui/DatabaseRepairWidget.cpp | 16 +++---- src/gui/DatabaseRepairWidget.h | 4 +- src/gui/DatabaseSettingsWidget.cpp | 4 +- src/gui/DatabaseSettingsWidget.h | 4 +- src/gui/DatabaseTabWidget.cpp | 28 ++++++------ src/gui/DatabaseTabWidget.h | 6 +-- src/gui/DatabaseWidget.cpp | 28 ++++++------ src/gui/DatabaseWidget.h | 6 +-- src/gui/DatabaseWidgetStateSync.h | 4 +- src/gui/DragTabBar.h | 2 +- src/gui/EditWidgetIcons.cpp | 2 +- src/gui/EditWidgetIcons.h | 6 +-- src/gui/KMessageWidget.h | 4 +- src/gui/KeePass1OpenWidget.cpp | 2 +- src/gui/LineEdit.h | 2 +- src/gui/MainWindow.h | 4 +- src/gui/MessageWidget.h | 2 +- src/gui/PasswordComboBox.h | 2 +- src/gui/PasswordEdit.cpp | 2 +- src/gui/PasswordEdit.h | 6 +-- src/gui/PasswordGeneratorWidget.cpp | 4 +- src/gui/PasswordGeneratorWidget.h | 4 +- src/gui/SettingsWidget.cpp | 4 +- src/gui/SettingsWidget.h | 4 +- src/gui/UnlockDatabaseDialog.cpp | 2 +- src/gui/UnlockDatabaseDialog.h | 4 +- src/gui/WelcomeWidget.cpp | 2 +- src/gui/WelcomeWidget.h | 4 +- src/gui/entry/AutoTypeAssociationsModel.cpp | 2 +- src/gui/entry/AutoTypeAssociationsModel.h | 2 +- src/gui/entry/EditEntryWidget.cpp | 10 ++--- src/gui/entry/EditEntryWidget.h | 4 +- src/gui/entry/EntryAttachmentsModel.cpp | 2 +- src/gui/entry/EntryAttachmentsModel.h | 2 +- src/gui/entry/EntryAttributesModel.cpp | 4 +- src/gui/entry/EntryAttributesModel.h | 2 +- src/gui/entry/EntryModel.cpp | 6 +-- src/gui/entry/EntryModel.h | 6 +-- src/gui/entry/EntryView.cpp | 6 +-- src/gui/entry/EntryView.h | 6 +-- src/gui/group/EditGroupWidget.cpp | 4 +- src/gui/group/EditGroupWidget.h | 4 +- src/gui/group/GroupModel.cpp | 2 +- src/gui/group/GroupModel.h | 2 +- src/gui/group/GroupView.cpp | 4 +- src/gui/group/GroupView.h | 4 +- src/http/HttpPasswordGeneratorWidget.h | 2 +- src/http/OptionDialog.h | 4 +- src/http/Service.h | 2 +- src/streams/LayeredStream.h | 2 +- tests/TestAutoType.h | 2 +- tests/TestCryptoHash.h | 2 +- tests/TestCsvExporter.h | 2 +- tests/TestDeletedObjects.h | 2 +- tests/TestEntry.h | 2 +- tests/TestEntryModel.h | 2 +- tests/TestEntrySearcher.h | 2 +- tests/TestExporter.h | 2 +- tests/TestGroup.h | 2 +- tests/TestGroupModel.h | 2 +- tests/TestHashedBlockStream.h | 2 +- tests/TestKeePass1Reader.h | 2 +- tests/TestKeePass2RandomStream.h | 2 +- tests/TestKeePass2Reader.h | 2 +- tests/TestKeePass2Writer.h | 2 +- tests/TestKeePass2XmlReader.h | 2 +- tests/TestKeys.h | 2 +- tests/TestModified.h | 2 +- tests/TestRandom.h | 2 +- tests/TestSymmetricCipher.h | 2 +- tests/TestWildcardMatcher.h | 2 +- tests/gui/TestGui.h | 2 +- tests/gui/TestGuiPixmaps.h | 2 +- tests/modeltest.h | 4 +- 109 files changed, 274 insertions(+), 274 deletions(-) diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index 9799af4f3..311eedaab 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -48,13 +48,13 @@ public: static AutoType* instance(); static void createTestInstance(); -public Q_SLOTS: +public slots: void performGlobalAutoType(const QList& dbList); -Q_SIGNALS: +signals: void globalShortcutTriggered(); -private Q_SLOTS: +private slots: void performAutoTypeFromGlobal(Entry* entry, const QString& sequence); void resetInAutoType(); void unloadPlugin(); diff --git a/src/autotype/AutoTypeSelectDialog.cpp b/src/autotype/AutoTypeSelectDialog.cpp index 6bb155b81..240dd723b 100644 --- a/src/autotype/AutoTypeSelectDialog.cpp +++ b/src/autotype/AutoTypeSelectDialog.cpp @@ -91,7 +91,7 @@ void AutoTypeSelectDialog::emitEntryActivated(const QModelIndex& index) Entry* entry = m_view->entryFromIndex(index); accept(); - Q_EMIT entryActivated(entry, m_sequences[entry]); + emit entryActivated(entry, m_sequences[entry]); } void AutoTypeSelectDialog::entryRemoved() diff --git a/src/autotype/AutoTypeSelectDialog.h b/src/autotype/AutoTypeSelectDialog.h index 7b3909a19..3d9c684ed 100644 --- a/src/autotype/AutoTypeSelectDialog.h +++ b/src/autotype/AutoTypeSelectDialog.h @@ -33,13 +33,13 @@ public: explicit AutoTypeSelectDialog(QWidget* parent = nullptr); void setEntries(const QList& entries, const QHash& sequences); -Q_SIGNALS: +signals: void entryActivated(Entry* entry, const QString& sequence); -public Q_SLOTS: +public slots: void done(int r) override; -private Q_SLOTS: +private slots: void emitEntryActivated(const QModelIndex& index); void entryRemoved(); diff --git a/src/autotype/AutoTypeSelectView.h b/src/autotype/AutoTypeSelectView.h index 749f6a9f3..a781757b8 100644 --- a/src/autotype/AutoTypeSelectView.h +++ b/src/autotype/AutoTypeSelectView.h @@ -32,7 +32,7 @@ public: protected: void mouseMoveEvent(QMouseEvent* event) override; -private Q_SLOTS: +private slots: void selectFirstEntry(); }; diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index e55c336cb..08df6310e 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -460,7 +460,7 @@ OSStatus AutoTypePlatformMac::hotkeyHandler(EventHandlerCallRef nextHandler, Eve if (::GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, nullptr, sizeof(hotkeyId), nullptr, &hotkeyId) == noErr && hotkeyId.id == HOTKEY_ID) { - Q_EMIT self->globalShortcutTriggered(); + emit self->globalShortcutTriggered(); } return noErr; diff --git a/src/autotype/mac/AutoTypeMac.h b/src/autotype/mac/AutoTypeMac.h index 475a4b99b..5fbbf763b 100644 --- a/src/autotype/mac/AutoTypeMac.h +++ b/src/autotype/mac/AutoTypeMac.h @@ -51,7 +51,7 @@ public: void sendChar(const QChar& ch, bool isKeyDown); void sendKey(Qt::Key key, bool isKeyDown); -Q_SIGNALS: +signals: void globalShortcutTriggered(); private: diff --git a/src/autotype/test/AutoTypeTest.h b/src/autotype/test/AutoTypeTest.h index 4feaab942..d9a86c3de 100644 --- a/src/autotype/test/AutoTypeTest.h +++ b/src/autotype/test/AutoTypeTest.h @@ -60,7 +60,7 @@ public: void addActionChar(AutoTypeChar* action); void addActionKey(AutoTypeKey* action); -Q_SIGNALS: +signals: void globalShortcutTriggered(); private: diff --git a/src/autotype/windows/AutoTypeWindows.cpp b/src/autotype/windows/AutoTypeWindows.cpp index 481caa83f..0818a37bc 100644 --- a/src/autotype/windows/AutoTypeWindows.cpp +++ b/src/autotype/windows/AutoTypeWindows.cpp @@ -96,7 +96,7 @@ int AutoTypePlatformWin::platformEventFilter(void* event) MSG *msg = static_cast(event); if (msg->message == WM_HOTKEY && msg->wParam == HOTKEY_ID) { - Q_EMIT globalShortcutTriggered(); + emit globalShortcutTriggered(); return 1; } diff --git a/src/autotype/windows/AutoTypeWindows.h b/src/autotype/windows/AutoTypeWindows.h index 7a8c4bcab..f8b213cb0 100644 --- a/src/autotype/windows/AutoTypeWindows.h +++ b/src/autotype/windows/AutoTypeWindows.h @@ -45,7 +45,7 @@ public: void sendChar(const QChar& ch, bool isKeyDown); void sendKey(Qt::Key key, bool isKeyDown); -Q_SIGNALS: +signals: void globalShortcutTriggered(); private: diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index a07a916c4..e6ac74bbf 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -214,7 +214,7 @@ int AutoTypePlatformX11::platformEventFilter(void* event) && (!QApplication::activeWindow() || QApplication::activeWindow()->isMinimized()) && m_loaded) { if (type == XCB_KEY_PRESS) { - Q_EMIT globalShortcutTriggered(); + emit globalShortcutTriggered(); } return 1; diff --git a/src/autotype/xcb/AutoTypeXCB.h b/src/autotype/xcb/AutoTypeXCB.h index 26d1e8102..dc251e3f9 100644 --- a/src/autotype/xcb/AutoTypeXCB.h +++ b/src/autotype/xcb/AutoTypeXCB.h @@ -59,7 +59,7 @@ public: void SendKeyPressedEvent(KeySym keysym); -Q_SIGNALS: +signals: void globalShortcutTriggered(); private: diff --git a/src/core/AutoTypeAssociations.cpp b/src/core/AutoTypeAssociations.cpp index 75d21fe3f..5ec4eb3b3 100644 --- a/src/core/AutoTypeAssociations.cpp +++ b/src/core/AutoTypeAssociations.cpp @@ -39,29 +39,29 @@ void AutoTypeAssociations::copyDataFrom(const AutoTypeAssociations* other) return; } - Q_EMIT aboutToReset(); + emit aboutToReset(); m_associations = other->m_associations; - Q_EMIT reset(); - Q_EMIT modified(); + emit reset(); + emit modified(); } void AutoTypeAssociations::add(const AutoTypeAssociations::Association& association) { int index = m_associations.size(); - Q_EMIT aboutToAdd(index); + emit aboutToAdd(index); m_associations.append(association); - Q_EMIT added(index); - Q_EMIT modified(); + emit added(index); + emit modified(); } void AutoTypeAssociations::remove(int index) { Q_ASSERT(index >= 0 && index < m_associations.size()); - Q_EMIT aboutToRemove(index); + emit aboutToRemove(index); m_associations.removeAt(index); - Q_EMIT removed(index); - Q_EMIT modified(); + emit removed(index); + emit modified(); } void AutoTypeAssociations::removeEmpty() @@ -81,8 +81,8 @@ void AutoTypeAssociations::update(int index, const AutoTypeAssociations::Associa if (m_associations.at(index) != association) { m_associations[index] = association; - Q_EMIT dataChanged(index); - Q_EMIT modified(); + emit dataChanged(index); + emit modified(); } } diff --git a/src/core/AutoTypeAssociations.h b/src/core/AutoTypeAssociations.h index 491a5db1c..61ef3fd4a 100644 --- a/src/core/AutoTypeAssociations.h +++ b/src/core/AutoTypeAssociations.h @@ -48,7 +48,7 @@ public: private: QList m_associations; -Q_SIGNALS: +signals: void modified(); void dataChanged(int index); void aboutToAdd(int index); diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 336820381..60874fd8f 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -227,7 +227,7 @@ bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, if (updateChangedTime) { m_metadata->setMasterKeyChanged(QDateTime::currentDateTimeUtc()); } - Q_EMIT modifiedImmediate(); + emit modifiedImmediate(); return true; } @@ -285,7 +285,7 @@ void Database::recycleGroup(Group* group) void Database::merge(const Database* other) { m_rootGroup->merge(other->rootGroup()); - Q_EMIT modified(); + emit modified(); } void Database::setEmitModified(bool value) diff --git a/src/core/Database.h b/src/core/Database.h index 3cd5ed1b1..e781e1aa3 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -115,7 +115,7 @@ public: static Database* databaseByUuid(const Uuid& uuid); -Q_SIGNALS: +signals: void groupDataChanged(Group* group); void groupAboutToAdd(Group* group, int index); void groupAdded(); @@ -127,7 +127,7 @@ Q_SIGNALS: void modified(); void modifiedImmediate(); -private Q_SLOTS: +private slots: void startModifiedTimer(); private: diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index a2e72f7fd..7edf7e788 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -62,7 +62,7 @@ template inline bool Entry::set(T& property, const T& value) { if (property != value) { property = value; - Q_EMIT modified(); + emit modified(); return true; } else { @@ -299,7 +299,7 @@ void Entry::setIcon(int iconNumber) m_data.iconNumber = iconNumber; m_data.customIcon = Uuid(); - Q_EMIT modified(); + emit modified(); emitDataChanged(); } } @@ -312,7 +312,7 @@ void Entry::setIcon(const Uuid& uuid) m_data.customIcon = uuid; m_data.iconNumber = 0; - Q_EMIT modified(); + emit modified(); emitDataChanged(); } } @@ -392,7 +392,7 @@ void Entry::setExpires(const bool& value) { if (m_data.timeInfo.expires() != value) { m_data.timeInfo.setExpires(value); - Q_EMIT modified(); + emit modified(); } } @@ -400,7 +400,7 @@ void Entry::setExpiryTime(const QDateTime& dateTime) { if (m_data.timeInfo.expiryTime() != dateTime) { m_data.timeInfo.setExpiryTime(dateTime); - Q_EMIT modified(); + emit modified(); } } @@ -419,7 +419,7 @@ void Entry::addHistoryItem(Entry* entry) Q_ASSERT(!entry->parent()); m_history.append(entry); - Q_EMIT modified(); + emit modified(); } void Entry::removeHistoryItems(const QList& historyEntries) @@ -437,7 +437,7 @@ void Entry::removeHistoryItems(const QList& historyEntries) delete entry; } - Q_EMIT modified(); + emit modified(); } void Entry::truncateHistory() @@ -633,7 +633,7 @@ void Entry::setGroup(Group* group) void Entry::emitDataChanged() { - Q_EMIT dataChanged(this); + emit dataChanged(this); } const Database* Entry::database() const diff --git a/src/core/Entry.h b/src/core/Entry.h index 38ec42d4e..25b9bc386 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -147,7 +147,7 @@ public: void setUpdateTimeinfo(bool value); -Q_SIGNALS: +signals: /** * Emitted when a default attribute has been changed. */ @@ -155,7 +155,7 @@ Q_SIGNALS: void modified(); -private Q_SLOTS: +private slots: void emitDataChanged(); void updateTimeinfo(); void updateModifiedSinceBegin(); diff --git a/src/core/EntryAttachments.cpp b/src/core/EntryAttachments.cpp index 7bd080bfa..a53a3c997 100644 --- a/src/core/EntryAttachments.cpp +++ b/src/core/EntryAttachments.cpp @@ -48,7 +48,7 @@ void EntryAttachments::set(const QString& key, const QByteArray& value) bool addAttachment = !m_attachments.contains(key); if (addAttachment) { - Q_EMIT aboutToBeAdded(key); + emit aboutToBeAdded(key); } if (addAttachment || m_attachments.value(key) != value) { @@ -57,14 +57,14 @@ void EntryAttachments::set(const QString& key, const QByteArray& value) } if (addAttachment) { - Q_EMIT added(key); + emit added(key); } else { - Q_EMIT keyModified(key); + emit keyModified(key); } if (emitModified) { - Q_EMIT modified(); + emit modified(); } } @@ -75,12 +75,12 @@ void EntryAttachments::remove(const QString& key) return; } - Q_EMIT aboutToBeRemoved(key); + emit aboutToBeRemoved(key); m_attachments.remove(key); - Q_EMIT removed(key); - Q_EMIT modified(); + emit removed(key); + emit modified(); } void EntryAttachments::clear() @@ -89,23 +89,23 @@ void EntryAttachments::clear() return; } - Q_EMIT aboutToBeReset(); + emit aboutToBeReset(); m_attachments.clear(); - Q_EMIT reset(); - Q_EMIT modified(); + emit reset(); + emit modified(); } void EntryAttachments::copyDataFrom(const EntryAttachments* other) { if (*this != *other) { - Q_EMIT aboutToBeReset(); + emit aboutToBeReset(); m_attachments = other->m_attachments; - Q_EMIT reset(); - Q_EMIT modified(); + emit reset(); + emit modified(); } } diff --git a/src/core/EntryAttachments.h b/src/core/EntryAttachments.h index 903ca10bb..04c22cb34 100644 --- a/src/core/EntryAttachments.h +++ b/src/core/EntryAttachments.h @@ -38,7 +38,7 @@ public: bool operator==(const EntryAttachments& other) const; bool operator!=(const EntryAttachments& other) const; -Q_SIGNALS: +signals: void modified(); void keyModified(const QString& key); void aboutToBeAdded(const QString& key); diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp index 865e853f2..c689f8ad6 100644 --- a/src/core/EntryAttributes.cpp +++ b/src/core/EntryAttributes.cpp @@ -98,7 +98,7 @@ void EntryAttributes::set(const QString& key, const QString& value, bool protect bool defaultAttribute = isDefaultAttribute(key); if (addAttribute && !defaultAttribute) { - Q_EMIT aboutToBeAdded(key); + emit aboutToBeAdded(key); } if (addAttribute || changeValue) { @@ -117,17 +117,17 @@ void EntryAttributes::set(const QString& key, const QString& value, bool protect } if (emitModified) { - Q_EMIT modified(); + emit modified(); } if (defaultAttribute && changeValue) { - Q_EMIT defaultKeyModified(); + emit defaultKeyModified(); } else if (addAttribute) { - Q_EMIT added(key); + emit added(key); } else if (emitModified) { - Q_EMIT customKeyModified(key); + emit customKeyModified(key); } } @@ -140,13 +140,13 @@ void EntryAttributes::remove(const QString& key) return; } - Q_EMIT aboutToBeRemoved(key); + emit aboutToBeRemoved(key); m_attributes.remove(key); m_protectedAttributes.remove(key); - Q_EMIT removed(key); - Q_EMIT modified(); + emit removed(key); + emit modified(); } void EntryAttributes::rename(const QString& oldKey, const QString& newKey) @@ -167,7 +167,7 @@ void EntryAttributes::rename(const QString& oldKey, const QString& newKey) QString data = value(oldKey); bool protect = isProtected(oldKey); - Q_EMIT aboutToRename(oldKey, newKey); + emit aboutToRename(oldKey, newKey); m_attributes.remove(oldKey); m_attributes.insert(newKey, data); @@ -176,8 +176,8 @@ void EntryAttributes::rename(const QString& oldKey, const QString& newKey) m_protectedAttributes.insert(newKey); } - Q_EMIT modified(); - Q_EMIT renamed(oldKey, newKey); + emit modified(); + emit renamed(oldKey, newKey); } void EntryAttributes::copyCustomKeysFrom(const EntryAttributes* other) @@ -186,7 +186,7 @@ void EntryAttributes::copyCustomKeysFrom(const EntryAttributes* other) return; } - Q_EMIT aboutToBeReset(); + emit aboutToBeReset(); // remove all non-default keys const QList keyList = keys(); @@ -207,8 +207,8 @@ void EntryAttributes::copyCustomKeysFrom(const EntryAttributes* other) } } - Q_EMIT reset(); - Q_EMIT modified(); + emit reset(); + emit modified(); } bool EntryAttributes::areCustomKeysDifferent(const EntryAttributes* other) @@ -235,13 +235,13 @@ bool EntryAttributes::areCustomKeysDifferent(const EntryAttributes* other) void EntryAttributes::copyDataFrom(const EntryAttributes* other) { if (*this != *other) { - Q_EMIT aboutToBeReset(); + emit aboutToBeReset(); m_attributes = other->m_attributes; m_protectedAttributes = other->m_protectedAttributes; - Q_EMIT reset(); - Q_EMIT modified(); + emit reset(); + emit modified(); } } @@ -259,7 +259,7 @@ bool EntryAttributes::operator!=(const EntryAttributes& other) const void EntryAttributes::clear() { - Q_EMIT aboutToBeReset(); + emit aboutToBeReset(); m_attributes.clear(); m_protectedAttributes.clear(); @@ -268,8 +268,8 @@ void EntryAttributes::clear() m_attributes.insert(key, ""); } - Q_EMIT reset(); - Q_EMIT modified(); + emit reset(); + emit modified(); } int EntryAttributes::attributesSize() diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h index 78afe5efa..58f1db61d 100644 --- a/src/core/EntryAttributes.h +++ b/src/core/EntryAttributes.h @@ -57,7 +57,7 @@ public: static const QString RememberCmdExecAttr; static bool isDefaultAttribute(const QString& key); -Q_SIGNALS: +signals: void modified(); void defaultKeyModified(); void customKeyModified(const QString& key); diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 8c96bb074..d8d609987 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -74,7 +74,7 @@ template inline bool Group::set(P& property, const V& value) if (property != value) { property = value; updateTimeinfo(); - Q_EMIT modified(); + emit modified(); return true; } else { @@ -249,7 +249,7 @@ void Group::setUuid(const Uuid& uuid) void Group::setName(const QString& name) { if (set(m_data.name, name)) { - Q_EMIT dataChanged(this); + emit dataChanged(this); } } @@ -267,8 +267,8 @@ void Group::setIcon(int iconNumber) m_data.customIcon = Uuid(); updateTimeinfo(); - Q_EMIT modified(); - Q_EMIT dataChanged(this); + emit modified(); + emit dataChanged(this); } } @@ -281,8 +281,8 @@ void Group::setIcon(const Uuid& uuid) m_data.iconNumber = 0; updateTimeinfo(); - Q_EMIT modified(); - Q_EMIT dataChanged(this); + emit modified(); + emit dataChanged(this); } } @@ -296,7 +296,7 @@ void Group::setExpanded(bool expanded) if (m_data.isExpanded != expanded) { m_data.isExpanded = expanded; updateTimeinfo(); - Q_EMIT modified(); + emit modified(); } } @@ -325,7 +325,7 @@ void Group::setExpires(bool value) if (m_data.timeInfo.expires() != value) { m_data.timeInfo.setExpires(value); updateTimeinfo(); - Q_EMIT modified(); + emit modified(); } } @@ -334,7 +334,7 @@ void Group::setExpiryTime(const QDateTime& dateTime) if (m_data.timeInfo.expiryTime() != dateTime) { m_data.timeInfo.setExpiryTime(dateTime); updateTimeinfo(); - Q_EMIT modified(); + emit modified(); } } @@ -391,12 +391,12 @@ void Group::setParent(Group* parent, int index) recSetDatabase(parent->m_db); } QObject::setParent(parent); - Q_EMIT aboutToAdd(this, index); + emit aboutToAdd(this, index); Q_ASSERT(index <= parent->m_children.size()); parent->m_children.insert(index, this); } else { - Q_EMIT aboutToMove(this, parent, index); + emit aboutToMove(this, parent, index); m_parent->m_children.removeAll(this); m_parent = parent; QObject::setParent(parent); @@ -408,13 +408,13 @@ void Group::setParent(Group* parent, int index) m_data.timeInfo.setLocationChanged(QDateTime::currentDateTimeUtc()); } - Q_EMIT modified(); + emit modified(); if (!moveWithinDatabase) { - Q_EMIT added(); + emit added(); } else { - Q_EMIT moved(); + emit moved(); } } @@ -566,7 +566,7 @@ void Group::merge(const Group* other) } } - Q_EMIT modified(); + emit modified(); } Group* Group::findChildByName(const QString& name) @@ -623,7 +623,7 @@ void Group::addEntry(Entry* entry) Q_ASSERT(entry); Q_ASSERT(!m_entries.contains(entry)); - Q_EMIT entryAboutToAdd(entry); + emit entryAboutToAdd(entry); m_entries << entry; connect(entry, SIGNAL(dataChanged(Entry*)), SIGNAL(entryDataChanged(Entry*))); @@ -631,23 +631,23 @@ void Group::addEntry(Entry* entry) connect(entry, SIGNAL(modified()), m_db, SIGNAL(modifiedImmediate())); } - Q_EMIT modified(); - Q_EMIT entryAdded(entry); + emit modified(); + emit entryAdded(entry); } void Group::removeEntry(Entry* entry) { Q_ASSERT(m_entries.contains(entry)); - Q_EMIT entryAboutToRemove(entry); + emit entryAboutToRemove(entry); entry->disconnect(this); if (m_db) { entry->disconnect(m_db); } m_entries.removeAll(entry); - Q_EMIT modified(); - Q_EMIT entryRemoved(entry); + emit modified(); + emit entryRemoved(entry); } void Group::recSetDatabase(Database* db) @@ -693,10 +693,10 @@ void Group::recSetDatabase(Database* db) void Group::cleanupParent() { if (m_parent) { - Q_EMIT aboutToRemove(this); + emit aboutToRemove(this); m_parent->m_children.removeAll(this); - Q_EMIT modified(); - Q_EMIT removed(); + emit modified(); + emit removed(); } } diff --git a/src/core/Group.h b/src/core/Group.h index 3c054f976..e3e5e7554 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -122,7 +122,7 @@ public: void copyDataFrom(const Group* other); void merge(const Group* other); -Q_SIGNALS: +signals: void dataChanged(Group* group); void aboutToAdd(Group* group, int index); diff --git a/src/core/InactivityTimer.cpp b/src/core/InactivityTimer.cpp index dd162e695..0cfc8f0d4 100644 --- a/src/core/InactivityTimer.cpp +++ b/src/core/InactivityTimer.cpp @@ -73,7 +73,7 @@ void InactivityTimer::timeout() } if (m_active && !m_timer->isActive()) { - Q_EMIT inactivityDetected(); + emit inactivityDetected(); } m_emitMutx.unlock(); diff --git a/src/core/InactivityTimer.h b/src/core/InactivityTimer.h index ba571a5ef..b9de80fb4 100644 --- a/src/core/InactivityTimer.h +++ b/src/core/InactivityTimer.h @@ -33,13 +33,13 @@ public: void activate(); void deactivate(); -Q_SIGNALS: +signals: void inactivityDetected(); protected: bool eventFilter(QObject* watched, QEvent* event); -private Q_SLOTS: +private slots: void timeout(); private: diff --git a/src/core/Metadata.cpp b/src/core/Metadata.cpp index bf68af3ca..a7207b592 100644 --- a/src/core/Metadata.cpp +++ b/src/core/Metadata.cpp @@ -55,7 +55,7 @@ template bool Metadata::set(P& property, const V& value) { if (property != value) { property = value; - Q_EMIT modified(); + emit modified(); return true; } else { @@ -69,7 +69,7 @@ template bool Metadata::set(P& property, const V& value, QDat if (m_updateDatetime) { dateTime = QDateTime::currentDateTimeUtc(); } - Q_EMIT modified(); + emit modified(); return true; } else { @@ -308,7 +308,7 @@ void Metadata::setGenerator(const QString& value) void Metadata::setName(const QString& value) { if (set(m_data.name, value, m_data.nameChanged)) { - Q_EMIT nameTextChanged(); + emit nameTextChanged(); } } @@ -391,7 +391,7 @@ void Metadata::addCustomIcon(const Uuid& uuid, const QImage& icon) m_customIconScaledCacheKeys[uuid] = QPixmapCache::Key(); m_customIconsOrder.append(uuid); Q_ASSERT(m_customIcons.count() == m_customIconsOrder.count()); - Q_EMIT modified(); + emit modified(); } void Metadata::addCustomIconScaled(const Uuid& uuid, const QImage& icon) @@ -422,7 +422,7 @@ void Metadata::removeCustomIcon(const Uuid& uuid) m_customIconScaledCacheKeys.remove(uuid); m_customIconsOrder.removeAll(uuid); Q_ASSERT(m_customIcons.count() == m_customIconsOrder.count()); - Q_EMIT modified(); + emit modified(); } void Metadata::copyCustomIcons(const QSet& iconList, const Metadata* otherMetadata) @@ -504,7 +504,7 @@ void Metadata::addCustomField(const QString& key, const QString& value) Q_ASSERT(!m_customFields.contains(key)); m_customFields.insert(key, value); - Q_EMIT modified(); + emit modified(); } void Metadata::removeCustomField(const QString& key) @@ -512,5 +512,5 @@ void Metadata::removeCustomField(const QString& key) Q_ASSERT(m_customFields.contains(key)); m_customFields.remove(key); - Q_EMIT modified(); + emit modified(); } diff --git a/src/core/Metadata.h b/src/core/Metadata.h index c35aed39b..4f435d759 100644 --- a/src/core/Metadata.h +++ b/src/core/Metadata.h @@ -146,7 +146,7 @@ public: */ void copyAttributesFrom(const Metadata* other); -Q_SIGNALS: +signals: void nameTextChanged(); void modified(); diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 34d1b8680..98c373ed3 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -96,7 +96,7 @@ bool Application::event(QEvent* event) { // Handle Apple QFileOpenEvent from finder (double click on .kdbx file) if (event->type() == QEvent::FileOpen) { - Q_EMIT openFile(static_cast(event)->file()); + emit openFile(static_cast(event)->file()); return true; } #ifdef Q_OS_MAC diff --git a/src/gui/Application.h b/src/gui/Application.h index 9bfe4d549..f4324fa2c 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -33,10 +33,10 @@ public: bool event(QEvent* event) override; -Q_SIGNALS: +signals: void openFile(const QString& filename); -private Q_SLOTS: +private slots: #if defined(Q_OS_UNIX) void quitBySignal(); #endif diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 812f7ec0f..f7e5b33ab 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -131,13 +131,13 @@ void ChangeMasterKeyWidget::generateKey() } m_ui->messageWidget->hideMessage(); - Q_EMIT editFinished(true); + emit editFinished(true); } void ChangeMasterKeyWidget::reject() { - Q_EMIT editFinished(false); + emit editFinished(false); } void ChangeMasterKeyWidget::setCancelEnabled(bool enabled) diff --git a/src/gui/ChangeMasterKeyWidget.h b/src/gui/ChangeMasterKeyWidget.h index fc4a1b9cb..1f90c4c5d 100644 --- a/src/gui/ChangeMasterKeyWidget.h +++ b/src/gui/ChangeMasterKeyWidget.h @@ -40,10 +40,10 @@ public: QLabel* headlineLabel(); void setCancelEnabled(bool enabled); -Q_SIGNALS: +signals: void editFinished(bool accepted); -private Q_SLOTS: +private slots: void generateKey(); void reject(); void createKeyFile(); diff --git a/src/gui/Clipboard.h b/src/gui/Clipboard.h index dafce70a2..e0a16d26d 100644 --- a/src/gui/Clipboard.h +++ b/src/gui/Clipboard.h @@ -31,10 +31,10 @@ public: static Clipboard* instance(); -public Q_SLOTS: +public slots: void clearCopiedText(); -private Q_SLOTS: +private slots: void clearClipboard(); private: diff --git a/src/gui/CloneDialog.h b/src/gui/CloneDialog.h index 277da4a82..094b0fe7d 100644 --- a/src/gui/CloneDialog.h +++ b/src/gui/CloneDialog.h @@ -39,7 +39,7 @@ public: private: QScopedPointer m_ui; -private Q_SLOTS: +private slots: void cloneEntry(); protected: diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 1e3016146..b6220d93a 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -130,7 +130,7 @@ void DatabaseOpenWidget::openDatabase() if (m_ui->messageWidget->isVisible()) { m_ui->messageWidget->animatedHide(); } - Q_EMIT editFinished(true); + emit editFinished(true); } else { m_ui->messageWidget->showMessage(tr("Unable to open the database.") @@ -174,7 +174,7 @@ CompositeKey DatabaseOpenWidget::databaseKey() void DatabaseOpenWidget::reject() { - Q_EMIT editFinished(false); + emit editFinished(false); } void DatabaseOpenWidget::activatePassword() diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index 34f401a09..405c7f3b8 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -41,18 +41,18 @@ public: void enterKey(const QString& pw, const QString& keyFile); Database* database(); -Q_SIGNALS: +signals: void editFinished(bool accepted); protected: void showEvent(QShowEvent* event) override; CompositeKey databaseKey(); -protected Q_SLOTS: +protected slots: virtual void openDatabase(); void reject(); -private Q_SLOTS: +private slots: void activatePassword(); void activateKeyFile(); void browseKeyFile(); diff --git a/src/gui/DatabaseRepairWidget.cpp b/src/gui/DatabaseRepairWidget.cpp index e48e0f1f6..2b0039408 100644 --- a/src/gui/DatabaseRepairWidget.cpp +++ b/src/gui/DatabaseRepairWidget.cpp @@ -50,7 +50,7 @@ void DatabaseRepairWidget::openDatabase() QString errorMsg; if (!key.load(keyFilename, &errorMsg)) { MessageBox::warning(this, tr("Error"), tr("Can't open key file").append(":\n").append(errorMsg)); - Q_EMIT editFinished(false); + emit editFinished(false); return; } masterKey.addKey(key); @@ -62,7 +62,7 @@ void DatabaseRepairWidget::openDatabase() if (!file.open(QIODevice::ReadOnly)) { MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n") .append(file.errorString())); - Q_EMIT editFinished(false); + emit editFinished(false); return; } if (m_db) { @@ -75,21 +75,21 @@ void DatabaseRepairWidget::openDatabase() switch (repairResult) { case KeePass2Repair::NothingTodo: MessageBox::information(this, tr("Error"), tr("Database opened fine. Nothing to do.")); - Q_EMIT editFinished(false); + emit editFinished(false); return; case KeePass2Repair::UnableToOpen: MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n") .append(repair.errorString())); - Q_EMIT editFinished(false); + emit editFinished(false); return; case KeePass2Repair::RepairSuccess: m_db = repair.database(); MessageBox::warning(this, tr("Success"), tr("The database has been successfully repaired\nYou can now save it.")); - Q_EMIT editFinished(true); + emit editFinished(true); return; case KeePass2Repair::RepairFailed: MessageBox::warning(this, tr("Error"), tr("Unable to repair the database.")); - Q_EMIT editFinished(false); + emit editFinished(false); return; } } @@ -97,9 +97,9 @@ void DatabaseRepairWidget::openDatabase() void DatabaseRepairWidget::processEditFinished(bool result) { if (result) { - Q_EMIT success(); + emit success(); } else { - Q_EMIT error(); + emit error(); } } diff --git a/src/gui/DatabaseRepairWidget.h b/src/gui/DatabaseRepairWidget.h index 6775d2dc1..67b48ce54 100644 --- a/src/gui/DatabaseRepairWidget.h +++ b/src/gui/DatabaseRepairWidget.h @@ -27,14 +27,14 @@ class DatabaseRepairWidget : public DatabaseOpenWidget public: explicit DatabaseRepairWidget(QWidget* parent = nullptr); -Q_SIGNALS: +signals: void success(); void error(); protected: void openDatabase() override; -private Q_SLOTS: +private slots: void processEditFinished(bool result); }; diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 59a3c5c3a..7c51edfd4 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -124,12 +124,12 @@ void DatabaseSettingsWidget::save() truncateHistories(); } - Q_EMIT editFinished(true); + emit editFinished(true); } void DatabaseSettingsWidget::reject() { - Q_EMIT editFinished(false); + emit editFinished(false); } void DatabaseSettingsWidget::transformRoundsBenchmark() diff --git a/src/gui/DatabaseSettingsWidget.h b/src/gui/DatabaseSettingsWidget.h index 040e0dbe7..733b32f87 100644 --- a/src/gui/DatabaseSettingsWidget.h +++ b/src/gui/DatabaseSettingsWidget.h @@ -38,10 +38,10 @@ public: void load(Database* db); -Q_SIGNALS: +signals: void editFinished(bool accepted); -private Q_SLOTS: +private slots: void save(); void reject(); void transformRoundsBenchmark(); diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 910e94899..d679ce294 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -120,7 +120,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, QFileInfo fileInfo(fileName); QString canonicalFilePath = fileInfo.canonicalFilePath(); if (canonicalFilePath.isEmpty()) { - Q_EMIT messageGlobal(tr("File not found!"), MessageWidget::Error); + emit messageGlobal(tr("File not found!"), MessageWidget::Error); return; } @@ -141,7 +141,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, if (!file.open(QIODevice::ReadWrite)) { if (!file.open(QIODevice::ReadOnly)) { // can't open - Q_EMIT messageGlobal( + emit messageGlobal( tr("Unable to open the database.").append("\n").append(file.errorString()), MessageWidget::Error); return; } @@ -198,7 +198,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, insertDatabase(db, dbStruct); if (dbStruct.readOnly) { - Q_EMIT messageTab(tr("File opened in read only mode."), MessageWidget::Warning); + emit messageTab(tr("File opened in read only mode."), MessageWidget::Warning); } updateLastDatabases(dbStruct.filePath); @@ -209,7 +209,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, else { dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath); } - Q_EMIT messageDismissGlobal(); + emit messageDismissGlobal(); } void DatabaseTabWidget::mergeDatabase() @@ -314,7 +314,7 @@ void DatabaseTabWidget::deleteDatabase(Database* db) delete db; if (emitDatabaseWithFileClosed) { - Q_EMIT databaseWithFileClosed(filePath); + emit databaseWithFileClosed(filePath); } } @@ -340,7 +340,7 @@ bool DatabaseTabWidget::saveDatabase(Database* db) // write the database to the file m_writer.writeDatabase(&saveFile, db); if (m_writer.hasError()) { - Q_EMIT messageTab(tr("Writing the database failed.").append("\n") + emit messageTab(tr("Writing the database failed.").append("\n") .append(m_writer.errorString()), MessageWidget::Error); return false; } @@ -350,17 +350,17 @@ bool DatabaseTabWidget::saveDatabase(Database* db) dbStruct.modified = false; dbStruct.dbWidget->databaseSaved(); updateTabName(db); - Q_EMIT messageDismissTab(); + emit messageDismissTab(); return true; } else { - Q_EMIT messageTab(tr("Writing the database failed.").append("\n") + emit messageTab(tr("Writing the database failed.").append("\n") .append(saveFile.errorString()), MessageWidget::Error); return false; } } else { - Q_EMIT messageTab(tr("Writing the database failed.").append("\n") + emit messageTab(tr("Writing the database failed.").append("\n") .append(saveFile.errorString()), MessageWidget::Error); return false; } @@ -503,7 +503,7 @@ void DatabaseTabWidget::exportToCsv() CsvExporter csvExporter; if (!csvExporter.exportDatabase(fileName, db)) { - Q_EMIT messageGlobal( + emit messageGlobal( tr("Writing the CSV file failed.").append("\n") .append(csvExporter.errorString()), MessageWidget::Error); } @@ -565,7 +565,7 @@ void DatabaseTabWidget::updateTabName(Database* db) } setTabText(index, tabName); - Q_EMIT tabNameChanged(); + emit tabNameChanged(); } void DatabaseTabWidget::updateTabNameFromDbSender() @@ -745,7 +745,7 @@ void DatabaseTabWidget::lockDatabases() // database has changed so we can't use the db variable anymore updateTabName(dbWidget->database()); - Q_EMIT databaseLocked(dbWidget); + emit databaseLocked(dbWidget); } } @@ -803,12 +803,12 @@ void DatabaseTabWidget::changeDatabase(Database* newDb, bool unsavedChanges) void DatabaseTabWidget::emitActivateDatabaseChanged() { - Q_EMIT activateDatabaseChanged(currentDatabaseWidget()); + emit activateDatabaseChanged(currentDatabaseWidget()); } void DatabaseTabWidget::emitDatabaseUnlockedFromDbWidgetSender() { - Q_EMIT databaseUnlocked(static_cast(sender())); + emit databaseUnlocked(static_cast(sender())); } void DatabaseTabWidget::connectDatabase(Database* newDb, Database* oldDb) diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 8f01a987d..d4955c6ea 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -63,7 +63,7 @@ public: static const int LastDatabasesCount; -public Q_SLOTS: +public slots: void newDatabase(); void openDatabase(); void mergeDatabase(); @@ -80,7 +80,7 @@ public Q_SLOTS: void performGlobalAutoType(); void lockDatabases(); -Q_SIGNALS: +signals: void tabNameChanged(); void databaseWithFileClosed(QString filePath); void activateDatabaseChanged(DatabaseWidget* dbWidget); @@ -91,7 +91,7 @@ Q_SIGNALS: void messageDismissGlobal(); void messageDismissTab(); -private Q_SLOTS: +private slots: void updateTabName(Database* db); void updateTabNameFromDbSender(); void updateTabNameFromDbWidgetSender(); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 1cb1882d3..f68fecdc4 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -263,7 +263,7 @@ void DatabaseWidget::clearAllWidgets() void DatabaseWidget::emitCurrentModeChanged() { - Q_EMIT currentModeChanged(currentMode()); + emit currentModeChanged(currentMode()); } Database* DatabaseWidget::database() @@ -309,7 +309,7 @@ void DatabaseWidget::replaceDatabase(Database* db) Database* oldDb = m_db; m_db = db; m_groupView->changeDatabase(m_db); - Q_EMIT databaseChanged(m_db, m_databaseModified); + emit databaseChanged(m_db, m_databaseModified); delete oldDb; } @@ -700,7 +700,7 @@ void DatabaseWidget::updateMasterKey(bool accepted) } } else if (!m_db->hasKey()) { - Q_EMIT closeRequest(); + emit closeRequest(); return; } @@ -712,7 +712,7 @@ void DatabaseWidget::openDatabase(bool accepted) if (accepted) { replaceDatabase(static_cast(sender())->database()); setCurrentWidget(m_mainWidget); - Q_EMIT unlockedDatabase(); + emit unlockedDatabase(); // We won't need those anymore and KeePass1OpenWidget closes // the file in its dtor. @@ -727,7 +727,7 @@ void DatabaseWidget::openDatabase(bool accepted) if (m_databaseOpenWidget->database()) { delete m_databaseOpenWidget->database(); } - Q_EMIT closeRequest(); + emit closeRequest(); } } @@ -750,13 +750,13 @@ void DatabaseWidget::mergeDatabase(bool accepted) } setCurrentWidget(m_mainWidget); - Q_EMIT databaseMerged(m_db); + emit databaseMerged(m_db); } void DatabaseWidget::unlockDatabase(bool accepted) { if (!accepted) { - Q_EMIT closeRequest(); + emit closeRequest(); return; } @@ -775,7 +775,7 @@ void DatabaseWidget::unlockDatabase(bool accepted) setCurrentWidget(m_mainWidget); m_unlockDatabaseWidget->clearForms(); - Q_EMIT unlockedDatabase(); + emit unlockedDatabase(); if (sender() == m_unlockDatabaseDialog) { QList dbList; @@ -888,7 +888,7 @@ void DatabaseWidget::search(const QString& searchtext) return; } - Q_EMIT searchModeAboutToActivate(); + emit searchModeAboutToActivate(); Qt::CaseSensitivity caseSensitive = m_searchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; @@ -907,7 +907,7 @@ void DatabaseWidget::search(const QString& searchtext) m_searchingLabel->setVisible(true); - Q_EMIT searchModeActivated(); + emit searchModeActivated(); } void DatabaseWidget::setSearchCaseSensitive(bool state) @@ -934,12 +934,12 @@ void DatabaseWidget::endSearch() { if (isInSearchMode()) { - Q_EMIT listModeAboutToActivate(); + emit listModeAboutToActivate(); // Show the normal entry view of the current group m_entryView->setGroup(currentGroup()); - Q_EMIT listModeActivated(); + emit listModeActivated(); } m_searchingLabel->setVisible(false); @@ -950,12 +950,12 @@ void DatabaseWidget::endSearch() void DatabaseWidget::emitGroupContextMenuRequested(const QPoint& pos) { - Q_EMIT groupContextMenuRequested(m_groupView->viewport()->mapToGlobal(pos)); + emit groupContextMenuRequested(m_groupView->viewport()->mapToGlobal(pos)); } void DatabaseWidget::emitEntryContextMenuRequested(const QPoint& pos) { - Q_EMIT entryContextMenuRequested(m_entryView->viewport()->mapToGlobal(pos)); + emit entryContextMenuRequested(m_entryView->viewport()->mapToGlobal(pos)); } bool DatabaseWidget::dbHasKey() const diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 66ece0537..3781af49f 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -101,7 +101,7 @@ public: void ignoreNextAutoreload(); void refreshSearch(); -Q_SIGNALS: +signals: void closeRequest(); void currentModeChanged(DatabaseWidget::Mode mode); void groupChanged(); @@ -119,7 +119,7 @@ Q_SIGNALS: void entryColumnSizesChanged(); void updateSearch(QString text); -public Q_SLOTS: +public slots: void createEntry(); void cloneEntry(); void deleteEntries(); @@ -157,7 +157,7 @@ public Q_SLOTS: void showMessage(const QString& text, MessageWidget::MessageType type); void hideMessage(); -private Q_SLOTS: +private slots: void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column); void switchBackToEntryEdit(); void switchToHistoryView(Entry* entry); diff --git a/src/gui/DatabaseWidgetStateSync.h b/src/gui/DatabaseWidgetStateSync.h index a4861179e..96ecd104a 100644 --- a/src/gui/DatabaseWidgetStateSync.h +++ b/src/gui/DatabaseWidgetStateSync.h @@ -29,12 +29,12 @@ public: explicit DatabaseWidgetStateSync(QObject* parent = nullptr); ~DatabaseWidgetStateSync(); -public Q_SLOTS: +public slots: void setActive(DatabaseWidget* dbWidget); void restoreListView(); void restoreSearchView(); -private Q_SLOTS: +private slots: void blockUpdates(); void updateSplitterSizes(); void updateColumnSizes(); diff --git a/src/gui/DragTabBar.h b/src/gui/DragTabBar.h index a6117a047..38de10dab 100644 --- a/src/gui/DragTabBar.h +++ b/src/gui/DragTabBar.h @@ -34,7 +34,7 @@ protected: void dropEvent(QDropEvent* event) override; void tabLayoutChange() override; -private Q_SLOTS: +private slots: void dragSwitchTab(); private: diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index d789eb498..7b46728c1 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -276,7 +276,7 @@ void EditWidgetIcons::addCustomIcon() m_ui->customIconsView->setCurrentIndex(index); } else { - Q_EMIT messageEditEntry(tr("Can't read icon"), MessageWidget::Error); + emit messageEditEntry(tr("Can't read icon"), MessageWidget::Error); } } } diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index b0ff6c6c9..745914bca 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -63,14 +63,14 @@ public: void reset(); void load(const Uuid& currentUuid, Database* database, const IconStruct& iconStruct, const QString& url = ""); -public Q_SLOTS: +public slots: void setUrl(const QString& url); -Q_SIGNALS: +signals: void messageEditEntry(QString, MessageWidget::MessageType); void messageEditEntryDismiss(); -private Q_SLOTS: +private slots: void downloadFavicon(); #ifdef WITH_XC_HTTP void fetchFavicon(const QUrl& url); diff --git a/src/gui/KMessageWidget.h b/src/gui/KMessageWidget.h index 4398e0f99..d47e78f9c 100644 --- a/src/gui/KMessageWidget.h +++ b/src/gui/KMessageWidget.h @@ -224,7 +224,7 @@ public: */ bool isShowAnimationRunning() const; -public Q_SLOTS: +public slots: /** * Set the text of the message widget to @p text. * If the message widget is already visible, the text changes on the fly. @@ -277,7 +277,7 @@ public Q_SLOTS: */ void setIcon(const QIcon &icon); -Q_SIGNALS: +signals: /** * This signal is emitted when the user clicks a link in the text label. * The URL referred to by the href anchor is passed in contents. diff --git a/src/gui/KeePass1OpenWidget.cpp b/src/gui/KeePass1OpenWidget.cpp index b63bbc485..915864241 100644 --- a/src/gui/KeePass1OpenWidget.cpp +++ b/src/gui/KeePass1OpenWidget.cpp @@ -62,7 +62,7 @@ void KeePass1OpenWidget::openDatabase() if (m_db) { m_db->metadata()->setName(QFileInfo(m_filename).completeBaseName()); - Q_EMIT editFinished(true); + emit editFinished(true); } else { m_ui->messageWidget->showMessage(tr("Unable to open the database.").append("\n") diff --git a/src/gui/LineEdit.h b/src/gui/LineEdit.h index f5f058401..1695e8551 100644 --- a/src/gui/LineEdit.h +++ b/src/gui/LineEdit.h @@ -34,7 +34,7 @@ public: protected: void resizeEvent(QResizeEvent* event) override; -private Q_SLOTS: +private slots: void updateCloseButton(const QString& text); private: diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 5c7ccad8e..7a314df8c 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -39,7 +39,7 @@ public: MainWindow(); ~MainWindow(); -public Q_SLOTS: +public slots: void openDatabase(const QString& fileName, const QString& pw = QString(), const QString& keyFile = QString()); void appExit(); @@ -48,7 +48,7 @@ protected: void closeEvent(QCloseEvent* event) override; void changeEvent(QEvent* event) override; -private Q_SLOTS: +private slots: void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::None); void updateWindowTitle(); void showAboutDialog(); diff --git a/src/gui/MessageWidget.h b/src/gui/MessageWidget.h index 34c06743c..a6c9425dc 100644 --- a/src/gui/MessageWidget.h +++ b/src/gui/MessageWidget.h @@ -27,7 +27,7 @@ class MessageWidget : public KMessageWidget public: explicit MessageWidget(QWidget* parent = 0); -public Q_SLOTS: +public slots: void showMessage(const QString& text, MessageWidget::MessageType type); void hideMessage(); diff --git a/src/gui/PasswordComboBox.h b/src/gui/PasswordComboBox.h index 7c54e278b..f7f118edf 100644 --- a/src/gui/PasswordComboBox.h +++ b/src/gui/PasswordComboBox.h @@ -35,7 +35,7 @@ public: void setNumberAlternatives(int alternatives); void showPopup(); -public Q_SLOTS: +public slots: void setEcho(bool echo); private: diff --git a/src/gui/PasswordEdit.cpp b/src/gui/PasswordEdit.cpp index 98a5e2a50..095a4e14f 100644 --- a/src/gui/PasswordEdit.cpp +++ b/src/gui/PasswordEdit.cpp @@ -69,7 +69,7 @@ void PasswordEdit::setShowPassword(bool show) } } updateStylesheet(); - Q_EMIT showPasswordChanged(show); + emit showPasswordChanged(show); } bool PasswordEdit::passwordsEqual() const diff --git a/src/gui/PasswordEdit.h b/src/gui/PasswordEdit.h index 994576d23..d527432d5 100644 --- a/src/gui/PasswordEdit.h +++ b/src/gui/PasswordEdit.h @@ -31,13 +31,13 @@ public: explicit PasswordEdit(QWidget* parent = nullptr); void enableVerifyMode(PasswordEdit* baseEdit); -public Q_SLOTS: +public slots: void setShowPassword(bool show); -Q_SIGNALS: +signals: void showPasswordChanged(bool show); -private Q_SLOTS: +private slots: void updateStylesheet(); void autocompletePassword(QString password); diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 4a4b438e3..11d50bae8 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -145,8 +145,8 @@ void PasswordGeneratorWidget::generatePassword() void PasswordGeneratorWidget::applyPassword() { saveSettings(); - Q_EMIT appliedPassword(m_ui->editNewPassword->text()); - Q_EMIT dialogTerminated(); + emit appliedPassword(m_ui->editNewPassword->text()); + emit dialogTerminated(); } void PasswordGeneratorWidget::sliderMoved() diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h index b8803f85e..bfa6d684e 100644 --- a/src/gui/PasswordGeneratorWidget.h +++ b/src/gui/PasswordGeneratorWidget.h @@ -43,11 +43,11 @@ public: void setStandaloneMode(bool standalone); void regeneratePassword(); -Q_SIGNALS: +signals: void appliedPassword(const QString& password); void dialogTerminated(); -private Q_SLOTS: +private slots: void applyPassword(); void generatePassword(); void updateApplyEnabled(const QString& password); diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 8aa6982d7..62af276e8 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -196,7 +196,7 @@ void SettingsWidget::saveSettings() page.saveSettings(); } - Q_EMIT editFinished(true); + emit editFinished(true); } void SettingsWidget::reject() @@ -206,7 +206,7 @@ void SettingsWidget::reject() autoType()->registerGlobalShortcut(m_globalAutoTypeKey, m_globalAutoTypeModifiers); } - Q_EMIT editFinished(false); + emit editFinished(false); } void SettingsWidget::enableAutoSaveOnExit(bool checked) diff --git a/src/gui/SettingsWidget.h b/src/gui/SettingsWidget.h index e94f48767..7037b2e37 100644 --- a/src/gui/SettingsWidget.h +++ b/src/gui/SettingsWidget.h @@ -45,10 +45,10 @@ public: void addSettingsPage(ISettingsPage * page); void loadSettings(); -Q_SIGNALS: +signals: void editFinished(bool accepted); -private Q_SLOTS: +private slots: void saveSettings(); void reject(); void enableAutoSaveOnExit(bool checked); diff --git a/src/gui/UnlockDatabaseDialog.cpp b/src/gui/UnlockDatabaseDialog.cpp index 679493903..3d002f756 100644 --- a/src/gui/UnlockDatabaseDialog.cpp +++ b/src/gui/UnlockDatabaseDialog.cpp @@ -49,7 +49,7 @@ void UnlockDatabaseDialog::complete(bool r) { if (r) { accept(); - Q_EMIT unlockDone(true); + emit unlockDone(true); } else { reject(); } diff --git a/src/gui/UnlockDatabaseDialog.h b/src/gui/UnlockDatabaseDialog.h index 1ba6d2e06..daf8a0f1f 100644 --- a/src/gui/UnlockDatabaseDialog.h +++ b/src/gui/UnlockDatabaseDialog.h @@ -36,10 +36,10 @@ public: void clearForms(); Database* database(); -Q_SIGNALS: +signals: void unlockDone(bool); -public Q_SLOTS: +public slots: void complete(bool r); private: diff --git a/src/gui/WelcomeWidget.cpp b/src/gui/WelcomeWidget.cpp index cb7a1de2e..d327ea84c 100644 --- a/src/gui/WelcomeWidget.cpp +++ b/src/gui/WelcomeWidget.cpp @@ -64,5 +64,5 @@ void WelcomeWidget::openDatabaseFromFile(QListWidgetItem* item) if (item->text().isEmpty()) { return; } - Q_EMIT openDatabaseFile(item->text()); + emit openDatabaseFile(item->text()); } \ No newline at end of file diff --git a/src/gui/WelcomeWidget.h b/src/gui/WelcomeWidget.h index dbd0d2e27..73c8c4f9b 100644 --- a/src/gui/WelcomeWidget.h +++ b/src/gui/WelcomeWidget.h @@ -33,13 +33,13 @@ public: explicit WelcomeWidget(QWidget* parent = nullptr); ~WelcomeWidget(); -Q_SIGNALS: +signals: void newDatabase(); void openDatabase(); void openDatabaseFile(QString); void importKeePass1Database(); -private Q_SLOTS: +private slots: void openDatabaseFromFile(QListWidgetItem* item); private: diff --git a/src/gui/entry/AutoTypeAssociationsModel.cpp b/src/gui/entry/AutoTypeAssociationsModel.cpp index 49f6786b3..4a76233b4 100644 --- a/src/gui/entry/AutoTypeAssociationsModel.cpp +++ b/src/gui/entry/AutoTypeAssociationsModel.cpp @@ -103,7 +103,7 @@ QVariant AutoTypeAssociationsModel::data(const QModelIndex& index, int role) con void AutoTypeAssociationsModel::associationChange(int i) { - Q_EMIT dataChanged(index(i, 0), index(i, columnCount() - 1)); + emit dataChanged(index(i, 0), index(i, columnCount() - 1)); } void AutoTypeAssociationsModel::associationAboutToAdd(int i) diff --git a/src/gui/entry/AutoTypeAssociationsModel.h b/src/gui/entry/AutoTypeAssociationsModel.h index c75168c32..cef8bc66b 100644 --- a/src/gui/entry/AutoTypeAssociationsModel.h +++ b/src/gui/entry/AutoTypeAssociationsModel.h @@ -36,7 +36,7 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; -public Q_SLOTS: +public slots: void associationChange(int i); void associationAboutToAdd(int i); void associationAdd(); diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index a30325057..4581c91f0 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -212,7 +212,7 @@ void EditEntryWidget::emitHistoryEntryActivated(const QModelIndex& index) Q_ASSERT(!m_history); Entry* entry = m_historyModel->entryFromIndex(index); - Q_EMIT historyEntryActivated(entry); + emit historyEntryActivated(entry); } void EditEntryWidget::histEntryActivated(const QModelIndex& index) @@ -407,7 +407,7 @@ void EditEntryWidget::saveEntry() if (m_history) { clear(); hideMessage(); - Q_EMIT editFinished(false); + emit editFinished(false); return; } @@ -442,7 +442,7 @@ void EditEntryWidget::saveEntry() clear(); - Q_EMIT editFinished(true); + emit editFinished(true); } void EditEntryWidget::updateEntryData(Entry* entry) const @@ -487,7 +487,7 @@ void EditEntryWidget::cancel() if (m_history) { clear(); hideMessage(); - Q_EMIT editFinished(false); + emit editFinished(false); return; } @@ -498,7 +498,7 @@ void EditEntryWidget::cancel() clear(); - Q_EMIT editFinished(false); + emit editFinished(false); } void EditEntryWidget::clear() diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h index 270542e8c..4027dd11a 100644 --- a/src/gui/entry/EditEntryWidget.h +++ b/src/gui/entry/EditEntryWidget.h @@ -63,11 +63,11 @@ public: void clear(); bool hasBeenModified() const; -Q_SIGNALS: +signals: void editFinished(bool accepted); void historyEntryActivated(Entry* entry); -private Q_SLOTS: +private slots: void saveEntry(); void cancel(); void togglePasswordGeneratorButton(bool checked); diff --git a/src/gui/entry/EntryAttachmentsModel.cpp b/src/gui/entry/EntryAttachmentsModel.cpp index 39ed69f1f..082641380 100644 --- a/src/gui/entry/EntryAttachmentsModel.cpp +++ b/src/gui/entry/EntryAttachmentsModel.cpp @@ -97,7 +97,7 @@ QString EntryAttachmentsModel::keyByIndex(const QModelIndex& index) const void EntryAttachmentsModel::attachmentChange(const QString& key) { int row = m_entryAttachments->keys().indexOf(key); - Q_EMIT dataChanged(index(row, 0), index(row, columnCount()-1)); + emit dataChanged(index(row, 0), index(row, columnCount()-1)); } void EntryAttachmentsModel::attachmentAboutToAdd(const QString& key) diff --git a/src/gui/entry/EntryAttachmentsModel.h b/src/gui/entry/EntryAttachmentsModel.h index c2e238aeb..6abcdc2e2 100644 --- a/src/gui/entry/EntryAttachmentsModel.h +++ b/src/gui/entry/EntryAttachmentsModel.h @@ -34,7 +34,7 @@ public: QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QString keyByIndex(const QModelIndex& index) const; -private Q_SLOTS: +private slots: void attachmentChange(const QString& key); void attachmentAboutToAdd(const QString& key); void attachmentAdd(); diff --git a/src/gui/entry/EntryAttributesModel.cpp b/src/gui/entry/EntryAttributesModel.cpp index b22380ae8..1b1eab220 100644 --- a/src/gui/entry/EntryAttributesModel.cpp +++ b/src/gui/entry/EntryAttributesModel.cpp @@ -147,7 +147,7 @@ void EntryAttributesModel::attributeChange(const QString& key) { int row = m_attributes.indexOf(key); Q_ASSERT(row != -1); - Q_EMIT dataChanged(index(row, 0), index(row, columnCount()-1)); + emit dataChanged(index(row, 0), index(row, columnCount()-1)); } void EntryAttributesModel::attributeAboutToAdd(const QString& key) @@ -213,7 +213,7 @@ void EntryAttributesModel::attributeRename(const QString& oldKey, const QString& m_nextRenameDataChange = false; QModelIndex keyIndex = index(m_attributes.indexOf(newKey), 0); - Q_EMIT dataChanged(keyIndex, keyIndex); + emit dataChanged(keyIndex, keyIndex); } } diff --git a/src/gui/entry/EntryAttributesModel.h b/src/gui/entry/EntryAttributesModel.h index 1eec8bff7..7d613c1f0 100644 --- a/src/gui/entry/EntryAttributesModel.h +++ b/src/gui/entry/EntryAttributesModel.h @@ -38,7 +38,7 @@ public: QModelIndex indexByKey(const QString& key) const; QString keyByIndex(const QModelIndex& index) const; -private Q_SLOTS: +private slots: void attributeChange(const QString& key); void attributeAboutToAdd(const QString& key); void attributeAdd(); diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index 323a55c82..b28eaed46 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -64,7 +64,7 @@ void EntryModel::setGroup(Group* group) makeConnections(group); endResetModel(); - Q_EMIT switchedToGroupMode(); + emit switchedToGroupMode(); } void EntryModel::setEntryList(const QList& entries) @@ -101,7 +101,7 @@ void EntryModel::setEntryList(const QList& entries) } endResetModel(); - Q_EMIT switchedToEntryListMode(); + emit switchedToEntryListMode(); } int EntryModel::rowCount(const QModelIndex& parent) const @@ -315,7 +315,7 @@ void EntryModel::entryRemoved() void EntryModel::entryDataChanged(Entry* entry) { int row = m_entries.indexOf(entry); - Q_EMIT dataChanged(index(row, 0), index(row, columnCount()-1)); + emit dataChanged(index(row, 0), index(row, columnCount()-1)); } void EntryModel::severConnections() diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h index 0183c47be..d12982d83 100644 --- a/src/gui/entry/EntryModel.h +++ b/src/gui/entry/EntryModel.h @@ -52,14 +52,14 @@ public: void setEntryList(const QList& entries); -Q_SIGNALS: +signals: void switchedToEntryListMode(); void switchedToGroupMode(); -public Q_SLOTS: +public slots: void setGroup(Group* group); -private Q_SLOTS: +private slots: void entryAboutToAdd(Entry* entry); void entryAdded(Entry* entry); void entryAboutToRemove(Entry* entry); diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index 31fae3e58..1bdd4fbcf 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -57,7 +57,7 @@ void EntryView::keyPressEvent(QKeyEvent* event) emitEntryActivated(currentIndex()); #ifdef Q_OS_MAC // Pressing return does not emit the QTreeView::activated signal on mac os - Q_EMIT activated(currentIndex()); + emit activated(currentIndex()); #endif } @@ -83,7 +83,7 @@ void EntryView::setFirstEntryActive() setCurrentEntry(m_model->entryFromIndex(index)); } else { - Q_EMIT entrySelectionChanged(); + emit entrySelectionChanged(); } } @@ -96,7 +96,7 @@ void EntryView::emitEntryActivated(const QModelIndex& index) { Entry* entry = entryFromIndex(index); - Q_EMIT entryActivated(entry, static_cast(m_sortModel->mapToSource(index).column())); + emit entryActivated(entry, static_cast(m_sortModel->mapToSource(index).column())); } void EntryView::setModel(QAbstractItemModel* model) diff --git a/src/gui/entry/EntryView.h b/src/gui/entry/EntryView.h index fb9e3566a..6a545f62a 100644 --- a/src/gui/entry/EntryView.h +++ b/src/gui/entry/EntryView.h @@ -42,17 +42,17 @@ public: int numberOfSelectedEntries(); void setFirstEntryActive(); -public Q_SLOTS: +public slots: void setGroup(Group* group); -Q_SIGNALS: +signals: void entryActivated(Entry* entry, EntryModel::ModelColumn column); void entrySelectionChanged(); protected: void keyPressEvent(QKeyEvent* event) override; -private Q_SLOTS: +private slots: void emitEntryActivated(const QModelIndex& index); void switchToEntryListMode(); void switchToGroupMode(); diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp index ac4e4ce99..4f2e9fec5 100644 --- a/src/gui/group/EditGroupWidget.cpp +++ b/src/gui/group/EditGroupWidget.cpp @@ -130,7 +130,7 @@ void EditGroupWidget::save() } clear(); - Q_EMIT editFinished(true); + emit editFinished(true); } void EditGroupWidget::cancel() @@ -141,7 +141,7 @@ void EditGroupWidget::cancel() } clear(); - Q_EMIT editFinished(false); + emit editFinished(false); } void EditGroupWidget::clear() diff --git a/src/gui/group/EditGroupWidget.h b/src/gui/group/EditGroupWidget.h index 606cc77b0..39f2c09b0 100644 --- a/src/gui/group/EditGroupWidget.h +++ b/src/gui/group/EditGroupWidget.h @@ -43,12 +43,12 @@ public: void loadGroup(Group* group, bool create, Database* database); void clear(); -Q_SIGNALS: +signals: void editFinished(bool accepted); void messageEditEntry(QString, MessageWidget::MessageType); void messageEditEntryDismiss(); -private Q_SLOTS: +private slots: void save(); void cancel(); diff --git a/src/gui/group/GroupModel.cpp b/src/gui/group/GroupModel.cpp index 5aafc1a79..87eacf275 100644 --- a/src/gui/group/GroupModel.cpp +++ b/src/gui/group/GroupModel.cpp @@ -365,7 +365,7 @@ QMimeData* GroupModel::mimeData(const QModelIndexList& indexes) const void GroupModel::groupDataChanged(Group* group) { QModelIndex ix = index(group); - Q_EMIT dataChanged(ix, ix); + emit dataChanged(ix, ix); } void GroupModel::groupAboutToRemove(Group* group) diff --git a/src/gui/group/GroupModel.h b/src/gui/group/GroupModel.h index 0ef0ba990..899aa3fd1 100644 --- a/src/gui/group/GroupModel.h +++ b/src/gui/group/GroupModel.h @@ -49,7 +49,7 @@ public: private: QModelIndex parent(Group* group) const; -private Q_SLOTS: +private slots: void groupDataChanged(Group* group); void groupAboutToRemove(Group* group); void groupRemoved(); diff --git a/src/gui/group/GroupView.cpp b/src/gui/group/GroupView.cpp index 18f7de804..e9649e441 100644 --- a/src/gui/group/GroupView.cpp +++ b/src/gui/group/GroupView.cpp @@ -112,7 +112,7 @@ void GroupView::expandGroup(Group* group, bool expand) void GroupView::emitGroupChanged(const QModelIndex& index) { - Q_EMIT groupChanged(m_model->groupFromIndex(index)); + emit groupChanged(m_model->groupFromIndex(index)); } void GroupView::setModel(QAbstractItemModel* model) @@ -123,7 +123,7 @@ void GroupView::setModel(QAbstractItemModel* model) void GroupView::emitGroupChanged() { - Q_EMIT groupChanged(currentGroup()); + emit groupChanged(currentGroup()); } void GroupView::syncExpandedState(const QModelIndex& parent, int start, int end) diff --git a/src/gui/group/GroupView.h b/src/gui/group/GroupView.h index 69ca82817..eaa290725 100644 --- a/src/gui/group/GroupView.h +++ b/src/gui/group/GroupView.h @@ -36,10 +36,10 @@ public: void setCurrentGroup(Group* group); void expandGroup(Group* group, bool expand = true); -Q_SIGNALS: +signals: void groupChanged(Group* group); -private Q_SLOTS: +private slots: void expandedChanged(const QModelIndex& index); void emitGroupChanged(const QModelIndex& index); void emitGroupChanged(); diff --git a/src/http/HttpPasswordGeneratorWidget.h b/src/http/HttpPasswordGeneratorWidget.h index f8e35c232..f9907600b 100644 --- a/src/http/HttpPasswordGeneratorWidget.h +++ b/src/http/HttpPasswordGeneratorWidget.h @@ -38,7 +38,7 @@ public: void saveSettings(); void reset(); -private Q_SLOTS: +private slots: void sliderMoved(); void spinBoxChanged(); diff --git a/src/http/OptionDialog.h b/src/http/OptionDialog.h index 1b1819159..ad535fdb8 100644 --- a/src/http/OptionDialog.h +++ b/src/http/OptionDialog.h @@ -29,11 +29,11 @@ public: explicit OptionDialog(QWidget *parent = nullptr); ~OptionDialog(); -public Q_SLOTS: +public slots: void loadSettings(); void saveSettings(); -Q_SIGNALS: +signals: void removeSharedEncryptionKeys(); void removeStoredPermissions(); diff --git a/src/http/Service.h b/src/http/Service.h index 6452d605a..b6ee5bea0 100644 --- a/src/http/Service.h +++ b/src/http/Service.h @@ -38,7 +38,7 @@ public: virtual void updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url); virtual QString generatePassword(); -public Q_SLOTS: +public slots: void removeSharedEncryptionKeys(); void removeStoredPermissions(); diff --git a/src/streams/LayeredStream.h b/src/streams/LayeredStream.h index 8586b4134..4ca7aba9a 100644 --- a/src/streams/LayeredStream.h +++ b/src/streams/LayeredStream.h @@ -37,7 +37,7 @@ protected: QIODevice* const m_baseDevice; -private Q_SLOTS: +private slots: void closeStream(); }; diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index c585fec25..569bc8c70 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -31,7 +31,7 @@ class TestAutoType : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void init(); void cleanup(); diff --git a/tests/TestCryptoHash.h b/tests/TestCryptoHash.h index 05700f349..d31501bae 100644 --- a/tests/TestCryptoHash.h +++ b/tests/TestCryptoHash.h @@ -24,7 +24,7 @@ class TestCryptoHash : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void test(); }; diff --git a/tests/TestCsvExporter.h b/tests/TestCsvExporter.h index a8cfe7f25..39597f752 100644 --- a/tests/TestCsvExporter.h +++ b/tests/TestCsvExporter.h @@ -31,7 +31,7 @@ class TestCsvExporter : public QObject public: static const QString ExpectedHeaderLine; -private Q_SLOTS: +private slots: void init(); void initTestCase(); void cleanup(); diff --git a/tests/TestDeletedObjects.h b/tests/TestDeletedObjects.h index 27b70cced..d96452093 100644 --- a/tests/TestDeletedObjects.h +++ b/tests/TestDeletedObjects.h @@ -29,7 +29,7 @@ class TestDeletedObjects : public QObject private: void createAndDelete(Database* db, int delObjectsSize); -private Q_SLOTS: +private slots: void initTestCase(); void testDeletedObjectsFromFile(); void testDeletedObjectsFromNewDb(); diff --git a/tests/TestEntry.h b/tests/TestEntry.h index ed772d505..0c97c0b9d 100644 --- a/tests/TestEntry.h +++ b/tests/TestEntry.h @@ -26,7 +26,7 @@ class TestEntry : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testHistoryItemDeletion(); void testCopyDataFrom(); diff --git a/tests/TestEntryModel.h b/tests/TestEntryModel.h index 778392f20..df80331e8 100644 --- a/tests/TestEntryModel.h +++ b/tests/TestEntryModel.h @@ -24,7 +24,7 @@ class TestEntryModel : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void test(); void testAttachmentsModel(); diff --git a/tests/TestEntrySearcher.h b/tests/TestEntrySearcher.h index 7c45451dc..3965c22e0 100644 --- a/tests/TestEntrySearcher.h +++ b/tests/TestEntrySearcher.h @@ -28,7 +28,7 @@ class TestEntrySearcher : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void cleanupTestCase(); diff --git a/tests/TestExporter.h b/tests/TestExporter.h index 15f9a7c33..8c9945252 100644 --- a/tests/TestExporter.h +++ b/tests/TestExporter.h @@ -25,7 +25,7 @@ class TestExporter : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testToDbExporter(); }; diff --git a/tests/TestGroup.h b/tests/TestGroup.h index 4a891ae6f..c9ed8f087 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -25,7 +25,7 @@ class TestGroup : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testParenting(); void testSignals(); diff --git a/tests/TestGroupModel.h b/tests/TestGroupModel.h index 093af9e0f..1b5c0ab46 100644 --- a/tests/TestGroupModel.h +++ b/tests/TestGroupModel.h @@ -24,7 +24,7 @@ class TestGroupModel : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void test(); }; diff --git a/tests/TestHashedBlockStream.h b/tests/TestHashedBlockStream.h index 9aeac1411..6c36f8e6a 100644 --- a/tests/TestHashedBlockStream.h +++ b/tests/TestHashedBlockStream.h @@ -24,7 +24,7 @@ class TestHashedBlockStream : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testWriteRead(); void testReset(); diff --git a/tests/TestKeePass1Reader.h b/tests/TestKeePass1Reader.h index 20acd4bb9..9a5ab9e49 100644 --- a/tests/TestKeePass1Reader.h +++ b/tests/TestKeePass1Reader.h @@ -27,7 +27,7 @@ class TestKeePass1Reader : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testBasic(); void testMasterKey(); diff --git a/tests/TestKeePass2RandomStream.h b/tests/TestKeePass2RandomStream.h index b001a05a2..967ed9c9e 100644 --- a/tests/TestKeePass2RandomStream.h +++ b/tests/TestKeePass2RandomStream.h @@ -24,7 +24,7 @@ class TestKeePass2RandomStream : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void test(); }; diff --git a/tests/TestKeePass2Reader.h b/tests/TestKeePass2Reader.h index 6f090de38..76ffe0297 100644 --- a/tests/TestKeePass2Reader.h +++ b/tests/TestKeePass2Reader.h @@ -24,7 +24,7 @@ class TestKeePass2Reader : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testNonAscii(); void testCompressed(); diff --git a/tests/TestKeePass2Writer.h b/tests/TestKeePass2Writer.h index 822883823..36a51dce6 100644 --- a/tests/TestKeePass2Writer.h +++ b/tests/TestKeePass2Writer.h @@ -26,7 +26,7 @@ class TestKeePass2Writer : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testBasic(); void testProtectedAttributes(); diff --git a/tests/TestKeePass2XmlReader.h b/tests/TestKeePass2XmlReader.h index ff83e2597..628964b46 100644 --- a/tests/TestKeePass2XmlReader.h +++ b/tests/TestKeePass2XmlReader.h @@ -27,7 +27,7 @@ class TestKeePass2XmlReader : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testMetadata(); void testCustomIcons(); diff --git a/tests/TestKeys.h b/tests/TestKeys.h index a6d0b7e1a..683f07683 100644 --- a/tests/TestKeys.h +++ b/tests/TestKeys.h @@ -24,7 +24,7 @@ class TestKeys : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testComposite(); void testCompositeKeyReadFromLine(); diff --git a/tests/TestModified.h b/tests/TestModified.h index ee598addf..518bea7c0 100644 --- a/tests/TestModified.h +++ b/tests/TestModified.h @@ -24,7 +24,7 @@ class TestModified : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testSignals(); void testGroupSets(); diff --git a/tests/TestRandom.h b/tests/TestRandom.h index c879f9450..323d6b613 100644 --- a/tests/TestRandom.h +++ b/tests/TestRandom.h @@ -38,7 +38,7 @@ class TestRandom : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testUInt(); void testUIntRange(); diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index 17fa77a49..8259af620 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -24,7 +24,7 @@ class TestSymmetricCipher : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testAes256CbcEncryption(); void testAes256CbcDecryption(); diff --git a/tests/TestWildcardMatcher.h b/tests/TestWildcardMatcher.h index c241c7553..e23770937 100644 --- a/tests/TestWildcardMatcher.h +++ b/tests/TestWildcardMatcher.h @@ -26,7 +26,7 @@ class TestWildcardMatcher : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void testMatcher(); void testMatcher_data(); diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index c2e0e372e..1ae297005 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -33,7 +33,7 @@ class TestGui : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void init(); void cleanup(); diff --git a/tests/gui/TestGuiPixmaps.h b/tests/gui/TestGuiPixmaps.h index ef0b664b5..6e649c0f7 100644 --- a/tests/gui/TestGuiPixmaps.h +++ b/tests/gui/TestGuiPixmaps.h @@ -26,7 +26,7 @@ class TestGuiPixmaps : public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void testDatabaseIcons(); void testEntryIcons(); diff --git a/tests/modeltest.h b/tests/modeltest.h index 3dcf18ceb..fdc5cf2f6 100644 --- a/tests/modeltest.h +++ b/tests/modeltest.h @@ -46,7 +46,7 @@ class ModelTest : public QObject public: ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); -private Q_SLOTS: +private slots: void nonDestructiveBasicTest(); void rowCount(); void columnCount(); @@ -55,7 +55,7 @@ private Q_SLOTS: void parent(); void data(); -protected Q_SLOTS: +protected slots: void runAllTests(); void layoutAboutToBeChanged(); void layoutChanged(); From 1984595d0d37e2626ed69fff077cf9dad5c3b459 Mon Sep 17 00:00:00 2001 From: rockihack Date: Fri, 3 Mar 2017 22:56:52 +0100 Subject: [PATCH 144/333] Enable DEP and ASLR. --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 08a42d1ca..017ab49d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,8 @@ if(MINGW) set(CMAKE_RC_COMPILER_INIT windres) enable_language(RC) set(CMAKE_RC_COMPILE_OBJECT " -O coff -i -o ") + # Enable DEP and ASLR + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") link_libraries(ws2_32 wsock32) endif() From 914b848e5849e26d1fe964c081a082fe8ad484b4 Mon Sep 17 00:00:00 2001 From: rockihack Date: Fri, 10 Mar 2017 13:09:52 +0100 Subject: [PATCH 145/333] Enable DEP+ASLR for cmake modules (autotype dll). --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 017ab49d2..51773ba02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,6 +129,7 @@ if(MINGW) set(CMAKE_RC_COMPILE_OBJECT " -O coff -i -o ") # Enable DEP and ASLR set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") link_libraries(ws2_32 wsock32) endif() From 8a942422dac52859f887fff0483c9c09c4f97aab Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Mar 2017 13:23:46 +0100 Subject: [PATCH 146/333] Harden Linux binary --- CMakeLists.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51773ba02..a0326997d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,7 +68,7 @@ endmacro(add_gcc_compiler_flags) add_definitions(-DQT_NO_EXCEPTIONS -DQT_STRICT_ITERATORS -DQT_NO_CAST_TO_ASCII) -add_gcc_compiler_flags("-fno-common -fstack-protector --param=ssp-buffer-size=4") +add_gcc_compiler_flags("-fno-common -fstack-protector-strong --param=ssp-buffer-size=4") add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long") add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute") add_gcc_compiler_flags("-fvisibility=hidden") @@ -79,7 +79,7 @@ add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virt add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings") string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) -if (CMAKE_BUILD_TYPE_LOWER MATCHES (release|relwithdebinfo|minsizerel)) +if (CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)") add_gcc_compiler_flags("-D_FORTIFY_SOURCE=2") endif() @@ -105,10 +105,14 @@ if(CMAKE_COMPILER_IS_GNUCC) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + if (CMAKE_COMPILER_IS_CLANGXX) + add_gcc_compiler_flags("-Qunused-arguments") + endif() + add_gcc_compiler_flags("-pie -fPIE") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed -Wl,--no-undefined") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro,-z,now") endif() add_gcc_compiler_cxxflags("-std=c++11") From 429bef6830ef1d3695e7edbed1fca4a29b6a7de6 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Mar 2017 18:06:22 +0100 Subject: [PATCH 147/333] Remove unused debug function --- src/keys/drivers/YubiKey.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index ffb48fc74..dfbc57c69 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -143,21 +143,6 @@ bool YubiKey::getSerial(unsigned int& serial) return true; } -#ifdef QT_DEBUG -/** - * @brief printByteArray - debug raw data - * @param a array input - * @return string representation of array - */ -static inline QString printByteArray(const QByteArray& a) -{ - QString s; - for (int i = 0; i < a.size(); i++) - s.append(QString::number(a[i] & 0xff, 16).rightJustified(2, '0')); - return s; -} -#endif - YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response) { if (!m_mutex.tryLock()) { From 34f037be9293703d9e85647a51e876f821ad853f Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Mar 2017 18:09:46 +0100 Subject: [PATCH 148/333] Enable -fstack-protector-strong flag only for GCC >= 4.9 and Clang --- CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0326997d..048bbf68a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,12 +68,18 @@ endmacro(add_gcc_compiler_flags) add_definitions(-DQT_NO_EXCEPTIONS -DQT_STRICT_ITERATORS -DQT_NO_CAST_TO_ASCII) -add_gcc_compiler_flags("-fno-common -fstack-protector-strong --param=ssp-buffer-size=4") +add_gcc_compiler_flags("-fno-common") add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long") add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute") add_gcc_compiler_flags("-fvisibility=hidden") add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden") +if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.8.999) OR CMAKE_COMPILER_IS_CLANGXX) + add_gcc_compiler_flags("-fstack-protector-strong") +else() + add_gcc_compiler_flags("-fstack-protector --param=ssp-buffer-size=4") +endif() + add_gcc_compiler_cxxflags("-fno-exceptions -fno-rtti") add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual") add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings") From 2ff57c2eb75f64db39111c6a5a02cf7c1cad87e5 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 10 Mar 2017 20:42:59 +0100 Subject: [PATCH 149/333] Coding style fixes --- src/gui/ChangeMasterKeyWidget.cpp | 13 ++++++++----- src/gui/DatabaseOpenWidget.cpp | 16 +++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index bb963d3cd..616b0ee01 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -166,16 +166,18 @@ void ChangeMasterKeyWidget::generateKey() #ifdef WITH_XC_YUBIKEY if (m_ui->challengeResponseGroup->isChecked()) { - int i = m_ui->comboChallengeResponse->currentIndex(); - i = m_ui->comboChallengeResponse->itemData(i).toInt(); + int selectionIndex = m_ui->comboChallengeResponse->currentIndex(); + int comboPayload = m_ui->comboChallengeResponse->itemData(selectionIndex).toInt(); - if (0 == i) { + if (0 == comboPayload) { m_ui->messageWidget->showMessage(tr("Changing master key failed: no YubiKey inserted."), MessageWidget::Error); return; } - bool blocking = i & true; - int slot = i >> 1; + + // read blocking mode from LSB and slot index number from second LSB + bool blocking = comboPayload & 1; + int slot = comboPayload >> 1; auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); m_key.addChallengeResponseKey(key); } @@ -212,6 +214,7 @@ void ChangeMasterKeyWidget::pollYubikey() void ChangeMasterKeyWidget::yubikeyDetected(int slot, bool blocking) { YkChallengeResponseKey yk(slot, blocking); + // add detected YubiKey to combo box and encode blocking mode in LSB, slot number in second LSB m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant((slot << 1) | blocking)); m_ui->comboChallengeResponse->setEnabled(m_ui->challengeResponseGroup->isChecked()); m_ui->buttonRedetectYubikey->setEnabled(m_ui->challengeResponseGroup->isChecked()); diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index 2eebcae3c..f7d432479 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -37,9 +37,9 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent) - : DialogyWidget(parent), - m_ui(new Ui::DatabaseOpenWidget()), - m_db(nullptr) + : DialogyWidget(parent) + , m_ui(new Ui::DatabaseOpenWidget()) + , m_db(nullptr) { m_ui->setupUi(this); @@ -208,11 +208,12 @@ CompositeKey DatabaseOpenWidget::databaseKey() } if (m_ui->checkChallengeResponse->isChecked()) { - int i = m_ui->comboChallengeResponse->currentIndex(); - i = m_ui->comboChallengeResponse->itemData(i).toInt(); + int selectionIndex = m_ui->comboChallengeResponse->currentIndex(); + int comboPayload = m_ui->comboChallengeResponse->itemData(selectionIndex).toInt(); - bool blocking = i & true; - int slot = i >> 1; + // read blocking mode from LSB and slot index number from second LSB + bool blocking = comboPayload & 1; + int slot = comboPayload >> 1; auto key = QSharedPointer(new YkChallengeResponseKey(slot, blocking)); masterKey.addChallengeResponseKey(key); } @@ -267,6 +268,7 @@ void DatabaseOpenWidget::pollYubikey() void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking) { YkChallengeResponseKey yk(slot, blocking); + // add detected YubiKey to combo box and encode blocking mode in LSB, slot number in second LSB m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant((slot << 1) | blocking)); m_ui->comboChallengeResponse->setEnabled(true); m_ui->checkChallengeResponse->setEnabled(true); From 1b10aae74c21ff035d6ddadd7c17517c409bf937 Mon Sep 17 00:00:00 2001 From: louib Date: Fri, 10 Mar 2017 21:25:56 -0500 Subject: [PATCH 150/333] Updating README.md (#390) * Adding Yubikey 2FA to feature list * Added project url --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2b6fd0315..93da44c6c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# KeePassXC - KeePass Cross-platform Community Edition +# 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) KeePassXC Authenticode Certificate Campaign! -[![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) -KeePassXC Authenticode Certificate Campaign! +KeePass Cross-platform Community Edition ## About -KeePassXC 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. +[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. ## Additional features compared to KeePassX - Auto-Type on all three major platforms (Linux, Windows, OS X) - Stand-alone password generator - Password strength meter +- Yubikey 2FA support for unlocking databases - Using website favicons as entry icons - Merging of databases - Automatic reload when the database changed on disk From f12c6bf748e133addc4bcd7e20fc3065164645c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20M=C3=BCller?= Date: Tue, 14 Mar 2017 14:55:25 +0100 Subject: [PATCH 151/333] Update feature and build instructions for Yubikey --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 93da44c6c..aced5afb7 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ KeePass Cross-platform Community Edition - Auto-Type on all three major platforms (Linux, Windows, OS X) - Stand-alone password generator - Password strength meter -- Yubikey 2FA support for unlocking databases +- YukiKey HMAC-SHA1 authentication for unlocking databases - Using website favicons as entry icons - Merging of databases - Automatic reload when the database changed on disk @@ -55,7 +55,7 @@ make -j8 sudo make install ``` -To enable autotype, add `-DWITH_XC_AUTOTYPE=ON` to the `cmake` command. KeePassHTTP support is compiled in by adding `-DWITH_XC_HTTP=ON`. If these options are not specified, KeePassXC will be built without these plugins. +To enable autotype, add `-DWITH_XC_AUTOTYPE=ON` to the `cmake` command. KeePassHTTP support is compiled in by adding `-DWITH_XC_HTTP=ON`. Yubikey HMAC-SHA1 authentication support is compiled in by adding `-DWITH_XC_YUBIKEY=ON`. If these options are not specified, KeePassXC will be built without these plugins. ### Contributing From 51b7ec2b26916abff475b878f562dfbdf961901b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20M=C3=BCller?= Date: Wed, 15 Mar 2017 15:04:43 +0100 Subject: [PATCH 152/333] List all cmake build options --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aced5afb7..e49512455 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,18 @@ make -j8 sudo make install ``` -To enable autotype, add `-DWITH_XC_AUTOTYPE=ON` to the `cmake` command. KeePassHTTP support is compiled in by adding `-DWITH_XC_HTTP=ON`. Yubikey HMAC-SHA1 authentication support is compiled in by adding `-DWITH_XC_YUBIKEY=ON`. If these options are not specified, KeePassXC will be built without these plugins. +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_COVERAGE=[ON|OFF] Enable/Disable coverage tests. (GCC ONLY) (default: OFF) +``` ### Contributing From 65d4a0a8cd5c59ebdd36a20f9f70f174acf5867d Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 14:29:09 +0100 Subject: [PATCH 153/333] Add ASAN option to CMake --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef1743df7..2ea0fc8f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ include(CheckCXXSourceCompiles) option(WITH_TESTS "Enable building of unit tests" ON) option(WITH_GUI_TESTS "Enable building of GUI tests" OFF) option(WITH_DEV_BUILD "Use only for development. Disables/warns about deprecated methods." OFF) +option(WITH_ASAN "Enable address sanitizer checks" OFF) option(WITH_COVERAGE "Use to build with coverage tests. (GCC ONLY)." OFF) option(WITH_XC_AUTOTYPE "Include Auto-Type." ON) @@ -83,6 +84,9 @@ endif() add_gcc_compiler_cxxflags("-fno-exceptions -fno-rtti") add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual") add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings") +if(WITH_ASAN) + add_gcc_compiler_flags("-fsanitize=address") +endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if (CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)") From 28ec015ef4ef2f142f56983e089dd7197d7245df Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 14:34:16 +0100 Subject: [PATCH 154/333] Add -DWITH_ASAN=ON requirement to pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c83ca4e53..b9852f3c9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,11 +3,11 @@ ## Description -## Motivation and Context +## Motivation and context -## How Has This Been Tested? +## How has this been tested? @@ -29,5 +29,6 @@ - ✅ I have read the **CONTRIBUTING** document. **[REQUIRED]** - ✅ My code follows the code style of this project. **[REQUIRED]** - ✅ All new and existing tests passed. **[REQUIRED]** +- ✅ I have compiled and verified my code with `-DWITH_ASAN=ON`. **[REQUIRED]** - ✅ My change requires a change to the documentation and I have updated it accordingly. - ✅ I have added tests to cover my changes. From 504bd402630ad0eacdd59b3f884e269c48c027c3 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 15:32:48 +0100 Subject: [PATCH 155/333] Prevent massive end-of-process leak sanitizer dump --- CMakeLists.txt | 2 +- src/main.cpp | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ea0fc8f3..86dedbc11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,7 +85,7 @@ add_gcc_compiler_cxxflags("-fno-exceptions -fno-rtti") add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual") add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings") if(WITH_ASAN) - add_gcc_compiler_flags("-fsanitize=address") + add_gcc_compiler_flags("-fsanitize=address -DWITH_ASAN") endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) diff --git a/src/main.cpp b/src/main.cpp index 0618cefae..2563c843b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,10 @@ #include "gui/MainWindow.h" #include "gui/MessageBox.h" +#ifdef WITH_ASAN +#include +#endif + #ifdef QT_STATIC #include @@ -130,6 +134,14 @@ int main(int argc, char** argv) } } } - - return app.exec(); + + int exitCode = app.exec(); + +#ifdef WITH_ASAN + // do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries + __lsan_do_leak_check(); + __lsan_disable(); +#endif + + return exitCode; } From 2587bac30024f5e2c59a1c33bd25609e2466e000 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 14:53:29 +0100 Subject: [PATCH 156/333] Enable ASAN option in Travis build --- .travis.yml | 8 ++++---- CMakeLists.txt | 4 ++++ src/main.cpp | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index be05d6e47..e24d1d178 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,15 +13,15 @@ compiler: - gcc env: - - CONFIG=Release - - CONFIG=Debug + - 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 git: depth: 3 before_install: - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libmicrohttpd10 libmicrohttpd-dev libxi-dev qtbase5-dev libqt5x11extras5-dev qttools5-dev qttools5-dev-tools libgcrypt20-dev zlib1g-dev libxtst-dev xvfb libyubikey-dev libykpers-1-dev; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libclang-common-3.5-dev libxi-dev qtbase5-dev libqt5x11extras5-dev qttools5-dev qttools5-dev-tools libgcrypt20-dev zlib1g-dev libxtst-dev xvfb libyubikey-dev libykpers-1-dev; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq cmake || brew install cmake; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq qt5 || brew install qt5; fi @@ -32,7 +32,7 @@ before_script: - mkdir build && pushd build script: - - cmake -DCMAKE_BUILD_TYPE=${CONFIG} -DWITH_GUI_TESTS=ON -DWITH_XC_HTTP=ON -DWITH_XC_AUTOTYPE=ON -DWITH_XC_YUBIKEY=ON $CMAKE_ARGS .. + - 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 86dedbc11..7b41ac2fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,10 @@ add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virt add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings") if(WITH_ASAN) add_gcc_compiler_flags("-fsanitize=address -DWITH_ASAN") + + if(NOT (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)) + add_gcc_compiler_flags("-fsanitize=leak -DWITH_LSAN") + endif() endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) diff --git a/src/main.cpp b/src/main.cpp index 2563c843b..8ed4ee14c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,7 @@ #include "gui/MainWindow.h" #include "gui/MessageBox.h" -#ifdef WITH_ASAN +#if defined(WITH_ASAN) && defined(WITH_LSAN) #include #endif @@ -137,7 +137,7 @@ int main(int argc, char** argv) int exitCode = app.exec(); -#ifdef WITH_ASAN +#if defined(WITH_ASAN) && defined(WITH_LSAN) // do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries __lsan_do_leak_check(); __lsan_disable(); From 9608464ed12b412d0a270d9baf34a647b22a2a49 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 21:24:32 +0100 Subject: [PATCH 157/333] Show error message when trying to use WITH_ASAN on Windows or OS X --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b41ac2fb..9448763fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,10 @@ add_gcc_compiler_cxxflags("-fno-exceptions -fno-rtti") add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual") add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings") if(WITH_ASAN) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + message(FATAL_ERROR "WITH_ASAN is only supported on Linux at the moment.") + endif() + add_gcc_compiler_flags("-fsanitize=address -DWITH_ASAN") if(NOT (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)) From 8b04040d7e1ce0b7c6e7184b1efcebb6d1426dcf Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 15 Mar 2017 15:26:40 +0100 Subject: [PATCH 158/333] Add WITH_ASAN option to README --- CMakeLists.txt | 4 ++-- README.md | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9448763fb..e7d22312f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,8 +32,8 @@ include(CheckCXXSourceCompiles) option(WITH_TESTS "Enable building of unit tests" ON) option(WITH_GUI_TESTS "Enable building of GUI tests" OFF) option(WITH_DEV_BUILD "Use only for development. Disables/warns about deprecated methods." OFF) -option(WITH_ASAN "Enable address sanitizer checks" OFF) -option(WITH_COVERAGE "Use to build with coverage tests. (GCC ONLY)." OFF) +option(WITH_ASAN "Enable address sanitizer checks (Linux only)" OFF) +option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF) option(WITH_XC_AUTOTYPE "Include Auto-Type." ON) option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF) diff --git a/README.md b/README.md index e49512455..fd439ded4 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,15 @@ 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_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_COVERAGE=[ON|OFF] Enable/Disable coverage tests. (GCC ONLY) (default: OFF) + -DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF) + -DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux only) (default: OFF) + -DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF) ``` ### Contributing From 52991f3d66eb5e7d7a5098dfe3b9bb7106466f52 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 30 Jan 2017 19:18:35 -0500 Subject: [PATCH 159/333] Add first keepassxc-cli version. --- src/CMakeLists.txt | 1 + {utils => src/cli}/CMakeLists.txt | 29 +++---- utils/kdbx-extract.cpp => src/cli/Extract.cpp | 9 +-- src/cli/Extract.h | 27 +++++++ utils/kdbx-merge.cpp => src/cli/Merge.cpp | 12 +-- src/cli/Merge.h | 27 +++++++ src/cli/keepassxc-cli.cpp | 77 +++++++++++++++++++ 7 files changed, 150 insertions(+), 32 deletions(-) rename {utils => src/cli}/CMakeLists.txt (58%) rename utils/kdbx-extract.cpp => src/cli/Extract.cpp (92%) create mode 100644 src/cli/Extract.h rename utils/kdbx-merge.cpp => src/cli/Merge.cpp (93%) create mode 100644 src/cli/Merge.h create mode 100644 src/cli/keepassxc-cli.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e221b916..1d9984460 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -164,6 +164,7 @@ if(WITH_XC_HTTP) endif() add_subdirectory(autotype) +add_subdirectory(cli) set(autotype_SOURCES core/Tools.cpp diff --git a/utils/CMakeLists.txt b/src/cli/CMakeLists.txt similarity index 58% rename from utils/CMakeLists.txt rename to src/cli/CMakeLists.txt index 83f00b4bc..4456cbeaa 100644 --- a/utils/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2010 Felix Geyer +# Copyright (C) 2017 KeePassXC Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -13,24 +13,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -include_directories(../src) +set(cli_SOURCES + Merge.cpp + Merge.h + Extract.cpp + Extract.h) -add_executable(kdbx-extract kdbx-extract.cpp) -target_link_libraries(kdbx-extract +add_library(cli STATIC ${cli_SOURCES}) +target_link_libraries(cli Qt5::Core Qt5::Widgets) + +add_executable(keepassxc-cli keepassxc-cli.cpp) +target_link_libraries(keepassxc-cli + cli keepassx_core Qt5::Core ${GCRYPT_LIBRARIES} - ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES}) - -add_executable(kdbx-merge kdbx-merge.cpp) -target_link_libraries(kdbx-merge - keepassx_core - Qt5::Core - ${GCRYPT_LIBRARIES} - ${GPGERROR_LIBRARIES} - ${ZLIB_LIBRARIES}) - - -add_executable(entropy-meter entropy-meter.cpp) -target_link_libraries(entropy-meter zxcvbn) diff --git a/utils/kdbx-extract.cpp b/src/cli/Extract.cpp similarity index 92% rename from utils/kdbx-extract.cpp rename to src/cli/Extract.cpp index 255f5d003..4ad658784 100644 --- a/utils/kdbx-extract.cpp +++ b/src/cli/Extract.cpp @@ -17,6 +17,8 @@ #include +#include "Extract.h" + #include #include #include @@ -30,7 +32,7 @@ #include "keys/FileKey.h" #include "keys/PasswordKey.h" -int main(int argc, char **argv) +int Extract::execute(int argc, char **argv) { QCoreApplication app(argc, argv); @@ -38,7 +40,6 @@ int main(int argc, char **argv) parser.setApplicationDescription(QCoreApplication::translate("main", "Extract and print a KeePassXC database file.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database to extract.")); - parser.addHelpOption(); parser.process(app); const QStringList args = parser.positionalArguments(); @@ -47,10 +48,6 @@ int main(int argc, char **argv) return 1; } - if (!Crypto::init()) { - qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); - } - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QString line = inputTextStream.readLine(); CompositeKey key = CompositeKey::readFromLine(line); diff --git a/src/cli/Extract.h b/src/cli/Extract.h new file mode 100644 index 000000000..9a6638e4b --- /dev/null +++ b/src/cli/Extract.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_EXTRACT_H +#define KEEPASSXC_EXTRACT_H + +class Extract +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_EXTRACT_H diff --git a/utils/kdbx-merge.cpp b/src/cli/Merge.cpp similarity index 93% rename from utils/kdbx-merge.cpp rename to src/cli/Merge.cpp index da780ea1b..bac8f4bd8 100644 --- a/utils/kdbx-merge.cpp +++ b/src/cli/Merge.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include +#include "Merge.h" #include #include @@ -25,12 +25,11 @@ #include #include "core/Database.h" -#include "crypto/Crypto.h" #include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" #include "keys/CompositeKey.h" -int main(int argc, char **argv) +int Merge::execute(int argc, char** argv) { QCoreApplication app(argc, argv); @@ -43,7 +42,6 @@ int main(int argc, char **argv) QCommandLineOption samePasswordOption(QStringList() << "s" << "same-password", QCoreApplication::translate("main", "use the same password for both database files.")); - parser.addHelpOption(); parser.addOption(samePasswordOption); parser.process(app); @@ -53,10 +51,6 @@ int main(int argc, char **argv) return 1; } - if (!Crypto::init()) { - qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); - } - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QString line1 = inputTextStream.readLine(); @@ -133,6 +127,6 @@ int main(int argc, char **argv) } qDebug("Successfully merged the database files.\n"); - return 1; + return 0; } diff --git a/src/cli/Merge.h b/src/cli/Merge.h new file mode 100644 index 000000000..dd9b8a4c0 --- /dev/null +++ b/src/cli/Merge.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_MERGE_H +#define KEEPASSXC_MERGE_H + +class Merge +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_MERGE_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp new file mode 100644 index 000000000..086e544a6 --- /dev/null +++ b/src/cli/keepassxc-cli.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include +#include + +#include "config-keepassx.h" +#include "crypto/Crypto.h" + +int main(int argc, char **argv) +{ + + if (!Crypto::init()) { + qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); + return 1; + } + + QCoreApplication app(argc, argv); + app.setApplicationVersion(KEEPASSX_VERSION); + + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::translate("main", "KeepassXC command line interface.")); + parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute.")); + + parser.addHelpOption(); + parser.addVersionOption(); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.size() < 1) { + parser.showHelp(); + return 1; + } + + QString commandName = args.at(0); + + for (int i = 1; i < argc - 1; ++i) { + argv[i] = argv[i+1]; + } + argv[argc - 1] = nullptr; + argc--; + + if (commandName == "merge") + { + argv[0] = const_cast("keepassxc-cli merge"); + return Merge::execute(argc, argv); + } + + if (commandName == "extract") + { + argv[0] = const_cast("keepassxc-cli extract"); + return Extract::execute(argc, argv); + } + + qCritical("Invalid command %s.", qPrintable(commandName)); + parser.showHelp(); + return 1; + +} From 9cfc862b0787567a06c330c5f72ccd7bccc3e5bd Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 10:33:50 -0500 Subject: [PATCH 160/333] Disable core dumps (keepassxc-cli). --- src/cli/keepassxc-cli.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 086e544a6..78b111647 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -23,10 +23,14 @@ #include #include "config-keepassx.h" +#include "core/Tools.h" #include "crypto/Crypto.h" int main(int argc, char **argv) { +#ifdef QT_NO_DEBUG + Tools::disableCoreDumps(); +#endif if (!Crypto::init()) { qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); From bf9b23539e172f1bd3aa9e2c4d3ff44726a2115c Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 10:51:33 -0500 Subject: [PATCH 161/333] Add dependency + adjust styling. --- src/cli/CMakeLists.txt | 1 + src/cli/keepassxc-cli.cpp | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 4456cbeaa..70dd420d5 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -28,4 +28,5 @@ target_link_libraries(keepassxc-cli keepassx_core Qt5::Core ${GCRYPT_LIBRARIES} + ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES}) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 78b111647..7df4d7d87 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -57,20 +57,18 @@ int main(int argc, char **argv) QString commandName = args.at(0); for (int i = 1; i < argc - 1; ++i) { - argv[i] = argv[i+1]; + argv[i] = argv[i + 1]; } argv[argc - 1] = nullptr; - argc--; + --argc; - if (commandName == "merge") - { - argv[0] = const_cast("keepassxc-cli merge"); + if (commandName == "merge") { + argv[0] = const_cast("keepassxc-cli merge"); return Merge::execute(argc, argv); } - if (commandName == "extract") - { - argv[0] = const_cast("keepassxc-cli extract"); + if (commandName == "extract") { + argv[0] = const_cast("keepassxc-cli extract"); return Extract::execute(argc, argv); } From 9b92e7f8e8fe6438f7916832ede22bdb9b22bdae Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 17:30:54 -0500 Subject: [PATCH 162/333] Use EXIT_FAILURE/SUCCESS --- src/cli/Extract.cpp | 11 ++++++----- src/cli/Merge.cpp | 24 +++++++++++++----------- src/cli/keepassxc-cli.cpp | 11 ++++++----- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 4ad658784..73581d8f1 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include #include #include "Extract.h" @@ -45,7 +46,7 @@ int Extract::execute(int argc, char **argv) const QStringList args = parser.positionalArguments(); if (args.size() != 1) { parser.showHelp(); - return 1; + return EXIT_FAILURE; } static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); @@ -56,11 +57,11 @@ int Extract::execute(int argc, char **argv) QFile dbFile(databaseFilename); if (!dbFile.exists()) { qCritical("File %s does not exist.", qPrintable(databaseFilename)); - return 1; + return EXIT_FAILURE; } if (!dbFile.open(QIODevice::ReadOnly)) { qCritical("Unable to open file %s.", qPrintable(databaseFilename)); - return 1; + return EXIT_FAILURE; } KeePass2Reader reader; @@ -73,15 +74,15 @@ int Extract::execute(int argc, char **argv) if (reader.hasError()) { if (xmlData.isEmpty()) { qCritical("Error while reading the database:\n%s", qPrintable(reader.errorString())); - return 1; } else { qWarning("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); } + return EXIT_FAILURE; } QTextStream out(stdout); out << xmlData.constData() << "\n"; - return 0; + return EXIT_SUCCESS; } diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index bac8f4bd8..caa8d19c1 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +#include + #include "Merge.h" #include @@ -48,7 +50,7 @@ int Merge::execute(int argc, char** argv) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { parser.showHelp(); - return 1; + return EXIT_FAILURE; } static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); @@ -70,11 +72,11 @@ int Merge::execute(int argc, char** argv) QFile dbFile1(databaseFilename1); if (!dbFile1.exists()) { qCritical("File %s does not exist.", qPrintable(databaseFilename1)); - return 1; + return EXIT_FAILURE; } if (!dbFile1.open(QIODevice::ReadOnly)) { qCritical("Unable to open file %s.", qPrintable(databaseFilename1)); - return 1; + return EXIT_FAILURE; } KeePass2Reader reader1; @@ -82,7 +84,7 @@ int Merge::execute(int argc, char** argv) if (reader1.hasError()) { qCritical("Error while parsing the database:\n%s\n", qPrintable(reader1.errorString())); - return 1; + return EXIT_FAILURE; } @@ -90,11 +92,11 @@ int Merge::execute(int argc, char** argv) QFile dbFile2(databaseFilename2); if (!dbFile2.exists()) { qCritical("File %s does not exist.", qPrintable(databaseFilename2)); - return 1; + return EXIT_FAILURE; } if (!dbFile2.open(QIODevice::ReadOnly)) { qCritical("Unable to open file %s.", qPrintable(databaseFilename2)); - return 1; + return EXIT_FAILURE; } KeePass2Reader reader2; @@ -102,7 +104,7 @@ int Merge::execute(int argc, char** argv) if (reader2.hasError()) { qCritical("Error while parsing the database:\n%s\n", qPrintable(reader2.errorString())); - return 1; + return EXIT_FAILURE; } db1->merge(db2); @@ -110,7 +112,7 @@ int Merge::execute(int argc, char** argv) QSaveFile saveFile(databaseFilename1); if (!saveFile.open(QIODevice::WriteOnly)) { qCritical("Unable to open file %s for writing.", qPrintable(databaseFilename1)); - return 1; + return EXIT_FAILURE; } KeePass2Writer writer; @@ -118,15 +120,15 @@ int Merge::execute(int argc, char** argv) if (writer.hasError()) { qCritical("Error while updating the database:\n%s\n", qPrintable(writer.errorString())); - return 1; + return EXIT_FAILURE; } if (!saveFile.commit()) { qCritical("Error while updating the database:\n%s\n", qPrintable(writer.errorString())); - return 0; + return EXIT_FAILURE; } qDebug("Successfully merged the database files.\n"); - return 0; + return EXIT_SUCCESS; } diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 7df4d7d87..e745dd09f 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -15,13 +15,14 @@ * along with this program. If not, see . */ -#include -#include +#include #include #include #include +#include +#include #include "config-keepassx.h" #include "core/Tools.h" #include "crypto/Crypto.h" @@ -34,7 +35,7 @@ int main(int argc, char **argv) if (!Crypto::init()) { qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); - return 1; + return EXIT_FAILURE; } QCoreApplication app(argc, argv); @@ -51,7 +52,7 @@ int main(int argc, char **argv) const QStringList args = parser.positionalArguments(); if (args.size() < 1) { parser.showHelp(); - return 1; + return EXIT_FAILURE; } QString commandName = args.at(0); @@ -74,6 +75,6 @@ int main(int argc, char **argv) qCritical("Invalid command %s.", qPrintable(commandName)); parser.showHelp(); - return 1; + return EXIT_FAILURE; } From 992d8a90c746f31ba77ada572d1e1c88ab299d79 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 17:54:39 -0500 Subject: [PATCH 163/333] Migrate entropy-meter to keepassxc-cli --- CMakeLists.txt | 1 - src/cli/CMakeLists.txt | 2 ++ .../cli/EntropyMeter.cpp | 4 ++- src/cli/EntropyMeter.h | 27 +++++++++++++++++++ src/cli/keepassxc-cli.cpp | 16 ++++++++--- 5 files changed, 44 insertions(+), 6 deletions(-) rename utils/entropy-meter.cpp => src/cli/EntropyMeter.cpp (98%) create mode 100644 src/cli/EntropyMeter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e7d22312f..3dd435553 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,7 +254,6 @@ include(FeatureSummary) add_subdirectory(src) add_subdirectory(share) -add_subdirectory(utils) if(WITH_TESTS) add_subdirectory(tests) endif(WITH_TESTS) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 70dd420d5..d678ff395 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -16,6 +16,8 @@ set(cli_SOURCES Merge.cpp Merge.h + EntropyMeter.cpp + EntropyMeter.h Extract.cpp Extract.h) diff --git a/utils/entropy-meter.cpp b/src/cli/EntropyMeter.cpp similarity index 98% rename from utils/entropy-meter.cpp rename to src/cli/EntropyMeter.cpp index 74f6bc11a..ffaecc8e6 100644 --- a/utils/entropy-meter.cpp +++ b/src/cli/EntropyMeter.cpp @@ -6,6 +6,8 @@ Copyright (c) 2016, KeePassXC Team See zxcvbn/zxcvbn.cpp for complete COPYRIGHT Notice */ +#include "EntropyMeter.h" + #include #include #include @@ -76,7 +78,7 @@ static void calculate(const char *pwd, int advanced) } } -int main(int argc, char **argv) +int EntropyMeter::execute(int argc, char **argv) { 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> "); diff --git a/src/cli/EntropyMeter.h b/src/cli/EntropyMeter.h new file mode 100644 index 000000000..5034b9660 --- /dev/null +++ b/src/cli/EntropyMeter.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_ENTROPYMETER_H +#define KEEPASSXC_ENTROPYMETER_H + +class EntropyMeter +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_ENTROPYMETER_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index e745dd09f..e8719d9c2 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -23,6 +23,7 @@ #include #include +#include #include "config-keepassx.h" #include "core/Tools.h" #include "crypto/Crypto.h" @@ -47,15 +48,17 @@ int main(int argc, char **argv) parser.addHelpOption(); parser.addVersionOption(); - parser.process(app); + // TODO : use process once the setOptionsAfterPositionalArgumentsMode (Qt 5.6) + // is available. Until then, options passed to sub-commands won't be + // recognized by this parser. + // parser.process(app); - const QStringList args = parser.positionalArguments(); - if (args.size() < 1) { + if (argc < 2) { parser.showHelp(); return EXIT_FAILURE; } - QString commandName = args.at(0); + QString commandName = argv[1]; for (int i = 1; i < argc - 1; ++i) { argv[i] = argv[i + 1]; @@ -73,6 +76,11 @@ int main(int argc, char **argv) return Extract::execute(argc, argv); } + if (commandName == "entropy-meter") { + argv[0] = const_cast("keepassxc-cli entropy-meter"); + return EntropyMeter::execute(argc, argv); + } + qCritical("Invalid command %s.", qPrintable(commandName)); parser.showHelp(); return EXIT_FAILURE; From 342d49d05048330d07f7062bcf989df5f9c8b21a Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 17:59:06 -0500 Subject: [PATCH 164/333] Missing zxcvbn dependency. --- src/cli/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index d678ff395..8b547636a 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -31,4 +31,5 @@ target_link_libraries(keepassxc-cli Qt5::Core ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES} - ${ZLIB_LIBRARIES}) + ${ZLIB_LIBRARIES} + zxcvbn) From 805600ad448bb129f745bfded33d18adc86a714e Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 18:29:31 -0500 Subject: [PATCH 165/333] Installing keepassxc-cli executable. --- src/cli/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 8b547636a..b8dce663c 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -33,3 +33,7 @@ target_link_libraries(keepassxc-cli ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES} zxcvbn) + +install(TARGETS keepassxc-cli + BUNDLE DESTINATION . COMPONENT Runtime + RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime) From e1e8f33f6776a82302f4fb64b7d69851407c0873 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 4 Feb 2017 14:52:43 -0500 Subject: [PATCH 166/333] Install path on Mac. --- CMakeLists.txt | 3 +++ src/cli/CMakeLists.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dd435553..56baa8f81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,16 +166,19 @@ if(APPLE AND "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") endif() if(MINGW) + set(CLI_INSTALL_DIR ".") set(BIN_INSTALL_DIR ".") set(PLUGIN_INSTALL_DIR ".") set(DATA_INSTALL_DIR "share") elseif(APPLE) + set(CLI_INSTALL_DIR "/usr/local/bin") set(BIN_INSTALL_DIR ".") set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns") set(DATA_INSTALL_DIR "${PROGNAME}.app/Contents/Resources") else() include(GNUInstallDirs) + set(CLI_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassxc") set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassxc") diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index b8dce663c..4f0386e36 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -36,4 +36,4 @@ target_link_libraries(keepassxc-cli install(TARGETS keepassxc-cli BUNDLE DESTINATION . COMPONENT Runtime - RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime) + RUNTIME DESTINATION ${CLI_INSTALL_DIR} COMPONENT Runtime) From 782d1f17d1d4c57ac940655ab48481de2ede5ed1 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 5 Feb 2017 12:56:44 -0500 Subject: [PATCH 167/333] Using ++argv --- src/cli/keepassxc-cli.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index e8719d9c2..e532e95f6 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -60,10 +60,8 @@ int main(int argc, char **argv) QString commandName = argv[1]; - for (int i = 1; i < argc - 1; ++i) { - argv[i] = argv[i + 1]; - } - argv[argc - 1] = nullptr; + // Removing the first cli argument before dispatching. + ++argv; --argc; if (commandName == "merge") { From b85941531df219cdc5547445df7e91318c026c3f Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 5 Feb 2017 13:40:40 -0500 Subject: [PATCH 168/333] Keepass -> KeePass. --- src/cli/keepassxc-cli.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index e532e95f6..a7e7c275c 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -43,7 +43,7 @@ int main(int argc, char **argv) app.setApplicationVersion(KEEPASSX_VERSION); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", "KeepassXC command line interface.")); + parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassXC command line interface.")); parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute.")); parser.addHelpOption(); From 7636a559f9408ee765665c9e94527bf9c63c0d21 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 13 Feb 2017 22:21:19 -0500 Subject: [PATCH 169/333] Remove unused imports. --- src/cli/Extract.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 73581d8f1..b08039aee 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -27,11 +27,8 @@ #include #include "core/Database.h" -#include "crypto/Crypto.h" #include "format/KeePass2Reader.h" #include "keys/CompositeKey.h" -#include "keys/FileKey.h" -#include "keys/PasswordKey.h" int Extract::execute(int argc, char **argv) { From 7ca475f968deba1a659c0b03235afe806a2016bf Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 13 Feb 2017 22:29:20 -0500 Subject: [PATCH 170/333] Add list to keepassxc-cli --- src/cli/CMakeLists.txt | 2 + src/cli/List.cpp | 101 ++++++++++++++++++++++++++++++++++++++ src/cli/List.h | 27 ++++++++++ src/cli/keepassxc-cli.cpp | 6 +++ 4 files changed, 136 insertions(+) create mode 100644 src/cli/List.cpp create mode 100644 src/cli/List.h diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 4f0386e36..0ed5d991d 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -14,6 +14,8 @@ # along with this program. If not, see . set(cli_SOURCES + List.cpp + List.h Merge.cpp Merge.h EntropyMeter.cpp diff --git a/src/cli/List.cpp b/src/cli/List.cpp new file mode 100644 index 000000000..2049cc1d4 --- /dev/null +++ b/src/cli/List.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "List.h" + +#include +#include +#include +#include +#include + +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "format/KeePass2Reader.h" +#include "keys/CompositeKey.h" + +void printGroup(Group* group, QString baseName, int depth) { + + QTextStream out(stdout); + + QString groupName = baseName + group->name() + "/"; + QString indentation = QString(" ").repeated(depth); + + out << indentation << groupName << "\n"; + out.flush(); + + if (group->entries().isEmpty() && group->children().isEmpty()) { + out << indentation << " [empty]\n"; + return; + } + + for (Entry* entry : group->entries()) { + out << indentation << " " << entry->title() << " " << entry->uuid().toHex() << "\n"; + } + + for (Group* innerGroup : group->children()) { + printGroup(innerGroup, groupName, depth + 1); + } + +} + +int List::execute(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::translate("main", + "List the passwords in the database.")); + parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database.")); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 1) { + parser.showHelp(); + return EXIT_FAILURE; + } + + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + QString line = inputTextStream.readLine(); + CompositeKey key = CompositeKey::readFromLine(line); + + QString databaseFilename = args.at(0); + QFile dbFile(databaseFilename); + if (!dbFile.exists()) { + qCritical("File %s does not exist.", qPrintable(databaseFilename)); + return EXIT_FAILURE; + } + if (!dbFile.open(QIODevice::ReadOnly)) { + qCritical("Unable to open file %s.", qPrintable(databaseFilename)); + return EXIT_FAILURE; + } + + KeePass2Reader reader; + Database* db = reader.readDatabase(&dbFile, key); + + if (reader.hasError()) { + qCritical("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + return EXIT_FAILURE; + } + + printGroup(db->rootGroup(), QString(""), 0); + return EXIT_SUCCESS; +} diff --git a/src/cli/List.h b/src/cli/List.h new file mode 100644 index 000000000..76f086c63 --- /dev/null +++ b/src/cli/List.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_LIST_H +#define KEEPASSXC_LIST_H + +class List +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_LIST_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index a7e7c275c..67eb8e8ad 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -64,6 +65,11 @@ int main(int argc, char **argv) ++argv; --argc; + if (commandName == "list") { + argv[0] = const_cast("keepassxc-cli list"); + return List::execute(argc, argv); + } + if (commandName == "merge") { argv[0] = const_cast("keepassxc-cli merge"); return Merge::execute(argc, argv); From 64dfada038f0f72465e2c6eebcb7ce42ace340b5 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 15 Feb 2017 21:01:50 -0500 Subject: [PATCH 171/333] Adding available commands. --- src/cli/keepassxc-cli.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 67eb8e8ad..c96da4d18 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -44,7 +44,15 @@ int main(int argc, char **argv) app.setApplicationVersion(KEEPASSX_VERSION); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassXC command line interface.")); + + QString description("KeePassXC command line interface."); + description = description.append(QString("\n\nAvailable commands:")); + description = description.append(QString("\n extract\tExtract and print a KeePassXC database file.")); + description = description.append(QString("\n entropy-meter\tCalculate password entropy.")); + description = description.append(QString("\n list\t\tList database entries.")); + description = description.append(QString("\n merge\t\tMerge 2 KeePassXC database files.")); + parser.setApplicationDescription(QCoreApplication::translate("main", qPrintable(description))); + parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute.")); parser.addHelpOption(); From f579345059815f55a88042ddb16e49a72a671b33 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 15 Feb 2017 21:05:40 -0500 Subject: [PATCH 172/333] Change cli commands description. --- src/cli/Extract.cpp | 2 +- src/cli/List.cpp | 2 +- src/cli/Merge.cpp | 2 +- src/cli/keepassxc-cli.cpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index b08039aee..cbdb2a394 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -36,7 +36,7 @@ int Extract::execute(int argc, char **argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", - "Extract and print a KeePassXC database file.")); + "Extract and print the content of a database.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database to extract.")); parser.process(app); diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 2049cc1d4..0c1389d20 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -63,7 +63,7 @@ int List::execute(int argc, char **argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", - "List the passwords in the database.")); + "List database entries.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database.")); parser.process(app); diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index caa8d19c1..fdc81b9f6 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -37,7 +37,7 @@ int Merge::execute(int argc, char** argv) QCoreApplication app(argc, argv); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", "Merge 2 KeePassXC database files.")); + parser.setApplicationDescription(QCoreApplication::translate("main", "Merge two databases.")); parser.addPositionalArgument("database1", QCoreApplication::translate("main", "path of the database to merge into.")); parser.addPositionalArgument("database2", QCoreApplication::translate("main", "path of the database to merge from.")); diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index c96da4d18..700f1263f 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -47,10 +47,10 @@ int main(int argc, char **argv) QString description("KeePassXC command line interface."); description = description.append(QString("\n\nAvailable commands:")); - description = description.append(QString("\n extract\tExtract and print a KeePassXC database file.")); + description = description.append(QString("\n extract\tExtract and print the content of a database.")); description = description.append(QString("\n entropy-meter\tCalculate password entropy.")); description = description.append(QString("\n list\t\tList database entries.")); - description = description.append(QString("\n merge\t\tMerge 2 KeePassXC database files.")); + description = description.append(QString("\n merge\t\tMerge two databases.")); parser.setApplicationDescription(QCoreApplication::translate("main", qPrintable(description))); parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute.")); From 98911af39646997250bfe9c0d68b34bfe9ae4542 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 16 Feb 2017 12:53:34 -0500 Subject: [PATCH 173/333] Fixed indentation. --- src/cli/keepassxc-cli.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 700f1263f..85b679bd7 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -74,23 +74,23 @@ int main(int argc, char **argv) --argc; if (commandName == "list") { - argv[0] = const_cast("keepassxc-cli list"); - return List::execute(argc, argv); + argv[0] = const_cast("keepassxc-cli list"); + return List::execute(argc, argv); } if (commandName == "merge") { - argv[0] = const_cast("keepassxc-cli merge"); - return Merge::execute(argc, argv); + argv[0] = const_cast("keepassxc-cli merge"); + return Merge::execute(argc, argv); } if (commandName == "extract") { - argv[0] = const_cast("keepassxc-cli extract"); - return Extract::execute(argc, argv); + argv[0] = const_cast("keepassxc-cli extract"); + return Extract::execute(argc, argv); } if (commandName == "entropy-meter") { - argv[0] = const_cast("keepassxc-cli entropy-meter"); - return EntropyMeter::execute(argc, argv); + argv[0] = const_cast("keepassxc-cli entropy-meter"); + return EntropyMeter::execute(argc, argv); } qCritical("Invalid command %s.", qPrintable(commandName)); From e01e9715b925da073c79a92e9c882416ca84706c Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 16 Feb 2017 20:26:51 +0100 Subject: [PATCH 174/333] text for inserting password --- src/cli/Extract.cpp | 5 ++++- src/cli/List.cpp | 4 ++++ src/cli/Merge.cpp | 7 +++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index cbdb2a394..5bfb1185f 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -33,6 +33,7 @@ int Extract::execute(int argc, char **argv) { QCoreApplication app(argc, argv); + QTextStream out(stdout); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", @@ -46,6 +47,9 @@ int Extract::execute(int argc, char **argv) return EXIT_FAILURE; } + out << "Insert the database password\n> "; + out.flush(); + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QString line = inputTextStream.readLine(); CompositeKey key = CompositeKey::readFromLine(line); @@ -78,7 +82,6 @@ int Extract::execute(int argc, char **argv) return EXIT_FAILURE; } - QTextStream out(stdout); out << xmlData.constData() << "\n"; return EXIT_SUCCESS; diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 0c1389d20..1702a469f 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -60,6 +60,7 @@ void printGroup(Group* group, QString baseName, int depth) { int List::execute(int argc, char **argv) { QCoreApplication app(argc, argv); + QTextStream out(stdout); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", @@ -73,6 +74,9 @@ int List::execute(int argc, char **argv) return EXIT_FAILURE; } + out << "Insert the database password\n> "; + out.flush(); + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QString line = inputTextStream.readLine(); CompositeKey key = CompositeKey::readFromLine(line); diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index fdc81b9f6..0f4a90117 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -35,6 +35,7 @@ int Merge::execute(int argc, char** argv) { QCoreApplication app(argc, argv); + QTextStream out(stdout); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "Merge two databases.")); @@ -53,8 +54,10 @@ int Merge::execute(int argc, char** argv) return EXIT_FAILURE; } + out << "Insert the database password\n> "; + out.flush(); + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line1 = inputTextStream.readLine(); CompositeKey key1 = CompositeKey::readFromLine(line1); @@ -128,7 +131,7 @@ int Merge::execute(int argc, char** argv) return EXIT_FAILURE; } - qDebug("Successfully merged the database files.\n"); + out << "Successfully merged the database files.\n"; return EXIT_SUCCESS; } From 15c2727a1d57980684218c60a36b1f29cf8e95a2 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:34:56 -0400 Subject: [PATCH 175/333] Adding the show command. --- src/cli/CMakeLists.txt | 10 +++-- src/cli/Extract.cpp | 2 +- src/cli/List.cpp | 2 +- src/cli/Merge.cpp | 6 +-- src/cli/Show.cpp | 83 +++++++++++++++++++++++++++++++++++++++ src/cli/Show.h | 27 +++++++++++++ src/cli/keepassxc-cli.cpp | 28 ++++++++----- 7 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 src/cli/Show.cpp create mode 100644 src/cli/Show.h diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 0ed5d991d..e090ad1d8 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -14,14 +14,16 @@ # along with this program. If not, see . set(cli_SOURCES + EntropyMeter.cpp + EntropyMeter.h + Extract.cpp + Extract.h List.cpp List.h Merge.cpp Merge.h - EntropyMeter.cpp - EntropyMeter.h - Extract.cpp - Extract.h) + Show.cpp + Show.h) add_library(cli STATIC ${cli_SOURCES}) target_link_libraries(cli Qt5::Core Qt5::Widgets) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 5bfb1185f..81a9ddf07 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -38,7 +38,7 @@ int Extract::execute(int argc, char **argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "Extract and print the content of a database.")); - parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database to extract.")); + parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database to extract.")); parser.process(app); const QStringList args = parser.positionalArguments(); diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 1702a469f..cfeba3ceb 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -65,7 +65,7 @@ int List::execute(int argc, char **argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "List database entries.")); - parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database.")); + parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); parser.process(app); const QStringList args = parser.positionalArguments(); diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 0f4a90117..404892f7b 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -39,11 +39,11 @@ int Merge::execute(int argc, char** argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "Merge two databases.")); - parser.addPositionalArgument("database1", QCoreApplication::translate("main", "path of the database to merge into.")); - parser.addPositionalArgument("database2", QCoreApplication::translate("main", "path of the database to merge from.")); + parser.addPositionalArgument("database1", QCoreApplication::translate("main", "Path of the database to merge into.")); + parser.addPositionalArgument("database2", QCoreApplication::translate("main", "Path of the database to merge from.")); QCommandLineOption samePasswordOption(QStringList() << "s" << "same-password", - QCoreApplication::translate("main", "use the same password for both database files.")); + QCoreApplication::translate("main", "Use the same password for both database files.")); parser.addOption(samePasswordOption); parser.process(app); diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp new file mode 100644 index 000000000..c4a3b1acd --- /dev/null +++ b/src/cli/Show.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "Show.h" + +#include +#include +#include +#include +#include + +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "format/KeePass2Reader.h" +#include "keys/CompositeKey.h" + +int Show::execute(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + QTextStream out(stdout); + + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::translate("main", + "Show a password.")); + parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); + parser.addPositionalArgument("uuid", QCoreApplication::translate("main", "Uuid of the entry to show")); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 2) { + parser.showHelp(); + return EXIT_FAILURE; + } + + out << "Insert the database password\n> "; + out.flush(); + + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + QString line = inputTextStream.readLine(); + CompositeKey key = CompositeKey::readFromLine(line); + + QString databaseFilename = args.at(0); + QFile dbFile(databaseFilename); + if (!dbFile.exists()) { + qCritical("File %s does not exist.", qPrintable(databaseFilename)); + return EXIT_FAILURE; + } + if (!dbFile.open(QIODevice::ReadOnly)) { + qCritical("Unable to open file %s.", qPrintable(databaseFilename)); + return EXIT_FAILURE; + } + + KeePass2Reader reader; + Database* db = reader.readDatabase(&dbFile, key); + + if (reader.hasError()) { + qCritical("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + return EXIT_FAILURE; + } + + Uuid uuid = Uuid::fromHex(args.at(1)); + Entry* entry = db->resolveEntry(uuid); + out << entry->password() << "\n"; + return EXIT_SUCCESS; +} diff --git a/src/cli/Show.h b/src/cli/Show.h new file mode 100644 index 000000000..aa06b5c9a --- /dev/null +++ b/src/cli/Show.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_SHOW_H +#define KEEPASSXC_SHOW_H + +class Show +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_SHOW_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 85b679bd7..1802870e3 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -21,10 +21,12 @@ #include #include +#include +#include #include #include -#include -#include +#include + #include "config-keepassx.h" #include "core/Tools.h" #include "crypto/Crypto.h" @@ -51,6 +53,7 @@ int main(int argc, char **argv) description = description.append(QString("\n entropy-meter\tCalculate password entropy.")); description = description.append(QString("\n list\t\tList database entries.")); description = description.append(QString("\n merge\t\tMerge two databases.")); + description = description.append(QString("\n show\t\tShow a password.")); parser.setApplicationDescription(QCoreApplication::translate("main", qPrintable(description))); parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute.")); @@ -73,6 +76,16 @@ int main(int argc, char **argv) ++argv; --argc; + if (commandName == "entropy-meter") { + argv[0] = const_cast("keepassxc-cli entropy-meter"); + return EntropyMeter::execute(argc, argv); + } + + if (commandName == "extract") { + argv[0] = const_cast("keepassxc-cli extract"); + return Extract::execute(argc, argv); + } + if (commandName == "list") { argv[0] = const_cast("keepassxc-cli list"); return List::execute(argc, argv); @@ -83,14 +96,9 @@ int main(int argc, char **argv) return Merge::execute(argc, argv); } - if (commandName == "extract") { - argv[0] = const_cast("keepassxc-cli extract"); - return Extract::execute(argc, argv); - } - - if (commandName == "entropy-meter") { - argv[0] = const_cast("keepassxc-cli entropy-meter"); - return EntropyMeter::execute(argc, argv); + if (commandName == "show") { + argv[0] = const_cast("keepassxc-cli show"); + return Show::execute(argc, argv); } qCritical("Invalid command %s.", qPrintable(commandName)); From db1bf889347d3327bcd6cd0a175780746f9dcee7 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:37:20 -0400 Subject: [PATCH 176/333] Handling entry not found. --- src/cli/Show.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index c4a3b1acd..b596e521b 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -78,6 +78,11 @@ int Show::execute(int argc, char **argv) Uuid uuid = Uuid::fromHex(args.at(1)); Entry* entry = db->resolveEntry(uuid); + if (entry == nullptr) { + qCritical("No entry found with uuid %s", qPrintable(uuid.toHex())); + return EXIT_FAILURE; + } + out << entry->password() << "\n"; return EXIT_SUCCESS; } From 993f90cb2cfcc71da99ea6dfef092fab9463169a Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:47:05 -0400 Subject: [PATCH 177/333] Extracting openDatabaseFile. --- src/cli/Show.cpp | 20 ++------------------ src/core/Database.cpp | 25 +++++++++++++++++++++++++ src/core/Database.h | 1 + 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index b596e521b..9222a093d 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -22,14 +22,12 @@ #include #include -#include #include #include #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" -#include "format/KeePass2Reader.h" #include "keys/CompositeKey.h" int Show::execute(int argc, char **argv) @@ -57,22 +55,8 @@ int Show::execute(int argc, char **argv) QString line = inputTextStream.readLine(); CompositeKey key = CompositeKey::readFromLine(line); - QString databaseFilename = args.at(0); - QFile dbFile(databaseFilename); - if (!dbFile.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename)); - return EXIT_FAILURE; - } - if (!dbFile.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename)); - return EXIT_FAILURE; - } - - KeePass2Reader reader; - Database* db = reader.readDatabase(&dbFile, key); - - if (reader.hasError()) { - qCritical("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + Database* db = Database::openDatabaseFile(args.at(0), key); + if (db == nullptr) { return EXIT_FAILURE; } diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 23b564143..4aa9e3f5b 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -25,6 +25,7 @@ #include "core/Metadata.h" #include "crypto/Random.h" #include "format/KeePass2.h" +#include "format/KeePass2Reader.h" QHash Database::m_uuidMap; @@ -355,3 +356,27 @@ const CompositeKey & Database::key() const return m_data.key; } +Database* Database::openDatabaseFile(QString fileName, CompositeKey key) +{ + + QFile dbFile(fileName); + if (!dbFile.exists()) { + qCritical("File %s does not exist.", qPrintable(fileName)); + return nullptr; + } + if (!dbFile.open(QIODevice::ReadOnly)) { + qCritical("Unable to open file %s.", qPrintable(fileName)); + return nullptr; + } + + KeePass2Reader reader; + Database* db = reader.readDatabase(&dbFile, key); + + if (reader.hasError()) { + qCritical("Error while parsing the database: %s", qPrintable(reader.errorString())); + return nullptr; + } + + return db; + +} diff --git a/src/core/Database.h b/src/core/Database.h index 3767bb30d..16c149608 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -118,6 +118,7 @@ public: Uuid uuid(); static Database* databaseByUuid(const Uuid& uuid); + static Database* openDatabaseFile(QString fileName, CompositeKey key); signals: void groupDataChanged(Group* group); From 780e23301b5bf5e27e06ac8f2397f633ce7a3424 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:56:30 -0400 Subject: [PATCH 178/333] Using openDatabaseFile in Merge. --- src/cli/Merge.cpp | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 404892f7b..118213b80 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -21,13 +21,11 @@ #include #include -#include #include #include #include #include "core/Database.h" -#include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" #include "keys/CompositeKey.h" @@ -71,50 +69,21 @@ int Merge::execute(int argc, char** argv) } - QString databaseFilename1 = args.at(0); - QFile dbFile1(databaseFilename1); - if (!dbFile1.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename1)); - return EXIT_FAILURE; - } - if (!dbFile1.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename1)); + Database* db1 = Database::openDatabaseFile(args.at(0), key1); + if (db1 == nullptr) { return EXIT_FAILURE; } - KeePass2Reader reader1; - Database* db1 = reader1.readDatabase(&dbFile1, key1); - - if (reader1.hasError()) { - qCritical("Error while parsing the database:\n%s\n", qPrintable(reader1.errorString())); - return EXIT_FAILURE; - } - - - QString databaseFilename2 = args.at(1); - QFile dbFile2(databaseFilename2); - if (!dbFile2.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename2)); - return EXIT_FAILURE; - } - if (!dbFile2.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename2)); - return EXIT_FAILURE; - } - - KeePass2Reader reader2; - Database* db2 = reader2.readDatabase(&dbFile2, key2); - - if (reader2.hasError()) { - qCritical("Error while parsing the database:\n%s\n", qPrintable(reader2.errorString())); + Database* db2 = Database::openDatabaseFile(args.at(1), key2); + if (db2 == nullptr) { return EXIT_FAILURE; } db1->merge(db2); - QSaveFile saveFile(databaseFilename1); + QSaveFile saveFile(args.at(0)); if (!saveFile.open(QIODevice::WriteOnly)) { - qCritical("Unable to open file %s for writing.", qPrintable(databaseFilename1)); + qCritical("Unable to open file %s for writing.", qPrintable(args.at(0))); return EXIT_FAILURE; } From cf2334391110d5443c37c00241bd53beee06d7db Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:57:44 -0400 Subject: [PATCH 179/333] Using openDatabaseFile in List. --- src/cli/List.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/cli/List.cpp b/src/cli/List.cpp index cfeba3ceb..b2d673f51 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -22,14 +22,12 @@ #include #include -#include #include #include #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" -#include "format/KeePass2Reader.h" #include "keys/CompositeKey.h" void printGroup(Group* group, QString baseName, int depth) { @@ -81,22 +79,8 @@ int List::execute(int argc, char **argv) QString line = inputTextStream.readLine(); CompositeKey key = CompositeKey::readFromLine(line); - QString databaseFilename = args.at(0); - QFile dbFile(databaseFilename); - if (!dbFile.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename)); - return EXIT_FAILURE; - } - if (!dbFile.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename)); - return EXIT_FAILURE; - } - - KeePass2Reader reader; - Database* db = reader.readDatabase(&dbFile, key); - - if (reader.hasError()) { - qCritical("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + Database* db = Database::openDatabaseFile(args.at(0), key); + if (db == nullptr) { return EXIT_FAILURE; } From fd9d372e6ab9780cb433d8ec334fcedd6550290c Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 14:17:03 -0400 Subject: [PATCH 180/333] Adding second prompt for merge. --- src/cli/Merge.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 118213b80..aa399dd5b 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -52,7 +52,7 @@ int Merge::execute(int argc, char** argv) return EXIT_FAILURE; } - out << "Insert the database password\n> "; + out << "Insert the first database password\n> "; out.flush(); static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); @@ -64,6 +64,8 @@ int Merge::execute(int argc, char** argv) key2 = *key1.clone(); } else { + out << "Insert the second database password\n> "; + out.flush(); QString line2 = inputTextStream.readLine(); key2 = CompositeKey::readFromLine(line2); } From a661c17eca4659303880b174ba9850b5b0c05c6e Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 14:24:22 -0400 Subject: [PATCH 181/333] Adding group uuid to list. --- src/cli/List.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/List.cpp b/src/cli/List.cpp index b2d673f51..1e3488106 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -37,7 +37,7 @@ void printGroup(Group* group, QString baseName, int depth) { QString groupName = baseName + group->name() + "/"; QString indentation = QString(" ").repeated(depth); - out << indentation << groupName << "\n"; + out << indentation << groupName << " " << group->uuid().toHex() << "\n"; out.flush(); if (group->entries().isEmpty() && group->children().isEmpty()) { From 558c75a452ccafa925c658db48a9e68e875c664b Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 16 Mar 2017 18:41:12 +0100 Subject: [PATCH 182/333] Add ASAN to keepassxc-cli --- src/cli/keepassxc-cli.cpp | 46 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 1802870e3..b27b7483f 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -31,6 +31,10 @@ #include "core/Tools.h" #include "crypto/Crypto.h" +#if defined(WITH_ASAN) && defined(WITH_LSAN) +#include +#endif + int main(int argc, char **argv) { #ifdef QT_NO_DEBUG @@ -76,33 +80,35 @@ int main(int argc, char **argv) ++argv; --argc; + int exitCode = EXIT_FAILURE; + if (commandName == "entropy-meter") { argv[0] = const_cast("keepassxc-cli entropy-meter"); - return EntropyMeter::execute(argc, argv); - } - - if (commandName == "extract") { + exitCode = EntropyMeter::execute(argc, argv); + } else if (commandName == "extract") { argv[0] = const_cast("keepassxc-cli extract"); - return Extract::execute(argc, argv); - } - - if (commandName == "list") { + exitCode = Extract::execute(argc, argv); + } else if (commandName == "list") { argv[0] = const_cast("keepassxc-cli list"); - return List::execute(argc, argv); - } - - if (commandName == "merge") { + exitCode = List::execute(argc, argv); + } else if (commandName == "merge") { argv[0] = const_cast("keepassxc-cli merge"); - return Merge::execute(argc, argv); - } - - if (commandName == "show") { + exitCode = Merge::execute(argc, argv); + } else if (commandName == "show") { argv[0] = const_cast("keepassxc-cli show"); - return Show::execute(argc, argv); + exitCode = Show::execute(argc, argv); + } else { + qCritical("Invalid command %s.", qPrintable(commandName)); + parser.showHelp(); + exitCode = EXIT_FAILURE; } - qCritical("Invalid command %s.", qPrintable(commandName)); - parser.showHelp(); - return EXIT_FAILURE; +#if defined(WITH_ASAN) && defined(WITH_LSAN) + // do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries + __lsan_do_leak_check(); + __lsan_disable(); +#endif + + return exitCode; } From e3602e3c75489fc39f752d05147ab189a3070555 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 16 Mar 2017 20:31:14 +0100 Subject: [PATCH 183/333] fix regex for placeholders, fix #402, add regression test --- src/core/Entry.cpp | 3 ++- src/gui/DatabaseWidget.cpp | 18 +++++++-------- src/gui/entry/EntryModel.cpp | 6 ++--- tests/gui/TestGui.cpp | 44 ++++++++++++++++++++++++++++++++++++ tests/gui/TestGui.h | 1 + 5 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 7edf7e788..d1672c5b1 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -649,7 +649,8 @@ const Database* Entry::database() const QString Entry::resolveMultiplePlaceholders(const QString& str) const { QString result = str; - QRegExp tmplRegEx("({.*})", Qt::CaseInsensitive, QRegExp::RegExp2); + QRegExp tmplRegEx("(\\{.*\\})", Qt::CaseInsensitive, QRegExp::RegExp2); + tmplRegEx.setMinimal(true); QStringList tmplList; int pos = 0; diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 8a40b6364..ca3257d0c 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -407,7 +407,7 @@ void DatabaseWidget::copyTitle() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->title())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->title())); } void DatabaseWidget::copyUsername() @@ -418,7 +418,7 @@ void DatabaseWidget::copyUsername() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->username())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->username())); } void DatabaseWidget::copyPassword() @@ -429,7 +429,7 @@ void DatabaseWidget::copyPassword() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->password())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->password())); } void DatabaseWidget::copyURL() @@ -440,7 +440,7 @@ void DatabaseWidget::copyURL() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->url())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->url())); } void DatabaseWidget::copyNotes() @@ -451,7 +451,7 @@ void DatabaseWidget::copyNotes() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->notes())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->notes())); } void DatabaseWidget::copyAttribute(QAction* action) @@ -1176,7 +1176,7 @@ bool DatabaseWidget::currentEntryHasUsername() Q_ASSERT(false); return false; } - return !currentEntry->resolvePlaceholder(currentEntry->username()).isEmpty(); + return !currentEntry->resolveMultiplePlaceholders(currentEntry->username()).isEmpty(); } bool DatabaseWidget::currentEntryHasPassword() @@ -1186,7 +1186,7 @@ bool DatabaseWidget::currentEntryHasPassword() Q_ASSERT(false); return false; } - return !currentEntry->resolvePlaceholder(currentEntry->password()).isEmpty(); + return !currentEntry->resolveMultiplePlaceholders(currentEntry->password()).isEmpty(); } bool DatabaseWidget::currentEntryHasUrl() @@ -1196,7 +1196,7 @@ bool DatabaseWidget::currentEntryHasUrl() Q_ASSERT(false); return false; } - return !currentEntry->resolvePlaceholder(currentEntry->url()).isEmpty(); + return !currentEntry->resolveMultiplePlaceholders(currentEntry->url()).isEmpty(); } bool DatabaseWidget::currentEntryHasNotes() @@ -1206,7 +1206,7 @@ bool DatabaseWidget::currentEntryHasNotes() Q_ASSERT(false); return false; } - return !currentEntry->resolvePlaceholder(currentEntry->notes()).isEmpty(); + return !currentEntry->resolveMultiplePlaceholders(currentEntry->notes()).isEmpty(); } GroupView* DatabaseWidget::groupView() { diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index b28eaed46..6bc10376f 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -139,19 +139,19 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const } break; case Title: - result = entry->resolvePlaceholder(entry->title()); + result = entry->resolveMultiplePlaceholders(entry->title()); if (attr->isReference(EntryAttributes::TitleKey)) { result.prepend(tr("Ref: ","Reference abbreviation")); } return result; case Username: - result = entry->resolvePlaceholder(entry->username()); + result = entry->resolveMultiplePlaceholders(entry->username()); if (attr->isReference(EntryAttributes::UserNameKey)) { result.prepend(tr("Ref: ","Reference abbreviation")); } return result; case Url: - result = entry->resolvePlaceholder(entry->url()); + result = entry->resolveMultiplePlaceholders(entry->url()); if (attr->isReference(EntryAttributes::URLKey)) { result.prepend(tr("Ref: ","Reference abbreviation")); } diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 7df5942e8..1cb3d6ada 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -588,6 +588,50 @@ void TestGui::testCloneEntry() QCOMPARE(entryClone->title(), entryOrg->title() + QString(" - Clone")); } +void TestGui::testEntryPlaceholders() +{ + QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + EntryView* entryView = m_dbWidget->findChild("entryView"); + + // Find the new entry action + QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + QVERIFY(entryNewAction->isEnabled()); + + // Find the button associated with the new entry action + QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction); + QVERIFY(entryNewWidget->isVisible()); + QVERIFY(entryNewWidget->isEnabled()); + + // Click the new entry button and check that we enter edit mode + QTest::mouseClick(entryNewWidget, Qt::LeftButton); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); + + // Add entry "test" and confirm added + EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + QTest::keyClicks(titleEdit, "test"); + QLineEdit* usernameEdit = editEntryWidget->findChild("usernameEdit"); + QTest::keyClicks(usernameEdit, "john"); + QLineEdit* urlEdit = editEntryWidget->findChild("urlEdit"); + QTest::keyClicks(urlEdit, "{TITLE}.{USERNAME}"); + QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); + + QCOMPARE(entryView->model()->rowCount(), 2); + + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); + QModelIndex item = entryView->model()->index(1, 1); + Entry* entry = entryView->entryFromIndex(item); + + QCOMPARE(entry->title(), QString("test")); + QCOMPARE(entry->url(), QString("{TITLE}.{USERNAME}")); + + // Test password copy + QClipboard *clipboard = QApplication::clipboard(); + m_dbWidget->copyURL(); + QTRY_COMPARE(clipboard->text(), QString("test.john")); +} + void TestGui::testDragAndDropEntry() { EntryView* entryView = m_dbWidget->findChild("entryView"); diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index 1ae297005..d05ab8f58 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -48,6 +48,7 @@ private slots: void testSearch(); void testDeleteEntry(); void testCloneEntry(); + void testEntryPlaceholders(); void testDragAndDropEntry(); void testDragAndDropGroup(); void testSaveAs(); From 0e5a1cc8e479db1d03cc8e13228a526e6c558655 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 16 Mar 2017 20:38:56 +0100 Subject: [PATCH 184/333] resolve placeholders for custom attributes --- src/gui/DatabaseWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index ca3257d0c..5caf662ce 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -462,7 +462,7 @@ void DatabaseWidget::copyAttribute(QAction* action) return; } - setClipboardTextAndMinimize(currentEntry->attributes()->value(action->text())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->attributes()->value(action->text()))); } void DatabaseWidget::setClipboardTextAndMinimize(const QString& text) From 7e515d9d5b91db0001b8c19fad0eaba0460b9900 Mon Sep 17 00:00:00 2001 From: Florin Andrei Date: Thu, 16 Mar 2017 11:58:16 -0700 Subject: [PATCH 185/333] Update README.md typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd439ded4..a277e5b5e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ KeePass Cross-platform Community Edition - Auto-Type on all three major platforms (Linux, Windows, OS X) - Stand-alone password generator - Password strength meter -- YukiKey HMAC-SHA1 authentication for unlocking databases +- YubiKey HMAC-SHA1 authentication for unlocking databases - Using website favicons as entry icons - Merging of databases - Automatic reload when the database changed on disk From 06bbd6e066857e0a18cf2e703fb94d6cb8e788ff Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Thu, 16 Mar 2017 21:46:53 +0100 Subject: [PATCH 186/333] Get rid of Q_{EMIT,SLOTS,SIGNALS} --- src/gui/DatabaseWidget.cpp | 2 +- src/gui/csvImport/CsvImportWidget.h | 4 ++-- src/gui/csvImport/CsvImportWizard.h | 4 ++-- src/gui/csvImport/CsvParserModel.h | 2 +- src/http/OptionDialog.ui | 2 +- src/keys/drivers/YubiKey.h | 2 +- tests/TestCsvParser.h | 2 +- tests/TestYkChallengeResponseKey.h | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 1b4c7cc66..b4f623155 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -634,7 +634,7 @@ void DatabaseWidget::setCurrentWidget(QWidget* widget) void DatabaseWidget::csvImportFinished(bool accepted) { if (!accepted) { - Q_EMIT closeRequest(); + emit closeRequest(); } else { setCurrentWidget(m_mainWidget); diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index 2079e9f96..aa463c08b 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -45,10 +45,10 @@ public: ~CsvImportWidget(); void load(const QString& filename, Database* const db); -Q_SIGNALS: +signals: void editFinished(bool accepted); -private Q_SLOTS: +private slots: void parse(); void comboChanged(int comboId); void skippedChanged(int rows); diff --git a/src/gui/csvImport/CsvImportWizard.h b/src/gui/csvImport/CsvImportWizard.h index 75d10bb9d..1c99259cd 100644 --- a/src/gui/csvImport/CsvImportWizard.h +++ b/src/gui/csvImport/CsvImportWizard.h @@ -38,10 +38,10 @@ public: ~CsvImportWizard(); void load(const QString& filename, Database *database); -Q_SIGNALS: +signals: void importFinished(bool accepted); -private Q_SLOTS: +private slots: void keyFinished(bool accepted); void parseFinished(bool accepted); diff --git a/src/gui/csvImport/CsvParserModel.h b/src/gui/csvImport/CsvParserModel.h index ff4f410d7..8cab4d4ff 100644 --- a/src/gui/csvImport/CsvParserModel.h +++ b/src/gui/csvImport/CsvParserModel.h @@ -43,7 +43,7 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; -public Q_SLOTS: +public slots: void setSkippedRows(int skipped); private: diff --git a/src/http/OptionDialog.ui b/src/http/OptionDialog.ui index abe994772..1959f8052 100644 --- a/src/http/OptionDialog.ui +++ b/src/http/OptionDialog.ui @@ -39,7 +39,7 @@ - 0 + 2 diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h index 47341f9a2..8a7552136 100644 --- a/src/keys/drivers/YubiKey.h +++ b/src/keys/drivers/YubiKey.h @@ -77,7 +77,7 @@ public: */ void detect(); -Q_SIGNALS: +signals: /** Emitted in response to detect() when a device is found * * @slot is the slot number detected diff --git a/tests/TestCsvParser.h b/tests/TestCsvParser.h index 928ac4201..0aa3d01ef 100644 --- a/tests/TestCsvParser.h +++ b/tests/TestCsvParser.h @@ -32,7 +32,7 @@ class TestCsvParser : public QObject public: -private Q_SLOTS: +private slots: void init(); void cleanup(); void initTestCase(); diff --git a/tests/TestYkChallengeResponseKey.h b/tests/TestYkChallengeResponseKey.h index 4699b9101..309e014d5 100644 --- a/tests/TestYkChallengeResponseKey.h +++ b/tests/TestYkChallengeResponseKey.h @@ -26,7 +26,7 @@ class TestYubiKeyChalResp: public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void cleanupTestCase(); From 506a2b99c53ee8013e47bffaef8f88877b180361 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Thu, 16 Mar 2017 22:55:26 +0100 Subject: [PATCH 187/333] Revert dialog index back to zero --- src/http/OptionDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/OptionDialog.ui b/src/http/OptionDialog.ui index 1959f8052..abe994772 100644 --- a/src/http/OptionDialog.ui +++ b/src/http/OptionDialog.ui @@ -39,7 +39,7 @@ - 2 + 0 From c4d6fa855c399a347b5be6159eb3599deea00507 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 17 Mar 2017 02:08:09 +0100 Subject: [PATCH 188/333] Force event processing after adding entries to prevent test failure --- tests/gui/TestGui.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 1cb3d6ada..90f873197 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -316,6 +316,8 @@ void TestGui::testAddEntry() QTest::keyClicks(titleEdit, "something 3"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); + QApplication::processEvents(); + // Confirm that 4 entries now exist QTRY_COMPARE(entryView->model()->rowCount(), 4); } From 15a288aa5bb5477ab01304a8660daff9862b4df5 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 6 Mar 2017 17:12:07 -0500 Subject: [PATCH 189/333] Adding warning messages when config access error. --- src/core/Config.cpp | 14 ++++++++++++++ src/core/Config.h | 2 ++ src/gui/SettingsWidget.cpp | 5 +++++ src/gui/SettingsWidgetGeneral.ui | 9 +++++++++ 4 files changed, 30 insertions(+) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 28b17536f..e3a1cb633 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -35,6 +35,16 @@ QVariant Config::get(const QString& key, const QVariant& defaultValue) return m_settings->value(key, defaultValue); } +bool Config::hasAccessError() +{ + return m_settings->status() & QSettings::AccessError; +} + +QString Config::getFileName() +{ + return m_settings->fileName(); +} + void Config::set(const QString& key, const QVariant& value) { m_settings->setValue(key, value); @@ -92,6 +102,10 @@ void Config::init(const QString& fileName) { m_settings.reset(new QSettings(fileName, QSettings::IniFormat)); + if (hasAccessError()) { + qWarning("Access error with config file %s", qPrintable(fileName)); + } + m_defaults.insert("RememberLastDatabases", true); m_defaults.insert("RememberLastKeyFiles", true); m_defaults.insert("OpenPreviousDatabasesOnStartup", true); diff --git a/src/core/Config.h b/src/core/Config.h index 09aa02fb1..1fb937cf9 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -31,7 +31,9 @@ public: ~Config(); QVariant get(const QString& key); QVariant get(const QString& key, const QVariant& defaultValue); + QString getFileName(); void set(const QString& key, const QVariant& value); + bool hasAccessError(); static Config* instance(); static void createConfigFromFile(const QString& file); diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 62af276e8..2002bc733 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -62,6 +62,11 @@ SettingsWidget::SettingsWidget(QWidget* parent) addPage(tr("General"), FilePath::instance()->icon("categories", "preferences-other"), m_generalWidget); addPage(tr("Security"), FilePath::instance()->icon("status", "security-high"), m_secWidget); + if (config()->hasAccessError()) { + m_generalUi->messageWidget->showMessage( + tr("Access error with config file ") + config()->getFileName(), MessageWidget::Error); + } + if (!autoType()->isAvailable()) { m_generalUi->generalSettingsTabWidget->removeTab(1); } diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 88d7cad45..fb9fb1709 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -23,6 +23,9 @@ 0 + + + @@ -368,6 +371,12 @@ QLineEdit
autotype/ShortcutWidget.h
+ + MessageWidget + QWidget +
gui/MessageWidget.h
+ 1 +
From da85252347c8fdc7332af2b38678df09048b305b Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 11 Mar 2017 22:04:38 -0500 Subject: [PATCH 190/333] Hide config errors by default. --- src/gui/SettingsWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 2002bc733..d68a11e98 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -62,6 +62,7 @@ SettingsWidget::SettingsWidget(QWidget* parent) addPage(tr("General"), FilePath::instance()->icon("categories", "preferences-other"), m_generalWidget); addPage(tr("Security"), FilePath::instance()->icon("status", "security-high"), m_secWidget); + m_generalUi->messageWidget->setVisible(false); if (config()->hasAccessError()) { m_generalUi->messageWidget->showMessage( tr("Access error with config file ") + config()->getFileName(), MessageWidget::Error); From 55a32c58a972760d5861163ccc3403c6dd69a3e2 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 13 Mar 2017 22:12:00 -0400 Subject: [PATCH 191/333] Moving access error to MainWindow. --- src/core/Config.cpp | 4 ---- src/gui/MainWindow.cpp | 6 ++++++ src/gui/SettingsWidget.cpp | 6 ------ src/gui/SettingsWidgetGeneral.ui | 9 --------- 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index e3a1cb633..e074df6cb 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -102,10 +102,6 @@ void Config::init(const QString& fileName) { m_settings.reset(new QSettings(fileName, QSettings::IniFormat)); - if (hasAccessError()) { - qWarning("Access error with config file %s", qPrintable(fileName)); - } - m_defaults.insert("RememberLastDatabases", true); m_defaults.insert("RememberLastKeyFiles", true); m_defaults.insert("OpenPreviousDatabasesOnStartup", true); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 442c04735..adb1ae297 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -314,6 +314,12 @@ MainWindow::MainWindow() connect(m_ui->tabWidget, SIGNAL(messageDismissTab()), this, SLOT(hideTabMessage())); updateTrayIcon(); + + if (config()->hasAccessError()) { + m_ui->globalMessageWidget->showMessage( + tr("Access error for config file ") + config()->getFileName(), MessageWidget::Error); + } + } MainWindow::~MainWindow() diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index d68a11e98..62af276e8 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -62,12 +62,6 @@ SettingsWidget::SettingsWidget(QWidget* parent) addPage(tr("General"), FilePath::instance()->icon("categories", "preferences-other"), m_generalWidget); addPage(tr("Security"), FilePath::instance()->icon("status", "security-high"), m_secWidget); - m_generalUi->messageWidget->setVisible(false); - if (config()->hasAccessError()) { - m_generalUi->messageWidget->showMessage( - tr("Access error with config file ") + config()->getFileName(), MessageWidget::Error); - } - if (!autoType()->isAvailable()) { m_generalUi->generalSettingsTabWidget->removeTab(1); } diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index fb9fb1709..88d7cad45 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -23,9 +23,6 @@ 0 - - - @@ -371,12 +368,6 @@ QLineEdit
autotype/ShortcutWidget.h
- - MessageWidget - QWidget -
gui/MessageWidget.h
- 1 -
From e6b452802808028033531c0ab55f29e6556925b1 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 18 Mar 2017 13:31:15 -0400 Subject: [PATCH 192/333] Adjust indentation. --- src/gui/SettingsWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 62af276e8..ff4c124e6 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -68,7 +68,7 @@ SettingsWidget::SettingsWidget(QWidget* parent) #ifdef Q_OS_MAC // systray not useful on OS X - m_generalUi->systraySettings->setVisible(false); + m_generalUi->systraySettings->setVisible(false); #endif connect(this, SIGNAL(accepted()), SLOT(saveSettings())); From a3840963e1b650baf89489ed0a92f34385a473b1 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 18 Mar 2017 14:00:31 -0400 Subject: [PATCH 193/333] Checking config access errors in settings. --- src/gui/SettingsWidget.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index ff4c124e6..caf9919c9 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -99,6 +99,12 @@ void SettingsWidget::addSettingsPage(ISettingsPage* page) void SettingsWidget::loadSettings() { + + if (config()->hasAccessError()) { + showMessage( + tr("Access error for config file ") + config()->getFileName(), MessageWidget::Error); + } + m_generalUi->rememberLastDatabasesCheckBox->setChecked(config()->get("RememberLastDatabases").toBool()); m_generalUi->rememberLastKeyFilesCheckBox->setChecked(config()->get("RememberLastKeyFiles").toBool()); m_generalUi->openPreviousDatabasesOnStartupCheckBox->setChecked( @@ -154,6 +160,15 @@ void SettingsWidget::loadSettings() void SettingsWidget::saveSettings() { + + if (config()->hasAccessError()) { + showMessage( + tr("Access error for config file ") + config()->getFileName(), MessageWidget::Error); + // We prevent closing the settings page if we could not write to + // the config file. + return; + } + config()->set("RememberLastDatabases", m_generalUi->rememberLastDatabasesCheckBox->isChecked()); config()->set("RememberLastKeyFiles", m_generalUi->rememberLastKeyFilesCheckBox->isChecked()); config()->set("OpenPreviousDatabasesOnStartup", From d8ad360b383fb9841b19997457c29efac5adae26 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 19 Mar 2017 16:05:52 -0400 Subject: [PATCH 194/333] Using format strings. --- src/gui/MainWindow.cpp | 2 +- src/gui/SettingsWidget.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index adb1ae297..d8dd7dcd9 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -317,7 +317,7 @@ MainWindow::MainWindow() if (config()->hasAccessError()) { m_ui->globalMessageWidget->showMessage( - tr("Access error for config file ") + config()->getFileName(), MessageWidget::Error); + tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error); } } diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index caf9919c9..19dae371d 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -102,7 +102,7 @@ void SettingsWidget::loadSettings() if (config()->hasAccessError()) { showMessage( - tr("Access error for config file ") + config()->getFileName(), MessageWidget::Error); + tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error); } m_generalUi->rememberLastDatabasesCheckBox->setChecked(config()->get("RememberLastDatabases").toBool()); @@ -163,7 +163,7 @@ void SettingsWidget::saveSettings() if (config()->hasAccessError()) { showMessage( - tr("Access error for config file ") + config()->getFileName(), MessageWidget::Error); + tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error); // We prevent closing the settings page if we could not write to // the config file. return; From a87fab8d187fc40e566be2d2c963f4f4403f2b91 Mon Sep 17 00:00:00 2001 From: Weslly Date: Mon, 20 Mar 2017 21:11:17 -0300 Subject: [PATCH 195/333] Set window modified indicator when database has unsaved changes --- src/gui/MainWindow.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index f93678474..8ed6bcbfa 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -519,6 +519,11 @@ void MainWindow::updateWindowTitle() windowTitle = BaseWindowTitle; } else { windowTitle = QString("%1 - %2").arg(customWindowTitlePart, BaseWindowTitle); + if (customWindowTitlePart.right(1) == "*") { + setWindowModified(true); + } else { + setWindowModified(false); + } } setWindowTitle(windowTitle); From 18b5b76a803f2289aebaba9356d329d2648207b9 Mon Sep 17 00:00:00 2001 From: Weslly Date: Mon, 20 Mar 2017 23:29:36 -0300 Subject: [PATCH 196/333] Get modified status directly from database object instead of using window title --- src/gui/DatabaseTabWidget.cpp | 9 +++++++++ src/gui/DatabaseTabWidget.h | 1 + src/gui/MainWindow.cpp | 11 ++++++----- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 6626b3428..984cde607 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -526,6 +526,15 @@ bool DatabaseTabWidget::readOnly(int index) return indexDatabaseManagerStruct(index).readOnly; } +bool DatabaseTabWidget::isModified(int index) +{ + if (index == -1) { + index = currentIndex(); + } + + return indexDatabaseManagerStruct(index).modified; +} + void DatabaseTabWidget::updateTabName(Database* db) { int index = databaseIndex(db); diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 6fa783dd0..f99a4ff16 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -77,6 +77,7 @@ public slots: void changeMasterKey(); void changeDatabaseSettings(); bool readOnly(int index = -1); + bool isModified(int index = -1); void performGlobalAutoType(); void lockDatabases(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 8ed6bcbfa..92c167e4f 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -519,11 +519,12 @@ void MainWindow::updateWindowTitle() windowTitle = BaseWindowTitle; } else { windowTitle = QString("%1 - %2").arg(customWindowTitlePart, BaseWindowTitle); - if (customWindowTitlePart.right(1) == "*") { - setWindowModified(true); - } else { - setWindowModified(false); - } + } + + if (m_ui->tabWidget->isModified(tabWidgetIndex)) { + setWindowModified(true); + } else { + setWindowModified(false); } setWindowTitle(windowTitle); From b6cfc82b4e384ea0495ebb80b3696cc1d6059267 Mon Sep 17 00:00:00 2001 From: Weslly Date: Tue, 21 Mar 2017 06:04:26 -0300 Subject: [PATCH 197/333] Review fixes --- src/gui/MainWindow.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index bdfa13b46..0f883e4aa 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -530,11 +530,7 @@ void MainWindow::updateWindowTitle() windowTitle = QString("%1 - %2").arg(customWindowTitlePart, BaseWindowTitle); } - if (m_ui->tabWidget->isModified(tabWidgetIndex)) { - setWindowModified(true); - } else { - setWindowModified(false); - } + setWindowModified(m_ui->tabWidget->isModified(tabWidgetIndex)); setWindowTitle(windowTitle); } From d9ccde94c9105590dcc42dd1493defbb7cc5ac3a Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 4 Mar 2017 15:10:50 +0100 Subject: [PATCH 198/333] New Diceware passphrase generator, close #21 --- share/CMakeLists.txt | 2 + share/wordlists/eff_distant.wordlist | 1296 +++ share/wordlists/eff_large.wordlist | 7776 +++++++++++++++++ share/wordlists/eff_short.wordlist | 1296 +++ src/CMakeLists.txt | 2 +- src/core/PassphraseGenerator.cpp | 110 + .../PassphraseGenerator.h} | 40 +- src/gui/PasswordComboBox.cpp | 97 - src/gui/PasswordGeneratorWidget.cpp | 191 +- src/gui/PasswordGeneratorWidget.h | 18 +- src/gui/PasswordGeneratorWidget.ui | 636 +- src/gui/PasswordGeneratorWidget.ui.autosave | 615 ++ src/gui/entry/EditEntryWidgetMain.ui | 7 + 13 files changed, 11662 insertions(+), 424 deletions(-) create mode 100644 share/wordlists/eff_distant.wordlist create mode 100644 share/wordlists/eff_large.wordlist create mode 100644 share/wordlists/eff_short.wordlist create mode 100644 src/core/PassphraseGenerator.cpp rename src/{gui/PasswordComboBox.h => core/PassphraseGenerator.h} (51%) delete mode 100644 src/gui/PasswordComboBox.cpp create mode 100644 src/gui/PasswordGeneratorWidget.ui.autosave diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index e57cdb3c7..4ae4c5f09 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -15,6 +15,8 @@ add_subdirectory(translations) +install(FILES wordlists/*.wordlist DESTINATION ${DATA_INSTALL_DIR}/wordlists) + file(GLOB DATABASE_ICONS icons/database/*.png) install(FILES ${DATABASE_ICONS} DESTINATION ${DATA_INSTALL_DIR}/icons/database) diff --git a/share/wordlists/eff_distant.wordlist b/share/wordlists/eff_distant.wordlist new file mode 100644 index 000000000..9ac732fe3 --- /dev/null +++ b/share/wordlists/eff_distant.wordlist @@ -0,0 +1,1296 @@ +aardvark +abandoned +abbreviate +abdomen +abhorrence +abiding +abnormal +abrasion +absorbing +abundant +abyss +academy +accountant +acetone +achiness +acid +acoustics +acquire +acrobat +actress +acuteness +aerosol +aesthetic +affidavit +afloat +afraid +aftershave +again +agency +aggressor +aghast +agitate +agnostic +agonizing +agreeing +aidless +aimlessly +ajar +alarmclock +albatross +alchemy +alfalfa +algae +aliens +alkaline +almanac +alongside +alphabet +already +also +altitude +aluminum +always +amazingly +ambulance +amendment +amiable +ammunition +amnesty +amoeba +amplifier +amuser +anagram +anchor +android +anesthesia +angelfish +animal +anklet +announcer +anonymous +answer +antelope +anxiety +anyplace +aorta +apartment +apnea +apostrophe +apple +apricot +aquamarine +arachnid +arbitrate +ardently +arena +argument +aristocrat +armchair +aromatic +arrowhead +arsonist +artichoke +asbestos +ascend +aseptic +ashamed +asinine +asleep +asocial +asparagus +astronaut +asymmetric +atlas +atmosphere +atom +atrocious +attic +atypical +auctioneer +auditorium +augmented +auspicious +automobile +auxiliary +avalanche +avenue +aviator +avocado +awareness +awhile +awkward +awning +awoke +axially +azalea +babbling +backpack +badass +bagpipe +bakery +balancing +bamboo +banana +barracuda +basket +bathrobe +bazooka +blade +blender +blimp +blouse +blurred +boatyard +bobcat +body +bogusness +bohemian +boiler +bonnet +boots +borough +bossiness +bottle +bouquet +boxlike +breath +briefcase +broom +brushes +bubblegum +buckle +buddhist +buffalo +bullfrog +bunny +busboy +buzzard +cabin +cactus +cadillac +cafeteria +cage +cahoots +cajoling +cakewalk +calculator +camera +canister +capsule +carrot +cashew +cathedral +caucasian +caviar +ceasefire +cedar +celery +cement +census +ceramics +cesspool +chalkboard +cheesecake +chimney +chlorine +chopsticks +chrome +chute +cilantro +cinnamon +circle +cityscape +civilian +clay +clergyman +clipboard +clock +clubhouse +coathanger +cobweb +coconut +codeword +coexistent +coffeecake +cognitive +cohabitate +collarbone +computer +confetti +copier +cornea +cosmetics +cotton +couch +coverless +coyote +coziness +crawfish +crewmember +crib +croissant +crumble +crystal +cubical +cucumber +cuddly +cufflink +cuisine +culprit +cup +curry +cushion +cuticle +cybernetic +cyclist +cylinder +cymbal +cynicism +cypress +cytoplasm +dachshund +daffodil +dagger +dairy +dalmatian +dandelion +dartboard +dastardly +datebook +daughter +dawn +daytime +dazzler +dealer +debris +decal +dedicate +deepness +defrost +degree +dehydrator +deliverer +democrat +dentist +deodorant +depot +deranged +desktop +detergent +device +dexterity +diamond +dibs +dictionary +diffuser +digit +dilated +dimple +dinnerware +dioxide +diploma +directory +dishcloth +ditto +dividers +dizziness +doctor +dodge +doll +dominoes +donut +doorstep +dorsal +double +downstairs +dozed +drainpipe +dresser +driftwood +droppings +drum +dryer +dubiously +duckling +duffel +dugout +dumpster +duplex +durable +dustpan +dutiful +duvet +dwarfism +dwelling +dwindling +dynamite +dyslexia +eagerness +earlobe +easel +eavesdrop +ebook +eccentric +echoless +eclipse +ecosystem +ecstasy +edged +editor +educator +eelworm +eerie +effects +eggnog +egomaniac +ejection +elastic +elbow +elderly +elephant +elfishly +eliminator +elk +elliptical +elongated +elsewhere +elusive +elves +emancipate +embroidery +emcee +emerald +emission +emoticon +emperor +emulate +enactment +enchilada +endorphin +energy +enforcer +engine +enhance +enigmatic +enjoyably +enlarged +enormous +enquirer +enrollment +ensemble +entryway +enunciate +envoy +enzyme +epidemic +equipment +erasable +ergonomic +erratic +eruption +escalator +eskimo +esophagus +espresso +essay +estrogen +etching +eternal +ethics +etiquette +eucalyptus +eulogy +euphemism +euthanize +evacuation +evergreen +evidence +evolution +exam +excerpt +exerciser +exfoliate +exhale +exist +exorcist +explode +exquisite +exterior +exuberant +fabric +factory +faded +failsafe +falcon +family +fanfare +fasten +faucet +favorite +feasibly +february +federal +feedback +feigned +feline +femur +fence +ferret +festival +fettuccine +feudalist +feverish +fiberglass +fictitious +fiddle +figurine +fillet +finalist +fiscally +fixture +flashlight +fleshiness +flight +florist +flypaper +foamless +focus +foggy +folksong +fondue +footpath +fossil +fountain +fox +fragment +freeway +fridge +frosting +fruit +fryingpan +gadget +gainfully +gallstone +gamekeeper +gangway +garlic +gaslight +gathering +gauntlet +gearbox +gecko +gem +generator +geographer +gerbil +gesture +getaway +geyser +ghoulishly +gibberish +giddiness +giftshop +gigabyte +gimmick +giraffe +giveaway +gizmo +glasses +gleeful +glisten +glove +glucose +glycerin +gnarly +gnomish +goatskin +goggles +goldfish +gong +gooey +gorgeous +gosling +gothic +gourmet +governor +grape +greyhound +grill +groundhog +grumbling +guacamole +guerrilla +guitar +gullible +gumdrop +gurgling +gusto +gutless +gymnast +gynecology +gyration +habitat +hacking +haggard +haiku +halogen +hamburger +handgun +happiness +hardhat +hastily +hatchling +haughty +hazelnut +headband +hedgehog +hefty +heinously +helmet +hemoglobin +henceforth +herbs +hesitation +hexagon +hubcap +huddling +huff +hugeness +hullabaloo +human +hunter +hurricane +hushing +hyacinth +hybrid +hydrant +hygienist +hypnotist +ibuprofen +icepack +icing +iconic +identical +idiocy +idly +igloo +ignition +iguana +illuminate +imaging +imbecile +imitator +immigrant +imprint +iodine +ionosphere +ipad +iphone +iridescent +irksome +iron +irrigation +island +isotope +issueless +italicize +itemizer +itinerary +itunes +ivory +jabbering +jackrabbit +jaguar +jailhouse +jalapeno +jamboree +janitor +jarring +jasmine +jaundice +jawbreaker +jaywalker +jazz +jealous +jeep +jelly +jeopardize +jersey +jetski +jezebel +jiffy +jigsaw +jingling +jobholder +jockstrap +jogging +john +joinable +jokingly +journal +jovial +joystick +jubilant +judiciary +juggle +juice +jujitsu +jukebox +jumpiness +junkyard +juror +justifying +juvenile +kabob +kamikaze +kangaroo +karate +kayak +keepsake +kennel +kerosene +ketchup +khaki +kickstand +kilogram +kimono +kingdom +kiosk +kissing +kite +kleenex +knapsack +kneecap +knickers +koala +krypton +laboratory +ladder +lakefront +lantern +laptop +laryngitis +lasagna +latch +laundry +lavender +laxative +lazybones +lecturer +leftover +leggings +leisure +lemon +length +leopard +leprechaun +lettuce +leukemia +levers +lewdness +liability +library +licorice +lifeboat +lightbulb +likewise +lilac +limousine +lint +lioness +lipstick +liquid +listless +litter +liverwurst +lizard +llama +luau +lubricant +lucidity +ludicrous +luggage +lukewarm +lullaby +lumberjack +lunchbox +luridness +luscious +luxurious +lyrics +macaroni +maestro +magazine +mahogany +maimed +majority +makeover +malformed +mammal +mango +mapmaker +marbles +massager +matchstick +maverick +maximum +mayonnaise +moaning +mobilize +moccasin +modify +moisture +molecule +momentum +monastery +moonshine +mortuary +mosquito +motorcycle +mousetrap +movie +mower +mozzarella +muckiness +mudflow +mugshot +mule +mummy +mundane +muppet +mural +mustard +mutation +myriad +myspace +myth +nail +namesake +nanosecond +napkin +narrator +nastiness +natives +nautically +navigate +nearest +nebula +nectar +nefarious +negotiator +neither +nemesis +neoliberal +nephew +nervously +nest +netting +neuron +nevermore +nextdoor +nicotine +niece +nimbleness +nintendo +nirvana +nuclear +nugget +nuisance +nullify +numbing +nuptials +nursery +nutcracker +nylon +oasis +oat +obediently +obituary +object +obliterate +obnoxious +observer +obtain +obvious +occupation +oceanic +octopus +ocular +office +oftentimes +oiliness +ointment +older +olympics +omissible +omnivorous +oncoming +onion +onlooker +onstage +onward +onyx +oomph +opaquely +opera +opium +opossum +opponent +optical +opulently +oscillator +osmosis +ostrich +otherwise +ought +outhouse +ovation +oven +owlish +oxford +oxidize +oxygen +oyster +ozone +pacemaker +padlock +pageant +pajamas +palm +pamphlet +pantyhose +paprika +parakeet +passport +patio +pauper +pavement +payphone +pebble +peculiarly +pedometer +pegboard +pelican +penguin +peony +pepperoni +peroxide +pesticide +petroleum +pewter +pharmacy +pheasant +phonebook +phrasing +physician +plank +pledge +plotted +plug +plywood +pneumonia +podiatrist +poetic +pogo +poison +poking +policeman +poncho +popcorn +porcupine +postcard +poultry +powerboat +prairie +pretzel +princess +propeller +prune +pry +pseudo +psychopath +publisher +pucker +pueblo +pulley +pumpkin +punchbowl +puppy +purse +pushup +putt +puzzle +pyramid +python +quarters +quesadilla +quilt +quote +racoon +radish +ragweed +railroad +rampantly +rancidity +rarity +raspberry +ravishing +rearrange +rebuilt +receipt +reentry +refinery +register +rehydrate +reimburse +rejoicing +rekindle +relic +remote +renovator +reopen +reporter +request +rerun +reservoir +retriever +reunion +revolver +rewrite +rhapsody +rhetoric +rhino +rhubarb +rhyme +ribbon +riches +ridden +rigidness +rimmed +riptide +riskily +ritzy +riverboat +roamer +robe +rocket +romancer +ropelike +rotisserie +roundtable +royal +rubber +rudderless +rugby +ruined +rulebook +rummage +running +rupture +rustproof +sabotage +sacrifice +saddlebag +saffron +sainthood +saltshaker +samurai +sandworm +sapphire +sardine +sassy +satchel +sauna +savage +saxophone +scarf +scenario +schoolbook +scientist +scooter +scrapbook +sculpture +scythe +secretary +sedative +segregator +seismology +selected +semicolon +senator +septum +sequence +serpent +sesame +settler +severely +shack +shelf +shirt +shovel +shrimp +shuttle +shyness +siamese +sibling +siesta +silicon +simmering +singles +sisterhood +sitcom +sixfold +sizable +skateboard +skeleton +skies +skulk +skylight +slapping +sled +slingshot +sloth +slumbering +smartphone +smelliness +smitten +smokestack +smudge +snapshot +sneezing +sniff +snowsuit +snugness +speakers +sphinx +spider +splashing +sponge +sprout +spur +spyglass +squirrel +statue +steamboat +stingray +stopwatch +strawberry +student +stylus +suave +subway +suction +suds +suffocate +sugar +suitcase +sulphur +superstore +surfer +sushi +swan +sweatshirt +swimwear +sword +sycamore +syllable +symphony +synagogue +syringes +systemize +tablespoon +taco +tadpole +taekwondo +tagalong +takeout +tallness +tamale +tanned +tapestry +tarantula +tastebud +tattoo +tavern +thaw +theater +thimble +thorn +throat +thumb +thwarting +tiara +tidbit +tiebreaker +tiger +timid +tinsel +tiptoeing +tirade +tissue +tractor +tree +tripod +trousers +trucks +tryout +tubeless +tuesday +tugboat +tulip +tumbleweed +tupperware +turtle +tusk +tutorial +tuxedo +tweezers +twins +tyrannical +ultrasound +umbrella +umpire +unarmored +unbuttoned +uncle +underwear +unevenness +unflavored +ungloved +unhinge +unicycle +unjustly +unknown +unlocking +unmarked +unnoticed +unopened +unpaved +unquenched +unroll +unscrewing +untied +unusual +unveiled +unwrinkled +unyielding +unzip +upbeat +upcountry +update +upfront +upgrade +upholstery +upkeep +upload +uppercut +upright +upstairs +uptown +upwind +uranium +urban +urchin +urethane +urgent +urologist +username +usher +utensil +utility +utmost +utopia +utterance +vacuum +vagrancy +valuables +vanquished +vaporizer +varied +vaseline +vegetable +vehicle +velcro +vendor +vertebrae +vestibule +veteran +vexingly +vicinity +videogame +viewfinder +vigilante +village +vinegar +violin +viperfish +virus +visor +vitamins +vivacious +vixen +vocalist +vogue +voicemail +volleyball +voucher +voyage +vulnerable +waffle +wagon +wakeup +walrus +wanderer +wasp +water +waving +wheat +whisper +wholesaler +wick +widow +wielder +wifeless +wikipedia +wildcat +windmill +wipeout +wired +wishbone +wizardry +wobbliness +wolverine +womb +woolworker +workbasket +wound +wrangle +wreckage +wristwatch +wrongdoing +xerox +xylophone +yacht +yahoo +yard +yearbook +yesterday +yiddish +yield +yo-yo +yodel +yogurt +yuppie +zealot +zebra +zeppelin +zestfully +zigzagged +zillion +zipping +zirconium +zodiac +zombie +zookeeper +zucchini diff --git a/share/wordlists/eff_large.wordlist b/share/wordlists/eff_large.wordlist new file mode 100644 index 000000000..caf71f526 --- /dev/null +++ b/share/wordlists/eff_large.wordlist @@ -0,0 +1,7776 @@ +abacus +abdomen +abdominal +abide +abiding +ability +ablaze +able +abnormal +abrasion +abrasive +abreast +abridge +abroad +abruptly +absence +absentee +absently +absinthe +absolute +absolve +abstain +abstract +absurd +accent +acclaim +acclimate +accompany +account +accuracy +accurate +accustom +acetone +achiness +aching +acid +acorn +acquaint +acquire +acre +acrobat +acronym +acting +action +activate +activator +active +activism +activist +activity +actress +acts +acutely +acuteness +aeration +aerobics +aerosol +aerospace +afar +affair +affected +affecting +affection +affidavit +affiliate +affirm +affix +afflicted +affluent +afford +affront +aflame +afloat +aflutter +afoot +afraid +afterglow +afterlife +aftermath +aftermost +afternoon +aged +ageless +agency +agenda +agent +aggregate +aghast +agile +agility +aging +agnostic +agonize +agonizing +agony +agreeable +agreeably +agreed +agreeing +agreement +aground +ahead +ahoy +aide +aids +aim +ajar +alabaster +alarm +albatross +album +alfalfa +algebra +algorithm +alias +alibi +alienable +alienate +aliens +alike +alive +alkaline +alkalize +almanac +almighty +almost +aloe +aloft +aloha +alone +alongside +aloof +alphabet +alright +although +altitude +alto +aluminum +alumni +always +amaretto +amaze +amazingly +amber +ambiance +ambiguity +ambiguous +ambition +ambitious +ambulance +ambush +amendable +amendment +amends +amenity +amiable +amicably +amid +amigo +amino +amiss +ammonia +ammonium +amnesty +amniotic +among +amount +amperage +ample +amplifier +amplify +amply +amuck +amulet +amusable +amused +amusement +amuser +amusing +anaconda +anaerobic +anagram +anatomist +anatomy +anchor +anchovy +ancient +android +anemia +anemic +aneurism +anew +angelfish +angelic +anger +angled +angler +angles +angling +angrily +angriness +anguished +angular +animal +animate +animating +animation +animator +anime +animosity +ankle +annex +annotate +announcer +annoying +annually +annuity +anointer +another +answering +antacid +antarctic +anteater +antelope +antennae +anthem +anthill +anthology +antibody +antics +antidote +antihero +antiquely +antiques +antiquity +antirust +antitoxic +antitrust +antiviral +antivirus +antler +antonym +antsy +anvil +anybody +anyhow +anymore +anyone +anyplace +anything +anytime +anyway +anywhere +aorta +apache +apostle +appealing +appear +appease +appeasing +appendage +appendix +appetite +appetizer +applaud +applause +apple +appliance +applicant +applied +apply +appointee +appraisal +appraiser +apprehend +approach +approval +approve +apricot +april +apron +aptitude +aptly +aqua +aqueduct +arbitrary +arbitrate +ardently +area +arena +arguable +arguably +argue +arise +armadillo +armband +armchair +armed +armful +armhole +arming +armless +armoire +armored +armory +armrest +army +aroma +arose +around +arousal +arrange +array +arrest +arrival +arrive +arrogance +arrogant +arson +art +ascend +ascension +ascent +ascertain +ashamed +ashen +ashes +ashy +aside +askew +asleep +asparagus +aspect +aspirate +aspire +aspirin +astonish +astound +astride +astrology +astronaut +astronomy +astute +atlantic +atlas +atom +atonable +atop +atrium +atrocious +atrophy +attach +attain +attempt +attendant +attendee +attention +attentive +attest +attic +attire +attitude +attractor +attribute +atypical +auction +audacious +audacity +audible +audibly +audience +audio +audition +augmented +august +authentic +author +autism +autistic +autograph +automaker +automated +automatic +autopilot +available +avalanche +avatar +avenge +avenging +avenue +average +aversion +avert +aviation +aviator +avid +avoid +await +awaken +award +aware +awhile +awkward +awning +awoke +awry +axis +babble +babbling +babied +baboon +backache +backboard +backboned +backdrop +backed +backer +backfield +backfire +backhand +backing +backlands +backlash +backless +backlight +backlit +backlog +backpack +backpedal +backrest +backroom +backshift +backside +backslid +backspace +backspin +backstab +backstage +backtalk +backtrack +backup +backward +backwash +backwater +backyard +bacon +bacteria +bacterium +badass +badge +badland +badly +badness +baffle +baffling +bagel +bagful +baggage +bagged +baggie +bagginess +bagging +baggy +bagpipe +baguette +baked +bakery +bakeshop +baking +balance +balancing +balcony +balmy +balsamic +bamboo +banana +banish +banister +banjo +bankable +bankbook +banked +banker +banking +banknote +bankroll +banner +bannister +banshee +banter +barbecue +barbed +barbell +barber +barcode +barge +bargraph +barista +baritone +barley +barmaid +barman +barn +barometer +barrack +barracuda +barrel +barrette +barricade +barrier +barstool +bartender +barterer +bash +basically +basics +basil +basin +basis +basket +batboy +batch +bath +baton +bats +battalion +battered +battering +battery +batting +battle +bauble +bazooka +blabber +bladder +blade +blah +blame +blaming +blanching +blandness +blank +blaspheme +blasphemy +blast +blatancy +blatantly +blazer +blazing +bleach +bleak +bleep +blemish +blend +bless +blighted +blimp +bling +blinked +blinker +blinking +blinks +blip +blissful +blitz +blizzard +bloated +bloating +blob +blog +bloomers +blooming +blooper +blot +blouse +blubber +bluff +bluish +blunderer +blunt +blurb +blurred +blurry +blurt +blush +blustery +boaster +boastful +boasting +boat +bobbed +bobbing +bobble +bobcat +bobsled +bobtail +bodacious +body +bogged +boggle +bogus +boil +bok +bolster +bolt +bonanza +bonded +bonding +bondless +boned +bonehead +boneless +bonelike +boney +bonfire +bonnet +bonsai +bonus +bony +boogeyman +boogieman +book +boondocks +booted +booth +bootie +booting +bootlace +bootleg +boots +boozy +borax +boring +borough +borrower +borrowing +boss +botanical +botanist +botany +botch +both +bottle +bottling +bottom +bounce +bouncing +bouncy +bounding +boundless +bountiful +bovine +boxcar +boxer +boxing +boxlike +boxy +breach +breath +breeches +breeching +breeder +breeding +breeze +breezy +brethren +brewery +brewing +briar +bribe +brick +bride +bridged +brigade +bright +brilliant +brim +bring +brink +brisket +briskly +briskness +bristle +brittle +broadband +broadcast +broaden +broadly +broadness +broadside +broadways +broiler +broiling +broken +broker +bronchial +bronco +bronze +bronzing +brook +broom +brought +browbeat +brownnose +browse +browsing +bruising +brunch +brunette +brunt +brush +brussels +brute +brutishly +bubble +bubbling +bubbly +buccaneer +bucked +bucket +buckle +buckshot +buckskin +bucktooth +buckwheat +buddhism +buddhist +budding +buddy +budget +buffalo +buffed +buffer +buffing +buffoon +buggy +bulb +bulge +bulginess +bulgur +bulk +bulldog +bulldozer +bullfight +bullfrog +bullhorn +bullion +bullish +bullpen +bullring +bullseye +bullwhip +bully +bunch +bundle +bungee +bunion +bunkbed +bunkhouse +bunkmate +bunny +bunt +busboy +bush +busily +busload +bust +busybody +buzz +cabana +cabbage +cabbie +cabdriver +cable +caboose +cache +cackle +cacti +cactus +caddie +caddy +cadet +cadillac +cadmium +cage +cahoots +cake +calamari +calamity +calcium +calculate +calculus +caliber +calibrate +calm +caloric +calorie +calzone +camcorder +cameo +camera +camisole +camper +campfire +camping +campsite +campus +canal +canary +cancel +candied +candle +candy +cane +canine +canister +cannabis +canned +canning +cannon +cannot +canola +canon +canopener +canopy +canteen +canyon +capable +capably +capacity +cape +capillary +capital +capitol +capped +capricorn +capsize +capsule +caption +captivate +captive +captivity +capture +caramel +carat +caravan +carbon +cardboard +carded +cardiac +cardigan +cardinal +cardstock +carefully +caregiver +careless +caress +caretaker +cargo +caring +carless +carload +carmaker +carnage +carnation +carnival +carnivore +carol +carpenter +carpentry +carpool +carport +carried +carrot +carrousel +carry +cartel +cartload +carton +cartoon +cartridge +cartwheel +carve +carving +carwash +cascade +case +cash +casing +casino +casket +cassette +casually +casualty +catacomb +catalog +catalyst +catalyze +catapult +cataract +catatonic +catcall +catchable +catcher +catching +catchy +caterer +catering +catfight +catfish +cathedral +cathouse +catlike +catnap +catnip +catsup +cattail +cattishly +cattle +catty +catwalk +caucasian +caucus +causal +causation +cause +causing +cauterize +caution +cautious +cavalier +cavalry +caviar +cavity +cedar +celery +celestial +celibacy +celibate +celtic +cement +census +ceramics +ceremony +certainly +certainty +certified +certify +cesarean +cesspool +chafe +chaffing +chain +chair +chalice +challenge +chamber +chamomile +champion +chance +change +channel +chant +chaos +chaperone +chaplain +chapped +chaps +chapter +character +charbroil +charcoal +charger +charging +chariot +charity +charm +charred +charter +charting +chase +chasing +chaste +chastise +chastity +chatroom +chatter +chatting +chatty +cheating +cheddar +cheek +cheer +cheese +cheesy +chef +chemicals +chemist +chemo +cherisher +cherub +chess +chest +chevron +chevy +chewable +chewer +chewing +chewy +chief +chihuahua +childcare +childhood +childish +childless +childlike +chili +chill +chimp +chip +chirping +chirpy +chitchat +chivalry +chive +chloride +chlorine +choice +chokehold +choking +chomp +chooser +choosing +choosy +chop +chosen +chowder +chowtime +chrome +chubby +chuck +chug +chummy +chump +chunk +churn +chute +cider +cilantro +cinch +cinema +cinnamon +circle +circling +circular +circulate +circus +citable +citadel +citation +citizen +citric +citrus +city +civic +civil +clad +claim +clambake +clammy +clamor +clamp +clamshell +clang +clanking +clapped +clapper +clapping +clarify +clarinet +clarity +clash +clasp +class +clatter +clause +clavicle +claw +clay +clean +clear +cleat +cleaver +cleft +clench +clergyman +clerical +clerk +clever +clicker +client +climate +climatic +cling +clinic +clinking +clip +clique +cloak +clobber +clock +clone +cloning +closable +closure +clothes +clothing +cloud +clover +clubbed +clubbing +clubhouse +clump +clumsily +clumsy +clunky +clustered +clutch +clutter +coach +coagulant +coastal +coaster +coasting +coastland +coastline +coat +coauthor +cobalt +cobbler +cobweb +cocoa +coconut +cod +coeditor +coerce +coexist +coffee +cofounder +cognition +cognitive +cogwheel +coherence +coherent +cohesive +coil +coke +cola +cold +coleslaw +coliseum +collage +collapse +collar +collected +collector +collide +collie +collision +colonial +colonist +colonize +colony +colossal +colt +coma +come +comfort +comfy +comic +coming +comma +commence +commend +comment +commerce +commode +commodity +commodore +common +commotion +commute +commuting +compacted +compacter +compactly +compactor +companion +company +compare +compel +compile +comply +component +composed +composer +composite +compost +composure +compound +compress +comprised +computer +computing +comrade +concave +conceal +conceded +concept +concerned +concert +conch +concierge +concise +conclude +concrete +concur +condense +condiment +condition +condone +conducive +conductor +conduit +cone +confess +confetti +confidant +confident +confider +confiding +configure +confined +confining +confirm +conflict +conform +confound +confront +confused +confusing +confusion +congenial +congested +congrats +congress +conical +conjoined +conjure +conjuror +connected +connector +consensus +consent +console +consoling +consonant +constable +constant +constrain +constrict +construct +consult +consumer +consuming +contact +container +contempt +contend +contented +contently +contents +contest +context +contort +contour +contrite +control +contusion +convene +convent +copartner +cope +copied +copier +copilot +coping +copious +copper +copy +coral +cork +cornball +cornbread +corncob +cornea +corned +corner +cornfield +cornflake +cornhusk +cornmeal +cornstalk +corny +coronary +coroner +corporal +corporate +corral +correct +corridor +corrode +corroding +corrosive +corsage +corset +cortex +cosigner +cosmetics +cosmic +cosmos +cosponsor +cost +cottage +cotton +couch +cough +could +countable +countdown +counting +countless +country +county +courier +covenant +cover +coveted +coveting +coyness +cozily +coziness +cozy +crabbing +crabgrass +crablike +crabmeat +cradle +cradling +crafter +craftily +craftsman +craftwork +crafty +cramp +cranberry +crane +cranial +cranium +crank +crate +crave +craving +crawfish +crawlers +crawling +crayfish +crayon +crazed +crazily +craziness +crazy +creamed +creamer +creamlike +crease +creasing +creatable +create +creation +creative +creature +credible +credibly +credit +creed +creme +creole +crepe +crept +crescent +crested +cresting +crestless +crevice +crewless +crewman +crewmate +crib +cricket +cried +crier +crimp +crimson +cringe +cringing +crinkle +crinkly +crisped +crisping +crisply +crispness +crispy +criteria +critter +croak +crock +crook +croon +crop +cross +crouch +crouton +crowbar +crowd +crown +crucial +crudely +crudeness +cruelly +cruelness +cruelty +crumb +crummiest +crummy +crumpet +crumpled +cruncher +crunching +crunchy +crusader +crushable +crushed +crusher +crushing +crust +crux +crying +cryptic +crystal +cubbyhole +cube +cubical +cubicle +cucumber +cuddle +cuddly +cufflink +culinary +culminate +culpable +culprit +cultivate +cultural +culture +cupbearer +cupcake +cupid +cupped +cupping +curable +curator +curdle +cure +curfew +curing +curled +curler +curliness +curling +curly +curry +curse +cursive +cursor +curtain +curtly +curtsy +curvature +curve +curvy +cushy +cusp +cussed +custard +custodian +custody +customary +customer +customize +customs +cut +cycle +cyclic +cycling +cyclist +cylinder +cymbal +cytoplasm +cytoplast +dab +dad +daffodil +dagger +daily +daintily +dainty +dairy +daisy +dallying +dance +dancing +dandelion +dander +dandruff +dandy +danger +dangle +dangling +daredevil +dares +daringly +darkened +darkening +darkish +darkness +darkroom +darling +darn +dart +darwinism +dash +dastardly +data +datebook +dating +daughter +daunting +dawdler +dawn +daybed +daybreak +daycare +daydream +daylight +daylong +dayroom +daytime +dazzler +dazzling +deacon +deafening +deafness +dealer +dealing +dealmaker +dealt +dean +debatable +debate +debating +debit +debrief +debtless +debtor +debug +debunk +decade +decaf +decal +decathlon +decay +deceased +deceit +deceiver +deceiving +december +decency +decent +deception +deceptive +decibel +decidable +decimal +decimeter +decipher +deck +declared +decline +decode +decompose +decorated +decorator +decoy +decrease +decree +dedicate +dedicator +deduce +deduct +deed +deem +deepen +deeply +deepness +deface +defacing +defame +default +defeat +defection +defective +defendant +defender +defense +defensive +deferral +deferred +defiance +defiant +defile +defiling +define +definite +deflate +deflation +deflator +deflected +deflector +defog +deforest +defraud +defrost +deftly +defuse +defy +degraded +degrading +degrease +degree +dehydrate +deity +dejected +delay +delegate +delegator +delete +deletion +delicacy +delicate +delicious +delighted +delirious +delirium +deliverer +delivery +delouse +delta +deluge +delusion +deluxe +demanding +demeaning +demeanor +demise +democracy +democrat +demote +demotion +demystify +denatured +deniable +denial +denim +denote +dense +density +dental +dentist +denture +deny +deodorant +deodorize +departed +departure +depict +deplete +depletion +deplored +deploy +deport +depose +depraved +depravity +deprecate +depress +deprive +depth +deputize +deputy +derail +deranged +derby +derived +desecrate +deserve +deserving +designate +designed +designer +designing +deskbound +desktop +deskwork +desolate +despair +despise +despite +destiny +destitute +destruct +detached +detail +detection +detective +detector +detention +detergent +detest +detonate +detonator +detoxify +detract +deuce +devalue +deviancy +deviant +deviate +deviation +deviator +device +devious +devotedly +devotee +devotion +devourer +devouring +devoutly +dexterity +dexterous +diabetes +diabetic +diabolic +diagnoses +diagnosis +diagram +dial +diameter +diaper +diaphragm +diary +dice +dicing +dictate +dictation +dictator +difficult +diffused +diffuser +diffusion +diffusive +dig +dilation +diligence +diligent +dill +dilute +dime +diminish +dimly +dimmed +dimmer +dimness +dimple +diner +dingbat +dinghy +dinginess +dingo +dingy +dining +dinner +diocese +dioxide +diploma +dipped +dipper +dipping +directed +direction +directive +directly +directory +direness +dirtiness +disabled +disagree +disallow +disarm +disarray +disaster +disband +disbelief +disburse +discard +discern +discharge +disclose +discolor +discount +discourse +discover +discuss +disdain +disengage +disfigure +disgrace +dish +disinfect +disjoin +disk +dislike +disliking +dislocate +dislodge +disloyal +dismantle +dismay +dismiss +dismount +disobey +disorder +disown +disparate +disparity +dispatch +dispense +dispersal +dispersed +disperser +displace +display +displease +disposal +dispose +disprove +dispute +disregard +disrupt +dissuade +distance +distant +distaste +distill +distinct +distort +distract +distress +district +distrust +ditch +ditto +ditzy +dividable +divided +dividend +dividers +dividing +divinely +diving +divinity +divisible +divisibly +division +divisive +divorcee +dizziness +dizzy +doable +docile +dock +doctrine +document +dodge +dodgy +doily +doing +dole +dollar +dollhouse +dollop +dolly +dolphin +domain +domelike +domestic +dominion +dominoes +donated +donation +donator +donor +donut +doodle +doorbell +doorframe +doorknob +doorman +doormat +doornail +doorpost +doorstep +doorstop +doorway +doozy +dork +dormitory +dorsal +dosage +dose +dotted +doubling +douche +dove +down +dowry +doze +drab +dragging +dragonfly +dragonish +dragster +drainable +drainage +drained +drainer +drainpipe +dramatic +dramatize +drank +drapery +drastic +draw +dreaded +dreadful +dreadlock +dreamboat +dreamily +dreamland +dreamless +dreamlike +dreamt +dreamy +drearily +dreary +drench +dress +drew +dribble +dried +drier +drift +driller +drilling +drinkable +drinking +dripping +drippy +drivable +driven +driver +driveway +driving +drizzle +drizzly +drone +drool +droop +drop-down +dropbox +dropkick +droplet +dropout +dropper +drove +drown +drowsily +drudge +drum +dry +dubbed +dubiously +duchess +duckbill +ducking +duckling +ducktail +ducky +duct +dude +duffel +dugout +duh +duke +duller +dullness +duly +dumping +dumpling +dumpster +duo +dupe +duplex +duplicate +duplicity +durable +durably +duration +duress +during +dusk +dust +dutiful +duty +duvet +dwarf +dweeb +dwelled +dweller +dwelling +dwindle +dwindling +dynamic +dynamite +dynasty +dyslexia +dyslexic +each +eagle +earache +eardrum +earflap +earful +earlobe +early +earmark +earmuff +earphone +earpiece +earplugs +earring +earshot +earthen +earthlike +earthling +earthly +earthworm +earthy +earwig +easeful +easel +easiest +easily +easiness +easing +eastbound +eastcoast +easter +eastward +eatable +eaten +eatery +eating +eats +ebay +ebony +ebook +ecard +eccentric +echo +eclair +eclipse +ecologist +ecology +economic +economist +economy +ecosphere +ecosystem +edge +edginess +edging +edgy +edition +editor +educated +education +educator +eel +effective +effects +efficient +effort +eggbeater +egging +eggnog +eggplant +eggshell +egomaniac +egotism +egotistic +either +eject +elaborate +elastic +elated +elbow +eldercare +elderly +eldest +electable +election +elective +elephant +elevate +elevating +elevation +elevator +eleven +elf +eligible +eligibly +eliminate +elite +elitism +elixir +elk +ellipse +elliptic +elm +elongated +elope +eloquence +eloquent +elsewhere +elude +elusive +elves +email +embargo +embark +embassy +embattled +embellish +ember +embezzle +emblaze +emblem +embody +embolism +emboss +embroider +emcee +emerald +emergency +emission +emit +emote +emoticon +emotion +empathic +empathy +emperor +emphases +emphasis +emphasize +emphatic +empirical +employed +employee +employer +emporium +empower +emptier +emptiness +empty +emu +enable +enactment +enamel +enchanted +enchilada +encircle +enclose +enclosure +encode +encore +encounter +encourage +encroach +encrust +encrypt +endanger +endeared +endearing +ended +ending +endless +endnote +endocrine +endorphin +endorse +endowment +endpoint +endurable +endurance +enduring +energetic +energize +energy +enforced +enforcer +engaged +engaging +engine +engorge +engraved +engraver +engraving +engross +engulf +enhance +enigmatic +enjoyable +enjoyably +enjoyer +enjoying +enjoyment +enlarged +enlarging +enlighten +enlisted +enquirer +enrage +enrich +enroll +enslave +ensnare +ensure +entail +entangled +entering +entertain +enticing +entire +entitle +entity +entomb +entourage +entrap +entree +entrench +entrust +entryway +entwine +enunciate +envelope +enviable +enviably +envious +envision +envoy +envy +enzyme +epic +epidemic +epidermal +epidermis +epidural +epilepsy +epileptic +epilogue +epiphany +episode +equal +equate +equation +equator +equinox +equipment +equity +equivocal +eradicate +erasable +erased +eraser +erasure +ergonomic +errand +errant +erratic +error +erupt +escalate +escalator +escapable +escapade +escapist +escargot +eskimo +esophagus +espionage +espresso +esquire +essay +essence +essential +establish +estate +esteemed +estimate +estimator +estranged +estrogen +etching +eternal +eternity +ethanol +ether +ethically +ethics +euphemism +evacuate +evacuee +evade +evaluate +evaluator +evaporate +evasion +evasive +even +everglade +evergreen +everybody +everyday +everyone +evict +evidence +evident +evil +evoke +evolution +evolve +exact +exalted +example +excavate +excavator +exceeding +exception +excess +exchange +excitable +exciting +exclaim +exclude +excluding +exclusion +exclusive +excretion +excretory +excursion +excusable +excusably +excuse +exemplary +exemplify +exemption +exerciser +exert +exes +exfoliate +exhale +exhaust +exhume +exile +existing +exit +exodus +exonerate +exorcism +exorcist +expand +expanse +expansion +expansive +expectant +expedited +expediter +expel +expend +expenses +expensive +expert +expire +expiring +explain +expletive +explicit +explode +exploit +explore +exploring +exponent +exporter +exposable +expose +exposure +express +expulsion +exquisite +extended +extending +extent +extenuate +exterior +external +extinct +extortion +extradite +extras +extrovert +extrude +extruding +exuberant +fable +fabric +fabulous +facebook +facecloth +facedown +faceless +facelift +faceplate +faceted +facial +facility +facing +facsimile +faction +factoid +factor +factsheet +factual +faculty +fade +fading +failing +falcon +fall +false +falsify +fame +familiar +family +famine +famished +fanatic +fancied +fanciness +fancy +fanfare +fang +fanning +fantasize +fantastic +fantasy +fascism +fastball +faster +fasting +fastness +faucet +favorable +favorably +favored +favoring +favorite +fax +feast +federal +fedora +feeble +feed +feel +feisty +feline +felt-tip +feminine +feminism +feminist +feminize +femur +fence +fencing +fender +ferment +fernlike +ferocious +ferocity +ferret +ferris +ferry +fervor +fester +festival +festive +festivity +fetal +fetch +fever +fiber +fiction +fiddle +fiddling +fidelity +fidgeting +fidgety +fifteen +fifth +fiftieth +fifty +figment +figure +figurine +filing +filled +filler +filling +film +filter +filth +filtrate +finale +finalist +finalize +finally +finance +financial +finch +fineness +finer +finicky +finished +finisher +finishing +finite +finless +finlike +fiscally +fit +five +flaccid +flagman +flagpole +flagship +flagstick +flagstone +flail +flakily +flaky +flame +flammable +flanked +flanking +flannels +flap +flaring +flashback +flashbulb +flashcard +flashily +flashing +flashy +flask +flatbed +flatfoot +flatly +flatness +flatten +flattered +flatterer +flattery +flattop +flatware +flatworm +flavored +flavorful +flavoring +flaxseed +fled +fleshed +fleshy +flick +flier +flight +flinch +fling +flint +flip +flirt +float +flock +flogging +flop +floral +florist +floss +flounder +flyable +flyaway +flyer +flying +flyover +flypaper +foam +foe +fog +foil +folic +folk +follicle +follow +fondling +fondly +fondness +fondue +font +food +fool +footage +football +footbath +footboard +footer +footgear +foothill +foothold +footing +footless +footman +footnote +footpad +footpath +footprint +footrest +footsie +footsore +footwear +footwork +fossil +foster +founder +founding +fountain +fox +foyer +fraction +fracture +fragile +fragility +fragment +fragrance +fragrant +frail +frame +framing +frantic +fraternal +frayed +fraying +frays +freckled +freckles +freebase +freebee +freebie +freedom +freefall +freehand +freeing +freeload +freely +freemason +freeness +freestyle +freeware +freeway +freewill +freezable +freezing +freight +french +frenzied +frenzy +frequency +frequent +fresh +fretful +fretted +friction +friday +fridge +fried +friend +frighten +frightful +frigidity +frigidly +frill +fringe +frisbee +frisk +fritter +frivolous +frolic +from +front +frostbite +frosted +frostily +frosting +frostlike +frosty +froth +frown +frozen +fructose +frugality +frugally +fruit +frustrate +frying +gab +gaffe +gag +gainfully +gaining +gains +gala +gallantly +galleria +gallery +galley +gallon +gallows +gallstone +galore +galvanize +gambling +game +gaming +gamma +gander +gangly +gangrene +gangway +gap +garage +garbage +garden +gargle +garland +garlic +garment +garnet +garnish +garter +gas +gatherer +gathering +gating +gauging +gauntlet +gauze +gave +gawk +gazing +gear +gecko +geek +geiger +gem +gender +generic +generous +genetics +genre +gentile +gentleman +gently +gents +geography +geologic +geologist +geology +geometric +geometry +geranium +gerbil +geriatric +germicide +germinate +germless +germproof +gestate +gestation +gesture +getaway +getting +getup +giant +gibberish +giblet +giddily +giddiness +giddy +gift +gigabyte +gigahertz +gigantic +giggle +giggling +giggly +gigolo +gilled +gills +gimmick +girdle +giveaway +given +giver +giving +gizmo +gizzard +glacial +glacier +glade +gladiator +gladly +glamorous +glamour +glance +glancing +glandular +glare +glaring +glass +glaucoma +glazing +gleaming +gleeful +glider +gliding +glimmer +glimpse +glisten +glitch +glitter +glitzy +gloater +gloating +gloomily +gloomy +glorified +glorifier +glorify +glorious +glory +gloss +glove +glowing +glowworm +glucose +glue +gluten +glutinous +glutton +gnarly +gnat +goal +goatskin +goes +goggles +going +goldfish +goldmine +goldsmith +golf +goliath +gonad +gondola +gone +gong +good +gooey +goofball +goofiness +goofy +google +goon +gopher +gore +gorged +gorgeous +gory +gosling +gossip +gothic +gotten +gout +gown +grab +graceful +graceless +gracious +gradation +graded +grader +gradient +grading +gradually +graduate +graffiti +grafted +grafting +grain +granddad +grandkid +grandly +grandma +grandpa +grandson +granite +granny +granola +grant +granular +grape +graph +grapple +grappling +grasp +grass +gratified +gratify +grating +gratitude +gratuity +gravel +graveness +graves +graveyard +gravitate +gravity +gravy +gray +grazing +greasily +greedily +greedless +greedy +green +greeter +greeting +grew +greyhound +grid +grief +grievance +grieving +grievous +grill +grimace +grimacing +grime +griminess +grimy +grinch +grinning +grip +gristle +grit +groggily +groggy +groin +groom +groove +grooving +groovy +grope +ground +grouped +grout +grove +grower +growing +growl +grub +grudge +grudging +grueling +gruffly +grumble +grumbling +grumbly +grumpily +grunge +grunt +guacamole +guidable +guidance +guide +guiding +guileless +guise +gulf +gullible +gully +gulp +gumball +gumdrop +gumminess +gumming +gummy +gurgle +gurgling +guru +gush +gusto +gusty +gutless +guts +gutter +guy +guzzler +gyration +habitable +habitant +habitat +habitual +hacked +hacker +hacking +hacksaw +had +haggler +haiku +half +halogen +halt +halved +halves +hamburger +hamlet +hammock +hamper +hamster +hamstring +handbag +handball +handbook +handbrake +handcart +handclap +handclasp +handcraft +handcuff +handed +handful +handgrip +handgun +handheld +handiness +handiwork +handlebar +handled +handler +handling +handmade +handoff +handpick +handprint +handrail +handsaw +handset +handsfree +handshake +handstand +handwash +handwork +handwoven +handwrite +handyman +hangnail +hangout +hangover +hangup +hankering +hankie +hanky +haphazard +happening +happier +happiest +happily +happiness +happy +harbor +hardcopy +hardcore +hardcover +harddisk +hardened +hardener +hardening +hardhat +hardhead +hardiness +hardly +hardness +hardship +hardware +hardwired +hardwood +hardy +harmful +harmless +harmonica +harmonics +harmonize +harmony +harness +harpist +harsh +harvest +hash +hassle +haste +hastily +hastiness +hasty +hatbox +hatchback +hatchery +hatchet +hatching +hatchling +hate +hatless +hatred +haunt +haven +hazard +hazelnut +hazily +haziness +hazing +hazy +headache +headband +headboard +headcount +headdress +headed +header +headfirst +headgear +heading +headlamp +headless +headlock +headphone +headpiece +headrest +headroom +headscarf +headset +headsman +headstand +headstone +headway +headwear +heap +heat +heave +heavily +heaviness +heaving +hedge +hedging +heftiness +hefty +helium +helmet +helper +helpful +helping +helpless +helpline +hemlock +hemstitch +hence +henchman +henna +herald +herbal +herbicide +herbs +heritage +hermit +heroics +heroism +herring +herself +hertz +hesitancy +hesitant +hesitate +hexagon +hexagram +hubcap +huddle +huddling +huff +hug +hula +hulk +hull +human +humble +humbling +humbly +humid +humiliate +humility +humming +hummus +humongous +humorist +humorless +humorous +humpback +humped +humvee +hunchback +hundredth +hunger +hungrily +hungry +hunk +hunter +hunting +huntress +huntsman +hurdle +hurled +hurler +hurling +hurray +hurricane +hurried +hurry +hurt +husband +hush +husked +huskiness +hut +hybrid +hydrant +hydrated +hydration +hydrogen +hydroxide +hyperlink +hypertext +hyphen +hypnoses +hypnosis +hypnotic +hypnotism +hypnotist +hypnotize +hypocrisy +hypocrite +ibuprofen +ice +iciness +icing +icky +icon +icy +idealism +idealist +idealize +ideally +idealness +identical +identify +identity +ideology +idiocy +idiom +idly +igloo +ignition +ignore +iguana +illicitly +illusion +illusive +image +imaginary +imagines +imaging +imbecile +imitate +imitation +immature +immerse +immersion +imminent +immobile +immodest +immorally +immortal +immovable +immovably +immunity +immunize +impaired +impale +impart +impatient +impeach +impeding +impending +imperfect +imperial +impish +implant +implement +implicate +implicit +implode +implosion +implosive +imply +impolite +important +importer +impose +imposing +impotence +impotency +impotent +impound +imprecise +imprint +imprison +impromptu +improper +improve +improving +improvise +imprudent +impulse +impulsive +impure +impurity +iodine +iodize +ion +ipad +iphone +ipod +irate +irk +iron +irregular +irrigate +irritable +irritably +irritant +irritate +islamic +islamist +isolated +isolating +isolation +isotope +issue +issuing +italicize +italics +item +itinerary +itunes +ivory +ivy +jab +jackal +jacket +jackknife +jackpot +jailbird +jailbreak +jailer +jailhouse +jalapeno +jam +janitor +january +jargon +jarring +jasmine +jaundice +jaunt +java +jawed +jawless +jawline +jaws +jaybird +jaywalker +jazz +jeep +jeeringly +jellied +jelly +jersey +jester +jet +jiffy +jigsaw +jimmy +jingle +jingling +jinx +jitters +jittery +job +jockey +jockstrap +jogger +jogging +john +joining +jokester +jokingly +jolliness +jolly +jolt +jot +jovial +joyfully +joylessly +joyous +joyride +joystick +jubilance +jubilant +judge +judgingly +judicial +judiciary +judo +juggle +juggling +jugular +juice +juiciness +juicy +jujitsu +jukebox +july +jumble +jumbo +jump +junction +juncture +june +junior +juniper +junkie +junkman +junkyard +jurist +juror +jury +justice +justifier +justify +justly +justness +juvenile +kabob +kangaroo +karaoke +karate +karma +kebab +keenly +keenness +keep +keg +kelp +kennel +kept +kerchief +kerosene +kettle +kick +kiln +kilobyte +kilogram +kilometer +kilowatt +kilt +kimono +kindle +kindling +kindly +kindness +kindred +kinetic +kinfolk +king +kinship +kinsman +kinswoman +kissable +kisser +kissing +kitchen +kite +kitten +kitty +kiwi +kleenex +knapsack +knee +knelt +knickers +knoll +koala +kooky +kosher +krypton +kudos +kung +labored +laborer +laboring +laborious +labrador +ladder +ladies +ladle +ladybug +ladylike +lagged +lagging +lagoon +lair +lake +lance +landed +landfall +landfill +landing +landlady +landless +landline +landlord +landmark +landmass +landmine +landowner +landscape +landside +landslide +language +lankiness +lanky +lantern +lapdog +lapel +lapped +lapping +laptop +lard +large +lark +lash +lasso +last +latch +late +lather +latitude +latrine +latter +latticed +launch +launder +laundry +laurel +lavender +lavish +laxative +lazily +laziness +lazy +lecturer +left +legacy +legal +legend +legged +leggings +legible +legibly +legislate +lego +legroom +legume +legwarmer +legwork +lemon +lend +length +lens +lent +leotard +lesser +letdown +lethargic +lethargy +letter +lettuce +level +leverage +levers +levitate +levitator +liability +liable +liberty +librarian +library +licking +licorice +lid +life +lifter +lifting +liftoff +ligament +likely +likeness +likewise +liking +lilac +lilly +lily +limb +limeade +limelight +limes +limit +limping +limpness +line +lingo +linguini +linguist +lining +linked +linoleum +linseed +lint +lion +lip +liquefy +liqueur +liquid +lisp +list +litigate +litigator +litmus +litter +little +livable +lived +lively +liver +livestock +lividly +living +lizard +lubricant +lubricate +lucid +luckily +luckiness +luckless +lucrative +ludicrous +lugged +lukewarm +lullaby +lumber +luminance +luminous +lumpiness +lumping +lumpish +lunacy +lunar +lunchbox +luncheon +lunchroom +lunchtime +lung +lurch +lure +luridness +lurk +lushly +lushness +luster +lustfully +lustily +lustiness +lustrous +lusty +luxurious +luxury +lying +lyrically +lyricism +lyricist +lyrics +macarena +macaroni +macaw +mace +machine +machinist +magazine +magenta +maggot +magical +magician +magma +magnesium +magnetic +magnetism +magnetize +magnifier +magnify +magnitude +magnolia +mahogany +maimed +majestic +majesty +majorette +majority +makeover +maker +makeshift +making +malformed +malt +mama +mammal +mammary +mammogram +manager +managing +manatee +mandarin +mandate +mandatory +mandolin +manger +mangle +mango +mangy +manhandle +manhole +manhood +manhunt +manicotti +manicure +manifesto +manila +mankind +manlike +manliness +manly +manmade +manned +mannish +manor +manpower +mantis +mantra +manual +many +map +marathon +marauding +marbled +marbles +marbling +march +mardi +margarine +margarita +margin +marigold +marina +marine +marital +maritime +marlin +marmalade +maroon +married +marrow +marry +marshland +marshy +marsupial +marvelous +marxism +mascot +masculine +mashed +mashing +massager +masses +massive +mastiff +matador +matchbook +matchbox +matcher +matching +matchless +material +maternal +maternity +math +mating +matriarch +matrimony +matrix +matron +matted +matter +maturely +maturing +maturity +mauve +maverick +maximize +maximum +maybe +mayday +mayflower +moaner +moaning +mobile +mobility +mobilize +mobster +mocha +mocker +mockup +modified +modify +modular +modulator +module +moisten +moistness +moisture +molar +molasses +mold +molecular +molecule +molehill +mollusk +mom +monastery +monday +monetary +monetize +moneybags +moneyless +moneywise +mongoose +mongrel +monitor +monkhood +monogamy +monogram +monologue +monopoly +monorail +monotone +monotype +monoxide +monsieur +monsoon +monstrous +monthly +monument +moocher +moodiness +moody +mooing +moonbeam +mooned +moonlight +moonlike +moonlit +moonrise +moonscape +moonshine +moonstone +moonwalk +mop +morale +morality +morally +morbidity +morbidly +morphine +morphing +morse +mortality +mortally +mortician +mortified +mortify +mortuary +mosaic +mossy +most +mothball +mothproof +motion +motivate +motivator +motive +motocross +motor +motto +mountable +mountain +mounted +mounting +mourner +mournful +mouse +mousiness +moustache +mousy +mouth +movable +move +movie +moving +mower +mowing +much +muck +mud +mug +mulberry +mulch +mule +mulled +mullets +multiple +multiply +multitask +multitude +mumble +mumbling +mumbo +mummified +mummify +mummy +mumps +munchkin +mundane +municipal +muppet +mural +murkiness +murky +murmuring +muscular +museum +mushily +mushiness +mushroom +mushy +music +musket +muskiness +musky +mustang +mustard +muster +mustiness +musty +mutable +mutate +mutation +mute +mutilated +mutilator +mutiny +mutt +mutual +muzzle +myself +myspace +mystified +mystify +myth +nacho +nag +nail +name +naming +nanny +nanometer +nape +napkin +napped +napping +nappy +narrow +nastily +nastiness +national +native +nativity +natural +nature +naturist +nautical +navigate +navigator +navy +nearby +nearest +nearly +nearness +neatly +neatness +nebula +nebulizer +nectar +negate +negation +negative +neglector +negligee +negligent +negotiate +nemeses +nemesis +neon +nephew +nerd +nervous +nervy +nest +net +neurology +neuron +neurosis +neurotic +neuter +neutron +never +next +nibble +nickname +nicotine +niece +nifty +nimble +nimbly +nineteen +ninetieth +ninja +nintendo +ninth +nuclear +nuclei +nucleus +nugget +nullify +number +numbing +numbly +numbness +numeral +numerate +numerator +numeric +numerous +nuptials +nursery +nursing +nurture +nutcase +nutlike +nutmeg +nutrient +nutshell +nuttiness +nutty +nuzzle +nylon +oaf +oak +oasis +oat +obedience +obedient +obituary +object +obligate +obliged +oblivion +oblivious +oblong +obnoxious +oboe +obscure +obscurity +observant +observer +observing +obsessed +obsession +obsessive +obsolete +obstacle +obstinate +obstruct +obtain +obtrusive +obtuse +obvious +occultist +occupancy +occupant +occupier +occupy +ocean +ocelot +octagon +octane +october +octopus +ogle +oil +oink +ointment +okay +old +olive +olympics +omega +omen +ominous +omission +omit +omnivore +onboard +oncoming +ongoing +onion +online +onlooker +only +onscreen +onset +onshore +onslaught +onstage +onto +onward +onyx +oops +ooze +oozy +opacity +opal +open +operable +operate +operating +operation +operative +operator +opium +opossum +opponent +oppose +opposing +opposite +oppressed +oppressor +opt +opulently +osmosis +other +otter +ouch +ought +ounce +outage +outback +outbid +outboard +outbound +outbreak +outburst +outcast +outclass +outcome +outdated +outdoors +outer +outfield +outfit +outflank +outgoing +outgrow +outhouse +outing +outlast +outlet +outline +outlook +outlying +outmatch +outmost +outnumber +outplayed +outpost +outpour +output +outrage +outrank +outreach +outright +outscore +outsell +outshine +outshoot +outsider +outskirts +outsmart +outsource +outspoken +outtakes +outthink +outward +outweigh +outwit +oval +ovary +oven +overact +overall +overarch +overbid +overbill +overbite +overblown +overboard +overbook +overbuilt +overcast +overcoat +overcome +overcook +overcrowd +overdraft +overdrawn +overdress +overdrive +overdue +overeager +overeater +overexert +overfed +overfeed +overfill +overflow +overfull +overgrown +overhand +overhang +overhaul +overhead +overhear +overheat +overhung +overjoyed +overkill +overlabor +overlaid +overlap +overlay +overload +overlook +overlord +overlying +overnight +overpass +overpay +overplant +overplay +overpower +overprice +overrate +overreach +overreact +override +overripe +overrule +overrun +overshoot +overshot +oversight +oversized +oversleep +oversold +overspend +overstate +overstay +overstep +overstock +overstuff +oversweet +overtake +overthrow +overtime +overtly +overtone +overture +overturn +overuse +overvalue +overview +overwrite +owl +oxford +oxidant +oxidation +oxidize +oxidizing +oxygen +oxymoron +oyster +ozone +paced +pacemaker +pacific +pacifier +pacifism +pacifist +pacify +padded +padding +paddle +paddling +padlock +pagan +pager +paging +pajamas +palace +palatable +palm +palpable +palpitate +paltry +pampered +pamperer +pampers +pamphlet +panama +pancake +pancreas +panda +pandemic +pang +panhandle +panic +panning +panorama +panoramic +panther +pantomime +pantry +pants +pantyhose +paparazzi +papaya +paper +paprika +papyrus +parabola +parachute +parade +paradox +paragraph +parakeet +paralegal +paralyses +paralysis +paralyze +paramedic +parameter +paramount +parasail +parasite +parasitic +parcel +parched +parchment +pardon +parish +parka +parking +parkway +parlor +parmesan +parole +parrot +parsley +parsnip +partake +parted +parting +partition +partly +partner +partridge +party +passable +passably +passage +passcode +passenger +passerby +passing +passion +passive +passivism +passover +passport +password +pasta +pasted +pastel +pastime +pastor +pastrami +pasture +pasty +patchwork +patchy +paternal +paternity +path +patience +patient +patio +patriarch +patriot +patrol +patronage +patronize +pauper +pavement +paver +pavestone +pavilion +paving +pawing +payable +payback +paycheck +payday +payee +payer +paying +payment +payphone +payroll +pebble +pebbly +pecan +pectin +peculiar +peddling +pediatric +pedicure +pedigree +pedometer +pegboard +pelican +pellet +pelt +pelvis +penalize +penalty +pencil +pendant +pending +penholder +penknife +pennant +penniless +penny +penpal +pension +pentagon +pentagram +pep +perceive +percent +perch +percolate +perennial +perfected +perfectly +perfume +periscope +perish +perjurer +perjury +perkiness +perky +perm +peroxide +perpetual +perplexed +persecute +persevere +persuaded +persuader +pesky +peso +pessimism +pessimist +pester +pesticide +petal +petite +petition +petri +petroleum +petted +petticoat +pettiness +petty +petunia +phantom +phobia +phoenix +phonebook +phoney +phonics +phoniness +phony +phosphate +photo +phrase +phrasing +placard +placate +placidly +plank +planner +plant +plasma +plaster +plastic +plated +platform +plating +platinum +platonic +platter +platypus +plausible +plausibly +playable +playback +player +playful +playgroup +playhouse +playing +playlist +playmaker +playmate +playoff +playpen +playroom +playset +plaything +playtime +plaza +pleading +pleat +pledge +plentiful +plenty +plethora +plexiglas +pliable +plod +plop +plot +plow +ploy +pluck +plug +plunder +plunging +plural +plus +plutonium +plywood +poach +pod +poem +poet +pogo +pointed +pointer +pointing +pointless +pointy +poise +poison +poker +poking +polar +police +policy +polio +polish +politely +polka +polo +polyester +polygon +polygraph +polymer +poncho +pond +pony +popcorn +pope +poplar +popper +poppy +popsicle +populace +popular +populate +porcupine +pork +porous +porridge +portable +portal +portfolio +porthole +portion +portly +portside +poser +posh +posing +possible +possibly +possum +postage +postal +postbox +postcard +posted +poster +posting +postnasal +posture +postwar +pouch +pounce +pouncing +pound +pouring +pout +powdered +powdering +powdery +power +powwow +pox +praising +prance +prancing +pranker +prankish +prankster +prayer +praying +preacher +preaching +preachy +preamble +precinct +precise +precision +precook +precut +predator +predefine +predict +preface +prefix +preflight +preformed +pregame +pregnancy +pregnant +preheated +prelaunch +prelaw +prelude +premiere +premises +premium +prenatal +preoccupy +preorder +prepaid +prepay +preplan +preppy +preschool +prescribe +preseason +preset +preshow +president +presoak +press +presume +presuming +preteen +pretended +pretender +pretense +pretext +pretty +pretzel +prevail +prevalent +prevent +preview +previous +prewar +prewashed +prideful +pried +primal +primarily +primary +primate +primer +primp +princess +print +prior +prism +prison +prissy +pristine +privacy +private +privatize +prize +proactive +probable +probably +probation +probe +probing +probiotic +problem +procedure +process +proclaim +procreate +procurer +prodigal +prodigy +produce +product +profane +profanity +professed +professor +profile +profound +profusely +progeny +prognosis +program +progress +projector +prologue +prolonged +promenade +prominent +promoter +promotion +prompter +promptly +prone +prong +pronounce +pronto +proofing +proofread +proofs +propeller +properly +property +proponent +proposal +propose +props +prorate +protector +protegee +proton +prototype +protozoan +protract +protrude +proud +provable +proved +proven +provided +provider +providing +province +proving +provoke +provoking +provolone +prowess +prowler +prowling +proximity +proxy +prozac +prude +prudishly +prune +pruning +pry +psychic +public +publisher +pucker +pueblo +pug +pull +pulmonary +pulp +pulsate +pulse +pulverize +puma +pumice +pummel +punch +punctual +punctuate +punctured +pungent +punisher +punk +pupil +puppet +puppy +purchase +pureblood +purebred +purely +pureness +purgatory +purge +purging +purifier +purify +purist +puritan +purity +purple +purplish +purposely +purr +purse +pursuable +pursuant +pursuit +purveyor +pushcart +pushchair +pusher +pushiness +pushing +pushover +pushpin +pushup +pushy +putdown +putt +puzzle +puzzling +pyramid +pyromania +python +quack +quadrant +quail +quaintly +quake +quaking +qualified +qualifier +qualify +quality +qualm +quantum +quarrel +quarry +quartered +quarterly +quarters +quartet +quench +query +quicken +quickly +quickness +quicksand +quickstep +quiet +quill +quilt +quintet +quintuple +quirk +quit +quiver +quizzical +quotable +quotation +quote +rabid +race +racing +racism +rack +racoon +radar +radial +radiance +radiantly +radiated +radiation +radiator +radio +radish +raffle +raft +rage +ragged +raging +ragweed +raider +railcar +railing +railroad +railway +raisin +rake +raking +rally +ramble +rambling +ramp +ramrod +ranch +rancidity +random +ranged +ranger +ranging +ranked +ranking +ransack +ranting +rants +rare +rarity +rascal +rash +rasping +ravage +raven +ravine +raving +ravioli +ravishing +reabsorb +reach +reacquire +reaction +reactive +reactor +reaffirm +ream +reanalyze +reappear +reapply +reappoint +reapprove +rearrange +rearview +reason +reassign +reassure +reattach +reawake +rebalance +rebate +rebel +rebirth +reboot +reborn +rebound +rebuff +rebuild +rebuilt +reburial +rebuttal +recall +recant +recapture +recast +recede +recent +recess +recharger +recipient +recital +recite +reckless +reclaim +recliner +reclining +recluse +reclusive +recognize +recoil +recollect +recolor +reconcile +reconfirm +reconvene +recopy +record +recount +recoup +recovery +recreate +rectal +rectangle +rectified +rectify +recycled +recycler +recycling +reemerge +reenact +reenter +reentry +reexamine +referable +referee +reference +refill +refinance +refined +refinery +refining +refinish +reflected +reflector +reflex +reflux +refocus +refold +reforest +reformat +reformed +reformer +reformist +refract +refrain +refreeze +refresh +refried +refueling +refund +refurbish +refurnish +refusal +refuse +refusing +refutable +refute +regain +regalia +regally +reggae +regime +region +register +registrar +registry +regress +regretful +regroup +regular +regulate +regulator +rehab +reheat +rehire +rehydrate +reimburse +reissue +reiterate +rejoice +rejoicing +rejoin +rekindle +relapse +relapsing +relatable +related +relation +relative +relax +relay +relearn +release +relenting +reliable +reliably +reliance +reliant +relic +relieve +relieving +relight +relish +relive +reload +relocate +relock +reluctant +rely +remake +remark +remarry +rematch +remedial +remedy +remember +reminder +remindful +remission +remix +remnant +remodeler +remold +remorse +remote +removable +removal +removed +remover +removing +rename +renderer +rendering +rendition +renegade +renewable +renewably +renewal +renewed +renounce +renovate +renovator +rentable +rental +rented +renter +reoccupy +reoccur +reopen +reorder +repackage +repacking +repaint +repair +repave +repaying +repayment +repeal +repeated +repeater +repent +rephrase +replace +replay +replica +reply +reporter +repose +repossess +repost +repressed +reprimand +reprint +reprise +reproach +reprocess +reproduce +reprogram +reps +reptile +reptilian +repugnant +repulsion +repulsive +repurpose +reputable +reputably +request +require +requisite +reroute +rerun +resale +resample +rescuer +reseal +research +reselect +reseller +resemble +resend +resent +reset +reshape +reshoot +reshuffle +residence +residency +resident +residual +residue +resigned +resilient +resistant +resisting +resize +resolute +resolved +resonant +resonate +resort +resource +respect +resubmit +result +resume +resupply +resurface +resurrect +retail +retainer +retaining +retake +retaliate +retention +rethink +retinal +retired +retiree +retiring +retold +retool +retorted +retouch +retrace +retract +retrain +retread +retreat +retrial +retrieval +retriever +retry +return +retying +retype +reunion +reunite +reusable +reuse +reveal +reveler +revenge +revenue +reverb +revered +reverence +reverend +reversal +reverse +reversing +reversion +revert +revisable +revise +revision +revisit +revivable +revival +reviver +reviving +revocable +revoke +revolt +revolver +revolving +reward +rewash +rewind +rewire +reword +rework +rewrap +rewrite +rhyme +ribbon +ribcage +rice +riches +richly +richness +rickety +ricotta +riddance +ridden +ride +riding +rifling +rift +rigging +rigid +rigor +rimless +rimmed +rind +rink +rinse +rinsing +riot +ripcord +ripeness +ripening +ripping +ripple +rippling +riptide +rise +rising +risk +risotto +ritalin +ritzy +rival +riverbank +riverbed +riverboat +riverside +riveter +riveting +roamer +roaming +roast +robbing +robe +robin +robotics +robust +rockband +rocker +rocket +rockfish +rockiness +rocking +rocklike +rockslide +rockstar +rocky +rogue +roman +romp +rope +roping +roster +rosy +rotten +rotting +rotunda +roulette +rounding +roundish +roundness +roundup +roundworm +routine +routing +rover +roving +royal +rubbed +rubber +rubbing +rubble +rubdown +ruby +ruckus +rudder +rug +ruined +rule +rumble +rumbling +rummage +rumor +runaround +rundown +runner +running +runny +runt +runway +rupture +rural +ruse +rush +rust +rut +sabbath +sabotage +sacrament +sacred +sacrifice +sadden +saddlebag +saddled +saddling +sadly +sadness +safari +safeguard +safehouse +safely +safeness +saffron +saga +sage +sagging +saggy +said +saint +sake +salad +salami +salaried +salary +saline +salon +saloon +salsa +salt +salutary +salute +salvage +salvaging +salvation +same +sample +sampling +sanction +sanctity +sanctuary +sandal +sandbag +sandbank +sandbar +sandblast +sandbox +sanded +sandfish +sanding +sandlot +sandpaper +sandpit +sandstone +sandstorm +sandworm +sandy +sanitary +sanitizer +sank +santa +sapling +sappiness +sappy +sarcasm +sarcastic +sardine +sash +sasquatch +sassy +satchel +satiable +satin +satirical +satisfied +satisfy +saturate +saturday +sauciness +saucy +sauna +savage +savanna +saved +savings +savior +savor +saxophone +say +scabbed +scabby +scalded +scalding +scale +scaling +scallion +scallop +scalping +scam +scandal +scanner +scanning +scant +scapegoat +scarce +scarcity +scarecrow +scared +scarf +scarily +scariness +scarring +scary +scavenger +scenic +schedule +schematic +scheme +scheming +schilling +schnapps +scholar +science +scientist +scion +scoff +scolding +scone +scoop +scooter +scope +scorch +scorebook +scorecard +scored +scoreless +scorer +scoring +scorn +scorpion +scotch +scoundrel +scoured +scouring +scouting +scouts +scowling +scrabble +scraggly +scrambled +scrambler +scrap +scratch +scrawny +screen +scribble +scribe +scribing +scrimmage +script +scroll +scrooge +scrounger +scrubbed +scrubber +scruffy +scrunch +scrutiny +scuba +scuff +sculptor +sculpture +scurvy +scuttle +secluded +secluding +seclusion +second +secrecy +secret +sectional +sector +secular +securely +security +sedan +sedate +sedation +sedative +sediment +seduce +seducing +segment +seismic +seizing +seldom +selected +selection +selective +selector +self +seltzer +semantic +semester +semicolon +semifinal +seminar +semisoft +semisweet +senate +senator +send +senior +senorita +sensation +sensitive +sensitize +sensually +sensuous +sepia +september +septic +septum +sequel +sequence +sequester +series +sermon +serotonin +serpent +serrated +serve +service +serving +sesame +sessions +setback +setting +settle +settling +setup +sevenfold +seventeen +seventh +seventy +severity +shabby +shack +shaded +shadily +shadiness +shading +shadow +shady +shaft +shakable +shakily +shakiness +shaking +shaky +shale +shallot +shallow +shame +shampoo +shamrock +shank +shanty +shape +shaping +share +sharpener +sharper +sharpie +sharply +sharpness +shawl +sheath +shed +sheep +sheet +shelf +shell +shelter +shelve +shelving +sherry +shield +shifter +shifting +shiftless +shifty +shimmer +shimmy +shindig +shine +shingle +shininess +shining +shiny +ship +shirt +shivering +shock +shone +shoplift +shopper +shopping +shoptalk +shore +shortage +shortcake +shortcut +shorten +shorter +shorthand +shortlist +shortly +shortness +shorts +shortwave +shorty +shout +shove +showbiz +showcase +showdown +shower +showgirl +showing +showman +shown +showoff +showpiece +showplace +showroom +showy +shrank +shrapnel +shredder +shredding +shrewdly +shriek +shrill +shrimp +shrine +shrink +shrivel +shrouded +shrubbery +shrubs +shrug +shrunk +shucking +shudder +shuffle +shuffling +shun +shush +shut +shy +siamese +siberian +sibling +siding +sierra +siesta +sift +sighing +silenced +silencer +silent +silica +silicon +silk +silliness +silly +silo +silt +silver +similarly +simile +simmering +simple +simplify +simply +sincere +sincerity +singer +singing +single +singular +sinister +sinless +sinner +sinuous +sip +siren +sister +sitcom +sitter +sitting +situated +situation +sixfold +sixteen +sixth +sixties +sixtieth +sixtyfold +sizable +sizably +size +sizing +sizzle +sizzling +skater +skating +skedaddle +skeletal +skeleton +skeptic +sketch +skewed +skewer +skid +skied +skier +skies +skiing +skilled +skillet +skillful +skimmed +skimmer +skimming +skimpily +skincare +skinhead +skinless +skinning +skinny +skintight +skipper +skipping +skirmish +skirt +skittle +skydiver +skylight +skyline +skype +skyrocket +skyward +slab +slacked +slacker +slacking +slackness +slacks +slain +slam +slander +slang +slapping +slapstick +slashed +slashing +slate +slather +slaw +sled +sleek +sleep +sleet +sleeve +slept +sliceable +sliced +slicer +slicing +slick +slider +slideshow +sliding +slighted +slighting +slightly +slimness +slimy +slinging +slingshot +slinky +slip +slit +sliver +slobbery +slogan +sloped +sloping +sloppily +sloppy +slot +slouching +slouchy +sludge +slug +slum +slurp +slush +sly +small +smartly +smartness +smasher +smashing +smashup +smell +smelting +smile +smilingly +smirk +smite +smith +smitten +smock +smog +smoked +smokeless +smokiness +smoking +smoky +smolder +smooth +smother +smudge +smudgy +smuggler +smuggling +smugly +smugness +snack +snagged +snaking +snap +snare +snarl +snazzy +sneak +sneer +sneeze +sneezing +snide +sniff +snippet +snipping +snitch +snooper +snooze +snore +snoring +snorkel +snort +snout +snowbird +snowboard +snowbound +snowcap +snowdrift +snowdrop +snowfall +snowfield +snowflake +snowiness +snowless +snowman +snowplow +snowshoe +snowstorm +snowsuit +snowy +snub +snuff +snuggle +snugly +snugness +speak +spearfish +spearhead +spearman +spearmint +species +specimen +specked +speckled +specks +spectacle +spectator +spectrum +speculate +speech +speed +spellbind +speller +spelling +spendable +spender +spending +spent +spew +sphere +spherical +sphinx +spider +spied +spiffy +spill +spilt +spinach +spinal +spindle +spinner +spinning +spinout +spinster +spiny +spiral +spirited +spiritism +spirits +spiritual +splashed +splashing +splashy +splatter +spleen +splendid +splendor +splice +splicing +splinter +splotchy +splurge +spoilage +spoiled +spoiler +spoiling +spoils +spoken +spokesman +sponge +spongy +sponsor +spoof +spookily +spooky +spool +spoon +spore +sporting +sports +sporty +spotless +spotlight +spotted +spotter +spotting +spotty +spousal +spouse +spout +sprain +sprang +sprawl +spray +spree +sprig +spring +sprinkled +sprinkler +sprint +sprite +sprout +spruce +sprung +spry +spud +spur +sputter +spyglass +squabble +squad +squall +squander +squash +squatted +squatter +squatting +squeak +squealer +squealing +squeamish +squeegee +squeeze +squeezing +squid +squiggle +squiggly +squint +squire +squirt +squishier +squishy +stability +stabilize +stable +stack +stadium +staff +stage +staging +stagnant +stagnate +stainable +stained +staining +stainless +stalemate +staleness +stalling +stallion +stamina +stammer +stamp +stand +stank +staple +stapling +starboard +starch +stardom +stardust +starfish +stargazer +staring +stark +starless +starlet +starlight +starlit +starring +starry +starship +starter +starting +startle +startling +startup +starved +starving +stash +state +static +statistic +statue +stature +status +statute +statutory +staunch +stays +steadfast +steadier +steadily +steadying +steam +steed +steep +steerable +steering +steersman +stegosaur +stellar +stem +stench +stencil +step +stereo +sterile +sterility +sterilize +sterling +sternness +sternum +stew +stick +stiffen +stiffly +stiffness +stifle +stifling +stillness +stilt +stimulant +stimulate +stimuli +stimulus +stinger +stingily +stinging +stingray +stingy +stinking +stinky +stipend +stipulate +stir +stitch +stock +stoic +stoke +stole +stomp +stonewall +stoneware +stonework +stoning +stony +stood +stooge +stool +stoop +stoplight +stoppable +stoppage +stopped +stopper +stopping +stopwatch +storable +storage +storeroom +storewide +storm +stout +stove +stowaway +stowing +straddle +straggler +strained +strainer +straining +strangely +stranger +strangle +strategic +strategy +stratus +straw +stray +streak +stream +street +strength +strenuous +strep +stress +stretch +strewn +stricken +strict +stride +strife +strike +striking +strive +striving +strobe +strode +stroller +strongbox +strongly +strongman +struck +structure +strudel +struggle +strum +strung +strut +stubbed +stubble +stubbly +stubborn +stucco +stuck +student +studied +studio +study +stuffed +stuffing +stuffy +stumble +stumbling +stump +stung +stunned +stunner +stunning +stunt +stupor +sturdily +sturdy +styling +stylishly +stylist +stylized +stylus +suave +subarctic +subatomic +subdivide +subdued +subduing +subfloor +subgroup +subheader +subject +sublease +sublet +sublevel +sublime +submarine +submerge +submersed +submitter +subpanel +subpar +subplot +subprime +subscribe +subscript +subsector +subside +subsiding +subsidize +subsidy +subsoil +subsonic +substance +subsystem +subtext +subtitle +subtly +subtotal +subtract +subtype +suburb +subway +subwoofer +subzero +succulent +such +suction +sudden +sudoku +suds +sufferer +suffering +suffice +suffix +suffocate +suffrage +sugar +suggest +suing +suitable +suitably +suitcase +suitor +sulfate +sulfide +sulfite +sulfur +sulk +sullen +sulphate +sulphuric +sultry +superbowl +superglue +superhero +superior +superjet +superman +supermom +supernova +supervise +supper +supplier +supply +support +supremacy +supreme +surcharge +surely +sureness +surface +surfacing +surfboard +surfer +surgery +surgical +surging +surname +surpass +surplus +surprise +surreal +surrender +surrogate +surround +survey +survival +survive +surviving +survivor +sushi +suspect +suspend +suspense +sustained +sustainer +swab +swaddling +swagger +swampland +swan +swapping +swarm +sway +swear +sweat +sweep +swell +swept +swerve +swifter +swiftly +swiftness +swimmable +swimmer +swimming +swimsuit +swimwear +swinger +swinging +swipe +swirl +switch +swivel +swizzle +swooned +swoop +swoosh +swore +sworn +swung +sycamore +sympathy +symphonic +symphony +symptom +synapse +syndrome +synergy +synopses +synopsis +synthesis +synthetic +syrup +system +t-shirt +tabasco +tabby +tableful +tables +tablet +tableware +tabloid +tackiness +tacking +tackle +tackling +tacky +taco +tactful +tactical +tactics +tactile +tactless +tadpole +taekwondo +tag +tainted +take +taking +talcum +talisman +tall +talon +tamale +tameness +tamer +tamper +tank +tanned +tannery +tanning +tantrum +tapeless +tapered +tapering +tapestry +tapioca +tapping +taps +tarantula +target +tarmac +tarnish +tarot +tartar +tartly +tartness +task +tassel +taste +tastiness +tasting +tasty +tattered +tattle +tattling +tattoo +taunt +tavern +thank +that +thaw +theater +theatrics +thee +theft +theme +theology +theorize +thermal +thermos +thesaurus +these +thesis +thespian +thicken +thicket +thickness +thieving +thievish +thigh +thimble +thing +think +thinly +thinner +thinness +thinning +thirstily +thirsting +thirsty +thirteen +thirty +thong +thorn +those +thousand +thrash +thread +threaten +threefold +thrift +thrill +thrive +thriving +throat +throbbing +throng +throttle +throwaway +throwback +thrower +throwing +thud +thumb +thumping +thursday +thus +thwarting +thyself +tiara +tibia +tidal +tidbit +tidiness +tidings +tidy +tiger +tighten +tightly +tightness +tightrope +tightwad +tigress +tile +tiling +till +tilt +timid +timing +timothy +tinderbox +tinfoil +tingle +tingling +tingly +tinker +tinkling +tinsel +tinsmith +tint +tinwork +tiny +tipoff +tipped +tipper +tipping +tiptoeing +tiptop +tiring +tissue +trace +tracing +track +traction +tractor +trade +trading +tradition +traffic +tragedy +trailing +trailside +train +traitor +trance +tranquil +transfer +transform +translate +transpire +transport +transpose +trapdoor +trapeze +trapezoid +trapped +trapper +trapping +traps +trash +travel +traverse +travesty +tray +treachery +treading +treadmill +treason +treat +treble +tree +trekker +tremble +trembling +tremor +trench +trend +trespass +triage +trial +triangle +tribesman +tribunal +tribune +tributary +tribute +triceps +trickery +trickily +tricking +trickle +trickster +tricky +tricolor +tricycle +trident +tried +trifle +trifocals +trillion +trilogy +trimester +trimmer +trimming +trimness +trinity +trio +tripod +tripping +triumph +trivial +trodden +trolling +trombone +trophy +tropical +tropics +trouble +troubling +trough +trousers +trout +trowel +truce +truck +truffle +trump +trunks +trustable +trustee +trustful +trusting +trustless +truth +try +tubby +tubeless +tubular +tucking +tuesday +tug +tuition +tulip +tumble +tumbling +tummy +turban +turbine +turbofan +turbojet +turbulent +turf +turkey +turmoil +turret +turtle +tusk +tutor +tutu +tux +tweak +tweed +tweet +tweezers +twelve +twentieth +twenty +twerp +twice +twiddle +twiddling +twig +twilight +twine +twins +twirl +twistable +twisted +twister +twisting +twisty +twitch +twitter +tycoon +tying +tyke +udder +ultimate +ultimatum +ultra +umbilical +umbrella +umpire +unabashed +unable +unadorned +unadvised +unafraid +unaired +unaligned +unaltered +unarmored +unashamed +unaudited +unawake +unaware +unbaked +unbalance +unbeaten +unbend +unbent +unbiased +unbitten +unblended +unblessed +unblock +unbolted +unbounded +unboxed +unbraided +unbridle +unbroken +unbuckled +unbundle +unburned +unbutton +uncanny +uncapped +uncaring +uncertain +unchain +unchanged +uncharted +uncheck +uncivil +unclad +unclaimed +unclamped +unclasp +uncle +unclip +uncloak +unclog +unclothed +uncoated +uncoiled +uncolored +uncombed +uncommon +uncooked +uncork +uncorrupt +uncounted +uncouple +uncouth +uncover +uncross +uncrown +uncrushed +uncured +uncurious +uncurled +uncut +undamaged +undated +undaunted +undead +undecided +undefined +underage +underarm +undercoat +undercook +undercut +underdog +underdone +underfed +underfeed +underfoot +undergo +undergrad +underhand +underline +underling +undermine +undermost +underpaid +underpass +underpay +underrate +undertake +undertone +undertook +undertow +underuse +underwear +underwent +underwire +undesired +undiluted +undivided +undocked +undoing +undone +undrafted +undress +undrilled +undusted +undying +unearned +unearth +unease +uneasily +uneasy +uneatable +uneaten +unedited +unelected +unending +unengaged +unenvied +unequal +unethical +uneven +unexpired +unexposed +unfailing +unfair +unfasten +unfazed +unfeeling +unfiled +unfilled +unfitted +unfitting +unfixable +unfixed +unflawed +unfocused +unfold +unfounded +unframed +unfreeze +unfrosted +unfrozen +unfunded +unglazed +ungloved +unglue +ungodly +ungraded +ungreased +unguarded +unguided +unhappily +unhappy +unharmed +unhealthy +unheard +unhearing +unheated +unhelpful +unhidden +unhinge +unhitched +unholy +unhook +unicorn +unicycle +unified +unifier +uniformed +uniformly +unify +unimpeded +uninjured +uninstall +uninsured +uninvited +union +uniquely +unisexual +unison +unissued +unit +universal +universe +unjustly +unkempt +unkind +unknotted +unknowing +unknown +unlaced +unlatch +unlawful +unleaded +unlearned +unleash +unless +unleveled +unlighted +unlikable +unlimited +unlined +unlinked +unlisted +unlit +unlivable +unloaded +unloader +unlocked +unlocking +unlovable +unloved +unlovely +unloving +unluckily +unlucky +unmade +unmanaged +unmanned +unmapped +unmarked +unmasked +unmasking +unmatched +unmindful +unmixable +unmixed +unmolded +unmoral +unmovable +unmoved +unmoving +unnamable +unnamed +unnatural +unneeded +unnerve +unnerving +unnoticed +unopened +unopposed +unpack +unpadded +unpaid +unpainted +unpaired +unpaved +unpeeled +unpicked +unpiloted +unpinned +unplanned +unplanted +unpleased +unpledged +unplowed +unplug +unpopular +unproven +unquote +unranked +unrated +unraveled +unreached +unread +unreal +unreeling +unrefined +unrelated +unrented +unrest +unretired +unrevised +unrigged +unripe +unrivaled +unroasted +unrobed +unroll +unruffled +unruly +unrushed +unsaddle +unsafe +unsaid +unsalted +unsaved +unsavory +unscathed +unscented +unscrew +unsealed +unseated +unsecured +unseeing +unseemly +unseen +unselect +unselfish +unsent +unsettled +unshackle +unshaken +unshaved +unshaven +unsheathe +unshipped +unsightly +unsigned +unskilled +unsliced +unsmooth +unsnap +unsocial +unsoiled +unsold +unsolved +unsorted +unspoiled +unspoken +unstable +unstaffed +unstamped +unsteady +unsterile +unstirred +unstitch +unstopped +unstuck +unstuffed +unstylish +unsubtle +unsubtly +unsuited +unsure +unsworn +untagged +untainted +untaken +untamed +untangled +untapped +untaxed +unthawed +unthread +untidy +untie +until +untimed +untimely +untitled +untoasted +untold +untouched +untracked +untrained +untreated +untried +untrimmed +untrue +untruth +unturned +untwist +untying +unusable +unused +unusual +unvalued +unvaried +unvarying +unveiled +unveiling +unvented +unviable +unvisited +unvocal +unwanted +unwarlike +unwary +unwashed +unwatched +unweave +unwed +unwelcome +unwell +unwieldy +unwilling +unwind +unwired +unwitting +unwomanly +unworldly +unworn +unworried +unworthy +unwound +unwoven +unwrapped +unwritten +unzip +upbeat +upchuck +upcoming +upcountry +update +upfront +upgrade +upheaval +upheld +uphill +uphold +uplifted +uplifting +upload +upon +upper +upright +uprising +upriver +uproar +uproot +upscale +upside +upstage +upstairs +upstart +upstate +upstream +upstroke +upswing +uptake +uptight +uptown +upturned +upward +upwind +uranium +urban +urchin +urethane +urgency +urgent +urging +urologist +urology +usable +usage +useable +used +uselessly +user +usher +usual +utensil +utility +utilize +utmost +utopia +utter +vacancy +vacant +vacate +vacation +vagabond +vagrancy +vagrantly +vaguely +vagueness +valiant +valid +valium +valley +valuables +value +vanilla +vanish +vanity +vanquish +vantage +vaporizer +variable +variably +varied +variety +various +varmint +varnish +varsity +varying +vascular +vaseline +vastly +vastness +veal +vegan +veggie +vehicular +velcro +velocity +velvet +vendetta +vending +vendor +veneering +vengeful +venomous +ventricle +venture +venue +venus +verbalize +verbally +verbose +verdict +verify +verse +version +versus +vertebrae +vertical +vertigo +very +vessel +vest +veteran +veto +vexingly +viability +viable +vibes +vice +vicinity +victory +video +viewable +viewer +viewing +viewless +viewpoint +vigorous +village +villain +vindicate +vineyard +vintage +violate +violation +violator +violet +violin +viper +viral +virtual +virtuous +virus +visa +viscosity +viscous +viselike +visible +visibly +vision +visiting +visitor +visor +vista +vitality +vitalize +vitally +vitamins +vivacious +vividly +vividness +vixen +vocalist +vocalize +vocally +vocation +voice +voicing +void +volatile +volley +voltage +volumes +voter +voting +voucher +vowed +vowel +voyage +wackiness +wad +wafer +waffle +waged +wager +wages +waggle +wagon +wake +waking +walk +walmart +walnut +walrus +waltz +wand +wannabe +wanted +wanting +wasabi +washable +washbasin +washboard +washbowl +washcloth +washday +washed +washer +washhouse +washing +washout +washroom +washstand +washtub +wasp +wasting +watch +water +waviness +waving +wavy +whacking +whacky +wham +wharf +wheat +whenever +whiff +whimsical +whinny +whiny +whisking +whoever +whole +whomever +whoopee +whooping +whoops +why +wick +widely +widen +widget +widow +width +wieldable +wielder +wife +wifi +wikipedia +wildcard +wildcat +wilder +wildfire +wildfowl +wildland +wildlife +wildly +wildness +willed +willfully +willing +willow +willpower +wilt +wimp +wince +wincing +wind +wing +winking +winner +winnings +winter +wipe +wired +wireless +wiring +wiry +wisdom +wise +wish +wisplike +wispy +wistful +wizard +wobble +wobbling +wobbly +wok +wolf +wolverine +womanhood +womankind +womanless +womanlike +womanly +womb +woof +wooing +wool +woozy +word +work +worried +worrier +worrisome +worry +worsening +worshiper +worst +wound +woven +wow +wrangle +wrath +wreath +wreckage +wrecker +wrecking +wrench +wriggle +wriggly +wrinkle +wrinkly +wrist +writing +written +wrongdoer +wronged +wrongful +wrongly +wrongness +wrought +xbox +xerox +yahoo +yam +yanking +yapping +yard +yarn +yeah +yearbook +yearling +yearly +yearning +yeast +yelling +yelp +yen +yesterday +yiddish +yield +yin +yippee +yo-yo +yodel +yoga +yogurt +yonder +yoyo +yummy +zap +zealous +zebra +zen +zeppelin +zero +zestfully +zesty +zigzagged +zipfile +zipping +zippy +zips +zit +zodiac +zombie +zone +zoning +zookeeper +zoologist +zoology +zoom diff --git a/share/wordlists/eff_short.wordlist b/share/wordlists/eff_short.wordlist new file mode 100644 index 000000000..4c8baa4ce --- /dev/null +++ b/share/wordlists/eff_short.wordlist @@ -0,0 +1,1296 @@ +acid +acorn +acre +acts +afar +affix +aged +agent +agile +aging +agony +ahead +aide +aids +aim +ajar +alarm +alias +alibi +alien +alike +alive +aloe +aloft +aloha +alone +amend +amino +ample +amuse +angel +anger +angle +ankle +apple +april +apron +aqua +area +arena +argue +arise +armed +armor +army +aroma +array +arson +art +ashen +ashes +atlas +atom +attic +audio +avert +avoid +awake +award +awoke +axis +bacon +badge +bagel +baggy +baked +baker +balmy +banjo +barge +barn +bash +basil +bask +batch +bath +baton +bats +blade +blank +blast +blaze +bleak +blend +bless +blimp +blink +bloat +blob +blog +blot +blunt +blurt +blush +boast +boat +body +boil +bok +bolt +boned +boney +bonus +bony +book +booth +boots +boss +botch +both +boxer +breed +bribe +brick +bride +brim +bring +brink +brisk +broad +broil +broke +brook +broom +brush +buck +bud +buggy +bulge +bulk +bully +bunch +bunny +bunt +bush +bust +busy +buzz +cable +cache +cadet +cage +cake +calm +cameo +canal +candy +cane +canon +cape +card +cargo +carol +carry +carve +case +cash +cause +cedar +chain +chair +chant +chaos +charm +chase +cheek +cheer +chef +chess +chest +chew +chief +chili +chill +chip +chomp +chop +chow +chuck +chump +chunk +churn +chute +cider +cinch +city +civic +civil +clad +claim +clamp +clap +clash +clasp +class +claw +clay +clean +clear +cleat +cleft +clerk +click +cling +clink +clip +cloak +clock +clone +cloth +cloud +clump +coach +coast +coat +cod +coil +coke +cola +cold +colt +coma +come +comic +comma +cone +cope +copy +coral +cork +cost +cot +couch +cough +cover +cozy +craft +cramp +crane +crank +crate +crave +crawl +crazy +creme +crepe +crept +crib +cried +crisp +crook +crop +cross +crowd +crown +crumb +crush +crust +cub +cult +cupid +cure +curl +curry +curse +curve +curvy +cushy +cut +cycle +dab +dad +daily +dairy +daisy +dance +dandy +darn +dart +dash +data +date +dawn +deaf +deal +dean +debit +debt +debug +decaf +decal +decay +deck +decor +decoy +deed +delay +denim +dense +dent +depth +derby +desk +dial +diary +dice +dig +dill +dime +dimly +diner +dingy +disco +dish +disk +ditch +ditzy +dizzy +dock +dodge +doing +doll +dome +donor +donut +dose +dot +dove +down +dowry +doze +drab +drama +drank +draw +dress +dried +drift +drill +drive +drone +droop +drove +drown +drum +dry +duck +duct +dude +dug +duke +duo +dusk +dust +duty +dwarf +dwell +eagle +early +earth +easel +east +eaten +eats +ebay +ebony +ebook +echo +edge +eel +eject +elbow +elder +elf +elk +elm +elope +elude +elves +email +emit +empty +emu +enter +entry +envoy +equal +erase +error +erupt +essay +etch +evade +even +evict +evil +evoke +exact +exit +fable +faced +fact +fade +fall +false +fancy +fang +fax +feast +feed +femur +fence +fend +ferry +fetal +fetch +fever +fiber +fifth +fifty +film +filth +final +finch +fit +five +flag +flaky +flame +flap +flask +fled +flick +fling +flint +flip +flirt +float +flock +flop +floss +flyer +foam +foe +fog +foil +folic +folk +food +fool +found +fox +foyer +frail +frame +fray +fresh +fried +frill +frisk +from +front +frost +froth +frown +froze +fruit +gag +gains +gala +game +gap +gas +gave +gear +gecko +geek +gem +genre +gift +gig +gills +given +giver +glad +glass +glide +gloss +glove +glow +glue +goal +going +golf +gong +good +gooey +goofy +gore +gown +grab +grain +grant +grape +graph +grasp +grass +grave +gravy +gray +green +greet +grew +grid +grief +grill +grip +grit +groom +grope +growl +grub +grunt +guide +gulf +gulp +gummy +guru +gush +gut +guy +habit +half +halo +halt +happy +harm +hash +hasty +hatch +hate +haven +hazel +hazy +heap +heat +heave +hedge +hefty +help +herbs +hers +hub +hug +hula +hull +human +humid +hump +hung +hunk +hunt +hurry +hurt +hush +hut +ice +icing +icon +icy +igloo +image +ion +iron +islam +issue +item +ivory +ivy +jab +jam +jaws +jazz +jeep +jelly +jet +jiffy +job +jog +jolly +jolt +jot +joy +judge +juice +juicy +july +jumbo +jump +junky +juror +jury +keep +keg +kept +kick +kilt +king +kite +kitty +kiwi +knee +knelt +koala +kung +ladle +lady +lair +lake +lance +land +lapel +large +lash +lasso +last +latch +late +lazy +left +legal +lemon +lend +lens +lent +level +lever +lid +life +lift +lilac +lily +limb +limes +line +lint +lion +lip +list +lived +liver +lunar +lunch +lung +lurch +lure +lurk +lying +lyric +mace +maker +malt +mama +mango +manor +many +map +march +mardi +marry +mash +match +mate +math +moan +mocha +moist +mold +mom +moody +mop +morse +most +motor +motto +mount +mouse +mousy +mouth +move +movie +mower +mud +mug +mulch +mule +mull +mumbo +mummy +mural +muse +music +musky +mute +nacho +nag +nail +name +nanny +nap +navy +near +neat +neon +nerd +nest +net +next +niece +ninth +nutty +oak +oasis +oat +ocean +oil +old +olive +omen +onion +only +ooze +opal +open +opera +opt +otter +ouch +ounce +outer +oval +oven +owl +ozone +pace +pagan +pager +palm +panda +panic +pants +panty +paper +park +party +pasta +patch +path +patio +payer +pecan +penny +pep +perch +perky +perm +pest +petal +petri +petty +photo +plank +plant +plaza +plead +plot +plow +pluck +plug +plus +poach +pod +poem +poet +pogo +point +poise +poker +polar +polio +polka +polo +pond +pony +poppy +pork +poser +pouch +pound +pout +power +prank +press +print +prior +prism +prize +probe +prong +proof +props +prude +prune +pry +pug +pull +pulp +pulse +puma +punch +punk +pupil +puppy +purr +purse +push +putt +quack +quake +query +quiet +quill +quilt +quit +quota +quote +rabid +race +rack +radar +radio +raft +rage +raid +rail +rake +rally +ramp +ranch +range +rank +rant +rash +raven +reach +react +ream +rebel +recap +relax +relay +relic +remix +repay +repel +reply +rerun +reset +rhyme +rice +rich +ride +rigid +rigor +rinse +riot +ripen +rise +risk +ritzy +rival +river +roast +robe +robin +rock +rogue +roman +romp +rope +rover +royal +ruby +rug +ruin +rule +runny +rush +rust +rut +sadly +sage +said +saint +salad +salon +salsa +salt +same +sandy +santa +satin +sauna +saved +savor +sax +say +scale +scam +scan +scare +scarf +scary +scoff +scold +scoop +scoot +scope +score +scorn +scout +scowl +scrap +scrub +scuba +scuff +sect +sedan +self +send +sepia +serve +set +seven +shack +shade +shady +shaft +shaky +sham +shape +share +sharp +shed +sheep +sheet +shelf +shell +shine +shiny +ship +shirt +shock +shop +shore +shout +shove +shown +showy +shred +shrug +shun +shush +shut +shy +sift +silk +silly +silo +sip +siren +sixth +size +skate +skew +skid +skier +skies +skip +skirt +skit +sky +slab +slack +slain +slam +slang +slash +slate +slaw +sled +sleek +sleep +sleet +slept +slice +slick +slimy +sling +slip +slit +slob +slot +slug +slum +slurp +slush +small +smash +smell +smile +smirk +smog +snack +snap +snare +snarl +sneak +sneer +sniff +snore +snort +snout +snowy +snub +snuff +speak +speed +spend +spent +spew +spied +spill +spiny +spoil +spoke +spoof +spool +spoon +sport +spot +spout +spray +spree +spur +squad +squat +squid +stack +staff +stage +stain +stall +stamp +stand +stank +stark +start +stash +state +stays +steam +steep +stem +step +stew +stick +sting +stir +stock +stole +stomp +stony +stood +stool +stoop +stop +storm +stout +stove +straw +stray +strut +stuck +stud +stuff +stump +stung +stunt +suds +sugar +sulk +surf +sushi +swab +swan +swarm +sway +swear +sweat +sweep +swell +swept +swim +swing +swipe +swirl +swoop +swore +syrup +tacky +taco +tag +take +tall +talon +tamer +tank +taper +taps +tarot +tart +task +taste +tasty +taunt +thank +thaw +theft +theme +thigh +thing +think +thong +thorn +those +throb +thud +thumb +thump +thus +tiara +tidal +tidy +tiger +tile +tilt +tint +tiny +trace +track +trade +train +trait +trap +trash +tray +treat +tree +trek +trend +trial +tribe +trick +trio +trout +truce +truck +trump +trunk +try +tug +tulip +tummy +turf +tusk +tutor +tutu +tux +tweak +tweet +twice +twine +twins +twirl +twist +uncle +uncut +undo +unify +union +unit +untie +upon +upper +urban +used +user +usher +utter +value +vapor +vegan +venue +verse +vest +veto +vice +video +view +viral +virus +visa +visor +vixen +vocal +voice +void +volt +voter +vowel +wad +wafer +wager +wages +wagon +wake +walk +wand +wasp +watch +water +wavy +wheat +whiff +whole +whoop +wick +widen +widow +width +wife +wifi +wilt +wimp +wind +wing +wink +wipe +wired +wiry +wise +wish +wispy +wok +wolf +womb +wool +woozy +word +work +worry +wound +woven +wrath +wreck +wrist +xerox +yahoo +yam +yard +year +yeast +yelp +yield +yo-yo +yodel +yoga +yoyo +yummy +zebra +zero +zesty +zippy +zone +zoom diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a681300b7..a87a21b2c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,6 +46,7 @@ set(keepassx_SOURCES core/ListDeleter.h core/Metadata.cpp core/PasswordGenerator.cpp + core/PassphraseGenerator.cpp core/SignalMultiplexer.cpp core/TimeDelta.cpp core/TimeInfo.cpp @@ -96,7 +97,6 @@ set(keepassx_SOURCES gui/MessageWidget.cpp gui/PasswordEdit.cpp gui/PasswordGeneratorWidget.cpp - gui/PasswordComboBox.cpp gui/SettingsWidget.cpp gui/SearchWidget.cpp gui/SortFilterHideProxyModel.cpp diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp new file mode 100644 index 000000000..1513338fe --- /dev/null +++ b/src/core/PassphraseGenerator.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2013 Felix Geyer + * + * 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 . + */ + +#include "PassphraseGenerator.h" + +#include +#include +#include + +#include "crypto/Random.h" +#include "core/FilePath.h" + +PassphraseGenerator::PassphraseGenerator() + : m_length(0) + , m_separator(' ') +{ + const QString path = filePath()->dataPath("wordlists/eff_large.wordlist"); + setWordlist(path); +} + +double PassphraseGenerator::calculateEntropy(QString passphrase) +{ + Q_UNUSED(passphrase); + + if (m_wordlist.size() == 0) { + return 0; + } + + return log(m_wordlist.size()) / log(2.0) * m_length; +} + +void PassphraseGenerator::setLength(int length) +{ + m_length = length; +} + +void PassphraseGenerator::setWordlist(QString path) +{ + m_wordlist.clear(); + + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("Couldn't load passphrase wordlist."); + return; + } + + QTextStream in(&file); + while (!in.atEnd()) { + m_wordlist.append(in.readLine()); + } + + if (m_wordlist.size() < 1000) { + qWarning("Wordlist too short!"); + return; + } +} + +void PassphraseGenerator::setWordseparator(QChar separator) { + m_separator = separator; +} + +QString PassphraseGenerator::generatePassphrase() const +{ + Q_ASSERT(isValid()); + + // In case there was an error loading the wordlist + if(m_wordlist.length() == 0) { + QString empty; + return empty; + } + + QString passphrase; + for (int i = 0; i < m_length; i ++) { + int word_index = randomGen()->randomUInt(m_wordlist.length()); + passphrase.append(m_wordlist.at(word_index)); + + if(i < m_length - 1) { + passphrase.append(m_separator); + } + } + + return passphrase; +} + +bool PassphraseGenerator::isValid() const +{ + if (m_length == 0) { + return false; + } + + if (m_wordlist.size() < 1000) { + return false; + } + + return true; +} diff --git a/src/gui/PasswordComboBox.h b/src/core/PassphraseGenerator.h similarity index 51% rename from src/gui/PasswordComboBox.h rename to src/core/PassphraseGenerator.h index f7f118edf..88e8654ee 100644 --- a/src/gui/PasswordComboBox.h +++ b/src/core/PassphraseGenerator.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2013 Michael Curtis - * Copyright (C) 2014 Felix Geyer + * Copyright (C) 2013 Felix Geyer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,31 +15,32 @@ * along with this program. If not, see . */ -#ifndef KEEPASSX_PASSWORDCOMBOBOX_H -#define KEEPASSX_PASSWORDCOMBOBOX_H +#ifndef KEEPASSX_PASSPHRASEGENERATOR_H +#define KEEPASSX_PASSPHRASEGENERATOR_H -#include +#include +#include +#include -class PasswordGenerator; - -class PasswordComboBox : public QComboBox +class PassphraseGenerator { - Q_OBJECT - public: - explicit PasswordComboBox(QWidget* parent = nullptr); - ~PasswordComboBox(); + PassphraseGenerator(); - void setGenerator(PasswordGenerator* generator); - void setNumberAlternatives(int alternatives); - void showPopup(); + double calculateEntropy(QString passphrase); + void setLength(int length); + void setWordlist(QString path); + void setWordseparator(QChar separator); + bool isValid() const; -public slots: - void setEcho(bool echo); + QString generatePassphrase() const; private: - PasswordGenerator* m_generator; - int m_alternatives; + int m_length; + QChar m_separator; + QVector m_wordlist; + + Q_DISABLE_COPY(PassphraseGenerator) }; -#endif // KEEPASSX_PASSWORDCOMBOBOX_H +#endif // KEEPASSX_PASSPHRASEGENERATOR_H \ No newline at end of file diff --git a/src/gui/PasswordComboBox.cpp b/src/gui/PasswordComboBox.cpp deleted file mode 100644 index 1f6c068f1..000000000 --- a/src/gui/PasswordComboBox.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2013 Michael Curtis - * Copyright (C) 2014 Felix Geyer - * - * 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 . - */ - -#include "PasswordComboBox.h" - -#include - -#include "core/PasswordGenerator.h" - -PasswordComboBox::PasswordComboBox(QWidget* parent) - : QComboBox(parent) - , m_generator(nullptr) - , m_alternatives(10) -{ - setEditable(true); - setEcho(false); -} - -PasswordComboBox::~PasswordComboBox() -{ -} - -void PasswordComboBox::setEcho(bool echo) -{ - lineEdit()->setEchoMode(echo ? QLineEdit::Normal : QLineEdit::Password); - - QString current = currentText(); - - if (echo) { - // add fake item to show visual indication that a popup is available - addItem(""); - -#ifdef Q_OS_MAC - // Qt on Mac OS doesn't seem to know the generic monospace family (tested with 4.8.6) - setStyleSheet("QComboBox { font-family: monospace,Menlo,Monaco; }"); -#else - setStyleSheet("QComboBox { font-family: monospace,Courier New; }"); -#endif - } - else { - // clear items so the combobox indicates that no popup menu is available - clear(); - - setStyleSheet("QComboBox { font-family: initial; }"); - } - - setEditText(current); -} - -void PasswordComboBox::setGenerator(PasswordGenerator* generator) -{ - m_generator = generator; -} - -void PasswordComboBox::setNumberAlternatives(int alternatives) -{ - m_alternatives = alternatives; -} - -void PasswordComboBox::showPopup() -{ - // no point in showing a bunch of hidden passwords - if (lineEdit()->echoMode() == QLineEdit::Password) { - hidePopup(); - return; - } - - // keep existing password as the first item in the popup - QString current = currentText(); - clear(); - addItem(current); - - if (m_generator && m_generator->isValid()) { - for (int alternative = 0; alternative < m_alternatives; alternative++) { - QString password = m_generator->generatePassword(); - - addItem(password); - } - } - - QComboBox::showPopup(); -} diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 11d50bae8..59960ff43 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -19,6 +19,7 @@ #include "ui_PasswordGeneratorWidget.h" #include +#include #include "core/Config.h" #include "core/PasswordGenerator.h" @@ -27,7 +28,8 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) : QWidget(parent) , m_updatingSpinBox(false) - , m_generator(new PasswordGenerator()) + , m_passwordGenerator(new PasswordGenerator()) + , m_dicewareGenerator(new PassphraseGenerator()) , m_ui(new Ui::PasswordGeneratorWidget()) { m_ui->setupUi(this); @@ -38,12 +40,18 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength(QString))); connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), SLOT(togglePasswordShown(bool))); connect(m_ui->buttonApply, SIGNAL(clicked()), SLOT(applyPassword())); - connect(m_ui->buttonGenerate, SIGNAL(clicked()), SLOT(generatePassword())); + connect(m_ui->buttonGenerate, SIGNAL(clicked()), SLOT(regeneratePassword())); - connect(m_ui->sliderLength, SIGNAL(valueChanged(int)), SLOT(sliderMoved())); - connect(m_ui->spinBoxLength, SIGNAL(valueChanged(int)), SLOT(spinBoxChanged())); + connect(m_ui->sliderLength, SIGNAL(valueChanged(int)), SLOT(passwordSliderMoved())); + connect(m_ui->spinBoxLength, SIGNAL(valueChanged(int)), SLOT(passwordSpinBoxChanged())); + connect(m_ui->sliderWordCount, SIGNAL(valueChanged(int)), SLOT(dicewareSliderMoved())); + connect(m_ui->spinBoxWordCount, SIGNAL(valueChanged(int)), SLOT(dicewareSpinBoxChanged())); + + connect(m_ui->comboBoxWordSeparator, SIGNAL(currentIndexChanged(int)), SLOT(updateGenerator())); + connect(m_ui->comboBoxWordList, SIGNAL(currentIndexChanged(int)), SLOT(updateGenerator())); connect(m_ui->optionButtons, SIGNAL(buttonClicked(int)), SLOT(updateGenerator())); + connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateGenerator())); // set font size of password quality and entropy labels dynamically to 80% of // the default font size, but make it no smaller than 8pt @@ -54,6 +62,12 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) m_ui->entropyLabel->setFont(defaultFont); m_ui->strengthLabel->setFont(defaultFont); } + + m_ui->comboBoxWordSeparator->addItems(QStringList() <<" " <<"#" <<";" <<"-" <<":" <<"." <<"@"); + + QDir path(filePath()->dataPath("wordlists/")); + QStringList files = path.entryList(QDir::Files); + m_ui->comboBoxWordList->addItems(files); loadSettings(); reset(); @@ -65,6 +79,7 @@ PasswordGeneratorWidget::~PasswordGeneratorWidget() void PasswordGeneratorWidget::loadSettings() { + // Password config m_ui->checkBoxLower->setChecked(config()->get("generator/LowerCase", true).toBool()); m_ui->checkBoxUpper->setChecked(config()->get("generator/UpperCase", true).toBool()); m_ui->checkBoxNumbers->setChecked(config()->get("generator/Numbers", true).toBool()); @@ -74,10 +89,19 @@ void PasswordGeneratorWidget::loadSettings() m_ui->checkBoxEnsureEvery->setChecked(config()->get("generator/EnsureEvery", true).toBool()); m_ui->spinBoxLength->setValue(config()->get("generator/Length", 16).toInt()); + + // Diceware config + m_ui->spinBoxWordCount->setValue(config()->get("generator/WordCount", 6).toInt()); + m_ui->comboBoxWordSeparator->setCurrentIndex(config()->get("generator/WordSeparator", 0).toInt()); + m_ui->comboBoxWordList->setCurrentText(config()->get("generator/WordList", "eff_large.wordlist").toString()); + + // Password or diceware? + m_ui->tabWidget->setCurrentIndex(config()->get("generator/Type", 1).toInt()); } void PasswordGeneratorWidget::saveSettings() { + // Password config config()->set("generator/LowerCase", m_ui->checkBoxLower->isChecked()); config()->set("generator/UpperCase", m_ui->checkBoxUpper->isChecked()); config()->set("generator/Numbers", m_ui->checkBoxNumbers->isChecked()); @@ -87,6 +111,14 @@ void PasswordGeneratorWidget::saveSettings() config()->set("generator/EnsureEvery", m_ui->checkBoxEnsureEvery->isChecked()); config()->set("generator/Length", m_ui->spinBoxLength->value()); + + // Diceware config + config()->set("generator/WordCount", m_ui->spinBoxWordCount->value()); + config()->set("generator/WordSeparator", m_ui->comboBoxWordSeparator->currentIndex()); + config()->set("generator/WordList", m_ui->comboBoxWordList->currentText()); + + // Password or diceware? + config()->set("generator/Type", m_ui->tabWidget->currentIndex()); } void PasswordGeneratorWidget::reset() @@ -108,11 +140,19 @@ void PasswordGeneratorWidget::setStandaloneMode(bool standalone) } void PasswordGeneratorWidget::regeneratePassword() -{ - if (m_generator->isValid()) { - QString password = m_generator->generatePassword(); - m_ui->editNewPassword->setText(password); - updatePasswordStrength(password); +{ + if (m_ui->tabWidget->currentIndex() == Password) { + if (m_passwordGenerator->isValid()) { + QString password = m_passwordGenerator->generatePassword(); + m_ui->editNewPassword->setText(password); + updatePasswordStrength(password); + } + } else { + if (m_dicewareGenerator->isValid()) { + QString password = m_dicewareGenerator->generatePassphrase(); + m_ui->editNewPassword->setText(password); + updatePasswordStrength(password); + } } } @@ -123,7 +163,13 @@ void PasswordGeneratorWidget::updateApplyEnabled(const QString& password) void PasswordGeneratorWidget::updatePasswordStrength(const QString& password) { - double entropy = m_generator->calculateEntropy(password); + double entropy = 0.0; + if (m_ui->tabWidget->currentIndex() == Password) { + entropy = m_passwordGenerator->calculateEntropy(password); + } else { + entropy = m_dicewareGenerator->calculateEntropy(password); + } + m_ui->entropyLabel->setText(tr("Entropy: %1 bit").arg(QString::number(entropy, 'f', 2))); if (entropy > m_ui->entropyProgressBar->maximum()) { @@ -134,14 +180,6 @@ void PasswordGeneratorWidget::updatePasswordStrength(const QString& password) colorStrengthIndicator(entropy); } -void PasswordGeneratorWidget::generatePassword() -{ - if (m_generator->isValid()) { - QString password = m_generator->generatePassword(); - m_ui->editNewPassword->setText(password); - } -} - void PasswordGeneratorWidget::applyPassword() { saveSettings(); @@ -149,7 +187,7 @@ void PasswordGeneratorWidget::applyPassword() emit dialogTerminated(); } -void PasswordGeneratorWidget::sliderMoved() +void PasswordGeneratorWidget::passwordSliderMoved() { if (m_updatingSpinBox) { return; @@ -160,7 +198,7 @@ void PasswordGeneratorWidget::sliderMoved() updateGenerator(); } -void PasswordGeneratorWidget::spinBoxChanged() +void PasswordGeneratorWidget::passwordSpinBoxChanged() { if (m_updatingSpinBox) { return; @@ -176,6 +214,20 @@ void PasswordGeneratorWidget::spinBoxChanged() updateGenerator(); } +void PasswordGeneratorWidget::dicewareSliderMoved() +{ + m_ui->spinBoxWordCount->setValue(m_ui->sliderWordCount->value()); + + updateGenerator(); +} + +void PasswordGeneratorWidget::dicewareSpinBoxChanged() +{ + m_ui->sliderWordCount->setValue(m_ui->spinBoxWordCount->value()); + + updateGenerator(); +} + void PasswordGeneratorWidget::togglePasswordShown(bool showing) { m_ui->editNewPassword->setShowPassword(showing); @@ -196,13 +248,13 @@ void PasswordGeneratorWidget::colorStrengthIndicator(double entropy) // Set the color and background based on entropy // colors are taking from the KDE breeze palette // - if (entropy < 35) { + if (entropy < 40) { m_ui->entropyProgressBar->setStyleSheet(style.arg("#c0392b")); m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Poor"))); - } else if (entropy >= 35 && entropy < 55) { + } else if (entropy >= 40 && entropy < 65) { m_ui->entropyProgressBar->setStyleSheet(style.arg("#f39c1f")); m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Weak"))); - } else if (entropy >= 55 && entropy < 100) { + } else if (entropy >= 65 && entropy < 100) { m_ui->entropyProgressBar->setStyleSheet(style.arg("#11d116")); m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Good"))); } else { @@ -251,44 +303,71 @@ PasswordGenerator::GeneratorFlags PasswordGeneratorWidget::generatorFlags() void PasswordGeneratorWidget::updateGenerator() { - PasswordGenerator::CharClasses classes = charClasses(); - PasswordGenerator::GeneratorFlags flags = generatorFlags(); + if (m_ui->tabWidget->currentIndex() == Password) { + PasswordGenerator::CharClasses classes = charClasses(); + PasswordGenerator::GeneratorFlags flags = generatorFlags(); - int minLength = 0; - if (flags.testFlag(PasswordGenerator::CharFromEveryGroup)) { - if (classes.testFlag(PasswordGenerator::LowerLetters)) { - minLength++; + int minLength = 0; + if (flags.testFlag(PasswordGenerator::CharFromEveryGroup)) { + if (classes.testFlag(PasswordGenerator::LowerLetters)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::UpperLetters)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::Numbers)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::SpecialCharacters)) { + minLength++; + } } - if (classes.testFlag(PasswordGenerator::UpperLetters)) { - minLength++; + minLength = qMax(minLength, 1); + + if (m_ui->spinBoxLength->value() < minLength) { + m_updatingSpinBox = true; + m_ui->spinBoxLength->setValue(minLength); + m_ui->sliderLength->setValue(minLength); + m_updatingSpinBox = false; } - if (classes.testFlag(PasswordGenerator::Numbers)) { - minLength++; + + m_ui->spinBoxLength->setMinimum(minLength); + m_ui->sliderLength->setMinimum(minLength); + + m_passwordGenerator->setLength(m_ui->spinBoxLength->value()); + m_passwordGenerator->setCharClasses(classes); + m_passwordGenerator->setFlags(flags); + + if (m_passwordGenerator->isValid()) { + m_ui->buttonGenerate->setEnabled(true); + } else { + m_ui->buttonGenerate->setEnabled(false); } - if (classes.testFlag(PasswordGenerator::SpecialCharacters)) { - minLength++; - } - } - minLength = qMax(minLength, 1); - - if (m_ui->spinBoxLength->value() < minLength) { - m_updatingSpinBox = true; - m_ui->spinBoxLength->setValue(minLength); - m_ui->sliderLength->setValue(minLength); - m_updatingSpinBox = false; - } - - m_ui->spinBoxLength->setMinimum(minLength); - m_ui->sliderLength->setMinimum(minLength); - - m_generator->setLength(m_ui->spinBoxLength->value()); - m_generator->setCharClasses(classes); - m_generator->setFlags(flags); - - if (m_generator->isValid()) { - m_ui->buttonGenerate->setEnabled(true); } else { - m_ui->buttonGenerate->setEnabled(false); + int minWordCount = 0; + + if (m_ui->spinBoxWordCount->value() < minWordCount) { + m_updatingSpinBox = true; + m_ui->spinBoxWordCount->setValue(minWordCount); + m_ui->sliderWordCount->setValue(minWordCount); + m_updatingSpinBox = false; + } + + m_ui->spinBoxWordCount->setMinimum(minWordCount); + m_ui->sliderWordCount->setMinimum(minWordCount); + + m_dicewareGenerator->setLength(m_ui->spinBoxWordCount->value()); + if (!m_ui->comboBoxWordList->currentText().isEmpty()) { + QString path = filePath()->dataPath("wordlists/" + m_ui->comboBoxWordList->currentText()); + m_dicewareGenerator->setWordlist(path); + } + m_dicewareGenerator->setWordseparator(m_ui->comboBoxWordSeparator->currentText().at(0)); + + if (m_dicewareGenerator->isValid()) { + m_ui->buttonGenerate->setEnabled(true); + } else { + m_ui->buttonGenerate->setEnabled(false); + } } regeneratePassword(); diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h index bfa6d684e..519f75f54 100644 --- a/src/gui/PasswordGeneratorWidget.h +++ b/src/gui/PasswordGeneratorWidget.h @@ -23,24 +23,32 @@ #include #include "core/PasswordGenerator.h" +#include "core/PassphraseGenerator.h" namespace Ui { class PasswordGeneratorWidget; } class PasswordGenerator; +class PassphraseGenerator; class PasswordGeneratorWidget : public QWidget { Q_OBJECT public: + enum GeneratorTypes + { + Password = 0, + Diceware = 1 + }; explicit PasswordGeneratorWidget(QWidget* parent = nullptr); ~PasswordGeneratorWidget(); void loadSettings(); void saveSettings(); void reset(); void setStandaloneMode(bool standalone); +public Q_SLOTS: void regeneratePassword(); signals: @@ -49,13 +57,14 @@ signals: private slots: void applyPassword(); - void generatePassword(); void updateApplyEnabled(const QString& password); void updatePasswordStrength(const QString& password); void togglePasswordShown(bool hidden); - void sliderMoved(); - void spinBoxChanged(); + void passwordSliderMoved(); + void passwordSpinBoxChanged(); + void dicewareSliderMoved(); + void dicewareSpinBoxChanged(); void colorStrengthIndicator(double entropy); void updateGenerator(); @@ -66,7 +75,8 @@ private: PasswordGenerator::CharClasses charClasses(); PasswordGenerator::GeneratorFlags generatorFlags(); - const QScopedPointer m_generator; + const QScopedPointer m_passwordGenerator; + const QScopedPointer m_dicewareGenerator; const QScopedPointer m_ui; }; diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index d45117802..0f803ba97 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -7,7 +7,7 @@ 0 0 575 - 284 + 305
@@ -31,14 +31,14 @@ - + QLayout::SetMinimumSize - 10 + 0 0 @@ -174,61 +174,6 @@ QProgressBar::chunk {
- - - - &Length: - - - spinBoxLength - - - - - - - 15 - - - 6 - - - - - 1 - - - 128 - - - 20 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 8 - - - - - - - 1 - - - 999 - - - 20 - - - - - @@ -239,197 +184,397 @@ QProgressBar::chunk { - + + + + 0 + 0 + + + + + 0 + 0 + + + + QTabWidget::North + + + QTabWidget::Rounded + + + 0 + + + + Random + + + + + + + + Character Types + + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Upper Case Letters + + + A-Z + + + true + + + optionButtons + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Lower Case Letters + + + a-z + + + true + + + optionButtons + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Numbers + + + 0-9 + + + true + + + optionButtons + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Special Characters + + + /*_& ... + + + true + + + optionButtons + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Exclude look-alike characters + + + optionButtons + + + + + + + Pick characters from every group + + + optionButtons + + + + + + + + + + + + 15 + + + 6 + + + + + &Length: + + + spinBoxLength + + + + + + + 1 + + + 128 + + + 20 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 8 + + + + + + + 1 + + + 999 + + + 20 + + + + + + + + + + Diceware + + + + + + + + + 0 + 0 + + + + Wordlist: + + + + + + + + 0 + 0 + + + + + + + + + + 15 + + + 6 + + + + + Word Count: + + + spinBoxLength + + + + + + + 1 + + + 40 + + + 6 + + + 6 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 8 + + + + + + + 1 + + + 100 + + + 6 + + + + + + + + + + + Word Separator: + + + + + + + + 0 + 0 + + + + + + + + + + + + - - - Character Types + + + Generate - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Upper Case Letters - - - A-Z - - - true - - - optionButtons - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Lower Case Letters - - - a-z - - - true - - - optionButtons - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Numbers - - - 0-9 - - - true - - - optionButtons - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Special Characters - - - /*_& ... - - - true - - - optionButtons - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Exclude look-alike characters - - - optionButtons - - - - - - - Pick characters from every group - - - optionButtons - - - - - - - - - Generate - - - - - - - false - - - Accept - - - - + + + false + + + Accept + + @@ -440,6 +585,7 @@ QProgressBar::chunk { PasswordEdit QLineEdit
gui/PasswordEdit.h
+ 1 @@ -452,9 +598,7 @@ QProgressBar::chunk { checkBoxNumbers checkBoxSpecialChars checkBoxExcludeAlike - checkBoxEnsureEvery buttonGenerate - buttonApply diff --git a/src/gui/PasswordGeneratorWidget.ui.autosave b/src/gui/PasswordGeneratorWidget.ui.autosave new file mode 100644 index 000000000..4c4ea1cd1 --- /dev/null +++ b/src/gui/PasswordGeneratorWidget.ui.autosave @@ -0,0 +1,615 @@ + + + PasswordGeneratorWidget + + + + 0 + 0 + 575 + 305 + + + + + 0 + 0 + + + + + 0 + 284 + + + + + 16777215 + 16777215 + + + + + + + + QLayout::SetMinimumSize + + + + + 0 + + + 0 + + + + + + 50 + 5 + + + + + 16777215 + 5 + + + + QProgressBar { + border: none; + height: 2px; + font-size: 1px; + background-color: transparent; + padding: 0 1px; +} +QProgressBar::chunk { + background-color: #c0392b; + border-radius: 2px; +} + + + 200 + + + 100 + + + false + + + Qt::Horizontal + + + false + + + QProgressBar::TopToBottom + + + %p% + + + + + + + Password: + + + editNewPassword + + + + + + + + + + 70 + 0 + + + + + 16777215 + 30 + + + + strength + + + Qt::PlainText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + 3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 70 + 0 + + + + entropy + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + 3 + + + + + + + + + 999 + + + + + + + true + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + QTabWidget::North + + + QTabWidget::Rounded + + + 0 + + + true + + + + Random + + + + + + + + Character Types + + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Upper Case Letters + + + A-Z + + + true + + + optionButtons + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Lower Case Letters + + + a-z + + + true + + + optionButtons + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Numbers + + + 0-9 + + + true + + + optionButtons + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Special Characters + + + /*_& ... + + + true + + + optionButtons + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Exclude look-alike characters + + + optionButtons + + + + + + + Pick characters from every group + + + optionButtons + + + + + + + + + + + + 15 + + + 6 + + + + + &Length: + + + spinBoxLength + + + + + + + 1 + + + 128 + + + 20 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 8 + + + + + + + 1 + + + 999 + + + 20 + + + + + + + + + + Diceware + + + + + + + + + 0 + 0 + + + + Wordlist: + + + + + + + + 0 + 0 + + + + + + + + + + 15 + + + 6 + + + + + Word Count: + + + spinBoxLength + + + + + + + 1 + + + 40 + + + 6 + + + 6 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 8 + + + + + + + 1 + + + 100 + + + 6 + + + + + + + + + + + Word Separator: + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + Generate + + + + + + + false + + + Accept + + + + + + + + + + PasswordEdit + QLineEdit +
gui/PasswordEdit.h
+ 1 +
+
+ + editNewPassword + togglePasswordButton + sliderLength + spinBoxLength + checkBoxUpper + checkBoxLower + checkBoxNumbers + checkBoxSpecialChars + checkBoxExcludeAlike + buttonGenerate + + + + + + + false + + + +
diff --git a/src/gui/entry/EditEntryWidgetMain.ui b/src/gui/entry/EditEntryWidgetMain.ui index 8df6c45a9..c22d4b8b1 100644 --- a/src/gui/entry/EditEntryWidgetMain.ui +++ b/src/gui/entry/EditEntryWidgetMain.ui @@ -148,6 +148,12 @@ 1
+ + + 0 + 100 + +
@@ -186,6 +192,7 @@ PasswordEdit QLineEdit
gui/PasswordEdit.h
+ 1 From be9bd16b4c5cd4fe49227aef7a727faebc7c0aac Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 4 Mar 2017 19:24:08 +0100 Subject: [PATCH 199/333] Add diceware tests --- src/gui/PasswordGeneratorWidget.cpp | 2 +- src/gui/PasswordGeneratorWidget.ui | 6 +- src/gui/PasswordGeneratorWidget.ui.autosave | 615 -------------------- tests/gui/TestGui.cpp | 47 +- tests/gui/TestGui.h | 3 +- 5 files changed, 52 insertions(+), 621 deletions(-) delete mode 100644 src/gui/PasswordGeneratorWidget.ui.autosave diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 59960ff43..dac7d0e0a 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -96,7 +96,7 @@ void PasswordGeneratorWidget::loadSettings() m_ui->comboBoxWordList->setCurrentText(config()->get("generator/WordList", "eff_large.wordlist").toString()); // Password or diceware? - m_ui->tabWidget->setCurrentIndex(config()->get("generator/Type", 1).toInt()); + m_ui->tabWidget->setCurrentIndex(config()->get("generator/Type", 0).toInt()); } void PasswordGeneratorWidget::saveSettings() diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 0f803ba97..5cd37ad06 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -204,9 +204,9 @@ QProgressBar::chunk { QTabWidget::Rounded - 0 + 1 - + Random @@ -442,7 +442,7 @@ QProgressBar::chunk {
- + Diceware diff --git a/src/gui/PasswordGeneratorWidget.ui.autosave b/src/gui/PasswordGeneratorWidget.ui.autosave deleted file mode 100644 index 4c4ea1cd1..000000000 --- a/src/gui/PasswordGeneratorWidget.ui.autosave +++ /dev/null @@ -1,615 +0,0 @@ - - - PasswordGeneratorWidget - - - - 0 - 0 - 575 - 305 - - - - - 0 - 0 - - - - - 0 - 284 - - - - - 16777215 - 16777215 - - - - - - - - QLayout::SetMinimumSize - - - - - 0 - - - 0 - - - - - - 50 - 5 - - - - - 16777215 - 5 - - - - QProgressBar { - border: none; - height: 2px; - font-size: 1px; - background-color: transparent; - padding: 0 1px; -} -QProgressBar::chunk { - background-color: #c0392b; - border-radius: 2px; -} - - - 200 - - - 100 - - - false - - - Qt::Horizontal - - - false - - - QProgressBar::TopToBottom - - - %p% - - - - - - - Password: - - - editNewPassword - - - - - - - - - - 70 - 0 - - - - - 16777215 - 30 - - - - strength - - - Qt::PlainText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - 3 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 70 - 0 - - - - entropy - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - 3 - - - - - - - - - 999 - - - - - - - true - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - QTabWidget::North - - - QTabWidget::Rounded - - - 0 - - - true - - - - Random - - - - - - - - Character Types - - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Upper Case Letters - - - A-Z - - - true - - - optionButtons - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Lower Case Letters - - - a-z - - - true - - - optionButtons - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Numbers - - - 0-9 - - - true - - - optionButtons - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Special Characters - - - /*_& ... - - - true - - - optionButtons - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Exclude look-alike characters - - - optionButtons - - - - - - - Pick characters from every group - - - optionButtons - - - - - - - - - - - - 15 - - - 6 - - - - - &Length: - - - spinBoxLength - - - - - - - 1 - - - 128 - - - 20 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 8 - - - - - - - 1 - - - 999 - - - 20 - - - - - - - - - - Diceware - - - - - - - - - 0 - 0 - - - - Wordlist: - - - - - - - - 0 - 0 - - - - - - - - - - 15 - - - 6 - - - - - Word Count: - - - spinBoxLength - - - - - - - 1 - - - 40 - - - 6 - - - 6 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 8 - - - - - - - 1 - - - 100 - - - 6 - - - - - - - - - - - Word Separator: - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - - Generate - - - - - - - false - - - Accept - - - - - - - - - - PasswordEdit - QLineEdit -
gui/PasswordEdit.h
- 1 -
-
- - editNewPassword - togglePasswordButton - sliderLength - spinBoxLength - checkBoxUpper - checkBoxLower - checkBoxNumbers - checkBoxSpecialChars - checkBoxExcludeAlike - buttonGenerate - - - - - - - false - - - -
diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 90f873197..4fd499f85 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -322,7 +323,7 @@ void TestGui::testAddEntry() QTRY_COMPARE(entryView->model()->rowCount(), 4); } -void TestGui::testEntryEntropy() +void TestGui::testPasswordEntryEntropy() { QToolBar* toolBar = m_mainWindow->findChild("toolBar"); @@ -396,6 +397,50 @@ void TestGui::testEntryEntropy() // We are done } +void TestGui::testDicewareEntryEntropy() +{ + QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + + // Find the new entry action + QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + QVERIFY(entryNewAction->isEnabled()); + + // Find the button associated with the new entry action + QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction); + QVERIFY(entryNewWidget->isVisible()); + QVERIFY(entryNewWidget->isEnabled()); + + // Click the new entry button and check that we enter edit mode + QTest::mouseClick(entryNewWidget, Qt::LeftButton); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); + + // Add entry "test" and confirm added + EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + QTest::keyClicks(titleEdit, "test"); + + // Open the password generator + QToolButton* generatorButton = editEntryWidget->findChild("togglePasswordGeneratorButton"); + QTest::mouseClick(generatorButton, Qt::LeftButton); + + // Select Diceware + QTabWidget* tabWidget = editEntryWidget->findChild("tabWidget"); + QWidget* dicewareWidget = editEntryWidget->findChild("dicewareWidget"); + tabWidget->setCurrentWidget(dicewareWidget); + + QComboBox* comboBoxWordList = dicewareWidget->findChild("comboBoxWordList"); + comboBoxWordList->setCurrentText("eff_large.wordlist"); + QSpinBox* spinBoxWordCount = dicewareWidget->findChild("spinBoxWordCount"); + spinBoxWordCount->setValue(6); + + // Type in some password + QLabel* entropyLabel = editEntryWidget->findChild("entropyLabel"); + QLabel* strengthLabel = editEntryWidget->findChild("strengthLabel"); + + QCOMPARE(entropyLabel->text(), QString("Entropy: 77.55 bit")); + QCOMPARE(strengthLabel->text(), QString("Password Quality: Good")); +} + void TestGui::testSearch() { // Add canned entries for consistent testing diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index d05ab8f58..fce5d69ee 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -44,7 +44,8 @@ private slots: void testTabs(); void testEditEntry(); void testAddEntry(); - void testEntryEntropy(); + void testPasswordEntryEntropy(); + void testDicewareEntryEntropy(); void testSearch(); void testDeleteEntry(); void testCloneEntry(); From 98e2c311c302a2802575a09a51db4aed1851209c Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 4 Mar 2017 22:28:41 +0100 Subject: [PATCH 200/333] fix wordCount instead of length --- src/core/PassphraseGenerator.cpp | 27 +++++++++++---------------- src/core/PassphraseGenerator.h | 4 ++-- src/gui/PasswordGeneratorWidget.cpp | 8 ++------ 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp index 1513338fe..716250f05 100644 --- a/src/core/PassphraseGenerator.cpp +++ b/src/core/PassphraseGenerator.cpp @@ -25,7 +25,7 @@ #include "core/FilePath.h" PassphraseGenerator::PassphraseGenerator() - : m_length(0) + : m_wordCount(0) , m_separator(' ') { const QString path = filePath()->dataPath("wordlists/eff_large.wordlist"); @@ -40,12 +40,12 @@ double PassphraseGenerator::calculateEntropy(QString passphrase) return 0; } - return log(m_wordlist.size()) / log(2.0) * m_length; + return log(m_wordlist.size()) / log(2.0) * m_wordCount; } -void PassphraseGenerator::setLength(int length) +void PassphraseGenerator::setWordCount(int wordCount) { - m_length = length; + m_wordCount = wordCount; } void PassphraseGenerator::setWordlist(QString path) @@ -79,26 +79,21 @@ QString PassphraseGenerator::generatePassphrase() const // In case there was an error loading the wordlist if(m_wordlist.length() == 0) { - QString empty; - return empty; + return QString(); } - QString passphrase; - for (int i = 0; i < m_length; i ++) { - int word_index = randomGen()->randomUInt(m_wordlist.length()); - passphrase.append(m_wordlist.at(word_index)); - - if(i < m_length - 1) { - passphrase.append(m_separator); - } + QStringList words; + for (int i = 0; i < m_wordCount; i++) { + int wordIndex = randomGen()->randomUInt(m_wordlist.length()); + words.append(m_wordlist.at(wordIndex)); } - return passphrase; + return words.join(m_separator); } bool PassphraseGenerator::isValid() const { - if (m_length == 0) { + if (m_wordCount == 0) { return false; } diff --git a/src/core/PassphraseGenerator.h b/src/core/PassphraseGenerator.h index 88e8654ee..729432922 100644 --- a/src/core/PassphraseGenerator.h +++ b/src/core/PassphraseGenerator.h @@ -28,7 +28,7 @@ public: PassphraseGenerator(); double calculateEntropy(QString passphrase); - void setLength(int length); + void setWordCount(int wordCount); void setWordlist(QString path); void setWordseparator(QChar separator); bool isValid() const; @@ -36,7 +36,7 @@ public: QString generatePassphrase() const; private: - int m_length; + int m_wordCount; QChar m_separator; QVector m_wordlist; diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index dac7d0e0a..59f444ebd 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -63,7 +63,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) m_ui->strengthLabel->setFont(defaultFont); } - m_ui->comboBoxWordSeparator->addItems(QStringList() <<" " <<"#" <<";" <<"-" <<":" <<"." <<"@"); + m_ui->comboBoxWordSeparator->addItems(QStringList() << " " << "#" << ";" << "-" << ":" << "." << "@"); QDir path(filePath()->dataPath("wordlists/")); QStringList files = path.entryList(QDir::Files); @@ -84,10 +84,8 @@ void PasswordGeneratorWidget::loadSettings() m_ui->checkBoxUpper->setChecked(config()->get("generator/UpperCase", true).toBool()); m_ui->checkBoxNumbers->setChecked(config()->get("generator/Numbers", true).toBool()); m_ui->checkBoxSpecialChars->setChecked(config()->get("generator/SpecialChars", false).toBool()); - m_ui->checkBoxExcludeAlike->setChecked(config()->get("generator/ExcludeAlike", true).toBool()); m_ui->checkBoxEnsureEvery->setChecked(config()->get("generator/EnsureEvery", true).toBool()); - m_ui->spinBoxLength->setValue(config()->get("generator/Length", 16).toInt()); // Diceware config @@ -106,10 +104,8 @@ void PasswordGeneratorWidget::saveSettings() config()->set("generator/UpperCase", m_ui->checkBoxUpper->isChecked()); config()->set("generator/Numbers", m_ui->checkBoxNumbers->isChecked()); config()->set("generator/SpecialChars", m_ui->checkBoxSpecialChars->isChecked()); - config()->set("generator/ExcludeAlike", m_ui->checkBoxExcludeAlike->isChecked()); config()->set("generator/EnsureEvery", m_ui->checkBoxEnsureEvery->isChecked()); - config()->set("generator/Length", m_ui->spinBoxLength->value()); // Diceware config @@ -356,7 +352,7 @@ void PasswordGeneratorWidget::updateGenerator() m_ui->spinBoxWordCount->setMinimum(minWordCount); m_ui->sliderWordCount->setMinimum(minWordCount); - m_dicewareGenerator->setLength(m_ui->spinBoxWordCount->value()); + m_dicewareGenerator->setWordCount(m_ui->spinBoxWordCount->value()); if (!m_ui->comboBoxWordList->currentText().isEmpty()) { QString path = filePath()->dataPath("wordlists/" + m_ui->comboBoxWordList->currentText()); m_dicewareGenerator->setWordlist(path); From 67c6e10f5fbb449300899109d2ff385670286d2f Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 8 Mar 2017 16:22:23 +0100 Subject: [PATCH 201/333] add underscore as separator --- src/gui/PasswordGeneratorWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 59f444ebd..2bc528e19 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -63,7 +63,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) m_ui->strengthLabel->setFont(defaultFont); } - m_ui->comboBoxWordSeparator->addItems(QStringList() << " " << "#" << ";" << "-" << ":" << "." << "@"); + m_ui->comboBoxWordSeparator->addItems(QStringList() << " " << "#" << "_" << ";" << "-" << ":" << "." << "@"); QDir path(filePath()->dataPath("wordlists/")); QStringList files = path.entryList(QDir::Files); From 0ba19cce4d66e63bcfe64de7ab9a4d107261645d Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 16 Mar 2017 23:34:28 +0100 Subject: [PATCH 202/333] fix wordlist file install --- share/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index 4ae4c5f09..9660d7509 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -15,7 +15,8 @@ add_subdirectory(translations) -install(FILES wordlists/*.wordlist DESTINATION ${DATA_INSTALL_DIR}/wordlists) +file(GLOB wordlists_files "wordlists/*.wordlist") +install(FILES ${wordlists_files} DESTINATION ${DATA_INSTALL_DIR}/wordlists) file(GLOB DATABASE_ICONS icons/database/*.png) From 044feea23a6a706fd8a67ebd9e80e0ae7b2f98b2 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 17 Mar 2017 00:12:55 +0100 Subject: [PATCH 203/333] remove short wordlists --- share/wordlists/eff_distant.wordlist | 1296 -------------------------- share/wordlists/eff_short.wordlist | 1296 -------------------------- src/core/PassphraseGenerator.cpp | 2 +- 3 files changed, 1 insertion(+), 2593 deletions(-) delete mode 100644 share/wordlists/eff_distant.wordlist delete mode 100644 share/wordlists/eff_short.wordlist diff --git a/share/wordlists/eff_distant.wordlist b/share/wordlists/eff_distant.wordlist deleted file mode 100644 index 9ac732fe3..000000000 --- a/share/wordlists/eff_distant.wordlist +++ /dev/null @@ -1,1296 +0,0 @@ -aardvark -abandoned -abbreviate -abdomen -abhorrence -abiding -abnormal -abrasion -absorbing -abundant -abyss -academy -accountant -acetone -achiness -acid -acoustics -acquire -acrobat -actress -acuteness -aerosol -aesthetic -affidavit -afloat -afraid -aftershave -again -agency -aggressor -aghast -agitate -agnostic -agonizing -agreeing -aidless -aimlessly -ajar -alarmclock -albatross -alchemy -alfalfa -algae -aliens -alkaline -almanac -alongside -alphabet -already -also -altitude -aluminum -always -amazingly -ambulance -amendment -amiable -ammunition -amnesty -amoeba -amplifier -amuser -anagram -anchor -android -anesthesia -angelfish -animal -anklet -announcer -anonymous -answer -antelope -anxiety -anyplace -aorta -apartment -apnea -apostrophe -apple -apricot -aquamarine -arachnid -arbitrate -ardently -arena -argument -aristocrat -armchair -aromatic -arrowhead -arsonist -artichoke -asbestos -ascend -aseptic -ashamed -asinine -asleep -asocial -asparagus -astronaut -asymmetric -atlas -atmosphere -atom -atrocious -attic -atypical -auctioneer -auditorium -augmented -auspicious -automobile -auxiliary -avalanche -avenue -aviator -avocado -awareness -awhile -awkward -awning -awoke -axially -azalea -babbling -backpack -badass -bagpipe -bakery -balancing -bamboo -banana -barracuda -basket -bathrobe -bazooka -blade -blender -blimp -blouse -blurred -boatyard -bobcat -body -bogusness -bohemian -boiler -bonnet -boots -borough -bossiness -bottle -bouquet -boxlike -breath -briefcase -broom -brushes -bubblegum -buckle -buddhist -buffalo -bullfrog -bunny -busboy -buzzard -cabin -cactus -cadillac -cafeteria -cage -cahoots -cajoling -cakewalk -calculator -camera -canister -capsule -carrot -cashew -cathedral -caucasian -caviar -ceasefire -cedar -celery -cement -census -ceramics -cesspool -chalkboard -cheesecake -chimney -chlorine -chopsticks -chrome -chute -cilantro -cinnamon -circle -cityscape -civilian -clay -clergyman -clipboard -clock -clubhouse -coathanger -cobweb -coconut -codeword -coexistent -coffeecake -cognitive -cohabitate -collarbone -computer -confetti -copier -cornea -cosmetics -cotton -couch -coverless -coyote -coziness -crawfish -crewmember -crib -croissant -crumble -crystal -cubical -cucumber -cuddly -cufflink -cuisine -culprit -cup -curry -cushion -cuticle -cybernetic -cyclist -cylinder -cymbal -cynicism -cypress -cytoplasm -dachshund -daffodil -dagger -dairy -dalmatian -dandelion -dartboard -dastardly -datebook -daughter -dawn -daytime -dazzler -dealer -debris -decal -dedicate -deepness -defrost -degree -dehydrator -deliverer -democrat -dentist -deodorant -depot -deranged -desktop -detergent -device -dexterity -diamond -dibs -dictionary -diffuser -digit -dilated -dimple -dinnerware -dioxide -diploma -directory -dishcloth -ditto -dividers -dizziness -doctor -dodge -doll -dominoes -donut -doorstep -dorsal -double -downstairs -dozed -drainpipe -dresser -driftwood -droppings -drum -dryer -dubiously -duckling -duffel -dugout -dumpster -duplex -durable -dustpan -dutiful -duvet -dwarfism -dwelling -dwindling -dynamite -dyslexia -eagerness -earlobe -easel -eavesdrop -ebook -eccentric -echoless -eclipse -ecosystem -ecstasy -edged -editor -educator -eelworm -eerie -effects -eggnog -egomaniac -ejection -elastic -elbow -elderly -elephant -elfishly -eliminator -elk -elliptical -elongated -elsewhere -elusive -elves -emancipate -embroidery -emcee -emerald -emission -emoticon -emperor -emulate -enactment -enchilada -endorphin -energy -enforcer -engine -enhance -enigmatic -enjoyably -enlarged -enormous -enquirer -enrollment -ensemble -entryway -enunciate -envoy -enzyme -epidemic -equipment -erasable -ergonomic -erratic -eruption -escalator -eskimo -esophagus -espresso -essay -estrogen -etching -eternal -ethics -etiquette -eucalyptus -eulogy -euphemism -euthanize -evacuation -evergreen -evidence -evolution -exam -excerpt -exerciser -exfoliate -exhale -exist -exorcist -explode -exquisite -exterior -exuberant -fabric -factory -faded -failsafe -falcon -family -fanfare -fasten -faucet -favorite -feasibly -february -federal -feedback -feigned -feline -femur -fence -ferret -festival -fettuccine -feudalist -feverish -fiberglass -fictitious -fiddle -figurine -fillet -finalist -fiscally -fixture -flashlight -fleshiness -flight -florist -flypaper -foamless -focus -foggy -folksong -fondue -footpath -fossil -fountain -fox -fragment -freeway -fridge -frosting -fruit -fryingpan -gadget -gainfully -gallstone -gamekeeper -gangway -garlic -gaslight -gathering -gauntlet -gearbox -gecko -gem -generator -geographer -gerbil -gesture -getaway -geyser -ghoulishly -gibberish -giddiness -giftshop -gigabyte -gimmick -giraffe -giveaway -gizmo -glasses -gleeful -glisten -glove -glucose -glycerin -gnarly -gnomish -goatskin -goggles -goldfish -gong -gooey -gorgeous -gosling -gothic -gourmet -governor -grape -greyhound -grill -groundhog -grumbling -guacamole -guerrilla -guitar -gullible -gumdrop -gurgling -gusto -gutless -gymnast -gynecology -gyration -habitat -hacking -haggard -haiku -halogen -hamburger -handgun -happiness -hardhat -hastily -hatchling -haughty -hazelnut -headband -hedgehog -hefty -heinously -helmet -hemoglobin -henceforth -herbs -hesitation -hexagon -hubcap -huddling -huff -hugeness -hullabaloo -human -hunter -hurricane -hushing -hyacinth -hybrid -hydrant -hygienist -hypnotist -ibuprofen -icepack -icing -iconic -identical -idiocy -idly -igloo -ignition -iguana -illuminate -imaging -imbecile -imitator -immigrant -imprint -iodine -ionosphere -ipad -iphone -iridescent -irksome -iron -irrigation -island -isotope -issueless -italicize -itemizer -itinerary -itunes -ivory -jabbering -jackrabbit -jaguar -jailhouse -jalapeno -jamboree -janitor -jarring -jasmine -jaundice -jawbreaker -jaywalker -jazz -jealous -jeep -jelly -jeopardize -jersey -jetski -jezebel -jiffy -jigsaw -jingling -jobholder -jockstrap -jogging -john -joinable -jokingly -journal -jovial -joystick -jubilant -judiciary -juggle -juice -jujitsu -jukebox -jumpiness -junkyard -juror -justifying -juvenile -kabob -kamikaze -kangaroo -karate -kayak -keepsake -kennel -kerosene -ketchup -khaki -kickstand -kilogram -kimono -kingdom -kiosk -kissing -kite -kleenex -knapsack -kneecap -knickers -koala -krypton -laboratory -ladder -lakefront -lantern -laptop -laryngitis -lasagna -latch -laundry -lavender -laxative -lazybones -lecturer -leftover -leggings -leisure -lemon -length -leopard -leprechaun -lettuce -leukemia -levers -lewdness -liability -library -licorice -lifeboat -lightbulb -likewise -lilac -limousine -lint -lioness -lipstick -liquid -listless -litter -liverwurst -lizard -llama -luau -lubricant -lucidity -ludicrous -luggage -lukewarm -lullaby -lumberjack -lunchbox -luridness -luscious -luxurious -lyrics -macaroni -maestro -magazine -mahogany -maimed -majority -makeover -malformed -mammal -mango -mapmaker -marbles -massager -matchstick -maverick -maximum -mayonnaise -moaning -mobilize -moccasin -modify -moisture -molecule -momentum -monastery -moonshine -mortuary -mosquito -motorcycle -mousetrap -movie -mower -mozzarella -muckiness -mudflow -mugshot -mule -mummy -mundane -muppet -mural -mustard -mutation -myriad -myspace -myth -nail -namesake -nanosecond -napkin -narrator -nastiness -natives -nautically -navigate -nearest -nebula -nectar -nefarious -negotiator -neither -nemesis -neoliberal -nephew -nervously -nest -netting -neuron -nevermore -nextdoor -nicotine -niece -nimbleness -nintendo -nirvana -nuclear -nugget -nuisance -nullify -numbing -nuptials -nursery -nutcracker -nylon -oasis -oat -obediently -obituary -object -obliterate -obnoxious -observer -obtain -obvious -occupation -oceanic -octopus -ocular -office -oftentimes -oiliness -ointment -older -olympics -omissible -omnivorous -oncoming -onion -onlooker -onstage -onward -onyx -oomph -opaquely -opera -opium -opossum -opponent -optical -opulently -oscillator -osmosis -ostrich -otherwise -ought -outhouse -ovation -oven -owlish -oxford -oxidize -oxygen -oyster -ozone -pacemaker -padlock -pageant -pajamas -palm -pamphlet -pantyhose -paprika -parakeet -passport -patio -pauper -pavement -payphone -pebble -peculiarly -pedometer -pegboard -pelican -penguin -peony -pepperoni -peroxide -pesticide -petroleum -pewter -pharmacy -pheasant -phonebook -phrasing -physician -plank -pledge -plotted -plug -plywood -pneumonia -podiatrist -poetic -pogo -poison -poking -policeman -poncho -popcorn -porcupine -postcard -poultry -powerboat -prairie -pretzel -princess -propeller -prune -pry -pseudo -psychopath -publisher -pucker -pueblo -pulley -pumpkin -punchbowl -puppy -purse -pushup -putt -puzzle -pyramid -python -quarters -quesadilla -quilt -quote -racoon -radish -ragweed -railroad -rampantly -rancidity -rarity -raspberry -ravishing -rearrange -rebuilt -receipt -reentry -refinery -register -rehydrate -reimburse -rejoicing -rekindle -relic -remote -renovator -reopen -reporter -request -rerun -reservoir -retriever -reunion -revolver -rewrite -rhapsody -rhetoric -rhino -rhubarb -rhyme -ribbon -riches -ridden -rigidness -rimmed -riptide -riskily -ritzy -riverboat -roamer -robe -rocket -romancer -ropelike -rotisserie -roundtable -royal -rubber -rudderless -rugby -ruined -rulebook -rummage -running -rupture -rustproof -sabotage -sacrifice -saddlebag -saffron -sainthood -saltshaker -samurai -sandworm -sapphire -sardine -sassy -satchel -sauna -savage -saxophone -scarf -scenario -schoolbook -scientist -scooter -scrapbook -sculpture -scythe -secretary -sedative -segregator -seismology -selected -semicolon -senator -septum -sequence -serpent -sesame -settler -severely -shack -shelf -shirt -shovel -shrimp -shuttle -shyness -siamese -sibling -siesta -silicon -simmering -singles -sisterhood -sitcom -sixfold -sizable -skateboard -skeleton -skies -skulk -skylight -slapping -sled -slingshot -sloth -slumbering -smartphone -smelliness -smitten -smokestack -smudge -snapshot -sneezing -sniff -snowsuit -snugness -speakers -sphinx -spider -splashing -sponge -sprout -spur -spyglass -squirrel -statue -steamboat -stingray -stopwatch -strawberry -student -stylus -suave -subway -suction -suds -suffocate -sugar -suitcase -sulphur -superstore -surfer -sushi -swan -sweatshirt -swimwear -sword -sycamore -syllable -symphony -synagogue -syringes -systemize -tablespoon -taco -tadpole -taekwondo -tagalong -takeout -tallness -tamale -tanned -tapestry -tarantula -tastebud -tattoo -tavern -thaw -theater -thimble -thorn -throat -thumb -thwarting -tiara -tidbit -tiebreaker -tiger -timid -tinsel -tiptoeing -tirade -tissue -tractor -tree -tripod -trousers -trucks -tryout -tubeless -tuesday -tugboat -tulip -tumbleweed -tupperware -turtle -tusk -tutorial -tuxedo -tweezers -twins -tyrannical -ultrasound -umbrella -umpire -unarmored -unbuttoned -uncle -underwear -unevenness -unflavored -ungloved -unhinge -unicycle -unjustly -unknown -unlocking -unmarked -unnoticed -unopened -unpaved -unquenched -unroll -unscrewing -untied -unusual -unveiled -unwrinkled -unyielding -unzip -upbeat -upcountry -update -upfront -upgrade -upholstery -upkeep -upload -uppercut -upright -upstairs -uptown -upwind -uranium -urban -urchin -urethane -urgent -urologist -username -usher -utensil -utility -utmost -utopia -utterance -vacuum -vagrancy -valuables -vanquished -vaporizer -varied -vaseline -vegetable -vehicle -velcro -vendor -vertebrae -vestibule -veteran -vexingly -vicinity -videogame -viewfinder -vigilante -village -vinegar -violin -viperfish -virus -visor -vitamins -vivacious -vixen -vocalist -vogue -voicemail -volleyball -voucher -voyage -vulnerable -waffle -wagon -wakeup -walrus -wanderer -wasp -water -waving -wheat -whisper -wholesaler -wick -widow -wielder -wifeless -wikipedia -wildcat -windmill -wipeout -wired -wishbone -wizardry -wobbliness -wolverine -womb -woolworker -workbasket -wound -wrangle -wreckage -wristwatch -wrongdoing -xerox -xylophone -yacht -yahoo -yard -yearbook -yesterday -yiddish -yield -yo-yo -yodel -yogurt -yuppie -zealot -zebra -zeppelin -zestfully -zigzagged -zillion -zipping -zirconium -zodiac -zombie -zookeeper -zucchini diff --git a/share/wordlists/eff_short.wordlist b/share/wordlists/eff_short.wordlist deleted file mode 100644 index 4c8baa4ce..000000000 --- a/share/wordlists/eff_short.wordlist +++ /dev/null @@ -1,1296 +0,0 @@ -acid -acorn -acre -acts -afar -affix -aged -agent -agile -aging -agony -ahead -aide -aids -aim -ajar -alarm -alias -alibi -alien -alike -alive -aloe -aloft -aloha -alone -amend -amino -ample -amuse -angel -anger -angle -ankle -apple -april -apron -aqua -area -arena -argue -arise -armed -armor -army -aroma -array -arson -art -ashen -ashes -atlas -atom -attic -audio -avert -avoid -awake -award -awoke -axis -bacon -badge -bagel -baggy -baked -baker -balmy -banjo -barge -barn -bash -basil -bask -batch -bath -baton -bats -blade -blank -blast -blaze -bleak -blend -bless -blimp -blink -bloat -blob -blog -blot -blunt -blurt -blush -boast -boat -body -boil -bok -bolt -boned -boney -bonus -bony -book -booth -boots -boss -botch -both -boxer -breed -bribe -brick -bride -brim -bring -brink -brisk -broad -broil -broke -brook -broom -brush -buck -bud -buggy -bulge -bulk -bully -bunch -bunny -bunt -bush -bust -busy -buzz -cable -cache -cadet -cage -cake -calm -cameo -canal -candy -cane -canon -cape -card -cargo -carol -carry -carve -case -cash -cause -cedar -chain -chair -chant -chaos -charm -chase -cheek -cheer -chef -chess -chest -chew -chief -chili -chill -chip -chomp -chop -chow -chuck -chump -chunk -churn -chute -cider -cinch -city -civic -civil -clad -claim -clamp -clap -clash -clasp -class -claw -clay -clean -clear -cleat -cleft -clerk -click -cling -clink -clip -cloak -clock -clone -cloth -cloud -clump -coach -coast -coat -cod -coil -coke -cola -cold -colt -coma -come -comic -comma -cone -cope -copy -coral -cork -cost -cot -couch -cough -cover -cozy -craft -cramp -crane -crank -crate -crave -crawl -crazy -creme -crepe -crept -crib -cried -crisp -crook -crop -cross -crowd -crown -crumb -crush -crust -cub -cult -cupid -cure -curl -curry -curse -curve -curvy -cushy -cut -cycle -dab -dad -daily -dairy -daisy -dance -dandy -darn -dart -dash -data -date -dawn -deaf -deal -dean -debit -debt -debug -decaf -decal -decay -deck -decor -decoy -deed -delay -denim -dense -dent -depth -derby -desk -dial -diary -dice -dig -dill -dime -dimly -diner -dingy -disco -dish -disk -ditch -ditzy -dizzy -dock -dodge -doing -doll -dome -donor -donut -dose -dot -dove -down -dowry -doze -drab -drama -drank -draw -dress -dried -drift -drill -drive -drone -droop -drove -drown -drum -dry -duck -duct -dude -dug -duke -duo -dusk -dust -duty -dwarf -dwell -eagle -early -earth -easel -east -eaten -eats -ebay -ebony -ebook -echo -edge -eel -eject -elbow -elder -elf -elk -elm -elope -elude -elves -email -emit -empty -emu -enter -entry -envoy -equal -erase -error -erupt -essay -etch -evade -even -evict -evil -evoke -exact -exit -fable -faced -fact -fade -fall -false -fancy -fang -fax -feast -feed -femur -fence -fend -ferry -fetal -fetch -fever -fiber -fifth -fifty -film -filth -final -finch -fit -five -flag -flaky -flame -flap -flask -fled -flick -fling -flint -flip -flirt -float -flock -flop -floss -flyer -foam -foe -fog -foil -folic -folk -food -fool -found -fox -foyer -frail -frame -fray -fresh -fried -frill -frisk -from -front -frost -froth -frown -froze -fruit -gag -gains -gala -game -gap -gas -gave -gear -gecko -geek -gem -genre -gift -gig -gills -given -giver -glad -glass -glide -gloss -glove -glow -glue -goal -going -golf -gong -good -gooey -goofy -gore -gown -grab -grain -grant -grape -graph -grasp -grass -grave -gravy -gray -green -greet -grew -grid -grief -grill -grip -grit -groom -grope -growl -grub -grunt -guide -gulf -gulp -gummy -guru -gush -gut -guy -habit -half -halo -halt -happy -harm -hash -hasty -hatch -hate -haven -hazel -hazy -heap -heat -heave -hedge -hefty -help -herbs -hers -hub -hug -hula -hull -human -humid -hump -hung -hunk -hunt -hurry -hurt -hush -hut -ice -icing -icon -icy -igloo -image -ion -iron -islam -issue -item -ivory -ivy -jab -jam -jaws -jazz -jeep -jelly -jet -jiffy -job -jog -jolly -jolt -jot -joy -judge -juice -juicy -july -jumbo -jump -junky -juror -jury -keep -keg -kept -kick -kilt -king -kite -kitty -kiwi -knee -knelt -koala -kung -ladle -lady -lair -lake -lance -land -lapel -large -lash -lasso -last -latch -late -lazy -left -legal -lemon -lend -lens -lent -level -lever -lid -life -lift -lilac -lily -limb -limes -line -lint -lion -lip -list -lived -liver -lunar -lunch -lung -lurch -lure -lurk -lying -lyric -mace -maker -malt -mama -mango -manor -many -map -march -mardi -marry -mash -match -mate -math -moan -mocha -moist -mold -mom -moody -mop -morse -most -motor -motto -mount -mouse -mousy -mouth -move -movie -mower -mud -mug -mulch -mule -mull -mumbo -mummy -mural -muse -music -musky -mute -nacho -nag -nail -name -nanny -nap -navy -near -neat -neon -nerd -nest -net -next -niece -ninth -nutty -oak -oasis -oat -ocean -oil -old -olive -omen -onion -only -ooze -opal -open -opera -opt -otter -ouch -ounce -outer -oval -oven -owl -ozone -pace -pagan -pager -palm -panda -panic -pants -panty -paper -park -party -pasta -patch -path -patio -payer -pecan -penny -pep -perch -perky -perm -pest -petal -petri -petty -photo -plank -plant -plaza -plead -plot -plow -pluck -plug -plus -poach -pod -poem -poet -pogo -point -poise -poker -polar -polio -polka -polo -pond -pony -poppy -pork -poser -pouch -pound -pout -power -prank -press -print -prior -prism -prize -probe -prong -proof -props -prude -prune -pry -pug -pull -pulp -pulse -puma -punch -punk -pupil -puppy -purr -purse -push -putt -quack -quake -query -quiet -quill -quilt -quit -quota -quote -rabid -race -rack -radar -radio -raft -rage -raid -rail -rake -rally -ramp -ranch -range -rank -rant -rash -raven -reach -react -ream -rebel -recap -relax -relay -relic -remix -repay -repel -reply -rerun -reset -rhyme -rice -rich -ride -rigid -rigor -rinse -riot -ripen -rise -risk -ritzy -rival -river -roast -robe -robin -rock -rogue -roman -romp -rope -rover -royal -ruby -rug -ruin -rule -runny -rush -rust -rut -sadly -sage -said -saint -salad -salon -salsa -salt -same -sandy -santa -satin -sauna -saved -savor -sax -say -scale -scam -scan -scare -scarf -scary -scoff -scold -scoop -scoot -scope -score -scorn -scout -scowl -scrap -scrub -scuba -scuff -sect -sedan -self -send -sepia -serve -set -seven -shack -shade -shady -shaft -shaky -sham -shape -share -sharp -shed -sheep -sheet -shelf -shell -shine -shiny -ship -shirt -shock -shop -shore -shout -shove -shown -showy -shred -shrug -shun -shush -shut -shy -sift -silk -silly -silo -sip -siren -sixth -size -skate -skew -skid -skier -skies -skip -skirt -skit -sky -slab -slack -slain -slam -slang -slash -slate -slaw -sled -sleek -sleep -sleet -slept -slice -slick -slimy -sling -slip -slit -slob -slot -slug -slum -slurp -slush -small -smash -smell -smile -smirk -smog -snack -snap -snare -snarl -sneak -sneer -sniff -snore -snort -snout -snowy -snub -snuff -speak -speed -spend -spent -spew -spied -spill -spiny -spoil -spoke -spoof -spool -spoon -sport -spot -spout -spray -spree -spur -squad -squat -squid -stack -staff -stage -stain -stall -stamp -stand -stank -stark -start -stash -state -stays -steam -steep -stem -step -stew -stick -sting -stir -stock -stole -stomp -stony -stood -stool -stoop -stop -storm -stout -stove -straw -stray -strut -stuck -stud -stuff -stump -stung -stunt -suds -sugar -sulk -surf -sushi -swab -swan -swarm -sway -swear -sweat -sweep -swell -swept -swim -swing -swipe -swirl -swoop -swore -syrup -tacky -taco -tag -take -tall -talon -tamer -tank -taper -taps -tarot -tart -task -taste -tasty -taunt -thank -thaw -theft -theme -thigh -thing -think -thong -thorn -those -throb -thud -thumb -thump -thus -tiara -tidal -tidy -tiger -tile -tilt -tint -tiny -trace -track -trade -train -trait -trap -trash -tray -treat -tree -trek -trend -trial -tribe -trick -trio -trout -truce -truck -trump -trunk -try -tug -tulip -tummy -turf -tusk -tutor -tutu -tux -tweak -tweet -twice -twine -twins -twirl -twist -uncle -uncut -undo -unify -union -unit -untie -upon -upper -urban -used -user -usher -utter -value -vapor -vegan -venue -verse -vest -veto -vice -video -view -viral -virus -visa -visor -vixen -vocal -voice -void -volt -voter -vowel -wad -wafer -wager -wages -wagon -wake -walk -wand -wasp -watch -water -wavy -wheat -whiff -whole -whoop -wick -widen -widow -width -wife -wifi -wilt -wimp -wind -wing -wink -wipe -wired -wiry -wise -wish -wispy -wok -wolf -womb -wool -woozy -word -work -worry -wound -woven -wrath -wreck -wrist -xerox -yahoo -yam -yard -year -yeast -yelp -yield -yo-yo -yodel -yoga -yoyo -yummy -zebra -zero -zesty -zippy -zone -zoom diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp index 716250f05..37f90c67f 100644 --- a/src/core/PassphraseGenerator.cpp +++ b/src/core/PassphraseGenerator.cpp @@ -63,7 +63,7 @@ void PassphraseGenerator::setWordlist(QString path) m_wordlist.append(in.readLine()); } - if (m_wordlist.size() < 1000) { + if (m_wordlist.size() < 4000) { qWarning("Wordlist too short!"); return; } From 1d32695f108b15d75e02cf4a5a1b6ce36c8cd634 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 17 Mar 2017 00:34:13 +0100 Subject: [PATCH 204/333] permit only wordcount > 0 --- src/core/PassphraseGenerator.cpp | 8 +++++++- src/gui/PasswordGeneratorWidget.cpp | 2 +- src/gui/PasswordGeneratorWidget.ui | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp index 37f90c67f..dca687226 100644 --- a/src/core/PassphraseGenerator.cpp +++ b/src/core/PassphraseGenerator.cpp @@ -45,7 +45,13 @@ double PassphraseGenerator::calculateEntropy(QString passphrase) void PassphraseGenerator::setWordCount(int wordCount) { - m_wordCount = wordCount; + if (wordCount > 0) { + m_wordCount = wordCount; + } else { + // safe default if something goes wrong + m_wordCount = 7; + } + } void PassphraseGenerator::setWordlist(QString path) diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 2bc528e19..423d6da26 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -340,7 +340,7 @@ void PasswordGeneratorWidget::updateGenerator() m_ui->buttonGenerate->setEnabled(false); } } else { - int minWordCount = 0; + int minWordCount = 1; if (m_ui->spinBoxWordCount->value() < minWordCount) { m_updatingSpinBox = true; diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 5cd37ad06..87111c0ab 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -204,7 +204,7 @@ QProgressBar::chunk { QTabWidget::Rounded - 1 + 0 From b6a7771a23f6e77da8edb8ab3433ddcf0e4219c9 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 17 Mar 2017 00:44:51 +0100 Subject: [PATCH 205/333] hide comboBoxWordList if only one worlist present --- src/gui/PasswordGeneratorWidget.cpp | 7 +++++++ src/gui/PasswordGeneratorWidget.ui | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 423d6da26..aa8e60856 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -68,6 +68,13 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) QDir path(filePath()->dataPath("wordlists/")); QStringList files = path.entryList(QDir::Files); m_ui->comboBoxWordList->addItems(files); + if (files.size() > 1) { + m_ui->comboBoxWordList->setVisible(true); + m_ui->labelWordList->setVisible(true); + } else { + m_ui->comboBoxWordList->setVisible(false); + m_ui->labelWordList->setVisible(false); + } loadSettings(); reset(); diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 87111c0ab..f23a4b0e8 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -450,7 +450,7 @@ QProgressBar::chunk { - + 0 From 7541f57aebf7e318e487830952cd59de5fb2ceca Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 17 Mar 2017 01:34:33 +0100 Subject: [PATCH 206/333] some UI fix --- src/gui/PasswordGeneratorWidget.ui | 214 +++++++++++++++-------------- 1 file changed, 112 insertions(+), 102 deletions(-) diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index f23a4b0e8..2e9711bef 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -208,7 +208,7 @@ QProgressBar::chunk { - Random + Password @@ -444,112 +444,109 @@ QProgressBar::chunk { - Diceware + Passphrase - - - - 0 - 0 - - - - Wordlist: - - - - - - - - 0 - 0 - - - - - - - - - - 15 - - - 6 - - - - - Word Count: - - - spinBoxLength - - - - - - - 1 - - - 40 - - - 6 - - - 6 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 8 - - - - - - - 1 - - - 100 - - - 6 - - - - - - - - - - - Word Separator: - - - - - - - - 0 - 0 - - - + + + + + + 0 + 0 + + + + Wordlist: + + + + + + + + 0 + 0 + + + + + + + + Word Count: + + + spinBoxLength + + + + + + + QLayout::SetMinimumSize + + + + + 1 + + + 40 + + + 6 + + + 6 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 8 + + + + + + + 1 + + + 100 + + + 6 + + + + + + + + + Word Separator: + + + + + + + + 0 + 0 + + + + + @@ -559,6 +556,19 @@ QProgressBar::chunk { + + + + Qt::Horizontal + + + + 40 + 20 + + + + From 8937647d5f176692f6e89a1a58f5d7334c62e371 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 17 Mar 2017 01:43:50 +0100 Subject: [PATCH 207/333] let every string as separator --- src/core/PassphraseGenerator.cpp | 2 +- src/core/PassphraseGenerator.h | 4 ++-- src/gui/PasswordGeneratorWidget.cpp | 11 ++++++----- src/gui/PasswordGeneratorWidget.ui | 9 +++------ 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp index dca687226..6585809b8 100644 --- a/src/core/PassphraseGenerator.cpp +++ b/src/core/PassphraseGenerator.cpp @@ -75,7 +75,7 @@ void PassphraseGenerator::setWordlist(QString path) } } -void PassphraseGenerator::setWordseparator(QChar separator) { +void PassphraseGenerator::setWordseparator(QString separator) { m_separator = separator; } diff --git a/src/core/PassphraseGenerator.h b/src/core/PassphraseGenerator.h index 729432922..be1c1e677 100644 --- a/src/core/PassphraseGenerator.h +++ b/src/core/PassphraseGenerator.h @@ -30,14 +30,14 @@ public: double calculateEntropy(QString passphrase); void setWordCount(int wordCount); void setWordlist(QString path); - void setWordseparator(QChar separator); + void setWordseparator(QString separator); bool isValid() const; QString generatePassphrase() const; private: int m_wordCount; - QChar m_separator; + QString m_separator; QVector m_wordlist; Q_DISABLE_COPY(PassphraseGenerator) diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index aa8e60856..52fb4ab3f 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -48,7 +48,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) connect(m_ui->sliderWordCount, SIGNAL(valueChanged(int)), SLOT(dicewareSliderMoved())); connect(m_ui->spinBoxWordCount, SIGNAL(valueChanged(int)), SLOT(dicewareSpinBoxChanged())); - connect(m_ui->comboBoxWordSeparator, SIGNAL(currentIndexChanged(int)), SLOT(updateGenerator())); + connect(m_ui->editWordSeparator, SIGNAL(textChanged(QString)), SLOT(updateGenerator())); connect(m_ui->comboBoxWordList, SIGNAL(currentIndexChanged(int)), SLOT(updateGenerator())); connect(m_ui->optionButtons, SIGNAL(buttonClicked(int)), SLOT(updateGenerator())); connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateGenerator())); @@ -63,7 +63,8 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) m_ui->strengthLabel->setFont(defaultFont); } - m_ui->comboBoxWordSeparator->addItems(QStringList() << " " << "#" << "_" << ";" << "-" << ":" << "." << "@"); + // set default separator to Space + m_ui->editWordSeparator->setText(" "); QDir path(filePath()->dataPath("wordlists/")); QStringList files = path.entryList(QDir::Files); @@ -97,7 +98,7 @@ void PasswordGeneratorWidget::loadSettings() // Diceware config m_ui->spinBoxWordCount->setValue(config()->get("generator/WordCount", 6).toInt()); - m_ui->comboBoxWordSeparator->setCurrentIndex(config()->get("generator/WordSeparator", 0).toInt()); + m_ui->editWordSeparator->setText(config()->get("generator/WordSeparator", " ").toString()); m_ui->comboBoxWordList->setCurrentText(config()->get("generator/WordList", "eff_large.wordlist").toString()); // Password or diceware? @@ -117,7 +118,7 @@ void PasswordGeneratorWidget::saveSettings() // Diceware config config()->set("generator/WordCount", m_ui->spinBoxWordCount->value()); - config()->set("generator/WordSeparator", m_ui->comboBoxWordSeparator->currentIndex()); + config()->set("generator/WordSeparator", m_ui->editWordSeparator->text()); config()->set("generator/WordList", m_ui->comboBoxWordList->currentText()); // Password or diceware? @@ -364,7 +365,7 @@ void PasswordGeneratorWidget::updateGenerator() QString path = filePath()->dataPath("wordlists/" + m_ui->comboBoxWordList->currentText()); m_dicewareGenerator->setWordlist(path); } - m_dicewareGenerator->setWordseparator(m_ui->comboBoxWordSeparator->currentText().at(0)); + m_dicewareGenerator->setWordseparator(m_ui->editWordSeparator->text()); if (m_dicewareGenerator->isValid()) { m_ui->buttonGenerate->setEnabled(true); diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 2e9711bef..6d47cd654 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -537,12 +537,9 @@ QProgressBar::chunk { - - - - 0 - 0 - + + + From 19d1fa8e7d9e68b0de429db80a45b3b7d49405f3 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 17 Mar 2017 12:51:25 +0100 Subject: [PATCH 208/333] change button position --- src/gui/PasswordGeneratorWidget.ui | 728 +++++++++++++++-------------- 1 file changed, 367 insertions(+), 361 deletions(-) diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 6d47cd654..bb8bc0e76 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -184,68 +184,188 @@ QProgressBar::chunk { - - - - 0 - 0 - - - - - 0 - 0 - - - - QTabWidget::North - - - QTabWidget::Rounded - - - 0 - - - - Password - - - - - - - - Character Types - - - - + + + + + + 0 + 0 + + + + + 0 + 0 + + + + QTabWidget::North + + + QTabWidget::Rounded + + + 0 + + + + Password + + + + + + + + Character Types + + - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Upper Case Letters - + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Upper Case Letters + + + A-Z + + + true + + + optionButtons + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Lower Case Letters + + + a-z + + + true + + + optionButtons + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Numbers + + + 0-9 + + + true + + + optionButtons + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Special Characters + + + /*_& ... + + + true + + + optionButtons + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + - A-Z - - - true + Exclude look-alike characters optionButtons @@ -253,338 +373,225 @@ QProgressBar::chunk { - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Lower Case Letters - + - a-z - - - true + Pick characters from every group optionButtons - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Numbers - - - 0-9 - - - true - - - optionButtons - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Special Characters - - - /*_& ... - - - true - - - optionButtons - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - Exclude look-alike characters - - - optionButtons - - - - - - - Pick characters from every group - - - optionButtons - - - - - - - - - - - - 15 - - - 6 - - - - - &Length: - - - spinBoxLength - - - - - - - 1 - - - 128 - - - 20 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 8 - - - - - - - 1 - - - 999 - - - 20 - - - - - - - - - - Passphrase - - - - - - - - - - - 0 - 0 - - - - Wordlist: - - - - - - 0 - 0 - - - - - - + + + + + + 15 + + + 6 + + + - Word Count: + &Length: spinBoxLength - - - - QLayout::SetMinimumSize + + + + 1 - - - - 1 - - - 40 - - - 6 - - - 6 - - - Qt::Horizontal - - - QSlider::TicksBelow - - - 8 - - - - - - - 1 - - - 100 - - - 6 - - - - - - - - - Word Separator: + + 128 + + + 20 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 8 - - - - + + + + 1 + + + 999 + + + 20 + + + + Passphrase + + + + + + + + + + + 0 + 0 + + + + Wordlist: + + + + + + + + 0 + 0 + + + + + + + + Word Count: + + + spinBoxLength + + + + + + + QLayout::SetMinimumSize + + + + + 1 + + + 40 + + + 6 + + + 6 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 8 + + + + + + + 1 + + + 100 + + + 6 + + + + + + + + + Word Separator: + + + + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Generate + + + + + + + false + + + Accept + + - -
-
- - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Generate - - - - - - - false - - - Accept - - + + +
@@ -605,7 +612,6 @@ QProgressBar::chunk { checkBoxNumbers checkBoxSpecialChars checkBoxExcludeAlike - buttonGenerate From 0c755846fe8e759cbd9aaec32670bb28701a5047 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 22 Mar 2017 00:04:36 +0100 Subject: [PATCH 209/333] fix camel case --- src/core/PassphraseGenerator.cpp | 6 +++--- src/core/PassphraseGenerator.h | 4 ++-- src/gui/PasswordGeneratorWidget.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp index 6585809b8..ba403389d 100644 --- a/src/core/PassphraseGenerator.cpp +++ b/src/core/PassphraseGenerator.cpp @@ -29,7 +29,7 @@ PassphraseGenerator::PassphraseGenerator() , m_separator(' ') { const QString path = filePath()->dataPath("wordlists/eff_large.wordlist"); - setWordlist(path); + setWordList(path); } double PassphraseGenerator::calculateEntropy(QString passphrase) @@ -54,7 +54,7 @@ void PassphraseGenerator::setWordCount(int wordCount) } -void PassphraseGenerator::setWordlist(QString path) +void PassphraseGenerator::setWordList(QString path) { m_wordlist.clear(); @@ -75,7 +75,7 @@ void PassphraseGenerator::setWordlist(QString path) } } -void PassphraseGenerator::setWordseparator(QString separator) { +void PassphraseGenerator::setWordSeparator(QString separator) { m_separator = separator; } diff --git a/src/core/PassphraseGenerator.h b/src/core/PassphraseGenerator.h index be1c1e677..e814a5873 100644 --- a/src/core/PassphraseGenerator.h +++ b/src/core/PassphraseGenerator.h @@ -29,8 +29,8 @@ public: double calculateEntropy(QString passphrase); void setWordCount(int wordCount); - void setWordlist(QString path); - void setWordseparator(QString separator); + void setWordList(QString path); + void setWordSeparator(QString separator); bool isValid() const; QString generatePassphrase() const; diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 52fb4ab3f..1f5606cf6 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -363,9 +363,9 @@ void PasswordGeneratorWidget::updateGenerator() m_dicewareGenerator->setWordCount(m_ui->spinBoxWordCount->value()); if (!m_ui->comboBoxWordList->currentText().isEmpty()) { QString path = filePath()->dataPath("wordlists/" + m_ui->comboBoxWordList->currentText()); - m_dicewareGenerator->setWordlist(path); + m_dicewareGenerator->setWordList(path); } - m_dicewareGenerator->setWordseparator(m_ui->editWordSeparator->text()); + m_dicewareGenerator->setWordSeparator(m_ui->editWordSeparator->text()); if (m_dicewareGenerator->isValid()) { m_ui->buttonGenerate->setEnabled(true); From 5776f43b98733d3cf2834ad11fcc5ae83b18974d Mon Sep 17 00:00:00 2001 From: Weslly Date: Tue, 21 Mar 2017 23:01:44 -0300 Subject: [PATCH 210/333] Add proxy icon to title bar in OSX --- src/gui/DatabaseTabWidget.cpp | 10 ++++++++++ src/gui/DatabaseTabWidget.h | 1 + src/gui/MainWindow.cpp | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index ea4723e5c..2c14ace16 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -552,6 +552,16 @@ bool DatabaseTabWidget::isModified(int index) return indexDatabaseManagerStruct(index).modified; } +QString DatabaseTabWidget::databasePath(int index) +{ + if (index == -1) { + index = currentIndex(); + } + + return indexDatabaseManagerStruct(index).filePath; +} + + void DatabaseTabWidget::updateTabName(Database* db) { int index = databaseIndex(db); diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 443fc73e5..ea8f60030 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -81,6 +81,7 @@ public slots: bool isModified(int index = -1); void performGlobalAutoType(); void lockDatabases(); + QString databasePath(int index = -1); signals: void tabNameChanged(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0f883e4aa..712566e5f 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -530,6 +530,12 @@ void MainWindow::updateWindowTitle() windowTitle = QString("%1 - %2").arg(customWindowTitlePart, BaseWindowTitle); } + if (customWindowTitlePart.isEmpty() || stackedWidgetIndex == 1) { + setWindowFilePath(""); + } else { + setWindowFilePath(m_ui->tabWidget->databasePath(tabWidgetIndex)); + } + setWindowModified(m_ui->tabWidget->isModified(tabWidgetIndex)); setWindowTitle(windowTitle); From 9307834ef0d1d8646ab8421a94f38c44bbee1eed Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Sun, 19 Mar 2017 22:04:03 +0100 Subject: [PATCH 211/333] Add a button for CSV import option --- src/gui/MainWindow.cpp | 7 +++++++ src/gui/MainWindow.h | 1 + src/gui/WelcomeWidget.cpp | 3 ++- src/gui/WelcomeWidget.h | 11 ++++++----- src/gui/WelcomeWidget.ui | 7 +++++++ 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 712566e5f..05071cc49 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -301,6 +301,7 @@ MainWindow::MainWindow() connect(m_ui->welcomeWidget, SIGNAL(openDatabase()), SLOT(switchToOpenDatabase())); connect(m_ui->welcomeWidget, SIGNAL(openDatabaseFile(QString)), SLOT(switchToDatabaseFile(QString))); connect(m_ui->welcomeWidget, SIGNAL(importKeePass1Database()), SLOT(switchToKeePass1Database())); + connect(m_ui->welcomeWidget, SIGNAL(importCsv()), SLOT(switchToCsvImport())); connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog())); @@ -605,6 +606,12 @@ void MainWindow::switchToKeePass1Database() switchToDatabases(); } +void MainWindow::switchToCsvImport() +{ + m_ui->tabWidget->importCsv(); + switchToDatabases(); +} + void MainWindow::databaseStatusChanged(DatabaseWidget *) { updateTrayIcon(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index cc7037959..388e1c8a4 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -65,6 +65,7 @@ private slots: void switchToOpenDatabase(); void switchToDatabaseFile(QString file); void switchToKeePass1Database(); + void switchToCsvImport(); void closePasswordGen(); void databaseStatusChanged(DatabaseWidget *dbWidget); void databaseTabChanged(int tabIndex); diff --git a/src/gui/WelcomeWidget.cpp b/src/gui/WelcomeWidget.cpp index d327ea84c..4629ffa28 100644 --- a/src/gui/WelcomeWidget.cpp +++ b/src/gui/WelcomeWidget.cpp @@ -51,6 +51,7 @@ WelcomeWidget::WelcomeWidget(QWidget* parent) connect(m_ui->buttonNewDatabase, SIGNAL(clicked()), SIGNAL(newDatabase())); connect(m_ui->buttonOpenDatabase, SIGNAL(clicked()), SIGNAL(openDatabase())); connect(m_ui->buttonImportKeePass1, SIGNAL(clicked()), SIGNAL(importKeePass1Database())); + connect(m_ui->buttonImportCSV, SIGNAL(clicked()), SIGNAL(importCsv())); connect(m_ui->recentListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(openDatabaseFromFile(QListWidgetItem*))); } @@ -65,4 +66,4 @@ void WelcomeWidget::openDatabaseFromFile(QListWidgetItem* item) return; } emit openDatabaseFile(item->text()); -} \ No newline at end of file +} diff --git a/src/gui/WelcomeWidget.h b/src/gui/WelcomeWidget.h index 73c8c4f9b..98d87acaa 100644 --- a/src/gui/WelcomeWidget.h +++ b/src/gui/WelcomeWidget.h @@ -34,13 +34,14 @@ public: ~WelcomeWidget(); signals: - void newDatabase(); - void openDatabase(); - void openDatabaseFile(QString); - void importKeePass1Database(); + void newDatabase(); + void openDatabase(); + void openDatabaseFile(QString); + void importKeePass1Database(); + void importCsv(); private slots: - void openDatabaseFromFile(QListWidgetItem* item); + void openDatabaseFromFile(QListWidgetItem* item); private: const QScopedPointer m_ui; diff --git a/src/gui/WelcomeWidget.ui b/src/gui/WelcomeWidget.ui index b08ec917e..4800ea3df 100644 --- a/src/gui/WelcomeWidget.ui +++ b/src/gui/WelcomeWidget.ui @@ -127,6 +127,13 @@
+ + + + Import from plain text CSV file + + + From e5c2b4457207f0eaadf31ce113ad652861ebd077 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Tue, 21 Mar 2017 23:54:30 +0100 Subject: [PATCH 212/333] Commit review request --- src/gui/MainWindow.cpp | 4 ++-- src/gui/MainWindow.h | 2 +- src/gui/WelcomeWidget.ui | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 05071cc49..0568ba0c3 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -301,7 +301,7 @@ MainWindow::MainWindow() connect(m_ui->welcomeWidget, SIGNAL(openDatabase()), SLOT(switchToOpenDatabase())); connect(m_ui->welcomeWidget, SIGNAL(openDatabaseFile(QString)), SLOT(switchToDatabaseFile(QString))); connect(m_ui->welcomeWidget, SIGNAL(importKeePass1Database()), SLOT(switchToKeePass1Database())); - connect(m_ui->welcomeWidget, SIGNAL(importCsv()), SLOT(switchToCsvImport())); + connect(m_ui->welcomeWidget, SIGNAL(importCsv()), SLOT(switchToImportCsv())); connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog())); @@ -606,7 +606,7 @@ void MainWindow::switchToKeePass1Database() switchToDatabases(); } -void MainWindow::switchToCsvImport() +void MainWindow::switchToImportCsv() { m_ui->tabWidget->importCsv(); switchToDatabases(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 388e1c8a4..f69fe720f 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -65,7 +65,7 @@ private slots: void switchToOpenDatabase(); void switchToDatabaseFile(QString file); void switchToKeePass1Database(); - void switchToCsvImport(); + void switchToImportCsv(); void closePasswordGen(); void databaseStatusChanged(DatabaseWidget *dbWidget); void databaseTabChanged(int tabIndex); diff --git a/src/gui/WelcomeWidget.ui b/src/gui/WelcomeWidget.ui index 4800ea3df..2b12e2a3c 100644 --- a/src/gui/WelcomeWidget.ui +++ b/src/gui/WelcomeWidget.ui @@ -130,7 +130,7 @@ - Import from plain text CSV file + Import from CSV From e48f86b289936bade6d65990dd1fcaea78849889 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Wed, 22 Mar 2017 22:32:50 +0100 Subject: [PATCH 213/333] Horizontal layout for import buttons --- src/gui/WelcomeWidget.ui | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/gui/WelcomeWidget.ui b/src/gui/WelcomeWidget.ui index 2b12e2a3c..da6bc859c 100644 --- a/src/gui/WelcomeWidget.ui +++ b/src/gui/WelcomeWidget.ui @@ -121,18 +121,22 @@ - - - Import from KeePass 1 - - - - - - - Import from CSV - - + + + + + Import from KeePass 1 + + + + + + + Import from CSV + + + + From 07dafd697ea33f43df9d4a0e13aa8f877d16e4a6 Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Thu, 23 Mar 2017 06:58:36 +0200 Subject: [PATCH 214/333] Treat empty booleans in XML as false --- src/format/KeePass2XmlReader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index dca387b19..e4ceb1762 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -1037,6 +1037,9 @@ bool KeePass2XmlReader::readBool() else if (str.compare("False", Qt::CaseInsensitive) == 0) { return false; } + else if (str.length() == 0) { + return false; + } else { raiseError("Invalid bool value"); return false; From 572212c374dfd2eab2971df7ab042d3f88fed26b Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 26 Mar 2017 09:55:06 -0400 Subject: [PATCH 215/333] Updated Dockerfile to meet new library requirements --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9623b60dd..486546b6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,10 +32,11 @@ RUN set -x \ qt58base \ qt58tools \ qt58x11extras \ - libmicrohttpd-dev \ libxi-dev \ libxtst-dev \ zlib1g-dev \ + libyubikey-dev \ + libykpers-1-dev \ wget \ file \ fuse \ From 1418712b4ce83ac8e36332284f802c6a9246974c Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 26 Mar 2017 23:25:17 -0400 Subject: [PATCH 216/333] Added xvfb to Dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 486546b6a..8602d44a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,7 @@ RUN set -x \ zlib1g-dev \ libyubikey-dev \ libykpers-1-dev \ + xvfb \ wget \ file \ fuse \ From 7ca13b3d513fafb0d2815d5da656d6c34c6b0ca2 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sun, 26 Mar 2017 22:55:56 +0200 Subject: [PATCH 217/333] fix RecentDatabases, closes #386 --- src/gui/MainWindow.cpp | 5 +++++ src/gui/WelcomeWidget.cpp | 22 ++++++++++++++-------- src/gui/WelcomeWidget.h | 1 + 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0568ba0c3..d68d18b46 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -378,6 +378,11 @@ void MainWindow::openRecentDatabase(QAction* action) void MainWindow::clearLastDatabases() { config()->set("LastDatabases", QVariant()); + bool inWelcomeWidget = (m_ui->stackedWidget->currentIndex() == 2); + + if (inWelcomeWidget) { + m_ui->welcomeWidget->refreshLastDatabases(); + } } void MainWindow::openDatabase(const QString& fileName, const QString& pw, const QString& keyFile) diff --git a/src/gui/WelcomeWidget.cpp b/src/gui/WelcomeWidget.cpp index 4629ffa28..96bf0a206 100644 --- a/src/gui/WelcomeWidget.cpp +++ b/src/gui/WelcomeWidget.cpp @@ -36,13 +36,8 @@ WelcomeWidget::WelcomeWidget(QWidget* parent) m_ui->iconLabel->setPixmap(filePath()->applicationIcon().pixmap(64)); - m_ui->recentListWidget->clear(); - const QStringList lastDatabases = config()->get("LastDatabases", QVariant()).toStringList(); - for (const QString& database : lastDatabases) { - QListWidgetItem *itm = new QListWidgetItem; - itm->setText(database); - m_ui->recentListWidget->addItem(itm); - } + refreshLastDatabases(); + bool recent_visibility = (m_ui->recentListWidget->count() > 0); m_ui->startLabel->setVisible(!recent_visibility); m_ui->recentListWidget->setVisible(recent_visibility); @@ -52,7 +47,7 @@ WelcomeWidget::WelcomeWidget(QWidget* parent) connect(m_ui->buttonOpenDatabase, SIGNAL(clicked()), SIGNAL(openDatabase())); connect(m_ui->buttonImportKeePass1, SIGNAL(clicked()), SIGNAL(importKeePass1Database())); connect(m_ui->buttonImportCSV, SIGNAL(clicked()), SIGNAL(importCsv())); - connect(m_ui->recentListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, + connect(m_ui->recentListWidget, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(openDatabaseFromFile(QListWidgetItem*))); } @@ -67,3 +62,14 @@ void WelcomeWidget::openDatabaseFromFile(QListWidgetItem* item) } emit openDatabaseFile(item->text()); } + +void WelcomeWidget::refreshLastDatabases() +{ + m_ui->recentListWidget->clear(); + const QStringList lastDatabases = config()->get("LastDatabases", QVariant()).toStringList(); + for (const QString& database : lastDatabases) { + QListWidgetItem *itm = new QListWidgetItem; + itm->setText(database); + m_ui->recentListWidget->addItem(itm); + } +} \ No newline at end of file diff --git a/src/gui/WelcomeWidget.h b/src/gui/WelcomeWidget.h index 98d87acaa..9f8d5d70d 100644 --- a/src/gui/WelcomeWidget.h +++ b/src/gui/WelcomeWidget.h @@ -32,6 +32,7 @@ class WelcomeWidget : public QWidget public: explicit WelcomeWidget(QWidget* parent = nullptr); ~WelcomeWidget(); + void refreshLastDatabases(); signals: void newDatabase(); From f183260ad6d03850b4e0800e2ade191206074a91 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 29 Mar 2017 22:00:03 +0200 Subject: [PATCH 218/333] make stackedWidget index using enum --- src/gui/MainWindow.cpp | 26 +++++++++++++------------- src/gui/MainWindow.h | 7 +++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index d68d18b46..6c2423bae 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -393,8 +393,8 @@ void MainWindow::openDatabase(const QString& fileName, const QString& pw, const void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) { int currentIndex = m_ui->stackedWidget->currentIndex(); - bool inDatabaseTabWidget = (currentIndex == 0); - bool inWelcomeWidget = (currentIndex == 2); + bool inDatabaseTabWidget = (currentIndex == DatabaseTabScreen); + bool inWelcomeWidget = (currentIndex == WelcomeScreen); if (inDatabaseTabWidget && m_ui->tabWidget->currentIndex() != -1) { DatabaseWidget* dbWidget = m_ui->tabWidget->currentDatabaseWidget(); @@ -508,7 +508,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionLockDatabases->setEnabled(m_ui->tabWidget->hasLockableDatabases()); - if ((3 == currentIndex) != m_ui->actionPasswordGenerator->isChecked()) { + if ((currentIndex == PasswordGeneratorScreen) != m_ui->actionPasswordGenerator->isChecked()) { bool blocked = m_ui->actionPasswordGenerator->blockSignals(true); m_ui->actionPasswordGenerator->toggle(); m_ui->actionPasswordGenerator->blockSignals(blocked); @@ -520,7 +520,7 @@ void MainWindow::updateWindowTitle() QString customWindowTitlePart; int stackedWidgetIndex = m_ui->stackedWidget->currentIndex(); int tabWidgetIndex = m_ui->tabWidget->currentIndex(); - if (stackedWidgetIndex == 0 && tabWidgetIndex != -1) { + if (stackedWidgetIndex == DatabaseTabScreen && tabWidgetIndex != -1) { customWindowTitlePart = m_ui->tabWidget->tabText(tabWidgetIndex); if (m_ui->tabWidget->readOnly(tabWidgetIndex)) { customWindowTitlePart.append(QString(" [%1]").arg(tr("read-only"))); @@ -556,17 +556,17 @@ void MainWindow::showAboutDialog() void MainWindow::switchToDatabases() { if (m_ui->tabWidget->currentIndex() == -1) { - m_ui->stackedWidget->setCurrentIndex(2); + m_ui->stackedWidget->setCurrentIndex(WelcomeScreen); } else { - m_ui->stackedWidget->setCurrentIndex(0); + m_ui->stackedWidget->setCurrentIndex(DatabaseTabScreen); } } void MainWindow::switchToSettings() { m_ui->settingsWidget->loadSettings(); - m_ui->stackedWidget->setCurrentIndex(1); + m_ui->stackedWidget->setCurrentIndex(SettingsScreen); } void MainWindow::switchToPasswordGen(bool enabled) @@ -575,7 +575,7 @@ void MainWindow::switchToPasswordGen(bool enabled) m_ui->passwordGeneratorWidget->loadSettings(); m_ui->passwordGeneratorWidget->regeneratePassword(); m_ui->passwordGeneratorWidget->setStandaloneMode(true); - m_ui->stackedWidget->setCurrentIndex(3); + m_ui->stackedWidget->setCurrentIndex(PasswordGeneratorScreen); } else { m_ui->passwordGeneratorWidget->saveSettings(); switchToDatabases(); @@ -624,11 +624,11 @@ void MainWindow::databaseStatusChanged(DatabaseWidget *) void MainWindow::databaseTabChanged(int tabIndex) { - if (tabIndex != -1 && m_ui->stackedWidget->currentIndex() == 2) { - m_ui->stackedWidget->setCurrentIndex(0); + if (tabIndex != -1 && m_ui->stackedWidget->currentIndex() == WelcomeScreen) { + m_ui->stackedWidget->setCurrentIndex(DatabaseTabScreen); } - else if (tabIndex == -1 && m_ui->stackedWidget->currentIndex() == 0) { - m_ui->stackedWidget->setCurrentIndex(2); + else if (tabIndex == -1 && m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) { + m_ui->stackedWidget->setCurrentIndex(WelcomeScreen); } m_actionMultiplexer.setCurrentObject(m_ui->tabWidget->currentDatabaseWidget()); @@ -919,7 +919,7 @@ void MainWindow::hideGlobalMessage() void MainWindow::hideTabMessage() { - if (m_ui->stackedWidget->currentIndex() == 0) { + if (m_ui->stackedWidget->currentIndex() == DatabaseTabScreen) { m_ui->tabWidget->currentDatabaseWidget()->hideMessage(); } } diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index f69fe720f..f35cd4658 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -39,6 +39,13 @@ class MainWindow : public QMainWindow public: MainWindow(); ~MainWindow(); + enum StackedWidgetIndex + { + DatabaseTabScreen = 0, + SettingsScreen = 1, + WelcomeScreen = 2, + PasswordGeneratorScreen = 3 + }; public slots: void openDatabase(const QString& fileName, const QString& pw = QString(), From b7546b45b3c48e2ffa150be72fc6ce03db1adf00 Mon Sep 17 00:00:00 2001 From: Weslly Date: Wed, 29 Mar 2017 16:44:01 -0300 Subject: [PATCH 219/333] Fix compiler warnings --- src/http/Service.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/http/Service.cpp b/src/http/Service.cpp index 25c69f614..b2ae0a803 100644 --- a/src/http/Service.cpp +++ b/src/http/Service.cpp @@ -84,6 +84,8 @@ bool Service::isDatabaseOpened() const case DatabaseWidget::ViewMode: case DatabaseWidget::EditMode: return true; + default: + break; } return false; } @@ -101,6 +103,8 @@ bool Service::openDatabase() case DatabaseWidget::ViewMode: case DatabaseWidget::EditMode: return true; + default: + break; } } //if (HttpSettings::showNotification() From bce747e7f9370d424d53bedebf8792e9a774543b Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Tue, 4 Apr 2017 10:21:45 -0400 Subject: [PATCH 220/333] Ignore group expansion. --- src/core/Config.cpp | 1 + src/core/Group.cpp | 3 +++ src/gui/SettingsWidget.cpp | 3 +++ src/gui/SettingsWidgetGeneral.ui | 7 +++++++ 4 files changed, 14 insertions(+) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index e074df6cb..c0876daa5 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -113,6 +113,7 @@ void Config::init(const QString& fileName) m_defaults.insert("UseGroupIconOnEntryCreation", false); m_defaults.insert("AutoTypeEntryTitleMatch", true); m_defaults.insert("UseGroupIconOnEntryCreation", true); + m_defaults.insert("IgnoreGroupExpansion", false); m_defaults.insert("security/clearclipboard", true); m_defaults.insert("security/clearclipboardtimeout", 10); m_defaults.insert("security/lockdatabaseidle", false); diff --git a/src/core/Group.cpp b/src/core/Group.cpp index d8d609987..bd4f8851b 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -296,6 +296,9 @@ void Group::setExpanded(bool expanded) if (m_data.isExpanded != expanded) { m_data.isExpanded = expanded; updateTimeinfo(); + if (config()->get("IgnoreGroupExpansion").toBool()) { + return; + } emit modified(); } } diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 19dae371d..716eb14f1 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -115,6 +115,7 @@ void SettingsWidget::loadSettings() m_generalUi->minimizeOnCopyCheckBox->setChecked(config()->get("MinimizeOnCopy").toBool()); m_generalUi->useGroupIconOnEntryCreationCheckBox->setChecked(config()->get("UseGroupIconOnEntryCreation").toBool()); m_generalUi->autoTypeEntryTitleMatchCheckBox->setChecked(config()->get("AutoTypeEntryTitleMatch").toBool()); + m_generalUi->ignoreGroupExpansionCheckBox->setChecked(config()->get("IgnoreGroupExpansion").toBool()); m_generalUi->languageComboBox->clear(); QList > languages = Translator::availableLanguages(); @@ -180,6 +181,8 @@ void SettingsWidget::saveSettings() config()->set("MinimizeOnCopy", m_generalUi->minimizeOnCopyCheckBox->isChecked()); config()->set("UseGroupIconOnEntryCreation", m_generalUi->useGroupIconOnEntryCreationCheckBox->isChecked()); + config()->set("IgnoreGroupExpansion", + m_generalUi->ignoreGroupExpansionCheckBox->isChecked()); config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 88d7cad45..143217505 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -105,6 +105,13 @@ + + + + Dot not mark a database as modified when groups are expanded + + + From a8b647e8865db46c687164c98d25830b313bba08 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Tue, 4 Apr 2017 10:23:48 -0400 Subject: [PATCH 221/333] Typo in label for ignoreGroup. --- src/gui/SettingsWidgetGeneral.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 143217505..ffa9f52c0 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -108,7 +108,7 @@ - Dot not mark a database as modified when groups are expanded + Do not mark a database as modified when groups are expanded From 86e88c18b088aebaa97299c8fbdac4b5cdf8dfdd Mon Sep 17 00:00:00 2001 From: Francois Ferrand Date: Wed, 5 Apr 2017 10:42:15 +0200 Subject: [PATCH 222/333] More compact search widget * Move the search icon (with popup menu) and clear icon inside the line edit * Move the search widget to the right-side of toolbar --- src/gui/SearchWidget.cpp | 17 +++++++++----- src/gui/SearchWidget.ui | 50 ++++++++++++++++------------------------ tests/gui/TestGui.cpp | 9 ++++++-- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index 933686dfa..e94f838c5 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "core/FilePath.h" @@ -34,9 +35,7 @@ SearchWidget::SearchWidget(QWidget *parent) m_searchTimer->setSingleShot(true); connect(m_ui->searchEdit, SIGNAL(textChanged(QString)), SLOT(startSearchTimer())); - connect(m_ui->searchIcon, SIGNAL(pressed()), m_ui->searchEdit, SLOT(setFocus())); - connect(m_ui->clearIcon, SIGNAL(pressed()), m_ui->searchEdit, SLOT(clear())); - connect(m_ui->clearIcon, SIGNAL(pressed()), m_ui->searchEdit, SLOT(setFocus())); + connect(m_ui->clearIcon, SIGNAL(triggered(bool)), m_ui->searchEdit, SLOT(clear())); connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(startSearch())); connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear())); @@ -52,10 +51,16 @@ SearchWidget::SearchWidget(QWidget *parent) m_ui->searchIcon->setIcon(filePath()->icon("actions", "system-search")); m_ui->searchIcon->setMenu(searchMenu); - m_ui->searchIcon->setPopupMode(QToolButton::MenuButtonPopup); + m_ui->searchEdit->addAction(m_ui->searchIcon, QLineEdit::LeadingPosition); m_ui->clearIcon->setIcon(filePath()->icon("actions", "edit-clear-locationbar-rtl")); - m_ui->clearIcon->setEnabled(false); + m_ui->clearIcon->setVisible(false); + m_ui->searchEdit->addAction(m_ui->clearIcon, QLineEdit::TrailingPosition); + + // Fix initial visibility of actions (bug in Qt) + for (QToolButton * toolButton: m_ui->searchEdit->findChildren()) { + toolButton->setVisible(toolButton->defaultAction()->isVisible()); + } } SearchWidget::~SearchWidget() @@ -133,7 +138,7 @@ void SearchWidget::startSearch() } bool hasText = m_ui->searchEdit->text().length() > 0; - m_ui->clearIcon->setEnabled(hasText); + m_ui->clearIcon->setVisible(hasText); search(m_ui->searchEdit->text()); } diff --git a/src/gui/SearchWidget.ui b/src/gui/SearchWidget.ui index 46c2699f0..1583ebe96 100644 --- a/src/gui/SearchWidget.ui +++ b/src/gui/SearchWidget.ui @@ -30,20 +30,17 @@ 0 - - - Qt::ClickFocus + + + Qt::Horizontal - - Search + + + 40 + 20 + - - Qt::ToolButtonIconOnly - - - true - - + @@ -51,33 +48,26 @@ padding:3px - Find + Search... false - - - - Qt::ClickFocus - - - Clear - - - Qt::ToolButtonIconOnly - - - true - - - + + + Search + + + + + Clear + + - searchIcon searchEdit diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 90f873197..704ef2e8c 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -410,19 +410,24 @@ void TestGui::testSearch() EntryView* entryView = m_dbWidget->findChild("entryView"); QVERIFY(entryView->isVisible()); + QAction* clearButton = searchWidget->findChild("clearIcon"); + QVERIFY(!clearButton->isVisible()); + // Enter search QTest::mouseClick(searchTextEdit, Qt::LeftButton); QTRY_VERIFY(searchTextEdit->hasFocus()); + QTRY_VERIFY(!clearButton->isVisible()); // Search for "ZZZ" QTest::keyClicks(searchTextEdit, "ZZZ"); QTRY_COMPARE(searchTextEdit->text(), QString("ZZZ")); + QTRY_VERIFY(clearButton->isVisible()); QTRY_VERIFY(m_dbWidget->isInSearchMode()); QTRY_COMPARE(entryView->model()->rowCount(), 0); // Press the search clear button - QToolButton* clearButton = searchWidget->findChild("clearIcon"); - QTest::mouseClick(clearButton, Qt::LeftButton); + clearButton->trigger(); QTRY_VERIFY(searchTextEdit->text().isEmpty()); QTRY_VERIFY(searchTextEdit->hasFocus()); + QTRY_VERIFY(!clearButton->isVisible()); // Escape clears searchedit and retains focus QTest::keyClicks(searchTextEdit, "ZZZ"); QTest::keyClick(searchTextEdit, Qt::Key_Escape); From 32fe0493c4e8262719482f66faa87697694fd92d Mon Sep 17 00:00:00 2001 From: Weslly Date: Thu, 23 Mar 2017 10:36:52 -0300 Subject: [PATCH 223/333] Add auto-type {CLEARFIELD} on mac --- src/autotype/mac/AutoTypeMac.cpp | 27 +++++++++++++++++++++++++++ src/autotype/mac/AutoTypeMac.h | 1 + 2 files changed, 28 insertions(+) diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 08df6310e..504103fca 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -488,3 +488,30 @@ void AutoTypeExecutorMac::execKey(AutoTypeKey* action) m_platform->sendKey(action->key, false); usleep(25 * 1000); } + +void execClearFieldHelper(uint16 keyCode, bool isKeyDown, bool modifier = false) +{ + CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); + if (keyEvent != nullptr) { + if (modifier) { + ::CGEventSetFlags(keyEvent, kCGEventFlagMaskCommand); + } + + ::CGEventPost(kCGSessionEventTap, keyEvent); + ::CFRelease(keyEvent); + } + usleep(25 * 1000); +} + +void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr) +{ + Q_UNUSED(action); + + execClearFieldHelper(kVK_ANSI_A, true, true); + execClearFieldHelper(kVK_ANSI_A, false); + execClearFieldHelper(kVK_Command, false); + execClearFieldHelper(kVK_Delete, true); + execClearFieldHelper(kVK_Delete, false); + + usleep(25 * 1000); +} diff --git a/src/autotype/mac/AutoTypeMac.h b/src/autotype/mac/AutoTypeMac.h index 5fbbf763b..c2dde398a 100644 --- a/src/autotype/mac/AutoTypeMac.h +++ b/src/autotype/mac/AutoTypeMac.h @@ -73,6 +73,7 @@ public: void execChar(AutoTypeChar* action) override; void execKey(AutoTypeKey* action) override; + void execClearField(AutoTypeClearField* action) override; private: AutoTypePlatformMac* const m_platform; From 720ae949aa1971c2f157cdb187cba7984d232620 Mon Sep 17 00:00:00 2001 From: Weslly Date: Thu, 23 Mar 2017 11:14:46 -0300 Subject: [PATCH 224/333] Rename ClearField keypress helper function --- src/autotype/mac/AutoTypeMac.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 504103fca..57e2e9e15 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -489,7 +489,7 @@ void AutoTypeExecutorMac::execKey(AutoTypeKey* action) usleep(25 * 1000); } -void execClearFieldHelper(uint16 keyCode, bool isKeyDown, bool modifier = false) +void execKeyPress(uint16 keyCode, bool isKeyDown, bool modifier = false) { CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); if (keyEvent != nullptr) { @@ -507,11 +507,11 @@ void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr) { Q_UNUSED(action); - execClearFieldHelper(kVK_ANSI_A, true, true); - execClearFieldHelper(kVK_ANSI_A, false); - execClearFieldHelper(kVK_Command, false); - execClearFieldHelper(kVK_Delete, true); - execClearFieldHelper(kVK_Delete, false); + execKeyPress(kVK_ANSI_A, true, true); + execKeyPress(kVK_ANSI_A, false); + execKeyPress(kVK_Command, false); + execKeyPress(kVK_Delete, true); + execKeyPress(kVK_Delete, false); usleep(25 * 1000); } From 28678f4e06cb1f466a6ed4c3081bc9ad95fcabbc Mon Sep 17 00:00:00 2001 From: Weslly Date: Fri, 24 Mar 2017 10:15:31 -0300 Subject: [PATCH 225/333] Add auto-type {CLEARFIELD} on Windows --- src/autotype/windows/AutoTypeWindows.cpp | 34 ++++++++++++++++++++++++ src/autotype/windows/AutoTypeWindows.h | 1 + 2 files changed, 35 insertions(+) diff --git a/src/autotype/windows/AutoTypeWindows.cpp b/src/autotype/windows/AutoTypeWindows.cpp index 0818a37bc..3c8d896ed 100644 --- a/src/autotype/windows/AutoTypeWindows.cpp +++ b/src/autotype/windows/AutoTypeWindows.cpp @@ -527,3 +527,37 @@ void AutoTypeExecutorWin::execKey(AutoTypeKey* action) ::Sleep(25); } +void execKeyPress(const DWORD keyCode, bool isKeyDown) +{ + DWORD nativeFlags = 0; + + if (!isKeyDown) { + nativeFlags |= KEYEVENTF_KEYUP; + } + + INPUT in; + in.type = INPUT_KEYBOARD; + in.ki.wVk = LOWORD(keyCode); + in.ki.wScan = LOWORD(::MapVirtualKeyW(keyCode, MAPVK_VK_TO_VSC)); + in.ki.dwFlags = nativeFlags; + in.ki.time = 0; + in.ki.dwExtraInfo = ::GetMessageExtraInfo(); + + ::SendInput(1, &in, sizeof(INPUT)); + + ::Sleep(25); +} + +void AutoTypeExecutorWin::execClearField(AutoTypeClearField* action = nullptr) +{ + Q_UNUSED(action); + + execKeyPress(VK_CONTROL, true); + execKeyPress(0x41, true); + execKeyPress(0x41, false); + execKeyPress(VK_CONTROL, false); + execKeyPress(VK_BACK, true); + execKeyPress(VK_BACK, false); + + ::Sleep(25); +} diff --git a/src/autotype/windows/AutoTypeWindows.h b/src/autotype/windows/AutoTypeWindows.h index f8b213cb0..6ffae4d1e 100644 --- a/src/autotype/windows/AutoTypeWindows.h +++ b/src/autotype/windows/AutoTypeWindows.h @@ -64,6 +64,7 @@ public: void execChar(AutoTypeChar* action) override; void execKey(AutoTypeKey* action) override; + void execClearField(AutoTypeClearField* action) override; private: AutoTypePlatformWin* const m_platform; From 7620395f92faab3949a7f0302e8b64e8ff492b08 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 25 Mar 2017 22:23:43 +0100 Subject: [PATCH 226/333] Add auto-type {CLEARFIELD} on XCB --- src/autotype/xcb/AutoTypeXCB.cpp | 29 +++++++++++++++++++++++++++-- src/autotype/xcb/AutoTypeXCB.h | 2 ++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index e6ac74bbf..5d58e33c6 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -723,6 +723,12 @@ bool AutoTypePlatformX11::keysymModifiers(KeySym keysym, int keycode, unsigned i * are set ON, many events will be sent. */ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym) +{ + SendKey(keysym,true); + SendKey(keysym,false); +} + +void AutoTypePlatformX11::SendKey(KeySym keysym, bool isKeyDown) { Window cur_focus; int revert_to; @@ -802,8 +808,11 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym) /* press and release key */ event.keycode = keycode; - SendEvent(&event, KeyPress); - SendEvent(&event, KeyRelease); + if (isKeyDown) { + SendEvent(&event, KeyPress); + } else { + SendEvent(&event, KeyRelease); + } /* release the modifiers */ SendModifier(&event, press_mask, KeyRelease); @@ -840,6 +849,22 @@ void AutoTypeExecutorX11::execKey(AutoTypeKey* action) m_platform->SendKeyPressedEvent(m_platform->keyToKeySym(action->key)); } +void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr) +{ + Q_UNUSED(action); + + m_platform->SendKey(XK_Control_L, true); + m_platform->SendKeyPressedEvent(XK_a); + m_platform->SendKey(XK_Control_L, false); + m_platform->SendKeyPressedEvent(XK_Delete); + + timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 25 * 1000 * 1000; + nanosleep(&ts, nullptr); +} + + int AutoTypePlatformX11::initialTimeout() { return 500; diff --git a/src/autotype/xcb/AutoTypeXCB.h b/src/autotype/xcb/AutoTypeXCB.h index dc251e3f9..186d0eb8b 100644 --- a/src/autotype/xcb/AutoTypeXCB.h +++ b/src/autotype/xcb/AutoTypeXCB.h @@ -58,6 +58,7 @@ public: KeySym keyToKeySym(Qt::Key key); void SendKeyPressedEvent(KeySym keysym); + void SendKey(KeySym keysym, bool isKeyDown); signals: void globalShortcutTriggered(); @@ -126,6 +127,7 @@ public: void execChar(AutoTypeChar* action) override; void execKey(AutoTypeKey* action) override; + void execClearField(AutoTypeClearField* action) override; private: AutoTypePlatformX11* const m_platform; From 268f09160d0881f55646e4b44def5e40fa40146a Mon Sep 17 00:00:00 2001 From: Weslly Date: Sat, 25 Mar 2017 18:42:52 -0300 Subject: [PATCH 227/333] Add command modifier support for AutoTypePlatformMac::sendKey --- src/autotype/mac/AutoTypeMac.cpp | 40 ++++++++++++++------------------ src/autotype/mac/AutoTypeMac.h | 2 +- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 57e2e9e15..122bffeaa 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -201,15 +201,25 @@ void AutoTypePlatformMac::sendChar(const QChar& ch, bool isKeyDown) // Send key code to active window // see: Quartz Event Services // -void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown) +void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, bool isCommand = false) { - uint16 keyCode = qtToNativeKeyCode(key); + uint16 keyCode = 0; + + if (isCommand && key == Qt::Key_Any) { + keyCode = kVK_Command; + } else { + keyCode = qtToNativeKeyCode(key); + } + if (keyCode == INVALID_KEYCODE) { return; } CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); if (keyEvent != nullptr) { + if (isCommand && isKeyDown) { + ::CGEventSetFlags(keyEvent, kCGEventFlagMaskCommand); + } ::CGEventPost(kCGSessionEventTap, keyEvent); ::CFRelease(keyEvent); } @@ -489,29 +499,13 @@ void AutoTypeExecutorMac::execKey(AutoTypeKey* action) usleep(25 * 1000); } -void execKeyPress(uint16 keyCode, bool isKeyDown, bool modifier = false) -{ - CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); - if (keyEvent != nullptr) { - if (modifier) { - ::CGEventSetFlags(keyEvent, kCGEventFlagMaskCommand); - } - - ::CGEventPost(kCGSessionEventTap, keyEvent); - ::CFRelease(keyEvent); - } - usleep(25 * 1000); -} - void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr) { Q_UNUSED(action); - - execKeyPress(kVK_ANSI_A, true, true); - execKeyPress(kVK_ANSI_A, false); - execKeyPress(kVK_Command, false); - execKeyPress(kVK_Delete, true); - execKeyPress(kVK_Delete, false); - + m_platform->sendKey(Qt::Key_A, true, true); + m_platform->sendKey(Qt::Key_A, false); + m_platform->sendKey(Qt::Key_Any, false, true); + m_platform->sendKey(Qt::Key_Backspace, true); + m_platform->sendKey(Qt::Key_Backspace, false); usleep(25 * 1000); } diff --git a/src/autotype/mac/AutoTypeMac.h b/src/autotype/mac/AutoTypeMac.h index c2dde398a..86b01826b 100644 --- a/src/autotype/mac/AutoTypeMac.h +++ b/src/autotype/mac/AutoTypeMac.h @@ -49,7 +49,7 @@ public: bool raiseOwnWindow() override; void sendChar(const QChar& ch, bool isKeyDown); - void sendKey(Qt::Key key, bool isKeyDown); + void sendKey(Qt::Key key, bool isKeyDown, bool isCommand); signals: void globalShortcutTriggered(); From 36250f518075d1b348eb4b63193590be5f0c608a Mon Sep 17 00:00:00 2001 From: Weslly Date: Sat, 25 Mar 2017 19:03:17 -0300 Subject: [PATCH 228/333] Use AutoTypePlatformWin::sendKey instead of creating a new function --- src/autotype/AutoTypeAction.cpp | 2 -- src/autotype/mac/AutoTypeMac.cpp | 2 ++ src/autotype/windows/AutoTypeWindows.cpp | 35 ++++++------------------ 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/src/autotype/AutoTypeAction.cpp b/src/autotype/AutoTypeAction.cpp index 090ca8234..64dae7962 100644 --- a/src/autotype/AutoTypeAction.cpp +++ b/src/autotype/AutoTypeAction.cpp @@ -90,6 +90,4 @@ void AutoTypeExecutor::execDelay(AutoTypeDelay* action) void AutoTypeExecutor::execClearField(AutoTypeClearField* action) { Q_UNUSED(action); - - // TODO: implement } diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 122bffeaa..2806a4486 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -502,10 +502,12 @@ void AutoTypeExecutorMac::execKey(AutoTypeKey* action) void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr) { Q_UNUSED(action); + m_platform->sendKey(Qt::Key_A, true, true); m_platform->sendKey(Qt::Key_A, false); m_platform->sendKey(Qt::Key_Any, false, true); m_platform->sendKey(Qt::Key_Backspace, true); m_platform->sendKey(Qt::Key_Backspace, false); + usleep(25 * 1000); } diff --git a/src/autotype/windows/AutoTypeWindows.cpp b/src/autotype/windows/AutoTypeWindows.cpp index 3c8d896ed..0b1474412 100644 --- a/src/autotype/windows/AutoTypeWindows.cpp +++ b/src/autotype/windows/AutoTypeWindows.cpp @@ -189,6 +189,8 @@ DWORD AutoTypePlatformWin::qtToNativeKeyCode(Qt::Key key) case Qt::Key_Enter: case Qt::Key_Return: return VK_RETURN; // 0x0D + case Qt::Key_Control: + return VK_CONTROL; // 0x11 case Qt::Key_Pause: return VK_PAUSE; // 0x13 case Qt::Key_CapsLock: @@ -527,37 +529,16 @@ void AutoTypeExecutorWin::execKey(AutoTypeKey* action) ::Sleep(25); } -void execKeyPress(const DWORD keyCode, bool isKeyDown) -{ - DWORD nativeFlags = 0; - - if (!isKeyDown) { - nativeFlags |= KEYEVENTF_KEYUP; - } - - INPUT in; - in.type = INPUT_KEYBOARD; - in.ki.wVk = LOWORD(keyCode); - in.ki.wScan = LOWORD(::MapVirtualKeyW(keyCode, MAPVK_VK_TO_VSC)); - in.ki.dwFlags = nativeFlags; - in.ki.time = 0; - in.ki.dwExtraInfo = ::GetMessageExtraInfo(); - - ::SendInput(1, &in, sizeof(INPUT)); - - ::Sleep(25); -} - void AutoTypeExecutorWin::execClearField(AutoTypeClearField* action = nullptr) { Q_UNUSED(action); - execKeyPress(VK_CONTROL, true); - execKeyPress(0x41, true); - execKeyPress(0x41, false); - execKeyPress(VK_CONTROL, false); - execKeyPress(VK_BACK, true); - execKeyPress(VK_BACK, false); + m_platform->sendKey(Qt::Key_Control, true); + m_platform->sendKey(Qt::Key_A, true); + m_platform->sendKey(Qt::Key_A, false); + m_platform->sendKey(Qt::Key_Control, false); + m_platform->sendKey(Qt::Key_Backspace, true); + m_platform->sendKey(Qt::Key_Backspace, false); ::Sleep(25); } From ea1ffe7fb55453ef37f95a0304ba2dcb1acecda8 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 25 Mar 2017 23:22:01 +0100 Subject: [PATCH 229/333] use QT key instead of native ones --- src/autotype/xcb/AutoTypeXCB.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index 5d58e33c6..b5fe9e7e7 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -473,6 +473,8 @@ KeySym AutoTypePlatformX11::keyToKeySym(Qt::Key key) return XK_Print; case Qt::Key_ScrollLock: return XK_Scroll_Lock; + case Qt::Key_Control: + return XK_Control_L; default: if (key >= Qt::Key_F1 && key <= Qt::Key_F16) { return XK_F1 + (key - Qt::Key_F1); @@ -853,10 +855,10 @@ void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr) { Q_UNUSED(action); - m_platform->SendKey(XK_Control_L, true); - m_platform->SendKeyPressedEvent(XK_a); - m_platform->SendKey(XK_Control_L, false); - m_platform->SendKeyPressedEvent(XK_Delete); + m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Control), true); + m_platform->SendKeyPressedEvent(m_platform->charToKeySym('a')); + m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Control), false); + m_platform->SendKeyPressedEvent(m_platform->keyToKeySym(Qt::Key_Delete)); timespec ts; ts.tv_sec = 0; From 6c1f023768f6ce83021c514f313569c3a3301fa4 Mon Sep 17 00:00:00 2001 From: Weslly Date: Sat, 25 Mar 2017 21:07:22 -0300 Subject: [PATCH 230/333] More accurate hotkey sequence --- src/autotype/mac/AutoTypeMac.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 2806a4486..75ff0099a 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -203,14 +203,7 @@ void AutoTypePlatformMac::sendChar(const QChar& ch, bool isKeyDown) // void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, bool isCommand = false) { - uint16 keyCode = 0; - - if (isCommand && key == Qt::Key_Any) { - keyCode = kVK_Command; - } else { - keyCode = qtToNativeKeyCode(key); - } - + uint16 keyCode = qtToNativeKeyCode(key); if (keyCode == INVALID_KEYCODE) { return; } @@ -327,6 +320,8 @@ uint16 AutoTypePlatformMac::qtToNativeKeyCode(Qt::Key key) case Qt::Key_Period: return kVK_ANSI_Period; + case Qt::Key_Control: + return kVK_Command; case Qt::Key_Backspace: return kVK_Delete; case Qt::Key_Tab: @@ -503,9 +498,10 @@ void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr) { Q_UNUSED(action); + m_platform->sendKey(Qt::Key_Control, true, true); m_platform->sendKey(Qt::Key_A, true, true); + m_platform->sendKey(Qt::Key_Control, false, true); m_platform->sendKey(Qt::Key_A, false); - m_platform->sendKey(Qt::Key_Any, false, true); m_platform->sendKey(Qt::Key_Backspace, true); m_platform->sendKey(Qt::Key_Backspace, false); From a6e142dd0240528545bfd74416831d0cf3657cfb Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 27 Mar 2017 18:11:34 +0200 Subject: [PATCH 231/333] support different keyboard layout --- src/autotype/xcb/AutoTypeXCB.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index b5fe9e7e7..9290639f2 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -473,8 +473,12 @@ KeySym AutoTypePlatformX11::keyToKeySym(Qt::Key key) return XK_Print; case Qt::Key_ScrollLock: return XK_Scroll_Lock; + case Qt::Key_Shift: + return XK_Shift_L; case Qt::Key_Control: return XK_Control_L; + case Qt::Key_Alt: + return XK_Alt_L; default: if (key >= Qt::Key_F1 && key <= Qt::Key_F16) { return XK_F1 + (key - Qt::Key_F1); @@ -855,14 +859,23 @@ void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr) { Q_UNUSED(action); - m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Control), true); - m_platform->SendKeyPressedEvent(m_platform->charToKeySym('a')); - m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Control), false); - m_platform->SendKeyPressedEvent(m_platform->keyToKeySym(Qt::Key_Delete)); - timespec ts; ts.tv_sec = 0; ts.tv_nsec = 25 * 1000 * 1000; + + m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Control), true); + m_platform->SendKeyPressedEvent(m_platform->keyToKeySym(Qt::Key_Home)); + m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Control), false); + nanosleep(&ts, nullptr); + + m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Control), true); + m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Shift), true); + m_platform->SendKeyPressedEvent(m_platform->keyToKeySym(Qt::Key_End)); + m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Shift), false); + m_platform->SendKey(m_platform->keyToKeySym(Qt::Key_Control), false); + nanosleep(&ts, nullptr); + + m_platform->SendKeyPressedEvent(m_platform->keyToKeySym(Qt::Key_Backspace)); nanosleep(&ts, nullptr); } From eefea5444e3af7f933a712518bc50007db79dd81 Mon Sep 17 00:00:00 2001 From: Weslly Date: Wed, 29 Mar 2017 12:52:35 -0300 Subject: [PATCH 232/333] Change auto-type sequence on mac to support other keyboard layouts --- src/autotype/mac/AutoTypeMac.cpp | 54 ++++++++++++++++++++++---------- src/autotype/mac/AutoTypeMac.h | 4 +-- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 75ff0099a..89435c263 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -120,7 +120,7 @@ bool AutoTypePlatformMac::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifi qWarning("Invalid key code"); return false; } - uint16 nativeModifiers = qtToNativeModifiers(modifiers); + CGEventFlags nativeModifiers = qtToNativeModifiers(modifiers, false); if (::RegisterEventHotKey(nativeKeyCode, nativeModifiers, m_hotkeyId, GetApplicationEventTarget(), 0, &m_hotkeyRef) != noErr) { qWarning("Register hotkey failed"); return false; @@ -201,7 +201,7 @@ void AutoTypePlatformMac::sendChar(const QChar& ch, bool isKeyDown) // Send key code to active window // see: Quartz Event Services // -void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, bool isCommand = false) +void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers = 0) { uint16 keyCode = qtToNativeKeyCode(key); if (keyCode == INVALID_KEYCODE) { @@ -209,10 +209,9 @@ void AutoTypePlatformMac::sendKey(Qt::Key key, bool isKeyDown, bool isCommand = } CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown); + CGEventFlags nativeModifiers = qtToNativeModifiers(modifiers, true); if (keyEvent != nullptr) { - if (isCommand && isKeyDown) { - ::CGEventSetFlags(keyEvent, kCGEventFlagMaskCommand); - } + ::CGEventSetFlags(keyEvent, nativeModifiers); ::CGEventPost(kCGSessionEventTap, keyEvent); ::CFRelease(keyEvent); } @@ -320,6 +319,8 @@ uint16 AutoTypePlatformMac::qtToNativeKeyCode(Qt::Key key) case Qt::Key_Period: return kVK_ANSI_Period; + case Qt::Key_Shift: + return kVK_Shift; case Qt::Key_Control: return kVK_Command; case Qt::Key_Backspace: @@ -400,21 +401,34 @@ uint16 AutoTypePlatformMac::qtToNativeKeyCode(Qt::Key key) // Translate qt key modifiers to mac os modifiers // see: https://doc.qt.io/qt-5/osx-issues.html#special-keys // -uint16 AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modifiers) +CGEventFlags AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native) { - uint16 nativeModifiers = 0; + CGEventFlags nativeModifiers = 0; + + CGEventFlags shiftMod = shiftKey; + CGEventFlags cmdMod = cmdKey; + CGEventFlags optionMod = optionKey; + CGEventFlags controlMod = controlKey; + + if (native) { + shiftMod = kCGEventFlagMaskShift; + cmdMod = kCGEventFlagMaskCommand; + optionMod = kCGEventFlagMaskAlternate; + controlMod = kCGEventFlagMaskControl; + } + if (modifiers & Qt::ShiftModifier) { - nativeModifiers |= shiftKey; + nativeModifiers |= shiftMod; } if (modifiers & Qt::ControlModifier) { - nativeModifiers |= cmdKey; + nativeModifiers |= cmdMod; } if (modifiers & Qt::AltModifier) { - nativeModifiers |= optionKey; + nativeModifiers |= optionMod; } if (modifiers & Qt::MetaModifier) { - nativeModifiers |= controlKey; + nativeModifiers |= controlMod; } return nativeModifiers; @@ -498,12 +512,20 @@ void AutoTypeExecutorMac::execClearField(AutoTypeClearField* action = nullptr) { Q_UNUSED(action); - m_platform->sendKey(Qt::Key_Control, true, true); - m_platform->sendKey(Qt::Key_A, true, true); - m_platform->sendKey(Qt::Key_Control, false, true); - m_platform->sendKey(Qt::Key_A, false); + m_platform->sendKey(Qt::Key_Control, true, Qt::ControlModifier); + m_platform->sendKey(Qt::Key_Up, true, Qt::ControlModifier); + m_platform->sendKey(Qt::Key_Up, false, Qt::ControlModifier); + m_platform->sendKey(Qt::Key_Control, false); + usleep(25 * 1000); + m_platform->sendKey(Qt::Key_Shift, true, Qt::ShiftModifier); + m_platform->sendKey(Qt::Key_Control, true, Qt::ShiftModifier | Qt::ControlModifier); + m_platform->sendKey(Qt::Key_Down, true, Qt::ShiftModifier | Qt::ControlModifier); + m_platform->sendKey(Qt::Key_Down, false, Qt::ShiftModifier | Qt::ControlModifier); + m_platform->sendKey(Qt::Key_Control, false, Qt::ShiftModifier); + m_platform->sendKey(Qt::Key_Shift, false); + usleep(25 * 1000); m_platform->sendKey(Qt::Key_Backspace, true); m_platform->sendKey(Qt::Key_Backspace, false); - + usleep(25 * 1000); } diff --git a/src/autotype/mac/AutoTypeMac.h b/src/autotype/mac/AutoTypeMac.h index 86b01826b..82f8b0eb6 100644 --- a/src/autotype/mac/AutoTypeMac.h +++ b/src/autotype/mac/AutoTypeMac.h @@ -49,7 +49,7 @@ public: bool raiseOwnWindow() override; void sendChar(const QChar& ch, bool isKeyDown); - void sendKey(Qt::Key key, bool isKeyDown, bool isCommand); + void sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers); signals: void globalShortcutTriggered(); @@ -60,7 +60,7 @@ private: EventHotKeyID m_hotkeyId; static uint16 qtToNativeKeyCode(Qt::Key key); - static uint16 qtToNativeModifiers(Qt::KeyboardModifiers modifiers); + static CGEventFlags qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native); static int windowLayer(CFDictionaryRef window); static QString windowTitle(CFDictionaryRef window); static OSStatus hotkeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData); From 0aa20f931df53a6a1563a54758b90eae40ab9b9e Mon Sep 17 00:00:00 2001 From: Weslly Date: Sat, 8 Apr 2017 02:13:12 -0300 Subject: [PATCH 233/333] Change windows clearfield key sequence to avoid keyboard layout errors --- src/autotype/windows/AutoTypeWindows.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/autotype/windows/AutoTypeWindows.cpp b/src/autotype/windows/AutoTypeWindows.cpp index 0b1474412..81baeefac 100644 --- a/src/autotype/windows/AutoTypeWindows.cpp +++ b/src/autotype/windows/AutoTypeWindows.cpp @@ -189,6 +189,8 @@ DWORD AutoTypePlatformWin::qtToNativeKeyCode(Qt::Key key) case Qt::Key_Enter: case Qt::Key_Return: return VK_RETURN; // 0x0D + case Qt::Key_Shift: + return VK_SHIFT; // 0x10 case Qt::Key_Control: return VK_CONTROL; // 0x11 case Qt::Key_Pause: @@ -534,9 +536,17 @@ void AutoTypeExecutorWin::execClearField(AutoTypeClearField* action = nullptr) Q_UNUSED(action); m_platform->sendKey(Qt::Key_Control, true); - m_platform->sendKey(Qt::Key_A, true); - m_platform->sendKey(Qt::Key_A, false); + m_platform->sendKey(Qt::Key_Home, true); + m_platform->sendKey(Qt::Key_Home, false); m_platform->sendKey(Qt::Key_Control, false); + ::Sleep(25); + m_platform->sendKey(Qt::Key_Control, true); + m_platform->sendKey(Qt::Key_Shift, true); + m_platform->sendKey(Qt::Key_End, true); + m_platform->sendKey(Qt::Key_End, false); + m_platform->sendKey(Qt::Key_Shift, false); + m_platform->sendKey(Qt::Key_Control, false); + ::Sleep(25); m_platform->sendKey(Qt::Key_Backspace, true); m_platform->sendKey(Qt::Key_Backspace, false); From beba23ea2e5d49b4cc189da116756b373fb4e9c6 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 5 Apr 2017 09:00:40 -0400 Subject: [PATCH 234/333] Prioritize explicit databases. --- src/main.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 49fbdb85b..5981999e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -115,6 +115,15 @@ int main(int argc, char** argv) mainWindow.show(); } + if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { + const QStringList filenames = config()->get("LastOpenedDatabases").toStringList(); + for (const QString& filename : filenames) { + if (!filename.isEmpty() && QFile::exists(filename)) { + mainWindow.openDatabase(filename, QString(), QString()); + } + } + } + for (int ii=0; ii < args.length(); ii++) { QString filename = args[ii]; if (!filename.isEmpty() && QFile::exists(filename)) { @@ -127,15 +136,6 @@ int main(int argc, char** argv) } } - if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { - const QStringList filenames = config()->get("LastOpenedDatabases").toStringList(); - for (const QString& filename : filenames) { - if (!filename.isEmpty() && QFile::exists(filename)) { - mainWindow.openDatabase(filename, QString(), QString()); - } - } - } - int exitCode = app.exec(); #if defined(WITH_ASAN) && defined(WITH_LSAN) From 89382f63064f2bd3eb151e5343a58e25a1c86689 Mon Sep 17 00:00:00 2001 From: Weslly Date: Sat, 8 Apr 2017 23:49:13 -0300 Subject: [PATCH 235/333] Implement support for auto-type {{} and {}} --- src/autotype/AutoType.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 25afccfe9..1d96377f5 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -142,6 +142,9 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow, const QS sequence = customSequence; } + sequence.replace("{{}", "{LEFTBRACE}"); + sequence.replace("{}}", "{RIGHTBRACE}"); + QList actions; ListDeleter actionsDeleter(&actions); @@ -317,8 +320,6 @@ bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList AutoType::createActionFromTemplate(const QString& tmpl, c else if (tmplName.compare(")",Qt::CaseInsensitive)==0) { list.append(new AutoTypeChar(')')); } - else if (tmplName.compare("{",Qt::CaseInsensitive)==0) { + else if (tmplName.compare("leftbrace",Qt::CaseInsensitive)==0) { list.append(new AutoTypeChar('{')); } - else if (tmplName.compare("}",Qt::CaseInsensitive)==0) { + else if (tmplName.compare("rightbrace",Qt::CaseInsensitive)==0) { list.append(new AutoTypeChar('}')); } else { From 8a78616351c3977104367304a4953bafa3c47c3f Mon Sep 17 00:00:00 2001 From: Weslly Date: Sun, 9 Apr 2017 07:44:17 -0300 Subject: [PATCH 236/333] Set menu roles for application menu on macOS --- src/gui/MainWindow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 6c2423bae..49f6960cb 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -184,6 +184,7 @@ MainWindow::MainWindow() m_ui->actionChangeMasterKey->setIcon(filePath()->icon("actions", "database-change-key", false)); m_ui->actionLockDatabases->setIcon(filePath()->icon("actions", "document-encrypt", false)); m_ui->actionQuit->setIcon(filePath()->icon("actions", "application-exit")); + m_ui->actionQuit->setMenuRole(QAction::QuitRole); m_ui->actionEntryNew->setIcon(filePath()->icon("actions", "entry-new", false)); m_ui->actionEntryClone->setIcon(filePath()->icon("actions", "entry-clone", false)); @@ -198,9 +199,11 @@ MainWindow::MainWindow() m_ui->actionGroupDelete->setIcon(filePath()->icon("actions", "group-delete", false)); m_ui->actionSettings->setIcon(filePath()->icon("actions", "configure")); + m_ui->actionSettings->setMenuRole(QAction::PreferencesRole); m_ui->actionPasswordGenerator->setIcon(filePath()->icon("actions", "password-generator", false)); m_ui->actionAbout->setIcon(filePath()->icon("actions", "help-about")); + m_ui->actionAbout->setMenuRole(QAction::AboutRole); m_actionMultiplexer.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(setMenuActionState(DatabaseWidget::Mode))); From f95e33cf7b3e795dbd9b6cc275583730b6608263 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 5 Apr 2017 05:30:39 -0400 Subject: [PATCH 237/333] Updating readme with logo --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a277e5b5e..6749c7453 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 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) KeePassXC Authenticode Certificate Campaign! +# 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) KeePassXC Authenticode Certificate Campaign! KeePass Cross-platform Community Edition @@ -25,7 +25,7 @@ KeePassHTTP is not a highly secure protocol and has certain flaw which allow an ### 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. -### Building KeePassXC yourself +### Building KeePassXC *More detailed instructions are available in the INSTALL file or on the [Wiki page](https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source).* @@ -45,9 +45,9 @@ To update the project from within the project's folder, you can run the followin git pull ``` -Once you have downloaded the source code, you can `cd` into the source code directory and build and install KeePassXC with +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 .. @@ -75,4 +75,4 @@ We are always looking for suggestions how to improve our application. If you fin You can of course also directly contribute your own code. We are happy to accept your pull requests. -Please read the [CONTRIBUTING](.github/CONTRIBUTING.md) document for further information. +Please read the [CONTRIBUTING document](.github/CONTRIBUTING.md) for further information. From c9498868e9ccd5bce53a448e9410631de31f29c9 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 5 Apr 2017 10:56:36 -0400 Subject: [PATCH 238/333] Removing pledge campaign. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6749c7453..caad69682 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 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) KeePassXC Authenticode Certificate Campaign! +# 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 From 5f5f7ec3dd1ad8db3849d4163112d9b466ef010f Mon Sep 17 00:00:00 2001 From: Typz Date: Wed, 5 Apr 2017 11:06:13 +0200 Subject: [PATCH 239/333] Reference Safari extension to support KeepassHTTP --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index caad69682..18a025aa8 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ 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 [PassIFox](https://addons.mozilla.org/en-us/firefox/addon/passifox/) in Mozilla Firefox and [chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae) in Google Chrome or Chromium. +- KeePassHTTP support for use with [PassIFox](https://addons.mozilla.org/en-us/firefox/addon/passifox/) in Mozilla Firefox and [chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae) in Google Chrome or Chromium, and [passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. - Many bug fixes For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document. From 4cd461f57afec65413377853e5907ecb253ee1fe Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 9 Apr 2017 18:52:54 -0400 Subject: [PATCH 240/333] Disable DEP and ASLR for Debug builds. This allows for debugging with GDB. --- CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b5e94ae0..52e9884c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,9 +149,11 @@ if(MINGW) set(CMAKE_RC_COMPILER_INIT windres) enable_language(RC) set(CMAKE_RC_COMPILE_OBJECT " -O coff -i -o ") - # Enable DEP and ASLR - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") + if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + # Enable DEP and ASLR + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") + endif() link_libraries(ws2_32 wsock32) endif() From 73b018812683bcd6217537ca6dc204ee37438ed3 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Mon, 10 Apr 2017 22:05:53 +0200 Subject: [PATCH 241/333] Substitute deprecated commands with feature_summary --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52e9884c9..2da138277 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,6 +268,6 @@ if(PRINT_SUMMARY) feature_summary(WHAT ALL) else() # This will only print ENABLED and DISABLED feature - print_enabled_features() - print_disabled_features() + feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") + feature_summary(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:") endif() From e2c088a5c915909bab97614f0697bca0b79fa7b0 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 11 Apr 2017 19:06:54 -0400 Subject: [PATCH 242/333] Add autotype plugin to snapcraft build --- snapcraft.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/snapcraft.yaml b/snapcraft.yaml index d8a292df9..532f12fa9 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: keepassxc -version: 2.1.3 +version: 2.1.4 grade: stable summary: community driven port of the windows application “Keepass Password Safe” description: | @@ -11,7 +11,7 @@ confinement: strict apps: keepassxc: command: desktop-launch keepassxc - plugs: [unity7, opengl, gsettings, home, network, network-bind] + plugs: [unity7, x11, opengl, gsettings, home, network, network-bind] parts: keepassxc: @@ -30,4 +30,6 @@ parts: - qttools5-dev - qttools5-dev-tools - zlib1g-dev + - libxi-dev + - libxtst-dev after: [desktop-qt5] From 7620baee8088c4ac23dc2ed523c231e7f687346d Mon Sep 17 00:00:00 2001 From: Weslly Date: Thu, 13 Apr 2017 22:43:51 -0300 Subject: [PATCH 243/333] Fix clone dialog layout --- src/gui/CloneDialog.ui | 121 +++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/src/gui/CloneDialog.ui b/src/gui/CloneDialog.ui index 3fef5222d..e64216d89 100644 --- a/src/gui/CloneDialog.ui +++ b/src/gui/CloneDialog.ui @@ -6,71 +6,72 @@ 0 0 - 338 - 120 + 347 + 136 Clone Options - - - - 12 - 12 - 323 - 62 - - - - - - - - 170 - 16777215 - - - - Append ' - Copy' to title - - - true - - - - - - - Replace username and password with references - - - - - - - Copy history - - - true - - - - - - - - - 160 - 90 - 164 - 32 - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - + + + + + + + + 170 + 16777215 + + + + Append ' - Copy' to title + + + true + + + + + + + Replace username and password with references + + + + + + + Copy history + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + From 6f25c4750d5f2200fe955f39ca666e8c0c404147 Mon Sep 17 00:00:00 2001 From: Weslly Date: Fri, 14 Apr 2017 07:09:20 -0300 Subject: [PATCH 244/333] Remove maximum size from label in clone dialog --- src/gui/CloneDialog.ui | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/gui/CloneDialog.ui b/src/gui/CloneDialog.ui index e64216d89..142b17b99 100644 --- a/src/gui/CloneDialog.ui +++ b/src/gui/CloneDialog.ui @@ -18,12 +18,6 @@ - - - 170 - 16777215 - - Append ' - Copy' to title From 3c85d29ece5718e4fcd63161cccaa63eb1ab6890 Mon Sep 17 00:00:00 2001 From: Weslly Date: Fri, 14 Apr 2017 09:02:22 -0300 Subject: [PATCH 245/333] Set fixed size for entry clone dialog --- src/gui/CloneDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/CloneDialog.cpp b/src/gui/CloneDialog.cpp index 6c8f83117..b6ff30bd7 100644 --- a/src/gui/CloneDialog.cpp +++ b/src/gui/CloneDialog.cpp @@ -35,6 +35,7 @@ CloneDialog::CloneDialog(DatabaseWidget* parent, Database* db, Entry* entry) m_parent = parent; m_ui->setupUi(this); + this->setFixedSize(this->sizeHint()); setAttribute(Qt::WA_DeleteOnClose); From 70bd598ead950ae8be8ddcdf8f5eb1595033cc2a Mon Sep 17 00:00:00 2001 From: Mike Kasberg Date: Sun, 16 Apr 2017 09:59:27 -0600 Subject: [PATCH 246/333] Fix #447 - Menu Accelerators for Database & Tools --- src/gui/MainWindow.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index fe9e3c921..c44bee9e7 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -167,7 +167,7 @@ - Database + &Database @@ -240,7 +240,7 @@ - Tools + &Tools From dc3e2238754edaf5438730f9babfc621f7ed6f6b Mon Sep 17 00:00:00 2001 From: Vladimir Svyatski Date: Tue, 18 Apr 2017 16:04:32 +0300 Subject: [PATCH 247/333] Moved the "Clear history" menu item caption from MainWindow (Database > Recent Databases > Clear history) to the string resources. As a result it is no longer hardcoded and can be translated. --- share/translations/keepassx_en.ts | 4 ++++ share/translations/keepassx_ru.ts | 6 +++++- src/gui/MainWindow.cpp | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/share/translations/keepassx_en.ts b/share/translations/keepassx_en.ts index d10bff0de..0638beb97 100644 --- a/share/translations/keepassx_en.ts +++ b/share/translations/keepassx_en.ts @@ -1287,6 +1287,10 @@ This is a one-way migration. You won't be able to open the imported databas Password Generator + + Clear history + + OptionDialog diff --git a/share/translations/keepassx_ru.ts b/share/translations/keepassx_ru.ts index faca7e2f0..94078a1d6 100644 --- a/share/translations/keepassx_ru.ts +++ b/share/translations/keepassx_ru.ts @@ -1291,6 +1291,10 @@ This is a one-way migration. You won't be able to open the imported databas Password Generator Генератор паролей + + Clear history + Очистить историю + OptionDialog @@ -1789,4 +1793,4 @@ give it a unique name to identify and accept it. имена файлов открываемого хранилища паролей (*.kdbx) - \ No newline at end of file + diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 49f6960cb..f3daf455e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -130,7 +130,7 @@ MainWindow::MainWindow() m_ui->toolBar->setVisible(showToolbar); connect(m_ui->toolBar, SIGNAL(visibilityChanged(bool)), this, SLOT(saveToolbarState(bool))); - m_clearHistoryAction = new QAction("Clear history", m_ui->menuFile); + m_clearHistoryAction = new QAction(tr("Clear history"), m_ui->menuFile); m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases); connect(m_clearHistoryAction, SIGNAL(triggered()), this, SLOT(clearLastDatabases())); connect(m_lastDatabasesActions, SIGNAL(triggered(QAction*)), this, SLOT(openRecentDatabase(QAction*))); From 52a264cc2b22197c3511600c466b57f214be6048 Mon Sep 17 00:00:00 2001 From: Vladimir Svyatski Date: Tue, 18 Apr 2017 16:04:32 +0300 Subject: [PATCH 248/333] Moved the "Clear history" menu item caption from MainWindow (Database > Recent Databases > Clear history) to the string resources. As a result it is no longer hardcoded and can be translated. --- share/translations/keepassx_en.ts | 4 ++++ share/translations/keepassx_ru.ts | 6 +++++- src/gui/MainWindow.cpp | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/share/translations/keepassx_en.ts b/share/translations/keepassx_en.ts index d10bff0de..0638beb97 100644 --- a/share/translations/keepassx_en.ts +++ b/share/translations/keepassx_en.ts @@ -1287,6 +1287,10 @@ This is a one-way migration. You won't be able to open the imported databas Password Generator + + Clear history + + OptionDialog diff --git a/share/translations/keepassx_ru.ts b/share/translations/keepassx_ru.ts index faca7e2f0..94078a1d6 100644 --- a/share/translations/keepassx_ru.ts +++ b/share/translations/keepassx_ru.ts @@ -1291,6 +1291,10 @@ This is a one-way migration. You won't be able to open the imported databas Password Generator Генератор паролей + + Clear history + Очистить историю + OptionDialog @@ -1789,4 +1793,4 @@ give it a unique name to identify and accept it. имена файлов открываемого хранилища паролей (*.kdbx) - \ No newline at end of file + diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 49f6960cb..f3daf455e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -130,7 +130,7 @@ MainWindow::MainWindow() m_ui->toolBar->setVisible(showToolbar); connect(m_ui->toolBar, SIGNAL(visibilityChanged(bool)), this, SLOT(saveToolbarState(bool))); - m_clearHistoryAction = new QAction("Clear history", m_ui->menuFile); + m_clearHistoryAction = new QAction(tr("Clear history"), m_ui->menuFile); m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases); connect(m_clearHistoryAction, SIGNAL(triggered()), this, SLOT(clearLastDatabases())); connect(m_lastDatabasesActions, SIGNAL(triggered(QAction*)), this, SLOT(openRecentDatabase(QAction*))); From 9477437256c8b34d0bf124c07abf7e08690dd824 Mon Sep 17 00:00:00 2001 From: Vladimir Svyatski Date: Thu, 20 Apr 2017 16:56:54 +0300 Subject: [PATCH 249/333] :bug: Fix for the issue #108: Add a scrollbar in the AddEntry window when on "small" screen --- src/gui/EditWidget.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index 24cc64fec..a0144c8cb 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -17,6 +17,7 @@ #include "EditWidget.h" #include "ui_EditWidget.h" +#include #include "core/FilePath.h" @@ -47,7 +48,17 @@ EditWidget::~EditWidget() void EditWidget::addPage(const QString& labelText, const QIcon& icon, QWidget* widget) { - m_ui->stackedWidget->addWidget(widget); + /* + * Instead of just adding a widget we're going to wrap it into a scroll area. It will automatically show + * scrollbars when the widget cannot fit into the page. This approach prevents the main window of the application + * from automatic resizing and it now should be able to fit into a user's monitor even if the monitor is only 768 + * pixels high. + */ + QScrollArea* scrollArea = new QScrollArea(m_ui->stackedWidget); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setWidget(widget); + scrollArea->setWidgetResizable(true); + m_ui->stackedWidget->addWidget(scrollArea); m_ui->categoryList->addCategory(labelText, icon); } From 5fbf4af59675c41eed2c95dbca49b74a2e339a02 Mon Sep 17 00:00:00 2001 From: Weslly Date: Thu, 20 Apr 2017 17:53:12 -0300 Subject: [PATCH 250/333] Fix OSX El Capitan compilation issue --- src/autotype/mac/AutoTypeMac.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 89435c263..51a5c5cda 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -403,12 +403,12 @@ uint16 AutoTypePlatformMac::qtToNativeKeyCode(Qt::Key key) // CGEventFlags AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native) { - CGEventFlags nativeModifiers = 0; + CGEventFlags nativeModifiers = CGEventFlags(0); - CGEventFlags shiftMod = shiftKey; - CGEventFlags cmdMod = cmdKey; - CGEventFlags optionMod = optionKey; - CGEventFlags controlMod = controlKey; + CGEventFlags shiftMod = CGEventFlags(shiftKey); + CGEventFlags cmdMod = CGEventFlags(cmdKey); + CGEventFlags optionMod = CGEventFlags(optionKey); + CGEventFlags controlMod = CGEventFlags(controlKey); if (native) { shiftMod = kCGEventFlagMaskShift; From 07050f6e9c7b534f7e4ee1e759ffb075880d8e38 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 20 Apr 2017 19:24:43 -0400 Subject: [PATCH 251/333] Generalizing option to non-data changes. --- src/gui/SettingsWidgetGeneral.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index ffa9f52c0..37a60912c 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -108,7 +108,7 @@ - Do not mark a database as modified when groups are expanded + Don't mark database as modified for non-data changes (e.g., expanding groups) From b706e8f1fdfc95a52de3b87d980e3ba9808c7a03 Mon Sep 17 00:00:00 2001 From: Yen Chi Hsuan Date: Fri, 21 Apr 2017 01:21:50 +0800 Subject: [PATCH 252/333] :bug: Really set the shortcut for "New database" to Ctrl+Shift+N Ref: #316 Fixes #513 --- src/gui/MainWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index f3daf455e..814501f6a 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -156,14 +156,14 @@ MainWindow::MainWindow() this, SLOT(lockDatabasesAfterInactivity())); applySettingsChanges(); - setShortcut(m_ui->actionDatabaseNew, QKeySequence::New, Qt::CTRL + Qt::SHIFT + Qt::Key_N); + m_ui->actionDatabaseNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N); setShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O); setShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S); setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs); setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W); m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L); setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q); - m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::Key_N); + setShortcut(m_ui->actionEntryNew, QKeySequence::New, Qt::CTRL + Qt::Key_N); m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E); m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D); m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K); From dea65b637ca2369524b9b1864eff3b2457fb37b7 Mon Sep 17 00:00:00 2001 From: Vladimir Svyatski Date: Fri, 21 Apr 2017 13:49:32 +0300 Subject: [PATCH 253/333] Add context menu entry to clean the Recycle Bin in databases This implements the feature request (issue) #503. --- .../16x16/actions/group-empty-trash.png | Bin 0 -> 1115 bytes src/gui/DatabaseWidget.cpp | 35 ++++++++++++++++++ src/gui/DatabaseWidget.h | 2 + src/gui/MainWindow.cpp | 6 +++ src/gui/MainWindow.ui | 12 +++++- 5 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 share/icons/application/16x16/actions/group-empty-trash.png diff --git a/share/icons/application/16x16/actions/group-empty-trash.png b/share/icons/application/16x16/actions/group-empty-trash.png new file mode 100644 index 0000000000000000000000000000000000000000..aa9d7321f94a42ed44418c86e8a26a585de0ed7c GIT binary patch literal 1115 zcmYL}YfMvT7{}jpTG~nv6l9Zg2BTe^!!WWI8ih>+x?mVvA$-u6qoJ%qucz;-`>+qs^UwdwFVBZx zp2od-*;RgmJ-sm9PC8Jp8Se;%1bLM zOOC04L|HCZ3385#{mgXO>JX{=g+`Dl;;P;Gzzb626Ed%s_3|csnvh(HW zuM=J*_-pF`Jb1JLXy*#>t#1Ik6M#?#1RORSaIjF|a;{zQz&*h+7wAy+QC9dl66*<1FP*Jf;hHTx63-_BqU{~+Nrttgfbp8( z(qqdHzCnPD3UAUulg2b;>D~p;q{GJ-GA4}0 zSWI4*LKf*%DF148unyZpI06IGGEd^)E`CpZLhhGOABdE>opy$qX6uQC#RXzPc@b~9 zHH!~;Lhx61tJJBEPtVxKSGzGQS*u_!KLs>Hm^^SOSv0nGI4QP{?zT`NZWxxvc80Pj9oIipMY#((L~TBt*lIpLBhRST`w7nm=wCbu9m; z*7^9PsIWTlnfAT5k*d?7~QV4j-ze z@0u*`Q`$~qyQ};8va5n6QkNoycsKx9TgCN((9*Kws%!t_#2x*m)d846&z=z9+L|xZuokT)uwT$-@ zqQpFNr`|{u8SAG^!*`gE(Xkzvh7mMm1&zBf9?d5eRgR5a=C6cCyyzBl)VB(;mBcd4 zI0WTx@A582)U>KM6FQTxhzwT@6dKtZEuc&zbUYuXv0wAJ$d`28u!A^Xr&3qBmU-qL zp%fDu4We4;ZPm7&Ch$wOSVsE@r9;24&$L+uL9duQWm9MGpJek2J(ltD+a|75R1Ue~ zuDWewng?G-n6L8Xn6&l|hV`PD#n_iTKlq{E=K3+f$EZl$&^Zkd|^ho#E`AZJfr*5%h_4gUgKas{FQ literal 0 HcmV?d00001 diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index b4f623155..342f37f09 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1267,3 +1267,38 @@ void DatabaseWidget::hideMessage() m_messageWidget->animatedHide(); } } + +bool DatabaseWidget::isRecycleBinSelected() const +{ + return m_groupView->currentGroup() && m_groupView->currentGroup() == m_db->metadata()->recycleBin(); +} + +void DatabaseWidget::emptyTrash() +{ + Group* currentGroup = m_groupView->currentGroup(); + if (!currentGroup) { + Q_ASSERT(false); + return; + } + + if (currentGroup == m_db->metadata()->recycleBin()) { + QMessageBox::StandardButton result = MessageBox::question( + this, tr("Empty recycle bin?"), + tr("Are you sure you want to permanently delete everytning from your recycle bin?"), + QMessageBox::Yes | QMessageBox::No); + + if (result == QMessageBox::Yes) { + // destroying direct entries of the recycle bin + QList subEntries = currentGroup->entries(); + for (Entry* entry : subEntries) { + delete entry; + } + // destroying direct subgroups of the recycle bin + QList subGroups = currentGroup->children(); + for (Group* group : subGroups) { + delete group; + } + refreshSearch(); + } + } +} diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index d98cb0722..5751aedb4 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -102,6 +102,7 @@ public: void closeUnlockDialog(); void blockAutoReload(bool block = true); void refreshSearch(); + bool isRecycleBinSelected() const; signals: void closeRequest(); @@ -152,6 +153,7 @@ public slots: void switchToImportKeepass1(const QString& fileName); void databaseModified(); void databaseSaved(); + void emptyTrash(); // Search related slots void search(const QString& searchtext); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 814501f6a..72a0f4c31 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -197,6 +197,7 @@ MainWindow::MainWindow() m_ui->actionGroupNew->setIcon(filePath()->icon("actions", "group-new", false)); m_ui->actionGroupEdit->setIcon(filePath()->icon("actions", "group-edit", false)); m_ui->actionGroupDelete->setIcon(filePath()->icon("actions", "group-delete", false)); + m_ui->actionGroupEmptyTrash->setIcon(filePath()->icon("actions", "group-empty-trash", false)); m_ui->actionSettings->setIcon(filePath()->icon("actions", "configure")); m_ui->actionSettings->setMenuRole(QAction::PreferencesRole); @@ -295,6 +296,8 @@ MainWindow::MainWindow() SLOT(switchToGroupEdit())); m_actionMultiplexer.connect(m_ui->actionGroupDelete, SIGNAL(triggered()), SLOT(deleteGroup())); + m_actionMultiplexer.connect(m_ui->actionGroupEmptyTrash, SIGNAL(triggered()), + SLOT(emptyTrash())); connect(m_ui->actionSettings, SIGNAL(triggered()), SLOT(switchToSettings())); connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(switchToPasswordGen(bool))); @@ -413,6 +416,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1; bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0; bool groupSelected = dbWidget->isGroupSelected(); + bool recycleBinSelected = dbWidget->isRecycleBinSelected(); m_ui->actionEntryNew->setEnabled(!inSearch); m_ui->actionEntryClone->setEnabled(singleEntrySelected); @@ -429,6 +433,8 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); + m_ui->actionGroupEmptyTrash->setVisible(recycleBinSelected); + m_ui->actionGroupEmptyTrash->setEnabled(recycleBinSelected); m_ui->actionChangeMasterKey->setEnabled(true); m_ui->actionChangeDatabaseSettings->setEnabled(true); m_ui->actionDatabaseSave->setEnabled(true); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index c44bee9e7..adacff5cb 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -162,7 +162,7 @@ 0 0 800 - 29 + 21 @@ -235,8 +235,10 @@ &Groups + + @@ -521,6 +523,14 @@ Re&pair database + + + Empty recycle bin + + + false + + From 75c16d1cbba7226fea7ada2a85a5b320bcdb06f9 Mon Sep 17 00:00:00 2001 From: Vladimir Svyatski Date: Fri, 21 Apr 2017 17:33:06 +0300 Subject: [PATCH 254/333] Add requested source code changes --- src/core/Database.cpp | 16 ++++++++++++++++ src/core/Database.h | 1 + src/gui/DatabaseWidget.cpp | 31 +++++++++---------------------- src/gui/DatabaseWidget.h | 2 +- src/gui/MainWindow.cpp | 10 +++++----- src/gui/MainWindow.ui | 4 ++-- 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 4aa9e3f5b..5b5a707f9 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -308,6 +308,22 @@ void Database::recycleGroup(Group* group) } } +void Database::emptyRecycleBin() +{ + if (m_metadata->recycleBinEnabled() && m_metadata->recycleBin()) { + // destroying direct entries of the recycle bin + QList subEntries = m_metadata->recycleBin()->entries(); + for (Entry* entry : subEntries) { + delete entry; + } + // destroying direct subgroups of the recycle bin + QList subGroups = m_metadata->recycleBin()->children(); + for (Group* group : subGroups) { + delete group; + } + } +} + void Database::merge(const Database* other) { m_rootGroup->merge(other->rootGroup()); diff --git a/src/core/Database.h b/src/core/Database.h index 16c149608..7728d14c8 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -108,6 +108,7 @@ public: bool verifyKey(const CompositeKey& key) const; void recycleEntry(Entry* entry); void recycleGroup(Group* group); + void emptyRecycleBin(); void setEmitModified(bool value); void copyAttributesFrom(const Database* other); void merge(const Database* other); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 342f37f09..115c560f0 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1273,32 +1273,19 @@ bool DatabaseWidget::isRecycleBinSelected() const return m_groupView->currentGroup() && m_groupView->currentGroup() == m_db->metadata()->recycleBin(); } -void DatabaseWidget::emptyTrash() +void DatabaseWidget::emptyRecycleBin() { - Group* currentGroup = m_groupView->currentGroup(); - if (!currentGroup) { - Q_ASSERT(false); + if(!isRecycleBinSelected()) { return; } - if (currentGroup == m_db->metadata()->recycleBin()) { - QMessageBox::StandardButton result = MessageBox::question( - this, tr("Empty recycle bin?"), - tr("Are you sure you want to permanently delete everytning from your recycle bin?"), - QMessageBox::Yes | QMessageBox::No); + QMessageBox::StandardButton result = MessageBox::question( + this, tr("Empty recycle bin?"), + tr("Are you sure you want to permanently delete everything from your recycle bin?"), + QMessageBox::Yes | QMessageBox::No); - if (result == QMessageBox::Yes) { - // destroying direct entries of the recycle bin - QList subEntries = currentGroup->entries(); - for (Entry* entry : subEntries) { - delete entry; - } - // destroying direct subgroups of the recycle bin - QList subGroups = currentGroup->children(); - for (Group* group : subGroups) { - delete group; - } - refreshSearch(); - } + if (result == QMessageBox::Yes) { + m_db->emptyRecycleBin(); + refreshSearch(); } } diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 5751aedb4..3add336f0 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -153,7 +153,7 @@ public slots: void switchToImportKeepass1(const QString& fileName); void databaseModified(); void databaseSaved(); - void emptyTrash(); + void emptyRecycleBin(); // Search related slots void search(const QString& searchtext); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 72a0f4c31..6883f8a7e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -197,7 +197,7 @@ MainWindow::MainWindow() m_ui->actionGroupNew->setIcon(filePath()->icon("actions", "group-new", false)); m_ui->actionGroupEdit->setIcon(filePath()->icon("actions", "group-edit", false)); m_ui->actionGroupDelete->setIcon(filePath()->icon("actions", "group-delete", false)); - m_ui->actionGroupEmptyTrash->setIcon(filePath()->icon("actions", "group-empty-trash", false)); + m_ui->actionGroupEmptyRecycleBin->setIcon(filePath()->icon("actions", "group-empty-trash", false)); m_ui->actionSettings->setIcon(filePath()->icon("actions", "configure")); m_ui->actionSettings->setMenuRole(QAction::PreferencesRole); @@ -296,8 +296,8 @@ MainWindow::MainWindow() SLOT(switchToGroupEdit())); m_actionMultiplexer.connect(m_ui->actionGroupDelete, SIGNAL(triggered()), SLOT(deleteGroup())); - m_actionMultiplexer.connect(m_ui->actionGroupEmptyTrash, SIGNAL(triggered()), - SLOT(emptyTrash())); + m_actionMultiplexer.connect(m_ui->actionGroupEmptyRecycleBin, SIGNAL(triggered()), + SLOT(emptyRecycleBin())); connect(m_ui->actionSettings, SIGNAL(triggered()), SLOT(switchToSettings())); connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(switchToPasswordGen(bool))); @@ -433,8 +433,8 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); - m_ui->actionGroupEmptyTrash->setVisible(recycleBinSelected); - m_ui->actionGroupEmptyTrash->setEnabled(recycleBinSelected); + m_ui->actionGroupEmptyRecycleBin->setVisible(recycleBinSelected); + m_ui->actionGroupEmptyRecycleBin->setEnabled(recycleBinSelected); m_ui->actionChangeMasterKey->setEnabled(true); m_ui->actionChangeDatabaseSettings->setEnabled(true); m_ui->actionDatabaseSave->setEnabled(true); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index adacff5cb..384586e8d 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -238,7 +238,7 @@ - + @@ -523,7 +523,7 @@ Re&pair database - + Empty recycle bin From 5792bf1a858d66cf2f3b0953e08cae0650f217c4 Mon Sep 17 00:00:00 2001 From: Vladimir Svyatski Date: Sat, 22 Apr 2017 10:02:59 +0300 Subject: [PATCH 255/333] Add skeleton for TestDatabase.cpp and test data for unit tests for the "empty recycle bin" functionality --- tests/CMakeLists.txt | 3 ++ tests/TestDatabase.cpp | 50 ++++++++++++++++++++++++ tests/TestDatabase.h | 37 ++++++++++++++++++ tests/data/RecycleBinDisabled.kdbx | Bin 0 -> 2302 bytes tests/data/RecycleBinEmpty.kdbx | Bin 0 -> 2430 bytes tests/data/RecycleBinNotYetCreated.kdbx | Bin 0 -> 2350 bytes tests/data/RecycleBinWithData.kdbx | Bin 0 -> 3854 bytes 7 files changed, 90 insertions(+) create mode 100644 tests/TestDatabase.cpp create mode 100644 tests/TestDatabase.h create mode 100644 tests/data/RecycleBinDisabled.kdbx create mode 100644 tests/data/RecycleBinEmpty.kdbx create mode 100644 tests/data/RecycleBinNotYetCreated.kdbx create mode 100644 tests/data/RecycleBinWithData.kdbx diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2ea50424a..1c9f1c0f5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -177,6 +177,9 @@ add_unit_test(NAME testykchallengeresponsekey SOURCES TestYkChallengeResponseKey.cpp TestYkChallengeResponseKey.h LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp + LIBS ${TEST_LIBRARIES}) + if(WITH_GUI_TESTS) add_subdirectory(gui) endif(WITH_GUI_TESTS) diff --git a/tests/TestDatabase.cpp b/tests/TestDatabase.cpp new file mode 100644 index 000000000..584fde434 --- /dev/null +++ b/tests/TestDatabase.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 Vladimir Svyatski + * + * 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 . + */ + +#include "TestDatabase.h" + +#include + +#include "core/Database.h" +#include "crypto/Crypto.h" + +QTEST_GUILESS_MAIN(TestDatabase) + +void TestDatabase::initTestCase() +{ + QVERIFY(Crypto::init()); +} + +void TestDatabase::testEmptyRecycleBinOnDisabled() +{ + +} + +void TestDatabase::testEmptyRecycleBinOnNotCreated() +{ + +} + +void TestDatabase::testEmptyRecycleBinOnEmpty() +{ + +} + +void TestDatabase::testEmptyRecycleBinWithHierarchicalData() +{ + +} diff --git a/tests/TestDatabase.h b/tests/TestDatabase.h new file mode 100644 index 000000000..550155225 --- /dev/null +++ b/tests/TestDatabase.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 Vladimir Svyatski + * + * 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 . + */ + +#ifndef KEEPASSX_TESTDATABASE_H +#define KEEPASSX_TESTDATABASE_H + +#include + +class Database; + +class TestDatabase : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testEmptyRecycleBinOnDisabled(); + void testEmptyRecycleBinOnNotCreated(); + void testEmptyRecycleBinOnEmpty(); + void testEmptyRecycleBinWithHierarchicalData(); +}; + +#endif // KEEPASSX_TESTDATABASE_H diff --git a/tests/data/RecycleBinDisabled.kdbx b/tests/data/RecycleBinDisabled.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..0bbfb3efee2ceecebb86991548b659289d102799 GIT binary patch literal 2302 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 Q0Bj7l@RJbtWXS~2c!b@0 Date: Sat, 22 Apr 2017 14:35:01 +0300 Subject: [PATCH 256/333] Add recycle bin test cases body --- tests/TestDatabase.cpp | 41 ++++++++++++++++++++++++++++++++++++++++- tests/TestDatabase.h | 2 -- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/tests/TestDatabase.cpp b/tests/TestDatabase.cpp index 584fde434..66eb870f7 100644 --- a/tests/TestDatabase.cpp +++ b/tests/TestDatabase.cpp @@ -18,9 +18,12 @@ #include "TestDatabase.h" #include +#include +#include "config-keepassx-tests.h" #include "core/Database.h" #include "crypto/Crypto.h" +#include "keys/PasswordKey.h" QTEST_GUILESS_MAIN(TestDatabase) @@ -31,20 +34,56 @@ void TestDatabase::initTestCase() void TestDatabase::testEmptyRecycleBinOnDisabled() { + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinDisabled.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("123")); + Database* db = Database::openDatabaseFile(filename, key); + QVERIFY(db); + QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); + + db->emptyRecycleBin(); + //The database must be unmodified in this test after emptying the recycle bin. + QCOMPARE(spyModified.count(), 0); + + delete db; } void TestDatabase::testEmptyRecycleBinOnNotCreated() { + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinNotYetCreated.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("123")); + Database* db = Database::openDatabaseFile(filename, key); + QVERIFY(db); + QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); + + db->emptyRecycleBin(); + //The database must be unmodified in this test after emptying the recycle bin. + QCOMPARE(spyModified.count(), 0); + + delete db; } void TestDatabase::testEmptyRecycleBinOnEmpty() { + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinEmpty.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("123")); + Database* db = Database::openDatabaseFile(filename, key); + QVERIFY(db); + QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); + + db->emptyRecycleBin(); + //The database must be unmodified in this test after emptying the recycle bin. + QCOMPARE(spyModified.count(), 0); + + delete db; } void TestDatabase::testEmptyRecycleBinWithHierarchicalData() { - +//TODO: implement } diff --git a/tests/TestDatabase.h b/tests/TestDatabase.h index 550155225..dc9609d72 100644 --- a/tests/TestDatabase.h +++ b/tests/TestDatabase.h @@ -20,8 +20,6 @@ #include -class Database; - class TestDatabase : public QObject { Q_OBJECT From c613f44991c52584121b8eeaeb26364a6ad91866 Mon Sep 17 00:00:00 2001 From: Vladimir Svyatski Date: Sun, 23 Apr 2017 00:50:26 +0300 Subject: [PATCH 257/333] Finish test cases for emptying recycle bin --- tests/TestDatabase.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/TestDatabase.cpp b/tests/TestDatabase.cpp index 66eb870f7..a70ada19d 100644 --- a/tests/TestDatabase.cpp +++ b/tests/TestDatabase.cpp @@ -19,11 +19,15 @@ #include #include +#include #include "config-keepassx-tests.h" #include "core/Database.h" #include "crypto/Crypto.h" #include "keys/PasswordKey.h" +#include "core/Metadata.h" +#include "core/Group.h" +#include "format/KeePass2Writer.h" QTEST_GUILESS_MAIN(TestDatabase) @@ -85,5 +89,24 @@ void TestDatabase::testEmptyRecycleBinOnEmpty() void TestDatabase::testEmptyRecycleBinWithHierarchicalData() { -//TODO: implement + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinWithData.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("123")); + Database* db = Database::openDatabaseFile(filename, key); + QVERIFY(db); + + QFile originalFile(filename); + qint64 initialSize = originalFile.size(); + + db->emptyRecycleBin(); + QVERIFY(db->metadata()->recycleBin()); + QVERIFY(db->metadata()->recycleBin()->entries().empty()); + QVERIFY(db->metadata()->recycleBin()->children().empty()); + + QTemporaryFile afterCleanup; + KeePass2Writer writer; + writer.writeDatabase(&afterCleanup, db); + QVERIFY(afterCleanup.size() < initialSize); + + delete db; } From 2e2e37098f0f58b1fe74291ef68264563d57f716 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Mon, 10 Apr 2017 17:39:54 +0200 Subject: [PATCH 258/333] [CSV import GUI] add option to choose dropdown menu field names from CSV This should also close #458 --- src/gui/csvImport/CsvImportWidget.cpp | 21 +- src/gui/csvImport/CsvImportWidget.h | 2 +- src/gui/csvImport/CsvImportWidget.ui | 588 +++++++++++--------------- 3 files changed, 261 insertions(+), 350 deletions(-) diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index ed00790f5..1f71e2ad7 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -92,6 +92,7 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) connect(m_ui->comboBoxComment, SIGNAL(currentIndexChanged(int)), SLOT(parse())); connect(m_ui->comboBoxFieldSeparator, SIGNAL(currentIndexChanged(int)), SLOT(parse())); connect(m_ui->checkBoxBackslash, SIGNAL(toggled(bool)), SLOT(parse())); + connect(m_ui->checkBoxFieldNames, SIGNAL(toggled(bool)), SLOT(updatePreview())); connect(m_comboMapper, SIGNAL(mapped(int)), this, SLOT(comboChanged(int))); connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(writeDatabase())); @@ -131,16 +132,26 @@ void CsvImportWidget::updateTableview() { void CsvImportWidget::updatePreview() { + int minSkip = 0; + if (m_ui->checkBoxFieldNames->isChecked()) + minSkip = 1; m_ui->labelSizeRowsCols->setText(m_parserModel->getFileInfo()); - m_ui->spinBoxSkip->setValue(0); - m_ui->spinBoxSkip->setMaximum(qMax(0, m_parserModel->rowCount() - 1)); + m_ui->spinBoxSkip->setRange(minSkip, qMax(minSkip, m_parserModel->rowCount() - 1)); + m_ui->spinBoxSkip->setValue(minSkip); - int i; + int i, emptyId = 0; + QString columnName; QStringList list(tr("Not present in CSV file")); for (i = 1; i < m_parserModel->getCsvCols(); ++i) { - QString s = QString(tr("Column ")) + QString::number(i); - list << s; + if (m_ui->checkBoxFieldNames->isChecked()) { + columnName = m_parserModel->getCsvTable().at(0).at(i); + if (columnName.isEmpty()) + columnName = "<" + tr("Empty fieldname ") + QString::number(++emptyId) + ">"; + list << columnName; + } else { + list << QString(tr("column ")) + QString::number(i); + } } m_comboModel->setStringList(list); diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index aa463c08b..9215fd368 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -53,6 +53,7 @@ private slots: void comboChanged(int comboId); void skippedChanged(int rows); void writeDatabase(); + void updatePreview(); void setRootGroup(); void reject(); @@ -68,7 +69,6 @@ private: KeePass2Writer m_writer; static const QStringList m_columnHeader; void configParser(); - void updatePreview(); void updateTableview(); Group* splitGroups(QString label); Group* hasChildren(Group* current, QString groupName); diff --git a/src/gui/csvImport/CsvImportWidget.ui b/src/gui/csvImport/CsvImportWidget.ui index 1b4bed729..df0af79f1 100644 --- a/src/gui/csvImport/CsvImportWidget.ui +++ b/src/gui/csvImport/CsvImportWidget.ui @@ -6,15 +6,15 @@ 0 0 - 779 - 691 + 892 + 525 - + @@ -67,7 +67,23 @@ - + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 758 + 24 + + + + + @@ -91,48 +107,6 @@ Encoding - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 114 - 20 - - - - - - - - Qt::Horizontal - - - - 114 - 20 - - - - - - - - - 50 - false - - - - Consider '\' an escape character - - - @@ -149,167 +123,7 @@ - - - - - 50 - false - true - - - - - - - - - - - - 50 - false - - - - Fields are separated by - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 50 - false - - - - false - - - - - - - - 50 - false - - - - Comments start with - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 50 - false - - - - false - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - - 50 - false - - - - Text is qualified by - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - + @@ -334,79 +148,105 @@ - - - - Qt::Horizontal + + + + + 50 + false + - - QSizePolicy::Expanding + + Text is qualified by - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + - 114 - 20 + 0 + 0 - + + + 50 + false + + + + false + + + + + + + + 50 + false + + + + Fields are separated by + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 50 + false + + + + false + + - - - - - - 50 - false - - - - Skip first - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 50 - false - - - - - - - - - 50 - false - - - - rows - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + 50 + false + + + + Comments start with + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + - + @@ -431,96 +271,112 @@ - + + + + + 50 + false + + + + First record has field names + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 50 + false + + + + Number of headers line to discard + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + false + + + + + + + Qt::Horizontal - 114 + 122 20 - - - - Qt::Horizontal + + + + + 0 + 0 + - - - 114 - 20 - + + + 50 + false + - + + Consider '\' an escape character + + + + + + + + 50 + false + true + + + + + + - - - - 0 - 0 - - - - - 75 - true - - - - Column layout - - - - - - 6 - - - 6 - - - 0 - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 758 - 24 - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - @@ -569,6 +425,50 @@ + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Column layout + + + + + + 6 + + + 6 + + + 0 + + + + + + From eb7f4d2eaabfe314ef670c58458375db4e15b156 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Thu, 27 Apr 2017 22:11:26 +0200 Subject: [PATCH 259/333] Apply requested changes --- src/gui/csvImport/CsvImportWidget.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 1f71e2ad7..93cba84c0 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -139,11 +139,11 @@ void CsvImportWidget::updatePreview() { m_ui->spinBoxSkip->setRange(minSkip, qMax(minSkip, m_parserModel->rowCount() - 1)); m_ui->spinBoxSkip->setValue(minSkip); - int i, emptyId = 0; + int emptyId = 0; QString columnName; QStringList list(tr("Not present in CSV file")); - for (i = 1; i < m_parserModel->getCsvCols(); ++i) { + for (int i = 1; i < m_parserModel->getCsvCols(); ++i) { if (m_ui->checkBoxFieldNames->isChecked()) { columnName = m_parserModel->getCsvTable().at(0).at(i); if (columnName.isEmpty()) @@ -155,13 +155,13 @@ void CsvImportWidget::updatePreview() { } m_comboModel->setStringList(list); - i=1; + int j=1; for (QComboBox* b : m_combos) { - if (i < m_parserModel->getCsvCols()) - b->setCurrentIndex(i); + if (j < m_parserModel->getCsvCols()) + b->setCurrentIndex(j); else b->setCurrentIndex(0); - ++i; + ++j; } } From a94efddfe66265746d8ce007021bdba45302c480 Mon Sep 17 00:00:00 2001 From: Weslly Date: Fri, 28 Apr 2017 11:03:15 -0300 Subject: [PATCH 260/333] Fix compile issue on OSX El Capitan --- src/autotype/mac/AutoTypeMac.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 51a5c5cda..b0e1360f3 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -419,16 +419,16 @@ CGEventFlags AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modi if (modifiers & Qt::ShiftModifier) { - nativeModifiers |= shiftMod; + nativeModifiers = CGEventFlags(nativeModifiers | shiftMod); } if (modifiers & Qt::ControlModifier) { - nativeModifiers |= cmdMod; + nativeModifiers = CGEventFlags(nativeModifiers | cmdMod); } if (modifiers & Qt::AltModifier) { - nativeModifiers |= optionMod; + nativeModifiers = CGEventFlags(nativeModifiers | optionMod); } if (modifiers & Qt::MetaModifier) { - nativeModifiers |= controlMod; + nativeModifiers = CGEventFlags(nativeModifiers | controlMod); } return nativeModifiers; From fa7c945363e86e2cf5d7f992461a17842febfe16 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 25 Feb 2017 17:41:37 -0500 Subject: [PATCH 261/333] Adding EASCII character class. --- src/core/PasswordGenerator.cpp | 16 ++++++++++++++++ src/core/PasswordGenerator.h | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index cee1c55be..8517d85c5 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -195,6 +195,22 @@ QVector PasswordGenerator::passwordGroups() const passwordGroups.append(group); } + if (m_classes & EASCII) { + PasswordGroup group; + + for (int i = 128; i <= 169; i++) { + group.append(i); + } + + for (int i = 171; i <= 254; i++) { + if ((m_flags & ExcludeLookAlike) && (i == 249)) { // "﹒" + continue; + } + group.append(i); + } + + passwordGroups.append(group); + } return passwordGroups; } diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index b47159324..7c909411c 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -32,7 +32,8 @@ public: LowerLetters = 0x1, UpperLetters = 0x2, Numbers = 0x4, - SpecialCharacters = 0x8 + SpecialCharacters = 0x8, + EASCII = 0x16 }; Q_DECLARE_FLAGS(CharClasses, CharClass) From b474d34cf0a0d6c4bc85b0956b0e4d73b65dc014 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 28 Apr 2017 21:36:43 +0200 Subject: [PATCH 262/333] Add Extended ASCII to password generator --- src/core/PasswordGenerator.cpp | 3 ++ src/core/PasswordGenerator.h | 2 +- src/gui/PasswordGeneratorWidget.cpp | 9 ++++++ src/gui/PasswordGeneratorWidget.ui | 47 ++++++++++++++++++++++++++--- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index 8517d85c5..8b0f99f8b 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -231,6 +231,9 @@ int PasswordGenerator::numCharClasses() const if (m_classes & SpecialCharacters) { numClasses++; } + if (m_classes & EASCII) { + numClasses++; + } return numClasses; } diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index 7c909411c..8ec82c0a0 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -33,7 +33,7 @@ public: UpperLetters = 0x2, Numbers = 0x4, SpecialCharacters = 0x8, - EASCII = 0x16 + EASCII = 0x10 }; Q_DECLARE_FLAGS(CharClasses, CharClass) diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 1f5606cf6..e585b6f58 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -92,6 +92,7 @@ void PasswordGeneratorWidget::loadSettings() m_ui->checkBoxUpper->setChecked(config()->get("generator/UpperCase", true).toBool()); m_ui->checkBoxNumbers->setChecked(config()->get("generator/Numbers", true).toBool()); m_ui->checkBoxSpecialChars->setChecked(config()->get("generator/SpecialChars", false).toBool()); + m_ui->checkBoxExtASCII->setChecked(config()->get("generator/EASCII", false).toBool()); m_ui->checkBoxExcludeAlike->setChecked(config()->get("generator/ExcludeAlike", true).toBool()); m_ui->checkBoxEnsureEvery->setChecked(config()->get("generator/EnsureEvery", true).toBool()); m_ui->spinBoxLength->setValue(config()->get("generator/Length", 16).toInt()); @@ -112,6 +113,7 @@ void PasswordGeneratorWidget::saveSettings() config()->set("generator/UpperCase", m_ui->checkBoxUpper->isChecked()); config()->set("generator/Numbers", m_ui->checkBoxNumbers->isChecked()); config()->set("generator/SpecialChars", m_ui->checkBoxSpecialChars->isChecked()); + config()->set("generator/EASCII", m_ui->checkBoxExtASCII->isChecked()); config()->set("generator/ExcludeAlike", m_ui->checkBoxExcludeAlike->isChecked()); config()->set("generator/EnsureEvery", m_ui->checkBoxEnsureEvery->isChecked()); config()->set("generator/Length", m_ui->spinBoxLength->value()); @@ -287,6 +289,10 @@ PasswordGenerator::CharClasses PasswordGeneratorWidget::charClasses() classes |= PasswordGenerator::SpecialCharacters; } + if (m_ui->checkBoxExtASCII->isChecked()) { + classes |= PasswordGenerator::EASCII; + } + return classes; } @@ -325,6 +331,9 @@ void PasswordGeneratorWidget::updateGenerator() if (classes.testFlag(PasswordGenerator::SpecialCharacters)) { minLength++; } + if (classes.testFlag(PasswordGenerator::EASCII)) { + minLength++; + } } minLength = qMax(minLength, 1); diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index bb8bc0e76..a7af3d7f0 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -222,11 +222,11 @@ QProgressBar::chunk { - + - + 0 0 @@ -257,7 +257,7 @@ QProgressBar::chunk { - + 0 0 @@ -288,7 +288,7 @@ QProgressBar::chunk { - + 0 0 @@ -319,7 +319,7 @@ QProgressBar::chunk { - + 0 0 @@ -347,6 +347,43 @@ QProgressBar::chunk { + + + + + 0 + 0 + + + + + 0 + 25 + + + + + 16777215 + 16777215 + + + + Qt::StrongFocus + + + Extended ASCII + + + Extended ASCII + + + true + + + optionButtons + + + From 40b4dc3b61b36c250a21de74dd2660fb4384683a Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 1 May 2017 01:18:42 +0200 Subject: [PATCH 263/333] Only printable extended ASCII --- src/core/PasswordGenerator.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index 8b0f99f8b..0fa5198fc 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -198,11 +198,13 @@ QVector PasswordGenerator::passwordGroups() const if (m_classes & EASCII) { PasswordGroup group; - for (int i = 128; i <= 169; i++) { + // [U+0080, U+009F] are C1 control characters, + // U+00A0 is non-breaking space + for (int i = 161; i <= 172; i++) { group.append(i); } - - for (int i = 171; i <= 254; i++) { + // U+00AD is soft hyphen (format character) + for (int i = 174; i <= 255; i++) { if ((m_flags & ExcludeLookAlike) && (i == 249)) { // "﹒" continue; } From bf57a286545a5fce783c463d208c7ffce9c115ef Mon Sep 17 00:00:00 2001 From: Weslly Date: Thu, 13 Apr 2017 07:05:36 -0300 Subject: [PATCH 264/333] Add TOTP support --- share/translations/keepassx_en.ts | 16 +++ src/CMakeLists.txt | 6 ++ src/autotype/AutoType.cpp | 8 ++ src/core/Entry.cpp | 77 +++++++++++++- src/core/Entry.h | 9 ++ src/gui/DatabaseWidget.cpp | 55 ++++++++++ src/gui/DatabaseWidget.h | 4 + src/gui/MainWindow.cpp | 15 +++ src/gui/MainWindow.ui | 27 +++++ src/gui/SetupTotpDialog.cpp | 101 ++++++++++++++++++ src/gui/SetupTotpDialog.h | 54 ++++++++++ src/gui/SetupTotpDialog.ui | 137 ++++++++++++++++++++++++ src/gui/TotpDialog.cpp | 100 ++++++++++++++++++ src/gui/TotpDialog.h | 56 ++++++++++ src/gui/TotpDialog.ui | 60 +++++++++++ src/totp/totp.cpp | 170 ++++++++++++++++++++++++++++++ src/totp/totp.h | 34 ++++++ tests/CMakeLists.txt | 3 + tests/TestTotp.cpp | 102 ++++++++++++++++++ tests/TestTotp.h | 36 +++++++ tests/gui/TestGui.cpp | 51 +++++++++ tests/gui/TestGui.h | 1 + 22 files changed, 1120 insertions(+), 2 deletions(-) create mode 100644 src/gui/SetupTotpDialog.cpp create mode 100644 src/gui/SetupTotpDialog.h create mode 100644 src/gui/SetupTotpDialog.ui create mode 100644 src/gui/TotpDialog.cpp create mode 100644 src/gui/TotpDialog.h create mode 100644 src/gui/TotpDialog.ui create mode 100644 src/totp/totp.cpp create mode 100644 src/totp/totp.h create mode 100644 tests/TestTotp.cpp create mode 100644 tests/TestTotp.h diff --git a/share/translations/keepassx_en.ts b/share/translations/keepassx_en.ts index 0638beb97..2fbe1d3be 100644 --- a/share/translations/keepassx_en.ts +++ b/share/translations/keepassx_en.ts @@ -1235,6 +1235,22 @@ This is a one-way migration. You won't be able to open the imported databas &Clone entry + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a87a21b2c..d50d27b69 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -100,6 +100,8 @@ set(keepassx_SOURCES gui/SettingsWidget.cpp gui/SearchWidget.cpp gui/SortFilterHideProxyModel.cpp + gui/SetupTotpDialog.cpp + gui/TotpDialog.cpp gui/UnlockDatabaseWidget.cpp gui/UnlockDatabaseDialog.cpp gui/WelcomeWidget.cpp @@ -129,6 +131,8 @@ set(keepassx_SOURCES streams/qtiocompressor.cpp streams/StoreDataStream.cpp streams/SymmetricCipherStream.cpp + totp/totp.h + totp/totp.cpp ) set(keepassx_SOURCES_MAINEXE @@ -151,6 +155,8 @@ set(keepassx_FORMS gui/SearchWidget.ui gui/SettingsWidgetGeneral.ui gui/SettingsWidgetSecurity.ui + gui/SetupTotpDialog.ui + gui/TotpDialog.ui gui/WelcomeWidget.ui gui/entry/EditEntryWidgetAdvanced.ui gui/entry/EditEntryWidgetAutoType.ui diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 1d96377f5..12367fe95 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -518,6 +518,14 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c else if (tmplName.compare("clearfield",Qt::CaseInsensitive)==0) { list.append(new AutoTypeClearField()); } + else if (tmplName.compare("totp", Qt::CaseInsensitive) == 0) { + QString totp = entry->totp(); + if (!totp.isEmpty()) { + for (const QChar& ch : totp) { + list.append(new AutoTypeChar(ch)); + } + } + } if (!list.isEmpty()) { return list; diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index d1672c5b1..e86a7092f 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -14,13 +14,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - #include "Entry.h" #include "core/Database.h" #include "core/DatabaseIcons.h" #include "core/Group.h" #include "core/Metadata.h" +#include "totp/totp.h" const int Entry::DefaultIconNumber = 0; @@ -35,6 +35,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; connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified())); connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged())); @@ -285,6 +287,77 @@ const EntryAttachments* Entry::attachments() const return m_attachments; } +bool Entry::hasTotp() const +{ + return m_attributes->hasKey("TOTP Seed") || m_attributes->hasKey("otp"); +} + +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); + + return QString(output); + } else { + return QString(""); + } +} + +void Entry::setTotp(const QString& seed, quint8& step, quint8& digits) +{ + if (step == 0) { + step = QTotp::defaultStep; + } + + if (digits == 0) { + digits = QTotp::defaultDigits; + } + + if (m_attributes->hasKey("otp")) { + m_attributes->set("otp", QString("key=%1&step=%2&size=%3").arg(seed).arg(step).arg(digits), true); + } else { + m_attributes->set("TOTP Seed", seed, true); + m_attributes->set("TOTP Settings", QString("%1;%2").arg(step).arg(digits)); + } +} + +QString Entry::totpSeed() const +{ + QString secret = ""; + + if (m_attributes->hasKey("otp")) { + secret = m_attributes->value("otp"); + } else { + secret = m_attributes->value("TOTP Seed"); + } + + m_data.totpDigits = QTotp::defaultDigits; + m_data.totpStep = QTotp::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(); + } + } + + return QTotp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep); +} + +quint8 Entry::totpStep() const +{ + return m_data.totpStep; +} + +quint8 Entry::totpDigits() const +{ + return m_data.totpDigits; +} + void Entry::setUuid(const Uuid& uuid) { Q_ASSERT(!uuid.isNull()); @@ -679,7 +752,7 @@ QString Entry::resolvePlaceholder(const QString& str) const k.prepend("{"); } - + k.append("}"); if (result.compare(k,cs)==0) { result.replace(result,attributes()->value(key)); diff --git a/src/core/Entry.h b/src/core/Entry.h index 25b9bc386..cdb826eca 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -47,6 +47,8 @@ struct EntryData int autoTypeObfuscation; QString defaultAutoTypeSequence; TimeInfo timeInfo; + mutable quint8 totpDigits; + mutable quint8 totpStep; }; class Entry : public QObject @@ -78,6 +80,12 @@ public: QString username() const; QString password() const; QString notes() const; + QString totp() const; + QString totpSeed() const; + quint8 totpDigits() const; + quint8 totpStep() const; + + bool hasTotp() const; bool isExpired() const; bool hasReferences() const; EntryAttributes* attributes(); @@ -105,6 +113,7 @@ public: void setNotes(const QString& notes); void setExpires(const bool& value); void setExpiryTime(const QDateTime& dateTime); + void setTotp(const QString& seed, quint8& step, quint8& digits); QList historyItems(); const QList& historyItems() const; diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 115c560f0..946757e40 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -42,6 +42,8 @@ #include "gui/ChangeMasterKeyWidget.h" #include "gui/Clipboard.h" #include "gui/CloneDialog.h" +#include "gui/SetupTotpDialog.h" +#include "gui/TotpDialog.h" #include "gui/DatabaseOpenWidget.h" #include "gui/DatabaseSettingsWidget.h" #include "gui/KeePass1OpenWidget.h" @@ -333,6 +335,48 @@ void DatabaseWidget::cloneEntry() return; } +void DatabaseWidget::showTotp() +{ + Entry* currentEntry = m_entryView->currentEntry(); + if (!currentEntry) { + Q_ASSERT(false); + return; + } + + TotpDialog* totpDialog = new TotpDialog(this, currentEntry); + totpDialog->open(); +} + +void DatabaseWidget::copyTotp() +{ + Entry* currentEntry = m_entryView->currentEntry(); + if (!currentEntry) { + Q_ASSERT(false); + return; + } + setClipboardTextAndMinimize(currentEntry->totp()); +} + +void DatabaseWidget::setupTotp() +{ + Entry* currentEntry = m_entryView->currentEntry(); + if (!currentEntry) { + Q_ASSERT(false); + return; + } + + SetupTotpDialog* setupTotpDialog = new SetupTotpDialog(this, currentEntry); + if (currentEntry->hasTotp()) { + setupTotpDialog->setSeed(currentEntry->totpSeed()); + setupTotpDialog->setStep(currentEntry->totpStep()); + setupTotpDialog->setDigits(currentEntry->totpDigits()); + } + + setupTotpDialog->open(); + +} + + void DatabaseWidget::deleteEntries() { const QModelIndexList selected = m_entryView->selectionModel()->selectedRows(); @@ -1225,6 +1269,17 @@ bool DatabaseWidget::currentEntryHasUrl() return !currentEntry->resolveMultiplePlaceholders(currentEntry->url()).isEmpty(); } + +bool DatabaseWidget::currentEntryHasTotp() +{ + Entry* currentEntry = m_entryView->currentEntry(); + if (!currentEntry) { + Q_ASSERT(false); + return false; + } + return currentEntry->hasTotp(); +} + bool DatabaseWidget::currentEntryHasNotes() { Entry* currentEntry = m_entryView->currentEntry(); diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 3add336f0..aa1c83443 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -96,6 +96,7 @@ public: bool currentEntryHasPassword(); bool currentEntryHasUrl(); bool currentEntryHasNotes(); + bool currentEntryHasTotp(); GroupView* groupView(); EntryView* entryView(); void showUnlockDialog(); @@ -133,6 +134,9 @@ public slots: void copyURL(); void copyNotes(); void copyAttribute(QAction* action); + void showTotp(); + void copyTotp(); + void setupTotp(); void performAutoType(); void openUrl(); void openUrlForEntry(Entry* entry); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 6883f8a7e..0415f2b4e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -167,6 +167,8 @@ MainWindow::MainWindow() m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E); m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D); m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K); + m_ui->actionEntryTotp->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_T); + m_ui->actionEntryCopyTotp->setShortcut(Qt::CTRL + Qt::Key_T); m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B); m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C); setShortcut(m_ui->actionEntryAutoType, QKeySequence::Paste, Qt::CTRL + Qt::Key_V); @@ -275,6 +277,13 @@ MainWindow::MainWindow() m_actionMultiplexer.connect(m_ui->actionEntryDelete, SIGNAL(triggered()), SLOT(deleteEntries())); + m_actionMultiplexer.connect(m_ui->actionEntryTotp, SIGNAL(triggered()), + SLOT(showTotp())); + m_actionMultiplexer.connect(m_ui->actionEntrySetupTotp, SIGNAL(triggered()), + SLOT(setupTotp())); + + m_actionMultiplexer.connect(m_ui->actionEntryCopyTotp, SIGNAL(triggered()), + SLOT(copyTotp())); m_actionMultiplexer.connect(m_ui->actionEntryCopyTitle, SIGNAL(triggered()), SLOT(copyTitle())); m_actionMultiplexer.connect(m_ui->actionEntryCopyUsername, SIGNAL(triggered()), @@ -428,8 +437,12 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasNotes()); m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected); + m_ui->menuEntryTotp->setEnabled(true); m_ui->actionEntryAutoType->setEnabled(singleEntrySelected); m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); + m_ui->actionEntryTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); + m_ui->actionEntryCopyTotp->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTotp()); + m_ui->actionEntrySetupTotp->setEnabled(singleEntrySelected); m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); @@ -463,6 +476,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryCopyURL->setEnabled(false); m_ui->actionEntryCopyNotes->setEnabled(false); m_ui->menuEntryCopyAttribute->setEnabled(false); + m_ui->menuEntryTotp->setEnabled(false); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); @@ -495,6 +509,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryCopyURL->setEnabled(false); m_ui->actionEntryCopyNotes->setEnabled(false); m_ui->menuEntryCopyAttribute->setEnabled(false); + m_ui->menuEntryTotp->setEnabled(false); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 384586e8d..2ed42d0ec 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -220,9 +220,21 @@ + + + false + + + Timed one-time password + + + + + + @@ -523,6 +535,21 @@ Re&pair database + + + Show TOTP + + + + + Setup TOTP + + + + + Copy &TOTP + + Empty recycle bin diff --git a/src/gui/SetupTotpDialog.cpp b/src/gui/SetupTotpDialog.cpp new file mode 100644 index 000000000..43a042df9 --- /dev/null +++ b/src/gui/SetupTotpDialog.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * + * 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 . + */ + +#include "SetupTotpDialog.h" +#include "ui_SetupTotpDialog.h" +#include "totp/totp.h" + + +SetupTotpDialog::SetupTotpDialog(DatabaseWidget* parent, Entry* entry) + : QDialog(parent) + , m_ui(new Ui::SetupTotpDialog()) +{ + m_entry = entry; + m_parent = parent; + + m_ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + this->setFixedSize(this->sizeHint()); + + 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))); +} + + +void SetupTotpDialog::setupTotp() +{ + quint8 digits; + + if (m_ui->radio8Digits->isChecked()) { + digits = 8; + } else { + digits = 6; + } + + quint8 step = m_ui->stepSpinBox->value(); + QString seed = m_ui->seedEdit->text(); + m_entry->setTotp(seed, step, digits); + emit m_parent->entrySelectionChanged(); + close(); +} + +void SetupTotpDialog::toggleCustom(bool status) +{ + m_ui->digitsLabel->setEnabled(status); + m_ui->radio6Digits->setEnabled(status); + m_ui->radio8Digits->setEnabled(status); + + m_ui->stepLabel->setEnabled(status); + m_ui->stepSpinBox->setEnabled(status); +} + + +void SetupTotpDialog::setSeed(QString value) +{ + m_ui->seedEdit->setText(value); +} + +void SetupTotpDialog::setStep(quint8 step) +{ + m_ui->stepSpinBox->setValue(step); + + if (step != QTotp::defaultStep) { + m_ui->customSettingsCheckBox->setChecked(true); + } +} + +void SetupTotpDialog::setDigits(quint8 digits) +{ + if (digits == 8) { + m_ui->radio8Digits->setChecked(true); + m_ui->radio6Digits->setChecked(false); + } else { + m_ui->radio6Digits->setChecked(true); + m_ui->radio8Digits->setChecked(false); + } + + if (digits != QTotp::defaultDigits) { + m_ui->customSettingsCheckBox->setChecked(true); + } +} + + +SetupTotpDialog::~SetupTotpDialog() +{ +} diff --git a/src/gui/SetupTotpDialog.h b/src/gui/SetupTotpDialog.h new file mode 100644 index 000000000..416e19a5c --- /dev/null +++ b/src/gui/SetupTotpDialog.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * + * 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 . + */ + +#ifndef KEEPASSX_SETUPTOTPDIALOG_H +#define KEEPASSX_SETUPTOTPDIALOG_H + +#include +#include +#include "core/Entry.h" +#include "core/Database.h" +#include "gui/DatabaseWidget.h" + +namespace Ui { + class SetupTotpDialog; +} + +class SetupTotpDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SetupTotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr); + ~SetupTotpDialog(); + void setSeed(QString value); + void setStep(quint8 step); + void setDigits(quint8 digits); + +private Q_SLOTS: + void toggleCustom(bool status); + void setupTotp(); + +private: + QScopedPointer m_ui; + +protected: + Entry* m_entry; + DatabaseWidget* m_parent; +}; + +#endif // KEEPASSX_SETUPTOTPDIALOG_H diff --git a/src/gui/SetupTotpDialog.ui b/src/gui/SetupTotpDialog.ui new file mode 100644 index 000000000..a6d806287 --- /dev/null +++ b/src/gui/SetupTotpDialog.ui @@ -0,0 +1,137 @@ + + + SetupTotpDialog + + + + 0 + 0 + 282 + 257 + + + + Setup TOTP + + + + + + + + Key: + + + + + + + + + + + + Use custom settings + + + + + + + Note: Change these settings only if you know what you are doing. + + + true + + + + + + + QFormLayout::ExpandingFieldsGrow + + + QFormLayout::DontWrapRows + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + false + + + Time step: + + + + + + + false + + + 8 digits + + + + + + + false + + + 6 digits + + + true + + + + + + + false + + + Code size: + + + + + + + false + + + sec + + + 1 + + + 60 + + + 30 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/gui/TotpDialog.cpp b/src/gui/TotpDialog.cpp new file mode 100644 index 000000000..2eb1c9e2c --- /dev/null +++ b/src/gui/TotpDialog.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * + * 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 . + */ + +#include "TotpDialog.h" +#include "ui_TotpDialog.h" + +#include "core/Config.h" +#include "core/Entry.h" +#include "gui/DatabaseWidget.h" +#include "gui/Clipboard.h" + +#include +#include +#include + + +TotpDialog::TotpDialog(DatabaseWidget* parent, Entry* entry) + : QDialog(parent) + , m_ui(new Ui::TotpDialog()) +{ + m_entry = entry; + m_parent = parent; + m_step = m_entry->totpStep(); + + m_ui->setupUi(this); + + uCounter = resetCounter(); + updateProgressBar(); + + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(updateProgressBar())); + connect(timer, SIGNAL(timeout()), this, SLOT(updateSeconds())); + timer->start(m_step * 10); + + updateTotp(); + + setAttribute(Qt::WA_DeleteOnClose); + + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Copy")); + + connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close())); + connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(copyToClipboard())); +} + +void TotpDialog::copyToClipboard() +{ + clipboard()->setText(m_entry->totp()); + if (config()->get("MinimizeOnCopy").toBool()) { + m_parent->window()->showMinimized(); + } +} + +void TotpDialog::updateProgressBar() +{ + if (uCounter < 100) { + m_ui->progressBar->setValue(100 - uCounter); + m_ui->progressBar->update(); + uCounter++; + } else { + updateTotp(); + uCounter = resetCounter(); + } +} + + +void TotpDialog::updateSeconds() +{ + uint epoch = QDateTime::currentDateTime().toTime_t() - 1; + m_ui->timerLabel->setText(tr("Expires in") + " " + QString::number(m_step - (epoch % m_step)) + " " + tr("seconds")); +} + +void TotpDialog::updateTotp() +{ + m_ui->totpLabel->setText(m_entry->totp()); +} + +double TotpDialog::resetCounter() +{ + uint epoch = QDateTime::currentDateTime().toTime_t(); + double counter = qRound(static_cast(epoch % m_step) / m_step * 100); + return counter; +} + +TotpDialog::~TotpDialog() +{ +} diff --git a/src/gui/TotpDialog.h b/src/gui/TotpDialog.h new file mode 100644 index 000000000..66754dd29 --- /dev/null +++ b/src/gui/TotpDialog.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * + * 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 . + */ + +#ifndef KEEPASSX_TOTPDIALOG_H +#define KEEPASSX_TOTPDIALOG_H + +#include +#include +#include "core/Entry.h" +#include "core/Database.h" +#include "gui/DatabaseWidget.h" + +namespace Ui { + class TotpDialog; +} + +class TotpDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TotpDialog(DatabaseWidget* parent = nullptr, Entry* entry = nullptr); + ~TotpDialog(); + +private: + double uCounter; + quint8 m_step; + QScopedPointer m_ui; + +private Q_SLOTS: + void updateTotp(); + void updateProgressBar(); + void updateSeconds(); + void copyToClipboard(); + double resetCounter(); + +protected: + Entry* m_entry; + DatabaseWidget* m_parent; +}; + +#endif // KEEPASSX_TOTPDIALOG_H diff --git a/src/gui/TotpDialog.ui b/src/gui/TotpDialog.ui new file mode 100644 index 000000000..e11e761e9 --- /dev/null +++ b/src/gui/TotpDialog.ui @@ -0,0 +1,60 @@ + + + TotpDialog + + + + 0 + 0 + 264 + 194 + + + + Timed Password + + + + + + + 53 + + + + 000000 + + + Qt::AlignCenter + + + + + + + 0 + + + false + + + + + + + + + + + + + + QDialogButtonBox::Close|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp new file mode 100644 index 000000000..45e98d38d --- /dev/null +++ b/src/totp/totp.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * + * 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 . + */ + +#include "totp.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +const quint8 QTotp::defaultStep = 30; +const quint8 QTotp::defaultDigits = 6; + +QTotp::QTotp() +{ +} + +QString QTotp::parseOtpString(QString key, quint8 &digits, quint8 &step) +{ + QUrl url(key); + + QString seed; + uint q_digits, q_step; + + // Default OTP url format + if (url.isValid() && url.scheme() == "otpauth") { + QUrlQuery query(url); + + seed = query.queryItemValue("secret"); + + q_digits = query.queryItemValue("digits").toUInt(); + if (q_digits == 6 || q_digits == 8) { + digits = q_digits; + } + + q_step = query.queryItemValue("period").toUInt(); + if (q_step > 0 && q_step <= 60) { + step = q_step; + } + + + } else { + // Compatibility with "KeeOtp" plugin string format + QRegExp rx("key=(.+)", Qt::CaseInsensitive, QRegExp::RegExp); + + if (rx.exactMatch(key)) { + QUrlQuery query(key); + + seed = query.queryItemValue("key"); + q_digits = query.queryItemValue("size").toUInt(); + if (q_digits == 6 || q_digits == 8) { + digits = q_digits; + } + + q_step = query.queryItemValue("step").toUInt(); + if (q_step > 0 && q_step <= 60) { + step = q_step; + } + + } else { + seed = key; + } + } + + if (digits == 0) { + digits = defaultDigits; + } + + if (step == 0) { + step = defaultStep; + } + + return seed; +} + + +QByteArray QTotp::base32_decode(const QByteArray encoded) +{ + // Base32 implementation + // Copyright 2010 Google Inc. + // Author: Markus Gutschke + // Licensed under the Apache License, Version 2.0 + + QByteArray result; + + int buffer = 0; + int bitsLeft = 0; + + for (char ch : encoded) { + if (ch == 0 || ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-' || ch == '=') { + continue; + } + + buffer <<= 5; + + // Deal with commonly mistyped characters + if (ch == '0') { + ch = 'O'; + } else if (ch == '1') { + ch = 'L'; + } else if (ch == '8') { + ch = 'B'; + } + + // Look up one base32 digit + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if (ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return QByteArray(); + } + + buffer |= ch; + bitsLeft += 5; + + if (bitsLeft >= 8) { + result.append(static_cast (buffer >> (bitsLeft - 8))); + bitsLeft -= 8; + } + } + + return result; +} + + +QString QTotp::generateTotp(const QByteArray key, quint64 time, const quint8 numDigits = defaultDigits, const quint8 step = defaultStep) +{ + quint64 current = qToBigEndian(time / step); + + QByteArray secret = QTotp::base32_decode(key); + if (secret.isEmpty()) { + return "Invalid TOTP secret key"; + } + + QMessageAuthenticationCode code(QCryptographicHash::Sha1); + code.setKey(secret); + code.addData(QByteArray(reinterpret_cast(¤t), sizeof(current))); + QByteArray hmac = code.result(); + + int offset = (hmac[hmac.length() - 1] & 0xf); + int binary = + ((hmac[offset] & 0x7f) << 24) + | ((hmac[offset + 1] & 0xff) << 16) + | ((hmac[offset + 2] & 0xff) << 8) + | (hmac[offset + 3] & 0xff); + + quint32 digitsPower = pow(10, numDigits); + + quint64 password = binary % digitsPower; + return QString("%1").arg(password, numDigits, 10, QChar('0')); +} diff --git a/src/totp/totp.h b/src/totp/totp.h new file mode 100644 index 000000000..8d7e86744 --- /dev/null +++ b/src/totp/totp.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * + * 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 . + */ + +#ifndef QTOTP_H +#define QTOTP_H + +#include + +class QTotp +{ +public: + QTotp(); + static QString parseOtpString(QString rawSecret, quint8 &digits, quint8 &step); + static QByteArray base32_decode(const QByteArray encoded); + static QString generateTotp(const QByteArray key, quint64 time, const quint8 numDigits, const quint8 step); + static const quint8 defaultStep; + static const quint8 defaultDigits; +}; + +#endif // QTOTP_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1c9f1c0f5..67661f55c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -158,6 +158,9 @@ endif() add_unit_test(NAME testentry SOURCES TestEntry.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testtotp SOURCES TestTotp.cpp + LIBS ${TEST_LIBRARIES}) + add_unit_test(NAME testcsvparser SOURCES TestCsvParser.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestTotp.cpp b/tests/TestTotp.cpp new file mode 100644 index 000000000..c1fe5943c --- /dev/null +++ b/tests/TestTotp.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * + * 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 . + */ + +#include "TestTotp.h" + +#include +#include +#include +#include +#include + +#include "crypto/Crypto.h" +#include "totp/totp.h" + +QTEST_GUILESS_MAIN(TestTotp) + +void TestTotp::initTestCase() +{ + QVERIFY(Crypto::init()); +} + + +void TestTotp::testSecret() +{ + quint8 digits = 0; + quint8 step = 0; + QString secret = "otpauth://totp/ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30"; + QCOMPARE(QTotp::parseOtpString(secret, digits, step), QString("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ")); + QCOMPARE(digits, quint8(6)); + QCOMPARE(step, quint8(30)); + + digits = QTotp::defaultDigits; + step = QTotp::defaultStep; + secret = "key=HXDMVJECJJWSRBY%3d&step=25&size=8"; + QCOMPARE(QTotp::parseOtpString(secret, digits, step), QString("HXDMVJECJJWSRBY=")); + QCOMPARE(digits, quint8(8)); + QCOMPARE(step, quint8(25)); + + digits = 0; + step = 0; + secret = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; + QCOMPARE(QTotp::parseOtpString(secret, digits, step), QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq")); + QCOMPARE(digits, quint8(6)); + QCOMPARE(step, quint8(30)); +} + +void TestTotp::testBase32() +{ + QByteArray key = QString("JBSW Y3DP EB3W 64TM MQXC 4LQA").toLatin1(); + QByteArray secret = QTotp::base32_decode(key); + QCOMPARE(QString::fromLatin1(secret), QString("Hello world...")); + + key = QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq").toLatin1(); + secret = QTotp::base32_decode(key); + QCOMPARE(QString::fromLatin1(secret), QString("12345678901234567890")); + + key = QString("ORSXG5A=").toLatin1(); + secret = QTotp::base32_decode(key); + QCOMPARE(QString::fromLatin1(secret), QString("test")); + + key = QString("MZXW6YTBOI======").toLatin1(); + secret = QTotp::base32_decode(key); + QCOMPARE(QString::fromLatin1(secret), QString("foobar")); +} + +void TestTotp::testTotpCode() +{ + // Test vectors from RFC 6238 + // https://tools.ietf.org/html/rfc6238#appendix-B + + QByteArray seed = QString("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ").toLatin1(); + + quint64 time = 1234567890; + QString output = QTotp::generateTotp(seed, time, 6, 30); + QCOMPARE(output, QString("005924")); + + time = 1111111109; + output = QTotp::generateTotp(seed, time, 6, 30); + QCOMPARE(output, QString("081804")); + + time = 1111111111; + output = QTotp::generateTotp(seed, time, 8, 30); + QCOMPARE(output, QString("14050471")); + + time = 2000000000; + output = QTotp::generateTotp(seed, time, 8, 30); + QCOMPARE(output, QString("69279037")); +} diff --git a/tests/TestTotp.h b/tests/TestTotp.h new file mode 100644 index 000000000..7bfa68055 --- /dev/null +++ b/tests/TestTotp.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * + * 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 . + */ + +#ifndef KEEPASSX_TESTTOTP_H +#define KEEPASSX_TESTTOTP_H + +#include + +class Totp; + +class TestTotp : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testSecret(); + void testBase32(); + void testTotpCode(); +}; + +#endif // KEEPASSX_TESTTOTP_H diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 5516aff7c..5a4142660 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -48,6 +48,8 @@ #include "gui/DatabaseTabWidget.h" #include "gui/DatabaseWidget.h" #include "gui/CloneDialog.h" +#include "gui/TotpDialog.h" +#include "gui/SetupTotpDialog.h" #include "gui/FileDialog.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" @@ -441,6 +443,55 @@ void TestGui::testDicewareEntryEntropy() QCOMPARE(strengthLabel->text(), QString("Password Quality: Good")); } +void TestGui::testTotp() +{ + QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + EntryView* entryView = m_dbWidget->findChild("entryView"); + + QCOMPARE(entryView->model()->rowCount(), 1); + + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); + QModelIndex item = entryView->model()->index(0, 1); + Entry* entry = entryView->entryFromIndex(item); + + clickIndex(item, entryView, Qt::LeftButton); + + triggerAction("actionEntrySetupTotp"); + + SetupTotpDialog* setupTotpDialog = m_dbWidget->findChild("SetupTotpDialog"); + + Tools::wait(100); + + QLineEdit* seedEdit = setupTotpDialog->findChild("seedEdit"); + + QString exampleSeed = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; + QTest::keyClicks(seedEdit, exampleSeed); + + QDialogButtonBox* setupTotpButtonBox = setupTotpDialog->findChild("buttonBox"); + QTest::mouseClick(setupTotpButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); + + QAction* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); + QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); + QTest::mouseClick(entryEditWidget, Qt::LeftButton); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); + EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + + editEntryWidget->setCurrentPage(1); + QPlainTextEdit* attrTextEdit = editEntryWidget->findChild("attributesEdit"); + QTest::mouseClick(editEntryWidget->findChild("revealAttributeButton"), Qt::LeftButton); + QCOMPARE(attrTextEdit->toPlainText(), exampleSeed); + + QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); + + triggerAction("actionEntryTotp"); + + TotpDialog* totpDialog = m_dbWidget->findChild("TotpDialog"); + QLabel* totpLabel = totpDialog->findChild("totpLabel"); + + QCOMPARE(totpLabel->text(), entry->totp()); +} + void TestGui::testSearch() { // Add canned entries for consistent testing diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index fce5d69ee..e5d41fb64 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -46,6 +46,7 @@ private slots: void testAddEntry(); void testPasswordEntryEntropy(); void testDicewareEntryEntropy(); + void testTotp(); void testSearch(); void testDeleteEntry(); void testCloneEntry(); From 4c9b8c77943b58e610910adb33d98def179357d7 Mon Sep 17 00:00:00 2001 From: Weslly Date: Wed, 3 May 2017 20:54:19 -0300 Subject: [PATCH 265/333] Review fixes --- src/CMakeLists.txt | 2 ++ src/core/Entry.cpp | 2 +- src/gui/TotpDialog.cpp | 2 +- src/totp/base32.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++ src/totp/base32.h | 34 +++++++++++++++++++++ src/totp/totp.cpp | 57 +++-------------------------------- src/totp/totp.h | 1 - tests/TestTotp.cpp | 11 +++---- tests/TestTotp.h | 2 +- 9 files changed, 117 insertions(+), 62 deletions(-) create mode 100644 src/totp/base32.cpp create mode 100644 src/totp/base32.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d50d27b69..750b6481c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -131,6 +131,8 @@ set(keepassx_SOURCES streams/qtiocompressor.cpp streams/StoreDataStream.cpp streams/SymmetricCipherStream.cpp + totp/base32.h + totp/base32.cpp totp/totp.h totp/totp.cpp ) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index e86a7092f..418e2a81e 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -329,7 +329,7 @@ QString Entry::totpSeed() const if (m_attributes->hasKey("otp")) { secret = m_attributes->value("otp"); - } else { + } else if (m_attributes->hasKey("TOTP Seed")) { secret = m_attributes->value("TOTP Seed"); } diff --git a/src/gui/TotpDialog.cpp b/src/gui/TotpDialog.cpp index 2eb1c9e2c..521ddbafe 100644 --- a/src/gui/TotpDialog.cpp +++ b/src/gui/TotpDialog.cpp @@ -41,7 +41,7 @@ TotpDialog::TotpDialog(DatabaseWidget* parent, Entry* entry) uCounter = resetCounter(); updateProgressBar(); - QTimer *timer = new QTimer(this); + QTimer* timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(updateProgressBar())); connect(timer, SIGNAL(timeout()), this, SLOT(updateSeconds())); timer->start(m_step * 10); diff --git a/src/totp/base32.cpp b/src/totp/base32.cpp new file mode 100644 index 000000000..07526aa02 --- /dev/null +++ b/src/totp/base32.cpp @@ -0,0 +1,68 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// Source: https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c +// Modifications copyright (C) 2017 KeePassXC team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base32.h" + +Base32::Base32() +{ +} + +QByteArray Base32::base32_decode(const QByteArray encoded) +{ + QByteArray result; + + int buffer = 0; + int bitsLeft = 0; + + for (char ch : encoded) { + if (ch == 0 || ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-' || ch == '=') { + continue; + } + + buffer <<= 5; + + // Deal with commonly mistyped characters + if (ch == '0') { + ch = 'O'; + } else if (ch == '1') { + ch = 'L'; + } else if (ch == '8') { + ch = 'B'; + } + + // Look up one base32 digit + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if (ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return QByteArray(); + } + + buffer |= ch; + bitsLeft += 5; + + if (bitsLeft >= 8) { + result.append(static_cast (buffer >> (bitsLeft - 8))); + bitsLeft -= 8; + } + } + + return result; +} \ No newline at end of file diff --git a/src/totp/base32.h b/src/totp/base32.h new file mode 100644 index 000000000..3f1965b2f --- /dev/null +++ b/src/totp/base32.h @@ -0,0 +1,34 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// Source: https://github.com/google/google-authenticator-libpam/blob/master/src/base32.h +// Modifications copyright (C) 2017 KeePassXC team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BASE32_H +#define BASE32_H + +#include +#include + +class Base32 +{ +public: + Base32(); + static QByteArray base32_decode(const QByteArray encoded); +}; + + +#endif //BASE32_H diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp index 45e98d38d..f85c76f06 100644 --- a/src/totp/totp.cpp +++ b/src/totp/totp.cpp @@ -16,6 +16,7 @@ */ #include "totp.h" +#include "base32.h" #include #include #include @@ -91,62 +92,12 @@ QString QTotp::parseOtpString(QString key, quint8 &digits, quint8 &step) return seed; } - -QByteArray QTotp::base32_decode(const QByteArray encoded) -{ - // Base32 implementation - // Copyright 2010 Google Inc. - // Author: Markus Gutschke - // Licensed under the Apache License, Version 2.0 - - QByteArray result; - - int buffer = 0; - int bitsLeft = 0; - - for (char ch : encoded) { - if (ch == 0 || ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-' || ch == '=') { - continue; - } - - buffer <<= 5; - - // Deal with commonly mistyped characters - if (ch == '0') { - ch = 'O'; - } else if (ch == '1') { - ch = 'L'; - } else if (ch == '8') { - ch = 'B'; - } - - // Look up one base32 digit - if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { - ch = (ch & 0x1F) - 1; - } else if (ch >= '2' && ch <= '7') { - ch -= '2' - 26; - } else { - return QByteArray(); - } - - buffer |= ch; - bitsLeft += 5; - - if (bitsLeft >= 8) { - result.append(static_cast (buffer >> (bitsLeft - 8))); - bitsLeft -= 8; - } - } - - return result; -} - - -QString QTotp::generateTotp(const QByteArray key, quint64 time, const quint8 numDigits = defaultDigits, const quint8 step = defaultStep) +QString QTotp::generateTotp(const QByteArray key, quint64 time, + const quint8 numDigits = defaultDigits, const quint8 step = defaultStep) { quint64 current = qToBigEndian(time / step); - QByteArray secret = QTotp::base32_decode(key); + QByteArray secret = Base32::base32_decode(key); if (secret.isEmpty()) { return "Invalid TOTP secret key"; } diff --git a/src/totp/totp.h b/src/totp/totp.h index 8d7e86744..260babc22 100644 --- a/src/totp/totp.h +++ b/src/totp/totp.h @@ -25,7 +25,6 @@ class QTotp public: QTotp(); static QString parseOtpString(QString rawSecret, quint8 &digits, quint8 &step); - static QByteArray base32_decode(const QByteArray encoded); static QString generateTotp(const QByteArray key, quint64 time, const quint8 numDigits, const quint8 step); static const quint8 defaultStep; static const quint8 defaultDigits; diff --git a/tests/TestTotp.cpp b/tests/TestTotp.cpp index c1fe5943c..e5da3c642 100644 --- a/tests/TestTotp.cpp +++ b/tests/TestTotp.cpp @@ -25,6 +25,7 @@ #include "crypto/Crypto.h" #include "totp/totp.h" +#include "totp/base32.h" QTEST_GUILESS_MAIN(TestTotp) @@ -34,7 +35,7 @@ void TestTotp::initTestCase() } -void TestTotp::testSecret() +void TestTotp::testParseSecret() { quint8 digits = 0; quint8 step = 0; @@ -61,19 +62,19 @@ void TestTotp::testSecret() void TestTotp::testBase32() { QByteArray key = QString("JBSW Y3DP EB3W 64TM MQXC 4LQA").toLatin1(); - QByteArray secret = QTotp::base32_decode(key); + QByteArray secret = Base32::base32_decode(key); QCOMPARE(QString::fromLatin1(secret), QString("Hello world...")); key = QString("gezdgnbvgy3tqojqgezdgnbvgy3tqojq").toLatin1(); - secret = QTotp::base32_decode(key); + secret = Base32::base32_decode(key); QCOMPARE(QString::fromLatin1(secret), QString("12345678901234567890")); key = QString("ORSXG5A=").toLatin1(); - secret = QTotp::base32_decode(key); + secret = Base32::base32_decode(key); QCOMPARE(QString::fromLatin1(secret), QString("test")); key = QString("MZXW6YTBOI======").toLatin1(); - secret = QTotp::base32_decode(key); + secret = Base32::base32_decode(key); QCOMPARE(QString::fromLatin1(secret), QString("foobar")); } diff --git a/tests/TestTotp.h b/tests/TestTotp.h index 7bfa68055..9871aaf27 100644 --- a/tests/TestTotp.h +++ b/tests/TestTotp.h @@ -28,7 +28,7 @@ class TestTotp : public QObject private slots: void initTestCase(); - void testSecret(); + void testParseSecret(); void testBase32(); void testTotpCode(); }; From d3ed14ebb7ffe94debe29fb64659e3e0168a08b2 Mon Sep 17 00:00:00 2001 From: Weslly Date: Wed, 3 May 2017 21:26:08 -0300 Subject: [PATCH 266/333] Display TOTP code split in halfs --- src/gui/TotpDialog.cpp | 7 +++++-- tests/gui/TestGui.cpp | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/gui/TotpDialog.cpp b/src/gui/TotpDialog.cpp index 521ddbafe..058210870 100644 --- a/src/gui/TotpDialog.cpp +++ b/src/gui/TotpDialog.cpp @@ -67,7 +67,7 @@ void TotpDialog::copyToClipboard() void TotpDialog::updateProgressBar() { if (uCounter < 100) { - m_ui->progressBar->setValue(100 - uCounter); + m_ui->progressBar->setValue(static_cast(100 - uCounter)); m_ui->progressBar->update(); uCounter++; } else { @@ -85,7 +85,10 @@ void TotpDialog::updateSeconds() void TotpDialog::updateTotp() { - m_ui->totpLabel->setText(m_entry->totp()); + QString totpCode = m_entry->totp(); + QString firstHalf = totpCode.left(totpCode.size()/2); + QString secondHalf = totpCode.right(totpCode.size()/2); + m_ui->totpLabel->setText(firstHalf + " " + secondHalf); } double TotpDialog::resetCounter() diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 5a4142660..b1cc02462 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -489,7 +489,7 @@ void TestGui::testTotp() TotpDialog* totpDialog = m_dbWidget->findChild("TotpDialog"); QLabel* totpLabel = totpDialog->findChild("totpLabel"); - QCOMPARE(totpLabel->text(), entry->totp()); + QCOMPARE(totpLabel->text().replace(" ", ""), entry->totp()); } void TestGui::testSearch() From 3640053415f6a749f10bfbdd86ce2962653520d0 Mon Sep 17 00:00:00 2001 From: Weslly Date: Wed, 3 May 2017 22:00:58 -0300 Subject: [PATCH 267/333] Parse TOTP input string before first save --- src/gui/SetupTotpDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SetupTotpDialog.cpp b/src/gui/SetupTotpDialog.cpp index 43a042df9..b088e8217 100644 --- a/src/gui/SetupTotpDialog.cpp +++ b/src/gui/SetupTotpDialog.cpp @@ -49,7 +49,7 @@ void SetupTotpDialog::setupTotp() } quint8 step = m_ui->stepSpinBox->value(); - QString seed = m_ui->seedEdit->text(); + QString seed = QTotp::parseOtpString(m_ui->seedEdit->text(), digits, step); m_entry->setTotp(seed, step, digits); emit m_parent->entrySelectionChanged(); close(); From af4f56abfd66458c74df4439eba3775eb2df372d Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Fri, 5 May 2017 20:20:27 -0400 Subject: [PATCH 268/333] Support vscode --- .gitignore | 2 ++ CMakeLists.txt | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 3a63e9705..0521a42e3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ release*/ .idea/ *.iml *.kdev4 + +\.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2da138277..d7ef18a6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,9 @@ cmake_minimum_required(VERSION 2.8.12) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +# Support Visual Studio Code +include(CMakeToolsHelpers OPTIONAL) + include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) include(CheckCXXSourceCompiles) From 5c8809e55d9763fc87ca5394aa6f5f388576ef4f Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 6 May 2017 11:50:05 -0400 Subject: [PATCH 269/333] Update cmake files, fixes #331 --- CMakeLists.txt | 10 ++++++--- cmake/FindLibMicroHTTPD.cmake | 9 -------- src/CMakeLists.txt | 39 +++-------------------------------- src/http/CMakeLists.txt | 7 ------- 4 files changed, 10 insertions(+), 55 deletions(-) delete mode 100644 cmake/FindLibMicroHTTPD.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index d7ef18a6e..cde795ad2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,8 +42,13 @@ 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) -set(KEEPASSXC_VERSION "2.1.4") -set(KEEPASSXC_VERSION_NUM "2.1.4") +# Process ui files automatically from source files +set(CMAKE_AUTOUIC ON) + +set(KEEPASSXC_VERSION_MAJOR "2") +set(KEEPASSXC_VERSION_MINOR "1") +set(KEEPASSXC_VERSION_PATCH "4") +set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}") if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_COMPILER_IS_CLANG 1) @@ -157,7 +162,6 @@ if(MINGW) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") endif() - link_libraries(ws2_32 wsock32) endif() if(APPLE OR MINGW) diff --git a/cmake/FindLibMicroHTTPD.cmake b/cmake/FindLibMicroHTTPD.cmake deleted file mode 100644 index f31928043..000000000 --- a/cmake/FindLibMicroHTTPD.cmake +++ /dev/null @@ -1,9 +0,0 @@ - -find_path(MHD_INCLUDE_DIR microhttpd.h) - -find_library(MHD_LIBRARIES microhttpd) - -mark_as_advanced(MHD_LIBRARIES MHD_INCLUDE_DIR) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibMicroHTTPD DEFAULT_MSG MHD_LIBRARIES MHD_INCLUDE_DIR) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 750b6481c..786a03a80 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,32 +141,6 @@ set(keepassx_SOURCES_MAINEXE main.cpp ) -set(keepassx_FORMS - gui/AboutDialog.ui - gui/ChangeMasterKeyWidget.ui - gui/CloneDialog.ui - gui/csvImport/CsvImportWidget.ui - gui/DatabaseOpenWidget.ui - gui/DatabaseSettingsWidget.ui - gui/CategoryListWidget.ui - gui/EditWidget.ui - gui/EditWidgetIcons.ui - gui/EditWidgetProperties.ui - gui/MainWindow.ui - gui/PasswordGeneratorWidget.ui - gui/SearchWidget.ui - gui/SettingsWidgetGeneral.ui - gui/SettingsWidgetSecurity.ui - gui/SetupTotpDialog.ui - gui/TotpDialog.ui - gui/WelcomeWidget.ui - gui/entry/EditEntryWidgetAdvanced.ui - gui/entry/EditEntryWidgetAutoType.ui - gui/entry/EditEntryWidgetHistory.ui - gui/entry/EditEntryWidgetMain.ui - gui/group/EditGroupWidgetMain.ui -) - 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") @@ -199,13 +173,11 @@ if(MINGW) endif() if(WITH_XC_YUBIKEY) - set(keepassx_SOURCES ${keepassx_SOURCES} keys/drivers/YubiKey.cpp) + list(APPEND keepassx_SOURCES keys/drivers/YubiKey.cpp) else() - set(keepassx_SOURCES ${keepassx_SOURCES} keys/drivers/YubiKeyStub.cpp) + list(APPEND keepassx_SOURCES keys/drivers/YubiKeyStub.cpp) endif() -qt5_wrap_ui(keepassx_SOURCES ${keepassx_FORMS}) - add_library(zxcvbn STATIC zxcvbn/zxcvbn.cpp) target_link_libraries(zxcvbn) @@ -233,11 +205,6 @@ if (UNIX AND NOT APPLE) endif() if(MINGW) - string(REPLACE "." ";" VERSION_LIST ${KEEPASSXC_VERSION}) - list(GET VERSION_LIST 0 KEEPASSXC_VERSION_MAJOR) - list(GET VERSION_LIST 1 KEEPASSXC_VERSION_MINOR) - list(GET VERSION_LIST 2 KEEPASSXC_VERSION_PATCH) - include(GenerateProductVersion) generate_product_version( WIN32_ProductVersionFiles @@ -275,7 +242,7 @@ if(APPLE) set(CPACK_DMG_VOLUME_NAME "${PROGNAME}") set(CPACK_SYSTEM_NAME "OSX") set(CPACK_STRIP_FILES ON) - set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION_NUM}") + set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}") include(CPack) if(NOT DEFINED QT_BINARY_DIR) diff --git a/src/http/CMakeLists.txt b/src/http/CMakeLists.txt index 53fe7e176..8a3b197ab 100644 --- a/src/http/CMakeLists.txt +++ b/src/http/CMakeLists.txt @@ -13,13 +13,6 @@ if(WITH_XC_HTTP) Server.cpp Service.cpp ) - set(keepasshttp_FORMS - AccessControlDialog.ui - HttpPasswordGeneratorWidget.ui - OptionDialog.ui - ) - - qt5_wrap_ui(keepasshttp_SOURCES ${keepasshttp_FORMS}) add_library(keepasshttp STATIC ${keepasshttp_SOURCES}) target_link_libraries(keepasshttp qhttp Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network) From e4e2e886e87507b1e7d521c819ecc9f7b6930163 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 6 May 2017 23:31:28 -0400 Subject: [PATCH 270/333] Added small delays to autotype on Linux --- src/autotype/xcb/AutoTypeXCB.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index 9290639f2..e15396122 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -848,11 +848,13 @@ AutoTypeExecutorX11::AutoTypeExecutorX11(AutoTypePlatformX11* platform) void AutoTypeExecutorX11::execChar(AutoTypeChar* action) { m_platform->SendKeyPressedEvent(m_platform->charToKeySym(action->character)); + Tools::wait(25); } void AutoTypeExecutorX11::execKey(AutoTypeKey* action) { m_platform->SendKeyPressedEvent(m_platform->keyToKeySym(action->key)); + Tools::wait(25); } void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr) From b3160a17ea1a8731c4d818ddc711d76b90464f46 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 8 May 2017 23:27:54 +0200 Subject: [PATCH 271/333] enable minimize on close for macOS --- src/gui/MainWindow.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0415f2b4e..c3ab306eb 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -660,8 +660,12 @@ void MainWindow::databaseTabChanged(int tabIndex) void MainWindow::closeEvent(QCloseEvent* event) { - bool minimizeOnClose = isTrayIconEnabled() && - config()->get("GUI/MinimizeOnClose").toBool(); + bool minimizeOnClose = config()->get("GUI/MinimizeOnClose").toBool(); +#ifndef Q_OS_MAC + // if we aren't on OS X, check if the tray is enabled. + // on OS X we are using the dock for the minimize action + minimizeOnClose = isTrayIconEnabled() && minimizeOnClose; +#endif if (minimizeOnClose && !appExitCalled) { event->ignore(); From d1310b33378fb5bc62f36328b2923b5c563988fa Mon Sep 17 00:00:00 2001 From: VukoDrakkeinen Date: Mon, 25 Jul 2016 06:41:13 +0200 Subject: [PATCH 272/333] Raise existing instance Closes #193 --- CMakeLists.txt | 1 + src/CMakeLists.txt | 1 + src/gui/Application.cpp | 63 +++++++++++++++++++++++++++++++++++++++++ src/gui/Application.h | 8 ++++++ src/main.cpp | 15 +++++++++- 5 files changed, 87 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cde795ad2..04136751f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,7 @@ if(WITH_TESTS) endif(WITH_TESTS) find_package(Qt5Core 5.2 REQUIRED) +find_package(Qt5Network 5.2 REQUIRED) find_package(Qt5Concurrent 5.2 REQUIRED) find_package(Qt5Widgets 5.2 REQUIRED) find_package(Qt5Test 5.2 REQUIRED) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 786a03a80..d0fb3ab51 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -195,6 +195,7 @@ target_link_libraries(keepassx_core ${YUBIKEY_LIBRARIES} zxcvbn Qt5::Core + Qt5::Network Qt5::Concurrent Qt5::Widgets ${GCRYPT_LIBRARIES} diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index f64a766f5..6a7e37057 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include #include "autotype/AutoType.h" @@ -76,6 +79,8 @@ Application::Application(int& argc, char** argv) #ifdef Q_OS_UNIX , m_unixSignalNotifier(nullptr) #endif + , alreadyRunning(false) + , lock(nullptr) { #if defined(Q_OS_UNIX) && !defined(Q_OS_OSX) installNativeEventFilter(new XcbEventFilter()); @@ -85,6 +90,58 @@ Application::Application(int& argc, char** argv) #if defined(Q_OS_UNIX) registerUnixSignals(); #endif + + QString userName = qgetenv("USER"); + if (userName.isEmpty()) { + userName = qgetenv("USERNAME"); + } + QString identifier = "keepassx2"; + if (!userName.isEmpty()) { + identifier.append("-"); + identifier.append(userName); + } + QString socketName = identifier + ".socket"; + QString lockName = identifier + ".lock"; + + // According to documentation we should use RuntimeLocation on *nixes, but even Qt doesn't respect + // this and creates sockets in TempLocation, so let's be consistent. + lock = new QLockFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + lockName); + lock->setStaleLockTime(0); + lock->tryLock(); + switch (lock->error()) { + case QLockFile::NoError: + server.setSocketOptions(QLocalServer::UserAccessOption); + server.listen(socketName); + connect(&server, SIGNAL(newConnection()), this, SIGNAL(anotherInstanceStarted())); + break; + case QLockFile::LockFailedError: { + alreadyRunning = true; + // notify the other instance + // try several times, in case the other instance is still starting up + QLocalSocket client; + for (int i = 0; i < 3; i++) { + client.connectToServer(socketName); + if (client.waitForConnected(150)) { + client.abort(); + break; + } + } + break; + } + default: + qWarning() << QCoreApplication::translate("Main", + "The lock file could not be created. Single-instance mode disabled.") + .toUtf8().constData(); + } +} + +Application::~Application() +{ + server.close(); + if (lock) { + lock->unlock(); + delete lock; + } } QWidget* Application::mainWindow() const @@ -171,3 +228,9 @@ void Application::quitBySignal() static_cast(m_mainWindow)->appExit(); } #endif + +bool Application::isAlreadyRunning() const +{ + return alreadyRunning; +} + diff --git a/src/gui/Application.h b/src/gui/Application.h index fd5ad12e0..1a1b0ee86 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -20,6 +20,8 @@ #define KEEPASSX_APPLICATION_H #include +#include +class QLockFile; class QSocketNotifier; @@ -30,12 +32,15 @@ class Application : public QApplication public: Application(int& argc, char** argv); QWidget* mainWindow() const; + ~Application(); void setMainWindow(QWidget* mainWindow); bool event(QEvent* event) override; + bool isAlreadyRunning() const; signals: void openFile(const QString& filename); + void anotherInstanceStarted(); private slots: #if defined(Q_OS_UNIX) @@ -54,6 +59,9 @@ private: static void handleUnixSignal(int sig); static int unixSignalSocket[2]; #endif + bool alreadyRunning; + QLockFile* lock; + QLocalServer server; }; #endif // KEEPASSX_APPLICATION_H diff --git a/src/main.cpp b/src/main.cpp index 5981999e7..a4416e0a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,6 +56,11 @@ int main(int argc, char** argv) // don't set organizationName as that changes the return value of // QStandardPaths::writableLocation(QDesktopServices::DataLocation) + if (app.isAlreadyRunning()) { + qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassX 2 is already running.").toUtf8().constData(); + return 0; + } + QApplication::setQuitOnLastWindowClosed(false); if (!Crypto::init()) { @@ -102,7 +107,15 @@ int main(int argc, char** argv) MainWindow mainWindow; app.setMainWindow(&mainWindow); - + + QObject::connect(&app, &Application::anotherInstanceStarted, + [&]() { + mainWindow.ensurePolished(); + mainWindow.setWindowState(mainWindow.windowState() & ~Qt::WindowMinimized); + mainWindow.show(); + mainWindow.raise(); + mainWindow.activateWindow(); + }); QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); // start minimized if configured From 58463bc3dc86e19f0226b6b4278af4331936b27e Mon Sep 17 00:00:00 2001 From: Anton Gulenko Date: Tue, 18 Apr 2017 16:14:23 +0200 Subject: [PATCH 273/333] Fixed string literals --- src/gui/Application.cpp | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 6a7e37057..1ae94ac87 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -95,7 +95,7 @@ Application::Application(int& argc, char** argv) if (userName.isEmpty()) { userName = qgetenv("USERNAME"); } - QString identifier = "keepassx2"; + QString identifier = "keepassxc"; if (!userName.isEmpty()) { identifier.append("-"); identifier.append(userName); diff --git a/src/main.cpp b/src/main.cpp index a4416e0a9..baa3df425 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,7 +57,7 @@ int main(int argc, char** argv) // QStandardPaths::writableLocation(QDesktopServices::DataLocation) if (app.isAlreadyRunning()) { - qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassX 2 is already running.").toUtf8().constData(); + qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.").toUtf8().constData(); return 0; } From c06e55df811d5c300dca25a7605654aef434d6a9 Mon Sep 17 00:00:00 2001 From: Jan Hellwig Date: Thu, 13 Apr 2017 22:02:09 +0200 Subject: [PATCH 274/333] Use a dedicated tray icon when the database is unlocked. --- COPYING | 2 ++ share/CMakeLists.txt | 16 ++++++++++++++++ .../128x128/apps/keepassxc-unlocked.png | Bin 0 -> 11058 bytes .../16x16/apps/keepassxc-unlocked.png | Bin 0 -> 880 bytes .../24x24/apps/keepassxc-unlocked.png | Bin 0 -> 1402 bytes .../256x256/apps/keepassxc-unlocked.png | Bin 0 -> 23418 bytes .../32x32/apps/keepassxc-unlocked.png | Bin 0 -> 2031 bytes .../48x48/apps/keepassxc-unlocked.png | Bin 0 -> 3317 bytes .../64x64/apps/keepassxc-unlocked.png | Bin 0 -> 4786 bytes .../scalable/apps/keepassxc-unlocked.svgz | Bin 0 -> 2334 bytes src/core/FilePath.cpp | 2 +- 11 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 share/icons/application/128x128/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/16x16/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/24x24/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/256x256/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/32x32/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/48x48/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/64x64/apps/keepassxc-unlocked.png create mode 100644 share/icons/application/scalable/apps/keepassxc-unlocked.svgz diff --git a/COPYING b/COPYING index 4dbaece33..d36384011 100644 --- a/COPYING +++ b/COPYING @@ -41,6 +41,8 @@ Files: share/icons/application/*/apps/keepassxc.png share/icons/application/scalable/apps/keepassxc-dark.svgz share/icons/application/*/apps/keepassxc-locked.png share/icons/application/scalable/apps/keepassxc-locked.svgz + share/icons/application/*/apps/keepassxc-unlocked.png + share/icons/application/scalable/apps/keepassxc-unlocked.svgz share/icons/application/*/mimetypes/application-x-keepassxc.png share/icons/application/scalable/mimetypes/application-x-keepassxc.svgz Copyright: 2016, Lorenzo Stella diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index 9660d7509..7d01a1c40 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -89,6 +89,22 @@ add_custom_target(icons COMMAND inkscape -z -w 256 -h 256 icons/application/scalable/apps/keepassxc-locked.svgz -e icons/application/256x256/apps/keepassxc-locked.png + # SVGZ to PNGs for KeePassXC + COMMAND inkscape -z -w 16 -h 16 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/16x16/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 24 -h 24 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/24x24/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 32 -h 32 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/32x32/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 48 -h 48 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/48x48/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 64 -h 64 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/64x64/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 128 -h 128 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/128x128/apps/keepassxc-unlocked.png + COMMAND inkscape -z -w 256 -h 256 + icons/application/scalable/apps/keepassxc-unlocked.svgz -e icons/application/256x256/apps/keepassxc-unlocked.png + # SVGZ to PNGs for KeePassXC MIME-Type COMMAND inkscape -z -w 16 -h 16 icons/application/scalable/mimetypes/application-x-keepassxc.svgz -e icons/application/16x16/mimetypes/application-x-keepassxc.png diff --git a/share/icons/application/128x128/apps/keepassxc-unlocked.png b/share/icons/application/128x128/apps/keepassxc-unlocked.png new file mode 100644 index 0000000000000000000000000000000000000000..69b0fe24ee854c6e67af8c70ae0676dd901b9bcb GIT binary patch literal 11058 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 literal 0 HcmV?d00001 diff --git a/share/icons/application/16x16/apps/keepassxc-unlocked.png b/share/icons/application/16x16/apps/keepassxc-unlocked.png new file mode 100644 index 0000000000000000000000000000000000000000..2164f0335eff2a7740bcbdb18fb623c4cf808aae GIT binary patch literal 880 zcmV-$1CRWPP)@1BR%VmQqzYi^JN{L8Jf5a#GMKg z&biGP6RN6C27|%<8z#r&@t4b`WikBUS%Y(KbI$Es&u_B$q`LMVzILvhFXe@*s*_)B z5{*W$GRDN|@l!_U`GCmfm#R+4VXm-L?K~e4r^injj4?4ZG;|pNXl-q+cVJ-P$fN87 z^XY6_aY+QR#3j?Rt+fG`E%5jKOQuUAcsiR_9?v{9`}+EN0c@ecV6dm9rKMpabIaZ& z6Ks+R9%U0sEcqvM3jVr((RiFqD2{$FquOn4Z4IGNC`{dMx1Un#TGAJ&OQvwBn=4RX zUpD~YbG1a)Ym53UrIgBUx7$xG%d!DLl0X!h!l8>2Sf~O3PIjGbWdfH3Y;E{S0)Tgg zDdPO^_V26g@&2mBVzJu*auNW{&CNY)Z*M<*{J?LG3;MfC(fA-0*asT$by>^hoVfe; zUzre_{M;C*^aqEP#6C-Y$;q`a>Mu`MZ*sNWK~w85z(tP1|q}Ua!{|iA1hUO-(HrhOu@`hGDQwCNn=c zIC$CT^Lf{2$cC0xYzc)z;rjaeW&kJ@3hz^?lnNmCf8!UnMS-(X?m+|q0000*FP)t?LBrF0M znJ(PmtrxN_OPtAMyV$rfE=#5?H0-Y+_oOl2qtlF8I$Y9h){9idcUP!5GsD70VC z-!3Rn`BC>JZ%)qlKHvMi@ALfR+2PH|m>G6230$^{MERdO*SyEC`(%jwM?PH8Z8Dsi7 z7Jc^YnXjazOQnqNs&S6A2L zbUMERK-Z1#a5zrhxpT+M7}F+a{A%xT?^5=^|J@kgTb--!tP?_rE-9^ZicX-!$Q;o+5Ve5US^zNmY|{fcA_53hvFfBGcq z9UoK}V_I!(ZFfdSM$wWav2TLI;rRO4v18kr!ti~=H`>J9I5#pqq{k;=Gus85*(SvI z4Nbk&$7jd5?;5I9rZ7}iR&LME&i)pFVTr(IvsIiwe|`@D{+IR(k&)mq&v6v(eXRmh z*efLQEh0zs4tJ}-aTLSTL;S_NKSuxvH8nMR?RI;`5`o2HIg*u?WtyH3sCV7B3<5_W za0GK~nx1Vw%@j>pMWSemezy5En~MhNBEH>y%Mc7tt4^oWB#PqUMFLYRuBWe!c(aPsnT`LXveLp&C`Oj>%HWB@{Oaj_jhG7lgvKR-VO z0G^LNC6r&i%0>c%{P@hMLO_Wn$;ySJ^V&#YkY5Ws;CBN63JVKU0HpCe&u?Dbp~1)` z<+-&tx5wYF0DvXQ$`L|fGMcoRa9|a~M94<~KorG|JkM{Y0CZhnDBr+y;CZs95dx4H z_Xfi|^oQ~bdeVj@Z6%HpjbI@t0O(XvlqpG)LI9AGWY#!Jv6c}+K*(}+UVpzTYhk() zm$J#C0e~b)vx=fjQ2_tmzkh!M0Cre&^>r(t0Er2rYO3Dc`x~WHUxnL|x*Gt{+1cp_ zFiHUgT3T8L0m%Ee76}GH0LPJ47B3M;Ir8Cq`RYoHK@hNiYmopT+TPwi0AOK~BuTpY z;>C+8(P-rlzF*4LCRmmz#DZ#5@2?3fF@;$lGqW+3bGzMBV`F1Cf!Id#csy6Ds;Zs< zkdN;EJRv=Ki^B69;>}^+C1jg-3k!=Ok*@S-Bxfo|Klm&GfPCuIsV82q_Zk3ju{l#2 zV`E06F)wf5esNb?9_f=FsS!CuDIuVQK-MDB-1LJ60J!$t6&arH=NEmcN$JXof*-j} zi6&ZKU*Ff**jOD3g*@>Dn4O*V4h#%%IXO8ya5o42Ky&Z{%b7xMBt)iD(8=jY^L zq*r>Rbb6Z;2qh3W9{KiCR=Ka*kd$bm9UUDn&zw1PVKIAP^_5BOcKa7qRaIYBR#rLz z2$SJ)_37xJvM11ksd{Zm_8Ti4gucM-t42P6O> zH8r)UxVX5ftE=lxS0?M~>KZ$A=unf@YAswV*6S{PgWYbg5JmAwX=$mgpr9Z{6vd6P zRX2O@-aUU?Tic+|=er)0B>H~|;#)F+G%P<`{8)Il1l~08KVbfwSeV1>xBvhE07*qo IM6N<$f+;?UzyJUM literal 0 HcmV?d00001 diff --git a/share/icons/application/256x256/apps/keepassxc-unlocked.png b/share/icons/application/256x256/apps/keepassxc-unlocked.png new file mode 100644 index 0000000000000000000000000000000000000000..d1c11781338b6541e013b56f0c7e8921baafd7e8 GIT binary patch literal 23418 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:&=`5_=sN1|2^%R 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 literal 0 HcmV?d00001 diff --git a/share/icons/application/32x32/apps/keepassxc-unlocked.png b/share/icons/application/32x32/apps/keepassxc-unlocked.png new file mode 100644 index 0000000000000000000000000000000000000000..de06bf03aea282b8f4d2374417c6d00c9df1287c GIT binary patch literal 2031 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 M07*qoM6N<$g2h`(_5c6? literal 0 HcmV?d00001 diff --git a/share/icons/application/scalable/apps/keepassxc-unlocked.svgz b/share/icons/application/scalable/apps/keepassxc-unlocked.svgz new file mode 100644 index 0000000000000000000000000000000000000000..84ce139046b21579c83ba1947304808a485695ec GIT binary patch literal 2334 zcmV+(3E}o1iwFP!000000M%IAj@!r)efL)onwK>qQhmS3BQKH|yU0TjAZsH)-Z&&@ zhHHrwNRH?7^*PnNNg9nkPOuy72nN_y)m>Gmt50?N^2dkWHu_RDZCO{F1y9%_Dypo` z%j#yc`1J9o*e#;AORGHH)>W}tRQ2M=_irxSFE?+ZD8l5bUDxf^W^vnf`}Jyde}A9U z`=V;^(=NNcs_V~5-Q2A1nsOx=^DEdc=9zgmP2Ar#+hCe!t72R1imGc@JmISY^K3TH znj-DWFGW`Gc6HT;nN@pnVBF-_M^W9&V8p%mD<)P##IT6l$Er&o;^V2v?%Y%)z*f-B zmanr~KWxkD^NYa*?V)#|zsJ96!YD}E`mV`}Ys@T?s_0f9K7N>#F-!6;KUfBfwpqF_ zj{J_o9NyG1cD7KXP)d2`#%<1b}#|Cjn3@j0vstI->|Ti_b-|Puun(OR(y)PGB3_4ri>`O$vwm4nysjhdMg-n!}oLPOU&QOBFB+fhK?K36vQ!G(VNzPvg^x+Hyg9@|7 zGlzq2mp+uc@~Oz@t(x85H4T_6-lmU5Gn|8H6)Gk#uG<+78mUl9P=Nr7wE4M7^AeML zqu%UTgIvrohf1)k_oFSa>>jt+auUX&%GMV`m_o>RK{RaodW<*}*!6W=bTbH}Q|iabYKEcGfmK zYfJlb9IWPS-nMUT@09RwQ5~`T!`27qF7bw0j`HBlC%8DNx4$)7vH8Q|HJ&GVaGv-# z0ef9El!-Hm)W&;#oV84Zbk67_gt!0}lF1V^pV>a&290h0&ZBd=qRtw=c14%wX_wAT zca+$%@!|!r{^y6Ermm1>>wneF=b6ySAib*Z!1Gh@pkAJ>X&ch+eYwM(iLU#9!mS(5 zC&iI*_qZ?SMf+u&qQB0ceF5d!uB1t;U%GO;{Tn$s=*GjMWw$L3TJ&m&e(X92$z86- z)Waw%n`}D-MC7K&9!8ut4G6J)+TCiw#-=^=$*~`@#92nSfAJTu}i$n^p#u+_?38f6b{vK(z1XTzn&^@C+M`*;x z8fC?D`ifZiO;$_IzG8;;Jc+b*{3~XY%{Nz@qx*NXi)d`oV5H?y77>s?OKS_JkCaWU)Se{hVoYiRj|Nh6Z31{PHEFN70_utI z&Itz);aMoAu~s5DA|nCd#aUPixPiGCM1UQ5Sc7g#A;Tf}#st`%gpR9(8m;ybNAyN1 zTGS@A0|;V0<`aWR14P7Jw2a1#G54(rF5ipe;=F_51lP&GMbU3v(oc$$E_)^%!7&;A6 zOmgf6u1bUyqa6rZfGqnH0$KF*Rf4{__VoK7I9mMwIhxuKZnq+~GgI#(O=O0n&goR6 z{W|bXHt?DDM}+32>-o&+X-w?V136F{A2$RoM(RFc~ATr1Trg;k1+E`R%X%M3YZgJ!uc>>&a@M`fSM;SD(AxrupxFaIFeRlldzQ6Qmb(0jRT}A$Sk*@3l1|d0udb1jkF_G z8I;UORRb4EYxT@kQs@(|3i`19R7>+R1AU3*c)K&rg-{WhmZjfP6i_;R_K2q z9~0t4>M#p|8vjBY1ZtrRYRdEIdw E0D&rzsQ>@~ literal 0 HcmV?d00001 diff --git a/src/core/FilePath.cpp b/src/core/FilePath.cpp index 0506e3ab7..67ececfbb 100644 --- a/src/core/FilePath.cpp +++ b/src/core/FilePath.cpp @@ -101,7 +101,7 @@ QIcon FilePath::trayIconLocked() QIcon FilePath::trayIconUnlocked() { - return applicationIcon(); + return icon("apps", "keepassxc-unlocked"); } QIcon FilePath::icon(const QString& category, const QString& name, bool fromTheme) From 8d6db27b3430543e6f262a566be0cbd0331a404e Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 11 May 2017 13:44:08 +0200 Subject: [PATCH 275/333] add tray icon to MacOS X --- src/gui/MainWindow.cpp | 15 +++------------ src/gui/SettingsWidget.cpp | 5 ----- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c3ab306eb..4e25a9426 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -660,13 +660,7 @@ void MainWindow::databaseTabChanged(int tabIndex) void MainWindow::closeEvent(QCloseEvent* event) { - bool minimizeOnClose = config()->get("GUI/MinimizeOnClose").toBool(); -#ifndef Q_OS_MAC - // if we aren't on OS X, check if the tray is enabled. - // on OS X we are using the dock for the minimize action - minimizeOnClose = isTrayIconEnabled() && minimizeOnClose; -#endif - if (minimizeOnClose && !appExitCalled) + if (isTrayIconEnabled() && config()->get("GUI/MinimizeOnClose").toBool() && !appExitCalled) { event->ignore(); hideWindow(); @@ -836,7 +830,9 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason) void MainWindow::hideWindow() { +#ifndef Q_OS_MAC setWindowState(windowState() | Qt::WindowMinimized); +#endif QTimer::singleShot(0, this, SLOT(hide())); if (config()->get("security/lockdatabaseminimize").toBool()) { @@ -919,13 +915,8 @@ void MainWindow::repairDatabase() bool MainWindow::isTrayIconEnabled() const { -#ifdef Q_OS_MAC - // systray not useful on OS X - return false; -#else return config()->get("GUI/ShowTrayIcon").toBool() && QSystemTrayIcon::isSystemTrayAvailable(); -#endif } void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 716eb14f1..46910a5a3 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -66,11 +66,6 @@ SettingsWidget::SettingsWidget(QWidget* parent) m_generalUi->generalSettingsTabWidget->removeTab(1); } -#ifdef Q_OS_MAC - // systray not useful on OS X - m_generalUi->systraySettings->setVisible(false); -#endif - connect(this, SIGNAL(accepted()), SLOT(saveSettings())); connect(this, SIGNAL(rejected()), SLOT(reject())); From 70357be029834710dca3dacede538705470fc210 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 12 May 2017 10:54:01 +0200 Subject: [PATCH 276/333] Update README.md changed reference to new build instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18a025aa8..bf214d3c1 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Pre-compiled binaries can be found on the [downloads page](https://keepassxc.org ### Building KeePassXC -*More detailed instructions are available in the INSTALL file or on the [Wiki page](https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source).* +*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). From 9a59a124aab4549e6db2c728ab9d515b0277aba7 Mon Sep 17 00:00:00 2001 From: Jens Rutschmann Date: Sun, 14 May 2017 01:02:54 +0200 Subject: [PATCH 277/333] Compare window title with entry URLs during autotype matching. (#556) * Compare window title with entry URLs during autotype matching. * Adapted option label to reflect that both entry title and URL are used for auto-type window matching. --- src/autotype/AutoType.cpp | 24 ++++++++++++++++++++++-- src/autotype/AutoType.h | 2 ++ src/gui/SettingsWidgetGeneral.ui | 2 +- tests/TestAutoType.cpp | 28 ++++++++++++++++++++++++++++ tests/TestAutoType.h | 3 +++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 12367fe95..6a066d4c4 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -574,8 +574,9 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl } } - if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->resolvePlaceholder(entry->title()).isEmpty() - && windowTitle.contains(entry->resolvePlaceholder(entry->title()), Qt::CaseInsensitive)) { + if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() + && (windowMatchesTitle(windowTitle, entry->resolvePlaceholder(entry->title())) + || windowMatchesUrl(windowTitle, entry->resolvePlaceholder(entry->url())))) { sequence = entry->defaultAutoTypeSequence(); match = true; } @@ -631,3 +632,22 @@ bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPa return WildcardMatcher(windowTitle).match(windowPattern); } } + +bool AutoType::windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle) +{ + return !resolvedTitle.isEmpty() && windowTitle.contains(resolvedTitle, Qt::CaseInsensitive); +} + +bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl) +{ + if (!resolvedUrl.isEmpty() && windowTitle.contains(resolvedUrl, Qt::CaseInsensitive)) { + return true; + } + + QUrl url(resolvedUrl); + if (url.isValid() && !url.host().isEmpty()) { + return windowTitle.contains(url.host(), Qt::CaseInsensitive); + } + + return false; +} diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index 311eedaab..ea5c95610 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -66,6 +66,8 @@ private: bool parseActions(const QString& sequence, const Entry* entry, QList& actions); QList createActionFromTemplate(const QString& tmpl, const Entry* entry); QString autoTypeSequence(const Entry* entry, const QString& windowTitle = QString()); + bool windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle); + bool windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl); bool windowMatches(const QString& windowTitle, const QString& windowPattern); bool m_inAutoType; diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 37a60912c..2fe0f4089 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -303,7 +303,7 @@ - Use entry title to match windows for global Auto-Type + Use entry title and URL to match windows for global Auto-Type diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index a73298866..a3ed8cbe2 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -104,6 +104,12 @@ void TestAutoType::init() association.window = "//^CustomAttr3$//"; association.sequence = "{PaSSworD}"; m_entry4->autoTypeAssociations()->add(association); + + m_entry5 = new Entry(); + m_entry5->setGroup(m_group); + m_entry5->setPassword("example5"); + m_entry5->setTitle("some title"); + m_entry5->setUrl("http://example.org"); } void TestAutoType::cleanup() @@ -172,6 +178,28 @@ void TestAutoType::testGlobalAutoTypeTitleMatch() QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter))); } +void TestAutoType::testGlobalAutoTypeUrlMatch() +{ + config()->set("AutoTypeEntryTitleMatch", true); + + m_test->setActiveWindowTitle("Dummy - http://example.org/ - "); + m_autoType->performGlobalAutoType(m_dbList); + + QCOMPARE(m_test->actionChars(), + QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); +} + +void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch() +{ + config()->set("AutoTypeEntryTitleMatch", true); + + m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - "); + m_autoType->performGlobalAutoType(m_dbList); + + QCOMPARE(m_test->actionChars(), + QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); +} + void TestAutoType::testGlobalAutoTypeTitleMatchDisabled() { m_test->setActiveWindowTitle("An Entry Title!"); diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index 569bc8c70..fb09a2783 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -42,6 +42,8 @@ private slots: void testGlobalAutoTypeWithNoMatch(); void testGlobalAutoTypeWithOneMatch(); void testGlobalAutoTypeTitleMatch(); + void testGlobalAutoTypeUrlMatch(); + void testGlobalAutoTypeUrlSubdomainMatch(); void testGlobalAutoTypeTitleMatchDisabled(); void testGlobalAutoTypeRegExp(); @@ -56,6 +58,7 @@ private: Entry* m_entry2; Entry* m_entry3; Entry* m_entry4; + Entry* m_entry5; }; #endif // KEEPASSX_TESTAUTOTYPE_H From c167693ae4918b8cf74d83bec6e923d8db2c5d7b Mon Sep 17 00:00:00 2001 From: Robert van Bregt Date: Fri, 12 May 2017 11:10:43 +0200 Subject: [PATCH 278/333] add fix for mac qt build environment Signed-off-by: Robert van Bregt --- utils/fix_mac.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 utils/fix_mac.sh diff --git a/utils/fix_mac.sh b/utils/fix_mac.sh new file mode 100755 index 000000000..2e4e84e5e --- /dev/null +++ b/utils/fix_mac.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Canonical path to qt5 directory +QT="/usr/local/Cellar/qt" +if [ ! -d "$QT" ]; then + # Alternative (old) path to qt5 directory + QT+="5" + if [ ! -d "$QT" ]; then + echo "Qt/Qt5 not found!" + exit + fi +fi +QT5_DIR="$QT/$(ls $QT | sort -r | head -n1)" +echo $QT5_DIR + +# Change qt5 framework ids +for framework in $(find "$QT5_DIR/lib" -regex ".*/\(Qt[a-zA-Z]*\)\.framework/Versions/5/\1"); do + echo "$framework" + install_name_tool -id "$framework" "$framework" +done From 00ae123736a16ab08e03364bb41d0cca70ff37ea Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 11 May 2017 14:36:06 -0400 Subject: [PATCH 279/333] Adding .clang-format file. --- .clang-format | 89 ++++++++++++++++++++++++++++++++++++++++ src/gui/SearchWidget.cpp | 24 +++++------ src/gui/SearchWidget.h | 12 +++--- 3 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..5dc8fc3b0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,89 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: true + AfterFunction: true + AfterControlStatement: false + AfterEnum: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +... + diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index e94f838c5..2142a96fe 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -25,7 +25,7 @@ #include "core/FilePath.h" -SearchWidget::SearchWidget(QWidget *parent) +SearchWidget::SearchWidget(QWidget* parent) : QWidget(parent) , m_ui(new Ui::SearchWidget()) { @@ -40,11 +40,11 @@ SearchWidget::SearchWidget(QWidget *parent) connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear())); new QShortcut(Qt::CTRL + Qt::Key_F, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut); - new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut); + new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut); m_ui->searchEdit->installEventFilter(this); - QMenu *searchMenu = new QMenu(); + QMenu* searchMenu = new QMenu(); m_actionCaseSensitive = searchMenu->addAction(tr("Case Sensitive"), this, SLOT(updateCaseSensitive())); m_actionCaseSensitive->setObjectName("actionSearchCaseSensitive"); m_actionCaseSensitive->setCheckable(true); @@ -58,39 +58,35 @@ SearchWidget::SearchWidget(QWidget *parent) m_ui->searchEdit->addAction(m_ui->clearIcon, QLineEdit::TrailingPosition); // Fix initial visibility of actions (bug in Qt) - for (QToolButton * toolButton: m_ui->searchEdit->findChildren()) { + for (QToolButton* toolButton : m_ui->searchEdit->findChildren()) { toolButton->setVisible(toolButton->defaultAction()->isVisible()); } } SearchWidget::~SearchWidget() { - } -bool SearchWidget::eventFilter(QObject *obj, QEvent *event) +bool SearchWidget::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); + QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape) { emit escapePressed(); return true; - } - else if (keyEvent->matches(QKeySequence::Copy)) { + } else if (keyEvent->matches(QKeySequence::Copy)) { // If Control+C is pressed in the search edit when no text // is selected, copy the password of the current entry if (!m_ui->searchEdit->hasSelectedText()) { emit copyPressed(); return true; } - } - else if (keyEvent->matches(QKeySequence::MoveToNextLine)) { + } else if (keyEvent->matches(QKeySequence::MoveToNextLine)) { if (m_ui->searchEdit->cursorPosition() == m_ui->searchEdit->text().length()) { // If down is pressed at EOL, move the focus to the entry view emit downPressed(); return true; - } - else { + } else { // Otherwise move the cursor to EOL m_ui->searchEdit->setCursorPosition(m_ui->searchEdit->text().length()); return true; @@ -110,7 +106,7 @@ void SearchWidget::connectSignals(SignalMultiplexer& mx) mx.connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(switchToEntryEdit())); } -void SearchWidget::databaseChanged(DatabaseWidget *dbWidget) +void SearchWidget::databaseChanged(DatabaseWidget* dbWidget) { if (dbWidget != nullptr) { // Set current search text from this database diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h index d2b94d979..e87701814 100644 --- a/src/gui/SearchWidget.h +++ b/src/gui/SearchWidget.h @@ -18,11 +18,11 @@ #ifndef KEEPASSX_SEARCHWIDGET_H #define KEEPASSX_SEARCHWIDGET_H -#include #include +#include -#include "gui/DatabaseWidget.h" #include "core/SignalMultiplexer.h" +#include "gui/DatabaseWidget.h" namespace Ui { class SearchWidget; @@ -33,17 +33,17 @@ class SearchWidget : public QWidget Q_OBJECT public: - explicit SearchWidget(QWidget *parent = 0); + explicit SearchWidget(QWidget* parent = 0); ~SearchWidget(); void connectSignals(SignalMultiplexer& mx); void setCaseSensitive(bool state); protected: - bool eventFilter(QObject *obj, QEvent *event); + bool eventFilter(QObject* obj, QEvent* event); signals: - void search(const QString &text); + void search(const QString& text); void caseSensitiveChanged(bool state); void escapePressed(); void copyPressed(); @@ -62,7 +62,7 @@ private slots: private: const QScopedPointer m_ui; QTimer* m_searchTimer; - QAction *m_actionCaseSensitive; + QAction* m_actionCaseSensitive; Q_DISABLE_COPY(SearchWidget) }; From 3822625e77a0d1736ab4e355a116ee2829508b05 Mon Sep 17 00:00:00 2001 From: Yen Chi Hsuan Date: Sat, 6 May 2017 01:49:52 +0800 Subject: [PATCH 280/333] Allow disabling .app bundles on Mac --- CMakeLists.txt | 11 ++++++++--- src/CMakeLists.txt | 7 ++++--- src/autotype/mac/CMakeLists.txt | 18 ++++++++++++------ src/core/FilePath.cpp | 6 +++--- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cde795ad2..98ae0eced 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ option(WITH_GUI_TESTS "Enable building of GUI tests" OFF) option(WITH_DEV_BUILD "Use only for development. Disables/warns about deprecated methods." OFF) option(WITH_ASAN "Enable address sanitizer checks (Linux only)" OFF) option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF) +option(WITH_APP_BUNDLE "Enable Application Bundle for OS X" ON) option(WITH_XC_AUTOTYPE "Include Auto-Type." ON) option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF) @@ -77,6 +78,10 @@ endmacro(add_gcc_compiler_flags) add_definitions(-DQT_NO_EXCEPTIONS -DQT_STRICT_ITERATORS -DQT_NO_CAST_TO_ASCII) +if(WITH_APP_BUNDLE) + add_definitions(-DWITH_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("-Wformat=2 -Wmissing-format-attribute") @@ -164,13 +169,13 @@ if(MINGW) endif() endif() -if(APPLE OR MINGW) +if(APPLE AND WITH_APP_BUNDLE OR MINGW) set(PROGNAME KeePassXC) else() set(PROGNAME keepassxc) endif() -if(APPLE AND "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") +if(APPLE AND WITH_APP_BUNDLE AND "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") set(CMAKE_INSTALL_PREFIX "/Applications") endif() @@ -179,7 +184,7 @@ if(MINGW) set(BIN_INSTALL_DIR ".") set(PLUGIN_INSTALL_DIR ".") set(DATA_INSTALL_DIR "share") -elseif(APPLE) +elseif(APPLE AND WITH_APP_BUNDLE) set(CLI_INSTALL_DIR "/usr/local/bin") set(BIN_INSTALL_DIR ".") set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 786a03a80..42cbe36bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -216,14 +216,15 @@ if(MINGW) ) endif() -add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles}) +add_executable(${PROGNAME} WIN32 ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles}) target_link_libraries(${PROGNAME} keepassx_core) set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON) -if(APPLE) +if(APPLE AND WITH_APP_BUNDLE) configure_file(${CMAKE_SOURCE_DIR}/share/macosx/Info.plist.cmake ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) set_target_properties(${PROGNAME} PROPERTIES + MACOSX_BUNDLE ON MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) endif() @@ -231,7 +232,7 @@ install(TARGETS ${PROGNAME} BUNDLE DESTINATION . COMPONENT Runtime RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime) -if(APPLE) +if(APPLE AND WITH_APP_BUNDLE) if(QT_MAC_USE_COCOA AND EXISTS "${QT_LIBRARY_DIR}/Resources/qt_menu.nib") install(DIRECTORY "${QT_LIBRARY_DIR}/Resources/qt_menu.nib" DESTINATION "${DATA_INSTALL_DIR}") diff --git a/src/autotype/mac/CMakeLists.txt b/src/autotype/mac/CMakeLists.txt index 076dd5038..ac93de0e7 100644 --- a/src/autotype/mac/CMakeLists.txt +++ b/src/autotype/mac/CMakeLists.txt @@ -12,9 +12,15 @@ target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets if(NOT DEFINED QT_BINARY_DIR) set(QT_BINARY_DIR "/usr/local/opt/qt5/bin" CACHE PATH "QT binary folder") endif() -add_custom_command(TARGET keepassx-autotype-cocoa - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR} - COMMAND ${QT_BINARY_DIR}/macdeployqt ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src -COMMENT "Deploying autotype plugin") +if(WITH_APP_BUNDLE) + add_custom_command(TARGET keepassx-autotype-cocoa + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR} + COMMAND ${QT_BINARY_DIR}/macdeployqt ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src + COMMENT "Deploying autotype plugin") +else() + install(TARGETS keepassx-autotype-cocoa + BUNDLE DESTINATION . COMPONENT Runtime + LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) +endif() diff --git a/src/core/FilePath.cpp b/src/core/FilePath.cpp index 0506e3ab7..2c1c20e84 100644 --- a/src/core/FilePath.cpp +++ b/src/core/FilePath.cpp @@ -49,7 +49,7 @@ QString FilePath::pluginPath(const QString& name) // for TestAutoType pluginPaths << QCoreApplication::applicationDirPath() + "/../src/autotype/test"; -#if defined(Q_OS_MAC) +#if defined(Q_OS_MAC) && defined(WITH_APP_BUNDLE) pluginPaths << QCoreApplication::applicationDirPath() + "/../PlugIns"; #endif @@ -195,7 +195,7 @@ FilePath::FilePath() else if (testSetDir(QString(KEEPASSX_SOURCE_DIR) + "/share")) { } #endif -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) +#if defined(Q_OS_UNIX) && !(defined(Q_OS_MAC) && defined(WITH_APP_BUNDLE)) else if (isDataDirAbsolute && testSetDir(KEEPASSX_DATA_DIR)) { } else if (!isDataDirAbsolute && testSetDir(QString("%1/../%2").arg(appDirPath, KEEPASSX_DATA_DIR))) { @@ -203,7 +203,7 @@ FilePath::FilePath() else if (!isDataDirAbsolute && testSetDir(QString("%1/%2").arg(KEEPASSX_PREFIX_DIR, KEEPASSX_DATA_DIR))) { } #endif -#ifdef Q_OS_MAC +#if defined(Q_OS_MAC) && defined(WITH_APP_BUNDLE) else if (testSetDir(appDirPath + "/../Resources")) { } #endif From 2de5a9d2812c0e4f026453cd98a85cf2b4217eaf Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sat, 24 Dec 2016 01:04:43 +0100 Subject: [PATCH 281/333] Lock database when OS is locked (Windows, DBus, macOS implementations) #134 --- src/CMakeLists.txt | 22 ++++++++ src/core/ScreenLockListener.cpp | 11 ++++ src/core/ScreenLockListener.h | 21 ++++++++ src/core/ScreenLockListenerDBus.cpp | 66 ++++++++++++++++++++++++ src/core/ScreenLockListenerDBus.h | 18 +++++++ src/core/ScreenLockListenerMac.cpp | 41 +++++++++++++++ src/core/ScreenLockListenerMac.h | 24 +++++++++ src/core/ScreenLockListenerPrivate.cpp | 26 ++++++++++ src/core/ScreenLockListenerPrivate.h | 19 +++++++ src/core/ScreenLockListenerWin.cpp | 69 ++++++++++++++++++++++++++ src/core/ScreenLockListenerWin.h | 21 ++++++++ src/gui/MainWindow.cpp | 10 ++++ src/gui/MainWindow.h | 3 ++ src/gui/SettingsWidget.cpp | 4 ++ src/gui/SettingsWidgetGeneral.ui | 7 +++ 15 files changed, 362 insertions(+) create mode 100644 src/core/ScreenLockListener.cpp create mode 100644 src/core/ScreenLockListener.h create mode 100644 src/core/ScreenLockListenerDBus.cpp create mode 100644 src/core/ScreenLockListenerDBus.h create mode 100644 src/core/ScreenLockListenerMac.cpp create mode 100644 src/core/ScreenLockListenerMac.h create mode 100644 src/core/ScreenLockListenerPrivate.cpp create mode 100644 src/core/ScreenLockListenerPrivate.h create mode 100644 src/core/ScreenLockListenerWin.cpp create mode 100644 src/core/ScreenLockListenerWin.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 42cbe36bb..dfdf96c8a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,10 @@ set(keepassx_SOURCES core/PasswordGenerator.cpp core/PassphraseGenerator.cpp core/SignalMultiplexer.cpp + core/ScreenLockListener.cpp + core/ScreenLockListener.h + core/ScreenLockListenerPrivate.h + core/ScreenLockListenerPrivate.cpp core/TimeDelta.cpp core/TimeInfo.cpp core/ToDbExporter.cpp @@ -136,6 +140,24 @@ set(keepassx_SOURCES totp/totp.h totp/totp.cpp ) +if(APPLE) + set(keepassx_SOURCES ${keepassx_SOURCES} + core/ScreenLockListenerMac.h + core/ScreenLockListenerMac.cpp + ) +endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(keepassx_SOURCES ${keepassx_SOURCES} + core/ScreenLockListenerDBus.h + core/ScreenLockListenerDBus.cpp + ) +endif() +if(MINGW) + set(keepassx_SOURCES ${keepassx_SOURCES} + core/ScreenLockListenerWin.h + core/ScreenLockListenerWin.cpp + ) +endif() set(keepassx_SOURCES_MAINEXE main.cpp diff --git a/src/core/ScreenLockListener.cpp b/src/core/ScreenLockListener.cpp new file mode 100644 index 000000000..2c17dfa14 --- /dev/null +++ b/src/core/ScreenLockListener.cpp @@ -0,0 +1,11 @@ +#include "ScreenLockListener.h" +#include "ScreenLockListenerPrivate.h" + +ScreenLockListener::ScreenLockListener(QWidget* parent): + QObject(parent){ + m_listener = ScreenLockListenerPrivate::instance(parent); + connect(m_listener,SIGNAL(screenLocked()), this,SIGNAL(screenLocked())); +} + +ScreenLockListener::~ScreenLockListener(){ +} diff --git a/src/core/ScreenLockListener.h b/src/core/ScreenLockListener.h new file mode 100644 index 000000000..7339054f9 --- /dev/null +++ b/src/core/ScreenLockListener.h @@ -0,0 +1,21 @@ +#ifndef SCREENLOCKLISTENER_H +#define SCREENLOCKLISTENER_H +#include + +class ScreenLockListenerPrivate; + +class ScreenLockListener : public QObject { + Q_OBJECT + +public: + ScreenLockListener(QWidget* parent=NULL); + ~ScreenLockListener(); + +Q_SIGNALS: + void screenLocked(); + +private: + ScreenLockListenerPrivate* m_listener; +}; + +#endif // SCREENLOCKLISTENER_H diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp new file mode 100644 index 000000000..283b1e27e --- /dev/null +++ b/src/core/ScreenLockListenerDBus.cpp @@ -0,0 +1,66 @@ +#include "ScreenLockListenerDBus.h" + +#include +#include +#include + +ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): + ScreenLockListenerMac(parent) +{ + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + QDBusConnection systemBus = QDBusConnection::systemBus(); + sessionBus.connect( + "org.gnome.SessionManager", // service + "/org/gnome/SessionManager/Presence", // path + "org.gnome.SessionManager.Presence", // interface + "StatusChanged", // signal name + this, //receiver + SLOT(gnomeSessionStatusChanged(uint))); + + systemBus.connect( + "org.freedesktop.login1", // service + "/org/freedesktop/login1", // path + "org.freedesktop.login1.Manager", // interface + "PrepareForSleep", // signal name + this, //receiver + SLOT(logindPrepareForSleep(bool))); + + sessionBus.connect( + "com.canonical.Unity", // service + "/com/canonical/Unity/Session", // path + "com.canonical.Unity.Session", // interface + "Locked", // signal name + this, //receiver + SLOT(unityLocked())); + + /* Currently unable to get the current user session from login1.Manager + QDBusInterface login1_manager_iface("org.freedesktop.login1", "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", systemBus); + if(login1_manager_iface.isValid()){ + qint64 my_pid = QCoreApplication::applicationPid(); + QDBusReply reply = login1_manager_iface.call("GetSessionByPID",static_cast(my_pid)); + if (reply.isValid()){ + QString current_session = reply.value(); + } + } + */ +} + +void ScreenLockListenerDBus::gnomeSessionStatusChanged(uint status) +{ + if(status != 0){ + Q_EMIT screenLocked(); + } +} + +void ScreenLockListenerDBus::logindPrepareForSleep(bool beforeSleep) +{ + if(beforeSleep){ + Q_EMIT screenLocked(); + } +} + +void ScreenLockListenerDBus::unityLocked() +{ + Q_EMIT screenLocked(); +} diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h new file mode 100644 index 000000000..016932b8a --- /dev/null +++ b/src/core/ScreenLockListenerDBus.h @@ -0,0 +1,18 @@ +#ifndef SCREENLOCKLISTENERDBUS_H +#define SCREENLOCKLISTENERDBUS_H +#include +#include + +class ScreenLockListenerDBus : public ScreenLockListenerPrivate +{ + Q_OBJECT +public: + explicit ScreenLockListenerDBus(QWidget *parent = 0); + +private Q_SLOTS: + void gnomeSessionStatusChanged(uint status); + void logindPrepareForSleep(bool beforeSleep); + void unityLocked(); +}; + +#endif // SCREENLOCKLISTENERDBUS_H diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp new file mode 100644 index 000000000..57dc2de3f --- /dev/null +++ b/src/core/ScreenLockListenerMac.cpp @@ -0,0 +1,41 @@ +#include "ScreenLockListenerMac.h" + +#include +#include + +ScreenLockListenerMac* ScreenLockListenerMac::instance(){ + static QMutex mutex; + QMutexLocker lock(&mutex); + + static ScreenLockListenerMac* m_ptr=NULL; + if (m_ptr==NULL){ + m_ptr = new ScreenLockListenerMac(); + } + return m_ptr; +} + +void ScreenLockListenerMac::notificationCenterCallBack(CFNotificationCenterRef /*center*/, void */*observer*/, + CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/){ + instance()->onSignalReception(); +} + +ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent): + ScreenLockListenerPrivate(parent){ + CFNotificationCenterRef distCenter; + CFStringRef screenIsLockedSignal = CFSTR("com.apple.screenIsLocked"); + distCenter = CFNotificationCenterGetDistributedCenter(); + if (NULL == distCenter) + return; + + CFNotificationCenterAddObserver( + distCenter, + this, &ScreenLockListenerMac::notificationCenterCallBack, + screenIsLockedSignal, + NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); +} + +void ScreenLockListenerMac::onSignalReception() +{ + Q_EMIT screenLocked(); +} diff --git a/src/core/ScreenLockListenerMac.h b/src/core/ScreenLockListenerMac.h new file mode 100644 index 000000000..5824339f1 --- /dev/null +++ b/src/core/ScreenLockListenerMac.h @@ -0,0 +1,24 @@ +#ifndef SCREENLOCKLISTENERMAC_H +#define SCREENLOCKLISTENERMAC_H +#include +#include + +#include + +#include "ScreenLockListenerPrivate.h" + +class ScreenLockListenerMac: public ScreenLockListenerPrivate { + Q_OBJECT + +public: + static ScreenLockListenerMac* instance(); + static void notificationCenterCallBack(CFNotificationCenterRef /*center*/, void */*observer*/, + CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/); + +private: + ScreenLockListenerMac(QWidget* parent=NULL); + void onSignalReception(); + +}; + +#endif // SCREENLOCKLISTENERMAC_H diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp new file mode 100644 index 000000000..e9355c7d0 --- /dev/null +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -0,0 +1,26 @@ +#include "ScreenLockListenerPrivate.h" +#if defined(Q_OS_OSX) +#include "ScreenLockListenerMac.h" +#endif +#if defined(Q_OS_LINUX) +#include "ScreenLockListenerDBus.h" +#endif +#if defined(Q_OS_WIN) +#include "ScreenLockListenerWin.h" +#endif + +ScreenLockListenerPrivate::ScreenLockListenerPrivate(QWidget* parent): + QObject(parent){ +} + +ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent){ +#if defined(Q_OS_OSX) + return ScreenLockListenerMac::instance(); +#endif +#if defined(Q_OS_LINUX) + return new ScreenLockListenerDBus(parent); +#endif +#if defined(Q_OS_WIN) + return new ScreenLockListenerWin(parent); +#endif +} diff --git a/src/core/ScreenLockListenerPrivate.h b/src/core/ScreenLockListenerPrivate.h new file mode 100644 index 000000000..aef20edcc --- /dev/null +++ b/src/core/ScreenLockListenerPrivate.h @@ -0,0 +1,19 @@ +#ifndef SCREENLOCKLISTENERPRIVATE_H +#define SCREENLOCKLISTENERPRIVATE_H +#include +#include + +class ScreenLockListenerPrivate : public QObject +{ + Q_OBJECT +public: + static ScreenLockListenerPrivate* instance(QWidget *parent = 0); + +protected: + ScreenLockListenerPrivate(QWidget *parent = 0); + +Q_SIGNALS: + void screenLocked(); +}; + +#endif // SCREENLOCKLISTENERPRIVATE_H diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp new file mode 100644 index 000000000..349d5de58 --- /dev/null +++ b/src/core/ScreenLockListenerWin.cpp @@ -0,0 +1,69 @@ +#include "ScreenLockListenerWin.h" +#include +#include +#include + +/* + * See https://msdn.microsoft.com/en-us/library/windows/desktop/aa373196(v=vs.85).aspx + * See https://msdn.microsoft.com/en-us/library/aa383841(v=vs.85).aspx + * See https://blogs.msdn.microsoft.com/oldnewthing/20060104-50/?p=32783 + */ +ScreenLockListenerWin::ScreenLockListenerWin(QWidget *parent) : + ScreenLockListenerPrivate(parent), + QAbstractNativeEventFilter() +{ + Q_ASSERT(parent!=NULL); + // On windows, we need to register for platform specific messages and + // install a message handler for them + QCoreApplication::instance()->installNativeEventFilter(this); + + // This call requests a notification from windows when a laptop is closed + HPOWERNOTIFY hPnotify = RegisterPowerSettingNotification( + reinterpret_cast(parent->winId()), + &GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE); + m_powernotificationhandle = reinterpret_cast(hPnotify); + + // This call requests a notification for session changes + if (!WTSRegisterSessionNotification( + reinterpret_cast(parent->winId()), + NOTIFY_FOR_THIS_SESSION)){ + } +} + +ScreenLockListenerWin::~ScreenLockListenerWin() +{ + HWND h= reinterpret_cast(static_cast(parent())->winId()); + WTSUnRegisterSessionNotification(h); + + if(m_powernotificationhandle){ + UnregisterPowerSettingNotification(reinterpret_cast(m_powernotificationhandle)); + } +} + +bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void *message, long *) +{ + if(eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG"){ + MSG* m = static_cast(message); + if(m->message == WM_POWERBROADCAST){ + const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); + if (setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE){ + const DWORD* state = reinterpret_cast(&setting->Data); + if (*state == 0){ + Q_EMIT screenLocked(); + return true; + } + } + } + if(m->message == WM_WTSSESSION_CHANGE){ + if (m->wParam==WTS_CONSOLE_DISCONNECT){ + Q_EMIT screenLocked(); + return true; + } + if (m->wParam==WTS_SESSION_LOCK){ + Q_EMIT screenLocked(); + return true; + } + } + } + return false; +} diff --git a/src/core/ScreenLockListenerWin.h b/src/core/ScreenLockListenerWin.h new file mode 100644 index 000000000..ccf42ad70 --- /dev/null +++ b/src/core/ScreenLockListenerWin.h @@ -0,0 +1,21 @@ +#ifndef SCREENLOCKLISTENERWIN_H +#define SCREENLOCKLISTENERWIN_H +#include +#include +#include + +#include "ScreenLockListenerPrivate.h" + +class ScreenLockListenerWin : public ScreenLockListenerPrivate, public QAbstractNativeEventFilter +{ + Q_OBJECT +public: + explicit ScreenLockListenerWin(QWidget *parent = 0); + ~ScreenLockListenerWin(); + virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *) Q_DECL_OVERRIDE; + +private: + void * m_powernotificationhandle; +}; + +#endif // SCREENLOCKLISTENERWIN_H diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 0415f2b4e..65062fb79 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -329,6 +329,9 @@ MainWindow::MainWindow() connect(m_ui->tabWidget, SIGNAL(messageTab(QString,MessageWidget::MessageType)), this, SLOT(displayTabMessage(QString, MessageWidget::MessageType))); connect(m_ui->tabWidget, SIGNAL(messageDismissTab()), this, SLOT(hideTabMessage())); + m_screenLockListener = new ScreenLockListener(this); + connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock())); + updateTrayIcon(); if (config()->hasAccessError()) { @@ -959,3 +962,10 @@ void MainWindow::hideYubiKeyPopup() hideGlobalMessage(); setEnabled(true); } + +void MainWindow::handleScreenLock() +{ + if (config()->get("AutoCloseOnScreenLock").toBool()){ + lockDatabasesAfterInactivity(); + } +} diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index f35cd4658..72082e4ed 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -23,6 +23,7 @@ #include #include "core/SignalMultiplexer.h" +#include "core/ScreenLockListener.h" #include "gui/DatabaseWidget.h" #include "gui/Application.h" @@ -91,6 +92,7 @@ private slots: void lockDatabasesAfterInactivity(); void repairDatabase(); void hideTabMessage(); + void handleScreenLock(); private: static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0); @@ -112,6 +114,7 @@ private: InactivityTimer* m_inactivityTimer; int m_countDefaultAttributes; QSystemTrayIcon* m_trayIcon; + ScreenLockListener* m_screenLockListener; Q_DISABLE_COPY(MainWindow) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 716eb14f1..15f812a0d 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -141,6 +141,8 @@ void SettingsWidget::loadSettings() } } + m_generalUi->autoCloseOnScreenLockCheckBox->setChecked(config()->get("AutoCloseOnScreenLock").toBool()); + m_secUi->clearClipboardCheckBox->setChecked(config()->get("security/clearclipboard").toBool()); m_secUi->clearClipboardSpinBox->setValue(config()->get("security/clearclipboardtimeout").toInt()); @@ -186,6 +188,8 @@ void SettingsWidget::saveSettings() config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); + config()->set("AutoCloseOnScreenLock", m_generalUi->autoCloseOnScreenLockCheckBox->isChecked()); + config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); config()->set("GUI/ShowTrayIcon", m_generalUi->systrayShowCheckBox->isChecked()); diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 2fe0f4089..ef947d3f0 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -367,6 +367,13 @@ + + + + Close database when session is locked or lid is closed + + + From c6ecf48ccdb5d8bc56c96707f39117c43afd45e7 Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sat, 24 Dec 2016 01:05:24 +0100 Subject: [PATCH 282/333] enable OS X build with Xcode 8.2 --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e24d1d178..f3aeccf72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,9 @@ services: [docker] os: - linux -# - osx + - osx + +osx_image: xcode8.2 # Define clang compiler without any frills compiler: @@ -26,6 +28,7 @@ before_install: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq cmake || brew install cmake; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq qt5 || brew install qt5; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libgcrypt || brew install libgcrypt; fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libmicrohttpd || brew install libmicrohttpd; fi before_script: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then CMAKE_ARGS="-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5"; fi From a3af8fc0ea0de6e3464dd8910d894bfbfade4025 Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sat, 31 Dec 2016 01:23:25 +0100 Subject: [PATCH 283/333] Fix Linux ScreenLockListener implementation --- src/core/ScreenLockListenerDBus.cpp | 2 +- src/core/ScreenLockListenerDBus.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index 283b1e27e..4ac54a839 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -5,7 +5,7 @@ #include ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): - ScreenLockListenerMac(parent) + ScreenLockListenerPrivate(parent) { QDBusConnection sessionBus = QDBusConnection::sessionBus(); QDBusConnection systemBus = QDBusConnection::systemBus(); diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h index 016932b8a..be6fcd5f0 100644 --- a/src/core/ScreenLockListenerDBus.h +++ b/src/core/ScreenLockListenerDBus.h @@ -2,6 +2,7 @@ #define SCREENLOCKLISTENERDBUS_H #include #include +#include "ScreenLockListenerPrivate.h" class ScreenLockListenerDBus : public ScreenLockListenerPrivate { From 44085df592a7de66d01eb6ea557964905598fc09 Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sat, 31 Dec 2016 01:32:29 +0100 Subject: [PATCH 284/333] Avoid warning in MacOS implementation --- src/core/ScreenLockListenerPrivate.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp index e9355c7d0..6f3f7bbf8 100644 --- a/src/core/ScreenLockListenerPrivate.cpp +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -15,6 +15,7 @@ ScreenLockListenerPrivate::ScreenLockListenerPrivate(QWidget* parent): ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent){ #if defined(Q_OS_OSX) + Q_UNUSED(parent); return ScreenLockListenerMac::instance(); #endif #if defined(Q_OS_LINUX) From d1acd750684b808e447c17e4b0b5125ed4a4ba95 Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sun, 1 Jan 2017 14:08:14 +0100 Subject: [PATCH 285/333] Moved "Lock databases on screen lock" setting to security settings widget. Changed wording and preference variable name for conformity with existing settings. --- src/gui/MainWindow.cpp | 2 +- src/gui/SettingsWidget.cpp | 4 ++-- src/gui/SettingsWidgetSecurity.ui | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 65062fb79..3d5fad919 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -965,7 +965,7 @@ void MainWindow::hideYubiKeyPopup() void MainWindow::handleScreenLock() { - if (config()->get("AutoCloseOnScreenLock").toBool()){ + if (config()->get("security/lockdatabasescreenlock").toBool()){ lockDatabasesAfterInactivity(); } } diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 15f812a0d..75ebf2601 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -141,7 +141,6 @@ void SettingsWidget::loadSettings() } } - m_generalUi->autoCloseOnScreenLockCheckBox->setChecked(config()->get("AutoCloseOnScreenLock").toBool()); m_secUi->clearClipboardCheckBox->setChecked(config()->get("security/clearclipboard").toBool()); m_secUi->clearClipboardSpinBox->setValue(config()->get("security/clearclipboardtimeout").toInt()); @@ -149,6 +148,7 @@ void SettingsWidget::loadSettings() m_secUi->lockDatabaseIdleCheckBox->setChecked(config()->get("security/lockdatabaseidle").toBool()); m_secUi->lockDatabaseIdleSpinBox->setValue(config()->get("security/lockdatabaseidlesec").toInt()); m_secUi->lockDatabaseMinimizeCheckBox->setChecked(config()->get("security/lockdatabaseminimize").toBool()); + m_secUi->lockDatabaseOnScreenLockCheckBox->setChecked(config()->get("security/lockdatabasescreenlock").toBool()); m_secUi->passwordCleartextCheckBox->setChecked(config()->get("security/passwordscleartext").toBool()); m_secUi->passwordRepeatCheckBox->setChecked(config()->get("security/passwordsrepeat").toBool()); @@ -188,7 +188,6 @@ void SettingsWidget::saveSettings() config()->set("AutoTypeEntryTitleMatch", m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked()); int currentLangIndex = m_generalUi->languageComboBox->currentIndex(); - config()->set("AutoCloseOnScreenLock", m_generalUi->autoCloseOnScreenLockCheckBox->isChecked()); config()->set("GUI/Language", m_generalUi->languageComboBox->itemData(currentLangIndex).toString()); @@ -210,6 +209,7 @@ void SettingsWidget::saveSettings() config()->set("security/lockdatabaseidle", m_secUi->lockDatabaseIdleCheckBox->isChecked()); config()->set("security/lockdatabaseidlesec", m_secUi->lockDatabaseIdleSpinBox->value()); config()->set("security/lockdatabaseminimize", m_secUi->lockDatabaseMinimizeCheckBox->isChecked()); + config()->set("security/lockdatabasescreenlock", m_secUi->lockDatabaseOnScreenLockCheckBox->isChecked()); config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked()); config()->set("security/passwordsrepeat", m_secUi->passwordRepeatCheckBox->isChecked()); diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index 08fa6f4ea..93e3e9114 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/SettingsWidgetSecurity.ui @@ -142,6 +142,13 @@ + + + + Lock databases when session is locked or lid is closed + + + From 289e98ed5b0e542aa3347f8ce2ef440ace6f3a0d Mon Sep 17 00:00:00 2001 From: Claudio Bantaloukas Date: Sun, 1 Jan 2017 14:11:31 +0100 Subject: [PATCH 286/333] remove commented code --- src/core/ScreenLockListenerDBus.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index 4ac54a839..3edb59577 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -32,18 +32,6 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): "Locked", // signal name this, //receiver SLOT(unityLocked())); - - /* Currently unable to get the current user session from login1.Manager - QDBusInterface login1_manager_iface("org.freedesktop.login1", "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", systemBus); - if(login1_manager_iface.isValid()){ - qint64 my_pid = QCoreApplication::applicationPid(); - QDBusReply reply = login1_manager_iface.call("GetSessionByPID",static_cast(my_pid)); - if (reply.isValid()){ - QString current_session = reply.value(); - } - } - */ } void ScreenLockListenerDBus::gnomeSessionStatusChanged(uint status) From 1b7b2ff456dca5b17d6c25ccfc368170950bf43c Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 4 May 2017 22:52:10 +0200 Subject: [PATCH 287/333] Added freedesktop DBus, fixed codestyle --- src/CMakeLists.txt | 7 +++++++ src/core/Config.cpp | 1 + src/core/ScreenLockListenerDBus.cpp | 20 ++++++++++++++++++-- src/core/ScreenLockListenerDBus.h | 1 + src/core/ScreenLockListenerMac.cpp | 16 ++++++++++------ src/core/ScreenLockListenerPrivate.cpp | 8 +++++--- src/core/ScreenLockListenerWin.cpp | 26 +++++++++++++------------- src/gui/SettingsWidgetSecurity.ui | 14 +++++++------- 8 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dfdf96c8a..9dfa52679 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -222,9 +222,16 @@ target_link_libraries(keepassx_core ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES}) + +if(APPLE) + target_link_libraries(keepassx_core "-framework Foundation") +endif() if (UNIX AND NOT APPLE) target_link_libraries(keepassx_core Qt5::DBus) endif() +if(MINGW) + target_link_libraries(keepassx_core Wtsapi32.lib) +endif() if(MINGW) include(GenerateProductVersion) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index c0876daa5..f0a369c30 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -119,6 +119,7 @@ void Config::init(const QString& fileName) m_defaults.insert("security/lockdatabaseidle", false); m_defaults.insert("security/lockdatabaseidlesec", 240); m_defaults.insert("security/lockdatabaseminimize", false); + m_defaults.insert("security/lockdatabasescreenlock", true); m_defaults.insert("security/passwordsrepeat", false); m_defaults.insert("security/passwordscleartext", false); m_defaults.insert("security/autotypeask", true); diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index 3edb59577..2b4de9da3 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -9,6 +9,15 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): { QDBusConnection sessionBus = QDBusConnection::sessionBus(); QDBusConnection systemBus = QDBusConnection::systemBus(); + + sessionBus.connect( + "org.freedesktop.ScreenSaver", // service + "/org/freedesktop/ScreenSaver", // path + "org.freedesktop.ScreenSaver", // interface + "ActiveChanged", // signal name + this, //receiver + SLOT(freedesktopScreenSaver(bool))); + sessionBus.connect( "org.gnome.SessionManager", // service "/org/gnome/SessionManager/Presence", // path @@ -36,14 +45,14 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): void ScreenLockListenerDBus::gnomeSessionStatusChanged(uint status) { - if(status != 0){ + if (status != 0) { Q_EMIT screenLocked(); } } void ScreenLockListenerDBus::logindPrepareForSleep(bool beforeSleep) { - if(beforeSleep){ + if (beforeSleep) { Q_EMIT screenLocked(); } } @@ -52,3 +61,10 @@ void ScreenLockListenerDBus::unityLocked() { Q_EMIT screenLocked(); } + +void ScreenLockListenerDBus::freedesktopScreenSaver(bool status) +{ + if (status) { + Q_EMIT screenLocked(); + } +} \ No newline at end of file diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h index be6fcd5f0..0c49323f7 100644 --- a/src/core/ScreenLockListenerDBus.h +++ b/src/core/ScreenLockListenerDBus.h @@ -14,6 +14,7 @@ private Q_SLOTS: void gnomeSessionStatusChanged(uint status); void logindPrepareForSleep(bool beforeSleep); void unityLocked(); + void freedesktopScreenSaver(bool status); }; #endif // SCREENLOCKLISTENERDBUS_H diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp index 57dc2de3f..7be9dd2f2 100644 --- a/src/core/ScreenLockListenerMac.cpp +++ b/src/core/ScreenLockListenerMac.cpp @@ -3,29 +3,33 @@ #include #include -ScreenLockListenerMac* ScreenLockListenerMac::instance(){ +ScreenLockListenerMac* ScreenLockListenerMac::instance() +{ static QMutex mutex; QMutexLocker lock(&mutex); static ScreenLockListenerMac* m_ptr=NULL; - if (m_ptr==NULL){ + if (m_ptr == NULL) { m_ptr = new ScreenLockListenerMac(); } return m_ptr; } void ScreenLockListenerMac::notificationCenterCallBack(CFNotificationCenterRef /*center*/, void */*observer*/, - CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/){ + CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/) +{ instance()->onSignalReception(); } -ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent): - ScreenLockListenerPrivate(parent){ +ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) + : ScreenLockListenerPrivate(parent) +{ CFNotificationCenterRef distCenter; CFStringRef screenIsLockedSignal = CFSTR("com.apple.screenIsLocked"); distCenter = CFNotificationCenterGetDistributedCenter(); - if (NULL == distCenter) + if (NULL == distCenter) { return; + } CFNotificationCenterAddObserver( distCenter, diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp index 6f3f7bbf8..e0c6d234c 100644 --- a/src/core/ScreenLockListenerPrivate.cpp +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -9,11 +9,13 @@ #include "ScreenLockListenerWin.h" #endif -ScreenLockListenerPrivate::ScreenLockListenerPrivate(QWidget* parent): - QObject(parent){ +ScreenLockListenerPrivate::ScreenLockListenerPrivate(QWidget* parent) + : QObject(parent) +{ } -ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent){ +ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent) +{ #if defined(Q_OS_OSX) Q_UNUSED(parent); return ScreenLockListenerMac::instance(); diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 349d5de58..21c59c723 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -8,11 +8,11 @@ * See https://msdn.microsoft.com/en-us/library/aa383841(v=vs.85).aspx * See https://blogs.msdn.microsoft.com/oldnewthing/20060104-50/?p=32783 */ -ScreenLockListenerWin::ScreenLockListenerWin(QWidget *parent) : - ScreenLockListenerPrivate(parent), - QAbstractNativeEventFilter() +ScreenLockListenerWin::ScreenLockListenerWin(QWidget *parent) + : ScreenLockListenerPrivate(parent) + , QAbstractNativeEventFilter() { - Q_ASSERT(parent!=NULL); + Q_ASSERT(parent != NULL); // On windows, we need to register for platform specific messages and // install a message handler for them QCoreApplication::instance()->installNativeEventFilter(this); @@ -26,7 +26,7 @@ ScreenLockListenerWin::ScreenLockListenerWin(QWidget *parent) : // This call requests a notification for session changes if (!WTSRegisterSessionNotification( reinterpret_cast(parent->winId()), - NOTIFY_FOR_THIS_SESSION)){ + NOTIFY_FOR_THIS_SESSION)) { } } @@ -35,31 +35,31 @@ ScreenLockListenerWin::~ScreenLockListenerWin() HWND h= reinterpret_cast(static_cast(parent())->winId()); WTSUnRegisterSessionNotification(h); - if(m_powernotificationhandle){ + if (m_powernotificationhandle) { UnregisterPowerSettingNotification(reinterpret_cast(m_powernotificationhandle)); } } bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void *message, long *) { - if(eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG"){ + if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { MSG* m = static_cast(message); - if(m->message == WM_POWERBROADCAST){ + if (m->message == WM_POWERBROADCAST) { const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); - if (setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE){ + if (setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE) { const DWORD* state = reinterpret_cast(&setting->Data); - if (*state == 0){ + if (*state == 0) { Q_EMIT screenLocked(); return true; } } } - if(m->message == WM_WTSSESSION_CHANGE){ - if (m->wParam==WTS_CONSOLE_DISCONNECT){ + if (m->message == WM_WTSSESSION_CHANGE) { + if (m->wParam == WTS_CONSOLE_DISCONNECT) { Q_EMIT screenLocked(); return true; } - if (m->wParam==WTS_SESSION_LOCK){ + if (m->wParam == WTS_SESSION_LOCK) { Q_EMIT screenLocked(); return true; } diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index 93e3e9114..30ab2ecfc 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/SettingsWidgetSecurity.ui @@ -123,6 +123,13 @@ + + + + Lock databases when session is locked or lid is closed + + + @@ -142,13 +149,6 @@ - - - - Lock databases when session is locked or lid is closed - - - From 8ddd0b2f6f12e248aa042155bd298bf6c0060901 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 4 May 2017 23:09:08 +0200 Subject: [PATCH 288/333] Revert travis settings --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f3aeccf72..e24d1d178 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,7 @@ services: [docker] os: - linux - - osx - -osx_image: xcode8.2 +# - osx # Define clang compiler without any frills compiler: @@ -28,7 +26,6 @@ before_install: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq cmake || brew install cmake; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq qt5 || brew install qt5; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libgcrypt || brew install libgcrypt; fi - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libmicrohttpd || brew install libmicrohttpd; fi before_script: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then CMAKE_ARGS="-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5"; fi From 147c000ef1478e305ed78e525a1184c2e08910a7 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 6 May 2017 22:14:53 -0400 Subject: [PATCH 289/333] Corrected nullptr crash on Windows when going to sleep --- src/core/ScreenLockListenerWin.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 21c59c723..4aae8a28d 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -45,13 +45,18 @@ bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { MSG* m = static_cast(message); if (m->message == WM_POWERBROADCAST) { - const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); - if (setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE) { - const DWORD* state = reinterpret_cast(&setting->Data); - if (*state == 0) { - Q_EMIT screenLocked(); - return true; + if (m->wParam == PBT_POWERSETTINGCHANGE) { + const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); + if (setting != nullptr && setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE) { + const DWORD* state = reinterpret_cast(&setting->Data); + if (*state == 0) { + Q_EMIT screenLocked(); + return true; + } } + } else if (m->wParam == PBT_APMSUSPEND) { + Q_EMIT screenLocked(); + return true; } } if (m->message == WM_WTSSESSION_CHANGE) { From 3218cb9ace1e3fb32e8cc2d74b2b839a920276bc Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 6 May 2017 22:34:12 -0400 Subject: [PATCH 290/333] Moved locking checkboxes into security settings --- src/gui/SettingsWidgetGeneral.ui | 7 ---- src/gui/SettingsWidgetSecurity.ui | 62 +++++++++++++++++-------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index ef947d3f0..2fe0f4089 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -367,13 +367,6 @@ - - - - Close database when session is locked or lid is closed - - - diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index 30ab2ecfc..679c470ad 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/SettingsWidgetSecurity.ui @@ -28,7 +28,14 @@ Timeouts - + + + + + Clear clipboard after + + + @@ -54,7 +61,20 @@ - + + + + + 0 + 0 + + + + Lock databases after inactivity of + + + + false @@ -79,20 +99,6 @@ - - - - Clear clipboard after - - - - - - - Lock databases after inactivity of - - - @@ -101,18 +107,11 @@ Convenience - + - + - Don't require password repeat when it is visible - - - - - - - Show passwords in cleartext by default + Lock databases when session is locked or lid is closed @@ -124,9 +123,16 @@ - + - Lock databases when session is locked or lid is closed + Don't require password repeat when it is visible + + + + + + + Show passwords in cleartext by default From 533136fb0eb0391a294f742ddfb099adb69cec92 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 8 May 2017 16:03:19 +0200 Subject: [PATCH 291/333] Add file header, use nullptr instead of NULL, fix code style --- src/core/ScreenLockListener.cpp | 17 ++++++++++++++++ src/core/ScreenLockListener.h | 19 +++++++++++++++++- src/core/ScreenLockListenerDBus.cpp | 17 ++++++++++++++++ src/core/ScreenLockListenerDBus.h | 17 ++++++++++++++++ src/core/ScreenLockListenerMac.cpp | 25 ++++++++++++++++++++---- src/core/ScreenLockListenerMac.h | 19 +++++++++++++++++- src/core/ScreenLockListenerPrivate.cpp | 17 ++++++++++++++++ src/core/ScreenLockListenerPrivate.h | 21 ++++++++++++++++++-- src/core/ScreenLockListenerWin.cpp | 27 +++++++++++++++++++++----- src/core/ScreenLockListenerWin.h | 23 +++++++++++++++++++--- 10 files changed, 186 insertions(+), 16 deletions(-) diff --git a/src/core/ScreenLockListener.cpp b/src/core/ScreenLockListener.cpp index 2c17dfa14..eb78cd608 100644 --- a/src/core/ScreenLockListener.cpp +++ b/src/core/ScreenLockListener.cpp @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "ScreenLockListener.h" #include "ScreenLockListenerPrivate.h" diff --git a/src/core/ScreenLockListener.h b/src/core/ScreenLockListener.h index 7339054f9..bc115d19c 100644 --- a/src/core/ScreenLockListener.h +++ b/src/core/ScreenLockListener.h @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef SCREENLOCKLISTENER_H #define SCREENLOCKLISTENER_H #include @@ -8,7 +25,7 @@ class ScreenLockListener : public QObject { Q_OBJECT public: - ScreenLockListener(QWidget* parent=NULL); + ScreenLockListener(QWidget* parent = nullptr); ~ScreenLockListener(); Q_SIGNALS: diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index 2b4de9da3..e24e388a3 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "ScreenLockListenerDBus.h" #include diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h index 0c49323f7..a907f20cc 100644 --- a/src/core/ScreenLockListenerDBus.h +++ b/src/core/ScreenLockListenerDBus.h @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef SCREENLOCKLISTENERDBUS_H #define SCREENLOCKLISTENERDBUS_H #include diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp index 7be9dd2f2..3917a888c 100644 --- a/src/core/ScreenLockListenerMac.cpp +++ b/src/core/ScreenLockListenerMac.cpp @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "ScreenLockListenerMac.h" #include @@ -8,8 +25,8 @@ ScreenLockListenerMac* ScreenLockListenerMac::instance() static QMutex mutex; QMutexLocker lock(&mutex); - static ScreenLockListenerMac* m_ptr=NULL; - if (m_ptr == NULL) { + static ScreenLockListenerMac* m_ptr = nullptr; + if (m_ptr == nullptr) { m_ptr = new ScreenLockListenerMac(); } return m_ptr; @@ -27,7 +44,7 @@ ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) CFNotificationCenterRef distCenter; CFStringRef screenIsLockedSignal = CFSTR("com.apple.screenIsLocked"); distCenter = CFNotificationCenterGetDistributedCenter(); - if (NULL == distCenter) { + if (nullptr == distCenter) { return; } @@ -35,7 +52,7 @@ ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) distCenter, this, &ScreenLockListenerMac::notificationCenterCallBack, screenIsLockedSignal, - NULL, + nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); } diff --git a/src/core/ScreenLockListenerMac.h b/src/core/ScreenLockListenerMac.h index 5824339f1..733cb3756 100644 --- a/src/core/ScreenLockListenerMac.h +++ b/src/core/ScreenLockListenerMac.h @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef SCREENLOCKLISTENERMAC_H #define SCREENLOCKLISTENERMAC_H #include @@ -16,7 +33,7 @@ public: CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/); private: - ScreenLockListenerMac(QWidget* parent=NULL); + ScreenLockListenerMac(QWidget* parent = nullptr); void onSignalReception(); }; diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp index e0c6d234c..9a0ebd69b 100644 --- a/src/core/ScreenLockListenerPrivate.cpp +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "ScreenLockListenerPrivate.h" #if defined(Q_OS_OSX) #include "ScreenLockListenerMac.h" diff --git a/src/core/ScreenLockListenerPrivate.h b/src/core/ScreenLockListenerPrivate.h index aef20edcc..781d64527 100644 --- a/src/core/ScreenLockListenerPrivate.h +++ b/src/core/ScreenLockListenerPrivate.h @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef SCREENLOCKLISTENERPRIVATE_H #define SCREENLOCKLISTENERPRIVATE_H #include @@ -7,10 +24,10 @@ class ScreenLockListenerPrivate : public QObject { Q_OBJECT public: - static ScreenLockListenerPrivate* instance(QWidget *parent = 0); + static ScreenLockListenerPrivate* instance(QWidget* parent = 0); protected: - ScreenLockListenerPrivate(QWidget *parent = 0); + ScreenLockListenerPrivate(QWidget* parent = 0); Q_SIGNALS: void screenLocked(); diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 4aae8a28d..3e7950abc 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "ScreenLockListenerWin.h" #include #include @@ -8,11 +25,11 @@ * See https://msdn.microsoft.com/en-us/library/aa383841(v=vs.85).aspx * See https://blogs.msdn.microsoft.com/oldnewthing/20060104-50/?p=32783 */ -ScreenLockListenerWin::ScreenLockListenerWin(QWidget *parent) +ScreenLockListenerWin::ScreenLockListenerWin(QWidget* parent) : ScreenLockListenerPrivate(parent) , QAbstractNativeEventFilter() { - Q_ASSERT(parent != NULL); + Q_ASSERT(parent != nullptr); // On windows, we need to register for platform specific messages and // install a message handler for them QCoreApplication::instance()->installNativeEventFilter(this); @@ -21,7 +38,7 @@ ScreenLockListenerWin::ScreenLockListenerWin(QWidget *parent) HPOWERNOTIFY hPnotify = RegisterPowerSettingNotification( reinterpret_cast(parent->winId()), &GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE); - m_powernotificationhandle = reinterpret_cast(hPnotify); + m_powerNotificationHandle = reinterpret_cast(hPnotify); // This call requests a notification for session changes if (!WTSRegisterSessionNotification( @@ -40,10 +57,10 @@ ScreenLockListenerWin::~ScreenLockListenerWin() } } -bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void *message, long *) +bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void* message, long *) { if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { - MSG* m = static_cast(message); + MSG* m = static_cast(message); if (m->message == WM_POWERBROADCAST) { if (m->wParam == PBT_POWERSETTINGCHANGE) { const POWERBROADCAST_SETTING* setting = reinterpret_cast(m->lParam); diff --git a/src/core/ScreenLockListenerWin.h b/src/core/ScreenLockListenerWin.h index ccf42ad70..0a1c37f91 100644 --- a/src/core/ScreenLockListenerWin.h +++ b/src/core/ScreenLockListenerWin.h @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef SCREENLOCKLISTENERWIN_H #define SCREENLOCKLISTENERWIN_H #include @@ -10,12 +27,12 @@ class ScreenLockListenerWin : public ScreenLockListenerPrivate, public QAbstract { Q_OBJECT public: - explicit ScreenLockListenerWin(QWidget *parent = 0); + explicit ScreenLockListenerWin(QWidget* parent = 0); ~ScreenLockListenerWin(); - virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *) Q_DECL_OVERRIDE; + virtual bool nativeEventFilter(const QByteArray &eventType, void* message, long *) override; private: - void * m_powernotificationhandle; + void* m_powerNotificationHandle ; }; #endif // SCREENLOCKLISTENERWIN_H From 970525cfd4c5714ce19516517254138153ca2b59 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 10 May 2017 11:05:17 -0400 Subject: [PATCH 292/333] Styling + CFNotificationName -> CFStringRef --- src/core/ScreenLockListenerMac.cpp | 17 +++++++++-------- src/core/ScreenLockListenerMac.h | 5 +++-- src/core/ScreenLockListenerWin.cpp | 2 +- src/core/ScreenLockListenerWin.h | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp index 3917a888c..d919adb6c 100644 --- a/src/core/ScreenLockListenerMac.cpp +++ b/src/core/ScreenLockListenerMac.cpp @@ -32,8 +32,9 @@ ScreenLockListenerMac* ScreenLockListenerMac::instance() return m_ptr; } -void ScreenLockListenerMac::notificationCenterCallBack(CFNotificationCenterRef /*center*/, void */*observer*/, - CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/) +void ScreenLockListenerMac::notificationCenterCallBack(CFNotificationCenterRef, void*, + CFStringRef, const void*, + CFDictionaryRef) { instance()->onSignalReception(); } @@ -48,12 +49,12 @@ ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) return; } - CFNotificationCenterAddObserver( - distCenter, - this, &ScreenLockListenerMac::notificationCenterCallBack, - screenIsLockedSignal, - nullptr, - CFNotificationSuspensionBehaviorDeliverImmediately); + CFNotificationCenterAddObserver(distCenter, + this, + &ScreenLockListenerMac::notificationCenterCallBack, + screenIsLockedSignal, + nullptr, + CFNotificationSuspensionBehaviorDeliverImmediately); } void ScreenLockListenerMac::onSignalReception() diff --git a/src/core/ScreenLockListenerMac.h b/src/core/ScreenLockListenerMac.h index 733cb3756..cd36ce9e6 100644 --- a/src/core/ScreenLockListenerMac.h +++ b/src/core/ScreenLockListenerMac.h @@ -29,8 +29,9 @@ class ScreenLockListenerMac: public ScreenLockListenerPrivate { public: static ScreenLockListenerMac* instance(); - static void notificationCenterCallBack(CFNotificationCenterRef /*center*/, void */*observer*/, - CFNotificationName /*name*/, const void */*object*/, CFDictionaryRef /*userInfo*/); + static void notificationCenterCallBack(CFNotificationCenterRef center, void* observer, + CFStringRef name, const void* object, + CFDictionaryRef userInfo); private: ScreenLockListenerMac(QWidget* parent = nullptr); diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 3e7950abc..9429393fe 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -57,7 +57,7 @@ ScreenLockListenerWin::~ScreenLockListenerWin() } } -bool ScreenLockListenerWin::nativeEventFilter(const QByteArray &eventType, void* message, long *) +bool ScreenLockListenerWin::nativeEventFilter(const QByteArray& eventType, void* message, long*) { if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") { MSG* m = static_cast(message); diff --git a/src/core/ScreenLockListenerWin.h b/src/core/ScreenLockListenerWin.h index 0a1c37f91..6a8380efe 100644 --- a/src/core/ScreenLockListenerWin.h +++ b/src/core/ScreenLockListenerWin.h @@ -29,7 +29,7 @@ class ScreenLockListenerWin : public ScreenLockListenerPrivate, public QAbstract public: explicit ScreenLockListenerWin(QWidget* parent = 0); ~ScreenLockListenerWin(); - virtual bool nativeEventFilter(const QByteArray &eventType, void* message, long *) override; + virtual bool nativeEventFilter(const QByteArray &eventType, void* message, long*) override; private: void* m_powerNotificationHandle ; From b69b50c6c6f2152cedf4528af24beed6179bcbbe Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 17 May 2017 13:03:51 +0200 Subject: [PATCH 293/333] fix codestyle and use C++11 keywords --- src/core/ScreenLockListener.h | 2 +- src/core/ScreenLockListenerDBus.cpp | 8 ++++---- src/core/ScreenLockListenerDBus.h | 2 +- src/core/ScreenLockListenerMac.cpp | 2 +- src/core/ScreenLockListenerPrivate.cpp | 6 ++---- src/core/ScreenLockListenerPrivate.h | 2 +- src/core/ScreenLockListenerWin.cpp | 8 ++++---- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/core/ScreenLockListener.h b/src/core/ScreenLockListener.h index bc115d19c..b4eb81e04 100644 --- a/src/core/ScreenLockListener.h +++ b/src/core/ScreenLockListener.h @@ -28,7 +28,7 @@ public: ScreenLockListener(QWidget* parent = nullptr); ~ScreenLockListener(); -Q_SIGNALS: +signals: void screenLocked(); private: diff --git a/src/core/ScreenLockListenerDBus.cpp b/src/core/ScreenLockListenerDBus.cpp index e24e388a3..1976b47ea 100644 --- a/src/core/ScreenLockListenerDBus.cpp +++ b/src/core/ScreenLockListenerDBus.cpp @@ -63,25 +63,25 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent): void ScreenLockListenerDBus::gnomeSessionStatusChanged(uint status) { if (status != 0) { - Q_EMIT screenLocked(); + emit screenLocked(); } } void ScreenLockListenerDBus::logindPrepareForSleep(bool beforeSleep) { if (beforeSleep) { - Q_EMIT screenLocked(); + emit screenLocked(); } } void ScreenLockListenerDBus::unityLocked() { - Q_EMIT screenLocked(); + emit screenLocked(); } void ScreenLockListenerDBus::freedesktopScreenSaver(bool status) { if (status) { - Q_EMIT screenLocked(); + emit screenLocked(); } } \ No newline at end of file diff --git a/src/core/ScreenLockListenerDBus.h b/src/core/ScreenLockListenerDBus.h index a907f20cc..72f308f72 100644 --- a/src/core/ScreenLockListenerDBus.h +++ b/src/core/ScreenLockListenerDBus.h @@ -27,7 +27,7 @@ class ScreenLockListenerDBus : public ScreenLockListenerPrivate public: explicit ScreenLockListenerDBus(QWidget *parent = 0); -private Q_SLOTS: +private slots: void gnomeSessionStatusChanged(uint status); void logindPrepareForSleep(bool beforeSleep); void unityLocked(); diff --git a/src/core/ScreenLockListenerMac.cpp b/src/core/ScreenLockListenerMac.cpp index d919adb6c..dce05de3f 100644 --- a/src/core/ScreenLockListenerMac.cpp +++ b/src/core/ScreenLockListenerMac.cpp @@ -59,5 +59,5 @@ ScreenLockListenerMac::ScreenLockListenerMac(QWidget* parent) void ScreenLockListenerMac::onSignalReception() { - Q_EMIT screenLocked(); + emit screenLocked(); } diff --git a/src/core/ScreenLockListenerPrivate.cpp b/src/core/ScreenLockListenerPrivate.cpp index 9a0ebd69b..36ee301f2 100644 --- a/src/core/ScreenLockListenerPrivate.cpp +++ b/src/core/ScreenLockListenerPrivate.cpp @@ -36,11 +36,9 @@ ScreenLockListenerPrivate* ScreenLockListenerPrivate::instance(QWidget* parent) #if defined(Q_OS_OSX) Q_UNUSED(parent); return ScreenLockListenerMac::instance(); -#endif -#if defined(Q_OS_LINUX) +#elif defined(Q_OS_LINUX) return new ScreenLockListenerDBus(parent); -#endif -#if defined(Q_OS_WIN) +#elif defined(Q_OS_WIN) return new ScreenLockListenerWin(parent); #endif } diff --git a/src/core/ScreenLockListenerPrivate.h b/src/core/ScreenLockListenerPrivate.h index 781d64527..8ecad17d8 100644 --- a/src/core/ScreenLockListenerPrivate.h +++ b/src/core/ScreenLockListenerPrivate.h @@ -29,7 +29,7 @@ public: protected: ScreenLockListenerPrivate(QWidget* parent = 0); -Q_SIGNALS: +signals: void screenLocked(); }; diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index 9429393fe..a1bf13d4f 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -67,22 +67,22 @@ bool ScreenLockListenerWin::nativeEventFilter(const QByteArray& eventType, void* if (setting != nullptr && setting->PowerSetting == GUID_LIDSWITCH_STATE_CHANGE) { const DWORD* state = reinterpret_cast(&setting->Data); if (*state == 0) { - Q_EMIT screenLocked(); + emit screenLocked(); return true; } } } else if (m->wParam == PBT_APMSUSPEND) { - Q_EMIT screenLocked(); + emit screenLocked(); return true; } } if (m->message == WM_WTSSESSION_CHANGE) { if (m->wParam == WTS_CONSOLE_DISCONNECT) { - Q_EMIT screenLocked(); + emit screenLocked(); return true; } if (m->wParam == WTS_SESSION_LOCK) { - Q_EMIT screenLocked(); + emit screenLocked(); return true; } } From 00dc4b9ace3b23cffc09115a9e05c06742dc01f1 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 17 May 2017 10:05:45 +0200 Subject: [PATCH 294/333] Ignore double close event on macOS, resolves #430 --- src/gui/MainWindow.cpp | 15 +++++++++++---- src/gui/MainWindow.h | 4 +++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 3d5fad919..6a4117377 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -103,9 +103,9 @@ const QString MainWindow::BaseWindowTitle = "KeePassXC"; MainWindow::MainWindow() : m_ui(new Ui::MainWindow()) , m_trayIcon(nullptr) + , m_appExitCalled(false) + , m_appExiting(false) { - appExitCalled = false; - m_ui->setupUi(this); // Setup the search widget in the toolbar @@ -347,7 +347,7 @@ MainWindow::~MainWindow() void MainWindow::appExit() { - appExitCalled = true; + m_appExitCalled = true; close(); } @@ -663,9 +663,15 @@ void MainWindow::databaseTabChanged(int tabIndex) void MainWindow::closeEvent(QCloseEvent* event) { + // ignore double close events (happens on macOS when closing from the dock) + if (m_appExiting) { + event->accept(); + return; + } + bool minimizeOnClose = isTrayIconEnabled() && config()->get("GUI/MinimizeOnClose").toBool(); - if (minimizeOnClose && !appExitCalled) + if (minimizeOnClose && !m_appExitCalled) { event->ignore(); hideWindow(); @@ -680,6 +686,7 @@ void MainWindow::closeEvent(QCloseEvent* event) bool accept = saveLastDatabases(); if (accept) { + m_appExiting = true; saveWindowInformation(); event->accept(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 72082e4ed..fffc634a9 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -40,6 +40,7 @@ class MainWindow : public QMainWindow public: MainWindow(); ~MainWindow(); + enum StackedWidgetIndex { DatabaseTabScreen = 0, @@ -118,7 +119,8 @@ private: Q_DISABLE_COPY(MainWindow) - bool appExitCalled; + bool m_appExitCalled; + bool m_appExiting; }; #define KEEPASSXC_MAIN_WINDOW (qobject_cast(qApp) ? \ From 1c54d2496269cf88f9d429d3a3f9671738c6fad9 Mon Sep 17 00:00:00 2001 From: Weslly Date: Fri, 19 May 2017 00:42:31 -0300 Subject: [PATCH 295/333] Fix quit submenu on macOS tray icon --- src/gui/MainWindow.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 4e25a9426..a02d75b80 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -747,10 +747,17 @@ void MainWindow::updateTrayIcon() QAction* actionToggle = new QAction(tr("Toggle window"), menu); menu->addAction(actionToggle); +#ifdef Q_OS_MAC + QAction* actionQuit = new QAction(tr("Quit KeePassXC"), menu); + menu->addAction(actionQuit); + + connect(actionQuit, SIGNAL(triggered()), SLOT(appExit())); +#else menu->addAction(m_ui->actionQuit); connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(trayIconTriggered(QSystemTrayIcon::ActivationReason))); +#endif connect(actionToggle, SIGNAL(triggered()), SLOT(toggleWindow())); m_trayIcon->setContextMenu(menu); From c0640e49eeed5b999739f397dee0389411394d24 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 19 May 2017 14:30:09 +0200 Subject: [PATCH 296/333] revert old if structure --- src/gui/MainWindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index a02d75b80..ce8653e63 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -660,7 +660,9 @@ void MainWindow::databaseTabChanged(int tabIndex) void MainWindow::closeEvent(QCloseEvent* event) { - if (isTrayIconEnabled() && config()->get("GUI/MinimizeOnClose").toBool() && !appExitCalled) + bool minimizeOnClose = isTrayIconEnabled() && + config()->get("GUI/MinimizeOnClose").toBool(); + if (minimizeOnClose && !appExitCalled) { event->ignore(); hideWindow(); From a2e82dc88395faa73dcf33471637c9465b6ae336 Mon Sep 17 00:00:00 2001 From: louib Date: Fri, 19 May 2017 14:04:11 -0400 Subject: [PATCH 297/333] Feature : clip command (#578) --- src/cli/CMakeLists.txt | 2 ++ src/cli/Clip.cpp | 73 +++++++++++++++++++++++++++++++++++++++ src/cli/Clip.h | 27 +++++++++++++++ src/cli/Extract.cpp | 9 +++-- src/cli/List.cpp | 11 +++--- src/cli/Merge.cpp | 29 ++++++++-------- src/cli/Show.cpp | 15 ++++---- src/cli/keepassxc-cli.cpp | 10 ++++-- src/core/Database.cpp | 2 +- src/core/Group.cpp | 46 ++++++++++++++++++++++-- src/core/Group.h | 4 ++- src/core/Uuid.cpp | 7 ++++ src/core/Uuid.h | 3 ++ tests/TestGroup.cpp | 52 ++++++++++++++++++++++++++++ tests/TestGroup.h | 1 + 15 files changed, 250 insertions(+), 41 deletions(-) create mode 100644 src/cli/Clip.cpp create mode 100644 src/cli/Clip.h diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index e090ad1d8..86edb9c01 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -14,6 +14,8 @@ # along with this program. If not, see . set(cli_SOURCES + Clip.cpp + Clip.h EntropyMeter.cpp EntropyMeter.h Extract.cpp diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp new file mode 100644 index 000000000..b3d4fd107 --- /dev/null +++ b/src/cli/Clip.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "Clip.h" + +#include +#include +#include +#include +#include + +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "gui/Clipboard.h" +#include "keys/CompositeKey.h" + +int Clip::execute(int argc, char** argv) +{ + QApplication app(argc, argv); + QTextStream out(stdout); + + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::translate("main", "Copy a password to the clipboard")); + parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); + parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Name of the entry to clip.")); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 2) { + parser.showHelp(); + return EXIT_FAILURE; + } + + out << "Insert the database password\n> "; + out.flush(); + + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + QString line = inputTextStream.readLine(); + CompositeKey key = CompositeKey::readFromLine(line); + + Database* db = Database::openDatabaseFile(args.at(0), key); + if (!db) { + return EXIT_FAILURE; + } + + QString entryId = args.at(1); + Entry* entry = db->rootGroup()->findEntry(entryId); + if (!entry) { + qCritical("Entry %s not found.", qPrintable(entryId)); + return EXIT_FAILURE; + } + + Clipboard::instance()->setText(entry->password()); + return EXIT_SUCCESS; +} diff --git a/src/cli/Clip.h b/src/cli/Clip.h new file mode 100644 index 000000000..944184095 --- /dev/null +++ b/src/cli/Clip.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_CLIP_H +#define KEEPASSXC_CLIP_H + +class Clip +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_CLIP_H diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 81a9ddf07..74fa33da7 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -30,14 +30,14 @@ #include "format/KeePass2Reader.h" #include "keys/CompositeKey.h" -int Extract::execute(int argc, char **argv) +int Extract::execute(int argc, char** argv) { QCoreApplication app(argc, argv); QTextStream out(stdout); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", - "Extract and print the content of a database.")); + parser.setApplicationDescription( + QCoreApplication::translate("main", "Extract and print the content of a database.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database to extract.")); parser.process(app); @@ -75,8 +75,7 @@ int Extract::execute(int argc, char **argv) if (reader.hasError()) { if (xmlData.isEmpty()) { qCritical("Error while reading the database:\n%s", qPrintable(reader.errorString())); - } - else { + } else { qWarning("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); } return EXIT_FAILURE; diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 1e3488106..af3bf0c6d 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -30,7 +30,8 @@ #include "core/Group.h" #include "keys/CompositeKey.h" -void printGroup(Group* group, QString baseName, int depth) { +void printGroup(Group* group, QString baseName, int depth) +{ QTextStream out(stdout); @@ -46,23 +47,21 @@ void printGroup(Group* group, QString baseName, int depth) { } for (Entry* entry : group->entries()) { - out << indentation << " " << entry->title() << " " << entry->uuid().toHex() << "\n"; + out << indentation << " " << entry->title() << " " << entry->uuid().toHex() << "\n"; } for (Group* innerGroup : group->children()) { printGroup(innerGroup, groupName, depth + 1); } - } -int List::execute(int argc, char **argv) +int List::execute(int argc, char** argv) { QCoreApplication app(argc, argv); QTextStream out(stdout); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", - "List database entries.")); + parser.setApplicationDescription(QCoreApplication::translate("main", "List database entries.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); parser.process(app); diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index aa399dd5b..8ff8a7b20 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -37,11 +37,15 @@ int Merge::execute(int argc, char** argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "Merge two databases.")); - parser.addPositionalArgument("database1", QCoreApplication::translate("main", "Path of the database to merge into.")); - parser.addPositionalArgument("database2", QCoreApplication::translate("main", "Path of the database to merge from.")); + parser.addPositionalArgument("database1", + QCoreApplication::translate("main", "Path of the database to merge into.")); + parser.addPositionalArgument("database2", + QCoreApplication::translate("main", "Path of the database to merge from.")); - QCommandLineOption samePasswordOption(QStringList() << "s" << "same-password", - QCoreApplication::translate("main", "Use the same password for both database files.")); + QCommandLineOption samePasswordOption( + QStringList() << "s" + << "same-password", + QCoreApplication::translate("main", "Use the same password for both database files.")); parser.addOption(samePasswordOption); parser.process(app); @@ -54,22 +58,20 @@ int Merge::execute(int argc, char** argv) out << "Insert the first database password\n> "; out.flush(); - + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QString line1 = inputTextStream.readLine(); CompositeKey key1 = CompositeKey::readFromLine(line1); CompositeKey key2; if (parser.isSet("same-password")) { - key2 = *key1.clone(); + key2 = *key1.clone(); + } else { + out << "Insert the second database password\n> "; + out.flush(); + QString line2 = inputTextStream.readLine(); + key2 = CompositeKey::readFromLine(line2); } - else { - out << "Insert the second database password\n> "; - out.flush(); - QString line2 = inputTextStream.readLine(); - key2 = CompositeKey::readFromLine(line2); - } - Database* db1 = Database::openDatabaseFile(args.at(0), key1); if (db1 == nullptr) { @@ -104,5 +106,4 @@ int Merge::execute(int argc, char** argv) out << "Successfully merged the database files.\n"; return EXIT_SUCCESS; - } diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index 9222a093d..59c0219a2 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -30,16 +30,15 @@ #include "core/Group.h" #include "keys/CompositeKey.h" -int Show::execute(int argc, char **argv) +int Show::execute(int argc, char** argv) { QCoreApplication app(argc, argv); QTextStream out(stdout); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", - "Show a password.")); + parser.setApplicationDescription(QCoreApplication::translate("main", "Show a password.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); - parser.addPositionalArgument("uuid", QCoreApplication::translate("main", "Uuid of the entry to show")); + parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Name of the entry to show.")); parser.process(app); const QStringList args = parser.positionalArguments(); @@ -60,10 +59,10 @@ int Show::execute(int argc, char **argv) return EXIT_FAILURE; } - Uuid uuid = Uuid::fromHex(args.at(1)); - Entry* entry = db->resolveEntry(uuid); - if (entry == nullptr) { - qCritical("No entry found with uuid %s", qPrintable(uuid.toHex())); + QString entryId = args.at(1); + Entry* entry = db->rootGroup()->findEntry(entryId); + if (!entry) { + qCritical("Entry %s not found.", qPrintable(entryId)); return EXIT_FAILURE; } diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index b27b7483f..f6b5df8d7 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -35,7 +36,7 @@ #include #endif -int main(int argc, char **argv) +int main(int argc, char** argv) { #ifdef QT_NO_DEBUG Tools::disableCoreDumps(); @@ -53,6 +54,7 @@ int main(int argc, char **argv) QString description("KeePassXC command line interface."); description = description.append(QString("\n\nAvailable commands:")); + description = description.append(QString("\n clip\t\tCopy a password to the clipboard.")); description = description.append(QString("\n extract\tExtract and print the content of a database.")); description = description.append(QString("\n entropy-meter\tCalculate password entropy.")); description = description.append(QString("\n list\t\tList database entries.")); @@ -82,7 +84,10 @@ int main(int argc, char **argv) int exitCode = EXIT_FAILURE; - if (commandName == "entropy-meter") { + if (commandName == "clip") { + argv[0] = const_cast("keepassxc-cli clip"); + exitCode = Clip::execute(argc, argv); + } else if (commandName == "entropy-meter") { argv[0] = const_cast("keepassxc-cli entropy-meter"); exitCode = EntropyMeter::execute(argc, argv); } else if (commandName == "extract") { @@ -110,5 +115,4 @@ int main(int argc, char **argv) #endif return exitCode; - } diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 5b5a707f9..b3897efae 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -367,7 +367,7 @@ void Database::startModifiedTimer() m_timer->start(150); } -const CompositeKey & Database::key() const +const CompositeKey& Database::key() const { return m_data.key; } diff --git a/src/core/Group.cpp b/src/core/Group.cpp index bd4f8851b..886e55cae 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -483,7 +483,24 @@ QList Group::entriesRecursive(bool includeHistoryItems) const return entryList; } -Entry* Group::findEntry(const Uuid& uuid) +Entry* Group::findEntry(QString entryId) +{ + Q_ASSERT(!entryId.isEmpty()); + Q_ASSERT(!entryId.isNull()); + + if (Uuid::isUuid(entryId)) { + Uuid entryUuid = Uuid::fromHex(entryId); + for (Entry* entry : entriesRecursive(false)) { + if (entry->uuid() == entryUuid) { + return entry; + } + } + } + + return findEntryByPath(entryId); +} + +Entry* Group::findEntryByUuid(const Uuid& uuid) { Q_ASSERT(!uuid.isNull()); for (Entry* entry : asConst(m_entries)) { @@ -495,6 +512,29 @@ Entry* Group::findEntry(const Uuid& uuid) return nullptr; } +Entry* Group::findEntryByPath(QString entryPath, QString basePath) +{ + + Q_ASSERT(!entryPath.isEmpty()); + Q_ASSERT(!entryPath.isNull()); + + for (Entry* entry : asConst(m_entries)) { + QString currentEntryPath = basePath + entry->title(); + if (entryPath == currentEntryPath) { + return entry; + } + } + + for (Group* group : asConst(m_children)) { + Entry* entry = group->findEntryByPath(entryPath, basePath + group->name() + QString("/")); + if (entry != nullptr) { + return entry; + } + } + + return nullptr; +} + QList Group::groupsRecursive(bool includeSelf) const { QList groupList; @@ -551,10 +591,10 @@ void Group::merge(const Group* other) const QList dbEntries = other->entries(); for (Entry* entry : dbEntries) { // entries are searched by uuid - if (!findEntry(entry->uuid())) { + if (!findEntryByUuid(entry->uuid())) { entry->clone(Entry::CloneNoFlags)->setGroup(this); } else { - resolveConflict(findEntry(entry->uuid()), entry); + resolveConflict(findEntryByUuid(entry->uuid()), entry); } } diff --git a/src/core/Group.h b/src/core/Group.h index e3e5e7554..a1b2bcb46 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -78,7 +78,9 @@ public: static const int DefaultIconNumber; static const int RecycleBinIconNumber; - Entry* findEntry(const Uuid& uuid); + Entry* findEntry(QString entryId); + Entry* findEntryByUuid(const Uuid& uuid); + Entry* findEntryByPath(QString entryPath, QString basePath = QString("")); Group* findChildByName(const QString& name); void setUuid(const Uuid& uuid); void setName(const QString& name); diff --git a/src/core/Uuid.cpp b/src/core/Uuid.cpp index 1b046c5a3..1b531159f 100644 --- a/src/core/Uuid.cpp +++ b/src/core/Uuid.cpp @@ -22,6 +22,8 @@ #include "crypto/Random.h" const int Uuid::Length = 16; +const QRegExp Uuid::HexRegExp = QRegExp(QString("^[0-9A-F]{%1}$").arg(QString::number(Uuid::Length * 2)), + Qt::CaseInsensitive); Uuid::Uuid() : m_data(Length, 0) @@ -115,3 +117,8 @@ QDataStream& operator>>(QDataStream& stream, Uuid& uuid) return stream; } + +bool Uuid::isUuid(const QString& uuid) +{ + return Uuid::HexRegExp.exactMatch(uuid); +} diff --git a/src/core/Uuid.h b/src/core/Uuid.h index 4164399d6..ecb20e0c3 100644 --- a/src/core/Uuid.h +++ b/src/core/Uuid.h @@ -20,6 +20,7 @@ #include #include +#include class Uuid { @@ -36,8 +37,10 @@ public: bool operator==(const Uuid& other) const; bool operator!=(const Uuid& other) const; static const int Length; + static const QRegExp HexRegExp; static Uuid fromBase64(const QString& str); static Uuid fromHex(const QString& str); + static bool isUuid(const QString& str); private: QByteArray m_data; diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index e87e6cedc..425bc75c7 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -567,3 +567,55 @@ Database* TestGroup::createMergeTestDatabase() return db; } + +void TestGroup::testFindEntry() +{ + Database* db = new Database(); + + Entry* entry1 = new Entry(); + entry1->setTitle(QString("entry1")); + entry1->setGroup(db->rootGroup()); + entry1->setUuid(Uuid::random()); + + Group* group1 = new Group(); + group1->setName("group1"); + + Entry* entry2 = new Entry(); + + entry2->setTitle(QString("entry2")); + entry2->setGroup(group1); + entry2->setUuid(Uuid::random()); + + group1->setParent(db->rootGroup()); + + Entry* entry; + + entry = db->rootGroup()->findEntry(entry1->uuid().toHex()); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry1")); + + entry = db->rootGroup()->findEntry(QString("entry1")); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry1")); + + entry = db->rootGroup()->findEntry(entry2->uuid().toHex()); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry2")); + + entry = db->rootGroup()->findEntry(QString("group1/entry2")); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry2")); + + entry = db->rootGroup()->findEntry(QString("invalid/path/to/entry2")); + QVERIFY(entry == nullptr); + + // A valid UUID that does not exist in this database. + entry = db->rootGroup()->findEntry(QString("febfb01ebcdf9dbd90a3f1579dc75281")); + QVERIFY(entry == nullptr); + + // An invalid UUID. + entry = db->rootGroup()->findEntry(QString("febfb01ebcdf9dbd90a3f1579dc")); + QVERIFY(entry == nullptr); + + delete db; +} diff --git a/tests/TestGroup.h b/tests/TestGroup.h index c9ed8f087..87795dea2 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -38,6 +38,7 @@ private slots: void testMergeConflict(); void testMergeDatabase(); void testMergeConflictKeepBoth(); + void testFindEntry(); private: Database* createMergeTestDatabase(); From 54ad927044439d5852f524edd7837157f43b43ad Mon Sep 17 00:00:00 2001 From: louib Date: Sun, 21 May 2017 13:05:44 -0400 Subject: [PATCH 298/333] Moving print group in Group class. (#586) --- src/cli/List.cpp | 32 +++++++------------------------- src/core/Group.cpp | 36 ++++++++++++++++++++++++++++++++++-- src/core/Group.h | 1 + tests/TestGroup.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ tests/TestGroup.h | 1 + 5 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/cli/List.cpp b/src/cli/List.cpp index af3bf0c6d..8dd250b34 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -30,30 +30,6 @@ #include "core/Group.h" #include "keys/CompositeKey.h" -void printGroup(Group* group, QString baseName, int depth) -{ - - QTextStream out(stdout); - - QString groupName = baseName + group->name() + "/"; - QString indentation = QString(" ").repeated(depth); - - out << indentation << groupName << " " << group->uuid().toHex() << "\n"; - out.flush(); - - if (group->entries().isEmpty() && group->children().isEmpty()) { - out << indentation << " [empty]\n"; - return; - } - - for (Entry* entry : group->entries()) { - out << indentation << " " << entry->title() << " " << entry->uuid().toHex() << "\n"; - } - - for (Group* innerGroup : group->children()) { - printGroup(innerGroup, groupName, depth + 1); - } -} int List::execute(int argc, char** argv) { @@ -63,6 +39,11 @@ int List::execute(int argc, char** argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "List database entries.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); + QCommandLineOption printUuidsOption( + QStringList() << "u" + << "print-uuids", + QCoreApplication::translate("main", "Print the UUIDs of the entries and groups.")); + parser.addOption(printUuidsOption); parser.process(app); const QStringList args = parser.positionalArguments(); @@ -83,6 +64,7 @@ int List::execute(int argc, char** argv) return EXIT_FAILURE; } - printGroup(db->rootGroup(), QString(""), 0); + out << db->rootGroup()->print(parser.isSet("print-uuids")); + out.flush(); return EXIT_SUCCESS; } diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 886e55cae..38acc2d60 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -18,8 +18,8 @@ #include "Group.h" #include "core/Config.h" -#include "core/Global.h" #include "core/DatabaseIcons.h" +#include "core/Global.h" #include "core/Metadata.h" const int Group::DefaultIconNumber = 48; @@ -202,7 +202,7 @@ QString Group::effectiveAutoTypeSequence() const } while (group && sequence.isEmpty()); if (sequence.isEmpty()) { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; + sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; } return sequence; @@ -535,6 +535,38 @@ Entry* Group::findEntryByPath(QString entryPath, QString basePath) return nullptr; } +QString Group::print(bool printUuids, QString baseName, int depth) +{ + + QString response; + QString indentation = QString(" ").repeated(depth); + + if (entries().isEmpty() && children().isEmpty()) { + response += indentation + "[empty]\n"; + return response; + } + + for (Entry* entry : entries()) { + response += indentation + entry->title(); + if (printUuids) { + response += " " + entry->uuid().toHex(); + } + response += "\n"; + } + + for (Group* innerGroup : children()) { + QString newBaseName = baseName + innerGroup->name() + "/"; + response += indentation + newBaseName; + if (printUuids) { + response += " " + innerGroup->uuid().toHex(); + } + response += "\n"; + response += innerGroup->print(printUuids, newBaseName, depth + 1); + } + + return response; +} + QList Group::groupsRecursive(bool includeSelf) const { QList groupList; diff --git a/src/core/Group.h b/src/core/Group.h index a1b2bcb46..9b1f0209f 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -123,6 +123,7 @@ public: Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo) const; void copyDataFrom(const Group* other); void merge(const Group* other); + QString print(bool printUuids = false, QString baseName = QString(""), int depth = 0); signals: void dataChanged(Group* group); diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 425bc75c7..4fb5e4b3b 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -619,3 +619,48 @@ void TestGroup::testFindEntry() delete db; } + +void TestGroup::testPrint() +{ + Database* db = new Database(); + + QString output = db->rootGroup()->print(); + QCOMPARE(output, QString("[empty]\n")); + + output = db->rootGroup()->print(true); + QCOMPARE(output, QString("[empty]\n")); + + Entry* entry1 = new Entry(); + entry1->setTitle(QString("entry1")); + entry1->setGroup(db->rootGroup()); + entry1->setUuid(Uuid::random()); + + output = db->rootGroup()->print(); + QCOMPARE(output, QString("entry1\n")); + + output = db->rootGroup()->print(true); + QCOMPARE(output, QString("entry1 " + entry1->uuid().toHex() + "\n")); + + + Group* group1 = new Group(); + group1->setName("group1"); + + Entry* entry2 = new Entry(); + + entry2->setTitle(QString("entry2")); + entry2->setGroup(group1); + entry2->setUuid(Uuid::random()); + + group1->setParent(db->rootGroup()); + + output = db->rootGroup()->print(); + QVERIFY(output.contains(QString("entry1\n"))); + QVERIFY(output.contains(QString("group1/\n"))); + QVERIFY(output.contains(QString(" entry2\n"))); + + output = db->rootGroup()->print(true); + QVERIFY(output.contains(QString("entry1 " + entry1->uuid().toHex() + "\n"))); + QVERIFY(output.contains(QString("group1/ " + group1->uuid().toHex() + "\n"))); + QVERIFY(output.contains(QString(" entry2 " + entry2->uuid().toHex() + "\n"))); + delete db; +} diff --git a/tests/TestGroup.h b/tests/TestGroup.h index 87795dea2..a0ed9282b 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -39,6 +39,7 @@ private slots: void testMergeDatabase(); void testMergeConflictKeepBoth(); void testFindEntry(); + void testPrint(); private: Database* createMergeTestDatabase(); From eeafe7761479068fda7aec772f0dc36a8d5b1422 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 21 May 2017 13:51:16 -0400 Subject: [PATCH 299/333] Find entry by title. --- src/core/Group.cpp | 13 ++++++++++++- tests/TestGroup.cpp | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 38acc2d60..0c83fa303 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -497,7 +497,18 @@ Entry* Group::findEntry(QString entryId) } } - return findEntryByPath(entryId); + Entry* entry = findEntryByPath(entryId); + if (entry) { + return entry; + } + + for (Entry* entry : entriesRecursive(false)) { + if (entry->title() == entryId) { + return entry; + } + } + + return nullptr; } Entry* Group::findEntryByUuid(const Uuid& uuid) diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 4fb5e4b3b..a706badad 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -606,9 +606,17 @@ void TestGroup::testFindEntry() QVERIFY(entry != nullptr); QCOMPARE(entry->title(), QString("entry2")); + // Should also find the entry only by title. + entry = db->rootGroup()->findEntry(QString("entry2")); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry2")); + entry = db->rootGroup()->findEntry(QString("invalid/path/to/entry2")); QVERIFY(entry == nullptr); + entry = db->rootGroup()->findEntry(QString("entry27")); + QVERIFY(entry == nullptr); + // A valid UUID that does not exist in this database. entry = db->rootGroup()->findEntry(QString("febfb01ebcdf9dbd90a3f1579dc75281")); QVERIFY(entry == nullptr); From c3bd5d21aa239761f6c4998cc79f8846c10d6718 Mon Sep 17 00:00:00 2001 From: louib Date: Mon, 22 May 2017 17:53:41 -0400 Subject: [PATCH 300/333] Adding a GUI prompt for password. (#587) --- src/cli/Clip.cpp | 30 ++++++++++++++++++++---------- src/cli/keepassxc-cli.cpp | 7 ++++--- src/core/Database.cpp | 15 +++++++++++++++ src/core/Database.h | 3 ++- src/gui/UnlockDatabaseDialog.cpp | 26 +++++++++++++++++++++----- src/gui/UnlockDatabaseDialog.h | 3 ++- 6 files changed, 64 insertions(+), 20 deletions(-) diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index b3d4fd107..f587b3f10 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -26,37 +26,47 @@ #include #include +#include "gui/UnlockDatabaseDialog.h" #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" #include "gui/Clipboard.h" -#include "keys/CompositeKey.h" int Clip::execute(int argc, char** argv) { - QApplication app(argc, argv); + + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << QString(argv[i]); + } QTextStream out(stdout); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "Copy a password to the clipboard")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); + QCommandLineOption guiPrompt( + QStringList() << "g" + << "gui-prompt", + QCoreApplication::translate("main", "Use a GUI prompt unlocking the database.")); + parser.addOption(guiPrompt); parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Name of the entry to clip.")); - parser.process(app); + parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() != 2) { + QCoreApplication app(argc, argv); parser.showHelp(); return EXIT_FAILURE; } - out << "Insert the database password\n> "; - out.flush(); + Database* db = nullptr; + QApplication app(argc, argv); + if (parser.isSet("gui-prompt")) { + db = UnlockDatabaseDialog::openDatabasePrompt(args.at(0)); + } else { + db = Database::unlockFromStdin(args.at(0)); + } - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line = inputTextStream.readLine(); - CompositeKey key = CompositeKey::readFromLine(line); - - Database* db = Database::openDatabaseFile(args.at(0), key); if (!db) { return EXIT_FAILURE; } diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index f6b5df8d7..cc09f8a98 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -47,9 +47,6 @@ int main(int argc, char** argv) return EXIT_FAILURE; } - QCoreApplication app(argc, argv); - app.setApplicationVersion(KEEPASSX_VERSION); - QCommandLineParser parser; QString description("KeePassXC command line interface."); @@ -72,6 +69,8 @@ int main(int argc, char** argv) // parser.process(app); if (argc < 2) { + QCoreApplication app(argc, argv); + app.setApplicationVersion(KEEPASSX_VERSION); parser.showHelp(); return EXIT_FAILURE; } @@ -104,6 +103,8 @@ int main(int argc, char** argv) exitCode = Show::execute(argc, argv); } else { qCritical("Invalid command %s.", qPrintable(commandName)); + QCoreApplication app(argc, argv); + app.setApplicationVersion(KEEPASSX_VERSION); parser.showHelp(); exitCode = EXIT_FAILURE; } diff --git a/src/core/Database.cpp b/src/core/Database.cpp index b3897efae..07bf575a8 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -18,6 +18,7 @@ #include "Database.h" #include +#include #include #include @@ -396,3 +397,17 @@ Database* Database::openDatabaseFile(QString fileName, CompositeKey key) return db; } + +Database* Database::unlockFromStdin(QString databaseFilename) +{ + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + QTextStream outputTextStream(stdout); + + outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n>"); + outputTextStream.flush(); + + QString line = inputTextStream.readLine(); + CompositeKey key = CompositeKey::readFromLine(line); + return Database::openDatabaseFile(databaseFilename, key); + +} diff --git a/src/core/Database.h b/src/core/Database.h index 7728d14c8..37745e840 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -90,7 +90,7 @@ public: QByteArray transformSeed() const; quint64 transformRounds() const; QByteArray transformedMasterKey() const; - const CompositeKey & key() const; + const CompositeKey& key() const; QByteArray challengeResponseKey() const; bool challengeMasterSeed(const QByteArray& masterSeed); @@ -120,6 +120,7 @@ public: static Database* databaseByUuid(const Uuid& uuid); static Database* openDatabaseFile(QString fileName, CompositeKey key); + static Database* unlockFromStdin(QString databaseFilename); signals: void groupDataChanged(Group* group); diff --git a/src/gui/UnlockDatabaseDialog.cpp b/src/gui/UnlockDatabaseDialog.cpp index 3d002f756..c3c62c5a8 100644 --- a/src/gui/UnlockDatabaseDialog.cpp +++ b/src/gui/UnlockDatabaseDialog.cpp @@ -19,18 +19,17 @@ #include "UnlockDatabaseWidget.h" #include "autotype/AutoType.h" -#include "gui/DragTabBar.h" #include "core/Database.h" +#include "gui/DragTabBar.h" - -UnlockDatabaseDialog::UnlockDatabaseDialog(QWidget *parent) +UnlockDatabaseDialog::UnlockDatabaseDialog(QWidget* parent) : QDialog(parent) , m_view(new UnlockDatabaseWidget(this)) { connect(m_view, SIGNAL(editFinished(bool)), this, SLOT(complete(bool))); } -void UnlockDatabaseDialog::setDBFilename(const QString &filename) +void UnlockDatabaseDialog::setDBFilename(const QString& filename) { m_view->load(filename); } @@ -40,7 +39,7 @@ void UnlockDatabaseDialog::clearForms() m_view->clearForms(); } -Database *UnlockDatabaseDialog::database() +Database* UnlockDatabaseDialog::database() { return m_view->database(); } @@ -54,3 +53,20 @@ void UnlockDatabaseDialog::complete(bool r) reject(); } } + +Database* UnlockDatabaseDialog::openDatabasePrompt(QString databaseFilename) +{ + + UnlockDatabaseDialog* unlockDatabaseDialog = new UnlockDatabaseDialog(); + unlockDatabaseDialog->setObjectName("Open database"); + unlockDatabaseDialog->setDBFilename(databaseFilename); + unlockDatabaseDialog->show(); + unlockDatabaseDialog->exec(); + + Database* db = unlockDatabaseDialog->database(); + if (!db) { + qWarning("Could not open database %s.", qPrintable(databaseFilename)); + } + delete unlockDatabaseDialog; + return db; +} diff --git a/src/gui/UnlockDatabaseDialog.h b/src/gui/UnlockDatabaseDialog.h index daf8a0f1f..732395eff 100644 --- a/src/gui/UnlockDatabaseDialog.h +++ b/src/gui/UnlockDatabaseDialog.h @@ -31,10 +31,11 @@ class UnlockDatabaseDialog : public QDialog { Q_OBJECT public: - explicit UnlockDatabaseDialog(QWidget *parent = Q_NULLPTR); + explicit UnlockDatabaseDialog(QWidget* parent = Q_NULLPTR); void setDBFilename(const QString& filename); void clearForms(); Database* database(); + static Database* openDatabasePrompt(QString databaseFilename); signals: void unlockDone(bool); From dcc8094ce4ae67f6dd48d9c3a4bf8325a416be89 Mon Sep 17 00:00:00 2001 From: louib Date: Thu, 25 May 2017 13:07:24 -0400 Subject: [PATCH 301/333] Add the GUI prompt option to the merge command. (#589) --- src/cli/Merge.cpp | 51 ++++++++++++++++++++++++++----------------- src/core/Database.cpp | 2 +- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 8ff8a7b20..ca2c71013 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -19,6 +19,7 @@ #include "Merge.h" +#include #include #include #include @@ -27,12 +28,15 @@ #include "core/Database.h" #include "format/KeePass2Writer.h" -#include "keys/CompositeKey.h" +#include "gui/UnlockDatabaseDialog.h" int Merge::execute(int argc, char** argv) { - QCoreApplication app(argc, argv); + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << QString(argv[i]); + } QTextStream out(stdout); QCommandLineParser parser; @@ -47,38 +51,45 @@ int Merge::execute(int argc, char** argv) << "same-password", QCoreApplication::translate("main", "Use the same password for both database files.")); + QCommandLineOption guiPrompt( + QStringList() << "g" + << "gui-prompt", + QCoreApplication::translate("main", "Use a GUI prompt unlocking the database.")); + parser.addOption(guiPrompt); + parser.addOption(samePasswordOption); - parser.process(app); + parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() != 2) { + QCoreApplication app(argc, argv); parser.showHelp(); return EXIT_FAILURE; } - out << "Insert the first database password\n> "; - out.flush(); + Database* db1; + Database* db2; - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line1 = inputTextStream.readLine(); - CompositeKey key1 = CompositeKey::readFromLine(line1); - - CompositeKey key2; - if (parser.isSet("same-password")) { - key2 = *key1.clone(); + if (parser.isSet("gui-prompt")) { + QApplication app(argc, argv); + db1 = UnlockDatabaseDialog::openDatabasePrompt(args.at(0)); + if (!parser.isSet("same-password")) { + db2 = UnlockDatabaseDialog::openDatabasePrompt(args.at(1)); + } else { + db2 = Database::openDatabaseFile(args.at(1), *(db1->key().clone())); + } } else { - out << "Insert the second database password\n> "; - out.flush(); - QString line2 = inputTextStream.readLine(); - key2 = CompositeKey::readFromLine(line2); + QCoreApplication app(argc, argv); + db1 = Database::unlockFromStdin(args.at(0)); + if (!parser.isSet("same-password")) { + db2 = Database::unlockFromStdin(args.at(1)); + } else { + db2 = Database::openDatabaseFile(args.at(1), *(db1->key().clone())); + } } - - Database* db1 = Database::openDatabaseFile(args.at(0), key1); if (db1 == nullptr) { return EXIT_FAILURE; } - - Database* db2 = Database::openDatabaseFile(args.at(1), key2); if (db2 == nullptr) { return EXIT_FAILURE; } diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 07bf575a8..64fc3469a 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -403,7 +403,7 @@ Database* Database::unlockFromStdin(QString databaseFilename) static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QTextStream outputTextStream(stdout); - outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n>"); + outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n> "); outputTextStream.flush(); QString line = inputTextStream.readLine(); From bfee7346699e73916edf13abe09f5c5ceb03a661 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 27 May 2017 21:33:30 -0400 Subject: [PATCH 302/333] Fixed variable naming error that prevented compiling on Windows --- src/core/ScreenLockListenerWin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/ScreenLockListenerWin.cpp b/src/core/ScreenLockListenerWin.cpp index a1bf13d4f..80fa32894 100644 --- a/src/core/ScreenLockListenerWin.cpp +++ b/src/core/ScreenLockListenerWin.cpp @@ -52,8 +52,8 @@ ScreenLockListenerWin::~ScreenLockListenerWin() HWND h= reinterpret_cast(static_cast(parent())->winId()); WTSUnRegisterSessionNotification(h); - if (m_powernotificationhandle) { - UnregisterPowerSettingNotification(reinterpret_cast(m_powernotificationhandle)); + if (m_powerNotificationHandle) { + UnregisterPowerSettingNotification(reinterpret_cast(m_powerNotificationHandle)); } } From ac5c0c5efa695dcb20a17a8367d2a497db8014b0 Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Sun, 28 May 2017 08:11:02 +0300 Subject: [PATCH 303/333] Fix about dialog report bugs link functionality --- src/gui/AboutDialog.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index a853c0413..5f20cf430 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -96,6 +96,9 @@ <p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues">https://github.com/</a></p> + + true + From 6f4b5fc71a964695ec724c46001d2488c2d121de Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Sun, 28 May 2017 10:11:11 +0300 Subject: [PATCH 304/333] :lock: Fix search information leak --- src/gui/DatabaseWidget.cpp | 1 + src/gui/MainWindow.cpp | 4 +++- src/gui/SearchWidget.h | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 946757e40..eb3a32e2b 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1064,6 +1064,7 @@ void DatabaseWidget::lock() m_entryBeforeLock = m_entryView->currentEntry()->uuid(); } + endSearch(); clearAllWidgets(); m_unlockDatabaseWidget->load(m_filename); setCurrentWidget(m_unlockDatabaseWidget); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 51d40dc57..450618719 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -219,9 +219,11 @@ MainWindow::MainWindow() m_actionMultiplexer.connect(SIGNAL(entryContextMenuRequested(QPoint)), this, SLOT(showEntryContextMenu(QPoint))); - // Notify search when the active database changes + // Notify search when the active database changes or gets locked connect(m_ui->tabWidget, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), search, SLOT(databaseChanged(DatabaseWidget*))); + connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), + search, SLOT(databaseChanged())); connect(m_ui->tabWidget, SIGNAL(tabNameChanged()), SLOT(updateWindowTitle())); diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h index e87701814..5a33b451f 100644 --- a/src/gui/SearchWidget.h +++ b/src/gui/SearchWidget.h @@ -51,7 +51,7 @@ signals: void enterPressed(); public slots: - void databaseChanged(DatabaseWidget* dbWidget); + void databaseChanged(DatabaseWidget* dbWidget = 0); private slots: void startSearchTimer(); From 398201f592e971bc248fb5b8406ef9274da1617f Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Sun, 28 May 2017 20:47:33 +0300 Subject: [PATCH 305/333] Add copy button to password generator (#595) --- src/gui/PasswordGeneratorWidget.cpp | 7 +++++++ src/gui/PasswordGeneratorWidget.h | 1 + src/gui/PasswordGeneratorWidget.ui | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index e585b6f58..64aa22da5 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -24,6 +24,7 @@ #include "core/Config.h" #include "core/PasswordGenerator.h" #include "core/FilePath.h" +#include "gui/Clipboard.h" PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) : QWidget(parent) @@ -40,6 +41,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength(QString))); connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), SLOT(togglePasswordShown(bool))); connect(m_ui->buttonApply, SIGNAL(clicked()), SLOT(applyPassword())); + connect(m_ui->buttonCopy, SIGNAL(clicked()), SLOT(copyPassword())); connect(m_ui->buttonGenerate, SIGNAL(clicked()), SLOT(regeneratePassword())); connect(m_ui->sliderLength, SIGNAL(valueChanged(int)), SLOT(passwordSliderMoved())); @@ -193,6 +195,11 @@ void PasswordGeneratorWidget::applyPassword() emit dialogTerminated(); } +void PasswordGeneratorWidget::copyPassword() +{ + clipboard()->setText(m_ui->editNewPassword->text()); +} + void PasswordGeneratorWidget::passwordSliderMoved() { if (m_updatingSpinBox) { diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h index 519f75f54..9fbcdbeb1 100644 --- a/src/gui/PasswordGeneratorWidget.h +++ b/src/gui/PasswordGeneratorWidget.h @@ -57,6 +57,7 @@ signals: private slots: void applyPassword(); + void copyPassword(); void updateApplyEnabled(const QString& password); void updatePasswordStrength(const QString& password); void togglePasswordShown(bool hidden); diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index a7af3d7f0..0b143b89e 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -612,6 +612,13 @@ QProgressBar::chunk { + + + + Copy + + + From 2b6059dee3a95591d787e8b8c931cd68c059d43f Mon Sep 17 00:00:00 2001 From: Yen Chi Hsuan Date: Fri, 2 Jun 2017 01:28:39 +0800 Subject: [PATCH 306/333] :bug: Fix building with Qt 5.9 (closes #528) --- tests/modeltest.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/modeltest.cpp b/tests/modeltest.cpp index 360a7bef1..6bf8124cf 100644 --- a/tests/modeltest.cpp +++ b/tests/modeltest.cpp @@ -448,7 +448,8 @@ void ModelTest::data() QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole ); if ( textAlignmentVariant.isValid() ) { int alignment = textAlignmentVariant.toInt(); - QCOMPARE( alignment, ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) ); + QCOMPARE( alignment, static_cast( alignment & ( Qt::AlignHorizontal_Mask + | Qt::AlignVertical_Mask ) ) ); } // General Purpose roles that should return a QColor From 3015baf6e6624914a1011956da62bcf7819d7247 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sun, 4 Jun 2017 21:06:27 +0200 Subject: [PATCH 307/333] fix password generator button's enable behavior --- src/gui/PasswordGeneratorWidget.cpp | 10 +++++++--- src/gui/PasswordGeneratorWidget.h | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 64aa22da5..8e369775b 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -37,7 +37,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); - connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updateApplyEnabled(QString))); + connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updateButtonsEnabled(QString))); connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength(QString))); connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), SLOT(togglePasswordShown(bool))); connect(m_ui->buttonApply, SIGNAL(clicked()), SLOT(applyPassword())); @@ -139,6 +139,7 @@ void PasswordGeneratorWidget::reset() void PasswordGeneratorWidget::setStandaloneMode(bool standalone) { + m_standalone = standalone; if (standalone) { m_ui->buttonApply->setText(tr("Close")); togglePasswordShown(true); @@ -164,9 +165,12 @@ void PasswordGeneratorWidget::regeneratePassword() } } -void PasswordGeneratorWidget::updateApplyEnabled(const QString& password) +void PasswordGeneratorWidget::updateButtonsEnabled(const QString& password) { - m_ui->buttonApply->setEnabled(!password.isEmpty()); + if (!m_standalone) { + m_ui->buttonApply->setEnabled(!password.isEmpty()); + } + m_ui->buttonCopy->setEnabled(!password.isEmpty()); } void PasswordGeneratorWidget::updatePasswordStrength(const QString& password) diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h index 9fbcdbeb1..b552e112b 100644 --- a/src/gui/PasswordGeneratorWidget.h +++ b/src/gui/PasswordGeneratorWidget.h @@ -58,7 +58,7 @@ signals: private slots: void applyPassword(); void copyPassword(); - void updateApplyEnabled(const QString& password); + void updateButtonsEnabled(const QString& password); void updatePasswordStrength(const QString& password); void togglePasswordShown(bool hidden); @@ -72,6 +72,7 @@ private slots: private: bool m_updatingSpinBox; + bool m_standalone = false; PasswordGenerator::CharClasses charClasses(); PasswordGenerator::GeneratorFlags generatorFlags(); From 458c76d3b70d3e90d8fd82d8694f1b9909612cd6 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 6 Jun 2017 10:35:16 -0400 Subject: [PATCH 308/333] Update release tool and snapcraft.yaml (#610) * Release tool checks snapcraft file for version and added removable-media plug * Added 'check' command to release tool. Code cleanup. --- release-tool | 177 +++++++++++++++++++++++++++++-------------------- snapcraft.yaml | 2 +- 2 files changed, 106 insertions(+), 73 deletions(-) diff --git a/release-tool b/release-tool index e5a49b05e..a08e9601b 100755 --- a/release-tool +++ b/release-tool @@ -50,19 +50,20 @@ printUsage() { local cmd if [ "" == "$1" ] || [ "help" == "$1" ]; then cmd="COMMAND" - elif [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "sign" == "$1" ]; then + elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "sign" == "$1" ]; then cmd="$1" else logError "Unknown command: '$1'\n" cmd="COMMAND" fi - printf "\e[1mUsage:\e[0m $(basename $0) $cmd [options]\n" + printf "\e[1mUsage:\e[0m $(basename $0) $cmd [--version x.y.z] [options]\n" if [ "COMMAND" == "$cmd" ]; then cat << EOF 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 @@ -134,7 +135,22 @@ logError() { } init() { + if [ "" == "$RELEASE_NAME" ]; then + logError "Missing arguments, --version is required!\n" + printUsage "check" + exit 1 + fi + + if [ "" == "$TAG_NAME" ]; then + TAG_NAME="$RELEASE_NAME" + fi + + if [ "" == "$SOURCE_BRANCH" ]; then + SOURCE_BRANCH="release/${RELEASE_NAME}" + fi + ORIG_CWD="$(pwd)" + SRC_DIR="$(realpath "$SRC_DIR")" cd "$SRC_DIR" > /dev/null 2>&1 ORIG_BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)" cd "$ORIG_CWD" @@ -214,15 +230,23 @@ checkTargetBranchExists() { checkVersionInCMake() { local app_name_upper="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')" - - grep -q "${app_name_upper}_VERSION \"${RELEASE_NAME}\"" CMakeLists.txt + local major_num="$(echo ${RELEASE_NAME} | cut -f1 -d.)" + local minor_num="$(echo ${RELEASE_NAME} | cut -f2 -d.)" + local patch_num="$(echo ${RELEASE_NAME} | cut -f3 -d.)" + + grep -q "${app_name_upper}_VERSION_MAJOR \"${major_num}\"" CMakeLists.txt if [ $? -ne 0 ]; then - exitError "${app_name_upper}_VERSION version not updated to '${RELEASE_NAME}' in CMakeLists.txt!" + exitError "${app_name_upper}_VERSION_MAJOR not updated to '${major_num}' in CMakeLists.txt!" fi - grep -q "${app_name_upper}_VERSION_NUM \"${RELEASE_NAME}\"" CMakeLists.txt + grep -q "${app_name_upper}_VERSION_MINOR \"${minor_num}\"" CMakeLists.txt if [ $? -ne 0 ]; then - exitError "${app_name_upper}_VERSION_NUM version not updated to '${RELEASE_NAME}' in CMakeLists.txt!" + exitError "${app_name_upper}_VERSION_MINOR not updated to '${minor_num}' in CMakeLists.txt!" + fi + + grep -q "${app_name_upper}_VERSION_PATCH \"${patch_num}\"" CMakeLists.txt + if [ $? -ne 0 ]; then + exitError "${app_name_upper}_VERSION_PATCH not updated to '${patch_num}' in CMakeLists.txt!" fi } @@ -242,6 +266,52 @@ checkTransifexCommandExists() { if [ 0 -ne $? ]; then exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'" fi + + command -v lupdate-qt5 > /dev/null + if [ 0 -ne $? ]; then + exitError "Qt Linguist tool (lupdate-qt5) is not installed! Please install using 'apt install qttools5-dev-tools'" + fi +} + +checkSnapcraft() { + if [ ! -f snapcraft.yaml ]; then + echo "No snapcraft file found!" + return + fi + + grep -qPzo "version: ${RELEASE_NAME}" snapcraft.yaml + if [ $? -ne 0 ]; then + exitError "snapcraft.yaml has not been updated to the '${RELEASE_NAME}' release!" + fi +} + +performChecks() { + logInfo "Performing basic checks..." + + checkSourceDirExists + + logInfo "Changing to source directory..." + cd "${SRC_DIR}" + + logInfo "Validating toolset and repository..." + + checkTransifexCommandExists + checkGitRepository + checkReleaseDoesNotExist + checkWorkingTreeClean + checkSourceBranchExists + checkTargetBranchExists + + logInfo "Checking out '${SOURCE_BRANCH}'..." + git checkout "$SOURCE_BRANCH" + + logInfo "Attempting to find '${RELEASE_NAME}' in various files..." + + checkVersionInCMake + checkChangeLog + checkSnapcraft + + logInfo "\e[1m\e[32mAll checks passed!\e[0m" } # re-implement realpath for OS X (thanks mschrag) @@ -269,6 +339,28 @@ fi trap exitTrap SIGINT SIGTERM +# ----------------------------------------------------------------------- +# check command +# ----------------------------------------------------------------------- +check() { + while [ $# -ge 1 ]; do + local arg="$1" + case "$arg" in + -v|--version) + RELEASE_NAME="$2" + shift ;; + esac + shift + done + + init + + performChecks + + cleanup + + logInfo "Congrats! You can successfully merge, build, and sign KeepassXC." +} # ----------------------------------------------------------------------- # merge command @@ -317,45 +409,9 @@ merge() { shift done - if [ "" == "$RELEASE_NAME" ]; then - logError "Missing arguments, --version is required!\n" - printUsage "merge" - exit 1 - fi - - if [ "" == "$TAG_NAME" ]; then - TAG_NAME="$RELEASE_NAME" - fi - - if [ "" == "$SOURCE_BRANCH" ]; then - SOURCE_BRANCH="release/${RELEASE_NAME}" - fi - init - SRC_DIR="$(realpath "$SRC_DIR")" - - logInfo "Performing basic checks..." - - checkSourceDirExists - - logInfo "Changing to source directory..." - cd "${SRC_DIR}" - - checkTransifexCommandExists - checkGitRepository - checkReleaseDoesNotExist - checkWorkingTreeClean - checkSourceBranchExists - checkTargetBranchExists - - logInfo "Checking out source branch '${SOURCE_BRANCH}'..." - git checkout "$SOURCE_BRANCH" - - checkVersionInCMake - checkChangeLog - - logInfo "All checks pass, getting our hands dirty now!" + performChecks logInfo "Updating language files..." ./share/translations/update.sh @@ -467,36 +523,13 @@ build() { esac shift done - - if [ "" == "$RELEASE_NAME" ]; then - logError "Missing arguments, --version is required!\n" - printUsage "build" - exit 1 - fi - - if [ "" == "$TAG_NAME" ]; then - TAG_NAME="$RELEASE_NAME" - fi - - init - SRC_DIR="$(realpath "$SRC_DIR")" + init + + performChecks + OUTPUT_DIR="$(realpath "$OUTPUT_DIR")" - logInfo "Performing basic checks..." - - checkSourceDirExists - - logInfo "Changing to source directory..." - cd "${SRC_DIR}" - - checkTagExists - checkGitRepository - checkWorkingTreeClean - checkOutputDirDoesNotExist - - logInfo "All checks pass, getting our hands dirty now!" - logInfo "Checking out release tag '${TAG_NAME}'..." git checkout "$TAG_NAME" @@ -678,7 +711,7 @@ if [ "" == "$MODE" ]; then elif [ "help" == "$MODE" ]; then printUsage "$1" exit -elif [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "sign" == "$MODE" ]; then +elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "sign" == "$MODE" ]; then $MODE "$@" else printUsage "$MODE" diff --git a/snapcraft.yaml b/snapcraft.yaml index 532f12fa9..aa5ab38fb 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -11,7 +11,7 @@ confinement: strict apps: keepassxc: command: desktop-launch keepassxc - plugs: [unity7, x11, opengl, gsettings, home, network, network-bind] + plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media] parts: keepassxc: From 08930ddffb85b86505018eb88ac11e46051fc7fa Mon Sep 17 00:00:00 2001 From: Weslly Date: Sat, 10 Jun 2017 10:35:17 -0300 Subject: [PATCH 309/333] Fix macOS file dialog localization --- share/macosx/Info.plist.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/share/macosx/Info.plist.cmake b/share/macosx/Info.plist.cmake index db5b8501e..ea1a9bc2c 100644 --- a/share/macosx/Info.plist.cmake +++ b/share/macosx/Info.plist.cmake @@ -4,6 +4,8 @@ NSPrincipalClass NSApplication + CFBundleAllowMixedLocalizations + CFBundleDevelopmentRegion English CFBundleDisplayName @@ -27,7 +29,7 @@ CFBundleVersion ${KEEPASSXC_VERSION_NUM} NSHumanReadableCopyright - Copyright 2016 KeePassXC Development Team + Copyright 2016-2017 KeePassXC Development Team CFBundleDocumentTypes From 6ffca842e636eb02c1007a8bbf9d49c00f50f8eb Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 13 Jun 2017 20:55:53 -0400 Subject: [PATCH 310/333] Add "Apply" button to entry and group edit windows (#624) --- src/gui/EditWidget.cpp | 6 +++++- src/gui/EditWidget.h | 1 + src/gui/EditWidget.ui | 2 +- src/gui/entry/EditEntryWidget.cpp | 8 ++++++-- src/gui/entry/EditEntryWidget.h | 1 + src/gui/group/EditGroupWidget.cpp | 11 ++++++++--- src/gui/group/EditGroupWidget.h | 1 + tests/gui/TestGui.cpp | 14 ++++++++++---- 8 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index a0144c8cb..e2c157edf 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -18,6 +18,7 @@ #include "EditWidget.h" #include "ui_EditWidget.h" #include +#include #include "core/FilePath.h" @@ -102,7 +103,10 @@ void EditWidget::setReadOnly(bool readOnly) m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Close); } else { - m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply); + // Find and connect the apply button + QPushButton* applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply); + connect(applyButton, SIGNAL(clicked()), SIGNAL(apply())); } } diff --git a/src/gui/EditWidget.h b/src/gui/EditWidget.h index 46951de41..4ea376215 100644 --- a/src/gui/EditWidget.h +++ b/src/gui/EditWidget.h @@ -48,6 +48,7 @@ public: bool readOnly() const; signals: + void apply(); void accepted(); void rejected(); diff --git a/src/gui/EditWidget.ui b/src/gui/EditWidget.ui index 6afce9f3e..b8ac5f3eb 100644 --- a/src/gui/EditWidget.ui +++ b/src/gui/EditWidget.ui @@ -66,7 +66,7 @@ - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 4581c91f0..c81214e45 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -75,8 +75,9 @@ EditEntryWidget::EditEntryWidget(QWidget* parent) setupProperties(); setupHistory(); - connect(this, SIGNAL(accepted()), SLOT(saveEntry())); + connect(this, SIGNAL(accepted()), SLOT(acceptEntry())); connect(this, SIGNAL(rejected()), SLOT(cancel())); + connect(this, SIGNAL(apply()), SLOT(saveEntry())); connect(m_iconsWidget, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType))); connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage())); @@ -439,9 +440,12 @@ void EditEntryWidget::saveEntry() if (!m_create) { m_entry->endUpdate(); } +} +void EditEntryWidget::acceptEntry() +{ + saveEntry(); clear(); - emit editFinished(true); } diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h index 4027dd11a..9277997e8 100644 --- a/src/gui/entry/EditEntryWidget.h +++ b/src/gui/entry/EditEntryWidget.h @@ -68,6 +68,7 @@ signals: void historyEntryActivated(Entry* entry); private slots: + void acceptEntry(); void saveEntry(); void cancel(); void togglePasswordGeneratorButton(bool checked); diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp index 4f2e9fec5..da9875fb4 100644 --- a/src/gui/group/EditGroupWidget.cpp +++ b/src/gui/group/EditGroupWidget.cpp @@ -42,6 +42,7 @@ EditGroupWidget::EditGroupWidget(QWidget* parent) connect(m_mainUi->autoTypeSequenceCustomRadio, SIGNAL(toggled(bool)), m_mainUi->autoTypeSequenceCustomEdit, SLOT(setEnabled(bool))); + connect(this, SIGNAL(apply()), SLOT(apply())); connect(this, SIGNAL(accepted()), SLOT(save())); connect(this, SIGNAL(rejected()), SLOT(cancel())); @@ -101,6 +102,13 @@ void EditGroupWidget::loadGroup(Group* group, bool create, Database* database) } void EditGroupWidget::save() +{ + apply(); + clear(); + emit editFinished(true); +} + +void EditGroupWidget::apply() { m_group->setName(m_mainUi->editName->text()); m_group->setNotes(m_mainUi->editNotes->toPlainText()); @@ -128,9 +136,6 @@ void EditGroupWidget::save() else { m_group->setIcon(iconStruct.uuid); } - - clear(); - emit editFinished(true); } void EditGroupWidget::cancel() diff --git a/src/gui/group/EditGroupWidget.h b/src/gui/group/EditGroupWidget.h index 39f2c09b0..2d1844934 100644 --- a/src/gui/group/EditGroupWidget.h +++ b/src/gui/group/EditGroupWidget.h @@ -49,6 +49,7 @@ signals: void messageEditEntryDismiss(); private slots: + void apply(); void save(); void cancel(); diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index b1cc02462..aed557a5e 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -230,6 +230,7 @@ void TestGui::testEditEntry() // Select the first entry in the database EntryView* entryView = m_dbWidget->findChild("entryView"); QModelIndex entryItem = entryView->model()->index(0, 1); + Entry* entry = entryView->entryFromIndex(entryItem); clickIndex(entryItem, entryView, Qt::LeftButton); // Confirm the edit action button is enabled @@ -246,6 +247,13 @@ void TestGui::testEditEntry() QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "_test"); + // Apply the edit + QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); + QCOMPARE(entry->title(), QString("Sample Entry_test")); + QCOMPARE(entry->historyItems().size(), 1); + // Test protected attributes editEntryWidget->setCurrentPage(1); QPlainTextEdit* attrTextEdit = editEntryWidget->findChild("attributesEdit"); @@ -259,15 +267,13 @@ void TestGui::testEditEntry() QCOMPARE(attrTextEdit->toPlainText(), attrText); editEntryWidget->setCurrentPage(0); - // Save the edit - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + // Save the edit (press OK) QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); // Confirm edit was made QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); - Entry* entry = entryView->entryFromIndex(entryItem); QCOMPARE(entry->title(), QString("Sample Entry_test")); - QCOMPARE(entry->historyItems().size(), 1); + QCOMPARE(entry->historyItems().size(), 2); // Confirm modified indicator is showing QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("%1*").arg(m_dbFileName)); From a53b111182a427824dcd7721bbecbe43b7bb676f Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 9 Jun 2017 23:40:36 +0200 Subject: [PATCH 311/333] Update and fix copyright headers --- CMakeLists.txt | 1 + COPYING | 49 ++++++++++++++++++++---- cmake/FindLibGPGError.cmake | 14 +++++++ share/CMakeLists.txt | 1 + src/CMakeLists.txt | 1 + src/autotype/AutoType.cpp | 1 + src/autotype/AutoType.h | 1 + src/autotype/mac/AppKit.h | 1 + src/autotype/mac/AppKitImpl.h | 1 + src/autotype/mac/AppKitImpl.mm | 1 + src/autotype/mac/AutoTypeMac.cpp | 1 + src/autotype/mac/AutoTypeMac.h | 1 + src/autotype/windows/AutoTypeWindows.cpp | 1 + src/autotype/windows/AutoTypeWindows.h | 1 + src/autotype/xcb/AutoTypeXCB.cpp | 1 + src/autotype/xcb/AutoTypeXCB.h | 1 + src/cli/Clip.cpp | 2 +- src/cli/Clip.h | 2 +- src/cli/EntropyMeter.cpp | 21 +++++++--- src/cli/EntropyMeter.h | 2 +- src/cli/Extract.cpp | 2 +- src/cli/Extract.h | 2 +- src/cli/List.cpp | 2 +- src/cli/List.h | 2 +- src/cli/Merge.cpp | 2 +- src/cli/Merge.h | 2 +- src/cli/Show.cpp | 2 +- src/cli/Show.h | 2 +- src/cli/keepassxc-cli.cpp | 2 +- src/core/Config.cpp | 1 + src/core/Config.h | 1 + src/core/CsvParser.cpp | 1 + src/core/CsvParser.h | 1 + src/core/Database.cpp | 1 + src/core/Database.h | 1 + src/core/Entry.cpp | 1 + src/core/Entry.h | 1 + src/core/EntryAttributes.cpp | 1 + src/core/EntryAttributes.h | 1 + src/core/EntrySearcher.cpp | 1 + src/core/EntrySearcher.h | 1 + src/core/Group.cpp | 1 + src/core/Group.h | 1 + src/core/PassphraseGenerator.cpp | 2 +- src/core/PassphraseGenerator.h | 2 +- src/core/PasswordGenerator.cpp | 1 + src/core/PasswordGenerator.h | 1 + src/core/Tools.cpp | 1 + src/core/Tools.h | 1 + src/format/KeePass2Reader.cpp | 2 +- src/gui/AboutDialog.cpp | 1 + src/gui/AboutDialog.h | 1 + src/gui/Application.cpp | 1 + src/gui/Application.h | 1 + src/gui/ChangeMasterKeyWidget.cpp | 1 + src/gui/ChangeMasterKeyWidget.h | 1 + src/gui/CloneDialog.cpp | 2 +- src/gui/CloneDialog.h | 2 +- src/gui/DatabaseOpenWidget.cpp | 1 + src/gui/DatabaseOpenWidget.h | 1 + src/gui/DatabaseTabWidget.cpp | 1 + src/gui/DatabaseTabWidget.h | 1 + src/gui/DatabaseWidget.cpp | 3 +- src/gui/DatabaseWidget.h | 3 +- src/gui/EditWidget.cpp | 1 + src/gui/EditWidget.h | 1 + src/gui/EditWidgetIcons.cpp | 1 + src/gui/EditWidgetIcons.h | 1 + src/gui/MainWindow.cpp | 1 + src/gui/MainWindow.h | 1 + src/gui/MessageWidget.cpp | 1 + src/gui/MessageWidget.h | 1 + src/gui/PasswordEdit.cpp | 1 + src/gui/PasswordEdit.h | 1 + src/gui/PasswordGeneratorWidget.cpp | 1 + src/gui/PasswordGeneratorWidget.h | 1 + src/gui/SearchWidget.cpp | 1 + src/gui/SearchWidget.h | 1 + src/gui/SettingsWidget.cpp | 1 + src/gui/SettingsWidget.h | 1 + src/gui/SetupTotpDialog.cpp | 1 + src/gui/SetupTotpDialog.h | 1 + src/gui/TotpDialog.cpp | 1 + src/gui/TotpDialog.h | 1 + src/gui/UnlockDatabaseDialog.cpp | 2 +- src/gui/UnlockDatabaseDialog.h | 2 +- src/gui/WelcomeWidget.cpp | 1 + src/gui/WelcomeWidget.h | 1 + src/gui/csvImport/CsvImportWidget.cpp | 1 + src/gui/csvImport/CsvImportWidget.h | 1 + src/gui/csvImport/CsvImportWizard.cpp | 1 + src/gui/csvImport/CsvImportWizard.h | 1 + src/gui/csvImport/CsvParserModel.cpp | 1 + src/gui/csvImport/CsvParserModel.h | 1 + src/gui/entry/EditEntryWidget.cpp | 1 + src/gui/entry/EditEntryWidget.h | 1 + src/http/AccessControlDialog.cpp | 29 ++++++++------ src/http/AccessControlDialog.h | 29 ++++++++------ src/http/EntryConfig.cpp | 29 ++++++++------ src/http/EntryConfig.h | 29 ++++++++------ src/http/HttpPasswordGeneratorWidget.cpp | 31 +++++++-------- src/http/HttpPasswordGeneratorWidget.h | 31 +++++++-------- src/http/HttpSettings.cpp | 29 ++++++++------ src/http/HttpSettings.h | 29 ++++++++------ src/http/OptionDialog.cpp | 29 ++++++++------ src/http/OptionDialog.h | 29 ++++++++------ src/http/Protocol.cpp | 29 ++++++++------ src/http/Protocol.h | 29 ++++++++------ src/http/Server.cpp | 29 ++++++++------ src/http/Server.h | 29 ++++++++------ src/http/Service.cpp | 29 ++++++++------ src/http/Service.h | 29 ++++++++------ src/keys/ChallengeResponseKey.h | 1 + src/keys/CompositeKey.cpp | 1 + src/keys/CompositeKey.h | 1 + src/keys/YkChallengeResponseKey.cpp | 1 + src/keys/YkChallengeResponseKey.h | 2 +- src/keys/drivers/YubiKey.cpp | 1 + src/keys/drivers/YubiKey.h | 1 + src/keys/drivers/YubiKeyStub.cpp | 1 + src/main.cpp | 1 + src/totp/totp.cpp | 1 + src/totp/totp.h | 1 + tests/TestAutoType.cpp | 1 + tests/TestAutoType.h | 1 + tests/TestCsvParser.cpp | 1 + tests/TestCsvParser.h | 1 + tests/TestDatabase.cpp | 1 + tests/TestDatabase.h | 1 + tests/TestGroup.cpp | 1 + tests/TestGroup.h | 1 + tests/TestKeys.cpp | 1 + tests/TestKeys.h | 1 + tests/TestSymmetricCipher.cpp | 1 + tests/TestSymmetricCipher.h | 1 + tests/TestTotp.cpp | 1 + tests/TestTotp.h | 1 + tests/TestYkChallengeResponseKey.cpp | 1 + tests/TestYkChallengeResponseKey.h | 1 + tests/gui/TemporaryFile.cpp | 1 + tests/gui/TemporaryFile.h | 1 + tests/gui/TestGui.cpp | 1 + tests/gui/TestGui.h | 1 + 143 files changed, 466 insertions(+), 234 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 881e0bdba..2f3677d68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright (C) 2010 Felix Geyer +# Copyright (C) 2017 KeePassXC Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/COPYING b/COPYING index d36384011..cffc57564 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ -KeePassX - http://www.keepassx.org/ -Copyright (C) 2010-2012 Felix Geyer +KeePassXC - http://www.keepassxc.org/ +Copyright (C) 2016-2017 KeePassXC Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,9 +14,9 @@ GNU General Public License for more details. -------------------------------------------------------------------- Format-Specification: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: KeePassX -Upstream-Contact: Felix Geyer -Source: http://www.keepassx.org/ +Upstream-Name: KeePassXC +Upstream-Contact: KeePassXC Team +Source: http://www.keepassxc.org/ Copyright: 2010-2012, Felix Geyer 2011-2012, Florian Geyer @@ -27,14 +27,38 @@ Copyright: 2010-2012, Felix Geyer 2000-2008, Tom Sato 2013, Laszlo Papp 2013, David Faure - 2016, KeePassXC Team + 2016-2017, KeePassXC Team License: GPL-2 or GPL-3 +Comment: The "KeePassXC Team" in every copyright notice is formed by the following people: + - droidmonkey + - phoerious + - TheZ3ro + - louib + - weslly + Every other contributor is listed on https://github.com/keepassxreboot/keepassxc/graphs/contributors + Files: cmake/GNUInstallDirs.cmake Copyright: 2011 Nikita Krupen'ko 2011 Kitware, Inc. License: BSD-3-clause +Files: cmake/CodeCoverage.cmake +Copyright: 2012 - 2015, Lars Bilke +License: BSD-3-clause + +Files: cmake/FindYubiKey.cmake +Copyright: 2014 Kyle Manna +License: GPL-2 or GPL-3 + +Files: cmake/GenerateProductVersion.cmake +Copyright: 2015 halex2005 +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 @@ -138,18 +162,25 @@ Files: share/icons/application/*/actions/application-exit.png share/icons/application/*/actions/document-encrypt.png share/icons/application/*/actions/document-new.png share/icons/application/*/actions/document-open.png + share/icons/application/*/actions/document-properties.png share/icons/application/*/actions/document-save.png share/icons/application/*/actions/document-save-as.png share/icons/application/*/actions/edit-clear-locationbar-ltr.png share/icons/application/*/actions/edit-clear-locationbar-rtl.png + share/icons/application/*/actions/key-enter.png share/icons/application/*/actions/password-generator.png share/icons/application/*/actions/password-copy.png share/icons/application/*/actions/password-show-*.png share/icons/application/*/actions/system-search.png share/icons/application/*/actions/username-copy.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/*/categories/preferences-other.png share/icons/application/*/status/dialog-error.png share/icons/application/*/status/dialog-information.png share/icons/application/*/status/dialog-warning.png + share/icons/application/*/status/security-high.png share/icons/svg/*.svgz Copyright: 2007, Nuno Pinheiro 2007, David Vignoni @@ -196,11 +227,13 @@ Copyright: 2009-2010, Iowa State University License: Boost-1.0 Files: src/zxcvbn/zxcvbn.* - src/utils/entropy-meter.cpp Copyright: 2015, Tony Evans - 2016, KeePassXC Team License: BSD 3-clause +Files: src/http/qhttp/* +Copyright: 2014, Amir Zamani +License: MIT + Files: src/gui/KMessageWidget.h src/gui/KMessageWidget.cpp Copyright: 2011 Aurélien Gâteau diff --git a/cmake/FindLibGPGError.cmake b/cmake/FindLibGPGError.cmake index fe9ef9123..c1e1b8686 100644 --- a/cmake/FindLibGPGError.cmake +++ b/cmake/FindLibGPGError.cmake @@ -1,3 +1,17 @@ +# Copyright (C) 2017 KeePassXC Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or (at your option) +# version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . find_path(GPGERROR_INCLUDE_DIR gpg-error.h) diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index 7d01a1c40..a609add79 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright (C) 2011 Felix Geyer +# Copyright (C) 2017 KeePassXC Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1c890e16f..5d01721b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright (C) 2010 Felix Geyer +# Copyright (C) 2017 KeePassXC Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 6a066d4c4..927d6822b 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index ea5c95610..6f4a815f8 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/mac/AppKit.h b/src/autotype/mac/AppKit.h index 114038d63..f1eced5bb 100644 --- a/src/autotype/mac/AppKit.h +++ b/src/autotype/mac/AppKit.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Lennart Glauer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/mac/AppKitImpl.h b/src/autotype/mac/AppKitImpl.h index 1b57d7b14..f370096fc 100644 --- a/src/autotype/mac/AppKitImpl.h +++ b/src/autotype/mac/AppKitImpl.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Lennart Glauer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/mac/AppKitImpl.mm b/src/autotype/mac/AppKitImpl.mm index ad600cb95..457044389 100644 --- a/src/autotype/mac/AppKitImpl.mm +++ b/src/autotype/mac/AppKitImpl.mm @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Lennart Glauer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index b0e1360f3..7056c7310 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Lennart Glauer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/mac/AutoTypeMac.h b/src/autotype/mac/AutoTypeMac.h index 82f8b0eb6..c554fa6e4 100644 --- a/src/autotype/mac/AutoTypeMac.h +++ b/src/autotype/mac/AutoTypeMac.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Lennart Glauer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/windows/AutoTypeWindows.cpp b/src/autotype/windows/AutoTypeWindows.cpp index 81baeefac..2dfc7a269 100644 --- a/src/autotype/windows/AutoTypeWindows.cpp +++ b/src/autotype/windows/AutoTypeWindows.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Lennart Glauer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/windows/AutoTypeWindows.h b/src/autotype/windows/AutoTypeWindows.h index 6ffae4d1e..88b9a9fd2 100644 --- a/src/autotype/windows/AutoTypeWindows.h +++ b/src/autotype/windows/AutoTypeWindows.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Lennart Glauer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/xcb/AutoTypeXCB.cpp b/src/autotype/xcb/AutoTypeXCB.cpp index e15396122..436cd5b59 100644 --- a/src/autotype/xcb/AutoTypeXCB.cpp +++ b/src/autotype/xcb/AutoTypeXCB.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2012 Felix Geyer * Copyright (C) 2000-2008 Tom Sato + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/autotype/xcb/AutoTypeXCB.h b/src/autotype/xcb/AutoTypeXCB.h index 186d0eb8b..34e539cf9 100644 --- a/src/autotype/xcb/AutoTypeXCB.h +++ b/src/autotype/xcb/AutoTypeXCB.h @@ -1,6 +1,7 @@ /* * Copyright (C) 2012 Felix Geyer * Copyright (C) 2000-2008 Tom Sato + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index f587b3f10..106fed6cd 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/Clip.h b/src/cli/Clip.h index 944184095..cb72e4299 100644 --- a/src/cli/Clip.h +++ b/src/cli/Clip.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/EntropyMeter.cpp b/src/cli/EntropyMeter.cpp index ffaecc8e6..a62cd3077 100644 --- a/src/cli/EntropyMeter.cpp +++ b/src/cli/EntropyMeter.cpp @@ -1,10 +1,19 @@ /* -Part of this code come from zxcvbn-c example. -Copyright (c) 2015, Tony Evans -Copyright (c) 2016, KeePassXC Team - -See zxcvbn/zxcvbn.cpp for complete COPYRIGHT Notice -*/ + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "EntropyMeter.h" diff --git a/src/cli/EntropyMeter.h b/src/cli/EntropyMeter.h index 5034b9660..d160115bf 100644 --- a/src/cli/EntropyMeter.h +++ b/src/cli/EntropyMeter.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 74fa33da7..0c8a39602 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/Extract.h b/src/cli/Extract.h index 9a6638e4b..e1b0672ec 100644 --- a/src/cli/Extract.h +++ b/src/cli/Extract.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 8dd250b34..685ef8ec1 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/List.h b/src/cli/List.h index 76f086c63..18528bd78 100644 --- a/src/cli/List.h +++ b/src/cli/List.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index ca2c71013..738f0ee43 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/Merge.h b/src/cli/Merge.h index dd9b8a4c0..95da7af81 100644 --- a/src/cli/Merge.h +++ b/src/cli/Merge.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index 59c0219a2..b9d6bed0f 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/Show.h b/src/cli/Show.h index aa06b5c9a..630b18f80 100644 --- a/src/cli/Show.h +++ b/src/cli/Show.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index cc09f8a98..0d261eb47 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Config.cpp b/src/core/Config.cpp index f0a369c30..c8d23b361 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Config.h b/src/core/Config.h index 1fb937cf9..2ee3f4dce 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/CsvParser.cpp b/src/core/CsvParser.cpp index 7f0443aac..70493804e 100644 --- a/src/core/CsvParser.cpp +++ b/src/core/CsvParser.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/CsvParser.h b/src/core/CsvParser.h index 48be05584..3ab31bf67 100644 --- a/src/core/CsvParser.h +++ b/src/core/CsvParser.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 64fc3469a..0572f5d72 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Database.h b/src/core/Database.h index 37745e840..8e62e5357 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 418e2a81e..a8cc6d3b7 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Entry.h b/src/core/Entry.h index cdb826eca..91a0012a1 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp index c689f8ad6..a5143aa04 100644 --- a/src/core/EntryAttributes.cpp +++ b/src/core/EntryAttributes.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h index 58f1db61d..40fc5dec4 100644 --- a/src/core/EntryAttributes.h +++ b/src/core/EntryAttributes.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp index df05711ac..deaefa1aa 100644 --- a/src/core/EntrySearcher.cpp +++ b/src/core/EntrySearcher.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Florian Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/EntrySearcher.h b/src/core/EntrySearcher.h index 4e8d4eabe..da51eebc7 100644 --- a/src/core/EntrySearcher.h +++ b/src/core/EntrySearcher.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Florian Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 0c83fa303..0bebfcb0d 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Group.h b/src/core/Group.h index 9b1f0209f..7c76e412d 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp index ba403389d..1af614795 100644 --- a/src/core/PassphraseGenerator.cpp +++ b/src/core/PassphraseGenerator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/PassphraseGenerator.h b/src/core/PassphraseGenerator.h index e814a5873..3be2d5836 100644 --- a/src/core/PassphraseGenerator.h +++ b/src/core/PassphraseGenerator.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index 0fa5198fc..cdff204a0 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2013 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index 8ec82c0a0..5a5c7a3f6 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2013 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index a1bfcb0c0..b9e4be8e0 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2012 Felix Geyer * Copyright (C) 2017 Lennart Glauer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/Tools.h b/src/core/Tools.h index ba55054a8..b6fa49c02 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index ffe4e94fc..b0d780724 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -198,7 +198,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256); if (headerHash != xmlReader.headerHash()) { raiseError("Header doesn't match hash"); - return Q_NULLPTR; + return nullptr; } } diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index 636b284f9..6204e5c8c 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/AboutDialog.h b/src/gui/AboutDialog.h index b69a14dbb..9d0c1c355 100644 --- a/src/gui/AboutDialog.h +++ b/src/gui/AboutDialog.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 1ae94ac87..7c369cf1c 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2012 Tobias Tangemann * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/Application.h b/src/gui/Application.h index 1a1b0ee86..5cb10e759 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -1,6 +1,7 @@ /* * Copyright (C) 2012 Tobias Tangemann * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index 616b0ee01..ef4b61ef2 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/ChangeMasterKeyWidget.h b/src/gui/ChangeMasterKeyWidget.h index b3e097276..2825d8d55 100644 --- a/src/gui/ChangeMasterKeyWidget.h +++ b/src/gui/ChangeMasterKeyWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/CloneDialog.cpp b/src/gui/CloneDialog.cpp index b6ff30bd7..5985e26a8 100644 --- a/src/gui/CloneDialog.cpp +++ b/src/gui/CloneDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/CloneDialog.h b/src/gui/CloneDialog.h index 094b0fe7d..a925bb47a 100644 --- a/src/gui/CloneDialog.h +++ b/src/gui/CloneDialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index f7d432479..18b4b2b62 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h index caba70a61..49d3fb83e 100644 --- a/src/gui/DatabaseOpenWidget.h +++ b/src/gui/DatabaseOpenWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 2c14ace16..038174617 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index ea8f60030..85b3f3af6 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index eb3a32e2b..b8e4f0535 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1,5 +1,6 @@ -/* +/* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index aa1c83443..73bc21224 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -1,5 +1,6 @@ -/* +/* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index e2c157edf..cf3568d11 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/EditWidget.h b/src/gui/EditWidget.h index 4ea376215..442365b96 100644 --- a/src/gui/EditWidget.h +++ b/src/gui/EditWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 7b46728c1..a68bda05e 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index 745914bca..3cc191d73 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 450618719..7027d94c2 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index fffc634a9..caf3f5854 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/MessageWidget.cpp b/src/gui/MessageWidget.cpp index 9360a6e62..de981b92a 100644 --- a/src/gui/MessageWidget.cpp +++ b/src/gui/MessageWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Pedro Alves + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/MessageWidget.h b/src/gui/MessageWidget.h index a6c9425dc..03ebee3eb 100644 --- a/src/gui/MessageWidget.h +++ b/src/gui/MessageWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Pedro Alves + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/PasswordEdit.cpp b/src/gui/PasswordEdit.cpp index 095a4e14f..54b0ca288 100644 --- a/src/gui/PasswordEdit.cpp +++ b/src/gui/PasswordEdit.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/PasswordEdit.h b/src/gui/PasswordEdit.h index d527432d5..d5439f1a0 100644 --- a/src/gui/PasswordEdit.h +++ b/src/gui/PasswordEdit.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 8e369775b..3753071d1 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2013 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h index b552e112b..130106461 100644 --- a/src/gui/PasswordGeneratorWidget.h +++ b/src/gui/PasswordGeneratorWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2013 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index 2142a96fe..3e987df99 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Jonathan White + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h index 5a33b451f..9f0e0d11c 100644 --- a/src/gui/SearchWidget.h +++ b/src/gui/SearchWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Jonathan White + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index d903c2def..e8fe9fcb9 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/SettingsWidget.h b/src/gui/SettingsWidget.h index 7037b2e37..f2fc9f2db 100644 --- a/src/gui/SettingsWidget.h +++ b/src/gui/SettingsWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/SetupTotpDialog.cpp b/src/gui/SetupTotpDialog.cpp index b088e8217..5521773bd 100644 --- a/src/gui/SetupTotpDialog.cpp +++ b/src/gui/SetupTotpDialog.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/SetupTotpDialog.h b/src/gui/SetupTotpDialog.h index 416e19a5c..243a05f9f 100644 --- a/src/gui/SetupTotpDialog.h +++ b/src/gui/SetupTotpDialog.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/TotpDialog.cpp b/src/gui/TotpDialog.cpp index 058210870..17cc1120f 100644 --- a/src/gui/TotpDialog.cpp +++ b/src/gui/TotpDialog.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/TotpDialog.h b/src/gui/TotpDialog.h index 66754dd29..33eac6658 100644 --- a/src/gui/TotpDialog.h +++ b/src/gui/TotpDialog.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/UnlockDatabaseDialog.cpp b/src/gui/UnlockDatabaseDialog.cpp index c3c62c5a8..3aca54cf2 100644 --- a/src/gui/UnlockDatabaseDialog.cpp +++ b/src/gui/UnlockDatabaseDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 KeePassXC Team + * Copyright (C) 2016 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/UnlockDatabaseDialog.h b/src/gui/UnlockDatabaseDialog.h index 732395eff..55830c97e 100644 --- a/src/gui/UnlockDatabaseDialog.h +++ b/src/gui/UnlockDatabaseDialog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 KeePassXC Team + * Copyright (C) 2016 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/WelcomeWidget.cpp b/src/gui/WelcomeWidget.cpp index 96bf0a206..9dc23d528 100644 --- a/src/gui/WelcomeWidget.cpp +++ b/src/gui/WelcomeWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/WelcomeWidget.h b/src/gui/WelcomeWidget.h index 9f8d5d70d..71ceda354 100644 --- a/src/gui/WelcomeWidget.h +++ b/src/gui/WelcomeWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 93cba84c0..1e49d283e 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index 9215fd368..d006b44e2 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/csvImport/CsvImportWizard.cpp b/src/gui/csvImport/CsvImportWizard.cpp index a1e1757bb..06ee23110 100644 --- a/src/gui/csvImport/CsvImportWizard.cpp +++ b/src/gui/csvImport/CsvImportWizard.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/csvImport/CsvImportWizard.h b/src/gui/csvImport/CsvImportWizard.h index 1c99259cd..317018d99 100644 --- a/src/gui/csvImport/CsvImportWizard.h +++ b/src/gui/csvImport/CsvImportWizard.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/csvImport/CsvParserModel.cpp b/src/gui/csvImport/CsvParserModel.cpp index efffda552..d4af2b785 100644 --- a/src/gui/csvImport/CsvParserModel.cpp +++ b/src/gui/csvImport/CsvParserModel.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/csvImport/CsvParserModel.h b/src/gui/csvImport/CsvParserModel.h index 8cab4d4ff..b092092ba 100644 --- a/src/gui/csvImport/CsvParserModel.h +++ b/src/gui/csvImport/CsvParserModel.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index c81214e45..aea0ac888 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h index 9277997e8..2888d43a8 100644 --- a/src/gui/entry/EditEntryWidget.h +++ b/src/gui/entry/EditEntryWidget.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/http/AccessControlDialog.cpp b/src/http/AccessControlDialog.cpp index 171a8f72d..ef02215a3 100644 --- a/src/http/AccessControlDialog.cpp +++ b/src/http/AccessControlDialog.cpp @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file AccessControlDialog.cpp - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #include "AccessControlDialog.h" #include "ui_AccessControlDialog.h" diff --git a/src/http/AccessControlDialog.h b/src/http/AccessControlDialog.h index 4ecef986d..76392eff1 100644 --- a/src/http/AccessControlDialog.h +++ b/src/http/AccessControlDialog.h @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file AccessControlDialog.h - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #ifndef ACCESSCONTROLDIALOG_H #define ACCESSCONTROLDIALOG_H diff --git a/src/http/EntryConfig.cpp b/src/http/EntryConfig.cpp index 3a7c17eac..309afafac 100644 --- a/src/http/EntryConfig.cpp +++ b/src/http/EntryConfig.cpp @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file EntryConfig.cpp - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #include "EntryConfig.h" #include diff --git a/src/http/EntryConfig.h b/src/http/EntryConfig.h index 40633162f..d5e9876ee 100644 --- a/src/http/EntryConfig.h +++ b/src/http/EntryConfig.h @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file EntryConfig.h - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #ifndef ENTRYCONFIG_H #define ENTRYCONFIG_H diff --git a/src/http/HttpPasswordGeneratorWidget.cpp b/src/http/HttpPasswordGeneratorWidget.cpp index 031ff30ad..55e5b08fc 100644 --- a/src/http/HttpPasswordGeneratorWidget.cpp +++ b/src/http/HttpPasswordGeneratorWidget.cpp @@ -1,19 +1,20 @@ /* - * Copyright (C) 2013 Felix Geyer - * - * 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 . - */ +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #include "HttpPasswordGeneratorWidget.h" #include "ui_HttpPasswordGeneratorWidget.h" diff --git a/src/http/HttpPasswordGeneratorWidget.h b/src/http/HttpPasswordGeneratorWidget.h index f9907600b..8ef6091b6 100644 --- a/src/http/HttpPasswordGeneratorWidget.h +++ b/src/http/HttpPasswordGeneratorWidget.h @@ -1,19 +1,20 @@ /* - * Copyright (C) 2013 Felix Geyer - * - * 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 . - */ +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #ifndef KEEPASSX_HTTPPASSWORDGENERATORWIDGET_H #define KEEPASSX_HTTPPASSWORDGENERATORWIDGET_H diff --git a/src/http/HttpSettings.cpp b/src/http/HttpSettings.cpp index e51f87cfb..60a35940c 100644 --- a/src/http/HttpSettings.cpp +++ b/src/http/HttpSettings.cpp @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file HttpSettings.cpp - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #include "HttpSettings.h" #include "core/Config.h" diff --git a/src/http/HttpSettings.h b/src/http/HttpSettings.h index bea5648c9..a4aee1a63 100644 --- a/src/http/HttpSettings.h +++ b/src/http/HttpSettings.h @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file HttpSettings.h - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #ifndef HTTPSETTINGS_H #define HTTPSETTINGS_H diff --git a/src/http/OptionDialog.cpp b/src/http/OptionDialog.cpp index fd30f8745..9fb66bd6f 100644 --- a/src/http/OptionDialog.cpp +++ b/src/http/OptionDialog.cpp @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file OptionDialog.cpp - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #include "OptionDialog.h" #include "ui_OptionDialog.h" diff --git a/src/http/OptionDialog.h b/src/http/OptionDialog.h index ad535fdb8..6139f929b 100644 --- a/src/http/OptionDialog.h +++ b/src/http/OptionDialog.h @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file OptionDialog.h - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #ifndef OPTIONDIALOG_H #define OPTIONDIALOG_H diff --git a/src/http/Protocol.cpp b/src/http/Protocol.cpp index 4fcdace6c..d6d5557a1 100644 --- a/src/http/Protocol.cpp +++ b/src/http/Protocol.cpp @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file Response.cpp - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #include "Protocol.h" #include diff --git a/src/http/Protocol.h b/src/http/Protocol.h index e20d19c31..ff48fe58c 100644 --- a/src/http/Protocol.h +++ b/src/http/Protocol.h @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file Response.h - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #ifndef RESPONSE_H #define RESPONSE_H diff --git a/src/http/Server.cpp b/src/http/Server.cpp index 5304c7f0f..faac7be23 100644 --- a/src/http/Server.cpp +++ b/src/http/Server.cpp @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file Server.cpp - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #include #include diff --git a/src/http/Server.h b/src/http/Server.h index 8421de06c..08cdfa24a 100644 --- a/src/http/Server.h +++ b/src/http/Server.h @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file Server.h - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #ifndef SERVER_H #define SERVER_H diff --git a/src/http/Service.cpp b/src/http/Service.cpp index b2ae0a803..639898da2 100644 --- a/src/http/Service.cpp +++ b/src/http/Service.cpp @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file Service.cpp - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #include #include diff --git a/src/http/Service.h b/src/http/Service.h index b6ee5bea0..d60d884bb 100644 --- a/src/http/Service.h +++ b/src/http/Service.h @@ -1,15 +1,20 @@ -/** - *************************************************************************** - * @file Service.h - * - * @brief - * - * Copyright (C) 2013 - * - * @author Francois Ferrand - * @date 4/2013 - *************************************************************************** - */ +/* +* Copyright (C) 2013 Francois Ferrand +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #ifndef SERVICE_H #define SERVICE_H diff --git a/src/keys/ChallengeResponseKey.h b/src/keys/ChallengeResponseKey.h index ac8c81650..698846a03 100644 --- a/src/keys/ChallengeResponseKey.h +++ b/src/keys/ChallengeResponseKey.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Kyle Manna +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 6114fd366..3b1a82a22 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index 50b2f699a..12e2d955d 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index dcd583358..cfb4a1dfe 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Kyle Manna +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h index 8c566ca41..66d821a69 100644 --- a/src/keys/YkChallengeResponseKey.h +++ b/src/keys/YkChallengeResponseKey.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 2011 Felix Geyer +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp index dfbc57c69..6fb44ec89 100644 --- a/src/keys/drivers/YubiKey.cpp +++ b/src/keys/drivers/YubiKey.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Kyle Manna +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h index 8a7552136..1467b9fd1 100644 --- a/src/keys/drivers/YubiKey.h +++ b/src/keys/drivers/YubiKey.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Kyle Manna +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/keys/drivers/YubiKeyStub.cpp b/src/keys/drivers/YubiKeyStub.cpp index 15eef27ad..9f6314f0e 100644 --- a/src/keys/drivers/YubiKeyStub.cpp +++ b/src/keys/drivers/YubiKeyStub.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Kyle Manna +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/main.cpp b/src/main.cpp index baa3df425..4368f7d54 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/totp/totp.cpp b/src/totp/totp.cpp index f85c76f06..51af0e086 100644 --- a/src/totp/totp.cpp +++ b/src/totp/totp.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/totp/totp.h b/src/totp/totp.h index 260babc22..642b4f9a3 100644 --- a/src/totp/totp.h +++ b/src/totp/totp.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index a3ed8cbe2..be73efd47 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index fb09a2783..0cd4a5bdd 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestCsvParser.cpp b/tests/TestCsvParser.cpp index 93bcf0060..57bc683a2 100644 --- a/tests/TestCsvParser.cpp +++ b/tests/TestCsvParser.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestCsvParser.h b/tests/TestCsvParser.h index 0aa3d01ef..0cf8b94d3 100644 --- a/tests/TestCsvParser.h +++ b/tests/TestCsvParser.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Enrico Mariotti + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestDatabase.cpp b/tests/TestDatabase.cpp index a70ada19d..284ba4bfb 100644 --- a/tests/TestDatabase.cpp +++ b/tests/TestDatabase.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Vladimir Svyatski + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestDatabase.h b/tests/TestDatabase.h index dc9609d72..46deb58aa 100644 --- a/tests/TestDatabase.h +++ b/tests/TestDatabase.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Vladimir Svyatski + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index a706badad..d2a8465bf 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestGroup.h b/tests/TestGroup.h index a0ed9282b..9b36ebabc 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index d5b35b1fb..dea0436f0 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestKeys.h b/tests/TestKeys.h index 683f07683..06ed3b0a1 100644 --- a/tests/TestKeys.h +++ b/tests/TestKeys.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index 3edf735b8..4f78693d6 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index 8259af620..009989500 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestTotp.cpp b/tests/TestTotp.cpp index e5da3c642..e22c2567e 100644 --- a/tests/TestTotp.cpp +++ b/tests/TestTotp.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestTotp.h b/tests/TestTotp.h index 9871aaf27..d197294dd 100644 --- a/tests/TestTotp.h +++ b/tests/TestTotp.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com> + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/TestYkChallengeResponseKey.cpp b/tests/TestYkChallengeResponseKey.cpp index 40eda3bf9..558920f4a 100644 --- a/tests/TestYkChallengeResponseKey.cpp +++ b/tests/TestYkChallengeResponseKey.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Kyle Manna + * Copyright (C) 2017 KeePassXC Team * * * This program is free software: you can redistribute it and/or modify diff --git a/tests/TestYkChallengeResponseKey.h b/tests/TestYkChallengeResponseKey.h index 309e014d5..2bc344ec0 100644 --- a/tests/TestYkChallengeResponseKey.h +++ b/tests/TestYkChallengeResponseKey.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Kyle Manna + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/gui/TemporaryFile.cpp b/tests/gui/TemporaryFile.cpp index 879a558a9..7c7a1c5d4 100644 --- a/tests/gui/TemporaryFile.cpp +++ b/tests/gui/TemporaryFile.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Danny Su + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/gui/TemporaryFile.h b/tests/gui/TemporaryFile.h index b16e2161a..f1cff3ef4 100644 --- a/tests/gui/TemporaryFile.h +++ b/tests/gui/TemporaryFile.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Danny Su + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index aed557a5e..5f969b038 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index e5d41fb64..904e5f21e 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 62748f6d4c173f2a2279ccc9c1a1496eacb32672 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sat, 10 Jun 2017 12:49:52 +0200 Subject: [PATCH 312/333] Add missing icon to COPYING --- COPYING | 2 ++ 1 file changed, 2 insertions(+) diff --git a/COPYING b/COPYING index cffc57564..6a0f05b90 100644 --- a/COPYING +++ b/COPYING @@ -77,6 +77,8 @@ Files: share/icons/application/*/actions/auto-type.png share/icons/application/*/actions/entry-clone.png share/icons/application/*/actions/entry-edit.png share/icons/application/*/actions/entry-new.png + share/icons/application/*/actions/group-empty-trash.png + share/icons/application/*/actions/help-about.png share/icons/application/*/actions/password-generate.png share/icons/database/C00_Password.png share/icons/database/C01_Package_Network.png From 11607b108cfe2d6986783e3f7f6b22d7862e73b7 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 14 Jun 2017 12:42:43 +0200 Subject: [PATCH 313/333] fix base32 copyright --- COPYING | 5 +++++ src/totp/base32.cpp | 4 ++-- src/totp/base32.h | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/COPYING b/COPYING index 6a0f05b90..481aaf726 100644 --- a/COPYING +++ b/COPYING @@ -241,3 +241,8 @@ Files: src/gui/KMessageWidget.h Copyright: 2011 Aurélien Gâteau 2014 Dominik Haumann License: LGPL-2.1 + +Files: src/totp/base32.cpp + src/totp/base32.h +Copyright: 2010 Google Inc. +License: Apache 2.0 \ No newline at end of file diff --git a/src/totp/base32.cpp b/src/totp/base32.cpp index 07526aa02..4c81cb491 100644 --- a/src/totp/base32.cpp +++ b/src/totp/base32.cpp @@ -1,9 +1,9 @@ // Base32 implementation +// Source: https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c // // Copyright 2010 Google Inc. // Author: Markus Gutschke -// Source: https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c -// Modifications copyright (C) 2017 KeePassXC team +// Modifications Copyright 2017 KeePassXC team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/totp/base32.h b/src/totp/base32.h index 3f1965b2f..75343fa43 100644 --- a/src/totp/base32.h +++ b/src/totp/base32.h @@ -1,9 +1,9 @@ // Base32 implementation +// Source: https://github.com/google/google-authenticator-libpam/blob/master/src/base32.h // // Copyright 2010 Google Inc. // Author: Markus Gutschke -// Source: https://github.com/google/google-authenticator-libpam/blob/master/src/base32.h -// Modifications copyright (C) 2017 KeePassXC team +// Modifications Copyright 2017 KeePassXC team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From f3f6f6a493cf4a46ab77c9eb2c534a8bbb69934f Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 14 Jun 2017 19:50:19 -0400 Subject: [PATCH 314/333] Adding saveToFile function. --- src/core/Database.cpp | 27 ++++++++++++++++++++++++ src/core/Database.h | 1 + src/gui/DatabaseTabWidget.cpp | 39 ++++++++++++----------------------- src/gui/DatabaseTabWidget.h | 2 -- 4 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 0572f5d72..aab9b5571 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -19,6 +19,7 @@ #include "Database.h" #include +#include #include #include #include @@ -28,6 +29,7 @@ #include "crypto/Random.h" #include "format/KeePass2.h" #include "format/KeePass2Reader.h" +#include "format/KeePass2Writer.h" QHash Database::m_uuidMap; @@ -412,3 +414,28 @@ Database* Database::unlockFromStdin(QString databaseFilename) return Database::openDatabaseFile(databaseFilename, key); } + +QString Database::saveToFile(QString filePath) +{ + KeePass2Writer writer; + QSaveFile saveFile(filePath); + if (saveFile.open(QIODevice::WriteOnly)) { + + // write the database to the file + writer.writeDatabase(&saveFile, this); + + if (writer.hasError()) { + return writer.errorString(); + } + + if (saveFile.commit()) { + // successfully saved database file + return QString(); + } else { + return saveFile.errorString(); + } + } else { + return saveFile.errorString(); + } + +} diff --git a/src/core/Database.h b/src/core/Database.h index 8e62e5357..a799e0b3b 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -113,6 +113,7 @@ public: void setEmitModified(bool value); void copyAttributesFrom(const Database* other); void merge(const Database* other); + QString saveToFile(QString filePath); /** * Returns a unique id that is only valid as long as the Database exists. diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 038174617..e13158eac 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include @@ -351,36 +350,24 @@ bool DatabaseTabWidget::saveDatabase(Database* db) DatabaseManagerStruct& dbStruct = m_dbList[db]; if (dbStruct.saveToFilename) { - QSaveFile saveFile(dbStruct.canonicalFilePath); - if (saveFile.open(QIODevice::WriteOnly)) { - // write the database to the file - dbStruct.dbWidget->blockAutoReload(true); - m_writer.writeDatabase(&saveFile, db); - dbStruct.dbWidget->blockAutoReload(false); - if (m_writer.hasError()) { - emit messageTab(tr("Writing the database failed.").append("\n") - .append(m_writer.errorString()), MessageWidget::Error); - return false; - } + dbStruct.dbWidget->blockAutoReload(true); + QString errorMessage = db->saveToFile(dbStruct.canonicalFilePath); + dbStruct.dbWidget->blockAutoReload(false); - if (saveFile.commit()) { - // successfully saved database file - dbStruct.modified = false; - dbStruct.dbWidget->databaseSaved(); - updateTabName(db); - emit messageDismissTab(); - return true; - } else { - emit messageTab(tr("Writing the database failed.").append("\n") - .append(saveFile.errorString()), MessageWidget::Error); - return false; - } + if (errorMessage.isEmpty()) { + // successfully saved database file + dbStruct.modified = false; + dbStruct.dbWidget->databaseSaved(); + updateTabName(db); + emit messageDismissTab(); + return true; } else { - emit messageTab(tr("Writing the database failed.").append("\n") - .append(saveFile.errorString()), MessageWidget::Error); + emit messageTab(tr("Writing the database failed.").append("\n").append(errorMessage), + MessageWidget::Error); return false; } + } else { return saveDatabaseAs(db); } diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 85b3f3af6..847eaef05 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -22,7 +22,6 @@ #include #include -#include "format/KeePass2Writer.h" #include "gui/DatabaseWidget.h" #include "gui/MessageWidget.h" @@ -118,7 +117,6 @@ private: void updateLastDatabases(const QString& filename); void connectDatabase(Database* newDb, Database* oldDb = nullptr); - KeePass2Writer m_writer; QHash m_dbList; DatabaseWidgetStateSync* m_dbWidgetStateSync; }; From 46f7d971766d404c7f666d340d5bbfb08b759407 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 14 Jun 2017 20:00:09 -0400 Subject: [PATCH 315/333] Using saveToFile in Merge command. --- src/cli/Merge.cpp | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 738f0ee43..c69dcd604 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -22,12 +22,10 @@ #include #include #include -#include #include #include #include "core/Database.h" -#include "format/KeePass2Writer.h" #include "gui/UnlockDatabaseDialog.h" int Merge::execute(int argc, char** argv) @@ -95,26 +93,13 @@ int Merge::execute(int argc, char** argv) } db1->merge(db2); - - QSaveFile saveFile(args.at(0)); - if (!saveFile.open(QIODevice::WriteOnly)) { - qCritical("Unable to open file %s for writing.", qPrintable(args.at(0))); - return EXIT_FAILURE; - } - - KeePass2Writer writer; - writer.writeDatabase(&saveFile, db1); - - if (writer.hasError()) { - qCritical("Error while updating the database:\n%s\n", qPrintable(writer.errorString())); - return EXIT_FAILURE; - } - - if (!saveFile.commit()) { - qCritical("Error while updating the database:\n%s\n", qPrintable(writer.errorString())); + QString errorMessage = db1->saveToFile(args.at(0)); + if (!errorMessage.isEmpty()) { + qCritical("Unable to save database to file : %s", qPrintable(errorMessage)); return EXIT_FAILURE; } out << "Successfully merged the database files.\n"; return EXIT_SUCCESS; + } From e0e8521eb9c88cfa355306eee70bdc461505bec8 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 15 Jun 2017 10:26:37 -0400 Subject: [PATCH 316/333] Removing m_writer from CsvImportWidget.h --- src/gui/csvImport/CsvImportWidget.cpp | 1 + src/gui/csvImport/CsvImportWidget.h | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 1e49d283e..23db871f3 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -23,6 +23,7 @@ #include #include +#include "format/KeePass2Writer.h" #include "gui/MessageBox.h" #include "gui/MessageWidget.h" diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index d006b44e2..463a92c5c 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -28,7 +28,6 @@ #include #include "core/Metadata.h" -#include "format/KeePass2Writer.h" #include "gui/csvImport/CsvParserModel.h" #include "keys/PasswordKey.h" @@ -42,7 +41,7 @@ class CsvImportWidget : public QWidget Q_OBJECT public: - explicit CsvImportWidget(QWidget *parent = nullptr); + explicit CsvImportWidget(QWidget* parent = nullptr); ~CsvImportWidget(); void load(const QString& filename, Database* const db); @@ -65,9 +64,8 @@ private: QStringListModel* const m_comboModel; QSignalMapper* m_comboMapper; QList m_combos; - Database *m_db; + Database* m_db; - KeePass2Writer m_writer; static const QStringList m_columnHeader; void configParser(); void updateTableview(); From 574c5cf1b26acda94cb615ff7252fa99033c5134 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 15 Jun 2017 10:31:14 -0400 Subject: [PATCH 317/333] clang-format Database.cpp --- src/core/Database.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index aab9b5571..e1a8610a2 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -224,14 +224,12 @@ bool Database::setTransformRounds(quint64 rounds) return true; } -bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, - bool updateChangedTime) +bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime) { bool ok; QString errorString; - QByteArray transformedMasterKey = - key.transform(transformSeed, transformRounds(), &ok, &errorString); + QByteArray transformedMasterKey = key.transform(transformSeed, transformRounds(), &ok, &errorString); if (!ok) { return false; } @@ -293,23 +291,21 @@ void Database::recycleEntry(Entry* entry) createRecycleBin(); } entry->setGroup(metadata()->recycleBin()); - } - else { + } else { delete entry; } } void Database::recycleGroup(Group* group) { - if (m_metadata->recycleBinEnabled()) { + if (m_metadata->recycleBinEnabled()) { if (!m_metadata->recycleBin()) { createRecycleBin(); } group->setParent(metadata()->recycleBin()); - } - else { + } else { delete group; - } + } } void Database::emptyRecycleBin() @@ -398,7 +394,6 @@ Database* Database::openDatabaseFile(QString fileName, CompositeKey key) } return db; - } Database* Database::unlockFromStdin(QString databaseFilename) @@ -412,7 +407,6 @@ Database* Database::unlockFromStdin(QString databaseFilename) QString line = inputTextStream.readLine(); CompositeKey key = CompositeKey::readFromLine(line); return Database::openDatabaseFile(databaseFilename, key); - } QString Database::saveToFile(QString filePath) @@ -437,5 +431,4 @@ QString Database::saveToFile(QString filePath) } else { return saveFile.errorString(); } - } From b75b9fb7d666621015b42d630a2e32ab3602d510 Mon Sep 17 00:00:00 2001 From: louib Date: Sun, 18 Jun 2017 14:43:02 -0400 Subject: [PATCH 318/333] Adding gui prompt to List command. (#643) --- src/cli/List.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 685ef8ec1..c7a862646 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -20,11 +20,13 @@ #include "List.h" +#include #include #include #include #include +#include "gui/UnlockDatabaseDialog.h" #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" @@ -33,7 +35,10 @@ int List::execute(int argc, char** argv) { - QCoreApplication app(argc, argv); + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << QString(argv[i]); + } QTextStream out(stdout); QCommandLineParser parser; @@ -44,22 +49,29 @@ int List::execute(int argc, char** argv) << "print-uuids", QCoreApplication::translate("main", "Print the UUIDs of the entries and groups.")); parser.addOption(printUuidsOption); - parser.process(app); + QCommandLineOption guiPrompt( + QStringList() << "g" + << "gui-prompt", + QCoreApplication::translate("main", "Use a GUI prompt unlocking the database.")); + parser.addOption(guiPrompt); + parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() != 1) { + QCoreApplication app(argc, argv); parser.showHelp(); return EXIT_FAILURE; } - out << "Insert the database password\n> "; - out.flush(); + Database* db = nullptr; + if (parser.isSet("gui-prompt")) { + QApplication app(argc, argv); + db = UnlockDatabaseDialog::openDatabasePrompt(args.at(0)); + } else { + QCoreApplication app(argc, argv); + db = Database::unlockFromStdin(args.at(0)); + } - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line = inputTextStream.readLine(); - CompositeKey key = CompositeKey::readFromLine(line); - - Database* db = Database::openDatabaseFile(args.at(0), key); if (db == nullptr) { return EXIT_FAILURE; } From 8d70167acfb62b3a88701b73b2de7b439fe5c4e1 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Mon, 19 Jun 2017 10:49:03 -0400 Subject: [PATCH 319/333] Add support for portable config settings (#645) * Add support for portable config settings * Use applicationDirPath instead of currentPath --- src/core/Config.cpp | 66 ++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index c8d23b361..013d148d7 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -60,39 +60,43 @@ Config::Config(const QString& fileName, QObject* parent) Config::Config(QObject* parent) : QObject(parent) { - QString userPath; - QString homePath = QDir::homePath(); + // Check if portable config is present. If not, find it in user's directory + QString portablePath = QCoreApplication::applicationDirPath() + "/keepassxc.ini"; + if (QFile::exists(portablePath)) { + init(portablePath); + } else { + QString userPath; + QString homePath = QDir::homePath(); -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) - // we can't use QStandardPaths on X11 as it uses XDG_DATA_HOME instead of XDG_CONFIG_HOME - QByteArray env = qgetenv("XDG_CONFIG_HOME"); - if (env.isEmpty()) { - userPath = homePath; - userPath += "/.config"; + #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) + // we can't use QStandardPaths on X11 as it uses XDG_DATA_HOME instead of XDG_CONFIG_HOME + QByteArray env = qgetenv("XDG_CONFIG_HOME"); + if (env.isEmpty()) { + userPath = homePath; + userPath += "/.config"; + } else if (env[0] == '/') { + userPath = QFile::decodeName(env); + } else { + userPath = homePath; + userPath += '/'; + userPath += QFile::decodeName(env); + } + + userPath += "/keepassxc/"; + #else + userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + // storageLocation() appends the application name ("/keepassxc") to the end + userPath += "/"; + #endif + + #ifdef QT_DEBUG + userPath += "keepassxc_debug.ini"; + #else + userPath += "keepassxc.ini"; + #endif + + init(userPath); } - else if (env[0] == '/') { - userPath = QFile::decodeName(env); - } - else { - userPath = homePath; - userPath += '/'; - userPath += QFile::decodeName(env); - } - - userPath += "/keepassxc/"; -#else - userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); - // storageLocation() appends the application name ("/keepassxc") to the end - userPath += "/"; -#endif - -#ifdef QT_DEBUG - userPath += "keepassxc_debug.ini"; -#else - userPath += "keepassxc.ini"; -#endif - - init(userPath); } Config::~Config() From 344235b1e1d51353df1d82317a1328b4c4262828 Mon Sep 17 00:00:00 2001 From: louib Date: Mon, 19 Jun 2017 11:09:19 -0400 Subject: [PATCH 320/333] Fix CLI help and version options. (#650) * Correct handling of --help and --version * Moving arguments building up. * Only manipulating argv is the command is valid. * Not a failure when --help * Not using showVersion() --- src/cli/keepassxc-cli.cpp | 45 ++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 0d261eb47..18bb224ec 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -47,6 +48,10 @@ int main(int argc, char** argv) return EXIT_FAILURE; } + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << QString(argv[i]); + } QCommandLineParser parser; QString description("KeePassXC command line interface."); @@ -63,50 +68,66 @@ int main(int argc, char** argv) parser.addHelpOption(); parser.addVersionOption(); - // TODO : use process once the setOptionsAfterPositionalArgumentsMode (Qt 5.6) - // is available. Until then, options passed to sub-commands won't be + // TODO : use the setOptionsAfterPositionalArgumentsMode (Qt 5.6) function + // when available. Until then, options passed to sub-commands won't be // recognized by this parser. - // parser.process(app); + parser.parse(arguments); - if (argc < 2) { + if (parser.positionalArguments().size() < 1) { QCoreApplication app(argc, argv); app.setApplicationVersion(KEEPASSX_VERSION); + if (parser.isSet("version")) { + // Switch to parser.showVersion() when available (QT 5.4). + QTextStream out(stdout); + out << KEEPASSX_VERSION << "\n"; + out.flush(); + return EXIT_SUCCESS; + } parser.showHelp(); - return EXIT_FAILURE; } - QString commandName = argv[1]; - - // Removing the first cli argument before dispatching. - ++argv; - --argc; + QString commandName = parser.positionalArguments().at(0); int exitCode = EXIT_FAILURE; if (commandName == "clip") { + // Removing the first cli argument before dispatching. + ++argv; + --argc; argv[0] = const_cast("keepassxc-cli clip"); exitCode = Clip::execute(argc, argv); } else if (commandName == "entropy-meter") { + ++argv; + --argc; argv[0] = const_cast("keepassxc-cli entropy-meter"); exitCode = EntropyMeter::execute(argc, argv); } else if (commandName == "extract") { + ++argv; + --argc; argv[0] = const_cast("keepassxc-cli extract"); exitCode = Extract::execute(argc, argv); } else if (commandName == "list") { + ++argv; + --argc; argv[0] = const_cast("keepassxc-cli list"); exitCode = List::execute(argc, argv); } else if (commandName == "merge") { + ++argv; + --argc; argv[0] = const_cast("keepassxc-cli merge"); exitCode = Merge::execute(argc, argv); } else if (commandName == "show") { + ++argv; + --argc; argv[0] = const_cast("keepassxc-cli show"); exitCode = Show::execute(argc, argv); } else { qCritical("Invalid command %s.", qPrintable(commandName)); QCoreApplication app(argc, argv); app.setApplicationVersion(KEEPASSX_VERSION); - parser.showHelp(); - exitCode = EXIT_FAILURE; + // showHelp exits the application immediately, so we need to set the + // exit code here. + parser.showHelp(EXIT_FAILURE); } #if defined(WITH_ASAN) && defined(WITH_LSAN) From 702a68307a5061db6e0fa9dfe07b58c226c4a790 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 20 Jun 2017 16:17:35 -0400 Subject: [PATCH 321/333] Allow multiple instances when debugging (#651) --- src/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 4368f7d54..7c4402b99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,10 +57,12 @@ int main(int argc, char** argv) // don't set organizationName as that changes the return value of // QStandardPaths::writableLocation(QDesktopServices::DataLocation) +#ifndef QT_DEBUG if (app.isAlreadyRunning()) { qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.").toUtf8().constData(); return 0; } +#endif QApplication::setQuitOnLastWindowClosed(false); From a3020b67870fee43d559099f5db9af9342f0d91f Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 20 Jun 2017 23:12:57 -0400 Subject: [PATCH 322/333] Bumped version to 2.2.0 and populated CHANGELOG --- CHANGELOG | 33 +++++++++++++++++++++++++++++++++ CMakeLists.txt | 4 ++-- snapcraft.yaml | 5 ++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a29371598..6c2cc9dfa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,36 @@ +2.2.0 (2017-06-23) +========================= + +- Added YubiKey 2FA integration for unlocking databases [#127] +- Added TOTP support [#519] +- Added CSV import tool [#146, #490] +- Added KeePassXC CLI tool [#254] +- Added diceware password generator [#373] +- Added support for entry references [#370, #378] +- Added support for Twofish encryption [#167] +- Enabled DEP and ASLR for in-memory protection [#371] +- Enabled single instance mode [#510] +- Enabled portable mode [#645] +- Enabled database lock on screensaver and session lock [#545] +- Redesigned welcome screen with common features and recent databases [#292] +- Multiple updates to search behavior [#168, #213, #374, #471, #603, #654] +- Added auto-type fields {CLEARFIELD}, {SPACE}, {{}, {}} [#267, #427, #480] +- Fixed auto-type errors on Linux [#550] +- Prompt user prior to executing a cmd:// URL [#235] +- Entry attributes can be protected (hidden) [#220] +- Added extended ascii to password generator [#538] +- Added new database icon to toolbar [#289] +- Added context menu entry to empty recycle bin in databases [#520] +- Added "apply" button to entry and group edit windows [#624] +- Added macOS tray icon and enabled minimize on close [#583] +- Fixed issues with unclean shutdowns [#170, #580] +- Changed keyboard shortcut to create new database to CTRL+SHIFT+N [#515] +- Compare window title to entry URLs [#556] +- Implemented inline error messages [#162] +- Ignore group expansion and other minor changes when making database "dirty" [#464] +- Updated license and copyright information on souce files [#632] +- Added contributors list to about dialog [#629] + 2.1.4 (2017-04-09) ========================= diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f3677d68..627676105 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,8 +48,8 @@ option(WITH_XC_YUBIKEY "Include YubiKey support." OFF) set(CMAKE_AUTOUIC ON) set(KEEPASSXC_VERSION_MAJOR "2") -set(KEEPASSXC_VERSION_MINOR "1") -set(KEEPASSXC_VERSION_PATCH "4") +set(KEEPASSXC_VERSION_MINOR "2") +set(KEEPASSXC_VERSION_PATCH "0") set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}") if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") diff --git a/snapcraft.yaml b/snapcraft.yaml index aa5ab38fb..0832ef48a 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: keepassxc -version: 2.1.4 +version: 2.2.0 grade: stable summary: community driven port of the windows application “Keepass Password Safe” description: | @@ -22,6 +22,7 @@ parts: - -DWITH_TESTS=OFF - -DWITH_XC_AUTOTYPE=ON - -DWITH_XC_HTTP=ON + - -DWITH_XC_YUBIKEY=ON build-packages: - g++ - libgcrypt20-dev @@ -32,4 +33,6 @@ parts: - zlib1g-dev - libxi-dev - libxtst-dev + - libyubikey-dev + - libykpers-1-dev after: [desktop-qt5] From 719323e9c37ce509fd734c8a8b88dc09177ee744 Mon Sep 17 00:00:00 2001 From: Weslly Date: Tue, 20 Jun 2017 16:54:13 -0300 Subject: [PATCH 323/333] Add option to limit search to current group --- src/core/Config.cpp | 1 + src/gui/DatabaseWidget.cpp | 11 ++++++++++- src/gui/DatabaseWidget.h | 2 ++ src/gui/SearchWidget.cpp | 21 +++++++++++++++++++++ src/gui/SearchWidget.h | 4 ++++ tests/gui/TestGui.cpp | 5 +++++ 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 013d148d7..5afbfcceb 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -114,6 +114,7 @@ void Config::init(const QString& fileName) 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); m_defaults.insert("AutoTypeEntryTitleMatch", true); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index b8e4f0535..4b9e85009 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -186,6 +186,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) m_ignoreAutoReload = false; m_searchCaseSensitive = false; + m_searchLimitGroup = config()->get("SearchLimitGroup", false).toBool(); setCurrentWidget(m_mainWidget); } @@ -963,7 +964,9 @@ void DatabaseWidget::search(const QString& searchtext) Qt::CaseSensitivity caseSensitive = m_searchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; - QList searchResult = EntrySearcher().search(searchtext, currentGroup(), caseSensitive); + Group* searchGroup = m_searchLimitGroup ? currentGroup() : m_db->rootGroup(); + + QList searchResult = EntrySearcher().search(searchtext, searchGroup, caseSensitive); m_entryView->setEntryList(searchResult); m_lastSearchText = searchtext; @@ -987,6 +990,12 @@ void DatabaseWidget::setSearchCaseSensitive(bool state) refreshSearch(); } +void DatabaseWidget::setSearchLimitGroup(bool state) +{ + m_searchLimitGroup = state; + refreshSearch(); +} + void DatabaseWidget::onGroupChanged(Group* group) { // Intercept group changes if in search mode diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 73bc21224..734e979e7 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -163,6 +163,7 @@ public slots: // Search related slots void search(const QString& searchtext); void setSearchCaseSensitive(bool state); + void setSearchLimitGroup(bool state); void endSearch(); void showMessage(const QString& text, MessageWidget::MessageType type); @@ -221,6 +222,7 @@ private: // Search state QString m_lastSearchText; bool m_searchCaseSensitive; + bool m_searchLimitGroup; // Autoreload QFileSystemWatcher m_fileWatcher; diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index 3e987df99..7aa5f2901 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -24,6 +24,7 @@ #include #include +#include "core/Config.h" #include "core/FilePath.h" SearchWidget::SearchWidget(QWidget* parent) @@ -50,6 +51,11 @@ SearchWidget::SearchWidget(QWidget* parent) m_actionCaseSensitive->setObjectName("actionSearchCaseSensitive"); m_actionCaseSensitive->setCheckable(true); + m_actionLimitGroup = searchMenu->addAction(tr("Limit search to selected group"), this, SLOT(updateLimitGroup())); + m_actionLimitGroup->setObjectName("actionSearchLimitGroup"); + m_actionLimitGroup->setCheckable(true); + m_actionLimitGroup->setChecked(config()->get("SearchLimitGroup", false).toBool()); + m_ui->searchIcon->setIcon(filePath()->icon("actions", "system-search")); m_ui->searchIcon->setMenu(searchMenu); m_ui->searchEdit->addAction(m_ui->searchIcon, QLineEdit::LeadingPosition); @@ -102,6 +108,7 @@ void SearchWidget::connectSignals(SignalMultiplexer& mx) { mx.connect(this, SIGNAL(search(QString)), SLOT(search(QString))); mx.connect(this, SIGNAL(caseSensitiveChanged(bool)), SLOT(setSearchCaseSensitive(bool))); + mx.connect(this, SIGNAL(limitGroupChanged(bool)), SLOT(setSearchLimitGroup(bool))); mx.connect(this, SIGNAL(copyPressed()), SLOT(copyPassword())); mx.connect(this, SIGNAL(downPressed()), SLOT(setFocus())); mx.connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(switchToEntryEdit())); @@ -115,6 +122,7 @@ void SearchWidget::databaseChanged(DatabaseWidget* dbWidget) // Enforce search policy emit caseSensitiveChanged(m_actionCaseSensitive->isChecked()); + emit limitGroupChanged(m_actionLimitGroup->isChecked()); } else { m_ui->searchEdit->clear(); } @@ -151,6 +159,19 @@ void SearchWidget::setCaseSensitive(bool state) updateCaseSensitive(); } +void SearchWidget::updateLimitGroup() +{ + config()->set("SearchLimitGroup", m_actionLimitGroup->isChecked()); + emit limitGroupChanged(m_actionLimitGroup->isChecked()); +} + +void SearchWidget::setLimitGroup(bool state) +{ + m_actionLimitGroup->setChecked(state); + updateLimitGroup(); +} + + void SearchWidget::searchFocus() { m_ui->searchEdit->setFocus(); diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h index 9f0e0d11c..2441ef60b 100644 --- a/src/gui/SearchWidget.h +++ b/src/gui/SearchWidget.h @@ -39,6 +39,7 @@ public: void connectSignals(SignalMultiplexer& mx); void setCaseSensitive(bool state); + void setLimitGroup(bool state); protected: bool eventFilter(QObject* obj, QEvent* event); @@ -46,6 +47,7 @@ protected: signals: void search(const QString& text); void caseSensitiveChanged(bool state); + void limitGroupChanged(bool state); void escapePressed(); void copyPressed(); void downPressed(); @@ -58,12 +60,14 @@ private slots: void startSearchTimer(); void startSearch(); void updateCaseSensitive(); + void updateLimitGroup(); void searchFocus(); private: const QScopedPointer m_ui; QTimer* m_searchTimer; QAction* m_actionCaseSensitive; + QAction* m_actionLimitGroup; Q_DISABLE_COPY(SearchWidget) }; diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 5f969b038..9abe31f38 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -577,7 +577,12 @@ void TestGui::testSearch() QModelIndex rootGroupIndex = groupView->model()->index(0, 0); clickIndex(groupView->model()->index(0, 0, rootGroupIndex), groupView, Qt::LeftButton); QCOMPARE(groupView->currentGroup()->name(), QString("General")); + + searchWidget->setLimitGroup(false); + QTRY_COMPARE(entryView->model()->rowCount(), 2); + searchWidget->setLimitGroup(true); QTRY_COMPARE(entryView->model()->rowCount(), 0); + // reset clickIndex(rootGroupIndex, groupView, Qt::LeftButton); QCOMPARE(groupView->currentGroup(), m_db->rootGroup()); From 7438d6db186b730e2990429f99427ffc250a212c Mon Sep 17 00:00:00 2001 From: Weslly Date: Wed, 21 Jun 2017 01:21:52 -0300 Subject: [PATCH 324/333] Change text color of search label --- src/gui/DatabaseWidget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index b8e4f0535..0b176ff6a 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -94,7 +94,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) m_searchingLabel = new QLabel(); m_searchingLabel->setText(tr("Searching...")); m_searchingLabel->setAlignment(Qt::AlignCenter); - m_searchingLabel->setStyleSheet("background-color: rgb(255, 253, 160);" + m_searchingLabel->setStyleSheet("color: rgb(0, 0, 0);" + "background-color: rgb(255, 253, 160);" "border: 2px solid rgb(190, 190, 190);" "border-radius: 5px;"); From 400073c7cc7a313038da5ae7e70149294b6a72c3 Mon Sep 17 00:00:00 2001 From: Weslly Date: Wed, 21 Jun 2017 00:05:24 -0300 Subject: [PATCH 325/333] Disable stdin echo when entering passwords on cli --- src/CMakeLists.txt | 2 ++ src/cli/Extract.cpp | 4 +-- src/cli/PasswordInput.cpp | 72 +++++++++++++++++++++++++++++++++++++++ src/cli/PasswordInput.h | 31 +++++++++++++++++ src/cli/Show.cpp | 4 +-- src/core/Database.cpp | 4 +-- 6 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 src/cli/PasswordInput.cpp create mode 100644 src/cli/PasswordInput.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d01721b7..cc9334842 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,6 +59,8 @@ set(keepassx_SOURCES core/Tools.cpp core/Translator.cpp core/Uuid.cpp + cli/PasswordInput.cpp + cli/PasswordInput.h crypto/Crypto.cpp crypto/CryptoHash.cpp crypto/Random.cpp diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 0c8a39602..36dde473b 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -29,6 +29,7 @@ #include "core/Database.h" #include "format/KeePass2Reader.h" #include "keys/CompositeKey.h" +#include "cli/PasswordInput.h" int Extract::execute(int argc, char** argv) { @@ -50,8 +51,7 @@ int Extract::execute(int argc, char** argv) out << "Insert the database password\n> "; out.flush(); - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line = inputTextStream.readLine(); + QString line = PasswordInput::getPassword(); CompositeKey key = CompositeKey::readFromLine(line); QString databaseFilename = args.at(0); diff --git a/src/cli/PasswordInput.cpp b/src/cli/PasswordInput.cpp new file mode 100644 index 000000000..9436f4918 --- /dev/null +++ b/src/cli/PasswordInput.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "PasswordInput.h" + +#ifdef Q_OS_WIN +#include +#else +#include +#include +#endif + +#include + + +PasswordInput::PasswordInput() +{ +} + +void PasswordInput::setStdinEcho(bool enable = true) +{ +#ifdef Q_OS_WIN + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + DWORD mode; + GetConsoleMode(hIn, &mode); + + if (enable) { + mode |= ENABLE_ECHO_INPUT; + } else { + mode &= ~ENABLE_ECHO_INPUT; + } + + SetConsoleMode(hIn, mode); + +#else + struct termios t; + tcgetattr(STDIN_FILENO, &t); + + if (enable) { + t.c_lflag |= ECHO; + } else { + t.c_lflag &= ~ECHO; + } + + tcsetattr(STDIN_FILENO, TCSANOW, &t); +#endif +} + +QString PasswordInput::getPassword() +{ + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + + setStdinEcho(false); + QString line = inputTextStream.readLine(); + setStdinEcho(true); + + return line; +} diff --git a/src/cli/PasswordInput.h b/src/cli/PasswordInput.h new file mode 100644 index 000000000..b76061864 --- /dev/null +++ b/src/cli/PasswordInput.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_PASSWORDINPUT_H +#define KEEPASSXC_PASSWORDINPUT_H + +#include + +class PasswordInput +{ +public: + PasswordInput(); + static void setStdinEcho(bool enable); + static QString getPassword(); +}; + +#endif // KEEPASSXC_PASSWORDINPUT_H diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index b9d6bed0f..680f7a452 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -29,6 +29,7 @@ #include "core/Entry.h" #include "core/Group.h" #include "keys/CompositeKey.h" +#include "cli/PasswordInput.h" int Show::execute(int argc, char** argv) { @@ -50,8 +51,7 @@ int Show::execute(int argc, char** argv) out << "Insert the database password\n> "; out.flush(); - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line = inputTextStream.readLine(); + QString line = PasswordInput::getPassword(); CompositeKey key = CompositeKey::readFromLine(line); Database* db = Database::openDatabaseFile(args.at(0), key); diff --git a/src/core/Database.cpp b/src/core/Database.cpp index e1a8610a2..d1c0fea4d 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -24,6 +24,7 @@ #include #include +#include "cli/PasswordInput.h" #include "core/Group.h" #include "core/Metadata.h" #include "crypto/Random.h" @@ -398,13 +399,12 @@ Database* Database::openDatabaseFile(QString fileName, CompositeKey key) Database* Database::unlockFromStdin(QString databaseFilename) { - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QTextStream outputTextStream(stdout); outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n> "); outputTextStream.flush(); - QString line = inputTextStream.readLine(); + QString line = PasswordInput::getPassword(); CompositeKey key = CompositeKey::readFromLine(line); return Database::openDatabaseFile(databaseFilename, key); } From 7654983d3dd5b586ec268d9c82b80221769100a5 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Wed, 7 Jun 2017 19:33:14 -0400 Subject: [PATCH 326/333] Added contributors to about dialog; general cleanup --- src/gui/AboutDialog.cpp | 2 +- src/gui/AboutDialog.ui | 98 ++++++++++++++++++++++++++++++++++------- 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index 6204e5c8c..0e0f2960a 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -37,7 +37,7 @@ AboutDialog::AboutDialog(QWidget* parent) setWindowFlags(Qt::Sheet); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - m_ui->nameLabel->setText(m_ui->nameLabel->text() + " " + KEEPASSX_VERSION); + m_ui->nameLabel->setText(m_ui->nameLabel->text().replace("${VERSION}", KEEPASSX_VERSION)); QFont nameLabelFont = m_ui->nameLabel->font(); nameLabelFont.setPointSize(nameLabelFont.pointSize() + 4); m_ui->nameLabel->setFont(nameLabelFont); diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index 5f20cf430..e707348e0 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -6,10 +6,22 @@ 0 0 - 479 - 478 + 450 + 450 + + + 0 + 0 + + + + + 450 + 450 + + About KeePassXC @@ -59,7 +71,13 @@ - KeePassXC + <span style="font-size: 24pt"> KeePassXC v${VERSION}</span> + + + 0 + + + 11 @@ -84,7 +102,7 @@ - <p>Website: <a href="https://keepassxc.org/">https://keepassxc.org/</a></p> + <html><head/><body><p><span style=" font-size:10pt;">Website: </span><a href="https://keepassxc.org/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">https://keepassxc.org</span></a></p></body></html> true @@ -94,7 +112,7 @@ - <p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues">https://github.com/</a></p> + <html><head/><body><p><span style=" font-size:10pt;">Report bugs at: </span><a href="https://github.com/keepassxreboot/keepassxc/issues"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> true @@ -126,7 +144,7 @@ - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. + <html><head/><body><p><span style=" font-size:10pt;">KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</span></p></body></html> true @@ -158,14 +176,7 @@ - <p>Main contributors:</p> -<ul> -<li>debfx (KeePassX)</li> -<li>droidmonkey</li> -<li>louib</li> -<li>phoerious</li> -<li>thezero</li> -</ul> + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> @@ -184,6 +195,63 @@ + + + Contributors + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:x-large; font-weight:600;">Code:</span></p> +<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:10pt;" style=" margin-top:12px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">debfx (KeePassX)</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">BlueIce (KeePassX)</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">droidmonkey</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">phoerious</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TheZ3ro</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">louib</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">weslly</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">keithbennett (KeePassHTTP)</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Typz (KeePassHTTP)</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">denk-mal (KeePassHTTP)</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">kylemanna (YubiKey)</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">seatedscribe (CSV Importer)</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pgalves (Inline Messages)</li></ul> +<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:x-large; font-weight:600;">Translations:</span></p> +<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:10pt;" style=" margin-top:12px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Czech:</span> pavelb, JosefVitu</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Finnish:</span> MawKKe</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Greek:</span> nplatis</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Kazakh:</span> sotrud_nik</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Lithuanian:</span> Moo</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Polish:</span> konradmb, mrerexx</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> +<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Swedish:</span> henziger</li></ul></body></html> + + + + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + true + + + + + Debug Info @@ -198,7 +266,7 @@ - Include the following information whenever you report a bug: + <html><head/><body><p><span style=" font-size:10pt;">Include the following information whenever you report a bug:</span></p></body></html> From 97c8603478e26530db0e4fbacee728ae121f0c14 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Tue, 13 Jun 2017 20:49:45 -0400 Subject: [PATCH 327/333] Removed font size on text labels in about dialog --- src/gui/AboutDialog.ui | 78 ++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index e707348e0..5bea301aa 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -102,7 +102,7 @@ - <html><head/><body><p><span style=" font-size:10pt;">Website: </span><a href="https://keepassxc.org/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">https://keepassxc.org</span></a></p></body></html> + <html><head/><body><p>Website: <a href="https://keepassxc.org/"><span style="text-decoration: underline; color:#0000ff;">https://keepassxc.org</span></a></p></body></html> true @@ -112,7 +112,7 @@ - <html><head/><body><p><span style=" font-size:10pt;">Report bugs at: </span><a href="https://github.com/keepassxreboot/keepassxc/issues"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> true @@ -144,7 +144,7 @@ - <html><head/><body><p><span style=" font-size:10pt;">KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</span></p></body></html> + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> true @@ -203,40 +203,42 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:x-large; font-weight:600;">Code:</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:10pt;" style=" margin-top:12px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">debfx (KeePassX)</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">BlueIce (KeePassX)</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">droidmonkey</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">phoerious</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TheZ3ro</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">louib</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">weslly</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">keithbennett (KeePassHTTP)</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Typz (KeePassHTTP)</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">denk-mal (KeePassHTTP)</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">kylemanna (YubiKey)</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">seatedscribe (CSV Importer)</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pgalves (Inline Messages)</li></ul> -<p style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:x-large; font-weight:600;">Translations:</span></p> -<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:10pt;" style=" margin-top:12px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Czech:</span> pavelb, JosefVitu</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Finnish:</span> MawKKe</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Greek:</span> nplatis</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Kazakh:</span> sotrud_nik</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Lithuanian:</span> Moo</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Polish:</span> konradmb, mrerexx</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:2px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> -<li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Swedish:</span> henziger</li></ul></body></html> + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> @@ -266,7 +268,7 @@ p, li { white-space: pre-wrap; } - <html><head/><body><p><span style=" font-size:10pt;">Include the following information whenever you report a bug:</span></p></body></html> + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> From 48ea024d7e1846beadfca557fa8f5a367c3a126f Mon Sep 17 00:00:00 2001 From: louib Date: Wed, 21 Jun 2017 17:34:49 -0400 Subject: [PATCH 328/333] Adding support for listing a group. (#652) * Adding support for listing a group. * added findGroupByPath * Removing useless asserts. * Code review. --- src/cli/List.cpp | 17 ++++++++-- src/core/Group.cpp | 37 +++++++++++++++++++-- src/core/Group.h | 3 +- tests/TestGroup.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++++ tests/TestGroup.h | 1 + 5 files changed, 133 insertions(+), 6 deletions(-) diff --git a/src/cli/List.cpp b/src/cli/List.cpp index c7a862646..b0b791882 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -44,6 +44,9 @@ int List::execute(int argc, char** argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "List database entries.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); + parser.addPositionalArgument("group", + QCoreApplication::translate("main", "Path of the group to list. Default is /"), + QString("[group]")); QCommandLineOption printUuidsOption( QStringList() << "u" << "print-uuids", @@ -57,7 +60,7 @@ int List::execute(int argc, char** argv) parser.process(arguments); const QStringList args = parser.positionalArguments(); - if (args.size() != 1) { + if (args.size() != 1 && args.size() != 2) { QCoreApplication app(argc, argv); parser.showHelp(); return EXIT_FAILURE; @@ -76,7 +79,17 @@ int List::execute(int argc, char** argv) return EXIT_FAILURE; } - out << db->rootGroup()->print(parser.isSet("print-uuids")); + Group* group = db->rootGroup(); + if (args.size() == 2) { + QString groupPath = args.at(1); + group = db->rootGroup()->findGroupByPath(groupPath); + if (group == nullptr) { + qCritical("Cannot find group %s.", qPrintable(groupPath)); + return EXIT_FAILURE; + } + } + + out << group->print(parser.isSet("print-uuids")); out.flush(); return EXIT_SUCCESS; } diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 0bebfcb0d..edbed947e 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -486,7 +486,6 @@ QList Group::entriesRecursive(bool includeHistoryItems) const Entry* Group::findEntry(QString entryId) { - Q_ASSERT(!entryId.isEmpty()); Q_ASSERT(!entryId.isNull()); if (Uuid::isUuid(entryId)) { @@ -527,12 +526,11 @@ Entry* Group::findEntryByUuid(const Uuid& uuid) Entry* Group::findEntryByPath(QString entryPath, QString basePath) { - Q_ASSERT(!entryPath.isEmpty()); Q_ASSERT(!entryPath.isNull()); for (Entry* entry : asConst(m_entries)) { QString currentEntryPath = basePath + entry->title(); - if (entryPath == currentEntryPath) { + if (entryPath == currentEntryPath || entryPath == QString("/" + currentEntryPath)) { return entry; } } @@ -547,6 +545,39 @@ Entry* Group::findEntryByPath(QString entryPath, QString basePath) return nullptr; } +Group* Group::findGroupByPath(QString groupPath, QString basePath) +{ + + Q_ASSERT(!groupPath.isNull()); + + QStringList possiblePaths; + possiblePaths << groupPath; + if (!groupPath.startsWith("/")) { + possiblePaths << QString("/" + groupPath); + } + if (!groupPath.endsWith("/")) { + possiblePaths << QString(groupPath + "/"); + } + if (!groupPath.endsWith("/") && !groupPath.endsWith("/")) { + possiblePaths << QString("/" + groupPath + "/"); + } + + if (possiblePaths.contains(basePath)) { + return this; + } + + for (Group* innerGroup : children()) { + QString innerBasePath = basePath + innerGroup->name() + "/"; + Group* group = innerGroup->findGroupByPath(groupPath, innerBasePath); + if (group != nullptr) { + return group; + } + } + + return nullptr; + +} + QString Group::print(bool printUuids, QString baseName, int depth) { diff --git a/src/core/Group.h b/src/core/Group.h index 7c76e412d..f8c976eeb 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -79,10 +79,11 @@ public: static const int DefaultIconNumber; static const int RecycleBinIconNumber; + Group* findChildByName(const QString& name); Entry* findEntry(QString entryId); Entry* findEntryByUuid(const Uuid& uuid); Entry* findEntryByPath(QString entryPath, QString basePath = QString("")); - Group* findChildByName(const QString& name); + Group* findGroupByPath(QString groupPath, QString basePath = QString("/")); void setUuid(const Uuid& uuid); void setName(const QString& name); void setNotes(const QString& notes); diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index d2a8465bf..50997dcca 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -599,6 +599,15 @@ void TestGroup::testFindEntry() QVERIFY(entry != nullptr); QCOMPARE(entry->title(), QString("entry1")); + // We also can find the entry with the leading slash. + entry = db->rootGroup()->findEntry(QString("/entry1")); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry1")); + + // But two slashes should not be accepted. + entry = db->rootGroup()->findEntry(QString("//entry1")); + QVERIFY(entry == nullptr); + entry = db->rootGroup()->findEntry(entry2->uuid().toHex()); QVERIFY(entry != nullptr); QCOMPARE(entry->title(), QString("entry2")); @@ -607,6 +616,14 @@ void TestGroup::testFindEntry() QVERIFY(entry != nullptr); QCOMPARE(entry->title(), QString("entry2")); + entry = db->rootGroup()->findEntry(QString("/entry2")); + QVERIFY(entry == nullptr); + + // We also can find the entry with the leading slash. + entry = db->rootGroup()->findEntry(QString("/group1/entry2")); + QVERIFY(entry != nullptr); + QCOMPARE(entry->title(), QString("entry2")); + // Should also find the entry only by title. entry = db->rootGroup()->findEntry(QString("entry2")); QVERIFY(entry != nullptr); @@ -629,6 +646,70 @@ void TestGroup::testFindEntry() delete db; } +void TestGroup::testFindGroupByPath() +{ + Database* db = new Database(); + + Group* group1 = new Group(); + group1->setName("group1"); + group1->setParent(db->rootGroup()); + + Group* group2 = new Group(); + group2->setName("group2"); + group2->setParent(group1); + + Group* group; + + group = db->rootGroup()->findGroupByPath("/"); + QVERIFY(group != nullptr); + QCOMPARE(group->uuid(), db->rootGroup()->uuid()); + + // We also accept it if the leading slash is missing. + group = db->rootGroup()->findGroupByPath(""); + QVERIFY(group != nullptr); + QCOMPARE(group->uuid(), db->rootGroup()->uuid()); + + group = db->rootGroup()->findGroupByPath("/group1/"); + QVERIFY(group != nullptr); + QCOMPARE(group->uuid(), group1->uuid()); + + // We also accept it if the leading slash is missing. + group = db->rootGroup()->findGroupByPath("group1/"); + QVERIFY(group != nullptr); + QCOMPARE(group->uuid(), group1->uuid()); + + // Too many slashes at the end + group = db->rootGroup()->findGroupByPath("group1//"); + QVERIFY(group == nullptr); + + // Missing a slash at the end. + group = db->rootGroup()->findGroupByPath("/group1"); + QVERIFY(group != nullptr); + QCOMPARE(group->uuid(), group1->uuid()); + + // Too many slashes at the start + group = db->rootGroup()->findGroupByPath("//group1"); + QVERIFY(group == nullptr); + + group = db->rootGroup()->findGroupByPath("/group1/group2/"); + QVERIFY(group != nullptr); + QCOMPARE(group->uuid(), group2->uuid()); + + // We also accept it if the leading slash is missing. + group = db->rootGroup()->findGroupByPath("group1/group2/"); + QVERIFY(group != nullptr); + QCOMPARE(group->uuid(), group2->uuid()); + + group = db->rootGroup()->findGroupByPath("group1/group2"); + QVERIFY(group != nullptr); + QCOMPARE(group->uuid(), group2->uuid()); + + group = db->rootGroup()->findGroupByPath("invalid"); + QVERIFY(group == nullptr); + + delete db; +} + void TestGroup::testPrint() { Database* db = new Database(); diff --git a/tests/TestGroup.h b/tests/TestGroup.h index 9b36ebabc..16bb42acd 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -40,6 +40,7 @@ private slots: void testMergeDatabase(); void testMergeConflictKeepBoth(); void testFindEntry(); + void testFindGroupByPath(); void testPrint(); private: From 95baf256484daec3edc04ef13902f2998b91f8f2 Mon Sep 17 00:00:00 2001 From: louib Date: Wed, 21 Jun 2017 19:09:44 -0400 Subject: [PATCH 329/333] failure when showing help (#658) --- src/cli/Clip.cpp | 3 +-- src/cli/Extract.cpp | 3 +-- src/cli/List.cpp | 3 +-- src/cli/Merge.cpp | 3 +-- src/cli/Show.cpp | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index 106fed6cd..70d97c0e2 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -55,8 +55,7 @@ int Clip::execute(int argc, char** argv) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { QCoreApplication app(argc, argv); - parser.showHelp(); - return EXIT_FAILURE; + parser.showHelp(EXIT_FAILURE); } Database* db = nullptr; diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 36dde473b..b9433801c 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -44,8 +44,7 @@ int Extract::execute(int argc, char** argv) const QStringList args = parser.positionalArguments(); if (args.size() != 1) { - parser.showHelp(); - return EXIT_FAILURE; + parser.showHelp(EXIT_FAILURE); } out << "Insert the database password\n> "; diff --git a/src/cli/List.cpp b/src/cli/List.cpp index b0b791882..de6bd5ef5 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -62,8 +62,7 @@ int List::execute(int argc, char** argv) const QStringList args = parser.positionalArguments(); if (args.size() != 1 && args.size() != 2) { QCoreApplication app(argc, argv); - parser.showHelp(); - return EXIT_FAILURE; + parser.showHelp(EXIT_FAILURE); } Database* db = nullptr; diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index c69dcd604..a179d69be 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -61,8 +61,7 @@ int Merge::execute(int argc, char** argv) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { QCoreApplication app(argc, argv); - parser.showHelp(); - return EXIT_FAILURE; + parser.showHelp(EXIT_FAILURE); } Database* db1; diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index 680f7a452..c212a9d61 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -44,8 +44,7 @@ int Show::execute(int argc, char** argv) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - parser.showHelp(); - return EXIT_FAILURE; + parser.showHelp(EXIT_FAILURE); } out << "Insert the database password\n> "; From fdbed324f781eb57db06e8944af68f5c4144bd73 Mon Sep 17 00:00:00 2001 From: louib Date: Thu, 22 Jun 2017 16:25:24 -0400 Subject: [PATCH 330/333] Outputing newline after password prompt. (#659) --- src/cli/PasswordInput.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli/PasswordInput.cpp b/src/cli/PasswordInput.cpp index 9436f4918..16913e956 100644 --- a/src/cli/PasswordInput.cpp +++ b/src/cli/PasswordInput.cpp @@ -63,10 +63,15 @@ void PasswordInput::setStdinEcho(bool enable = true) QString PasswordInput::getPassword() { static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + static QTextStream outputTextStream(stdout, QIODevice::WriteOnly); setStdinEcho(false); QString line = inputTextStream.readLine(); setStdinEcho(true); + // The new line was also not echoed, but we do want to echo it. + outputTextStream << "\n"; + outputTextStream.flush(); + return line; } From b8028ff318d0bfbc6ad7a2c3a14c856aff31eb60 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 24 Jun 2017 11:24:41 -0400 Subject: [PATCH 331/333] Updated snapcraft file to compile on Ubuntu 17.04 --- snapcraft.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/snapcraft.yaml b/snapcraft.yaml index 0832ef48a..c05ad2aab 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -36,3 +36,15 @@ parts: - libyubikey-dev - libykpers-1-dev after: [desktop-qt5] + desktop-qt5: + # Redefine stage packages to work with Ubuntu 17.04 + stage-packages: + - libxkbcommon0 + - ttf-ubuntu-font-family + - dmz-cursor-theme + - light-themes + - shared-mime-info + - libqt5gui5 + - libgdk-pixbuf2.0-0 + - libqt5svg5 # for loading icon themes which are svg + - locales-all From 836c996544d13154cef8cbaf2d6cfe510edb1144 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 25 Jun 2017 17:15:20 -0400 Subject: [PATCH 332/333] Cleanup before release * Cleanup cpack commands * Add default config for portable install * Force translation downloads * Reduce translation download threshold to 40% --- share/keepassxc.ini | 57 ++++++++++++++++++++++++++++++++++++ share/translations/update.sh | 2 +- src/CMakeLists.txt | 9 +++++- 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 share/keepassxc.ini diff --git a/share/keepassxc.ini b/share/keepassxc.ini new file mode 100644 index 000000000..f7ff52cbc --- /dev/null +++ b/share/keepassxc.ini @@ -0,0 +1,57 @@ +[General] +ShowToolbar=true +RememberLastDatabases=true +RememberLastKeyFiles=true +OpenPreviousDatabasesOnStartup=true +AutoSaveAfterEveryChange=false +AutoSaveOnExit=false +AutoReloadOnChange=true +MinimizeOnCopy=false +UseGroupIconOnEntryCreation=true +IgnoreGroupExpansion=false +AutoTypeEntryTitleMatch=true +GlobalAutoTypeKey=0 +GlobalAutoTypeModifiers=0 +LastOpenedDatabases=@Invalid() + +[GUI] +Language=system +ShowTrayIcon=false +MinimizeToTray=false +MinimizeOnClose=false +MinimizeOnStartup=false +MainWindowGeometry="@ByteArray(\x1\xd9\xd0\xcb\0\x2\0\0\0\0\x2(\0\0\0\xbd\0\0\x5W\0\0\x3;\0\0\x2\x30\0\0\0\xdc\0\0\x5O\0\0\x3\x33\0\0\0\0\0\0\0\0\a\x80)" +SplitterState=@Invalid() +EntryListColumnSizes=@Invalid() +EntrySearchColumnSizes=@Invalid() + +[security] +autotypeask=true +clearclipboard=true +clearclipboardtimeout=10 +lockdatabaseidle=false +lockdatabaseidlesec=240 +lockdatabaseminimize=false +lockdatabasescreenlock=true +passwordscleartext=false +passwordsrepeat=false + +[Http] +Enabled=false +ShowNotification=true +BestMatchOnly=false +UnlockDatabase=true +MatchUrlScheme=true +SortByUsername=false +Port=19455 +AlwaysAllowAccess=false +AlwaysAllowUpdate=false +SearchInAllDatabases=false +SupportKphFields=true +generator\LowerCase=true +generator\UpperCase=true +generator\Numbers=true +generator\SpecialChars=false +generator\ExcludeAlike=true +generator\EnsureEvery=true +generator\Length=16 diff --git a/share/translations/update.sh b/share/translations/update.sh index 7e8069e0d..eaa1179d4 100755 --- a/share/translations/update.sh +++ b/share/translations/update.sh @@ -14,4 +14,4 @@ tx push -s echo echo Pulling translations from Transifex -tx pull -a --minimum-perc=80 +tx pull -af --minimum-perc=40 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cc9334842..791685576 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -290,7 +290,12 @@ if(APPLE AND WITH_APP_BUNDLE) endif() if(MINGW) - string(REPLACE "AMD" "Win" OUTPUT_FILE_POSTFIX "${CMAKE_HOST_SYSTEM_PROCESSOR}") + if(${CMAKE_SIZEOF_VOID_P} EQUAL "8") + set(OUTPUT_FILE_POSTFIX "Win64") + else() + set(OUTPUT_FILE_POSTFIX "Win32") + endif() + set(CPACK_GENERATOR "ZIP;NSIS") set(CPACK_STRIP_FILES ON) set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}-${OUTPUT_FILE_POSTFIX}") @@ -299,6 +304,7 @@ if(MINGW) set(CPACK_PACKAGE_VENDOR "${PROGNAME} Team") string(REGEX REPLACE "/" "\\\\\\\\" CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/share/windows/installer-header.bmp") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.GPL-2") + set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico") set(CPACK_NSIS_MUI_UNIICON "${CPACK_NSIS_MUI_ICON}") set(CPACK_NSIS_INSTALLED_ICON_NAME "\\\\${PROGNAME}.exe") @@ -307,6 +313,7 @@ if(MINGW) set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${PROGNAME}.lnk' '$INSTDIR\\\\${PROGNAME}.exe'") set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\${PROGNAME}.lnk'") set(CPACK_NSIS_URL_INFO_ABOUT "https://keepassxc.org") + set(CPACK_NSIS_DISPLAY_NAME ${PROGNAME}) set(CPACK_NSIS_PACKAGE_NAME "${PROGNAME} v${KEEPASSXC_VERSION}") set(CPACK_NSIS_MUI_FINISHPAGE_RUN "../${PROGNAME}.exe") include(CPack) From 9a6a78719118d9cd79232ec37ba6b8b8a0ef8d9a Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 25 Jun 2017 17:52:35 -0400 Subject: [PATCH 333/333] Update translations --- share/translations/keepassx_cs.ts | 881 ++++++++-- share/translations/keepassx_da.ts | 1662 ++++++++++++++---- share/translations/keepassx_de.ts | 877 ++++++++-- share/translations/keepassx_el.ts | 1664 ++++++++++++++---- share/translations/keepassx_en.ts | 833 +++++++-- share/translations/keepassx_es.ts | 885 ++++++++-- share/translations/keepassx_fi.ts | 2384 +++++++++++++++++++++++++ share/translations/keepassx_fr.ts | 887 ++++++++-- share/translations/keepassx_id.ts | 1662 ++++++++++++++---- share/translations/keepassx_it.ts | 885 ++++++++-- share/translations/keepassx_ja.ts | 1660 ++++++++++++++---- share/translations/keepassx_kk.ts | 2390 ++++++++++++++++++++++++++ share/translations/keepassx_ko.ts | 1660 ++++++++++++++---- share/translations/keepassx_lt.ts | 878 ++++++++-- share/translations/keepassx_nl_NL.ts | 883 ++++++++-- share/translations/keepassx_pl.ts | 887 ++++++++-- share/translations/keepassx_pt_BR.ts | 883 ++++++++-- share/translations/keepassx_pt_PT.ts | 881 ++++++++-- share/translations/keepassx_ru.ts | 991 ++++++++--- share/translations/keepassx_sl_SI.ts | 1665 ++++++++++++++---- share/translations/keepassx_sv.ts | 1660 ++++++++++++++---- share/translations/keepassx_uk.ts | 1665 ++++++++++++++---- share/translations/keepassx_zh_CN.ts | 879 ++++++++-- share/translations/keepassx_zh_TW.ts | 1669 ++++++++++++++---- 24 files changed, 26425 insertions(+), 4846 deletions(-) create mode 100644 share/translations/keepassx_fi.ts create mode 100644 share/translations/keepassx_kk.ts diff --git a/share/translations/keepassx_cs.ts b/share/translations/keepassx_cs.ts index f54a1db8e..cdd273667 100644 --- a/share/translations/keepassx_cs.ts +++ b/share/translations/keepassx_cs.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - Revize - - - Using: - S použitím: - About KeePassXC O aplikaci KeePassXC - Extensions: - - Rozšíření: - + About + O aplikaci - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC je šířeno pod GNU obecnou veřejnou licencí (GPL) verze 2 a (případně) 3. + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -120,10 +200,6 @@ Umožnit přístup? Create Key File... Vytvořit soubor s klíčem… - - Error - Chyba - Unable to create Key File : Nedaří se vytvořit soubor s klíčem: @@ -132,10 +208,6 @@ Umožnit přístup? Select a key file Vyberte soubor s klíčem - - Question - Dotaz - Do you really want to use an empty string as password? Opravdu ponechat bez hesla, tedy nechráněné? @@ -144,10 +216,6 @@ Umožnit přístup? Different passwords supplied. Nepodařilo se vám zadat heslo stejně do obou kolonek. - - Failed to set key file - Nepodařilo se nastavit soubor s klíčem - Failed to set %1 as the Key file: %2 @@ -158,6 +226,163 @@ Umožnit přístup? &Key file Soubor s &klíčem + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Chyba + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Chyba + + + Unable to calculate master key + Nedaří se spočítat hlavní klíč + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -177,10 +402,6 @@ Umožnit přístup? Browse Procházet - - Error - Chyba - Unable to open the database. Databázi se nedaří otevřít. @@ -201,6 +422,14 @@ Umožnit přístup? Select key file Vyberte soubor s klíčem + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -277,6 +506,18 @@ Nyní je možné ji uložit. Use recycle bin Namísto mazání přesouvat do Koše + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -296,10 +537,6 @@ Nyní je možné ji uložit. Open database Otevřít databázi - - Warning - Varování - File not found! Soubor nebyl nalezen! @@ -330,10 +567,6 @@ Save changes? „%1“ bylo změněno. Uložit změny? - - Error - Chyba - Writing the database failed. Zápis do databáze se nezdařil. @@ -426,6 +659,14 @@ Chcete ji přesto otevřít? Open read-only Otevřít pouze pro čtení + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -465,10 +706,6 @@ Chcete ji přesto otevřít? Do you really want to delete the group "%1" for good? Opravdu chcete nenávratně smazat skupinu „%1“? - - Error - Chyba - Unable to calculate master key Nedaří se spočítat hlavní klíč @@ -529,14 +766,18 @@ Chcete ji přesto otevřít? The database file has changed and you have unsaved changes.Do you want to merge your changes? Soubor s databází byl změněn a vaše změny do něj nejsou uloženy. Přejete si své změny začlenit? - - Autoreload Failed - Automatické opětovné načtení se nezdařilo - Could not open the new database file while attempting to autoreload this database. Nepodařilo se otevřít nový soubor s databází během pokusu o opětovné načtení této. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -576,10 +817,6 @@ Chcete ji přesto otevřít? Edit entry Upravit záznam - - Error - Chyba - Different passwords supplied. Nepodařilo se vám zadat heslo stejně do obou kolonek. @@ -622,6 +859,22 @@ Chcete ji přesto otevřít? 1 year 1 rok + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -633,10 +886,6 @@ Chcete ji přesto otevřít? Add Přidat - - Edit - Upravit - Remove Odebrat @@ -653,6 +902,18 @@ Chcete ji přesto otevřít? Open Otevřít + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -688,6 +949,10 @@ Chcete ji přesto otevřít? Set custo&m sequence: Nastavit vlastní posloupnost: + + Window Associations + + EditEntryWidgetHistory @@ -797,16 +1062,16 @@ Chcete ji přesto otevřít? Hledat - Auto-type + Auto-Type Automatické vyplňování - Use default auto-type sequence of parent group - Použít výchozí posloupnost automatického vyplňování od nadřazené skupiny + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Nastavit výchozí posloupnost automatického vyplňování + Set default Auto-Type se&quence + @@ -831,10 +1096,6 @@ Chcete ji přesto otevřít? Select Image Vyberte obrázek - - Can't delete icon! - Ikonu nelze smazat! - Error Chyba @@ -851,10 +1112,6 @@ Chcete ji přesto otevřít? Can't read icon Ikonu se nedaří načíst - - Can't delete icon. Still used by %1 items. - Ikonu nelze smazat, protože je používaná ještě %1 dalšími záznamy. - &Use default icon Po&užít výchozí ikonu @@ -863,6 +1120,14 @@ Chcete ji přesto otevřít? Use custo&m icon Použít svou vlastní ikonu + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -934,6 +1199,11 @@ Chcete ji přesto otevřít? URL URL adresa + + Ref: + Reference abbreviation + + Group @@ -992,9 +1262,16 @@ Chcete ji přesto otevřít? Ensure that the password contains characters from every group Zajistit aby heslo obsahovalo znaky ze všech zvolených skupin znaků + + + KMessageWidget - Accept - Přijmout + &Close + + + + Close message + @@ -1003,10 +1280,6 @@ Chcete ji přesto otevřít? Import KeePass1 database Importovat databázi ve formátu KeePass verze 1 - - Error - Chyba - Unable to open the database. Databázi se nedaří otevřít. @@ -1071,6 +1344,10 @@ This is a one-way migration. You won't be able to open the imported databas Můžete ho importovat pomocí Databáze → Importovat databázi ve formátu KeePass 1. Jedná se o jednosměrný převod. Databázi, vzniklou z importu, nepůjde otevřít ve staré verzi KeePassX 0.4. + + Unable to issue challenge-response. + + Main @@ -1082,13 +1359,17 @@ Jedná se o jednosměrný převod. Databázi, vzniklou z importu, nepůjde otev KeePassXC - Error KeePassXC – chyba + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Databáze - Open database Otevřít databázi @@ -1121,10 +1402,6 @@ Jedná se o jednosměrný převod. Databázi, vzniklou z importu, nepůjde otev Toggle window Zobrazit/skrýt okno - - Tools - Nástroje - KeePass 2 Database Databáze ve formátu KeePass 2 @@ -1137,10 +1414,6 @@ Jedná se o jednosměrný převod. Databázi, vzniklou z importu, nepůjde otev Save repaired database Uložit opravenou databázi - - Error - Chyba - Writing the database failed. Zápis do databáze se nezdařil. @@ -1233,14 +1506,26 @@ Jedná se o jednosměrný převod. Databázi, vzniklou z importu, nepůjde otev &Database settings Nastavení &databáze - - &Import KeePass 1 database - &Importovat databázi ve formátu KeePass 1 - &Clone entry Klonovat záznam + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find Najít @@ -1293,6 +1578,46 @@ Jedná se o jednosměrný převod. Databázi, vzniklou z importu, nepůjde otev Password Generator Generátor hesel + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importovat databázi aplikace KeePass verze 1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1308,12 +1633,6 @@ Jedná se o jednosměrný převod. Databázi, vzniklou z importu, nepůjde otev Sh&ow a notification when credentials are requested Z&obrazit oznámení když jsou požadovány přihlašovací údaje - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - &Odpovídající schémata URL adres -Je odpovídáno pouze záznamy se stejným schématem (http://, https://, ftp://, atp.) - Sort matching entries by &username Seřadit odpovídající záznamy dle &uživatelského jména @@ -1322,10 +1641,6 @@ Je odpovídáno pouze záznamy se stejným schématem (http://, https://, ftp:// Re&move all stored permissions from entries in active database Z právě otevřené databáze odebrat veškerá uložená oprávnění - - Password generator - Generátor hesel - Advanced Pokročilé @@ -1342,10 +1657,6 @@ Je odpovídáno pouze záznamy se stejným schématem (http://, https://, ftp:// Searc&h in all opened databases for matching entries Vy&hledat odpovídající záznamy ve všech otevřených databázích - - Only the selected database has to be connected with a client! - Je třeba, aby ke klientovi byly připojené pouze vybrané databáze! - HTTP Port: HTTP port: @@ -1362,12 +1673,6 @@ Je odpovídáno pouze záznamy se stejným schématem (http://, https://, ftp:// Sort &matching entries by title Seřadit odpovídající záznamy dle názvu - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Zapnout protokol KeePassXC HTTP -Toto je zapotřebí pro přístup do databáze z doplňku ChromeIPass nebo PassIFox pro webové prohlížeče - KeePassXC will listen to this port on 127.0.0.1 KeePassXC bude očekávat spojení na tomto portu na adrese 127.0.0.1 (localhost) @@ -1381,21 +1686,11 @@ Toto je zapotřebí pro přístup do databáze z doplňku ChromeIPass nebo PassI Using default port 19455. Není možné navázat na porty s číslem nižším, než 1024! Náhradně bude použit port 19455. - - - &Return only best matching entries for a URL instead -of all entries for the whole domain - Odpovědět pouze záznamy, které nejlépe odpovídají dané -URL ad&rese namísto záznamů pro celou doménu R&emove all shared encryption keys from active database Z právě otevřené databáze od&ebrat veškeré sdílené šifrovací klíče - - The following options can be dangerous. Change them only if you know what you are doing. - Následující předvolby mohou být nebezpečné. Měňte je pouze pokud víte, co děláte! - &Return advanced string fields which start with "KPH: " Odpovědět také kolonkami pok&ročilých textových řetězců které začínají na „KPH:“ @@ -1404,6 +1699,43 @@ URL ad&rese namísto záznamů pro celou doménu Automatically creating or updating string fields is not supported. Automatická vytváření nebo aktualizace nejsou u textových kolonek podporované! + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Generátor hesel + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1495,12 +1827,101 @@ URL ad&rese namísto záznamů pro celou doménu Excellent Skvělé + + Password + Heslo + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Skupina + + + Title + Titulek + + + Username + Uživatelské jméno + + + Password + Heslo + + + URL + URL adresa + + + Notes + Poznámky + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1547,14 +1968,18 @@ URL ad&rese namísto záznamů pro celou doménu Search Hledat - - Find - Najít - Clear Vyčistit + + Search... + + + + Limit search to selected group + + Service @@ -1661,6 +2086,10 @@ jedinečný název pro identifikaci a potvrďte ho. Security Zabezpečení + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1688,10 +2117,6 @@ jedinečný název pro identifikaci a potvrďte ho. Global Auto-Type shortcut Klávesová zkratka pro všeobecné automatické vyplňování - - Use entry title to match windows for global auto-type - Všeobecné automatické vyplňování provádět na základě shody titulku záznamu s titulkem okna. - Language Jazyk @@ -1704,10 +2129,6 @@ jedinečný název pro identifikaci a potvrďte ho. Hide window to system tray when minimized Minimalizovat okno aplikace do oznamovací oblasti systémového panelu - - Remember last key files - Pamatovat si nedávno otevřené soubory s klíči - Load previous databases on startup Při spouštění aplikace načíst minule otevřené databáze @@ -1724,6 +2145,30 @@ jedinečný název pro identifikaci a potvrďte ho. Minimize window at application startup Spouštět aplikaci s minimalizovaným oknem + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Automatické vyplňování + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1743,10 +2188,6 @@ jedinečný název pro identifikaci a potvrďte ho. Show passwords in cleartext by default Hesla vždy viditelná (nezakrývat hvězdičkami) - - Always ask before performing auto-type - Před provedením automatického vyplnění se vždy dotázat - Lock databases after minimizing the window Při minimalizaci okna uzamknout databáze @@ -1755,6 +2196,80 @@ jedinečný název pro identifikaci a potvrďte ho. Don't require password repeat when it is visible Pokud je viditelné, nevyžadovat zopakování zadání hesla + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + sek. + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1766,8 +2281,32 @@ jedinečný název pro identifikaci a potvrďte ho. WelcomeWidget - Welcome! - Vítejte! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Nedávno otevřené databáze @@ -1792,5 +2331,69 @@ jedinečný název pro identifikaci a potvrďte ho. filenames of the password databases to open (*.kdbx) soubory s databázemi hesel k otevření (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_da.ts b/share/translations/keepassx_da.ts index 3828f05eb..0e2f63c33 100644 --- a/share/translations/keepassx_da.ts +++ b/share/translations/keepassx_da.ts @@ -1,33 +1,143 @@ - + AboutDialog - About KeePassX - Om KeePassX + About KeePassXC + - KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassX distribueres under betingelserne i GNU General Public License (GPL) version 2 eller (efter eget valg) version 3. + About + Om - Revision - Revision + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + - Using: - Bruger: + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + + + + Allow + + + + Deny + + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + + + + KeePassXC HTTP Confirm Access + AutoType - - Auto-Type - KeePassX - Auto-indsæt - KeePassX - Couldn't find an entry that matches the window title: Kunne ikke finde en post, der matcher vinduets titel: + + Auto-Type - KeePassXC + + AutoTypeAssociationsModel @@ -46,14 +156,14 @@ AutoTypeSelectDialog - - Auto-Type - KeePassX - Auto-indsæt - KeePassX - Select entry to Auto-Type: Vælg post til Auto-Indsæt: + + Auto-Type - KeePassXC + + ChangeMasterKeyWidget @@ -69,10 +179,6 @@ Repeat password: Gentag kodeord - - Key file - Nøglefil - Browse Gennemse @@ -93,10 +199,6 @@ Create Key File... Opret Nøglefil... - - Error - Fejl - Unable to create Key File : Kan ikke oprette Nøglefil : @@ -105,10 +207,6 @@ Select a key file Vælg en nøglefil - - Question - Spørgsmål - Do you really want to use an empty string as password? Vil du virkelig bruge en tom streng som kodeord? @@ -117,16 +215,173 @@ Different passwords supplied. Andre kodeord leveret. - - Failed to set key file - Kan ikke sætte nøglefil - Failed to set %1 as the Key file: %2 Kunne ikke sætte %1 som Nøglefil: %2 + + &Key file + + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Fejl + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Fejl + + + Unable to calculate master key + Kan ikke beregne hovednøgle + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -146,10 +401,6 @@ Browse Gennemse - - Error - Fejl - Unable to open the database. Kan ikke åbne databasen. @@ -170,6 +421,14 @@ Select key file Vælg nøglefil + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -226,10 +485,6 @@ Du kan gemme den nu. Default username: Standard brugernavn: - - Use recycle bin: - Brug skraldespand: - MiB MB @@ -246,6 +501,22 @@ Du kan gemme den nu. Max. history size: Maks. historikstørrelse: + + Use recycle bin + + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -265,10 +536,6 @@ Du kan gemme den nu. Open database Åben database - - Warning - Advarsel - File not found! Filen blev ikke fundet! @@ -299,10 +566,6 @@ Save changes? "%1" blev ændret. Gem disse ændringer? - - Error - Fejl - Writing the database failed. Kan ikke skrive til databasen. @@ -319,12 +582,6 @@ Gem disse ændringer? locked låst - - The database you are trying to open is locked by another instance of KeePassX. -Do you want to open it anyway? Alternatively the database is opened read-only. - Den database, du prøver at åbne er låst af en anden forekomst af KeePassX. -Vil du åbne den alligevel? Alternativt åbnes databasen skrivebeskyttet. - Lock database Lås database @@ -368,13 +625,42 @@ Kassér ændringer og luk alligevel? Kan ikke skrive til CSV-fil. - The database you are trying to save as is locked by another instance of KeePassX. -Do you want to save it anyway? - Databasen som du prøver at gemme er låst af en anden instans af KeePassX. -Vil du alligevel gemme? + Unable to open the database. + Kan ikke åbne databasen. - Unable to open the database. + Merge database + + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + + + + Passwords + + + + Database already opened + + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + + + + File opened in read only mode. + + + + Open CSV file @@ -416,14 +702,6 @@ Vil du alligevel gemme? Do you really want to delete the group "%1" for good? Ønsker du at slette gruppen "%1" permanent? - - Current group - Nuværende gruppe - - - Error - Fejl - Unable to calculate master key Kan ikke beregne hovednøgle @@ -436,6 +714,66 @@ Vil du alligevel gemme? Do you really want to move entry "%1" to the recycle bin? + + Searching... + + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + + + + No Results + + + + Execute command? + + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -475,10 +813,6 @@ Vil du alligevel gemme? Edit entry Rediger post - - Error - Fejl - Different passwords supplied. Andre kodeord leveret. @@ -520,6 +854,22 @@ Vil du alligevel gemme? 1 year Et år + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -531,10 +881,6 @@ Vil du alligevel gemme? Add Tilføj - - Edit - Rediger - Remove Fjern @@ -551,6 +897,18 @@ Vil du alligevel gemme? Open Åben + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -558,14 +916,6 @@ Vil du alligevel gemme? Enable Auto-Type for this entry Aktivér Auto-Indsæt for denne post - - Inherit default Auto-Type sequence from the group - Nedarv standard Auto-Indsæt sekvens fra gruppe - - - Use custom Auto-Type sequence: - Brug brugerdefineret Auto-indsæt sekvens: - + + @@ -579,12 +929,24 @@ Vil du alligevel gemme? Vinduestitel: - Use default sequence - Brug standardsekvens + Inherit default Auto-Type sequence from the &group + - Set custom sequence: - Definér brugervalgt sekvens: + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + @@ -624,10 +986,6 @@ Vil du alligevel gemme? Repeat: Gentag: - - Gen. - Generer - URL: URL: @@ -699,28 +1057,20 @@ Vil du alligevel gemme? Søg - Auto-type - Auto-indsæt + Auto-Type + Auto-Indsæt - Use default auto-type sequence of parent group - Brug standard Auto-Indsæt sekvens fra forældregruppe + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Definér standard auto-indsæt sekvens + Set default Auto-Type se&quence + EditWidgetIcons - - Use default icon - Brug standardikon - - - Use custom icon - Brug brugerbestemt ikon - Add custom icon Tilføj brugerbestemt ikon @@ -742,19 +1092,35 @@ Vil du alligevel gemme? Vælg Billede - Can't delete icon! - Kan ikke slette ikon! - - - Can't delete icon. Still used by %n item(s). - Kan ikke slette ikonet. Det anvendes stadig af %n element.Kan ikke slette ikonet. Det anvendes stadig af %n elementer. + Error + Fejl - Error + Download favicon - Can't read icon: + Unable to fetch favicon. + + + + Can't read icon + + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? @@ -777,6 +1143,13 @@ Vil du alligevel gemme? Uuid: + + Entry + + - Clone + + + EntryAttributesModel @@ -821,6 +1194,11 @@ Vil du alligevel gemme? URL URL + + Ref: + Reference abbreviation + + Group @@ -829,16 +1207,74 @@ Vil du alligevel gemme? Skraldespand + + HttpPasswordGeneratorWidget + + Length: + Længde: + + + Character Types + Tegntyper + + + Upper Case Letters + Store Bogstaver + + + A-Z + + + + Lower Case Letters + Små Bogstaver + + + a-z + + + + Numbers + Numre + + + 0-9 + + + + Special Characters + Specialtegn + + + /*_& ... + + + + Exclude look-alike characters + Udeluk lool-alike tegn + + + Ensure that the password contains characters from every group + Vær sikker på at dit kodeord indeholder tegn fra alle grupper + + + + KMessageWidget + + &Close + + + + Close message + + + KeePass1OpenWidget Import KeePass1 database Importér KeePass1 database - - Error - Fejl - Unable to open the database. Kan ikke åbne databasen. @@ -872,7 +1308,7 @@ Vil du alligevel gemme? Wrong key or database file is corrupt. - + Forkert nøgle eller databasefil er korrupt. @@ -903,6 +1339,10 @@ This is a one-way migration. You won't be able to open the imported databas Du kan importere den ved at klikke på Database > 'Importér KeePass 1 database'. Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den importerede database med den gamle KeePassX 0.4 version. + + Unable to issue challenge-response. + + Main @@ -911,112 +1351,28 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Fatal fejl ved test af kryptografiske funktioner. - KeePassX - Error - KeePassX - Fejl + KeePassXC - Error + + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - Database - - - Recent databases - Seneste databaser - - - Help - Hjælp - - - Entries - Poster - - - Copy attribute to clipboard - Kopiér attribut til udklipsholder - - - Groups - Grupper - - - View - Vis - - - Quit - Afslut - - - About - Om - Open database Åben database - - Save database - Gem database - - - Close database - Luk databasen - - - New database - Ny database - - - Add new entry - Tilføj ny post - - - View/Edit entry - Vis/Rediger post - - - Delete entry - Slet post - - - Add new group - Tilføj ny gruppe - - - Edit group - Rediger gruppe - - - Delete group - Slet gruppe - - - Save database as - Gem database som - - - Change master key - Skift hovednøgle - Database settings Databaseindstillinger - - Import KeePass 1 database - Importér KeePass 1 database - - - Clone entry - Klon post - - - Find - Find - Copy username to clipboard Kopiér brugernavn til udklipsholder @@ -1029,30 +1385,6 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Settings Indstillinger - - Perform Auto-Type - Udfør Auto-indsæt - - - Open URL - Åben URL - - - Lock databases - Lås databaser - - - Title - Titel - - - URL - URL - - - Notes - Noter - Show toolbar Vis værktøjslinie @@ -1065,26 +1397,6 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Toggle window Skift vindue - - Tools - Værktøj - - - Copy username - Kopiér brugernavn - - - Copy password - Kopiér kodeord - - - Export to CSV file - Eksportér til CSV-fil - - - Repair database - Reparer database - KeePass 2 Database KeePass 2 Database @@ -1097,14 +1409,327 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Save repaired database Gem repareret database - - Error - Fejl - Writing the database failed. Skrivning til database fejler. + + &Recent databases + + + + He&lp + + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + + + + &View + + + + &Quit + + + + &About + + + + &Open database + + + + &Save database + + + + &Close database + + + + &New database + + + + Merge from KeePassX database + + + + &Add new entry + + + + &View/Edit entry + + + + &Delete entry + + + + &Add new group + + + + &Edit group + + + + &Delete group + + + + Sa&ve database as + + + + Change &master key + + + + &Database settings + + + + &Clone entry + + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + + + + Copy &username + + + + Cop&y password + + + + &Settings + + + + &Perform Auto-Type + + + + &Open URL + + + + &Lock databases + + + + &Title + + + + &URL + + + + &Notes + + + + &Export to CSV file + + + + Re&pair database + + + + Password Generator + + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importér KeePass 1 database + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + + + + General + Generelt + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + Avanceret + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + + + + Default port: 19455 + + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1112,10 +1737,6 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Password: Kodeord: - - Length: - Længde: - Character Types Tegntyper @@ -1140,71 +1761,161 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Exclude look-alike characters Udeluk lool-alike tegn - - Ensure that the password contains characters from every group - Vær sikker på at dit kodeord indeholder tegn fra alle grupper - Accept Acceptér - - - QCommandLineParser - Displays version information. - Vis versionsinformation + %p% + - Displays this help. - Vis denne hjælp. + strength + - Unknown option '%1'. - Ukendt valgmulighed '%1'. + entropy + - Unknown options: %1. - Ukendt valgmuligheder '%1'. + &Length: + - Missing value after '%1'. - Manglende værdi efter '%1'. + Pick characters from every group + - Unexpected value after '%1'. - Uventet værdi efter '%1'. + Generate + - [options] - [Valgmuligheder] + Close + - Usage: %1 - Brug: %1 + Apply + - Options: - Muligheder: + Entropy: %1 bit + - Arguments: - Argumenter: + Password Quality: %1 + + + + Poor + + + + Weak + + + + Good + + + + Excellent + + + + Password + Kodeord + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + - QSaveFile + QObject - Existing file %1 is not writable - Eksisterende fil %1 er ikke skrivbar + NULL device + - Writing canceled by application - Skrivning afbrudt af programmet + error reading from device + - Partial write. Partition full? - Delvis gemt. Diskafsnit fyldt op? + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Gruppe + + + Title + Titel + + + Username + Brugernavn + + + Password + Kodeord + + + URL + URL + + + Notes + Noter + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1244,20 +1955,111 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo SearchWidget - Find: - Find: + Case Sensitive + - Case sensitive - Versalfølsom + Search + Søg - Current group - Nuværende gruppe + Clear + - Root group - Rodgruppe + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + @@ -1274,6 +2076,10 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Security Sikkerhed + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1281,10 +2087,6 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Remember last databases Husk seneste databaser - - Open previous databases on startup - Åben foregående databaser ved opstart - Automatically save on exit Gem automatisk ved afslutning @@ -1305,10 +2107,6 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Global Auto-Type shortcut Global Auto-Indsæt genvej - - Use entry title to match windows for global auto-type - Brug titel på post til at matche global aito-indsæt - Language Sprog @@ -1322,15 +2120,43 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Skjul vindue i systembakken når det er minimeret - Remember last key files - Husk de sidste nøglefiler - - - Hide window to system tray instead of App Exit + Load previous databases on startup - Hide window to system tray on App start + Automatically reload the database when modified externally + + + + Hide window to system tray instead of app exit + + + + Minimize window at application startup + + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Auto-Indsæt + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type @@ -1353,8 +2179,86 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo Vis kodeord i klartekst som standard - Always ask before performing auto-type - Spørg altid før auto-indsæt + Lock databases after minimizing the window + + + + Don't require password repeat when it is visible + + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + sek + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1367,20 +2271,36 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo WelcomeWidget - Welcome! - Velkommen! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Seneste databaser main - - KeePassX - cross-platform password manager - KeePassX - cross-platform password manager - - - filename of the password database to open (*.kdbx) - filnavn på databasen der skal åbnes (* .kdbx) - path to a custom config file sti til brugerdefineret indstillingsfil @@ -1389,5 +2309,81 @@ Dette er en envejs konvertering. Du vil ikke være i stand til at åbne den impo key file of the database databasens nøglefil + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_de.ts b/share/translations/keepassx_de.ts index 30d5330d7..f885b6484 100644 --- a/share/translations/keepassx_de.ts +++ b/share/translations/keepassx_de.ts @@ -1,27 +1,110 @@ AboutDialog - - Revision - Revision - - - Using: - Verwendet: - About KeePassXC Über KeePassXC - Extensions: + About + Über + + + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + Debug-Info + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + In Zwischenablage kopieren + + + Version %1 - Erweiterungen: + Version %1 - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC wird unter den Bedingungen der GNU General Public License (GPL) Version 2 oder Version 3 (je nach Ihrer Auswahl) vertrieben. + Revision: %1 + Revision: %1 + + + Libraries: + Bibliotheken: + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + Betriebssystem: %1 +CPU-Architektur: %2 +Kernel: %3 %4 + + + Enabled extensions: + Aktivierte Erweiterungen: @@ -120,10 +203,6 @@ Bitte wählen Sie, ob Sie den Zugriff erlauben möchten. Create Key File... Erzeuge eine Schlüsseldatei... - - Error - Fehler - Unable to create Key File : Erzeugen der Schlüsseldatei nicht möglich: @@ -132,10 +211,6 @@ Bitte wählen Sie, ob Sie den Zugriff erlauben möchten. Select a key file Schlüsseldatei auswählen - - Question - Frage - Do you really want to use an empty string as password? Wollen Sie wirklich eine leere Zeichenkette als Passwort verwenden? @@ -144,10 +219,6 @@ Bitte wählen Sie, ob Sie den Zugriff erlauben möchten. Different passwords supplied. Unterschiedliche Passwörter eingegeben. - - Failed to set key file - Festlegen der Schlüsseldatei nicht möglich. - Failed to set %1 as the Key file: %2 @@ -157,6 +228,163 @@ Bitte wählen Sie, ob Sie den Zugriff erlauben möchten. &Key file &Schlüsseldatei + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Fehler + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Fehler + + + Unable to calculate master key + Berechnung des "master keys" gescheitert + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -176,10 +404,6 @@ Bitte wählen Sie, ob Sie den Zugriff erlauben möchten. Browse Durchsuchen - - Error - Fehler - Unable to open the database. Öffnen der Datenbank nicht möglich. @@ -200,6 +424,14 @@ Bitte wählen Sie, ob Sie den Zugriff erlauben möchten. Select key file Schlüsseldatei auswählen + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -276,6 +508,18 @@ sie kann nun gespeichert werden. Use recycle bin Papierkorb verwenden + + AES: 256 Bit (default) + AES: 256 Bit (Standard) + + + Twofish: 256 Bit + Twofish: 256 Bit + + + Algorithm: + Algorithmus: + DatabaseTabWidget @@ -295,10 +539,6 @@ sie kann nun gespeichert werden. Open database Datenbank öffnen - - Warning - Warnung - File not found! Datei nicht gefunden! @@ -329,10 +569,6 @@ Save changes? "%1" wurde geändert. Änderungen speichern? - - Error - Fehler - Writing the database failed. Schreiben der Datenbank fehlgeschlagen. @@ -425,6 +661,14 @@ Möchten Sie diese dennoch öffnen? Open read-only Schreibgeschützt öffnen + + File opened in read only mode. + Datei ist schreibgeschützt + + + Open CSV file + + DatabaseWidget @@ -464,10 +708,6 @@ Möchten Sie diese dennoch öffnen? Do you really want to delete the group "%1" for good? Wollen Sie die Gruppe "%1" wirklich löschen? - - Error - Fehler - Unable to calculate master key Berechnung des "master keys" gescheitert @@ -528,14 +768,18 @@ Möchten Sie diese dennoch öffnen? The database file has changed and you have unsaved changes.Do you want to merge your changes? Die Datenbankdatei wurde geändert und Sie haben noch nicht gespeicherte Änderungen. Wollen Sie Ihre Änderungen zusammenführen? - - Autoreload Failed - Autoreload fehlgeschlagen - Could not open the new database file while attempting to autoreload this database. Die neue Datenbankdatei konnte nicht geöffnet werden, während versucht wurde, diese neu zu laden. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -575,10 +819,6 @@ Möchten Sie diese dennoch öffnen? Edit entry Eintrag bearbeiten - - Error - Fehler - Different passwords supplied. Unterschiedliche Passwörter eingegeben. @@ -620,6 +860,22 @@ Möchten Sie diese dennoch öffnen? 1 year 1 Jahr + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -631,10 +887,6 @@ Möchten Sie diese dennoch öffnen? Add Hinzufügen - - Edit - Bearbeiten - Remove Entfernen @@ -651,6 +903,18 @@ Möchten Sie diese dennoch öffnen? Open Offen + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -686,6 +950,10 @@ Möchten Sie diese dennoch öffnen? Set custo&m sequence: B&enutzerdefinierte Sequenz: + + Window Associations + Fenster-Einstellungen + EditEntryWidgetHistory @@ -795,15 +1063,15 @@ Möchten Sie diese dennoch öffnen? Suche - Auto-type + Auto-Type Auto-Type - Use default auto-type sequence of parent group + &Use default Auto-Type sequence of parent group Verwende Standard-A&uto-Type-Sequenz der übergeordneten Gruppe - Set default auto-type sequence + Set default Auto-Type se&quence Standard-Auto-Type-Se&quenz setzen @@ -829,10 +1097,6 @@ Möchten Sie diese dennoch öffnen? Select Image Bild auswählen - - Can't delete icon! - Symbol kann nicht gelöscht werden! - Error Fehler @@ -849,10 +1113,6 @@ Möchten Sie diese dennoch öffnen? Can't read icon Icon kann nicht gelesen werden - - Can't delete icon. Still used by %1 items. - Icon kann nicht gelöscht werden. Es wird noch von %1 Einträgen verwendet. - &Use default icon &Standardsymbol verwenden @@ -861,6 +1121,14 @@ Möchten Sie diese dennoch öffnen? Use custo&m icon B&enutzerdefiniertes Symbol verwenden + + Confirm Delete + Löschen bestätigen + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + Dieses Icon wird noch von %1 Einträgen verwendet und würde mit dem Standard-Icon ersetzt. Sind Sie sicher, dass die fortfahren wollen? + EditWidgetProperties @@ -932,6 +1200,11 @@ Möchten Sie diese dennoch öffnen? URL URL + + Ref: + Reference abbreviation + + Group @@ -990,9 +1263,16 @@ Möchten Sie diese dennoch öffnen? Ensure that the password contains characters from every group Sicherstellen, dass das Passwort Zeichen aus allen Gruppen enthält. + + + KMessageWidget - Accept - Akzeptieren + &Close + S&chließen + + + Close message + Meldung schließen @@ -1001,10 +1281,6 @@ Möchten Sie diese dennoch öffnen? Import KeePass1 database KeePass 1 Datenbank importieren - - Error - Fehler - Unable to open the database. Öffnen der Datenbank nicht möglich. @@ -1069,6 +1345,10 @@ This is a one-way migration. You won't be able to open the imported databas Zum Importieren gehen Sie auf Datenbank > 'KeePass 1 Datenbank importieren'. Dieser Vorgang ist nur in eine Richtung möglich. Die importierte Datenbank kann später nicht mehr mit der alten KeePassX Version 0.4 geöffnet werden. + + Unable to issue challenge-response. + + Main @@ -1080,13 +1360,17 @@ Dieser Vorgang ist nur in eine Richtung möglich. Die importierte Datenbank kann KeePassXC - Error KeePassXC - Fehler + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Datenbank - Open database Datenbank öffnen @@ -1119,10 +1403,6 @@ Dieser Vorgang ist nur in eine Richtung möglich. Die importierte Datenbank kann Toggle window Fenster zeigen/verstecken - - Tools - Tools - KeePass 2 Database KeePass 2 Datenbank @@ -1135,10 +1415,6 @@ Dieser Vorgang ist nur in eine Richtung möglich. Die importierte Datenbank kann Save repaired database Reparierte Datenbank speichern - - Error - Fehler - Writing the database failed. Schreiben der Datenbank fehlgeschlagen. @@ -1231,14 +1507,26 @@ Dieser Vorgang ist nur in eine Richtung möglich. Die importierte Datenbank kann &Database settings &Datenbankeinstellungen - - &Import KeePass 1 database - &KeePass 1 Datenbank importieren - &Clone entry Eintrag &klonen + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find &Suchen @@ -1291,6 +1579,46 @@ Dieser Vorgang ist nur in eine Richtung möglich. Die importierte Datenbank kann Password Generator Passwortgenerator + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + KeePass 1 Datenbank importieren + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1306,12 +1634,6 @@ Dieser Vorgang ist nur in eine Richtung möglich. Die importierte Datenbank kann Sh&ow a notification when credentials are requested Zeig&e eine Benachrichtigung, wenn Anmeldedaten angefordert werden. - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - Passendes URL Schema -Nur Einträge mit dem gleichen Schema (http://, https://, ftp://, ...) werden angezeigt - Sort matching entries by &username Sortiere gefundene Einträge nach &Benutzername @@ -1320,10 +1642,6 @@ Nur Einträge mit dem gleichen Schema (http://, https://, ftp://, ...) werden an Re&move all stored permissions from entries in active database Entferne alle gespeicherten Berechtigungen für Einträge in der aktiven Datenbank - - Password generator - Passwortgenerator - Advanced Fortgeschritten @@ -1340,10 +1658,6 @@ Nur Einträge mit dem gleichen Schema (http://, https://, ftp://, ...) werden an Searc&h in all opened databases for matching entries Suche in allen offenen Datenbanken nach übereinstimmenden Einträgen - - Only the selected database has to be connected with a client! - Nur die ausgewählte Datenbank muss mit dem Client verbunden sein. - HTTP Port: HTTP-Port: @@ -1360,12 +1674,6 @@ Nur Einträge mit dem gleichen Schema (http://, https://, ftp://, ...) werden an Sort &matching entries by title Sortiere gefundene Einträge nach Titel - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - KeepassXC-HTTP-Protokoll aktivieren -Dies ist für den Zugriff auf Ihre Datenbanken von ChromeIPass oder Passifox notwendig. - KeePassXC will listen to this port on 127.0.0.1 KeePassXC überwacht diesen Port auf 127.0.0.1 @@ -1380,20 +1688,10 @@ Using default port 19455. Privilegierte Ports unterhalb von 1024 können nicht überwacht werden. Es wird der Standard-Port 19455 verwendet. - - &Return only best matching entries for a URL instead -of all entries for the whole domain - Zeige nur die am besten passenden Einträge für eine URL anstatt aller Einträge der ganzen Domäne. - R&emove all shared encryption keys from active database &Entferne alle freigegebenen Chiffrierschlüssel aus der aktiven Datenbank - - The following options can be dangerous. Change them only if you know what you are doing. - Die folgenden Optionen können gefährlich sein! -Ändern Sie diese nur, wenn Sie wissen, was Sie tun. - &Return advanced string fields which start with "KPH: " Zeige auch erweiterte Zeichenfelder, welche mit "KPH: " beginnen @@ -1402,6 +1700,44 @@ of all entries for the whole domain Automatically creating or updating string fields is not supported. Automatisches Erstellen und Aktualisieren von Zeichenfeldern wird nicht unterstützt! + + This is required for accessing your databases from ChromeIPass or PassIFox + Dies wird benötigt, um auf Ihre Datenbanken in ChromeIPass oder PassIFox zuzugreifen. + + + Enable KeePassHTTP server + KeePassHTTP-Server aktivieren + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + Zeige nur die am besten passenden Einträge für eine URL anstatt aller Einträge der ganzen Domäne. + + + &Return only best matching entries + Nur beste Treffer anzeigen + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + Nur Einträge mit dem gleichen Schema (http://, https://, ftp://, …) anzeigen + + + &Match URL schemes + URL-Schema verwenden + + + Password Generator + Passwortgenerator + + + Only the selected database has to be connected with a client. + Nur die ausgewählte Datenbank muss mit dem Client verbunden sein. + + + The following options can be dangerous! +Change them only if you know what you are doing. + Die folgenden Optionen können gefährlich sein! +Ändern Sie diese nur, wenn Sie wissen, was Sie tun. + PasswordGeneratorWidget @@ -1493,12 +1829,101 @@ of all entries for the whole domain Excellent Ausgezeichnet + + Password + Passwort + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Gruppe + + + Title + Titel + + + Username + Benutzername + + + Password + Passwort + + + URL + URL + + + Notes + Notizen + + + Browser Integration + Browser-Integration + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1545,14 +1970,18 @@ of all entries for the whole domain Search Suche - - Find - Suchen - Clear Löschen + + Search... + + + + Limit search to selected group + + Service @@ -1660,6 +2089,10 @@ Namen und akzeptieren Sie. Security Sicherheit + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1687,10 +2120,6 @@ Namen und akzeptieren Sie. Global Auto-Type shortcut Globale Tastenkombination für Auto-Type - - Use entry title to match windows for global auto-type - Verwende Eintragstitel, um entsprechende Fenster für globales Auto-Type zu finden - Language Sprache @@ -1703,10 +2132,6 @@ Namen und akzeptieren Sie. Hide window to system tray when minimized Fenster verstecken wenn minimiert - - Remember last key files - Letzte Schlüsseldateien merken - Load previous databases on startup Letzte Datenbank beim Starten laden @@ -1723,6 +2148,30 @@ Namen und akzeptieren Sie. Minimize window at application startup Fenster beim Programmstart minimieren + + Basic Settings + Grundeinstellungen + + + Remember last key files and security dongles + Letzte Schlüsseldateien und Sicherheits-Dongles merken + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Auto-Type + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + Immer vor einem Auto-Type fragen + SettingsWidgetSecurity @@ -1742,10 +2191,6 @@ Namen und akzeptieren Sie. Show passwords in cleartext by default Passwörter standardmäßig in Klartext anzeigen - - Always ask before performing auto-type - Immer vor einem Auto-Type fragen - Lock databases after minimizing the window Datenbank sperren nach dem Minimieren des Fensters @@ -1754,6 +2199,80 @@ Namen und akzeptieren Sie. Don't require password repeat when it is visible Keine erneute Passworteingabe verlangen wenn das Passwort sichtbar ist. + + Timeouts + Timeouts + + + Convenience + Komfort + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + sek + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1765,8 +2284,32 @@ Namen und akzeptieren Sie. WelcomeWidget - Welcome! - Willkommen! + Welcome to KeePassXC + Willkommen bei KeePassXC + + + Start storing your passwords securely in a KeePassXC database + Speichern Sie Ihre Passwörter sicher in einer KeePassXC-Datenbank + + + Create new database + Neue Datenbank erstellen + + + Open existing database + Existierende Datenbank öffnen + + + Import from KeePass 1 + Aus KeePass 1 importieren + + + Import from CSV + + + + Recent databases + Zuletzt verwendete Datenbanken @@ -1791,5 +2334,69 @@ Namen und akzeptieren Sie. filenames of the password databases to open (*.kdbx) Dateinamen der zu öffnenden Datenbanken (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_el.ts b/share/translations/keepassx_el.ts index 4e554df9e..ffd6131bc 100644 --- a/share/translations/keepassx_el.ts +++ b/share/translations/keepassx_el.ts @@ -1,33 +1,143 @@ - + AboutDialog - About KeePassX - Σχετικά με το KeepPassX + About KeePassXC + - KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassX διανέμεται υπό τον όρο από το GNU γενικής δημόσιας άδειας (GPL) έκδοση 2 ή (κατ ' επιλογή σας) έκδοση 3. + About + Σχετικά - Revision - Αναθεώρηση + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + - Using: - Χρήση: + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + + + + Allow + + + + Deny + + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + + + + KeePassXC HTTP Confirm Access + AutoType - - Auto-Type - KeePassX - Αυτόματη-Γραφή - KeePassX - Couldn't find an entry that matches the window title: Αποτυχία να βρεθεί μια καταχώρηση που να ταιριάζει με τον τίτλο του παραθύρου: + + Auto-Type - KeePassXC + + AutoTypeAssociationsModel @@ -37,23 +147,23 @@ Sequence - Ακολουθεία + Ακολουθία Default sequence - Προεπιλεγμένη ακολουθεία + Προεπιλεγμένη ακολουθία AutoTypeSelectDialog - - Auto-Type - KeePassX - Αυτόματη-Γραφή - KeePassX - Select entry to Auto-Type: Επιλέξτε καταχώρηση για αυτόματη γραφή: + + Auto-Type - KeePassXC + + ChangeMasterKeyWidget @@ -69,10 +179,6 @@ Repeat password: Επαναλάβετε τον κωδικό: - - Key file - Αρχείο κλειδί - Browse Αναζήτηση @@ -93,10 +199,6 @@ Create Key File... Δημιουργεία αρχείου κλειδιού... - - Error - Σφάλμα - Unable to create Key File : Αποτυχία δημιουργεία αρχείου κλειδιού: @@ -105,10 +207,6 @@ Select a key file Επιλέξτε ένα αρχείο κλειδί - - Question - Ερώτηση - Do you really want to use an empty string as password? Θέλετε στα αλήθεια να χρησιμοποιήσετε μια άδεια σειρά σαν κωδικό; @@ -117,15 +215,172 @@ Different passwords supplied. Έχετε εισάγει διαφορετικούς κωδικούς. - - Failed to set key file - Αποτυχία ορισμού αρχείου κλειδιού - Failed to set %1 as the Key file: %2 Αποτυχία ορισμού του %1 ως αρχείου κλειδιού + + &Key file + + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Σφάλμα + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Σφάλμα + + + Unable to calculate master key + Σε θέση να υπολογίσει το κύριο κλειδί + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -145,10 +400,6 @@ Browse Αναζήτηση - - Error - Σφάλμα - Unable to open the database. Αδύνατο να ανοιχτεί η βάση δεδομένων. @@ -169,6 +420,14 @@ Select key file Επιλέξτε αρχείο κλειδί + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -224,10 +483,6 @@ You can now save it. Default username: Προεπιλεγμένο όνομα χρήστη: - - Use recycle bin: - Χρήση καλαθιού αχρήστων: - MiB MiB @@ -244,6 +499,22 @@ You can now save it. Max. history size: Μέγιστο μέγεθος ιστορικού: + + Use recycle bin + + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -263,10 +534,6 @@ You can now save it. Open database Άνοιγμα βάσης δεδομένων - - Warning - Προειδοποίηση - File not found! Αρχείο δεν βρέθηκε! @@ -297,10 +564,6 @@ Save changes? "%1" έχει τροποποιηθή. Αποθήκευση αλλαγών; - - Error - Σφάλμα - Writing the database failed. Εγγραφή της βάσης δεδομένων απέτυχε. @@ -317,12 +580,6 @@ Save changes? locked κλειδωμένο - - The database you are trying to open is locked by another instance of KeePassX. -Do you want to open it anyway? Alternatively the database is opened read-only. - Η βάση δεδομένω που προσπαθείται να ανοίξετε ειναι κλειδωμένη από μια άλλη διεργασία του KeePassX. -Θέλετε να την ανοίξετε ούτως η άλλως; Αλλίως η βαση δεδομένων θα ανοιχτή μόνο για ανάγνωση. - Lock database Κλείδωμα βάσης δεδομένων @@ -365,16 +622,45 @@ Discard changes and close anyway? Writing the CSV file failed. Γράψιμο στο αρχείο CSV απέτυχε. - - The database you are trying to save as is locked by another instance of KeePassX. -Do you want to save it anyway? - Η βάση δεδομένων που πρασπαθείται να αποθηκεύσετε είναι κλειδωμένη από μία άλλη διεργασία KeePassX. -Θέλετε να την αποθηκεύσετε ούτως η άλλως; - Unable to open the database. Δεν είναι δυνατό να ανοίξει τη βάση δεδομένων. + + Merge database + + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + + + + Passwords + + + + Database already opened + + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + + + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -414,14 +700,6 @@ Do you want to save it anyway? Do you really want to delete the group "%1" for good? Θέλετε στα αλήθεια να διαγράψετε την ομάδα "%1" μόνιμα; - - Current group - Τρέχων ομάδα - - - Error - Σφάλμα - Unable to calculate master key Σε θέση να υπολογίσει το κύριο κλειδί @@ -434,6 +712,66 @@ Do you want to save it anyway? Do you really want to move entry "%1" to the recycle bin? Θέλετε πραγματικά να κινηθεί εισόδου "%1" στον κάδο ανακύκλωσης; + + Searching... + + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + + + + No Results + + + + Execute command? + + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -473,10 +811,6 @@ Do you want to save it anyway? Edit entry Επεξεργασία καταχώρησης - - Error - Σφάλμα - Different passwords supplied. Παρέχονται διαφορετικοί κωδικοί. @@ -519,6 +853,22 @@ Do you want to save it anyway? 1 year 1 χρόνο + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -530,10 +880,6 @@ Do you want to save it anyway? Add Πρόσθεση - - Edit - Επεξεργασία - Remove Αφαίρεση @@ -550,6 +896,18 @@ Do you want to save it anyway? Open Άνοιγμα + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -557,14 +915,6 @@ Do you want to save it anyway? Enable Auto-Type for this entry Ενεργοποίηση Αυτόματης-Γραφής για αυτήν την καταχώρηση - - Inherit default Auto-Type sequence from the group - Χρησιμοποίηση προεπιλεγμένης ακολουθείας Αυτόματης-Γραφής απο την ομάδα - - - Use custom Auto-Type sequence: - Χρησιμοποίηση προσαρμοσμένης ακολουθείας Αυτόματης Γραφής: - + + @@ -578,12 +928,24 @@ Do you want to save it anyway? Τίτλος Παραθύρου: - Use default sequence - Χρησιμοποίηση προεπιλεγμένης ακολουθείας + Inherit default Auto-Type sequence from the &group + - Set custom sequence: - Ορισμός προσαρμοσμένης ακολουθείας: + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + @@ -623,10 +985,6 @@ Do you want to save it anyway? Repeat: Επαναλάβετε: - - Gen. - - URL: URL: @@ -698,28 +1056,20 @@ Do you want to save it anyway? Αναζήτηση - Auto-type - Αυτόματη-γραφή + Auto-Type + Αυτόματη-Γραφή - Use default auto-type sequence of parent group - Χρησιμοποίηση προεπιλεγμένης ακολουθείας αυτόματης γραφής της μητρικής ομάδας + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Ορισμός προεπιλεγμένης ακολουθείας αυτόματης-γραφής + Set default Auto-Type se&quence + EditWidgetIcons - - Use default icon - Χρήση προεπιλεγμένου εικονιδίου - - - Use custom icon - Χρήση προσαρμοσμένου εικονιδίου - Add custom icon Πρόσθεση προσαρμοσμένου εικονιδίου @@ -740,21 +1090,37 @@ Do you want to save it anyway? Select Image Επιλογή εικόνας - - Can't delete icon! - Αποτυχία διαγραφής εικονίδιου! - - - Can't delete icon. Still used by %n item(s). - - Error Σφάλμα - Can't read icon: - Δεν μπορεί να διαβάσει το εικονίδιο: + Download favicon + + + + Unable to fetch favicon. + + + + Can't read icon + + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + @@ -776,6 +1142,13 @@ Do you want to save it anyway? UUID: + + Entry + + - Clone + + + EntryAttributesModel @@ -820,6 +1193,11 @@ Do you want to save it anyway? URL URL + + Ref: + Reference abbreviation + + Group @@ -828,16 +1206,74 @@ Do you want to save it anyway? Καλάθι ανακύκλωσης + + HttpPasswordGeneratorWidget + + Length: + Μήκος: + + + Character Types + Τύποι χαρακτήρων + + + Upper Case Letters + Κεφαλαία γράμματα + + + A-Z + + + + Lower Case Letters + Πεζά γράμματα + + + a-z + + + + Numbers + Αριθμοί + + + 0-9 + + + + Special Characters + Ειδικοί χαρακτήρες + + + /*_& ... + + + + Exclude look-alike characters + Εξαίρεση χαρακτήρων που μοίαζουν + + + Ensure that the password contains characters from every group + Βεβαιωθείται οτι ο κωδικός περιέχει χαρακτήρες απο κάθε ομάδα + + + + KMessageWidget + + &Close + + + + Close message + + + KeePass1OpenWidget Import KeePass1 database Εισαγωγή βάσης δεδομένων KeePass1 - - Error - Σφάλμα - Unable to open the database. Αποτυχία ανοίγματος βάσης δεδομένων. @@ -899,6 +1335,10 @@ You can import it by clicking on Database > 'Import KeePass 1 database'. This is a one-way migration. You won't be able to open the imported database with the old KeePassX 0.4 version. + + Unable to issue challenge-response. + + Main @@ -907,112 +1347,28 @@ This is a one-way migration. You won't be able to open the imported databas Ανεπανόρθωτο σφάλμα κατά τον έλεγχο των κρυπτογραφικών λειτουργιών. - KeePassX - Error - KeePassX - Σφάλμα + KeePassXC - Error + KeePassXC - Σφάλμα + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - Βάση Δεδομένων - - - Recent databases - Πρόσφατες βάσεις δεδομένων - - - Help - Βοήθεια - - - Entries - Καταχωρήσεις - - - Copy attribute to clipboard - Αντιγραφή χαρακτηριστικού στο πρόχειρο - - - Groups - Ομάδες - - - View - Προβολή - - - Quit - Έξοδος - - - About - Σχετικά - Open database Άνοιγμα Βάσης Δεδομένων - - Save database - Αποθήκευση Βάσης Δεδομένων - - - Close database - Κλείσιμο Βάσης Δεδομένων - - - New database - Νέα Βάση Δεδομένων - - - Add new entry - Πρόσθεση νέα καταχώρησης - - - View/Edit entry - Προβολή/επεξεργασία καταχώρησης - - - Delete entry - Διαγραφή Καταχώρησης - - - Add new group - Πρόσθεση νέας ομάδας - - - Edit group - Επεξεργασία Ομάδας - - - Delete group - Διαγραφή ομάδας - - - Save database as - Αποθήκευση βάσης δεδομένων ως - - - Change master key - Αλλαγή πρωτεύοντος κλειδιού - Database settings Ρυθμίσεις βάσης δεδομένων - - Import KeePass 1 database - Εισαγωγή βάσης δεδομένων KeePass1 - - - Clone entry - Κλωνοποίηση Καταχώρησης - - - Find - Εύρεση - Copy username to clipboard Αντιγραφή όνομα χρήστη στο πρόχειρο @@ -1025,30 +1381,6 @@ This is a one-way migration. You won't be able to open the imported databas Settings Ρύθμίσεις - - Perform Auto-Type - Εκτέλεση Αυτόματης-Γραφής - - - Open URL - Άνοιγμα ιστοσελίδας - - - Lock databases - Κλείδωμα βάσεων δεδομένων - - - Title - Τίτλος - - - URL - URL - - - Notes - Σημειώσεις - Show toolbar Εμφάνιση γραμμής εργαλείων @@ -1061,29 +1393,9 @@ This is a one-way migration. You won't be able to open the imported databas Toggle window Εναλλαγή παραθύρων - - Tools - Εργαλεία - - - Copy username - Αντιγραφή όνομα χρήστη - - - Copy password - Αντιγραφή κωδικού - - - Export to CSV file - Εξαγωγή σε αρχείο CSV - - - Repair database - Επισκευή βάσης δεδομένων - KeePass 2 Database - + Βάση Δεδομένων KeePass 2 All files @@ -1093,14 +1405,327 @@ This is a one-way migration. You won't be able to open the imported databas Save repaired database Αποθήκευση επιδιορθωμένη βάση δεδομένων - - Error - Σφάλμα - Writing the database failed. Εγγραφή της βάσης δεδομένων απέτυχε. + + &Recent databases + + + + He&lp + + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + + + + &View + + + + &Quit + + + + &About + + + + &Open database + + + + &Save database + + + + &Close database + + + + &New database + + + + Merge from KeePassX database + + + + &Add new entry + + + + &View/Edit entry + + + + &Delete entry + + + + &Add new group + + + + &Edit group + + + + &Delete group + + + + Sa&ve database as + + + + Change &master key + + + + &Database settings + + + + &Clone entry + + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + + + + Copy &username + + + + Cop&y password + + + + &Settings + + + + &Perform Auto-Type + + + + &Open URL + + + + &Lock databases + + + + &Title + + + + &URL + + + + &Notes + + + + &Export to CSV file + + + + Re&pair database + + + + Password Generator + + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Εισαγωγή βάσης δεδομένων KeePass1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + + + + General + Γενικά + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + Για προχωρημένους + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + + + + Default port: 19455 + + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1108,10 +1733,6 @@ This is a one-way migration. You won't be able to open the imported databas Password: Κωδικός: - - Length: - Μήκος: - Character Types Τύποι χαρακτήρων @@ -1136,70 +1757,160 @@ This is a one-way migration. You won't be able to open the imported databas Exclude look-alike characters Εξαίρεση χαρακτήρων που μοίαζουν - - Ensure that the password contains characters from every group - Βεβαιωθείται οτι ο κωδικός περιέχει χαρακτήρες απο κάθε ομάδα - Accept Αποδοχή - - - QCommandLineParser - Displays version information. - Προβολή πληροφοριών έκδοσης. + %p% + - Displays this help. - Δείχνει αυτήν την βοήθεια. + strength + - Unknown option '%1'. - Άγνωστη επιλογή '%1'. + entropy + - Unknown options: %1. - Άγνωστο επιλογές: %1. + &Length: + - Missing value after '%1'. - Τιμή που λείπει μετά από '%1'. + Pick characters from every group + - Unexpected value after '%1'. - Μη αναμενόμενη τιμή μετά από '%1'. + Generate + - [options] - [επιλογές] + Close + - Usage: %1 - Χρήση: %1 + Apply + - Options: - Επιλογές: + Entropy: %1 bit + - Arguments: - Επιχειρήματα: + Password Quality: %1 + + + + Poor + + + + Weak + + + + Good + + + + Excellent + + + + Password + Κωδικός + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + - QSaveFile + QObject - Existing file %1 is not writable - Υπάρχον αρχείο %1 δεν είναι εγγράψιμο + NULL device + - Writing canceled by application - Γράψιμο ακυρώθηκε από την εφαρμογή + error reading from device + - Partial write. Partition full? + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Όμαδα + + + Title + Τίτλος + + + Username + Όνομα χρήστη + + + Password + Κωδικός + + + URL + URL + + + Notes + Σημειώσεις + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive @@ -1240,20 +1951,111 @@ This is a one-way migration. You won't be able to open the imported databas SearchWidget - Find: - Εύρεση + Case Sensitive + - Case sensitive - Διάκριση πεζών-κεφαλαίων + Search + Αναζήτηση - Current group - Τρέχων ομάδα + Clear + - Root group - Ομάδα ρίζα + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + @@ -1270,6 +2072,10 @@ This is a one-way migration. You won't be able to open the imported databas Security Ασφάλεια + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1277,10 +2083,6 @@ This is a one-way migration. You won't be able to open the imported databas Remember last databases Θυμηθείτε την τελευταία βάσεις δεδομένων - - Open previous databases on startup - Άνοιγμα προηγούμενων βάσεω δεδομένων κατα την εκκίνηση - Automatically save on exit Αυτόματη αποθήκευση κατα την έξοδο @@ -1301,10 +2103,6 @@ This is a one-way migration. You won't be able to open the imported databas Global Auto-Type shortcut - - Use entry title to match windows for global auto-type - Χρησιμοποιήστε εγγραφή τίτλου ώστε να ταιριάζει με windows για παγκόσμια αυτόματος-τύπο - Language Γλώσσα @@ -1318,15 +2116,43 @@ This is a one-way migration. You won't be able to open the imported databas - Remember last key files + Load previous databases on startup - Hide window to system tray instead of App Exit + Automatically reload the database when modified externally - Hide window to system tray on App start + Hide window to system tray instead of app exit + + + + Minimize window at application startup + + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Αυτόματη-Γραφή + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type @@ -1349,8 +2175,86 @@ This is a one-way migration. You won't be able to open the imported databas - Always ask before performing auto-type - Πάντα να ρωτάτε πριν να εκτελείται η αυτόματη γραφή + Lock databases after minimizing the window + + + + Don't require password repeat when it is visible + + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + δευτερόλεπτα + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1363,20 +2267,36 @@ This is a one-way migration. You won't be able to open the imported databas WelcomeWidget - Welcome! - Καλως ήρθατε! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Πρόσφατες βάσεις δεδομένων main - - KeePassX - cross-platform password manager - - - - filename of the password database to open (*.kdbx) - Όνομα της βάσης δεδομένων κωδικών για άνοιγμα (*.kdbx) - path to a custom config file @@ -1385,5 +2305,81 @@ This is a one-way migration. You won't be able to open the imported databas key file of the database Αρχείο κλειδί της βάσεως δεδομένων + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_en.ts b/share/translations/keepassx_en.ts index 2fbe1d3be..7b014b632 100644 --- a/share/translations/keepassx_en.ts +++ b/share/translations/keepassx_en.ts @@ -3,25 +3,106 @@ AboutDialog - - Revision - - - - Using: - - About KeePassXC - Extensions: + About + + + + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: @@ -120,10 +201,6 @@ Please select whether you want to allow access. Create Key File... - - Error - - Unable to create Key File : @@ -132,10 +209,6 @@ Please select whether you want to allow access. Select a key file - - Question - - Do you really want to use an empty string as password? @@ -144,10 +217,6 @@ Please select whether you want to allow access. Different passwords supplied. - - Failed to set key file - - Failed to set %1 as the Key file: %2 @@ -157,6 +226,163 @@ Please select whether you want to allow access. &Key file + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + + + + Unable to calculate master key + + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -176,10 +402,6 @@ Please select whether you want to allow access. Browse - - Error - - Unable to open the database. @@ -200,6 +422,14 @@ Please select whether you want to allow access. Select key file + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -275,6 +505,18 @@ You can now save it. Use recycle bin + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -294,10 +536,6 @@ You can now save it. Open database - - Warning - - File not found! @@ -327,10 +565,6 @@ You can now save it. Save changes? - - Error - - Writing the database failed. @@ -415,6 +649,14 @@ Do you want to open it anyway? Open read-only + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -457,10 +699,6 @@ Do you want to open it anyway? Do you really want to delete the group "%1" for good? - - Error - - Unable to calculate master key @@ -522,11 +760,15 @@ Do you want to open it anyway? - Autoreload Failed + Could not open the new database file while attempting to autoreload this database. - Could not open the new database file while attempting to autoreload this database. + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? @@ -568,10 +810,6 @@ Do you want to open it anyway? Edit entry - - Error - - Different passwords supplied. @@ -619,6 +857,22 @@ Do you want to open it anyway? 1 year + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -630,10 +884,6 @@ Do you want to open it anyway? Add - - Edit - - Remove @@ -650,6 +900,18 @@ Do you want to open it anyway? Open + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -685,6 +947,10 @@ Do you want to open it anyway? Set custo&m sequence: + + Window Associations + + EditEntryWidgetHistory @@ -794,15 +1060,15 @@ Do you want to open it anyway? - Auto-type + Auto-Type - Use default auto-type sequence of parent group + &Use default Auto-Type sequence of parent group - Set default auto-type sequence + Set default Auto-Type se&quence @@ -828,10 +1094,6 @@ Do you want to open it anyway? Select Image - - Can't delete icon! - - Error @@ -848,10 +1110,6 @@ Do you want to open it anyway? Can't read icon - - Can't delete icon. Still used by %1 items. - - &Use default icon @@ -860,6 +1118,14 @@ Do you want to open it anyway? Use custo&m icon + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -931,6 +1197,11 @@ Do you want to open it anyway? URL + + Ref: + Reference abbreviation + + Group @@ -989,8 +1260,15 @@ Do you want to open it anyway? Ensure that the password contains characters from every group + + + KMessageWidget - Accept + &Close + + + + Close message @@ -1000,10 +1278,6 @@ Do you want to open it anyway? Import KeePass1 database - - Error - - Unable to open the database. @@ -1065,6 +1339,10 @@ You can import it by clicking on Database > 'Import KeePass 1 database&a This is a one-way migration. You won't be able to open the imported database with the old KeePassX 0.4 version. + + Unable to issue challenge-response. + + Main @@ -1076,13 +1354,17 @@ This is a one-way migration. You won't be able to open the imported databas KeePassXC - Error + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - - Open database @@ -1115,10 +1397,6 @@ This is a one-way migration. You won't be able to open the imported databas Toggle window - - Tools - - KeePass 2 Database @@ -1131,10 +1409,6 @@ This is a one-way migration. You won't be able to open the imported databas Save repaired database - - Error - - Writing the database failed. @@ -1227,10 +1501,6 @@ This is a one-way migration. You won't be able to open the imported databas &Database settings - - &Import KeePass 1 database - - &Clone entry @@ -1307,6 +1577,42 @@ This is a one-way migration. You won't be able to open the imported databas Clear history + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1322,11 +1628,6 @@ This is a one-way migration. You won't be able to open the imported databas Sh&ow a notification when credentials are requested - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - - Sort matching entries by &username @@ -1335,10 +1636,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Re&move all stored permissions from entries in active database - - Password generator - - Advanced @@ -1355,10 +1652,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Searc&h in all opened databases for matching entries - - Only the selected database has to be connected with a client! - - HTTP Port: @@ -1375,11 +1668,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Sort &matching entries by title - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - - KeePassXC will listen to this port on 127.0.0.1 @@ -1393,19 +1681,10 @@ This is required for accessing your databases from ChromeIPass or PassIFox - - &Return only best matching entries for a URL instead -of all entries for the whole domain - - R&emove all shared encryption keys from active database - - The following options can be dangerous. Change them only if you know what you are doing. - - &Return advanced string fields which start with "KPH: " @@ -1414,6 +1693,43 @@ of all entries for the whole domain Automatically creating or updating string fields is not supported. + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1505,11 +1821,100 @@ of all entries for the whole domain Excellent + + Password + + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + + + + Title + + + + Username + + + + Password + + + + URL + + + + Notes + + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive @@ -1558,11 +1963,15 @@ of all entries for the whole domain - Find + Clear - Clear + Search... + + + + Limit search to selected group @@ -1667,6 +2076,10 @@ give it a unique name to identify and accept it. Security + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1694,10 +2107,6 @@ give it a unique name to identify and accept it. Global Auto-Type shortcut - - Use entry title to match windows for global auto-type - - Language @@ -1710,10 +2119,6 @@ give it a unique name to identify and accept it. Hide window to system tray when minimized - - Remember last key files - - Load previous databases on startup @@ -1730,6 +2135,30 @@ give it a unique name to identify and accept it. Minimize window at application startup + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1749,10 +2178,6 @@ give it a unique name to identify and accept it. Show passwords in cleartext by default - - Always ask before performing auto-type - - Lock databases after minimizing the window @@ -1761,6 +2186,80 @@ give it a unique name to identify and accept it. Don't require password repeat when it is visible + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1772,7 +2271,31 @@ give it a unique name to identify and accept it. WelcomeWidget - Welcome! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases @@ -1798,5 +2321,69 @@ give it a unique name to identify and accept it. filenames of the password databases to open (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + diff --git a/share/translations/keepassx_es.ts b/share/translations/keepassx_es.ts index 4a9a46c38..16138ecf2 100644 --- a/share/translations/keepassx_es.ts +++ b/share/translations/keepassx_es.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - Revisión - - - Using: - Usando: - About KeePassXC Acerca de KeePassXC - Extensions: - - Extensiones: - + About + Acerca de - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC se distribuye bajo la Licencia Pública General de GNU (GPL) versión 2 o versión 3 (si así lo prefiere). + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -120,10 +200,6 @@ Por favor seleccione si desea autorizar su acceso. Create Key File... Crear un Archivo Llave .... - - Error - Error - Unable to create Key File : No se puede crear el Archivo Llave: @@ -132,10 +208,6 @@ Por favor seleccione si desea autorizar su acceso. Select a key file Seleccione un archivo llave - - Question - Pregunta - Do you really want to use an empty string as password? ¿Realmente desea usar una cadena vacía como contraseña? @@ -144,10 +216,6 @@ Por favor seleccione si desea autorizar su acceso. Different passwords supplied. Las contraseñas ingresadas son distintas. - - Failed to set key file - No se pudo establecer el archivo llave. - Failed to set %1 as the Key file: %2 @@ -158,6 +226,163 @@ Por favor seleccione si desea autorizar su acceso. &Key file &Archivo llave + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Error + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Error + + + Unable to calculate master key + No se puede calcular la clave maestra + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -177,10 +402,6 @@ Por favor seleccione si desea autorizar su acceso. Browse Navegar - - Error - Error - Unable to open the database. Incapaz de abrir la base de datos. @@ -201,6 +422,14 @@ Por favor seleccione si desea autorizar su acceso. Select key file Seleccionar archivo llave + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -277,6 +506,18 @@ Ahora puede guardarla. Use recycle bin Usar papelera de reciclaje + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -296,10 +537,6 @@ Ahora puede guardarla. Open database Abrir base de datos - - Warning - Advertencia - File not found! ¡Archivo no encontrado! @@ -330,10 +567,6 @@ Save changes? "%1" ha sido modificado. ¿Guardar cambios? - - Error - Error - Writing the database failed. La escritura de la base de datos falló. @@ -425,6 +658,14 @@ Do you want to open it anyway? Open read-only Abrir como sólo lectura + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -464,10 +705,6 @@ Do you want to open it anyway? Do you really want to delete the group "%1" for good? ¿Realmente quiere eliminar el grupo "%1" de forma definitiva? - - Error - Error - Unable to calculate master key No se puede calcular la llave maestra @@ -528,14 +765,18 @@ Do you want to open it anyway? The database file has changed and you have unsaved changes.Do you want to merge your changes? El archivo de la base de datos ha cambiado y usted tiene modificaciones sin guardar. ¿Desea unir sus modificaciones? - - Autoreload Failed - La recarga automática falló - Could not open the new database file while attempting to autoreload this database. No se pudo abrir el nuevo archivo de la base de datos mientras se intentaba recargar la base de datos actual. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -575,10 +816,6 @@ Do you want to open it anyway? Edit entry Editar entrada - - Error - Error - Different passwords supplied. Las contraseñas ingresadas son distintas. @@ -621,6 +858,22 @@ Do you want to open it anyway? 1 year 1 año + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -632,10 +885,6 @@ Do you want to open it anyway? Add Añadir - - Edit - Editar - Remove Eliminar @@ -652,6 +901,18 @@ Do you want to open it anyway? Open Abrir + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -687,6 +948,10 @@ Do you want to open it anyway? Set custo&m sequence: Definir secuencia personalizada: + + Window Associations + + EditEntryWidgetHistory @@ -796,16 +1061,16 @@ Do you want to open it anyway? Buscar - Auto-type - Auto-escritura + Auto-Type + Auto-Escritura - Use default auto-type sequence of parent group - Usar secuencia de auto-escritura por defecto del grupo padre + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Definir secuencia de Auto-Escritura por defecto + Set default Auto-Type se&quence + @@ -830,10 +1095,6 @@ Do you want to open it anyway? Select Image Seleccionar imagen - - Can't delete icon! - ¡No se puede eliminar el ícono! - Error Error @@ -850,10 +1111,6 @@ Do you want to open it anyway? Can't read icon No se puede leer el ícono - - Can't delete icon. Still used by %1 items. - No se puede eliminar el icono. Utilizado aún en %1 elementos - &Use default icon &Usar icono por defecto @@ -862,6 +1119,14 @@ Do you want to open it anyway? Use custo&m icon Usar icono &personalizado + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -933,6 +1198,11 @@ Do you want to open it anyway? URL URL + + Ref: + Reference abbreviation + + Group @@ -991,9 +1261,16 @@ Do you want to open it anyway? Ensure that the password contains characters from every group Asegurar que la contraseña contiene caracteres de todos los grupos + + + KMessageWidget - Accept - Aceptar + &Close + + + + Close message + @@ -1002,10 +1279,6 @@ Do you want to open it anyway? Import KeePass1 database Importar base de datos KeePass1 - - Error - Error - Unable to open the database. Incapaz de abrir la base de datos. @@ -1070,6 +1343,10 @@ This is a one-way migration. You won't be able to open the imported databas Puede importarla haciendo click en 'Base de datos' > 'Importar base de datos de Keepass 1'. Esta migración es en un único sentido. No podrá abrir la base importada con la vieja versión 0.4 de KeePassX. + + Unable to issue challenge-response. + + Main @@ -1081,13 +1358,17 @@ Esta migración es en un único sentido. No podrá abrir la base importada con l KeePassXC - Error KeePassXC - Error + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Base de datos - Open database Abrir base de datos @@ -1120,10 +1401,6 @@ Esta migración es en un único sentido. No podrá abrir la base importada con l Toggle window Cambiar a ventana - - Tools - Herramientas - KeePass 2 Database Base de datos de KeePass 2 @@ -1136,10 +1413,6 @@ Esta migración es en un único sentido. No podrá abrir la base importada con l Save repaired database Guardar base de datos reparada - - Error - Error - Writing the database failed. Fallo al escribir la base de datos. @@ -1232,14 +1505,26 @@ Esta migración es en un único sentido. No podrá abrir la base importada con l &Database settings Configuración de la base de &datos - - &Import KeePass 1 database - &Importar base de datos KeePass 1 - &Clone entry &Clonar entrada + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find &Buscar @@ -1292,6 +1577,46 @@ Esta migración es en un único sentido. No podrá abrir la base importada con l Password Generator Generador de contraseñas + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importar base de datos KeePass 1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1307,12 +1632,6 @@ Esta migración es en un único sentido. No podrá abrir la base importada con l Sh&ow a notification when credentials are requested M&ostrar una notificación cuando se pidan credenciales - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - &Coincidir esquemas URL -Solo se muestran entradas con el mismo esquema (http://, https://, ftp://, ...) - Sort matching entries by &username Ordenar entradas por nombre de &usuario @@ -1321,10 +1640,6 @@ Solo se muestran entradas con el mismo esquema (http://, https://, ftp://, ...)< Re&move all stored permissions from entries in active database Eli&minar todos los permisos guardados de las entradas de la base de datos activa - - Password generator - Generador de contraseñas - Advanced Avanzado @@ -1341,10 +1656,6 @@ Solo se muestran entradas con el mismo esquema (http://, https://, ftp://, ...)< Searc&h in all opened databases for matching entries Buscar entradas que coincidan en todas las bases de datos abiertas - - Only the selected database has to be connected with a client! - ¡Solo la base de datos seleccionada necesita conectarse con un cliente! - HTTP Port: Puerto HTTP: @@ -1361,12 +1672,6 @@ Solo se muestran entradas con el mismo esquema (http://, https://, ftp://, ...)< Sort &matching entries by title Ordenar entradas por &título - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Habilitar el protocolo KeepassXC HTTP -Necesario para acceder a tus bases de datos desde ChromeIPass o PassIFox - KeePassXC will listen to this port on 127.0.0.1 KeePassXC escuchará por este puerto en 127.0.0.1 @@ -1380,21 +1685,11 @@ Necesario para acceder a tus bases de datos desde ChromeIPass o PassIFox ¡No se puede asociar a puertos con privilegios debajo de 1024! Usando el puerto por defecto 19455 - - - &Return only best matching entries for a URL instead -of all entries for the whole domain - Mostra&r solo las mejores coincidencias para una URL -en vez de todas las entradas para el dominio completo R&emove all shared encryption keys from active database &Eliminar todas las claves de cifrado compartidas de la base de datos activa - - The following options can be dangerous. Change them only if you know what you are doing. - Las siguientes opciones pueden ocasionar problemas. Cámbielas solo si sabe lo que está haciendo. - &Return advanced string fields which start with "KPH: " Mostra&r campos de caracteres avanzados que comiencen con "KPH: " @@ -1403,6 +1698,43 @@ en vez de todas las entradas para el dominio completo Automatically creating or updating string fields is not supported. No se permite crear o actualizar campos de caracteres automáticamente. + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Generador de contraseñas + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1456,7 +1788,7 @@ en vez de todas las entradas para el dominio completo Pick characters from every group - Elige caracteres de todos los grupos + Elegir caracteres de todos los grupos Generate @@ -1494,12 +1826,101 @@ en vez de todas las entradas para el dominio completo Excellent Excelente + + Password + Contraseña + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Grupo + + + Title + Título + + + Username + Nombre de usuario: + + + Password + Contraseña + + + URL + URL + + + Notes + Notas + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1546,14 +1967,18 @@ en vez de todas las entradas para el dominio completo Search Buscar - - Find - Buscar - Clear Limpiar + + Search... + + + + Limit search to selected group + + Service @@ -1660,6 +2085,10 @@ asigne un nombre único para identificarla y acepte. Security Seguridad + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1687,10 +2116,6 @@ asigne un nombre único para identificarla y acepte. Global Auto-Type shortcut Atajo global de Auto-Escritura - - Use entry title to match windows for global auto-type - Usar el título de la entrada para coincidir con la ventana para la auto-escritura global - Language Idioma @@ -1703,10 +2128,6 @@ asigne un nombre único para identificarla y acepte. Hide window to system tray when minimized Ocultar la ventana a la bandeja del sistema cuando se minimiza - - Remember last key files - Recordar últimos archivos llave - Load previous databases on startup Abrir base de datos anterior al inicio @@ -1723,6 +2144,30 @@ asigne un nombre único para identificarla y acepte. Minimize window at application startup Minimizar la ventana al iniciar + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Auto-Escritura + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1742,10 +2187,6 @@ asigne un nombre único para identificarla y acepte. Show passwords in cleartext by default Mostrar contraseñas en texto claro por defecto - - Always ask before performing auto-type - Preguntar siempre antes de realizar auto-escritura - Lock databases after minimizing the window Bloquear base de datos al minimizar la ventana @@ -1754,6 +2195,80 @@ asigne un nombre único para identificarla y acepte. Don't require password repeat when it is visible No pedir repetición de la contraseña cuando está visible + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + segundos + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1765,8 +2280,32 @@ asigne un nombre único para identificarla y acepte. WelcomeWidget - Welcome! - ¡Bienvenid@! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Bases de datos recientes @@ -1791,5 +2330,69 @@ asigne un nombre único para identificarla y acepte. filenames of the password databases to open (*.kdbx) nombre de archivo de la base de datos de contraseñas a abrir (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_fi.ts b/share/translations/keepassx_fi.ts new file mode 100644 index 000000000..aefa69b1d --- /dev/null +++ b/share/translations/keepassx_fi.ts @@ -0,0 +1,2384 @@ + + + AboutDialog + + About KeePassXC + Tietoja ohjelmasta KeePassXC + + + About + + + + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + Muista tämä valinta + + + Allow + Salli + + + Deny + Estä + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + %1 pyytää pääsyä seuraavien alkioiden salasanoihin. +Ole hyvä ja valitse sallitaanko pääsy. + + + KeePassXC HTTP Confirm Access + + + + + AutoType + + Couldn't find an entry that matches the window title: + Ikkunan nimeä vastaavaa merkintää ei löytynyt: + + + Auto-Type - KeePassXC + Automaattitäydennys - KeePassXC + + + + AutoTypeAssociationsModel + + Window + Ikkuna + + + Sequence + Sekvenssi + + + Default sequence + Oletussekvenssi + + + + AutoTypeSelectDialog + + Select entry to Auto-Type: + Valitse merkintä automaattitäydennystä varten: + + + Auto-Type - KeePassXC + Automaattitäydennys - KeePassXC + + + + ChangeMasterKeyWidget + + Password + Salasana + + + Enter password: + Syötä salasana: + + + Repeat password: + Toista salasana: + + + Browse + Selaa + + + Create + Luo + + + Key files + Avaintiedostot + + + All files + Kaikki tiedostot + + + Create Key File... + Luo avaintiedosto... + + + Unable to create Key File : + Avaintiedoston luonti ei onnistunut: + + + Select a key file + Valitse avaintiedosto + + + Do you really want to use an empty string as password? + Haluatko varmasti asettaa tyhjän merkkijonon salasanaksi? + + + Different passwords supplied. + Annetut salasanat eivät täsmää. + + + Failed to set %1 as the Key file: +%2 + + + + &Key file + Avaintiedosto + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Virhe + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Virhe + + + Unable to calculate master key + Pääavaimen laskeminen ei onnistu + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + + + + DatabaseOpenWidget + + Enter master key + Syötä pääsalasana + + + Key File: + Avaintiedosto: + + + Password: + Salasana + + + Browse + Selaa + + + Unable to open the database. + Tietokannan avaaminen ei onnistunut. + + + Can't open key file + Avaintiedostoa ei voitu avata + + + All files + Kaikki tiedostot + + + Key files + Avaintiedostot + + + Select key file + Valitse avaintiedosto + + + Refresh + + + + Challenge Response: + + + + + DatabaseRepairWidget + + Repair database + Korjaa tietokanta + + + Error + Virhe + + + Can't open key file + Avaintiedoston avaaminen epäonnistui + + + Database opened fine. Nothing to do. + Tietokannan avaaminen onnistui. Ei tehtävää. + + + Unable to open the database. + Tietokannan avaaminen epäonnistui. + + + Success + Onnistui! + + + The database has been successfully repaired +You can now save it. + Tietokanta korjattiin onnistuneesti. +Voit nyt tallentaa sen. + + + Unable to repair the database. + Tietokannan korjaus epäonnistui. + + + + DatabaseSettingsWidget + + Database name: + Tietokannan nimi: + + + Database description: + Tietokannan kuvaus: + + + Transform rounds: + Muunnoskierroksia: + + + Default username: + Oletuskäyttäjänimi: + + + MiB + MiB + + + Benchmark + Suorituskykytesti + + + Max. history items: + Maks. historia-alkioiden lukumäärä: + + + Max. history size: + Maks. historian koko: + + + Use recycle bin + Käytä roskakoria + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + + + + DatabaseTabWidget + + Root + Juuri + + + KeePass 2 Database + KeePass 2 -tietokanta + + + All files + Kaikki tiedostot + + + Open database + Avaa tietokanta + + + File not found! + Tiedostoa ei löytynyt! + + + Open KeePass 1 database + Avaa KeePass 1 -tietokanta + + + KeePass 1 database + KeePass 1 -tietokanta + + + All files (*) + Kaikki tiedostot(*) + + + Close? + Sulje? + + + Save changes? + Tallenna muutokset? + + + "%1" was modified. +Save changes? + + + + Writing the database failed. + Tietokannan kirjoitus levylle epäonnistui. + + + Save database as + Tallenna tietokanta nimellä + + + New database + Uusi tietokanta + + + locked + Lukittu + + + Lock database + Lukitse tietokanta + + + Can't lock the database as you are currently editing it. +Please press cancel to finish your changes or discard them. + Tietokantaa ei voida lukita, sillä se on muokkaustilassa. +Paina Peruuta jos haluat viimeistellä muutoksesi, muussa tapauksessa muutoksesi hylätään. + + + This database has never been saved. +You can save the database or stop locking it. + + + + This database has been modified. +Do you want to save the database before locking it? +Otherwise your changes are lost. + + + + "%1" is in edit mode. +Discard changes and close anyway? + "%1" on muokkaustilassa. +Hylkää muutokset ja sulje? + + + Export database to CSV file + Vie tietokanta CSV-tiedostoon + + + CSV file + CSV-tiedosto + + + Writing the CSV file failed. + CSV-tiedoston kirjoitus levylle epäonnistui. + + + Unable to open the database. + Tietokannan avaaminen ei onnistunut. + + + Merge database + Yhdistä tietokanta + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + Tietokanta jota yrität avata on avoinna toisessa KeePassXC-ikkunassa. +Haluatko tallentaa tietokannan siitä huolimatta? + + + Passwords + Salasanat + + + Database already opened + Tietokanta on jo avattu + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + Avaa "vain luku"-tilassa + + + File opened in read only mode. + + + + Open CSV file + + + + + DatabaseWidget + + Change master key + Vaihda pääsalasana + + + Delete entry? + Poista merkintä? + + + Do you really want to delete the entry "%1" for good? + Haluatko varmasti poistaa merkinnän "%1", lopullisesti? + + + Delete entries? + Poista alkiot? + + + Do you really want to delete %1 entries for good? + Haluatko varmasti poistaa alkiot %1, lopullisesti? + + + Move entries to recycle bin? + Siirrä alkiot roskakoriin? + + + Do you really want to move %n entry(s) to the recycle bin? + Haluatko varmasti siirtää %n kappaletta alkioita roskakoriin?Haluatko varmasti siirtää %n merkintää roskakoriin? + + + Delete group? + Poista ryhmä? + + + Do you really want to delete the group "%1" for good? + Haluatko varmasti poistaa ryhmän "%1", lopullisesti? + + + Unable to calculate master key + Pääavaimen laskeminen ei onnistu + + + Move entry to recycle bin? + Siirrä merkintä roskakoriin? + + + Do you really want to move entry "%1" to the recycle bin? + + + + Searching... + Etsitään... + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + Etsinnän tulokset (%1) + + + No Results + Ei tuloksia. + + + Execute command? + Suorita komento? + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + Muista valintani + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + + + + EditEntryWidget + + Entry + Merkintä + + + Advanced + Lisäasetukset + + + Icon + Kuvake + + + Auto-Type + Automaattitäydennys + + + Properties + Ominaisuudet + + + History + Historia + + + Entry history + Alkioiden historia + + + Add entry + Lisää alkio + + + Edit entry + Muokkaa alkiota + + + Different passwords supplied. + Annetut salasanat eivät täsmää. + + + New attribute + Uusi attribuutti + + + Select file + Valitse tiedosto + + + Unable to open file + Tiedoston avaus ei onnistu + + + Save attachment + Tallenna liite + + + Unable to save the attachment: + + Liitteen tallentaminen ei onnistu: + + + + Tomorrow + Huomenna + + + %n week(s) + %n viikkoa%n viikkoa + + + %n month(s) + %n kuukautta%n kuukautta + + + 1 year + + + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + + + + EditEntryWidgetAdvanced + + Additional attributes + + + + Add + Lisää + + + Remove + Poista + + + Attachments + Liitteet + + + Save + Tallenna + + + Open + Avaa + + + Edit Name + + + + Protect + + + + Reveal + + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Salli automaattitäydennys tälle merkinnälle + + + + + + + + + - + - + + + Window title: + Ikkunan otsikko: + + + Inherit default Auto-Type sequence from the &group + Peri automaattitäydennyksen oletussekvenssi &ryhmältä + + + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + + + + + EditEntryWidgetHistory + + Show + Näytä + + + Restore + Palauta + + + Delete + Poista + + + Delete all + Poista kaikki + + + + EditEntryWidgetMain + + Title: + Otsikko: + + + Username: + Käyttäjänimi + + + Password: + Salasana + + + Repeat: + Toista: + + + URL: + URL: + + + Expires + Erääntyy + + + Presets + Esiasetus + + + Notes: + Muistiinpanot: + + + + EditGroupWidget + + Group + Ryhmä + + + Icon + Kuvake + + + Properties + Ominaisuudet + + + Add group + Lisää ryhmä + + + Edit group + Muokkaa ryhmää + + + Enable + Kytke päälle + + + Disable + Kytke pois päältä + + + Inherit from parent group (%1) + Peri ylemmältä ryhmältä (%1) + + + + EditGroupWidgetMain + + Name + Nimi + + + Notes + Muistiinpanot + + + Expires + Erääntyy + + + Search + Etsi + + + Auto-Type + Automaattitäydennys + + + &Use default Auto-Type sequence of parent group + + + + Set default Auto-Type se&quence + + + + + EditWidgetIcons + + Add custom icon + + + + Delete custom icon + + + + Images + Kuvat + + + All files + Kaikki tiedostot + + + Select Image + Valitse kuva + + + Error + Virhe + + + Download favicon + Lataa favicon + + + Unable to fetch favicon. + Faviconin noutaminen ei onnistu + + + Can't read icon + Kuvaketta ei voida lukea + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + + + + EditWidgetProperties + + Created: + Luotu: + + + Modified: + Muokattu: + + + Accessed: + Käytetty: + + + Uuid: + UUID: + + + + Entry + + - Clone + - Klooni + + + + EntryAttributesModel + + Name + Nimi + + + + EntryHistoryModel + + Last modified + Viimeksi muokattu + + + Title + Otsikko + + + Username + Käyttäjätunnus + + + URL + URL + + + + EntryModel + + Group + Ryhmä + + + Title + Otsikko + + + Username + Käyttäjätunnus + + + URL + URL + + + Ref: + Reference abbreviation + + + + + Group + + Recycle Bin + Roskakori + + + + HttpPasswordGeneratorWidget + + Length: + Pituus: + + + Character Types + Merkkityypit + + + Upper Case Letters + Isot kirjaimet + + + A-Z + A-Z + + + Lower Case Letters + Pienet kirjaimet + + + a-z + a-z + + + Numbers + Numerot + + + 0-9 + 0-9 + + + Special Characters + Erikoismerkit + + + /*_& ... + /*_& ... + + + Exclude look-alike characters + Poissulje samannäköiset merkit + + + Ensure that the password contains characters from every group + Varmista, että salasana sisältää merkkejä jokaisesta ryhmästä + + + + KMessageWidget + + &Close + + + + Close message + + + + + KeePass1OpenWidget + + Import KeePass1 database + Tuo KeePass 1 -tietokanta + + + Unable to open the database. + Tietokannan avaaminen ei onnistunut. + + + + KeePass1Reader + + Unable to read keyfile. + Avaintiedoston luku ei onnistu + + + Not a KeePass database. + Tiedosto ei ole KeePass-tietokanta + + + Unsupported encryption algorithm. + Tukematon salausalgoritmi. + + + Unsupported KeePass database version. + Tukematon KeePass-tietokantaversio + + + Root + Juuri + + + Unable to calculate master key + Pääavaimen laskeminen ei onnistu + + + Wrong key or database file is corrupt. + Väärä avain tai tietokanta on korruptoitunut. + + + + KeePass2Reader + + Not a KeePass database. + Tiedosto ei ole KeePass-tietokanta + + + Unsupported KeePass database version. + Tukematon KeePass-tietokantaversio + + + Wrong key or database file is corrupt. + Väärä avain tai tietokanta on korruptoitunut. + + + Unable to calculate master key + Pääavaimen laskeminen ei onnistu + + + The selected file is an old KeePass 1 database (.kdb). + +You can import it by clicking on Database > 'Import KeePass 1 database'. +This is a one-way migration. You won't be able to open the imported database with the old KeePassX 0.4 version. + + + + Unable to issue challenge-response. + + + + + Main + + Fatal error while testing the cryptographic functions. + + + + KeePassXC - Error + KeePassXC - Virhe + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + + + + MainWindow + + Open database + Avaa tietokanta + + + Database settings + Tietokannan asetukset + + + Copy username to clipboard + Kopioi käyttäjätunnus leikepöydälle + + + Copy password to clipboard + Kopioi salasana leikepöydälle + + + Settings + Asetukset + + + Show toolbar + Näytä työkalupalkki + + + read-only + vain-luku + + + Toggle window + + + + KeePass 2 Database + Keepass 2 -tietokanta + + + All files + Kaikki tiedostot + + + Save repaired database + Tallenna korjattu tietokanta + + + Writing the database failed. + Tietokannan kirjoitus levylle epäonnistui. + + + &Recent databases + Viimeisimmät tietokannat + + + He&lp + Apua + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + Ryhmät + + + &View + Näkymä + + + &Quit + Lopeta + + + &About + Tietoja + + + &Open database + Avaa tietokanta + + + &Save database + Tallenna tietokanta + + + &Close database + Sulje tietokanta + + + &New database + Uusi tietokanta + + + Merge from KeePassX database + + + + &Add new entry + Lisää merkintä + + + &View/Edit entry + Näytä/muokkaa merkintää + + + &Delete entry + Poista merkintä + + + &Add new group + Lisää uusi ryhmä + + + &Edit group + Muokkaa ryhmää + + + &Delete group + Poista ryhmä + + + Sa&ve database as + Tallenna tietokanta nimellä + + + Change &master key + Vaihda pääsalasana + + + &Database settings + Tietokannan asetukset + + + &Clone entry + Kloonaa merkintä + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + Etsi + + + Copy &username + Kopioi käyttäjätunnus + + + Cop&y password + Kopioi salasana + + + &Settings + Asetukset + + + &Perform Auto-Type + Suorita automaattitäydennys + + + &Open URL + Avaa URL + + + &Lock databases + Lukitse tietokannat + + + &Title + Otsikko + + + &URL + URL + + + &Notes + Muistiinpanot + + + &Export to CSV file + Vie CSV-tiedostoon + + + Re&pair database + Korjaa tietokanta + + + Password Generator + Salasanageneraattori + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + Dialogi + + + General + Yleistä + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + Lisää.. + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + HTTP-portti: + + + Default port: 19455 + Oletusportti: 19455 + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Salasanageneraattori + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + + + + PasswordGeneratorWidget + + Password: + Salasana + + + Character Types + Merkkityypit + + + Upper Case Letters + Isot kirjaimet + + + Lower Case Letters + Pienet kirjaimet + + + Numbers + Numerot + + + Special Characters + Erikoismerkit + + + Exclude look-alike characters + Poissulje samannäköiset merkit + + + Accept + Hyväksy + + + %p% + + + + strength + + + + entropy + entropia + + + &Length: + + + + Pick characters from every group + + + + Generate + Generoi + + + Close + Sulje + + + Apply + + + + Entropy: %1 bit + + + + Password Quality: %1 + + + + Poor + Huono + + + Weak + Heikko + + + Good + Hyvä + + + Excellent + Erinomainen + + + Password + Salasana + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + + + + QObject + + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Ryhmä + + + Title + Otsikko + + + Username + Käyttäjätunnus + + + Password + Salasana + + + URL + URL + + + Notes + Muistiinpanot + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + + + + + QtIOCompressor + + Internal zlib error when compressing: + Sisäinen zlib virhe pakatessa: + + + Error writing to underlying device: + + + + Error opening underlying device: + + + + Error reading data from underlying device: + + + + Internal zlib error when decompressing: + + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + gzip-formaatti ei ole tuettu tässä zlib-versiossa. + + + Internal zlib error: + Sisäinen zlib-virhe: + + + + SearchWidget + + Case Sensitive + Kirjainkoko on merkitsevä + + + Search + Etsi + + + Clear + Tyhjennä + + + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + Keskeytä + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + + + + + SettingsWidget + + Application Settings + + + + General + Yleistä + + + Security + Turvallisuus + + + Access error for config file %1 + + + + + SettingsWidgetGeneral + + Remember last databases + Muista viimeisimmät tietokannat + + + Automatically save on exit + Tallenna automaattisesti suljettaessa + + + Automatically save after every change + Tallenna automaattisesti jokaisen muutoksen jälkeen + + + Minimize when copying to clipboard + Pienennä ikkuna kopioidessa leikepöydälle + + + Use group icon on entry creation + Käytä ryhmän kuvaketta merkintää luodessa + + + Global Auto-Type shortcut + Globaalin automaattitäydennyksen pikanäppäin + + + Language + Kieli + + + Show a system tray icon + Näytä ilmoitusalueen kuvake + + + Hide window to system tray when minimized + Piiloita pienennetty ikkuna ilmoitusalueelle + + + Load previous databases on startup + Lataa edelliset tietokannat käynnistäessä + + + Automatically reload the database when modified externally + Lataa tietokanta automaattisesti uudelleen jos tietokantaa muokattiin muualla + + + Hide window to system tray instead of app exit + Piiloita ikkuna ilmoitusalueelle sulkemisen sijaan + + + Minimize window at application startup + Pienennä ikkuna ohjelman käynnistyessä + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Automaattitäydennys + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + + + + SettingsWidgetSecurity + + Clear clipboard after + Tyhjennä leikepöytä kun on kulunut + + + sec + s. + + + Lock databases after inactivity of + Lukitse tietokannat jos on oltu joutilaana + + + Show passwords in cleartext by default + Näytä salasanat oletuksena selkokielisenä + + + Lock databases after minimizing the window + Lukitse tietokanta ikkunan pienennyksen jälkeen + + + Don't require password repeat when it is visible + Älä vaadi salasanan toistoa jos salasana on näkyvillä + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + s. + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + + + + UnlockDatabaseWidget + + Unlock database + Avaa tietokannan lukitus + + + + WelcomeWidget + + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + + + + + main + + path to a custom config file + + + + key file of the database + + + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + + + \ No newline at end of file diff --git a/share/translations/keepassx_fr.ts b/share/translations/keepassx_fr.ts index cccd183b5..7c14f5ef1 100644 --- a/share/translations/keepassx_fr.ts +++ b/share/translations/keepassx_fr.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - Révision - - - Using: - Utilise : - About KeePassXC À propos de KeePassXC - Extensions: - - Extensions : - + About + À propos - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC est distribué suivant les termes de la GNU Licence Publique Générale (GNU GPL) version 2 ou version 3 de la licence (à votre gré). + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -120,10 +200,6 @@ Veuillez sélectionner si vous souhaitez autoriser l’accès. Create Key File... Créer un fichier-clé... - - Error - Erreur - Unable to create Key File : Impossible de créer un fichier-clé : @@ -132,10 +208,6 @@ Veuillez sélectionner si vous souhaitez autoriser l’accès. Select a key file Choisir un fichier-clé - - Question - Question - Do you really want to use an empty string as password? Voulez-vous vraiment utiliser une chaîne vide comme mot de passe ? @@ -144,10 +216,6 @@ Veuillez sélectionner si vous souhaitez autoriser l’accès. Different passwords supplied. Les mots de passe ne sont pas identiques. - - Failed to set key file - Impossible de définir le fichier-clé - Failed to set %1 as the Key file: %2 @@ -158,6 +226,163 @@ Veuillez sélectionner si vous souhaitez autoriser l’accès. &Key file Fichier-clé + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Erreur + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Erreur + + + Unable to calculate master key + Impossible de calculer la clé maître + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -177,10 +402,6 @@ Veuillez sélectionner si vous souhaitez autoriser l’accès. Browse Naviguer - - Error - Erreur - Unable to open the database. Impossible d'ouvrir la base de données. @@ -201,6 +422,14 @@ Veuillez sélectionner si vous souhaitez autoriser l’accès. Select key file Choisissez un fichier-clé + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -277,6 +506,18 @@ Vous pouvez maintenant la sauvegarder. Use recycle bin Utiliser la corbeille + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -296,10 +537,6 @@ Vous pouvez maintenant la sauvegarder. Open database Ouvrir la base de données - - Warning - Attention - File not found! Fichier introuvable ! @@ -330,10 +567,6 @@ Save changes? "%1" a été modifié. Enregistrer les modifications ? - - Error - Erreur - Writing the database failed. Une erreur s'est produite lors de l'écriture de la base de données. @@ -426,6 +659,14 @@ Voulez vous l'ouvrir quand même ? Open read-only Ouvrir en lecture seule + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -465,10 +706,6 @@ Voulez vous l'ouvrir quand même ? Do you really want to delete the group "%1" for good? Voulez-vous supprimer le groupe "%1" définitivement ? - - Error - Erreur - Unable to calculate master key Impossible de calculer la clé maître @@ -529,14 +766,18 @@ Voulez vous l'ouvrir quand même ? The database file has changed and you have unsaved changes.Do you want to merge your changes? Le fichier de la base de données à changé et vous avez des modification non-enregistrés. Voulez-vous fusionner vos modifications? - - Autoreload Failed - Échec du rafraîchissement automatique - Could not open the new database file while attempting to autoreload this database. La nouvelle base de données ne peux être ouverte pendant qu'un rafraîchissement automatique de l'actuelle est en cours. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -576,10 +817,6 @@ Voulez vous l'ouvrir quand même ? Edit entry Modifier l'entrée - - Error - Erreur - Different passwords supplied. Les mots de passe ne sont pas identiques. @@ -622,6 +859,22 @@ Voulez vous l'ouvrir quand même ? 1 year 1 an + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -633,10 +886,6 @@ Voulez vous l'ouvrir quand même ? Add Ajouter - - Edit - Modifier - Remove Supprimer @@ -653,6 +902,18 @@ Voulez vous l'ouvrir quand même ? Open Ouvrir + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -688,6 +949,10 @@ Voulez vous l'ouvrir quand même ? Set custo&m sequence: Définir une séquence personnalisée : + + Window Associations + + EditEntryWidgetHistory @@ -797,16 +1062,16 @@ Voulez vous l'ouvrir quand même ? Chercher - Auto-type + Auto-Type Remplissage automatique - Use default auto-type sequence of parent group - Utiliser la séquence de remplissage automatique par défaut du groupe parent + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Définir une séquence de remplissage automatique par défaut + Set default Auto-Type se&quence + @@ -831,10 +1096,6 @@ Voulez vous l'ouvrir quand même ? Select Image Choisir une image - - Can't delete icon! - Impossible de supprimer l'icône ! - Error Erreur @@ -851,10 +1112,6 @@ Voulez vous l'ouvrir quand même ? Can't read icon Impossible de lire l'icône - - Can't delete icon. Still used by %1 items. - Impossible de supprimer l'icône. Encore utilisée par 1% éléments. - &Use default icon Utiliser l'icône par défaut @@ -863,6 +1120,14 @@ Voulez vous l'ouvrir quand même ? Use custo&m icon Utiliser une icône personnalisée + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -934,6 +1199,11 @@ Voulez vous l'ouvrir quand même ? URL URL + + Ref: + Reference abbreviation + + Group @@ -992,9 +1262,16 @@ Voulez vous l'ouvrir quand même ? Ensure that the password contains characters from every group S'assurer que le mot de passe contienne des caractères de chaque groupe + + + KMessageWidget - Accept - Accepter + &Close + + + + Close message + @@ -1003,10 +1280,6 @@ Voulez vous l'ouvrir quand même ? Import KeePass1 database Importer une base de données au format KeePass1 - - Error - Erreur - Unable to open the database. Impossible d'ouvrir la base de données. @@ -1071,6 +1344,10 @@ This is a one-way migration. You won't be able to open the imported databas Vous pouvez l'importer en cliquant sur "Base de données" > "Importer une base de données KeePass 1". Ceci est une migration à sens unique. Vous ne serez plus en mesure d'ouvrir la base de données importée avec l'ancienne version KeePassX version 0.4. + + Unable to issue challenge-response. + + Main @@ -1082,13 +1359,17 @@ Ceci est une migration à sens unique. Vous ne serez plus en mesure d'ouvri KeePassXC - Error KeePassXC - Erreur + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Base de données - Open database Ouvrir une base de données @@ -1121,10 +1402,6 @@ Ceci est une migration à sens unique. Vous ne serez plus en mesure d'ouvri Toggle window Basculer de fenêtre - - Tools - Outils - KeePass 2 Database Base de données KeePass 2 @@ -1137,10 +1414,6 @@ Ceci est une migration à sens unique. Vous ne serez plus en mesure d'ouvri Save repaired database Sauvegarder la base de données réparée - - Error - Erreur - Writing the database failed. Une erreur s'est produite lors de l'écriture de la base de données. @@ -1175,7 +1448,7 @@ Ceci est une migration à sens unique. Vous ne serez plus en mesure d'ouvri &About - &A propos + &À propos &Open database @@ -1233,14 +1506,26 @@ Ceci est une migration à sens unique. Vous ne serez plus en mesure d'ouvri &Database settings Paramètre de la base de &données - - &Import KeePass 1 database - &Importer 1 base de données KeePass - &Clone entry Cloner l'entrée + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find Trouver @@ -1293,6 +1578,46 @@ Ceci est une migration à sens unique. Vous ne serez plus en mesure d'ouvri Password Generator Générateur de mot de passe + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importer une base de données KeePass 1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1308,12 +1633,6 @@ Ceci est une migration à sens unique. Vous ne serez plus en mesure d'ouvri Sh&ow a notification when credentials are requested Montrer une notification quand les références sont demandées - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - & Cible des types d'URL -Seules les entrées de même type (http://, https://, ftp://, ...) sont retournées - Sort matching entries by &username Trier les entrées correspondantes par nom d'&utilisateur @@ -1322,10 +1641,6 @@ Seules les entrées de même type (http://, https://, ftp://, ...) sont retourn Re&move all stored permissions from entries in active database Supprimer toutes les permissions enregistrées des entrées de la base de données active - - Password generator - Générateur de mots de passe - Advanced Avancé @@ -1342,10 +1657,6 @@ Seules les entrées de même type (http://, https://, ftp://, ...) sont retourn Searc&h in all opened databases for matching entries Cherc&her dans toutes les bases de données ouvertes les entrées correspondantes - - Only the selected database has to be connected with a client! - Seule la base de données sélectionnée doit être connectée à un client ! - HTTP Port: Port HTTP: @@ -1356,18 +1667,12 @@ Seules les entrées de même type (http://, https://, ftp://, ...) sont retourn Re&quest to unlock the database if it is locked - Demander de déverrouiller la base de données lorsque celle-ci est verrouiller + Demander de déverrouiller la base de données lorsque celle-ci est verrouillée Sort &matching entries by title Trier les entrées correspondantes par titre - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Activer le protocole HTTP de KeePassXC -Ce protocole est nécessaire si vous souhaitez accéder à vos bases de données avec ChromeIPas ou PassIFox - KeePassXC will listen to this port on 127.0.0.1 KeepassXC va écouter ce port sur 127.0.0.1 @@ -1381,21 +1686,11 @@ Ce protocole est nécessaire si vous souhaitez accéder à vos bases de données Using default port 19455. Liaison impossible avec les ports privilégiés, ceux avant 1024 ! Restauration du port 19455 par défaut. - - - &Return only best matching entries for a URL instead -of all entries for the whole domain - & Retourne seulement les meilleures entrées correspondantes pour une URL, -au lieu de toutes pour le domaine entier R&emove all shared encryption keys from active database Supprimer toutes les clés de chiffrement partagées de la base de données active - - The following options can be dangerous. Change them only if you know what you are doing. - La modification de ces préférences peuvent entrainer des problèmes de sécurité. Ne continuez que si vous savez ce que vous faites ! - &Return advanced string fields which start with "KPH: " & Retourne les champs avancés de type chaîne qui commencent par "KPH:" @@ -1404,6 +1699,43 @@ au lieu de toutes pour le domaine entier Automatically creating or updating string fields is not supported. La création ou la mise a jour automatique ne sont pas pris en charge pour les champs de chaines de caractères ! + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Générateur de mot de passe + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1495,12 +1827,101 @@ au lieu de toutes pour le domaine entier Excellent Excellent + + Password + Mot de passe + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Groupe + + + Title + Titre + + + Username + Nom d'utilisateur + + + Password + Mot de passe + + + URL + URL + + + Notes + Notes + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1547,14 +1968,18 @@ au lieu de toutes pour le domaine entier Search Chercher - - Find - Trouver - Clear Effacer + + Search... + + + + Limit search to selected group + + Service @@ -1661,6 +2086,10 @@ attribuez lui un nom unique pour l'identifier et acceptez la. Security Sécurité + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1688,10 +2117,6 @@ attribuez lui un nom unique pour l'identifier et acceptez la. Global Auto-Type shortcut Raccourci de remplissage automatique global - - Use entry title to match windows for global auto-type - Utiliser le titre d’entrée pour correspondre à windows pour auto-type global - Language Langue @@ -1704,17 +2129,13 @@ attribuez lui un nom unique pour l'identifier et acceptez la. Hide window to system tray when minimized Réduire la fenêtre vers la zone de notification lors de sa réduction - - Remember last key files - Se rappeler les derniers fichiers-clés ouverts - Load previous databases on startup Charger les bases de données précédentes au démarrage Automatically reload the database when modified externally - Recharger automatiquement la base de données quand celle-ci est modifier depuis l'extérieur + Recharger automatiquement la base de données quand celle-ci est modifiée depuis l'extérieur Hide window to system tray instead of app exit @@ -1724,6 +2145,30 @@ attribuez lui un nom unique pour l'identifier et acceptez la. Minimize window at application startup Minimiser la fenêtre lors du démarrage de l'application + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Remplissage automatique + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1743,10 +2188,6 @@ attribuez lui un nom unique pour l'identifier et acceptez la. Show passwords in cleartext by default Afficher les mots de passe en clair par défaut - - Always ask before performing auto-type - Toujours demander avant d'effectuer un remplissage automatique - Lock databases after minimizing the window Verrouiller la base de données lorsque la fenêtre est minimisée @@ -1755,6 +2196,80 @@ attribuez lui un nom unique pour l'identifier et acceptez la. Don't require password repeat when it is visible Ne pas demander de répéter le mot de passe lorsque celui-ci est visible + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + s + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1766,8 +2281,32 @@ attribuez lui un nom unique pour l'identifier et acceptez la. WelcomeWidget - Welcome! - Bienvenue ! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Bases de données récentes @@ -1792,5 +2331,69 @@ attribuez lui un nom unique pour l'identifier et acceptez la. filenames of the password databases to open (*.kdbx) noms de fichiers des bases de données de mot de passe à ouvrir (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_id.ts b/share/translations/keepassx_id.ts index 25cc301ec..cddd1b6ee 100644 --- a/share/translations/keepassx_id.ts +++ b/share/translations/keepassx_id.ts @@ -1,33 +1,143 @@ - + AboutDialog - About KeePassX - Tentang KeePassX + About KeePassXC + - KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassX disebarluaskan dibawah ketentuan dari Lisensi Publik Umum GNU (GPL) versi 2 atau (sesuai pilihan Anda) versi 3. + About + Tentang - Revision - Revisi + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + - Using: - Menggunakan: + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + + + + Allow + + + + Deny + + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + + + + KeePassXC HTTP Confirm Access + AutoType - - Auto-Type - KeePassX - Ketik-Otomatis - KeePassX - Couldn't find an entry that matches the window title: Tidak bisa menemukan entri yang cocok dengan judul jendela: + + Auto-Type - KeePassXC + + AutoTypeAssociationsModel @@ -46,14 +156,14 @@ AutoTypeSelectDialog - - Auto-Type - KeePassX - Ketik-Otomatis - KeePassX - Select entry to Auto-Type: Pilih entri untuk Ketik-Otomatis: + + Auto-Type - KeePassXC + + ChangeMasterKeyWidget @@ -69,10 +179,6 @@ Repeat password: Ulangi sandi: - - Key file - Berkas kunci - Browse Telusuri @@ -93,10 +199,6 @@ Create Key File... Buat Berkas Kunci... - - Error - Galat - Unable to create Key File : Tidak bisa membuat Berkas Kunci : @@ -105,10 +207,6 @@ Select a key file Pilih berkas kunci - - Question - Pertanyaan - Do you really want to use an empty string as password? Apakah Anda benar-benar ingin menggunakan lema kosong sebagai sandi? @@ -117,16 +215,173 @@ Different passwords supplied. Sandi berbeda. - - Failed to set key file - Gagal menetapkan berkas kunci - Failed to set %1 as the Key file: %2 Gagal menetapkan %1 sebagai berkas Kunci: %2 + + &Key file + + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Galat + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Galat + + + Unable to calculate master key + Tidak bisa mengkalkulasi kunci utama + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -146,10 +401,6 @@ Browse Telusuri - - Error - Galat - Unable to open the database. Tidak bisa membuka basis data. @@ -170,6 +421,14 @@ Select key file Pilih berkas kunci + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -226,10 +485,6 @@ Anda bisa menyimpannya sekarang. Default username: Nama pengguna baku: - - Use recycle bin: - Gunakan tong sampah: - MiB MiB @@ -246,6 +501,22 @@ Anda bisa menyimpannya sekarang. Max. history size: Maks. ukuran riwayat: + + Use recycle bin + + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -265,10 +536,6 @@ Anda bisa menyimpannya sekarang. Open database Buka basis data - - Warning - Peringatan - File not found! Berkas tidak ditemukan! @@ -299,10 +566,6 @@ Save changes? "%1" telah dimodifikasi. Simpan perubahan? - - Error - Galat - Writing the database failed. Gagal membuat basis data. @@ -319,12 +582,6 @@ Simpan perubahan? locked terkunci - - The database you are trying to open is locked by another instance of KeePassX. -Do you want to open it anyway? Alternatively the database is opened read-only. - Basis data yang Anda coba buka terkunci oleh KeePassX lain yang sedang berjalan. -Apakah Anda tetap ingin membukanya? Alternatif lain buka basis data baca-saja. - Lock database Kunci basis data @@ -368,13 +625,42 @@ Tetap buang ubahan dan tutup? Gagal membuat berkas CSV. - The database you are trying to save as is locked by another instance of KeePassX. -Do you want to save it anyway? - Basis data yang Anda coba buka terkunci oleh KeePassX lain yang sedang berjalan. -Apakah Anda tetap ingin menyimpannya? + Unable to open the database. + Tidak bisa membuka basis data. - Unable to open the database. + Merge database + + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + + + + Passwords + + + + Database already opened + + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + + + + File opened in read only mode. + + + + Open CSV file @@ -416,14 +702,6 @@ Apakah Anda tetap ingin menyimpannya? Do you really want to delete the group "%1" for good? Apakah Anda benar-benar ingin menghapus grup "%1" untuk selamanya? - - Current group - Grup saat ini - - - Error - Galat - Unable to calculate master key Tidak bisa mengkalkulasi kunci utama @@ -436,6 +714,66 @@ Apakah Anda tetap ingin menyimpannya? Do you really want to move entry "%1" to the recycle bin? + + Searching... + + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + + + + No Results + + + + Execute command? + + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -475,10 +813,6 @@ Apakah Anda tetap ingin menyimpannya? Edit entry Sunting entri - - Error - Galat - Different passwords supplied. Sandi berbeda. @@ -521,6 +855,22 @@ Apakah Anda tetap ingin menyimpannya? 1 year 1 tahun + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -532,10 +882,6 @@ Apakah Anda tetap ingin menyimpannya? Add Tambah - - Edit - Sunting - Remove Buang @@ -552,6 +898,18 @@ Apakah Anda tetap ingin menyimpannya? Open Buka + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -559,14 +917,6 @@ Apakah Anda tetap ingin menyimpannya? Enable Auto-Type for this entry Aktifkan Ketik-Otomatis untuk entri ini - - Inherit default Auto-Type sequence from the group - Mengikuti urutan Ketik-Otomatis baku grup - - - Use custom Auto-Type sequence: - Gunakan urutan Ketik-Otomatis ubahsuai: - + + @@ -580,12 +930,24 @@ Apakah Anda tetap ingin menyimpannya? Judul jendela: - Use default sequence - Gunakan urutan baku + Inherit default Auto-Type sequence from the &group + - Set custom sequence: - Tetapkan urutan ubahsuai: + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + @@ -625,10 +987,6 @@ Apakah Anda tetap ingin menyimpannya? Repeat: Ulangi: - - Gen. - Gen. - URL: URL: @@ -700,28 +1058,20 @@ Apakah Anda tetap ingin menyimpannya? Cari - Auto-type - Ketik-otomatis + Auto-Type + Ketik-Otomatis - Use default auto-type sequence of parent group - Gunakan urutan ketik-otomatis baku grup induk + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Tetapkan urutan ketik-otomatis baku + Set default Auto-Type se&quence + EditWidgetIcons - - Use default icon - Gunakan ikon baku - - - Use custom icon - Gunakan ikon ubahsuai - Add custom icon Tambah ikon ubahsuai @@ -743,19 +1093,35 @@ Apakah Anda tetap ingin menyimpannya? Pilih gambar - Can't delete icon! - Tidak bisa menghapus ikon! - - - Can't delete icon. Still used by %n item(s). - Tidak bisa menghapus ikon. Masih digunakan oleh %n item. + Error + Galat - Error + Download favicon - Can't read icon: + Unable to fetch favicon. + + + + Can't read icon + + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? @@ -778,6 +1144,13 @@ Apakah Anda tetap ingin menyimpannya? Uuid: + + Entry + + - Clone + + + EntryAttributesModel @@ -822,6 +1195,11 @@ Apakah Anda tetap ingin menyimpannya? URL URL + + Ref: + Reference abbreviation + + Group @@ -830,16 +1208,74 @@ Apakah Anda tetap ingin menyimpannya? Tong Sampah + + HttpPasswordGeneratorWidget + + Length: + Panjang: + + + Character Types + Tipe Karakter + + + Upper Case Letters + Huruf Besar + + + A-Z + + + + Lower Case Letters + Huruf Kecil + + + a-z + + + + Numbers + Angka + + + 0-9 + + + + Special Characters + Karakter Spesial + + + /*_& ... + + + + Exclude look-alike characters + Kecualikan karakter mirip + + + Ensure that the password contains characters from every group + Pastikan sandi berisi karakter dari setiap grup + + + + KMessageWidget + + &Close + + + + Close message + + + KeePass1OpenWidget Import KeePass1 database Impor basis data KeePass1 - - Error - Galat - Unable to open the database. Tidak bisa membuka basis data. @@ -873,7 +1309,7 @@ Apakah Anda tetap ingin menyimpannya? Wrong key or database file is corrupt. - + Kunci salah atau berkas basis data rusak. @@ -904,6 +1340,10 @@ This is a one-way migration. You won't be able to open the imported databas Anda bisa mengimpornya dengan mengklik Basis Data > 'Impor basis data KeePass 1'. Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang diimpor dengan versi lama KeePassX 0.4. + + Unable to issue challenge-response. + + Main @@ -912,112 +1352,28 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Galat saat menguji fungsi kriptografi. - KeePassX - Error - KeePassX - Galat + KeePassXC - Error + + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - Basis data - - - Recent databases - Basis data baru-baru ini - - - Help - Bantuan - - - Entries - Entri - - - Copy attribute to clipboard - Salin atribut ke papan klip - - - Groups - Grup - - - View - Lihat - - - Quit - Keluar - - - About - Tentang - Open database Buka basis data - - Save database - Simpan basis data - - - Close database - Tutup basis data - - - New database - Basis data baru - - - Add new entry - Tambah entri baru - - - View/Edit entry - Lihat/Sunting entri - - - Delete entry - Hapus entri - - - Add new group - Tambah grup baru - - - Edit group - Sunting grup - - - Delete group - Hapus grup - - - Save database as - Simpan basis data sebagai - - - Change master key - Ubah kunci utama - Database settings Pengaturan basis data - - Import KeePass 1 database - Impor basis data KeePass 1 - - - Clone entry - Duplikat entri - - - Find - Temukan - Copy username to clipboard Salin nama pengguna ke papan klip @@ -1030,30 +1386,6 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Settings Pengaturan - - Perform Auto-Type - Lakukan Ketik-Otomatis - - - Open URL - Buka URL - - - Lock databases - Kunci basis data - - - Title - Judul - - - URL - URL - - - Notes - Catatan - Show toolbar Tampilkan bilah alat @@ -1066,26 +1398,6 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Toggle window Jungkit jendela - - Tools - Perkakas - - - Copy username - Salin nama pengguna - - - Copy password - Salin sandi - - - Export to CSV file - Ekspor ke berkas CSV - - - Repair database - Perbaiki basis data - KeePass 2 Database Basis Data KeePass 2 @@ -1098,14 +1410,327 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Save repaired database Simpan basis data yang sudah diperbaiki - - Error - Galat - Writing the database failed. Gagal menyimpan basis data. + + &Recent databases + + + + He&lp + + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + + + + &View + + + + &Quit + + + + &About + + + + &Open database + + + + &Save database + + + + &Close database + + + + &New database + + + + Merge from KeePassX database + + + + &Add new entry + + + + &View/Edit entry + + + + &Delete entry + + + + &Add new group + + + + &Edit group + + + + &Delete group + + + + Sa&ve database as + + + + Change &master key + + + + &Database settings + + + + &Clone entry + + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + + + + Copy &username + + + + Cop&y password + + + + &Settings + + + + &Perform Auto-Type + + + + &Open URL + + + + &Lock databases + + + + &Title + + + + &URL + + + + &Notes + + + + &Export to CSV file + + + + Re&pair database + + + + Password Generator + + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Impor basis data KeePass 1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + + + + General + Umum + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + Tingkat Lanjut + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + + + + Default port: 19455 + + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1113,10 +1738,6 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Password: Sandi: - - Length: - Panjang: - Character Types Tipe Karakter @@ -1141,71 +1762,161 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Exclude look-alike characters Kecualikan karakter mirip - - Ensure that the password contains characters from every group - Pastikan sandi berisi karakter dari setiap grup - Accept Terima - - - QCommandLineParser - Displays version information. - Tampilkan informasi versi. + %p% + - Displays this help. - Tampilkan bantuan ini. + strength + - Unknown option '%1'. - Opsi tidak diketahui '%1'. + entropy + - Unknown options: %1. - Opsi tidak diketahui: %1. + &Length: + - Missing value after '%1'. - Nilai hilang setelah '%1'. + Pick characters from every group + - Unexpected value after '%1'. - Nilai tidak terduga setelah '%1'. + Generate + - [options] - [opsi] + Close + - Usage: %1 - Penggunaan: %1 + Apply + - Options: - Opsi: + Entropy: %1 bit + - Arguments: - Argumen: + Password Quality: %1 + + + + Poor + + + + Weak + + + + Good + + + + Excellent + + + + Password + Sandi + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + - QSaveFile + QObject - Existing file %1 is not writable - Berkas yang ada %1 tidak bisa ditulis + NULL device + - Writing canceled by application - Penulisan dibatalkan oleh aplikasi + error reading from device + - Partial write. Partition full? - Penulisan parsial. Partisi penuh? + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Grup + + + Title + Judul + + + Username + Nama pengguna + + + Password + Sandi + + + URL + URL + + + Notes + Catatan + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1245,20 +1956,111 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang SearchWidget - Find: - Temukan: + Case Sensitive + - Case sensitive - Sensitif besar kecil huruf + Search + Cari - Current group - Grup saat ini + Clear + - Root group - Grup root + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + @@ -1275,6 +2077,10 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Security Keamanan + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1282,10 +2088,6 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Remember last databases Ingat basis data terakhir - - Open previous databases on startup - Buka basis data sebelumnya saat mulai - Automatically save on exit Otomatis simpan ketika keluar @@ -1306,10 +2108,6 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Global Auto-Type shortcut Pintasan global Ketik-Otomatis - - Use entry title to match windows for global auto-type - Gunakan judul entri untuk mencocokkan jendela untuk ketik-otomatis global - Language Bahasa @@ -1323,15 +2121,43 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Sembunyikan jendela ke baki sistem ketika diminimalkan - Remember last key files - Ingat berkas kunci terakhir - - - Hide window to system tray instead of App Exit + Load previous databases on startup - Hide window to system tray on App start + Automatically reload the database when modified externally + + + + Hide window to system tray instead of app exit + + + + Minimize window at application startup + + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Ketik-Otomatis + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type @@ -1354,8 +2180,86 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang Tampilkan teks sandi secara baku - Always ask before performing auto-type - Selalu tanya sebelum melakukan ketik-otomatis + Lock databases after minimizing the window + + + + Don't require password repeat when it is visible + + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + det + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1368,20 +2272,36 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang WelcomeWidget - Welcome! - Selamat datang! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Basis data baru-baru ini main - - KeePassX - cross-platform password manager - KeePassX - pengelola sandi lintas platform - - - filename of the password database to open (*.kdbx) - nama berkas dari basis data sandi untuk dibuka (*.kdbx) - path to a custom config file jalur ke berkas konfig ubahsuai @@ -1390,5 +2310,81 @@ Ini adalah migrasi satu arah. Anda tidak akan bisa lagi membuka basis data yang key file of the database berkas kunci dari basis data + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_it.ts b/share/translations/keepassx_it.ts index 791942838..f5f7fede3 100644 --- a/share/translations/keepassx_it.ts +++ b/share/translations/keepassx_it.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - Revisione - - - Using: - Utilizzare: - About KeePassXC A proposito di KeePassXC - Extensions: - - Estensioni: - + About + Informazioni - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC è distribuito sotto i termini della licenza GNU General Public License (GPL) versione 2 o (come opzione) versione 3. + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -120,10 +200,6 @@ Perfavore seleziona se vuoi consentire l'accesso. Create Key File... Crea file chiave... - - Error - Errore - Unable to create Key File : Impossibile creare file chiave: @@ -132,10 +208,6 @@ Perfavore seleziona se vuoi consentire l'accesso. Select a key file Seleziona il file chiave - - Question - Domanda - Do you really want to use an empty string as password? Vuoi veramente usare una stringa vuota come password? @@ -144,10 +216,6 @@ Perfavore seleziona se vuoi consentire l'accesso. Different passwords supplied. Sono state fornite password differenti. - - Failed to set key file - Fallimento a impostare il file chiave - Failed to set %1 as the Key file: %2 @@ -158,6 +226,163 @@ Perfavore seleziona se vuoi consentire l'accesso. &Key file &File chiave + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Errore + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Errore + + + Unable to calculate master key + Impossibile calcolare la chiave principale + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -177,10 +402,6 @@ Perfavore seleziona se vuoi consentire l'accesso. Browse Sfoglia - - Error - Errore - Unable to open the database. Impossibile aprire il database. @@ -201,6 +422,14 @@ Perfavore seleziona se vuoi consentire l'accesso. Select key file Seleziona file chiave + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -277,6 +506,18 @@ Adesso puoi salvarlo. Use recycle bin Usa il cestino + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -296,10 +537,6 @@ Adesso puoi salvarlo. Open database Apri database - - Warning - Attenzione - File not found! File non trovato! @@ -330,10 +567,6 @@ Save changes? "%1" è stata modificato. Salvare le modifiche? - - Error - Errore - Writing the database failed. Scrittura del database non riuscita. @@ -425,6 +658,14 @@ Vuoi aprilo comunque? Open read-only Aperto in sola lettura + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -464,10 +705,6 @@ Vuoi aprilo comunque? Do you really want to delete the group "%1" for good? Vuoi veramente eliminare il gruppo "%1"? - - Error - Errore - Unable to calculate master key Impossibile calcolare la chiave principale @@ -528,14 +765,18 @@ Vuoi aprilo comunque? The database file has changed and you have unsaved changes.Do you want to merge your changes? Il file del database è cambiato e ci sono delle modifiche non salvate. Vuoi unire i tuoi cambiamenti. - - Autoreload Failed - Aggiornamento fallito - Could not open the new database file while attempting to autoreload this database. Non è stato possibile aprire il nuovo database mentre si tentava il caricamento automatico di questo database. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -575,10 +816,6 @@ Vuoi aprilo comunque? Edit entry Modificare voce - - Error - Errore - Different passwords supplied. Sono state immesse password differenti. @@ -621,6 +858,22 @@ Vuoi aprilo comunque? 1 year Un anno + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -632,10 +885,6 @@ Vuoi aprilo comunque? Add Aggiungi - - Edit - Modifica - Remove Rimuovi @@ -652,6 +901,18 @@ Vuoi aprilo comunque? Open Apri + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -687,6 +948,10 @@ Vuoi aprilo comunque? Set custo&m sequence: Imposta sequenza &personalizzata: + + Window Associations + + EditEntryWidgetHistory @@ -796,16 +1061,16 @@ Vuoi aprilo comunque? Cerca - Auto-type + Auto-Type Auto-Type - Use default auto-type sequence of parent group - Usa la sequenza auto-type del gruppo genitore + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Imposta la sequenza auto-type + Set default Auto-Type se&quence + @@ -830,10 +1095,6 @@ Vuoi aprilo comunque? Select Image Seleziona immagine - - Can't delete icon! - Non puoi eliminare l'icona! - Error Errore @@ -850,10 +1111,6 @@ Vuoi aprilo comunque? Can't read icon Impossibile leggere l'icona - - Can't delete icon. Still used by %1 items. - Non puoi eliminare l'icona. Utilizzata da %1 voce. - &Use default icon &Usa icona predefinita @@ -862,6 +1119,14 @@ Vuoi aprilo comunque? Use custo&m icon Usa &icona personalizzata + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -933,6 +1198,11 @@ Vuoi aprilo comunque? URL URL + + Ref: + Reference abbreviation + + Group @@ -991,9 +1261,16 @@ Vuoi aprilo comunque? Ensure that the password contains characters from every group Verifica che la password contenga caratteri di ogni gruppo + + + KMessageWidget - Accept - Accetta + &Close + + + + Close message + @@ -1002,10 +1279,6 @@ Vuoi aprilo comunque? Import KeePass1 database Importa database KeePass1 - - Error - Errore - Unable to open the database. Impossibile aprire il database. @@ -1070,6 +1343,10 @@ This is a one-way migration. You won't be able to open the imported databas Puoi importarlo facendo clic su Database > 'Importa database KeePass 1'. Questa è una migrazione in una sola direzione. Non potrai aprire il database importato con la vecchia versione 0.4 di KeePassX. + + Unable to issue challenge-response. + + Main @@ -1081,13 +1358,17 @@ Questa è una migrazione in una sola direzione. Non potrai aprire il database im KeePassXC - Error KeePassXC - Errore + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Database - Open database Apri database @@ -1120,10 +1401,6 @@ Questa è una migrazione in una sola direzione. Non potrai aprire il database im Toggle window Cambia finestra - - Tools - Strumenti - KeePass 2 Database Database KeePass 2 @@ -1136,10 +1413,6 @@ Questa è una migrazione in una sola direzione. Non potrai aprire il database im Save repaired database Salva il database riparato - - Error - Errore - Writing the database failed. Scrittura del database non riuscita. @@ -1232,14 +1505,26 @@ Questa è una migrazione in una sola direzione. Non potrai aprire il database im &Database settings Impostazioni &Database - - &Import KeePass 1 database - &Importa database KeePass 1 - &Clone entry &Clona elemento + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find &Trova @@ -1292,6 +1577,46 @@ Questa è una migrazione in una sola direzione. Non potrai aprire il database im Password Generator Generatore Password + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importa database KeePass 1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1307,12 +1632,6 @@ Questa è una migrazione in una sola direzione. Non potrai aprire il database im Sh&ow a notification when credentials are requested M&ostra una notifica quando sono richeste le credenziali - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - &Schema URL -Solo le voci con lo stesso schema (http://, https://, ftp://, ...) sono selezionate - Sort matching entries by &username Ordina le voci trovate per &nome utente @@ -1321,10 +1640,6 @@ Solo le voci con lo stesso schema (http://, https://, ftp://, ...) sono selezion Re&move all stored permissions from entries in active database R&imuovi tutti i permessi presenti dalle voci nel database attivo - - Password generator - Generatore di password - Advanced Avanzate @@ -1341,10 +1656,6 @@ Solo le voci con lo stesso schema (http://, https://, ftp://, ...) sono selezion Searc&h in all opened databases for matching entries Cerc&a in tutti i database aperti per la ricerca delle voci - - Only the selected database has to be connected with a client! - Solo i database selezionati sono connessi con il client! - HTTP Port: Porta HTTP: @@ -1361,12 +1672,6 @@ Solo le voci con lo stesso schema (http://, https://, ftp://, ...) sono selezion Sort &matching entries by title Ordina le voci per &titolo - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Abilita il protocollo KeePassXC HTTP -Richiesto per accedere al tuo database da ChromeIPass o PassIFox - KeePassXC will listen to this port on 127.0.0.1 KeePassXC rimarrà in ascolto su questa porta su 127.0.0.1 @@ -1380,20 +1685,10 @@ Richiesto per accedere al tuo database da ChromeIPass o PassIFox Using default port 19455. Non è possibile collegarsi a porte sotto la 1024! Utilizza la porta predefinita 19455. - - - &Return only best matching entries for a URL instead -of all entries for the whole domain - &Ritorna solo solo le voci più idonee alla URL invece -di tutte le voci del dominio R&emove all shared encryption keys from active database - R&imuovi tutte le chiavi di condivisione criptate dal database attivo - - - The following options can be dangerous. Change them only if you know what you are doing. - Le opzioni seguenti sono pericolose. Cambia solo se sai cosa stai facendo. + R&imuovi tutte le chiavi condivise di cifratura dal database attivo &Return advanced string fields which start with "KPH: " @@ -1401,7 +1696,44 @@ di tutte le voci del dominio Automatically creating or updating string fields is not supported. - Crea automaticamente o aggiorna i campi stringa non supportati. + La creazione o l'aggiornamento automatico dei campi stringa non è supportato. + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Generatore Password + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + @@ -1494,12 +1826,101 @@ di tutte le voci del dominio Excellent Eccellente + + Password + Password + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Gruppo + + + Title + Titolo + + + Username + Nome utente + + + Password + Password + + + URL + URL + + + Notes + Note + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1546,14 +1967,18 @@ di tutte le voci del dominio Search Cerca - - Find - Trova - Clear Pulisci + + Search... + + + + Limit search to selected group + + Service @@ -1660,6 +2085,10 @@ imposta un nome unico per identificarla ed accettarla. Security Sicurezza + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1687,10 +2116,6 @@ imposta un nome unico per identificarla ed accettarla. Global Auto-Type shortcut Scorciatoia Auto-Type globale - - Use entry title to match windows for global auto-type - Usa il titolo delle voci per trovare le finestre per l'auto-type globale - Language Lingua @@ -1703,10 +2128,6 @@ imposta un nome unico per identificarla ed accettarla. Hide window to system tray when minimized Nascondi la finestra nell'area di notifica del sistema quando viene minimizzata - - Remember last key files - Ricorda l'ultimo file chiave - Load previous databases on startup Carica i database precedenti all'avvio @@ -1723,6 +2144,30 @@ imposta un nome unico per identificarla ed accettarla. Minimize window at application startup Minimizza la finestra all'avvio della applicazione + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Auto-Type + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1742,10 +2187,6 @@ imposta un nome unico per identificarla ed accettarla. Show passwords in cleartext by default Mostra la password in chiaro in maniera predefinita - - Always ask before performing auto-type - Chiedi sempre di di effettuare auto-type - Lock databases after minimizing the window Blocca il database dopo la minimizzazione della finestra @@ -1754,6 +2195,80 @@ imposta un nome unico per identificarla ed accettarla. Don't require password repeat when it is visible Non richiedere di ripetere la password quando è visibile + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + sec + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1765,8 +2280,32 @@ imposta un nome unico per identificarla ed accettarla. WelcomeWidget - Welcome! - Benvenuto! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Database recenti @@ -1791,5 +2330,69 @@ imposta un nome unico per identificarla ed accettarla. filenames of the password databases to open (*.kdbx) i nomi dei file dei database delle password da aprire (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_ja.ts b/share/translations/keepassx_ja.ts index 182ed9948..c134fb923 100644 --- a/share/translations/keepassx_ja.ts +++ b/share/translations/keepassx_ja.ts @@ -1,33 +1,143 @@ - + AboutDialog - About KeePassX - KeePassX について + About KeePassXC + - KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXはGNU General Public License (GPL) version 2 または version 3 (どちらかを選択)の条件で配布されます。 + About + このソフトウェアについて - Revision - リビジョン + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + - Using: - 利用中: + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + + + + Allow + + + + Deny + + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + + + + KeePassXC HTTP Confirm Access + AutoType - - Auto-Type - KeePassX - 自動入力 - KeePassX - Couldn't find an entry that matches the window title: ウィンドウタイトルに一致するエントリーが見つかりませんでした: + + Auto-Type - KeePassXC + + AutoTypeAssociationsModel @@ -46,14 +156,14 @@ AutoTypeSelectDialog - - Auto-Type - KeePassX - 自動入力 - KeePassX - Select entry to Auto-Type: 自動入力するエントリーを選択してください: + + Auto-Type - KeePassXC + + ChangeMasterKeyWidget @@ -69,10 +179,6 @@ Repeat password: パスワードを再入力: - - Key file - キーファイル - Browse 参照 @@ -93,10 +199,6 @@ Create Key File... キーファイルを作成... - - Error - エラー - Unable to create Key File : キーファイルを作成できませんでした: @@ -105,10 +207,6 @@ Select a key file キーファイルを選択 - - Question - 質問 - Do you really want to use an empty string as password? 本当に空のパスワード文字列で使いますか? @@ -117,16 +215,173 @@ Different passwords supplied. 異なるパスワードが入力されました。 - - Failed to set key file - キーファイルのセットに失敗しました - Failed to set %1 as the Key file: %2 %1 をキーファイルとしてセットできませんでした: %2 + + &Key file + + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + エラー + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + エラー + + + Unable to calculate master key + マスターキーを計算できません + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -146,10 +401,6 @@ Browse 参照 - - Error - エラー - Unable to open the database. データベースを開けませんでした。 @@ -170,6 +421,14 @@ Select key file キーファイルを選択 + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -226,10 +485,6 @@ You can now save it. Default username: ユーザー名の初期値: - - Use recycle bin: - ゴミ箱を使う: - MiB MiB @@ -246,6 +501,22 @@ You can now save it. Max. history size: 最大履歴データサイズ: + + Use recycle bin + + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -265,10 +536,6 @@ You can now save it. Open database データベースを開く - - Warning - 警告 - File not found! ファイルが見つかりません! @@ -299,10 +566,6 @@ Save changes? "%1" は編集されています。 変更を保存しますか? - - Error - エラー - Writing the database failed. データベースが保存できませんでした。 @@ -319,12 +582,6 @@ Save changes? locked ロック済み - - The database you are trying to open is locked by another instance of KeePassX. -Do you want to open it anyway? Alternatively the database is opened read-only. - 開こうとしたデータベースは別のKeePassXプログラムからロックされています。 -とにかく開きますか? データベースを読み取り専用で開きます。 - Lock database データベースをロックする @@ -368,13 +625,42 @@ Discard changes and close anyway? CSVファイルの書き込みに失敗しました。 - The database you are trying to save as is locked by another instance of KeePassX. -Do you want to save it anyway? - 保存しようとしたデータベースは別のKeePassXプログラムからロックされています。 -とにかく保存しますか? + Unable to open the database. + データベースを開けませんでした。 - Unable to open the database. + Merge database + + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + + + + Passwords + + + + Database already opened + + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + + + + File opened in read only mode. + + + + Open CSV file @@ -416,14 +702,6 @@ Do you want to save it anyway? Do you really want to delete the group "%1" for good? グループ "%1" を完全に削除しますがよろしいですか? - - Current group - 現在のグループ - - - Error - エラー - Unable to calculate master key マスターキーを計算できませんでした @@ -436,6 +714,66 @@ Do you want to save it anyway? Do you really want to move entry "%1" to the recycle bin? + + Searching... + + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + + + + No Results + + + + Execute command? + + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -475,10 +813,6 @@ Do you want to save it anyway? Edit entry エントリーを編集 - - Error - エラー - Different passwords supplied. 異なるパスワードが入力されました。 @@ -521,6 +855,22 @@ Do you want to save it anyway? 1 year 1年 + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -532,10 +882,6 @@ Do you want to save it anyway? Add 追加 - - Edit - 編集 - Remove 削除 @@ -552,6 +898,18 @@ Do you want to save it anyway? Open 開く + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -559,14 +917,6 @@ Do you want to save it anyway? Enable Auto-Type for this entry エントリーの自動入力を有効にする - - Inherit default Auto-Type sequence from the group - 自動入力手順をグループから引き継ぐ - - - Use custom Auto-Type sequence: - カスタムの自動入力手順を使う: - + + @@ -580,12 +930,24 @@ Do you want to save it anyway? ウインドウタイトル: - Use default sequence - デフォルトの手順を使う + Inherit default Auto-Type sequence from the &group + - Set custom sequence: - カスタムの手順を入力: + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + @@ -625,10 +987,6 @@ Do you want to save it anyway? Repeat: パスワード確認: - - Gen. - 生成 - URL: URL: @@ -700,28 +1058,20 @@ Do you want to save it anyway? 検索 - Auto-type + Auto-Type 自動入力 - Use default auto-type sequence of parent group - 親グループのデフォルトの自動入力手順を使う + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - デフォルトの自動入力手順をセット + Set default Auto-Type se&quence + EditWidgetIcons - - Use default icon - デフォルトアイコンから選択 - - - Use custom icon - カスタムアイコンから選択 - Add custom icon カスタムアイコンを追加 @@ -743,19 +1093,35 @@ Do you want to save it anyway? 画像を選択 - Can't delete icon! - アイコンを削除できません! - - - Can't delete icon. Still used by %n item(s). - %n個のアイテムから使われているので、アイコンを削除できません。 + Error + エラー - Error + Download favicon - Can't read icon: + Unable to fetch favicon. + + + + Can't read icon + + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? @@ -778,6 +1144,13 @@ Do you want to save it anyway? UUID: + + Entry + + - Clone + + + EntryAttributesModel @@ -822,6 +1195,11 @@ Do you want to save it anyway? URL URL + + Ref: + Reference abbreviation + + Group @@ -830,16 +1208,74 @@ Do you want to save it anyway? ゴミ箱 + + HttpPasswordGeneratorWidget + + Length: + 文字数: + + + Character Types + 文字種 + + + Upper Case Letters + 大文字 + + + A-Z + + + + Lower Case Letters + 小文字 + + + a-z + + + + Numbers + 数字 + + + 0-9 + + + + Special Characters + 特殊な文字 + + + /*_& ... + + + + Exclude look-alike characters + よく似た文字を除外する + + + Ensure that the password contains characters from every group + 使用する文字種の文字が必ず含まれるようにする + + + + KMessageWidget + + &Close + + + + Close message + + + KeePass1OpenWidget Import KeePass1 database KeePass1 データベースをインポートする - - Error - エラー - Unable to open the database. データベースを開けませんでした。 @@ -873,7 +1309,7 @@ Do you want to save it anyway? Wrong key or database file is corrupt. - + キーが間違っているかデータベースファイルが破損しています。 @@ -904,6 +1340,10 @@ This is a one-way migration. You won't be able to open the imported databas データベース > 'KeePass 1 データベースをインポート' をクリックすることでインポートできます。 これは一方向の移行操作であり、インポートされたデータベースは古い KeePassX 0.4 のバージョンでは開くことはできません。 + + Unable to issue challenge-response. + + Main @@ -912,112 +1352,28 @@ This is a one-way migration. You won't be able to open the imported databas 暗号化機能のテスト中に致命的なエラーが発生しました。 - KeePassX - Error - KeePassX - エラー + KeePassXC - Error + + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - データベース - - - Recent databases - 最近使ったデータベース - - - Help - ヘルプ - - - Entries - エントリー - - - Copy attribute to clipboard - クリップボードにコピーする - - - Groups - グループ - - - View - 表示 - - - Quit - 終了 - - - About - このソフトウェアについて - Open database データベースを開く - - Save database - データベースを保存 - - - Close database - データベースを閉じる - - - New database - 新規データベース - - - Add new entry - 新規エントリーの追加 - - - View/Edit entry - エントリーの表示/編集 - - - Delete entry - エントリーの削除 - - - Add new group - 新規グループの追加 - - - Edit group - グループの編集 - - - Delete group - グループの削除 - - - Save database as - ファイル名をつけてデータベースを保存 - - - Change master key - マスターキーを変更 - Database settings データベースの設定 - - Import KeePass 1 database - KeePass1 データベースをインポートする - - - Clone entry - エントリーの複製 - - - Find - 検索 - Copy username to clipboard ユーザー名をコピー @@ -1030,30 +1386,6 @@ This is a one-way migration. You won't be able to open the imported databas Settings 設定 - - Perform Auto-Type - 自動入力の実行 - - - Open URL - URLを開く - - - Lock databases - データベースをロック - - - Title - タイトル - - - URL - URL - - - Notes - メモ - Show toolbar ツールバーを表示 @@ -1066,26 +1398,6 @@ This is a one-way migration. You won't be able to open the imported databas Toggle window ウィンドウ切替 - - Tools - ツール - - - Copy username - ユーザ名をコピー - - - Copy password - パスワードをコピー - - - Export to CSV file - CSVファイルへエクスポート - - - Repair database - データベースを修復する - KeePass 2 Database KeePass 2 データベース @@ -1098,14 +1410,327 @@ This is a one-way migration. You won't be able to open the imported databas Save repaired database 修復されたデータベースを保存する - - Error - エラー - Writing the database failed. データベースの書き込みに失敗しました。 + + &Recent databases + + + + He&lp + + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + + + + &View + + + + &Quit + + + + &About + + + + &Open database + + + + &Save database + + + + &Close database + + + + &New database + + + + Merge from KeePassX database + + + + &Add new entry + + + + &View/Edit entry + + + + &Delete entry + + + + &Add new group + + + + &Edit group + + + + &Delete group + + + + Sa&ve database as + + + + Change &master key + + + + &Database settings + + + + &Clone entry + + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + + + + Copy &username + + + + Cop&y password + + + + &Settings + + + + &Perform Auto-Type + + + + &Open URL + + + + &Lock databases + + + + &Title + + + + &URL + + + + &Notes + + + + &Export to CSV file + + + + Re&pair database + + + + Password Generator + + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + KeePass1 データベースをインポートする + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + + + + General + 一般 + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + 詳細設定 + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + + + + Default port: 19455 + + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1113,10 +1738,6 @@ This is a one-way migration. You won't be able to open the imported databas Password: パスワード: - - Length: - 文字数: - Character Types 文字種 @@ -1141,71 +1762,161 @@ This is a one-way migration. You won't be able to open the imported databas Exclude look-alike characters よく似た文字を除外する - - Ensure that the password contains characters from every group - 使用する文字種の文字が必ず含まれるようにする - Accept 適用 - - - QCommandLineParser - Displays version information. - バージョン情報を表示する。 + %p% + - Displays this help. - このヘルプを表示する。 + strength + - Unknown option '%1'. - '%1' は不明なオプションです。 + entropy + - Unknown options: %1. - %1 は不明なオプションです。 + &Length: + - Missing value after '%1'. - '%1' の後に値が見つかりません。 + Pick characters from every group + - Unexpected value after '%1'. - '%1' の後に予期しない値があります。 + Generate + - [options] - [オプション] + Close + - Usage: %1 - 使い方: %1 + Apply + - Options: - オプション: + Entropy: %1 bit + - Arguments: - 引数: + Password Quality: %1 + + + + Poor + + + + Weak + + + + Good + + + + Excellent + + + + Password + パスワード + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + - QSaveFile + QObject - Existing file %1 is not writable - 存在するファイル %1 は書き込みできません + NULL device + - Writing canceled by application - アプリケーションにより書き込みがキャンセルされました + error reading from device + - Partial write. Partition full? - 一部しか書き込めませんでした。パーティションがいっぱいかも? + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + グループ + + + Title + タイトル + + + Username + ユーザー名 + + + Password + パスワード + + + URL + URL + + + Notes + メモ + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1245,20 +1956,111 @@ This is a one-way migration. You won't be able to open the imported databas SearchWidget - Find: - 検索: + Case Sensitive + - Case sensitive - 大文字と小文字を区別 + Search + 検索 - Current group - 現在のグループ + Clear + - Root group - 全て + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + @@ -1275,6 +2077,10 @@ This is a one-way migration. You won't be able to open the imported databas Security セキュリティ + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1282,10 +2088,6 @@ This is a one-way migration. You won't be able to open the imported databas Remember last databases 最近使用したデータベースを記憶する - - Open previous databases on startup - KeePassX起動時に前回使用したデータベースを開く - Automatically save on exit 終了時に自動的に保存する @@ -1306,10 +2108,6 @@ This is a one-way migration. You won't be able to open the imported databas Global Auto-Type shortcut 全体の自動入力ショートカット - - Use entry title to match windows for global auto-type - グローバル自動入力の際に、エントリーのタイトルとウィンドウのマッチングを行う - Language 言語 @@ -1323,15 +2121,43 @@ This is a one-way migration. You won't be able to open the imported databas 最小化された際にシステムトレイへ格納する - Remember last key files - 最後のキーファイルを記憶 - - - Hide window to system tray instead of App Exit + Load previous databases on startup - Hide window to system tray on App start + Automatically reload the database when modified externally + + + + Hide window to system tray instead of app exit + + + + Minimize window at application startup + + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + 自動入力 + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type @@ -1354,8 +2180,86 @@ This is a one-way migration. You won't be able to open the imported databas パスワードはデフォルトで平文表示にする - Always ask before performing auto-type - 自動入力の実行前に常に確認する + Lock databases after minimizing the window + + + + Don't require password repeat when it is visible + + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1368,20 +2272,36 @@ This is a one-way migration. You won't be able to open the imported databas WelcomeWidget - Welcome! - ようこそ! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + 最近使ったデータベース main - - KeePassX - cross-platform password manager - KeePassX - クロスプラットフォーム パスワードマネージャー - - - filename of the password database to open (*.kdbx) - 開くパスワードデータベースのファイル名 (*.kdbx) - path to a custom config file カスタム設定ファイルへのパス @@ -1390,5 +2310,81 @@ This is a one-way migration. You won't be able to open the imported databas key file of the database データベースのキーファイル + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_kk.ts b/share/translations/keepassx_kk.ts new file mode 100644 index 000000000..0ceda8971 --- /dev/null +++ b/share/translations/keepassx_kk.ts @@ -0,0 +1,2390 @@ + + + AboutDialog + + About KeePassXC + + + + About + + + + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + + + + Allow + + + + Deny + + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + + + + KeePassXC HTTP Confirm Access + + + + + AutoType + + Couldn't find an entry that matches the window title: + Терезе атауына сай келетін жазбаны табу мүмкін емес: + + + Auto-Type - KeePassXC + + + + + AutoTypeAssociationsModel + + Window + Терезе + + + Sequence + Тізбек + + + Default sequence + Үнсіз келісім тізбегі + + + + AutoTypeSelectDialog + + Select entry to Auto-Type: + Автотеру үшін жазбаны таңдаңыз: + + + Auto-Type - KeePassXC + + + + + ChangeMasterKeyWidget + + Password + Пароль + + + Enter password: + Парольді енгізіңіз: + + + Repeat password: + Парольді қайталаңыз: + + + Browse + Шолу + + + Create + Жасау + + + Key files + Кілттер файлдары + + + All files + Барлық файлдар + + + Create Key File... + Кілттер файлын жасау... + + + Unable to create Key File : + Кілттер файлын жасау мүмкін емес: + + + Select a key file + Кілттер файлын таңдаңыз + + + Do you really want to use an empty string as password? + Пароль ретінде бос жолды қолдануды шынымен қалайсыз ба? + + + Different passwords supplied. + Әр түрлі парольдер көрсетілді. + + + Failed to set %1 as the Key file: +%2 + %1 файлын кілттер файлы ретінде орнату қатесі: +%2 + + + &Key file + + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Қате + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Қате + + + Unable to calculate master key + Басты парольді есептеу мүмкін емес + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + + + + DatabaseOpenWidget + + Enter master key + Басты парольді енгізіңіз: + + + Key File: + Кілттер файлы: + + + Password: + Пароль: + + + Browse + Шолу + + + Unable to open the database. + Дерекқорды ашу мүмкін емес. + + + Can't open key file + Кілттер файлын ашу мүмкін емес + + + All files + Барлық файлдар + + + Key files + Кілттер файлдары + + + Select key file + Кілттер файлын таңдаңыз + + + Refresh + + + + Challenge Response: + + + + + DatabaseRepairWidget + + Repair database + Дерекқорды жөндеу + + + Error + Қате + + + Can't open key file + Кілттер файлын ашу мүмкін емес + + + Database opened fine. Nothing to do. + Дерекқор сәтті ашылды. Басқа орындайтын әрекеттер жоқ. + + + Unable to open the database. + Дерекқорды ашу мүмкін емес. + + + Success + Сәтті + + + The database has been successfully repaired +You can now save it. + Дерекқор қалпына сәтті келтірілді. +Енді оны сақтауыңызға болады. + + + Unable to repair the database. + Дерекқорды жөндеу мүмкін емес. + + + + DatabaseSettingsWidget + + Database name: + Дерекқор аты: + + + Database description: + Дерекқор сипаттамасы: + + + Transform rounds: + Түрлендірулер саны: + + + Default username: + Үнсіз келісім пайдаланушы аты: + + + MiB + МиБ + + + Benchmark + Сынау + + + Max. history items: + Макс. тарих саны: + + + Max. history size: + Макс. тарих өлшемі: + + + Use recycle bin + + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + + + + DatabaseTabWidget + + Root + Түбір + + + KeePass 2 Database + KeePass 2 дерекқоры + + + All files + Барлық файлдар + + + Open database + Дерекқорды ашу + + + File not found! + Файл табылмады! + + + Open KeePass 1 database + KeePass 1 дерекқорын ашу + + + KeePass 1 database + KeePass 1 дерекқоры + + + All files (*) + Барлық файлдар (*) + + + Close? + Жабу керек пе? + + + Save changes? + Өзгерістерді сақтау керек пе? + + + "%1" was modified. +Save changes? + "%1" өзгертілген. +Өзгерістерді сақтау керек пе? + + + Writing the database failed. + Дерекқорға жазу сәтсіз аяқталды. + + + Save database as + Дерекқорды қалайша сақтау + + + New database + Жаңа дерекқор + + + locked + блокталған + + + Lock database + Дерекқорды блоктау + + + Can't lock the database as you are currently editing it. +Please press cancel to finish your changes or discard them. + Дерекқорды блоктау мүмкін емес, өйткені сіз оны қазір түзетудесіз. +Өзгерістерді аяқтау үшін бас тартуды басыңыз, немесе оларды елемеңіз. + + + This database has never been saved. +You can save the database or stop locking it. + Дерекқор ешқашан сақталмаған. +Сіз дерекқорды сақтай аласыз, немесе оның блокауын алып тастай аласыз. + + + This database has been modified. +Do you want to save the database before locking it? +Otherwise your changes are lost. + Дерекқор өзгертілген. +Оны блоктау алдында өзгерістерді сақтау керек пе? +Сақтамасаңыз, өзгерістер жоғалады. + + + "%1" is in edit mode. +Discard changes and close anyway? + "%1" қазір түзету режимінде. +Оған қарамастан, өзгерістерді елемей, оны жабу керек пе? + + + Export database to CSV file + Дерекқорды CSV файлына экспорттау + + + CSV file + CSV файлы + + + Writing the CSV file failed. + CSV файлына жазу сәтсіз аяқталды. + + + Unable to open the database. + Дерекқорды ашу мүмкін емес. + + + Merge database + + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + + + + Passwords + + + + Database already opened + + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + + + + File opened in read only mode. + + + + Open CSV file + + + + + DatabaseWidget + + Change master key + Басты парольді өзгерту + + + Delete entry? + Жазбаны өшіру керек пе? + + + Do you really want to delete the entry "%1" for good? + "%1" жазбасын өшіруді шынымен қалайсыз ба? + + + Delete entries? + Жазбаларды өшіру керек пе? + + + Do you really want to delete %1 entries for good? + %1 жазбаны өшіруді шынымен қалайсыз ба? + + + Move entries to recycle bin? + Жазбаларды қоқыс шелегіне тастау керек пе? + + + Do you really want to move %n entry(s) to the recycle bin? + %n жазбаны қоқыс шелегіне тастауды шынымен қалайсыз ба? + + + Delete group? + Топты өшіру керек пе? + + + Do you really want to delete the group "%1" for good? + "%1" тобын өшіруді шынымен қалайсыз ба? + + + Unable to calculate master key + Басты парольді есептеу мүмкін емес + + + Move entry to recycle bin? + Жазбаны қоқыс шелегіне тастау керек пе? + + + Do you really want to move entry "%1" to the recycle bin? + "%1" жазбасын қоқыс шелегіне тастауды шынымен қалайсыз ба? + + + Searching... + + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + + + + No Results + + + + Execute command? + + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + + + + EditEntryWidget + + Entry + Жазба + + + Advanced + Кеңейтілген + + + Icon + Таңбаша + + + Auto-Type + Автотеру + + + Properties + Қасиеттері + + + History + Тарихы + + + Entry history + Жазба тарихы + + + Add entry + Жазбаны қосу + + + Edit entry + Жазбаны түзету + + + Different passwords supplied. + Әр түрлі парольдер көрсетілді. + + + New attribute + Жаңа атрибут + + + Select file + Файлды таңдау + + + Unable to open file + Файлды ашу мүмкін емес + + + Save attachment + Салынымды сақтау + + + Unable to save the attachment: + + Салынымды сақтау мүмкін емес: + + + + Tomorrow + Ертең + + + %n week(s) + %n апта + + + %n month(s) + %n ай + + + 1 year + 1 жыл + + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + + + + EditEntryWidgetAdvanced + + Additional attributes + Қосымша атрибуттар + + + Add + Қосу + + + Remove + Өшіру + + + Attachments + Салынымдар + + + Save + Сақтау + + + Open + Ашу + + + Edit Name + + + + Protect + + + + Reveal + + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Бұл жазба үшін автотеруді іске қосу + + + + + + + + + - + - + + + Window title: + Терезе атауы: + + + Inherit default Auto-Type sequence from the &group + + + + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + + + + + EditEntryWidgetHistory + + Show + Көрсету + + + Restore + Қалпына келтіру + + + Delete + Өшіру + + + Delete all + Барлығын өшіру + + + + EditEntryWidgetMain + + Title: + Атауы: + + + Username: + Пайдаланушы аты: + + + Password: + Пароль: + + + Repeat: + Қайталау: + + + URL: + URL: + + + Expires + Мерзімі аяқталады + + + Presets + Сақталған баптаулар + + + Notes: + Естеліктер: + + + + EditGroupWidget + + Group + Топ + + + Icon + Таңбаша + + + Properties + Қасиеттері + + + Add group + Топты қосу + + + Edit group + Топты түзету + + + Enable + Іске қосу + + + Disable + Сөндіру + + + Inherit from parent group (%1) + Аталық топтан мұралау (%1) + + + + EditGroupWidgetMain + + Name + Аты + + + Notes + Естеліктер + + + Expires + Мерзімі аяқталады + + + Search + Іздеу + + + Auto-Type + Автотеру + + + &Use default Auto-Type sequence of parent group + + + + Set default Auto-Type se&quence + + + + + EditWidgetIcons + + Add custom icon + Таңдауыңызша таңбашаны қосу + + + Delete custom icon + Таңдауыңызша таңбашаны өшіру + + + Images + Суреттер + + + All files + Барлық файлдар + + + Select Image + Суретті таңдау + + + Error + Қате + + + Download favicon + + + + Unable to fetch favicon. + + + + Can't read icon + + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + + + + EditWidgetProperties + + Created: + Жасалған: + + + Modified: + Өзгертілген: + + + Accessed: + Қатынаған: + + + Uuid: + Uuid: + + + + Entry + + - Clone + + + + + EntryAttributesModel + + Name + Аты + + + + EntryHistoryModel + + Last modified + Соңғы өзгертілген + + + Title + Атауы + + + Username + Пайдаланушы аты + + + URL + URL + + + + EntryModel + + Group + Топ + + + Title + Атауы + + + Username + Пайдаланушы аты + + + URL + URL + + + Ref: + Reference abbreviation + + + + + Group + + Recycle Bin + Қоқыс шелегі + + + + HttpPasswordGeneratorWidget + + Length: + + + + Character Types + Таңбалар түрлері + + + Upper Case Letters + Бас әріптер + + + A-Z + + + + Lower Case Letters + Кіші әріптер + + + a-z + + + + Numbers + Сандар + + + 0-9 + + + + Special Characters + Арнайы таңбалар + + + /*_& ... + + + + Exclude look-alike characters + Ұқсайтын таңбаларға жол бермеу + + + Ensure that the password contains characters from every group + + + + + KMessageWidget + + &Close + + + + Close message + + + + + KeePass1OpenWidget + + Import KeePass1 database + KeePass1 дерекқорын импорттау + + + Unable to open the database. + Дерекқорды ашу мүмкін емес. + + + + KeePass1Reader + + Unable to read keyfile. + Кілттер файлын оқу мүмкін емес. + + + Not a KeePass database. + KeePass дерекқоры емес. + + + Unsupported encryption algorithm. + Шифрлеу алгоритміне қолдау жоқ. + + + Unsupported KeePass database version. + KeePass дерекқоры нұсқасына қолдау жоқ. + + + Root + Түбір + + + Unable to calculate master key + Басты парольді есептеу мүмкін емес + + + Wrong key or database file is corrupt. + Пароль қате, немесе дерекқор файлы зақымдалған. + + + + KeePass2Reader + + Not a KeePass database. + KeePass дерекқоры емес. + + + Unsupported KeePass database version. + KeePass дерекқоры нұсқасына қолдау жоқ. + + + Wrong key or database file is corrupt. + Пароль қате, немесе дерекқор файлы зақымдалған. + + + Unable to calculate master key + Басты парольді есептеу мүмкін емес + + + The selected file is an old KeePass 1 database (.kdb). + +You can import it by clicking on Database > 'Import KeePass 1 database'. +This is a one-way migration. You won't be able to open the imported database with the old KeePassX 0.4 version. + Таңдалған файл ескі KeePass 1 дерекқоры (.kdb) болып табылады. + +Оны Дерекқор > 'KeePass 1 дерекқорын импорттау' арқылы импорттай аласыз. +Бұл - бір жақты миграция. Одан кейін сіз импортталған дерекқорды ескі KeePassX 0.4 нұсқасымен аша алмайтын боласыз. + + + Unable to issue challenge-response. + + + + + Main + + Fatal error while testing the cryptographic functions. + Криптографиялық функцияларды сынау кезіндегі қатаң қате орын алды. + + + KeePassXC - Error + + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + + + + MainWindow + + Open database + Дерекқорды ашу + + + Database settings + Дерекқор баптаулары + + + Copy username to clipboard + Пайдаланушы атын алмасу буферіне көшіріп алу + + + Copy password to clipboard + Парольді алмасу буферіне көшіріп алу + + + Settings + Баптаулар + + + Show toolbar + Саймандар панелін көрсету + + + read-only + тек оқу + + + Toggle window + Терезені көрсету/жасыру + + + KeePass 2 Database + KeePass 2 дерекқоры + + + All files + Барлық файлдар + + + Save repaired database + Жөнделген дерекқорды сақтау + + + Writing the database failed. + Дерекқорды жазу сәтсіз аяқталды. + + + &Recent databases + + + + He&lp + + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + + + + &View + + + + &Quit + + + + &About + + + + &Open database + + + + &Save database + + + + &Close database + + + + &New database + + + + Merge from KeePassX database + + + + &Add new entry + + + + &View/Edit entry + + + + &Delete entry + + + + &Add new group + + + + &Edit group + + + + &Delete group + + + + Sa&ve database as + + + + Change &master key + + + + &Database settings + + + + &Clone entry + + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + + + + Copy &username + + + + Cop&y password + + + + &Settings + + + + &Perform Auto-Type + + + + &Open URL + + + + &Lock databases + + + + &Title + + + + &URL + + + + &Notes + + + + &Export to CSV file + + + + Re&pair database + + + + Password Generator + + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + + + + General + Жалпы + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + Кеңейтілген + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + + + + Default port: 19455 + + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + + + + PasswordGeneratorWidget + + Password: + Пароль: + + + Character Types + Таңбалар түрлері + + + Upper Case Letters + Бас әріптер + + + Lower Case Letters + Кіші әріптер + + + Numbers + Сандар + + + Special Characters + Арнайы таңбалар + + + Exclude look-alike characters + Ұқсайтын таңбаларға жол бермеу + + + Accept + Қабылдау + + + %p% + + + + strength + + + + entropy + + + + &Length: + + + + Pick characters from every group + + + + Generate + + + + Close + + + + Apply + + + + Entropy: %1 bit + + + + Password Quality: %1 + + + + Poor + + + + Weak + + + + Good + + + + Excellent + + + + Password + Пароль + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + + + + QObject + + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Топ + + + Title + Атауы + + + Username + Пайдаланушы аты + + + Password + Пароль + + + URL + URL + + + Notes + Естеліктер + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + + + + + QtIOCompressor + + Internal zlib error when compressing: + Сығу кезінде zlib ішкі қатесі орын алған: + + + Error writing to underlying device: + Астындағы құрылғыға жазу қатесі: + + + Error opening underlying device: + Астындағы құрылғыны ашу қатесі: + + + Error reading data from underlying device: + Астындағы құрылғыдан деректерді оқу қатесі: + + + Internal zlib error when decompressing: + Тарқату кезінде zlib ішкі қатесі орын алған: + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + zlib-тің бұл нұсқасы gzip пішімін қолдамайды. + + + Internal zlib error: + Ішкі zlib қатесі: + + + + SearchWidget + + Case Sensitive + + + + Search + Іздеу + + + Clear + + + + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + + + + + SettingsWidget + + Application Settings + Қолданба баптаулары + + + General + Жалпы + + + Security + Қауіпсіздік + + + Access error for config file %1 + + + + + SettingsWidgetGeneral + + Remember last databases + Соңғы дерекқорларды есте сақтау: + + + Automatically save on exit + Шығу кезінде автосақтау + + + Automatically save after every change + Әр өзгерістен кейін автосақтау + + + Minimize when copying to clipboard + Алмасу буферіне көшіру кезінде қолданбаны қайыру + + + Use group icon on entry creation + Жазбаны жасау кезінде топ таңбашасын қолдану + + + Global Auto-Type shortcut + Глобалды автотеру жарлығы + + + Language + Тіл + + + Show a system tray icon + Жүйелік трей таңбашасын қолдану + + + Hide window to system tray when minimized + Қолданба қайырылған кезде терезені жүйелік трейге жасыру + + + Load previous databases on startup + + + + Automatically reload the database when modified externally + + + + Hide window to system tray instead of app exit + + + + Minimize window at application startup + + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Автотеру + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + + + + SettingsWidgetSecurity + + Clear clipboard after + Алмасу буферін тазалау алдындағы кідіріс + + + sec + сек + + + Lock databases after inactivity of + Дерекқорларды белсенділік жоқ кезде блоктау алдындағы кідіріс + + + Show passwords in cleartext by default + Парольдерді үнсіз келісім бойынша ашық мәтінмен көрсету + + + Lock databases after minimizing the window + + + + Don't require password repeat when it is visible + + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + сек + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + + + + UnlockDatabaseWidget + + Unlock database + Дерекқорды блоктаудан босату + + + + WelcomeWidget + + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + + + + + main + + path to a custom config file + таңдауыңызша баптаулар файлына дейінгі жол + + + key file of the database + дерекқордың кілттер файлы + + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + + + \ No newline at end of file diff --git a/share/translations/keepassx_ko.ts b/share/translations/keepassx_ko.ts index 208c92a09..f89dc32db 100644 --- a/share/translations/keepassx_ko.ts +++ b/share/translations/keepassx_ko.ts @@ -1,33 +1,143 @@ - + AboutDialog - About KeePassX - KeePassX 정보 + About KeePassXC + - KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassX는 GNU General Public License(GPL) 버전 2 혹은 버전 3(선택적)으로 배포됩니다. + About + 정보 - Revision - 리비전 + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + - Using: - 사용: + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + + + + Allow + + + + Deny + + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + + + + KeePassXC HTTP Confirm Access + AutoType - - Auto-Type - KeePassX - 자동 입력 - KeePassX - Couldn't find an entry that matches the window title: 창 제목과 일치하는 항목을 찾을 수 없습니다: + + Auto-Type - KeePassXC + + AutoTypeAssociationsModel @@ -46,14 +156,14 @@ AutoTypeSelectDialog - - Auto-Type - KeePassX - 자동 입력 - KeePassX - Select entry to Auto-Type: 자동으로 입력할 항목 선택: + + Auto-Type - KeePassXC + + ChangeMasterKeyWidget @@ -69,10 +179,6 @@ Repeat password: 암호 확인: - - Key file - 키 파일 - Browse 찾아보기 @@ -93,10 +199,6 @@ Create Key File... 키 파일 만들기... - - Error - 오류 - Unable to create Key File : 키 파일을 만들 수 없습니다: @@ -105,10 +207,6 @@ Select a key file 키 파일 선택 - - Question - 질문 - Do you really want to use an empty string as password? 빈 문자열을 암호로 사용하시겠습니까? @@ -117,15 +215,172 @@ Different passwords supplied. 다른 암호를 입력하였습니다. - - Failed to set key file - 키 파일을 설정할 수 없음 - Failed to set %1 as the Key file: %2 %1을(를) 키 파일로 설정할 수 없습니다: %2 + + &Key file + + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + 오류 + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + 오류 + + + Unable to calculate master key + 마스터 키를 계산할 수 없습니다 + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -145,10 +400,6 @@ Browse 찾아보기 - - Error - 오류 - Unable to open the database. 데이터베이스를 열 수 없습니다. @@ -169,6 +420,14 @@ Select key file 키 파일 선택 + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -225,10 +484,6 @@ You can now save it. Default username: 기본 사용자 이름: - - Use recycle bin: - 휴지통 사용: - MiB MiB @@ -245,6 +500,22 @@ You can now save it. Max. history size: 최대 과거 항목 크기: + + Use recycle bin + + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -264,10 +535,6 @@ You can now save it. Open database 데이터베이스 열기 - - Warning - 경고 - File not found! 파일을 찾을 수 없습니다! @@ -297,10 +564,6 @@ You can now save it. Save changes? "%1"이(가) 변경되었습니다. 저장하시겠습니까? - - Error - 오류 - Writing the database failed. 데이터베이스에 쓸 수 없습니다. @@ -317,12 +580,6 @@ Save changes? locked 잠김 - - The database you are trying to open is locked by another instance of KeePassX. -Do you want to open it anyway? Alternatively the database is opened read-only. - 열려고 하는 데이터베이스를 다른 KeePassX 인스턴스에서 잠갔습니다. -그래도 여시겠습니까? 읽기 전용으로 열 수도 있습니다. - Lock database 데이터베이스 잠금 @@ -366,13 +623,42 @@ Discard changes and close anyway? CSV 파일에 기록할 수 없습니다. - The database you are trying to save as is locked by another instance of KeePassX. -Do you want to save it anyway? - 저장하려고 하는 데이터베이스를 다른 KeePassX 인스턴스에서 잠갔습니다. -그래도 저장하시겠습니까? + Unable to open the database. + 데이터베이스를 열 수 없습니다. - Unable to open the database. + Merge database + + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + + + + Passwords + + + + Database already opened + + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + + + + File opened in read only mode. + + + + Open CSV file @@ -414,14 +700,6 @@ Do you want to save it anyway? Do you really want to delete the group "%1" for good? 정말 그룹 "%1"을(를) 삭제하시겠습니까? - - Current group - 현재 그룹 - - - Error - 오류 - Unable to calculate master key 마스터 키를 계산할 수 없음 @@ -434,6 +712,66 @@ Do you want to save it anyway? Do you really want to move entry "%1" to the recycle bin? + + Searching... + + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + + + + No Results + + + + Execute command? + + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -473,10 +811,6 @@ Do you want to save it anyway? Edit entry 항목 편집 - - Error - 오류 - Different passwords supplied. 다른 암호를 입력하였습니다. @@ -518,6 +852,22 @@ Do you want to save it anyway? 1 year 1년 + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -529,10 +879,6 @@ Do you want to save it anyway? Add 추가 - - Edit - 편집 - Remove 삭제 @@ -549,6 +895,18 @@ Do you want to save it anyway? Open 열기 + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -556,14 +914,6 @@ Do you want to save it anyway? Enable Auto-Type for this entry 이 항목 자동 입력 사용 - - Inherit default Auto-Type sequence from the group - 그룹의 기본 자동 입력 시퀀스 사용 - - - Use custom Auto-Type sequence: - 사용자 정의 자동 입력 시퀀스 사용: - + + @@ -577,12 +927,24 @@ Do you want to save it anyway? 창 제목: - Use default sequence - 기본 시퀀스 사용 + Inherit default Auto-Type sequence from the &group + - Set custom sequence: - 사용자 정의 시퀀스 설정: + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + @@ -622,10 +984,6 @@ Do you want to save it anyway? Repeat: 암호 확인: - - Gen. - 생성 - URL: URL: @@ -697,28 +1055,20 @@ Do you want to save it anyway? 찾기 - Auto-type + Auto-Type 자동 입력 - Use default auto-type sequence of parent group - 부모 그룹의 기본 자동 입력 시퀀스 사용 + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - 기본 자동 입력 시퀀스 설정 + Set default Auto-Type se&quence + EditWidgetIcons - - Use default icon - 기본 아이콘 사용 - - - Use custom icon - 사용자 정의 아이콘 사용 - Add custom icon 사용자 정의 아이콘 추가 @@ -740,19 +1090,35 @@ Do you want to save it anyway? 그림 선택 - Can't delete icon! - 아이콘을 삭제할 수 없습니다! - - - Can't delete icon. Still used by %n item(s). - 아이콘을 삭제할 수 없습니다. 항목 %n개에서 사용 중입니다. + Error + 오류 - Error + Download favicon - Can't read icon: + Unable to fetch favicon. + + + + Can't read icon + + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? @@ -775,6 +1141,13 @@ Do you want to save it anyway? UUID: + + Entry + + - Clone + + + EntryAttributesModel @@ -819,6 +1192,11 @@ Do you want to save it anyway? URL URL + + Ref: + Reference abbreviation + + Group @@ -827,16 +1205,74 @@ Do you want to save it anyway? 휴지통 + + HttpPasswordGeneratorWidget + + Length: + 길이: + + + Character Types + 문자 종류 + + + Upper Case Letters + 대문자 + + + A-Z + + + + Lower Case Letters + 소문자 + + + a-z + + + + Numbers + 숫자 + + + 0-9 + + + + Special Characters + 특수 문자 + + + /*_& ... + + + + Exclude look-alike characters + 비슷하게 생긴 문자 제외 + + + Ensure that the password contains characters from every group + 모든 그룹에서 최소 1글자 이상 포함 + + + + KMessageWidget + + &Close + + + + Close message + + + KeePass1OpenWidget Import KeePass1 database KeePass1 데이터베이스 가져오기 - - Error - 오류 - Unable to open the database. 데이터베이스를 열 수 없습니다. @@ -870,7 +1306,7 @@ Do you want to save it anyway? Wrong key or database file is corrupt. - + 키가 잘못되었거나 데이터베이스가 손상되었습니다. @@ -901,6 +1337,10 @@ This is a one-way migration. You won't be able to open the imported databas 데이터베이스 > 'KeePass 1 데이터베이스 가져오기' 항목을 선택해서 변환해야 합니다. 변환은 한 방향으로만 이루어지며, 가져온 데이터베이스는 KeePassX 0.4 버전으로 더 이상 열 수 없습니다. + + Unable to issue challenge-response. + + Main @@ -909,112 +1349,28 @@ This is a one-way migration. You won't be able to open the imported databas 암호화 함수를 시험하는 중 오류가 발생하였습니다. - KeePassX - Error - KeePassX - 오류 + KeePassXC - Error + + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - 데이터베이스 - - - Recent databases - 최근 데이터베이스 - - - Help - 도움말 - - - Entries - 항목 - - - Copy attribute to clipboard - 클립보드에 속성 복사 - - - Groups - 그룹 - - - View - 보기 - - - Quit - 끝내기 - - - About - 정보 - Open database 데이터베이스 열기 - - Save database - 데이터베이스 저장 - - - Close database - 데이터베이스 닫기 - - - New database - 새 데이터베이스 - - - Add new entry - 새 항목 추가 - - - View/Edit entry - 항목 보기/편집 - - - Delete entry - 항목 삭제 - - - Add new group - 새 그룹 추가 - - - Edit group - 그룹 편집 - - - Delete group - 그룹 삭제 - - - Save database as - 다른 이름으로 데이터베이스 저장 - - - Change master key - 마스터 키 변경 - Database settings 데이터베이스 설정 - - Import KeePass 1 database - KeePass 1 데이터베이스 가져오기 - - - Clone entry - 항목 복제 - - - Find - 찾기 - Copy username to clipboard 클립보드에 사용자 이름 복사 @@ -1027,30 +1383,6 @@ This is a one-way migration. You won't be able to open the imported databas Settings 설정 - - Perform Auto-Type - 자동 입력 실행 - - - Open URL - URL 열기 - - - Lock databases - 데이터베이스 잠금 - - - Title - 제목 - - - URL - URL - - - Notes - 메모 - Show toolbar 도구 모음 보이기 @@ -1063,26 +1395,6 @@ This is a one-way migration. You won't be able to open the imported databas Toggle window 창 전환 - - Tools - 도구 - - - Copy username - 사용자 이름 복사 - - - Copy password - 암호 복사 - - - Export to CSV file - CSV 파일로 내보내기 - - - Repair database - 데이터베이스 복구 - KeePass 2 Database KeePass 2 데이터베이스 @@ -1095,14 +1407,327 @@ This is a one-way migration. You won't be able to open the imported databas Save repaired database 복구한 데이터베이스 저장 - - Error - 오류 - Writing the database failed. 데이터베이스에 쓸 수 없습니다. + + &Recent databases + + + + He&lp + + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + + + + &View + + + + &Quit + + + + &About + + + + &Open database + + + + &Save database + + + + &Close database + + + + &New database + + + + Merge from KeePassX database + + + + &Add new entry + + + + &View/Edit entry + + + + &Delete entry + + + + &Add new group + + + + &Edit group + + + + &Delete group + + + + Sa&ve database as + + + + Change &master key + + + + &Database settings + + + + &Clone entry + + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + + + + Copy &username + + + + Cop&y password + + + + &Settings + + + + &Perform Auto-Type + + + + &Open URL + + + + &Lock databases + + + + &Title + + + + &URL + + + + &Notes + + + + &Export to CSV file + + + + Re&pair database + + + + Password Generator + + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + KeePass 1 데이터베이스 가져오기 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + + + + General + 일반 + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + 고급 + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + + + + Default port: 19455 + + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1110,10 +1735,6 @@ This is a one-way migration. You won't be able to open the imported databas Password: 암호: - - Length: - 길이: - Character Types 문자 종류 @@ -1138,71 +1759,161 @@ This is a one-way migration. You won't be able to open the imported databas Exclude look-alike characters 비슷하게 생긴 문자 제외 - - Ensure that the password contains characters from every group - 모든 그룹에서 최소 1글자 이상 포함 - Accept 사용 - - - QCommandLineParser - Displays version information. - 버전 정보를 표시합니다. + %p% + - Displays this help. - 이 도움말을 표시합니다. + strength + - Unknown option '%1'. - 알 수 없는 옵션 '%1'. + entropy + - Unknown options: %1. - 알 수 없는 옵션 '%1'. + &Length: + - Missing value after '%1'. - '%1' 다음에 값이 없습니다. + Pick characters from every group + - Unexpected value after '%1'. - '%1' 다음에 예상하지 못한 값이 왔습니다. + Generate + - [options] - [옵션] + Close + - Usage: %1 - 사용 방법: %1 + Apply + - Options: - 옵션: + Entropy: %1 bit + - Arguments: - 인자: + Password Quality: %1 + + + + Poor + + + + Weak + + + + Good + + + + Excellent + + + + Password + 암호 + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + - QSaveFile + QObject - Existing file %1 is not writable - 존재하는 파일 %1에 기록할 수 없음 + NULL device + - Writing canceled by application - 프로그램에서 쓰기 작업 취소함 + error reading from device + - Partial write. Partition full? - 일부분만 기록되었습니다. 파티션이 가득 찼습니까? + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + 그룹 + + + Title + 제목 + + + Username + 사용자 이름 + + + Password + 암호 + + + URL + URL + + + Notes + 메모 + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1242,20 +1953,111 @@ This is a one-way migration. You won't be able to open the imported databas SearchWidget - Find: - 찾기: + Case Sensitive + - Case sensitive - 대소문자 구분 + Search + 찾기 - Current group - 현재 그룹 + Clear + - Root group - 루트 그룹 + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + @@ -1272,6 +2074,10 @@ This is a one-way migration. You won't be able to open the imported databas Security 보안 + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1279,10 +2085,6 @@ This is a one-way migration. You won't be able to open the imported databas Remember last databases 마지막 데이터베이스 기억 - - Open previous databases on startup - 시작할 때 이전 데이터베이스 열기 - Automatically save on exit 끝낼 때 자동 저장 @@ -1303,10 +2105,6 @@ This is a one-way migration. You won't be able to open the imported databas Global Auto-Type shortcut 전역 자동 입력 단축키 - - Use entry title to match windows for global auto-type - 전역 자동 입력 시 항목 제목과 일치하는 창 찾기 - Language 언어 @@ -1320,15 +2118,43 @@ This is a one-way migration. You won't be able to open the imported databas 시스템 트레이로 최소화 - Remember last key files - 마지막 키 파일 기억 - - - Hide window to system tray instead of App Exit + Load previous databases on startup - Hide window to system tray on App start + Automatically reload the database when modified externally + + + + Hide window to system tray instead of app exit + + + + Minimize window at application startup + + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + 자동 입력 + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type @@ -1351,8 +2177,86 @@ This is a one-way migration. You won't be able to open the imported databas 기본값으로 암호를 평문으로 표시 - Always ask before performing auto-type - 자동으로 입력하기 전에 항상 묻기 + Lock databases after minimizing the window + + + + Don't require password repeat when it is visible + + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1365,20 +2269,36 @@ This is a one-way migration. You won't be able to open the imported databas WelcomeWidget - Welcome! - 환영합니다! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + 최근 데이터베이스 main - - KeePassX - cross-platform password manager - KeePassX - 크로스 플랫폼 암호 관리자 - - - filename of the password database to open (*.kdbx) - 열 암호 데이터베이스 파일 이름 (*.kdbx) - path to a custom config file 사용자 정의 설정 파일 경로 @@ -1387,5 +2307,81 @@ This is a one-way migration. You won't be able to open the imported databas key file of the database 데이터베이스 키 파일 + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_lt.ts b/share/translations/keepassx_lt.ts index 152dad900..a0836e035 100644 --- a/share/translations/keepassx_lt.ts +++ b/share/translations/keepassx_lt.ts @@ -1,27 +1,108 @@ AboutDialog - - Revision - Poversijis - - - Using: - Naudojama: - About KeePassXC Apie KeePassXC - Extensions: + About + Apie + + + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + Derinimo informacija + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + Kopijuoti į iškarpinę + + + Version %1 - Plėtiniai: + Versija %1 - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC yra platinama GNU Bendrosios Viešosios Licencijos (GPL) versijos 2 arba (jūsų pasirinkimu) versijos 3 sąlygomis. + Revision: %1 + Poversijis: %1 + + + Libraries: + Bibliotekos: + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -120,10 +201,6 @@ Pasirinkite, ar norite leisti prieigą. Create Key File... Sukurti rakto failą... - - Error - Klaida - Unable to create Key File : Nepavyko sukurti rakto failo : @@ -132,10 +209,6 @@ Pasirinkite, ar norite leisti prieigą. Select a key file Pasirinkite rakto failą - - Question - Klausimas - Do you really want to use an empty string as password? Ar tikrai norite naudoti tuščią eilutę kaip slaptažodį? @@ -144,10 +217,6 @@ Pasirinkite, ar norite leisti prieigą. Different passwords supplied. Pateikti skirtingi slaptažodžiai. - - Failed to set key file - Nepavyko nustatyti rakto failo - Failed to set %1 as the Key file: %2 @@ -158,6 +227,163 @@ Pasirinkite, ar norite leisti prieigą. &Key file &Rakto failas + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Klaida + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Klaida + + + Unable to calculate master key + Nepavyko apskaičiuoti pagrindinio rakto + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -177,10 +403,6 @@ Pasirinkite, ar norite leisti prieigą. Browse Naršyti - - Error - Klaida - Unable to open the database. Nepavyko atverti duomenų bazės. @@ -201,6 +423,14 @@ Pasirinkite, ar norite leisti prieigą. Select key file Pasirinkite rakto failą + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -277,6 +507,18 @@ Dabar galite ją įrašyti. Use recycle bin Naudoti šiukšlinę + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -296,10 +538,6 @@ Dabar galite ją įrašyti. Open database Atverti duomenų bazę - - Warning - Įspėjimas - File not found! Failas nerastas! @@ -330,10 +568,6 @@ Save changes? "%1" buvo pakeista. Įrašyti pakeitimus? - - Error - Klaida - Writing the database failed. Duomenų bazės rašymas nepavyko. @@ -425,6 +659,14 @@ Ar vis tiek norite ją atverti? Open read-only Atverti tik skaitymui + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -464,10 +706,6 @@ Ar vis tiek norite ją atverti? Do you really want to delete the group "%1" for good? Ar tikrai norite ištrinti grupę "%1"? - - Error - Klaida - Unable to calculate master key Nepavyko apskaičiuoti pagrindinio rakto @@ -528,14 +766,18 @@ Ar vis tiek norite ją atverti? The database file has changed and you have unsaved changes.Do you want to merge your changes? Duomenų bazės failas pasikeitė ir jūs turite neįrašytų pakeitimų. Ar norite sulieti savo pakeitimus? - - Autoreload Failed - Automatinis įkėlimas iš naujo nepavyko - Could not open the new database file while attempting to autoreload this database. Nepavyko atverti naujos duomenų bazės failo, bandant automatiškai iš naujo įkelti šią duomenų bazę. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -575,10 +817,6 @@ Ar vis tiek norite ją atverti? Edit entry Keisti įrašą - - Error - Klaida - Different passwords supplied. Pateikti skirtingi slaptažodžiai. @@ -621,6 +859,22 @@ Ar vis tiek norite ją atverti? 1 year 1 metai + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -632,10 +886,6 @@ Ar vis tiek norite ją atverti? Add Pridėti - - Edit - Keisti - Remove Šalinti @@ -652,6 +902,18 @@ Ar vis tiek norite ją atverti? Open Atverti + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -687,6 +949,10 @@ Ar vis tiek norite ją atverti? Set custo&m sequence: Nustatyti tinkintą s&eką: + + Window Associations + + EditEntryWidgetHistory @@ -796,16 +1062,16 @@ Ar vis tiek norite ją atverti? Paieška - Auto-type + Auto-Type Automatinis rinkimas - Use default auto-type sequence of parent group - Naudoti numatytąją pirminės grupės automatinio rinkimo seką + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Nustatyti numatytąją automatinio rinkimo seką + Set default Auto-Type se&quence + @@ -830,10 +1096,6 @@ Ar vis tiek norite ją atverti? Select Image Pasirinkite paveikslą - - Can't delete icon! - Nepavyksta ištrinti piktogramos! - Error Klaida @@ -850,10 +1112,6 @@ Ar vis tiek norite ją atverti? Can't read icon Nepavyksta perskaityti piktogramos - - Can't delete icon. Still used by %1 items. - Nepavyksta ištrinti piktogramos. Vis dar naudojama %1 elementų. - &Use default icon Na&udoti numatytąją piktogramą @@ -862,6 +1120,14 @@ Ar vis tiek norite ją atverti? Use custo&m icon Naudoti tinkintą piktogra&mą + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -933,6 +1199,11 @@ Ar vis tiek norite ją atverti? URL URL + + Ref: + Reference abbreviation + + Group @@ -991,9 +1262,16 @@ Ar vis tiek norite ją atverti? Ensure that the password contains characters from every group Užtikrinti, kad slaptažodyje būtų simboliai iš kiekvienos grupės + + + KMessageWidget - Accept - Priimti + &Close + + + + Close message + @@ -1002,10 +1280,6 @@ Ar vis tiek norite ją atverti? Import KeePass1 database Importuoti KeePass1 duomenų bazę - - Error - Klaida - Unable to open the database. Nepavyko atverti duomenų bazės. @@ -1070,6 +1344,10 @@ This is a one-way migration. You won't be able to open the imported databas Jūs galite ją importuoti, nuspausdami Duomenų bazė > "Importuoti KeePass 1 duomenų bazę". Tai yra vienakryptis perkėlimas. Jūs negalėsite atverti importuotos duomenų bazės, naudodami senąją KeePassX 0.4 versija. + + Unable to issue challenge-response. + + Main @@ -1081,13 +1359,17 @@ Tai yra vienakryptis perkėlimas. Jūs negalėsite atverti importuotos duomenų KeePassXC - Error KeePassXC - Klaida + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Duomenų bazė - Open database Atverti duomenų bazę @@ -1120,10 +1402,6 @@ Tai yra vienakryptis perkėlimas. Jūs negalėsite atverti importuotos duomenų Toggle window Perjungti langą - - Tools - Įrankiai - KeePass 2 Database KeePass 2 duomenų bazė @@ -1136,10 +1414,6 @@ Tai yra vienakryptis perkėlimas. Jūs negalėsite atverti importuotos duomenų Save repaired database Įrašyti pataisytą duomenų bazę - - Error - Klaida - Writing the database failed. Duomenų bazės rašymas nepavyko. @@ -1232,14 +1506,26 @@ Tai yra vienakryptis perkėlimas. Jūs negalėsite atverti importuotos duomenų &Database settings &Duomenų bazės nustatymai - - &Import KeePass 1 database - &Importuoti KeePass 1 duomenų bazę - &Clone entry &Dublikuoti įrašą + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find &Rasti @@ -1292,6 +1578,46 @@ Tai yra vienakryptis perkėlimas. Jūs negalėsite atverti importuotos duomenų Password Generator Slaptažodžių generatorius + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importuoti KeePass 1 duomenų bazę + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1307,12 +1633,6 @@ Tai yra vienakryptis perkėlimas. Jūs negalėsite atverti importuotos duomenų Sh&ow a notification when credentials are requested R&odyti pranešimą, kai reikalaujama prisijungimo duomenų - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - &Atitikti URL schemas -Bus grąžinami įrašai tik su ta pačia schema (http://, https://, ftp://, ...) - Sort matching entries by &username Rikiuoti atitinkančius įrašus pagal na&udotojo vardą @@ -1321,10 +1641,6 @@ Bus grąžinami įrašai tik su ta pačia schema (http://, https://, ftp://, ... Re&move all stored permissions from entries in active database Šal&inti iš įrašų aktyvioje duomenų bazėje visus saugomus leidimus - - Password generator - Slaptažodžių generatorius - Advanced Išplėstiniai @@ -1341,10 +1657,6 @@ Bus grąžinami įrašai tik su ta pačia schema (http://, https://, ftp://, ... Searc&h in all opened databases for matching entries Ieš&koti atitinkančių įrašų visose atvertose duomenų bazėse - - Only the selected database has to be connected with a client! - Su klientu turi būti sujungta tik pasirinkta duomenų bazė! - HTTP Port: HTTP prievadas: @@ -1361,12 +1673,6 @@ Bus grąžinami įrašai tik su ta pačia schema (http://, https://, ftp://, ... Sort &matching entries by title Rikiuoti atitinkančius įrašus pagal &antraštę - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Įjungti KeepassXC HTTP protokolą -Tai reikalinga, norint prie savo duomenų bazių gauti prieigą iš ChromeIPass ar PassIFox - KeePassXC will listen to this port on 127.0.0.1 KeePassXC klausysis šio prievado ties 127.0.0.1 @@ -1380,21 +1686,11 @@ Tai reikalinga, norint prie savo duomenų bazių gauti prieigą iš ChromeIPass Using default port 19455. Nepavyksta susieti su privilegijuotais prievadais žemiau 1024! Naudojamas numatytasis prievadas 19455. - - - &Return only best matching entries for a URL instead -of all entries for the whole domain - &Vietoj visų įrašų, skirtų visai sričiai, -grąžinti tik geriausiai atitinkančius įrašus, skirtus URL R&emove all shared encryption keys from active database Ša&linti iš aktyvios duomenų bazės visus bendrinamus šifravimo raktus - - The following options can be dangerous. Change them only if you know what you are doing. - Šios parinktys gali būti pavojingos. Keiskite jas tik tuo atveju, jeigu žinote ką darote! - &Return advanced string fields which start with "KPH: " &Grąžinti išplėstines eilutes, kurios prasideda "KPH: " @@ -1403,6 +1699,43 @@ grąžinti tik geriausiai atitinkančius įrašus, skirtus URL Automatically creating or updating string fields is not supported. Automatinis eilutės laukų kūrimas ar atnaujinimas nėra palaikomas. + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Slaptažodžių generatorius + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1494,12 +1827,101 @@ grąžinti tik geriausiai atitinkančius įrašus, skirtus URL Excellent Puikus + + Password + Slaptažodis + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Grupė + + + Title + Antraštė + + + Username + Naudotojo vardas + + + Password + Slaptažodis + + + URL + URL + + + Notes + Pastabos + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1546,14 +1968,18 @@ grąžinti tik geriausiai atitinkančius įrašus, skirtus URL Search Paieška - - Find - Rasti - Clear Išvalyti + + Search... + + + + Limit search to selected group + + Service @@ -1661,6 +2087,10 @@ ir priimtumėte jį. Security Saugumas + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1688,10 +2118,6 @@ ir priimtumėte jį. Global Auto-Type shortcut Visuotinis automatinio rinkimo spartusis klavišas - - Use entry title to match windows for global auto-type - Naudoti įrašo antraštę, norint sutapatinti langus visuotiniam automatiniam rinkimui - Language Kalba @@ -1704,10 +2130,6 @@ ir priimtumėte jį. Hide window to system tray when minimized Suskleidus langą, slėpti jį į sistemos dėklą - - Remember last key files - Prisiminti paskutinius rakto failus - Load previous databases on startup Paleidžiant programą, įkelti ankstesnes duomenų bazes @@ -1724,6 +2146,30 @@ ir priimtumėte jį. Minimize window at application startup Paleidus programą, suskleisti langą + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Automatinis rinkimas + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1743,10 +2189,6 @@ ir priimtumėte jį. Show passwords in cleartext by default Pagal numatymą, rodyti slaptažodžius atviruoju tekstu - - Always ask before performing auto-type - Visuomet klausti prieš atliekant automatinį rinkimą - Lock databases after minimizing the window Suskleidus langą, užrakinti duomenų bazes @@ -1755,6 +2197,80 @@ ir priimtumėte jį. Don't require password repeat when it is visible Nereikalauti pakartoti slaptažodį, kai šis yra matomas + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + sek. + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1766,8 +2282,32 @@ ir priimtumėte jį. WelcomeWidget - Welcome! - Sveiki atvykę! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Paskiausios duomenų bazės @@ -1792,5 +2332,69 @@ ir priimtumėte jį. filenames of the password databases to open (*.kdbx) norimų atverti slaptažodžių duomenų bazių failų pavadinimai (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_nl_NL.ts b/share/translations/keepassx_nl_NL.ts index 33c4e6245..b15ff0a6a 100644 --- a/share/translations/keepassx_nl_NL.ts +++ b/share/translations/keepassx_nl_NL.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - Revisie - - - Using: - Maakt gebruik van: - About KeePassXC Over KeePassXC - Extensions: - - Extensies: - + About + Over - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC wordt verspreid onder de voorwaarden van de GNU General Public License (GPL) versie 2 of (als u wenst) versie 3. + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -120,10 +200,6 @@ Geef aan of u toegang wilt toestaan of niet. Create Key File... Sleutelbestand creëren... - - Error - Fout - Unable to create Key File : Het creëren van het sleutelbestand is mislukt: @@ -132,10 +208,6 @@ Geef aan of u toegang wilt toestaan of niet. Select a key file Kies een sleutelbestand - - Question - Vraag - Do you really want to use an empty string as password? Weet u zeker dat u een leeg veld als wachtwoord wilt gebruiken? @@ -144,10 +216,6 @@ Geef aan of u toegang wilt toestaan of niet. Different passwords supplied. U heeft verschillende wachtwoorden opgegeven. - - Failed to set key file - Het instellen van het sleutelbestand is mislukt - Failed to set %1 as the Key file: %2 @@ -158,6 +226,163 @@ Geef aan of u toegang wilt toestaan of niet. &Key file &Sleutelbestand + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Fout + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Fout + + + Unable to calculate master key + Niet mogelijk om hoofdsleutel te berekenen + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -177,10 +402,6 @@ Geef aan of u toegang wilt toestaan of niet. Browse Bladeren - - Error - Fout - Unable to open the database. Niet mogelijk om de database te openen. @@ -201,6 +422,14 @@ Geef aan of u toegang wilt toestaan of niet. Select key file Kies sleutelbestand + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -277,6 +506,18 @@ U kunt deze nu opslaan. Use recycle bin Prullenbak gebruiken + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -296,10 +537,6 @@ U kunt deze nu opslaan. Open database Database openen - - Warning - Waarschuwing - File not found! Bestand niet gevonden! @@ -330,10 +567,6 @@ Save changes? "%1" is gewijzigd. Opslaan? - - Error - Fout - Writing the database failed. Het opslaan van de database is mislukt. @@ -425,6 +658,14 @@ Wilt u toch doorgaan met openen? Open read-only Openen als alleen-lezen + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -464,10 +705,6 @@ Wilt u toch doorgaan met openen? Do you really want to delete the group "%1" for good? Weet u zeker dat u de groep "%1" wilt verwijderen? - - Error - Fout - Unable to calculate master key Niet mogelijk om hoofdsleutel te berekenen @@ -528,14 +765,18 @@ Wilt u toch doorgaan met openen? The database file has changed and you have unsaved changes.Do you want to merge your changes? Het database-bestand is gewijzigd en u heeft niet-opgeslagen wijzigingen. Wilt u uw wijzigingen samenvoegen? - - Autoreload Failed - Automatisch herladen mislukt - Could not open the new database file while attempting to autoreload this database. De nieuwe database kan niet worden geopend tijdens het automatisch herladen van deze database. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -575,10 +816,6 @@ Wilt u toch doorgaan met openen? Edit entry Element wijzigen - - Error - Fout - Different passwords supplied. Verschillende wachtwoorden opgegeven. @@ -621,6 +858,22 @@ Wilt u toch doorgaan met openen? 1 year 1 jaar + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -632,10 +885,6 @@ Wilt u toch doorgaan met openen? Add Toevoegen - - Edit - Wijzigen - Remove Verwijderen @@ -652,6 +901,18 @@ Wilt u toch doorgaan met openen? Open Open + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -687,6 +948,10 @@ Wilt u toch doorgaan met openen? Set custo&m sequence: Stel aangepaste volgorde in: + + Window Associations + + EditEntryWidgetHistory @@ -796,16 +1061,16 @@ Wilt u toch doorgaan met openen? Zoeken - Auto-type - Auto-typen + Auto-Type + Auto-typen - KeePassX - Use default auto-type sequence of parent group - Gebruik standaard auto-typevolgorde van bovenliggende groep + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Stel standaard auto-typevolgorde in + Set default Auto-Type se&quence + @@ -830,10 +1095,6 @@ Wilt u toch doorgaan met openen? Select Image Kies afbeelding - - Can't delete icon! - Kan icoon niet verwijderen! - Error Fout @@ -850,10 +1111,6 @@ Wilt u toch doorgaan met openen? Can't read icon Kan icoon niet lezen - - Can't delete icon. Still used by %1 items. - Kan icoon niet verwijderen. Het wordt nog gebruikt door %1 elementen. - &Use default icon &Gebruik standaardicoon @@ -862,6 +1119,14 @@ Wilt u toch doorgaan met openen? Use custo&m icon Gebruik aangepast icoon + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -933,6 +1198,11 @@ Wilt u toch doorgaan met openen? URL URL + + Ref: + Reference abbreviation + + Group @@ -991,9 +1261,16 @@ Wilt u toch doorgaan met openen? Ensure that the password contains characters from every group Zorg ervoor dat het wachtwoord tekens uit iedere groep bevat + + + KMessageWidget - Accept - Accepteren + &Close + + + + Close message + @@ -1002,10 +1279,6 @@ Wilt u toch doorgaan met openen? Import KeePass1 database Importeer Keepass 1-database - - Error - Fout - Unable to open the database. Niet mogelijk om de database te openen. @@ -1070,6 +1343,10 @@ This is a one-way migration. You won't be able to open the imported databas U kunt het importeren door te klikken op Database > 'KeePass 1 database importeren'. Deze actie is niet omkeerbaar. U kunt de geimporteerde database niet meer openen met KeePassX 0.4. + + Unable to issue challenge-response. + + Main @@ -1081,13 +1358,17 @@ Deze actie is niet omkeerbaar. U kunt de geimporteerde database niet meer openen KeePassXC - Error KeePassX - Fout + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Database - Open database Open database @@ -1120,10 +1401,6 @@ Deze actie is niet omkeerbaar. U kunt de geimporteerde database niet meer openen Toggle window Wissel venster - - Tools - Hulpmiddelen - KeePass 2 Database KeePass 2 Database @@ -1136,10 +1413,6 @@ Deze actie is niet omkeerbaar. U kunt de geimporteerde database niet meer openen Save repaired database Gerepareerde database opslaan - - Error - Fout - Writing the database failed. Opslaan van de database is mislukt. @@ -1232,14 +1505,26 @@ Deze actie is niet omkeerbaar. U kunt de geimporteerde database niet meer openen &Database settings &Database-instellingen - - &Import KeePass 1 database - &Importeer KeePass 1-database - &Clone entry &Kloon item + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find &Zoeken @@ -1292,6 +1577,46 @@ Deze actie is niet omkeerbaar. U kunt de geimporteerde database niet meer openen Password Generator Wachtwoord generator + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importeer Keepass 1-database + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1307,12 +1632,6 @@ Deze actie is niet omkeerbaar. U kunt de geimporteerde database niet meer openen Sh&ow a notification when credentials are requested Toon een notificatie wanneer inloggegevens worden aangevraagd - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - &Gebruik URL-velden -Alleen items met hetzelfde schema (http://, https://, ftp://) worden gegeven. - Sort matching entries by &username Sorteer gegeven items op $gebruikersnaam @@ -1321,10 +1640,6 @@ Alleen items met hetzelfde schema (http://, https://, ftp://) worden gegeven.Re&move all stored permissions from entries in active database Verwijder alle opgeslagen permissies van items uit de actieve database - - Password generator - Wachtwoord generator - Advanced Geavanceerd @@ -1341,10 +1656,6 @@ Alleen items met hetzelfde schema (http://, https://, ftp://) worden gegeven.Searc&h in all opened databases for matching entries Zoek in alle geopende databases naar overeenkomende items - - Only the selected database has to be connected with a client! - Alleen de geselecteerde database heeft een verbinding nodig met een client! - HTTP Port: HTTP-poort: @@ -1361,12 +1672,6 @@ Alleen items met hetzelfde schema (http://, https://, ftp://) worden gegeven.Sort &matching entries by title Sorteer &overeenkomende items op titel - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Schakel het KeePassXC HTTP-protocol in -Dit is vereist om databases vanuit ChromeIPass of PassIFox te bereiken. - KeePassXC will listen to this port on 127.0.0.1 KeePassXC zal op deze poort op 127.0.0.1 luisteren @@ -1380,21 +1685,11 @@ Dit is vereist om databases vanuit ChromeIPass of PassIFox te bereiken. Kan niet binden naar bevoorrechte poorten onder 1024! Standaardpoort 19455 wordt gebruikt. - - - &Return only best matching entries for a URL instead -of all entries for the whole domain - &Geef alleen de meest overeenkomende items voor een URL -in plaats van alle items voor het gehele domein R&emove all shared encryption keys from active database Verwijder alle gedeelde encryptiesleutels uit de actieve database - - The following options can be dangerous. Change them only if you know what you are doing. - De volgende opties kunnen gevaarlijk zijn. Verander deze dus alleen als je weet wat je doet. - &Return advanced string fields which start with "KPH: " &Geef geadvanceerde tekenreeks velden terug die met "KH: " beginnen. @@ -1403,6 +1698,43 @@ in plaats van alle items voor het gehele domein Automatically creating or updating string fields is not supported. Het automatisch aanmaken of wijzigen van tekenreeks velden wordt niet ondersteund. + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Wachtwoord generator + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1494,12 +1826,101 @@ in plaats van alle items voor het gehele domein Excellent Uitstekend + + Password + Wachtwoord + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - HTTP + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Groep + + + Title + Titel + + + Username + Gebruikersnaam + + + Password + Wachtwoord + + + URL + URL + + + Notes + Opmerkingen + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1546,14 +1967,18 @@ in plaats van alle items voor het gehele domein Search Zoeken - - Find - Zoek - Clear Wissen + + Search... + + + + Limit search to selected group + + Service @@ -1659,6 +2084,10 @@ Geef het een unieke identificerende naam en accepteer de associate wanneer je de Security Beveiliging + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1686,10 +2115,6 @@ Geef het een unieke identificerende naam en accepteer de associate wanneer je de Global Auto-Type shortcut Globale sneltoets voor auto-typen - - Use entry title to match windows for global auto-type - Gebruik titel van item als vensternaam voor auto-typen - Language Taal @@ -1702,10 +2127,6 @@ Geef het een unieke identificerende naam en accepteer de associate wanneer je de Hide window to system tray when minimized Bij minimaliseren enkel icoon in systray tonen - - Remember last key files - Onthoud laatste sleutelbestanden - Load previous databases on startup Open vorige databases bij starten @@ -1722,6 +2143,30 @@ Geef het een unieke identificerende naam en accepteer de associate wanneer je de Minimize window at application startup Scherm minimaliseren bij het opstarten + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Auto-typen - KeePassX + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1741,10 +2186,6 @@ Geef het een unieke identificerende naam en accepteer de associate wanneer je de Show passwords in cleartext by default Laat wachtwoorden standaard zien - - Always ask before performing auto-type - Altijd vragen alvorens auto-type uit te voeren - Lock databases after minimizing the window Vergrendel databases na het minimaliseren van het scherm @@ -1753,6 +2194,80 @@ Geef het een unieke identificerende naam en accepteer de associate wanneer je de Don't require password repeat when it is visible Herhalen van wachtwoord niet vereisen als deze zichtbaar is + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + sec + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1764,8 +2279,32 @@ Geef het een unieke identificerende naam en accepteer de associate wanneer je de WelcomeWidget - Welcome! - Welkom! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Recente databases @@ -1790,5 +2329,69 @@ Geef het een unieke identificerende naam en accepteer de associate wanneer je de filenames of the password databases to open (*.kdbx) bestandsnamen van de te openen wachtwoorddatabases (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_pl.ts b/share/translations/keepassx_pl.ts index ba964d96a..2462b1304 100644 --- a/share/translations/keepassx_pl.ts +++ b/share/translations/keepassx_pl.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - Rewizja - - - Using: - Używa: - About KeePassXC O KeePassXC - Extensions: - - Rozszerzenia: - + About + O - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC jest dystrybuowany zgodnie z warunkami licencji GNU General Public License (GPL) w wersji 2 lub (opcjonalnie) w wersji 3. + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -120,10 +200,6 @@ Wybierz, czy chcesz zezwolić na dostęp. Create Key File... Utwórz plik klucza - - Error - Błąd - Unable to create Key File : Nie można utworzyć pliku klucza : @@ -132,10 +208,6 @@ Wybierz, czy chcesz zezwolić na dostęp. Select a key file Wybierz plik z kluczem - - Question - Pytanie - Do you really want to use an empty string as password? Czy naprawdę chcesz użyć pusty ciąg znaków jako hasło ? @@ -144,10 +216,6 @@ Wybierz, czy chcesz zezwolić na dostęp. Different passwords supplied. Podano różne hasła. - - Failed to set key file - Nie udało się ustawić pliku klucza - Failed to set %1 as the Key file: %2 @@ -158,6 +226,163 @@ Wybierz, czy chcesz zezwolić na dostęp. &Key file &Plik klucza + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Błąd + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Błąd + + + Unable to calculate master key + Nie mogę wyliczyć głównego klucza + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -177,10 +402,6 @@ Wybierz, czy chcesz zezwolić na dostęp. Browse Przeglądaj - - Error - Błąd - Unable to open the database. Nie można otworzyć bazy kluczy. @@ -201,6 +422,14 @@ Wybierz, czy chcesz zezwolić na dostęp. Select key file Wybierz plik z kluczem + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -277,6 +506,18 @@ Możesz teraz ją już zapisać. Use recycle bin Użyj kosza: + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -296,10 +537,6 @@ Możesz teraz ją już zapisać. Open database Otwórz bazę danych - - Warning - Ostrzeżenie - File not found! Nie znaleziono pliku! @@ -330,10 +567,6 @@ Save changes? "%1" został zmieniony. Zapisać zmiany? - - Error - Błąd - Writing the database failed. Błąd w zapisywaniu bazy kluczy. @@ -426,6 +659,14 @@ Czy chcesz ją otworzyć mimo to? Open read-only Otwórz tylko do odczytu + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -465,10 +706,6 @@ Czy chcesz ją otworzyć mimo to? Do you really want to delete the group "%1" for good? Czy na pewno całkowicie usunąć grupę "%1"? - - Error - Błąd - Unable to calculate master key Nie mogę wyliczyć głównego klucza @@ -529,14 +766,18 @@ Czy chcesz ją otworzyć mimo to? The database file has changed and you have unsaved changes.Do you want to merge your changes? Plik bazy danych został zmieniony, a masz niezapisane zmiany. Czy chcesz połączyć twoje zmiany? - - Autoreload Failed - Nieudane automatyczne przeładowanie - Could not open the new database file while attempting to autoreload this database. Nie można otworzyć nowego pliku bazy danych podczas próby automatycznego przeładowania tej bazy. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -576,10 +817,6 @@ Czy chcesz ją otworzyć mimo to? Edit entry Edycja wpisu - - Error - Błąd - Different passwords supplied. Podano różne hasła. @@ -622,6 +859,22 @@ Czy chcesz ją otworzyć mimo to? 1 year 1 rok + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -633,10 +886,6 @@ Czy chcesz ją otworzyć mimo to? Add Dodaj - - Edit - Edytuj - Remove Usuń @@ -653,6 +902,18 @@ Czy chcesz ją otworzyć mimo to? Open Otwórz + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -688,6 +949,10 @@ Czy chcesz ją otworzyć mimo to? Set custo&m sequence: Ustaw niest&andardową sekwencję: + + Window Associations + + EditEntryWidgetHistory @@ -797,16 +1062,16 @@ Czy chcesz ją otworzyć mimo to? Szukaj - Auto-type + Auto-Type Auto-uzupełnianie - Use default auto-type sequence of parent group - Korzystaj z domyślnej sekwencji auto-uzupełniania z nadrzędnej grupy + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Ustaw domyślną sekwencję auto-uzupełniania + Set default Auto-Type se&quence + @@ -831,10 +1096,6 @@ Czy chcesz ją otworzyć mimo to? Select Image Wybierz obraz - - Can't delete icon! - Nie można usunąć ikony! - Error Błąd @@ -851,10 +1112,6 @@ Czy chcesz ją otworzyć mimo to? Can't read icon Nie można odczytać ikony - - Can't delete icon. Still used by %1 items. - Nie można usunąć ikony. Nadal używana przez %1 wpisów. - &Use default icon &Użyj ikony domyślnej @@ -863,6 +1120,14 @@ Czy chcesz ją otworzyć mimo to? Use custo&m icon Użyj niesta&ndardowej ikony + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -934,6 +1199,11 @@ Czy chcesz ją otworzyć mimo to? URL URL + + Ref: + Reference abbreviation + + Group @@ -992,9 +1262,16 @@ Czy chcesz ją otworzyć mimo to? Ensure that the password contains characters from every group Zapewnij, że hasło będzie zawierało znaki ze wszystkich grup + + + KMessageWidget - Accept - Zaakceptuj + &Close + + + + Close message + @@ -1003,10 +1280,6 @@ Czy chcesz ją otworzyć mimo to? Import KeePass1 database Importuj bazę danych KeePass1 - - Error - Błąd - Unable to open the database. Nie można otworzyć bazy kluczy. @@ -1071,6 +1344,10 @@ This is a one-way migration. You won't be able to open the imported databas Możesz zaimportować ją przez wybranie Baza > 'Importuj bazę danych KeePass 1'. Nie będzie można skonwertować nowej bazy do starego programu KeePassX 0.4. + + Unable to issue challenge-response. + + Main @@ -1082,13 +1359,17 @@ Nie będzie można skonwertować nowej bazy do starego programu KeePassX 0.4.KeePassXC - Error KeePassXC - Błąd + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Baza danych - Open database Otwórz bazę danych @@ -1121,10 +1402,6 @@ Nie będzie można skonwertować nowej bazy do starego programu KeePassX 0.4.Toggle window Pokaż/ukryj okno - - Tools - Narzędzia - KeePass 2 Database Baza KeePass 2 @@ -1137,10 +1414,6 @@ Nie będzie można skonwertować nowej bazy do starego programu KeePassX 0.4.Save repaired database Zapisz naprawioną bazę - - Error - Błąd - Writing the database failed. Błąd przy zapisie bazy. @@ -1163,11 +1436,11 @@ Nie będzie można skonwertować nowej bazy do starego programu KeePassX 0.4. &Groups - %Grupy + &Grupy &View - Wi%dok + Wi&dok &Quit @@ -1233,14 +1506,26 @@ Nie będzie można skonwertować nowej bazy do starego programu KeePassX 0.4.&Database settings Ustawienia bazy &danych - - &Import KeePass 1 database - I&mportuj bazę danych KeePass 1 - &Clone entry &Sklonuj wpis + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find &Znajdź @@ -1293,6 +1578,46 @@ Nie będzie można skonwertować nowej bazy do starego programu KeePassX 0.4.Password Generator Generator hasła + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importuj bazę danych KeePass 1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1308,12 +1633,6 @@ Nie będzie można skonwertować nowej bazy do starego programu KeePassX 0.4.Sh&ow a notification when credentials are requested P&okaż powiadomienie, gdy wymagane są poświadczenia - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - &Dopasuj schematy URL -Tylko wpisy z tym samym schematem (http://, https://, ftp://, ...) są zwracane - Sort matching entries by &username Sortuj dopasowane wpisy według &użytkownika @@ -1322,10 +1641,6 @@ Tylko wpisy z tym samym schematem (http://, https://, ftp://, ...) są zwracane< Re&move all stored permissions from entries in active database U&suń wszystkie przechowywane uprawnienia z wpisów w aktywnej bazie danych - - Password generator - Generator hasła - Advanced Zaawansowane @@ -1342,10 +1657,6 @@ Tylko wpisy z tym samym schematem (http://, https://, ftp://, ...) są zwracane< Searc&h in all opened databases for matching entries Szuk&aj we wszystkich otwartych bazach dopasowanych wpisów - - Only the selected database has to be connected with a client! - Tylko wybrana baza danych musi być podłączona do klienta! - HTTP Port: Port HTTP: @@ -1362,12 +1673,6 @@ Tylko wpisy z tym samym schematem (http://, https://, ftp://, ...) są zwracane< Sort &matching entries by title Sortuj dopasowane wpisy według &tytułu - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Włącz protokół HTTP KeepassXC -Jest to wymagane dla dostępu do twoich baz danych z ChromeIPass albo PassIFox - KeePassXC will listen to this port on 127.0.0.1 KeePassXC będzie nasłuchiwać ten port na 127.0.0.1 @@ -1381,21 +1686,11 @@ Jest to wymagane dla dostępu do twoich baz danych z ChromeIPass albo PassIFox Nie można powiązać do uprzywilejowanych portów poniżej 1024! Używam domyślnego portu 19455. - - - &Return only best matching entries for a URL instead -of all entries for the whole domain - &Zwracaj tylko najlepsze dopasowania wpisów dla URL zamiast -wszystkich wpisów całej domeny R&emove all shared encryption keys from active database U&suń wszystkie współdzielone klucze szyfrujące z aktywnej bazy danych - - The following options can be dangerous. Change them only if you know what you are doing. - Poniższe opcje mogą być niebezpieczne. Zmieniaj je tylko wtedy, gdy wiesz, co robisz. - &Return advanced string fields which start with "KPH: " &Zwracaj zaawansowane pola ciągów znaków, które zaczynają się od "KPH: " @@ -1404,6 +1699,43 @@ wszystkich wpisów całej domeny Automatically creating or updating string fields is not supported. Automatyczne tworzenie albo aktualizowanie pól ciągów znaków nie jest obsługiwane. + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Generator hasła + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1453,7 +1785,7 @@ wszystkich wpisów całej domeny &Length: - %Długość: + &Długość: Pick characters from every group @@ -1495,12 +1827,101 @@ wszystkich wpisów całej domeny Excellent Znakomita + + Password + Hasło + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - HTTP + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Grupa + + + Title + Tytuł + + + Username + Użytkownik + + + Password + Hasło + + + URL + URL + + + Notes + Notatki + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1547,14 +1968,18 @@ wszystkich wpisów całej domeny Search Szukaj - - Find - Znajdź - Clear Wyczyść + + Search... + + + + Limit search to selected group + + Service @@ -1661,6 +2086,10 @@ nadaj unikatową nazwę do zidentyfikowania i zaakceptuj. Security Bezpieczeństwo + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1688,10 +2117,6 @@ nadaj unikatową nazwę do zidentyfikowania i zaakceptuj. Global Auto-Type shortcut Globalny skrót auto-uzupełnianie - - Use entry title to match windows for global auto-type - Wykorzystaj tytuł wpisu do dopasowania okien dla globalnego auto-wpisywania - Language Język @@ -1704,10 +2129,6 @@ nadaj unikatową nazwę do zidentyfikowania i zaakceptuj. Hide window to system tray when minimized Schowaj okno do zasobnika podczas minimalizacji - - Remember last key files - Zapamiętaj ostatnie pliki klucza - Load previous databases on startup Załaduj poprzednie bazy danych podczas uruchomienia @@ -1724,6 +2145,30 @@ nadaj unikatową nazwę do zidentyfikowania i zaakceptuj. Minimize window at application startup Minimalizuj okno podczas uruchomienia aplikacji + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Auto-uzupełnianie + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1743,10 +2188,6 @@ nadaj unikatową nazwę do zidentyfikowania i zaakceptuj. Show passwords in cleartext by default Domyślnie pokazuj hasła - - Always ask before performing auto-type - Zawsze pytaj przed wykonaniem auto-uzupełninia - Lock databases after minimizing the window Zablokuj bazę danych po zminimalizowaniu okna @@ -1755,6 +2196,80 @@ nadaj unikatową nazwę do zidentyfikowania i zaakceptuj. Don't require password repeat when it is visible Nie wymagaj powtarzania hasła, gdy jest widoczne + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + s + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1766,8 +2281,32 @@ nadaj unikatową nazwę do zidentyfikowania i zaakceptuj. WelcomeWidget - Welcome! - Witaj! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Niedawne bazy danych @@ -1792,5 +2331,69 @@ nadaj unikatową nazwę do zidentyfikowania i zaakceptuj. filenames of the password databases to open (*.kdbx) nazwy plików baz danych haseł do otwarcia (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_pt_BR.ts b/share/translations/keepassx_pt_BR.ts index 16bb4a32e..a8b33de98 100644 --- a/share/translations/keepassx_pt_BR.ts +++ b/share/translations/keepassx_pt_BR.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - Revisão - - - Using: - Usando: - About KeePassXC Sobre KeePassXC - Extensions: - - Extensões: - + About + Sobre - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC é distribuído nos termos da Licença Pública Geral (GPL), versão 2 ou (à sua escolha) versão 3, do GNU. + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -120,10 +200,6 @@ Selecione se deseja permitir o acesso. Create Key File... Criar Arquivo-Chave... - - Error - Erro - Unable to create Key File : Não foi possível criar o Arquivo-Chave : @@ -132,10 +208,6 @@ Selecione se deseja permitir o acesso. Select a key file Escolha um arquivo-chave - - Question - Pergunta - Do you really want to use an empty string as password? Você realmente quer usar uma sequência vazia como senha? @@ -144,10 +216,6 @@ Selecione se deseja permitir o acesso. Different passwords supplied. Senhas diferentes fornecidas. - - Failed to set key file - Falha ao definir arquivo-chave - Failed to set %1 as the Key file: %2 @@ -158,6 +226,163 @@ Selecione se deseja permitir o acesso. &Key file &Arquivo-Chave + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Erro + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Erro + + + Unable to calculate master key + Não foi possível calcular a chave mestre + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -177,10 +402,6 @@ Selecione se deseja permitir o acesso. Browse Navegar - - Error - Erro - Unable to open the database. Não foi possível abrir o banco de dados. @@ -201,6 +422,14 @@ Selecione se deseja permitir o acesso. Select key file Escolha o arquivo-chave + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -277,6 +506,18 @@ Você pode salvá-lo agora. Use recycle bin Usar lixeira + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -296,10 +537,6 @@ Você pode salvá-lo agora. Open database Abrir banco de dados - - Warning - Aviso - File not found! Arquivo não localizado! @@ -330,10 +567,6 @@ Save changes? "%1" foi modificado. Salvar alterações? - - Error - Erro - Writing the database failed. Escrever no banco de dados falhou. @@ -426,6 +659,14 @@ Mesmo assim deseja salvá-la? Open read-only Abrir somente leitura + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -465,10 +706,6 @@ Mesmo assim deseja salvá-la? Do you really want to delete the group "%1" for good? Você realmente quer apagar o grupo "%1" para sempre? - - Error - Erro - Unable to calculate master key Não foi possível calcular chave mestra @@ -529,14 +766,18 @@ Mesmo assim deseja salvá-la? The database file has changed and you have unsaved changes.Do you want to merge your changes? A base de dados foi alterada e tem alterações não gravadas. Deseja juntar as suas alterações? - - Autoreload Failed - Carregamento Automático Falhou - Could not open the new database file while attempting to autoreload this database. Não foi possível abrir a nova base de dados ao tentar recarregar automaticamente essa base de dados. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -576,10 +817,6 @@ Mesmo assim deseja salvá-la? Edit entry Editar entrada - - Error - Erro - Different passwords supplied. Senhas diferentes fornecidas. @@ -622,6 +859,22 @@ Mesmo assim deseja salvá-la? 1 year 1 ano + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -633,10 +886,6 @@ Mesmo assim deseja salvá-la? Add Adicionar - - Edit - Editar - Remove Remover @@ -653,6 +902,18 @@ Mesmo assim deseja salvá-la? Open Abrir + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -688,6 +949,10 @@ Mesmo assim deseja salvá-la? Set custo&m sequence: Definir sequência &personalizada: + + Window Associations + + EditEntryWidgetHistory @@ -797,16 +1062,16 @@ Mesmo assim deseja salvá-la? Buscar - Auto-type - Auto-digitar + Auto-Type + Auto-Digitação - Use default auto-type sequence of parent group - Usar sequência de auto-digitação padrão do grupo pai + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Definir sequência auto-digitação padrão + Set default Auto-Type se&quence + @@ -831,10 +1096,6 @@ Mesmo assim deseja salvá-la? Select Image Selecionar imagem - - Can't delete icon! - Não é possível apagar o ícone! - Error Erro @@ -851,10 +1112,6 @@ Mesmo assim deseja salvá-la? Can't read icon Não foi possível ler ícone - - Can't delete icon. Still used by %1 items. - Não é possível apagar ícone. Ainda usado por %1 itens. - &Use default icon &Usar ícone padrão @@ -863,6 +1120,14 @@ Mesmo assim deseja salvá-la? Use custo&m icon Usar ícone &personalizado + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -934,6 +1199,11 @@ Mesmo assim deseja salvá-la? URL URL + + Ref: + Reference abbreviation + + Group @@ -992,9 +1262,16 @@ Mesmo assim deseja salvá-la? Ensure that the password contains characters from every group Verificar se a senha contém caracteres de todos os grupos + + + KMessageWidget - Accept - Aceitar + &Close + + + + Close message + @@ -1003,10 +1280,6 @@ Mesmo assim deseja salvá-la? Import KeePass1 database Importar banco de dados KeePass1 - - Error - Erro - Unable to open the database. Não foi possível abrir o banco de dados. @@ -1071,6 +1344,10 @@ This is a one-way migration. You won't be able to open the imported databas Você pode importá-lo clicando em Banco de Dados > 'Importar banco de dados KeePass 1'. Esta é uma migração de uma via. Você não poderá abrir o banco de dados importado com a versão antiga do KeePassX 0.4. + + Unable to issue challenge-response. + + Main @@ -1082,13 +1359,17 @@ Esta é uma migração de uma via. Você não poderá abrir o banco de dados imp KeePassXC - Error KeePassXC - Erro + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Banco de Dados - Open database Abrir banco de dados @@ -1121,10 +1402,6 @@ Esta é uma migração de uma via. Você não poderá abrir o banco de dados imp Toggle window Alternar Janela - - Tools - Ferramentas - KeePass 2 Database Banco de dados Keepass 2 @@ -1137,10 +1414,6 @@ Esta é uma migração de uma via. Você não poderá abrir o banco de dados imp Save repaired database Salvar banco de dados reparado - - Error - Erro - Writing the database failed. Escrita do banco de dados falhou. @@ -1233,14 +1506,26 @@ Esta é uma migração de uma via. Você não poderá abrir o banco de dados imp &Database settings &Definições da base de dados - - &Import KeePass 1 database - &Importar base de dados KeePass 1 - &Clone entry &Clonar entrada + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find &Encontrar @@ -1293,6 +1578,46 @@ Esta é uma migração de uma via. Você não poderá abrir o banco de dados imp Password Generator Gerador de Senha + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importar banco de dados KeePass1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1308,12 +1633,6 @@ Esta é uma migração de uma via. Você não poderá abrir o banco de dados imp Sh&ow a notification when credentials are requested M&ostrar uma notificação quando as credenciais forem solicitadas - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - &Esquemas de URL coincidentes -Somente entradas com o mesmo esquema (http://, https://, ftp://, ...) são mostradas - Sort matching entries by &username Ordenar entradas coincidentes por nome de &usuário @@ -1322,10 +1641,6 @@ Somente entradas com o mesmo esquema (http://, https://, ftp://, ...) são mostr Re&move all stored permissions from entries in active database R&emover todas as permissões armazenadas de entradas na base de dados ativa - - Password generator - Gerador de senha - Advanced Avançado @@ -1342,10 +1657,6 @@ Somente entradas com o mesmo esquema (http://, https://, ftp://, ...) são mostr Searc&h in all opened databases for matching entries Procurar em todas as base de dados abertas por entradas semel&hantes - - Only the selected database has to be connected with a client! - Somente a base de dados selecionada tem que ser conectada com um cliente! - HTTP Port: Porta HTTP: @@ -1362,12 +1673,6 @@ Somente entradas com o mesmo esquema (http://, https://, ftp://, ...) são mostr Sort &matching entries by title Ordenar &entradas por título - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Habilitar o protocolo KeepassXC HTTP -Isso é necessário para acessar os seus bancos de dados usando o ChromeIPass ou PassIFox - KeePassXC will listen to this port on 127.0.0.1 KeePassXC irá escutar esta porta em 127.0.0.1 @@ -1381,21 +1686,11 @@ Isso é necessário para acessar os seus bancos de dados usando o ChromeIPass ou Using default port 19455. Não é possível ligar a portas privilegiadas abaixo de 1024! Usando porta padrão 19455. - - - &Return only best matching entries for a URL instead -of all entries for the whole domain - &Mostrar apenas as melhores entradas correspondentes para um URL em vez de -todas as entradas para o domínio completo R&emove all shared encryption keys from active database R&emover todas as chaves criptografadas compartilhadas da base de dados ativa - - The following options can be dangerous. Change them only if you know what you are doing. - As configurações abaixo podem ser perigosas. Altere-as somente se souber o que está fazendo. - &Return advanced string fields which start with "KPH: " &Mostrar também campos avançados que começam com "KPH: " @@ -1404,6 +1699,43 @@ todas as entradas para o domínio completo Automatically creating or updating string fields is not supported. Criação automática ou atualizações não são suportadas para os valores dos campos. + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Gerador de Senha + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1495,12 +1827,101 @@ todas as entradas para o domínio completo Excellent Excelente + + Password + Senha + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Grupo + + + Title + Título + + + Username + Nome de usuário + + + Password + Senha + + + URL + URL + + + Notes + Notas + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1547,14 +1968,18 @@ todas as entradas para o domínio completo Search Pesquisar - - Find - Localizar - Clear Limpar + + Search... + + + + Limit search to selected group + + Service @@ -1661,6 +2086,10 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Security Segurança + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1688,10 +2117,6 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Global Auto-Type shortcut Atalho para Auto-Digitação Global - - Use entry title to match windows for global auto-type - Usar título da entrada para comparar janelas para auto-digitação global - Language Idioma @@ -1704,10 +2129,6 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Hide window to system tray when minimized Ocultar janela na bandeja de sistema quando minimizada - - Remember last key files - Lembrar dos últimos arquivos-chave - Load previous databases on startup Carregar bancos de dados anteriores na inicialização @@ -1724,6 +2145,30 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Minimize window at application startup Iniciar programa com janela minimizada + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Auto-Digitação + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1743,10 +2188,6 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Show passwords in cleartext by default Mostrar senhas em texto claro por padrão - - Always ask before performing auto-type - Sempre perguntar antes de realizar auto-digitação - Lock databases after minimizing the window Bloquear bancos de dados após minimizar a janela @@ -1755,6 +2196,80 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Don't require password repeat when it is visible Quando a senha for visível não pedir para repeti-la + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + seg + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1766,8 +2281,32 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. WelcomeWidget - Welcome! - Bem-vindo! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Bancos de dados recentes @@ -1792,5 +2331,69 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. filenames of the password databases to open (*.kdbx) nome de arquivo do banco de dados de senhas a ser aberto (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_pt_PT.ts b/share/translations/keepassx_pt_PT.ts index d2f165401..2ceb72672 100644 --- a/share/translations/keepassx_pt_PT.ts +++ b/share/translations/keepassx_pt_PT.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - Revisão - - - Using: - Usando: - About KeePassXC Sobre KeePassXC - Extensions: - - Extensões: - + About + Sobre - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC é distribuído sob os termos da GNU General Public License (GPL) versão 2 ou (em sua opção) versão 3. + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -120,10 +200,6 @@ Selecione se deseja permitir o acesso. Create Key File... Criar ficheiro chave - - Error - Erro - Unable to create Key File : Impossível criar ficheiro chave: @@ -132,10 +208,6 @@ Selecione se deseja permitir o acesso. Select a key file Seleccionar ficheiro chave - - Question - Questão - Do you really want to use an empty string as password? Pretende utilizar um valor sem conteúdo como senha ? @@ -144,10 +216,6 @@ Selecione se deseja permitir o acesso. Different passwords supplied. As senhas inseridas não coincidem. - - Failed to set key file - Falha ao definir o ficheiro chave - Failed to set %1 as the Key file: %2 @@ -158,6 +226,163 @@ Selecione se deseja permitir o acesso. &Key file Ficheiro &chave + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Erro + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Erro + + + Unable to calculate master key + Impossível calcular chave mestra: + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -177,10 +402,6 @@ Selecione se deseja permitir o acesso. Browse Procurar - - Error - Erro - Unable to open the database. Impossível abrir a base de dados. @@ -201,6 +422,14 @@ Selecione se deseja permitir o acesso. Select key file Seleccionar o ficheiro chave + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -277,6 +506,18 @@ Agora pode gravar. Use recycle bin Utilizar reciclagem + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -296,10 +537,6 @@ Agora pode gravar. Open database Abrir base de dados - - Warning - Aviso - File not found! Ficheiro não encontrado ! @@ -330,10 +567,6 @@ Save changes? "%1" foi modificado. Guardar alterações ? - - Error - Erro - Writing the database failed. Falha na escrita da base de dados. @@ -426,6 +659,14 @@ Você quer abri-lo de qualquer maneira? Open read-only Abrir como somente leitura + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -465,10 +706,6 @@ Você quer abri-lo de qualquer maneira? Do you really want to delete the group "%1" for good? Pretender realmente apagar o grupo "%1" para sempre ? - - Error - Erro - Unable to calculate master key Impossível calcular ficheiro chave @@ -530,14 +767,18 @@ Você quer abri-lo de qualquer maneira? The database file has changed and you have unsaved changes.Do you want to merge your changes? O ficheiro da base de dados foi alterado e tem alterações não gravadas. Deseja juntar as suas alterações? - - Autoreload Failed - Carregamento Automático Falhou - Could not open the new database file while attempting to autoreload this database. Não foi possível abrir a nova base de dados ao tentar recarregar automaticamente essa base de dados. + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -577,10 +818,6 @@ Você quer abri-lo de qualquer maneira? Edit entry Editar entrada - - Error - Erro - Different passwords supplied. As senhas inseridas não coincidem. @@ -622,6 +859,22 @@ Você quer abri-lo de qualquer maneira? 1 year 1 ano + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -633,10 +886,6 @@ Você quer abri-lo de qualquer maneira? Add Adicionar - - Edit - Editar - Remove Remover @@ -653,6 +902,18 @@ Você quer abri-lo de qualquer maneira? Open Abrir + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -688,6 +949,10 @@ Você quer abri-lo de qualquer maneira? Set custo&m sequence: Especificar sequência de personalizada: + + Window Associations + + EditEntryWidgetHistory @@ -797,16 +1062,16 @@ Você quer abri-lo de qualquer maneira? Procurar - Auto-type + Auto-Type Auto escrita - Use default auto-type sequence of parent group - Herdar sequência de auto escrita padrão do grupo relacionado + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Especificar sequência padrão de auto escrita + Set default Auto-Type se&quence + @@ -831,10 +1096,6 @@ Você quer abri-lo de qualquer maneira? Select Image Seleccionar imagem - - Can't delete icon! - Impossível apagar o icon - Error Erro @@ -851,10 +1112,6 @@ Você quer abri-lo de qualquer maneira? Can't read icon Não foi possível ler ícone - - Can't delete icon. Still used by %1 items. - Não é possível apagar ícone. Ainda usado por %1 itens. - &Use default icon &Utilizar icon padrão @@ -863,6 +1120,14 @@ Você quer abri-lo de qualquer maneira? Use custo&m icon Utilizar icon personalizado + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -934,6 +1199,11 @@ Você quer abri-lo de qualquer maneira? URL URL + + Ref: + Reference abbreviation + + Group @@ -992,9 +1262,16 @@ Você quer abri-lo de qualquer maneira? Ensure that the password contains characters from every group Verificar que a senha contém caracteres de todos os grupos + + + KMessageWidget - Accept - Aceitar + &Close + + + + Close message + @@ -1003,10 +1280,6 @@ Você quer abri-lo de qualquer maneira? Import KeePass1 database Importar de dados KeePass 1 - - Error - Erro - Unable to open the database. Impossível abrir a base de dados. @@ -1071,6 +1344,10 @@ This is a one-way migration. You won't be able to open the imported databas Pode importá-lo clicando em Base de dados> 'Importar base de dados KeePass 1'. Esta é uma migração unidirecional. Não será possível abrir a base de dados importada com a versão antiga do KeePassX 0.4. + + Unable to issue challenge-response. + + Main @@ -1082,13 +1359,17 @@ Esta é uma migração unidirecional. Não será possível abrir a base de dados KeePassXC - Error KeePassXC - Erro + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - Base de dados - Open database Abrir base de dados @@ -1121,10 +1402,6 @@ Esta é uma migração unidirecional. Não será possível abrir a base de dados Toggle window Alternar janela - - Tools - Ferramentas - KeePass 2 Database Base de dados KeePass 2 @@ -1137,10 +1414,6 @@ Esta é uma migração unidirecional. Não será possível abrir a base de dados Save repaired database Gravar base de dados reparada - - Error - Erro - Writing the database failed. Falha na escrita da base de dados. @@ -1233,14 +1506,26 @@ Esta é uma migração unidirecional. Não será possível abrir a base de dados &Database settings &Definições da base de dados - - &Import KeePass 1 database - &Importar de dados KeePass 1 - &Clone entry &Clonar entrada + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find &Encontrar @@ -1293,6 +1578,46 @@ Esta é uma migração unidirecional. Não será possível abrir a base de dados Password Generator Gerador de senhas + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importar base de dados KeePass 1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1308,12 +1633,6 @@ Esta é uma migração unidirecional. Não será possível abrir a base de dados Sh&ow a notification when credentials are requested M&ostrar uma notificação quando as credenciais forem solicitadas - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - &Esquemas de URL coincidentes -Somente entradas com o mesmo esquema (http://, https://, ftp://, ...) são mostradas - Sort matching entries by &username Ordenar entradas coincidentes por nome de &utilizador @@ -1322,10 +1641,6 @@ Somente entradas com o mesmo esquema (http://, https://, ftp://, ...) são mostr Re&move all stored permissions from entries in active database R&emover todas as permissões armazenadas de entradas na base de dados ativa - - Password generator - Gerador de senhas - Advanced Avançado @@ -1342,10 +1657,6 @@ Somente entradas com o mesmo esquema (http://, https://, ftp://, ...) são mostr Searc&h in all opened databases for matching entries Procurar em todas as base de dados abertas por entradas semel&hantes - - Only the selected database has to be connected with a client! - Somente a base de dados selecionada tem que ser conectada com um cliente! - HTTP Port: Porto HTTP: @@ -1362,12 +1673,6 @@ Somente entradas com o mesmo esquema (http://, https://, ftp://, ...) são mostr Sort &matching entries by title Ordenar entradas por título - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Ativar o protocolo KeepassXC HTTP -Isso é necessário para acessar a sua base de dados a partir do ChromeIPass ou do PassIFox - KeePassXC will listen to this port on 127.0.0.1 KeePassXC vai escutar neste porto em 127.0.0.1 @@ -1381,21 +1686,11 @@ Isso é necessário para acessar a sua base de dados a partir do ChromeIPass ou Using default port 19455. Não é possível ligar a portos privilegiados abaixo de 1024! A utilizar porto por omissão 19455 - - - &Return only best matching entries for a URL instead -of all entries for the whole domain - &Mostrar apenas as melhores entradas correspondentes para um URL em vez -de todas as entradas para todo o domínio R&emove all shared encryption keys from active database R&emover todas as chaves encriptadas partilhadas da base de dados ativa - - The following options can be dangerous. Change them only if you know what you are doing. - As seguintes opções podem ser perigosas. Mudá-los apenas se você sabe o que está fazendo. - &Return advanced string fields which start with "KPH: " &Mostrar também campos avançados que começam com "KPH: " @@ -1404,6 +1699,43 @@ de todas as entradas para todo o domínio Automatically creating or updating string fields is not supported. Automaticamente criando ou atualizando os campos de sequência de caracteres não é suportado. + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Gerador de senhas + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1495,12 +1827,101 @@ de todas as entradas para todo o domínio Excellent Excelente + + Password + Senha + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Grupo + + + Title + Título + + + Username + Nome de utilizador + + + Password + Senha + + + URL + URL + + + Notes + Notas + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1547,14 +1968,18 @@ de todas as entradas para todo o domínio Search Procurar - - Find - Encontrar - Clear Limpar + + Search... + + + + Limit search to selected group + + Service @@ -1661,6 +2086,10 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Security Segurança + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1688,10 +2117,6 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Global Auto-Type shortcut Atalho global de auto escrita - - Use entry title to match windows for global auto-type - Utilizar titulo de entrada para coincidir com janela de entrada de auto escrita global - Language Língua @@ -1704,10 +2129,6 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Hide window to system tray when minimized Esconder janela na barra de sistema quando minimizada - - Remember last key files - Lembrar os últimos ficheiro chave - Load previous databases on startup Carregar base de dados anterior no arranque @@ -1724,6 +2145,30 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Minimize window at application startup Minimizar janela no arranque da aplicação + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Auto escrita + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1743,10 +2188,6 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Show passwords in cleartext by default Revelar senhas em texto por padrão - - Always ask before performing auto-type - Confirmar antes de executar auto escrita - Lock databases after minimizing the window Trancar base de dados ao minimizar a janela @@ -1755,6 +2196,80 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. Don't require password repeat when it is visible Não exigir a repetição da senha quando ela estiver visível + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + seg + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1766,8 +2281,32 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. WelcomeWidget - Welcome! - Bem vindo/a ! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Base de dados recentes @@ -1792,5 +2331,69 @@ dar-lhe um nome único para identificá-lo e aceitá-lo. filenames of the password databases to open (*.kdbx) ficheiro chave para abrir a base de dados (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_ru.ts b/share/translations/keepassx_ru.ts index 94078a1d6..59fc73b37 100644 --- a/share/translations/keepassx_ru.ts +++ b/share/translations/keepassx_ru.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - Ревизия - - - Using: - С помощью: - About KeePassXC О KeePassXC - Extensions: - - Расширения: - + About + О программе - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC распространяется на условиях Стандартной общественной лицензии GNU (GPL) версии 2 или (на ваше усмотрение) версии 3. + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -41,11 +121,12 @@ %1 has requested access to passwords for the following item(s). Please select whether you want to allow access. - %1 запросил доступ к паролям для следующего элемента(ов). Выберете, хотите ли вы разрешить доступ. + %1 запросил доступ к паролям для следующего элемента(ов). +Выберите, хотите ли Вы разрешить доступ. KeePassXC HTTP Confirm Access - Подтверждение доступа KeePassXC HTTP + Подтверждение доступа к KeePassXC HTTP @@ -56,7 +137,7 @@ Please select whether you want to allow access. Auto-Type - KeePassXC - Автоввод — KeePassXC + Автоввод - KeePassXC @@ -82,7 +163,7 @@ Please select whether you want to allow access. Auto-Type - KeePassXC - Автоввод — KeePassXC + Автоввод - KeePassXC @@ -119,10 +200,6 @@ Please select whether you want to allow access. Create Key File... Создать файл-ключ... - - Error - Ошибка - Unable to create Key File : Невозможно создать файл-ключ: @@ -131,10 +208,6 @@ Please select whether you want to allow access. Select a key file Выбрать файл-ключ - - Question - Вопрос - Do you really want to use an empty string as password? Вы действительно хотите использовать в качестве пароля пустую строку? @@ -143,10 +216,6 @@ Please select whether you want to allow access. Different passwords supplied. Пароли не совпадают. - - Failed to set key file - Не удалось установить файл-ключ - Failed to set %1 as the Key file: %2 @@ -155,7 +224,164 @@ Please select whether you want to allow access. &Key file - Файл—&ключ + Файл-&ключ + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Ошибка + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Ошибка + + + Unable to calculate master key + Невозможно вычислить мастер-пароль + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + @@ -176,10 +402,6 @@ Please select whether you want to allow access. Browse Обзор - - Error - Ошибка - Unable to open the database. Невозможно открыть хранилище. @@ -200,6 +422,14 @@ Please select whether you want to allow access. Select key file Выберите файл-ключ + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -217,7 +447,7 @@ Please select whether you want to allow access. Database opened fine. Nothing to do. - Хранилище открылось. Больше нечего делать. + Хранилище открылось прекрасно. Больше нечего делать. Unable to open the database. @@ -276,6 +506,18 @@ You can now save it. Use recycle bin Использовать корзину + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -295,10 +537,6 @@ You can now save it. Open database Открыть хранилище - - Warning - Внимание - File not found! Файл не найден! @@ -329,10 +567,6 @@ Save changes? «%1» изменён. Сохранить изменения? - - Error - Ошибка - Writing the database failed. Не удалось записать хранилище. @@ -402,8 +636,8 @@ Discard changes and close anyway? The database you are trying to save as is locked by another instance of KeePassXC. Do you want to save it anyway? - Хранилище, которые вы пытаетесь сохранить, заблокировано другим экземпляром KeePassXC. -Хотите сохранить во всех случаях? + Хранилище, в которое Вы пытаетесь сохранить, заблокировано другим экземпляром KeePassXC. +Хотите сохранить в любом случе? Passwords @@ -417,13 +651,22 @@ Do you want to save it anyway? The database you are trying to open is locked by another instance of KeePassXC. Do you want to open it anyway? - Хранилище, которые вы пытаетесь открыть, заблокировано другим экземпляром KeePassXC. -Хотите открыть во всех случаях? + Хранилище, которое Вы пытаетесь открыть, заблокировано другим экземпляром KeePassXC. + +Хотите открыть в любом случае? Open read-only Открыть в режиме "только чтение" + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -463,10 +706,6 @@ Do you want to open it anyway? Do you really want to delete the group "%1" for good? Вы действительно хотите навсегда удалить группу «%1»? - - Error - Ошибка - Unable to calculate master key Невозможно вычислить мастер-пароль @@ -477,7 +716,7 @@ Do you want to open it anyway? Do you really want to move entry "%1" to the recycle bin? - Действительно переместить запись "%1" в корзину? + Вы действительно хотите переместить запись "%1" в корзину? Searching... @@ -489,7 +728,7 @@ Do you want to open it anyway? No source database, nothing to do. - Нет исходного хранилища, нечего обрабатывать. + Нет исходного хранилища, нечего обрабатывать. Search Results (%1) @@ -509,15 +748,15 @@ Do you want to open it anyway? Remember my choice - Запомнить выбор + Запомнить мой выбор Autoreload Request - Запрос на автоматическую загрузку + Запрос на автозагрузку The database file has changed. Do you want to load the changes? - Хранилище было изменено. Вы хотите загрузить изменения? + Файл хранилища изменился. Вы хотите загрузить изменения? Merge Request @@ -525,15 +764,19 @@ Do you want to open it anyway? The database file has changed and you have unsaved changes.Do you want to merge your changes? - Файл хранилища был изменён, а так же присутствуют несохранённые изменения. Вы хотите объеденить изменения? - - - Autoreload Failed - Ошибка автоматической загрузки + Файл хранилища изменился, а также присутствуют несохранённые изменения. Вы хотите объединить изменения? Could not open the new database file while attempting to autoreload this database. - Не удаётся открыть новый файл хранилища при попытке автоматической загрузки этого файла. + Не удалось открыть новый файл хранилища при попытке автоматической перезагрузки этого хранилища. + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + @@ -574,10 +817,6 @@ Do you want to open it anyway? Edit entry Редактировать запись - - Error - Ошибка - Different passwords supplied. Пароли не совпадают. @@ -620,6 +859,22 @@ Do you want to open it anyway? 1 year 1 год + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -631,10 +886,6 @@ Do you want to open it anyway? Add Добавить - - Edit - Изменить - Remove Удалить @@ -651,6 +902,18 @@ Do you want to open it anyway? Open Открыть + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -686,6 +949,10 @@ Do you want to open it anyway? Set custo&m sequence: Установить сво&ю последовательность: + + Window Associations + + EditEntryWidgetHistory @@ -795,16 +1062,16 @@ Do you want to open it anyway? Поиск - Auto-type + Auto-Type Автоввод - Use default auto-type sequence of parent group - Используйте стандартный автоввод из последовательности родительской группы + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Последовательность автоввода указать по умолчанию + Set default Auto-Type se&quence + @@ -829,10 +1096,6 @@ Do you want to open it anyway? Select Image Выбор изображения - - Can't delete icon! - Не могу удалить значок! - Error Ошибка @@ -843,16 +1106,12 @@ Do you want to open it anyway? Unable to fetch favicon. - Не удалось получить значок сайта + Не удаётся получить значок сайта Can't read icon Не могу прочитать значок - - Can't delete icon. Still used by %1 items. - Не удается удалить значок, она продолжает использоваться %1 записями. - &Use default icon Использовать с&тандартный значок @@ -861,6 +1120,14 @@ Do you want to open it anyway? Use custo&m icon Использовать св&ой значок + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -885,7 +1152,7 @@ Do you want to open it anyway? Entry - Clone - - Колинировать + - Клон @@ -932,6 +1199,11 @@ Do you want to open it anyway? URL URL + + Ref: + Reference abbreviation + + Group @@ -976,7 +1248,7 @@ Do you want to open it anyway? Special Characters - Особые символы + Специальные символы /*_& ... @@ -984,15 +1256,22 @@ Do you want to open it anyway? Exclude look-alike characters - Исключить выглядящие похожие символы + Исключить визуально схожие символы Ensure that the password contains characters from every group - Убедитесь, что пароль содержит символы всех видов + Убедиться, что пароль содержит символы из каждой группы + + + + KMessageWidget + + &Close + - Accept - Принять + Close message + @@ -1001,10 +1280,6 @@ Do you want to open it anyway? Import KeePass1 database Импортировать хранилище KeePass 1 - - Error - Ошибка - Unable to open the database. Невозможно открыть хранилище. @@ -1069,6 +1344,10 @@ This is a one-way migration. You won't be able to open the imported databas Вы можете импортировать его, нажав на База Данных > 'Импорт KeePass 1 базы данных'. Это одностороннее перемещение. Вы не сможете открыть импортированный базу данных на старой версии KeePassX 0,4. + + Unable to issue challenge-response. + + Main @@ -1078,15 +1357,19 @@ This is a one-way migration. You won't be able to open the imported databas KeePassXC - Error - KeePassXC — Ошибка + KeePassXC - Ошибка + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - Хранилище - Open database Открыть хранилище @@ -1119,10 +1402,6 @@ This is a one-way migration. You won't be able to open the imported databas Toggle window Переключить окно - - Tools - Инструменты - KeePass 2 Database Хранилище KeePass 2 @@ -1135,10 +1414,6 @@ This is a one-way migration. You won't be able to open the imported databas Save repaired database Сохранить восстановленное хранилище - - Error - Ошибка - Writing the database failed. Не удалось записать хранилище. @@ -1193,7 +1468,7 @@ This is a one-way migration. You won't be able to open the imported databas Merge from KeePassX database - Объединить из хранилища KeePassX + Объединить с хранилищем KeePassX &Add new entry @@ -1225,20 +1500,32 @@ This is a one-way migration. You won't be able to open the imported databas Change &master key - Изменить мастер-пароль + Изменить мастер-ключ &Database settings - Параметры хранилища - - - &Import KeePass 1 database - Импортировать хранилище KeePass 1 + Настройки хранилища &Clone entry Клонировать запись + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find Найти @@ -1265,7 +1552,7 @@ This is a one-way migration. You won't be able to open the imported databas &Lock databases - Заблокировать хранилище + Заблокировать хранилища &Title @@ -1285,7 +1572,7 @@ This is a one-way migration. You won't be able to open the imported databas Re&pair database - Восстановление хранилища + Восстановить хранилище Password Generator @@ -1293,7 +1580,43 @@ This is a one-way migration. You won't be able to open the imported databas Clear history - Очистить историю + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Импортировать хранилище KeePass 1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + @@ -1308,13 +1631,7 @@ This is a one-way migration. You won't be able to open the imported databas Sh&ow a notification when credentials are requested - Показывать уведомление при запросе данных для входа - - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - Совпадение со схемой URL -Возвращат&ь только записи с соответствующей схемой (http://, https://, ftp://, ...) + Показывать уведомление при запросе учётных данных Sort matching entries by &username @@ -1322,15 +1639,11 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Re&move all stored permissions from entries in active database - Удалить все сохраненные права доступа из активного хранилища - - - Password generator - Генератор паролей + Удалить все сохранённые права доступа из записей активного хранилища Advanced - Расширенные + Продвинутые Always allow &access to entries @@ -1342,11 +1655,7 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Searc&h in all opened databases for matching entries - Искать соответствующие записи по всем открытым хранилищам - - - Only the selected database has to be connected with a client! - Только выбранное хранилище должно быть соединено с клиентом! + Искать подходящие записи во всех открытых хранилищах HTTP Port: @@ -1362,49 +1671,71 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Sort &matching entries by title - Сортировать совпавшие записи по названию - - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - Включить протокол KeepassXC HTTP -Это требуется для доступа к хранилищам из ChromeIPass или PassIFox + Сортировать совпадающие записи по названию KeePassXC will listen to this port on 127.0.0.1 - KeePassXC будет слушать указнный порт на 127.0.0.1 + KeePassXC будет слушать этот порт на 127.0.0.1 Cannot bind to privileged ports - Не удается выполнить привязку к привилегированным портам + Не удаётся выполнить привязку к привилегированным портам Cannot bind to privileged ports below 1024! Using default port 19455. - Не удается привязать к привилегированным портам с номерами меньше 1024! + Не удаётся привязать к привилегированным портам с номерами меньше 1024! Используется порт по умолчанию: 19455. - - &Return only best matching entries for a URL instead -of all entries for the whole domain - Возвращать толь&ко наиболее совпавшие с URL записи, а не все записи для домена - R&emove all shared encryption keys from active database &Удалить все общие ключи шифрования из активного хранилища - - The following options can be dangerous. Change them only if you know what you are doing. - Используйте эти настройки только если знаете, что делаете! - &Return advanced string fields which start with "KPH: " - Возвращать дополнительные стро&ковые поля, начинающиеся с "KPH: " + Возвращать продвинутые стро&ковые поля, начинающиеся с "KPH: " Automatically creating or updating string fields is not supported. Автоматическое создание или обновление полей, содержащих строки, не поддерживается. + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + Генератор паролей + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1458,7 +1789,7 @@ of all entries for the whole domain Pick characters from every group - Выберете символы из каждой группы + Подобрать символы из каждой группы Generate @@ -1482,26 +1813,115 @@ of all entries for the whole domain Poor - Плохой + Плохое Weak - Слабый + Слабое Good - Хороший + Хорошее Excellent - Отличный + Отличное + + + Password + Пароль + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + QObject - Http - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Группа + + + Title + Заголовок + + + Username + Имя пользователя + + + Password + Пароль + + + URL + URL + + + Notes + Примечания + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1548,14 +1968,18 @@ of all entries for the whole domain Search Поиск - - Find - Найти - Clear Очистить + + Search... + + + + Limit search to selected group + + Service @@ -1573,7 +1997,7 @@ Do you want to overwrite it? The active database is locked! Please unlock the selected database or choose another one which is unlocked. Активное хранилище заблокировано! -Разблокируйте выбранное хранилище или выберите другое, незаблокированное. +Пожалуйста, разблокируйте выбранное хранилище или выберите другое, незаблокированное. Successfully removed %1 encryption-%2 from KeePassX/Http Settings. @@ -1589,11 +2013,11 @@ Please unlock the selected database or choose another one which is unlocked. Removing stored permissions... - Удаляются сохранённые права доступа... + Удаляю сохранённые права доступа... Abort - Отмена + Прервать Successfully removed permissions from %1 %2. @@ -1611,8 +2035,9 @@ Please unlock the selected database or choose another one which is unlocked.You have received an association request for the above key. If you would like to allow it access to your KeePassXC database give it a unique name to identify and accept it. - Вы получили запрос на ассоциацию указанного ключа. -Если вы хотите разрешить доступ к вашему хранилищу KeePassXC, дайте ему уникальное имя и примите запрос. + Вы получили запрос на ассоциацию вышеуказанного ключа. +Если Вы хотите разрешить доступ к Вашему хранилищу KeePassXC, +дайте ему уникальное имя, чтобы распознать и принять ключ. KeePassXC: Overwrite existing key? @@ -1636,7 +2061,7 @@ give it a unique name to identify and accept it. KeePassXC: Settings not available! - KeePassXC% Настройки недоступны! + KeePassXC: Настройки недоступны! KeePassXC: Removed permissions @@ -1644,7 +2069,7 @@ give it a unique name to identify and accept it. KeePassXC: No entry with permissions found! - KeePassXC: Не найдено записей с назначенными правами доступа! + KeePassXC: Не найдена запись с правами доступа! @@ -1661,6 +2086,10 @@ give it a unique name to identify and accept it. Security Безопасность + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1688,10 +2117,6 @@ give it a unique name to identify and accept it. Global Auto-Type shortcut Глобальное сочетание клавиш для автоввода - - Use entry title to match windows for global auto-type - Использовать заголовок записи для подбора окон для глобального автоввода - Language Язык @@ -1704,17 +2129,13 @@ give it a unique name to identify and accept it. Hide window to system tray when minimized При сворачивании прятать окно в область системных уведомлений - - Remember last key files - Запоминать последние файл-ключи - Load previous databases on startup - Открывать предыдущие хранилища при запуске + Загружать предыдущие хранилища при запуске Automatically reload the database when modified externally - Автоматически перечитывать хранилище при его изменении внешними приложениями + Автоматически перезагружать хранилище при его изменении извне Hide window to system tray instead of app exit @@ -1724,6 +2145,30 @@ give it a unique name to identify and accept it. Minimize window at application startup Сворачивать окно при запуске приложения + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Автоввод + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1743,17 +2188,87 @@ give it a unique name to identify and accept it. Show passwords in cleartext by default По умолчанию показывать пароль в открытую - - Always ask before performing auto-type - Всегда спрашивать перед тем, как производить автоввод - Lock databases after minimizing the window - Заблокировать хранилище при сворачивании окна + Блокировать хранилища после сворачивания окна Don't require password repeat when it is visible - Не требовать поворный ввод пароля когда он показывается + Не требовать повторный ввод пароля, когда он показывается + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + сек + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1766,8 +2281,32 @@ give it a unique name to identify and accept it. WelcomeWidget - Welcome! - Добро пожаловать! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Недавние хранилища @@ -1782,7 +2321,7 @@ give it a unique name to identify and accept it. KeePassXC - cross-platform password manager - KeePassXC — кросс-платформенный менеджер паролей + KeePassXC - кроссплатформенный менеджер паролей read password of the database from stdin @@ -1792,5 +2331,69 @@ give it a unique name to identify and accept it. filenames of the password databases to open (*.kdbx) имена файлов открываемого хранилища паролей (*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + - + \ No newline at end of file diff --git a/share/translations/keepassx_sl_SI.ts b/share/translations/keepassx_sl_SI.ts index 164f84ac6..cba2c7621 100644 --- a/share/translations/keepassx_sl_SI.ts +++ b/share/translations/keepassx_sl_SI.ts @@ -1,33 +1,143 @@ - + AboutDialog - About KeePassX - O KeePassX - - - KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassX se razširja pod GNU General Public License (GPL) licenco verzija 2 ali (po želji) verzija 3. - - - Revision + About KeePassXC - Using: + About + O programu + + + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + + + + Allow + + + + Deny + + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + + + + KeePassXC HTTP Confirm Access AutoType - - Auto-Type - KeePassX - Samodejno tipkanje - KeePassX - Couldn't find an entry that matches the window title: Ne najdem vnosa, ki bi ustrezal: + + Auto-Type - KeePassXC + + AutoTypeAssociationsModel @@ -46,14 +156,14 @@ AutoTypeSelectDialog - - Auto-Type - KeePassX - Samodejno tipkanje - KeePassX - Select entry to Auto-Type: Izberi vnos za samodejno tipkanje: + + Auto-Type - KeePassXC + + ChangeMasterKeyWidget @@ -69,10 +179,6 @@ Repeat password: Ponovi geslo: - - Key file - Datoteka s ključi - Browse Prebrskaj @@ -93,10 +199,6 @@ Create Key File... Ustvari datoteko s ključi... - - Error - Napaka - Unable to create Key File : Ustvarjanje datoteke s ključi ni uspelo: @@ -105,10 +207,6 @@ Select a key file Izberi datoteko s kljući - - Question - Vprašanje - Do you really want to use an empty string as password? Ali res želite uporabiti prazen niz kot geslo? @@ -117,16 +215,173 @@ Different passwords supplied. Vnešeni gesli sta različni. - - Failed to set key file - Nastavljanje datoteke s ključi ni uspelo - Failed to set %1 as the Key file: %2 Nastavljanje %1 kot datoteko s ključi ni uspelo: %2 + + &Key file + + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Napaka + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Napaka + + + Unable to calculate master key + Izračun glavnega ključa ni uspel + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -146,10 +401,6 @@ Browse Prebrskaj - - Error - Napaka - Unable to open the database. Odpiranje podatkovne baze ni uspelo. @@ -170,6 +421,14 @@ Select key file Izberi datoteko s ključi + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -179,11 +438,11 @@ Error - + Napaka Can't open key file - + Odpiranje datoteke s ključi ni uspelo Database opened fine. Nothing to do. @@ -191,7 +450,7 @@ Unable to open the database. - + Odpiranje podatkovne baze ni uspelo. Success @@ -225,10 +484,6 @@ You can now save it. Default username: Privzeto uporabniško ime: - - Use recycle bin: - Uporaba koša: - MiB MiB @@ -245,6 +500,22 @@ You can now save it. Max. history size: Max. velikost zgodovine: + + Use recycle bin + + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -264,10 +535,6 @@ You can now save it. Open database Odpri podatkovno bazo - - Warning - Opozorilo - File not found! Datoteke ni mogoče najti! @@ -298,10 +565,6 @@ Save changes? "%1" spremenjeno. Shrani spremembe? - - Error - Napaka - Writing the database failed. Zapis podatkovne baze ni uspel. @@ -318,12 +581,6 @@ Shrani spremembe? locked zaklenjeno - - The database you are trying to open is locked by another instance of KeePassX. -Do you want to open it anyway? Alternatively the database is opened read-only. - Podatkovna baza ki jo želite odpreti je že odprta v drugem KeePassX. -Ali jo vseeno želite odpreti? Lahko jo odprete tudi samo za branje. - Lock database Zakleni podatkovno bazo @@ -367,12 +624,42 @@ Zavrži spremembe in zapri? Pisanje v CSV datoteko ni uspelo - The database you are trying to save as is locked by another instance of KeePassX. + Unable to open the database. + Odpiranje podatkovne baze ni uspelo. + + + Merge database + + + + The database you are trying to save as is locked by another instance of KeePassXC. Do you want to save it anyway? - Unable to open the database. + Passwords + + + + Database already opened + + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + + + + File opened in read only mode. + + + + Open CSV file @@ -414,14 +701,6 @@ Do you want to save it anyway? Do you really want to delete the group "%1" for good? Ali res želite izbrisati skupino "%1"? - - Current group - Trenutna skupina - - - Error - Napaka - Unable to calculate master key Izračun glavnega ključa ni uspel @@ -434,6 +713,66 @@ Do you want to save it anyway? Do you really want to move entry "%1" to the recycle bin? + + Searching... + + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + + + + No Results + + + + Execute command? + + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -473,10 +812,6 @@ Do you want to save it anyway? Edit entry Uredi vnos - - Error - Napaka - Different passwords supplied. Gesli se ne ujemata. @@ -518,6 +853,22 @@ Do you want to save it anyway? 1 year 1 leto + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -529,10 +880,6 @@ Do you want to save it anyway? Add Dodaj - - Edit - Uredi - Remove Odstrani @@ -549,6 +896,18 @@ Do you want to save it anyway? Open Odpri + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -556,14 +915,6 @@ Do you want to save it anyway? Enable Auto-Type for this entry Omogoči samodejno tipkanje za ta vnos - - Inherit default Auto-Type sequence from the group - Dedovanje privzete sekvence za samodejno tipkanje iz skupine - - - Use custom Auto-Type sequence: - Uporabi poljubno sekvenco za samodejno tipkanje: - + + @@ -577,12 +928,24 @@ Do you want to save it anyway? Naslov okna: - Use default sequence - Uporabi privzeto sekvenco + Inherit default Auto-Type sequence from the &group + - Set custom sequence: - Nastavi privzeto sekvenco: + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + @@ -622,10 +985,6 @@ Do you want to save it anyway? Repeat: Ponovi geslo: - - Gen. - Samodejno generiraj - URL: URL: @@ -697,28 +1056,20 @@ Do you want to save it anyway? Išči - Auto-type + Auto-Type Samodejno tipkanje - Use default auto-type sequence of parent group - Za samodejno tipkanje uporabi privzeto sekvenco nadrejene skupine + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Nastavi privzeto sekvenco za samodejno tipkanje + Set default Auto-Type se&quence + EditWidgetIcons - - Use default icon - Uporabi privzeto ikono - - - Use custom icon - Uporabi ikono po meri - Add custom icon Dodaj poljubno ikono @@ -740,19 +1091,35 @@ Do you want to save it anyway? Izberi sliko - Can't delete icon! - Ikone ni mogoče izbrisati! - - - Can't delete icon. Still used by %n item(s). - Ikone ni mogoče izbrisati. Uporablja jo še %n vnos.Ikone ni mogoče izbrisati. Uporabljata jo še %n vnosa.Ikone ni mogoče izbrisati. Uporabljajo jo še %n vnosi.Ikone ni mogoče izbrisati. Uporablja jo še %n vnosov. + Error + Napaka - Error + Download favicon - Can't read icon: + Unable to fetch favicon. + + + + Can't read icon + + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? @@ -775,6 +1142,13 @@ Do you want to save it anyway? Uuid: + + Entry + + - Clone + + + EntryAttributesModel @@ -819,6 +1193,11 @@ Do you want to save it anyway? URL URL + + Ref: + Reference abbreviation + + Group @@ -827,16 +1206,74 @@ Do you want to save it anyway? Koš + + HttpPasswordGeneratorWidget + + Length: + Dolžina: + + + Character Types + Tipi znakov + + + Upper Case Letters + Velike črke + + + A-Z + + + + Lower Case Letters + Male črke + + + a-z + + + + Numbers + Številke + + + 0-9 + + + + Special Characters + Posebni znaki + + + /*_& ... + + + + Exclude look-alike characters + Izključi podobne znake + + + Ensure that the password contains characters from every group + Geslo naj vsebuje znake iz vsake skupine + + + + KMessageWidget + + &Close + + + + Close message + + + KeePass1OpenWidget Import KeePass1 database Uvozi KeePass1 podatkovno bazo - - Error - Napaka - Unable to open the database. Odpiranje podatkovne baze ni uspelo. @@ -870,7 +1307,7 @@ Do you want to save it anyway? Wrong key or database file is corrupt. - + Napačno geslo ali pa je podatkovna baza poškodovana. @@ -898,6 +1335,10 @@ You can import it by clicking on Database > 'Import KeePass 1 database'. This is a one-way migration. You won't be able to open the imported database with the old KeePassX 0.4 version. + + Unable to issue challenge-response. + + Main @@ -906,112 +1347,28 @@ This is a one-way migration. You won't be able to open the imported databas Napaka pri testiranju kriptografskih funkcij. - KeePassX - Error - KeePassX - Napaka + KeePassXC - Error + + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - Podatkovna baza - - - Recent databases - Nedavne podatkovne baze - - - Help - Pomoč - - - Entries - Vnosi - - - Copy attribute to clipboard - Kopiraj atribut v odložišče - - - Groups - Skupine - - - View - Pogled - - - Quit - Izhod - - - About - O programu - Open database Odpri podatkovno bazo - - Save database - Shrani podatkovno bazo - - - Close database - Zapri podatkovno bazo - - - New database - Nova podatkovna baza - - - Add new entry - Dodaj vnos - - - View/Edit entry - Uredi vnos - - - Delete entry - Izbriši vnos - - - Add new group - Dodaj novo skupino - - - Edit group - Uredi skupino - - - Delete group - Izbriši skupino - - - Save database as - Shrani podatkovno bazo kot - - - Change master key - Spremeni glavni ključ - Database settings Nastavitve podatkovne baze - - Import KeePass 1 database - Uvozi KeePass 1 podatkovno bazo - - - Clone entry - Kloniraj vnos - - - Find - Išči - Copy username to clipboard Kopiraj uporabniško ime v odložišče @@ -1024,30 +1381,6 @@ This is a one-way migration. You won't be able to open the imported databas Settings Nastavitve - - Perform Auto-Type - Izvedi samodejno tipkanje - - - Open URL - Odpri URL - - - Lock databases - Zakleni podatkovne baze - - - Title - Naslov - - - URL - URL - - - Notes - Opombe - Show toolbar Prikaži orodno vrstico @@ -1060,44 +1393,337 @@ This is a one-way migration. You won't be able to open the imported databas Toggle window Preklopi okno - - Tools - Orodja - - - Copy username - Kopiraj uporabniško ime - - - Copy password - Kopiraj geslo - - - Export to CSV file - Izvozi v CSV datoteko - - - Repair database - - KeePass 2 Database - + KeePass 2 podatkovna baza All files - + Vse datoteke Save repaired database - Error + Writing the database failed. + Zapis podatkovne baze ni uspel. + + + &Recent databases - Writing the database failed. + He&lp + + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + + + + &View + + + + &Quit + + + + &About + + + + &Open database + + + + &Save database + + + + &Close database + + + + &New database + + + + Merge from KeePassX database + + + + &Add new entry + + + + &View/Edit entry + + + + &Delete entry + + + + &Add new group + + + + &Edit group + + + + &Delete group + + + + Sa&ve database as + + + + Change &master key + + + + &Database settings + + + + &Clone entry + + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + + + + Copy &username + + + + Cop&y password + + + + &Settings + + + + &Perform Auto-Type + + + + &Open URL + + + + &Lock databases + + + + &Title + + + + &URL + + + + &Notes + + + + &Export to CSV file + + + + Re&pair database + + + + Password Generator + + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Uvozi KeePass 1 podatkovno bazo + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + + + + General + Splošno + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + Napredno + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + + + + Default port: 19455 + + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. @@ -1107,10 +1733,6 @@ This is a one-way migration. You won't be able to open the imported databas Password: Geslo: - - Length: - Dolžina: - Character Types Tipi znakov @@ -1135,71 +1757,161 @@ This is a one-way migration. You won't be able to open the imported databas Exclude look-alike characters Izključi podobne znake - - Ensure that the password contains characters from every group - Geslo naj vsebuje znake iz vsake skupine - Accept Sprejmi - - - QCommandLineParser - Displays version information. - Prikaže informacije o različici. + %p% + - Displays this help. - Prikaže pomoč. + strength + - Unknown option '%1'. - Neznana izbrira %1. + entropy + - Unknown options: %1. - Neznane izbire: %1. + &Length: + - Missing value after '%1'. - Manjkajoča vrednost po '%1'. + Pick characters from every group + - Unexpected value after '%1'. - Nepričakovana vrednost po '%1'. + Generate + - [options] - [možnosti] + Close + - Usage: %1 - Uporaba: %1 + Apply + - Options: - Možnosti: + Entropy: %1 bit + - Arguments: - Argumenti: + Password Quality: %1 + + + + Poor + + + + Weak + + + + Good + + + + Excellent + + + + Password + Geslo + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + - QSaveFile + QObject - Existing file %1 is not writable - Obstoječa datoteka %1 ni zapisljiva + NULL device + - Writing canceled by application - Aplikacija je prekinila pisanje + error reading from device + - Partial write. Partition full? - Delno pisanje. Polna particija? + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Skupina + + + Title + Naslov + + + Username + Uporabniško ime + + + Password + Geslo + + + URL + URL + + + Notes + Opombe + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1239,20 +1951,111 @@ This is a one-way migration. You won't be able to open the imported databas SearchWidget - Find: - Išči: + Case Sensitive + - Case sensitive - Razlikuj med velikimi in malimi črkami + Search + Išči - Current group - Trenutna skupina + Clear + - Root group - Korenska skupina + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + @@ -1269,6 +2072,10 @@ This is a one-way migration. You won't be able to open the imported databas Security Varnost + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1276,10 +2083,6 @@ This is a one-way migration. You won't be able to open the imported databas Remember last databases Zapomni si zadnje podatkovne baze - - Open previous databases on startup - Odpri prejšnje podatkovne baze ob zagonu programa - Automatically save on exit Samodejno shrani ob izhodu @@ -1300,10 +2103,6 @@ This is a one-way migration. You won't be able to open the imported databas Global Auto-Type shortcut Globalna bližnjica za samodejno tipkanje - - Use entry title to match windows for global auto-type - Uporabi ujemanje naslova vnosa in naslova okna pri samodejnem tipkanju - Language Jezik @@ -1317,15 +2116,43 @@ This is a one-way migration. You won't be able to open the imported databas Minimiziraj v sistemsko vrstico - Remember last key files - Zapomni si zadnje datoteke s ključi - - - Hide window to system tray instead of App Exit + Load previous databases on startup - Hide window to system tray on App start + Automatically reload the database when modified externally + + + + Hide window to system tray instead of app exit + + + + Minimize window at application startup + + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Samodejno tipkanje + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type @@ -1348,8 +2175,86 @@ This is a one-way migration. You won't be able to open the imported databas Gesla privzeto v čistopisu - Always ask before performing auto-type - Pred izvedbo samodejnega tipkanja vprašaj za potrditev + Lock databases after minimizing the window + + + + Don't require password repeat when it is visible + + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + sekundah + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1362,20 +2267,36 @@ This is a one-way migration. You won't be able to open the imported databas WelcomeWidget - Welcome! - Dobrodošli! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Nedavne podatkovne baze main - - KeePassX - cross-platform password manager - KeePassX - urejevalnik gesel za različne platforme - - - filename of the password database to open (*.kdbx) - končnica podatkovne baze (*.kdbx) - path to a custom config file pot do konfiguracijske datoteke po meri @@ -1384,5 +2305,81 @@ This is a one-way migration. You won't be able to open the imported databas key file of the database datoteka s ključi podatkovne baze + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_sv.ts b/share/translations/keepassx_sv.ts index 70dcd1bfd..7953bf0fa 100644 --- a/share/translations/keepassx_sv.ts +++ b/share/translations/keepassx_sv.ts @@ -1,33 +1,143 @@ - + AboutDialog - About KeePassX - Om KeePassX + About KeePassXC + Om KeePassXC - KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. - Keepassx distribueras enligt villkoren i GNU General Public License (GPL) version 2 eller (om du vill) version 3. + About + Om - Revision - Revision + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + - Using: - Använder: + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + + + + Allow + + + + Deny + + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + + + + KeePassXC HTTP Confirm Access + AutoType - - Auto-Type - KeePassX - Auto-skriv - KeePassX - Couldn't find an entry that matches the window title: Kunde inte hitta en post som matchar fönstertiteln: + + Auto-Type - KeePassXC + + AutoTypeAssociationsModel @@ -46,14 +156,14 @@ AutoTypeSelectDialog - - Auto-Type - KeePassX - Auto-skriv - KeePassX - Select entry to Auto-Type: Välj post att auto-skriva + + Auto-Type - KeePassXC + + ChangeMasterKeyWidget @@ -69,10 +179,6 @@ Repeat password: Repetera lösenord: - - Key file - Nyckel-fil - Browse Bläddra @@ -93,10 +199,6 @@ Create Key File... Skapa nyckel-fil... - - Error - Fel - Unable to create Key File : Kunde inte skapa nyckel-fil @@ -105,10 +207,6 @@ Select a key file Välj nyckel-fil - - Question - Fråga - Do you really want to use an empty string as password? Vill du verkligen vill använda en tom sträng som lösenord? @@ -117,16 +215,173 @@ Different passwords supplied. Olika lösenord angivna - - Failed to set key file - Kunde inte sätta nyckel-fil - Failed to set %1 as the Key file: %2 Kunde inte sätta %1 som nyckel-fil: %2 + + &Key file + + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Fel + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Fel + + + Unable to calculate master key + Kunde inte räkna nu master-nyckeln + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -146,10 +401,6 @@ Browse Bläddra - - Error - Fel - Unable to open the database. Kunde inte öppna databas. @@ -170,6 +421,14 @@ Select key file Välj nyckel-fil + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -226,10 +485,6 @@ Du kan nu spara den. Default username: Standard användarnamn: - - Use recycle bin: - Använd papperskorg: - MiB MiB @@ -246,6 +501,22 @@ Du kan nu spara den. Max. history size: Maximal historik storlek: + + Use recycle bin + + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -265,10 +536,6 @@ Du kan nu spara den. Open database Öppna databas - - Warning - Varning - File not found! Filen kunde inte hittas! @@ -299,10 +566,6 @@ Save changes? "%1" har ändrats. Spara ändringarna? - - Error - Fel - Writing the database failed. Kunde inte skriva till databasen. @@ -319,12 +582,6 @@ Spara ändringarna? locked låst - - The database you are trying to open is locked by another instance of KeePassX. -Do you want to open it anyway? Alternatively the database is opened read-only. - Databasen som du försöker öppna är låst av en annan instans av KeePassX. -Vill du öppna den ändå? Databasen kommer då att öppnas skrivskyddad. - Lock database Lås databasen @@ -368,13 +625,42 @@ Kasta ändringarna och stäng endå? Kunde inte skriva till CSV-filen - The database you are trying to save as is locked by another instance of KeePassX. -Do you want to save it anyway? - Databasen du försöker spara som är låst av en annan instans av KeePassX. -Vill du spara endå? + Unable to open the database. + Kunde inte öppna databas. - Unable to open the database. + Merge database + + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + + + + Passwords + Lösenord + + + Database already opened + + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + + + + File opened in read only mode. + + + + Open CSV file @@ -416,14 +702,6 @@ Vill du spara endå? Do you really want to delete the group "%1" for good? Vill du verkligen ta bort gruppen "%1" för gott? - - Current group - Nuvarande grupp - - - Error - Fel - Unable to calculate master key Kunde inte räkna nu master-nyckeln @@ -436,6 +714,66 @@ Vill du spara endå? Do you really want to move entry "%1" to the recycle bin? + + Searching... + + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + + + + No Results + + + + Execute command? + + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -475,10 +813,6 @@ Vill du spara endå? Edit entry Ändra post - - Error - Fel - Different passwords supplied. Olika lösenord angivna @@ -521,6 +855,22 @@ Vill du spara endå? 1 year 1 år + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -532,10 +882,6 @@ Vill du spara endå? Add Lägg till - - Edit - Ändra - Remove Ta bort @@ -552,6 +898,18 @@ Vill du spara endå? Open Öppna + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -559,14 +917,6 @@ Vill du spara endå? Enable Auto-Type for this entry Slå på auto-skriv för denna post - - Inherit default Auto-Type sequence from the group - Ärv standard auto-skriv sekvens för grupp - - - Use custom Auto-Type sequence: - Använd egen auto-skriv sekvens: - + + @@ -580,12 +930,24 @@ Vill du spara endå? Fönster titel: - Use default sequence - Använd standard sekvens + Inherit default Auto-Type sequence from the &group + - Set custom sequence: - Egen sekvens: + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + @@ -625,10 +987,6 @@ Vill du spara endå? Repeat: Repetera: - - Gen. - Gen. - URL: URL: @@ -700,28 +1058,20 @@ Vill du spara endå? Sök - Auto-type + Auto-Type Auto-skriv - Use default auto-type sequence of parent group - Använd standard auto-skriv sekvensen från föräldergruppen + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Ange standard auto-skriv sekvens + Set default Auto-Type se&quence + EditWidgetIcons - - Use default icon - Använd standard ikon - - - Use custom icon - Använd egen ikon - Add custom icon Lägg till egen ikon @@ -743,19 +1093,35 @@ Vill du spara endå? Välj bild - Can't delete icon! - Kan inte ta bort ikon! - - - Can't delete icon. Still used by %n item(s). - Kan inte ta bort ikonen. Den används fortfarande av %n postKan inte ta bort ikonen. Den används fortfarande av %n poster + Error + Fel - Error + Download favicon - Can't read icon: + Unable to fetch favicon. + + + + Can't read icon + + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? @@ -778,6 +1144,13 @@ Vill du spara endå? UUID: + + Entry + + - Clone + + + EntryAttributesModel @@ -822,6 +1195,11 @@ Vill du spara endå? URL URL + + Ref: + Reference abbreviation + + Group @@ -830,16 +1208,74 @@ Vill du spara endå? Papperskorg + + HttpPasswordGeneratorWidget + + Length: + Längd: + + + Character Types + Teckentyper + + + Upper Case Letters + Versaler + + + A-Z + + + + Lower Case Letters + Gemener + + + a-z + + + + Numbers + Siffror + + + 0-9 + + + + Special Characters + Specialtecken + + + /*_& ... + + + + Exclude look-alike characters + Uteslut liknande tecken + + + Ensure that the password contains characters from every group + Säkerställ att lösenordet innehåller tecken från varje grupp + + + + KMessageWidget + + &Close + + + + Close message + + + KeePass1OpenWidget Import KeePass1 database Importera KeePass1 databas - - Error - Fel - Unable to open the database. Kunde inte öppna databas. @@ -873,7 +1309,7 @@ Vill du spara endå? Wrong key or database file is corrupt. - + Fel lösenord eller korrupt databas-fil @@ -904,6 +1340,10 @@ This is a one-way migration. You won't be able to open the imported databas Du kan importera den genom att klicka på Databas > Importera KeePass 1 databas. Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas. Det som används i KeePassX 0.4. + + Unable to issue challenge-response. + + Main @@ -912,112 +1352,28 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Allvarligt fel vid testning av kryptografiska funktioner. - KeePassX - Error - KeePassX - Fel + KeePassXC - Error + + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - Databas - - - Recent databases - Senast använda databaser - - - Help - Hjälp - - - Entries - Poster - - - Copy attribute to clipboard - Kopiera attribut - - - Groups - Grupper - - - View - Vy - - - Quit - Avsluta - - - About - Om - Open database Öppna databas - - Save database - Spara databas - - - Close database - Stäng databas - - - New database - Ny databas - - - Add new entry - Lägg till ny post - - - View/Edit entry - Visa/ändra post - - - Delete entry - Ta bort post - - - Add new group - Lägg till ny grupp - - - Edit group - Ändra grupp - - - Delete group - Ta bort grupp - - - Save database as - Spara databas som - - - Change master key - Ändra huvud lösenord - Database settings Databasinställningar - - Import KeePass 1 database - Importera KeePass1 databas - - - Clone entry - Klona post - - - Find - Sök - Copy username to clipboard Kopiera användarnamn @@ -1030,30 +1386,6 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Settings Inställningar - - Perform Auto-Type - Utför auto-skriv - - - Open URL - Öppna URL - - - Lock databases - Lås databaser - - - Title - Titel - - - URL - URL - - - Notes - Anteckningar - Show toolbar Visa verktygsfält @@ -1066,26 +1398,6 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Toggle window Visa/dölj fönster - - Tools - Verktyg - - - Copy username - Kopiera användarnamn - - - Copy password - Kopiera lösenord - - - Export to CSV file - Exportera till CSV-fil - - - Repair database - Laga databasen - KeePass 2 Database KeePass 2 databas @@ -1098,14 +1410,327 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Save repaired database Spara lagad databas - - Error - Fel - Writing the database failed. Misslyckades med att skriva till databasen. + + &Recent databases + + + + He&lp + + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + + + + &View + + + + &Quit + + + + &About + + + + &Open database + + + + &Save database + + + + &Close database + + + + &New database + + + + Merge from KeePassX database + + + + &Add new entry + + + + &View/Edit entry + + + + &Delete entry + + + + &Add new group + + + + &Edit group + + + + &Delete group + + + + Sa&ve database as + + + + Change &master key + + + + &Database settings + + + + &Clone entry + + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + + + + Copy &username + + + + Cop&y password + + + + &Settings + + + + &Perform Auto-Type + + + + &Open URL + + + + &Lock databases + + + + &Title + + + + &URL + + + + &Notes + + + + &Export to CSV file + + + + Re&pair database + + + + Password Generator + + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Importera KeePass1 databas + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + + + + General + Allmän + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + Avancerat + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + + + + Default port: 19455 + + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1113,10 +1738,6 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Password: Lösenord: - - Length: - Längd: - Character Types Teckentyper @@ -1141,71 +1762,161 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Exclude look-alike characters Uteslut liknande tecken - - Ensure that the password contains characters from every group - Säkerställ att lösenordet innehåller tecken från varje grupp - Accept Acceptera - - - QCommandLineParser - Displays version information. - Visar versionsinformation. + %p% + - Displays this help. - Visa denna hjälp. + strength + - Unknown option '%1'. - Okänt alternativ: '%1' + entropy + - Unknown options: %1. - Okända alternativ: '%1' + &Length: + - Missing value after '%1'. - Saknar värde efter '%1' + Pick characters from every group + - Unexpected value after '%1'. - Oväntat värde efter '%1' + Generate + - [options] - [alternativ] + Close + - Usage: %1 - Användning: %1 + Apply + - Options: - Alternativ: + Entropy: %1 bit + - Arguments: - Argument: + Password Quality: %1 + + + + Poor + + + + Weak + + + + Good + + + + Excellent + + + + Password + Lösenord + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + - QSaveFile + QObject - Existing file %1 is not writable - Den existerande filen %1 är inte skrivbar + NULL device + - Writing canceled by application - Skrivning avbruten av applikation + error reading from device + - Partial write. Partition full? - Delvis skrivet. Är partitionen full? + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Grupp + + + Title + Titel + + + Username + Användarnamn + + + Password + Lösenord + + + URL + URL + + + Notes + Anteckningar + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1245,20 +1956,111 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas SearchWidget - Find: - Sök: + Case Sensitive + - Case sensitive - Skiftlägeskänslig + Search + Sök - Current group - Nuvarande grupp + Clear + - Root group - Root grupp + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + @@ -1275,6 +2077,10 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Security Säkerhet + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1282,10 +2088,6 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Remember last databases Komihåg senaste databasen - - Open previous databases on startup - Öppna senaste databasen när programmet startar - Automatically save on exit Spara automatiskt när applikationen anslutas @@ -1306,10 +2108,6 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Global Auto-Type shortcut Globalt auto-skriv kortkommando - - Use entry title to match windows for global auto-type - Använda postens titel till matchning med fönster för globalt auto-skriv - Language Språk @@ -1323,15 +2121,43 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Vid minimering, minimera fönstret till systemfältet - Remember last key files - Komihåg senaste nyckel-filen - - - Hide window to system tray instead of App Exit + Load previous databases on startup - Hide window to system tray on App start + Automatically reload the database when modified externally + + + + Hide window to system tray instead of app exit + + + + Minimize window at application startup + + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Auto-skriv + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type @@ -1354,8 +2180,86 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas Visa lösenord i klartext som standard - Always ask before performing auto-type - Fråga alltid innan auto-skriv utförs + Lock databases after minimizing the window + + + + Don't require password repeat when it is visible + + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + sek + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1368,20 +2272,36 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas WelcomeWidget - Welcome! - Välkommen! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Senast använda databaser main - - KeePassX - cross-platform password manager - KeePassX - plattformsoberoende lösenordshanterare - - - filename of the password database to open (*.kdbx) - namn på databas fil att öppna (*.kdbx) - path to a custom config file Sökväg till egen konfigurations-fil @@ -1390,5 +2310,81 @@ Detta är en envägsmigration. Du kan inte spara en databas som KeePass1 databas key file of the database nyckel-fil för databas + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_uk.ts b/share/translations/keepassx_uk.ts index 46541a059..d2ceb1d32 100644 --- a/share/translations/keepassx_uk.ts +++ b/share/translations/keepassx_uk.ts @@ -1,33 +1,143 @@ - + AboutDialog - About KeePassX - Про KeePassX + About KeePassXC + - KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassX розповсюджується на умовах Загальної публічної ліцензії GNU (GPL) версії 2 або (на ваш вибір) версії 3. + About + Про програму - Revision - Ревізія + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + - Using: - Використання: + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + + + + Allow + + + + Deny + + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + + + + KeePassXC HTTP Confirm Access + AutoType - - Auto-Type - KeePassX - Автозаповнення — KeePassX - Couldn't find an entry that matches the window title: Не знайдено запис, що відповідає заголовку вікна: + + Auto-Type - KeePassXC + + AutoTypeAssociationsModel @@ -46,14 +156,14 @@ AutoTypeSelectDialog - - Auto-Type - KeePassX - Автозаповнення — KeePassX - Select entry to Auto-Type: Оберіть запис для автозаповнення: + + Auto-Type - KeePassXC + + ChangeMasterKeyWidget @@ -69,10 +179,6 @@ Repeat password: Повторіть пароль: - - Key file - Файл-ключ - Browse Огляд @@ -93,10 +199,6 @@ Create Key File... Створити файл-ключ... - - Error - Помилка - Unable to create Key File : Неможливо створити файл-ключ: @@ -105,10 +207,6 @@ Select a key file Обрати файл-ключ - - Question - Питання - Do you really want to use an empty string as password? Ви дійсно хочете використати порожній рядок в якості пароля? @@ -117,16 +215,173 @@ Different passwords supplied. Паролі не співпадають. - - Failed to set key file - Не вдалося встановити файл-ключ - Failed to set %1 as the Key file: %2 Не вдалося встановити %1 в якості файл-ключа: %2 + + &Key file + + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + Помилка + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + Помилка + + + Unable to calculate master key + Неможливо вирахувати майстер-пароль + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -146,10 +401,6 @@ Browse Огляд - - Error - Помилка - Unable to open the database. Неможливо відкрити сховище. @@ -170,6 +421,14 @@ Select key file Оберіть файл-ключ + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -179,11 +438,11 @@ Error - + Помилка Can't open key file - + Не вдається відкрити файл-ключ Database opened fine. Nothing to do. @@ -191,7 +450,7 @@ Unable to open the database. - + Неможливо відкрити сховище. Success @@ -225,10 +484,6 @@ You can now save it. Default username: Типове ім’я користувача: - - Use recycle bin: - Використати смітник: - MiB MiB @@ -245,6 +500,22 @@ You can now save it. Max. history size: Максимальний розмір історії: + + Use recycle bin + + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -264,10 +535,6 @@ You can now save it. Open database Відкрити сховище - - Warning - Увага - File not found! Файл не знайдено! @@ -298,10 +565,6 @@ Save changes? "%1" змінено. Зберегти зміни? - - Error - Помилка - Writing the database failed. Записати сховище не вдалося. @@ -318,11 +581,6 @@ Save changes? locked заблоковано - - The database you are trying to open is locked by another instance of KeePassX. -Do you want to open it anyway? Alternatively the database is opened read-only. - Сховище, яке ви хочете відкрити, заблоковано іншою запущеною копією KeePassX. Все одно відкрити? Сховище буде відкрито тільки для читання. - Lock database Заблокувати сховище @@ -366,13 +624,42 @@ Discard changes and close anyway? Не вдалось записати CSV файл. - The database you are trying to save as is locked by another instance of KeePassX. -Do you want to save it anyway? - Це сховище заблоковано іншою запущеною копією KeePassX. -Ви впевнені, що хочете зберегти його? + Unable to open the database. + Неможливо відкрити сховище. - Unable to open the database. + Merge database + + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + + + + Passwords + + + + Database already opened + + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + + + + Open read-only + + + + File opened in read only mode. + + + + Open CSV file @@ -414,14 +701,6 @@ Do you want to save it anyway? Do you really want to delete the group "%1" for good? Ви дійсно хочете назавжди видалити групу «%1»? - - Current group - Поточна група - - - Error - Помилка - Unable to calculate master key Неможливо вирахувати майстер-пароль @@ -434,6 +713,66 @@ Do you want to save it anyway? Do you really want to move entry "%1" to the recycle bin? + + Searching... + + + + No current database. + + + + No source database, nothing to do. + + + + Search Results (%1) + + + + No Results + + + + Execute command? + + + + Do you really want to execute the following command?<br><br>%1<br> + + + + Remember my choice + + + + Autoreload Request + + + + The database file has changed. Do you want to load the changes? + + + + Merge Request + + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + + + + Could not open the new database file while attempting to autoreload this database. + + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -473,10 +812,6 @@ Do you want to save it anyway? Edit entry Змінити запис - - Error - Помилка - Different passwords supplied. Паролі не співпадають. @@ -519,6 +854,22 @@ Do you want to save it anyway? 1 year 1 рік + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -530,10 +881,6 @@ Do you want to save it anyway? Add Додати - - Edit - Змінити - Remove Видалити @@ -550,6 +897,18 @@ Do you want to save it anyway? Open Відкрити + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -557,14 +916,6 @@ Do you want to save it anyway? Enable Auto-Type for this entry Увімкнути автозаповнення для цього запису - - Inherit default Auto-Type sequence from the group - Успадкувати типову послідовність автозаповнення від групи - - - Use custom Auto-Type sequence: - Використовувати свою послідовність автозаповнення: - + + @@ -578,12 +929,24 @@ Do you want to save it anyway? Заголовок вікна: - Use default sequence - Використовувати типову послідовність + Inherit default Auto-Type sequence from the &group + - Set custom sequence: - Встановити свою послідовність: + &Use custom Auto-Type sequence: + + + + Use default se&quence + + + + Set custo&m sequence: + + + + Window Associations + @@ -623,10 +986,6 @@ Do you want to save it anyway? Repeat: Пароль ще раз: - - Gen. - Генер. - URL: URL: @@ -698,28 +1057,20 @@ Do you want to save it anyway? Пошук - Auto-type + Auto-Type Автозаповнення - Use default auto-type sequence of parent group - Використовувати типову послідовність автозаповнення батьківської групи + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - Типова послідовність автозаповнення + Set default Auto-Type se&quence + EditWidgetIcons - - Use default icon - Використовувати типовий значок - - - Use custom icon - Використовувати свій значок - Add custom icon Додати свій значок @@ -741,19 +1092,35 @@ Do you want to save it anyway? Вибір зображення - Can't delete icon! - Неможливо видалити значок! - - - Can't delete icon. Still used by %n item(s). - Ви дійсно хочете перемістити %n запис в смітник?Ви дійсно хочете перемістити %n записи в смітник?Ви дійсно хочете перемістити %n записів в смітник? + Error + Помилка - Error + Download favicon - Can't read icon: + Unable to fetch favicon. + + + + Can't read icon + + + + &Use default icon + + + + Use custo&m icon + + + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? @@ -776,6 +1143,13 @@ Do you want to save it anyway? Uuid: + + Entry + + - Clone + + + EntryAttributesModel @@ -820,6 +1194,11 @@ Do you want to save it anyway? URL URL + + Ref: + Reference abbreviation + + Group @@ -828,16 +1207,74 @@ Do you want to save it anyway? Смітник + + HttpPasswordGeneratorWidget + + Length: + Довжина: + + + Character Types + Види символів + + + Upper Case Letters + Великі літери + + + A-Z + + + + Lower Case Letters + Малі літери + + + a-z + + + + Numbers + Цифри + + + 0-9 + + + + Special Characters + Спеціальні символи + + + /*_& ... + + + + Exclude look-alike characters + Виключити неоднозначні символи + + + Ensure that the password contains characters from every group + Переконатися, що пароль містить символи всіх видів + + + + KMessageWidget + + &Close + + + + Close message + + + KeePass1OpenWidget Import KeePass1 database Імпортувати сховище KeePass 1 - - Error - Помилка - Unable to open the database. Неможливо відкрити сховище. @@ -871,7 +1308,7 @@ Do you want to save it anyway? Wrong key or database file is corrupt. - + Неправильний ключ або файл сховища пошкоджено. @@ -902,6 +1339,10 @@ This is a one-way migration. You won't be able to open the imported databas Ви можете імпортувати його, натиснувши Сховище > 'Імпортувати сховище KeePass 1'. Це односторонній спосіб міграції. Ви не зможете відкрити імпортоване сховище в попередній версії KeePassX 0.4. + + Unable to issue challenge-response. + + Main @@ -910,112 +1351,28 @@ This is a one-way migration. You won't be able to open the imported databas Невиправна помилка в процесі тестування криптографічних функцій. - KeePassX - Error - KeePassX — Помилка + KeePassXC - Error + + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - Сховище - - - Recent databases - Недавні сховища - - - Help - Довідка - - - Entries - Записи - - - Copy attribute to clipboard - Копіювати атрибут в буфер обміну - - - Groups - Групи - - - View - Вигляд - - - Quit - Вихід - - - About - Про програму - Open database Відкрити сховище - - Save database - Зберегти сховище - - - Close database - Закрити сховище - - - New database - Нове сховище - - - Add new entry - Додати новий запис - - - View/Edit entry - Проглянути/змінити запис - - - Delete entry - Видалити запис - - - Add new group - Додати нову групу - - - Edit group - Редагувати групу - - - Delete group - Видалити групу - - - Save database as - Зберегти сховище як - - - Change master key - Змінити майстер-пароль - Database settings Параметри сховища - - Import KeePass 1 database - Імпортувати сховище KeePass 1 - - - Clone entry - Клонувати запис - - - Find - Знайти - Copy username to clipboard Копіювати ім’я користувача в буфер обміну @@ -1028,30 +1385,6 @@ This is a one-way migration. You won't be able to open the imported databas Settings Налаштування - - Perform Auto-Type - Здійснити автозаповнення - - - Open URL - Відкрити URL - - - Lock databases - Заблокувати сховище - - - Title - Заголовок - - - URL - URL - - - Notes - Примітки - Show toolbar Показати панель инструментів @@ -1064,44 +1397,337 @@ This is a one-way migration. You won't be able to open the imported databas Toggle window Перемкнути вікно - - Tools - Інструменти - - - Copy username - Копіювати ім’я користувача - - - Copy password - Копіювати пароль - - - Export to CSV file - Експортувати в файл CSV - - - Repair database - - KeePass 2 Database - + Сховище KeePass 2 All files - + Всі файли Save repaired database - Error + Writing the database failed. + Записати сховище не вдалося. + + + &Recent databases - Writing the database failed. + He&lp + + + + E&ntries + + + + Copy att&ribute to clipboard + + + + &Groups + + + + &View + + + + &Quit + + + + &About + + + + &Open database + + + + &Save database + + + + &Close database + + + + &New database + + + + Merge from KeePassX database + + + + &Add new entry + + + + &View/Edit entry + + + + &Delete entry + + + + &Add new group + + + + &Edit group + + + + &Delete group + + + + Sa&ve database as + + + + Change &master key + + + + &Database settings + + + + &Clone entry + + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + + + + Copy &username + + + + Cop&y password + + + + &Settings + + + + &Perform Auto-Type + + + + &Open URL + + + + &Lock databases + + + + &Title + + + + &URL + + + + &Notes + + + + &Export to CSV file + + + + Re&pair database + + + + Password Generator + + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + Імпортувати сховище KeePass 1 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + + + + General + Загальні + + + Sh&ow a notification when credentials are requested + + + + Sort matching entries by &username + + + + Re&move all stored permissions from entries in active database + + + + Advanced + Розширені + + + Always allow &access to entries + + + + Always allow &updating entries + + + + Searc&h in all opened databases for matching entries + + + + HTTP Port: + + + + Default port: 19455 + + + + Re&quest to unlock the database if it is locked + + + + Sort &matching entries by title + + + + KeePassXC will listen to this port on 127.0.0.1 + + + + Cannot bind to privileged ports + + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + + + + R&emove all shared encryption keys from active database + + + + &Return advanced string fields which start with "KPH: " + + + + Automatically creating or updating string fields is not supported. + + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. @@ -1111,10 +1737,6 @@ This is a one-way migration. You won't be able to open the imported databas Password: Пароль: - - Length: - Довжина: - Character Types Види символів @@ -1139,71 +1761,161 @@ This is a one-way migration. You won't be able to open the imported databas Exclude look-alike characters Виключити неоднозначні символи - - Ensure that the password contains characters from every group - Переконатися, що пароль містить символи всіх видів - Accept Прийняти - - - QCommandLineParser - Displays version information. - Показує інформацію про версію. + %p% + - Displays this help. - Показує цю довідку. + strength + - Unknown option '%1'. - Невідома опція «%1». + entropy + - Unknown options: %1. - Невідомі опції %1. + &Length: + - Missing value after '%1'. - Пропущено значення після «%1». + Pick characters from every group + - Unexpected value after '%1'. - Непередбачене значення після «%1». + Generate + - [options] - [опції] + Close + - Usage: %1 - Використання: %1 + Apply + - Options: - Опції: + Entropy: %1 bit + - Arguments: - Аргументи: + Password Quality: %1 + + + + Poor + + + + Weak + + + + Good + + + + Excellent + + + + Password + Пароль + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + - QSaveFile + QObject - Existing file %1 is not writable - Існуючий файл %1 непридатний для запису + NULL device + - Writing canceled by application - Запис відмінено застосунком + error reading from device + - Partial write. Partition full? - Частковий запис. Разділ переповнений? + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + Група + + + Title + Заголовок + + + Username + Ім’я користувача + + + Password + Пароль + + + URL + URL + + + Notes + Примітки + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1243,20 +1955,111 @@ This is a one-way migration. You won't be able to open the imported databas SearchWidget - Find: - Знайти: + Case Sensitive + - Case sensitive - Враховується регістр + Search + Пошук - Current group - Поточна група + Clear + - Root group - Коренева група + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + + + + Do you want to update the information in %1 - %2? + + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + + + + No shared encryption-keys found in KeePassHttp Settings. + + + + The active database does not contain an entry of KeePassHttp Settings. + + + + Removing stored permissions... + + + + Abort + + + + Successfully removed permissions from %1 %2. + + + + The active database does not contain an entry with permissions. + + + + KeePassXC: New key association request + + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + + + + KeePassXC: Overwrite existing key? + + + + KeePassXC: Update Entry + + + + KeePassXC: Database locked! + + + + KeePassXC: Removed keys from database + + + + KeePassXC: No keys found + + + + KeePassXC: Settings not available! + + + + KeePassXC: Removed permissions + + + + KeePassXC: No entry with permissions found! + @@ -1273,6 +2076,10 @@ This is a one-way migration. You won't be able to open the imported databas Security Безпека + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1280,10 +2087,6 @@ This is a one-way migration. You won't be able to open the imported databas Remember last databases Пам’ятати останнє сховище - - Open previous databases on startup - Відкривати останнє сховище під час запуску - Automatically save on exit Автоматично зберігати при виході @@ -1304,10 +2107,6 @@ This is a one-way migration. You won't be able to open the imported databas Global Auto-Type shortcut Глобальні сполучення клавіш для автозаповнення - - Use entry title to match windows for global auto-type - Використовувати заголовок запису для вибору вікон для глобального автозаповнення - Language Мова @@ -1321,15 +2120,43 @@ This is a one-way migration. You won't be able to open the imported databas При згортанні ховати вікно в область системних повідомлень - Remember last key files - Пам’ятати останні файл-ключі - - - Hide window to system tray instead of App Exit + Load previous databases on startup - Hide window to system tray on App start + Automatically reload the database when modified externally + + + + Hide window to system tray instead of app exit + + + + Minimize window at application startup + + + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + Автозаповнення + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type @@ -1352,8 +2179,86 @@ This is a one-way migration. You won't be able to open the imported databas Типово показувати пароль у відкритому вигляді - Always ask before performing auto-type - Завжди запитувати перед автозаповненням + Lock databases after minimizing the window + + + + Don't require password repeat when it is visible + + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + сек + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1366,20 +2271,36 @@ This is a one-way migration. You won't be able to open the imported databas WelcomeWidget - Welcome! - Ласкаво просимо! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + Недавні сховища main - - KeePassX - cross-platform password manager - KeePassX — кросплатформний менеджер паролів - - - filename of the password database to open (*.kdbx) - назва файла сховища паролів, що відкривається (*.kdbx) - path to a custom config file шлях до власного файла налаштувань @@ -1388,5 +2309,81 @@ This is a one-way migration. You won't be able to open the imported databas key file of the database файл-ключ сховища + + KeePassXC - cross-platform password manager + + + + read password of the database from stdin + + + + filenames of the password databases to open (*.kdbx) + + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_zh_CN.ts b/share/translations/keepassx_zh_CN.ts index bbb55e651..8bc3aaac6 100644 --- a/share/translations/keepassx_zh_CN.ts +++ b/share/translations/keepassx_zh_CN.ts @@ -1,27 +1,107 @@ AboutDialog - - Revision - 修改 - - - Using: - 使用: - About KeePassXC 关于 KeePassXC - Extensions: - - 扩展: - + About + 关于 - KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassXC 使用的是第 2 版 GNU 通用公共授权协议(GPL)(你可以根据需要选用第 3 版). + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + + + + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + @@ -119,10 +199,6 @@ Please select whether you want to allow access. Create Key File... 创建秘钥文件... - - Error - 错误 - Unable to create Key File : 无法创建秘钥文件: @@ -131,10 +207,6 @@ Please select whether you want to allow access. Select a key file 选择一个秘钥文件 - - Question - 问题 - Do you really want to use an empty string as password? 你确定要使用空密码? @@ -143,10 +215,6 @@ Please select whether you want to allow access. Different passwords supplied. 你输入了不同的密码 - - Failed to set key file - 设置秘钥文件失败 - Failed to set %1 as the Key file: %2 @@ -157,6 +225,163 @@ Please select whether you want to allow access. &Key file 秘钥文件 + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + 错误 + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + 错误 + + + Unable to calculate master key + 无法计算主密码 + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -176,10 +401,6 @@ Please select whether you want to allow access. Browse 浏览 - - Error - 错误 - Unable to open the database. 无法打开数据库 @@ -200,6 +421,14 @@ Please select whether you want to allow access. Select key file 选择秘钥文件 + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -276,6 +505,18 @@ You can now save it. Use recycle bin 使用回收站 + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -295,10 +536,6 @@ You can now save it. Open database 打开数据库 - - Warning - 警告 - File not found! 找不到文件! @@ -329,10 +566,6 @@ Save changes? "%1" 已被修改。 要保存吗? - - Error - 错误 - Writing the database failed. 数据库写入失败 @@ -424,6 +657,14 @@ Do you want to open it anyway? Open read-only 已只读方式打开 + + File opened in read only mode. + + + + Open CSV file + + DatabaseWidget @@ -463,10 +704,6 @@ Do you want to open it anyway? Do you really want to delete the group "%1" for good? 你确定永远删除 "%1" 群组吗? - - Error - 错误 - Unable to calculate master key 无法计算主密码 @@ -527,14 +764,18 @@ Do you want to open it anyway? The database file has changed and you have unsaved changes.Do you want to merge your changes? 数据库文件已更改,您有未保存的更改。是否合并您的更改? - - Autoreload Failed - 自动加载失败 - Could not open the new database file while attempting to autoreload this database. 在尝试 autoreload 此数据库不打开新的数据库文件。 + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? + + EditEntryWidget @@ -574,10 +815,6 @@ Do you want to open it anyway? Edit entry 编辑项目 - - Error - 错误 - Different passwords supplied. 你输入了不同的密码 @@ -620,6 +857,22 @@ Do you want to open it anyway? 1 year 1 年 + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -631,10 +884,6 @@ Do you want to open it anyway? Add 添加 - - Edit - 编辑 - Remove 移除 @@ -651,6 +900,18 @@ Do you want to open it anyway? Open 打开 + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -686,6 +947,10 @@ Do you want to open it anyway? Set custo&m sequence: 设置自定义顺序 + + Window Associations + + EditEntryWidgetHistory @@ -795,16 +1060,16 @@ Do you want to open it anyway? 搜索 - Auto-type + Auto-Type 自动输入 - Use default auto-type sequence of parent group - 使用父群组默认顺序 + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - 设置默认自动输入顺序 + Set default Auto-Type se&quence + @@ -829,10 +1094,6 @@ Do you want to open it anyway? Select Image 选择图片 - - Can't delete icon! - 不能删除图标! - Error 错误 @@ -849,10 +1110,6 @@ Do you want to open it anyway? Can't read icon 无法读取图标 - - Can't delete icon. Still used by %1 items. - %1 项目正在使用,无法删除图标。 - &Use default icon 使用默认图标 @@ -861,6 +1118,14 @@ Do you want to open it anyway? Use custo&m icon 使用自定义图标 + + Confirm Delete + + + + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? + + EditWidgetProperties @@ -932,6 +1197,11 @@ Do you want to open it anyway? URL 网址 + + Ref: + Reference abbreviation + + Group @@ -990,9 +1260,16 @@ Do you want to open it anyway? Ensure that the password contains characters from every group 确保密码包含每种的字符 + + + KMessageWidget - Accept - 接受 + &Close + + + + Close message + @@ -1001,10 +1278,6 @@ Do you want to open it anyway? Import KeePass1 database 导入 KeePass 1 数据库 - - Error - 错误 - Unable to open the database. 无法打开数据库。 @@ -1068,6 +1341,10 @@ This is a one-way migration. You won't be able to open the imported databas 你可以通过点击 数据库 > '导入KeePass 1 数据库’ 来导入。 这是不可逆的修改。导入后的数据库将无法由旧版的KeePassX 0.4版本打开。 + + Unable to issue challenge-response. + + Main @@ -1079,13 +1356,17 @@ This is a one-way migration. You won't be able to open the imported databas KeePassXC - Error KeePassXC - 错误 + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + + MainWindow - - Database - 数据库 - Open database 打开数据库 @@ -1118,10 +1399,6 @@ This is a one-way migration. You won't be able to open the imported databas Toggle window 切换窗口 - - Tools - 工具 - KeePass 2 Database KeePass 2 数据库 @@ -1134,10 +1411,6 @@ This is a one-way migration. You won't be able to open the imported databas Save repaired database 保存修复后的数据库 - - Error - 错误 - Writing the database failed. 数据库写入失败 @@ -1230,14 +1503,26 @@ This is a one-way migration. You won't be able to open the imported databas &Database settings 数据库设置 - - &Import KeePass 1 database - 导入KeePass 1 数据库 - &Clone entry 复制项目 + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + &Find 查找 @@ -1290,6 +1575,46 @@ This is a one-way migration. You won't be able to open the imported databas Password Generator 密码生成器 + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + 导入KeePass 1 数据库 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + OptionDialog @@ -1305,11 +1630,6 @@ This is a one-way migration. You won't be able to open the imported databas Sh&ow a notification when credentials are requested - - &Match URL schemes -Only entries with the same scheme (http://, https://, ftp://, ...) are returned - - Sort matching entries by &username 按匹配用户名排序 @@ -1318,10 +1638,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Re&move all stored permissions from entries in active database - - Password generator - 密码生成器 - Advanced 高级 @@ -1338,10 +1654,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Searc&h in all opened databases for matching entries 在所有打开的数据库中查找匹配项目 - - Only the selected database has to be connected with a client! - 客户端只能连接选中的数据库! - HTTP Port: HTTP端口: @@ -1358,12 +1670,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned< Sort &matching entries by title 用标题排序匹配的项目 - - Enable KeepassXC HTTP protocol -This is required for accessing your databases from ChromeIPass or PassIFox - 启用KeepassXC HTTP协议 -这需要ChromeIPass或PassIFox访问你的数据库 - KeePassXC will listen to this port on 127.0.0.1 KeePassXC 将监听 127.0.0.1上的此端口 @@ -1378,19 +1684,10 @@ Using default port 19455. 无法绑定低于 1024的特殊端口 ! 使用默认端口 19455。 - - &Return only best matching entries for a URL instead -of all entries for the whole domain - 只返回最佳匹配条目的 URL 而不是整个域的所有条目 - R&emove all shared encryption keys from active database 移除所有激活数据库共享的加密密钥 - - The following options can be dangerous. Change them only if you know what you are doing. - 以下选项不要修改。除非你知道自己在做什么。 - &Return advanced string fields which start with "KPH: " @@ -1399,6 +1696,43 @@ of all entries for the whole domain Automatically creating or updating string fields is not supported. 不支持自动创建或更新字符串字段。 + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + 密码生成器 + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1490,12 +1824,101 @@ of all entries for the whole domain Excellent 优秀 + + Password + 密码 + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + + QObject - Http - Http + NULL device + + + + error reading from device + + + + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + 群组 + + + Title + 标题 + + + Username + 用户名 + + + Password + 密码 + + + URL + 网址 + + + Notes + 备注 + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1542,14 +1965,18 @@ of all entries for the whole domain Search 搜索 - - Find - 查找 - Clear 清除 + + Search... + + + + Limit search to selected group + + Service @@ -1654,6 +2081,10 @@ give it a unique name to identify and accept it. Security 安全 + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1681,10 +2112,6 @@ give it a unique name to identify and accept it. Global Auto-Type shortcut 自动输入全局快捷键 - - Use entry title to match windows for global auto-type - 使用项目标题来查找自动输入的目标窗口 - Language 语言 @@ -1697,10 +2124,6 @@ give it a unique name to identify and accept it. Hide window to system tray when minimized 将窗口最小化至任务栏 - - Remember last key files - 记住最近的秘钥文件 - Load previous databases on startup 在启动时加载最近的数据库 @@ -1717,6 +2140,30 @@ give it a unique name to identify and accept it. Minimize window at application startup 在应用程序启动时窗口最小化 + + Basic Settings + + + + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + 自动输入 + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type + + SettingsWidgetSecurity @@ -1736,10 +2183,6 @@ give it a unique name to identify and accept it. Show passwords in cleartext by default 默认以明码显示密码 - - Always ask before performing auto-type - 在执行自动输入前询问 - Lock databases after minimizing the window 在最小化窗口后锁定数据库 @@ -1748,6 +2191,80 @@ give it a unique name to identify and accept it. Don't require password repeat when it is visible 可见时不需要重复输入密码 + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + + UnlockDatabaseWidget @@ -1759,8 +2276,32 @@ give it a unique name to identify and accept it. WelcomeWidget - Welcome! - 欢迎! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + 最近的数据库 @@ -1785,5 +2326,69 @@ give it a unique name to identify and accept it. filenames of the password databases to open (*.kdbx) 打开密码数据库文件名(*.kdbx) + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file diff --git a/share/translations/keepassx_zh_TW.ts b/share/translations/keepassx_zh_TW.ts index c8c2f9e1a..d50444e76 100644 --- a/share/translations/keepassx_zh_TW.ts +++ b/share/translations/keepassx_zh_TW.ts @@ -1,33 +1,144 @@ - + AboutDialog - About KeePassX - 關於 KeePassX + About KeePassXC + 關於 KeePassXC - KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. - KeePassX 是使用第 2 版 GNU 通用公共授權條款所發佈的 (或者,可根據你的選擇選用第 3 版) + About + 關於 - Revision - 修改紀錄 + <html><head/><body><p>Report bugs at: <a href="https://github.com/keepassxreboot/keepassxc/issues"><span style="text-decoration: underline; color:#0000ff;">https://github.com</span></a></p></body></html> + - Using: - 使用: + <html><head/><body><p>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</p></body></html> + + + + <html><head><style>li {font-size: 10pt}</style></head><body><p><span style=" font-size:10pt;">Project Maintainers:</span></p><ul><li>droidmonkey</li><li>phoerious</li><li>TheZ3ro</li><li>louib</li><li>Weslly</li><li>debfx (KeePassX)</li></ul></body></html> + + + + Contributors + + + + <html><body> + <p style="font-size:x-large; font-weight:600;">Code:</p> + <ul> + <li style="font-size:10pt">debfx (KeePassX)</li> + <li style="font-size:10pt">BlueIce (KeePassX)</li> + <li style="font-size:10pt">droidmonkey</li> + <li style="font-size:10pt">phoerious</li> + <li style="font-size:10pt">TheZ3ro</li> + <li style="font-size:10pt">louib</li> + <li style="font-size:10pt">weslly</li> + <li style="font-size:10pt">keithbennett (KeePassHTTP)</li> + <li style="font-size:10pt">Typz (KeePassHTTP)</li> + <li style="font-size:10pt">denk-mal (KeePassHTTP)</li> + <li style="font-size:10pt">kylemanna (YubiKey)</li> + <li style="font-size:10pt">seatedscribe (CSV Importer)</li> + <li style="font-size:10pt">pgalves (Inline Messages)</li> + </ul> + <p style="font-size:x-large; font-weight:600;">Translations:</p> + <ul> + <li style="font-size:10pt"><span style="font-weight:600;">Chinese:</span> Biggulu, ligyxy, BestSteve</li> + <li style="font-size:10pt"><span style="font-weight:600;">Czech:</span> pavelb, JosefVitu</li> + <li style="font-size:10pt"><span style="font-weight:600;">Dutch:</span> Vistaus, KnooL, apie</li> + <li style="font-size:10pt"><span style="font-weight:600;">Finnish:</span> MawKKe</li> + <li style="font-size:10pt"><span style="font-weight:600;">French:</span> Scrat15, frgnca, gilbsgilbs, gtalbot, iannick, kyodev, logut</li> + <li style="font-size:10pt"><span style="font-weight:600;">German:</span> Calyrx, DavidHamburg, antsas, codejunky, jensrutschmann, montilo, omnisome4, origin_de, pcrcoding, phoerious, rgloor, vlenzer</li> + <li style="font-size:10pt"><span style="font-weight:600;">Greek:</span> nplatis</li> + <li style="font-size:10pt"><span style="font-weight:600;">Italian:</span> TheZ3ro, FranzMari, Mte90, tosky</li> + <li style="font-size:10pt"><span style="font-weight:600;">Kazakh:</span> sotrud_nik</li> + <li style="font-size:10pt"><span style="font-weight:600;">Lithuanian:</span> Moo</li> + <li style="font-size:10pt"><span style="font-weight:600;">Polish:</span> konradmb, mrerexx</li> + <li style="font-size:10pt"><span style="font-weight:600;">Portuguese: </span>vitor895, weslly, American_Jesus, mihai.ile</li> + <li style="font-size:10pt"><span style="font-weight:600;">Russian:</span> vsvyatski, KekcuHa, wkill95</li> + <li style="font-size:10pt"><span style="font-weight:600;">Spanish:</span> EdwardNavarro, antifaz, piegope, pquin, vsvyatski</li> + <li style="font-size:10pt"><span style="font-weight:600;">Swedish:</span> henziger</li> + </ul> + </body></html> + + + + <html><head/><body><p align="center"><a href="https://github.com/keepassxreboot/keepassxc/graphs/contributors"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">See Contributions on GitHub</span></a></p></body></html> + + + + Debug Info + + + + <html><head/><body><p>Include the following information whenever you report a bug:</p></body></html> + + + + Copy to clipboard + + + + Version %1 + + + + + Revision: %1 + + + + Libraries: + + + + Operating system: %1 +CPU architecture: %2 +Kernel: %3 %4 + + + + Enabled extensions: + + + + + AccessControlDialog + + Remember this decision + 記住此決定 + + + Allow + 允許 + + + Deny + 禁止 + + + %1 has requested access to passwords for the following item(s). +Please select whether you want to allow access. + %1 要求存取下列項目的密碼。 +請選擇是否允許存取。 + + + KeePassXC HTTP Confirm Access + KeePassXC HTTP 確認存取 AutoType - - Auto-Type - KeePassX - KeePassX - 自動輸入 - Couldn't find an entry that matches the window title: 無法找到符合視窗標題的項目 + + Auto-Type - KeePassXC + 自動輸入 - KeePassXC + AutoTypeAssociationsModel @@ -46,14 +157,14 @@ AutoTypeSelectDialog - - Auto-Type - KeePassX - KeePassX - 自動輸入 - Select entry to Auto-Type: 選擇自動輸入的項目 + + Auto-Type - KeePassXC + 自動輸入 - KeePassXC + ChangeMasterKeyWidget @@ -69,10 +180,6 @@ Repeat password: 再次輸入密碼 - - Key file - 金鑰檔案 - Browse 瀏覽 @@ -93,10 +200,6 @@ Create Key File... 建立一個金鑰檔案 - - Error - 錯誤 - Unable to create Key File : 無法建立金鑰檔案: @@ -105,10 +208,6 @@ Select a key file 選擇一個金鑰檔案 - - Question - 問題 - Do you really want to use an empty string as password? 你真的想使用空白密碼嗎? @@ -117,16 +216,173 @@ Different passwords supplied. 提供了不同的密碼 - - Failed to set key file - 無法設定金鑰檔案 - Failed to set %1 as the Key file: %2 無法設定 %1 成為金鑰檔案: %2 + + &Key file + 金鑰檔案 (&K) + + + Cha&llenge Response + + + + Refresh + + + + Empty password + + + + Changing master key failed: no YubiKey inserted. + + + + + CloneDialog + + Clone Options + + + + Append ' - Copy' to title + + + + Replace username and password with references + + + + Copy history + + + + + CsvImportWidget + + Import CSV fields + + + + filename + + + + size, rows, columns + + + + Encoding + + + + Codec + + + + Text is qualified by + + + + Fields are separated by + + + + Comments start with + + + + First record has field names + + + + Number of headers line to discard + + + + Consider '\' an escape character + + + + Preview + + + + Column layout + + + + Not present in CSV file + + + + Empty fieldname + + + + column + + + + Imported from CSV file + + + + Original data: + + + + Error(s) detected in CSV file ! + + + + more messages skipped] + + + + Error + 錯誤 + + + CSV import: writer has errors: + + + + + + CsvImportWizard + + Import CSV file + + + + Error + 錯誤 + + + Unable to calculate master key + 無法計算主金鑰 + + + + CsvParserModel + + byte, + + + + rows, + + + + columns + + DatabaseOpenWidget @@ -146,10 +402,6 @@ Browse 瀏覽 - - Error - 錯誤 - Unable to open the database. 無法打開這個資料庫 @@ -170,6 +422,14 @@ Select key file 選擇金鑰檔案 + + Refresh + + + + Challenge Response: + + DatabaseRepairWidget @@ -226,10 +486,6 @@ You can now save it. Default username: 預設的使用者名稱: - - Use recycle bin: - 使用垃圾桶: - MiB MiB @@ -246,6 +502,22 @@ You can now save it. Max. history size: 最大的歷史大小: + + Use recycle bin + 使用回收桶 + + + AES: 256 Bit (default) + + + + Twofish: 256 Bit + + + + Algorithm: + + DatabaseTabWidget @@ -265,10 +537,6 @@ You can now save it. Open database 打開資料庫 - - Warning - 警告 - File not found! 找不到檔案! @@ -298,10 +566,6 @@ You can now save it. Save changes? "%1" 已被修改。要儲存嗎? - - Error - 錯誤 - Writing the database failed. 寫入資料庫失敗 @@ -318,12 +582,6 @@ Save changes? locked 已鎖住 - - The database you are trying to open is locked by another instance of KeePassX. -Do you want to open it anyway? Alternatively the database is opened read-only. - 你嘗試要打開的資料庫已經被另一個正在執行的 KeePassX 鎖定 -你要打開它嗎?或者,打開唯讀的資料庫 - Lock database 鎖定資料庫 @@ -367,13 +625,45 @@ Discard changes and close anyway? 寫入 CSV 檔案失敗 - The database you are trying to save as is locked by another instance of KeePassX. -Do you want to save it anyway? - 你嘗試要打開的資料庫已經被另一個正在執行的 KeePassX 鎖定 -還要儲存嗎? + Unable to open the database. + 無法開啟這個資料庫 - Unable to open the database. + Merge database + 合併資料庫 + + + The database you are trying to save as is locked by another instance of KeePassXC. +Do you want to save it anyway? + 欲保存的資料庫已被其他 KeePassXC 程式鎖定。 +確定仍要繼續儲存? + + + Passwords + 密碼 + + + Database already opened + 資料庫已經開啟 + + + The database you are trying to open is locked by another instance of KeePassXC. + +Do you want to open it anyway? + 欲開啟的資料庫已被其他 KeePassXC 程式鎖定。 + +確定仍要繼續開啟? + + + Open read-only + 以唯讀模式開啟 + + + File opened in read only mode. + + + + Open CSV file @@ -415,24 +705,76 @@ Do you want to save it anyway? Do you really want to delete the group "%1" for good? 你真的想永遠刪除 "%1 " 群組嗎? - - Current group - 目前的群組 - - - Error - 錯誤 - Unable to calculate master key 無法計算主金鑰 Move entry to recycle bin? - + 移動項目到回收桶? Do you really want to move entry "%1" to the recycle bin? + 你真的想將 "%1" 移到回收桶? + + + Searching... + 搜尋中…… + + + No current database. + 無目前資料庫。 + + + No source database, nothing to do. + 無來源資料庫,無事可做。 + + + Search Results (%1) + 搜尋結果 (%1) + + + No Results + 無結果 + + + Execute command? + 執行命令? + + + Do you really want to execute the following command?<br><br>%1<br> + 你真的想執行下列命令嗎?<br><br>%1<br> + + + Remember my choice + 記住我的選擇 + + + Autoreload Request + 自動重新讀取請求 + + + The database file has changed. Do you want to load the changes? + 資料庫檔案已變更。要讀取變更嗎? + + + Merge Request + 合併請求 + + + The database file has changed and you have unsaved changes.Do you want to merge your changes? + 資料庫檔案已變更,且你有尚未儲存的變更。要合併你的變更嗎? + + + Could not open the new database file while attempting to autoreload this database. + 自動重新讀取此資料庫時無法開啟新資料褲檔案。 + + + Empty recycle bin? + + + + Are you sure you want to permanently delete everything from your recycle bin? @@ -474,10 +816,6 @@ Do you want to save it anyway? Edit entry 編輯項目 - - Error - 錯誤 - Different passwords supplied. 提供了不同的密碼 @@ -520,6 +858,22 @@ Do you want to save it anyway? 1 year 1 年 + + Confirm Remove + + + + Are you sure you want to remove this attribute? + + + + [PROTECTED] Press reveal to view or edit + + + + Are you sure you want to remove this attachment? + + EditEntryWidgetAdvanced @@ -531,10 +885,6 @@ Do you want to save it anyway? Add 加入 - - Edit - 編輯 - Remove 移除 @@ -551,6 +901,18 @@ Do you want to save it anyway? Open 打開 + + Edit Name + + + + Protect + + + + Reveal + + EditEntryWidgetAutoType @@ -558,14 +920,6 @@ Do you want to save it anyway? Enable Auto-Type for this entry 打開此項目的自動輸入 - - Inherit default Auto-Type sequence from the group - 從父群組繼承預設的自動輸入序列 - - - Use custom Auto-Type sequence: - 使用自訂的自動輸入序列 - + + @@ -579,12 +933,24 @@ Do you want to save it anyway? 視窗標題: - Use default sequence - 使用預設序列 + Inherit default Auto-Type sequence from the &group + 從群組中繼承預設的自動輸入序列 (&G) - Set custom sequence: - 設定自訂的序列 + &Use custom Auto-Type sequence: + 使用自訂的自動輸入序列:(&U) + + + Use default se&quence + 使用預設序列 (&Q) + + + Set custo&m sequence: + 設定預設的序列:(&M) + + + Window Associations + @@ -624,10 +990,6 @@ Do you want to save it anyway? Repeat: 重複: - - Gen. - 產生 - URL: 網址: @@ -699,28 +1061,20 @@ Do you want to save it anyway? 搜尋 - Auto-type + Auto-Type 自動輸入 - Use default auto-type sequence of parent group - 使用預設的父群組自動輸入序列 + &Use default Auto-Type sequence of parent group + - Set default auto-type sequence - 設定預設自動輸入序列 + Set default Auto-Type se&quence + EditWidgetIcons - - Use default icon - 使用預設的圖示 - - - Use custom icon - 使用自訂的圖示 - Add custom icon 加入自訂的圖示 @@ -742,19 +1096,35 @@ Do you want to save it anyway? 選擇圖片 - Can't delete icon! - 不能刪除圖示! - - - Can't delete icon. Still used by %n item(s). - 不能刪除圖示。仍在被 %n 個使用 + Error + 錯誤 - Error + Download favicon + 下載圖示 + + + Unable to fetch favicon. + 無法擷取圖示。 + + + Can't read icon + 無法讀取圖示 + + + &Use default icon + 使用預設圖示 (&U) + + + Use custo&m icon + 使用自訂圖示 (&M) + + + Confirm Delete - Can't read icon: + This icon is used by %1 entries, and will be replaced by the default icon. Are you sure you want to delete it? @@ -777,6 +1147,13 @@ Do you want to save it anyway? Uuid (通用唯一識別碼) + + Entry + + - Clone + - 複製 + + EntryAttributesModel @@ -821,6 +1198,11 @@ Do you want to save it anyway? URL 網址 + + Ref: + Reference abbreviation + + Group @@ -829,16 +1211,74 @@ Do you want to save it anyway? 回收桶 + + HttpPasswordGeneratorWidget + + Length: + 長度: + + + Character Types + 字元類型 + + + Upper Case Letters + 大寫英文字母 + + + A-Z + A-Z + + + Lower Case Letters + 小寫英文字母 + + + a-z + a-z + + + Numbers + 數字 + + + 0-9 + 0-9 + + + Special Characters + 特殊字元 + + + /*_& ... + /*_& ... + + + Exclude look-alike characters + 去除相似的字元 + + + Ensure that the password contains characters from every group + 確定密碼包含每一組的字元 + + + + KMessageWidget + + &Close + + + + Close message + + + KeePass1OpenWidget Import KeePass1 database 匯入 KeePass 1 資料庫 - - Error - 錯誤 - Unable to open the database. 無法開啟這個資料庫 @@ -872,7 +1312,7 @@ Do you want to save it anyway? Wrong key or database file is corrupt. - + 無法的金鑰或資料庫損壞 @@ -904,6 +1344,10 @@ This is a one-way migration. You won't be able to open the imported databas 你可以點選 資料庫 > 「匯入 KeePass 1 資料庫」。 這是單向遷移。你無法用舊的 KeePassX 0.4 的版本打開被匯入的資料庫。 + + Unable to issue challenge-response. + + Main @@ -912,112 +1356,28 @@ This is a one-way migration. You won't be able to open the imported databas 重大錯誤,在測試加密函數時 - KeePassX - Error - KeePassX - 錯誤 + KeePassXC - Error + KeePassXC - 錯誤 + + + The lock file could not be created. Single-instance mode disabled. + + + + Another instance of KeePassXC is already running. + MainWindow - - Database - 資料庫 - - - Recent databases - 近期的資料庫 - - - Help - 幫助 - - - Entries - 項目 - - - Copy attribute to clipboard - 將屬性複製到剪貼簿 - - - Groups - 群組 - - - View - 顯示 - - - Quit - 關閉 - - - About - 關於 - Open database 打開資料庫 - - Save database - 儲存資料庫 - - - Close database - 關閉資料庫 - - - New database - 新增資料庫 - - - Add new entry - 加入項目 - - - View/Edit entry - 瀏覽/編輯項目 - - - Delete entry - 刪除項目 - - - Add new group - 增加新的群組 - - - Edit group - 編輯群組 - - - Delete group - 刪除群組 - - - Save database as - 儲存資料庫為 - - - Change master key - 變更主金鑰 - Database settings 資料庫設定 - - Import KeePass 1 database - 匯入 KeePass 1 資料庫 - - - Clone entry - 拷貝項目 - - - Find - 尋找 - Copy username to clipboard 將使用者名稱複製到剪貼簿 @@ -1030,30 +1390,6 @@ This is a one-way migration. You won't be able to open the imported databas Settings 設定 - - Perform Auto-Type - 執行自動輸入 - - - Open URL - 打開網址 - - - Lock databases - 鎖住資料庫 - - - Title - 標題 - - - URL - 網址 - - - Notes - 附註 - Show toolbar 顯示工具列 @@ -1066,26 +1402,6 @@ This is a one-way migration. You won't be able to open the imported databas Toggle window 切換視窗 - - Tools - 工具 - - - Copy username - 複製使用者名稱 - - - Copy password - 複製密碼 - - - Export to CSV file - 輸出成 CSV 檔案 - - - Repair database - 修復資料庫 - KeePass 2 Database KeePass 2 資料庫 @@ -1098,14 +1414,328 @@ This is a one-way migration. You won't be able to open the imported databas Save repaired database 儲存已修復的資料庫 - - Error - 錯誤 - Writing the database failed. 寫入資料庫失敗 + + &Recent databases + 最近的資料庫 (&R) + + + He&lp + 幫助 (&L) + + + E&ntries + 項目 (&N) + + + Copy att&ribute to clipboard + 複製屬性到剪貼簿 (&R) + + + &Groups + 群組 (&G) + + + &View + 檢視 (&V) + + + &Quit + 離開 (&Q) + + + &About + 關於 (&A) + + + &Open database + 開啟資料庫 (&O) + + + &Save database + 儲存資料庫 (&S) + + + &Close database + 關閉資料庫 (&C) + + + &New database + 新的資料庫 (&N) + + + Merge from KeePassX database + 從 KeePassX 資料庫合併 + + + &Add new entry + 新增項目 (&A) + + + &View/Edit entry + 檢視/編輯項目 (&V) + + + &Delete entry + 刪除項目 (&D) + + + &Add new group + 新增群組 (&A) + + + &Edit group + 編輯群組 (&E) + + + &Delete group + 刪除群組 (&D) + + + Sa&ve database as + 將資料庫儲存為 (&V) + + + Change &master key + 變更主金鑰 (&M) + + + &Database settings + 資料庫設定 (&D) + + + &Clone entry + 複製項目 (&C) + + + Timed one-time password + + + + Setup TOTP + + + + Copy &TOTP + + + + Show TOTP + + + + &Find + 尋找 (&F) + + + Copy &username + 複製使用者名稱 (&U) + + + Cop&y password + 複製密碼 (&Y) + + + &Settings + 設定 (&S) + + + &Perform Auto-Type + 執行自動輸入 (&P) + + + &Open URL + 開啟網址 (&O) + + + &Lock databases + 鎖定資料庫 (&L) + + + &Title + 標題 (&T) + + + &URL + 網址 (&U) + + + &Notes + 附註 (&N) + + + &Export to CSV file + 匯出到 CSV 檔案 (&E) + + + Re&pair database + 修復資料庫 (&P) + + + Password Generator + 密碼產生器 + + + Clear history + + + + &Database + + + + Import + + + + &Tools + + + + Import KeePass 1 database + 匯入 KeePass 1 資料庫 + + + Import CSV file + + + + Empty recycle bin + + + + Access error for config file %1 + + + + Quit KeePassXC + + + + Please touch the button on your YubiKey! + + + + + OptionDialog + + Dialog + 對話方塊 + + + General + 一般 + + + Sh&ow a notification when credentials are requested + 要求憑證時顯示通知 (&O) + + + Sort matching entries by &username + 依使用者名稱排序符合項目 (&U) + + + Re&move all stored permissions from entries in active database + 從目前的資料庫項目中移除所有權限 (&M) + + + Advanced + 進階的 + + + Always allow &access to entries + 總是允許存取項目 (&A) + + + Always allow &updating entries + 總是允許更新項目 (&U) + + + Searc&h in all opened databases for matching entries + 在所有開啟的資料庫內搜尋相符的項目 (&H) + + + HTTP Port: + HTTP Port: + + + Default port: 19455 + 預設 port: 19455 + + + Re&quest to unlock the database if it is locked + 若資料庫已鎖定,則請求解鎖 (&Q) + + + Sort &matching entries by title + 依名稱排序符合項目 (&M) + + + KeePassXC will listen to this port on 127.0.0.1 + KeePassXC 會在 127.0.0.1 上監聽此 port + + + Cannot bind to privileged ports + 無法綁定到特殊權限 port + + + Cannot bind to privileged ports below 1024! +Using default port 19455. + 無法綁定到 1024 以下的特殊權限 port! +使用預設 port 19455。 + + + R&emove all shared encryption keys from active database + 從目前的資料庫移除所有共用的加密金鑰 (&E) + + + &Return advanced string fields which start with "KPH: " + 回傳 "KPH: " 起首的進階文字欄位 (&R) + + + Automatically creating or updating string fields is not supported. + 不支援自動建立或更新文字欄位 + + + This is required for accessing your databases from ChromeIPass or PassIFox + + + + Enable KeePassHTTP server + + + + Only returns the best matches for a specific URL instead of all entries for the whole domain. + + + + &Return only best matching entries + + + + Only entries with the same scheme (http://, https://, ftp://, ...) are returned. + + + + &Match URL schemes + + + + Password Generator + 密碼產生器 + + + Only the selected database has to be connected with a client. + + + + The following options can be dangerous! +Change them only if you know what you are doing. + + PasswordGeneratorWidget @@ -1113,10 +1743,6 @@ This is a one-way migration. You won't be able to open the imported databas Password: 密碼: - - Length: - 長度: - Character Types 字元類型 @@ -1141,71 +1767,161 @@ This is a one-way migration. You won't be able to open the imported databas Exclude look-alike characters 去除相似的字元 - - Ensure that the password contains characters from every group - 確定密碼包含每一組的字元 - Accept 接受 - - - QCommandLineParser - Displays version information. - 顯示版本資訊 + %p% + %p% - Displays this help. - 顯示這個幫助訊息 + strength + 強度 - Unknown option '%1'. - 不知的選項 '%1' + entropy + - Unknown options: %1. - 不知的選項 '%1' + &Length: + 長度 (&L): - Missing value after '%1'. - 在 "%1" 後缺少值 + Pick characters from every group + 從每一組中選擇字元 - Unexpected value after '%1'. - "%1" 後有不預期的值 + Generate + 產生 - [options] - [選項] + Close + 關閉 - Usage: %1 - 使用方式:%1 + Apply + 套用 - Options: - 選項: + Entropy: %1 bit + 熵:%1 bit - Arguments: - 參數 + Password Quality: %1 + 密碼素質:%1 + + + Poor + 極弱 + + + Weak + 較弱 + + + Good + 較好 + + + Excellent + 極好 + + + Password + 密碼 + + + Extended ASCII + + + + Passphrase + + + + Wordlist: + + + + Word Count: + + + + Word Separator: + + + + Copy + - QSaveFile + QObject - Existing file %1 is not writable - 現有的檔案 %1 不可寫入 + NULL device + - Writing canceled by application - 應用程式取消寫入 + error reading from device + - Partial write. Partition full? - 部分寫入。分區滿了嗎? + file empty ! + + + + + malformed string + + + + missing closing quote + + + + INTERNAL - unget lower bound exceeded + + + + Group + 群組 + + + Title + 標題 + + + Username + 使用者名稱 + + + Password + 密碼 + + + URL + 網址 + + + Notes + 附註 + + + Browser Integration + + + + YubiKey[%1] Challenge Response - Slot %2 - %3 + + + + Press + + + + Passive + @@ -1245,20 +1961,115 @@ This is a one-way migration. You won't be able to open the imported databas SearchWidget - Find: - 尋找: - - - Case sensitive + Case Sensitive 區分大小寫 - Current group - 目前的群組 + Search + 搜尋 - Root group - 根群組 + Clear + 清除 + + + Search... + + + + Limit search to selected group + + + + + Service + + A shared encryption-key with the name "%1" already exists. +Do you want to overwrite it? + 已存在名為 "%1" 的共用加密金鑰。 +你想覆蓋嗎? + + + Do you want to update the information in %1 - %2? + 你想更新 %1 到 %2 的資訊嗎? + + + The active database is locked! +Please unlock the selected database or choose another one which is unlocked. + 目前的資料庫已鎖定! +請解鎖所選的資料庫或選擇其他已解鎖的資料庫。 + + + Successfully removed %1 encryption-%2 from KeePassX/Http Settings. + 成功從 KeePassX/Http Settings 移除 %1 encryption-%2。 + + + No shared encryption-keys found in KeePassHttp Settings. + KeePassHttp Settings 中找不到共享加密金鑰。 + + + The active database does not contain an entry of KeePassHttp Settings. + 目前的資料庫沒有 KeePassHttp Settings 項目。 + + + Removing stored permissions... + 正在移除所有已儲存的權限…… + + + Abort + 中止 + + + Successfully removed permissions from %1 %2. + 成功從 %1 %2 移除權限。 + + + The active database does not contain an entry with permissions. + 目前的資料庫中無包含權限的項目。 + + + KeePassXC: New key association request + KeePassXC:新的金鑰關聯請求 + + + You have received an association request for the above key. +If you would like to allow it access to your KeePassXC database +give it a unique name to identify and accept it. + 你已接收到上述金鑰的關聯請求。 +如果你允許透過此金鑰存取你的 KeePassXC 資料庫 +請命名專屬的名稱並按下接受。 + + + KeePassXC: Overwrite existing key? + KeePassXC:覆蓋現有的金鑰嗎? + + + KeePassXC: Update Entry + KeePassXC:更新項目 + + + KeePassXC: Database locked! + KeePassXC:資料庫已鎖定! + + + KeePassXC: Removed keys from database + KeePassXC:從資料庫中刪除金鑰 + + + KeePassXC: No keys found + KeePassXC:沒有找到金鑰 + + + KeePassXC: Settings not available! + KeePassXC:設定不可用! + + + KeePassXC: Removed permissions + KeePassXC:已移除權限 + + + KeePassXC: No entry with permissions found! + KeePassXC:無含有權限的項目! @@ -1275,6 +2086,10 @@ This is a one-way migration. You won't be able to open the imported databas Security 安全性 + + Access error for config file %1 + + SettingsWidgetGeneral @@ -1282,10 +2097,6 @@ This is a one-way migration. You won't be able to open the imported databas Remember last databases 記住最近的資料庫 - - Open previous databases on startup - 在啟動時開啟最近的資料庫 - Automatically save on exit 離開時,自動儲存 @@ -1306,10 +2117,6 @@ This is a one-way migration. You won't be able to open the imported databas Global Auto-Type shortcut 全域自動輸入快捷鍵 - - Use entry title to match windows for global auto-type - 使用項目標題來找尋自動輸入的目標視窗 - Language 語言 @@ -1323,15 +2130,43 @@ This is a one-way migration. You won't be able to open the imported databas 將視窗最小化至工作列 - Remember last key files - 記住最近的金鑰檔案 + Load previous databases on startup + 啟動時載入之前的資料庫 - Hide window to system tray instead of App Exit + Automatically reload the database when modified externally + 當有外部修改時自動重新載入資料庫 + + + Hide window to system tray instead of app exit + 將視窗最小化至工作列而非關閉程式 + + + Minimize window at application startup + 程式啟動時視窗最小化 + + + Basic Settings - Hide window to system tray on App start + Remember last key files and security dongles + + + + Don't mark database as modified for non-data changes (e.g., expanding groups) + + + + Auto-Type + 自動輸入 + + + Use entry title and URL to match windows for global Auto-Type + + + + Always ask before performing Auto-Type @@ -1354,8 +2189,86 @@ This is a one-way migration. You won't be able to open the imported databas 預設以明碼顯示密碼 - Always ask before performing auto-type - 在執行自動輸入前通常要詢問 + Lock databases after minimizing the window + 最小化視窗後鎖定資料庫 + + + Don't require password repeat when it is visible + 顯示密碼時不需要重複輸入密碼 + + + Timeouts + + + + Convenience + + + + Lock databases when session is locked or lid is closed + + + + + SetupTotpDialog + + Setup TOTP + + + + Key: + + + + Use custom settings + + + + Note: Change these settings only if you know what you are doing. + + + + Time step: + + + + 8 digits + + + + 6 digits + + + + Code size: + + + + sec + + + + + TotpDialog + + Timed Password + + + + 000000 + + + + Copy + + + + Expires in + + + + seconds + @@ -1368,20 +2281,36 @@ This is a one-way migration. You won't be able to open the imported databas WelcomeWidget - Welcome! - 歡迎! + Welcome to KeePassXC + + + + Start storing your passwords securely in a KeePassXC database + + + + Create new database + + + + Open existing database + + + + Import from KeePass 1 + + + + Import from CSV + + + + Recent databases + 近期的資料庫 main - - KeePassX - cross-platform password manager - KeePassX - 跨平台密碼管理軟體 - - - filename of the password database to open (*.kdbx) - 要開啟的密碼資料庫檔案名稱 (*.kdbx) - path to a custom config file 自定設定檔的路徑 @@ -1390,5 +2319,81 @@ This is a one-way migration. You won't be able to open the imported databas key file of the database 資料庫的金鑰 + + KeePassXC - cross-platform password manager + KeePassXC - 跨平台的密碼管理工具 + + + read password of the database from stdin + 從 stdin 讀取資料庫密碼 + + + filenames of the password databases to open (*.kdbx) + 欲開啟的密碼資料庫檔名 (*.kdbx) + + + Copy a password to the clipboard + + + + Path of the database. + + + + Use a GUI prompt unlocking the database. + + + + Name of the entry to clip. + + + + Extract and print the content of a database. + + + + Path of the database to extract. + + + + Name of the command to execute. + + + + List database entries. + + + + Path of the group to list. Default is / + + + + Print the UUIDs of the entries and groups. + + + + Merge two databases. + + + + Path of the database to merge into. + + + + Path of the database to merge from. + + + + Use the same password for both database files. + + + + Show a password. + + + + Name of the entry to show. + + \ No newline at end of file