Merge branch 'master' into develop

This commit is contained in:
Jonathan White 2021-01-31 16:47:35 -05:00
parent 4e90cb5818
commit 61b85183f9
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
13 changed files with 158 additions and 74 deletions

View File

@ -1,5 +1,27 @@
# Changelog # Changelog
## 2.6.4 (2021-01-31)
### Added
- Automatically adapt to light/dark system theme changes (Windows/macOS only) [#6034]
### Changed
- Show window title as tooltip on system tray [#5948]
- Compress Snap release as LZO for faster initial startup [#5877]
- Password generator: Set maximum selectable password length to 999 [#5937]
### Fixed
- Fix crash on app close when using SSH agent [#5935]
- Fix KDF selection showing wrong item when using Argon2id [#5923]
- Automatically close About dialog on database lock if it is still open [#5947]
- Linux: Fix automatic launch at system startup with AppImages [#5901]
- Linux: Fix click-to-move on empty area activating when using menus [#5971]
- Linux: Try multiple times to show tray icon if tray is not ready yet [#5948]
- macOS: Fix KeePassXC blocking clean shutdown [#6002]
## 2.6.3 (2021-01-12) ## 2.6.3 (2021-01-12)
### Added ### Added

View File

@ -101,7 +101,7 @@ NOTE: You can disable the recycle bin within the Database Settings. If the recyc
Creating a clone of an entry provides you a ready-to-use template for creating new entries with similar details of a master entry. Creating a clone of an entry provides you a ready-to-use template for creating new entries with similar details of a master entry.
To create a clone of an existing entry, perform the following steps: To create a clone of an existing entry, perform the following steps:
1. Right-click on the entry for which you want to create a clone and select _Clone Entry_. Alternatively, select the desired entry and press `Ctrl+K`. 1. Right-click on the entry for which you want to create a clone and select _Clone Entry_. Alternatively, select the desired entry and press `Ctrl+K`.
+ +
.Clone entry from context menu .Clone entry from context menu
@ -127,7 +127,7 @@ image::clone_entry_references.png[]
Where `<UUID>` is the Unique Identifier of the entry to pull data from and `<ShortCode>` is from the following: Where `<UUID>` is the Unique Identifier of the entry to pull data from and `<ShortCode>` is from the following:
+ +
* T - Title * T - Title
* U - Username * U - Username
* P - Password * P - Password
* A - URL * A - URL
* N - Notes * N - Notes
@ -288,6 +288,8 @@ image::database_security.png[]
.Database credentials .Database credentials
image::database_security_credentials.png[] image::database_security_credentials.png[]
WARNING: Consider creating a backup of your YubiKey. Please refer to <<Creating a YubiKey backup>>
5. Encryption settings allows you to change the average time it takes to encrypt and decrypt the database. The longer time that is chosen, the harder it will be to brute force attack your database. *We recommend a setting of one second.* 5. Encryption settings allows you to change the average time it takes to encrypt and decrypt the database. The longer time that is chosen, the harder it will be to brute force attack your database. *We recommend a setting of one second.*
+ +
.Database encryption .Database encryption
@ -305,6 +307,23 @@ The following key derivation functions are supported:
* AES-KDF (KDBX 4 and KDBX 3.1): This key derivation function is based on iterating AES. Users can change the number of iterations. The more iterations, the harder are dictionary and guessing attacks, but also database loading/saving takes more time (linearly). KDBX 3.1 only supports AES-KDF; any other key derivation function, like for instance Argon2, requires KDBX 4. * AES-KDF (KDBX 4 and KDBX 3.1): This key derivation function is based on iterating AES. Users can change the number of iterations. The more iterations, the harder are dictionary and guessing attacks, but also database loading/saving takes more time (linearly). KDBX 3.1 only supports AES-KDF; any other key derivation function, like for instance Argon2, requires KDBX 4.
* Argon2 (KDBX 4 - recommended): KDBX 4, the Argon2 key derivation function can be used for transforming the composite master key (as protection against dictionary attacks). The main advantage of Argon2 over AES-KDF is that it provides a better resistance against GPU/ASIC attacks (due to being a memory-hard function). The number of iterations scales linearly with the required time. By increasing the memory parameter, GPU/ASIC attacks become harder (and the required time increases). The parallelism parameter can be used to specify how many threads should be used. * Argon2 (KDBX 4 - recommended): KDBX 4, the Argon2 key derivation function can be used for transforming the composite master key (as protection against dictionary attacks). The main advantage of Argon2 over AES-KDF is that it provides a better resistance against GPU/ASIC attacks (due to being a memory-hard function). The number of iterations scales linearly with the required time. By increasing the memory parameter, GPU/ASIC attacks become harder (and the required time increases). The parallelism parameter can be used to specify how many threads should be used.
=== Creating a YubiKey backup
It is advisable to have a backup replica YubiKey In case your main YubiKey gets damaged, lost, or stolen. The same HMAC key will need to be written to both keys. To do this you can either use the YubiKey Personalization Tool GUI or the ykpersonalize CLI tool. The steps for the CLI tool are shown:
1. Create a 20 byte HMAC key:
+
```
dd status=none if=/dev/random bs=20 count=1 | xxd -p -c 40
```
2. Write the HMAC key to slot 2 _(Set through the first switch. Out of the box the YubiKey OTP resides in slot 1)_:
+
```
ykpersonalize -2 -a -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible -oallow-update
```
You will be asked to enter the HMAC key you created earlier, copy/paste they key output in the first step. Repeat both steps for your second YubiKey. We recommend storing your HMAC key in a safe place (e.g., printed on paper) in case you need to recreate another key.
// end::advanced[] // end::advanced[]
== Storing a Database File == Storing a Database File

View File

@ -44,6 +44,7 @@ ORIG_BRANCH=""
ORIG_CWD="$(pwd)" ORIG_CWD="$(pwd)"
MACOSX_DEPLOYMENT_TARGET=10.12 MACOSX_DEPLOYMENT_TARGET=10.12
GREP="grep" GREP="grep"
TIMESTAMP_SERVER="http://timestamp.sectigo.com"
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# helper functions # helper functions
@ -114,6 +115,7 @@ Options:
--appimage Build a Linux AppImage after compilation. --appimage Build a Linux AppImage after compilation.
If this option is set, --install-prefix has no effect If this option is set, --install-prefix has no effect
--appsign Perform platform specific App Signing before packaging --appsign Perform platform specific App Signing before packaging
--timestamp Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}')
-k, --key Specify the App Signing Key/Identity -k, --key Specify the App Signing Key/Identity
-c, --cmake-options Additional CMake options for compiling the sources -c, --cmake-options Additional CMake options for compiling the sources
--compiler Compiler to use (default: '${COMPILER}') --compiler Compiler to use (default: '${COMPILER}')
@ -145,6 +147,8 @@ Options:
-f, --files Files to sign (required) -f, --files Files to sign (required)
-k, --key, -i, --identity -k, --key, -i, --identity
Signing Key or Apple Developer ID (required) Signing Key or Apple Developer ID (required)
--timestamp Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}')
-u, --username Apple username for notarization (required on macOS)
-h, --help Show this help -h, --help Show this help
EOF EOF
elif [ "notarize" == "$cmd" ]; then elif [ "notarize" == "$cmd" ]; then
@ -494,6 +498,10 @@ merge() {
GPG_GIT_KEY="$2" GPG_GIT_KEY="$2"
shift ;; shift ;;
--timestamp)
TIMESTAMP_SERVER="$2"
shift ;;
-r|--release-branch) -r|--release-branch)
SOURCE_BRANCH="$2" SOURCE_BRANCH="$2"
shift ;; shift ;;
@ -810,6 +818,10 @@ build() {
--appsign) --appsign)
build_appsign=true ;; build_appsign=true ;;
--timestamp)
TIMESTAMP_SERVER="$2"
shift ;;
-k|--key) -k|--key)
build_key="$2" build_key="$2"
shift ;; shift ;;
@ -1268,8 +1280,10 @@ appsign() {
# osslsigncode does not succeed at signing MSI files at this time... # osslsigncode does not succeed at signing MSI files at this time...
logInfo "Signing file '${f}' using Microsoft signtool..." logInfo "Signing file '${f}' using Microsoft signtool..."
if ! signtool sign -f "${key}" -p "${password}" -d "KeePassXC" -td sha256 \ signtool sign -f "${key}" -p "${password}" -d "KeePassXC" -td sha256 \
-fd sha256 -tr "http://timestamp.comodoca.com/authenticode" "${f}"; then -fd sha256 -tr "${TIMESTAMP_SERVER}" "${f}"
if [ 0 -ne $? ]; then
exitError "Signing failed!" exitError "Signing failed!"
fi fi
else else

View File

@ -50,6 +50,23 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.6.4" date="2021-01-31">
<description>
<ul>
<li>Automatically adapt to light/dark system theme changes (Windows/macOS only) [#6034]</li>
<li>Show window title as tooltip on system tray [#5948]</li>
<li>Compress Snap release as LZO for faster initial startup [#5877]</li>
<li>Password generator: Set maximum selectable password length to 999 [#5937]</li>
<li>Fix crash on app close when using SSH agent [#5935]</li>
<li>Fix KDF selection showing wrong item when using Argon2id [#5923]</li>
<li>Automatically close About dialog on database lock if it is still open [#5947]</li>
<li>Linux: Fix automatic launch at system startup with AppImages [#5901]</li>
<li>Linux: Fix click-to-move on empty area activating when using menus [#5971]</li>
<li>Linux: Try multiple times to show tray icon if tray is not ready yet [#5948]</li>
<li>macOS: Fix KeePassXC blocking clean shutdown [#6002]</li>
</ul>
</description>
</release>
<release version="2.6.3" date="2021-01-12"> <release version="2.6.3" date="2021-01-12">
<description> <description>
<ul> <ul>

View File

@ -8,6 +8,7 @@ description: |
published under the terms of the GNU General Public License. published under the terms of the GNU General Public License.
confinement: strict confinement: strict
base: core18 base: core18
compression: lzo
plugs: # plugs for theming, font settings, cursor and to use gtk3 file chooser plugs: # plugs for theming, font settings, cursor and to use gtk3 file chooser
gtk-3-themes: gtk-3-themes:

View File

@ -30,8 +30,7 @@
* or associated data. KeePass uses the latest version of Argon2, v1.3. * or associated data. KeePass uses the latest version of Argon2, v1.3.
*/ */
Argon2Kdf::Argon2Kdf(Type type) Argon2Kdf::Argon2Kdf(Type type)
: Kdf::Kdf(KeePass2::KDF_ARGON2D) : Kdf::Kdf(type == Type::Argon2d ? KeePass2::KDF_ARGON2D : KeePass2::KDF_ARGON2ID)
, m_type(type)
, m_version(0x13) , m_version(0x13)
, m_memory(1 << 16) , m_memory(1 << 16)
, m_parallelism(static_cast<quint32>(QThread::idealThreadCount())) , m_parallelism(static_cast<quint32>(QThread::idealThreadCount()))
@ -57,12 +56,7 @@ bool Argon2Kdf::setVersion(quint32 version)
Argon2Kdf::Type Argon2Kdf::type() const Argon2Kdf::Type Argon2Kdf::type() const
{ {
return m_type; return uuid() == KeePass2::KDF_ARGON2D ? Type::Argon2d : Type::Argon2id;
}
void Argon2Kdf::setType(Type type)
{
m_type = type;
} }
quint64 Argon2Kdf::memory() const quint64 Argon2Kdf::memory() const
@ -144,11 +138,7 @@ bool Argon2Kdf::processParameters(const QVariantMap& p)
QVariantMap Argon2Kdf::writeParameters() QVariantMap Argon2Kdf::writeParameters()
{ {
QVariantMap p; QVariantMap p;
if (type() == Type::Argon2d) { p.insert(KeePass2::KDFPARAM_UUID, uuid().toRfc4122());
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2D.toRfc4122());
} else {
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2ID.toRfc4122());
}
p.insert(KeePass2::KDFPARAM_ARGON2_VERSION, version()); p.insert(KeePass2::KDFPARAM_ARGON2_VERSION, version());
p.insert(KeePass2::KDFPARAM_ARGON2_PARALLELISM, parallelism()); p.insert(KeePass2::KDFPARAM_ARGON2_PARALLELISM, parallelism());
p.insert(KeePass2::KDFPARAM_ARGON2_MEMORY, memory() * 1024); p.insert(KeePass2::KDFPARAM_ARGON2_MEMORY, memory() * 1024);

View File

@ -39,7 +39,6 @@ public:
quint32 version() const; quint32 version() const;
bool setVersion(quint32 version); bool setVersion(quint32 version);
Type type() const; Type type() const;
void setType(Type type);
quint64 memory() const; quint64 memory() const;
bool setMemory(quint64 kibibytes); bool setMemory(quint64 kibibytes);
quint32 parallelism() const; quint32 parallelism() const;
@ -49,7 +48,6 @@ public:
protected: protected:
int benchmarkImpl(int msec) const override; int benchmarkImpl(int msec) const override;
Type m_type;
quint32 m_version; quint32 m_version;
quint64 m_memory; quint64 m_memory;
quint32 m_parallelism; quint32 m_parallelism;

View File

@ -214,13 +214,6 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
m_EntrySearcher = new EntrySearcher(false); m_EntrySearcher = new EntrySearcher(false);
m_searchLimitGroup = config()->get(Config::SearchLimitGroup).toBool(); m_searchLimitGroup = config()->get(Config::SearchLimitGroup).toBool();
#ifdef WITH_XC_SSHAGENT
if (sshAgent()->isEnabled()) {
connect(this, SIGNAL(databaseLocked()), sshAgent(), SLOT(databaseLocked()));
connect(this, SIGNAL(databaseUnlocked()), sshAgent(), SLOT(databaseUnlocked()));
}
#endif
#ifdef WITH_XC_KEESHARE #ifdef WITH_XC_KEESHARE
// We need to reregister the database to allow exports // We need to reregister the database to allow exports
// from a newly created database // from a newly created database
@ -1091,6 +1084,9 @@ void DatabaseWidget::loadDatabase(bool accepted)
m_entryBeforeLock = QUuid(); m_entryBeforeLock = QUuid();
m_saveAttempts = 0; m_saveAttempts = 0;
emit databaseUnlocked(); emit databaseUnlocked();
#ifdef WITH_XC_SSHAGENT
sshAgent()->databaseUnlocked(m_db);
#endif
if (config()->get(Config::MinimizeAfterUnlock).toBool()) { if (config()->get(Config::MinimizeAfterUnlock).toBool()) {
getMainWindow()->minimizeOrHide(); getMainWindow()->minimizeOrHide();
} }
@ -1178,6 +1174,10 @@ void DatabaseWidget::unlockDatabase(bool accepted)
processAutoOpen(); processAutoOpen();
emit databaseUnlocked(); emit databaseUnlocked();
#ifdef WITH_XC_SSHAGENT
sshAgent()->databaseUnlocked(m_db);
#endif
if (senderDialog && senderDialog->intent() == DatabaseOpenDialog::Intent::AutoType) { if (senderDialog && senderDialog->intent() == DatabaseOpenDialog::Intent::AutoType) {
QList<QSharedPointer<Database>> dbList; QList<QSharedPointer<Database>> dbList;
dbList.append(m_db); dbList.append(m_db);
@ -1599,6 +1599,10 @@ bool DatabaseWidget::lock()
m_entryBeforeLock = currentEntry->uuid(); m_entryBeforeLock = currentEntry->uuid();
} }
#ifdef WITH_XC_SSHAGENT
sshAgent()->databaseLocked(m_db);
#endif
endSearch(); endSearch();
clearAllWidgets(); clearAllWidgets();
switchToOpenDatabase(m_db->filePath()); switchToOpenDatabase(m_db->filePath());

View File

@ -411,7 +411,6 @@ MainWindow::MainWindow()
connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateWindowTitle())); connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateWindowTitle()));
connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(databaseTabChanged(int))); connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(databaseTabChanged(int)));
connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(setMenuActionState())); connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(setMenuActionState()));
connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateTrayIcon()));
connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), SLOT(databaseStatusChanged(DatabaseWidget*))); connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), SLOT(databaseStatusChanged(DatabaseWidget*)));
connect(m_ui->tabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), SLOT(databaseStatusChanged(DatabaseWidget*))); connect(m_ui->tabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), SLOT(databaseStatusChanged(DatabaseWidget*)));
connect(m_ui->tabWidget, SIGNAL(tabVisibilityChanged(bool)), SLOT(updateToolbarSeparatorVisibility())); connect(m_ui->tabWidget, SIGNAL(tabVisibilityChanged(bool)), SLOT(updateToolbarSeparatorVisibility()));
@ -565,6 +564,9 @@ MainWindow::MainWindow()
MessageWidget::Error); MessageWidget::Error);
} }
// Properly shutdown on logoff, restart, and shutdown
connect(qApp, &QGuiApplication::commitDataRequest, this, [this] { m_appExitCalled = true; });
#if defined(KEEPASSXC_BUILD_TYPE_SNAPSHOT) || defined(KEEPASSXC_BUILD_TYPE_PRE_RELEASE) #if defined(KEEPASSXC_BUILD_TYPE_SNAPSHOT) || defined(KEEPASSXC_BUILD_TYPE_PRE_RELEASE)
auto* hidePreRelWarn = new QAction(tr("Don't show again for this version"), m_ui->globalMessageWidget); auto* hidePreRelWarn = new QAction(tr("Don't show again for this version"), m_ui->globalMessageWidget);
m_ui->globalMessageWidget->addAction(hidePreRelWarn); m_ui->globalMessageWidget->addAction(hidePreRelWarn);
@ -617,6 +619,9 @@ MainWindow::MainWindow()
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
#ifdef WITH_XC_SSHAGENT
sshAgent()->removeAllIdentities();
#endif
} }
/** /**
@ -968,11 +973,20 @@ void MainWindow::updateWindowTitle()
setWindowTitle(windowTitle); setWindowTitle(windowTitle);
setWindowModified(isModified); setWindowModified(isModified);
updateTrayIcon();
} }
void MainWindow::showAboutDialog() void MainWindow::showAboutDialog()
{ {
auto* aboutDialog = new AboutDialog(this); auto* aboutDialog = new AboutDialog(this);
// Auto close the about dialog before attempting database locks
if (m_ui->tabWidget->currentDatabaseWidget()) {
connect(m_ui->tabWidget->currentDatabaseWidget(),
&DatabaseWidget::databaseLockRequested,
aboutDialog,
&AboutDialog::close);
}
aboutDialog->open(); aboutDialog->open();
} }
@ -1208,8 +1222,7 @@ void MainWindow::closeEvent(QCloseEvent* event)
void MainWindow::changeEvent(QEvent* event) void MainWindow::changeEvent(QEvent* event)
{ {
if ((event->type() == QEvent::WindowStateChange) && isMinimized()) { if ((event->type() == QEvent::WindowStateChange) && isMinimized()) {
if (isTrayIconEnabled() && m_trayIcon && m_trayIcon->isVisible() if (isTrayIconEnabled() && config()->get(Config::GUI_MinimizeToTray).toBool()) {
&& config()->get(Config::GUI_MinimizeToTray).toBool()) {
event->ignore(); event->ignore();
hide(); hide();
} }
@ -1309,9 +1322,7 @@ bool MainWindow::saveLastDatabases()
void MainWindow::updateTrayIcon() void MainWindow::updateTrayIcon()
{ {
if (isTrayIconEnabled()) { if (config()->get(Config::GUI_ShowTrayIcon).toBool()) {
QApplication::setQuitOnLastWindowClosed(false);
if (!m_trayIcon) { if (!m_trayIcon) {
m_trayIcon = new QSystemTrayIcon(this); m_trayIcon = new QSystemTrayIcon(this);
auto* menu = new QMenu(this); auto* menu = new QMenu(this);
@ -1323,40 +1334,46 @@ void MainWindow::updateTrayIcon()
menu->addAction(m_ui->actionLockDatabases); menu->addAction(m_ui->actionLockDatabases);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
QAction* actionQuit = new QAction(tr("Quit KeePassXC"), menu); auto actionQuit = new QAction(tr("Quit KeePassXC"), menu);
menu->addAction(actionQuit);
connect(actionQuit, SIGNAL(triggered()), SLOT(appExit())); connect(actionQuit, SIGNAL(triggered()), SLOT(appExit()));
menu->addAction(actionQuit);
#else #else
menu->addAction(m_ui->actionQuit); menu->addAction(m_ui->actionQuit);
#endif #endif
m_trayIcon->setContextMenu(menu);
connect(m_trayIcon, connect(m_trayIcon,
SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
SLOT(trayIconTriggered(QSystemTrayIcon::ActivationReason))); SLOT(trayIconTriggered(QSystemTrayIcon::ActivationReason)));
connect(actionToggle, SIGNAL(triggered()), SLOT(toggleWindow())); connect(actionToggle, SIGNAL(triggered()), SLOT(toggleWindow()));
m_trayIcon->setContextMenu(menu);
m_trayIcon->setIcon(icons()->trayIcon());
m_trayIcon->show();
} }
if (m_ui->tabWidget->count() == 0) { if (m_ui->tabWidget->hasLockableDatabases()) {
m_trayIcon->setIcon(icons()->trayIcon());
} else if (m_ui->tabWidget->hasLockableDatabases()) {
m_trayIcon->setIcon(icons()->trayIconUnlocked()); m_trayIcon->setIcon(icons()->trayIconUnlocked());
} else { } else {
m_trayIcon->setIcon(icons()->trayIconLocked()); m_trayIcon->setIcon(icons()->trayIconLocked());
} }
} else {
QApplication::setQuitOnLastWindowClosed(true);
m_trayIcon->setToolTip(windowTitle().replace("[*]", isWindowModified() ? "*" : ""));
m_trayIcon->show();
if (!isTrayIconEnabled() || !QSystemTrayIcon::isSystemTrayAvailable()) {
// Try to show tray icon after 5 seconds, try 5 times
// This can happen if KeePassXC starts before the system tray is available
static int trayIconAttempts = 0;
if (trayIconAttempts < 5) {
QTimer::singleShot(5000, this, &MainWindow::updateTrayIcon);
++trayIconAttempts;
}
}
} else {
if (m_trayIcon) { if (m_trayIcon) {
m_trayIcon->hide(); m_trayIcon->hide();
delete m_trayIcon; delete m_trayIcon;
} }
} }
QApplication::setQuitOnLastWindowClosed(!isTrayIconEnabled());
} }
void MainWindow::obtainContextFocusLock() void MainWindow::obtainContextFocusLock()
@ -1598,7 +1615,7 @@ void MainWindow::forgetTouchIDAfterInactivity()
bool MainWindow::isTrayIconEnabled() const bool MainWindow::isTrayIconEnabled() const
{ {
return config()->get(Config::GUI_ShowTrayIcon).toBool() && QSystemTrayIcon::isSystemTrayAvailable(); return m_trayIcon && m_trayIcon->isVisible();
} }
void MainWindow::displayGlobalMessage(const QString& text, void MainWindow::displayGlobalMessage(const QString& text,
@ -1833,9 +1850,11 @@ bool MainWindowEventFilter::eventFilter(QObject* watched, QEvent* event)
if (event->type() == QEvent::MouseButtonPress) { if (event->type() == QEvent::MouseButtonPress) {
if (watched == mainWindow->m_ui->menubar) { if (watched == mainWindow->m_ui->menubar) {
mainWindow->windowHandle()->startSystemMove(); auto* m = static_cast<QMouseEvent*>(event);
// Continue processing events, so menus keep working. if (!mainWindow->m_ui->menubar->actionAt(m->pos())) {
return false; mainWindow->windowHandle()->startSystemMove();
return false;
}
} else if (watched == mainWindow->m_ui->toolBar) { } else if (watched == mainWindow->m_ui->toolBar) {
if (!mainWindow->m_ui->toolBar->isMovable() || mainWindow->m_ui->toolBar->cursor() != Qt::SizeAllCursor) { if (!mainWindow->m_ui->toolBar->isMovable() || mainWindow->m_ui->toolBar->cursor() != Qt::SizeAllCursor) {
mainWindow->windowHandle()->startSystemMove(); mainWindow->windowHandle()->startSystemMove();

View File

@ -259,6 +259,9 @@ QProgressBar::chunk {
<property name="minimum"> <property name="minimum">
<number>1</number> <number>1</number>
</property> </property>
<property name="maximum">
<number>999</number>
</property>
<property name="value"> <property name="value">
<number>20</number> <number>20</number>
</property> </property>

View File

@ -115,13 +115,18 @@ void NixUtils::setLaunchAtStartup(bool enable)
qWarning("Failed to create autostart desktop file."); qWarning("Failed to create autostart desktop file.");
return; return;
} }
const QString appImagePath = QString::fromLocal8Bit(qgetenv("APPIMAGE"));
const bool isAppImage = !appImagePath.isNull() && QFile::exists(appImagePath);
const QString executeablePath = isAppImage ? appImagePath : QApplication::applicationFilePath();
QTextStream stream(&desktopFile); QTextStream stream(&desktopFile);
stream.setCodec("UTF-8"); stream.setCodec("UTF-8");
stream << QStringLiteral("[Desktop Entry]") << '\n' stream << QStringLiteral("[Desktop Entry]") << '\n'
<< QStringLiteral("Name=") << QApplication::applicationDisplayName() << '\n' << QStringLiteral("Name=") << QApplication::applicationDisplayName() << '\n'
<< QStringLiteral("GenericName=") << tr("Password Manager") << '\n' << QStringLiteral("GenericName=") << tr("Password Manager") << '\n'
<< QStringLiteral("Exec=") << QApplication::applicationFilePath() << '\n' << QStringLiteral("Exec=") << executeablePath << '\n'
<< QStringLiteral("TryExec=") << QApplication::applicationFilePath() << '\n' << QStringLiteral("TryExec=") << executeablePath << '\n'
<< QStringLiteral("Icon=") << QApplication::applicationName().toLower() << '\n' << QStringLiteral("Icon=") << QApplication::applicationName().toLower() << '\n'
<< QStringLiteral("StartupWMClass=keepassxc") << '\n' << QStringLiteral("StartupWMClass=keepassxc") << '\n'
<< QStringLiteral("StartupNotify=true") << '\n' << QStringLiteral("StartupNotify=true") << '\n'

View File

@ -19,6 +19,9 @@
#include "SSHAgent.h" #include "SSHAgent.h"
#include "core/Config.h" #include "core/Config.h"
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/ssh/BinaryStream.h" #include "crypto/ssh/BinaryStream.h"
#include "crypto/ssh/OpenSSHKey.h" #include "crypto/ssh/OpenSSHKey.h"
#include "sshagent/KeeAgentSettings.h" #include "sshagent/KeeAgentSettings.h"
@ -31,11 +34,6 @@
Q_GLOBAL_STATIC(SSHAgent, s_sshAgent); Q_GLOBAL_STATIC(SSHAgent, s_sshAgent);
SSHAgent::~SSHAgent()
{
removeAllIdentities();
}
SSHAgent* SSHAgent::instance() SSHAgent* SSHAgent::instance()
{ {
return s_sshAgent; return s_sshAgent;
@ -427,18 +425,15 @@ void SSHAgent::setAutoRemoveOnLock(const OpenSSHKey& key, bool autoRemove)
} }
} }
void SSHAgent::databaseLocked() void SSHAgent::databaseLocked(QSharedPointer<Database> db)
{ {
auto* widget = qobject_cast<DatabaseWidget*>(sender()); if (!db) {
if (!widget) {
return; return;
} }
QUuid databaseUuid = widget->database()->uuid();
auto it = m_addedKeys.begin(); auto it = m_addedKeys.begin();
while (it != m_addedKeys.end()) { while (it != m_addedKeys.end()) {
if (it.value().first != databaseUuid) { if (it.value().first != db->uuid()) {
++it; ++it;
continue; continue;
} }
@ -452,16 +447,14 @@ void SSHAgent::databaseLocked()
} }
} }
void SSHAgent::databaseUnlocked() void SSHAgent::databaseUnlocked(QSharedPointer<Database> db)
{ {
auto* widget = qobject_cast<DatabaseWidget*>(sender()); if (!db || !isEnabled()) {
if (!widget) {
return; return;
} }
for (Entry* e : widget->database()->rootGroup()->entriesRecursive()) { for (Entry* e : db->rootGroup()->entriesRecursive()) {
if (widget->database()->metadata()->recycleBinEnabled() if (db->metadata()->recycleBinEnabled() && e->group() == db->metadata()->recycleBin()) {
&& e->group() == widget->database()->metadata()->recycleBin()) {
continue; continue;
} }
@ -483,7 +476,7 @@ void SSHAgent::databaseUnlocked()
// Add key to agent; ignore errors if we have previously added the key // Add key to agent; ignore errors if we have previously added the key
bool known_key = m_addedKeys.contains(key); bool known_key = m_addedKeys.contains(key);
if (!addIdentity(key, settings, widget->database()->uuid()) && !known_key) { if (!addIdentity(key, settings, db->uuid()) && !known_key) {
emit error(m_error); emit error(m_error);
} }
} }

View File

@ -24,7 +24,6 @@
#include <QtCore> #include <QtCore>
#include "crypto/ssh/OpenSSHKey.h" #include "crypto/ssh/OpenSSHKey.h"
#include "gui/DatabaseWidget.h"
#include "sshagent/KeeAgentSettings.h" #include "sshagent/KeeAgentSettings.h"
class SSHAgent : public QObject class SSHAgent : public QObject
@ -32,7 +31,7 @@ class SSHAgent : public QObject
Q_OBJECT Q_OBJECT
public: public:
~SSHAgent() override; ~SSHAgent() override = default;
static SSHAgent* instance(); static SSHAgent* instance();
bool isEnabled() const; bool isEnabled() const;
@ -59,8 +58,8 @@ signals:
void enabledChanged(bool enabled); void enabledChanged(bool enabled);
public slots: public slots:
void databaseLocked(); void databaseLocked(QSharedPointer<Database> db);
void databaseUnlocked(); void databaseUnlocked(QSharedPointer<Database> db);
private: private:
const quint8 SSH_AGENT_FAILURE = 5; const quint8 SSH_AGENT_FAILURE = 5;