This commit is contained in:
Michael Ziminsky (Z) 2026-03-09 16:01:12 +01:00 committed by GitHub
commit 246ca20f9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 75 additions and 60 deletions

View file

@ -49,6 +49,13 @@ namespace
int g_OriginalFontSize = 0;
} // namespace
enum Application::SocketCmd : quint32
{
OpenFiles = 1,
LockAll,
Unlock,
};
Application::Application(int& argc, char** argv)
: QApplication(argc, argv)
#ifdef Q_OS_UNIX
@ -56,11 +63,7 @@ Application::Application(int& argc, char** argv)
#endif
, m_alreadyRunning(false)
, m_lockFile(nullptr)
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
{
#else
{
#endif
#if defined(Q_OS_UNIX)
registerUnixSignals();
#endif
@ -324,13 +327,12 @@ void Application::socketReadyRead()
return;
}
QStringList fileNames;
quint32 id;
SocketCmd id;
in >> id;
// TODO: move constants to enum
switch (id) {
case 1:
case SocketCmd::OpenFiles: {
QStringList fileNames;
in >> fileNames;
for (const QString& fileName : asConst(fileNames)) {
const QFileInfo fInfo(fileName);
@ -338,11 +340,16 @@ void Application::socketReadyRead()
emit openFile(fileName);
}
}
break;
case 2:
}
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();
@ -350,20 +357,10 @@ 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;
}
/**
* Send to-open file names to the running UI instance
*
* @param fileNames - list of file names to open
* @return true if all operations succeeded (connection made, data sent, connection closed)
*/
bool Application::sendFileNamesToRunningInstance(const QStringList& fileNames)
bool Application::sendSocketCommand(SocketCmd id, const std::function<void(QDataStream&)>& caller)
{
QLocalSocket client;
client.connectToServer(m_socketName);
@ -376,8 +373,8 @@ bool Application::sendFileNamesToRunningInstance(const QStringList& fileNames)
QDataStream out(&data, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_0);
out << quint32(0); // reserve space for block size
out << quint32(1); // ID for file name send. TODO: move to enum
out << fileNames; // send file names to be opened
out << id; // ID of command being sent.
caller(out); // Pass to caller to add any additional data
out.device()->seek(0);
out << quint32(data.size() - sizeof(quint32)); // replace the previous constant 0 with block size
@ -388,6 +385,17 @@ bool Application::sendFileNamesToRunningInstance(const QStringList& fileNames)
return writeOk && disconnected;
}
/**
* Send to-open file names to the running UI instance
*
* @param fileNames - list of file names to open
* @return true if all operations succeeded (connection made, data sent, connection closed)
*/
bool Application::sendFileNamesToRunningInstance(const QStringList& fileNames)
{
return this->sendSocketCommand(SocketCmd::OpenFiles, [&](QDataStream& out) { out << fileNames; });
}
/**
* Locks all open databases in the running instance
*
@ -395,29 +403,18 @@ bool Application::sendFileNamesToRunningInstance(const QStringList& fileNames)
*/
bool Application::sendLockToInstance()
{
// Make a connection to avoid SIGSEGV
QLocalSocket client;
client.connectToServer(m_socketName);
const bool connected = client.waitForConnected(WaitTimeoutMSec);
if (!connected) {
return false;
}
return this->sendSocketCommand(SocketCmd::LockAll, [](QDataStream&) { /* No Data */ });
}
// Send lock signal
QByteArray data;
QDataStream out(&data, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_0);
out << quint32(0); // reserve space for block size
out << quint32(2); // ID for database lock. TODO: move to enum
out.device()->seek(0);
out << quint32(data.size() - sizeof(quint32)); // replace the previous constant 0 with block size
// Finish gracefully
const bool writeOk = client.write(data) != -1 && client.waitForBytesWritten(WaitTimeoutMSec);
client.disconnectFromServer();
const bool disconnected =
client.state() == QLocalSocket::UnconnectedState || client.waitForConnected(WaitTimeoutMSec);
return writeOk && disconnected;
/**
* 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

View file

@ -23,6 +23,7 @@
#include <QApplication>
#include <QString>
#include <QtNetwork/qlocalserver.h>
#include <functional>
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
@ -52,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();
@ -78,6 +80,10 @@ private:
static void handleUnixSignal(int sig);
static int unixSignalSocket[2];
#endif
enum SocketCmd : quint32;
bool sendSocketCommand(SocketCmd id, const std::function<void(QDataStream&)>&);
bool m_alreadyRunning;
bool m_darkTheme = false;
QLockFile* m_lockFile;

View file

@ -671,8 +671,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