diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 432525ad6..fc14bafb3 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -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& 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 diff --git a/src/gui/Application.h b/src/gui/Application.h index 349c93923..b00f8ce99 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -23,6 +23,7 @@ #include #include #include +#include #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&); + bool m_alreadyRunning; bool m_darkTheme = false; QLockFile* m_lockFile; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 25b96c5ae..6ccd759ab 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -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(qApp), &Application::openFile, this, &MainWindow::openDatabase); // Setup the status bar statusBar()->setFixedHeight(24); diff --git a/src/main.cpp b/src/main.cpp index 2c4da4c1f..f9af22ecc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,6 +49,18 @@ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) #include #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