diff --git a/CMakeLists.txt b/CMakeLists.txt index cde795ad2..04136751f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,7 @@ if(WITH_TESTS) endif(WITH_TESTS) find_package(Qt5Core 5.2 REQUIRED) +find_package(Qt5Network 5.2 REQUIRED) find_package(Qt5Concurrent 5.2 REQUIRED) find_package(Qt5Widgets 5.2 REQUIRED) find_package(Qt5Test 5.2 REQUIRED) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 786a03a80..d0fb3ab51 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -195,6 +195,7 @@ target_link_libraries(keepassx_core ${YUBIKEY_LIBRARIES} zxcvbn Qt5::Core + Qt5::Network Qt5::Concurrent Qt5::Widgets ${GCRYPT_LIBRARIES} diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index f64a766f5..6a7e37057 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include #include "autotype/AutoType.h" @@ -76,6 +79,8 @@ Application::Application(int& argc, char** argv) #ifdef Q_OS_UNIX , m_unixSignalNotifier(nullptr) #endif + , alreadyRunning(false) + , lock(nullptr) { #if defined(Q_OS_UNIX) && !defined(Q_OS_OSX) installNativeEventFilter(new XcbEventFilter()); @@ -85,6 +90,58 @@ Application::Application(int& argc, char** argv) #if defined(Q_OS_UNIX) registerUnixSignals(); #endif + + QString userName = qgetenv("USER"); + if (userName.isEmpty()) { + userName = qgetenv("USERNAME"); + } + QString identifier = "keepassx2"; + if (!userName.isEmpty()) { + identifier.append("-"); + identifier.append(userName); + } + QString socketName = identifier + ".socket"; + QString lockName = identifier + ".lock"; + + // 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. + lock = new QLockFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + lockName); + lock->setStaleLockTime(0); + lock->tryLock(); + switch (lock->error()) { + case QLockFile::NoError: + server.setSocketOptions(QLocalServer::UserAccessOption); + server.listen(socketName); + connect(&server, SIGNAL(newConnection()), this, SIGNAL(anotherInstanceStarted())); + break; + case QLockFile::LockFailedError: { + alreadyRunning = true; + // notify the other instance + // try several times, in case the other instance is still starting up + QLocalSocket client; + for (int i = 0; i < 3; i++) { + client.connectToServer(socketName); + if (client.waitForConnected(150)) { + client.abort(); + break; + } + } + break; + } + default: + qWarning() << QCoreApplication::translate("Main", + "The lock file could not be created. Single-instance mode disabled.") + .toUtf8().constData(); + } +} + +Application::~Application() +{ + server.close(); + if (lock) { + lock->unlock(); + delete lock; + } } QWidget* Application::mainWindow() const @@ -171,3 +228,9 @@ void Application::quitBySignal() static_cast(m_mainWindow)->appExit(); } #endif + +bool Application::isAlreadyRunning() const +{ + return alreadyRunning; +} + diff --git a/src/gui/Application.h b/src/gui/Application.h index fd5ad12e0..1a1b0ee86 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -20,6 +20,8 @@ #define KEEPASSX_APPLICATION_H #include +#include +class QLockFile; class QSocketNotifier; @@ -30,12 +32,15 @@ class Application : public QApplication public: Application(int& argc, char** argv); QWidget* mainWindow() const; + ~Application(); void setMainWindow(QWidget* mainWindow); bool event(QEvent* event) override; + bool isAlreadyRunning() const; signals: void openFile(const QString& filename); + void anotherInstanceStarted(); private slots: #if defined(Q_OS_UNIX) @@ -54,6 +59,9 @@ private: static void handleUnixSignal(int sig); static int unixSignalSocket[2]; #endif + bool alreadyRunning; + QLockFile* lock; + QLocalServer server; }; #endif // KEEPASSX_APPLICATION_H diff --git a/src/main.cpp b/src/main.cpp index 5981999e7..a4416e0a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,6 +56,11 @@ 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 KeePassX 2 is already running.").toUtf8().constData(); + return 0; + } + QApplication::setQuitOnLastWindowClosed(false); if (!Crypto::init()) { @@ -102,7 +107,15 @@ 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(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); // start minimized if configured