diff options
author | Itay Grudev <itay@grudev.com> | 2016-08-10 02:42:46 +0100 |
---|---|---|
committer | Itay Grudev <itay@grudev.com> | 2016-08-10 03:41:03 +0100 |
commit | 4e5c1647ccb119b6a536f7ac89e70018427b3ece (patch) | |
tree | de97f11898a02a7671aa58ae66dd78eaed83b32b | |
parent | Fixed signal formatting (diff) | |
download | singleapplication-4e5c1647ccb119b6a536f7ac89e70018427b3ece.tar.xz |
SingleApplication v3.0a
-rw-r--r-- | CHANGELOG.md | 34 | ||||
-rw-r--r-- | README.md | 84 | ||||
-rw-r--r-- | singleapplication.cpp | 404 | ||||
-rw-r--r-- | singleapplication.h | 79 | ||||
-rw-r--r-- | singleapplication.pri | 8 | ||||
-rw-r--r-- | singleapplication_p.h | 80 |
6 files changed, 512 insertions, 177 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 74503aa..a09e6d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,43 @@ Changelog ========= -__v2.4__ +__v3.0a__ -------- +* Depricated secondary instances count. +* Added a sendMessage() method to send a message to the primary instance. +* Added a receivedMessage() signal, emmited when a message is received from a + secondary instance. +* The SingleApplication constructor's third parameter is now a bool + specifying if the current instance should be allowed to run as a secondary + instance of there is already a primary instance. +* The SingleApplication constructor accept a fourth parameter specifying if + the SingleApplication block should be User-wide or System-wide. +* SingleApplication no longer relies on `applicationName` and + `organizationName` to be set. It instead concatenates all of the following + data and computes a `SHA256` hash which is uses as the key for the + `QSharedMemory` block and the `QLocalServer`. Since at least + `applicationFilePath` is always present there is no need to explicitly set + any of these prior to initialising `SingleApplication`. + * QCoreApplication::applicationName + * QCoreApplication::applicationVersion + * QCoreApplication::applicationFilePath + * QCoreApplication::organizationName + * QCoreApplication::organizationDomain + * User name or home directory path if in User mode +* The primary instance is no longer notified when a secondary instance had + been started by default. An setting for this feature exists. +* Added instanceNumber() which represents a unique identifier for each + secondary instance started. When called from the primary instance will + return `0`. + +__v2.4__ + -------- + * Stability improvements * Support for secondary instances. * The library now recovers safely after the primary process has crashed - and the shared memory had not been deleted. +and the shared memory had not been deleted. __v2.3__ -------- @@ -4,8 +4,8 @@ SingleApplication This is a replacement of the QSingleApplication for `Qt5`. Keeps the Primary Instance of your Application and kills each subsequent -instances. It can (if enabled) spawn a certain number of secondary instances -(with the `--secondary` command line argument). +instances. It can (if enabled) spawn secondary (non-related to the primary) +instances and can send data to the primary instance from secondary instances. Usage ----- @@ -15,32 +15,26 @@ 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 uses your `Organization Name` and `Application Name` to set 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 then listen for connections -on it. 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 it that a new instance had been started, after which it would terminate - with status code `0`. The Primary Instance, `SingleApplication` would emit the - `showUp()` signal upon detecting that a new instance had been started. +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 then +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 it that a new instance had been started, after which it +would terminate with status code `0`. 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. -Here is an example usage of the library: - -In your main you need to set the the `applicationName` and `organizationName` of -the `QCoreApplication` class like so: +You can use the library as if you use any other `QCoreApplication` class: ```cpp #include <QApplication> -#include "singleapplication.h" +#include <SingleApplication.h> int main( int argc, char* argv[] ) { - QApplication::setApplicationName("{Your App Name}"); - QApplication::setOrganizationName("{Your Organization Name}"); - SingleApplication app( argc, argv ); return app.exec(); @@ -55,24 +49,28 @@ how: git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication ``` -And include the `singleapplication.pri` file in your `.pro` project file: +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 include(singleapplication/singleapplication.pri) +DEFINES += QAPPLICATION_CLASS=QApplication ``` -The `Show Up` signal +The `Instance Started` signal ------------------------ -The SingleApplication class implements a `showUp()` signal. You can bind to that -signal to raise your application's window when a new instance had been started. +The SingleApplication class implements a `instanceStarted()` signal. You can +bind to that signal to raise your application's window when a new instance had +been started. ```cpp // window is a QWindow instance -QObject::connect( &app, &SingleApplication::showUp, window, &QWindow::raise ); +QObject::connect( &app, &SingleApplication::instanceStarted, window, &QWindow::raise ); ``` -Using `QCoreApplication::instance()` is a neat way to get the +Using `SingleApplication::instance()` is a neat way to get the `SingleApplication` instance for binding to it's signals anywhere in your program. @@ -81,15 +79,27 @@ Secondary Instances If you want to be able to launch additional Secondary Instances (not related to your Primary Instance) you have to enable that with the third parameter of the -`SingleApplication` constructor. The default is `0` meaning no Secondary -Instances. Here is an example allowing spawning up to `2` Secondary Instances. +`SingleApplication` constructor. The default is `false` meaning no Secondary +Instances. Here is an example of how you would start a Secondary Instance send +a message with the command line arguments to the primary instance and then shut +down. ```cpp -SingleApplication app( argc, argv, 2 ); +int main(int argc, char *argv[]) +{ + SingleApplication app( argc, argv, true ); + + if( app.isSecondary() ) { + app.sendMessage( app.arguments().join(' ')).toUtf8() ); + app.exit( 0 ); + } + + return app.exec(); +} ``` -After which just call your program with the `--secondary` argument to launch a -secondary instance. +___Note:__ A secondary instance won't cause the emission of the +`instanceStarted()` signal. You can check whether your instance is a primary or secondary with the following methods: @@ -100,20 +110,18 @@ app.isPrimary(); app.isSecondary(); ``` -__*Note:*__ If your Primary Instance is terminated upon launch of a new one it -will replace it as Primary even if the `--secondary` argument has been set. - -*P.S. If you think this behavior could be improved create an issue and explain -why.* +__*Note:*__ If your Primary Instance is terminated a newly launched instance +will replace the Primary one even if the Secondary flag has been set. Versioning ---------- -The current library versions is `2.4`. +The current library versions is `3.0a`. + Each major version introduces either very significant changes or is not backwards compatible with the previous version. Minor versions only add additional features, bug fixes or performance improvements and are backwards -compatible with the previous release. +compatible with the previous release. See `CHANGELOG.md` for more details. Implementation -------------- @@ -121,7 +129,7 @@ Implementation The library is implemented with a QSharedMemory block which is thread safe and guarantees a race condition will not occur. It also uses a QLocalSocket to notify the main process that a new instance had been spawned and thus invoke the -`showUp()` signal. +`instanceStarted()` signal. To handle an issue on `*nix` systems, where the operating system owns the shared memory block and if the program crashes the memory remains untouched, the diff --git a/singleapplication.cpp b/singleapplication.cpp index 56c0f9e..6b3b85b 100644 --- a/singleapplication.cpp +++ b/singleapplication.cpp @@ -23,95 +23,163 @@ #include <cstdlib> #include <QtCore/QMutex> +#include <QtCore/QProcess> +#include <QtCore/QByteArray> #include <QtCore/QSemaphore> #include <QtCore/QSharedMemory> -#include <QtNetwork/QLocalSocket> +#include <QtCore/QStandardPaths> +#include <QtCore/QCryptographicHash> #include <QtNetwork/QLocalServer> +#include <QtNetwork/QLocalSocket> #ifdef Q_OS_UNIX #include <signal.h> #include <unistd.h> #endif +#ifdef Q_OS_WIN + #include <windows.h> + #include <lmcons.h> +#endif + #include "singleapplication.h" +#include "singleapplication_p.h" -struct InstancesInfo { - bool primary; - uint8_t secondary; -}; +SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr ) : q_ptr( q_ptr ) { + server = nullptr; + socket = nullptr; +} -class SingleApplicationPrivate { -public: - Q_DECLARE_PUBLIC(SingleApplication) +SingleApplicationPrivate::~SingleApplicationPrivate() +{ + cleanUp(); +} - SingleApplicationPrivate( SingleApplication *q_ptr ) : q_ptr( q_ptr ) { - server = NULL; +void SingleApplicationPrivate::genBlockServerName( int timeout ) +{ + QCryptographicHash appData( QCryptographicHash::Sha256 ); + appData.addData( "SingleApplication", 17 ); + appData.addData( SingleApplication::app_t::applicationName().toUtf8() ); + appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() ); + appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() ); + appData.addData( SingleApplication::app_t::organizationName().toUtf8() ); + appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() ); + + // User level block requires a user specific data in the hash + if( options & SingleApplication::Mode::User ) { +#ifdef Q_OS_WIN + char username[UNLEN + 1]; + // Specifies size of the buffer on input + DWORD usernameLength = sizeof( username ); + if( GetUserName( username, &usernameLength ) ) { + // usernameLength includes the null terminating character + appData.addData( username, usernameLength - 1 ); + } else { + appData.addData( QStandardPaths::standardLocations( QStandardPaths::HomeLocation ).join("").toUtf8() ); + } +#endif +#ifdef Q_OS_UNIX + QString username; + QProcess process; + process.start( "whoami" ); + if( process.waitForFinished( timeout ) && + process.exitCode() == QProcess::NormalExit) { + appData.addData( process.readLine() ); + } else { + appData.addData( QStandardPaths::standardLocations( QStandardPaths::HomeLocation ).join("").toUtf8() ); + } +#endif } - ~SingleApplicationPrivate() - { - cleanUp(); - } + // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with + // server naming requirements. + blockServerName = appData.result().toBase64().replace("/", "_"); +} - void startPrimary( bool resetMemory ) - { - Q_Q(SingleApplication); +void SingleApplicationPrivate::startPrimary( bool resetMemory ) +{ #ifdef Q_OS_UNIX - // Handle any further termination signals to ensure the - // QSharedMemory block is deleted even if the process crashes - crashHandler(); + // Handle any further termination signals to ensure the + // QSharedMemory block is deleted even if the process crashes + crashHandler(); #endif - // Successful creation means that no main process exists - // So we start a QLocalServer to listen for connections - QLocalServer::removeServer( memory->key() ); - server = new QLocalServer(); - server->listen( memory->key() ); - QObject::connect( - server, - &QLocalServer::newConnection, - q, - &SingleApplication::slotConnectionEstablished - ); - - // Reset the number of connections - memory->lock(); - InstancesInfo* inst = (InstancesInfo*)memory->data(); - - if( resetMemory ){ - inst->primary = true; - inst->secondary = 0; - } else { - inst->primary = true; - } - - memory->unlock(); + // Successful creation means that no main process exists + // So we start a QLocalServer to listen for connections + QLocalServer::removeServer( blockServerName ); + server = new QLocalServer(); + server->listen( blockServerName ); + QObject::connect( + server, + &QLocalServer::newConnection, + this, + &SingleApplicationPrivate::slotConnectionEstablished + ); + + // Reset the number of connections + memory->lock(); + InstancesInfo* inst = (InstancesInfo*)memory->data(); + + if( resetMemory ){ + inst->primary = true; + inst->secondary = 0; + } else { + inst->primary = true; } - void startSecondary() - { + memory->unlock(); + + instanceNumber = 0; +} + +void SingleApplicationPrivate::startSecondary() +{ #ifdef Q_OS_UNIX - // Handle any further termination signals to ensure the - // QSharedMemory block is deleted even if the process crashes - crashHandler(); + // Handle any further termination signals to ensure the + // QSharedMemory block is deleted even if the process crashes + crashHandler(); #endif +} - notifyPrimary(); +void SingleApplicationPrivate::connectToPrimary( int msecs, char connectionType ) +{ + // Connect to the Local Server of the Primary Instance if not already + // connected. + if( socket == nullptr ) { + socket = new QLocalSocket(); } - void notifyPrimary() - { - // Connect to the Local Server of the main process to notify it - // that a new process had been started - QLocalSocket socket; - socket.connectToServer( memory->key() ); + // If already connected - we are done; + if( socket->state() == QLocalSocket::ConnectedState ) + return; + + // If not connect + if( socket->state() == QLocalSocket::UnconnectedState || + socket->state() == QLocalSocket::ClosingState ) { + socket->connectToServer( blockServerName ); + } + // Wait for being connected + if( socket->state() == QLocalSocket::ConnectingState ) { + socket->waitForConnected( msecs ); + } + + // Initialisation message according to the SingleApplication protocol + if( socket->state() == QLocalSocket::ConnectedState ) { // Notify the parent that a new instance had been started; - socket.waitForConnected( 100 ); - socket.close(); + QByteArray initMsg = blockServerName.toLatin1(); + + initMsg.append( connectionType ); + initMsg.append( (const char *)&instanceNumber, sizeof(quint32) ); + initMsg.append( QByteArray::number( qChecksum( initMsg.constData(), initMsg.length() ), 256) ); + + socket->write( initMsg ); + socket->flush(); + socket->waitForBytesWritten( msecs ); } +} #ifdef Q_OS_UNIX - void crashHandler() + void SingleApplicationPrivate::crashHandler() { // This guarantees the program will work even with multiple // instances of SingleApplication in different threads. @@ -140,7 +208,7 @@ public: signal( SIGXFSZ, SingleApplicationPrivate::terminate ); // 25 } - static void terminate( int signum ) + void SingleApplicationPrivate::terminate( int signum ) { while( ! sharedMem.empty() ) { delete sharedMem.back(); @@ -149,83 +217,154 @@ public: ::exit( 128 + signum ); } - static QList<SingleApplicationPrivate*> sharedMem; - static QMutex sharedMemMutex; + QList<SingleApplicationPrivate*> SingleApplicationPrivate::sharedMem; + QMutex SingleApplicationPrivate::sharedMemMutex; #endif - void cleanUp() { - memory->lock(); - InstancesInfo* inst = (InstancesInfo*)memory->data(); - if( server != NULL ) { - server->close(); - inst->primary = false; +void SingleApplicationPrivate::cleanUp() { + if( socket != nullptr ) { + socket->close(); + delete socket; + } + memory->lock(); + InstancesInfo* inst = (InstancesInfo*)memory->data(); + if( server != nullptr ) { + server->close(); + inst->primary = false; + } + memory->unlock(); + delete memory; +} + +/** + * @brief Executed when a connection has been made to the LocalServer + */ +void SingleApplicationPrivate::slotConnectionEstablished() +{ + Q_Q(SingleApplication); + + QLocalSocket *socket = server->nextPendingConnection(); + + // Verify that the new connection follows the SingleApplication protocol + char connectionType; + quint32 instanceId; + QByteArray initMsg, tmp; + bool invalidConnection = false; + if( socket->waitForReadyRead( 100 ) ) { + tmp = socket->read( blockServerName.length() ); + // Verify that the socket data start with blockServerName + if( tmp == blockServerName.toLatin1() ) { + initMsg = tmp; + tmp = socket->read(1); + // Verify that the next charecter is N/S/R (connecion type) + // Stands for New Instance/Secondary Instance/Reconnect + if( tmp == "N" || tmp == "S" || tmp == "R" ) { + connectionType = tmp.at(0); + initMsg += tmp; + tmp = socket->read( sizeof(quint32) ); + const char * data = tmp.constData(); + instanceId = (quint32)*data; + initMsg += tmp; + // Verify the checksum of the initMsg + QByteArray checksum = QByteArray::number( + qChecksum( initMsg.constData(), initMsg.length() ), + 256 + ); + tmp = socket->read( checksum.length() ); + if( checksum != tmp ) { + invalidConnection = true; + } + } else { + invalidConnection = true; + } } else { - if( inst->secondary > 0 ) - inst->secondary -= 1; + invalidConnection = true; } - memory->unlock(); - delete memory; + } else { + invalidConnection = true; } - QSharedMemory *memory; - SingleApplication *q_ptr; - QLocalServer *server; -}; + if( invalidConnection ) { + socket->close(); + delete socket; + return; + } -#ifdef Q_OS_UNIX - QList<SingleApplicationPrivate*> SingleApplicationPrivate::sharedMem; - QMutex SingleApplicationPrivate::sharedMemMutex; -#endif + QObject::connect( + socket, + &QLocalSocket::aboutToClose, + this, + [socket, instanceId, this]() { + Q_EMIT this->slotClientConnectionClosed( socket, instanceId ); + } + ); + + QObject::connect( + socket, + &QLocalSocket::readyRead, + this, + [socket, instanceId, this]() { + Q_EMIT this->slotDataAvailable( socket, instanceId ); + } + ); + + if( connectionType == 'N' || ( + connectionType == 'S' && + options & SingleApplication::Mode::SecondaryNotification + ) + ) { + Q_EMIT q->instanceStarted(); + } + + if( socket->bytesAvailable() > 0 ) { + Q_EMIT this->slotDataAvailable( socket, instanceId ); + } +} + +void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *socket, quint32 instanceId ) +{ + Q_Q(SingleApplication); + Q_EMIT q->receivedMessage( instanceId, socket->readAll() ); +} + +void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *socket, quint32 instanceId ) +{ + if( socket->bytesAvailable() > 0 ) + Q_EMIT slotDataAvailable( socket, instanceId ); + socket->deleteLater(); +} /** * @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[], uint8_t secondaryInstances ) +SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout ) : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) ) { Q_D(SingleApplication); - // Check command line arguments for the force primary and secondary flags -#ifdef Q_OS_UNIX - bool forcePrimary = false; -#endif - bool secondary = false; - for( int i = 0; i < argc; ++i ) { - if( strcmp( argv[i], "--secondary" ) == 0 ) { - secondary = true; -#ifndef Q_OS_UNIX - break; -#endif - } -#ifdef Q_OS_UNIX - if( strcmp( argv[i], "--primary" ) == 0 ) { - secondary = false; - forcePrimary = true; - break; - } -#endif - } + // Store the current mode of the program + d->options = options; - QString serverName = app_t::organizationName() + app_t::applicationName(); - serverName.replace( QRegExp("[^\\w\\-. ]"), "" ); + // Generating an application ID used for identifying the shared memory + // block and QLocalServer + d->genBlockServerName( timeout ); // Guarantee thread safe behaviour with a shared memory block. Also by - // attaching to it and deleting it we make sure that the memory i deleted - // even if the process had crashed - d->memory = new QSharedMemory( serverName ); + // explicitly attaching it and then deleting it we make sure that the + // memory is deleted even if the process had crashed on Unix. +#ifdef Q_OS_UNIX + d->memory = new QSharedMemory( d->blockServerName ); d->memory->attach(); delete d->memory; - d->memory = new QSharedMemory( serverName ); - - // Create a shared memory block with a minimum size of 1 byte -#ifdef Q_OS_UNIX - if( d->memory->create( sizeof(InstancesInfo) ) || forcePrimary ) { -#else - if( d->memory->create( sizeof(InstancesInfo) ) ) { #endif + d->memory = new QSharedMemory( d->blockServerName ); + + // Create a shared memory block + if( d->memory->create( sizeof( InstancesInfo ) ) ) { d->startPrimary( true ); return; } else { @@ -241,9 +380,13 @@ SingleApplication::SingleApplication( int &argc, char *argv[], uint8_t secondary } // Check if another instance can be started - if( secondary && inst->secondary < secondaryInstances ) { + if( allowSecondary ) { inst->secondary += 1; + d->instanceNumber = inst->secondary; d->startSecondary(); + if( d->options & Mode::SecondaryNotification ) { + d->connectToPrimary( timeout, 'S' ); + } d->memory->unlock(); return; } @@ -252,9 +395,9 @@ SingleApplication::SingleApplication( int &argc, char *argv[], uint8_t secondary } } - d->notifyPrimary(); + d->connectToPrimary( timeout, 'N' ); delete d; - ::exit(EXIT_SUCCESS); + ::exit( EXIT_SUCCESS ); } /** @@ -269,24 +412,33 @@ SingleApplication::~SingleApplication() bool SingleApplication::isPrimary() { Q_D(SingleApplication); - return d->server != NULL; + return d->server != nullptr; } bool SingleApplication::isSecondary() { Q_D(SingleApplication); - return d->server == NULL; + return d->server == nullptr; } -/** - * @brief Executed when a connection has been made to the LocalServer - */ -void SingleApplication::slotConnectionEstablished() +quint32 SingleApplication::instanceId() { Q_D(SingleApplication); + return d->instanceNumber; +} + +bool SingleApplication::sendMessage( QByteArray message, int timeout ) +{ + Q_D(SingleApplication); + + // Nobody to connect to + if( isPrimary() ) return false; + + // Make sure the socket is connected + d->connectToPrimary( timeout, 'R' ); - QLocalSocket *socket = d->server->nextPendingConnection(); - socket->close(); - delete socket; - Q_EMIT showUp(); + d->socket->write( message ); + bool dataWritten = d->socket->flush(); + d->socket->waitForBytesWritten( timeout ); + return dataWritten; } diff --git a/singleapplication.h b/singleapplication.h index 933e5b1..9376dfe 100644 --- a/singleapplication.h +++ b/singleapplication.h @@ -24,6 +24,7 @@ #define SINGLE_APPLICATION_H #include <QtCore/QtGlobal> +#include <QtNetwork/QLocalSocket> #ifndef QAPPLICATION_CLASS #define QAPPLICATION_CLASS QCoreApplication @@ -34,31 +35,93 @@ class SingleApplicationPrivate; /** - * @brief The SingleApplication class handles multipe instances of the same Application - * @see QApplication + * @brief The SingleApplication class handles multipe instances of the same + * Application + * @see QCoreApplication */ class SingleApplication : public QAPPLICATION_CLASS { Q_OBJECT - Q_DECLARE_PRIVATE(SingleApplication) typedef QAPPLICATION_CLASS app_t; public: - explicit SingleApplication( int &argc, char *argv[], uint8_t secondaryInstances = 0 ); + /** + * @brief Mode of operation of SingleApplication. + * 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 Mode { + User = 1 << 0, + System = 1 << 1, + SecondaryNotification = 1 << 2 + }; + Q_DECLARE_FLAGS(Options, Mode) + + /** + * @brief Intitializes a SingleApplication 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 SingleApplication block to be applied + * User wide or System wide. + * @arg {int} timeout - Timeout to wait in miliseconds. + * @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 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 = 100 ); ~SingleApplication(); + /** + * @brief Returns if the instance is the primary instance + * @returns {bool} + */ bool isPrimary(); + + /** + * @brief Returns if the instance is a secondary instance + * @returns {bool} + */ bool isSecondary(); -Q_SIGNALS: - void showUp(); + /** + * @brief Returns a unique identifier for the current instance + * @returns {int} + */ + quint32 instanceId(); -private Q_SLOTS: - void slotConnectionEstablished(); + /** + * @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( QByteArray message, int timeout = 100 ); + +Q_SIGNALS: + void instanceStarted(); + void receivedMessage( quint32 instanceId, QByteArray message ); private: SingleApplicationPrivate *d_ptr; + Q_DECLARE_PRIVATE(SingleApplication) }; +Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options) + #endif // SINGLE_APPLICATION_H diff --git a/singleapplication.pri b/singleapplication.pri index 2c739a6..388e59f 100644 --- a/singleapplication.pri +++ b/singleapplication.pri @@ -1,6 +1,8 @@ -DEFINES += QAPPLICATION_CLASS=QApplication +QT += core network +CONFIG += c++11 -HEADERS += $$PWD/singleapplication.h +HEADERS += $$PWD/singleapplication.h \ + $$PWD/singleapplication_p.h SOURCES += $$PWD/singleapplication.cpp -QT += core network +INCLUDEPATH += $$PWD diff --git a/singleapplication_p.h b/singleapplication_p.h new file mode 100644 index 0000000..0769a53 --- /dev/null +++ b/singleapplication_p.h @@ -0,0 +1,80 @@ +// The MIT License (MIT) +// +// Copyright (c) Itay Grudev 2015 - 2016 +// +// 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 file is not part of the SingleApplication API. It is used purely as an +// implementation detail. This header file may change from version to +// version without notice, or may even be removed. +// + +#ifndef SINGLEAPPLICATION_P_H +#define SINGLEAPPLICATION_P_H + +#include <QtCore/QSharedMemory> +#include <QtNetwork/QLocalServer> +#include <QtNetwork/QLocalSocket> +#include "singleapplication.h" + +struct InstancesInfo { + bool primary; + quint32 secondary; +}; + +class SingleApplicationPrivate : public QObject { +Q_OBJECT +public: + Q_DECLARE_PUBLIC(SingleApplication) + + SingleApplicationPrivate( SingleApplication *q_ptr ); + ~SingleApplicationPrivate(); + + void genBlockServerName( int msecs ); + void startPrimary( bool resetMemory ); + void startSecondary(); + void connectToPrimary( int msecs, char connectionType ); + void cleanUp(); + +#ifdef Q_OS_UNIX + void crashHandler(); + static void terminate( int signum ); + static QList<SingleApplicationPrivate*> sharedMem; + static QMutex sharedMemMutex; +#endif + + QSharedMemory *memory; + SingleApplication *q_ptr; + QLocalSocket *socket; + QLocalServer *server; + quint32 instanceNumber; + QString blockServerName; + SingleApplication::Options options; + +public Q_SLOTS: + void slotConnectionEstablished(); + void slotDataAvailable( QLocalSocket*, quint32 ); + void slotClientConnectionClosed( QLocalSocket*, quint32 ); +}; + +#endif // SINGLEAPPLICATION_P_H |