SingleApplication v3.0a

This commit is contained in:
Itay Grudev 2016-08-10 02:42:46 +01:00
parent b459c9247b
commit 4e5c1647cc
No known key found for this signature in database
GPG Key ID: 913C021BA6F9DE98
6 changed files with 522 additions and 187 deletions

View File

@ -1,13 +1,43 @@
Changelog 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 * Stability improvements
* Support for secondary instances. * Support for secondary instances.
* The library now recovers safely after the primary process has crashed * 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__ __v2.3__
-------- --------

View File

@ -4,8 +4,8 @@ SingleApplication
This is a replacement of the QSingleApplication for `Qt5`. This is a replacement of the QSingleApplication for `Qt5`.
Keeps the Primary Instance of your Application and kills each subsequent Keeps the Primary Instance of your Application and kills each subsequent
instances. It can (if enabled) spawn a certain number of secondary instances instances. It can (if enabled) spawn secondary (non-related to the primary)
(with the `--secondary` command line argument). instances and can send data to the primary instance from secondary instances.
Usage 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` default). Further usage is similar to the use of the `Q[Core|Gui]Application`
classes. classes.
The library uses your `Organization Name` and `Application Name` to set up a The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
`QLocalServer` and a `QSharedMemory` block. The first instance of your instance of your Application is your Primary Instance. It would check if the
Application is your Primary Instance. It would check if the shared memory block shared memory block exists and if not it will start a `QLocalServer` and then
exists and if not it will start a `QLocalServer` and then listen for connections listen for connections. Each subsequent instance of your application would
on it. Each subsequent instance of your application would check if the shared check if the shared memory block exists and if it does, it will connect to the
memory block exists and if it does, it will connect to the QLocalServer to QLocalServer to notify it that a new instance had been started, after which it
notify it that a new instance had been started, after which it would terminate would terminate with status code `0`. The Primary Instance, `SingleApplication`
with status code `0`. The Primary Instance, `SingleApplication` would emit the would emit the `instanceStarted()` signal upon detecting that a new instance
`showUp()` signal upon detecting that a new instance had been started. had been started.
The library uses `stdlib` to terminate the program with the `exit()` function. The library uses `stdlib` to terminate the program with the `exit()` function.
Here is an example usage of the library: You can use the library as if you use any other `QCoreApplication` class:
In your main you need to set the the `applicationName` and `organizationName` of
the `QCoreApplication` class like so:
```cpp ```cpp
#include <QApplication> #include <QApplication>
#include "singleapplication.h" #include <SingleApplication.h>
int main( int argc, char* argv[] ) int main( int argc, char* argv[] )
{ {
QApplication::setApplicationName("{Your App Name}");
QApplication::setOrganizationName("{Your Organization Name}");
SingleApplication app( argc, argv ); SingleApplication app( argc, argv );
return app.exec(); return app.exec();
@ -55,24 +49,28 @@ how:
git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication 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 ```qmake
include(singleapplication/singleapplication.pri) 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 The SingleApplication class implements a `instanceStarted()` signal. You can
signal to raise your application's window when a new instance had been started. bind to that signal to raise your application's window when a new instance had
been started.
```cpp ```cpp
// window is a QWindow instance // 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 `SingleApplication` instance for binding to it's signals anywhere in your
program. program.
@ -81,15 +79,27 @@ Secondary Instances
If you want to be able to launch additional Secondary Instances (not related to 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 your Primary Instance) you have to enable that with the third parameter of the
`SingleApplication` constructor. The default is `0` meaning no Secondary `SingleApplication` constructor. The default is `false` meaning no Secondary
Instances. Here is an example allowing spawning up to `2` Secondary Instances. 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 ```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 ___Note:__ A secondary instance won't cause the emission of the
secondary instance. `instanceStarted()` signal.
You can check whether your instance is a primary or secondary with the following You can check whether your instance is a primary or secondary with the following
methods: methods:
@ -100,20 +110,18 @@ app.isPrimary();
app.isSecondary(); app.isSecondary();
``` ```
__*Note:*__ If your Primary Instance is terminated upon launch of a new one it __*Note:*__ If your Primary Instance is terminated a newly launched instance
will replace it as Primary even if the `--secondary` argument has been set. will replace the Primary one even if the Secondary flag has been set.
*P.S. If you think this behavior could be improved create an issue and explain
why.*
Versioning 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 Each major version introduces either very significant changes or is not
backwards compatible with the previous version. Minor versions only add backwards compatible with the previous version. Minor versions only add
additional features, bug fixes or performance improvements and are backwards 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 Implementation
-------------- --------------
@ -121,7 +129,7 @@ Implementation
The library is implemented with a QSharedMemory block which is thread safe and 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 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 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 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 memory block and if the program crashes the memory remains untouched, the

View File

@ -23,39 +23,81 @@
#include <cstdlib> #include <cstdlib>
#include <QtCore/QMutex> #include <QtCore/QMutex>
#include <QtCore/QProcess>
#include <QtCore/QByteArray>
#include <QtCore/QSemaphore> #include <QtCore/QSemaphore>
#include <QtCore/QSharedMemory> #include <QtCore/QSharedMemory>
#include <QtNetwork/QLocalSocket> #include <QtCore/QStandardPaths>
#include <QtCore/QCryptographicHash>
#include <QtNetwork/QLocalServer> #include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
#include <signal.h> #include <signal.h>
#include <unistd.h> #include <unistd.h>
#endif #endif
#ifdef Q_OS_WIN
#include <windows.h>
#include <lmcons.h>
#endif
#include "singleapplication.h" #include "singleapplication.h"
#include "singleapplication_p.h"
struct InstancesInfo { SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr ) : q_ptr( q_ptr ) {
bool primary; server = nullptr;
uint8_t secondary; socket = nullptr;
}; }
class SingleApplicationPrivate { SingleApplicationPrivate::~SingleApplicationPrivate()
public: {
Q_DECLARE_PUBLIC(SingleApplication)
SingleApplicationPrivate( SingleApplication *q_ptr ) : q_ptr( q_ptr ) {
server = NULL;
}
~SingleApplicationPrivate()
{
cleanUp(); cleanUp();
}
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
} }
void startPrimary( bool resetMemory ) // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
{ // server naming requirements.
Q_Q(SingleApplication); blockServerName = appData.result().toBase64().replace("/", "_");
}
void SingleApplicationPrivate::startPrimary( bool resetMemory )
{
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
// Handle any further termination signals to ensure the // Handle any further termination signals to ensure the
// QSharedMemory block is deleted even if the process crashes // QSharedMemory block is deleted even if the process crashes
@ -63,14 +105,14 @@ public:
#endif #endif
// Successful creation means that no main process exists // Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections // So we start a QLocalServer to listen for connections
QLocalServer::removeServer( memory->key() ); QLocalServer::removeServer( blockServerName );
server = new QLocalServer(); server = new QLocalServer();
server->listen( memory->key() ); server->listen( blockServerName );
QObject::connect( QObject::connect(
server, server,
&QLocalServer::newConnection, &QLocalServer::newConnection,
q, this,
&SingleApplication::slotConnectionEstablished &SingleApplicationPrivate::slotConnectionEstablished
); );
// Reset the number of connections // Reset the number of connections
@ -85,33 +127,59 @@ public:
} }
memory->unlock(); memory->unlock();
}
void startSecondary() instanceNumber = 0;
{ }
void SingleApplicationPrivate::startSecondary()
{
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
// Handle any further termination signals to ensure the // Handle any further termination signals to ensure the
// QSharedMemory block is deleted even if the process crashes // QSharedMemory block is deleted even if the process crashes
crashHandler(); crashHandler();
#endif #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() // If already connected - we are done;
{ if( socket->state() == QLocalSocket::ConnectedState )
// Connect to the Local Server of the main process to notify it return;
// that a new process had been started
QLocalSocket socket;
socket.connectToServer( memory->key() );
// 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; // Notify the parent that a new instance had been started;
socket.waitForConnected( 100 ); QByteArray initMsg = blockServerName.toLatin1();
socket.close();
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 #ifdef Q_OS_UNIX
void crashHandler() void SingleApplicationPrivate::crashHandler()
{ {
// This guarantees the program will work even with multiple // This guarantees the program will work even with multiple
// instances of SingleApplication in different threads. // instances of SingleApplication in different threads.
@ -140,7 +208,7 @@ public:
signal( SIGXFSZ, SingleApplicationPrivate::terminate ); // 25 signal( SIGXFSZ, SingleApplicationPrivate::terminate ); // 25
} }
static void terminate( int signum ) void SingleApplicationPrivate::terminate( int signum )
{ {
while( ! sharedMem.empty() ) { while( ! sharedMem.empty() ) {
delete sharedMem.back(); delete sharedMem.back();
@ -149,83 +217,154 @@ public:
::exit( 128 + signum ); ::exit( 128 + signum );
} }
static QList<SingleApplicationPrivate*> sharedMem;
static QMutex sharedMemMutex;
#endif
void cleanUp() {
memory->lock();
InstancesInfo* inst = (InstancesInfo*)memory->data();
if( server != NULL ) {
server->close();
inst->primary = false;
} else {
if( inst->secondary > 0 )
inst->secondary -= 1;
}
memory->unlock();
delete memory;
}
QSharedMemory *memory;
SingleApplication *q_ptr;
QLocalServer *server;
};
#ifdef Q_OS_UNIX
QList<SingleApplicationPrivate*> SingleApplicationPrivate::sharedMem; QList<SingleApplicationPrivate*> SingleApplicationPrivate::sharedMem;
QMutex SingleApplicationPrivate::sharedMemMutex; QMutex SingleApplicationPrivate::sharedMemMutex;
#endif #endif
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 {
invalidConnection = true;
}
} else {
invalidConnection = true;
}
if( invalidConnection ) {
socket->close();
delete socket;
return;
}
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 * @brief Constructor. Checks and fires up LocalServer or closes the program
* if another instance already exists * if another instance already exists
* @param argc * @param argc
* @param argv * @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 ) ) : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
{ {
Q_D(SingleApplication); Q_D(SingleApplication);
// Check command line arguments for the force primary and secondary flags // Store the current mode of the program
#ifdef Q_OS_UNIX d->options = options;
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
}
QString serverName = app_t::organizationName() + app_t::applicationName(); // Generating an application ID used for identifying the shared memory
serverName.replace( QRegExp("[^\\w\\-. ]"), "" ); // block and QLocalServer
d->genBlockServerName( timeout );
// Guarantee thread safe behaviour with a shared memory block. Also by // 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 // explicitly attaching it and then deleting it we make sure that the
// even if the process had crashed // memory is deleted even if the process had crashed on Unix.
d->memory = new QSharedMemory( serverName ); #ifdef Q_OS_UNIX
d->memory = new QSharedMemory( d->blockServerName );
d->memory->attach(); d->memory->attach();
delete d->memory; 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 #endif
d->memory = new QSharedMemory( d->blockServerName );
// Create a shared memory block
if( d->memory->create( sizeof( InstancesInfo ) ) ) {
d->startPrimary( true ); d->startPrimary( true );
return; return;
} else { } else {
@ -241,9 +380,13 @@ SingleApplication::SingleApplication( int &argc, char *argv[], uint8_t secondary
} }
// Check if another instance can be started // Check if another instance can be started
if( secondary && inst->secondary < secondaryInstances ) { if( allowSecondary ) {
inst->secondary += 1; inst->secondary += 1;
d->instanceNumber = inst->secondary;
d->startSecondary(); d->startSecondary();
if( d->options & Mode::SecondaryNotification ) {
d->connectToPrimary( timeout, 'S' );
}
d->memory->unlock(); d->memory->unlock();
return; return;
} }
@ -252,9 +395,9 @@ SingleApplication::SingleApplication( int &argc, char *argv[], uint8_t secondary
} }
} }
d->notifyPrimary(); d->connectToPrimary( timeout, 'N' );
delete d; delete d;
::exit(EXIT_SUCCESS); ::exit( EXIT_SUCCESS );
} }
/** /**
@ -269,24 +412,33 @@ SingleApplication::~SingleApplication()
bool SingleApplication::isPrimary() bool SingleApplication::isPrimary()
{ {
Q_D(SingleApplication); Q_D(SingleApplication);
return d->server != NULL; return d->server != nullptr;
} }
bool SingleApplication::isSecondary() bool SingleApplication::isSecondary()
{ {
Q_D(SingleApplication); Q_D(SingleApplication);
return d->server == NULL; return d->server == nullptr;
} }
/** quint32 SingleApplication::instanceId()
* @brief Executed when a connection has been made to the LocalServer {
*/ Q_D(SingleApplication);
void SingleApplication::slotConnectionEstablished() return d->instanceNumber;
}
bool SingleApplication::sendMessage( QByteArray message, int timeout )
{ {
Q_D(SingleApplication); Q_D(SingleApplication);
QLocalSocket *socket = d->server->nextPendingConnection(); // Nobody to connect to
socket->close(); if( isPrimary() ) return false;
delete socket;
Q_EMIT showUp(); // Make sure the socket is connected
d->connectToPrimary( timeout, 'R' );
d->socket->write( message );
bool dataWritten = d->socket->flush();
d->socket->waitForBytesWritten( timeout );
return dataWritten;
} }

View File

@ -24,6 +24,7 @@
#define SINGLE_APPLICATION_H #define SINGLE_APPLICATION_H
#include <QtCore/QtGlobal> #include <QtCore/QtGlobal>
#include <QtNetwork/QLocalSocket>
#ifndef QAPPLICATION_CLASS #ifndef QAPPLICATION_CLASS
#define QAPPLICATION_CLASS QCoreApplication #define QAPPLICATION_CLASS QCoreApplication
@ -34,31 +35,93 @@
class SingleApplicationPrivate; class SingleApplicationPrivate;
/** /**
* @brief The SingleApplication class handles multipe instances of the same Application * @brief The SingleApplication class handles multipe instances of the same
* @see QApplication * Application
* @see QCoreApplication
*/ */
class SingleApplication : public QAPPLICATION_CLASS class SingleApplication : public QAPPLICATION_CLASS
{ {
Q_OBJECT Q_OBJECT
Q_DECLARE_PRIVATE(SingleApplication)
typedef QAPPLICATION_CLASS app_t; typedef QAPPLICATION_CLASS app_t;
public: 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(); ~SingleApplication();
/**
* @brief Returns if the instance is the primary instance
* @returns {bool}
*/
bool isPrimary(); bool isPrimary();
/**
* @brief Returns if the instance is a secondary instance
* @returns {bool}
*/
bool isSecondary(); 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: private:
SingleApplicationPrivate *d_ptr; SingleApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleApplication)
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
#endif // SINGLE_APPLICATION_H #endif // SINGLE_APPLICATION_H

View File

@ -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 SOURCES += $$PWD/singleapplication.cpp
QT += core network INCLUDEPATH += $$PWD

80
singleapplication_p.h Normal file
View File

@ -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