From 137394727a89a8a33929296173661fc7e254f3dc Mon Sep 17 00:00:00 2001 From: Sunshine Date: Thu, 15 Sep 2022 16:31:53 -0400 Subject: [PATCH] implement singleinstance module prototype --- .gitignore | 4 + inc/liquidappwindow.hpp | 5 + inc/mainwindow.hpp | 9 +- liquid.pro | 37 +-- modules/modules.pri | 1 + modules/singleinstance/inc/singleinstance.hpp | 27 ++ modules/singleinstance/singleinstance.pri | 16 ++ modules/singleinstance/src/singleinstance.cpp | 118 +++++++++ src/liquidappwindow.cpp | 232 ++++++++++-------- src/main.cpp | 67 ++--- src/mainwindow.cpp | 175 +++++++------ 11 files changed, 441 insertions(+), 250 deletions(-) create mode 100644 modules/modules.pri create mode 100644 modules/singleinstance/inc/singleinstance.hpp create mode 100644 modules/singleinstance/singleinstance.pri create mode 100644 modules/singleinstance/src/singleinstance.cpp diff --git a/.gitignore b/.gitignore index 2d195fe..feb5761 100644 --- a/.gitignore +++ b/.gitignore @@ -26,10 +26,14 @@ object_script.*.Debug *.pro.user.* *.qbs.user *.qbs.user.* +.objs/ +.mocs/ *.moc moc_*.cpp moc_*.h +.qrcs/ qrc_*.cpp +.uis/ ui_*.h *.qmlc *.jsc diff --git a/inc/liquidappwindow.hpp b/inc/liquidappwindow.hpp index d6d08f0..bf98996 100644 --- a/inc/liquidappwindow.hpp +++ b/inc/liquidappwindow.hpp @@ -10,6 +10,8 @@ #include #include +#include "singleinstance.hpp" + #include "liquidappwebpage.hpp" class LiquidAppWebPage; @@ -22,6 +24,8 @@ class LiquidAppWindow : public QWebEngineView explicit LiquidAppWindow(const QString* name); ~LiquidAppWindow(void); + bool isAlreadyRunning(void); + void run(void); void setForgiveNextPageLoadError(const bool ok); QSettings* liquidAppConfig; @@ -60,6 +64,7 @@ public slots: QString liquidAppWindowTitle; QIcon iconToSave; + SingleInstance *singleInstance; LiquidAppWebPage* liquidAppWebPage = Q_NULLPTR; QWebEngineProfile* liquidAppWebProfile = Q_NULLPTR; diff --git a/inc/mainwindow.hpp b/inc/mainwindow.hpp index 9a7e5dc..1425905 100644 --- a/inc/mainwindow.hpp +++ b/inc/mainwindow.hpp @@ -6,12 +6,17 @@ #include #include +#include "singleinstance.hpp" + class MainWindow : public QScrollArea { public: MainWindow(); ~MainWindow(); + bool isAlreadyRunning(void); + void run(void); + protected: void closeEvent(QCloseEvent *event) override; @@ -24,7 +29,7 @@ class MainWindow : public QScrollArea QTableWidget* appListTable; QPushButton* createNewLiquidAppButton; - QSettings* settings; - QAction* quitAction; + QSettings* settings; + SingleInstance *singleInstance; }; diff --git a/liquid.pro b/liquid.pro index 45795f5..5bd863d 100644 --- a/liquid.pro +++ b/liquid.pro @@ -1,3 +1,7 @@ +include($${PWD}/modules/modules.pri) + +PROG_NAME = liquid + VERSION_MAJOR = 0 VERSION_MINOR = 7 VERSION_PATCH = 11 @@ -9,10 +13,9 @@ CONFIG += c++11 TEMPLATE = app DESTDIR = build -PROG_NAME = liquid -SRC_DIR = src INC_DIR = inc +SRC_DIR = src FORMS_DIR = ui OBJECTS_DIR = .objs @@ -22,21 +25,21 @@ RCC_DIR = .qrcs INCLUDEPATH += $${INC_DIR} -SOURCES += src/liquid.cpp \ - src/liquidappcookiejar.cpp \ - src/liquidappconfigwindow.cpp \ - src/liquidappwebpage.cpp \ - src/liquidappwindow.cpp \ - src/main.cpp \ - src/mainwindow.cpp \ - -HEADERS += inc/lqd.h \ - inc/liquid.hpp \ - inc/liquidappcookiejar.hpp \ - inc/liquidappconfigwindow.hpp \ - inc/liquidappwebpage.hpp \ - inc/liquidappwindow.hpp \ - inc/mainwindow.hpp \ +HEADERS += $${INC_DIR}/lqd.h \ + $${INC_DIR}/liquid.hpp \ + $${INC_DIR}/liquidappcookiejar.hpp \ + $${INC_DIR}/liquidappconfigwindow.hpp \ + $${INC_DIR}/liquidappwebpage.hpp \ + $${INC_DIR}/liquidappwindow.hpp \ + $${INC_DIR}/mainwindow.hpp \ + +SOURCES += $${SRC_DIR}/liquid.cpp \ + $${SRC_DIR}/liquidappcookiejar.cpp \ + $${SRC_DIR}/liquidappconfigwindow.cpp \ + $${SRC_DIR}/liquidappwebpage.cpp \ + $${SRC_DIR}/liquidappwindow.cpp \ + $${SRC_DIR}/main.cpp \ + $${SRC_DIR}/mainwindow.cpp \ RESOURCES = res/resources.qrc diff --git a/modules/modules.pri b/modules/modules.pri new file mode 100644 index 0000000..503af35 --- /dev/null +++ b/modules/modules.pri @@ -0,0 +1 @@ +include($${PWD}/singleinstance/singleinstance.pri) diff --git a/modules/singleinstance/inc/singleinstance.hpp b/modules/singleinstance/inc/singleinstance.hpp new file mode 100644 index 0000000..77f9574 --- /dev/null +++ b/modules/singleinstance/inc/singleinstance.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +class SingleInstance : public QObject +{ + Q_OBJECT + +public: + explicit SingleInstance(QWidget *parent = Q_NULLPTR, QString *instanceId = new QString()); + ~SingleInstance(); + + bool isAlreadyRunning(const bool raiseExisting = false); + static void raiseWindow(QWidget *window); + +private: + struct SharedData + { + bool needToRaiseExistingWindow = false; + }; + + QSharedMemory *shMem; + QString shmemName; + QString smphorName; +}; diff --git a/modules/singleinstance/singleinstance.pri b/modules/singleinstance/singleinstance.pri new file mode 100644 index 0000000..ba46166 --- /dev/null +++ b/modules/singleinstance/singleinstance.pri @@ -0,0 +1,16 @@ +!win:!mac { + LIBS += -lX11 -lXfixes -lXinerama -lXext -lXcomposite +} + +QT += core +CONFIG += c++11 +TEMPLATE = lib + +INC_DIR = inc +SRC_DIR = src + +INCLUDEPATH += $${PWD}/$${INC_DIR} + +HEADERS += $${PWD}/inc/singleinstance.hpp + +SOURCES += $${PWD}/src/singleinstance.cpp diff --git a/modules/singleinstance/src/singleinstance.cpp b/modules/singleinstance/src/singleinstance.cpp new file mode 100644 index 0000000..feaa000 --- /dev/null +++ b/modules/singleinstance/src/singleinstance.cpp @@ -0,0 +1,118 @@ +#include + +#include "singleinstance.hpp" + +#if defined(Q_OS_WIN) +#include +#elif !defined(Q_OS_MACOS) +#include +#endif + +SingleInstance::SingleInstance(QWidget *parent, QString *instanceId) : QObject(parent) +{ + shmemName = instanceId + QString("_shrdmmr"); + smphorName = instanceId + QString("_smphr"); +} + +SingleInstance::~SingleInstance() +{ +} + +bool SingleInstance::isAlreadyRunning(const bool raiseExisting) +{ + QSystemSemaphore sem(smphorName, 1); + + // Fix danging memory on Linux + { + QSharedMemory fix(shmemName); + fix.attach(); + } + + shMem = new QSharedMemory(shmemName); + if (shMem->create(sizeof(SharedData))) { // This is the first instance + shMem->attach(); + + sem.acquire(); + memset(shMem->data(), 0, shMem->size()); + sem.release(); + + QTimer* t = new QTimer(this); + connect(t, &QTimer::timeout, this, [this]() { + QSystemSemaphore sem(smphorName, 1); + sem.acquire(); + SharedData* data = reinterpret_cast(shMem->data()); + if (data->needToRaiseExistingWindow) { + SingleInstance::raiseWindow((QWidget*)parent()); + data->needToRaiseExistingWindow = false; + } + sem.release(); + }); + t->start(0); + } else { // Another instance is already running + shMem->attach(); + + sem.acquire(); + SharedData* data = reinterpret_cast(shMem->data()); + data->needToRaiseExistingWindow = raiseExisting; + + return true; + } + + return false; +} + +void SingleInstance::raiseWindow(QWidget *window) +{ +#if defined(Q_OS_WIN) // Windows + Window winId = window->effectiveWinId(); + + SetWindowPos(winId, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + SetWindowPos(winId, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); +#elif defined(Q_OS_MACOS) // macOS + window->show(); + window->raise(); + window->activateWindow(); +#else // GNU/Linux, FreeBSD, etc + Window winId = window->effectiveWinId(); + + if (winId > 0) { + Display* disp = XOpenDisplay(nullptr); + + if (disp) { + XWindowAttributes attributes; + + if (XGetWindowAttributes(disp, winId, &attributes)) { + XSetInputFocus(disp, winId, RevertToPointerRoot, CurrentTime); + XRaiseWindow(disp, winId); + + // Show window if minimized, instead of just highlighting its icon + { + const Atom atom = XInternAtom(disp, "_NET_ACTIVE_WINDOW", True); + + if (atom != None) { + XEvent xev; + + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.message_type = atom; + xev.xclient.display = disp; + xev.xclient.window = winId; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 1; + xev.xclient.data.l[1] = 0; + xev.xclient.data.l[2] = None; + xev.xclient.data.l[3] = 0; + xev.xclient.data.l[4] = 0; + + XSendEvent(disp, DefaultRootWindow(disp), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + } + } + } + + XFlush(disp); + XCloseDisplay(disp); + } + } +#endif +} diff --git a/src/liquidappwindow.cpp b/src/liquidappwindow.cpp index 13cef42..6f756bf 100644 --- a/src/liquidappwindow.cpp +++ b/src/liquidappwindow.cpp @@ -19,118 +19,12 @@ LiquidAppWindow::LiquidAppWindow(const QString* name) : QWebEngineView() { - // Prevent window from getting way too tiny - setMinimumSize(LQD_APP_WIN_MIN_SIZE_W, LQD_APP_WIN_MIN_SIZE_H); - -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - // Ensure dark mode is enabled on the web page in case the system theme is dark - if (Liquid::detectDarkMode()) { - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--force-dark-mode --blink-settings=darkMode=4 --blink-settings=forceDarkModeEnabled=true --blink-settings=darkModeEnabled=true"); - } -#endif - - // Set default icon -#if !defined(Q_OS_LINUX) // This doesn't work on X11 - setWindowIcon(QIcon(":/images/" PROG_NAME ".svg")); -#endif - - // Disable default QWebEngineView's context menu - setContextMenuPolicy(Qt::PreventContextMenu); - liquidAppName = (QString*)name; - liquidAppConfig = new QSettings(QSettings::IniFormat, - QSettings::UserScope, - QString(PROG_NAME "%1" LQD_APPS_DIR_NAME).arg(QDir::separator()), - *name, - Q_NULLPTR); - - // These default settings affect everything (including sub-frames) -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - LiquidAppWebPage::setWebSettingsToDefault(QWebEngineSettings::globalSettings()); -#endif - - liquidAppWebProfile = new QWebEngineProfile(QString(), this); - liquidAppWebProfile->setHttpCacheType(QWebEngineProfile::MemoryHttpCache); - liquidAppWebProfile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); - - if (!liquidAppWebProfile->isOffTheRecord()) { - qDebug().noquote() << "Web profile is not off-the-record!"; - // Privacy is paramount for this program, separate apps need to be completely siloed - exit(EXIT_FAILURE); - } - - Liquid::applyQtStyleSheets(this); - - liquidAppWebPage = new LiquidAppWebPage(liquidAppWebProfile, this); - setPage(liquidAppWebPage); - - liquidAppWebSettings = liquidAppWebPage->settings(); - - // Set default window title - liquidAppWindowTitle = *liquidAppName; - - updateWindowTitle(*liquidAppName); - - // Pre-fill all possible zoom factors to snap desired zoom level to { - for (qreal z = 1.0 - LQD_ZOOM_LVL_STEP_FINE; z >= LQD_ZOOM_LVL_MIN - LQD_ZOOM_LVL_STEP_FINE && z > 0; z -= LQD_ZOOM_LVL_STEP_FINE) { - if (z >= LQD_ZOOM_LVL_MIN) { - zoomFactors.prepend(z); - } else { - zoomFactors.prepend(LQD_ZOOM_LVL_MIN); - } - } - - if (LQD_ZOOM_LVL_MIN <= 1 && LQD_ZOOM_LVL_MAX >= 1) { - zoomFactors.append(1.0); - } - - for (qreal z = 1.0 + LQD_ZOOM_LVL_STEP_FINE; z <= LQD_ZOOM_LVL_MAX + LQD_ZOOM_LVL_STEP_FINE; z += LQD_ZOOM_LVL_STEP_FINE) { - if (z <= LQD_ZOOM_LVL_MAX) { - zoomFactors.append(z); - } else { - zoomFactors.append(LQD_ZOOM_LVL_MAX); - } - } + QString instanceName = QApplication::applicationName() + "_" + liquidAppName; + singleInstance = new SingleInstance((QWidget*)this, &instanceName); } - - const QUrl startingUrl(liquidAppConfig->value(LQD_CFG_KEY_NAME_URL).toString()); - - if (!startingUrl.isValid()) { - qDebug().noquote() << "Invalid Liquid application URL:" << startingUrl; - return; - } - - liquidAppWebPage->addAllowedDomain(startingUrl.host()); - - loadLiquidAppConfig(); - - // Reveal Liquid app's window and bring it to front - show(); - raise(); - activateWindow(); - - // Connect keyboard shortcuts - bindKeyboardShortcuts(); - - // Initialize context menu - setupContextMenu(); - - // Trigger window title update if changes - connect(this, &QWebEngineView::titleChanged, this, &LiquidAppWindow::updateWindowTitle); - - // Update Liquid app's icon using the one provided by the website - connect(liquidAppWebPage, &QWebEnginePage::iconChanged, this, &LiquidAppWindow::onIconChanged); - - // Catch loading's start - connect(liquidAppWebPage, &QWebEnginePage::loadStarted, this, &LiquidAppWindow::loadStarted); - - // Catch loading's end - connect(liquidAppWebPage, &QWebEnginePage::loadFinished, this, &LiquidAppWindow::loadFinished); - - // Load Liquid app's starting URL - load(startingUrl); } LiquidAppWindow::~LiquidAppWindow(void) @@ -434,6 +328,14 @@ void LiquidAppWindow::hardReload(void) setUrl(url); } +bool LiquidAppWindow::isAlreadyRunning(void) +{ + const bool raiseExisting = true; + const bool isAnotherInstanceRunning = singleInstance->isAlreadyRunning(raiseExisting); + + return isAnotherInstanceRunning; +} + void LiquidAppWindow::loadFinished(bool ok) { pageIsLoading = false; @@ -674,6 +576,120 @@ void LiquidAppWindow::resizeEvent(QResizeEvent* event) QWebEngineView::resizeEvent(event); } +void LiquidAppWindow::run(void) +{ + // Prevent window from getting way too tiny + setMinimumSize(LQD_APP_WIN_MIN_SIZE_W, LQD_APP_WIN_MIN_SIZE_H); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + // Ensure dark mode is enabled on the web page in case the system theme is dark + if (Liquid::detectDarkMode()) { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--force-dark-mode --blink-settings=darkMode=4 --blink-settings=forceDarkModeEnabled=true --blink-settings=darkModeEnabled=true"); + } +#endif + + // Set default icon +#if !defined(Q_OS_LINUX) // This doesn't work on X11 + setWindowIcon(QIcon(":/images/" PROG_NAME ".svg")); +#endif + + // Disable default QWebEngineView's context menu + setContextMenuPolicy(Qt::PreventContextMenu); + + liquidAppConfig = new QSettings(QSettings::IniFormat, + QSettings::UserScope, + QString(PROG_NAME "%1" LQD_APPS_DIR_NAME).arg(QDir::separator()), + *liquidAppName, + Q_NULLPTR); + + // These default settings affect everything (including sub-frames) +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + LiquidAppWebPage::setWebSettingsToDefault(QWebEngineSettings::globalSettings()); +#endif + + liquidAppWebProfile = new QWebEngineProfile(QString(), this); + liquidAppWebProfile->setHttpCacheType(QWebEngineProfile::MemoryHttpCache); + liquidAppWebProfile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); + + if (!liquidAppWebProfile->isOffTheRecord()) { + qDebug().noquote() << "Web profile is not off-the-record!"; + // Privacy is paramount for this program, separate apps need to be completely siloed + exit(EXIT_FAILURE); + } + + Liquid::applyQtStyleSheets(this); + + liquidAppWebPage = new LiquidAppWebPage(liquidAppWebProfile, this); + setPage(liquidAppWebPage); + + liquidAppWebSettings = liquidAppWebPage->settings(); + + // Set default window title + liquidAppWindowTitle = *liquidAppName; + + updateWindowTitle(*liquidAppName); + + // Pre-fill all possible zoom factors to snap desired zoom level to + { + for (qreal z = 1.0 - LQD_ZOOM_LVL_STEP_FINE; z >= LQD_ZOOM_LVL_MIN - LQD_ZOOM_LVL_STEP_FINE && z > 0; z -= LQD_ZOOM_LVL_STEP_FINE) { + if (z >= LQD_ZOOM_LVL_MIN) { + zoomFactors.prepend(z); + } else { + zoomFactors.prepend(LQD_ZOOM_LVL_MIN); + } + } + + if (LQD_ZOOM_LVL_MIN <= 1 && LQD_ZOOM_LVL_MAX >= 1) { + zoomFactors.append(1.0); + } + + for (qreal z = 1.0 + LQD_ZOOM_LVL_STEP_FINE; z <= LQD_ZOOM_LVL_MAX + LQD_ZOOM_LVL_STEP_FINE; z += LQD_ZOOM_LVL_STEP_FINE) { + if (z <= LQD_ZOOM_LVL_MAX) { + zoomFactors.append(z); + } else { + zoomFactors.append(LQD_ZOOM_LVL_MAX); + } + } + } + + const QUrl startingUrl(liquidAppConfig->value(LQD_CFG_KEY_NAME_URL).toString()); + + if (!startingUrl.isValid()) { + qDebug().noquote() << "Invalid Liquid application URL:" << startingUrl; + return; + } + + liquidAppWebPage->addAllowedDomain(startingUrl.host()); + + loadLiquidAppConfig(); + + // Connect keyboard shortcuts + bindKeyboardShortcuts(); + + // Initialize context menu + setupContextMenu(); + + // Trigger window title update if <title> changes + connect(this, &QWebEngineView::titleChanged, this, &LiquidAppWindow::updateWindowTitle); + + // Update Liquid app's icon using the one provided by the website + connect(liquidAppWebPage, &QWebEnginePage::iconChanged, this, &LiquidAppWindow::onIconChanged); + + // Catch loading's start + connect(liquidAppWebPage, &QWebEnginePage::loadStarted, this, &LiquidAppWindow::loadStarted); + + // Catch loading's end + connect(liquidAppWebPage, &QWebEnginePage::loadFinished, this, &LiquidAppWindow::loadFinished); + + // Reveal Liquid app's window + show(); + raise(); + activateWindow(); + + // Load Liquid app's starting URL + load(startingUrl); +} + void LiquidAppWindow::saveLiquidAppConfig(void) { if (qFuzzyCompare(zoomFactor(), 1.0)) { diff --git a/src/main.cpp b/src/main.cpp index 2eea674..dce103d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,29 +13,12 @@ QTextStream cout(stdout); -static QSharedMemory* sharedMemory = Q_NULLPTR; - -LiquidAppWindow* liquidAppWindow; MainWindow* mainWindow; +LiquidAppWindow* liquidAppWindow; -QString getUserName() -{ - QString name = qgetenv("USER"); - - if (name.isEmpty()) { - name = qgetenv("USERNAME"); - } - - return name; -} - +#if defined(Q_OS_UNIX) static void onSignalHandler(int signum) { - if (sharedMemory) { - delete sharedMemory; - sharedMemory = Q_NULLPTR; - } - if (liquidAppWindow) { liquidAppWindow->close(); delete liquidAppWindow; @@ -52,15 +35,13 @@ static void onSignalHandler(int signum) exit(128 + signum); } +#endif int main(int argc, char **argv) { int ret = EXIT_SUCCESS; -#if defined(Q_OS_LINUX) || defined(Q_OS_MAC) - // Handle any further termination signals to ensure - // that the QSharedMemory block is deleted - // even if the process crashes +#if defined(Q_OS_UNIX) signal(SIGHUP, onSignalHandler); signal(SIGINT, onSignalHandler); signal(SIGQUIT, onSignalHandler); @@ -77,32 +58,35 @@ int main(int argc, char **argv) signal(SIGXFSZ, onSignalHandler); #endif -#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // Account for running on high-DPI displays QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif QApplication app(argc, argv); + QApplication::setApplicationName(PROG_NAME); + QApplication::setApplicationDisplayName("Liquid"); + QApplication::setApplicationVersion(VERSION); + QApplication::setOrganizationDomain("y2z.github.io"); + QApplication::setOrganizationName("Y2Z"); + if (argc < 2) { + // Show main program window + mainWindow = new MainWindow; + // Allow only one instance - sharedMemory = new QSharedMemory(getUserName() + "_Liquid"); - if (!sharedMemory->create(4, QSharedMemory::ReadOnly)) { - delete sharedMemory; + if (mainWindow->isAlreadyRunning()) { qDebug().noquote() << QString("Only one instance of Liquid is allowed"); exit(EXIT_FAILURE); } - // Show main program window - mainWindow = new MainWindow; + mainWindow->run(); } else { // App name provided // CLI flags and options QCommandLineParser parser; - QCoreApplication::setApplicationName(PROG_NAME); - QCoreApplication::setApplicationVersion(VERSION); - // parser.setApplicationDescription("Test helper"); - parser.setApplicationDescription("Test helper"); + parser.setApplicationDescription("Convert web resources into desktop applications"); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("app-name", QCoreApplication::translate("main", "Liquid App name")); @@ -147,16 +131,16 @@ int main(int argc, char **argv) // Attempt to load app settings from a config file if (!parser.isSet(editAppDialogFlag) && tempAppSettings->contains(LQD_CFG_KEY_NAME_URL)) { - // // Allow only one instance - sharedMemory = new QSharedMemory(getUserName() + "_Liquid_app_" + liquidAppName); - if (!sharedMemory->create(4, QSharedMemory::ReadOnly)) { - delete sharedMemory; + // Found existing liquid app settings file, show it + liquidAppWindow = new LiquidAppWindow(&liquidAppName); + + // Allow only one instance + if (liquidAppWindow->isAlreadyRunning()) { qDebug().noquote() << QString("Only one instance of Liquid app “%1” is allowed").arg(liquidAppName); exit(EXIT_FAILURE); } - // Found existing liquid app settings file, show it - liquidAppWindow = new LiquidAppWindow(&liquidAppName); + liquidAppWindow->run(); } else { // No such Liquid app found, open Liquid app creation dialog LiquidAppConfigDialog LiquidAppConfigDialog(mainWindow, liquidAppName); @@ -192,10 +176,5 @@ int main(int argc, char **argv) ret = app.exec(); done: - if (sharedMemory != Q_NULLPTR) { - delete sharedMemory; - sharedMemory = Q_NULLPTR; - } - return ret; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index add31f0..59c7053 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -12,86 +12,10 @@ MainWindow::MainWindow() : QScrollArea() { - setWindowTitle(LQD_PROG_TITLE); - - setMinimumSize(LQD_WIN_MIN_SIZE_W, LQD_WIN_MIN_SIZE_H); - setWidgetResizable(true); - - // Set icon -#if !defined(Q_OS_LINUX) // This doesn't work on X11 - setWindowIcon(QIcon(":/images/" PROG_NAME ".svg")); -#endif - - settings = new QSettings(PROG_NAME, PROG_NAME); - if (settings->contains(LQD_CFG_KEY_NAME_WIN_GEOM)) { - QByteArray geometry = QByteArray::fromHex( - settings->value(LQD_CFG_KEY_NAME_WIN_GEOM).toByteArray() - ); - restoreGeometry(geometry); + { + QString instanceName = QApplication::applicationName(); + singleInstance = new SingleInstance((QWidget*)this, &instanceName); } - - Liquid::applyQtStyleSheets(this); - - QWidget* mainWindowWidget = new QWidget(); - setWidget(mainWindowWidget); - - QVBoxLayout* mainWindowLayout = new QVBoxLayout(); - mainWindowLayout->setSpacing(0); - mainWindowLayout->setContentsMargins(0, 0, 0, 0); - - appListTable = new QTableWidget(); - appListTable->setColumnCount(2); - appListTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); - appListTable->horizontalHeader()->hide(); - appListTable->verticalHeader()->hide(); - appListTable->setShowGrid(false); - appListTable->setFocusPolicy(Qt::NoFocus); - // appListTable->setSelectionMode(QAbstractItemView::SingleSelection); - appListTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mainWindowLayout->addWidget(appListTable); - - // Add new liquid app button - createNewLiquidAppButton = new QPushButton(tr(LQD_ICON_ADD)); - createNewLiquidAppButton->setCursor(Qt::PointingHandCursor); - connect(createNewLiquidAppButton, &QPushButton::clicked, [&]() { - LiquidAppConfigDialog LiquidAppConfigDialog(this, ""); - switch (LiquidAppConfigDialog.exec()) { - case QDialog::Accepted: - // Give some time to the filesystem before scanning for the newly created Liquid App - { - QTime proceedAfter = QTime::currentTime().addMSecs(100); - while (QTime::currentTime() < proceedAfter) { - QCoreApplication::processEvents(QEventLoop::AllEvents, 50); - } - } - flushTable(); - populateTable(); - - if (LiquidAppConfigDialog.isPlanningToRun()) { - Liquid::runLiquidApp(LiquidAppConfigDialog.getName()); - } - break; - } - }); - mainWindowLayout->addWidget(createNewLiquidAppButton); - - mainWindowWidget->setLayout(mainWindowLayout); - - // Run the liquid app upon double-click on its row in the table - connect(appListTable, &QTableWidget::cellDoubleClicked, [&](int row, int col) { - Q_UNUSED(col); - Liquid::runLiquidApp(appListTable->item(row, 0)->text()); - }); - - // Connect keyboard shortcuts - bindShortcuts(); - - show(); - raise(); - activateWindow(); - - // Fill the table - populateTable(); } MainWindow::~MainWindow() @@ -122,6 +46,14 @@ void MainWindow::flushTable(void) } } +bool MainWindow::isAlreadyRunning(void) +{ + const bool raiseExisting = true; + const bool isAnotherInstanceRunning = singleInstance->isAlreadyRunning(raiseExisting); + + return isAnotherInstanceRunning; +} + void MainWindow::populateTable(void) { foreach (const QString liquidAppName, Liquid::getLiquidAppsList()) { @@ -240,6 +172,91 @@ void MainWindow::populateTable(void) } } +void MainWindow::run(void) +{ + setWindowTitle(LQD_PROG_TITLE); + + setMinimumSize(LQD_WIN_MIN_SIZE_W, LQD_WIN_MIN_SIZE_H); + setWidgetResizable(true); + + // Set icon +#if !defined(Q_OS_LINUX) // This doesn't work on X11 + setWindowIcon(QIcon(":/images/" PROG_NAME ".svg")); +#endif + + settings = new QSettings(PROG_NAME, PROG_NAME); + if (settings->contains(LQD_CFG_KEY_NAME_WIN_GEOM)) { + QByteArray geometry = QByteArray::fromHex( + settings->value(LQD_CFG_KEY_NAME_WIN_GEOM).toByteArray() + ); + restoreGeometry(geometry); + } + + Liquid::applyQtStyleSheets(this); + + QWidget* mainWindowWidget = new QWidget(); + setWidget(mainWindowWidget); + + QVBoxLayout* mainWindowLayout = new QVBoxLayout(); + mainWindowLayout->setSpacing(0); + mainWindowLayout->setContentsMargins(0, 0, 0, 0); + + appListTable = new QTableWidget(); + appListTable->setColumnCount(2); + appListTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + appListTable->horizontalHeader()->hide(); + appListTable->verticalHeader()->hide(); + appListTable->setShowGrid(false); + appListTable->setFocusPolicy(Qt::NoFocus); + // appListTable->setSelectionMode(QAbstractItemView::SingleSelection); + appListTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mainWindowLayout->addWidget(appListTable); + + // Add new liquid app button + createNewLiquidAppButton = new QPushButton(tr(LQD_ICON_ADD)); + createNewLiquidAppButton->setCursor(Qt::PointingHandCursor); + connect(createNewLiquidAppButton, &QPushButton::clicked, [&]() { + LiquidAppConfigDialog LiquidAppConfigDialog(this, ""); + switch (LiquidAppConfigDialog.exec()) { + case QDialog::Accepted: + // Give some time to the filesystem before scanning for the newly created Liquid App + { + QTime proceedAfter = QTime::currentTime().addMSecs(100); + while (QTime::currentTime() < proceedAfter) { + QCoreApplication::processEvents(QEventLoop::AllEvents, 50); + } + } + flushTable(); + populateTable(); + + if (LiquidAppConfigDialog.isPlanningToRun()) { + Liquid::runLiquidApp(LiquidAppConfigDialog.getName()); + } + break; + } + }); + mainWindowLayout->addWidget(createNewLiquidAppButton); + + mainWindowWidget->setLayout(mainWindowLayout); + + // Run the liquid app upon double-click on its row in the table + connect(appListTable, &QTableWidget::cellDoubleClicked, [&](int row, int col) { + Q_UNUSED(col); + Liquid::runLiquidApp(appListTable->item(row, 0)->text()); + }); + + // Reveal Liquid's main window + show(); + raise(); + activateWindow(); + + // Connect keyboard shortcuts + bindShortcuts(); + + // Fill the table + populateTable(); +} + void MainWindow::saveSettings(void) { settings->setValue(LQD_CFG_KEY_NAME_WIN_GEOM, QString(saveGeometry().toHex()));