mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-06 05:06:04 -05:00
Allow selecting any open database in unlock dialog
* Closes #2322 * Show locked databases in tabbed interface in unlock dialog for browser and auto-type workflows. * Make the DatabaseOpenDialog window Application-Modal so that it blocks input to the main UI when the dialog is open. This reduces corner cases by avoiding the possibility of databases getting closed or unlocked behind the open dialog.
This commit is contained in:
parent
37d29b5e8c
commit
53dcafaa58
@ -18,9 +18,12 @@
|
||||
#include "DatabaseOpenDialog.h"
|
||||
|
||||
#include "DatabaseOpenWidget.h"
|
||||
#include "DatabaseTabWidget.h"
|
||||
#include "DatabaseWidget.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QLayout>
|
||||
#include <QShortcut>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QtPlatformHeaders/QWindowsWindowFunctions>
|
||||
@ -29,37 +32,109 @@
|
||||
DatabaseOpenDialog::DatabaseOpenDialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_view(new DatabaseOpenWidget(this))
|
||||
, m_tabBar(new QTabBar(this))
|
||||
{
|
||||
setWindowTitle(tr("Unlock Database - KeePassXC"));
|
||||
setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint);
|
||||
// block input to the main window/application while the dialog is open
|
||||
setWindowModality(Qt::ApplicationModal);
|
||||
#ifdef Q_OS_WIN
|
||||
QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow);
|
||||
#endif
|
||||
connect(m_view, SIGNAL(dialogFinished(bool)), this, SLOT(complete(bool)));
|
||||
connect(m_view, &DatabaseOpenWidget::dialogFinished, this, &DatabaseOpenDialog::complete);
|
||||
|
||||
m_tabBar->setAutoHide(true);
|
||||
m_tabBar->setExpanding(false);
|
||||
connect(m_tabBar, &QTabBar::currentChanged, this, &DatabaseOpenDialog::tabChanged);
|
||||
|
||||
auto* layout = new QVBoxLayout();
|
||||
layout->setMargin(0);
|
||||
setLayout(layout);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(m_tabBar);
|
||||
layout->addWidget(m_view);
|
||||
setLayout(layout);
|
||||
setMinimumWidth(700);
|
||||
|
||||
// set up Ctrl+PageUp and Ctrl+PageDown shortcuts to cycle tabs
|
||||
auto* shortcut = new QShortcut(Qt::CTRL + Qt::Key_PageUp, this);
|
||||
shortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() { selectTabOffset(-1); });
|
||||
shortcut = new QShortcut(Qt::CTRL + Qt::Key_PageDown, this);
|
||||
shortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() { selectTabOffset(1); });
|
||||
}
|
||||
|
||||
void DatabaseOpenDialog::setFilePath(const QString& filePath)
|
||||
void DatabaseOpenDialog::selectTabOffset(int offset)
|
||||
{
|
||||
m_view->load(filePath);
|
||||
if (offset == 0 || m_tabBar->count() <= 1) {
|
||||
return;
|
||||
}
|
||||
int tab = m_tabBar->currentIndex() + offset;
|
||||
int last = m_tabBar->count() - 1;
|
||||
if (tab < 0) {
|
||||
tab = last;
|
||||
} else if (tab > last) {
|
||||
tab = 0;
|
||||
}
|
||||
m_tabBar->setCurrentIndex(tab);
|
||||
}
|
||||
|
||||
void DatabaseOpenDialog::addDatabaseTab(DatabaseWidget* dbWidget)
|
||||
{
|
||||
Q_ASSERT(dbWidget);
|
||||
if (!dbWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// important - we must add the DB widget first, because addTab will fire
|
||||
// tabChanged immediately which will look for a dbWidget in the list
|
||||
m_tabDbWidgets.append(dbWidget);
|
||||
QFileInfo fileInfo(dbWidget->database()->filePath());
|
||||
m_tabBar->addTab(fileInfo.fileName());
|
||||
Q_ASSERT(m_tabDbWidgets.count() == m_tabBar->count());
|
||||
}
|
||||
|
||||
void DatabaseOpenDialog::setActiveDatabaseTab(DatabaseWidget* dbWidget)
|
||||
{
|
||||
if (!dbWidget) {
|
||||
return;
|
||||
}
|
||||
int index = m_tabDbWidgets.indexOf(dbWidget);
|
||||
if (index != -1) {
|
||||
m_tabBar->setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseOpenDialog::tabChanged(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_tabDbWidgets.count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_tabDbWidgets.count() == m_tabBar->count()) {
|
||||
DatabaseWidget* dbWidget = m_tabDbWidgets[index];
|
||||
setTarget(dbWidget, dbWidget->database()->filePath());
|
||||
} else {
|
||||
// if these list sizes don't match, there's a bug somewhere nearby
|
||||
qWarning("DatabaseOpenDialog: mismatch between tab count %d and DB count %d",
|
||||
m_tabBar->count(),
|
||||
m_tabDbWidgets.count());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set target DatabaseWidget to which signals are connected.
|
||||
*
|
||||
* @param dbWidget database widget
|
||||
* Sets the target DB and reloads the UI.
|
||||
*/
|
||||
void DatabaseOpenDialog::setTargetDatabaseWidget(DatabaseWidget* dbWidget)
|
||||
void DatabaseOpenDialog::setTarget(DatabaseWidget* dbWidget, const QString& filePath)
|
||||
{
|
||||
if (m_dbWidget) {
|
||||
disconnect(this, nullptr, m_dbWidget, nullptr);
|
||||
// reconnect finished signal to new dbWidget, then reload the UI
|
||||
if (m_currentDbWidget) {
|
||||
disconnect(this, &DatabaseOpenDialog::dialogFinished, m_currentDbWidget, nullptr);
|
||||
}
|
||||
m_dbWidget = dbWidget;
|
||||
connect(this, &DatabaseOpenDialog::dialogFinished, dbWidget, &DatabaseWidget::unlockDatabase);
|
||||
|
||||
m_currentDbWidget = dbWidget;
|
||||
m_view->load(filePath);
|
||||
}
|
||||
|
||||
void DatabaseOpenDialog::setIntent(DatabaseOpenDialog::Intent intent)
|
||||
@ -77,13 +152,21 @@ void DatabaseOpenDialog::clearForms()
|
||||
m_view->clearForms();
|
||||
m_db.reset();
|
||||
m_intent = Intent::None;
|
||||
if (m_dbWidget) {
|
||||
disconnect(this, nullptr, m_dbWidget, nullptr);
|
||||
m_dbWidget = nullptr;
|
||||
if (m_currentDbWidget) {
|
||||
disconnect(this, &DatabaseOpenDialog::dialogFinished, m_currentDbWidget, nullptr);
|
||||
}
|
||||
m_currentDbWidget.clear();
|
||||
m_tabDbWidgets.clear();
|
||||
|
||||
// block signals while removing tabs so that tabChanged doesn't get called
|
||||
m_tabBar->blockSignals(true);
|
||||
while (m_tabBar->count() > 0) {
|
||||
m_tabBar->removeTab(0);
|
||||
}
|
||||
m_tabBar->blockSignals(false);
|
||||
}
|
||||
|
||||
QSharedPointer<Database> DatabaseOpenDialog::database()
|
||||
QSharedPointer<Database> DatabaseOpenDialog::database() const
|
||||
{
|
||||
return m_db;
|
||||
}
|
||||
@ -98,6 +181,7 @@ void DatabaseOpenDialog::complete(bool accepted)
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
emit dialogFinished(accepted, m_dbWidget);
|
||||
|
||||
emit dialogFinished(accepted, m_currentDbWidget);
|
||||
clearForms();
|
||||
}
|
||||
|
@ -21,7 +21,9 @@
|
||||
#include "core/Global.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QTabBar>
|
||||
|
||||
class Database;
|
||||
class DatabaseWidget;
|
||||
@ -41,11 +43,12 @@ public:
|
||||
};
|
||||
|
||||
explicit DatabaseOpenDialog(QWidget* parent = nullptr);
|
||||
void setFilePath(const QString& filePath);
|
||||
void setTargetDatabaseWidget(DatabaseWidget* dbWidget);
|
||||
void setTarget(DatabaseWidget* dbWidget, const QString& filePath);
|
||||
void addDatabaseTab(DatabaseWidget* dbWidget);
|
||||
void setActiveDatabaseTab(DatabaseWidget* dbWidget);
|
||||
void setIntent(Intent intent);
|
||||
Intent intent() const;
|
||||
QSharedPointer<Database> database();
|
||||
QSharedPointer<Database> database() const;
|
||||
void clearForms();
|
||||
|
||||
signals:
|
||||
@ -53,11 +56,16 @@ signals:
|
||||
|
||||
public slots:
|
||||
void complete(bool accepted);
|
||||
void tabChanged(int index);
|
||||
|
||||
private:
|
||||
void selectTabOffset(int offset);
|
||||
|
||||
QPointer<DatabaseOpenWidget> m_view;
|
||||
QPointer<QTabBar> m_tabBar;
|
||||
QSharedPointer<Database> m_db;
|
||||
QPointer<DatabaseWidget> m_dbWidget;
|
||||
QList<QPointer<DatabaseWidget>> m_tabDbWidgets;
|
||||
QPointer<DatabaseWidget> m_currentDbWidget;
|
||||
Intent m_intent = Intent::None;
|
||||
};
|
||||
|
||||
|
@ -51,7 +51,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
|
||||
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
|
||||
connect(m_databaseOpenDialog.data(), &DatabaseOpenDialog::dialogFinished,
|
||||
this, &DatabaseTabWidget::databaseUnlockDialogFinished);
|
||||
this, &DatabaseTabWidget::handleDatabaseUnlockDialogFinished);
|
||||
// clang-format on
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
@ -664,11 +664,43 @@ void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget,
|
||||
DatabaseOpenDialog::Intent intent,
|
||||
const QString& filePath)
|
||||
{
|
||||
m_databaseOpenDialog->setTargetDatabaseWidget(dbWidget);
|
||||
m_databaseOpenDialog->clearForms();
|
||||
m_databaseOpenDialog->setIntent(intent);
|
||||
m_databaseOpenDialog->setFilePath(filePath);
|
||||
m_databaseOpenDialog->setTarget(dbWidget, filePath);
|
||||
displayUnlockDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a database with an unlock popup dialog.
|
||||
* The dialog allows the user to select any open & locked database.
|
||||
*
|
||||
* @param intent intent for unlocking
|
||||
*/
|
||||
void DatabaseTabWidget::unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent intent)
|
||||
{
|
||||
m_databaseOpenDialog->clearForms();
|
||||
m_databaseOpenDialog->setIntent(intent);
|
||||
|
||||
// add a tab to the dialog for each open unlocked database
|
||||
for (int i = 0, c = count(); i < c; ++i) {
|
||||
auto* dbWidget = databaseWidgetFromIndex(i);
|
||||
if (dbWidget && dbWidget->isLocked()) {
|
||||
m_databaseOpenDialog->addDatabaseTab(dbWidget);
|
||||
}
|
||||
}
|
||||
// default to the current tab
|
||||
m_databaseOpenDialog->setActiveDatabaseTab(currentDatabaseWidget());
|
||||
displayUnlockDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the unlock dialog after it's been initialized.
|
||||
* This is an internal method, it should only be called by unlockDatabaseInDialog or unlockAnyDatabaseInDialog.
|
||||
*/
|
||||
void DatabaseTabWidget::displayUnlockDialog()
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
auto intent = m_databaseOpenDialog->intent();
|
||||
if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) {
|
||||
macUtils()->raiseOwnWindow();
|
||||
Tools::wait(200);
|
||||
@ -680,6 +712,29 @@ void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget,
|
||||
m_databaseOpenDialog->activateWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to take when the unlock dialog has completed.
|
||||
*/
|
||||
void DatabaseTabWidget::handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget)
|
||||
{
|
||||
// change the active tab to the database that was just unlocked in the dialog
|
||||
auto intent = m_databaseOpenDialog->intent();
|
||||
if (accepted && intent != DatabaseOpenDialog::Intent::Merge) {
|
||||
int index = indexOf(dbWidget);
|
||||
if (index != -1) {
|
||||
setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
// if unlocked for AutoType, set pending lock flag if needed
|
||||
if (intent == DatabaseOpenDialog::Intent::AutoType && config()->get(Config::Security_RelockAutoType).toBool()) {
|
||||
m_dbWidgetPendingLock = dbWidget;
|
||||
}
|
||||
|
||||
// signal other objects that the dialog finished
|
||||
emit databaseUnlockDialogFinished(accepted, dbWidget);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function relock the pending database when autotype has been performed successfully
|
||||
* A database is marked as pending when it's unlocked after a global Auto-Type invocation
|
||||
@ -737,23 +792,26 @@ void DatabaseTabWidget::emitDatabaseLockChanged()
|
||||
|
||||
void DatabaseTabWidget::performGlobalAutoType()
|
||||
{
|
||||
QList<QSharedPointer<Database>> unlockedDatabases;
|
||||
|
||||
for (int i = 0, c = count(); i < c; ++i) {
|
||||
auto* dbWidget = databaseWidgetFromIndex(i);
|
||||
if (!dbWidget->isLocked()) {
|
||||
unlockedDatabases.append(dbWidget->database());
|
||||
auto currentDbWidget = currentDatabaseWidget();
|
||||
if (!currentDbWidget) {
|
||||
// no open databases, nothing to do
|
||||
return;
|
||||
} else if (currentDbWidget->isLocked()) {
|
||||
// Current database tab is locked, match behavior of browser unlock - prompt with
|
||||
// the unlock dialog even if there are additional unlocked open database tabs.
|
||||
unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::AutoType);
|
||||
} else {
|
||||
// current database is unlocked, use it for AutoType along with any other unlocked databases
|
||||
QList<QSharedPointer<Database>> unlockedDatabases;
|
||||
for (int i = 0, c = count(); i < c; ++i) {
|
||||
auto* dbWidget = databaseWidgetFromIndex(i);
|
||||
if (!dbWidget->isLocked()) {
|
||||
unlockedDatabases.append(dbWidget->database());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: allow for database selection during Auto-Type instead of using the current tab
|
||||
if (!unlockedDatabases.isEmpty()) {
|
||||
Q_ASSERT(!unlockedDatabases.isEmpty());
|
||||
autoType()->performGlobalAutoType(unlockedDatabases);
|
||||
} else if (count() > 0) {
|
||||
if (config()->get(Config::Security_RelockAutoType).toBool()) {
|
||||
m_dbWidgetPendingLock = currentDatabaseWidget();
|
||||
}
|
||||
unlockDatabaseInDialog(currentDatabaseWidget(), DatabaseOpenDialog::Intent::AutoType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -761,6 +819,6 @@ void DatabaseTabWidget::performBrowserUnlock()
|
||||
{
|
||||
auto dbWidget = currentDatabaseWidget();
|
||||
if (dbWidget && dbWidget->isLocked()) {
|
||||
unlockDatabaseInDialog(dbWidget, DatabaseOpenDialog::Intent::Browser);
|
||||
unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent::Browser);
|
||||
}
|
||||
}
|
||||
|
@ -99,11 +99,14 @@ private slots:
|
||||
void toggleTabbar();
|
||||
void emitActiveDatabaseChanged();
|
||||
void emitDatabaseLockChanged();
|
||||
void handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);
|
||||
|
||||
private:
|
||||
QSharedPointer<Database> execNewDatabaseWizard();
|
||||
void updateLastDatabases(const QString& filename);
|
||||
bool warnOnExport();
|
||||
void unlockAnyDatabaseInDialog(DatabaseOpenDialog::Intent intent);
|
||||
void displayUnlockDialog();
|
||||
|
||||
QPointer<DatabaseWidgetStateSync> m_dbWidgetStateSync;
|
||||
QPointer<DatabaseWidget> m_dbWidgetPendingLock;
|
||||
|
@ -186,6 +186,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
connect(m_opVaultOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
|
||||
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
|
||||
connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged()));
|
||||
connect(this, SIGNAL(requestGlobalAutoType()), parent, SLOT(performGlobalAutoType()));
|
||||
// clang-format on
|
||||
|
||||
connectDatabaseSignals();
|
||||
@ -1109,9 +1110,10 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
||||
}
|
||||
|
||||
if (senderDialog && senderDialog->intent() == DatabaseOpenDialog::Intent::AutoType) {
|
||||
QList<QSharedPointer<Database>> dbList;
|
||||
dbList.append(m_db);
|
||||
autoType()->performGlobalAutoType(dbList);
|
||||
// Rather than starting AutoType directly for this database, signal the parent DatabaseTabWidget to
|
||||
// restart AutoType now that this database is unlocked, so that other open+unlocked databases
|
||||
// can be included in the search.
|
||||
emit requestGlobalAutoType();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,6 +151,7 @@ signals:
|
||||
void previewSplitterSizesChanged();
|
||||
void entryViewStateChanged();
|
||||
void clearSearch();
|
||||
void requestGlobalAutoType();
|
||||
|
||||
public slots:
|
||||
bool lock();
|
||||
|
Loading…
x
Reference in New Issue
Block a user