SingleApplication: Share code between SingleApplication and SingleCoreApplication

This commit is contained in:
Jonas Kvinge 2023-03-04 19:31:04 +01:00
parent f9ca24598e
commit 4cb3bc4185
14 changed files with 216 additions and 1172 deletions

View File

@ -6,31 +6,7 @@ include(CheckFunctionExists)
check_function_exists(geteuid HAVE_GETEUID)
check_function_exists(getpwuid HAVE_GETPWUID)
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
qt_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
target_include_directories(singleapplication PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(singleapplication PRIVATE
${QtCore_LIBRARIES}
${QtWidgets_LIBRARIES}
${QtNetwork_LIBRARIES}
)
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
qt_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
target_include_directories(singlecoreapplication PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(singlecoreapplication PRIVATE
${QtCore_LIBRARIES}
${QtNetwork_LIBRARIES}
)
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h")
add_subdirectory(singleapplication)
add_subdirectory(singlecoreapplication)

View File

@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.7)
add_definitions(-DSINGLEAPPLICATION)
set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp)
set(HEADERS ../singleapplication_t.h ../singleapplication_p.h)
qt_wrap_cpp(MOC ${HEADERS})
add_library(singleapplication STATIC ${SOURCES} ${MOC})
target_include_directories(singleapplication PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_BINARY_DIR}/..
)
target_link_libraries(singleapplication PUBLIC
${QtCore_LIBRARIES}
${QtWidgets_LIBRARIES}
${QtNetwork_LIBRARIES}
)

View File

@ -0,0 +1,13 @@
#ifndef SINGLEAPPLICATION_H
#define SINGLEAPPLICATION_H
#ifdef SINGLEAPPLICATION
# error "SINGLEAPPLICATION already defined."
#endif
#define SINGLEAPPLICATION
#include "../singleapplication_t.h"
#undef SINGLEAPPLICATION_T_H
#undef SINGLEAPPLICATION
#endif // SINGLEAPPLICATION_H

View File

