mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-04-28 11:26:11 -04:00
Merge branch 'release/2.4.1' into develop
This commit is contained in:
commit
1f28dc6013
@ -49,6 +49,7 @@ option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
|
|||||||
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
||||||
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF)
|
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF)
|
||||||
option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" OFF)
|
option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" OFF)
|
||||||
|
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
|
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
|
||||||
endif()
|
endif()
|
||||||
@ -76,10 +77,15 @@ else()
|
|||||||
set(WITH_XC_CRYPTO_SSH OFF)
|
set(WITH_XC_CRYPTO_SSH OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(WITH_XC_UPDATECHECK)
|
||||||
|
set(WITH_XC_NETWORKING ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(KEEPASSXC_VERSION_MAJOR "2")
|
set(KEEPASSXC_VERSION_MAJOR "2")
|
||||||
set(KEEPASSXC_VERSION_MINOR "4")
|
set(KEEPASSXC_VERSION_MINOR "4")
|
||||||
set(KEEPASSXC_VERSION_PATCH "0")
|
set(KEEPASSXC_VERSION_PATCH "0")
|
||||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
|
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
|
||||||
|
set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds")
|
||||||
|
|
||||||
set(KEEPASSXC_BUILD_TYPE "Snapshot" CACHE STRING "Set KeePassXC build type to distinguish between stable releases and snapshots")
|
set(KEEPASSXC_BUILD_TYPE "Snapshot" CACHE STRING "Set KeePassXC build type to distinguish between stable releases and snapshots")
|
||||||
set_property(CACHE KEEPASSXC_BUILD_TYPE PROPERTY STRINGS Snapshot Release PreRelease)
|
set_property(CACHE KEEPASSXC_BUILD_TYPE PROPERTY STRINGS Snapshot Release PreRelease)
|
||||||
@ -91,8 +97,10 @@ execute_process(COMMAND git rev-parse --short=7 HEAD
|
|||||||
OUTPUT_VARIABLE GIT_HEAD
|
OUTPUT_VARIABLE GIT_HEAD
|
||||||
ERROR_QUIET)
|
ERROR_QUIET)
|
||||||
string(STRIP "${GIT_HEAD}" GIT_HEAD)
|
string(STRIP "${GIT_HEAD}" GIT_HEAD)
|
||||||
if(GIT_HEAD STREQUAL "")
|
if(GIT_HEAD STREQUAL "" AND NOT GIT_HEAD_OVERRIDE STREQUAL "")
|
||||||
string(SUBSTRING "${GIT_HEAD_OVERRIDE}" 0 7 GIT_HEAD)
|
string(SUBSTRING "${GIT_HEAD_OVERRIDE}" 0 7 GIT_HEAD)
|
||||||
|
elseif(EXISTS ${CMAKE_SOURCE_DIR}/.gitrev)
|
||||||
|
file(READ ${CMAKE_SOURCE_DIR}/.gitrev GIT_HEAD)
|
||||||
endif()
|
endif()
|
||||||
message(STATUS "Found Git HEAD Revision: ${GIT_HEAD}\n")
|
message(STATUS "Found Git HEAD Revision: ${GIT_HEAD}\n")
|
||||||
|
|
||||||
@ -116,14 +124,17 @@ if(OVERRIDE_VERSION)
|
|||||||
elseif(OVERRIDE_VERSION MATCHES "^[\\.0-9]+$")
|
elseif(OVERRIDE_VERSION MATCHES "^[\\.0-9]+$")
|
||||||
set(KEEPASSXC_BUILD_TYPE Release)
|
set(KEEPASSXC_BUILD_TYPE Release)
|
||||||
set(KEEPASSXC_VERSION ${OVERRIDE_VERSION})
|
set(KEEPASSXC_VERSION ${OVERRIDE_VERSION})
|
||||||
|
else()
|
||||||
|
set(KEEPASSXC_BUILD_TYPE Snapshot)
|
||||||
|
set(KEEPASSXC_VERSION ${OVERRIDE_VERSION})
|
||||||
endif()
|
endif()
|
||||||
endif()
|
else()
|
||||||
|
if(KEEPASSXC_BUILD_TYPE STREQUAL "PreRelease")
|
||||||
if(KEEPASSXC_BUILD_TYPE STREQUAL "PreRelease" AND NOT OVERRIDE_VERSION)
|
|
||||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-preview")
|
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-preview")
|
||||||
elseif(KEEPASSXC_BUILD_TYPE STREQUAL "Snapshot")
|
elseif(KEEPASSXC_BUILD_TYPE STREQUAL "Snapshot")
|
||||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-snapshot")
|
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-snapshot")
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(KEEPASSXC_BUILD_TYPE STREQUAL "Release")
|
if(KEEPASSXC_BUILD_TYPE STREQUAL "Release")
|
||||||
set(KEEPASSXC_BUILD_TYPE_RELEASE ON)
|
set(KEEPASSXC_BUILD_TYPE_RELEASE ON)
|
||||||
|
@ -29,7 +29,7 @@ so please check out your distribution's package list to see if KeePassXC is avai
|
|||||||
- Using website favicons as entry icons
|
- Using website favicons as entry icons
|
||||||
- Merging of databases
|
- Merging of databases
|
||||||
- Automatic reload when the database changed on disk
|
- Automatic reload when the database changed on disk
|
||||||
- Browser integration with KeePassXC-Browser using [native messaging](https://developer.chrome.com/extensions/nativeMessaging) for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk)
|
- Browser integration with KeePassXC-Browser using [native messaging](https://developer.chrome.com/extensions/nativeMessaging) for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) and [Google Chrome, Chromium, Vivaldi, or Brave](https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk)
|
||||||
- Synchronize passwords using KeeShare. See [Using Sharing](./docs/QUICKSTART.md#using-sharing) for more details.
|
- Synchronize passwords using KeeShare. See [Using Sharing](./docs/QUICKSTART.md#using-sharing) for more details.
|
||||||
- Many bug fixes
|
- Many bug fixes
|
||||||
|
|
||||||
|
27
release-tool
27
release-tool
@ -813,13 +813,17 @@ build() {
|
|||||||
init
|
init
|
||||||
|
|
||||||
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
|
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
|
||||||
|
# Resolve appsign key to absolute path if under Windows
|
||||||
|
if [[ "${build_key}" && "$(uname -o)" == "Msys" ]]; then
|
||||||
|
build_key="$(realpath "${build_key}")"
|
||||||
|
fi
|
||||||
|
|
||||||
if ${build_snapshot}; then
|
if ${build_snapshot}; then
|
||||||
TAG_NAME="HEAD"
|
TAG_NAME="HEAD"
|
||||||
local branch=`git rev-parse --abbrev-ref HEAD`
|
local branch=`git rev-parse --abbrev-ref HEAD`
|
||||||
logInfo "Using current branch ${branch} to build..."
|
logInfo "Using current branch ${branch} to build..."
|
||||||
RELEASE_NAME="${RELEASE_NAME}-snapshot"
|
RELEASE_NAME="${RELEASE_NAME}-snapshot"
|
||||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot"
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot -DOVERRIDE_VERSION=${RELEASE_NAME}"
|
||||||
else
|
else
|
||||||
checkWorkingTreeClean
|
checkWorkingTreeClean
|
||||||
|
|
||||||
@ -848,14 +852,13 @@ build() {
|
|||||||
|
|
||||||
git archive --format=tar "$TAG_NAME" --prefix="${prefix}/" --output="${OUTPUT_DIR}/${tarball_name}"
|
git archive --format=tar "$TAG_NAME" --prefix="${prefix}/" --output="${OUTPUT_DIR}/${tarball_name}"
|
||||||
|
|
||||||
if ! ${build_snapshot}; then
|
# add .version and .gitrev files to tarball
|
||||||
# add .version file to tar
|
|
||||||
mkdir "${prefix}"
|
mkdir "${prefix}"
|
||||||
echo -n ${RELEASE_NAME} > "${prefix}/.version"
|
echo -n ${RELEASE_NAME} > "${prefix}/.version"
|
||||||
tar --append --file="${OUTPUT_DIR}/${tarball_name}" "${prefix}/.version"
|
echo -n `git rev-parse --short=7 HEAD` > "${prefix}/.gitrev"
|
||||||
rm "${prefix}/.version"
|
tar --append --file="${OUTPUT_DIR}/${tarball_name}" "${prefix}/.version" "${prefix}/.gitrev"
|
||||||
|
rm "${prefix}/.version" "${prefix}/.gitrev"
|
||||||
rmdir "${prefix}" 2> /dev/null
|
rmdir "${prefix}" 2> /dev/null
|
||||||
fi
|
|
||||||
|
|
||||||
xz -6 "${OUTPUT_DIR}/${tarball_name}"
|
xz -6 "${OUTPUT_DIR}/${tarball_name}"
|
||||||
fi
|
fi
|
||||||
@ -881,6 +884,8 @@ build() {
|
|||||||
# linuxdeploy requires /usr as install prefix
|
# linuxdeploy requires /usr as install prefix
|
||||||
INSTALL_PREFIX="/usr"
|
INSTALL_PREFIX="/usr"
|
||||||
fi
|
fi
|
||||||
|
# Do not build tests cases
|
||||||
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_TESTS=OFF"
|
||||||
|
|
||||||
if [ "$COMPILER" == "g++" ]; then
|
if [ "$COMPILER" == "g++" ]; then
|
||||||
export CC=gcc
|
export CC=gcc
|
||||||
@ -913,14 +918,14 @@ build() {
|
|||||||
elif [ "$(uname -o)" == "Msys" ]; then
|
elif [ "$(uname -o)" == "Msys" ]; then
|
||||||
# Building on Windows with Msys2
|
# Building on Windows with Msys2
|
||||||
logInfo "Configuring build..."
|
logInfo "Configuring build..."
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off -G"MSYS Makefiles" \
|
cmake -DCMAKE_BUILD_TYPE=Release -G"MSYS Makefiles" \
|
||||||
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" ${CMAKE_OPTIONS} "$SRC_DIR"
|
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" ${CMAKE_OPTIONS} "$SRC_DIR"
|
||||||
|
|
||||||
logInfo "Compiling and packaging sources..."
|
logInfo "Compiling and packaging sources..."
|
||||||
mingw32-make ${MAKE_OPTIONS} preinstall
|
mingw32-make ${MAKE_OPTIONS} preinstall
|
||||||
|
|
||||||
# Appsign the executables if desired
|
# Appsign the executables if desired
|
||||||
if ${build_appsign} && [ -f ${build_key} ]; then
|
if ${build_appsign} && [ -f "${build_key}" ]; then
|
||||||
logInfo "Signing executable files"
|
logInfo "Signing executable files"
|
||||||
appsign "-f" $(find src | grep -P '\.exe$|\.dll$') "-k" "${build_key}"
|
appsign "-f" $(find src | grep -P '\.exe$|\.dll$') "-k" "${build_key}"
|
||||||
fi
|
fi
|
||||||
@ -945,7 +950,7 @@ build() {
|
|||||||
|
|
||||||
# Building on Linux without Docker container
|
# Building on Linux without Docker container
|
||||||
logInfo "Configuring build..."
|
logInfo "Configuring build..."
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off ${CMAKE_OPTIONS} \
|
cmake -DCMAKE_BUILD_TYPE=Release ${CMAKE_OPTIONS} \
|
||||||
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" "$SRC_DIR"
|
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" "$SRC_DIR"
|
||||||
|
|
||||||
logInfo "Compiling sources..."
|
logInfo "Compiling sources..."
|
||||||
@ -977,7 +982,7 @@ build() {
|
|||||||
-v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \
|
-v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \
|
||||||
"$DOCKER_IMAGE" \
|
"$DOCKER_IMAGE" \
|
||||||
bash -c "cd /keepassxc/out/build-release && \
|
bash -c "cd /keepassxc/out/build-release && \
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off ${CMAKE_OPTIONS} \
|
cmake -DCMAKE_BUILD_TYPE=Release ${CMAKE_OPTIONS} \
|
||||||
-DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} /keepassxc/src && \
|
-DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} /keepassxc/src && \
|
||||||
make ${MAKE_OPTIONS} && make DESTDIR=/keepassxc/out/KeePassXC.AppDir install/strip"
|
make ${MAKE_OPTIONS} && make DESTDIR=/keepassxc/out/KeePassXC.AppDir install/strip"
|
||||||
fi
|
fi
|
||||||
@ -1139,7 +1144,7 @@ appsign() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
logInfo "Signing app using codesign..."
|
logInfo "Signing app using codesign..."
|
||||||
codesign --sign "${key}" --verbose --deep --entitlements ${orig_dir}/share/macosx/keepassxc.entitlements ./app/KeePassXC.app
|
codesign --sign "${key}" --verbose --deep --entitlements "${SRC_DIR}/share/macosx/keepassxc.entitlements" ./app/KeePassXC.app
|
||||||
|
|
||||||
if [ 0 -ne $? ]; then
|
if [ 0 -ne $? ]; then
|
||||||
cd "${orig_dir}"
|
cd "${orig_dir}"
|
||||||
|
@ -195,6 +195,7 @@ add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible wit
|
|||||||
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare")
|
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare")
|
||||||
add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources")
|
add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources")
|
||||||
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
||||||
|
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")
|
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")
|
||||||
endif()
|
endif()
|
||||||
|
@ -214,7 +214,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
|
|||||||
|
|
||||||
if (hideWindow) {
|
if (hideWindow) {
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
m_plugin->raiseLastActiveWindow();
|
m_plugin->hideOwnWindow();
|
||||||
#else
|
#else
|
||||||
hideWindow->showMinimized();
|
hideWindow->showMinimized();
|
||||||
#endif
|
#endif
|
||||||
|
@ -43,7 +43,7 @@ public:
|
|||||||
virtual AutoTypeExecutor* createExecutor() = 0;
|
virtual AutoTypeExecutor* createExecutor() = 0;
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
virtual bool raiseLastActiveWindow() = 0;
|
virtual bool hideOwnWindow() = 0;
|
||||||
virtual bool raiseOwnWindow() = 0;
|
virtual bool raiseOwnWindow() = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -165,9 +165,9 @@ bool AutoTypePlatformMac::raiseWindow(WId pid)
|
|||||||
//
|
//
|
||||||
// Activate last active window
|
// Activate last active window
|
||||||
//
|
//
|
||||||
bool AutoTypePlatformMac::raiseLastActiveWindow()
|
bool AutoTypePlatformMac::hideOwnWindow()
|
||||||
{
|
{
|
||||||
return macUtils()->raiseLastActiveWindow();
|
return macUtils()->hideOwnWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -44,7 +44,7 @@ public:
|
|||||||
bool raiseWindow(WId pid) override;
|
bool raiseWindow(WId pid) override;
|
||||||
AutoTypeExecutor* createExecutor() override;
|
AutoTypeExecutor* createExecutor() override;
|
||||||
|
|
||||||
bool raiseLastActiveWindow() override;
|
bool hideOwnWindow() override;
|
||||||
bool raiseOwnWindow() override;
|
bool raiseOwnWindow() override;
|
||||||
|
|
||||||
void sendChar(const QChar& ch, bool isKeyDown);
|
void sendChar(const QChar& ch, bool isKeyDown);
|
||||||
|
@ -111,7 +111,7 @@ bool AutoTypePlatformTest::raiseWindow(WId window)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
bool AutoTypePlatformTest::raiseLastActiveWindow()
|
bool AutoTypePlatformTest::hideOwnWindow()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ public:
|
|||||||
AutoTypeExecutor* createExecutor() override;
|
AutoTypeExecutor* createExecutor() override;
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
bool raiseLastActiveWindow() override;
|
bool hideOwnWindow() override;
|
||||||
bool raiseOwnWindow() override;
|
bool raiseOwnWindow() override;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
|
|||||||
tr("KeePassXC-Browser is needed for the browser integration to work. <br />Download it for %1 and %2. %3")
|
tr("KeePassXC-Browser is needed for the browser integration to work. <br />Download it for %1 and %2. %3")
|
||||||
.arg("<a href=\"https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/\">Firefox</a>",
|
.arg("<a href=\"https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/\">Firefox</a>",
|
||||||
"<a href=\"https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk\">"
|
"<a href=\"https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk\">"
|
||||||
"Google Chrome / Chromium / Vivaldi</a>",
|
"Google Chrome / Chromium / Vivaldi / Brave</a>",
|
||||||
snapInstructions));
|
snapInstructions));
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
@ -75,9 +75,11 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
|
|||||||
connect(m_ui->customProxyLocationBrowseButton, SIGNAL(clicked()), this, SLOT(showProxyLocationFileDialog()));
|
connect(m_ui->customProxyLocationBrowseButton, SIGNAL(clicked()), this, SLOT(showProxyLocationFileDialog()));
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
// Brave uses Chrome's registry settings
|
||||||
|
m_ui->braveSupport->setHidden(true);
|
||||||
// Vivaldi uses Chrome's registry settings
|
// Vivaldi uses Chrome's registry settings
|
||||||
m_ui->vivaldiSupport->setHidden(true);
|
m_ui->vivaldiSupport->setHidden(true);
|
||||||
m_ui->chromeSupport->setText("Chrome and Vivaldi");
|
m_ui->chromeSupport->setText("Chrome, Vivaldi, and Brave");
|
||||||
// Tor Browser uses Firefox's registry settings
|
// Tor Browser uses Firefox's registry settings
|
||||||
m_ui->torBrowserSupport->setHidden(true);
|
m_ui->torBrowserSupport->setHidden(true);
|
||||||
m_ui->firefoxSupport->setText("Firefox and Tor Browser");
|
m_ui->firefoxSupport->setText("Firefox and Tor Browser");
|
||||||
@ -122,6 +124,7 @@ void BrowserOptionDialog::loadSettings()
|
|||||||
m_ui->chromiumSupport->setChecked(settings->chromiumSupport());
|
m_ui->chromiumSupport->setChecked(settings->chromiumSupport());
|
||||||
m_ui->firefoxSupport->setChecked(settings->firefoxSupport());
|
m_ui->firefoxSupport->setChecked(settings->firefoxSupport());
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
|
m_ui->braveSupport->setChecked(settings->braveSupport());
|
||||||
m_ui->vivaldiSupport->setChecked(settings->vivaldiSupport());
|
m_ui->vivaldiSupport->setChecked(settings->vivaldiSupport());
|
||||||
m_ui->torBrowserSupport->setChecked(settings->torBrowserSupport());
|
m_ui->torBrowserSupport->setChecked(settings->torBrowserSupport());
|
||||||
#endif
|
#endif
|
||||||
@ -183,6 +186,7 @@ void BrowserOptionDialog::saveSettings()
|
|||||||
settings->setChromiumSupport(m_ui->chromiumSupport->isChecked());
|
settings->setChromiumSupport(m_ui->chromiumSupport->isChecked());
|
||||||
settings->setFirefoxSupport(m_ui->firefoxSupport->isChecked());
|
settings->setFirefoxSupport(m_ui->firefoxSupport->isChecked());
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
|
settings->setBraveSupport(m_ui->braveSupport->isChecked());
|
||||||
settings->setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
|
settings->setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
|
||||||
settings->setTorBrowserSupport(m_ui->torBrowserSupport->isChecked());
|
settings->setTorBrowserSupport(m_ui->torBrowserSupport->isChecked());
|
||||||
#endif
|
#endif
|
||||||
|
@ -150,6 +150,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QCheckBox" name="braveSupport">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Brave</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -238,6 +238,17 @@ void BrowserSettings::setVivaldiSupport(bool enabled)
|
|||||||
HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation());
|
HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BrowserSettings::braveSupport()
|
||||||
|
{
|
||||||
|
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::BRAVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BrowserSettings::setBraveSupport(bool enabled)
|
||||||
|
{
|
||||||
|
m_hostInstaller.installBrowser(
|
||||||
|
HostInstaller::SupportedBrowsers::BRAVE, enabled, supportBrowserProxy(), customProxyLocation());
|
||||||
|
}
|
||||||
|
|
||||||
bool BrowserSettings::torBrowserSupport()
|
bool BrowserSettings::torBrowserSupport()
|
||||||
{
|
{
|
||||||
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::TOR_BROWSER);
|
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::TOR_BROWSER);
|
||||||
|
@ -72,6 +72,8 @@ public:
|
|||||||
void setFirefoxSupport(bool enabled);
|
void setFirefoxSupport(bool enabled);
|
||||||
bool vivaldiSupport();
|
bool vivaldiSupport();
|
||||||
void setVivaldiSupport(bool enabled);
|
void setVivaldiSupport(bool enabled);
|
||||||
|
bool braveSupport();
|
||||||
|
void setBraveSupport(bool enabled);
|
||||||
bool torBrowserSupport();
|
bool torBrowserSupport();
|
||||||
void setTorBrowserSupport(bool enabled);
|
void setTorBrowserSupport(bool enabled);
|
||||||
|
|
||||||
|
@ -39,12 +39,14 @@ HostInstaller::HostInstaller()
|
|||||||
, TARGET_DIR_FIREFOX("/Library/Application Support/Mozilla/NativeMessagingHosts")
|
, TARGET_DIR_FIREFOX("/Library/Application Support/Mozilla/NativeMessagingHosts")
|
||||||
, TARGET_DIR_VIVALDI("/Library/Application Support/Vivaldi/NativeMessagingHosts")
|
, TARGET_DIR_VIVALDI("/Library/Application Support/Vivaldi/NativeMessagingHosts")
|
||||||
, TARGET_DIR_TOR_BROWSER("/Library/Application Support/TorBrowser-Data/Browser/Mozilla/NativeMessagingHosts")
|
, TARGET_DIR_TOR_BROWSER("/Library/Application Support/TorBrowser-Data/Browser/Mozilla/NativeMessagingHosts")
|
||||||
|
, TARGET_DIR_BRAVE("/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts")
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX)
|
||||||
, TARGET_DIR_CHROME("/.config/google-chrome/NativeMessagingHosts")
|
, TARGET_DIR_CHROME("/.config/google-chrome/NativeMessagingHosts")
|
||||||
, TARGET_DIR_CHROMIUM("/.config/chromium/NativeMessagingHosts")
|
, TARGET_DIR_CHROMIUM("/.config/chromium/NativeMessagingHosts")
|
||||||
, TARGET_DIR_FIREFOX("/.mozilla/native-messaging-hosts")
|
, TARGET_DIR_FIREFOX("/.mozilla/native-messaging-hosts")
|
||||||
, TARGET_DIR_VIVALDI("/.config/vivaldi/NativeMessagingHosts")
|
, TARGET_DIR_VIVALDI("/.config/vivaldi/NativeMessagingHosts")
|
||||||
, TARGET_DIR_TOR_BROWSER("/.tor-browser/app/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts")
|
, TARGET_DIR_TOR_BROWSER("/.tor-browser/app/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts")
|
||||||
|
, TARGET_DIR_BRAVE("/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts")
|
||||||
#elif defined(Q_OS_WIN)
|
#elif defined(Q_OS_WIN)
|
||||||
// clang-format off
|
// clang-format off
|
||||||
, TARGET_DIR_CHROME("HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
|
, TARGET_DIR_CHROME("HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
|
||||||
@ -53,6 +55,7 @@ HostInstaller::HostInstaller()
|
|||||||
, TARGET_DIR_FIREFOX("HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
|
, TARGET_DIR_FIREFOX("HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser")
|
||||||
, TARGET_DIR_VIVALDI(TARGET_DIR_CHROME)
|
, TARGET_DIR_VIVALDI(TARGET_DIR_CHROME)
|
||||||
, TARGET_DIR_TOR_BROWSER(TARGET_DIR_FIREFOX)
|
, TARGET_DIR_TOR_BROWSER(TARGET_DIR_FIREFOX)
|
||||||
|
, TARGET_DIR_BRAVE(TARGET_DIR_CHROME)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -140,7 +143,8 @@ void HostInstaller::installBrowser(SupportedBrowsers browser,
|
|||||||
*/
|
*/
|
||||||
void HostInstaller::updateBinaryPaths(const bool& proxy, const QString& location)
|
void HostInstaller::updateBinaryPaths(const bool& proxy, const QString& location)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 4; ++i) {
|
// Where 6 is the number of entries in the SupportedBrowsers enum declared in HostInstaller.h
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
if (checkIfInstalled(static_cast<SupportedBrowsers>(i))) {
|
if (checkIfInstalled(static_cast<SupportedBrowsers>(i))) {
|
||||||
installBrowser(static_cast<SupportedBrowsers>(i), true, proxy, location);
|
installBrowser(static_cast<SupportedBrowsers>(i), true, proxy, location);
|
||||||
}
|
}
|
||||||
@ -166,6 +170,8 @@ QString HostInstaller::getTargetPath(SupportedBrowsers browser) const
|
|||||||
return TARGET_DIR_VIVALDI;
|
return TARGET_DIR_VIVALDI;
|
||||||
case SupportedBrowsers::TOR_BROWSER:
|
case SupportedBrowsers::TOR_BROWSER:
|
||||||
return TARGET_DIR_TOR_BROWSER;
|
return TARGET_DIR_TOR_BROWSER;
|
||||||
|
case SupportedBrowsers::BRAVE:
|
||||||
|
return TARGET_DIR_BRAVE;
|
||||||
default:
|
default:
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
@ -191,6 +197,8 @@ QString HostInstaller::getBrowserName(SupportedBrowsers browser) const
|
|||||||
return "vivaldi";
|
return "vivaldi";
|
||||||
case SupportedBrowsers::TOR_BROWSER:
|
case SupportedBrowsers::TOR_BROWSER:
|
||||||
return "tor-browser";
|
return "tor-browser";
|
||||||
|
case SupportedBrowsers::BRAVE:
|
||||||
|
return "brave";
|
||||||
default:
|
default:
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,8 @@ public:
|
|||||||
CHROMIUM = 1,
|
CHROMIUM = 1,
|
||||||
FIREFOX = 2,
|
FIREFOX = 2,
|
||||||
VIVALDI = 3,
|
VIVALDI = 3,
|
||||||
TOR_BROWSER = 4
|
TOR_BROWSER = 4,
|
||||||
|
BRAVE = 5
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -66,6 +67,7 @@ private:
|
|||||||
const QString TARGET_DIR_FIREFOX;
|
const QString TARGET_DIR_FIREFOX;
|
||||||
const QString TARGET_DIR_VIVALDI;
|
const QString TARGET_DIR_VIVALDI;
|
||||||
const QString TARGET_DIR_TOR_BROWSER;
|
const QString TARGET_DIR_TOR_BROWSER;
|
||||||
|
const QString TARGET_DIR_BRAVE;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // HOSTINSTALLER_H
|
#endif // HOSTINSTALLER_H
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#cmakedefine WITH_XC_KEESHARE
|
#cmakedefine WITH_XC_KEESHARE
|
||||||
#cmakedefine WITH_XC_KEESHARE_INSECURE
|
#cmakedefine WITH_XC_KEESHARE_INSECURE
|
||||||
#cmakedefine WITH_XC_KEESHARE_SECURE
|
#cmakedefine WITH_XC_KEESHARE_SECURE
|
||||||
|
#cmakedefine WITH_XC_UPDATECHECK
|
||||||
#cmakedefine WITH_XC_TOUCHID
|
#cmakedefine WITH_XC_TOUCHID
|
||||||
|
|
||||||
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
|
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
|
||||||
|
@ -30,6 +30,7 @@ QDateTime Clock::currentDateTime()
|
|||||||
|
|
||||||
uint Clock::currentSecondsSinceEpoch()
|
uint Clock::currentSecondsSinceEpoch()
|
||||||
{
|
{
|
||||||
|
// TODO: change to toSecsSinceEpoch() when min Qt >= 5.8
|
||||||
return instance().currentDateTimeImpl().toTime_t();
|
return instance().currentDateTimeImpl().toTime_t();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,6 +222,7 @@ bool Database::save(const QString& filePath, QString* error, bool atomic, bool b
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
*error = saveFile.errorString();
|
*error = saveFile.errorString();
|
||||||
}
|
}
|
||||||
@ -241,27 +242,34 @@ bool Database::save(const QString& filePath, QString* error, bool atomic, bool b
|
|||||||
|
|
||||||
// Delete the original db and move the temp file in place
|
// Delete the original db and move the temp file in place
|
||||||
QFile::remove(filePath);
|
QFile::remove(filePath);
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
// workaround to make this workaround work, see: https://bugreports.qt.io/browse/QTBUG-64008
|
// Note: call into the QFile rename instead of QTemporaryFile
|
||||||
if (tempFile.copy(filePath)) {
|
// due to an undocumented difference in how the function handles
|
||||||
// successfully saved database file
|
// errors. This prevents errors when saving across file systems.
|
||||||
return true;
|
if (tempFile.QFile::rename(filePath)) {
|
||||||
}
|
// successfully saved the database
|
||||||
#else
|
|
||||||
if (tempFile.rename(filePath)) {
|
|
||||||
// successfully saved database file
|
|
||||||
tempFile.setAutoRemove(false);
|
tempFile.setAutoRemove(false);
|
||||||
setFilePath(filePath);
|
setFilePath(filePath);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (!backup || !restoreDatabase(filePath)) {
|
||||||
|
// Failed to copy new database in place, and
|
||||||
|
// failed to restore from backup or backups disabled
|
||||||
|
tempFile.setAutoRemove(false);
|
||||||
|
if (error) {
|
||||||
|
*error = tr("%1\nBackup database located at %2").arg(tempFile.errorString(), tempFile.fileName());
|
||||||
}
|
}
|
||||||
#endif
|
markAsModified();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
*error = tempFile.errorString();
|
*error = tempFile.errorString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saving failed
|
// Saving failed
|
||||||
|
markAsModified();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,6 +341,28 @@ bool Database::backupDatabase(const QString& filePath)
|
|||||||
return QFile::copy(filePath, backupFilePath);
|
return QFile::copy(filePath, backupFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the database file from the backup file with
|
||||||
|
* name <filename>.old.<extension> to filePath. This will
|
||||||
|
* overwrite the existing file!
|
||||||
|
*
|
||||||
|
* @param filePath Path to the file to restore
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
bool Database::restoreDatabase(const QString& filePath)
|
||||||
|
{
|
||||||
|
static auto re = QRegularExpression("^(.*?)(\\.[^.]+)?$");
|
||||||
|
|
||||||
|
auto match = re.match(filePath);
|
||||||
|
auto backupFilePath = match.captured(1) + ".old" + match.captured(2);
|
||||||
|
// Only try to restore if the backup file actually exists
|
||||||
|
if (QFile::exists(backupFilePath)) {
|
||||||
|
QFile::remove(filePath);
|
||||||
|
return QFile::copy(backupFilePath, filePath);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool Database::isReadOnly() const
|
bool Database::isReadOnly() const
|
||||||
{
|
{
|
||||||
return m_data.isReadOnly;
|
return m_data.isReadOnly;
|
||||||
@ -401,11 +431,31 @@ const Metadata* Database::metadata() const
|
|||||||
return m_metadata;
|
return m_metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original file path that was provided for
|
||||||
|
* this database. This path may not exist, may contain
|
||||||
|
* unresolved symlinks, or have malformed slashes.
|
||||||
|
*
|
||||||
|
* @return original file path
|
||||||
|
*/
|
||||||
QString Database::filePath() const
|
QString Database::filePath() const
|
||||||
{
|
{
|
||||||
return m_data.filePath;
|
return m_data.filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the canonical file path of this databases'
|
||||||
|
* set file path. This returns an empty string if the
|
||||||
|
* file does not exist or cannot be resolved.
|
||||||
|
*
|
||||||
|
* @return canonical file path
|
||||||
|
*/
|
||||||
|
QString Database::canonicalFilePath() const
|
||||||
|
{
|
||||||
|
QFileInfo fileInfo(m_data.filePath);
|
||||||
|
return fileInfo.canonicalFilePath();
|
||||||
|
}
|
||||||
|
|
||||||
void Database::setFilePath(const QString& filePath)
|
void Database::setFilePath(const QString& filePath)
|
||||||
{
|
{
|
||||||
if (filePath == m_data.filePath) {
|
if (filePath == m_data.filePath) {
|
||||||
|
@ -83,6 +83,7 @@ public:
|
|||||||
|
|
||||||
QUuid uuid() const;
|
QUuid uuid() const;
|
||||||
QString filePath() const;
|
QString filePath() const;
|
||||||
|
QString canonicalFilePath() const;
|
||||||
void setFilePath(const QString& filePath);
|
void setFilePath(const QString& filePath);
|
||||||
|
|
||||||
Metadata* metadata();
|
Metadata* metadata();
|
||||||
@ -172,6 +173,7 @@ private:
|
|||||||
|
|
||||||
bool writeDatabase(QIODevice* device, QString* error = nullptr);
|
bool writeDatabase(QIODevice* device, QString* error = nullptr);
|
||||||
bool backupDatabase(const QString& filePath);
|
bool backupDatabase(const QString& filePath);
|
||||||
|
bool restoreDatabase(const QString& filePath);
|
||||||
|
|
||||||
Metadata* const m_metadata;
|
Metadata* const m_metadata;
|
||||||
DatabaseData m_data;
|
DatabaseData m_data;
|
||||||
|
@ -205,7 +205,7 @@ namespace Tools
|
|||||||
|
|
||||||
bool isBase64(const QByteArray& ba)
|
bool isBase64(const QByteArray& ba)
|
||||||
{
|
{
|
||||||
constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)";
|
constexpr auto pattern = R"(^(?:[a-z0-9+/]{4})*(?:[a-z0-9+/]{3}=|[a-z0-9+/]{2}==)?$)";
|
||||||
QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
|
QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||||
|
|
||||||
QString base64 = QString::fromLatin1(ba.constData(), ba.size());
|
QString base64 = QString::fromLatin1(ba.constData(), ba.size());
|
||||||
|
@ -78,7 +78,8 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
|||||||
QByteArray realStart = cipherStream.read(32);
|
QByteArray realStart = cipherStream.read(32);
|
||||||
|
|
||||||
if (realStart != m_streamStartBytes) {
|
if (realStart != m_streamStartBytes) {
|
||||||
raiseError(tr("Wrong key or database file is corrupt."));
|
raiseError(tr("Invalid credentials were provided, please try again.\n"
|
||||||
|
"If this reoccurs, then your database file may be corrupt."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,8 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
|||||||
// clang-format off
|
// clang-format off
|
||||||
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, db->transformedMasterKey());
|
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, db->transformedMasterKey());
|
||||||
if (headerHmac != CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
|
if (headerHmac != CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
|
||||||
raiseError(tr("Wrong key or database file is corrupt. (HMAC mismatch)"));
|
raiseError(tr("Invalid credentials were provided, please try again.\n"
|
||||||
|
"If this reoccurs, then your database file may be corrupt.") + " " + tr("(HMAC mismatch)"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
HmacBlockStream hmacStream(device, hmacKey);
|
HmacBlockStream hmacStream(device, hmacKey);
|
||||||
|
@ -1028,10 +1028,8 @@ bool KdbxXmlReader::readBool()
|
|||||||
|
|
||||||
QDateTime KdbxXmlReader::readDateTime()
|
QDateTime KdbxXmlReader::readDateTime()
|
||||||
{
|
{
|
||||||
static QRegularExpression b64regex("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$");
|
|
||||||
QString str = readString();
|
QString str = readString();
|
||||||
|
if (Tools::isBase64(str.toLatin1())) {
|
||||||
if (b64regex.match(str).hasMatch()) {
|
|
||||||
QByteArray secsBytes = QByteArray::fromBase64(str.toUtf8()).leftJustified(8, '\0', true).left(8);
|
QByteArray secsBytes = QByteArray::fromBase64(str.toUtf8()).leftJustified(8, '\0', true).left(8);
|
||||||
qint64 secs = Endian::bytesToSizedInt<quint64>(secsBytes, KeePass2::BYTEORDER);
|
qint64 secs = Endian::bytesToSizedInt<quint64>(secsBytes, KeePass2::BYTEORDER);
|
||||||
return QDateTime(QDate(1, 1, 1), QTime(0, 0, 0, 0), Qt::UTC).addSecs(secs);
|
return QDateTime(QDate(1, 1, 1), QTime(0, 0, 0, 0), Qt::UTC).addSecs(secs);
|
||||||
|
@ -372,7 +372,8 @@ KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!cipherStream) {
|
if (!cipherStream) {
|
||||||
raiseError(tr("Wrong key or database file is corrupt."));
|
raiseError(tr("Invalid credentials were provided, please try again.\n"
|
||||||
|
"If this reoccurs, then your database file may be corrupt."));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cipherStream.take();
|
return cipherStream.take();
|
||||||
|
@ -92,8 +92,15 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
|||||||
m_secUi->touchIDResetSpinBox, SLOT(setEnabled(bool)));
|
m_secUi->touchIDResetSpinBox, SLOT(setEnabled(bool)));
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
#ifndef WITH_XC_NETWORKING
|
#ifdef WITH_XC_UPDATECHECK
|
||||||
|
connect(m_generalUi->checkForUpdatesOnStartupCheckBox, SIGNAL(toggled(bool)), SLOT(checkUpdatesToggled(bool)));
|
||||||
|
#else
|
||||||
m_generalUi->checkForUpdatesOnStartupCheckBox->setVisible(false);
|
m_generalUi->checkForUpdatesOnStartupCheckBox->setVisible(false);
|
||||||
|
m_generalUi->checkForUpdatesIncludeBetasCheckBox->setVisible(false);
|
||||||
|
m_generalUi->checkUpdatesSpacer->changeSize(0,0, QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WITH_XC_NETWORKING
|
||||||
m_secUi->privacy->setVisible(false);
|
m_secUi->privacy->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -350,3 +357,8 @@ void ApplicationSettingsWidget::rememberDatabasesToggled(bool checked)
|
|||||||
m_generalUi->rememberLastKeyFilesCheckBox->setEnabled(checked);
|
m_generalUi->rememberLastKeyFilesCheckBox->setEnabled(checked);
|
||||||
m_generalUi->openPreviousDatabasesOnStartupCheckBox->setEnabled(checked);
|
m_generalUi->openPreviousDatabasesOnStartupCheckBox->setEnabled(checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplicationSettingsWidget::checkUpdatesToggled(bool checked)
|
||||||
|
{
|
||||||
|
m_generalUi->checkForUpdatesIncludeBetasCheckBox->setEnabled(checked);
|
||||||
|
}
|
||||||
|
@ -57,6 +57,7 @@ private slots:
|
|||||||
void systrayToggled(bool checked);
|
void systrayToggled(bool checked);
|
||||||
void toolbarSettingsToggled(bool checked);
|
void toolbarSettingsToggled(bool checked);
|
||||||
void rememberDatabasesToggled(bool checked);
|
void rememberDatabasesToggled(bool checked);
|
||||||
|
void checkUpdatesToggled(bool checked);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWidget* const m_secWidget;
|
QWidget* const m_secWidget;
|
||||||
|
@ -141,10 +141,40 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkForUpdatesOnStartupCheckBox">
|
<widget class="QCheckBox" name="checkForUpdatesOnStartupCheckBox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Check for updates at application startup</string>
|
<string>Check for updates at application startup once per week</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="checkUpdatesSubLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<spacer name="checkUpdatesSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkForUpdatesIncludeBetasCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Include beta releases when checking for updates</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -241,13 +271,6 @@
|
|||||||
<string>General</string>
|
<string>General</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="checkForUpdatesIncludeBetasCheckBox">
|
|
||||||
<property name="text">
|
|
||||||
<string>Include pre-releases when checking for updates</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="toolbarHideCheckBox">
|
<widget class="QCheckBox" name="toolbarHideCheckBox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -470,7 +493,7 @@
|
|||||||
<item alignment="Qt::AlignRight">
|
<item alignment="Qt::AlignRight">
|
||||||
<widget class="QLabel" name="languageLabel_2">
|
<widget class="QLabel" name="languageLabel_2">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
@ -490,6 +513,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="languageLabel_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>(restart program to activate)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -45,7 +45,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
|||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
m_ui->messageWidget->setHidden(true);
|
m_ui->messageWidget->setHidden(true);
|
||||||
m_ui->checkPassword->setChecked(true);
|
|
||||||
|
|
||||||
QFont font = m_ui->labelHeadline->font();
|
QFont font = m_ui->labelHeadline->font();
|
||||||
font.setBold(true);
|
font.setBold(true);
|
||||||
@ -159,7 +158,7 @@ void DatabaseOpenWidget::clearForms()
|
|||||||
m_ui->editPassword->setText("");
|
m_ui->editPassword->setText("");
|
||||||
m_ui->comboKeyFile->clear();
|
m_ui->comboKeyFile->clear();
|
||||||
m_ui->comboKeyFile->setEditText("");
|
m_ui->comboKeyFile->setEditText("");
|
||||||
m_ui->checkPassword->setChecked(true);
|
m_ui->checkPassword->setChecked(false);
|
||||||
m_ui->checkKeyFile->setChecked(false);
|
m_ui->checkKeyFile->setChecked(false);
|
||||||
m_ui->checkChallengeResponse->setChecked(false);
|
m_ui->checkChallengeResponse->setChecked(false);
|
||||||
m_ui->checkTouchID->setChecked(false);
|
m_ui->checkTouchID->setChecked(false);
|
||||||
@ -174,13 +173,8 @@ QSharedPointer<Database> DatabaseOpenWidget::database()
|
|||||||
|
|
||||||
void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
|
void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
|
||||||
{
|
{
|
||||||
if (!pw.isEmpty()) {
|
|
||||||
m_ui->editPassword->setText(pw);
|
m_ui->editPassword->setText(pw);
|
||||||
}
|
|
||||||
if (!keyFile.isEmpty()) {
|
|
||||||
m_ui->comboKeyFile->setEditText(keyFile);
|
m_ui->comboKeyFile->setEditText(keyFile);
|
||||||
}
|
|
||||||
|
|
||||||
openDatabase();
|
openDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,9 +185,7 @@ void DatabaseOpenWidget::openDatabase()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_ui->editPassword->isPasswordVisible()) {
|
|
||||||
m_ui->editPassword->setShowPassword(false);
|
m_ui->editPassword->setShowPassword(false);
|
||||||
}
|
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
m_db.reset(new Database());
|
m_db.reset(new Database());
|
||||||
@ -202,8 +194,7 @@ void DatabaseOpenWidget::openDatabase()
|
|||||||
bool ok = m_db->open(m_filename, masterKey, &error, false);
|
bool ok = m_db->open(m_filename, masterKey, &error, false);
|
||||||
QApplication::restoreOverrideCursor();
|
QApplication::restoreOverrideCursor();
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
m_ui->messageWidget->showMessage(tr("Unable to open the database:\n%1").arg(error),
|
m_ui->messageWidget->showMessage(error, MessageWidget::MessageType::Error);
|
||||||
MessageWidget::MessageType::Error);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +222,7 @@ void DatabaseOpenWidget::openDatabase()
|
|||||||
}
|
}
|
||||||
emit dialogFinished(true);
|
emit dialogFinished(true);
|
||||||
} else {
|
} else {
|
||||||
m_ui->messageWidget->showMessage(tr("Unable to open the database:\n%1").arg(error), MessageWidget::Error);
|
m_ui->messageWidget->showMessage(error, MessageWidget::Error);
|
||||||
m_ui->editPassword->setText("");
|
m_ui->editPassword->setText("");
|
||||||
|
|
||||||
#ifdef WITH_XC_TOUCHID
|
#ifdef WITH_XC_TOUCHID
|
||||||
@ -276,7 +267,7 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::databaseKey()
|
|||||||
QString keyFilename = m_ui->comboKeyFile->currentText();
|
QString keyFilename = m_ui->comboKeyFile->currentText();
|
||||||
QString errorMsg;
|
QString errorMsg;
|
||||||
if (!key->load(keyFilename, &errorMsg)) {
|
if (!key->load(keyFilename, &errorMsg)) {
|
||||||
m_ui->messageWidget->showMessage(tr("Can't open key file:\n%1").arg(errorMsg), MessageWidget::Error);
|
m_ui->messageWidget->showMessage(tr("Failed to open key file: %1").arg(errorMsg), MessageWidget::Error);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
if (key->type() != FileKey::Hashed && !config()->get("Messages/NoLegacyKeyFileWarning").toBool()) {
|
if (key->type() != FileKey::Hashed && !config()->get("Messages/NoLegacyKeyFileWarning").toBool()) {
|
||||||
@ -339,17 +330,20 @@ void DatabaseOpenWidget::reject()
|
|||||||
|
|
||||||
void DatabaseOpenWidget::activatePassword()
|
void DatabaseOpenWidget::activatePassword()
|
||||||
{
|
{
|
||||||
m_ui->checkPassword->setChecked(true);
|
bool hasPassword = !m_ui->editPassword->text().isEmpty();
|
||||||
|
m_ui->checkPassword->setChecked(hasPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::activateKeyFile()
|
void DatabaseOpenWidget::activateKeyFile()
|
||||||
{
|
{
|
||||||
m_ui->checkKeyFile->setChecked(true);
|
bool hasKeyFile = !m_ui->comboKeyFile->lineEdit()->text().isEmpty();
|
||||||
|
m_ui->checkKeyFile->setChecked(hasKeyFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::activateChallengeResponse()
|
void DatabaseOpenWidget::activateChallengeResponse()
|
||||||
{
|
{
|
||||||
m_ui->checkChallengeResponse->setChecked(true);
|
bool hasCR = m_ui->comboChallengeResponse->currentData().toInt() != -1;
|
||||||
|
m_ui->checkChallengeResponse->setChecked(hasCR);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::browseKeyFile()
|
void DatabaseOpenWidget::browseKeyFile()
|
||||||
@ -372,6 +366,7 @@ void DatabaseOpenWidget::pollYubikey()
|
|||||||
m_ui->checkChallengeResponse->setChecked(false);
|
m_ui->checkChallengeResponse->setChecked(false);
|
||||||
m_ui->comboChallengeResponse->setEnabled(false);
|
m_ui->comboChallengeResponse->setEnabled(false);
|
||||||
m_ui->comboChallengeResponse->clear();
|
m_ui->comboChallengeResponse->clear();
|
||||||
|
m_ui->comboChallengeResponse->addItem(tr("Select slot..."), -1);
|
||||||
m_ui->yubikeyProgress->setVisible(true);
|
m_ui->yubikeyProgress->setVisible(true);
|
||||||
|
|
||||||
// YubiKey init is slow, detect asynchronously to not block the UI
|
// YubiKey init is slow, detect asynchronously to not block the UI
|
||||||
@ -388,6 +383,7 @@ void DatabaseOpenWidget::yubikeyDetected(int slot, bool blocking)
|
|||||||
QHash<QString, QVariant> lastChallengeResponse = config()->get("LastChallengeResponse").toHash();
|
QHash<QString, QVariant> lastChallengeResponse = config()->get("LastChallengeResponse").toHash();
|
||||||
if (lastChallengeResponse.contains(m_filename)) {
|
if (lastChallengeResponse.contains(m_filename)) {
|
||||||
m_ui->checkChallengeResponse->setChecked(true);
|
m_ui->checkChallengeResponse->setChecked(true);
|
||||||
|
m_ui->comboChallengeResponse->setCurrentIndex(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,17 +150,16 @@ void DatabaseTabWidget::addDatabaseTab(const QString& filePath,
|
|||||||
QFileInfo fileInfo(filePath);
|
QFileInfo fileInfo(filePath);
|
||||||
QString canonicalFilePath = fileInfo.canonicalFilePath();
|
QString canonicalFilePath = fileInfo.canonicalFilePath();
|
||||||
if (canonicalFilePath.isEmpty()) {
|
if (canonicalFilePath.isEmpty()) {
|
||||||
emit messageGlobal(tr("The database file does not exist or is not accessible."), MessageWidget::Error);
|
emit messageGlobal(tr("Failed to open %1. It either does not exist or is not accessible.").arg(filePath),
|
||||||
|
MessageWidget::Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0, c = count(); i < c; ++i) {
|
for (int i = 0, c = count(); i < c; ++i) {
|
||||||
auto* dbWidget = databaseWidgetFromIndex(i);
|
auto* dbWidget = databaseWidgetFromIndex(i);
|
||||||
Q_ASSERT(dbWidget);
|
Q_ASSERT(dbWidget);
|
||||||
if (dbWidget && dbWidget->database()->filePath() == canonicalFilePath) {
|
if (dbWidget && dbWidget->database()->canonicalFilePath() == canonicalFilePath) {
|
||||||
if (!password.isEmpty()) {
|
|
||||||
dbWidget->performUnlockDatabase(password, keyfile);
|
dbWidget->performUnlockDatabase(password, keyfile);
|
||||||
}
|
|
||||||
if (!inBackground) {
|
if (!inBackground) {
|
||||||
// switch to existing tab if file is already open
|
// switch to existing tab if file is already open
|
||||||
setCurrentIndex(indexOf(dbWidget));
|
setCurrentIndex(indexOf(dbWidget));
|
||||||
@ -171,9 +170,7 @@ void DatabaseTabWidget::addDatabaseTab(const QString& filePath,
|
|||||||
|
|
||||||
auto* dbWidget = new DatabaseWidget(QSharedPointer<Database>::create(filePath), this);
|
auto* dbWidget = new DatabaseWidget(QSharedPointer<Database>::create(filePath), this);
|
||||||
addDatabaseTab(dbWidget, inBackground);
|
addDatabaseTab(dbWidget, inBackground);
|
||||||
if (!password.isEmpty()) {
|
|
||||||
dbWidget->performUnlockDatabase(password, keyfile);
|
dbWidget->performUnlockDatabase(password, keyfile);
|
||||||
}
|
|
||||||
updateLastDatabases(filePath);
|
updateLastDatabases(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +89,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
|||||||
, m_databaseOpenWidget(new DatabaseOpenWidget(this))
|
, m_databaseOpenWidget(new DatabaseOpenWidget(this))
|
||||||
, m_keepass1OpenWidget(new KeePass1OpenWidget(this))
|
, m_keepass1OpenWidget(new KeePass1OpenWidget(this))
|
||||||
, m_groupView(new GroupView(m_db.data(), m_mainSplitter))
|
, m_groupView(new GroupView(m_db.data(), m_mainSplitter))
|
||||||
|
, m_saveAttempts(0)
|
||||||
, m_fileWatcher(new DelayingFileWatcher(this))
|
, m_fileWatcher(new DelayingFileWatcher(this))
|
||||||
{
|
{
|
||||||
m_messageWidget->setHidden(true);
|
m_messageWidget->setHidden(true);
|
||||||
@ -260,12 +261,11 @@ bool DatabaseWidget::isSearchActive() const
|
|||||||
bool DatabaseWidget::isEditWidgetModified() const
|
bool DatabaseWidget::isEditWidgetModified() const
|
||||||
{
|
{
|
||||||
if (currentWidget() == m_editEntryWidget) {
|
if (currentWidget() == m_editEntryWidget) {
|
||||||
return m_editEntryWidget->hasBeenModified();
|
return m_editEntryWidget->isModified();
|
||||||
} else {
|
} else if (currentWidget() == m_editGroupWidget) {
|
||||||
// other edit widget don't have a hasBeenModified() method yet
|
return m_editGroupWidget->isModified();
|
||||||
// assume that they already have been modified
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<int> DatabaseWidget::mainSplitterSizes() const
|
QList<int> DatabaseWidget::mainSplitterSizes() const
|
||||||
@ -859,6 +859,7 @@ void DatabaseWidget::loadDatabase(bool accepted)
|
|||||||
replaceDatabase(openWidget->database());
|
replaceDatabase(openWidget->database());
|
||||||
switchToMainView();
|
switchToMainView();
|
||||||
m_fileWatcher->restart();
|
m_fileWatcher->restart();
|
||||||
|
m_saveAttempts = 0;
|
||||||
emit databaseUnlocked();
|
emit databaseUnlocked();
|
||||||
} else {
|
} else {
|
||||||
m_fileWatcher->stop();
|
m_fileWatcher->stop();
|
||||||
@ -1247,7 +1248,7 @@ bool DatabaseWidget::lock()
|
|||||||
|
|
||||||
clipboard()->clearCopiedText();
|
clipboard()->clearCopiedText();
|
||||||
|
|
||||||
if (currentMode() == DatabaseWidget::Mode::EditMode) {
|
if (isEditWidgetModified()) {
|
||||||
auto result = MessageBox::question(this,
|
auto result = MessageBox::question(this,
|
||||||
tr("Lock Database?"),
|
tr("Lock Database?"),
|
||||||
tr("You are editing an entry. Discard changes and lock anyway?"),
|
tr("You are editing an entry. Discard changes and lock anyway?"),
|
||||||
@ -1512,7 +1513,7 @@ EntryView* DatabaseWidget::entryView()
|
|||||||
* @param attempt current save attempt or -1 to disable attempts
|
* @param attempt current save attempt or -1 to disable attempts
|
||||||
* @return true on success
|
* @return true on success
|
||||||
*/
|
*/
|
||||||
bool DatabaseWidget::save(int attempt)
|
bool DatabaseWidget::save()
|
||||||
{
|
{
|
||||||
// Never allow saving a locked database; it causes corruption
|
// Never allow saving a locked database; it causes corruption
|
||||||
Q_ASSERT(!isLocked());
|
Q_ASSERT(!isLocked());
|
||||||
@ -1527,6 +1528,8 @@ bool DatabaseWidget::save(int attempt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
blockAutoReload(true);
|
blockAutoReload(true);
|
||||||
|
++m_saveAttempts;
|
||||||
|
|
||||||
// TODO: Make this async, but lock out the database widget to prevent re-entrance
|
// TODO: Make this async, but lock out the database widget to prevent re-entrance
|
||||||
bool useAtomicSaves = config()->get("UseAtomicSaves", true).toBool();
|
bool useAtomicSaves = config()->get("UseAtomicSaves", true).toBool();
|
||||||
QString errorMessage;
|
QString errorMessage;
|
||||||
@ -1534,14 +1537,11 @@ bool DatabaseWidget::save(int attempt)
|
|||||||
blockAutoReload(false);
|
blockAutoReload(false);
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
m_saveAttempts = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attempt >= 0 && attempt <= 2) {
|
if (m_saveAttempts > 2 && useAtomicSaves) {
|
||||||
return save(attempt + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attempt > 2 && useAtomicSaves) {
|
|
||||||
// Saving failed 3 times, issue a warning and attempt to resolve
|
// Saving failed 3 times, issue a warning and attempt to resolve
|
||||||
auto result = MessageBox::question(this,
|
auto result = MessageBox::question(this,
|
||||||
tr("Disable safe saves?"),
|
tr("Disable safe saves?"),
|
||||||
@ -1552,11 +1552,15 @@ bool DatabaseWidget::save(int attempt)
|
|||||||
MessageBox::Disable);
|
MessageBox::Disable);
|
||||||
if (result == MessageBox::Disable) {
|
if (result == MessageBox::Disable) {
|
||||||
config()->set("UseAtomicSaves", false);
|
config()->set("UseAtomicSaves", false);
|
||||||
return save(attempt + 1);
|
return save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessage(tr("Writing the database failed.\n%1").arg(errorMessage), MessageWidget::Error);
|
showMessage(tr("Writing the database failed: %1").arg(errorMessage),
|
||||||
|
MessageWidget::Error,
|
||||||
|
true,
|
||||||
|
MessageWidget::LongAutoHideTimeout);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1585,8 +1589,9 @@ bool DatabaseWidget::saveAs()
|
|||||||
// Ensure we don't recurse back into this function
|
// Ensure we don't recurse back into this function
|
||||||
m_db->setReadOnly(false);
|
m_db->setReadOnly(false);
|
||||||
m_db->setFilePath(newFilePath);
|
m_db->setFilePath(newFilePath);
|
||||||
|
m_saveAttempts = 0;
|
||||||
|
|
||||||
if (!save(-1)) {
|
if (!save()) {
|
||||||
// Failed to save, try again
|
// Failed to save, try again
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ signals:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
bool lock();
|
bool lock();
|
||||||
bool save(int attempt = 0);
|
bool save();
|
||||||
bool saveAs();
|
bool saveAs();
|
||||||
|
|
||||||
void replaceDatabase(QSharedPointer<Database> db);
|
void replaceDatabase(QSharedPointer<Database> db);
|
||||||
@ -255,6 +255,8 @@ private:
|
|||||||
QUuid m_groupBeforeLock;
|
QUuid m_groupBeforeLock;
|
||||||
QUuid m_entryBeforeLock;
|
QUuid m_entryBeforeLock;
|
||||||
|
|
||||||
|
int m_saveAttempts;
|
||||||
|
|
||||||
// Search state
|
// Search state
|
||||||
EntrySearcher* m_EntrySearcher;
|
EntrySearcher* m_EntrySearcher;
|
||||||
QString m_lastSearchText;
|
QString m_lastSearchText;
|
||||||
|
@ -30,6 +30,7 @@ EditWidget::EditWidget(QWidget* parent)
|
|||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
setReadOnly(false);
|
setReadOnly(false);
|
||||||
|
setModified(false);
|
||||||
|
|
||||||
m_ui->messageWidget->setHidden(true);
|
m_ui->messageWidget->setHidden(true);
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ EditWidget::EditWidget(QWidget* parent)
|
|||||||
|
|
||||||
connect(m_ui->buttonBox, SIGNAL(accepted()), SIGNAL(accepted()));
|
connect(m_ui->buttonBox, SIGNAL(accepted()), SIGNAL(accepted()));
|
||||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SIGNAL(rejected()));
|
connect(m_ui->buttonBox, SIGNAL(rejected()), SIGNAL(rejected()));
|
||||||
|
connect(m_ui->buttonBox, SIGNAL(clicked(QAbstractButton*)), SLOT(buttonClicked(QAbstractButton*)));
|
||||||
}
|
}
|
||||||
|
|
||||||
EditWidget::~EditWidget()
|
EditWidget::~EditWidget()
|
||||||
@ -106,9 +108,6 @@ void EditWidget::setReadOnly(bool readOnly)
|
|||||||
m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Close);
|
m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Close);
|
||||||
} else {
|
} else {
|
||||||
m_ui->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply);
|
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()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +116,17 @@ bool EditWidget::readOnly() const
|
|||||||
return m_readOnly;
|
return m_readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EditWidget::setModified(bool state)
|
||||||
|
{
|
||||||
|
m_modified = state;
|
||||||
|
enableApplyButton(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EditWidget::isModified() const
|
||||||
|
{
|
||||||
|
return m_modified;
|
||||||
|
}
|
||||||
|
|
||||||
void EditWidget::enableApplyButton(bool enabled)
|
void EditWidget::enableApplyButton(bool enabled)
|
||||||
{
|
{
|
||||||
QPushButton* applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply);
|
QPushButton* applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply);
|
||||||
@ -125,6 +135,27 @@ void EditWidget::enableApplyButton(bool enabled)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EditWidget::showApplyButton(bool state)
|
||||||
|
{
|
||||||
|
if (!m_readOnly) {
|
||||||
|
auto buttons = m_ui->buttonBox->standardButtons();
|
||||||
|
if (state) {
|
||||||
|
buttons |= QDialogButtonBox::Apply;
|
||||||
|
} else {
|
||||||
|
buttons &= ~QDialogButtonBox::Apply;
|
||||||
|
}
|
||||||
|
m_ui->buttonBox->setStandardButtons(buttons);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditWidget::buttonClicked(QAbstractButton* button)
|
||||||
|
{
|
||||||
|
auto stdButton = m_ui->buttonBox->standardButton(button);
|
||||||
|
if (stdButton == QDialogButtonBox::Apply) {
|
||||||
|
emit apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type)
|
void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type)
|
||||||
{
|
{
|
||||||
// Show error messages for a longer time to make sure the user can read them
|
// Show error messages for a longer time to make sure the user can read them
|
||||||
|
@ -49,6 +49,8 @@ public:
|
|||||||
void setReadOnly(bool readOnly);
|
void setReadOnly(bool readOnly);
|
||||||
bool readOnly() const;
|
bool readOnly() const;
|
||||||
void enableApplyButton(bool enabled);
|
void enableApplyButton(bool enabled);
|
||||||
|
void showApplyButton(bool state);
|
||||||
|
virtual bool isModified() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void apply();
|
void apply();
|
||||||
@ -58,10 +60,13 @@ signals:
|
|||||||
protected slots:
|
protected slots:
|
||||||
void showMessage(const QString& text, MessageWidget::MessageType type);
|
void showMessage(const QString& text, MessageWidget::MessageType type);
|
||||||
void hideMessage();
|
void hideMessage();
|
||||||
|
void setModified(bool state = true);
|
||||||
|
void buttonClicked(QAbstractButton* button);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QScopedPointer<Ui::EditWidget> m_ui;
|
const QScopedPointer<Ui::EditWidget> m_ui;
|
||||||
bool m_readOnly;
|
bool m_readOnly;
|
||||||
|
bool m_modified;
|
||||||
|
|
||||||
Q_DISABLE_COPY(EditWidget)
|
Q_DISABLE_COPY(EditWidget)
|
||||||
};
|
};
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
#include "keys/FileKey.h"
|
#include "keys/FileKey.h"
|
||||||
#include "keys/PasswordKey.h"
|
#include "keys/PasswordKey.h"
|
||||||
|
|
||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_UPDATECHECK
|
||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
#include "gui/UpdateCheckDialog.h"
|
#include "gui/UpdateCheckDialog.h"
|
||||||
#include "updatecheck/UpdateChecker.h"
|
#include "updatecheck/UpdateChecker.h"
|
||||||
@ -372,12 +372,12 @@ MainWindow::MainWindow()
|
|||||||
setUnifiedTitleAndToolBarOnMac(true);
|
setUnifiedTitleAndToolBarOnMac(true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_UPDATECHECK
|
||||||
connect(m_ui->actionCheckForUpdates, SIGNAL(triggered()), SLOT(showUpdateCheckDialog()));
|
connect(m_ui->actionCheckForUpdates, SIGNAL(triggered()), SLOT(showUpdateCheckDialog()));
|
||||||
connect(UpdateChecker::instance(),
|
connect(UpdateChecker::instance(),
|
||||||
SIGNAL(updateCheckFinished(bool, QString, bool)),
|
SIGNAL(updateCheckFinished(bool, QString, bool)),
|
||||||
SLOT(hasUpdateAvailable(bool, QString, bool)));
|
SLOT(hasUpdateAvailable(bool, QString, bool)));
|
||||||
QTimer::singleShot(3000, this, SLOT(showUpdateCheckStartup()));
|
QTimer::singleShot(500, this, SLOT(showUpdateCheckStartup()));
|
||||||
#else
|
#else
|
||||||
m_ui->actionCheckForUpdates->setVisible(false);
|
m_ui->actionCheckForUpdates->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
@ -670,7 +670,7 @@ void MainWindow::showAboutDialog()
|
|||||||
|
|
||||||
void MainWindow::showUpdateCheckStartup()
|
void MainWindow::showUpdateCheckStartup()
|
||||||
{
|
{
|
||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_UPDATECHECK
|
||||||
if (!config()->get("UpdateCheckMessageShown", false).toBool()) {
|
if (!config()->get("UpdateCheckMessageShown", false).toBool()) {
|
||||||
auto result =
|
auto result =
|
||||||
MessageBox::question(this,
|
MessageBox::question(this,
|
||||||
@ -693,7 +693,7 @@ void MainWindow::showUpdateCheckStartup()
|
|||||||
|
|
||||||
void MainWindow::hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested)
|
void MainWindow::hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested)
|
||||||
{
|
{
|
||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_UPDATECHECK
|
||||||
if (hasUpdate && !isManuallyRequested) {
|
if (hasUpdate && !isManuallyRequested) {
|
||||||
auto* updateCheckDialog = new UpdateCheckDialog(this);
|
auto* updateCheckDialog = new UpdateCheckDialog(this);
|
||||||
updateCheckDialog->showUpdateCheckResponse(hasUpdate, version);
|
updateCheckDialog->showUpdateCheckResponse(hasUpdate, version);
|
||||||
@ -708,7 +708,7 @@ void MainWindow::hasUpdateAvailable(bool hasUpdate, const QString& version, bool
|
|||||||
|
|
||||||
void MainWindow::showUpdateCheckDialog()
|
void MainWindow::showUpdateCheckDialog()
|
||||||
{
|
{
|
||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_UPDATECHECK
|
||||||
updateCheck()->checkForUpdates(true);
|
updateCheck()->checkForUpdates(true);
|
||||||
auto* updateCheckDialog = new UpdateCheckDialog(this);
|
auto* updateCheckDialog = new UpdateCheckDialog(this);
|
||||||
updateCheckDialog->show();
|
updateCheckDialog->show();
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
const int MessageWidget::DefaultAutoHideTimeout = 6000;
|
const int MessageWidget::DefaultAutoHideTimeout = 6000;
|
||||||
|
const int MessageWidget::LongAutoHideTimeout = 15000;
|
||||||
const int MessageWidget::DisableAutoHide = -1;
|
const int MessageWidget::DisableAutoHide = -1;
|
||||||
|
|
||||||
MessageWidget::MessageWidget(QWidget* parent)
|
MessageWidget::MessageWidget(QWidget* parent)
|
||||||
|
@ -33,6 +33,7 @@ public:
|
|||||||
int autoHideTimeout() const;
|
int autoHideTimeout() const;
|
||||||
|
|
||||||
static const int DefaultAutoHideTimeout;
|
static const int DefaultAutoHideTimeout;
|
||||||
|
static const int LongAutoHideTimeout;
|
||||||
static const int DisableAutoHide;
|
static const int DisableAutoHide;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2016 Jonathan White <support@dmapps.us>
|
|
||||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -97,6 +96,13 @@ bool SearchWidget::eventFilter(QObject* obj, QEvent* event)
|
|||||||
if (keyEvent->key() == Qt::Key_Escape) {
|
if (keyEvent->key() == Qt::Key_Escape) {
|
||||||
emit escapePressed();
|
emit escapePressed();
|
||||||
return true;
|
return true;
|
||||||
|
} 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 (m_ui->searchEdit->cursorPosition() == m_ui->searchEdit->text().length()) {
|
||||||
// If down is pressed at EOL, move the focus to the entry view
|
// If down is pressed at EOL, move the focus to the entry view
|
||||||
|
@ -77,11 +77,8 @@ void DatabaseSettingsWidgetMasterKey::load(QSharedPointer<Database> db)
|
|||||||
// database has no key, we are about to add a new one
|
// database has no key, we are about to add a new one
|
||||||
m_passwordEditWidget->changeVisiblePage(KeyComponentWidget::Page::Edit);
|
m_passwordEditWidget->changeVisiblePage(KeyComponentWidget::Page::Edit);
|
||||||
m_passwordEditWidget->setPasswordVisible(true);
|
m_passwordEditWidget->setPasswordVisible(true);
|
||||||
m_isDirty = true;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDirty = false;
|
|
||||||
bool hasAdditionalKeys = false;
|
bool hasAdditionalKeys = false;
|
||||||
for (const auto& key : m_db->key()->keys()) {
|
for (const auto& key : m_db->key()->keys()) {
|
||||||
if (key->uuid() == PasswordKey::UUID) {
|
if (key->uuid() == PasswordKey::UUID) {
|
||||||
@ -103,7 +100,9 @@ void DatabaseSettingsWidgetMasterKey::load(QSharedPointer<Database> db)
|
|||||||
|
|
||||||
setAdditionalKeyOptionsVisible(hasAdditionalKeys);
|
setAdditionalKeyOptionsVisible(hasAdditionalKeys);
|
||||||
|
|
||||||
m_isDirty = isDirty;
|
connect(m_passwordEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
|
||||||
|
connect(m_keyFileEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
|
||||||
|
connect(m_yubiKeyEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseSettingsWidgetMasterKey::initialize()
|
void DatabaseSettingsWidgetMasterKey::initialize()
|
||||||
|
@ -276,55 +276,54 @@ void EditEntryWidget::setupHistory()
|
|||||||
void EditEntryWidget::setupEntryUpdate()
|
void EditEntryWidget::setupEntryUpdate()
|
||||||
{
|
{
|
||||||
// Entry tab
|
// Entry tab
|
||||||
connect(m_mainUi->titleEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_mainUi->titleEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
|
||||||
connect(m_mainUi->usernameEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_mainUi->usernameEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
|
||||||
connect(m_mainUi->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_mainUi->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
|
||||||
connect(m_mainUi->passwordRepeatEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_mainUi->passwordRepeatEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
|
||||||
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
|
||||||
#ifdef WITH_XC_NETWORKING
|
#ifdef WITH_XC_NETWORKING
|
||||||
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
|
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
|
||||||
#endif
|
#endif
|
||||||
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_mainUi->notesEnabled, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_mainUi->notesEnabled, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setUnsavedChanges()));
|
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified()));
|
||||||
connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setUnsavedChanges()));
|
connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
|
||||||
|
|
||||||
// Advanced tab
|
// Advanced tab
|
||||||
connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setUnsavedChanges()));
|
connect(m_advancedUi->attributesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
|
||||||
connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_advancedUi->protectAttributeButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_advancedUi->fgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_advancedUi->bgColorCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setUnsavedChanges()));
|
connect(m_advancedUi->attachmentsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
|
||||||
|
|
||||||
// Icon tab
|
// Icon tab
|
||||||
connect(m_iconsWidget, SIGNAL(widgetUpdated()), this, SLOT(setUnsavedChanges()));
|
connect(m_iconsWidget, SIGNAL(widgetUpdated()), this, SLOT(setModified()));
|
||||||
|
|
||||||
// Auto-Type tab
|
// Auto-Type tab
|
||||||
connect(m_autoTypeUi->enableButton, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_autoTypeUi->enableButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_autoTypeUi->inheritSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges()));
|
connect(m_autoTypeUi->inheritSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
|
||||||
connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges()));
|
connect(m_autoTypeUi->customSequenceButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
|
||||||
connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
|
||||||
connect(m_autoTypeUi->sequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_autoTypeUi->sequenceEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
|
||||||
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
|
||||||
|
|
||||||
// Properties and History tabs don't need extra connections
|
// Properties and History tabs don't need extra connections
|
||||||
|
|
||||||
#ifdef WITH_XC_SSHAGENT
|
#ifdef WITH_XC_SSHAGENT
|
||||||
// SSH Agent tab
|
// SSH Agent tab
|
||||||
if (config()->get("SSHAgent", false).toBool()) {
|
if (config()->get("SSHAgent", false).toBool()) {
|
||||||
connect(m_sshAgentUi->attachmentRadioButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges()));
|
connect(m_sshAgentUi->attachmentRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
|
||||||
connect(m_sshAgentUi->externalFileRadioButton, SIGNAL(toggled(bool)), this, SLOT(setUnsavedChanges()));
|
connect(m_sshAgentUi->externalFileRadioButton, SIGNAL(toggled(bool)), this, SLOT(setModified()));
|
||||||
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(editTextChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_sshAgentUi->attachmentComboBox, SIGNAL(editTextChanged(QString)), this, SLOT(setModified()));
|
||||||
connect(m_sshAgentUi->externalFileEdit, SIGNAL(textChanged(QString)), this, SLOT(setUnsavedChanges()));
|
connect(m_sshAgentUi->externalFileEdit, SIGNAL(textChanged(QString)), this, SLOT(setModified()));
|
||||||
connect(m_sshAgentUi->addKeyToAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_sshAgentUi->addKeyToAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_sshAgentUi->removeKeyFromAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_sshAgentUi->removeKeyFromAgentCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(
|
connect(m_sshAgentUi->requireUserConfirmationCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
m_sshAgentUi->requireUserConfirmationCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_sshAgentUi->lifetimeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_sshAgentUi->lifetimeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setUnsavedChanges()));
|
connect(m_sshAgentUi->lifetimeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setModified()));
|
||||||
connect(m_sshAgentUi->lifetimeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setUnsavedChanges()));
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -703,8 +702,10 @@ void EditEntryWidget::loadEntry(Entry* entry,
|
|||||||
setCurrentPage(0);
|
setCurrentPage(0);
|
||||||
setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1);
|
setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1);
|
||||||
|
|
||||||
// Force the user to Save/Apply/Discard new entries
|
// Force the user to Save/Discard new entries
|
||||||
setUnsavedChanges(m_create);
|
showApplyButton(!m_create);
|
||||||
|
|
||||||
|
setModified(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditEntryWidget::setForms(Entry* entry, bool restore)
|
void EditEntryWidget::setForms(Entry* entry, bool restore)
|
||||||
@ -881,7 +882,6 @@ bool EditEntryWidget::commitEntry()
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateEntryData(m_entry);
|
updateEntryData(m_entry);
|
||||||
setUnsavedChanges(false);
|
|
||||||
|
|
||||||
if (!m_create) {
|
if (!m_create) {
|
||||||
m_entry->endUpdate();
|
m_entry->endUpdate();
|
||||||
@ -896,6 +896,7 @@ bool EditEntryWidget::commitEntry()
|
|||||||
m_historyModel->setEntries(m_entry->historyItems());
|
m_historyModel->setEntries(m_entry->historyItems());
|
||||||
|
|
||||||
showMessage(tr("Entry updated successfully."), MessageWidget::Positive);
|
showMessage(tr("Entry updated successfully."), MessageWidget::Positive);
|
||||||
|
setModified(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -968,7 +969,7 @@ void EditEntryWidget::cancel()
|
|||||||
m_entry->setIcon(Entry::DefaultIconNumber);
|
m_entry->setIcon(Entry::DefaultIconNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_saved) {
|
if (isModified()) {
|
||||||
auto result = MessageBox::question(this,
|
auto result = MessageBox::question(this,
|
||||||
QString(),
|
QString(),
|
||||||
tr("Entry has unsaved changes"),
|
tr("Entry has unsaved changes"),
|
||||||
@ -980,19 +981,26 @@ void EditEntryWidget::cancel()
|
|||||||
}
|
}
|
||||||
if (result == MessageBox::Save) {
|
if (result == MessageBox::Save) {
|
||||||
commitEntry();
|
commitEntry();
|
||||||
m_saved = true;
|
setModified(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
emit editFinished(m_saved);
|
emit editFinished(!isModified());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditEntryWidget::clear()
|
void EditEntryWidget::clear()
|
||||||
{
|
{
|
||||||
m_entry = nullptr;
|
m_entry = nullptr;
|
||||||
m_db.reset();
|
m_db.reset();
|
||||||
|
|
||||||
|
m_mainUi->titleEdit->setText("");
|
||||||
|
m_mainUi->passwordEdit->setText("");
|
||||||
|
m_mainUi->passwordRepeatEdit->setText("");
|
||||||
|
m_mainUi->urlEdit->setText("");
|
||||||
|
m_mainUi->notesEdit->clear();
|
||||||
|
|
||||||
m_entryAttributes->clear();
|
m_entryAttributes->clear();
|
||||||
m_advancedUi->attachmentsWidget->clearAttachments();
|
m_advancedUi->attachmentsWidget->clearAttachments();
|
||||||
m_autoTypeAssoc->clear();
|
m_autoTypeAssoc->clear();
|
||||||
@ -1001,22 +1009,6 @@ void EditEntryWidget::clear()
|
|||||||
hideMessage();
|
hideMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EditEntryWidget::hasBeenModified() const
|
|
||||||
{
|
|
||||||
// entry has been modified if a history item is to be deleted
|
|
||||||
if (!m_historyModel->deletedEntries().isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if updating the entry would modify it
|
|
||||||
auto* entry = new Entry();
|
|
||||||
entry->copyDataFrom(m_entry.data());
|
|
||||||
|
|
||||||
entry->beginUpdate();
|
|
||||||
updateEntryData(entry);
|
|
||||||
return entry->endUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EditEntryWidget::togglePasswordGeneratorButton(bool checked)
|
void EditEntryWidget::togglePasswordGeneratorButton(bool checked)
|
||||||
{
|
{
|
||||||
if (checked) {
|
if (checked) {
|
||||||
@ -1063,7 +1055,7 @@ void EditEntryWidget::insertAttribute()
|
|||||||
m_advancedUi->attributesView->setCurrentIndex(index);
|
m_advancedUi->attributesView->setCurrentIndex(index);
|
||||||
m_advancedUi->attributesView->edit(index);
|
m_advancedUi->attributesView->edit(index);
|
||||||
|
|
||||||
setUnsavedChanges(true);
|
setModified(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditEntryWidget::editCurrentAttribute()
|
void EditEntryWidget::editCurrentAttribute()
|
||||||
@ -1074,7 +1066,7 @@ void EditEntryWidget::editCurrentAttribute()
|
|||||||
|
|
||||||
if (index.isValid()) {
|
if (index.isValid()) {
|
||||||
m_advancedUi->attributesView->edit(index);
|
m_advancedUi->attributesView->edit(index);
|
||||||
setUnsavedChanges(true);
|
setModified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1094,7 +1086,7 @@ void EditEntryWidget::removeCurrentAttribute()
|
|||||||
|
|
||||||
if (result == MessageBox::Remove) {
|
if (result == MessageBox::Remove) {
|
||||||
m_entryAttributes->remove(m_attributesModel->keyByIndex(index));
|
m_entryAttributes->remove(m_attributesModel->keyByIndex(index));
|
||||||
setUnsavedChanges(true);
|
setModified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1216,7 +1208,7 @@ void EditEntryWidget::insertAutoTypeAssoc()
|
|||||||
m_autoTypeUi->assocView->setCurrentIndex(newIndex);
|
m_autoTypeUi->assocView->setCurrentIndex(newIndex);
|
||||||
loadCurrentAssoc(newIndex);
|
loadCurrentAssoc(newIndex);
|
||||||
m_autoTypeUi->windowTitleCombo->setFocus();
|
m_autoTypeUi->windowTitleCombo->setFocus();
|
||||||
setUnsavedChanges(true);
|
setModified(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditEntryWidget::removeAutoTypeAssoc()
|
void EditEntryWidget::removeAutoTypeAssoc()
|
||||||
@ -1225,7 +1217,7 @@ void EditEntryWidget::removeAutoTypeAssoc()
|
|||||||
|
|
||||||
if (currentIndex.isValid()) {
|
if (currentIndex.isValid()) {
|
||||||
m_autoTypeAssoc->remove(currentIndex.row());
|
m_autoTypeAssoc->remove(currentIndex.row());
|
||||||
setUnsavedChanges(true);
|
setModified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1288,7 +1280,7 @@ void EditEntryWidget::restoreHistoryEntry()
|
|||||||
QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
|
QModelIndex index = m_sortModel->mapToSource(m_historyUi->historyView->currentIndex());
|
||||||
if (index.isValid()) {
|
if (index.isValid()) {
|
||||||
setForms(m_historyModel->entryFromIndex(index), true);
|
setForms(m_historyModel->entryFromIndex(index), true);
|
||||||
setUnsavedChanges(true);
|
setModified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1302,7 +1294,7 @@ void EditEntryWidget::deleteHistoryEntry()
|
|||||||
} else {
|
} else {
|
||||||
m_historyUi->deleteAllButton->setEnabled(false);
|
m_historyUi->deleteAllButton->setEnabled(false);
|
||||||
}
|
}
|
||||||
setUnsavedChanges(true);
|
setModified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1310,7 +1302,7 @@ void EditEntryWidget::deleteAllHistoryEntries()
|
|||||||
{
|
{
|
||||||
m_historyModel->deleteAll();
|
m_historyModel->deleteAll();
|
||||||
m_historyUi->deleteAllButton->setEnabled(m_historyModel->rowCount() > 0);
|
m_historyUi->deleteAllButton->setEnabled(m_historyModel->rowCount() > 0);
|
||||||
setUnsavedChanges(true);
|
setModified(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
QMenu* EditEntryWidget::createPresetsMenu()
|
QMenu* EditEntryWidget::createPresetsMenu()
|
||||||
@ -1363,12 +1355,6 @@ void EditEntryWidget::pickColor()
|
|||||||
QColor newColor = QColorDialog::getColor(oldColor);
|
QColor newColor = QColorDialog::getColor(oldColor);
|
||||||
if (newColor.isValid()) {
|
if (newColor.isValid()) {
|
||||||
setupColorButton(isForeground, newColor);
|
setupColorButton(isForeground, newColor);
|
||||||
setUnsavedChanges(true);
|
setModified(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditEntryWidget::setUnsavedChanges(bool hasUnsaved)
|
|
||||||
{
|
|
||||||
m_saved = !hasUnsaved;
|
|
||||||
enableApplyButton(hasUnsaved);
|
|
||||||
}
|
|
||||||
|
@ -68,7 +68,6 @@ public:
|
|||||||
|
|
||||||
QString entryTitle() const;
|
QString entryTitle() const;
|
||||||
void clear();
|
void clear();
|
||||||
bool hasBeenModified() const;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void editFinished(bool accepted);
|
void editFinished(bool accepted);
|
||||||
@ -106,7 +105,6 @@ private slots:
|
|||||||
void useExpiryPreset(QAction* action);
|
void useExpiryPreset(QAction* action);
|
||||||
void toggleHideNotes(bool visible);
|
void toggleHideNotes(bool visible);
|
||||||
void pickColor();
|
void pickColor();
|
||||||
void setUnsavedChanges(bool hasUnsaved = true);
|
|
||||||
#ifdef WITH_XC_SSHAGENT
|
#ifdef WITH_XC_SSHAGENT
|
||||||
void updateSSHAgent();
|
void updateSSHAgent();
|
||||||
void updateSSHAgentAttachment();
|
void updateSSHAgentAttachment();
|
||||||
@ -148,7 +146,6 @@ private:
|
|||||||
|
|
||||||
bool m_create;
|
bool m_create;
|
||||||
bool m_history;
|
bool m_history;
|
||||||
bool m_saved;
|
|
||||||
#ifdef WITH_XC_SSHAGENT
|
#ifdef WITH_XC_SSHAGENT
|
||||||
bool m_sshAgentEnabled;
|
bool m_sshAgentEnabled;
|
||||||
KeeAgentSettings m_sshAgentSettings;
|
KeeAgentSettings m_sshAgentSettings;
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "core/Metadata.h"
|
#include "core/Metadata.h"
|
||||||
#include "gui/EditWidgetIcons.h"
|
#include "gui/EditWidgetIcons.h"
|
||||||
#include "gui/EditWidgetProperties.h"
|
#include "gui/EditWidgetProperties.h"
|
||||||
|
#include "gui/MessageBox.h"
|
||||||
|
|
||||||
#if defined(WITH_XC_KEESHARE)
|
#if defined(WITH_XC_KEESHARE)
|
||||||
#include "keeshare/group/EditGroupPageKeeShare.h"
|
#include "keeshare/group/EditGroupPageKeeShare.h"
|
||||||
@ -46,6 +47,11 @@ public:
|
|||||||
editPage->assign(widget);
|
editPage->assign(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QWidget* getWidget()
|
||||||
|
{
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<IEditGroupPage> editPage;
|
QSharedPointer<IEditGroupPage> editPage;
|
||||||
QWidget* widget;
|
QWidget* widget;
|
||||||
@ -85,18 +91,38 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
|
connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
|
||||||
|
|
||||||
|
setupModifiedTracking();
|
||||||
}
|
}
|
||||||
|
|
||||||
EditGroupWidget::~EditGroupWidget()
|
EditGroupWidget::~EditGroupWidget()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EditGroupWidget::setupModifiedTracking()
|
||||||
|
{
|
||||||
|
// Group tab
|
||||||
|
connect(m_mainUi->editName, SIGNAL(textChanged(QString)), SLOT(setModified()));
|
||||||
|
connect(m_mainUi->editNotes, SIGNAL(textChanged()), SLOT(setModified()));
|
||||||
|
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), SLOT(setModified()));
|
||||||
|
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), SLOT(setModified()));
|
||||||
|
connect(m_mainUi->searchComboBox, SIGNAL(currentIndexChanged(int)), SLOT(setModified()));
|
||||||
|
connect(m_mainUi->autotypeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(setModified()));
|
||||||
|
connect(m_mainUi->autoTypeSequenceInherit, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
|
connect(m_mainUi->autoTypeSequenceCustomRadio, SIGNAL(toggled(bool)), SLOT(setModified()));
|
||||||
|
connect(m_mainUi->autoTypeSequenceCustomEdit, SIGNAL(textChanged(QString)), SLOT(setModified()));
|
||||||
|
|
||||||
|
// Icon tab
|
||||||
|
connect(m_editGroupWidgetIcons, SIGNAL(widgetUpdated()), SLOT(setModified()));
|
||||||
|
}
|
||||||
|
|
||||||
void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<Database>& database)
|
void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<Database>& database)
|
||||||
{
|
{
|
||||||
m_group = group;
|
m_group = group;
|
||||||
m_db = database;
|
m_db = database;
|
||||||
|
|
||||||
m_temporaryGroup.reset(group->clone(Entry::CloneNoFlags, Group::CloneNoFlags));
|
m_temporaryGroup.reset(group->clone(Entry::CloneNoFlags, Group::CloneNoFlags));
|
||||||
|
connect(m_temporaryGroup->customData(), SIGNAL(customDataModified()), SLOT(setModified()));
|
||||||
|
|
||||||
if (create) {
|
if (create) {
|
||||||
setHeadline(tr("Add group"));
|
setHeadline(tr("Add group"));
|
||||||
@ -139,6 +165,11 @@ void EditGroupWidget::loadGroup(Group* group, bool create, const QSharedPointer<
|
|||||||
setCurrentPage(0);
|
setCurrentPage(0);
|
||||||
|
|
||||||
m_mainUi->editName->setFocus();
|
m_mainUi->editName->setFocus();
|
||||||
|
|
||||||
|
// Force the user to Save/Discard new groups
|
||||||
|
showApplyButton(!create);
|
||||||
|
|
||||||
|
setModified(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditGroupWidget::save()
|
void EditGroupWidget::save()
|
||||||
@ -180,6 +211,8 @@ void EditGroupWidget::apply()
|
|||||||
|
|
||||||
// Icons add/remove are applied globally outside the transaction!
|
// Icons add/remove are applied globally outside the transaction!
|
||||||
m_group->copyDataFrom(m_temporaryGroup.data());
|
m_group->copyDataFrom(m_temporaryGroup.data());
|
||||||
|
|
||||||
|
setModified(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditGroupWidget::cancel()
|
void EditGroupWidget::cancel()
|
||||||
@ -188,6 +221,18 @@ void EditGroupWidget::cancel()
|
|||||||
m_group->setIcon(Entry::DefaultIconNumber);
|
m_group->setIcon(Entry::DefaultIconNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isModified()) {
|
||||||
|
auto result = MessageBox::question(this,
|
||||||
|
QString(),
|
||||||
|
tr("Entry has unsaved changes"),
|
||||||
|
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
|
||||||
|
MessageBox::Cancel);
|
||||||
|
if (result == MessageBox::Save) {
|
||||||
|
apply();
|
||||||
|
setModified(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
emit editFinished(false);
|
emit editFinished(false);
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ private:
|
|||||||
void addTriStateItems(QComboBox* comboBox, bool inheritValue);
|
void addTriStateItems(QComboBox* comboBox, bool inheritValue);
|
||||||
int indexFromTriState(Group::TriState triState);
|
int indexFromTriState(Group::TriState triState);
|
||||||
Group::TriState triStateFromIndex(int index);
|
Group::TriState triStateFromIndex(int index);
|
||||||
|
void setupModifiedTracking();
|
||||||
|
|
||||||
const QScopedPointer<Ui::EditGroupWidgetMain> m_mainUi;
|
const QScopedPointer<Ui::EditGroupWidgetMain> m_mainUi;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ KeyComponentWidget::KeyComponentWidget(const QString& name, QWidget* parent)
|
|||||||
connect(m_ui->removeButton, SIGNAL(clicked(bool)), SIGNAL(componentRemovalRequested()));
|
connect(m_ui->removeButton, SIGNAL(clicked(bool)), SIGNAL(componentRemovalRequested()));
|
||||||
connect(m_ui->cancelButton, SIGNAL(clicked(bool)), SLOT(cancelEdit()));
|
connect(m_ui->cancelButton, SIGNAL(clicked(bool)), SLOT(cancelEdit()));
|
||||||
|
|
||||||
connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(reset()));
|
connect(m_ui->stackedWidget, SIGNAL(currentChanged(int)), SLOT(resetComponentEditWidget()));
|
||||||
|
|
||||||
connect(this, SIGNAL(nameChanged(QString)), SLOT(updateComponentName(QString)));
|
connect(this, SIGNAL(nameChanged(QString)), SLOT(updateComponentName(QString)));
|
||||||
connect(this, SIGNAL(descriptionChanged(QString)), SLOT(updateComponentDescription(QString)));
|
connect(this, SIGNAL(descriptionChanged(QString)), SLOT(updateComponentDescription(QString)));
|
||||||
@ -46,11 +46,13 @@ KeyComponentWidget::KeyComponentWidget(const QString& name, QWidget* parent)
|
|||||||
connect(this, SIGNAL(componentRemovalRequested()), SLOT(doRemove()));
|
connect(this, SIGNAL(componentRemovalRequested()), SLOT(doRemove()));
|
||||||
connect(this, SIGNAL(componentAddChanged(bool)), SLOT(updateAddStatus(bool)));
|
connect(this, SIGNAL(componentAddChanged(bool)), SLOT(updateAddStatus(bool)));
|
||||||
|
|
||||||
blockSignals(true);
|
bool prev = blockSignals(true);
|
||||||
setComponentName(name);
|
setComponentName(name);
|
||||||
|
blockSignals(prev);
|
||||||
|
|
||||||
|
prev = m_ui->stackedWidget->blockSignals(true);
|
||||||
m_ui->stackedWidget->setCurrentIndex(Page::AddNew);
|
m_ui->stackedWidget->setCurrentIndex(Page::AddNew);
|
||||||
updateSize();
|
m_ui->stackedWidget->blockSignals(prev);
|
||||||
blockSignals(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyComponentWidget::~KeyComponentWidget()
|
KeyComponentWidget::~KeyComponentWidget()
|
||||||
@ -164,21 +166,22 @@ void KeyComponentWidget::cancelEdit()
|
|||||||
emit editCanceled();
|
emit editCanceled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyComponentWidget::reset()
|
void KeyComponentWidget::showEvent(QShowEvent* event)
|
||||||
{
|
{
|
||||||
if (static_cast<Page>(m_ui->stackedWidget->currentIndex()) == Page::Edit) {
|
resetComponentEditWidget();
|
||||||
if (!m_ui->componentWidgetLayout->isEmpty()) {
|
QWidget::showEvent(event);
|
||||||
auto* item = m_ui->componentWidgetLayout->takeAt(0);
|
|
||||||
if (item->widget()) {
|
|
||||||
delete item->widget();
|
|
||||||
}
|
|
||||||
delete item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QWidget* widget = componentEditWidget();
|
void KeyComponentWidget::resetComponentEditWidget()
|
||||||
m_ui->componentWidgetLayout->addWidget(widget);
|
{
|
||||||
|
if (!m_componentWidget || static_cast<Page>(m_ui->stackedWidget->currentIndex()) == Page::Edit) {
|
||||||
|
if (m_componentWidget) {
|
||||||
|
delete m_componentWidget;
|
||||||
|
}
|
||||||
|
|
||||||
initComponentEditWidget(widget);
|
m_componentWidget = componentEditWidget();
|
||||||
|
m_ui->componentWidgetLayout->addWidget(m_componentWidget);
|
||||||
|
initComponentEditWidget(m_componentWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
QTimer::singleShot(0, this, SLOT(updateSize()));
|
QTimer::singleShot(0, this, SLOT(updateSize()));
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
@ -109,6 +110,9 @@ signals:
|
|||||||
void editCanceled();
|
void editCanceled();
|
||||||
void componentRemovalRequested();
|
void componentRemovalRequested();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void showEvent(QShowEvent* event) override ;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateComponentName(const QString& name);
|
void updateComponentName(const QString& name);
|
||||||
void updateComponentDescription(const QString& decription);
|
void updateComponentDescription(const QString& decription);
|
||||||
@ -117,7 +121,7 @@ private slots:
|
|||||||
void doEdit();
|
void doEdit();
|
||||||
void doRemove();
|
void doRemove();
|
||||||
void cancelEdit();
|
void cancelEdit();
|
||||||
void reset();
|
void resetComponentEditWidget();
|
||||||
void updateSize();
|
void updateSize();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -125,6 +129,7 @@ private:
|
|||||||
Page m_previousPage = Page::AddNew;
|
Page m_previousPage = Page::AddNew;
|
||||||
QString m_componentName;
|
QString m_componentName;
|
||||||
QString m_componentDescription;
|
QString m_componentDescription;
|
||||||
|
QPointer<QWidget> m_componentWidget;
|
||||||
|
|
||||||
const QScopedPointer<Ui::KeyComponentWidget> m_ui;
|
const QScopedPointer<Ui::KeyComponentWidget> m_ui;
|
||||||
};
|
};
|
||||||
|
@ -92,6 +92,18 @@ void PasswordEditWidget::initComponentEditWidget(QWidget* widget)
|
|||||||
m_compUi->enterPasswordEdit->setFocus();
|
m_compUi->enterPasswordEdit->setFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PasswordEditWidget::hideEvent(QHideEvent* event)
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_compUi->enterPasswordEdit);
|
||||||
|
|
||||||
|
if (!isVisible() && m_compUi->enterPasswordEdit) {
|
||||||
|
m_compUi->enterPasswordEdit->setText("");
|
||||||
|
m_compUi->repeatPasswordEdit->setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget::hideEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
bool PasswordEditWidget::validate(QString& errorMessage) const
|
bool PasswordEditWidget::validate(QString& errorMessage) const
|
||||||
{
|
{
|
||||||
if (m_compUi->enterPasswordEdit->text() != m_compUi->repeatPasswordEdit->text()) {
|
if (m_compUi->enterPasswordEdit->text() != m_compUi->repeatPasswordEdit->text()) {
|
||||||
|
@ -44,6 +44,7 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
QWidget* componentEditWidget() override;
|
QWidget* componentEditWidget() override;
|
||||||
void initComponentEditWidget(QWidget* widget) override;
|
void initComponentEditWidget(QWidget* widget) override;
|
||||||
|
void hideEvent(QHideEvent* event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void showPasswordGenerator();
|
void showPasswordGenerator();
|
||||||
|
@ -162,18 +162,32 @@ QString KeeShare::sharingLabel(const Group* group)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto reference = referenceOf(share);
|
const auto reference = referenceOf(share);
|
||||||
|
if (!reference.isValid()) {
|
||||||
|
return tr("Invalid sharing reference");
|
||||||
|
}
|
||||||
|
QStringList messages;
|
||||||
switch (reference.type) {
|
switch (reference.type) {
|
||||||
case KeeShareSettings::Inactive:
|
case KeeShareSettings::Inactive:
|
||||||
return tr("Disabled share %1").arg(reference.path);
|
messages << tr("Inactive share %1").arg(reference.path);
|
||||||
|
break;
|
||||||
case KeeShareSettings::ImportFrom:
|
case KeeShareSettings::ImportFrom:
|
||||||
return tr("Import from share %1").arg(reference.path);
|
messages << tr("Imported from %1").arg(reference.path);
|
||||||
|
break;
|
||||||
case KeeShareSettings::ExportTo:
|
case KeeShareSettings::ExportTo:
|
||||||
return tr("Export to share %1").arg(reference.path);
|
messages << tr("Exported to %1").arg(reference.path);
|
||||||
|
break;
|
||||||
case KeeShareSettings::SynchronizeWith:
|
case KeeShareSettings::SynchronizeWith:
|
||||||
return tr("Synchronize with share %1").arg(reference.path);
|
messages << tr("Synchronized with %1").arg(reference.path);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
const auto active = KeeShare::active();
|
||||||
return {};
|
if (reference.isImporting() && !active.in) {
|
||||||
|
messages << tr("Import is disabled in settings");
|
||||||
|
}
|
||||||
|
if (reference.isExporting() && !active.out) {
|
||||||
|
messages << tr("Export is disabled in settings");
|
||||||
|
}
|
||||||
|
return messages.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap KeeShare::indicatorBadge(const Group* group, QPixmap pixmap)
|
QPixmap KeeShare::indicatorBadge(const Group* group, QPixmap pixmap)
|
||||||
@ -196,13 +210,13 @@ QString KeeShare::referenceTypeLabel(const KeeShareSettings::Reference& referenc
|
|||||||
{
|
{
|
||||||
switch (reference.type) {
|
switch (reference.type) {
|
||||||
case KeeShareSettings::Inactive:
|
case KeeShareSettings::Inactive:
|
||||||
return tr("Disabled share");
|
return tr("Inactive share");
|
||||||
case KeeShareSettings::ImportFrom:
|
case KeeShareSettings::ImportFrom:
|
||||||
return tr("Import from");
|
return tr("Imported from");
|
||||||
case KeeShareSettings::ExportTo:
|
case KeeShareSettings::ExportTo:
|
||||||
return tr("Export to");
|
return tr("Exported to");
|
||||||
case KeeShareSettings::SynchronizeWith:
|
case KeeShareSettings::SynchronizeWith:
|
||||||
return tr("Synchronize with");
|
return tr("Synchronized with");
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ namespace
|
|||||||
key.openKey(QString());
|
key.openKey(QString());
|
||||||
const auto signer = Signature();
|
const auto signer = Signature();
|
||||||
if (!signer.verify(data, sign.signature, key)) {
|
if (!signer.verify(data, sign.signature, key)) {
|
||||||
qCritical("Invalid signature for sharing container %s.", qPrintable(reference.path));
|
qCritical("Invalid signature for shared container %s.", qPrintable(reference.path));
|
||||||
return {Invalid, KeeShareSettings::Certificate()};
|
return {Invalid, KeeShareSettings::Certificate()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +190,6 @@ void ShareObserver::reinitialize()
|
|||||||
KeeShareSettings::Reference newReference;
|
KeeShareSettings::Reference newReference;
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto active = KeeShare::active();
|
|
||||||
QList<Update> updated;
|
QList<Update> updated;
|
||||||
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
||||||
for (Group* group : groups) {
|
for (Group* group : groups) {
|
||||||
@ -202,9 +201,7 @@ void ShareObserver::reinitialize()
|
|||||||
m_groupToReference.remove(couple.group);
|
m_groupToReference.remove(couple.group);
|
||||||
m_referenceToGroup.remove(couple.oldReference);
|
m_referenceToGroup.remove(couple.oldReference);
|
||||||
m_shareToGroup.remove(couple.oldReference.path);
|
m_shareToGroup.remove(couple.oldReference.path);
|
||||||
if (couple.newReference.isValid()
|
if (couple.newReference.isValid()) {
|
||||||
&& ((active.in && couple.newReference.isImporting())
|
|
||||||
|| (active.out && couple.newReference.isExporting()))) {
|
|
||||||
m_groupToReference[couple.group] = couple.newReference;
|
m_groupToReference[couple.group] = couple.newReference;
|
||||||
m_referenceToGroup[couple.newReference] = couple.group;
|
m_referenceToGroup[couple.newReference] = couple.group;
|
||||||
m_shareToGroup[couple.newReference.path] = couple.group;
|
m_shareToGroup[couple.newReference.path] = couple.group;
|
||||||
|
@ -68,13 +68,13 @@ EditGroupWidgetKeeShare::EditGroupWidgetKeeShare(QWidget* parent)
|
|||||||
name = tr("Inactive");
|
name = tr("Inactive");
|
||||||
break;
|
break;
|
||||||
case KeeShareSettings::ImportFrom:
|
case KeeShareSettings::ImportFrom:
|
||||||
name = tr("Import from path");
|
name = tr("Import");
|
||||||
break;
|
break;
|
||||||
case KeeShareSettings::ExportTo:
|
case KeeShareSettings::ExportTo:
|
||||||
name = tr("Export to path");
|
name = tr("Export");
|
||||||
break;
|
break;
|
||||||
case KeeShareSettings::SynchronizeWith:
|
case KeeShareSettings::SynchronizeWith:
|
||||||
name = tr("Synchronize with path");
|
name = tr("Synchronize");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
m_ui->typeComboBox->insertItem(type, name, static_cast<int>(type));
|
m_ui->typeComboBox->insertItem(type, name, static_cast<int>(type));
|
||||||
@ -124,8 +124,8 @@ void EditGroupWidgetKeeShare::showSharingState()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!supported) {
|
if (!supported) {
|
||||||
m_ui->messageWidget->showMessage(
|
m_ui->messageWidget->showMessage(tr("Your KeePassXC version does not support sharing this container type.\n"
|
||||||
tr("Your KeePassXC version does not support sharing your container type. Please use %1.")
|
"Supported extensions are: %1.")
|
||||||
.arg(supportedExtensions.join(", ")),
|
.arg(supportedExtensions.join(", ")),
|
||||||
MessageWidget::Warning);
|
MessageWidget::Warning);
|
||||||
return;
|
return;
|
||||||
@ -149,18 +149,18 @@ void EditGroupWidgetKeeShare::showSharingState()
|
|||||||
(other.isImporting() && reference.isExporting()) || (other.isExporting() && reference.isImporting());
|
(other.isImporting() && reference.isExporting()) || (other.isExporting() && reference.isImporting());
|
||||||
}
|
}
|
||||||
if (conflictExport) {
|
if (conflictExport) {
|
||||||
m_ui->messageWidget->showMessage(tr("The export container %1 is already referenced.").arg(reference.path),
|
m_ui->messageWidget->showMessage(tr("%1 is already being exported by this database.").arg(reference.path),
|
||||||
MessageWidget::Error);
|
MessageWidget::Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (multipleImport) {
|
if (multipleImport) {
|
||||||
m_ui->messageWidget->showMessage(tr("The import container %1 is already imported.").arg(reference.path),
|
m_ui->messageWidget->showMessage(tr("%1 is already being imported by this database.").arg(reference.path),
|
||||||
MessageWidget::Warning);
|
MessageWidget::Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cycleImportExport) {
|
if (cycleImportExport) {
|
||||||
m_ui->messageWidget->showMessage(
|
m_ui->messageWidget->showMessage(
|
||||||
tr("The container %1 imported and export by different groups.").arg(reference.path),
|
tr("%1 is being imported and exported by different groups in this database.").arg(reference.path),
|
||||||
MessageWidget::Warning);
|
MessageWidget::Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -169,15 +169,20 @@ void EditGroupWidgetKeeShare::showSharingState()
|
|||||||
}
|
}
|
||||||
const auto active = KeeShare::active();
|
const auto active = KeeShare::active();
|
||||||
if (!active.in && !active.out) {
|
if (!active.in && !active.out) {
|
||||||
m_ui->messageWidget->showMessage(tr("Database sharing is disabled"), MessageWidget::Information);
|
m_ui->messageWidget->showMessage(
|
||||||
|
tr("KeeShare is currently disabled. You can enable import/export in the application settings.",
|
||||||
|
"KeeShare is a proper noun"),
|
||||||
|
MessageWidget::Information);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (active.in && !active.out) {
|
if (active.in && !active.out) {
|
||||||
m_ui->messageWidget->showMessage(tr("Database export is disabled"), MessageWidget::Information);
|
m_ui->messageWidget->showMessage(tr("Database export is currently disabled by application settings."),
|
||||||
|
MessageWidget::Information);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!active.in && active.out) {
|
if (!active.in && active.out) {
|
||||||
m_ui->messageWidget->showMessage(tr("Database import is disabled"), MessageWidget::Information);
|
m_ui->messageWidget->showMessage(tr("Database import is currently disabled by application settings."),
|
||||||
|
MessageWidget::Information);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
#include "UpdateChecker.h"
|
#include "UpdateChecker.h"
|
||||||
#include "config-keepassx.h"
|
#include "config-keepassx.h"
|
||||||
|
#include "core/Clock.h"
|
||||||
#include "core/Config.h"
|
#include "core/Config.h"
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
@ -38,7 +39,10 @@ UpdateChecker::~UpdateChecker()
|
|||||||
|
|
||||||
void UpdateChecker::checkForUpdates(bool manuallyRequested)
|
void UpdateChecker::checkForUpdates(bool manuallyRequested)
|
||||||
{
|
{
|
||||||
|
auto nextCheck = config()->get("GUI/CheckForUpdatesNextCheck", 0).toULongLong();
|
||||||
m_isManuallyRequested = manuallyRequested;
|
m_isManuallyRequested = manuallyRequested;
|
||||||
|
|
||||||
|
if (m_isManuallyRequested || Clock::currentSecondsSinceEpoch() >= nextCheck) {
|
||||||
m_bytesReceived.clear();
|
m_bytesReceived.clear();
|
||||||
|
|
||||||
QString apiUrlStr = QString("https://api.github.com/repos/keepassxreboot/keepassxc/releases");
|
QString apiUrlStr = QString("https://api.github.com/repos/keepassxreboot/keepassxc/releases");
|
||||||
@ -57,6 +61,7 @@ void UpdateChecker::checkForUpdates(bool manuallyRequested)
|
|||||||
connect(m_reply, &QNetworkReply::finished, this, &UpdateChecker::fetchFinished);
|
connect(m_reply, &QNetworkReply::finished, this, &UpdateChecker::fetchFinished);
|
||||||
connect(m_reply, &QIODevice::readyRead, this, &UpdateChecker::fetchReadyRead);
|
connect(m_reply, &QIODevice::readyRead, this, &UpdateChecker::fetchReadyRead);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateChecker::fetchReadyRead()
|
void UpdateChecker::fetchReadyRead()
|
||||||
{
|
{
|
||||||
@ -84,8 +89,12 @@ void UpdateChecker::fetchFinished()
|
|||||||
|
|
||||||
if (!jsonObject.value("tag_name").isUndefined()) {
|
if (!jsonObject.value("tag_name").isUndefined()) {
|
||||||
version = jsonObject.value("tag_name").toString();
|
version = jsonObject.value("tag_name").toString();
|
||||||
hasNewVersion = compareVersions(version, QString(KEEPASSXC_VERSION));
|
hasNewVersion = compareVersions(QString(KEEPASSXC_VERSION), version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check again in 7 days
|
||||||
|
// TODO: change to toSecsSinceEpoch() when min Qt >= 5.8
|
||||||
|
config()->set("GUI/CheckForUpdatesNextCheck", Clock::currentDateTime().addDays(7).toTime_t());
|
||||||
} else {
|
} else {
|
||||||
version = "error";
|
version = "error";
|
||||||
}
|
}
|
||||||
@ -93,38 +102,46 @@ void UpdateChecker::fetchFinished()
|
|||||||
emit updateCheckFinished(hasNewVersion, version, m_isManuallyRequested);
|
emit updateCheckFinished(hasNewVersion, version, m_isManuallyRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UpdateChecker::compareVersions(const QString& remoteVersion, const QString& localVersion)
|
bool UpdateChecker::compareVersions(const QString& localVersion, const QString& remoteVersion)
|
||||||
{
|
{
|
||||||
|
// Quick full-string equivalence check
|
||||||
if (localVersion == remoteVersion) {
|
if (localVersion == remoteVersion) {
|
||||||
return false; // Currently using updated version
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QRegularExpression verRegex("^(\\d+(\\.\\d+){0,2})(-\\w+)?$", QRegularExpression::CaseInsensitiveOption);
|
QRegularExpression verRegex(R"(^((?:\d+\.){2}\d+)(?:-(\w+?)(\d+)?)?$)");
|
||||||
|
|
||||||
QRegularExpressionMatch lmatch = verRegex.match(localVersion);
|
auto lmatch = verRegex.match(localVersion);
|
||||||
QRegularExpressionMatch rmatch = verRegex.match(remoteVersion);
|
auto rmatch = verRegex.match(remoteVersion);
|
||||||
|
|
||||||
if (!lmatch.captured(1).isNull() && !rmatch.captured(1).isNull()) {
|
auto lVersion = lmatch.captured(1).split(".");
|
||||||
if (lmatch.captured(1) == rmatch.captured(1) && !lmatch.captured(3).isNull()) {
|
auto lSuffix = lmatch.captured(2);
|
||||||
// Same version, but installed version has snapshot/beta suffix and should be updated to stable
|
auto lBetaNum = lmatch.captured(3);
|
||||||
return true;
|
|
||||||
|
auto rVersion = rmatch.captured(1).split(".");
|
||||||
|
auto rSuffix = rmatch.captured(2);
|
||||||
|
auto rBetaNum = rmatch.captured(3);
|
||||||
|
|
||||||
|
if (!lVersion.isEmpty() && !rVersion.isEmpty()) {
|
||||||
|
if (lSuffix.compare("snapshot", Qt::CaseInsensitive) == 0) {
|
||||||
|
// Snapshots are not checked for version updates
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList lparts = lmatch.captured(1).split(".");
|
// Check "-beta[X]" versions
|
||||||
QStringList rparts = rmatch.captured(1).split(".");
|
if (lVersion == rVersion && !lSuffix.isEmpty()) {
|
||||||
|
// Check if stable version has been released or new beta is available
|
||||||
if (lparts.length() < 3)
|
// otherwise the version numbers are equal
|
||||||
lparts << "0";
|
return rSuffix.isEmpty() || lBetaNum.toInt() < rBetaNum.toInt();
|
||||||
|
}
|
||||||
if (rparts.length() < 3)
|
|
||||||
rparts << "0";
|
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
int l = lparts[i].toInt();
|
int l = lVersion[i].toInt();
|
||||||
int r = rparts[i].toInt();
|
int r = rVersion[i].toInt();
|
||||||
|
|
||||||
if (l == r)
|
if (l == r) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (l > r) {
|
if (l > r) {
|
||||||
return false; // Installed version is newer than release
|
return false; // Installed version is newer than release
|
||||||
|
@ -31,7 +31,7 @@ public:
|
|||||||
~UpdateChecker() override;
|
~UpdateChecker() override;
|
||||||
|
|
||||||
void checkForUpdates(bool manuallyRequested);
|
void checkForUpdates(bool manuallyRequested);
|
||||||
static bool compareVersions(const QString& remoteVersion, const QString& localVersion);
|
static bool compareVersions(const QString& localVersion, const QString& remoteVersion);
|
||||||
static UpdateChecker* instance();
|
static UpdateChecker* instance();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -702,8 +702,7 @@ void TestCli::testKeyFileOption()
|
|||||||
m_stdoutFile->readLine(); // skip password prompt
|
m_stdoutFile->readLine(); // skip password prompt
|
||||||
m_stderrFile->seek(posErr);
|
m_stderrFile->seek(posErr);
|
||||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||||
QCOMPARE(m_stderrFile->readAll(),
|
QVERIFY(m_stderrFile->readAll().contains("Invalid credentials were provided"));
|
||||||
QByteArray("Error while reading the database: Wrong key or database file is corrupt. (HMAC mismatch)\n"));
|
|
||||||
|
|
||||||
// Should raise an error if key file path is invalid.
|
// Should raise an error if key file path is invalid.
|
||||||
pos = m_stdoutFile->pos();
|
pos = m_stdoutFile->pos();
|
||||||
@ -736,8 +735,7 @@ void TestCli::testNoPasswordOption()
|
|||||||
m_stdoutFile->readLine(); // skip password prompt
|
m_stdoutFile->readLine(); // skip password prompt
|
||||||
m_stderrFile->seek(posErr);
|
m_stderrFile->seek(posErr);
|
||||||
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
|
||||||
QCOMPARE(m_stderrFile->readAll(),
|
QVERIFY(m_stderrFile->readAll().contains("Invalid credentials were provided"));
|
||||||
QByteArray("Error while reading the database: Wrong key or database file is corrupt. (HMAC mismatch)\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestCli::testList()
|
void TestCli::testList()
|
||||||
|
@ -59,6 +59,7 @@ void TestTools::testIsBase64()
|
|||||||
QVERIFY(Tools::isBase64(QByteArray("12==")));
|
QVERIFY(Tools::isBase64(QByteArray("12==")));
|
||||||
QVERIFY(Tools::isBase64(QByteArray("abcd9876MN==")));
|
QVERIFY(Tools::isBase64(QByteArray("abcd9876MN==")));
|
||||||
QVERIFY(Tools::isBase64(QByteArray("abcd9876DEFGhijkMNO=")));
|
QVERIFY(Tools::isBase64(QByteArray("abcd9876DEFGhijkMNO=")));
|
||||||
|
QVERIFY(Tools::isBase64(QByteArray("abcd987/DEFGh+jk/NO=")));
|
||||||
QVERIFY(not Tools::isBase64(QByteArray("abcd123==")));
|
QVERIFY(not Tools::isBase64(QByteArray("abcd123==")));
|
||||||
QVERIFY(not Tools::isBase64(QByteArray("abc_")));
|
QVERIFY(not Tools::isBase64(QByteArray("abc_")));
|
||||||
QVERIFY(not Tools::isBase64(QByteArray("123")));
|
QVERIFY(not Tools::isBase64(QByteArray("123")));
|
||||||
|
@ -29,13 +29,32 @@ void TestUpdateCheck::initTestCase()
|
|||||||
|
|
||||||
void TestUpdateCheck::testCompareVersion()
|
void TestUpdateCheck::testCompareVersion()
|
||||||
{
|
{
|
||||||
// Remote Version , Installed Version
|
// No upgrade
|
||||||
QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("2.3.4")), true);
|
|
||||||
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.4.0")), false);
|
|
||||||
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.0")), false);
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.0")), false);
|
||||||
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.0-beta1")), true);
|
|
||||||
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta2"), QString("2.3.0-beta1")), true);
|
// First digit upgrade
|
||||||
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.4"), QString("2.4.0-snapshot")), false);
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("3.0.0")), true);
|
||||||
QCOMPARE(UpdateChecker::compareVersions(QString("invalid"), QString("2.4.0")), false);
|
QCOMPARE(UpdateChecker::compareVersions(QString("3.0.0"), QString("2.4.0")), false);
|
||||||
QCOMPARE(UpdateChecker::compareVersions(QString(""), QString("2.4.0")), false);
|
|
||||||
|
// Second digit upgrade
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.4"), QString("2.4.0")), true);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("2.3.4")), false);
|
||||||
|
|
||||||
|
// Third digit upgrade
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.1")), true);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.1"), QString("2.3.0")), false);
|
||||||
|
|
||||||
|
// Beta builds
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.0-beta1")), false);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0"), QString("2.3.1-beta1")), true);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta1"), QString("2.3.0")), true);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta"), QString("2.3.0-beta1")), true);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta1"), QString("2.3.0-beta")), false);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta1"), QString("2.3.0-beta2")), true);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.0-beta2"), QString("2.3.0-beta1")), false);
|
||||||
|
|
||||||
|
// Snapshot and invalid data
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.3.4-snapshot"), QString("2.4.0")), false);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("invalid")), false);
|
||||||
|
QCOMPARE(UpdateChecker::compareVersions(QString("2.4.0"), QString("")), false);
|
||||||
}
|
}
|
||||||
|
@ -429,13 +429,20 @@ void TestGui::testEditEntry()
|
|||||||
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
|
||||||
QTest::keyClicks(titleEdit, "_test");
|
QTest::keyClicks(titleEdit, "_test");
|
||||||
|
|
||||||
// Apply the edit
|
|
||||||
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
|
||||||
QVERIFY(editEntryWidgetButtonBox);
|
QVERIFY(editEntryWidgetButtonBox);
|
||||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
|
auto* okButton = editEntryWidgetButtonBox->button(QDialogButtonBox::Ok);
|
||||||
|
QVERIFY(okButton);
|
||||||
|
auto* applyButton = editEntryWidgetButtonBox->button(QDialogButtonBox::Apply);
|
||||||
|
QVERIFY(applyButton);
|
||||||
|
|
||||||
|
// Apply the edit
|
||||||
|
QTRY_VERIFY(applyButton->isEnabled());
|
||||||
|
QTest::mouseClick(applyButton, Qt::LeftButton);
|
||||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
|
||||||
QCOMPARE(entry->title(), QString("Sample Entry_test"));
|
QCOMPARE(entry->title(), QString("Sample Entry_test"));
|
||||||
QCOMPARE(entry->historyItems().size(), ++editCount);
|
QCOMPARE(entry->historyItems().size(), ++editCount);
|
||||||
|
QVERIFY(!applyButton->isEnabled());
|
||||||
|
|
||||||
// Test entry colors (simulate choosing a color)
|
// Test entry colors (simulate choosing a color)
|
||||||
editEntryWidget->setCurrentPage(1);
|
editEntryWidget->setCurrentPage(1);
|
||||||
@ -451,7 +458,7 @@ void TestGui::testEditEntry()
|
|||||||
colorCheckBox = editEntryWidget->findChild<QCheckBox*>("bgColorCheckBox");
|
colorCheckBox = editEntryWidget->findChild<QCheckBox*>("bgColorCheckBox");
|
||||||
colorButton->setProperty("color", bgColor);
|
colorButton->setProperty("color", bgColor);
|
||||||
colorCheckBox->setChecked(true);
|
colorCheckBox->setChecked(true);
|
||||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
|
QTest::mouseClick(applyButton, Qt::LeftButton);
|
||||||
QCOMPARE(entry->historyItems().size(), ++editCount);
|
QCOMPARE(entry->historyItems().size(), ++editCount);
|
||||||
|
|
||||||
// Test protected attributes
|
// Test protected attributes
|
||||||
@ -471,7 +478,7 @@ void TestGui::testEditEntry()
|
|||||||
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
|
||||||
QString originalPassword = passwordEdit->text();
|
QString originalPassword = passwordEdit->text();
|
||||||
passwordEdit->setText("newpass");
|
passwordEdit->setText("newpass");
|
||||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
QTest::mouseClick(okButton, Qt::LeftButton);
|
||||||
auto* messageWiget = editEntryWidget->findChild<MessageWidget*>("messageWidget");
|
auto* messageWiget = editEntryWidget->findChild<MessageWidget*>("messageWidget");
|
||||||
QTRY_VERIFY(messageWiget->isVisible());
|
QTRY_VERIFY(messageWiget->isVisible());
|
||||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
|
||||||
@ -479,7 +486,7 @@ void TestGui::testEditEntry()
|
|||||||
passwordEdit->setText(originalPassword);
|
passwordEdit->setText(originalPassword);
|
||||||
|
|
||||||
// Save the edit (press OK)
|
// Save the edit (press OK)
|
||||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
QTest::mouseClick(okButton, Qt::LeftButton);
|
||||||
QApplication::processEvents();
|
QApplication::processEvents();
|
||||||
|
|
||||||
// Confirm edit was made
|
// Confirm edit was made
|
||||||
@ -496,13 +503,15 @@ void TestGui::testEditEntry()
|
|||||||
|
|
||||||
// Test copy & paste newline sanitization
|
// Test copy & paste newline sanitization
|
||||||
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
|
||||||
|
okButton = editEntryWidgetButtonBox->button(QDialogButtonBox::Ok);
|
||||||
|
QVERIFY(okButton);
|
||||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
|
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
|
||||||
titleEdit->setText("multiline\ntitle");
|
titleEdit->setText("multiline\ntitle");
|
||||||
editEntryWidget->findChild<QLineEdit*>("usernameEdit")->setText("multiline\nusername");
|
editEntryWidget->findChild<QLineEdit*>("usernameEdit")->setText("multiline\nusername");
|
||||||
editEntryWidget->findChild<QLineEdit*>("passwordEdit")->setText("multiline\npassword");
|
editEntryWidget->findChild<QLineEdit*>("passwordEdit")->setText("multiline\npassword");
|
||||||
editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit")->setText("multiline\npassword");
|
editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit")->setText("multiline\npassword");
|
||||||
editEntryWidget->findChild<QLineEdit*>("urlEdit")->setText("multiline\nurl");
|
editEntryWidget->findChild<QLineEdit*>("urlEdit")->setText("multiline\nurl");
|
||||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
QTest::mouseClick(okButton, Qt::LeftButton);
|
||||||
|
|
||||||
QCOMPARE(entry->title(), QString("multiline title"));
|
QCOMPARE(entry->title(), QString("multiline title"));
|
||||||
QCOMPARE(entry->username(), QString("multiline username"));
|
QCOMPARE(entry->username(), QString("multiline username"));
|
||||||
@ -849,19 +858,31 @@ void TestGui::testSearch()
|
|||||||
QTRY_VERIFY(searchTextEdit->hasFocus());
|
QTRY_VERIFY(searchTextEdit->hasFocus());
|
||||||
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
||||||
QTRY_VERIFY(entryView->hasFocus());
|
QTRY_VERIFY(entryView->hasFocus());
|
||||||
|
auto* searchedEntry = entryView->currentEntry();
|
||||||
// Restore focus and search text selection
|
// Restore focus and search text selection
|
||||||
QTest::keyClick(m_mainWindow.data(), Qt::Key_F, Qt::ControlModifier);
|
QTest::keyClick(m_mainWindow.data(), Qt::Key_F, Qt::ControlModifier);
|
||||||
QTRY_COMPARE(searchTextEdit->selectedText(), QString("someTHING"));
|
QTRY_COMPARE(searchTextEdit->selectedText(), QString("someTHING"));
|
||||||
|
QTRY_VERIFY(searchTextEdit->hasFocus());
|
||||||
|
|
||||||
|
searchedEntry->setPassword("password");
|
||||||
|
QClipboard* clipboard = QApplication::clipboard();
|
||||||
|
|
||||||
|
// Attempt password copy with selected test (should fail)
|
||||||
|
QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier);
|
||||||
|
QVERIFY(clipboard->text() != searchedEntry->password());
|
||||||
|
// Deselect text and confirm password copies
|
||||||
|
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
||||||
|
QTRY_VERIFY(searchTextEdit->selectedText().isEmpty());
|
||||||
|
QTRY_VERIFY(searchTextEdit->hasFocus());
|
||||||
|
QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier);
|
||||||
|
QCOMPARE(searchedEntry->password(), clipboard->text());
|
||||||
// Ensure Down focuses on entry view when search text is selected
|
// Ensure Down focuses on entry view when search text is selected
|
||||||
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
QTest::keyClick(searchTextEdit, Qt::Key_Down);
|
||||||
QTRY_VERIFY(entryView->hasFocus());
|
QTRY_VERIFY(entryView->hasFocus());
|
||||||
QCOMPARE(entryView->selectionModel()->currentIndex().row(), 0);
|
QCOMPARE(entryView->currentEntry(), searchedEntry);
|
||||||
// Test that password copies (entry has focus)
|
// Test that password copies with entry focused
|
||||||
QClipboard* clipboard = QApplication::clipboard();
|
|
||||||
QTest::keyClick(entryView, Qt::Key_C, Qt::ControlModifier);
|
QTest::keyClick(entryView, Qt::Key_C, Qt::ControlModifier);
|
||||||
QModelIndex searchedItem = entryView->model()->index(0, 1);
|
QCOMPARE(searchedEntry->password(), clipboard->text());
|
||||||
Entry* searchedEntry = entryView->entryFromIndex(searchedItem);
|
|
||||||
QTRY_COMPARE(searchedEntry->password(), clipboard->text());
|
|
||||||
// Refocus back to search edit
|
// Refocus back to search edit
|
||||||
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
|
||||||
QTRY_VERIFY(searchTextEdit->hasFocus());
|
QTRY_VERIFY(searchTextEdit->hasFocus());
|
||||||
|
@ -92,6 +92,11 @@ setupVivaldi() {
|
|||||||
INSTALL_DIR="${BASE_DIR}/.config/vivaldi/NativeMessagingHosts"
|
INSTALL_DIR="${BASE_DIR}/.config/vivaldi/NativeMessagingHosts"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupBrave() {
|
||||||
|
buildJson
|
||||||
|
INSTALL_DIR="${BASE_DIR}/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts"
|
||||||
|
}
|
||||||
|
|
||||||
setupTorBrowser() {
|
setupTorBrowser() {
|
||||||
buildJson "firefox"
|
buildJson "firefox"
|
||||||
INSTALL_DIR="${BASE_DIR}/.tor-browser/app/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts"
|
INSTALL_DIR="${BASE_DIR}/.tor-browser/app/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts"
|
||||||
@ -109,7 +114,8 @@ BROWSER=$(whiptail \
|
|||||||
"2" "Chrome" \
|
"2" "Chrome" \
|
||||||
"3" "Chromium" \
|
"3" "Chromium" \
|
||||||
"4" "Vivaldi" \
|
"4" "Vivaldi" \
|
||||||
"5" "Tor Browser" \
|
"5" "Brave" \
|
||||||
|
"6" "Tor Browser" \
|
||||||
3>&1 1>&2 2>&3)
|
3>&1 1>&2 2>&3)
|
||||||
|
|
||||||
clear
|
clear
|
||||||
@ -122,7 +128,8 @@ if [ $exitstatus = 0 ]; then
|
|||||||
2) setupChrome ;;
|
2) setupChrome ;;
|
||||||
3) setupChromium ;;
|
3) setupChromium ;;
|
||||||
4) setupVivaldi ;;
|
4) setupVivaldi ;;
|
||||||
5) setupTorBrowser ;;
|
5) setupBrave ;;
|
||||||
|
6) setupTorBrowser ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Install the JSON file
|
# Install the JSON file
|
||||||
@ -139,4 +146,3 @@ if [ $exitstatus = 0 ]; then
|
|||||||
else
|
else
|
||||||
whiptail --title "Installation Canceled" --msgbox "No changes were made to your system" 8 50
|
whiptail --title "Installation Canceled" --msgbox "No changes were made to your system" 8 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user