Pass (using IPC) command line filenames to already running instance

This commit is contained in:
frostasm 2017-12-05 17:25:08 +02:00
parent 7b9b23b143
commit 165d664524
5 changed files with 136 additions and 58 deletions

View File

@ -22,13 +22,15 @@
#include "core/Config.h" #include "core/Config.h"
#include <QAbstractNativeEventFilter> #include <QAbstractNativeEventFilter>
#include <QFileInfo>
#include <QFileOpenEvent> #include <QFileOpenEvent>
#include <QSocketNotifier>
#include <QLockFile> #include <QLockFile>
#include <QSocketNotifier>
#include <QStandardPaths> #include <QStandardPaths>
#include <QtNetwork/QLocalSocket> #include <QtNetwork/QLocalSocket>
#include "autotype/AutoType.h" #include "autotype/AutoType.h"
#include "core/Global.h"
#if defined(Q_OS_UNIX) #if defined(Q_OS_UNIX)
#include <signal.h> #include <signal.h>
@ -36,6 +38,11 @@
#include <sys/socket.h> #include <sys/socket.h>
#endif #endif
namespace {
constexpr int WaitTimeoutMSec = 150;
const char BlockSizeProperty[] = "blockSize";
}
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX) #if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
class XcbEventFilter : public QAbstractNativeEventFilter class XcbEventFilter : public QAbstractNativeEventFilter
{ {
@ -105,8 +112,8 @@ Application::Application(int& argc, char** argv)
// In DEBUG mode don't interfere with Release instances // In DEBUG mode don't interfere with Release instances
identifier += "-DEBUG"; identifier += "-DEBUG";
#endif #endif
QString socketName = identifier + ".socket";
QString lockName = identifier + ".lock"; QString lockName = identifier + ".lock";
m_socketName = identifier + ".socket";
// According to documentation we should use RuntimeLocation on *nixes, but even Qt doesn't respect // 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. // 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->setStaleLockTime(0);
m_lockFile->tryLock(); 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()) { switch (m_lockFile->error()) {
case QLockFile::NoError: case QLockFile::NoError:
// No existing lock was found, start listener // No existing lock was found, start listener
m_lockServer.setSocketOptions(QLocalServer::UserAccessOption); m_lockServer.listen(m_socketName);
m_lockServer.listen(socketName);
connect(&m_lockServer, SIGNAL(newConnection()), this, SIGNAL(anotherInstanceStarted()));
break; break;
case QLockFile::LockFailedError: { case QLockFile::LockFailedError: {
if (config()->get("SingleInstance").toBool()) { if (config()->get("SingleInstance").toBool()) {
// Attempt to connect to the existing instance // Attempt to connect to the existing instance
QLocalSocket client; QLocalSocket client;
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; ++i) {
client.connectToServer(socketName); client.connectToServer(m_socketName);
if (client.waitForConnected(150)) { if (client.waitForConnected(WaitTimeoutMSec)) {
// Connection succeeded, this will raise the existing window if minimized // Connection succeeded, this will raise the existing window if minimized
client.abort(); client.abort();
m_alreadyRunning = true; m_alreadyRunning = true;
@ -145,9 +154,7 @@ Application::Application(int& argc, char** argv)
m_lockFile->removeStaleLockFile(); m_lockFile->removeStaleLockFile();
m_lockFile->tryLock(); m_lockFile->tryLock();
// start the listen server // start the listen server
m_lockServer.setSocketOptions(QLocalServer::UserAccessOption); m_lockServer.listen(m_socketName);
m_lockServer.listen(socketName);
connect(&m_lockServer, SIGNAL(newConnection()), this, SIGNAL(anotherInstanceStarted()));
} }
} }
break; break;
@ -187,12 +194,8 @@ bool Application::event(QEvent* event)
} }
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
// restore main window when clicking on the docker icon // restore main window when clicking on the docker icon
else if ((event->type() == QEvent::ApplicationActivate) && m_mainWindow) { else if (event->type() == QEvent::ApplicationActivate) {
m_mainWindow->ensurePolished(); emit applicationActivated();
m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized);
m_mainWindow->show();
m_mainWindow->raise();
m_mainWindow->activateWindow();
} }
#endif #endif
@ -247,12 +250,54 @@ void Application::quitBySignal()
m_unixSignalNotifier->setEnabled(false); m_unixSignalNotifier->setEnabled(false);
char buf; char buf;
Q_UNUSED(::read(unixSignalSocket[1], &buf, sizeof(buf))); Q_UNUSED(::read(unixSignalSocket[1], &buf, sizeof(buf)));
emit quitSignalReceived();
if (nullptr != m_mainWindow)
static_cast<MainWindow*>(m_mainWindow)->appExit();
} }
#endif #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<QLocalSocket*>(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 bool Application::isAlreadyRunning() const
{ {
#ifdef QT_DEBUG #ifdef QT_DEBUG
@ -262,3 +307,26 @@ bool Application::isAlreadyRunning() const
return config()->get("SingleInstance").toBool() && m_alreadyRunning; 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;
}

View File

@ -39,14 +39,20 @@ public:
bool event(QEvent* event) override; bool event(QEvent* event) override;
bool isAlreadyRunning() const; bool isAlreadyRunning() const;
bool sendFileNamesToRunningInstance(const QStringList& fileNames);
signals: signals:
void openFile(const QString& filename); void openFile(const QString& filename);
void anotherInstanceStarted(); void anotherInstanceStarted();
void applicationActivated();
void quitSignalReceived();
private slots: private slots:
#if defined(Q_OS_UNIX) #if defined(Q_OS_UNIX)
void quitBySignal(); void quitBySignal();
#endif #endif
void processIncomingConnection();
void socketReadyRead();
private: private:
QWidget* m_mainWindow; QWidget* m_mainWindow;
@ -63,6 +69,7 @@ private:
bool m_alreadyRunning; bool m_alreadyRunning;
QLockFile* m_lockFile; QLockFile* m_lockFile;
QLocalServer m_lockServer; QLocalServer m_lockServer;
QString m_socketName;
}; };
#endif // KEEPASSX_APPLICATION_H #endif // KEEPASSX_APPLICATION_H

