diff options
author | LorenDB <computersemiexpert@outlook.com> | 2020-11-09 21:28:41 -0500 |
---|---|---|
committer | Loren Burkholder <computersemiexpert@outlook.com> | 2020-12-24 21:16:46 -0500 |
commit | 53f45bdb1c9ee2b42b88ca4587775756f8daa124 (patch) | |
tree | ab8b89b8016d37dfb4a1e9e0aaae50a9d86b2d6f /third_party | |
parent | Merge pull request #357 from LorenDB/qkchdocs (diff) | |
download | nheko-53f45bdb1c9ee2b42b88ca4587775756f8daa124.tar.xz |
Switch profile code to a more flexible method
This introduces a new version of SingleApplication as well.
Diffstat (limited to 'third_party')
-rw-r--r-- | third_party/SingleApplication-3.1.3.1/singleapplication.cpp | 201 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/.gitignore (renamed from third_party/SingleApplication-3.1.3.1/.gitignore) | 0 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/CHANGELOG.md (renamed from third_party/SingleApplication-3.1.3.1/CHANGELOG.md) | 30 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/CMakeLists.txt (renamed from third_party/SingleApplication-3.1.3.1/CMakeLists.txt) | 22 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/LICENSE (renamed from third_party/SingleApplication-3.1.3.1/LICENSE) | 0 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/README.md (renamed from third_party/SingleApplication-3.1.3.1/README.md) | 15 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/SingleApplication (renamed from third_party/SingleApplication-3.1.3.1/SingleApplication) | 0 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/Windows.md (renamed from third_party/SingleApplication-3.1.3.1/Windows.md) | 0 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/singleapplication.cpp | 274 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/singleapplication.h (renamed from third_party/SingleApplication-3.1.3.1/singleapplication.h) | 9 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/singleapplication.pri (renamed from third_party/SingleApplication-3.1.3.1/singleapplication.pri) | 0 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.cpp (renamed from third_party/SingleApplication-3.1.3.1/singleapplication_p.cpp) | 202 | ||||
-rw-r--r-- | third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.h (renamed from third_party/SingleApplication-3.1.3.1/singleapplication_p.h) | 18 |
13 files changed, 473 insertions, 298 deletions
diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication.cpp b/third_party/SingleApplication-3.1.3.1/singleapplication.cpp deleted file mode 100644 index 9af38804..00000000 --- a/third_party/SingleApplication-3.1.3.1/singleapplication.cpp +++ /dev/null @@ -1,201 +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. - -#include <QtCore/QElapsedTimer> -#include <QtCore/QThread> -#include <QtCore/QByteArray> -#include <QtCore/QSharedMemory> -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) -#include <QtCore/QRandomGenerator> -#else -#include <QtCore/QDateTime> -#endif - -#include "singleapplication.h" -#include "singleapplication_p.h" - -/** - * @brief Constructor. Checks and fires up LocalServer or closes the program - * if another instance already exists - * @param argc - * @param argv - * @param {bool} allowSecondaryInstances - */ -SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout ) - : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) ) -{ - Q_D(SingleApplication); - -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - // On Android and iOS since the library is not supported fallback to - // standard QApplication behaviour by simply returning at this point. - qWarning() << "SingleApplication is not supported on Android and iOS systems."; - return; -#endif - - // 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(); - -#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. - d->memory = new QSharedMemory( d->blockServerName ); - d->memory->attach(); - delete d->memory; -#endif - // Guarantee thread safe behaviour with a shared memory block. - d->memory = new QSharedMemory( d->blockServerName ); - - // Create a shared memory block - if( d->memory->create( sizeof( InstancesInfo ) ) ) { - // Initialize the shared memory block - d->memory->lock(); - d->initializeMemoryBlock(); - d->memory->unlock(); - } else { - // Attempt to attach to the memory segment - if( ! d->memory->attach() ) { - qCritical() << "SingleApplication: Unable to attach to shared memory block."; - qCritical() << d->memory->errorString(); - delete d; - ::exit( EXIT_FAILURE ); - } - } - - auto *inst = static_cast<InstancesInfo*>( d->memory->data() ); - QElapsedTimer time; - time.start(); - - // Make sure the shared memory block is initialised and in consistent state - while( true ) { - d->memory->lock(); - - if( d->blockChecksum() == inst->checksum ) break; - - if( time.elapsed() > 5000 ) { - qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; - d->initializeMemoryBlock(); - } - - d->memory->unlock(); - - // Random sleep here limits the probability of a collision between two racing apps -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QThread::sleep( QRandomGenerator::global()->bounded( 8u, 18u ) ); -#else - qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() ); - QThread::sleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ) ); -#endif - } - - if( inst->primary == false) { - d->startPrimary(); - d->memory->unlock(); - return; - } - - // Check if another instance can be started - if( allowSecondary ) { - inst->secondary += 1; - inst->checksum = d->blockChecksum(); - d->instanceNumber = inst->secondary; - d->startSecondary(); - if( d->options & Mode::SecondaryNotification ) { - d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance ); - } - d->memory->unlock(); - return; - } - - d->memory->unlock(); - - d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance ); - - delete d; - - ::exit( EXIT_SUCCESS ); -} - -/** - * @brief Destructor - */ -SingleApplication::~SingleApplication() -{ - Q_D(SingleApplication); - delete d; -} - -bool SingleApplication::isPrimary() -{ - Q_D(SingleApplication); - return d->server != nullptr; -} - -bool SingleApplication::isSecondary() -{ - Q_D(SingleApplication); - return d->server == nullptr; -} - -quint32 SingleApplication::instanceId() -{ - Q_D(SingleApplication); - return d->instanceNumber; -} - -qint64 SingleApplication::primaryPid() -{ - Q_D(SingleApplication); - return d->primaryPid(); -} - -QString SingleApplication::primaryUser() -{ - Q_D(SingleApplication); - return d->primaryUser(); -} - -QString SingleApplication::currentUser() -{ - Q_D(SingleApplication); - return d->getUsername(); -} - -bool SingleApplication::sendMessage( const QByteArray &message, int timeout ) -{ - Q_D(SingleApplication); - - // Nobody to connect to - if( isPrimary() ) return false; - - // Make sure the socket is connected - d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ); - - d->socket->write( message ); - bool dataWritten = d->socket->waitForBytesWritten( timeout ); - d->socket->flush(); - return dataWritten; -} diff --git a/third_party/SingleApplication-3.1.3.1/.gitignore b/third_party/SingleApplication-3.2.0-dc8042b/.gitignore index 35533fe8..35533fe8 100644 --- a/third_party/SingleApplication-3.1.3.1/.gitignore +++ b/third_party/SingleApplication-3.2.0-dc8042b/.gitignore diff --git a/third_party/SingleApplication-3.1.3.1/CHANGELOG.md b/third_party/SingleApplication-3.2.0-dc8042b/CHANGELOG.md index 3662b0b2..e2ba290e 100644 --- a/third_party/SingleApplication-3.1.3.1/CHANGELOG.md +++ b/third_party/SingleApplication-3.2.0-dc8042b/CHANGELOG.md @@ -3,6 +3,30 @@ Changelog If by accident I have forgotten to credit someone in the CHANGELOG, email me and I will fix it. +__3.2.0__ +--------- + +* Added support for Qt 6 - _Jonas Kvinge_ +* Fixed warning in `Qt 5.9` with `min`/`max` functions on Windows - _Nick Korotysh_ +* Fix return value of connectToPrimary() when connect is successful - _Jonas Kvinge_ +* Fix build issue with MinGW GCC pedantic mode - _Iakov Kirilenko_ +* Fixed conversion from `int` to `quint32` and Clang Tidy warnings - _Hennadii Chernyshchyk_ + +__3.1.5__ +--------- + +* Improved library stability in edge cases and very rapid process initialisation +* Fixed Bug where the shared memory block may have been modified without a lock +* Fixed Bug causing `instanceStarted()` to not get emitted when a second instance + has been started before the primary has initiated it's `QLocalServer`. + +__3.1.4__ +--------- +* Officially supporting and build-testing against Qt 5.15 +* Fixed an MSVC C4996 warning that suggests using `strncpy_s`. + + _Hennadii Chernyshchyk_ + __3.1.3.1__ --------- * CMake build system improvements @@ -38,18 +62,18 @@ __3.1.0a__ __3.0.19__ ---------- -* Fixed code warning for depricated functions in Qt 5.10 related to `QTime` and `qrand()`. +* Fixed code warning for depricated functions in Qt 5.10 related to `QTime` and `qrand()`. _Hennadii Chernyshchyk_ _Anton Filimonov_ _Jonas Kvinge_ - + __3.0.18__ ---------- * Fallback to standard QApplication class on iOS and Android systems where the library is not supported. - + * Added Build CI tests to verify the library builds successfully on Linux, Windows and MacOS across multiple Qt versions. _Anton Filimonov_ diff --git a/third_party/SingleApplication-3.1.3.1/CMakeLists.txt b/third_party/SingleApplication-3.2.0-dc8042b/CMakeLists.txt index 85dba84c..ae1b1439 100644 --- a/third_party/SingleApplication-3.1.3.1/CMakeLists.txt +++ b/third_party/SingleApplication-3.2.0-dc8042b/CMakeLists.txt @@ -10,22 +10,28 @@ add_library(${PROJECT_NAME} STATIC ) add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) +if(NOT QT_DEFAULT_MAJOR_VERSION) + set(QT_DEFAULT_MAJOR_VERSION 5 CACHE STRING "Qt version to use (5 or 6), defaults to 5") +endif() + # Find dependencies -find_package(Qt5 COMPONENTS Network REQUIRED) -target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network) +set(QT_COMPONENTS Core Network) +set(QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Core Qt${QT_DEFAULT_MAJOR_VERSION}::Network) if(QAPPLICATION_CLASS STREQUAL QApplication) - find_package(Qt5 COMPONENTS Widgets REQUIRED) - target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Widgets) + list(APPEND QT_COMPONENTS Widgets) + list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Widgets) elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication) - find_package(Qt5 COMPONENTS Gui REQUIRED) - target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Gui) + list(APPEND QT_COMPONENTS Gui) + list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Gui) else() set(QAPPLICATION_CLASS QCoreApplication) - find_package(Qt5 COMPONENTS Core REQUIRED) - target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Core) endif() +find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED) + +target_link_libraries(${PROJECT_NAME} PUBLIC ${QT_LIBRARIES}) + if(WIN32) target_link_libraries(${PROJECT_NAME} PRIVATE advapi32) endif() diff --git a/third_party/SingleApplication-3.1.3.1/LICENSE b/third_party/SingleApplication-3.2.0-dc8042b/LICENSE index a82e5a68..a82e5a68 100644 --- a/third_party/SingleApplication-3.1.3.1/LICENSE +++ b/third_party/SingleApplication-3.2.0-dc8042b/LICENSE diff --git a/third_party/SingleApplication-3.1.3.1/README.md b/third_party/SingleApplication-3.2.0-dc8042b/README.md index 3c36b557..457ab339 100644 --- a/third_party/SingleApplication-3.1.3.1/README.md +++ b/third_party/SingleApplication-3.2.0-dc8042b/README.md @@ -2,7 +2,7 @@ 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) @@ -139,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 @@ -154,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.* diff --git a/third_party/SingleApplication-3.1.3.1/SingleApplication b/third_party/SingleApplication-3.2.0-dc8042b/SingleApplication index 8ead1a42..8ead1a42 100644 --- a/third_party/SingleApplication-3.1.3.1/SingleApplication +++ b/third_party/SingleApplication-3.2.0-dc8042b/SingleApplication diff --git a/third_party/SingleApplication-3.1.3.1/Windows.md b/third_party/SingleApplication-3.2.0-dc8042b/Windows.md index 13c52da0..13c52da0 100644 --- a/third_party/SingleApplication-3.1.3.1/Windows.md +++ b/third_party/SingleApplication-3.2.0-dc8042b/Windows.md diff --git a/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.cpp b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.cpp new file mode 100644 index 00000000..276ceee9 --- /dev/null +++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.cpp @@ -0,0 +1,274 @@ +// 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. + +#include <QtCore/QElapsedTimer> +#include <QtCore/QByteArray> +#include <QtCore/QSharedMemory> + +#include "singleapplication.h" +#include "singleapplication_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 + */ +SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, QString userData ) + : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) ) +{ + Q_D( SingleApplication ); + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + // On Android and iOS since the library is not supported fallback to + // standard QApplication behaviour by simply returning at this point. + qWarning() << "SingleApplication is not supported on Android and iOS systems."; + return; +#endif + + // Store the current mode of the program + d->options = options; + + // Add any unique user data + if ( ! userData.isEmpty() ) + d->addAppData( userData ); + + // 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 + 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. + d->memory = new QSharedMemory( d->blockServerName ); + d->memory->attach(); + delete d->memory; +#endif + // Guarantee thread safe behaviour with a shared memory block. + d->memory = new QSharedMemory( d->blockServerName ); + + // Create a shared memory block + if( d->memory->create( sizeof( InstancesInfo ) )){ + // Initialize the shared memory block + if( ! d->memory->lock() ){ + qCritical() << "SingleApplication: 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() << "SingleApplication: Unable to attach to shared memory block."; + abortSafely(); + } + if( ! d->memory->lock() ){ + qCritical() << "SingleApplication: Unable to lock memory block after attach."; + abortSafely(); + } + } else { + qCritical() << "SingleApplication: Unable to create block."; + abortSafely(); + } + } + + auto *inst = static_cast<InstancesInfo*>( d->memory->data() ); + QElapsedTimer time; + time.start(); + + // Make sure the shared memory block is initialised and in consistent state + while( true ){ + // If the shared memory block's checksum is valid continue + if( d->blockChecksum() == inst->checksum ) break; + + // If more than 5s have elapsed, assume the primary instance crashed and + // assume it's position + if( time.elapsed() > 5000 ){ + qWarning() << "SingleApplication: 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() << "SingleApplication: Unable to unlock memory for random wait."; + qDebug() << d->memory->errorString(); + } + SingleApplicationPrivate::randomSleep(); + if( ! d->memory->lock() ){ + qCritical() << "SingleApplication: Unable to lock memory after random wait."; + abortSafely(); + } + } + + if( inst->primary == false ){ + d->startPrimary(); + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: 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, SingleApplicationPrivate::SecondaryInstance ); + } + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory after secondary start."; + qDebug() << d->memory->errorString(); + } + return; + } + + if( ! d->memory->unlock() ){ + qDebug() << "SingleApplication: Unable to unlock memory at end of execution."; + qDebug() << d->memory->errorString(); + } + + d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance ); + + delete d; + + ::exit( EXIT_SUCCESS ); +} + +SingleApplication::~SingleApplication() +{ + Q_D( SingleApplication ); + delete d; +} + +/** + * Checks if the current application instance is primary. + * @return Returns true if the instance is primary, false otherwise. + */ +bool SingleApplication::isPrimary() +{ + Q_D( SingleApplication ); + 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() +{ + Q_D( SingleApplication ); + 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 SingleApplication::instanceId() +{ + Q_D( SingleApplication ); + return d->instanceNumber; +} + +/** + * Returns the OS PID (Process Identifier) of the process running the primary + * instance. Especially useful when SingleApplication is coupled with OS. + * specific APIs. + * @return Returns the primary instance PID. + */ +qint64 SingleApplication::primaryPid() +{ + Q_D( SingleApplication ); + 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() +{ + Q_D( SingleApplication ); + 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() +{ + return SingleApplicationPrivate::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 successfuly, false otherwise. + */ +bool SingleApplication::sendMessage( const QByteArray &message, int timeout ) +{ + Q_D( SingleApplication ); + + // Nobody to connect to + if( isPrimary() ) return false; + + // Make sure the socket is connected + if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) ) + return false; + + d->socket->write( message ); + bool dataWritten = d->socket->waitForBytesWritten( timeout ); + d->socket->flush(); + return dataWritten; +} + +/** + * Cleans up the shared memory block and exits with a failure. + * This function halts program execution. + */ +void SingleApplication::abortSafely() +{ + Q_D( SingleApplication ); + + qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString(); + delete d; + ::exit( EXIT_FAILURE ); +} + +QStringList SingleApplication::userData() +{ + Q_D( SingleApplication ); + return d->appData(); +} diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication.h b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.h index fd806a3d..d39a6614 100644 --- a/third_party/SingleApplication-3.1.3.1/singleapplication.h +++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.h @@ -85,7 +85,7 @@ public: * 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[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, QString userData = QString() ); ~SingleApplication() override; /** @@ -133,6 +133,12 @@ public: */ bool sendMessage( const QByteArray &message, int timeout = 100 ); + /** + * @brief Get the set user data. + * @returns {QStringList} + */ + QStringList userData(); + Q_SIGNALS: void instanceStarted(); void receivedMessage( quint32 instanceId, QByteArray message ); @@ -140,6 +146,7 @@ Q_SIGNALS: private: SingleApplicationPrivate *d_ptr; Q_DECLARE_PRIVATE(SingleApplication) + void abortSafely(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options) diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication.pri b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.pri index ae81f599..ae81f599 100644 --- a/third_party/SingleApplication-3.1.3.1/singleapplication.pri +++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication.pri diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication_p.cpp b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.cpp index 705609f2..1ab58c23 100644 --- a/third_party/SingleApplication-3.1.3.1/singleapplication_p.cpp +++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.cpp @@ -33,12 +33,20 @@ #include <cstddef> #include <QtCore/QDir> +#include <QtCore/QThread> #include <QtCore/QByteArray> #include <QtCore/QDataStream> +#include <QtCore/QElapsedTimer> #include <QtCore/QCryptographicHash> #include <QtNetwork/QLocalServer> #include <QtNetwork/QLocalSocket> +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +#include <QtCore/QRandomGenerator> +#else +#include <QtCore/QDateTime> +#endif + #include "singleapplication.h" #include "singleapplication_p.h" @@ -49,6 +57,9 @@ #endif #ifdef Q_OS_WIN + #ifndef NOMINMAX + #define NOMINMAX 1 + #endif #include <windows.h> #include <lmcons.h> #endif @@ -59,20 +70,20 @@ SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr ) server = nullptr; socket = nullptr; memory = nullptr; - instanceNumber = -1; + instanceNumber = 0; } SingleApplicationPrivate::~SingleApplicationPrivate() { - if( socket != nullptr ) { + if( socket != nullptr ){ socket->close(); delete socket; } - if( memory != nullptr ) { + if( memory != nullptr ){ memory->lock(); auto *inst = static_cast<InstancesInfo*>(memory->data()); - if( server != nullptr ) { + if( server != nullptr ){ server->close(); delete server; inst->primary = false; @@ -106,7 +117,7 @@ QString SingleApplicationPrivate::getUsername() struct passwd *pw = getpwuid( uid ); if( pw ) username = QString::fromLocal8Bit( pw->pw_name ); - if ( username.isEmpty() ) { + if ( username.isEmpty() ){ #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) username = QString::fromLocal8Bit( qgetenv( "USER" ) ); #else @@ -125,11 +136,14 @@ void SingleApplicationPrivate::genBlockServerName() appData.addData( SingleApplication::app_t::organizationName().toUtf8() ); appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() ); - if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ) { + if ( ! appDataList.isEmpty() ) + appData.addData( appDataList.join( "" ).toUtf8() ); + + if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){ appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() ); } - if( ! (options & SingleApplication::Mode::ExcludeAppPath) ) { + if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){ #ifdef Q_OS_WIN appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() ); #else @@ -138,7 +152,7 @@ void SingleApplicationPrivate::genBlockServerName() } // User level block requires a user specific data in the hash - if( options & SingleApplication::Mode::User ) { + if( options & SingleApplication::Mode::User ){ appData.addData( getUsername().toUtf8() ); } @@ -147,7 +161,7 @@ void SingleApplicationPrivate::genBlockServerName() blockServerName = appData.result().toBase64().replace("/", "_"); } -void SingleApplicationPrivate::initializeMemoryBlock() +void SingleApplicationPrivate::initializeMemoryBlock() const { auto *inst = static_cast<InstancesInfo*>( memory->data() ); inst->primary = false; @@ -159,8 +173,14 @@ void SingleApplicationPrivate::initializeMemoryBlock() void SingleApplicationPrivate::startPrimary() { - Q_Q(SingleApplication); + // Reset the number of connections + auto *inst = static_cast <InstancesInfo*>( memory->data() ); + inst->primary = true; + inst->primaryPid = QCoreApplication::applicationPid(); + qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) ); + inst->checksum = blockChecksum(); + instanceNumber = 0; // Successful creation means that no main process exists // So we start a QLocalServer to listen for connections QLocalServer::removeServer( blockServerName ); @@ -168,10 +188,10 @@ void SingleApplicationPrivate::startPrimary() // Restrict access to the socket according to the // SingleApplication::Mode::User flag on User level or no restrictions - if( options & SingleApplication::Mode::User ) { - server->setSocketOptions( QLocalServer::UserAccessOption ); + if( options & SingleApplication::Mode::User ){ + server->setSocketOptions( QLocalServer::UserAccessOption ); } else { - server->setSocketOptions( QLocalServer::WorldAccessOption ); + server->setSocketOptions( QLocalServer::WorldAccessOption ); } server->listen( blockServerName ); @@ -181,87 +201,95 @@ void SingleApplicationPrivate::startPrimary() this, &SingleApplicationPrivate::slotConnectionEstablished ); - - // Reset the number of connections - auto *inst = static_cast <InstancesInfo*>( memory->data() ); - - inst->primary = true; - inst->primaryPid = q->applicationPid(); - strncpy( inst->primaryUser, getUsername().toUtf8().data(), 127 ); - inst->primaryUser[127] = '\0'; - inst->checksum = blockChecksum(); - - instanceNumber = 0; } void SingleApplicationPrivate::startSecondary() { + auto *inst = static_cast <InstancesInfo*>( memory->data() ); + + inst->secondary += 1; + inst->checksum = blockChecksum(); + instanceNumber = inst->secondary; } -void SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType ) +bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType ) { + QElapsedTimer time; + time.start(); + // Connect to the Local Server of the Primary Instance if not already // connected. - if( socket == nullptr ) { + if( socket == nullptr ){ socket = new QLocalSocket(); } - // If already connected - we are done; - if( socket->state() == QLocalSocket::ConnectedState ) - return; + if( socket->state() == QLocalSocket::ConnectedState ) return true; - // If not connect - if( socket->state() == QLocalSocket::UnconnectedState || - socket->state() == QLocalSocket::ClosingState ) { - socket->connectToServer( blockServerName ); - } + if( socket->state() != QLocalSocket::ConnectedState ){ + + while( true ){ + randomSleep(); - // Wait for being connected - if( socket->state() == QLocalSocket::ConnectingState ) { - socket->waitForConnected( msecs ); + if( socket->state() != QLocalSocket::ConnectingState ) + socket->connectToServer( blockServerName ); + + if( socket->state() == QLocalSocket::ConnectingState ){ + socket->waitForConnected( static_cast<int>(msecs - 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() >= msecs ) return false; + } } // Initialisation message according to the SingleApplication protocol - if( socket->state() == QLocalSocket::ConnectedState ) { - // Notify the parent that a new instance had been started; - QByteArray initMsg; - QDataStream writeStream(&initMsg, QIODevice::WriteOnly); + QByteArray initMsg; + QDataStream writeStream(&initMsg, QIODevice::WriteOnly); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - writeStream.setVersion(QDataStream::Qt_5_6); + writeStream.setVersion(QDataStream::Qt_5_6); #endif - writeStream << blockServerName.toLatin1(); - writeStream << static_cast<quint8>(connectionType); - writeStream << instanceNumber; - quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length())); - writeStream << checksum; + writeStream << blockServerName.toLatin1(); + writeStream << static_cast<quint8>(connectionType); + writeStream << instanceNumber; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length()))); +#else + quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length())); +#endif + writeStream << checksum; - // The header indicates the message length that follows - QByteArray header; - QDataStream headerStream(&header, QIODevice::WriteOnly); + // The header indicates the message length that follows + QByteArray header; + QDataStream headerStream(&header, QIODevice::WriteOnly); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - headerStream.setVersion(QDataStream::Qt_5_6); + headerStream.setVersion(QDataStream::Qt_5_6); #endif - headerStream << static_cast <quint64>( initMsg.length() ); + headerStream << static_cast <quint64>( initMsg.length() ); - socket->write( header ); - socket->write( initMsg ); - socket->flush(); - socket->waitForBytesWritten( msecs ); - } + socket->write( header ); + socket->write( initMsg ); + bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) ); + socket->flush(); + return result; } -quint16 SingleApplicationPrivate::blockChecksum() +quint16 SingleApplicationPrivate::blockChecksum() const { - return qChecksum( - static_cast <const char *>( memory->data() ), - offsetof( InstancesInfo, checksum ) - ); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum))); +#else + quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)); +#endif + return checksum; } -qint64 SingleApplicationPrivate::primaryPid() +qint64 SingleApplicationPrivate::primaryPid() const { qint64 pid; @@ -273,7 +301,7 @@ qint64 SingleApplicationPrivate::primaryPid() return pid; } -QString SingleApplicationPrivate::primaryUser() +QString SingleApplicationPrivate::primaryUser() const { QByteArray username; @@ -294,7 +322,7 @@ void SingleApplicationPrivate::slotConnectionEstablished() connectionMap.insert(nextConnSocket, ConnectionInfo()); QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, - [nextConnSocket, this]() { + [nextConnSocket, this](){ auto &info = connectionMap[nextConnSocket]; Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId ); } @@ -308,9 +336,9 @@ void SingleApplicationPrivate::slotConnectionEstablished() ); QObject::connect(nextConnSocket, &QLocalSocket::readyRead, - [nextConnSocket, this]() { + [nextConnSocket, this](){ auto &info = connectionMap[nextConnSocket]; - switch(info.stage) { + switch(info.stage){ case StageHeader: readInitMessageHeader(nextConnSocket); break; @@ -329,11 +357,11 @@ void SingleApplicationPrivate::slotConnectionEstablished() void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock ) { - if (!connectionMap.contains( sock )) { + if (!connectionMap.contains( sock )){ return; } - if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ) { + if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){ return; } @@ -350,7 +378,7 @@ void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock ) info.stage = StageBody; info.msgLen = msgLen; - if ( sock->bytesAvailable() >= (qint64) msgLen ) { + if ( sock->bytesAvailable() >= (qint64) msgLen ){ readInitMessageBody( sock ); } } @@ -359,12 +387,12 @@ void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock ) { Q_Q(SingleApplication); - if (!connectionMap.contains( sock )) { + if (!connectionMap.contains( sock )){ return; } ConnectionInfo &info = connectionMap[sock]; - if( sock->bytesAvailable() < ( qint64 )info.msgLen ) { + if( sock->bytesAvailable() < ( qint64 )info.msgLen ){ return; } @@ -394,13 +422,17 @@ void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock ) quint16 msgChecksum = 0; readStream >> msgChecksum; - const quint16 actualChecksum = qChecksum( msgBytes.constData(), static_cast<quint32>( msgBytes.length() - sizeof( quint16 ) ) ); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16)))); +#else + const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16))); +#endif bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum; - if( !isValid ) { + if( !isValid ){ sock->close(); return; } @@ -415,7 +447,7 @@ void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock ) Q_EMIT q->instanceStarted(); } - if (sock->bytesAvailable() > 0) { + if (sock->bytesAvailable() > 0){ Q_EMIT this->slotDataAvailable( sock, instanceId ); } } @@ -431,3 +463,23 @@ void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedS if( closedSocket->bytesAvailable() > 0 ) Q_EMIT slotDataAvailable( closedSocket, instanceId ); } + +void SingleApplicationPrivate::randomSleep() +{ +#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 ) + QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u )); +#else + qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() ); + QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 )); +#endif +} + +void SingleApplicationPrivate::addAppData(const QString &data) +{ + appDataList.push_back(data); +} + +QStringList SingleApplicationPrivate::appData() const +{ + return appDataList; +} diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication_p.h b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.h index 29ba346b..c49a46dd 100644 --- a/third_party/SingleApplication-3.1.3.1/singleapplication_p.h +++ b/third_party/SingleApplication-3.2.0-dc8042b/singleapplication_p.h @@ -41,8 +41,8 @@ struct InstancesInfo { bool primary; quint32 secondary; qint64 primaryPid; - quint16 checksum; char primaryUser[128]; + quint16 checksum; // Must be the last field }; struct ConnectionInfo { @@ -70,17 +70,20 @@ public: SingleApplicationPrivate( SingleApplication *q_ptr ); ~SingleApplicationPrivate() override; - QString getUsername(); + static QString getUsername(); void genBlockServerName(); - void initializeMemoryBlock(); + void initializeMemoryBlock() const; void startPrimary(); void startSecondary(); - void connectToPrimary(int msecs, ConnectionType connectionType ); - quint16 blockChecksum(); - qint64 primaryPid(); - QString primaryUser(); + bool connectToPrimary( int msecs, ConnectionType connectionType ); + quint16 blockChecksum() const; + qint64 primaryPid() const; + QString primaryUser() const; void readInitMessageHeader(QLocalSocket *socket); void readInitMessageBody(QLocalSocket *socket); + static void randomSleep(); + void addAppData(const QString &data); + QStringList appData() const; SingleApplication *q_ptr; QSharedMemory *memory; @@ -90,6 +93,7 @@ public: QString blockServerName; SingleApplication::Options options; QMap<QLocalSocket*, ConnectionInfo> connectionMap; + QStringList appDataList; public Q_SLOTS: void slotConnectionEstablished(); |