From 1dd4eb05f2eca5d930e6d0fbae28450b39500f3c Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sun, 6 Feb 2022 00:07:15 +0100 Subject: [PATCH] Update SingleApplication --- 3rdparty/singleapplication/LICENSE | 2 +- 3rdparty/singleapplication/README.md | 86 +++++--- .../singleapplication/singleapplication.cpp | 44 ++-- .../singleapplication/singleapplication.h | 17 +- .../singleapplication/singleapplication_p.cpp | 193 ++++++++++++------ .../singleapplication/singleapplication_p.h | 13 +- .../singlecoreapplication.cpp | 44 ++-- .../singleapplication/singlecoreapplication.h | 16 +- .../singlecoreapplication_p.cpp | 193 ++++++++++++------ .../singlecoreapplication_p.h | 13 +- 10 files changed, 390 insertions(+), 231 deletions(-) diff --git a/3rdparty/singleapplication/LICENSE b/3rdparty/singleapplication/LICENSE index 85b2a149..a82e5a68 100644 --- a/3rdparty/singleapplication/LICENSE +++ b/3rdparty/singleapplication/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) Itay Grudev 2015 - 2016 +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 diff --git a/3rdparty/singleapplication/README.md b/3rdparty/singleapplication/README.md index 0b1a3552..716f9a7e 100644 --- a/3rdparty/singleapplication/README.md +++ b/3rdparty/singleapplication/README.md @@ -1,7 +1,8 @@ SingleApplication ================= +[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](https://github.com/itay-grudev/SingleApplication/actions) -This is a replacement of the QtSingleApplication for `Qt5`. +This is a replacement of the QtSingleApplication for `Qt5` and `Qt6`. Keeps the Primary Instance of your Application and kills each subsequent instances. It can (if enabled) spawn secondary (non-related to the primary) @@ -15,18 +16,6 @@ class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the default). Further usage is similar to the use of the `Q[Core|Gui]Application` classes. -The library sets up a `QLocalServer` and a `QSharedMemory` block. The first -instance of your Application is your Primary Instance. It would check if the -shared memory block exists and if not it will start a `QLocalServer` and listen -for connections. Each subsequent instance of your application would check if the -shared memory block exists and if it does, it will connect to the QLocalServer -to notify the primary instance that a new instance had been started, after which -it would terminate with status code `0`. In the Primary Instance -`SingleApplication` would emit the `instanceStarted()` signal upon detecting -that a new instance had been started. - -The library uses `stdlib` to terminate the program with the `exit()` function. - You can use the library as if you use any other `QCoreApplication` derived class: @@ -43,24 +32,49 @@ int main( int argc, char* argv[] ) ``` To include the library files I would recommend that you add it as a git -submodule to your project and include it's contents with a `.pri` file. Here is -how: +submodule to your project. Here is how: ```bash -git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication +git submodule add https://github.com/itay-grudev/SingleApplication.git singleapplication ``` -Then include the `singleapplication.pri` file in your `.pro` project file. Also -don't forget to specify which `QCoreApplication` class your app is using if it -is not `QCoreApplication`. +**Qmake:** + +Then include the `singleapplication.pri` file in your `.pro` project file. ```qmake include(singleapplication/singleapplication.pri) DEFINES += QAPPLICATION_CLASS=QApplication ``` +**CMake:** + +Then include the subdirectory in your `CMakeLists.txt` project file. + +```cmake +set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") +add_subdirectory(src/third-party/singleapplication) +target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication) +``` + + +The library sets up a `QLocalServer` and a `QSharedMemory` block. The first +instance of your Application is your Primary Instance. It would check if the +shared memory block exists and if not it will start a `QLocalServer` and listen +for connections. Each subsequent instance of your application would check if the +shared memory block exists and if it does, it will connect to the QLocalServer +to notify the primary instance that a new instance had been started, after which +it would terminate with status code `0`. In the Primary Instance +`SingleApplication` would emit the `instanceStarted()` signal upon detecting +that a new instance had been started. + +The library uses `stdlib` to terminate the program with the `exit()` function. + +Also don't forget to specify which `QCoreApplication` class your app is using if it +is not `QCoreApplication` as in examples above. + The `Instance Started` signal ------------------------- +----------------------------- The SingleApplication class implements a `instanceStarted()` signal. You can bind to that signal to raise your application's window when a new instance had @@ -125,13 +139,22 @@ app.isSecondary(); *__Note:__ If your Primary Instance is terminated a newly launched instance will replace the Primary one even if the Secondary flag has been set.* +Examples +-------- + +There are three examples provided in this repository: + +* Basic example that prevents a secondary instance from starting [`examples/basic`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/basic) +* An example of a graphical application raising it's parent window [`examples/calculator`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/calculator) +* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/sending_arguments) + API --- ### Members ```cpp -SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100 ) +SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100, QString userData = QString() ) ``` Depending on whether `allowSecondary` is set, this constructor may terminate @@ -140,7 +163,7 @@ can be specified to set whether the SingleApplication block should work user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be used to notify the primary instance whenever a secondary instance had been started (disabled by default). `timeout` specifies the maximum time in -milliseconds to wait for blocking operations. +milliseconds to wait for blocking operations. Setting `userData` provides additional data that will isolate this instance from other instances that do not have the same (or any) user data set. *__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it recognizes.* @@ -159,7 +182,8 @@ bool SingleApplication::sendMessage( QByteArray message, int timeout = 100 ) ``` Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout -in milliseconds for blocking functions +in milliseconds for blocking functions. Returns `true` if the message has been sent +successfully. If the message can't be sent or the function timeouts - returns `false`. --- @@ -192,6 +216,22 @@ qint64 SingleApplication::primaryPid() Returns the process ID (PID) of the primary instance. +--- + +```cpp +QString SingleApplication::primaryUser() +``` + +Returns the username the primary instance is running as. + +--- + +```cpp +QString SingleApplication::currentUser() +``` + +Returns the username the current instance is running as. + ### Signals ```cpp diff --git a/3rdparty/singleapplication/singleapplication.cpp b/3rdparty/singleapplication/singleapplication.cpp index 82a681f9..042ef660 100644 --- a/3rdparty/singleapplication/singleapplication.cpp +++ b/3rdparty/singleapplication/singleapplication.cpp @@ -54,7 +54,7 @@ * @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[], bool allowSecondary, Options options, int timeout) +SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout) : app_t(argc, argv), d_ptr(new SingleApplicationPrivate(this)) { @@ -67,7 +67,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar d->genBlockServerName(); // To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time - d->randomSleep(); + SingleApplicationPrivate::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. @@ -106,14 +106,14 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar } } - InstancesInfo *inst = static_cast(d->memory_->data()); + 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() == inst->checksum) break; + 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) { @@ -127,14 +127,14 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar qDebug() << "SingleApplication: Unable to unlock memory for random wait."; qDebug() << d->memory_->errorString(); } - d->randomSleep(); + SingleApplicationPrivate::randomSleep(); if (!d->memory_->lock()) { qCritical() << "SingleApplication: Unable to lock memory after random wait."; abortSafely(); } } - if (!inst->primary) { + if (!instance->primary) { d->startPrimary(); if (!d->memory_->unlock()) { qDebug() << "SingleApplication: Unable to unlock memory after primary start."; @@ -178,8 +178,8 @@ SingleApplication::~SingleApplication() { * Checks if the current application instance is primary. * @return Returns true if the instance is primary, false otherwise. */ -bool SingleApplication::isPrimary() { - Q_D(SingleApplication); +bool SingleApplication::isPrimary() const { + Q_D(const SingleApplication); return d->server_ != nullptr; } @@ -187,8 +187,8 @@ bool SingleApplication::isPrimary() { * Checks if the current application instance is secondary. * @return Returns true if the instance is secondary, false otherwise. */ -bool SingleApplication::isSecondary() { - Q_D(SingleApplication); +bool SingleApplication::isSecondary() const { + Q_D(const SingleApplication); return d->server_ == nullptr; } @@ -197,8 +197,8 @@ bool SingleApplication::isSecondary() { * 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() { - Q_D(SingleApplication); +quint32 SingleApplication::instanceId() const { + Q_D(const SingleApplication); return d->instanceNumber_; } @@ -207,8 +207,8 @@ quint32 SingleApplication::instanceId() { * Especially useful when SingleApplication is coupled with OS. specific APIs. * @return Returns the primary instance PID. */ -qint64 SingleApplication::primaryPid() { - Q_D(SingleApplication); +qint64 SingleApplication::primaryPid() const { + Q_D(const SingleApplication); return d->primaryPid(); } @@ -216,8 +216,8 @@ qint64 SingleApplication::primaryPid() { * Returns the username the primary instance is running as. * @return Returns the username the primary instance is running as. */ -QString SingleApplication::primaryUser() { - Q_D(SingleApplication); +QString SingleApplication::primaryUser() const { + Q_D(const SingleApplication); return d->primaryUser(); } @@ -225,9 +225,8 @@ QString SingleApplication::primaryUser() { * Returns the username the current instance is running as. * @return Returns the username the current instance is running as. */ -QString SingleApplication::currentUser() { - Q_D(SingleApplication); - return d->getUsername(); +QString SingleApplication::currentUser() const { + return SingleApplicationPrivate::getUsername(); } /** @@ -248,11 +247,7 @@ bool SingleApplication::sendMessage(const QByteArray &message, const int timeout return false; } - d->socket_->write(message); - const bool dataWritten = d->socket_->waitForBytesWritten(timeout); - d->socket_->flush(); - return dataWritten; - + return d->writeConfirmedMessage(timeout, message); } /** @@ -265,6 +260,7 @@ void SingleApplication::abortSafely() { qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString(); delete d; + ::exit(EXIT_FAILURE); } diff --git a/3rdparty/singleapplication/singleapplication.h b/3rdparty/singleapplication/singleapplication.h index b55a46a2..003df688 100644 --- a/3rdparty/singleapplication/singleapplication.h +++ b/3rdparty/singleapplication/singleapplication.h @@ -48,7 +48,7 @@ class SingleApplicationPrivate; class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument Q_OBJECT - typedef QApplication app_t; + using app_t = QApplication; public: /** @@ -88,46 +88,45 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p * operations. It does not guarantee that the SingleApplication * initialisation will be completed in given time, though is a good hint. * Usually 4*timeout would be the worst case (fail) scenario. - * @see See the corresponding QAPPLICATION_CLASS constructor for reference */ - explicit SingleApplication(int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000); + explicit SingleApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000); ~SingleApplication() override; /** * @brief Returns if the instance is the primary instance * @returns {bool} */ - bool isPrimary(); + bool isPrimary() const; /** * @brief Returns if the instance is a secondary instance * @returns {bool} */ - bool isSecondary(); + bool isSecondary() const; /** * @brief Returns a unique identifier for the current instance * @returns {qint32} */ - quint32 instanceId(); + quint32 instanceId() const; /** * @brief Returns the process ID (PID) of the primary instance * @returns {qint64} */ - qint64 primaryPid(); + qint64 primaryPid() const; /** * @brief Returns the username of the user running the primary instance * @returns {QString} */ - QString primaryUser(); + QString primaryUser() const; /** * @brief Returns the username of the current user * @returns {QString} */ - QString currentUser(); + QString currentUser() const; /** * @brief Sends a message to the primary instance. Returns true on success. diff --git a/3rdparty/singleapplication/singleapplication_p.cpp b/3rdparty/singleapplication/singleapplication_p.cpp index 47dfd4f2..71f66f9d 100644 --- a/3rdparty/singleapplication/singleapplication_p.cpp +++ b/3rdparty/singleapplication/singleapplication_p.cpp @@ -44,6 +44,14 @@ # include #endif +#ifdef Q_OS_WIN +# ifndef NOMINMAX +# define NOMINMAX 1 +# endif +# include +# include +#endif + #include #include #include @@ -53,7 +61,6 @@ #include #include #include -#include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) # include @@ -64,11 +71,6 @@ #include "singleapplication.h" #include "singleapplication_p.h" -#ifdef Q_OS_WIN -# include -# include -#endif - SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr) : q_ptr(ptr), memory_(nullptr), @@ -86,14 +88,14 @@ SingleApplicationPrivate::~SingleApplicationPrivate() { if (memory_ != nullptr) { memory_->lock(); - InstancesInfo *inst = static_cast(memory_->data()); + InstancesInfo *instance = static_cast(memory_->data()); if (server_ != nullptr) { server_->close(); delete server_; - inst->primary = false; - inst->primaryPid = -1; - inst->primaryUser[0] = '\0'; - inst->checksum = blockChecksum(); + instance->primary = false; + instance->primaryPid = -1; + instance->primaryUser[0] = '\0'; + instance->checksum = blockChecksum(); } memory_->unlock(); @@ -152,7 +154,15 @@ void SingleApplicationPrivate::genBlockServerName() { } if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) { -#ifdef Q_OS_WIN +#if defined(Q_OS_UNIX) + const QByteArray appImagePath = qgetenv("APPIMAGE"); + if (appImagePath.isEmpty()) { + appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8()); + } + else { + appData.addData(appImagePath); + }; +#elif defined(Q_OS_WIN) appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8()); #else appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8()); @@ -171,26 +181,24 @@ void SingleApplicationPrivate::genBlockServerName() { void SingleApplicationPrivate::initializeMemoryBlock() const { - InstancesInfo *inst = static_cast(memory_->data()); - inst->primary = false; - inst->secondary = 0; - inst->primaryPid = -1; - inst->primaryUser[0] = '\0'; - inst->checksum = blockChecksum(); + InstancesInfo *instance = static_cast(memory_->data()); + instance->primary = false; + instance->secondary = 0; + instance->primaryPid = -1; + instance->primaryUser[0] = '\0'; + instance->checksum = blockChecksum(); } void SingleApplicationPrivate::startPrimary() { - Q_Q(SingleApplication); - // Reset the number of connections - InstancesInfo *inst = static_cast(memory_->data()); + InstancesInfo *instance = static_cast(memory_->data()); - inst->primary = true; - inst->primaryPid = q->applicationPid(); - qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser)); - inst->checksum = blockChecksum(); + 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 @@ -212,11 +220,11 @@ void SingleApplicationPrivate::startPrimary() { void SingleApplicationPrivate::startSecondary() { - InstancesInfo *inst = static_cast(memory_->data()); + InstancesInfo *instance = static_cast(memory_->data()); - inst->secondary += 1; - inst->checksum = blockChecksum(); - instanceNumber_ = inst->secondary; + instance->secondary += 1; + instance->checksum = blockChecksum(); + instanceNumber_ = instance->secondary; } @@ -263,25 +271,53 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect writeStream << instanceNumber_; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); + quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); #else quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); #endif writeStream << checksum; - // The header indicates the message length that follows + return writeConfirmedMessage(static_cast(timeout - time.elapsed()), initMsg); + +} + +void SingleApplicationPrivate::writeAck(QLocalSocket *sock) { + sock->putChar('\n'); +} + +bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) { + + 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(initMsg.length()); + headerStream << static_cast(msg.length()); - socket_->write(header); - socket_->write(initMsg); - bool result = socket_->waitForBytesWritten(static_cast(timeout - time.elapsed())); + if (!writeConfirmedFrame(static_cast(timeout - time.elapsed()), header)) { + return false; + } + + // Frame 2: The message + return writeConfirmedFrame(static_cast(timeout - time.elapsed()), msg); + +} + +bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) { + + socket_->write(msg); socket_->flush(); - return result; + bool result = socket_->waitForReadyRead(timeout); + if (result) { + socket_->read(1); + return true; + } + + return false; } @@ -300,8 +336,8 @@ quint16 SingleApplicationPrivate::blockChecksum() const { qint64 SingleApplicationPrivate::primaryPid() const { memory_->lock(); - InstancesInfo *inst = static_cast(memory_->data()); - qint64 pid = inst->primaryPid; + InstancesInfo *instance = static_cast(memory_->data()); + qint64 pid = instance->primaryPid; memory_->unlock(); return pid; @@ -311,8 +347,8 @@ qint64 SingleApplicationPrivate::primaryPid() const { QString SingleApplicationPrivate::primaryUser() const { memory_->lock(); - InstancesInfo *inst = static_cast(memory_->data()); - QByteArray username = inst->primaryUser; + InstancesInfo *instance = static_cast(memory_->data()); + QByteArray username = instance->primaryUser; memory_->unlock(); return QString::fromUtf8(username); @@ -328,26 +364,30 @@ void SingleApplicationPrivate::slotConnectionEstablished() { connectionMap_.insert(nextConnSocket, ConnectionInfo()); QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() { - const ConnectionInfo info = connectionMap_[nextConnSocket]; + const ConnectionInfo &info = connectionMap_[nextConnSocket]; slotClientConnectionClosed(nextConnSocket, info.instanceId); }); - QObject::connect(nextConnSocket, &QLocalSocket::disconnected, this, [nextConnSocket, this]() { + QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater); + + QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() { connectionMap_.remove(nextConnSocket); - nextConnSocket->deleteLater(); }); QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() { - const ConnectionInfo info = connectionMap_[nextConnSocket]; + const ConnectionInfo &info = connectionMap_[nextConnSocket]; switch (info.stage) { - case StageHeader: - readInitMessageHeader(nextConnSocket); + case StageInitHeader: + readMessageHeader(nextConnSocket, StageInitBody); break; - case StageBody: + case StageInitBody: readInitMessageBody(nextConnSocket); break; - case StageConnected: - slotDataAvailable(nextConnSocket, info.instanceId); + case StageConnectedHeader: + readMessageHeader(nextConnSocket, StageConnectedBody); + break; + case StageConnectedBody: + this->slotDataAvailable(nextConnSocket, info.instanceId); break; default: break; @@ -356,7 +396,7 @@ void SingleApplicationPrivate::slotConnectionEstablished() { } -void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { +void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::ConnectionStage nextStage) { if (!connectionMap_.contains(sock)) { return; @@ -373,30 +413,38 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { quint64 msgLen = 0; headerStream >> msgLen; ConnectionInfo &info = connectionMap_[sock]; - info.stage = StageBody; + info.stage = nextStage; info.msgLen = msgLen; - if (sock->bytesAvailable() >= static_cast(msgLen)) { - readInitMessageBody(sock); + writeAck(sock); + +} + +bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) { + + if (!connectionMap_.contains(sock)) { + return false; } + const ConnectionInfo &info = connectionMap_[sock]; + if (sock->bytesAvailable() < static_cast(info.msgLen)) { + return false; + } + + return true; + } void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { Q_Q(SingleApplication); - if (!connectionMap_.contains(sock)) { - return; - } - - ConnectionInfo &info = connectionMap_[sock]; - if (sock->bytesAvailable() < static_cast(info.msgLen)) { + if (!isFrameComplete(sock)) { return; } // Read the message body - QByteArray msgBytes = sock->read(static_cast(info.msgLen)); + QByteArray msgBytes = sock->readAll(); QDataStream readStream(msgBytes); readStream.setVersion(QDataStream::Qt_5_8); @@ -431,23 +479,34 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { return; } + ConnectionInfo &info = connectionMap_[sock]; info.instanceId = instanceId; - info.stage = StageConnected; + info.stage = StageConnectedHeader; if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) { - Q_EMIT q->instanceStarted(); + emit q->instanceStarted(); } - if (sock->bytesAvailable() > 0) { - slotDataAvailable(sock, instanceId); - } + writeAck(sock); } void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) { Q_Q(SingleApplication); - Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll()); + + if (!isFrameComplete(dataSocket)) { + return; + } + + const QByteArray message = dataSocket->readAll(); + + writeAck(dataSocket); + + ConnectionInfo &info = connectionMap_[dataSocket]; + info.stage = StageConnectedHeader; + + emit q->receivedMessage(instanceId, message); } @@ -465,7 +524,7 @@ void SingleApplicationPrivate::randomSleep() { QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U)); #else qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max()); - QThread::msleep(8 + static_cast(static_cast(qrand()) / RAND_MAX * 10)); + QThread::msleep(qrand() % 11 + 8); #endif } diff --git a/3rdparty/singleapplication/singleapplication_p.h b/3rdparty/singleapplication/singleapplication_p.h index 99b1cc95..2f698741 100644 --- a/3rdparty/singleapplication/singleapplication_p.h +++ b/3rdparty/singleapplication/singleapplication_p.h @@ -71,9 +71,10 @@ class SingleApplicationPrivate : public QObject { Reconnect = 3 }; enum ConnectionStage : quint8 { - StageHeader = 0, - StageBody = 1, - StageConnected = 2, + StageInitHeader = 0, + StageInitBody = 1, + StageConnectedHeader = 2, + StageConnectedBody = 3, }; Q_DECLARE_PUBLIC(SingleApplication) @@ -89,8 +90,12 @@ class SingleApplicationPrivate : public QObject { quint16 blockChecksum() const; qint64 primaryPid() const; QString primaryUser() const; - void readInitMessageHeader(QLocalSocket *socket); + 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); + bool writeConfirmedMessage(const int timeout, const QByteArray &msg); static void randomSleep(); SingleApplication *q_ptr; diff --git a/3rdparty/singleapplication/singlecoreapplication.cpp b/3rdparty/singleapplication/singlecoreapplication.cpp index db3a6d05..3dcb5d92 100644 --- a/3rdparty/singleapplication/singlecoreapplication.cpp +++ b/3rdparty/singleapplication/singlecoreapplication.cpp @@ -54,7 +54,7 @@ * @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[], bool allowSecondary, Options options, int timeout) +SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout) : app_t(argc, argv), d_ptr(new SingleCoreApplicationPrivate(this)) { @@ -67,7 +67,7 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow d->genBlockServerName(); // To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time - d->randomSleep(); + 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. @@ -106,14 +106,14 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow } } - InstancesInfo *inst = static_cast(d->memory_->data()); + 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() == inst->checksum) break; + 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) { @@ -127,14 +127,14 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait."; qDebug() << d->memory_->errorString(); } - d->randomSleep(); + SingleCoreApplicationPrivate::randomSleep(); if (!d->memory_->lock()) { qCritical() << "SingleCoreApplication: Unable to lock memory after random wait."; abortSafely(); } } - if (!inst->primary) { + if (!instance->primary) { d->startPrimary(); if (!d->memory_->unlock()) { qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start."; @@ -178,8 +178,8 @@ SingleCoreApplication::~SingleCoreApplication() { * Checks if the current application instance is primary. * @return Returns true if the instance is primary, false otherwise. */ -bool SingleCoreApplication::isPrimary() { - Q_D(SingleCoreApplication); +bool SingleCoreApplication::isPrimary() const { + Q_D(const SingleCoreApplication); return d->server_ != nullptr; } @@ -187,8 +187,8 @@ bool SingleCoreApplication::isPrimary() { * Checks if the current application instance is secondary. * @return Returns true if the instance is secondary, false otherwise. */ -bool SingleCoreApplication::isSecondary() { - Q_D(SingleCoreApplication); +bool SingleCoreApplication::isSecondary() const { + Q_D(const SingleCoreApplication); return d->server_ == nullptr; } @@ -197,8 +197,8 @@ bool SingleCoreApplication::isSecondary() { * 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() { - Q_D(SingleCoreApplication); +quint32 SingleCoreApplication::instanceId() const { + Q_D(const SingleCoreApplication); return d->instanceNumber_; } @@ -207,8 +207,8 @@ quint32 SingleCoreApplication::instanceId() { * Especially useful when SingleCoreApplication is coupled with OS. specific APIs. * @return Returns the primary instance PID. */ -qint64 SingleCoreApplication::primaryPid() { - Q_D(SingleCoreApplication); +qint64 SingleCoreApplication::primaryPid() const { + Q_D(const SingleCoreApplication); return d->primaryPid(); } @@ -216,8 +216,8 @@ qint64 SingleCoreApplication::primaryPid() { * Returns the username the primary instance is running as. * @return Returns the username the primary instance is running as. */ -QString SingleCoreApplication::primaryUser() { - Q_D(SingleCoreApplication); +QString SingleCoreApplication::primaryUser() const { + Q_D(const SingleCoreApplication); return d->primaryUser(); } @@ -225,9 +225,8 @@ QString SingleCoreApplication::primaryUser() { * Returns the username the current instance is running as. * @return Returns the username the current instance is running as. */ -QString SingleCoreApplication::currentUser() { - Q_D(SingleCoreApplication); - return d->getUsername(); +QString SingleCoreApplication::currentUser() const { + return SingleCoreApplicationPrivate::getUsername(); } /** @@ -248,11 +247,7 @@ bool SingleCoreApplication::sendMessage(const QByteArray &message, const int tim return false; } - d->socket_->write(message); - const bool dataWritten = d->socket_->waitForBytesWritten(timeout); - d->socket_->flush(); - return dataWritten; - + return d->writeConfirmedMessage(timeout, message); } /** @@ -265,6 +260,7 @@ void SingleCoreApplication::abortSafely() { 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 index 2b1eff79..9136af4f 100644 --- a/3rdparty/singleapplication/singlecoreapplication.h +++ b/3rdparty/singleapplication/singlecoreapplication.h @@ -45,10 +45,10 @@ class SingleCoreApplicationPrivate; * @brief The SingleCoreApplication class handles multiple instances of the same Application * @see QCoreApplication */ -class SingleCoreApplication : public QCoreApplication { +class SingleCoreApplication : public QCoreApplication { // clazy:exclude=ctor-missing-parent-argument Q_OBJECT - typedef QCoreApplication app_t; + using app_t = QCoreApplication; public: /** @@ -96,37 +96,37 @@ class SingleCoreApplication : public QCoreApplication { * @brief Returns if the instance is the primary instance * @returns {bool} */ - bool isPrimary(); + bool isPrimary() const; /** * @brief Returns if the instance is a secondary instance * @returns {bool} */ - bool isSecondary(); + bool isSecondary() const; /** * @brief Returns a unique identifier for the current instance * @returns {qint32} */ - quint32 instanceId(); + quint32 instanceId() const; /** * @brief Returns the process ID (PID) of the primary instance * @returns {qint64} */ - qint64 primaryPid(); + qint64 primaryPid() const; /** * @brief Returns the username of the user running the primary instance * @returns {QString} */ - QString primaryUser(); + QString primaryUser() const; /** * @brief Returns the username of the current user * @returns {QString} */ - QString currentUser(); + QString currentUser() const; /** * @brief Sends a message to the primary instance. Returns true on success. diff --git a/3rdparty/singleapplication/singlecoreapplication_p.cpp b/3rdparty/singleapplication/singlecoreapplication_p.cpp index 1bab6caf..536fcf73 100644 --- a/3rdparty/singleapplication/singlecoreapplication_p.cpp +++ b/3rdparty/singleapplication/singlecoreapplication_p.cpp @@ -44,6 +44,14 @@ # include #endif +#ifdef Q_OS_WIN +# ifndef NOMINMAX +# define NOMINMAX 1 +# endif +# include +# include +#endif + #include #include #include @@ -53,7 +61,6 @@ #include #include #include -#include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) # include @@ -64,11 +71,6 @@ #include "singlecoreapplication.h" #include "singlecoreapplication_p.h" -#ifdef Q_OS_WIN -# include -# include -#endif - SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr) : q_ptr(ptr), memory_(nullptr), @@ -86,14 +88,14 @@ SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() { if (memory_ != nullptr) { memory_->lock(); - InstancesInfo *inst = static_cast(memory_->data()); + InstancesInfo *instance = static_cast(memory_->data()); if (server_ != nullptr) { server_->close(); delete server_; - inst->primary = false; - inst->primaryPid = -1; - inst->primaryUser[0] = '\0'; - inst->checksum = blockChecksum(); + instance->primary = false; + instance->primaryPid = -1; + instance->primaryUser[0] = '\0'; + instance->checksum = blockChecksum(); } memory_->unlock(); @@ -152,7 +154,15 @@ void SingleCoreApplicationPrivate::genBlockServerName() { } if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) { -#ifdef Q_OS_WIN +#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()); @@ -171,26 +181,24 @@ void SingleCoreApplicationPrivate::genBlockServerName() { void SingleCoreApplicationPrivate::initializeMemoryBlock() const { - InstancesInfo *inst = static_cast(memory_->data()); - inst->primary = false; - inst->secondary = 0; - inst->primaryPid = -1; - inst->primaryUser[0] = '\0'; - inst->checksum = blockChecksum(); + 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() { - Q_Q(SingleCoreApplication); - // Reset the number of connections - InstancesInfo *inst = static_cast(memory_->data()); + InstancesInfo *instance = static_cast(memory_->data()); - inst->primary = true; - inst->primaryPid = q->applicationPid(); - qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser)); - inst->checksum = blockChecksum(); + 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 @@ -212,11 +220,11 @@ void SingleCoreApplicationPrivate::startPrimary() { void SingleCoreApplicationPrivate::startSecondary() { - InstancesInfo *inst = static_cast(memory_->data()); + InstancesInfo *instance = static_cast(memory_->data()); - inst->secondary += 1; - inst->checksum = blockChecksum(); - instanceNumber_ = inst->secondary; + instance->secondary += 1; + instance->checksum = blockChecksum(); + instanceNumber_ = instance->secondary; } @@ -263,25 +271,53 @@ bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const Con writeStream << instanceNumber_; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); + quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); #else quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); #endif writeStream << checksum; - // The header indicates the message length that follows + 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) { + + 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(initMsg.length()); + headerStream << static_cast(msg.length()); - socket_->write(header); - socket_->write(initMsg); - bool result = socket_->waitForBytesWritten(timeout - static_cast(time.elapsed())); + 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) { + + socket_->write(msg); socket_->flush(); - return result; + bool result = socket_->waitForReadyRead(timeout); + if (result) { + socket_->read(1); + return true; + } + + return false; } @@ -300,8 +336,8 @@ quint16 SingleCoreApplicationPrivate::blockChecksum() const { qint64 SingleCoreApplicationPrivate::primaryPid() const { memory_->lock(); - InstancesInfo *inst = static_cast(memory_->data()); - qint64 pid = inst->primaryPid; + InstancesInfo *instance = static_cast(memory_->data()); + qint64 pid = instance->primaryPid; memory_->unlock(); return pid; @@ -311,8 +347,8 @@ qint64 SingleCoreApplicationPrivate::primaryPid() const { QString SingleCoreApplicationPrivate::primaryUser() const { memory_->lock(); - InstancesInfo *inst = static_cast(memory_->data()); - QByteArray username = inst->primaryUser; + InstancesInfo *instance = static_cast(memory_->data()); + QByteArray username = instance->primaryUser; memory_->unlock(); return QString::fromUtf8(username); @@ -328,26 +364,30 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() { connectionMap_.insert(nextConnSocket, ConnectionInfo()); QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() { - const ConnectionInfo info = connectionMap_[nextConnSocket]; + const ConnectionInfo &info = connectionMap_[nextConnSocket]; slotClientConnectionClosed(nextConnSocket, info.instanceId); }); - QObject::connect(nextConnSocket, &QLocalSocket::disconnected, this, [nextConnSocket, this]() { + QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater); + + QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() { connectionMap_.remove(nextConnSocket); - nextConnSocket->deleteLater(); }); QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() { - const ConnectionInfo info = connectionMap_[nextConnSocket]; + const ConnectionInfo &info = connectionMap_[nextConnSocket]; switch (info.stage) { - case StageHeader: - readInitMessageHeader(nextConnSocket); + case StageInitHeader: + readMessageHeader(nextConnSocket, StageInitBody); break; - case StageBody: + case StageInitBody: readInitMessageBody(nextConnSocket); break; - case StageConnected: - slotDataAvailable(nextConnSocket, info.instanceId); + case StageConnectedHeader: + readMessageHeader(nextConnSocket, StageConnectedBody); + break; + case StageConnectedBody: + this->slotDataAvailable(nextConnSocket, info.instanceId); break; default: break; @@ -356,7 +396,7 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() { } -void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { +void SingleCoreApplicationPrivate::readMessageHeader(QLocalSocket *sock, SingleCoreApplicationPrivate::ConnectionStage nextStage) { if (!connectionMap_.contains(sock)) { return; @@ -373,30 +413,38 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { quint64 msgLen = 0; headerStream >> msgLen; ConnectionInfo &info = connectionMap_[sock]; - info.stage = StageBody; + info.stage = nextStage; info.msgLen = msgLen; - if (sock->bytesAvailable() >= static_cast(msgLen)) { - readInitMessageBody(sock); + writeAck(sock); + +} + +bool SingleCoreApplicationPrivate::isFrameComplete(QLocalSocket *sock) { + + if (!connectionMap_.contains(sock)) { + return false; } + ConnectionInfo &info = connectionMap_[sock]; + if (sock->bytesAvailable() < static_cast(info.msgLen)) { + return false; + } + + return true; + } void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { Q_Q(SingleCoreApplication); - if (!connectionMap_.contains(sock)) { - return; - } - - ConnectionInfo &info = connectionMap_[sock]; - if (sock->bytesAvailable() < static_cast(info.msgLen)) { + if (!isFrameComplete(sock)) { return; } // Read the message body - QByteArray msgBytes = sock->read(static_cast(info.msgLen)); + QByteArray msgBytes = sock->readAll(); QDataStream readStream(msgBytes); readStream.setVersion(QDataStream::Qt_5_8); @@ -431,23 +479,34 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { return; } + ConnectionInfo &info = connectionMap_[sock]; info.instanceId = instanceId; - info.stage = StageConnected; + info.stage = StageConnectedHeader; if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) { - Q_EMIT q->instanceStarted(); + emit q->instanceStarted(); } - if (sock->bytesAvailable() > 0) { - slotDataAvailable(sock, instanceId); - } + writeAck(sock); } void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) { Q_Q(SingleCoreApplication); - Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll()); + + if (!isFrameComplete(dataSocket)) { + return; + } + + const QByteArray message = dataSocket->readAll(); + + writeAck(dataSocket); + + ConnectionInfo &info = connectionMap_[dataSocket]; + info.stage = StageConnectedHeader; + + emit q->receivedMessage(instanceId, message); } @@ -465,7 +524,7 @@ void SingleCoreApplicationPrivate::randomSleep() { QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U)); #else qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max()); - QThread::msleep(8 + static_cast(static_cast(qrand()) / RAND_MAX * 10)); + QThread::msleep(qrand() % 11 + 8); #endif } diff --git a/3rdparty/singleapplication/singlecoreapplication_p.h b/3rdparty/singleapplication/singlecoreapplication_p.h index 222e1c70..8a0fa427 100644 --- a/3rdparty/singleapplication/singlecoreapplication_p.h +++ b/3rdparty/singleapplication/singlecoreapplication_p.h @@ -71,9 +71,10 @@ class SingleCoreApplicationPrivate : public QObject { Reconnect = 3 }; enum ConnectionStage : quint8 { - StageHeader = 0, - StageBody = 1, - StageConnected = 2, + StageInitHeader = 0, + StageInitBody = 1, + StageConnectedHeader = 2, + StageConnectedBody = 3, }; Q_DECLARE_PUBLIC(SingleCoreApplication) @@ -89,8 +90,12 @@ class SingleCoreApplicationPrivate : public QObject { quint16 blockChecksum() const; qint64 primaryPid() const; QString primaryUser() const; - void readInitMessageHeader(QLocalSocket *socket); + 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); + bool writeConfirmedMessage(const int timeout, const QByteArray &msg); static void randomSleep(); SingleCoreApplication *q_ptr;