aboutsummaryrefslogtreecommitdiff
path: root/3rd-party/SingleApplication
diff options
context:
space:
mode:
Diffstat (limited to '3rd-party/SingleApplication')
-rw-r--r--3rd-party/SingleApplication/CHANGELOG.md162
-rw-r--r--3rd-party/SingleApplication/CMakeLists.txt26
-rw-r--r--3rd-party/SingleApplication/LICENSE24
-rw-r--r--3rd-party/SingleApplication/README.md287
-rw-r--r--3rd-party/SingleApplication/Windows.md46
-rw-r--r--3rd-party/SingleApplication/singleapplication.cpp468
-rw-r--r--3rd-party/SingleApplication/singleapplication.h135
-rw-r--r--3rd-party/SingleApplication/singleapplication_p.h85
8 files changed, 1233 insertions, 0 deletions
diff --git a/3rd-party/SingleApplication/CHANGELOG.md b/3rd-party/SingleApplication/CHANGELOG.md
new file mode 100644
index 0000000..4665222
--- /dev/null
+++ b/3rd-party/SingleApplication/CHANGELOG.md
@@ -0,0 +1,162 @@
+Changelog
+=========
+
+__3.0.10__
+----------
+
+* Removed C style casts and eliminated all clang warnings. Fixed `instanceId`
+ reading from only one byte in the message deserialization. Cleaned up
+ serialization code using `QDataStream`. Changed connection type to use
+ `quint8 enum` rather than `char`.
+* Renamed `SingleAppConnectionType` to `ConnectionType`. Added initialization
+ values to all `ConnectionType` enum cases.
+
+ _Jedidiah Buck McCready_
+
+__3.0.9__
+---------
+
+* Added SingleApplicationPrivate::primaryPid() as a solution to allow
+ bringing the primary window of an application to the foreground on
+ Windows.
+
+ _Eelco van Dam from Peacs BV_
+
+__3.0.8__
+---------
+
+* Bug fix - changed QApplication::instance() to QCoreApplication::instance()
+
+ _Evgeniy Bazhenov_
+
+__3.0.7a__
+----------
+
+* Fixed compilation error with Mingw32 in MXE thanks to Vitaly Tonkacheyev.
+* Removed QMutex used for thread safe behaviour. The implementation now uses
+ QCoreApplication::instance() to get an instance to SingleApplication for
+ memory deallocation.
+
+__3.0.6a__
+----------
+
+* Reverted GetUserName API usage on Windows. Fixed bug with missing library.
+* Fixed bug in the Calculator example, preventing it's window to be raised
+ on Windows.
+
+ Special thanks to Charles Gunawan.
+
+__3.0.5a__
+----------
+
+* Fixed a memory leak in the SingleApplicationPrivate destructor.
+
+ _Sergei Moiseev_
+
+__3.0.4a__
+----------
+
+* Fixed shadow and uninitialised variable warnings.
+
+ _Paul Walmsley_
+
+__3.0.3a__
+----------
+
+* Removed Microsoft Windows specific code for getting username due to
+ multiple problems and compiler differences on Windows platforms. On
+ Windows the shared memory block in User mode now includes the user's
+ home path (which contains the user's username).
+
+* Explicitly getting absolute path of the user's home directory as on Unix
+ a relative path (`~`) may be returned.
+
+__3.0.2a__
+----------
+
+* Fixed bug on Windows when username containing wide characters causes the
+ library to crash.
+
+ _Le Liu_
+
+__3.0.1a__
+----------
+
+* Allows the application path and version to be excluded from the server name
+ hash. The following flags were added for this purpose:
+ * `SingleApplication::Mode::ExcludeAppVersion`
+ * `SingleApplication::Mode::ExcludeAppPath`
+* Allow a non elevated process to connect to a local server created by an
+ elevated process run by the same user on Windows
+* Fixes a problem with upper case letters in paths on Windows
+
+ _Le Liu_
+
+__v3.0a__
+---------
+
+* Depricated secondary instances count.
+* Added a sendMessage() method to send a message to the primary instance.
+* Added a receivedMessage() signal, emitted 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 if 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 used as the key of the
+ `QSharedMemory` block and the `QLocalServer`. Since at least
+ `applicationFilePath` is always present there is no need to explicitly set
+ any of the following 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. A `Mode` flag 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.
+
+__v2.3__
+--------
+
+* Improved pimpl design and inheritance safety.
+
+ _Vladislav Pyatnichenko_
+
+__v2.2__
+--------
+
+* The `QAPPLICATION_CLASS` macro can now be defined in the file including the
+Single Application header or with a `DEFINES+=` statement in the project file.
+
+__v2.1__
+--------
+
+* A race condition can no longer occur when starting two processes nearly
+ simultaneously.
+
+ Fix issue [#3](https://github.com/itay-grudev/SingleApplication/issues/3)
+
+__v2.0__
+--------
+
+* SingleApplication is now being passed a reference to `argc` instead of a
+ copy.
+
+ Fix issue [#1](https://github.com/itay-grudev/SingleApplication/issues/1)
+
+* Improved documentation.
diff --git a/3rd-party/SingleApplication/CMakeLists.txt b/3rd-party/SingleApplication/CMakeLists.txt
new file mode 100644
index 0000000..5de0253
--- /dev/null
+++ b/3rd-party/SingleApplication/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Find includes in corresponding build directories
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+# Instruct CMake to run moc automatically when needed.
+set(CMAKE_AUTOMOC ON)
+#set(CMAKE_AUTOUIC ON)
+#set(CMAKE_AUTORCC ON)
+
+add_library(SingleApplication
+ singleapplication.cpp
+ singleapplication.h
+ singleapplication_p.h)
+
+target_link_libraries(SingleApplication
+ Qt5::Core Qt5::Network Qt5::Widgets
+)
+
+target_compile_definitions(SingleApplication
+ PRIVATE QAPPLICATION_CLASS=QApplication
+)
+
+if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
+ target_link_libraries(SingleApplication
+ Advapi32
+ )
+endif()
diff --git a/3rd-party/SingleApplication/LICENSE b/3rd-party/SingleApplication/LICENSE
new file mode 100644
index 0000000..85b2a14
--- /dev/null
+++ b/3rd-party/SingleApplication/LICENSE
@@ -0,0 +1,24 @@
+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.
+
+Note: Some of the examples include code not distributed under the terms of the
+MIT License.
diff --git a/3rd-party/SingleApplication/README.md b/3rd-party/SingleApplication/README.md
new file mode 100644
index 0000000..82e7a5b
--- /dev/null
+++ b/3rd-party/SingleApplication/README.md
@@ -0,0 +1,287 @@
+SingleApplication
+=================
+
+This is a replacement of the QtSingleApplication for `Qt5`.
+
+Keeps the Primary Instance of your Application and kills each subsequent
+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
+-----
+
+The `SingleApplication` class inherits from whatever `Q[Core|Gui]Application`
+class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the
+default). Further usage is similar to the use of the `Q[Core|Gui]Application`
+classes.
+
+The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
+instance of your Application is your Primary Instance. It would check if the
+shared memory block exists and if not it will start a `QLocalServer` and listen
+for connections. Each subsequent instance of your application would check if the
+shared memory block exists and if it does, it will connect to the QLocalServer
+to notify the primary instance that a new instance had been started, after which
+it would terminate with status code `0`. In the Primary Instance
+`SingleApplication` would emit the `instanceStarted()` signal upon detecting
+that a new instance had been started.
+
+The library uses `stdlib` to terminate the program with the `exit()` function.
+
+You can use the library as if you use any other `QCoreApplication` derived
+class:
+
+```cpp
+#include <QApplication>
+#include <SingleApplication.h>
+
+int main( int argc, char* argv[] )
+{
+ SingleApplication app( argc, argv );
+
+ return app.exec();
+}
+```
+
+To include the library files I would recommend that you add it as a git
+submodule to your project and include it's contents with a `.pri` file. Here is
+how:
+
+```bash
+git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication
+```
+
+Then include the `singleapplication.pri` file in your `.pro` project file. Also
+don't forget to specify which `QCoreApplication` class your app is using if it
+is not `QCoreApplication`.
+
+```qmake
+include(singleapplication/singleapplication.pri)
+DEFINES += QAPPLICATION_CLASS=QApplication
+```
+
+The `Instance Started` signal
+------------------------
+
+The SingleApplication class implements a `instanceStarted()` signal. You can
+bind to that signal to raise your application's window when a new instance had
+been started, for example.
+
+```cpp
+// window is a QWindow instance
+QObject::connect(
+ &app,
+ &SingleApplication::instanceStarted,
+ &window,
+ &QWindow::raise
+);
+```
+
+Using `SingleApplication::instance()` is a neat way to get the
+`SingleApplication` instance for binding to it's signals anywhere in your
+program.
+
+__Note:__ On Windows the ability to bring the application windows to the
+foreground is restricted. See [Windows specific implementations](Windows.md)
+for a workaround and an example implementation.
+
+
+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 `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
+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();
+}
+```
+
+*__Note:__ A secondary instance won't cause the emission of the
+`instanceStarted()` signal by default. See `SingleApplication::Mode` for more
+details.*
+
+You can check whether your instance is a primary or secondary with the following
+methods:
+
+```cpp
+app.isPrimary();
+// or
+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.*
+
+API
+---
+
+### Members
+
+```cpp
+SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100 )
+```
+
+Depending on whether `allowSecondary` is set, this constructor may terminate
+your app if there is already a primary instance running. Additional `Options`
+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.
+
+*__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
+and the secondary instance.*
+
+*__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.*
+
+---
+
+```cpp
+bool SingleApplication::sendMessage( QByteArray message, int timeout = 100 )
+```
+
+Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout
+in milliseconds for blocking functions
+
+---
+
+```cpp
+bool SingleApplication::isPrimary()
+```
+
+Returns if the instance is the primary instance.
+
+---
+
+```cpp
+bool SingleApplication::isSecondary()
+```
+Returns if the instance is a secondary instance.
+
+---
+
+```cpp
+quint32 SingleApplication::instanceId()
+```
+
+Returns a unique identifier for the current instance.
+
+---
+
+```cpp
+qint64 SingleApplication::primaryPid()
+```
+
+Returns the process ID (PID) of the primary instance.
+
+### Signals
+
+```cpp
+void SingleApplication::instanceStarted()
+```
+
+Triggered whenever a new instance had been started, except for secondary
+instances if the `Mode::SecondaryNotification` flag is not specified.
+
+---
+
+```cpp
+void SingleApplication::receivedMessage( quint32 instanceId, QByteArray message )
+```
+
+Triggered whenever there is a message received from a secondary instance.
+
+---
+
+### Flags
+
+```cpp
+enum SingleApplication::Mode
+```
+
+* `Mode::User` - The SingleApplication block should apply user wide. This adds
+ user specific data to the key used for the shared memory and server name.
+ This is the default functionality.
+* `Mode::System` – The SingleApplication block applies system-wide.
+* `Mode::SecondaryNotification` – Whether to trigger `instanceStarted()` even
+ whenever secondary instances are started.
+* `Mode::ExcludeAppPath` – Excludes the application path from the server name
+ (and memory block) hash.
+* `Mode::ExcludeAppVersion` – Excludes the application version from the server
+ name (and memory block) hash.
+
+*__Note:__ `Mode::SecondaryNotification` only works if set on both the primary
+and the secondary instance.*
+
+*__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.*
+
+---
+
+Versioning
+----------
+
+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. See [`CHANGELOG.md`](CHANGELOG.md) for
+more details.
+
+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
+`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
+library binds to the following signals and closes the program with
+`error code = 128 + signum` where signum is the number representation of the
+signal listed below. Handling the signal is required in order to safely delete
+the `QSharedMemory` block. Each of these signals are potentially lethal and will
+results in process termination.
+
+* `SIGHUP` - `1`, Hangup.
+* `SIGINT` - `2`, Terminal interrupt signal
+* `SIGQUIT` - `3`, Terminal quit signal.
+* `SIGILL` - `4`, Illegal instruction.
+* `SIGABRT` - `6`, Process abort signal.
+* `SIGBUS` - `7`, Access to an undefined portion of a memory object.
+* `SIGFPE` - `8`, Erroneous arithmetic operation (such as division by zero).
+* `SIGSEGV` - `11`, Invalid memory reference.
+* `SIGSYS` - `12`, Bad system call.
+* `SIGPIPE` - `13`, Write on a pipe with no one to read it.
+* `SIGALRM` - `14`, Alarm clock.
+* `SIGTERM` - `15`, Termination signal.
+* `SIGXCPU` - `24`, CPU time limit exceeded.
+* `SIGXFSZ` - `25`, File size limit exceeded.
+
+Additionally the library can recover from being killed with uncatchable signals
+and will reset the memory block given that there are no other instances running.
+
+License
+-------
+This library and it's supporting documentation are released under
+`The MIT License (MIT)` with the exception of some of the examples distributed
+under the BSD license.
diff --git a/3rd-party/SingleApplication/Windows.md b/3rd-party/SingleApplication/Windows.md
new file mode 100644
index 0000000..48b0748
--- /dev/null
+++ b/3rd-party/SingleApplication/Windows.md
@@ -0,0 +1,46 @@
+Windows Specific Implementations
+================================
+
+Setting the foreground window
+-----------------------------
+
+In the `instanceStarted()` example in the `README` we demonstrated how an
+application can bring it's primary instance window whenever a second copy
+of the application is started.
+
+On Windows the ability to bring the application windows to the foreground is
+restricted, see [`AllowSetForegroundWindow()`][AllowSetForegroundWindow] for more
+details.
+
+The background process (the primary instance) can bring its windows to the
+foreground if it is allowed by the current foreground process (the secondary
+instance). To bypass this `SingleApplication` must be initialized with the
+`allowSecondary` parameter set to `true` and the `options` parameter must
+include `Mode::SecondaryNotification`, See `SingleApplication::Mode` for more
+details.
+
+Here is an example:
+
+```cpp
+if( app.isSecondary() ) {
+ // This API requires LIBS += User32.lib to be added to the project
+ AllowSetForegroundWindow( DWORD( app.getPrimaryPid() ) );
+}
+
+if( app.isPrimary() ) {
+ QObject::connect(
+ &app,
+ &SingleApplication::instanceStarted,
+ this,
+ &App::instanceStarted
+ );
+}
+```
+
+```cpp
+void App::instanceStarted() {
+ QApplication::setActiveWindow( [window/widget to set to the foreground] );
+}
+```
+
+[AllowSetForegroundWindow]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632668.aspx
diff --git a/3rd-party/SingleApplication/singleapplication.cpp b/3rd-party/SingleApplication/singleapplication.cpp
new file mode 100644
index 0000000..c74d6db
--- /dev/null
+++ b/3rd-party/SingleApplication/singleapplication.cpp
@@ -0,0 +1,468 @@
+// 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.
+
+#include <cstdlib>
+
+#include <QtCore/QDir>
+#include <QtCore/QProcess>
+#include <QtCore/QByteArray>
+#include <QtCore/QSemaphore>
+#include <QtCore/QSharedMemory>
+#include <QtCore/QStandardPaths>
+#include <QtCore/QCryptographicHash>
+#include <QtCore/QDataStream>
+#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"
+
+
+SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr ) : q_ptr( q_ptr ) {
+ server = nullptr;
+ socket = nullptr;
+}
+
+SingleApplicationPrivate::~SingleApplicationPrivate()
+{
+ if( socket != nullptr ) {
+ socket->close();
+ delete socket;
+ }
+
+ memory->lock();
+ InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
+ if( server != nullptr ) {
+ server->close();
+ delete server;
+ inst->primary = false;
+ inst->primaryPid = -1;
+ }
+ memory->unlock();
+
+ delete memory;
+}
+
+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::organizationName().toUtf8() );
+ appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
+
+ if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ) {
+ appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
+ }
+
+ if( ! (options & SingleApplication::Mode::ExcludeAppPath) ) {
+#ifdef Q_OS_WIN
+ appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
+#else
+ appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
+#endif
+ }
+
+ // User level block requires a user specific data in the hash
+ if( options & SingleApplication::Mode::User ) {
+#ifdef Q_OS_WIN
+ Q_UNUSED(timeout);
+ wchar_t username [ UNLEN + 1 ];
+ // Specifies size of the buffer on input
+ DWORD usernameLength = UNLEN + 1;
+ if( GetUserNameW( username, &usernameLength ) ) {
+ appData.addData( QString::fromWCharArray(username).toUtf8() );
+ } else {
+ appData.addData( QStandardPaths::standardLocations( QStandardPaths::HomeLocation ).join("").toUtf8() );
+ }
+#endif
+#ifdef Q_OS_UNIX
+ QProcess process;
+ process.start( "whoami" );
+ if( process.waitForFinished( timeout ) &&
+ process.exitCode() == QProcess::NormalExit) {
+ appData.addData( process.readLine() );
+ } else {
+ appData.addData(
+ QDir(
+ QStandardPaths::standardLocations( QStandardPaths::HomeLocation ).first()
+ ).absolutePath().toUtf8()
+ );
+ }
+#endif
+ }
+
+ // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
+ // server naming requirements.
+ blockServerName = appData.result().toBase64().replace("/", "_");
+}
+
+void SingleApplicationPrivate::startPrimary( bool resetMemory )
+{
+ Q_Q(SingleApplication);
+
+#ifdef Q_OS_UNIX
+ // 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( blockServerName );
+ server = new QLocalServer();
+
+ // Restrict access to the socket according to the
+ // SingleApplication::Mode::User flag on User level or no restrictions
+ if( options & SingleApplication::Mode::User ) {
+ server->setSocketOptions( QLocalServer::UserAccessOption );
+ } else {
+ server->setSocketOptions( QLocalServer::WorldAccessOption );
+ }
+
+ server->listen( blockServerName );
+ QObject::connect(
+ server,
+ &QLocalServer::newConnection,
+ this,
+ &SingleApplicationPrivate::slotConnectionEstablished
+ );
+
+ // Reset the number of connections
+ memory->lock();
+ InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
+
+ if( resetMemory ) {
+ inst->secondary = 0;
+ }
+
+ inst->primary = true;
+ inst->primaryPid = q->applicationPid();
+
+ 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();
+#endif
+}
+
+void SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
+{
+ // Connect to the Local Server of the Primary Instance if not already
+ // connected.
+ if( socket == nullptr ) {
+ socket = new QLocalSocket();
+ }
+
+ // 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;
+ QByteArray initMsg;
+ QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
+ writeStream.setVersion(QDataStream::Qt_5_6);
+ writeStream << blockServerName.toLatin1();
+ writeStream << static_cast<quint8>(connectionType);
+ writeStream << instanceNumber;
+ quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
+ writeStream << checksum;
+
+ socket->write( initMsg );
+ socket->flush();
+ socket->waitForBytesWritten( msecs );
+ }
+}
+
+qint64 SingleApplicationPrivate::primaryPid()
+{
+ qint64 pid;
+
+ memory->lock();
+ InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
+ pid = inst->primaryPid;
+ memory->unlock();
+
+ return pid;
+}
+
+#ifdef Q_OS_UNIX
+ void SingleApplicationPrivate::crashHandler()
+ {
+ // Handle any further termination signals to ensure the
+ // QSharedMemory block is deleted even if the process crashes
+ signal( SIGHUP, SingleApplicationPrivate::terminate ); // 1
+ signal( SIGINT, SingleApplicationPrivate::terminate ); // 2
+ signal( SIGQUIT, SingleApplicationPrivate::terminate ); // 3
+ signal( SIGILL, SingleApplicationPrivate::terminate ); // 4
+ signal( SIGABRT, SingleApplicationPrivate::terminate ); // 6
+ signal( SIGFPE, SingleApplicationPrivate::terminate ); // 8
+ signal( SIGBUS, SingleApplicationPrivate::terminate ); // 10
+ signal( SIGSEGV, SingleApplicationPrivate::terminate ); // 11
+ signal( SIGSYS, SingleApplicationPrivate::terminate ); // 12
+ signal( SIGPIPE, SingleApplicationPrivate::terminate ); // 13
+ signal( SIGALRM, SingleApplicationPrivate::terminate ); // 14
+ signal( SIGTERM, SingleApplicationPrivate::terminate ); // 15
+ signal( SIGXCPU, SingleApplicationPrivate::terminate ); // 24
+ signal( SIGXFSZ, SingleApplicationPrivate::terminate ); // 25
+ }
+
+ void SingleApplicationPrivate::terminate( int signum )
+ {
+ delete ((SingleApplication*)QCoreApplication::instance())->d_ptr;
+ ::exit( 128 + signum );
+ }
+#endif
+
+/**
+ * @brief Executed when a connection has been made to the LocalServer
+ */
+void SingleApplicationPrivate::slotConnectionEstablished()
+{
+ Q_Q(SingleApplication);
+
+ QLocalSocket *nextConnSocket = server->nextPendingConnection();
+
+ quint32 instanceId = 0;
+ ConnectionType connectionType = InvalidConnection;
+ if( nextConnSocket->waitForReadyRead( 100 ) ) {
+ // read all data from message in same order/format as written
+ QByteArray msgBytes = nextConnSocket->read(nextConnSocket->bytesAvailable() - static_cast<qint64>(sizeof(quint16)));
+ QByteArray checksumBytes = nextConnSocket->read(sizeof(quint16));
+ QDataStream readStream(msgBytes);
+ readStream.setVersion(QDataStream::Qt_5_6);
+
+ // server name
+ QByteArray latin1Name;
+ readStream >> latin1Name;
+ // connectioon type
+ quint8 connType = InvalidConnection;
+ readStream >> connType;
+ connectionType = static_cast<ConnectionType>(connType);
+ // instance id
+ readStream >> instanceId;
+ // checksum
+ quint16 msgChecksum = 0;
+ QDataStream checksumStream(checksumBytes);
+ checksumStream.setVersion(QDataStream::Qt_5_6);
+ checksumStream >> msgChecksum;
+
+ const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length()));
+
+ if (readStream.status() != QDataStream::Ok || QLatin1String(latin1Name) != blockServerName || msgChecksum != actualChecksum) {
+ connectionType = InvalidConnection;
+ }
+ }
+
+ if( connectionType == InvalidConnection ) {
+ nextConnSocket->close();
+ delete nextConnSocket;
+ return;
+ }
+
+ QObject::connect(
+ nextConnSocket,
+ &QLocalSocket::aboutToClose,
+ this,
+ [nextConnSocket, instanceId, this]() {
+ Q_EMIT this->slotClientConnectionClosed( nextConnSocket, instanceId );
+ }
+ );
+
+ QObject::connect(
+ nextConnSocket,
+ &QLocalSocket::readyRead,
+ this,
+ [nextConnSocket, instanceId, this]() {
+ Q_EMIT this->slotDataAvailable( nextConnSocket, instanceId );
+ }
+ );
+
+ if( connectionType == NewInstance || (
+ connectionType == SecondaryInstance &&
+ options & SingleApplication::Mode::SecondaryNotification
+ )
+ ) {
+ Q_EMIT q->instanceStarted();
+ }
+
+ if( nextConnSocket->bytesAvailable() > 0 ) {
+ Q_EMIT this->slotDataAvailable( nextConnSocket, instanceId );
+ }
+}
+
+void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
+{
+ Q_Q(SingleApplication);
+ Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
+}
+
+void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
+{
+ if( closedSocket->bytesAvailable() > 0 )
+ Q_EMIT slotDataAvailable( closedSocket, instanceId );
+ closedSocket->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[], bool allowSecondary, Options options, int timeout )
+ : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
+{
+ Q_D(SingleApplication);
+
+ // 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( timeout );
+
+ // Guarantee thread safe behaviour with a shared memory block. Also by
+ // 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;
+#endif
+ d->memory = new QSharedMemory( d->blockServerName );
+
+ // Create a shared memory block
+ if( d->memory->create( sizeof( InstancesInfo ) ) ) {
+ d->startPrimary( true );
+ return;
+ } else {
+ // Attempt to attach to the memory segment
+ if( d->memory->attach() ) {
+ d->memory->lock();
+ InstancesInfo* inst = static_cast<InstancesInfo*>(d->memory->data());
+
+ if( ! inst->primary ) {
+ d->startPrimary( false );
+ d->memory->unlock();
+ return;
+ }
+
+ // Check if another instance can be started
+ if( allowSecondary ) {
+ inst->secondary += 1;
+ 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();
+}
+
+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, SingleApplicationPrivate::Reconnect );
+
+ d->socket->write( message );
+ bool dataWritten = d->socket->flush();
+ d->socket->waitForBytesWritten( timeout );
+ return dataWritten;
+}
diff --git a/3rd-party/SingleApplication/singleapplication.h b/3rd-party/SingleApplication/singleapplication.h
new file mode 100644
index 0000000..33a9898
--- /dev/null
+++ b/3rd-party/SingleApplication/singleapplication.h
@@ -0,0 +1,135 @@
+// 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.
+
+#ifndef SINGLE_APPLICATION_H
+#define SINGLE_APPLICATION_H
+
+#include <QtCore/QtGlobal>
+#include <QtNetwork/QLocalSocket>
+
+#ifndef QAPPLICATION_CLASS
+ #define QAPPLICATION_CLASS QCoreApplication
+#endif
+
+#include QT_STRINGIFY(QAPPLICATION_CLASS)
+
+class SingleApplicationPrivate;
+
+/**
+ * @brief The SingleApplication class handles multipe instances of the same
+ * Application
+ * @see QCoreApplication
+ */
+class SingleApplication : public QAPPLICATION_CLASS
+{
+ Q_OBJECT
+
+ typedef QAPPLICATION_CLASS app_t;
+
+public:
+ /**
+ * @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,
+ ExcludeAppVersion = 1 << 3,
+ ExcludeAppPath = 1 << 4
+ };
+ 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();
+
+ /**
+ * @brief Returns a unique identifier for the current instance
+ * @returns {qint32}
+ */
+ quint32 instanceId();
+
+ /**
+ * @brief Returns the process ID (PID) of the primary instance
+ * @returns {qint64}
+ */
+ qint64 primaryPid();
+
+ /**
+ * @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/3rd-party/SingleApplication/singleapplication_p.h b/3rd-party/SingleApplication/singleapplication_p.h
new file mode 100644
index 0000000..a990a53
--- /dev/null
+++ b/3rd-party/SingleApplication/singleapplication_p.h
@@ -0,0 +1,85 @@
+// 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;
+ qint64 primaryPid;
+};
+
+class SingleApplicationPrivate : public QObject {
+Q_OBJECT
+public:
+ enum ConnectionType : quint8 {
+ InvalidConnection = 0,
+ NewInstance = 1,
+ SecondaryInstance = 2,
+ Reconnect = 3
+ };
+ Q_DECLARE_PUBLIC(SingleApplication)
+
+ SingleApplicationPrivate( SingleApplication *q_ptr );
+ ~SingleApplicationPrivate();
+
+ void genBlockServerName( int msecs );
+ void startPrimary( bool resetMemory );
+ void startSecondary();
+ void connectToPrimary(int msecs, ConnectionType connectionType );
+ qint64 primaryPid();
+
+#ifdef Q_OS_UNIX
+ void crashHandler();
+ static void terminate( int signum );
+#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