Replace SingleApplication with KDSingleApplication
This commit is contained in:
parent
b861703dad
commit
919ff414e6
|
@ -1,15 +1,13 @@
|
|||
3rdparty libraries located in this directory
|
||||
============================================
|
||||
|
||||
singleapplication
|
||||
KDSingleApplication
|
||||
-----------------
|
||||
This is a small static library used by Strawberry to prevent it from starting twice per user session.
|
||||
If the user tries to start strawberry twice, the main window will maximize instead of starting another instance.
|
||||
If you dynamically link to your systems version, you'll need two versions, one defined as QApplication and
|
||||
one as a QCoreApplication.
|
||||
It is included here because it is not packed by distros and is also used on macOS and Windows.
|
||||
It is also used to pass command-line options through to the first instance.
|
||||
|
||||
URL: https://github.com/itay-grudev/SingleApplication
|
||||
URL: https://github.com/KDAB/KDSingleApplication/
|
||||
|
||||
|
||||
SPMediaKeyTap
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
cmake_minimum_required(VERSION 3.7)
|
||||
set(SOURCES kdsingleapplication.cpp kdsingleapplication_localsocket.cpp)
|
||||
set(HEADERS kdsingleapplication.h kdsingleapplication_localsocket_p.h)
|
||||
qt_wrap_cpp(MOC ${HEADERS})
|
||||
add_library(kdsingleapplication STATIC ${SOURCES} ${MOC})
|
||||
target_compile_definitions(kdsingleapplication PRIVATE -DKDSINGLEAPPLICATION_STATIC_BUILD)
|
||||
target_include_directories(kdsingleapplication PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(kdsingleapplication PUBLIC ${QtCore_LIBRARIES} ${QtNetwork_LIBRARIES} )
|
|
@ -0,0 +1,6 @@
|
|||
KDSingleApplication is (C) 2019-2023, Klarälvdalens Datakonsult AB,
|
||||
and is available under the terms of the MIT license.
|
||||
|
||||
See the full license text in the LICENSES folder.
|
||||
|
||||
Contact KDAB at <info@kdab.com> to inquire about commercial licensing.
|
|
@ -0,0 +1,53 @@
|
|||
# KDSingleApplication
|
||||
|
||||
`KDSingleApplication` is a helper class for single-instance policy applications
|
||||
written by [KDAB](https://www.kdab.com).
|
||||
|
||||
## Usage
|
||||
|
||||
Currently the documentation is woefully lacking, but see the examples or tests
|
||||
for inspiration. Basically it involves:
|
||||
|
||||
1. Create a `Q(Core|Gui)Application` object.
|
||||
2. Create a `KDSingleApplication` object.
|
||||
3. Check if the current instance is *primary* (or "master") or
|
||||
*secondary* (or "slave") by calling `isPrimaryInstance`:
|
||||
* the *primary* instance needs to listen from messages coming from the
|
||||
secondary instances, by connecting a slot to the `messageReceived` signal;
|
||||
* the *secondary* instances can send messages to the primary instance
|
||||
by calling `sendMessage`.
|
||||
|
||||
## Licensing
|
||||
|
||||
KDSingleApplication is (C) 2019-2023, Klarälvdalens Datakonsult AB, and is available
|
||||
under the terms of the [MIT license](LICENSES/MIT.txt).
|
||||
|
||||
Contact KDAB at <info@kdab.com> if you need different licensing options.
|
||||
|
||||
## Get Involved
|
||||
|
||||
KDAB will happily accept external contributions.
|
||||
|
||||
Please submit your contributions or issue reports from our GitHub space at
|
||||
<https://github.com/KDAB/KDSingleApplication>.
|
||||
|
||||
## About KDAB
|
||||
|
||||
KDSingleApplication is supported and maintained by Klarälvdalens Datakonsult AB (KDAB).
|
||||
|
||||
The KDAB Group is the global No.1 software consultancy for Qt, C++ and
|
||||
OpenGL applications across desktop, embedded and mobile platforms.
|
||||
|
||||
The KDAB Group provides consulting and mentoring for developing Qt applications
|
||||
from scratch and in porting from all popular and legacy frameworks to Qt.
|
||||
We continue to help develop parts of Qt and are one of the major contributors
|
||||
to the Qt Project. We can give advanced or standard trainings anywhere
|
||||
around the globe on Qt as well as C++, OpenGL, 3D and more.
|
||||
|
||||
Please visit <https://www.kdab.com> to meet the people who write code like this.
|
||||
|
||||
Stay up-to-date with KDAB product announcements:
|
||||
|
||||
* [KDAB Newsletter](https://news.kdab.com)
|
||||
* [KDAB Blogs](https://www.kdab.com/category/blogs)
|
||||
* [KDAB on Twitter](https://twitter.com/KDABQt)
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#include "kdsingleapplication.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QFileInfo>
|
||||
|
||||
// TODO: make this pluggable.
|
||||
#include "kdsingleapplication_localsocket_p.h"
|
||||
|
||||
// Avoiding dragging in Qt private APIs for now, so this does not inherit
|
||||
// from QObjectPrivate.
|
||||
class KDSingleApplicationPrivate
|
||||
{
|
||||
public:
|
||||
explicit KDSingleApplicationPrivate(const QString &name, KDSingleApplication *q);
|
||||
|
||||
void initialize();
|
||||
|
||||
QString name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
bool isPrimaryInstance() const
|
||||
{
|
||||
return m_impl.isPrimaryInstance();
|
||||
}
|
||||
|
||||
bool sendMessage(const QByteArray &message, int timeout)
|
||||
{
|
||||
return m_impl.sendMessage(message, timeout);
|
||||
}
|
||||
|
||||
private:
|
||||
Q_DECLARE_PUBLIC(KDSingleApplication)
|
||||
|
||||
KDSingleApplication *q_ptr;
|
||||
QString m_name;
|
||||
|
||||
KDSingleApplicationLocalSocket m_impl;
|
||||
};
|
||||
|
||||
KDSingleApplicationPrivate::KDSingleApplicationPrivate(const QString &name, KDSingleApplication *q)
|
||||
: q_ptr(q)
|
||||
, m_name(name)
|
||||
, m_impl(name)
|
||||
{
|
||||
if (Q_UNLIKELY(name.isEmpty()))
|
||||
qFatal("KDSingleApplication requires a non-empty application name");
|
||||
|
||||
if (isPrimaryInstance()) {
|
||||
QObject::connect(&m_impl, &KDSingleApplicationLocalSocket::messageReceived,
|
||||
q, &KDSingleApplication::messageReceived);
|
||||
}
|
||||
}
|
||||
|
||||
static QString extractExecutableName(const QString &applicationFilePath)
|
||||
{
|
||||
return QFileInfo(applicationFilePath).fileName();
|
||||
}
|
||||
|
||||
KDSingleApplication::KDSingleApplication(QObject *parent)
|
||||
: KDSingleApplication(extractExecutableName(QCoreApplication::applicationFilePath()), parent)
|
||||
{
|
||||
}
|
||||
|
||||
KDSingleApplication::KDSingleApplication(const QString &name, QObject *parent)
|
||||
: QObject(parent)
|
||||
, d_ptr(new KDSingleApplicationPrivate(name, this))
|
||||
{
|
||||
}
|
||||
|
||||
QString KDSingleApplication::name() const
|
||||
{
|
||||
Q_D(const KDSingleApplication);
|
||||
return d->name();
|
||||
}
|
||||
|
||||
bool KDSingleApplication::isPrimaryInstance() const
|
||||
{
|
||||
Q_D(const KDSingleApplication);
|
||||
return d->isPrimaryInstance();
|
||||
}
|
||||
|
||||
bool KDSingleApplication::sendMessage(const QByteArray &message)
|
||||
{
|
||||
return sendMessageWithTimeout(message, 5000);
|
||||
}
|
||||
|
||||
bool KDSingleApplication::sendMessageWithTimeout(const QByteArray &message, int timeout)
|
||||
{
|
||||
Q_ASSERT(!isPrimaryInstance());
|
||||
|
||||
Q_D(KDSingleApplication);
|
||||
return d->sendMessage(message, timeout);
|
||||
}
|
||||
|
||||
|
||||
KDSingleApplication::~KDSingleApplication() = default;
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
#ifndef KDSINGLEAPPLICATION_H
|
||||
#define KDSINGLEAPPLICATION_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "kdsingleapplication_lib.h"
|
||||
|
||||
class KDSingleApplicationPrivate;
|
||||
|
||||
class KDSINGLEAPPLICATION_EXPORT KDSingleApplication : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString name READ name CONSTANT)
|
||||
Q_PROPERTY(bool isPrimaryInstance READ isPrimaryInstance CONSTANT)
|
||||
|
||||
public:
|
||||
explicit KDSingleApplication(QObject *parent = nullptr);
|
||||
explicit KDSingleApplication(const QString &name, QObject *parent = nullptr);
|
||||
~KDSingleApplication();
|
||||
|
||||
QString name() const;
|
||||
bool isPrimaryInstance() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
// avoid default arguments and overloads, as they don't mix with connections
|
||||
bool sendMessage(const QByteArray &message);
|
||||
bool sendMessageWithTimeout(const QByteArray &message, int timeout);
|
||||
|
||||
Q_SIGNALS:
|
||||
void messageReceived(const QByteArray &message);
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(KDSingleApplication)
|
||||
std::unique_ptr<KDSingleApplicationPrivate> d_ptr;
|
||||
};
|
||||
|
||||
#endif // KDSINGLEAPPLICATION_H
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
#ifndef KDSINGLEAPPLICATION_LIB_H
|
||||
#define KDSINGLEAPPLICATION_LIB_H
|
||||
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
#if defined(KDSINGLEAPPLICATION_STATIC_BUILD)
|
||||
#define KDSINGLEAPPLICATION_EXPORT
|
||||
#elif defined(KDSINGLEAPPLICATION_SHARED_BUILD)
|
||||
#define KDSINGLEAPPLICATION_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define KDSINGLEAPPLICATION_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
|
||||
#endif // KDSINGLEAPPLICATION_LIB_H
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#include "kdsingleapplication_localsocket_p.h"
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QDeadlineTimer>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QLockFile>
|
||||
#include <QtCore/QDataStream>
|
||||
|
||||
#include <QtCore/QtDebug>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
// for ::getuid()
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <qt_windows.h>
|
||||
#endif
|
||||
|
||||
static const auto LOCALSOCKET_CONNECTION_TIMEOUT = std::chrono::seconds(5);
|
||||
static const char LOCALSOCKET_PROTOCOL_VERSION = 2;
|
||||
|
||||
Q_LOGGING_CATEGORY(kdsaLocalSocket, "kdsingleapplication.localsocket", QtWarningMsg);
|
||||
|
||||
KDSingleApplicationLocalSocket::KDSingleApplicationLocalSocket(const QString &name, QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
#if defined(Q_OS_UNIX)
|
||||
/* cppcheck-suppress useInitializationList */
|
||||
m_socketName = QStringLiteral("kdsingleapp-%1-%2-%3")
|
||||
.arg(::getuid())
|
||||
.arg(qEnvironmentVariable("XDG_SESSION_ID"), name);
|
||||
#elif defined(Q_OS_WIN)
|
||||
// I'm not sure of a "global session identifier" on Windows; are
|
||||
// multiple logins from the same user a possibility? For now, following this:
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/devnotes/getting-the-session-id-of-the-current-process
|
||||
|
||||
DWORD sessionId;
|
||||
BOOL haveSessionId = ProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
|
||||
|
||||
m_socketName = QString::fromUtf8("kdsingleapp-%1-%2")
|
||||
.arg(haveSessionId ? sessionId : 0)
|
||||
.arg(name);
|
||||
#else
|
||||
#error "KDSingleApplication has not been ported to this platform"
|
||||
#endif
|
||||
|
||||
const QString lockFilePath =
|
||||
QDir::tempPath() + QLatin1Char('/') + m_socketName + QLatin1String(".lock");
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Socket name is" << m_socketName;
|
||||
qCDebug(kdsaLocalSocket) << "Lock file path is" << lockFilePath;
|
||||
|
||||
std::unique_ptr<QLockFile> lockFile(new QLockFile(lockFilePath));
|
||||
lockFile->setStaleLockTime(0);
|
||||
|
||||
if (!lockFile->tryLock()) {
|
||||
// someone else has the lock => we're secondary
|
||||
qCDebug(kdsaLocalSocket) << "Secondary instance";
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Primary instance";
|
||||
|
||||
std::unique_ptr<QLocalServer> server = std::make_unique<QLocalServer>();
|
||||
if (!server->listen(m_socketName)) {
|
||||
// maybe the primary crashed, leaving a stale socket; delete it and try again
|
||||
QLocalServer::removeServer(m_socketName);
|
||||
if (!server->listen(m_socketName)) {
|
||||
// TODO: better error handling.
|
||||
qWarning("KDSingleApplication: unable to make the primary instance listen on %ls: %ls",
|
||||
qUtf16Printable(m_socketName),
|
||||
qUtf16Printable(server->errorString()));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connect(server.get(), &QLocalServer::newConnection,
|
||||
this, &KDSingleApplicationLocalSocket::handleNewConnection);
|
||||
|
||||
m_lockFile = std::move(lockFile);
|
||||
m_localServer = std::move(server);
|
||||
}
|
||||
|
||||
KDSingleApplicationLocalSocket::~KDSingleApplicationLocalSocket() = default;
|
||||
|
||||
bool KDSingleApplicationLocalSocket::isPrimaryInstance() const
|
||||
{
|
||||
return m_localServer != nullptr;
|
||||
}
|
||||
|
||||
bool KDSingleApplicationLocalSocket::sendMessage(const QByteArray &message, int timeout)
|
||||
{
|
||||
Q_ASSERT(!isPrimaryInstance());
|
||||
QLocalSocket socket;
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Preparing to send message" << message << "with timeout" << timeout;
|
||||
|
||||
QDeadlineTimer deadline(timeout);
|
||||
|
||||
// There is an inherent race here with the setup of the server side.
|
||||
// Even if the socket lock is held by the server, the server may not
|
||||
// be listening yet. So this connection may fail; keep retrying
|
||||
// until we hit the timeout.
|
||||
do {
|
||||
socket.connectToServer(m_socketName);
|
||||
if (socket.waitForConnected(deadline.remainingTime()))
|
||||
break;
|
||||
} while (!deadline.hasExpired());
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Socket state:" << socket.state() << "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
|
||||
|
||||
if (deadline.hasExpired()) {
|
||||
qCWarning(kdsaLocalSocket) << "Connection timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
socket.write(&LOCALSOCKET_PROTOCOL_VERSION, 1);
|
||||
|
||||
{
|
||||
QByteArray encodedMessage;
|
||||
QDataStream ds(&encodedMessage, QIODevice::WriteOnly);
|
||||
ds << message;
|
||||
socket.write(encodedMessage);
|
||||
}
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Wrote message in the socket"
|
||||
<< "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
|
||||
|
||||
// There is no acknowledgement mechanism here.
|
||||
// Should there be one?
|
||||
|
||||
while (socket.bytesToWrite() > 0) {
|
||||
if (!socket.waitForBytesWritten(deadline.remainingTime())) {
|
||||
qCWarning(kdsaLocalSocket) << "Message to primary timed out";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Bytes written, now disconnecting"
|
||||
<< "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
|
||||
|
||||
socket.disconnectFromServer();
|
||||
|
||||
if (socket.state() == QLocalSocket::UnconnectedState) {
|
||||
qCDebug(kdsaLocalSocket) << "Disconnected -- success!";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!socket.waitForDisconnected(deadline.remainingTime())) {
|
||||
qCWarning(kdsaLocalSocket) << "Disconnection from primary timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Disconnected -- success!";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::handleNewConnection()
|
||||
{
|
||||
Q_ASSERT(m_localServer);
|
||||
|
||||
QLocalSocket *socket;
|
||||
while ((socket = m_localServer->nextPendingConnection())) {
|
||||
qCDebug(kdsaLocalSocket) << "Got new connection on" << m_socketName << "state" << socket->state();
|
||||
|
||||
Connection c(std::move(socket));
|
||||
socket = c.socket.get();
|
||||
|
||||
c.readDataConnection = QObjectConnectionHolder(
|
||||
connect(socket, &QLocalSocket::readyRead,
|
||||
this, &KDSingleApplicationLocalSocket::readDataFromSecondary));
|
||||
|
||||
c.secondaryDisconnectedConnection = QObjectConnectionHolder(
|
||||
connect(socket, &QLocalSocket::disconnected,
|
||||
this, &KDSingleApplicationLocalSocket::secondaryDisconnected));
|
||||
|
||||
c.abortConnection = QObjectConnectionHolder(
|
||||
connect(c.timeoutTimer.get(), &QTimer::timeout,
|
||||
this, &KDSingleApplicationLocalSocket::abortConnectionToSecondary));
|
||||
|
||||
m_clients.push_back(std::move(c));
|
||||
|
||||
// Note that by the time we get here, the socket could've already been closed,
|
||||
// and no signals emitted (hello, Windows!). Read what's already in the socket.
|
||||
if (readDataFromSecondarySocket(socket))
|
||||
return;
|
||||
|
||||
if (socket->state() == QLocalSocket::UnconnectedState)
|
||||
secondarySocketDisconnected(socket);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
static auto findConnectionBySocket(Container &container, QLocalSocket *socket)
|
||||
{
|
||||
auto i = std::find_if(container.begin(),
|
||||
container.end(),
|
||||
[socket](const auto &c) { return c.socket.get() == socket; });
|
||||
Q_ASSERT(i != container.end());
|
||||
return i;
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
static auto findConnectionByTimer(Container &container, QTimer *timer)
|
||||
{
|
||||
auto i = std::find_if(container.begin(),
|
||||
container.end(),
|
||||
[timer](const auto &c) { return c.timeoutTimer.get() == timer; });
|
||||
Q_ASSERT(i != container.end());
|
||||
return i;
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::readDataFromSecondary()
|
||||
{
|
||||
QLocalSocket *socket = static_cast<QLocalSocket *>(sender());
|
||||
readDataFromSecondarySocket(socket);
|
||||
}
|
||||
|
||||
bool KDSingleApplicationLocalSocket::readDataFromSecondarySocket(QLocalSocket *socket)
|
||||
{
|
||||
auto i = findConnectionBySocket(m_clients, socket);
|
||||
Connection &c = *i;
|
||||
c.readData.append(socket->readAll());
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Got more data from a secondary. Data read so far:" << c.readData;
|
||||
|
||||
const QByteArray &data = c.readData;
|
||||
|
||||
if (data.size() >= 1) {
|
||||
if (data[0] != LOCALSOCKET_PROTOCOL_VERSION) {
|
||||
qCDebug(kdsaLocalSocket) << "Got an invalid protocol version";
|
||||
m_clients.erase(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
QDataStream ds(data);
|
||||
ds.skipRawData(1);
|
||||
|
||||
ds.startTransaction();
|
||||
QByteArray message;
|
||||
ds >> message;
|
||||
|
||||
if (ds.commitTransaction()) {
|
||||
qCDebug(kdsaLocalSocket) << "Got a complete message:" << message;
|
||||
Q_EMIT messageReceived(message);
|
||||
m_clients.erase(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::secondaryDisconnected()
|
||||
{
|
||||
QLocalSocket *socket = static_cast<QLocalSocket *>(sender());
|
||||
secondarySocketDisconnected(socket);
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::secondarySocketDisconnected(QLocalSocket *socket)
|
||||
{
|
||||
auto i = findConnectionBySocket(m_clients, socket);
|
||||
Connection c = std::move(*i);
|
||||
m_clients.erase(i);
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Secondary disconnected. Data read:" << c.readData;
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::abortConnectionToSecondary()
|
||||
{
|
||||
QTimer *timer = static_cast<QTimer *>(sender());
|
||||
|
||||
auto i = findConnectionByTimer(m_clients, timer);
|
||||
Connection c = std::move(*i);
|
||||
m_clients.erase(i);
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Secondary timed out. Data read:" << c.readData;
|
||||
}
|
||||
|
||||
KDSingleApplicationLocalSocket::Connection::Connection(QLocalSocket *_socket)
|
||||
: socket(_socket)
|
||||
, timeoutTimer(new QTimer)
|
||||
{
|
||||
timeoutTimer->start(LOCALSOCKET_CONNECTION_TIMEOUT);
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#ifndef KDSINGLEAPPLICATION_LOCALSOCKET_P_H
|
||||
#define KDSINGLEAPPLICATION_LOCALSOCKET_P_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLockFile;
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
class QTimer;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
struct QObjectDeleteLater
|
||||
{
|
||||
void operator()(QObject *o)
|
||||
{
|
||||
o->deleteLater();
|
||||
}
|
||||
};
|
||||
|
||||
class QObjectConnectionHolder
|
||||
{
|
||||
Q_DISABLE_COPY(QObjectConnectionHolder)
|
||||
QMetaObject::Connection c;
|
||||
|
||||
public:
|
||||
QObjectConnectionHolder()
|
||||
{
|
||||
}
|
||||
|
||||
explicit QObjectConnectionHolder(QMetaObject::Connection _c)
|
||||
: c(std::move(_c))
|
||||
{
|
||||
}
|
||||
|
||||
~QObjectConnectionHolder()
|
||||
{
|
||||
QObject::disconnect(c);
|
||||
}
|
||||
|
||||
QObjectConnectionHolder(QObjectConnectionHolder &&other) noexcept
|
||||
: c(std::exchange(other.c, {}))
|
||||
{
|
||||
}
|
||||
|
||||
QObjectConnectionHolder &operator=(QObjectConnectionHolder &&other) noexcept
|
||||
{
|
||||
QObjectConnectionHolder moved(std::move(other));
|
||||
swap(moved);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(QObjectConnectionHolder &other) noexcept
|
||||
{
|
||||
using std::swap;
|
||||
swap(c, other.c);
|
||||
}
|
||||
};
|
||||
|
||||
class KDSingleApplicationLocalSocket : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit KDSingleApplicationLocalSocket(const QString &name,
|
||||
QObject *parent = nullptr);
|
||||
~KDSingleApplicationLocalSocket();
|
||||
|
||||
bool isPrimaryInstance() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
bool sendMessage(const QByteArray &message, int timeout);
|
||||
|
||||
Q_SIGNALS:
|
||||
void messageReceived(const QByteArray &message);
|
||||
|
||||
private:
|
||||
void handleNewConnection();
|
||||
void readDataFromSecondary();
|
||||
bool readDataFromSecondarySocket(QLocalSocket *socket);
|
||||
void secondaryDisconnected();
|
||||
void secondarySocketDisconnected(QLocalSocket *socket);
|
||||
void abortConnectionToSecondary();
|
||||
|
||||
QString m_socketName;
|
||||
|
||||
std::unique_ptr<QLockFile> m_lockFile; // protects m_localServer
|
||||
std::unique_ptr<QLocalServer> m_localServer;
|
||||
|
||||
struct Connection
|
||||
{
|
||||
explicit Connection(QLocalSocket *s);
|
||||
|
||||
std::unique_ptr<QLocalSocket, QObjectDeleteLater> socket;
|
||||
std::unique_ptr<QTimer, QObjectDeleteLater> timeoutTimer;
|
||||
QByteArray readData;
|
||||
|
||||
// socket/timeoutTimer are deleted via deleteLater (as we delete them
|
||||
// in slots connected to their signals). Before the deleteLater is acted upon,
|
||||
// they may emit further signals, triggering logic that it's not supposed
|
||||
// to be triggered (as the Connection has already been destroyed).
|
||||
// Use this Holder to break the connections.
|
||||
QObjectConnectionHolder readDataConnection;
|
||||
QObjectConnectionHolder secondaryDisconnectedConnection;
|
||||
QObjectConnectionHolder abortConnection;
|
||||
};
|
||||
|
||||
std::vector<Connection> m_clients;
|
||||
};
|
||||
|
||||
#endif // KDSINGLEAPPLICATION_LOCALSOCKET_P_H
|
|
@ -1,12 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
include(CheckIncludeFiles)
|
||||
include(CheckFunctionExists)
|
||||
|
||||
check_function_exists(geteuid HAVE_GETEUID)
|
||||
check_function_exists(getpwuid HAVE_GETPWUID)
|
||||
|
||||
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h")
|
||||
|
||||
add_subdirectory(singleapplication)
|
||||
add_subdirectory(singlecoreapplication)
|
|
@ -1,24 +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.
|
||||
|
||||
Note: Some of the examples include code not distributed under the terms of the
|
||||
MIT License.
|
|
@ -1,305 +0,0 @@
|
|||
SingleApplication
|
||||
=================
|
||||
[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](https://github.com/itay-grudev/SingleApplication/actions)
|
||||
|
||||
This is a replacement of the QtSingleApplication for `Qt5` and `Qt6`.
|
||||
|
||||
Keeps the Primary Instance of your Application and kills each subsequent
|
||||
instances. It can (if enabled) spawn secondary (non-related to the primary)
|
||||
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.
|
||||
|
||||
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. Here is how:
|
||||
|
||||
```bash
|
||||
git submodule add https://github.com/itay-grudev/SingleApplication.git singleapplication
|
||||
```
|
||||
|
||||
**Qmake:**
|
||||
|
||||
Then include the `singleapplication.pri` file in your `.pro` project file.
|
||||
|
||||
```qmake
|
||||
include(singleapplication/singleapplication.pri)
|
||||
DEFINES += QAPPLICATION_CLASS=QApplication
|
||||
```
|
||||
|
||||
**CMake:**
|
||||
|
||||
Then include the subdirectory in your `CMakeLists.txt` project file.
|
||||
|
||||
```cmake
|
||||
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
|
||||
add_subdirectory(src/third-party/singleapplication)
|
||||
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
|
||||
```
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Also don't forget to specify which `QCoreApplication` class your app is using if it
|
||||
is not `QCoreApplication` as in examples above.
|
||||
|
||||
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.*
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
There are three examples provided in this repository:
|
||||
|
||||
* Basic example that prevents a secondary instance from starting [`examples/basic`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/basic)
|
||||
* An example of a graphical application raising it's parent window [`examples/calculator`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/calculator)
|
||||
* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/sending_arguments)
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
### Members
|
||||
|
||||
```cpp
|
||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100, QString userData = QString() )
|
||||
```
|
||||
|
||||
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. Setting `userData` provides additional data that will isolate this instance from other instances that do not have the same (or any) user data set.
|
||||
|
||||
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
|
||||
recognizes.*
|
||||
|
||||
*__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. Returns `true` if the message has been sent
|
||||
successfully. If the message can't be sent or the function timeouts - returns `false`.
|
||||
|
||||
---
|
||||
|
||||
```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.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
QString SingleApplication::primaryUser()
|
||||
```
|
||||
|
||||
Returns the username the primary instance is running as.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
QString SingleApplication::currentUser()
|
||||
```
|
||||
|
||||
Returns the username the current instance is running as.
|
||||
|
||||
### 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 and for messaging the primary instance.
|
||||
|
||||
Additionally the library can recover from being forcefully killed on *nix
|
||||
systems 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 the Qt calculator examples which
|
||||
is distributed under the BSD license.
|
|
@ -1,2 +0,0 @@
|
|||
#cmakedefine HAVE_GETEUID
|
||||
#cmakedefine HAVE_GETPWUID
|
|
@ -1,18 +0,0 @@
|
|||
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}/..
|
||||
${Boost_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(singleapplication PUBLIC
|
||||
${QtCore_LIBRARIES}
|
||||
${QtWidgets_LIBRARIES}
|
||||
${QtNetwork_LIBRARIES}
|
||||
)
|
|
@ -1,13 +0,0 @@
|
|||
#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
|
|
@ -1,505 +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>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include "singleapplication_t.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
SingleApplicationPrivateClass::SingleApplicationPrivateClass(SingleApplicationClass *ptr)
|
||||
: q_ptr(ptr),
|
||||
memory_(nullptr),
|
||||
socket_(nullptr),
|
||||
server_(nullptr),
|
||||
instanceNumber_(-1) {}
|
||||
|
||||
SingleApplicationPrivateClass::~SingleApplicationPrivateClass() {
|
||||
|
||||
if (socket_ != nullptr && socket_->isOpen()) {
|
||||
socket_->close();
|
||||
}
|
||||
|
||||
if (memory_ != nullptr) {
|
||||
memory_->lock();
|
||||
if (server_ != nullptr) {
|
||||
server_->close();
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
instance->primary = false;
|
||||
instance->primaryPid = -1;
|
||||
instance->primaryUser[0] = '\0';
|
||||
instance->checksum = blockChecksum();
|
||||
}
|
||||
memory_->unlock();
|
||||
if (memory_->isAttached()) {
|
||||
memory_->detach();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivateClass::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()) {
|
||||
username = qEnvironmentVariable("USER");
|
||||
}
|
||||
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);
|
||||
}
|
||||
return qEnvironmentVariable("USERNAME");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivateClass::genBlockServerName() {
|
||||
|
||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||
appData.addData("SingleApplication");
|
||||
appData.addData(SingleApplicationClass::applicationName().toUtf8());
|
||||
appData.addData(SingleApplicationClass::organizationName().toUtf8());
|
||||
appData.addData(SingleApplicationClass::organizationDomain().toUtf8());
|
||||
|
||||
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppVersion)) {
|
||||
appData.addData(SingleApplicationClass::applicationVersion().toUtf8());
|
||||
}
|
||||
|
||||
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppPath)) {
|
||||
#if defined(Q_OS_UNIX)
|
||||
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
||||
if (appImagePath.isEmpty()) {
|
||||
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||
}
|
||||
else {
|
||||
appData.addData(appImagePath);
|
||||
}
|
||||
#elif defined(Q_OS_WIN)
|
||||
appData.addData(SingleApplicationClass::applicationFilePath().toLower().toUtf8());
|
||||
#else
|
||||
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||
#endif
|
||||
}
|
||||
|
||||
// User level block requires a user specific data in the hash
|
||||
if (options_ & SingleApplicationClass::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 SingleApplicationPrivateClass::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 SingleApplicationPrivateClass::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(this);
|
||||
|
||||
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
||||
if (options_ & SingleApplicationClass::Mode::User) {
|
||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
}
|
||||
else {
|
||||
server_->setSocketOptions(QLocalServer::WorldAccessOption);
|
||||
}
|
||||
|
||||
server_->listen(blockServerName_);
|
||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivateClass::slotConnectionEstablished);
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivateClass::startSecondary() {
|
||||
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
instance->secondary += 1;
|
||||
instance->checksum = blockChecksum();
|
||||
instanceNumber_ = instance->secondary;
|
||||
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
||||
|
||||
// Connect to the Local Server of the Primary Instance if not already connected.
|
||||
if (socket_ == nullptr) {
|
||||
socket_ = new QLocalSocket(this);
|
||||
}
|
||||
|
||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization message according to the SingleApplication 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 SingleApplicationPrivateClass::writeAck(QLocalSocket *sock) {
|
||||
sock->putChar('\n');
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivateClass::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 SingleApplicationPrivateClass::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 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)));
|
||||
#else
|
||||
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
|
||||
#endif
|
||||
|
||||
return checksum;
|
||||
|
||||
}
|
||||
|
||||
qint64 SingleApplicationPrivateClass::primaryPid() const {
|
||||
|
||||
memory_->lock();
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = instance->primaryPid;
|
||||
memory_->unlock();
|
||||
|
||||
return pid;
|
||||
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivateClass::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 SingleApplicationPrivateClass::slotConnectionEstablished() {
|
||||
|
||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [this, nextConnSocket]() {
|
||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [this, nextConnSocket]() {
|
||||
connectionMap_.remove(nextConnSocket);
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [this, nextConnSocket]() {
|
||||
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:
|
||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivateClass::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivateClass::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 SingleApplicationPrivateClass::isFrameComplete(QLocalSocket *sock) {
|
||||
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ConnectionInfo &info = connectionMap_[sock];
|
||||
return (sock->bytesAvailable() >= static_cast<qint64>(info.msgLen));
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivateClass::readInitMessageBody(QLocalSocket *sock) {
|
||||
|
||||
Q_Q(SingleApplicationClass);
|
||||
|
||||
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_ & SingleApplicationClass::Mode::SecondaryNotification)) {
|
||||
emit q->instanceStarted();
|
||||
}
|
||||
|
||||
writeAck(sock);
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivateClass::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||
|
||||
Q_Q(SingleApplicationClass);
|
||||
|
||||
if (!isFrameComplete(dataSocket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray message = dataSocket->readAll();
|
||||
|
||||
writeAck(dataSocket);
|
||||
|
||||
ConnectionInfo &info = connectionMap_[dataSocket];
|
||||
info.stage = StageConnectedHeader;
|
||||
|
||||
emit q->receivedMessage(instanceId, message);
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivateClass::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||
|
||||
if (closedSocket->bytesAvailable() > 0) {
|
||||
slotDataAvailable(closedSocket, instanceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivateClass::randomSleep() {
|
||||
|
||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||
|
||||
}
|
|
@ -1,117 +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 SINGLEAPPLICATION_P_H
|
||||
#define SINGLEAPPLICATION_P_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
|
||||
#include "singleapplication_t.h"
|
||||
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
class QSharedMemory;
|
||||
|
||||
class SingleApplicationPrivateClass : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SingleApplicationPrivateClass(SingleApplicationClass *ptr);
|
||||
~SingleApplicationPrivateClass() override;
|
||||
|
||||
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(SingleApplicationClass)
|
||||
|
||||
struct InstancesInfo {
|
||||
explicit InstancesInfo() : primary(false), secondary(0), primaryPid(0), checksum(0) {}
|
||||
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;
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
SingleApplicationClass *q_ptr;
|
||||
QSharedMemory *memory_;
|
||||
QLocalSocket *socket_;
|
||||
QLocalServer *server_;
|
||||
quint32 instanceNumber_;
|
||||
QString blockServerName_;
|
||||
SingleApplicationClass::Options options_;
|
||||
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||
|
||||
public slots:
|
||||
void slotConnectionEstablished();
|
||||
void slotDataAvailable(QLocalSocket*, const quint32);
|
||||
void slotClientConnectionClosed(QLocalSocket*, const quint32);
|
||||
};
|
||||
|
||||
#endif // SINGLEAPPLICATION_P_H
|
|
@ -1,330 +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 <memory>
|
||||
|
||||
#include <boost/scope_exit.hpp>
|
||||
|
||||
#include <QtGlobal>
|
||||
#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 "singleapplication_t.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param allowSecondary Whether to enable secondary instance support
|
||||
* @param options Optional flags to toggle specific behaviour
|
||||
* @param timeout Maximum time blocking functions are allowed during app load
|
||||
*/
|
||||
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;
|
||||
|
||||
// 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
|
||||
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.
|
||||
{
|
||||
# if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(QNativeIpcKey(d->blockServerName_));
|
||||
# else
|
||||
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(d->blockServerName_);
|
||||
# endif
|
||||
if (memory->attach()) {
|
||||
memory->detach();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Guarantee thread safe behaviour with a shared memory block.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
QSharedMemory *memory = new QSharedMemory(QNativeIpcKey(d->blockServerName_), this);
|
||||
#else
|
||||
QSharedMemory *memory = new QSharedMemory(d->blockServerName_, this);
|
||||
#endif
|
||||
d->memory_ = memory;
|
||||
|
||||
bool primary = false;
|
||||
|
||||
// Create a shared memory block
|
||||
if (d->memory_->create(sizeof(SingleApplicationPrivateClass::InstancesInfo))) {
|
||||
primary = true;
|
||||
}
|
||||
else if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||
if (!d->memory_->attach()) {
|
||||
qCritical() << "SingleApplication: Unable to attach to shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
qCritical() << "SingleApplication: Unable to create shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
bool locked = false;
|
||||
|
||||
BOOST_SCOPE_EXIT((memory)(&locked)) {
|
||||
if (locked && !memory->unlock()) {
|
||||
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||
return;
|
||||
}
|
||||
}BOOST_SCOPE_EXIT_END
|
||||
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleApplication: Unable to lock shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||
return;
|
||||
}
|
||||
locked = true;
|
||||
|
||||
if (primary) {
|
||||
// Initialize the shared memory block
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
|
||||
SingleApplicationPrivateClass::InstancesInfo *instance = static_cast<SingleApplicationPrivateClass::InstancesInfo*>(d->memory_->data());
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialized and in a consistent state
|
||||
while (d->blockChecksum() != instance->checksum) {
|
||||
|
||||
// If more than 5 seconds have elapsed, assume the primary instance crashed and assume its position
|
||||
if (time.elapsed() > 5000) {
|
||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5 seconds. 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 initialize faster
|
||||
if (locked) {
|
||||
if (d->memory_->unlock()) {
|
||||
locked = false;
|
||||
}
|
||||
else {
|
||||
qCritical() << "SingleApplication: Unable to unlock shared memory block for random wait:" << memory->error() << memory->errorString();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SingleApplicationPrivateClass::randomSleep();
|
||||
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleApplication: Unable to lock shared memory block after random wait:" << memory->error() << memory->errorString();
|
||||
return;
|
||||
}
|
||||
locked = true;
|
||||
|
||||
}
|
||||
|
||||
if (instance->primary) {
|
||||
// Check if another instance can be started
|
||||
if (allowSecondary) {
|
||||
d->startSecondary();
|
||||
if (d->options_ & Mode::SecondaryNotification) {
|
||||
d->connectToPrimary(timeout, SingleApplicationPrivateClass::SecondaryInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
d->startPrimary();
|
||||
primary = true;
|
||||
}
|
||||
|
||||
if (locked) {
|
||||
if (d->memory_->unlock()) {
|
||||
locked = false;
|
||||
}
|
||||
else {
|
||||
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!primary && !allowSecondary) {
|
||||
d->connectToPrimary(timeout, SingleApplicationPrivateClass::NewInstance);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 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 SingleApplicationClass::isSecondary() const {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(const SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(const SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
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 SingleApplicationClass::instanceId() const {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(const SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(const SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
return d->instanceNumber_;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
||||
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
||||
* @return Returns the primary instance PID.
|
||||
*/
|
||||
qint64 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 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 SingleApplicationClass::currentUser() const {
|
||||
return SingleApplicationPrivateClass::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 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, SingleApplicationPrivateClass::Reconnect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return d->writeConfirmedMessage(timeout, message);
|
||||
|
||||
}
|
|
@ -1,172 +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 SINGLEAPPLICATION_T_H
|
||||
#define SINGLEAPPLICATION_T_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#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 SingleApplicationPrivateClass;
|
||||
|
||||
/**
|
||||
* @brief The SingleApplication class handles multiple instances of the same Application
|
||||
* @see QApplication
|
||||
*/
|
||||
class SingleApplicationClass : public ApplicationClass { // clazy:exclude=ctor-missing-parent-argument
|
||||
Q_OBJECT
|
||||
|
||||
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 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 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 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 SingleApplication
|
||||
* initialization will be completed in given time, though is a good hint.
|
||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||
*/
|
||||
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
|
||||
* @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:
|
||||
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(SingleApplicationClass::Options)
|
||||
|
||||
#endif // SINGLEAPPLICATION_T_H
|
|
@ -1,17 +0,0 @@
|
|||
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}/..
|
||||
${Boost_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(singlecoreapplication PUBLIC
|
||||
${QtCore_LIBRARIES}
|
||||
${QtNetwork_LIBRARIES}
|
||||
)
|
|
@ -1,13 +0,0 @@
|
|||
#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
|
|
@ -328,13 +328,10 @@ if(NOT TAGLIB_FOUND AND NOT TAGPARSER_FOUND)
|
|||
endif()
|
||||
|
||||
# SingleApplication
|
||||
add_subdirectory(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)
|
||||
add_subdirectory(3rdparty/kdsingleapplication)
|
||||
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/kdsingleapplication)
|
||||
set(SINGLEAPPLICATION_LIBRARIES kdsingleapplication)
|
||||
add_definitions(-DKDSINGLEAPPLICATION_STATIC_BUILD)
|
||||
|
||||
if(APPLE)
|
||||
add_subdirectory(3rdparty/SPMediaKeyTap)
|
||||
|
|
|
@ -267,8 +267,8 @@ Copyright: 2010, Spotify AB
|
|||
2011, Joachim Bengtsson
|
||||
License: BSD-3-clause
|
||||
|
||||
Files: 3rdparty/singleapplication/*
|
||||
Copyright: 2015-2022, Itay Grudev
|
||||
Files: 3rdparty/kdsingleapplication/*
|
||||
Copyright: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
License: MIT
|
||||
|
||||
|
||||
|
|
|
@ -972,7 +972,6 @@ link_directories(
|
|||
${SQLITE_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if(HAVE_ICU)
|
||||
|
@ -1080,7 +1079,6 @@ target_include_directories(strawberry_lib PUBLIC
|
|||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader
|
||||
${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader
|
||||
${SINGLEAPPLICATION_INCLUDE_DIRS}
|
||||
${SINGLECOREAPPLICATION_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(strawberry_lib PUBLIC
|
||||
|
@ -1092,7 +1090,6 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||
${QT_LIBRARIES}
|
||||
${Protobuf_LIBRARIES}
|
||||
${SINGLEAPPLICATION_LIBRARIES}
|
||||
${SINGLECOREAPPLICATION_LIBRARIES}
|
||||
libstrawberry-common
|
||||
libstrawberry-tagreader
|
||||
)
|
||||
|
|
|
@ -2330,9 +2330,7 @@ void MainWindow::PlaylistEditFinished(const int playlist_id, const QModelIndex &
|
|||
|
||||
}
|
||||
|
||||
void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByteArray &string_options) {
|
||||
|
||||
Q_UNUSED(instanceId);
|
||||
void MainWindow::CommandlineOptionsReceived(const QByteArray &string_options) {
|
||||
|
||||
CommandlineOptions options;
|
||||
options.Load(string_options);
|
||||
|
@ -2342,9 +2340,10 @@ void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByt
|
|||
show();
|
||||
activateWindow();
|
||||
hidden_ = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
CommandlineOptionsReceived(options);
|
||||
|
||||
CommandlineOptionsReceived(options);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -272,7 +272,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||
void FocusSearchField();
|
||||
|
||||
public slots:
|
||||
void CommandlineOptionsReceived(const quint32 instanceId, const QByteArray &string_options);
|
||||
void CommandlineOptionsReceived(const QByteArray &string_options);
|
||||
void Raise();
|
||||
|
||||
private:
|
||||
|
|
22
src/main.cpp
22
src/main.cpp
|
@ -66,8 +66,7 @@
|
|||
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <singleapplication.h>
|
||||
#include <singlecoreapplication.h>
|
||||
#include <kdsingleapplication.h>
|
||||
|
||||
#ifdef HAVE_QTSPARKLE
|
||||
# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
|
@ -148,15 +147,16 @@ int main(int argc, char *argv[]) {
|
|||
{
|
||||
// Only start a core application now, so we can check if there's another instance without requiring an X server.
|
||||
// This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames.
|
||||
SingleCoreApplication core_app(argc, argv, true, SingleCoreApplication::Mode::User | SingleCoreApplication::Mode::ExcludeAppVersion | SingleCoreApplication::Mode::ExcludeAppPath);
|
||||
QCoreApplication core_app(argc, argv);
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName());
|
||||
// Parse commandline options - need to do this before starting the full QApplication, so it works without an X server
|
||||
if (!options.Parse()) return 1;
|
||||
logging::SetLevels(options.log_levels());
|
||||
if (core_app.isSecondary()) {
|
||||
if (!single_app.isPrimaryInstance()) {
|
||||
if (options.is_empty()) {
|
||||
qLog(Info) << "Strawberry is already running - activating existing window (1)";
|
||||
}
|
||||
if (!core_app.sendMessage(options.Serialize(), 5000)) {
|
||||
if (!single_app.sendMessage(options.Serialize())) {
|
||||
qLog(Error) << "Could not send message to primary instance.";
|
||||
}
|
||||
return 0;
|
||||
|
@ -182,14 +182,13 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
|
||||
// important: Do not remove this.
|
||||
// This must also be done as a SingleApplication, in case SingleCoreApplication was compiled with a different appdata.
|
||||
SingleApplication a(argc, argv, true, SingleApplication::Mode::User | SingleApplication::Mode::ExcludeAppVersion | SingleApplication::Mode::ExcludeAppPath);
|
||||
if (a.isSecondary()) {
|
||||
QApplication a(argc, argv);
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName());
|
||||
if (!single_app.isPrimaryInstance()) {
|
||||
if (options.is_empty()) {
|
||||
qLog(Info) << "Strawberry is already running - activating existing window (2)";
|
||||
}
|
||||
if (!a.sendMessage(options.Serialize(), 5000)) {
|
||||
if (!single_app.sendMessage(options.Serialize())) {
|
||||
qLog(Error) << "Could not send message to primary instance.";
|
||||
}
|
||||
return 0;
|
||||
|
@ -319,9 +318,10 @@ int main(int argc, char *argv[]) {
|
|||
#ifdef HAVE_DBUS
|
||||
QObject::connect(&mpris2, &mpris::Mpris2::RaiseMainWindow, &w, &MainWindow::Raise);
|
||||
#endif
|
||||
QObject::connect(&a, &SingleApplication::receivedMessage, &w, QOverload<quint32, const QByteArray&>::of(&MainWindow::CommandlineOptionsReceived));
|
||||
QObject::connect(&single_app, &KDSingleApplication::messageReceived, &w, QOverload<const QByteArray&>::of(&MainWindow::CommandlineOptionsReceived));
|
||||
|
||||
int ret = QCoreApplication::exec();
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue