diff --git a/3rdparty/README.md b/3rdparty/README.md index 783f7684..93ff89f5 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -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 diff --git a/3rdparty/kdsingleapplication/CMakeLists.txt b/3rdparty/kdsingleapplication/CMakeLists.txt new file mode 100644 index 00000000..e41ed9a1 --- /dev/null +++ b/3rdparty/kdsingleapplication/CMakeLists.txt @@ -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} ) diff --git a/3rdparty/kdsingleapplication/LICENSE b/3rdparty/kdsingleapplication/LICENSE new file mode 100644 index 00000000..12ee5866 --- /dev/null +++ b/3rdparty/kdsingleapplication/LICENSE @@ -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 to inquire about commercial licensing. diff --git a/3rdparty/kdsingleapplication/README.md b/3rdparty/kdsingleapplication/README.md new file mode 100644 index 00000000..d18e788d --- /dev/null +++ b/3rdparty/kdsingleapplication/README.md @@ -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 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 +. + +## 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 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) diff --git a/3rdparty/kdsingleapplication/kdsingleapplication.cpp b/3rdparty/kdsingleapplication/kdsingleapplication.cpp new file mode 100644 index 00000000..f8055383 --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication.cpp @@ -0,0 +1,108 @@ +/* + This file is part of KDSingleApplication. + + SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company + + SPDX-License-Identifier: MIT + + Contact KDAB at for commercial licensing options. +*/ + +#include "kdsingleapplication.h" + +#include +#include + +// 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; diff --git a/3rdparty/kdsingleapplication/kdsingleapplication.h b/3rdparty/kdsingleapplication/kdsingleapplication.h new file mode 100644 index 00000000..2d9ab6c0 --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication.h @@ -0,0 +1,48 @@ +/* + This file is part of KDSingleApplication. + + SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company + + SPDX-License-Identifier: MIT + + Contact KDAB at for commercial licensing options. +*/ +#ifndef KDSINGLEAPPLICATION_H +#define KDSINGLEAPPLICATION_H + +#include + +#include + +#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 d_ptr; +}; + +#endif // KDSINGLEAPPLICATION_H diff --git a/3rdparty/kdsingleapplication/kdsingleapplication_lib.h b/3rdparty/kdsingleapplication/kdsingleapplication_lib.h new file mode 100644 index 00000000..060dd673 --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication_lib.h @@ -0,0 +1,23 @@ +/* + This file is part of KDSingleApplication. + + SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company + + SPDX-License-Identifier: MIT + + Contact KDAB at for commercial licensing options. +*/ +#ifndef KDSINGLEAPPLICATION_LIB_H +#define KDSINGLEAPPLICATION_LIB_H + +#include + +#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 diff --git a/3rdparty/kdsingleapplication/kdsingleapplication_localsocket.cpp b/3rdparty/kdsingleapplication/kdsingleapplication_localsocket.cpp new file mode 100644 index 00000000..38ec887c --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication_localsocket.cpp @@ -0,0 +1,306 @@ +/* + This file is part of KDSingleApplication. + + SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company + + SPDX-License-Identifier: MIT + + Contact KDAB at for commercial licensing options. +*/ + +#include "kdsingleapplication_localsocket_p.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#if defined(Q_OS_UNIX) +// for ::getuid() +#include +#include +#endif + +#if defined(Q_OS_WIN) +#include +#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 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 server = std::make_unique(); + 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 +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 +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(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(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(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); +} diff --git a/3rdparty/kdsingleapplication/kdsingleapplication_localsocket_p.h b/3rdparty/kdsingleapplication/kdsingleapplication_localsocket_p.h new file mode 100644 index 00000000..ec9c60ee --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication_localsocket_p.h @@ -0,0 +1,126 @@ +/* + This file is part of KDSingleApplication. + + SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company + + SPDX-License-Identifier: MIT + + Contact KDAB at for commercial licensing options. +*/ + +#ifndef KDSINGLEAPPLICATION_LOCALSOCKET_P_H +#define KDSINGLEAPPLICATION_LOCALSOCKET_P_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QLockFile; +class QLocalServer; +class QLocalSocket; +class QTimer; +QT_END_NAMESPACE + +#include +#include + +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 m_lockFile; // protects m_localServer + std::unique_ptr m_localServer; + + struct Connection + { + explicit Connection(QLocalSocket *s); + + std::unique_ptr socket; + std::unique_ptr 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 m_clients; +}; + +#endif // KDSINGLEAPPLICATION_LOCALSOCKET_P_H diff --git a/3rdparty/singleapplication/CMakeLists.txt b/3rdparty/singleapplication/CMakeLists.txt deleted file mode 100644 index 2febf5aa..00000000 --- a/3rdparty/singleapplication/CMakeLists.txt +++ /dev/null @@ -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) diff --git a/3rdparty/singleapplication/LICENSE b/3rdparty/singleapplication/LICENSE deleted file mode 100644 index a82e5a68..00000000 --- a/3rdparty/singleapplication/LICENSE +++ /dev/null @@ -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. diff --git a/3rdparty/singleapplication/README.md b/3rdparty/singleapplication/README.md deleted file mode 100644 index 716f9a7e..00000000 --- a/3rdparty/singleapplication/README.md +++ /dev/null @@ -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 -#include - -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. diff --git a/3rdparty/singleapplication/config.h.in b/3rdparty/singleapplication/config.h.in deleted file mode 100644 index e7c1343f..00000000 --- a/3rdparty/singleapplication/config.h.in +++ /dev/null @@ -1,2 +0,0 @@ -#cmakedefine HAVE_GETEUID -#cmakedefine HAVE_GETPWUID diff --git a/3rdparty/singleapplication/singleapplication/CMakeLists.txt b/3rdparty/singleapplication/singleapplication/CMakeLists.txt deleted file mode 100644 index 929f8cb4..00000000 --- a/3rdparty/singleapplication/singleapplication/CMakeLists.txt +++ /dev/null @@ -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} -) diff --git a/3rdparty/singleapplication/singleapplication/singleapplication.h b/3rdparty/singleapplication/singleapplication/singleapplication.h deleted file mode 100644 index 34beb3ce..00000000 --- a/3rdparty/singleapplication/singleapplication/singleapplication.h +++ /dev/null @@ -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 diff --git a/3rdparty/singleapplication/singleapplication_p.cpp b/3rdparty/singleapplication/singleapplication_p.cpp deleted file mode 100644 index 3e2721ca..00000000 --- a/3rdparty/singleapplication/singleapplication_p.cpp +++ /dev/null @@ -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 - -#include -#include - -#ifdef Q_OS_UNIX -# include -# include -# include -#endif - -#ifdef Q_OS_WIN -# ifndef NOMINMAX -# define NOMINMAX 1 -# endif -# include -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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(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(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(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(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(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(connectionType); - writeStream << instanceNumber_; - -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length()))); -#else - quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); -#endif - - writeStream << checksum; - - return writeConfirmedMessage(static_cast(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(msg.length()); - - if (!writeConfirmedFrame(static_cast(timeout - time.elapsed()), header)) { - return false; - } - - // Frame 2: The message - return writeConfirmedFrame(static_cast(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(memory_->constData()), offsetof(InstancesInfo, checksum))); -#else - quint16 checksum = qChecksum(static_cast(memory_->constData()), offsetof(InstancesInfo, checksum)); -#endif - - return checksum; - -} - -qint64 SingleApplicationPrivateClass::primaryPid() const { - - memory_->lock(); - InstancesInfo *instance = static_cast(memory_->data()); - qint64 pid = instance->primaryPid; - memory_->unlock(); - - return pid; - -} - -QString SingleApplicationPrivateClass::primaryUser() const { - - memory_->lock(); - InstancesInfo *instance = static_cast(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(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(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(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(msgBytes.length() - sizeof(quint16)))); -#else - const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast(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)); - -} diff --git a/3rdparty/singleapplication/singleapplication_p.h b/3rdparty/singleapplication/singleapplication_p.h deleted file mode 100644 index 1c14c3c7..00000000 --- a/3rdparty/singleapplication/singleapplication_p.h +++ /dev/null @@ -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 -#include -#include -#include - -#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 connectionMap_; - - public slots: - void slotConnectionEstablished(); - void slotDataAvailable(QLocalSocket*, const quint32); - void slotClientConnectionClosed(QLocalSocket*, const quint32); -}; - -#endif // SINGLEAPPLICATION_P_H diff --git a/3rdparty/singleapplication/singleapplication_t.cpp b/3rdparty/singleapplication/singleapplication_t.cpp deleted file mode 100644 index 2a75dcde..00000000 --- a/3rdparty/singleapplication/singleapplication_t.cpp +++ /dev/null @@ -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 -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) -# include -#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 memory = std::make_unique(QNativeIpcKey(d->blockServerName_)); -# else - std::unique_ptr memory = std::make_unique(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(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); - -} diff --git a/3rdparty/singleapplication/singleapplication_t.h b/3rdparty/singleapplication/singleapplication_t.h deleted file mode 100644 index f3bb3767..00000000 --- a/3rdparty/singleapplication/singleapplication_t.h +++ /dev/null @@ -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 - -#undef ApplicationClass -#undef SingleApplicationClass -#undef SingleApplicationPrivateClass - -#if defined(SINGLEAPPLICATION) -# include -# define ApplicationClass QApplication -# define SingleApplicationClass SingleApplication -# define SingleApplicationPrivateClass SingleApplicationPrivate -#elif defined(SINGLECOREAPPLICATION) -# include -# define ApplicationClass QCoreApplication -# define SingleApplicationClass SingleCoreApplication -# define SingleApplicationPrivateClass SingleCoreApplicationPrivate -#else -# error "Define SINGLEAPPLICATION or SINGLECOREAPPLICATION." -#endif - -#include -#include - -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 diff --git a/3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt b/3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt deleted file mode 100644 index 3ea6ca94..00000000 --- a/3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt +++ /dev/null @@ -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} -) diff --git a/3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h b/3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h deleted file mode 100644 index 0d510c61..00000000 --- a/3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h +++ /dev/null @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 502e1b9f..025a671b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/debian/copyright b/debian/copyright index 61a6c0c6..b4a50cb9 100644 --- a/debian/copyright +++ b/debian/copyright @@ -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 License: MIT diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2d1c3b49..43a1ce59 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 ) diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index 1735255c..32b91d47 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -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); } diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h index f436c33e..12f1e2e6 100644 --- a/src/core/mainwindow.h +++ b/src/core/mainwindow.h @@ -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: diff --git a/src/main.cpp b/src/main.cpp index 51d41c44..dd9afe9a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,8 +66,7 @@ #include "core/logging.h" -#include -#include +#include #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::of(&MainWindow::CommandlineOptionsReceived)); + QObject::connect(&single_app, &KDSingleApplication::messageReceived, &w, QOverload::of(&MainWindow::CommandlineOptionsReceived)); int ret = QCoreApplication::exec(); return ret; + }