Allow only one application instance

fixes #141
This commit is contained in:
Konstantinos Sideris 2017-12-14 21:55:00 +02:00
parent 7de5af83db
commit 65672d3dfb
4 changed files with 180 additions and 5 deletions

View file

@ -199,6 +199,7 @@ set(SRC_FILES
src/RoomList.cc src/RoomList.cc
src/RoomMessages.cc src/RoomMessages.cc
src/RoomState.cc src/RoomState.cc
src/RunGuard.cc
src/SideBarActions.cc src/SideBarActions.cc
src/Splitter.cc src/Splitter.cc
src/TextInputWidget.cc src/TextInputWidget.cc

31
include/RunGuard.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
//
// Taken from
// https://stackoverflow.com/questions/5006547/qt-best-practice-for-a-single-instance-app-protection
//
#include <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>
class RunGuard
{
public:
RunGuard(const QString &key);
~RunGuard();
bool isAnotherRunning();
bool tryToRun();
void release();
private:
const QString key;
const QString memLockKey;
const QString sharedmemKey;
QSharedMemory sharedMem;
QSystemSemaphore memLock;
Q_DISABLE_COPY(RunGuard)
};

84
src/RunGuard.cc Normal file
View file

@ -0,0 +1,84 @@
#include "RunGuard.h"
#include <QCryptographicHash>
namespace {
QString
generateKeyHash(const QString &key, const QString &salt)
{
QByteArray data;
data.append(key.toUtf8());
data.append(salt.toUtf8());
data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();
return data;
}
}
RunGuard::RunGuard(const QString &key)
: key(key)
, memLockKey(generateKeyHash(key, "_memLockKey"))
, sharedmemKey(generateKeyHash(key, "_sharedmemKey"))
, sharedMem(sharedmemKey)
, memLock(memLockKey, 1)
{
memLock.acquire();
{
// Fix for *nix: http://habrahabr.ru/post/173281/
QSharedMemory fix(sharedmemKey);
fix.attach();
}
memLock.release();
}
RunGuard::~RunGuard() { release(); }
bool
RunGuard::isAnotherRunning()
{
if (sharedMem.isAttached())
return false;
memLock.acquire();
const bool isRunning = sharedMem.attach();
if (isRunning)
sharedMem.detach();
memLock.release();
return isRunning;
}
bool
RunGuard::tryToRun()
{
// Extra check
if (isAnotherRunning())
return false;
memLock.acquire();
const bool result = sharedMem.create(sizeof(quint64));
memLock.release();
if (!result) {
release();
return false;
}
return true;
}
void
RunGuard::release()
{
memLock.acquire();
if (sharedMem.isAttached())
sharedMem.detach();
memLock.release();
}

View file

@ -19,12 +19,31 @@
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QFile> #include <QFile>
#include <QFontDatabase> #include <QFontDatabase>
#include <QLabel>
#include <QLayout>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QNetworkProxy> #include <QNetworkProxy>
#include <QPalette>
#include <QPoint>
#include <QPushButton>
#include <QSettings> #include <QSettings>
#include <QTranslator> #include <QTranslator>
#include "Config.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "RaisedButton.h"
#include "RunGuard.h"
QPoint
screenCenter(int width, int height)
{
QRect screenGeometry = QApplication::desktop()->screenGeometry();
int x = (screenGeometry.width() - width) / 2;
int y = (screenGeometry.height() - height) / 2;
return QPoint(x, y);
}
void void
setupProxy() setupProxy()
@ -55,6 +74,50 @@ setupProxy()
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
RunGuard guard("run_guard");
if (!guard.tryToRun()) {
QApplication a(argc, argv);
QFont font;
font.setPointSize(15);
font.setWeight(60);
QWidget widget;
QVBoxLayout layout(&widget);
layout.setContentsMargins(20, 10, 20, 20);
layout.setSpacing(0);
QHBoxLayout btnLayout;
QLabel msg("Another instance of nheko is currently running.");
msg.setWordWrap(true);
msg.setFont(font);
QPalette pal;
RaisedButton submitBtn("OK");
submitBtn.setBackgroundColor(pal.color(QPalette::Button));
submitBtn.setForegroundColor(pal.color(QPalette::ButtonText));
submitBtn.setMinimumSize(120, 35);
submitBtn.setFontSize(conf::btn::fontSize);
submitBtn.setCornerRadius(conf::btn::cornerRadius);
btnLayout.addStretch(1);
btnLayout.addWidget(&submitBtn);
layout.addWidget(&msg);
layout.addLayout(&btnLayout);
widget.setFixedSize(480, 180);
widget.move(screenCenter(widget.width(), widget.height()));
widget.show();
QObject::connect(&submitBtn, &QPushButton::clicked, &widget, &QWidget::close);
return a.exec();
}
QCoreApplication::setApplicationName("nheko"); QCoreApplication::setApplicationName("nheko");
QCoreApplication::setApplicationVersion("0.1.0"); QCoreApplication::setApplicationVersion("0.1.0");
QCoreApplication::setOrganizationName("nheko"); QCoreApplication::setOrganizationName("nheko");
@ -95,11 +158,7 @@ main(int argc, char *argv[])
MainWindow w; MainWindow w;
// Move the MainWindow to the center // Move the MainWindow to the center
QRect screenGeometry = QApplication::desktop()->screenGeometry(); w.move(screenCenter(w.width(), w.height()));
int x = (screenGeometry.width() - w.width()) / 2;
int y = (screenGeometry.height() - w.height()) / 2;
w.move(x, y);
w.show(); w.show();
QObject::connect(&app, &QApplication::aboutToQuit, &w, &MainWindow::saveCurrentWindowSize); QObject::connect(&app, &QApplication::aboutToQuit, &w, &MainWindow::saveCurrentWindowSize);