mirror of
https://github.com/Murmele/Gittyup.git
synced 2024-09-17 13:37:18 +03:00
Add support for running in single instance mode on Linux and Windows
Prevent reopening already opened repo on IPC call
This commit is contained in:
parent
5ebdca8879
commit
193e1f3b0a
@ -49,7 +49,7 @@ set(QT_MODULES
|
||||
Test
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
if(UNIX)
|
||||
set(QT_MODULES ${QT_MODULES} DBus)
|
||||
endif()
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ui/MenuBar.h"
|
||||
#include "ui/RepoView.h"
|
||||
#include "ui/TabWidget.h"
|
||||
#include "update/Updater.h"
|
||||
#include <QCloseEvent>
|
||||
#include <QCommandLineParser>
|
||||
@ -33,13 +34,17 @@
|
||||
#include <QUrlQuery>
|
||||
#include <QUuid>
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
#if defined(Q_OS_LINUX)
|
||||
#include <QtDBus/QtDBus>
|
||||
|
||||
#elif defined(Q_OS_MAC)
|
||||
#include <unistd.h>
|
||||
|
||||
#elif defined(Q_OS_WIN)
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <strsafe.h>
|
||||
#include <QWindow>
|
||||
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER defaultFilter = nullptr;
|
||||
|
||||
@ -274,6 +279,178 @@ bool Application::restoreWindows()
|
||||
return MainWindow::restoreWindows();
|
||||
}
|
||||
|
||||
static MainWindow *openOrSwitch(QDir repo)
|
||||
{
|
||||
repo.makeAbsolute();
|
||||
|
||||
QList<MainWindow *> windows = MainWindow::windows();
|
||||
for (MainWindow *window : windows) {
|
||||
TabWidget *tabs = window->tabWidget();
|
||||
|
||||
for (int i = 0; i < tabs->count(); ++i) {
|
||||
RepoView *view = (RepoView *)tabs->widget(i);
|
||||
QDir openRepo = QDir(view->repo().workdir().path());
|
||||
|
||||
if (openRepo == repo) {
|
||||
tabs->setCurrentIndex(i);
|
||||
return window;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MainWindow::open(repo.path(), true);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
#define DBUS_SERVICE_NAME "com.github.Murmele.Gittyup"
|
||||
#define DBUS_INTERFACE_NAME "com.github.Murmele.Gittyup.Application"
|
||||
#define DBUS_OBJECT_PATH "/com/github/Murmele/Gittyup/Application"
|
||||
|
||||
DBusGittyup::DBusGittyup(QObject *parent): QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void DBusGittyup::openRepository(const QString &repo)
|
||||
{
|
||||
openOrSwitch(QDir(repo));
|
||||
}
|
||||
|
||||
void DBusGittyup::openAndFocusRepository(const QString &repo)
|
||||
{
|
||||
openOrSwitch(QDir(repo))->activateWindow();
|
||||
}
|
||||
|
||||
void DBusGittyup::setFocus()
|
||||
{
|
||||
MainWindow::activeWindow()->activateWindow();
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_WIN)
|
||||
#define COPYDATA_WINDOW_TITLE "Gittyup WM_COPYDATA receiver 16b8b3f6-6446-4fa7-8c72-53c25b1f206c"
|
||||
enum CopyDataCommand
|
||||
{
|
||||
Focus = 0,
|
||||
FocusAndOpen = 1
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
// Helper window class for receiving IPC messages
|
||||
class CopyDataWindow: public QWindow
|
||||
{
|
||||
public:
|
||||
CopyDataWindow()
|
||||
{
|
||||
setTitle(COPYDATA_WINDOW_TITLE);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result) Q_DECL_OVERRIDE
|
||||
{
|
||||
MSG *msg = (MSG*) message;
|
||||
|
||||
if (msg->message == WM_COPYDATA) {
|
||||
COPYDATASTRUCT *cds = (COPYDATASTRUCT*) msg->lParam;
|
||||
|
||||
switch (cds->dwData) {
|
||||
case CopyDataCommand::Focus:
|
||||
MainWindow::activeWindow()->activateWindow();
|
||||
break;
|
||||
|
||||
case CopyDataCommand::FocusAndOpen:
|
||||
if (cds->cbData % 2 == 0) {
|
||||
QString repo = QString::fromUtf16((const char16_t*) cds->lpData, cds->cbData / 2);
|
||||
openOrSwitch(QDir(repo));
|
||||
|
||||
MainWindow::activeWindow()->activateWindow();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return QWindow::nativeEvent(eventType, message, result);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Application::runSingleInstance()
|
||||
{
|
||||
if (Settings::instance()->value("singleInstance").toBool()) {
|
||||
#if defined(Q_OS_LINUX)
|
||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||
|
||||
if (bus.isConnected()) {
|
||||
QDBusInterface masterInstance(DBUS_SERVICE_NAME, DBUS_OBJECT_PATH, DBUS_INTERFACE_NAME, bus);
|
||||
|
||||
// Is another instance running on the current DBus session bus?
|
||||
if (masterInstance.isValid()) {
|
||||
if (!mPositionalArguments.isEmpty())
|
||||
masterInstance.call("openAndFocusRepository", QDir(mPositionalArguments.first()).absolutePath());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_WIN)
|
||||
HWND handle = FindWindowA(nullptr, COPYDATA_WINDOW_TITLE);
|
||||
// Is another instance running in the current session?
|
||||
if (handle != nullptr) {
|
||||
QWindow sender;
|
||||
|
||||
COPYDATASTRUCT cds;
|
||||
|
||||
if (mPositionalArguments.isEmpty()) {
|
||||
cds.dwData = CopyDataCommand::Focus;
|
||||
cds.cbData = 0;
|
||||
cds.lpData = nullptr;
|
||||
|
||||
SendMessage(handle, WM_COPYDATA, sender.winId(), (LPARAM) &cds);
|
||||
|
||||
} else {
|
||||
QString arg = QDir(mPositionalArguments.first()).absolutePath();
|
||||
|
||||
cds.dwData = CopyDataCommand::FocusAndOpen;
|
||||
cds.cbData = arg.length() * 2;
|
||||
cds.lpData = (LPVOID) arg.utf16();
|
||||
|
||||
SendMessage(handle, WM_COPYDATA, sender.winId(), (LPARAM) &cds);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
registerService();
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
void Application::registerService()
|
||||
{
|
||||
#if defined(Q_OS_LINUX)
|
||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||
|
||||
if (!bus.isConnected())
|
||||
return;
|
||||
|
||||
if (!bus.registerService(DBUS_SERVICE_NAME))
|
||||
return;
|
||||
|
||||
bus.registerObject(DBUS_OBJECT_PATH, DBUS_INTERFACE_NAME, new DBusGittyup(), QDBusConnection::ExportScriptableSlots);
|
||||
|
||||
#elif defined(Q_OS_WIN)
|
||||
CopyDataWindow *receiver = new CopyDataWindow();
|
||||
receiver->winId();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
Theme *Application::theme()
|
||||
{
|
||||
return static_cast<Application *>(instance())->mTheme.data();
|
||||
|
@ -27,6 +27,7 @@ public:
|
||||
|
||||
void autoUpdate();
|
||||
bool restoreWindows();
|
||||
bool runSingleInstance();
|
||||
|
||||
static Theme *theme();
|
||||
|
||||
@ -42,4 +43,19 @@ private:
|
||||
QStringList mPositionalArguments;
|
||||
};
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
class DBusGitAhead: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DBusGitAhead(QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
Q_SCRIPTABLE void openRepository(const QString &repo);
|
||||
Q_SCRIPTABLE void openAndFocusRepository(const QString &repo);
|
||||
Q_SCRIPTABLE void setFocus();
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -52,6 +52,10 @@ add_library(app
|
||||
${THEME_IMPL_FILE}
|
||||
)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
set_target_properties(app PROPERTIES AUTOMOC ON)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(app
|
||||
PUBLIC
|
||||
GITTYUP_NAME="${GITTYUP_NAME}"
|
||||
@ -72,6 +76,12 @@ set_target_properties(app PROPERTIES
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(app
|
||||
Qt5::DBus
|
||||
)
|
||||
endif()
|
||||
|
||||
# Add main executable.
|
||||
add_executable(gittyup WIN32 MACOSX_BUNDLE
|
||||
Gittyup.cpp
|
||||
|
@ -16,6 +16,10 @@ int main(int argc, char *argv[])
|
||||
Application::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
Application app(argc, argv, true);
|
||||
|
||||
// Check if only one running instance is allowed and already running
|
||||
if (app.runSingleInstance())
|
||||
return 0;
|
||||
|
||||
// Restore windows before checking for updates so that
|
||||
// the update dialog pops up on top of the other windows.
|
||||
if (!app.restoreWindows())
|
||||
|
@ -125,6 +125,9 @@ public:
|
||||
AboutDialog::openSharedInstance(AboutDialog::Privacy);
|
||||
});
|
||||
|
||||
mSingleInstance = new QCheckBox(
|
||||
tr("Only allow a single running instance"), this);
|
||||
|
||||
QFormLayout *form = new QFormLayout;
|
||||
form->addRow(tr("User name:"), mName);
|
||||
form->addRow(tr("User email:"), mEmail);
|
||||
@ -136,6 +139,10 @@ public:
|
||||
form->addRow(tr("Credentials:"), mStoreCredentials);
|
||||
form->addRow(QString(), privacy);
|
||||
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
|
||||
form->addRow(tr("Single instance:"), mSingleInstance);
|
||||
#endif
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(16,12,16,12);
|
||||
layout->addLayout(form);
|
||||
@ -186,6 +193,10 @@ public:
|
||||
Settings::instance()->setValue("credential/store", checked);
|
||||
delete CredentialHelper::instance();
|
||||
});
|
||||
|
||||
connect(mSingleInstance, &QCheckBox::toggled, [](bool checked) {
|
||||
Settings::instance()->setValue("singleInstance", checked);
|
||||
});
|
||||
}
|
||||
|
||||
void init()
|
||||
@ -208,6 +219,8 @@ public:
|
||||
|
||||
mNoTranslation->setChecked(settings->value("translation/disable").toBool());
|
||||
mStoreCredentials->setChecked(settings->value("credential/store").toBool());
|
||||
|
||||
mSingleInstance->setChecked(settings->value("singleInstance").toBool());
|
||||
}
|
||||
|
||||
private:
|
||||
@ -221,6 +234,7 @@ private:
|
||||
QCheckBox *mAutoPrune;
|
||||
QCheckBox *mNoTranslation;
|
||||
QCheckBox *mStoreCredentials;
|
||||
QCheckBox *mSingleInstance;
|
||||
};
|
||||
|
||||
class ToolsPanel : public QWidget
|
||||
|
Loading…
Reference in New Issue
Block a user