mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-28 16:59:44 -05:00
Merge pull request #1258 from frostasm/pass-command-line-filenames-to-already-running-instance
Pass command line filenames to already running instance
This commit is contained in:
commit
ffddcb5060
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -114,6 +114,8 @@ MainWindow::MainWindow()
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
setAcceptDrops(true);
|
||||
|
||||
m_ui->toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
|
||||
|
||||
// Setup the search widget in the toolbar
|
||||
@ -879,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)
|
||||
@ -991,9 +989,57 @@ 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()){
|
||||
lockDatabasesAfterInactivity();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList MainWindow::kdbxFilesFromUrls(const QList<QUrl>& urls)
|
||||
{
|
||||
QStringList kdbxFiles;
|
||||
for (const QUrl& url: urls) {
|
||||
const QFileInfo fInfo(url.toLocalFile());
|
||||
const bool isKdbxFile = fInfo.isFile() && fInfo.suffix().toLower() == "kdbx";
|
||||
if (isKdbxFile) {
|
||||
kdbxFiles.append(fInfo.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
return kdbxFiles;
|
||||
}
|
||||
|
||||
void MainWindow::dragEnterEvent(QDragEnterEvent* event)
|
||||
{
|
||||
const QMimeData* mimeData = event->mimeData();
|
||||
if (mimeData->hasUrls()) {
|
||||
const QStringList kdbxFiles = kdbxFilesFromUrls(mimeData->urls());
|
||||
if (!kdbxFiles.isEmpty()) {
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::dropEvent(QDropEvent* event)
|
||||
{
|
||||
const QMimeData* mimeData = event->mimeData();
|
||||
if (mimeData->hasUrls()) {
|
||||
const QStringList kdbxFiles = kdbxFilesFromUrls(mimeData->urls());
|
||||
if (!kdbxFiles.isEmpty()) {
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
for (const QString& kdbxFile: kdbxFiles) {
|
||||
openDatabase(kdbxFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ public slots:
|
||||
void hideGlobalMessage();
|
||||
void showYubiKeyPopup();
|
||||
void hideYubiKeyPopup();
|
||||
void bringToFront();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
@ -107,6 +108,10 @@ private:
|
||||
void updateTrayIcon();
|
||||
bool isTrayIconEnabled() const;
|
||||
|
||||
static QStringList kdbxFilesFromUrls(const QList<QUrl>& urls);
|
||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||
void dropEvent(QDropEvent* event) override;
|
||||
|
||||
const QScopedPointer<Ui::MainWindow> m_ui;
|
||||
SignalMultiplexer m_actionMultiplexer;
|
||||
QAction* m_clearHistoryAction;
|
||||
|
61
src/main.cpp
61
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();
|
||||
}
|
||||
|
@ -952,6 +952,43 @@ void TestGui::testDatabaseLocking()
|
||||
QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName);
|
||||
}
|
||||
|
||||
void TestGui::testDragAndDropKdbxFiles()
|
||||
{
|
||||
const int openedDatabasesCount = m_tabWidget->count();
|
||||
|
||||
const QString badDatabaseFilePath(QString(KEEPASSX_TEST_DATA_DIR).append("/NotDatabase.notkdbx"));
|
||||
QMimeData badMimeData;
|
||||
badMimeData.setUrls({QUrl::fromLocalFile(badDatabaseFilePath)});
|
||||
QDragEnterEvent badDragEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &badDragEvent);
|
||||
QCOMPARE(badDragEvent.isAccepted(), false);
|
||||
|
||||
QDropEvent badDropEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &badDropEvent);
|
||||
QCOMPARE(badDropEvent.isAccepted(), false);
|
||||
|
||||
QCOMPARE(m_tabWidget->count(), openedDatabasesCount);
|
||||
|
||||
const QString goodDatabaseFilePath(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx"));
|
||||
QMimeData goodMimeData;
|
||||
goodMimeData.setUrls({QUrl::fromLocalFile(goodDatabaseFilePath)});
|
||||
QDragEnterEvent goodDragEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &goodDragEvent);
|
||||
QCOMPARE(goodDragEvent.isAccepted(), true);
|
||||
|
||||
QDropEvent goodDropEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier);
|
||||
qApp->notify(m_mainWindow, &goodDropEvent);
|
||||
QCOMPARE(goodDropEvent.isAccepted(), true);
|
||||
|
||||
QCOMPARE(m_tabWidget->count(), openedDatabasesCount + 1);
|
||||
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
triggerAction("actionDatabaseClose");
|
||||
Tools::wait(100);
|
||||
|
||||
QCOMPARE(m_tabWidget->count(), openedDatabasesCount);
|
||||
}
|
||||
|
||||
void TestGui::cleanupTestCase()
|
||||
{
|
||||
delete m_mainWindow;
|
||||
|
@ -60,6 +60,7 @@ private slots:
|
||||
void testDatabaseSettings();
|
||||
void testKeePass1Import();
|
||||
void testDatabaseLocking();
|
||||
void testDragAndDropKdbxFiles();
|
||||
|
||||
private:
|
||||
int addCannedEntries();
|
||||
|
Loading…
Reference in New Issue
Block a user