@ -68,17 +68,17 @@
# include <QDateTime>
#endif
#include "singleapplication.h"
#include "singleapplication_t.h"
#include "singleapplication_p.h"
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
SingleApplicationPrivateClass::SingleApplicationPrivateClass(SingleApplicationClass *ptr)
: q_ptr(ptr),
memory_(nullptr),
socket_(nullptr),
server_(nullptr),
instanceNumber_(-1) {}
SingleApplicationPrivate::~SingleApplicationPrivate() {
SingleApplicationPrivateClass::~SingleApplicationPrivateClass() {
if (socket_ != nullptr) {
socket_->close();
@ -105,7 +105,7 @@ SingleApplicationPrivate::~SingleApplicationPrivate() {
}
QString SingleApplicationPrivate::getUsername() {
QString SingleApplicationPrivateClass::getUsername() {
#ifdef Q_OS_UNIX
QString username;
@ -141,36 +141,36 @@ QString SingleApplicationPrivate::getUsername() {
}
void SingleApplicationPrivate::genBlockServerName() {
void SingleApplicationPrivateClass::genBlockServerName() {
QCryptographicHash appData(QCryptographicHash::Sha256);
appData.addData("SingleApplication");
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
appData.addData(SingleApplicationClass::applicationName().toUtf8());
appData.addData(SingleApplicationClass::organizationName().toUtf8());
appData.addData(SingleApplicationClass::organizationDomain().toUtf8());
if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppVersion)) {
appData.addData(SingleApplicationClass::applicationVersion().toUtf8());
}
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppPath)) {
#if defined(Q_OS_UNIX)
const QByteArray appImagePath = qgetenv("APPIMAGE");
if (appImagePath.isEmpty()) {
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
}
else {
appData.addData(appImagePath);
}
#elif defined(Q_OS_WIN)
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
appData.addData(SingleApplicationClass::applicationFilePath().toLower().toUtf8());
#else
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
#endif
}
// User level block requires a user specific data in the hash
if (options_ & SingleApplication::Mode::User) {
if (options_ & SingleApplicationClass::Mode::User) {
appData.addData(getUsername().toUtf8());
}
@ -179,7 +179,7 @@ void SingleApplicationPrivate::genBlockServerName() {
}
void SingleApplicationPrivate::initializeMemoryBlock() const {
void SingleApplicationPrivateClass::initializeMemoryBlock() const {
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
instance->primary = false;
@ -190,7 +190,7 @@ void SingleApplicationPrivate::initializeMemoryBlock() const {
}
void SingleApplicationPrivate::startPrimary() {
void SingleApplicationPrivateClass::startPrimary() {
// Reset the number of connections
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
@ -206,7 +206,7 @@ void SingleApplicationPrivate::startPrimary() {
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) {
if (options_ & SingleApplicationClass::Mode::User) {
server_->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
@ -214,11 +214,11 @@ void SingleApplicationPrivate::startPrimary() {
}
server_->listen(blockServerName_);
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivateClass::slotConnectionEstablished);
}
void SingleApplicationPrivate::startSecondary() {
void SingleApplicationPrivateClass::startSecondary() {
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
@ -228,7 +228,7 @@ void SingleApplicationPrivate::startSecondary() {
}
bool SingleApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) {
bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const ConnectionType connectionType) {
QElapsedTimer time;
time.start();
@ -282,11 +282,11 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
}
void SingleApplicationPrivate::writeAck(QLocalSocket *sock) {
void SingleApplicationPrivateClass::writeAck(QLocalSocket *sock) {
sock->putChar('\n');
}
bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
bool SingleApplicationPrivateClass::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
QElapsedTimer time;
time.start();
@ -306,7 +306,7 @@ bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QB
}
bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
bool SingleApplicationPrivateClass::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
socket_->write(msg);
socket_->flush();
@ -321,7 +321,7 @@ bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByt
}
quint16 SingleApplicationPrivate::blockChecksum() const {
quint16 SingleApplicationPrivateClass::blockChecksum() const {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
@ -333,7 +333,7 @@ quint16 SingleApplicationPrivate::blockChecksum() const {
}
qint64 SingleApplicationPrivate::primaryPid() const {
qint64 SingleApplicationPrivateClass::primaryPid() const {
memory_->lock();
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
@ -344,7 +344,7 @@ qint64 SingleApplicationPrivate::primaryPid() const {
}
QString SingleApplicationPrivate::primaryUser() const {
QString SingleApplicationPrivateClass::primaryUser() const {
memory_->lock();
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
@ -358,7 +358,7 @@ QString SingleApplicationPrivate::primaryUser() const {
/**
* @brief Executed when a connection has been made to the LocalServer
*/
void SingleApplicationPrivate::slotConnectionEstablished() {
void SingleApplicationPrivateClass::slotConnectionEstablished() {
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
connectionMap_.insert(nextConnSocket, ConnectionInfo());
@ -396,7 +396,7 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
}
void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::ConnectionStage nextStage) {
void SingleApplicationPrivateClass::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivateClass::ConnectionStage nextStage) {
if (!connectionMap_.contains(sock)) {
return;
@ -420,7 +420,7 @@ void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const Singl
}
bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
bool SingleApplicationPrivateClass::isFrameComplete(QLocalSocket *sock) {
if (!connectionMap_.contains(sock)) {
return false;
@ -431,9 +431,9 @@ bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
}
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
void SingleApplicationPrivateClass::readInitMessageBody(QLocalSocket *sock) {
Q_Q(SingleApplication);
Q_Q(SingleApplicationClass);
if (!isFrameComplete(sock)) {
return;
@ -478,7 +478,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
info.instanceId = instanceId;
info.stage = StageConnectedHeader;
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplicationClass::Mode::SecondaryNotification)) {
emit q->instanceStarted();
}
@ -486,9 +486,9 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
}
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
void SingleApplicationPrivateClass::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
Q_Q(SingleApplication);
Q_Q(SingleApplicationClass);
if (!isFrameComplete(dataSocket)) {
return;
@ -505,7 +505,7 @@ void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const
}
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
void SingleApplicationPrivateClass::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
if (closedSocket->bytesAvailable() > 0) {
slotDataAvailable(closedSocket, instanceId);
@ -513,7 +513,7 @@ void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSo
}
void SingleApplicationPrivate::randomSleep() {
void SingleApplicationPrivateClass::randomSleep() {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));

View File

@ -39,7 +39,7 @@
#include <QString>
#include <QHash>
#include "singleapplication.h"
#include "singleapplication_t.h"
class QLocalServer;
class QLocalSocket;
@ -60,7 +60,7 @@ struct ConnectionInfo {
quint8 stage;
};
class SingleApplicationPrivate : public QObject {
class SingleApplicationPrivateClass : public QObject {
Q_OBJECT
public:
@ -76,10 +76,10 @@ class SingleApplicationPrivate : public QObject {
StageConnectedHeader = 2,
StageConnectedBody = 3,
};
Q_DECLARE_PUBLIC(SingleApplication)
Q_DECLARE_PUBLIC(SingleApplicationClass)
explicit SingleApplicationPrivate(SingleApplication *ptr);
~SingleApplicationPrivate() override;
explicit SingleApplicationPrivateClass(SingleApplicationClass *ptr);
~SingleApplicationPrivateClass() override;
static QString getUsername();
void genBlockServerName();
@ -98,13 +98,13 @@ class SingleApplicationPrivate : public QObject {
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
static void randomSleep();
SingleApplication *q_ptr;
SingleApplicationClass *q_ptr;
QSharedMemory *memory_;
QLocalSocket *socket_;
QLocalServer *server_;
quint32 instanceNumber_;
QString blockServerName_;
SingleApplication::Options options_;
SingleApplicationClass::Options options_;
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
public slots:

View File

@ -35,7 +35,6 @@
#include <limits>
#include <QtGlobal>
#include <QApplication>
#include <QThread>
#include <QSharedMemory>
#include <QLocalSocket>
@ -46,7 +45,7 @@
# include <QNativeIpcKey>
#endif
#include "singleapplication.h"
#include "singleapplication_t.h"
#include "singleapplication_p.h"
/**
@ -57,11 +56,15 @@
* @param options Optional flags to toggle specific behaviour
* @param timeout Maximum time blocking functions are allowed during app load
*/
SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
: app_t(argc, argv),
d_ptr(new SingleApplicationPrivate(this)) {
SingleApplicationClass::SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
: ApplicationClass(argc, argv),
d_ptr(new SingleApplicationPrivateClass(this)) {
#if defined(SINGLEAPPLICATION)
Q_D(SingleApplication);
#elif defined(SINGLECOREAPPLICATION)
Q_D(SingleCoreApplication);
#endif
// Store the current mode of the program
d->options_ = options;
@ -70,7 +73,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSe
d->genBlockServerName();
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
SingleApplicationPrivate::randomSleep();
SingleApplicationPrivateClass::randomSleep();
#ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
@ -138,7 +141,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSe
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
qDebug() << d->memory_->errorString();
}
SingleApplicationPrivate::randomSleep();
SingleApplicationPrivateClass::randomSleep();
if (!d->memory_->lock()) {
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
abortSafely();
@ -158,7 +161,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSe
if (allowSecondary) {
d->startSecondary();
if (d->options_ & Mode::SecondaryNotification) {
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
d->connectToPrimary(timeout, SingleApplicationPrivateClass::SecondaryInstance);
}
if (!d->memory_->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
@ -172,33 +175,54 @@ SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSe
qDebug() << d->memory_->errorString();
}
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
d->connectToPrimary(timeout, SingleApplicationPrivateClass::NewInstance);
delete d;
}
SingleApplication::~SingleApplication() {
SingleApplicationClass::~SingleApplicationClass() {
#if defined(SINGLEAPPLICATION)
Q_D(SingleApplication);
#elif defined(SINGLECOREAPPLICATION)
Q_D(SingleCoreApplication);
#endif
delete d;
}
/**
* Checks if the current application instance is primary.
* @return Returns true if the instance is primary, false otherwise.
*/
bool SingleApplication::isPrimary() const {
bool SingleApplicationClass::isPrimary() const {
#if defined(SINGLEAPPLICATION)
Q_D(const SingleApplication);
#elif defined(SINGLECOREAPPLICATION)
Q_D(const SingleCoreApplication);
#endif
return d->server_ != nullptr;
}
/**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleApplication::isSecondary() const {
bool SingleApplicationClass::isSecondary() const {
#if defined(SINGLEAPPLICATION)
Q_D(const SingleApplication);
#elif defined(SINGLECOREAPPLICATION)
Q_D(const SingleCoreApplication);
#endif
return d->server_ == nullptr;
}
/**
@ -206,9 +230,16 @@ bool SingleApplication::isSecondary() const {
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleApplication::instanceId() const {
quint32 SingleApplicationClass::instanceId() const {
#if defined(SINGLEAPPLICATION)
Q_D(const SingleApplication);
#elif defined(SINGLECOREAPPLICATION)
Q_D(const SingleCoreApplication);
#endif
return d->instanceNumber_;
}
/**
@ -216,26 +247,40 @@ quint32 SingleApplication::instanceId() const {
* Especially useful when SingleApplication is coupled with OS. specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleApplication::primaryPid() const {
qint64 SingleApplicationClass::primaryPid() const {
#if defined(SINGLEAPPLICATION)
Q_D(const SingleApplication);
#elif defined(SINGLECOREAPPLICATION)
Q_D(const SingleCoreApplication);
#endif
return d->primaryPid();
}
/**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleApplication::primaryUser() const {
QString SingleApplicationClass::primaryUser() const {
#if defined(SINGLEAPPLICATION)
Q_D(const SingleApplication);
#elif defined(SINGLECOREAPPLICATION)
Q_D(const SingleCoreApplication);
#endif
return d->primaryUser();
}
/**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleApplication::currentUser() const {
return SingleApplicationPrivate::getUsername();
QString SingleApplicationClass::currentUser() const {
return SingleApplicationPrivateClass::getUsername();
}
/**
@ -244,28 +289,37 @@ QString SingleApplication::currentUser() const {
* @param timeout the maximum timeout in milliseconds for blocking functions.
* @return true if the message was sent successfully, false otherwise.
*/
bool SingleApplication::sendMessage(const QByteArray &message, const int timeout) {
bool SingleApplicationClass::sendMessage(const QByteArray &message, const int timeout) {
#if defined(SINGLEAPPLICATION)
Q_D(SingleApplication);
#elif defined(SINGLECOREAPPLICATION)
Q_D(SingleCoreApplication);
#endif
// Nobody to connect to
if (isPrimary()) return false;
// Make sure the socket is connected
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) {
if (!d->connectToPrimary(timeout, SingleApplicationPrivateClass::Reconnect)) {
return false;
}
return d->writeConfirmedMessage(timeout, message);
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleApplication::abortSafely() {
void SingleApplicationClass::abortSafely() {
#if defined(SINGLEAPPLICATION)
Q_D(SingleApplication);
#elif defined(SINGLECOREAPPLICATION)
Q_D(SingleCoreApplication);
#endif
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
delete d;

View File

@ -31,25 +31,41 @@
//
//
#ifndef SINGLEAPPLICATION_H
#define SINGLEAPPLICATION_H
#ifndef SINGLEAPPLICATION_T_H
#define SINGLEAPPLICATION_T_H
#include <QtGlobal>
#include <QApplication>
#undef ApplicationClass
#undef SingleApplicationClass
#undef SingleApplicationPrivateClass
#if defined(SINGLEAPPLICATION)
# include <QApplication>
# define ApplicationClass QApplication
# define SingleApplicationClass SingleApplication
# define SingleApplicationPrivateClass SingleApplicationPrivate
#elif defined(SINGLECOREAPPLICATION)
# include <QCoreApplication>
# define ApplicationClass QCoreApplication
# define SingleApplicationClass SingleCoreApplication
# define SingleApplicationPrivateClass SingleCoreApplicationPrivate
#else
# error "Define SINGLEAPPLICATION or SINGLECOREAPPLICATION."
#endif
#include <QFlags>
#include <QByteArray>
class SingleApplicationPrivate;
class SingleApplicationPrivateClass;
/**
* @brief The SingleApplication class handles multiple instances of the same Application
* @see QApplication
*/
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
class SingleApplicationClass : public ApplicationClass { // clazy:exclude=ctor-missing-parent-argument
Q_OBJECT
using app_t = QApplication;
public:
/**
* @brief Mode of operation of SingleApplication.
@ -89,8 +105,8 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
* initialisation will be completed in given time, though is a good hint.
* Usually 4*timeout would be the worst case (fail) scenario.
*/
explicit SingleApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
~SingleApplication() override;
explicit SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
~SingleApplicationClass() override;
/**
* @brief Returns if the instance is the primary instance
@ -142,11 +158,15 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
void receivedMessage(quint32 instanceId, QByteArray message);
private:
SingleApplicationPrivate *d_ptr;
SingleApplicationPrivateClass *d_ptr;
#if defined(SINGLEAPPLICATION)
Q_DECLARE_PRIVATE(SingleApplication)
#elif defined(SINGLECOREAPPLICATION)
Q_DECLARE_PRIVATE(SingleCoreApplication)
#endif
void abortSafely();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplicationClass::Options)
#endif // SINGLEAPPLICATION_H
#endif // SINGLEAPPLICATION_T_H

View File

@ -1,275 +0,0 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This is a modified version of SingleApplication,
// The original version is at:
//
// https://github.com/itay-grudev/SingleApplication
//
//
#include <cstdlib>
#include <limits>
#include <QtGlobal>
#include <QCoreApplication>
#include <QThread>
#include <QSharedMemory>
#include <QLocalSocket>
#include <QByteArray>
#include <QElapsedTimer>
#include <QtDebug>
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
# include <QNativeIpcKey>
#endif
#include "singlecoreapplication.h"
#include "singlecoreapplication_p.h"
/**
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
* @param argc
* @param argv
* @param allowSecondary Whether to enable secondary instance support
* @param options Optional flags to toggle specific behaviour
* @param timeout Maximum time blocking functions are allowed during app load
*/
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
: app_t(argc, argv),
d_ptr(new SingleCoreApplicationPrivate(this)) {
Q_D(SingleCoreApplication);
// 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();
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
SingleCoreApplicationPrivate::randomSleep();
#ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_));
#else
d->memory_ = new QSharedMemory(d->blockServerName_);
#endif
d->memory_->attach();
delete d->memory_;
#endif
// Guarantee thread safe behaviour with a shared memory block.
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_));
#else
d->memory_ = new QSharedMemory(d->blockServerName_);
#endif
// Create a shared memory block
if (d->memory_->create(sizeof(InstancesInfo))) {
// Initialize the shared memory block
if (!d->memory_->lock()) {
qCritical() << "SingleCoreApplication: Unable to lock memory block after create.";
abortSafely();
}
d->initializeMemoryBlock();
}
else {
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
// Attempt to attach to the memory segment
if (!d->memory_->attach()) {
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
abortSafely();
}
if (!d->memory_->lock()) {
qCritical() << "SingleCoreApplication: Unable to lock memory block after attach.";
abortSafely();
}
}
else {
qCritical() << "SingleCoreApplication: Unable to create block.";
abortSafely();
}
}
InstancesInfo *instance = static_cast<InstancesInfo*>(d->memory_->data());
QElapsedTimer time;
time.start();
// Make sure the shared memory block is initialised and in consistent state
forever {
// If the shared memory block's checksum is valid continue
if (d->blockChecksum() == instance->checksum) break;
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
if (time.elapsed() > 5000) {
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
d->initializeMemoryBlock();
}
// Otherwise wait for a random period and try again.
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
qDebug() << d->memory_->errorString();
}
SingleCoreApplicationPrivate::randomSleep();
if (!d->memory_->lock()) {
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
abortSafely();
}
}
if (!instance->primary) {
d->startPrimary();
if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
qDebug() << d->memory_->errorString();
}
return;
}
// Check if another instance can be started
if (allowSecondary) {
d->startSecondary();
if (d->options_ & Mode::SecondaryNotification) {
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
}
if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start.";
qDebug() << d->memory_->errorString();
}
return;
}
if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution.";
qDebug() << d->memory_->errorString();
}
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
delete d;
}
SingleCoreApplication::~SingleCoreApplication() {
Q_D(SingleCoreApplication);
delete d;
}
/**
* Checks if the current application instance is primary.
* @return Returns true if the instance is primary, false otherwise.
*/
bool SingleCoreApplication::isPrimary() const {
Q_D(const SingleCoreApplication);
return d->server_ != nullptr;
}
/**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleCoreApplication::isSecondary() const {
Q_D(const SingleCoreApplication);
return d->server_ == nullptr;
}
/**
* Allows you to identify an instance by returning unique consecutive instance ids.
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleCoreApplication::instanceId() const {
Q_D(const SingleCoreApplication);
return d->instanceNumber_;
}
/**
* Returns the OS PID (Process Identifier) of the process running the primary instance.
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleCoreApplication::primaryPid() const {
Q_D(const SingleCoreApplication);
return d->primaryPid();
}
/**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleCoreApplication::primaryUser() const {
Q_D(const SingleCoreApplication);
return d->primaryUser();
}
/**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleCoreApplication::currentUser() const {
return SingleCoreApplicationPrivate::getUsername();
}
/**
* Sends message to the Primary Instance.
* @param message The message to send.
* @param timeout the maximum timeout in milliseconds for blocking functions.
* @return true if the message was sent successfully, false otherwise.
*/
bool SingleCoreApplication::sendMessage(const QByteArray &message, const int timeout) {
Q_D(SingleCoreApplication);
// Nobody to connect to
if (isPrimary()) return false;
// Make sure the socket is connected
if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect)) {
return false;
}
return d->writeConfirmedMessage(timeout, message);
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleCoreApplication::abortSafely() {
Q_D(SingleCoreApplication);
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
delete d;
::exit(EXIT_FAILURE);
}

View File

@ -1,152 +0,0 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This is a modified version of SingleApplication,
// The original version is at:
//
// https://github.com/itay-grudev/SingleApplication
//
//
#ifndef SINGLECOREAPPLICATION_H
#define SINGLECOREAPPLICATION_H
#include <QtGlobal>
#include <QCoreApplication>
#include <QFlags>
#include <QByteArray>
class SingleCoreApplicationPrivate;
/**
* @brief The SingleCoreApplication class handles multiple instances of the same Application
* @see QCoreApplication
*/
class SingleCoreApplication : public QCoreApplication { // clazy:exclude=ctor-missing-parent-argument
Q_OBJECT
using app_t = QCoreApplication;
public:
/**
* @brief Mode of operation of SingleCoreApplication.
* 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 class Mode {
User = 1 << 0,
System = 1 << 1,
SecondaryNotification = 1 << 2,
ExcludeAppVersion = 1 << 3,
ExcludeAppPath = 1 << 4
};
Q_DECLARE_FLAGS(Options, Mode)
/**
* @brief Intitializes a SingleCoreApplication 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 SingleCoreApplication block to be applied
* User wide or System wide.
* @arg {int} timeout - Timeout to wait in milliseconds.
* @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 SingleCoreApplication
* initialisation will be completed in given time, though is a good hint.
* Usually 4*timeout would be the worst case (fail) scenario.
*/
explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
~SingleCoreApplication() override;
/**
* @brief Returns if the instance is the primary instance
* @returns {bool}
*/
bool isPrimary() const;
/**
* @brief Returns if the instance is a secondary instance
* @returns {bool}
*/
bool isSecondary() const;
/**
* @brief Returns a unique identifier for the current instance
* @returns {qint32}
*/
quint32 instanceId() const;
/**
* @brief Returns the process ID (PID) of the primary instance
* @returns {qint64}
*/
qint64 primaryPid() const;
/**
* @brief Returns the username of the user running the primary instance
* @returns {QString}
*/
QString primaryUser() const;
/**
* @brief Returns the username of the current user
* @returns {QString}
*/
QString currentUser() const;
/**
* @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(const QByteArray &message, const int timeout = 1000);
signals:
void instanceStarted();
void receivedMessage(quint32 instanceId, QByteArray message);
private:
SingleCoreApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleCoreApplication)
void abortSafely();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
#endif // SINGLECOREAPPLICATION_H

View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.7)
add_definitions(-DSINGLECOREAPPLICATION)
set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp)
set(HEADERS ../singleapplication_t.h ../singleapplication_p.h)
qt_wrap_cpp(MOC ${HEADERS})
add_library(singlecoreapplication STATIC ${SOURCES} ${MOC})
target_include_directories(singlecoreapplication PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_BINARY_DIR}/..
)
target_link_libraries(singlecoreapplication PUBLIC
${QtCore_LIBRARIES}
${QtNetwork_LIBRARIES}
)

View File

@ -0,0 +1,13 @@
#ifndef SINGLECOREAPPLICATION_H
#define SINGLECOREAPPLICATION_H
#ifdef SINGLECOREAPPLICATION
# error "SINGLECOREAPPLICATION already defined."
#endif
#define SINGLECOREAPPLICATION
#include "../singleapplication_t.h"
#undef SINGLEAPPLICATION_T_H
#undef SINGLECOREAPPLICATION
#endif // SINGLECOREAPPLICATION_H

View File

@ -1,525 +0,0 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This is a modified version of SingleApplication,
// The original version is at:
//
// https://github.com/itay-grudev/SingleApplication
//
//
#include "config.h"
#include <QtGlobal>
#include <cstdlib>
#include <cstddef>
#ifdef Q_OS_UNIX
# include <unistd.h>
# include <sys/types.h>
# include <pwd.h>
#endif
#ifdef Q_OS_WIN
# ifndef NOMINMAX
# define NOMINMAX 1
# endif
# include <windows.h>
# include <lmcons.h>
#endif
#include <QObject>
#include <QThread>
#include <QIODevice>
#include <QSharedMemory>
#include <QByteArray>
#include <QDataStream>
#include <QCryptographicHash>
#include <QLocalServer>
#include <QLocalSocket>
#include <QElapsedTimer>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#else
# include <QDateTime>
#endif
#include "singlecoreapplication.h"
#include "singlecoreapplication_p.h"
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
: q_ptr(ptr),
memory_(nullptr),
socket_(nullptr),
server_(nullptr),
instanceNumber_(-1) {}
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
if (socket_ != nullptr) {
socket_->close();
delete socket_;
socket_ = nullptr;
}
if (memory_ != nullptr) {
memory_->lock();
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
if (server_ != nullptr) {
server_->close();
delete server_;
instance->primary = false;
instance->primaryPid = -1;
instance->primaryUser[0] = '\0';
instance->checksum = blockChecksum();
}
memory_->unlock();
delete memory_;
memory_ = nullptr;
}
}
QString SingleCoreApplicationPrivate::getUsername() {
#ifdef Q_OS_UNIX
QString username;
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
struct passwd *pw = getpwuid(geteuid());
if (pw) {
username = QString::fromLocal8Bit(pw->pw_name);
}
#endif
if (username.isEmpty()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
username = qEnvironmentVariable("USER");
#else
username = QString::fromLocal8Bit(qgetenv("USER"));
#endif
}
return username;
#endif
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) {
return QString::fromWCharArray(username);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
return qEnvironmentVariable("USERNAME");
#else
return QString::fromLocal8Bit(qgetenv("USERNAME"));
#endif
#endif
}
void SingleCoreApplicationPrivate::genBlockServerName() {
QCryptographicHash appData(QCryptographicHash::Sha256);
appData.addData("SingleApplication");
appData.addData(SingleCoreApplication::app_t::applicationName().toUtf8());
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) {
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
}
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
#if defined(Q_OS_UNIX)
const QByteArray appImagePath = qgetenv("APPIMAGE");
if (appImagePath.isEmpty()) {
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
}
else {
appData.addData(appImagePath);
}
#elif defined(Q_OS_WIN)
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
#else
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
#endif
}
// User level block requires a user specific data in the hash
if (options_ & SingleCoreApplication::Mode::User) {
appData.addData(getUsername().toUtf8());
}
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
blockServerName_ = appData.result().toBase64().replace("/", "_");
}
void SingleCoreApplicationPrivate::initializeMemoryBlock() const {
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
instance->primary = false;
instance->secondary = 0;
instance->primaryPid = -1;
instance->primaryUser[0] = '\0';
instance->checksum = blockChecksum();
}
void SingleCoreApplicationPrivate::startPrimary() {
// Reset the number of connections
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
instance->primary = true;
instance->primaryPid = QCoreApplication::applicationPid();
qstrncpy(instance->primaryUser, getUsername().toUtf8().data(), sizeof(instance->primaryUser));
instance->checksum = blockChecksum();
instanceNumber_ = 0;
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName_);
server_ = new QLocalServer();
// Restrict access to the socket according to the SingleCoreApplication::Mode::User flag on User level or no restrictions
if (options_ & SingleCoreApplication::Mode::User) {
server_->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
server_->setSocketOptions(QLocalServer::WorldAccessOption);
}
server_->listen(blockServerName_);
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
}
void SingleCoreApplicationPrivate::startSecondary() {
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
instance->secondary += 1;
instance->checksum = blockChecksum();
instanceNumber_ = instance->secondary;
}
bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) {
QElapsedTimer time;
time.start();
// Connect to the Local Server of the Primary Instance if not already connected.
if (socket_ == nullptr) {
socket_ = new QLocalSocket();
}
if (socket_->state() == QLocalSocket::ConnectedState) return true;
if (socket_->state() != QLocalSocket::ConnectedState) {
forever {
randomSleep();
if (socket_->state() != QLocalSocket::ConnectingState) {
socket_->connectToServer(blockServerName_);
}
if (socket_->state() == QLocalSocket::ConnectingState) {
socket_->waitForConnected(static_cast<int>(timeout - time.elapsed()));
}
// If connected break out of the loop
if (socket_->state() == QLocalSocket::ConnectedState) break;
// If elapsed time since start is longer than the method timeout return
if (time.elapsed() >= timeout) return false;
}
}
// Initialisation message according to the SingleCoreApplication protocol
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
writeStream.setVersion(QDataStream::Qt_5_8);
writeStream << blockServerName_.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber_;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
#else
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
#endif
writeStream << checksum;
return writeConfirmedMessage(static_cast<int>(timeout - time.elapsed()), initMsg);
}
void SingleCoreApplicationPrivate::writeAck(QLocalSocket *sock) {
sock->putChar('\n');
}
bool SingleCoreApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
QElapsedTimer time;
time.start();
// Frame 1: The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
headerStream.setVersion(QDataStream::Qt_5_8);
headerStream << static_cast<quint64>(msg.length());
if (!writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), header)) {
return false;
}
// Frame 2: The message
return writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), msg);
}
bool SingleCoreApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
socket_->write(msg);
socket_->flush();
bool result = socket_->waitForReadyRead(timeout);
if (result) {
socket_->read(1);
return true;
}
return false;
}
quint16 SingleCoreApplicationPrivate::blockChecksum() const {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
#else
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
#endif
return checksum;
}
qint64 SingleCoreApplicationPrivate::primaryPid() const {
memory_->lock();
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
qint64 pid = instance->primaryPid;
memory_->unlock();
return pid;
}
QString SingleCoreApplicationPrivate::primaryUser() const {
memory_->lock();
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
QByteArray username = instance->primaryUser;
memory_->unlock();
return QString::fromUtf8(username);
}
/**
* @brief Executed when a connection has been made to the LocalServer
*/
void SingleCoreApplicationPrivate::slotConnectionEstablished() {
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
connectionMap_.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
const ConnectionInfo &info = connectionMap_[nextConnSocket];
slotClientConnectionClosed(nextConnSocket, info.instanceId);
});
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
connectionMap_.remove(nextConnSocket);
});
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
const ConnectionInfo &info = connectionMap_[nextConnSocket];
switch (info.stage) {
case StageInitHeader:
readMessageHeader(nextConnSocket, StageInitBody);
break;
case StageInitBody:
readInitMessageBody(nextConnSocket);
break;
case StageConnectedHeader:
readMessageHeader(nextConnSocket, StageConnectedBody);
break;
case StageConnectedBody:
this->slotDataAvailable(nextConnSocket, info.instanceId);
break;
default:
break;
}
});
}
void SingleCoreApplicationPrivate::readMessageHeader(QLocalSocket *sock, SingleCoreApplicationPrivate::ConnectionStage nextStage) {
if (!connectionMap_.contains(sock)) {
return;
}
if (sock->bytesAvailable() < static_cast<qint64>(sizeof(quint64))) {
return;
}
QDataStream headerStream(sock);
headerStream.setVersion(QDataStream::Qt_5_8);
// Read the header to know the message length
quint64 msgLen = 0;
headerStream >> msgLen;
ConnectionInfo &info = connectionMap_[sock];
info.stage = nextStage;
info.msgLen = msgLen;
writeAck(sock);
}
bool SingleCoreApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
if (!connectionMap_.contains(sock)) {
return false;
}
ConnectionInfo &info = connectionMap_[sock];
return (sock->bytesAvailable() >= static_cast<qint64>(info.msgLen));
}
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
Q_Q(SingleCoreApplication);
if (!isFrameComplete(sock)) {
return;
}
// Read the message body
QByteArray msgBytes = sock->readAll();
QDataStream readStream(msgBytes);
readStream.setVersion(QDataStream::Qt_5_8);
// server name
QByteArray latin1Name;
readStream >> latin1Name;
// connection type
quint8 connTypeVal = InvalidConnection;
readStream >> connTypeVal;
const ConnectionType connectionType = static_cast<ConnectionType>(connTypeVal);
// instance id
quint32 instanceId = 0;
readStream >> instanceId;
// checksum
quint16 msgChecksum = 0;
readStream >> msgChecksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
#else
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
#endif
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
if (!isValid) {
sock->close();
return;
}
ConnectionInfo &info = connectionMap_[sock];
info.instanceId = instanceId;
info.stage = StageConnectedHeader;
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
emit q->instanceStarted();
}
writeAck(sock);
}
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
Q_Q(SingleCoreApplication);
if (!isFrameComplete(dataSocket)) {
return;
}
const QByteArray message = dataSocket->readAll();
writeAck(dataSocket);
ConnectionInfo &info = connectionMap_[dataSocket];
info.stage = StageConnectedHeader;
emit q->receivedMessage(instanceId, message);
}
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
if (closedSocket->bytesAvailable() > 0) {
slotDataAvailable(closedSocket, instanceId);
}
}
void SingleCoreApplicationPrivate::randomSleep() {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
#else
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
QThread::msleep(qrand() % 11 + 8);
#endif
}

View File

@ -1,116 +0,0 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This is a modified version of SingleApplication,
// The original version is at:
//
// https://github.com/itay-grudev/SingleApplication
//
//
#ifndef SINGLECOREAPPLICATION_P_H
#define SINGLECOREAPPLICATION_P_H
#include <QtGlobal>
#include <QObject>
#include <QString>
#include <QHash>
#include "singlecoreapplication.h"
class QLocalServer;
class QLocalSocket;
class QSharedMemory;
struct InstancesInfo {
bool primary;
quint32 secondary;
qint64 primaryPid;
char primaryUser[128];
quint16 checksum;
};
struct ConnectionInfo {
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
quint64 msgLen;
quint32 instanceId;
quint8 stage;
};
class SingleCoreApplicationPrivate : public QObject {
Q_OBJECT
public:
enum ConnectionType : quint8 {
InvalidConnection = 0,
NewInstance = 1,
SecondaryInstance = 2,
Reconnect = 3
};
enum ConnectionStage : quint8 {
StageInitHeader = 0,
StageInitBody = 1,
StageConnectedHeader = 2,
StageConnectedBody = 3,
};
Q_DECLARE_PUBLIC(SingleCoreApplication)
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
~SingleCoreApplicationPrivate() override;
static QString getUsername();
void genBlockServerName();
void initializeMemoryBlock() const;
void startPrimary();
void startSecondary();
bool connectToPrimary(const int timeout, const ConnectionType connectionType);
quint16 blockChecksum() const;
qint64 primaryPid() const;
QString primaryUser() const;
bool isFrameComplete(QLocalSocket *sock);
void readMessageHeader(QLocalSocket *socket, const ConnectionStage nextStage);
void readInitMessageBody(QLocalSocket *socket);
void writeAck(QLocalSocket *sock);
bool writeConfirmedFrame(const int timeout, const QByteArray &msg) const;
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
static void randomSleep();
SingleCoreApplication *q_ptr;
QSharedMemory *memory_;
QLocalSocket *socket_;
QLocalServer *server_;
quint32 instanceNumber_;
QString blockServerName_;
SingleCoreApplication::Options options_;
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
public slots:
void slotConnectionEstablished();
void slotDataAvailable(QLocalSocket*, const quint32);
void slotClientConnectionClosed(QLocalSocket*, const quint32);
};
#endif // SINGLECOREAPPLICATION_P_H

View File

@ -309,7 +309,10 @@ endif()
# SingleApplication
add_subdirectory(3rdparty/singleapplication)
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication)
set(SINGLEAPPLICATION_INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singleapplication
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singlecoreapplication
)
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)