diff --git a/3rdparty/singleapplication/CMakeLists.txt b/3rdparty/singleapplication/CMakeLists.txt index 033f0a61a..2febf5aa6 100644 --- a/3rdparty/singleapplication/CMakeLists.txt +++ b/3rdparty/singleapplication/CMakeLists.txt @@ -6,31 +6,7 @@ include(CheckFunctionExists) check_function_exists(geteuid HAVE_GETEUID) check_function_exists(getpwuid HAVE_GETPWUID) -set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp) -set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h) -qt_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS}) -add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC}) -target_include_directories(singleapplication PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} -) -target_link_libraries(singleapplication PRIVATE - ${QtCore_LIBRARIES} - ${QtWidgets_LIBRARIES} - ${QtNetwork_LIBRARIES} -) - -set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp) -set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h) -qt_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS}) -add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC}) -target_include_directories(singlecoreapplication PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} -) -target_link_libraries(singlecoreapplication PRIVATE - ${QtCore_LIBRARIES} - ${QtNetwork_LIBRARIES} -) - configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h") + +add_subdirectory(singleapplication) +add_subdirectory(singlecoreapplication) diff --git a/3rdparty/singleapplication/singleapplication/CMakeLists.txt b/3rdparty/singleapplication/singleapplication/CMakeLists.txt new file mode 100644 index 000000000..ac57d2b0f --- /dev/null +++ b/3rdparty/singleapplication/singleapplication/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.7) + +add_definitions(-DSINGLEAPPLICATION) + +set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp) +set(HEADERS ../singleapplication_t.h ../singleapplication_p.h) +qt_wrap_cpp(MOC ${HEADERS}) +add_library(singleapplication STATIC ${SOURCES} ${MOC}) +target_include_directories(singleapplication PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_BINARY_DIR}/.. +) +target_link_libraries(singleapplication PUBLIC + ${QtCore_LIBRARIES} + ${QtWidgets_LIBRARIES} + ${QtNetwork_LIBRARIES} +) diff --git a/3rdparty/singleapplication/singleapplication/singleapplication.h b/3rdparty/singleapplication/singleapplication/singleapplication.h new file mode 100644 index 000000000..34beb3ce8 --- /dev/null +++ b/3rdparty/singleapplication/singleapplication/singleapplication.h @@ -0,0 +1,13 @@ +#ifndef SINGLEAPPLICATION_H +#define SINGLEAPPLICATION_H + +#ifdef SINGLEAPPLICATION +# error "SINGLEAPPLICATION already defined." +#endif + +#define SINGLEAPPLICATION +#include "../singleapplication_t.h" +#undef SINGLEAPPLICATION_T_H +#undef SINGLEAPPLICATION + +#endif // SINGLEAPPLICATION_H diff --git a/3rdparty/singleapplication/singleapplication_p.cpp b/3rdparty/singleapplication/singleapplication_p.cpp index 125d09a88..c2df52863 100644 --- a/3rdparty/singleapplication/singleapplication_p.cpp +++ b/3rdparty/singleapplication/singleapplication_p.cpp @@ -68,17 +68,17 @@ # include #endif -#include "singleapplication.h" +#include "singleapplication_t.h" #include "singleapplication_p.h" -SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr) +SingleApplicationPrivateClass::SingleApplicationPrivateClass(SingleApplicationClass *ptr) : q_ptr(ptr), memory_(nullptr), socket_(nullptr), server_(nullptr), instanceNumber_(-1) {} -SingleApplicationPrivate::~SingleApplicationPrivate() { +SingleApplicationPrivateClass::~SingleApplicationPrivateClass() { if (socket_ != nullptr) { socket_->close(); @@ -105,7 +105,7 @@ SingleApplicationPrivate::~SingleApplicationPrivate() { } -QString SingleApplicationPrivate::getUsername() { +QString SingleApplicationPrivateClass::getUsername() { #ifdef Q_OS_UNIX QString username; @@ -141,36 +141,36 @@ QString SingleApplicationPrivate::getUsername() { } -void SingleApplicationPrivate::genBlockServerName() { +void SingleApplicationPrivateClass::genBlockServerName() { QCryptographicHash appData(QCryptographicHash::Sha256); appData.addData("SingleApplication"); - appData.addData(SingleApplication::app_t::applicationName().toUtf8()); - appData.addData(SingleApplication::app_t::organizationName().toUtf8()); - appData.addData(SingleApplication::app_t::organizationDomain().toUtf8()); + appData.addData(SingleApplicationClass::applicationName().toUtf8()); + appData.addData(SingleApplicationClass::organizationName().toUtf8()); + appData.addData(SingleApplicationClass::organizationDomain().toUtf8()); - if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) { - appData.addData(SingleApplication::app_t::applicationVersion().toUtf8()); + if (!(options_ & SingleApplicationClass::Mode::ExcludeAppVersion)) { + appData.addData(SingleApplicationClass::applicationVersion().toUtf8()); } - if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) { + if (!(options_ & SingleApplicationClass::Mode::ExcludeAppPath)) { #if defined(Q_OS_UNIX) const QByteArray appImagePath = qgetenv("APPIMAGE"); if (appImagePath.isEmpty()) { - appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8()); + appData.addData(SingleApplicationClass::applicationFilePath().toUtf8()); } else { appData.addData(appImagePath); } #elif defined(Q_OS_WIN) - appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8()); + appData.addData(SingleApplicationClass::applicationFilePath().toLower().toUtf8()); #else - appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8()); + appData.addData(SingleApplicationClass::applicationFilePath().toUtf8()); #endif } // User level block requires a user specific data in the hash - if (options_ & SingleApplication::Mode::User) { + if (options_ & SingleApplicationClass::Mode::User) { appData.addData(getUsername().toUtf8()); } @@ -179,7 +179,7 @@ void SingleApplicationPrivate::genBlockServerName() { } -void SingleApplicationPrivate::initializeMemoryBlock() const { +void SingleApplicationPrivateClass::initializeMemoryBlock() const { InstancesInfo *instance = static_cast(memory_->data()); instance->primary = false; @@ -190,7 +190,7 @@ void SingleApplicationPrivate::initializeMemoryBlock() const { } -void SingleApplicationPrivate::startPrimary() { +void SingleApplicationPrivateClass::startPrimary() { // Reset the number of connections InstancesInfo *instance = static_cast(memory_->data()); @@ -206,7 +206,7 @@ void SingleApplicationPrivate::startPrimary() { server_ = new QLocalServer(); // Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions - if (options_ & SingleApplication::Mode::User) { + if (options_ & SingleApplicationClass::Mode::User) { server_->setSocketOptions(QLocalServer::UserAccessOption); } else { @@ -214,11 +214,11 @@ void SingleApplicationPrivate::startPrimary() { } server_->listen(blockServerName_); - QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished); + QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivateClass::slotConnectionEstablished); } -void SingleApplicationPrivate::startSecondary() { +void SingleApplicationPrivateClass::startSecondary() { InstancesInfo *instance = static_cast(memory_->data()); @@ -228,7 +228,7 @@ void SingleApplicationPrivate::startSecondary() { } -bool SingleApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) { +bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const ConnectionType connectionType) { QElapsedTimer time; time.start(); @@ -282,11 +282,11 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect } -void SingleApplicationPrivate::writeAck(QLocalSocket *sock) { +void SingleApplicationPrivateClass::writeAck(QLocalSocket *sock) { sock->putChar('\n'); } -bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const { +bool SingleApplicationPrivateClass::writeConfirmedMessage(const int timeout, const QByteArray &msg) const { QElapsedTimer time; time.start(); @@ -306,7 +306,7 @@ bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QB } -bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const { +bool SingleApplicationPrivateClass::writeConfirmedFrame(const int timeout, const QByteArray &msg) const { socket_->write(msg); socket_->flush(); @@ -321,7 +321,7 @@ bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByt } -quint16 SingleApplicationPrivate::blockChecksum() const { +quint16 SingleApplicationPrivateClass::blockChecksum() const { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) quint16 checksum = qChecksum(QByteArray(static_cast(memory_->constData()), offsetof(InstancesInfo, checksum))); @@ -333,7 +333,7 @@ quint16 SingleApplicationPrivate::blockChecksum() const { } -qint64 SingleApplicationPrivate::primaryPid() const { +qint64 SingleApplicationPrivateClass::primaryPid() const { memory_->lock(); InstancesInfo *instance = static_cast(memory_->data()); @@ -344,7 +344,7 @@ qint64 SingleApplicationPrivate::primaryPid() const { } -QString SingleApplicationPrivate::primaryUser() const { +QString SingleApplicationPrivateClass::primaryUser() const { memory_->lock(); InstancesInfo *instance = static_cast(memory_->data()); @@ -358,7 +358,7 @@ QString SingleApplicationPrivate::primaryUser() const { /** * @brief Executed when a connection has been made to the LocalServer */ -void SingleApplicationPrivate::slotConnectionEstablished() { +void SingleApplicationPrivateClass::slotConnectionEstablished() { QLocalSocket *nextConnSocket = server_->nextPendingConnection(); connectionMap_.insert(nextConnSocket, ConnectionInfo()); @@ -396,7 +396,7 @@ void SingleApplicationPrivate::slotConnectionEstablished() { } -void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::ConnectionStage nextStage) { +void SingleApplicationPrivateClass::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivateClass::ConnectionStage nextStage) { if (!connectionMap_.contains(sock)) { return; @@ -420,7 +420,7 @@ void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const Singl } -bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) { +bool SingleApplicationPrivateClass::isFrameComplete(QLocalSocket *sock) { if (!connectionMap_.contains(sock)) { return false; @@ -431,9 +431,9 @@ bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) { } -void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { +void SingleApplicationPrivateClass::readInitMessageBody(QLocalSocket *sock) { - Q_Q(SingleApplication); + Q_Q(SingleApplicationClass); if (!isFrameComplete(sock)) { return; @@ -478,7 +478,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { info.instanceId = instanceId; info.stage = StageConnectedHeader; - if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) { + if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplicationClass::Mode::SecondaryNotification)) { emit q->instanceStarted(); } @@ -486,9 +486,9 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { } -void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) { +void SingleApplicationPrivateClass::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) { - Q_Q(SingleApplication); + Q_Q(SingleApplicationClass); if (!isFrameComplete(dataSocket)) { return; @@ -505,7 +505,7 @@ void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const } -void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) { +void SingleApplicationPrivateClass::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) { if (closedSocket->bytesAvailable() > 0) { slotDataAvailable(closedSocket, instanceId); @@ -513,7 +513,7 @@ void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSo } -void SingleApplicationPrivate::randomSleep() { +void SingleApplicationPrivateClass::randomSleep() { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U)); diff --git a/3rdparty/singleapplication/singleapplication_p.h b/3rdparty/singleapplication/singleapplication_p.h index 26f334aee..26f2e39a1 100644 --- a/3rdparty/singleapplication/singleapplication_p.h +++ b/3rdparty/singleapplication/singleapplication_p.h @@ -39,7 +39,7 @@ #include #include -#include "singleapplication.h" +#include "singleapplication_t.h" class QLocalServer; class QLocalSocket; @@ -60,7 +60,7 @@ struct ConnectionInfo { quint8 stage; }; -class SingleApplicationPrivate : public QObject { +class SingleApplicationPrivateClass : public QObject { Q_OBJECT public: @@ -76,10 +76,10 @@ class SingleApplicationPrivate : public QObject { StageConnectedHeader = 2, StageConnectedBody = 3, }; - Q_DECLARE_PUBLIC(SingleApplication) + Q_DECLARE_PUBLIC(SingleApplicationClass) - explicit SingleApplicationPrivate(SingleApplication *ptr); - ~SingleApplicationPrivate() override; + explicit SingleApplicationPrivateClass(SingleApplicationClass *ptr); + ~SingleApplicationPrivateClass() override; static QString getUsername(); void genBlockServerName(); @@ -98,13 +98,13 @@ class SingleApplicationPrivate : public QObject { bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const; static void randomSleep(); - SingleApplication *q_ptr; + SingleApplicationClass *q_ptr; QSharedMemory *memory_; QLocalSocket *socket_; QLocalServer *server_; quint32 instanceNumber_; QString blockServerName_; - SingleApplication::Options options_; + SingleApplicationClass::Options options_; QHash connectionMap_; public slots: diff --git a/3rdparty/singleapplication/singleapplication.cpp b/3rdparty/singleapplication/singleapplication_t.cpp similarity index 78% rename from 3rdparty/singleapplication/singleapplication.cpp rename to 3rdparty/singleapplication/singleapplication_t.cpp index 650944f97..724b6da37 100644 --- a/3rdparty/singleapplication/singleapplication.cpp +++ b/3rdparty/singleapplication/singleapplication_t.cpp @@ -35,7 +35,6 @@ #include #include -#include #include #include #include @@ -46,7 +45,7 @@ # include #endif -#include "singleapplication.h" +#include "singleapplication_t.h" #include "singleapplication_p.h" /** @@ -57,11 +56,15 @@ * @param options Optional flags to toggle specific behaviour * @param timeout Maximum time blocking functions are allowed during app load */ -SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout) - : app_t(argc, argv), - d_ptr(new SingleApplicationPrivate(this)) { +SingleApplicationClass::SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout) + : ApplicationClass(argc, argv), + d_ptr(new SingleApplicationPrivateClass(this)) { +#if defined(SINGLEAPPLICATION) Q_D(SingleApplication); +#elif defined(SINGLECOREAPPLICATION) + Q_D(SingleCoreApplication); +#endif // Store the current mode of the program d->options_ = options; @@ -70,7 +73,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSe d->genBlockServerName(); // To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time - SingleApplicationPrivate::randomSleep(); + SingleApplicationPrivateClass::randomSleep(); #ifdef Q_OS_UNIX // By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix. @@ -138,7 +141,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSe qDebug() << "SingleApplication: Unable to unlock memory for random wait."; qDebug() << d->memory_->errorString(); } - SingleApplicationPrivate::randomSleep(); + SingleApplicationPrivateClass::randomSleep(); if (!d->memory_->lock()) { qCritical() << "SingleApplication: Unable to lock memory after random wait."; abortSafely(); @@ -158,7 +161,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSe if (allowSecondary) { d->startSecondary(); if (d->options_ & Mode::SecondaryNotification) { - d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance); + d->connectToPrimary(timeout, SingleApplicationPrivateClass::SecondaryInstance); } if (!d->memory_->unlock()) { qDebug() << "SingleApplication: Unable to unlock memory after secondary start."; @@ -172,33 +175,54 @@ SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSe qDebug() << d->memory_->errorString(); } - d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance); + d->connectToPrimary(timeout, SingleApplicationPrivateClass::NewInstance); delete d; } -SingleApplication::~SingleApplication() { +SingleApplicationClass::~SingleApplicationClass() { + +#if defined(SINGLEAPPLICATION) Q_D(SingleApplication); +#elif defined(SINGLECOREAPPLICATION) + Q_D(SingleCoreApplication); +#endif + delete d; + } /** * Checks if the current application instance is primary. * @return Returns true if the instance is primary, false otherwise. */ -bool SingleApplication::isPrimary() const { +bool SingleApplicationClass::isPrimary() const { + +#if defined(SINGLEAPPLICATION) Q_D(const SingleApplication); +#elif defined(SINGLECOREAPPLICATION) + Q_D(const SingleCoreApplication); +#endif + return d->server_ != nullptr; + } /** * Checks if the current application instance is secondary. * @return Returns true if the instance is secondary, false otherwise. */ -bool SingleApplication::isSecondary() const { +bool SingleApplicationClass::isSecondary() const { + +#if defined(SINGLEAPPLICATION) Q_D(const SingleApplication); +#elif defined(SINGLECOREAPPLICATION) + Q_D(const SingleCoreApplication); +#endif + return d->server_ == nullptr; + } /** @@ -206,9 +230,16 @@ bool SingleApplication::isSecondary() const { * It is reset when the first (primary) instance of your app starts and only incremented afterwards. * @return Returns a unique instance id. */ -quint32 SingleApplication::instanceId() const { +quint32 SingleApplicationClass::instanceId() const { + +#if defined(SINGLEAPPLICATION) Q_D(const SingleApplication); +#elif defined(SINGLECOREAPPLICATION) + Q_D(const SingleCoreApplication); +#endif + return d->instanceNumber_; + } /** @@ -216,26 +247,40 @@ quint32 SingleApplication::instanceId() const { * Especially useful when SingleApplication is coupled with OS. specific APIs. * @return Returns the primary instance PID. */ -qint64 SingleApplication::primaryPid() const { +qint64 SingleApplicationClass::primaryPid() const { + +#if defined(SINGLEAPPLICATION) Q_D(const SingleApplication); +#elif defined(SINGLECOREAPPLICATION) + Q_D(const SingleCoreApplication); +#endif + return d->primaryPid(); + } /** * Returns the username the primary instance is running as. * @return Returns the username the primary instance is running as. */ -QString SingleApplication::primaryUser() const { +QString SingleApplicationClass::primaryUser() const { + +#if defined(SINGLEAPPLICATION) Q_D(const SingleApplication); +#elif defined(SINGLECOREAPPLICATION) + Q_D(const SingleCoreApplication); +#endif + return d->primaryUser(); + } /** * Returns the username the current instance is running as. * @return Returns the username the current instance is running as. */ -QString SingleApplication::currentUser() const { - return SingleApplicationPrivate::getUsername(); +QString SingleApplicationClass::currentUser() const { + return SingleApplicationPrivateClass::getUsername(); } /** @@ -244,28 +289,37 @@ QString SingleApplication::currentUser() const { * @param timeout the maximum timeout in milliseconds for blocking functions. * @return true if the message was sent successfully, false otherwise. */ -bool SingleApplication::sendMessage(const QByteArray &message, const int timeout) { +bool SingleApplicationClass::sendMessage(const QByteArray &message, const int timeout) { +#if defined(SINGLEAPPLICATION) Q_D(SingleApplication); +#elif defined(SINGLECOREAPPLICATION) + Q_D(SingleCoreApplication); +#endif // Nobody to connect to if (isPrimary()) return false; // Make sure the socket is connected - if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) { + if (!d->connectToPrimary(timeout, SingleApplicationPrivateClass::Reconnect)) { return false; } return d->writeConfirmedMessage(timeout, message); + } /** * Cleans up the shared memory block and exits with a failure. * This function halts program execution. */ -void SingleApplication::abortSafely() { +void SingleApplicationClass::abortSafely() { +#if defined(SINGLEAPPLICATION) Q_D(SingleApplication); +#elif defined(SINGLECOREAPPLICATION) + Q_D(SingleCoreApplication); +#endif qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString(); delete d; diff --git a/3rdparty/singleapplication/singleapplication.h b/3rdparty/singleapplication/singleapplication_t.h similarity index 77% rename from 3rdparty/singleapplication/singleapplication.h rename to 3rdparty/singleapplication/singleapplication_t.h index 2e92da140..3e1cb24ba 100644 --- a/3rdparty/singleapplication/singleapplication.h +++ b/3rdparty/singleapplication/singleapplication_t.h @@ -31,25 +31,41 @@ // // -#ifndef SINGLEAPPLICATION_H -#define SINGLEAPPLICATION_H +#ifndef SINGLEAPPLICATION_T_H +#define SINGLEAPPLICATION_T_H #include -#include + +#undef ApplicationClass +#undef SingleApplicationClass +#undef SingleApplicationPrivateClass + +#if defined(SINGLEAPPLICATION) +# include +# define ApplicationClass QApplication +# define SingleApplicationClass SingleApplication +# define SingleApplicationPrivateClass SingleApplicationPrivate +#elif defined(SINGLECOREAPPLICATION) +# include +# define ApplicationClass QCoreApplication +# define SingleApplicationClass SingleCoreApplication +# define SingleApplicationPrivateClass SingleCoreApplicationPrivate +#else +# error "Define SINGLEAPPLICATION or SINGLECOREAPPLICATION." +#endif + #include #include -class SingleApplicationPrivate; +class SingleApplicationPrivateClass; /** * @brief The SingleApplication class handles multiple instances of the same Application * @see QApplication */ -class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument +class SingleApplicationClass : public ApplicationClass { // clazy:exclude=ctor-missing-parent-argument Q_OBJECT - using app_t = QApplication; - public: /** * @brief Mode of operation of SingleApplication. @@ -89,8 +105,8 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p * initialisation will be completed in given time, though is a good hint. * Usually 4*timeout would be the worst case (fail) scenario. */ - explicit SingleApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000); - ~SingleApplication() override; + explicit SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000); + ~SingleApplicationClass() override; /** * @brief Returns if the instance is the primary instance @@ -142,11 +158,15 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p void receivedMessage(quint32 instanceId, QByteArray message); private: - SingleApplicationPrivate *d_ptr; + SingleApplicationPrivateClass *d_ptr; +#if defined(SINGLEAPPLICATION) Q_DECLARE_PRIVATE(SingleApplication) +#elif defined(SINGLECOREAPPLICATION) + Q_DECLARE_PRIVATE(SingleCoreApplication) +#endif void abortSafely(); }; -Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options) +Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplicationClass::Options) -#endif // SINGLEAPPLICATION_H +#endif // SINGLEAPPLICATION_T_H diff --git a/3rdparty/singleapplication/singlecoreapplication.cpp b/3rdparty/singleapplication/singlecoreapplication.cpp deleted file mode 100644 index 1b960ea7a..000000000 --- a/3rdparty/singleapplication/singlecoreapplication.cpp +++ /dev/null @@ -1,275 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) Itay Grudev 2015 - 2020 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// -// W A R N I N G !!! -// ----------------- -// -// This is a modified version of SingleApplication, -// The original version is at: -// -// https://github.com/itay-grudev/SingleApplication -// -// - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) -# include -#endif - -#include "singlecoreapplication.h" -#include "singlecoreapplication_p.h" - -/** - * @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists - * @param argc - * @param argv - * @param allowSecondary Whether to enable secondary instance support - * @param options Optional flags to toggle specific behaviour - * @param timeout Maximum time blocking functions are allowed during app load - */ -SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout) - : app_t(argc, argv), - d_ptr(new SingleCoreApplicationPrivate(this)) { - - Q_D(SingleCoreApplication); - - // Store the current mode of the program - d->options_ = options; - - // Generating an application ID used for identifying the shared memory block and QLocalServer - d->genBlockServerName(); - - // To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time - SingleCoreApplicationPrivate::randomSleep(); - -#ifdef Q_OS_UNIX - // By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix. -#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) - d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_)); -#else - d->memory_ = new QSharedMemory(d->blockServerName_); -#endif - d->memory_->attach(); - delete d->memory_; -#endif - - // Guarantee thread safe behaviour with a shared memory block. -#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) - d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_)); -#else - d->memory_ = new QSharedMemory(d->blockServerName_); -#endif - - // Create a shared memory block - if (d->memory_->create(sizeof(InstancesInfo))) { - // Initialize the shared memory block - if (!d->memory_->lock()) { - qCritical() << "SingleCoreApplication: Unable to lock memory block after create."; - abortSafely(); - } - d->initializeMemoryBlock(); - } - else { - if (d->memory_->error() == QSharedMemory::AlreadyExists) { - // Attempt to attach to the memory segment - if (!d->memory_->attach()) { - qCritical() << "SingleCoreApplication: Unable to attach to shared memory block."; - abortSafely(); - } - if (!d->memory_->lock()) { - qCritical() << "SingleCoreApplication: Unable to lock memory block after attach."; - abortSafely(); - } - } - else { - qCritical() << "SingleCoreApplication: Unable to create block."; - abortSafely(); - } - } - - InstancesInfo *instance = static_cast(d->memory_->data()); - QElapsedTimer time; - time.start(); - - // Make sure the shared memory block is initialised and in consistent state - forever { - // If the shared memory block's checksum is valid continue - if (d->blockChecksum() == instance->checksum) break; - - // If more than 5s have elapsed, assume the primary instance crashed and assume it's position - if (time.elapsed() > 5000) { - qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; - d->initializeMemoryBlock(); - } - - // Otherwise wait for a random period and try again. - // The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster - if (!d->memory_->unlock()) { - qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait."; - qDebug() << d->memory_->errorString(); - } - SingleCoreApplicationPrivate::randomSleep(); - if (!d->memory_->lock()) { - qCritical() << "SingleCoreApplication: Unable to lock memory after random wait."; - abortSafely(); - } - } - - if (!instance->primary) { - d->startPrimary(); - if (!d->memory_->unlock()) { - qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start."; - qDebug() << d->memory_->errorString(); - } - return; - } - - // Check if another instance can be started - if (allowSecondary) { - d->startSecondary(); - if (d->options_ & Mode::SecondaryNotification) { - d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance); - } - if (!d->memory_->unlock()) { - qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start."; - qDebug() << d->memory_->errorString(); - } - return; - } - - if (!d->memory_->unlock()) { - qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution."; - qDebug() << d->memory_->errorString(); - } - - d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance); - - delete d; - -} - -SingleCoreApplication::~SingleCoreApplication() { - Q_D(SingleCoreApplication); - delete d; -} - -/** - * Checks if the current application instance is primary. - * @return Returns true if the instance is primary, false otherwise. - */ -bool SingleCoreApplication::isPrimary() const { - Q_D(const SingleCoreApplication); - return d->server_ != nullptr; -} - -/** - * Checks if the current application instance is secondary. - * @return Returns true if the instance is secondary, false otherwise. - */ -bool SingleCoreApplication::isSecondary() const { - Q_D(const SingleCoreApplication); - return d->server_ == nullptr; -} - -/** - * Allows you to identify an instance by returning unique consecutive instance ids. - * It is reset when the first (primary) instance of your app starts and only incremented afterwards. - * @return Returns a unique instance id. - */ -quint32 SingleCoreApplication::instanceId() const { - Q_D(const SingleCoreApplication); - return d->instanceNumber_; -} - -/** - * Returns the OS PID (Process Identifier) of the process running the primary instance. - * Especially useful when SingleCoreApplication is coupled with OS. specific APIs. - * @return Returns the primary instance PID. - */ -qint64 SingleCoreApplication::primaryPid() const { - Q_D(const SingleCoreApplication); - return d->primaryPid(); -} - -/** - * Returns the username the primary instance is running as. - * @return Returns the username the primary instance is running as. - */ -QString SingleCoreApplication::primaryUser() const { - Q_D(const SingleCoreApplication); - return d->primaryUser(); -} - -/** - * Returns the username the current instance is running as. - * @return Returns the username the current instance is running as. - */ -QString SingleCoreApplication::currentUser() const { - return SingleCoreApplicationPrivate::getUsername(); -} - -/** - * Sends message to the Primary Instance. - * @param message The message to send. - * @param timeout the maximum timeout in milliseconds for blocking functions. - * @return true if the message was sent successfully, false otherwise. - */ -bool SingleCoreApplication::sendMessage(const QByteArray &message, const int timeout) { - - Q_D(SingleCoreApplication); - - // Nobody to connect to - if (isPrimary()) return false; - - // Make sure the socket is connected - if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect)) { - return false; - } - - return d->writeConfirmedMessage(timeout, message); -} - -/** - * Cleans up the shared memory block and exits with a failure. - * This function halts program execution. - */ -void SingleCoreApplication::abortSafely() { - - Q_D(SingleCoreApplication); - - qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString(); - delete d; - - ::exit(EXIT_FAILURE); - -} diff --git a/3rdparty/singleapplication/singlecoreapplication.h b/3rdparty/singleapplication/singlecoreapplication.h deleted file mode 100644 index 97759f1c4..000000000 --- a/3rdparty/singleapplication/singlecoreapplication.h +++ /dev/null @@ -1,152 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) Itay Grudev 2015 - 2020 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// -// W A R N I N G !!! -// ----------------- -// -// This is a modified version of SingleApplication, -// The original version is at: -// -// https://github.com/itay-grudev/SingleApplication -// -// - -#ifndef SINGLECOREAPPLICATION_H -#define SINGLECOREAPPLICATION_H - -#include -#include -#include -#include - -class SingleCoreApplicationPrivate; - -/** - * @brief The SingleCoreApplication class handles multiple instances of the same Application - * @see QCoreApplication - */ -class SingleCoreApplication : public QCoreApplication { // clazy:exclude=ctor-missing-parent-argument - Q_OBJECT - - using app_t = QCoreApplication; - - public: - /** - * @brief Mode of operation of SingleCoreApplication. - * Whether the block should be user-wide or system-wide and whether the - * primary instance should be notified when a secondary instance had been - * started. - * @note Operating system can restrict the shared memory blocks to the same - * user, in which case the User/System modes will have no effect and the - * block will be user wide. - * @enum - */ - enum class Mode { - User = 1 << 0, - System = 1 << 1, - SecondaryNotification = 1 << 2, - ExcludeAppVersion = 1 << 3, - ExcludeAppPath = 1 << 4 - }; - Q_DECLARE_FLAGS(Options, Mode) - - /** - * @brief Intitializes a SingleCoreApplication instance with argc command line - * arguments in argv - * @arg {int &} argc - Number of arguments in argv - * @arg {const char *[]} argv - Supplied command line arguments - * @arg {bool} allowSecondary - Whether to start the instance as secondary - * if there is already a primary instance. - * @arg {Mode} mode - Whether for the SingleCoreApplication block to be applied - * User wide or System wide. - * @arg {int} timeout - Timeout to wait in milliseconds. - * @note argc and argv may be changed as Qt removes arguments that it - * recognizes - * @note Mode::SecondaryNotification only works if set on both the primary - * instance and the secondary instance. - * @note The timeout is just a hint for the maximum time of blocking - * operations. It does not guarantee that the SingleCoreApplication - * initialisation will be completed in given time, though is a good hint. - * Usually 4*timeout would be the worst case (fail) scenario. - */ - explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000); - ~SingleCoreApplication() override; - - /** - * @brief Returns if the instance is the primary instance - * @returns {bool} - */ - bool isPrimary() const; - - /** - * @brief Returns if the instance is a secondary instance - * @returns {bool} - */ - bool isSecondary() const; - - /** - * @brief Returns a unique identifier for the current instance - * @returns {qint32} - */ - quint32 instanceId() const; - - /** - * @brief Returns the process ID (PID) of the primary instance - * @returns {qint64} - */ - qint64 primaryPid() const; - - /** - * @brief Returns the username of the user running the primary instance - * @returns {QString} - */ - QString primaryUser() const; - - /** - * @brief Returns the username of the current user - * @returns {QString} - */ - QString currentUser() const; - - /** - * @brief Sends a message to the primary instance. Returns true on success. - * @param {int} timeout - Timeout for connecting - * @returns {bool} - * @note sendMessage() will return false if invoked from the primary - * instance. - */ - bool sendMessage(const QByteArray &message, const int timeout = 1000); - - signals: - void instanceStarted(); - void receivedMessage(quint32 instanceId, QByteArray message); - - private: - SingleCoreApplicationPrivate *d_ptr; - Q_DECLARE_PRIVATE(SingleCoreApplication) - void abortSafely(); -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options) - -#endif // SINGLECOREAPPLICATION_H diff --git a/3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt b/3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt new file mode 100644 index 000000000..919c896cb --- /dev/null +++ b/3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.7) + +add_definitions(-DSINGLECOREAPPLICATION) + +set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp) +set(HEADERS ../singleapplication_t.h ../singleapplication_p.h) +qt_wrap_cpp(MOC ${HEADERS}) +add_library(singlecoreapplication STATIC ${SOURCES} ${MOC}) +target_include_directories(singlecoreapplication PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_BINARY_DIR}/.. +) +target_link_libraries(singlecoreapplication PUBLIC + ${QtCore_LIBRARIES} + ${QtNetwork_LIBRARIES} +) diff --git a/3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h b/3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h new file mode 100644 index 000000000..0d510c61f --- /dev/null +++ b/3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h @@ -0,0 +1,13 @@ +#ifndef SINGLECOREAPPLICATION_H +#define SINGLECOREAPPLICATION_H + +#ifdef SINGLECOREAPPLICATION +# error "SINGLECOREAPPLICATION already defined." +#endif + +#define SINGLECOREAPPLICATION +#include "../singleapplication_t.h" +#undef SINGLEAPPLICATION_T_H +#undef SINGLECOREAPPLICATION + +#endif // SINGLECOREAPPLICATION_H diff --git a/3rdparty/singleapplication/singlecoreapplication_p.cpp b/3rdparty/singleapplication/singlecoreapplication_p.cpp deleted file mode 100644 index 2fd87bcec..000000000 --- a/3rdparty/singleapplication/singlecoreapplication_p.cpp +++ /dev/null @@ -1,525 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) Itay Grudev 2015 - 2020 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// -// W A R N I N G !!! -// ----------------- -// -// This is a modified version of SingleApplication, -// The original version is at: -// -// https://github.com/itay-grudev/SingleApplication -// -// - -#include "config.h" - -#include - -#include -#include - -#ifdef Q_OS_UNIX -# include -# include -# include -#endif - -#ifdef Q_OS_WIN -# ifndef NOMINMAX -# define NOMINMAX 1 -# endif -# include -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) -# include -#else -# include -#endif - -#include "singlecoreapplication.h" -#include "singlecoreapplication_p.h" - -SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr) - : q_ptr(ptr), - memory_(nullptr), - socket_(nullptr), - server_(nullptr), - instanceNumber_(-1) {} - -SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() { - - if (socket_ != nullptr) { - socket_->close(); - delete socket_; - socket_ = nullptr; - } - - if (memory_ != nullptr) { - memory_->lock(); - InstancesInfo *instance = static_cast(memory_->data()); - if (server_ != nullptr) { - server_->close(); - delete server_; - instance->primary = false; - instance->primaryPid = -1; - instance->primaryUser[0] = '\0'; - instance->checksum = blockChecksum(); - } - memory_->unlock(); - - delete memory_; - memory_ = nullptr; - } - -} - -QString SingleCoreApplicationPrivate::getUsername() { - -#ifdef Q_OS_UNIX - QString username; -#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID) - struct passwd *pw = getpwuid(geteuid()); - if (pw) { - username = QString::fromLocal8Bit(pw->pw_name); - } -#endif - if (username.isEmpty()) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - username = qEnvironmentVariable("USER"); -#else - username = QString::fromLocal8Bit(qgetenv("USER")); -#endif - } - return username; -#endif - -#ifdef Q_OS_WIN - wchar_t username[UNLEN + 1]; - // Specifies size of the buffer on input - DWORD usernameLength = UNLEN + 1; - if (GetUserNameW(username, &usernameLength)) { - return QString::fromWCharArray(username); - } -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - return qEnvironmentVariable("USERNAME"); -#else - return QString::fromLocal8Bit(qgetenv("USERNAME")); -#endif -#endif - -} - -void SingleCoreApplicationPrivate::genBlockServerName() { - - QCryptographicHash appData(QCryptographicHash::Sha256); - appData.addData("SingleApplication"); - appData.addData(SingleCoreApplication::app_t::applicationName().toUtf8()); - appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8()); - appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8()); - - if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) { - appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8()); - } - - if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) { -#if defined(Q_OS_UNIX) - const QByteArray appImagePath = qgetenv("APPIMAGE"); - if (appImagePath.isEmpty()) { - appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8()); - } - else { - appData.addData(appImagePath); - } -#elif defined(Q_OS_WIN) - appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8()); -#else - appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8()); -#endif - } - - // User level block requires a user specific data in the hash - if (options_ & SingleCoreApplication::Mode::User) { - appData.addData(getUsername().toUtf8()); - } - - // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements. - blockServerName_ = appData.result().toBase64().replace("/", "_"); - -} - -void SingleCoreApplicationPrivate::initializeMemoryBlock() const { - - InstancesInfo *instance = static_cast(memory_->data()); - instance->primary = false; - instance->secondary = 0; - instance->primaryPid = -1; - instance->primaryUser[0] = '\0'; - instance->checksum = blockChecksum(); - -} - -void SingleCoreApplicationPrivate::startPrimary() { - - // Reset the number of connections - InstancesInfo *instance = static_cast(memory_->data()); - - instance->primary = true; - instance->primaryPid = QCoreApplication::applicationPid(); - qstrncpy(instance->primaryUser, getUsername().toUtf8().data(), sizeof(instance->primaryUser)); - instance->checksum = blockChecksum(); - instanceNumber_ = 0; - // Successful creation means that no main process exists - // So we start a QLocalServer to listen for connections - QLocalServer::removeServer(blockServerName_); - server_ = new QLocalServer(); - - // Restrict access to the socket according to the SingleCoreApplication::Mode::User flag on User level or no restrictions - if (options_ & SingleCoreApplication::Mode::User) { - server_->setSocketOptions(QLocalServer::UserAccessOption); - } - else { - server_->setSocketOptions(QLocalServer::WorldAccessOption); - } - - server_->listen(blockServerName_); - QObject::connect(server_, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished); - -} - -void SingleCoreApplicationPrivate::startSecondary() { - - InstancesInfo *instance = static_cast(memory_->data()); - - instance->secondary += 1; - instance->checksum = blockChecksum(); - instanceNumber_ = instance->secondary; - -} - -bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) { - - QElapsedTimer time; - time.start(); - - // Connect to the Local Server of the Primary Instance if not already connected. - if (socket_ == nullptr) { - socket_ = new QLocalSocket(); - } - - if (socket_->state() == QLocalSocket::ConnectedState) return true; - - if (socket_->state() != QLocalSocket::ConnectedState) { - - forever { - randomSleep(); - - if (socket_->state() != QLocalSocket::ConnectingState) { - socket_->connectToServer(blockServerName_); - } - - if (socket_->state() == QLocalSocket::ConnectingState) { - socket_->waitForConnected(static_cast(timeout - time.elapsed())); - } - - // If connected break out of the loop - if (socket_->state() == QLocalSocket::ConnectedState) break; - - // If elapsed time since start is longer than the method timeout return - if (time.elapsed() >= timeout) return false; - } - } - - // Initialisation message according to the SingleCoreApplication protocol - QByteArray initMsg; - QDataStream writeStream(&initMsg, QIODevice::WriteOnly); - writeStream.setVersion(QDataStream::Qt_5_8); - - writeStream << blockServerName_.toLatin1(); - writeStream << static_cast(connectionType); - writeStream << instanceNumber_; - -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); -#else - quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); -#endif - - writeStream << checksum; - - return writeConfirmedMessage(static_cast(timeout - time.elapsed()), initMsg); - -} - -void SingleCoreApplicationPrivate::writeAck(QLocalSocket *sock) { - sock->putChar('\n'); -} - -bool SingleCoreApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const { - - QElapsedTimer time; - time.start(); - - // Frame 1: The header indicates the message length that follows - QByteArray header; - QDataStream headerStream(&header, QIODevice::WriteOnly); - headerStream.setVersion(QDataStream::Qt_5_8); - headerStream << static_cast(msg.length()); - - if (!writeConfirmedFrame(static_cast(timeout - time.elapsed()), header)) { - return false; - } - - // Frame 2: The message - return writeConfirmedFrame(static_cast(timeout - time.elapsed()), msg); - -} - -bool SingleCoreApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const { - - socket_->write(msg); - socket_->flush(); - - bool result = socket_->waitForReadyRead(timeout); - if (result) { - socket_->read(1); - return true; - } - - return false; - -} - -quint16 SingleCoreApplicationPrivate::blockChecksum() const { - -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(QByteArray(static_cast(memory_->constData()), offsetof(InstancesInfo, checksum))); -#else - quint16 checksum = qChecksum(static_cast(memory_->constData()), offsetof(InstancesInfo, checksum)); -#endif - - return checksum; - -} - -qint64 SingleCoreApplicationPrivate::primaryPid() const { - - memory_->lock(); - InstancesInfo *instance = static_cast(memory_->data()); - qint64 pid = instance->primaryPid; - memory_->unlock(); - - return pid; - -} - -QString SingleCoreApplicationPrivate::primaryUser() const { - - memory_->lock(); - InstancesInfo *instance = static_cast(memory_->data()); - QByteArray username = instance->primaryUser; - memory_->unlock(); - - return QString::fromUtf8(username); - -} - -/** - * @brief Executed when a connection has been made to the LocalServer - */ -void SingleCoreApplicationPrivate::slotConnectionEstablished() { - - QLocalSocket *nextConnSocket = server_->nextPendingConnection(); - connectionMap_.insert(nextConnSocket, ConnectionInfo()); - - QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() { - const ConnectionInfo &info = connectionMap_[nextConnSocket]; - slotClientConnectionClosed(nextConnSocket, info.instanceId); - }); - - QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater); - - QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() { - connectionMap_.remove(nextConnSocket); - }); - - QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() { - const ConnectionInfo &info = connectionMap_[nextConnSocket]; - switch (info.stage) { - case StageInitHeader: - readMessageHeader(nextConnSocket, StageInitBody); - break; - case StageInitBody: - readInitMessageBody(nextConnSocket); - break; - case StageConnectedHeader: - readMessageHeader(nextConnSocket, StageConnectedBody); - break; - case StageConnectedBody: - this->slotDataAvailable(nextConnSocket, info.instanceId); - break; - default: - break; - } - }); - -} - -void SingleCoreApplicationPrivate::readMessageHeader(QLocalSocket *sock, SingleCoreApplicationPrivate::ConnectionStage nextStage) { - - if (!connectionMap_.contains(sock)) { - return; - } - - if (sock->bytesAvailable() < static_cast(sizeof(quint64))) { - return; - } - - QDataStream headerStream(sock); - headerStream.setVersion(QDataStream::Qt_5_8); - - // Read the header to know the message length - quint64 msgLen = 0; - headerStream >> msgLen; - ConnectionInfo &info = connectionMap_[sock]; - info.stage = nextStage; - info.msgLen = msgLen; - - writeAck(sock); - -} - -bool SingleCoreApplicationPrivate::isFrameComplete(QLocalSocket *sock) { - - if (!connectionMap_.contains(sock)) { - return false; - } - - ConnectionInfo &info = connectionMap_[sock]; - return (sock->bytesAvailable() >= static_cast(info.msgLen)); - -} - -void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { - - Q_Q(SingleCoreApplication); - - if (!isFrameComplete(sock)) { - return; - } - - // Read the message body - QByteArray msgBytes = sock->readAll(); - QDataStream readStream(msgBytes); - readStream.setVersion(QDataStream::Qt_5_8); - - // server name - QByteArray latin1Name; - readStream >> latin1Name; - - // connection type - quint8 connTypeVal = InvalidConnection; - readStream >> connTypeVal; - const ConnectionType connectionType = static_cast(connTypeVal); - - // instance id - quint32 instanceId = 0; - readStream >> instanceId; - - // checksum - quint16 msgChecksum = 0; - readStream >> msgChecksum; - -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast(msgBytes.length() - sizeof(quint16)))); -#else - const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast(msgBytes.length() - sizeof(quint16))); -#endif - - bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum; - - if (!isValid) { - sock->close(); - return; - } - - ConnectionInfo &info = connectionMap_[sock]; - info.instanceId = instanceId; - info.stage = StageConnectedHeader; - - if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) { - emit q->instanceStarted(); - } - - writeAck(sock); - -} - -void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) { - - Q_Q(SingleCoreApplication); - - if (!isFrameComplete(dataSocket)) { - return; - } - - const QByteArray message = dataSocket->readAll(); - - writeAck(dataSocket); - - ConnectionInfo &info = connectionMap_[dataSocket]; - info.stage = StageConnectedHeader; - - emit q->receivedMessage(instanceId, message); - -} - -void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) { - - if (closedSocket->bytesAvailable() > 0) { - slotDataAvailable(closedSocket, instanceId); - } - -} - -void SingleCoreApplicationPrivate::randomSleep() { - -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U)); -#else - qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max()); - QThread::msleep(qrand() % 11 + 8); -#endif - -} diff --git a/3rdparty/singleapplication/singlecoreapplication_p.h b/3rdparty/singleapplication/singlecoreapplication_p.h deleted file mode 100644 index bc3d72369..000000000 --- a/3rdparty/singleapplication/singlecoreapplication_p.h +++ /dev/null @@ -1,116 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) Itay Grudev 2015 - 2020 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// -// W A R N I N G !!! -// ----------------- -// -// This is a modified version of SingleApplication, -// The original version is at: -// -// https://github.com/itay-grudev/SingleApplication -// -// - -#ifndef SINGLECOREAPPLICATION_P_H -#define SINGLECOREAPPLICATION_P_H - -#include -#include -#include -#include - -#include "singlecoreapplication.h" - -class QLocalServer; -class QLocalSocket; -class QSharedMemory; - -struct InstancesInfo { - bool primary; - quint32 secondary; - qint64 primaryPid; - char primaryUser[128]; - quint16 checksum; -}; - -struct ConnectionInfo { - explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {} - quint64 msgLen; - quint32 instanceId; - quint8 stage; -}; - -class SingleCoreApplicationPrivate : public QObject { - Q_OBJECT - - public: - enum ConnectionType : quint8 { - InvalidConnection = 0, - NewInstance = 1, - SecondaryInstance = 2, - Reconnect = 3 - }; - enum ConnectionStage : quint8 { - StageInitHeader = 0, - StageInitBody = 1, - StageConnectedHeader = 2, - StageConnectedBody = 3, - }; - Q_DECLARE_PUBLIC(SingleCoreApplication) - - explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr); - ~SingleCoreApplicationPrivate() override; - - static QString getUsername(); - void genBlockServerName(); - void initializeMemoryBlock() const; - void startPrimary(); - void startSecondary(); - bool connectToPrimary(const int timeout, const ConnectionType connectionType); - quint16 blockChecksum() const; - qint64 primaryPid() const; - QString primaryUser() const; - bool isFrameComplete(QLocalSocket *sock); - void readMessageHeader(QLocalSocket *socket, const ConnectionStage nextStage); - void readInitMessageBody(QLocalSocket *socket); - void writeAck(QLocalSocket *sock); - bool writeConfirmedFrame(const int timeout, const QByteArray &msg) const; - bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const; - static void randomSleep(); - - SingleCoreApplication *q_ptr; - QSharedMemory *memory_; - QLocalSocket *socket_; - QLocalServer *server_; - quint32 instanceNumber_; - QString blockServerName_; - SingleCoreApplication::Options options_; - QHash connectionMap_; - - public slots: - void slotConnectionEstablished(); - void slotDataAvailable(QLocalSocket*, const quint32); - void slotClientConnectionClosed(QLocalSocket*, const quint32); -}; - -#endif // SINGLECOREAPPLICATION_P_H diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cfcafaf6..40821906e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -309,7 +309,10 @@ endif() # SingleApplication add_subdirectory(3rdparty/singleapplication) -set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication) +set(SINGLEAPPLICATION_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singleapplication + ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singlecoreapplication +) set(SINGLEAPPLICATION_LIBRARIES singleapplication) set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)