Support full DB unlock in single instance mode

Closes #2089
This commit is contained in:
Michael Ziminsky (Z) 2025-06-07 21:18:25 -07:00
parent dcdd599622
commit 4132a7efac
No known key found for this signature in database
4 changed files with 47 additions and 22 deletions

View file

@ -51,6 +51,7 @@ enum Application::SocketCmd : quint32
{
OpenFiles = 1,
LockAll,
Unlock,
};
Application::Application(int& argc, char** argv)
@ -327,8 +328,7 @@ void Application::socketReadyRead()
}
SocketCmd id;
// manual reinterpret_cast not needed for Qt 5.14+
in >> reinterpret_cast<typename std::underlying_type<SocketCmd>::type&>(id);
in >> id;
switch (id) {
case SocketCmd::OpenFiles: {
@ -345,6 +345,11 @@ void Application::socketReadyRead()
case SocketCmd::LockAll:
getMainWindow()->lockAllDatabases();
break;
case SocketCmd::Unlock:
QString filename, password, keyfile;
in >> filename >> password >> keyfile;
emit openFile(filename, password, keyfile);
break;
}
socket->deleteLater();
@ -352,10 +357,6 @@ void Application::socketReadyRead()
bool Application::isAlreadyRunning() const
{
#ifdef QT_DEBUG
// In DEBUG mode we can run unlimited instances
return false;
#endif
return config()->get(Config::SingleInstance).toBool() && m_alreadyRunning;
}
@ -392,7 +393,7 @@ bool Application::sendSocketCommand(SocketCmd id, const std::function<void(QData
*/
bool Application::sendFileNamesToRunningInstance(const QStringList& fileNames)
{
return this->sendSocketCommand(SocketCmd::OpenFiles, [fileNames](QDataStream& out) { out << fileNames; });
return this->sendSocketCommand(SocketCmd::OpenFiles, [&](QDataStream& out) { out << fileNames; });
}
/**
@ -405,6 +406,17 @@ bool Application::sendLockToInstance()
return this->sendSocketCommand(SocketCmd::LockAll, [](QDataStream&) { /* No Data */ });
}
/**
* Open and unlock a database file in the running instance
*
* @return true if the instance receives the request
*/
bool Application::sendUnlockToInstance(const QString& filename, const QString& password, const QString& keyfile)
{
return this->sendSocketCommand(SocketCmd::Unlock,
[&](QDataStream& out) { out << filename << password << keyfile; });
}
bool Application::isDarkTheme() const
{
return m_darkTheme;

View file

@ -53,11 +53,12 @@ public:
bool sendFileNamesToRunningInstance(const QStringList& fileNames);
bool sendLockToInstance();
bool sendUnlockToInstance(const QString& filename, const QString& password = {}, const QString& keyfile = {});
void restart();
signals:
void openFile(const QString& filename);
void openFile(const QString& filename, const QString& password = {}, const QString& keyfile = {});
void anotherInstanceStarted();
void applicationActivated();
void quitSignalReceived();

View file

@ -661,8 +661,8 @@ MainWindow::MainWindow()
connect(qApp, SIGNAL(anotherInstanceStarted()), this, SLOT(bringToFront()));
connect(qApp, SIGNAL(applicationActivated()), this, SLOT(bringToFront()));
connect(qApp, SIGNAL(openFile(QString)), this, SLOT(openDatabase(QString)));
connect(qApp, SIGNAL(quitSignalReceived()), this, SLOT(appExit()), Qt::DirectConnection);
connect(static_cast<Application*>(qApp), &Application::openFile, this, &MainWindow::openDatabase);
// Setup the status bar
statusBar()->setFixedHeight(24);

View file

@ -49,6 +49,18 @@ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
#include <windows.h>
#endif
namespace
{
QString promptPassword()
{
// we always need consume a line of STDIN if --pw-stdin is set to clear out the
// buffer for native messaging, even if the specified file does not exist
QTextStream out(stdout, QIODevice::WriteOnly);
out << QObject::tr("Database password: ") << Qt::flush;
return Utils::getPassword();
}
} // namespace
int main(int argc, char** argv)
{
QT_REQUIRE_VERSION(argc, argv, QT_VERSION_STR)
@ -140,9 +152,14 @@ int main(int argc, char** argv)
}
}
#endif
Utils::setDefaultTextStreams();
const bool pwstdin = parser.isSet(pwstdinOption);
const QString keyfile = parser.value(keyfileOption);
// Process single instance and early exit if already running
if (app.isAlreadyRunning()) {
qWarning() << QObject::tr("Another instance of KeePassXC is already running.").toUtf8().constData();
if (parser.isSet(lockOption)) {
if (app.sendLockToInstance()) {
qInfo() << QObject::tr("Databases have been locked.").toUtf8().constData();
@ -151,11 +168,13 @@ int main(int argc, char** argv)
return EXIT_FAILURE;
}
} else {
if (!fileNames.isEmpty()) {
app.sendFileNamesToRunningInstance(fileNames);
for (const QString& filename : fileNames) {
QString password;
if (pwstdin) {
password = promptPassword();
}
app.sendUnlockToInstance(filename, password, keyfile);
}
qWarning() << QObject::tr("Another instance of KeePassXC is already running.").toUtf8().constData();
}
return EXIT_SUCCESS;
}
@ -176,8 +195,6 @@ int main(int argc, char** argv)
return EXIT_FAILURE;
}
Utils::setDefaultTextStreams();
// Apply the configured theme before creating any GUI elements
app.applyTheme();
@ -195,17 +212,12 @@ int main(int argc, char** argv)
// This ensures any top-level windows (Main Window, Modal Dialogs, etc.) are excluded from screenshots
mainWindow.setAllowScreenCapture(parser.isSet(allowScreenCaptureOption));
const bool pwstdin = parser.isSet(pwstdinOption);
for (const QString& filename : fileNames) {
QString password;
if (pwstdin) {
// we always need consume a line of STDIN if --pw-stdin is set to clear out the
// buffer for native messaging, even if the specified file does not exist
QTextStream out(stdout, QIODevice::WriteOnly);
out << QObject::tr("Database password: ") << Qt::flush;
password = Utils::getPassword();
password = promptPassword();
}
mainWindow.openDatabase(filename, password, parser.value(keyfileOption));
mainWindow.openDatabase(filename, password, keyfile);
}
// start minimized if configured