0
0
mirror of https://github.com/keepassxreboot/keepassxc.git synced 2024-09-20 04:12:15 +02:00

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 <QAbstractNativeEventFilter>
#include <QFileInfo>
#include <QFileOpenEvent>
#include <QSocketNotifier>
#include <QLockFile>
#include <QSocketNotifier>
#include <QStandardPaths>
#include <QtNetwork/QLocalSocket>
#include "autotype/AutoType.h"
#include "core/Global.h"
#if defined(Q_OS_UNIX)
#include <signal.h>
@ -36,6 +38,11 @@
#include <sys/socket.h>
#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<MainWindow*>(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<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
{
#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;
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -61,6 +61,7 @@ public slots:
void hideGlobalMessage();
void showYubiKeyPopup();
void hideYubiKeyPopup();
void bringToFront();
protected:
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
// 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();
}