diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index 98ae83a6..48cd0d8d 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -22,13 +22,15 @@ #include "core/Config.h" #include +#include #include -#include #include +#include #include #include #include "autotype/AutoType.h" +#include "core/Global.h" #if defined(Q_OS_UNIX) #include @@ -36,6 +38,11 @@ #include #endif +namespace { +constexpr int WaitTimeoutMSec = 150; +const char BlockSizeProperty[] = "blockSize"; +} + #if defined(Q_OS_UNIX) && !defined(Q_OS_OSX) class XcbEventFilter : public QAbstractNativeEventFilter { @@ -105,8 +112,8 @@ Application::Application(int& argc, char** argv) // In DEBUG mode don't interfere with Release instances identifier += "-DEBUG"; #endif - QString socketName = identifier + ".socket"; QString lockName = identifier + ".lock"; + m_socketName = identifier + ".socket"; // According to documentation we should use RuntimeLocation on *nixes, but even Qt doesn't respect // this and creates sockets in TempLocation, so let's be consistent. @@ -114,20 +121,22 @@ Application::Application(int& argc, char** argv) m_lockFile->setStaleLockTime(0); m_lockFile->tryLock(); + m_lockServer.setSocketOptions(QLocalServer::UserAccessOption); + connect(&m_lockServer, SIGNAL(newConnection()), this, SIGNAL(anotherInstanceStarted())); + connect(&m_lockServer, SIGNAL(newConnection()), this, SLOT(processIncomingConnection())); + switch (m_lockFile->error()) { case QLockFile::NoError: // No existing lock was found, start listener - m_lockServer.setSocketOptions(QLocalServer::UserAccessOption); - m_lockServer.listen(socketName); - connect(&m_lockServer, SIGNAL(newConnection()), this, SIGNAL(anotherInstanceStarted())); + m_lockServer.listen(m_socketName); break; case QLockFile::LockFailedError: { if (config()->get("SingleInstance").toBool()) { // Attempt to connect to the existing instance QLocalSocket client; - for (int i = 0; i < 3; i++) { - client.connectToServer(socketName); - if (client.waitForConnected(150)) { + for (int i = 0; i < 3; ++i) { + client.connectToServer(m_socketName); + if (client.waitForConnected(WaitTimeoutMSec)) { // Connection succeeded, this will raise the existing window if minimized client.abort(); m_alreadyRunning = true; @@ -145,9 +154,7 @@ Application::Application(int& argc, char** argv) m_lockFile->removeStaleLockFile(); m_lockFile->tryLock(); // start the listen server - m_lockServer.setSocketOptions(QLocalServer::UserAccessOption); - m_lockServer.listen(socketName); - connect(&m_lockServer, SIGNAL(newConnection()), this, SIGNAL(anotherInstanceStarted())); + m_lockServer.listen(m_socketName); } } break; @@ -187,12 +194,8 @@ bool Application::event(QEvent* event) } #ifdef Q_OS_MAC // restore main window when clicking on the docker icon - else if ((event->type() == QEvent::ApplicationActivate) && m_mainWindow) { - m_mainWindow->ensurePolished(); - m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized); - m_mainWindow->show(); - m_mainWindow->raise(); - m_mainWindow->activateWindow(); + else if (event->type() == QEvent::ApplicationActivate) { + emit applicationActivated(); } #endif @@ -247,12 +250,54 @@ void Application::quitBySignal() m_unixSignalNotifier->setEnabled(false); char buf; Q_UNUSED(::read(unixSignalSocket[1], &buf, sizeof(buf))); - - if (nullptr != m_mainWindow) - static_cast(m_mainWindow)->appExit(); + emit quitSignalReceived(); } #endif +void Application::processIncomingConnection() +{ + if (m_lockServer.hasPendingConnections()) { + QLocalSocket* socket = m_lockServer.nextPendingConnection(); + socket->setProperty(BlockSizeProperty, 0); + connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead())); + } +} + +void Application::socketReadyRead() +{ + QLocalSocket* socket = qobject_cast(sender()); + if (!socket) { + return; + } + + QDataStream in(socket); + in.setVersion(QDataStream::Qt_5_0); + + int blockSize = socket->property(BlockSizeProperty).toInt(); + if (blockSize == 0) { + // Relies on the fact that QDataStream format streams a quint32 into sizeof(quint32) bytes + if (socket->bytesAvailable() < qint64(sizeof(quint32))) { + return; + } + in >> blockSize; + } + + if (socket->bytesAvailable() < blockSize || in.atEnd()) { + socket->setProperty(BlockSizeProperty, blockSize); + return; + } + + QStringList fileNames; + in >> fileNames; + for (const QString& fileName: asConst(fileNames)) { + const QFileInfo fInfo(fileName); + if (fInfo.isFile() && fInfo.suffix().toLower() == "kdbx") { + emit openFile(fileName); + } + } + socket->deleteLater(); +} + bool Application::isAlreadyRunning() const { #ifdef QT_DEBUG @@ -262,3 +307,26 @@ bool Application::isAlreadyRunning() const return config()->get("SingleInstance").toBool() && m_alreadyRunning; } +bool Application::sendFileNamesToRunningInstance(const QStringList& fileNames) +{ + QLocalSocket client; + client.connectToServer(m_socketName); + const bool connected = client.waitForConnected(WaitTimeoutMSec); + if (!connected) { + return false; + } + + QByteArray data; + QDataStream out(&data, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_5_0); + out << quint32(0) + << fileNames; + out.device()->seek(0); + out << quint32(data.size() - sizeof(quint32)); + + const bool writeOk = client.write(data) != -1 && client.waitForBytesWritten(WaitTimeoutMSec); + client.disconnectFromServer(); + const bool disconnected = client.waitForDisconnected(WaitTimeoutMSec); + return writeOk && disconnected; +} + diff --git a/src/gui/Application.h b/src/gui/Application.h index 670342ca..1f575936 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -39,14 +39,20 @@ public: bool event(QEvent* event) override; bool isAlreadyRunning() const; + bool sendFileNamesToRunningInstance(const QStringList& fileNames); + signals: void openFile(const QString& filename); void anotherInstanceStarted(); + void applicationActivated(); + void quitSignalReceived(); private slots: #if defined(Q_OS_UNIX) void quitBySignal(); #endif + void processIncomingConnection(); + void socketReadyRead(); private: QWidget* m_mainWindow; @@ -63,6 +69,7 @@ private: bool m_alreadyRunning; QLockFile* m_lockFile; QLocalServer m_lockServer; + QString m_socketName; }; #endif // KEEPASSX_APPLICATION_H diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 2715a3df..718eb1c6 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -881,11 +881,7 @@ void MainWindow::toggleWindow() if ((QApplication::activeWindow() == this) && isVisible() && !isMinimized()) { hideWindow(); } else { - ensurePolished(); - setWindowState(windowState() & ~Qt::WindowMinimized); - show(); - raise(); - activateWindow(); + bringToFront(); #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(QT_NO_DBUS) && (QT_VERSION < QT_VERSION_CHECK(5, 9, 0)) // re-register global D-Bus menu (needed on Ubuntu with Unity) @@ -993,6 +989,15 @@ void MainWindow::hideYubiKeyPopup() setEnabled(true); } +void MainWindow::bringToFront() +{ + ensurePolished(); + setWindowState(windowState() & ~Qt::WindowMinimized); + show(); + raise(); + activateWindow(); +} + void MainWindow::handleScreenLock() { if (config()->get("security/lockdatabasescreenlock").toBool()){ @@ -1030,7 +1035,7 @@ void MainWindow::dropEvent(QDropEvent* event) const QMimeData* mimeData = event->mimeData(); if (mimeData->hasUrls()) { const QStringList kdbxFiles = kdbxFilesFromUrls(mimeData->urls()); - for(const QString &kdbxFile: kdbxFiles) { + for (const QString& kdbxFile: kdbxFiles) { openDatabase(kdbxFile); } } diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index bb18e170..b70dd907 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -61,6 +61,7 @@ public slots: void hideGlobalMessage(); void showYubiKeyPopup(); void hideYubiKeyPopup(); + void bringToFront(); protected: void closeEvent(QCloseEvent* event) override; diff --git a/src/main.cpp b/src/main.cpp index c3b1c698..82331f07 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,22 +57,6 @@ int main(int argc, char** argv) // don't set organizationName as that changes the return value of // QStandardPaths::writableLocation(QDesktopServices::DataLocation) - if (app.isAlreadyRunning()) { - qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.").toUtf8().constData(); - return 0; - } - - QApplication::setQuitOnLastWindowClosed(false); - - if (!Crypto::init()) { - QString error = QCoreApplication::translate("Main", - "Fatal error while testing the cryptographic functions."); - error.append("\n"); - error.append(Crypto::errorString()); - MessageBox::critical(nullptr, QCoreApplication::translate("Main", "KeePassXC - Error"), error); - return 1; - } - QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassXC - cross-platform password manager")); parser.addPositionalArgument("filename", QCoreApplication::translate("main", "filenames of the password databases to open (*.kdbx)"), "[filename(s)]"); @@ -93,7 +77,26 @@ int main(int argc, char** argv) parser.addOption(pwstdinOption); parser.process(app); - const QStringList args = parser.positionalArguments(); + const QStringList fileNames = parser.positionalArguments(); + + if (app.isAlreadyRunning()) { + if (!fileNames.isEmpty()) { + app.sendFileNamesToRunningInstance(fileNames); + } + qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.").toUtf8().constData(); + return 0; + } + + QApplication::setQuitOnLastWindowClosed(false); + + if (!Crypto::init()) { + QString error = QCoreApplication::translate("Main", + "Fatal error while testing the cryptographic functions."); + error.append("\n"); + error.append(Crypto::errorString()); + MessageBox::critical(nullptr, QCoreApplication::translate("Main", "KeePassXC - Error"), error); + return 1; + } if (parser.isSet(configOption)) { Config::createConfigFromFile(parser.value(configOption)); @@ -109,15 +112,10 @@ int main(int argc, char** argv) MainWindow mainWindow; app.setMainWindow(&mainWindow); - QObject::connect(&app, &Application::anotherInstanceStarted, - [&]() { - mainWindow.ensurePolished(); - mainWindow.setWindowState(mainWindow.windowState() & ~Qt::WindowMinimized); - mainWindow.show(); - mainWindow.raise(); - mainWindow.activateWindow(); - }); + QObject::connect(&app, SIGNAL(anotherInstanceStarted()), &mainWindow, SLOT(bringToFront())); + QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront())); QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); + QObject::connect(&app, SIGNAL(quitSignalReceived()), &mainWindow, SLOT(appExit()), Qt::DirectConnection); // start minimized if configured bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool(); @@ -130,20 +128,19 @@ int main(int argc, char** argv) } if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { - const QStringList filenames = config()->get("LastOpenedDatabases").toStringList(); - for (int ii = filenames.size()-1; ii >= 0; ii--) { - QString filename = filenames.at(ii); + const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList(); + for (const QString& filename: fileNames) { if (!filename.isEmpty() && QFile::exists(filename)) { - mainWindow.openDatabase(filename, QString(), QString()); + mainWindow.openDatabase(filename); } } } - for (int ii=0; ii < args.length(); ii++) { - QString filename = args[ii]; + const bool pwstdin = parser.isSet(pwstdinOption); + for (const QString& filename: fileNames) { if (!filename.isEmpty() && QFile::exists(filename)) { QString password; - if (parser.isSet(pwstdinOption)) { + if (pwstdin) { static QTextStream in(stdin, QIODevice::ReadOnly); password = in.readLine(); }