View File

@ -881,11 +881,7 @@ void MainWindow::toggleWindow()
if ((QApplication::activeWindow() == this) && isVisible() && !isMinimized()) { if ((QApplication::activeWindow() == this) && isVisible() && !isMinimized()) {
hideWindow(); hideWindow();
} else { } else {
ensurePolished(); bringToFront();
setWindowState(windowState() & ~Qt::WindowMinimized);
show();
raise();
activateWindow();
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(QT_NO_DBUS) && (QT_VERSION < QT_VERSION_CHECK(5, 9, 0)) #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) // re-register global D-Bus menu (needed on Ubuntu with Unity)
@ -993,6 +989,15 @@ void MainWindow::hideYubiKeyPopup()
setEnabled(true); setEnabled(true);
} }
void MainWindow::bringToFront()
{
ensurePolished();
setWindowState(windowState() & ~Qt::WindowMinimized);
show();
raise();
activateWindow();
}
void MainWindow::handleScreenLock() void MainWindow::handleScreenLock()
{ {
if (config()->get("security/lockdatabasescreenlock").toBool()){ if (config()->get("security/lockdatabasescreenlock").toBool()){
@ -1030,7 +1035,7 @@ void MainWindow::dropEvent(QDropEvent* event)
const QMimeData* mimeData = event->mimeData(); const QMimeData* mimeData = event->mimeData();
if (mimeData->hasUrls()) { if (mimeData->hasUrls()) {
const QStringList kdbxFiles = kdbxFilesFromUrls(mimeData->urls()); const QStringList kdbxFiles = kdbxFilesFromUrls(mimeData->urls());
for(const QString &kdbxFile: kdbxFiles) { for (const QString& kdbxFile: kdbxFiles) {
openDatabase(kdbxFile); openDatabase(kdbxFile);
} }
} }

View File

@ -61,6 +61,7 @@ public slots:
void hideGlobalMessage(); void hideGlobalMessage();
void showYubiKeyPopup(); void showYubiKeyPopup();
void hideYubiKeyPopup(); void hideYubiKeyPopup();
void bringToFront();
protected: protected:
void closeEvent(QCloseEvent* event) override; void closeEvent(QCloseEvent* event) override;

View File

@ -57,22 +57,6 @@ int main(int argc, char** argv)
// don't set organizationName as that changes the return value of // don't set organizationName as that changes the return value of
// QStandardPaths::writableLocation(QDesktopServices::DataLocation) // 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; QCommandLineParser parser;
parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassXC - cross-platform password manager")); 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)]"); 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.addOption(pwstdinOption);
parser.process(app); 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)) { if (parser.isSet(configOption)) {
Config::createConfigFromFile(parser.value(configOption)); Config::createConfigFromFile(parser.value(configOption));
@ -109,15 +112,10 @@ int main(int argc, char** argv)
MainWindow mainWindow; MainWindow mainWindow;
app.setMainWindow(&mainWindow); app.setMainWindow(&mainWindow);
QObject::connect(&app, &Application::anotherInstanceStarted, QObject::connect(&app, SIGNAL(anotherInstanceStarted()), &mainWindow, SLOT(bringToFront()));
[&]() { QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront()));
mainWindow.ensurePolished();
mainWindow.setWindowState(mainWindow.windowState() & ~Qt::WindowMinimized);
mainWindow.show();
mainWindow.raise();
mainWindow.activateWindow();
});
QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString)));
QObject::connect(&app, SIGNAL(quitSignalReceived()), &mainWindow, SLOT(appExit()), Qt::DirectConnection);
// start minimized if configured // start minimized if configured
bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool(); bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool();
@ -130,20 +128,19 @@ int main(int argc, char** argv)
} }
if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) {
const QStringList filenames = config()->get("LastOpenedDatabases").toStringList(); const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList();
for (int ii = filenames.size()-1; ii >= 0; ii--) { for (const QString& filename: fileNames) {
QString filename = filenames.at(ii);
if (!filename.isEmpty() && QFile::exists(filename)) { if (!filename.isEmpty() && QFile::exists(filename)) {
mainWindow.openDatabase(filename, QString(), QString()); mainWindow.openDatabase(filename);
} }
} }
} }
for (int ii=0; ii < args.length(); ii++) { const bool pwstdin = parser.isSet(pwstdinOption);
QString filename = args[ii]; for (const QString& filename: fileNames) {
if (!filename.isEmpty() && QFile::exists(filename)) { if (!filename.isEmpty() && QFile::exists(filename)) {
QString password; QString password;
if (parser.isSet(pwstdinOption)) { if (pwstdin) {
static QTextStream in(stdin, QIODevice::ReadOnly); static QTextStream in(stdin, QIODevice::ReadOnly);
password = in.readLine(); password = in.readLine();
} }