diff --git a/CMakeLists.txt b/CMakeLists.txt index 39245b5bf7856ab4e9cb54001d8d64ab87f0528d..b9d94f24b92e66b995043419beb49863ca1764a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,7 @@ set(COMMON_SOURCES ${SRC_DIR}/bannedlistmodel.cpp ${SRC_DIR}/accountlistmodel.cpp ${SRC_DIR}/networkmanager.cpp - ${SRC_DIR}/runguard.cpp + ${SRC_DIR}/instancemanager.cpp ${SRC_DIR}/updatemanager.cpp ${SRC_DIR}/main.cpp ${SRC_DIR}/smartlistmodel.cpp @@ -152,7 +152,7 @@ set(COMMON_HEADERS ${SRC_DIR}/bannedlistmodel.h ${SRC_DIR}/version.h ${SRC_DIR}/accountlistmodel.h - ${SRC_DIR}/runguard.h + ${SRC_DIR}/instancemanager.h ${SRC_DIR}/rendermanager.h ${SRC_DIR}/connectivitymonitor.h ${SRC_DIR}/jamiavatartheme.h diff --git a/JamiInstaller/Config.wxi b/JamiInstaller/Config.wxi index f24ee3ff2a76444a61c4d479aac7bdfafe02f96f..66157310bcb6399597d04fdae03855273fe52bdf 100644 --- a/JamiInstaller/Config.wxi +++ b/JamiInstaller/Config.wxi @@ -6,6 +6,8 @@ <?define Name="Jami (BETA)" ?> <?endif ?> + <?define ExeName="Jami"?> + <?if $(var.Configuration) = Release ?> <?define ReleaseDir="..\x64\Release"?> <?else?> diff --git a/JamiInstaller/Product.wxs b/JamiInstaller/Product.wxs index cf8b8f773680091df78ca6e3c4ec2680385531c9..112c31f944e206cbf0fc5d967a4e62d4745ad74b 100644 --- a/JamiInstaller/Product.wxs +++ b/JamiInstaller/Product.wxs @@ -5,7 +5,7 @@ <Package InstallerVersion="301" Compressed="yes" InstallScope="perMachine" /> <MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes"/> - <MediaTemplate EmbedCab="yes" CompressionLevel="high" /> + <MediaTemplate EmbedCab="yes" CompressionLevel="high" MaximumUncompressedMediaSize="4" /> <!--Disables interaction of the package with the Restart Manager.--> <Property Id="MSIRESTARTMANAGERCONTROL" Value="Disable" /> @@ -44,7 +44,7 @@ <WixVariable Id="WixUIDialogBmp" Value="main-banner.bmp" /> <WixVariable Id="WixUISupportPerUser" Value="0" /> - <CustomAction Id="removeOldJamiFiles" + <CustomAction Id="RemoveOldJamiFiles" Directory="APPLICATIONFOLDER" ExeCommand="cmd /c "del vc_redist.x64.exe; del uninstall.exe; del WinSparkle.dll;"" Execute="deferred" @@ -52,8 +52,9 @@ HideTarget="no" Impersonate="no"/> - <Property Id="QtExecCmdLine" Value='"[WindowsFolder]\System32\taskkill.exe" /F /IM QtWebEngineProcess.exe /IM Jami.exe'/> - <CustomAction Id="JamiProcesses.TaskKill" + <Property Id="QtExecCmdLine" + Value='"[APPLICATIONFOLDER]/$(var.ExeName).exe" --term'/> + <CustomAction Id="TerminateAppProcess" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="immediate" @@ -123,8 +124,8 @@ </InstallUISequence> </UI> <InstallExecuteSequence> - <Custom Action='JamiProcesses.TaskKill' Before='InstallValidate'/> - <Custom Action="removeOldJamiFiles" After="RemoveFiles" /> + <Custom Action='TerminateAppProcess' Before='InstallValidate'/> + <Custom Action="RemoveOldJamiFiles" After="RemoveFiles" /> <Custom Action="LaunchApplication_nonUI" After="InstallFinalize"> WIXNONUILAUNCH </Custom> <Custom Action="Overwrite_WixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" /> </InstallExecuteSequence> diff --git a/JamiInstaller/StandardComponents.wxs b/JamiInstaller/StandardComponents.wxs index 85847159828e8c112bacb9366d9280311e1657a6..0876e216904d4c012bfc294cf543fb15463e0572 100644 --- a/JamiInstaller/StandardComponents.wxs +++ b/JamiInstaller/StandardComponents.wxs @@ -79,7 +79,7 @@ <File Id="filD6887AD9110E4A8D49143C9A8F0B5843" KeyPath="yes" Source="$(var.UcrtDir)\ucrtbase.dll" /> </Component> <Component Id="cmp9CFEE34E3A162AB05264E8B756EC1DEC" Directory="APPLICATIONFOLDER" Guid="*"> - <File Id="fileMain.exe" KeyPath="yes" Source="$(var.ReleaseDir)\Jami.exe" /> + <File Id="fileMain.exe" KeyPath="yes" Source="$(var.ReleaseDir)\$(var.ExeName).exe" /> </Component> </ComponentGroup> </Fragment> diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..11cf7fdec78300bc528c47d8d4ab8e234191eb4a --- /dev/null +++ b/src/instancemanager.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2019-2022 Savoir-faire Linux Inc. + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "instancemanager.h" + +#include "mainapplication.h" + +#include <QCryptographicHash> +#include <QLocalSocket> +#include <QLocalServer> +#include <QSharedMemory> +#include <QSystemSemaphore> + +static 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; +} + +class InstanceManager::Impl : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(Impl) +public: + Impl(const QString& key, MainApplication* mainApp) + : QObject(nullptr) + , mainAppInstance_(mainApp) + , key_(key) + , memLockKey_(generateKeyHash(key, "_memLockKey")) + , sharedmemKey_(generateKeyHash(key, "_sharedmemKey")) + , sharedMem_(sharedmemKey_) + , memLock_(memLockKey_, 1) + {} + ~Impl() = default; + + bool tryToRun() + { + if (isAnotherRunning()) { + // This is a secondary instance, + // connect to the primary instance to trigger a restore + // then fail. + if (connectToLocal()) { + return false; + } + // If not connected, this means that the server doesn't exist + // and the app can be relaunched (can be the case after a client crash or Ctrl+C) + } + + memLock_.acquire(); + const bool result = sharedMem_.create(sizeof(quint64)); + memLock_.release(); + if (!result) { + release(); + return false; + } + + // This is the primary instance, + // listen for subsequent instances. + QLocalServer::removeServer(key_); + server_ = new QLocalServer(); + server_->setSocketOptions(QLocalServer::UserAccessOption); + server_->listen(key_); + QObject::connect(server_, + &QLocalServer::newConnection, + this, + &Impl::handleIncomingConnection); + + return true; + }; + + void tryToKill() + { + if (!isAnotherRunning()) { + return; + } + + // This is a secondary instance, connect to the primary + // instance to trigger a termination then fail. + if (!connectToLocal()) { + return; + } + + socket_->write(reinterpret_cast<const char*>(terminateSeq_.data()), 4); + socket_->waitForBytesWritten(); + }; + + void release() + { + memLock_.acquire(); + if (sharedMem_.isAttached()) + sharedMem_.detach(); + memLock_.release(); + }; + +private Q_SLOTS: + bool connectToLocal() + { + if (!socket_) + socket_ = new QLocalSocket(); + if (!socket_) + return false; + if (socket_->state() == QLocalSocket::UnconnectedState + || socket_->state() == QLocalSocket::ClosingState) { + socket_->connectToServer(key_); + } + if (socket_->state() == QLocalSocket::ConnectingState) { + socket_->waitForConnected(connectionTimeoutMs_); + } + return socket_->state() == QLocalSocket::ConnectedState; + } + + void handleIncomingConnection() + { + connection_ = new QLocalSocket(this); + connection_ = server_->nextPendingConnection(); + connect(connection_, &QLocalSocket::readyRead, this, [this] { + QLocalSocket* clientSocket = (QLocalSocket*) sender(); + QByteArray recievedData; + recievedData = clientSocket->readAll(); + if (recievedData == terminateSeq_) { + qWarning() << "Received terminate signal."; + mainAppInstance_->quit(); + } + }); + + // Restore primary instance + mainAppInstance_->restoreApp(); + }; + +private: + MainApplication* mainAppInstance_; + + const QString key_; + const QString memLockKey_; + const QString sharedmemKey_; + + QSharedMemory sharedMem_; + QSystemSemaphore memLock_; + + QLocalSocket* socket_ {nullptr}; + QLocalServer* server_ {nullptr}; + QLocalSocket* connection_ {nullptr}; + + const int connectionTimeoutMs_ {2000}; + const QByteArray terminateSeq_ {QByteArrayLiteral("\xde\xad\xbe\xef")}; + + bool isAnotherRunning() + { + if (sharedMem_.isAttached()) + return false; + + memLock_.acquire(); + const bool isRunning = sharedMem_.attach(); + if (isRunning) + sharedMem_.detach(); + memLock_.release(); + + return isRunning; + }; +}; + +InstanceManager::InstanceManager(MainApplication* mainApp) + : QObject(mainApp) +{ + QCryptographicHash appData(QCryptographicHash::Sha256); + appData.addData(QApplication::applicationName().toUtf8()); + appData.addData(QApplication::organizationDomain().toUtf8()); + pimpl_ = std::make_unique<Impl>(appData.result(), mainApp); +} + +InstanceManager::~InstanceManager() +{ + pimpl_->release(); +} + +bool +InstanceManager::tryToRun() +{ + return pimpl_->tryToRun(); +} + +void +InstanceManager::tryToKill() +{ + pimpl_->tryToKill(); +} + +#include "moc_instancemanager.cpp" +#include "instancemanager.moc" diff --git a/src/runguard.h b/src/instancemanager.h similarity index 56% rename from src/runguard.h rename to src/instancemanager.h index f1d1e287f7556c6eebb69b39a465e194ef064ac6..6da3056470a4706f4995511f90e910b79477734d 100644 --- a/src/runguard.h +++ b/src/instancemanager.h @@ -15,45 +15,27 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -// Based on: https://stackoverflow.com/a/28172162 #pragma once #include <QObject> -#include <QSharedMemory> -#include <QSystemSemaphore> -#include <QtNetwork/QLocalServer> -#include <QtNetwork/QLocalSocket> + +#include <memory> class MainApplication; -class RunGuard : public QObject +class InstanceManager final : public QObject { Q_OBJECT; - + Q_DISABLE_COPY(InstanceManager) public: - RunGuard(const QString& key, MainApplication* mainApp = nullptr); - ~RunGuard(); + explicit InstanceManager(MainApplication* mainApp); + ~InstanceManager(); - bool isAnotherRunning(); bool tryToRun(); - void release(); - -private Q_SLOTS: - void tryRestorePrimaryInstance(); + void tryToKill(); private: - MainApplication* mainAppInstance_; - - const QString key_; - const QString memLockKey_; - const QString sharedmemKey_; - - QSharedMemory sharedMem_; - QSystemSemaphore memLock_; - - QLocalSocket* socket_ {nullptr}; - QLocalServer* server_ {nullptr}; - - Q_DISABLE_COPY(RunGuard) + class Impl; + std::unique_ptr<Impl> pimpl_; }; diff --git a/src/main.cpp b/src/main.cpp index e8ab2806466da495f97f0f46e012df1b2f9903a9..52f6c9c9eff6770ac378bba436dcdbd8c0fad967 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -/*! +/* * Copyright (C) 2015-2022 Savoir-faire Linux Inc. * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com> * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> @@ -19,7 +19,7 @@ */ #include "mainapplication.h" -#include "runguard.h" +#include "instancemanager.h" #include "version.h" #include <QCryptographicHash> @@ -97,29 +97,24 @@ main(int argc, char* argv[]) MainApplication app(argc, newArgv); - /* - * Runguard to make sure that only one instance runs at a time. - * Note: needs to be after the creation of the application - */ - QCryptographicHash appData(QCryptographicHash::Sha256); - appData.addData(QApplication::applicationName().toUtf8()); - appData.addData(QApplication::organizationDomain().toUtf8()); - RunGuard guard(appData.result(), &app); - if (!guard.tryToRun()) { + // InstanceManager prevents multiple instances, and will handle + // IPC termination requests to and from secondary instances, which + // is used to gracefully terminate the app from an installer script + // during an update. + InstanceManager im(&app); + if (app.getOpt(MainApplication::Option::TerminationRequested).toBool()) { + qWarning() << "Attempting to terminate other instances."; + im.tryToKill(); + return 0; + } else if (!im.tryToRun()) { + qWarning() << "Another instance is running."; return 0; } if (!app.init()) { - guard.release(); return 0; } - /* - * Exec the application. - */ - auto ret = app.exec(); - - guard.release(); - return ret; + return app.exec(); } #endif diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp index 7bced74af70dcf057f372017a87f8a4ee2e4b4e9..8af2c3bbe2809c0bc56a89a739f07e2d1366ca74 100644 --- a/src/mainapplication.cpp +++ b/src/mainapplication.cpp @@ -53,16 +53,6 @@ #include <gnutls/gnutls.h> #endif -namespace opts { -// Keys used to store command-line options. -constexpr static const char STARTMINIMIZED[] = "STARTMINIMIZED"; -constexpr static const char DEBUG[] = "DEBUG"; -constexpr static const char DEBUGCONSOLE[] = "DEBUGCONSOLE"; -constexpr static const char DEBUGFILE[] = "DEBUGFILE"; -constexpr static const char UPDATEURL[] = "UPDATEURL"; -constexpr static const char MUTEDAEMON[] = "MUTEDAEMON"; -} // namespace opts - static void consoleDebug() { @@ -147,6 +137,7 @@ MainApplication::fileDebug(QFile* debugFile) MainApplication::MainApplication(int& argc, char** argv) : QApplication(argc, argv) { + parseArguments(); QObject::connect(this, &QApplication::aboutToQuit, [this] { cleanup(); }); } @@ -179,9 +170,7 @@ MainApplication::init() setenv("QT_QPA_PLATFORMTHEME", "gtk3", true); #endif - auto results = parseArguments(); - - if (results[opts::DEBUG].toBool()) { + if (runOptions_[Option::Debug].toBool()) { consoleDebug(); } @@ -193,9 +182,9 @@ MainApplication::init() gnutls_global_init(); #endif - initLrc(results[opts::UPDATEURL].toString(), + initLrc(runOptions_[Option::UpdateUrl].toString(), connectivityMonitor_.get(), - results[opts::DEBUG].toBool() && !results[opts::MUTEDAEMON].toBool()); + runOptions_[Option::Debug].toBool() && !runOptions_[Option::MuteJamid].toBool()); #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) using namespace Interfaces; @@ -206,10 +195,12 @@ MainApplication::init() engine_->load(QUrl(QStringLiteral("qrc:/src/DaemonReconnectWindow.qml"))); exec(); - if ((!lrc::api::Lrc::isConnected()) || (!lrc::api::Lrc::dbusIsValid())) + if ((!lrc::api::Lrc::isConnected()) || (!lrc::api::Lrc::dbusIsValid())) { + qWarning() << "Can't connect to the daemon via D-Bus."; return false; - else + } else { engine_.reset(new QQmlApplicationEngine()); + } } #endif @@ -228,14 +219,14 @@ MainApplication::init() [this] { engine_->quit(); }, Qt::DirectConnection); - if (results[opts::DEBUGFILE].toBool()) { + if (runOptions_[Option::DebugToFile].toBool()) { debugFile_.reset(new QFile(getDebugFilePath())); debugFile_->open(QIODevice::WriteOnly | QIODevice::Truncate); debugFile_->close(); fileDebug(debugFile_.get()); } - if (results[opts::DEBUGCONSOLE].toBool()) { + if (runOptions_[Option::DebugToConsole].toBool()) { vsConsoleDebug(); } @@ -253,7 +244,7 @@ MainApplication::init() initQmlLayer(); settingsManager_->setValue(Settings::Key::StartMinimized, - results[opts::STARTMINIMIZED].toBool()); + runOptions_[Option::StartMinimized].toBool()); initSystray(); @@ -296,10 +287,9 @@ MainApplication::initLrc(const QString& downloadUrl, ConnectivityMonitor* cm, bo lrcInstance_->subscribeToDebugReceived(); } -const QVariantMap +void MainApplication::parseArguments() { - QVariantMap results; QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); @@ -325,32 +315,34 @@ MainApplication::parseArguments() QCommandLineOption debugOption({"d", "debug"}, "Debug out."); parser.addOption(debugOption); + QCommandLineOption debugFileOption({"f", "file"}, "Debug to file."); + parser.addOption(debugFileOption); + #ifdef Q_OS_WINDOWS QCommandLineOption debugConsoleOption({"c", "console"}, "Debug out to IDE console."); parser.addOption(debugConsoleOption); - QCommandLineOption debugFileOption({"f", "file"}, "Debug to file."); - parser.addOption(debugFileOption); - QCommandLineOption updateUrlOption({"u", "url"}, "<url> for debugging version queries.", "url"); parser.addOption(updateUrlOption); + #endif + QCommandLineOption terminateOption({"t", "term"}, "Terminate all instances."); + parser.addOption(terminateOption); QCommandLineOption muteDaemonOption({"q", "quiet"}, "Mute daemon logging. (only if debug)"); parser.addOption(muteDaemonOption); parser.process(*this); - results[opts::STARTMINIMIZED] = parser.isSet(minimizedOption); - results[opts::DEBUG] = parser.isSet(debugOption); + runOptions_[Option::StartMinimized] = parser.isSet(minimizedOption); + runOptions_[Option::Debug] = parser.isSet(debugOption); + runOptions_[Option::DebugToFile] = parser.isSet(debugFileOption); #ifdef Q_OS_WINDOWS - results[opts::DEBUGCONSOLE] = parser.isSet(debugConsoleOption); - results[opts::DEBUGFILE] = parser.isSet(debugFileOption); - results[opts::UPDATEURL] = parser.value(updateUrlOption); + runOptions_[Option::DebugToConsole] = parser.isSet(debugConsoleOption); + runOptions_[Option::UpdateUrl] = parser.value(updateUrlOption); #endif - results[opts::MUTEDAEMON] = parser.isSet(muteDaemonOption); - - return results; + runOptions_[Option::TerminationRequested] = parser.isSet(terminateOption); + runOptions_[Option::MuteJamid] = parser.isSet(muteDaemonOption); } void diff --git a/src/mainapplication.h b/src/mainapplication.h index 9de1b751fc5525a549e9134ec04693421800dfd8..f2b06d9747e88f97011fc7fa82abdaebdb792b79 100644 --- a/src/mainapplication.h +++ b/src/mainapplication.h @@ -57,6 +57,7 @@ private: class MainApplication : public QApplication { Q_OBJECT + Q_DISABLE_COPY(MainApplication) public: explicit MainApplication(int& argc, char** argv); @@ -65,21 +66,36 @@ public: bool init(); void restoreApp(); + enum class Option { + StartMinimized = 0, + Debug, + DebugToConsole, + DebugToFile, + UpdateUrl, + MuteJamid, + TerminationRequested + }; + QVariant getOpt(const Option opt) + { + return runOptions_[opt]; + }; + Q_SIGNALS: void closeRequested(); private: void vsConsoleDebug(); void fileDebug(QFile* debugFile); - void initLrc(const QString& downloadUrl, ConnectivityMonitor* cm, bool logDaemon); - const QVariantMap parseArguments(); + void parseArguments(); void setApplicationFont(); void initQmlLayer(); void initSystray(); void cleanup(); private: + std::map<Option, QVariant> runOptions_; + QScopedPointer<QFile> debugFile_; QScopedPointer<QQmlApplicationEngine> engine_; QScopedPointer<LRCInstance> lrcInstance_; diff --git a/src/runguard.cpp b/src/runguard.cpp deleted file mode 100644 index 8c4d46b3ff55adc6b5aaa2ed5ac9b6f15ff28bdd..0000000000000000000000000000000000000000 --- a/src/runguard.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2019-2022 Savoir-faire Linux Inc. - * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -// Based on: https://stackoverflow.com/a/28172162 - -#include "runguard.h" - -#include "mainapplication.h" - -#include <QCryptographicHash> -#include <QLocalSocket> - -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; -} - -} // namespace - -RunGuard::RunGuard(const QString& key, MainApplication* mainApp) - : key_(key) - , memLockKey_(generateKeyHash(key, "_memLockKey")) - , sharedmemKey_(generateKeyHash(key, "_sharedmemKey")) - , sharedMem_(sharedmemKey_) - , memLock_(memLockKey_, 1) - , mainAppInstance_(mainApp) -{} - -RunGuard::~RunGuard() -{ - release(); -} - -void -RunGuard::tryRestorePrimaryInstance() -{ - mainAppInstance_->restoreApp(); -} - -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() -{ - if (isAnotherRunning()) { - /* - * This is a secondary instance, - * connect to the primary instance to trigger a restore - * then fail. - */ - if (!socket_) - socket_ = new QLocalSocket(); - if (!socket_) - return false; - if (socket_->state() == QLocalSocket::UnconnectedState - || socket_->state() == QLocalSocket::ClosingState) { - socket_->connectToServer(key_); - } - if (socket_->state() == QLocalSocket::ConnectingState) { - socket_->waitForConnected(); - } - if (socket_->state() == QLocalSocket::ConnectedState) { - return false; - } - // If not connected, this means that the server doesn't exists - // and the app can be relaunched (can be the case after a client crash or Ctrl+C) - } - - memLock_.acquire(); - const bool result = sharedMem_.create(sizeof(quint64)); - memLock_.release(); - if (!result) { - release(); - return false; - } - - /* - * This is the primary instance, - * listen for subsequent instances. - */ - QLocalServer::removeServer(key_); - server_ = new QLocalServer(); - server_->setSocketOptions(QLocalServer::UserAccessOption); - server_->listen(key_); - QObject::connect(server_, - &QLocalServer::newConnection, - this, - &RunGuard::tryRestorePrimaryInstance); - - return true; -} - -void -RunGuard::release() -{ - memLock_.acquire(); - if (sharedMem_.isAttached()) - sharedMem_.detach(); - memLock_.release(); -}