From e80764a01ea827cd3de6bab70bb1023341037829 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 26 Jun 2022 21:59:53 +0200 Subject: [PATCH] 3rdparty: import KDSingleApplication This was removed (in favor of DBus activation) but DBus activation does not work; re-import the latest version, now under the MIT license. Pull in only src/ and the license file, though. https://github.com/KDAB/KDSingleApplication 9dc8b2f61638aa1c4dbf49d38f8b97178974409f --- .reuse/dep5 | 4 + 3rdparty/kdsingleapplication/CMakeLists.txt | 65 ++++ .../KDSingleApplicationConfig.cmake.in | 7 + 3rdparty/kdsingleapplication/LICENSE.MIT.txt | 22 ++ .../kdsingleapplication.cpp | 123 +++++++ .../kdsingleapplication/kdsingleapplication.h | 62 ++++ .../kdsingleapplication_lib.h | 37 +++ .../kdsingleapplication_localsocket.cpp | 304 ++++++++++++++++++ .../kdsingleapplication_localsocket_p.h | 133 ++++++++ 3rdparty/kdsingleapplication/src.pro | 20 ++ 10 files changed, 777 insertions(+) create mode 100644 3rdparty/kdsingleapplication/CMakeLists.txt create mode 100644 3rdparty/kdsingleapplication/KDSingleApplicationConfig.cmake.in create mode 100644 3rdparty/kdsingleapplication/LICENSE.MIT.txt create mode 100644 3rdparty/kdsingleapplication/kdsingleapplication.cpp create mode 100644 3rdparty/kdsingleapplication/kdsingleapplication.h create mode 100644 3rdparty/kdsingleapplication/kdsingleapplication_lib.h create mode 100644 3rdparty/kdsingleapplication/kdsingleapplication_localsocket.cpp create mode 100644 3rdparty/kdsingleapplication/kdsingleapplication_localsocket_p.h create mode 100644 3rdparty/kdsingleapplication/src.pro diff --git a/.reuse/dep5 b/.reuse/dep5 index cfd836c01..09ac6c165 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -15,6 +15,10 @@ Files: man/calamares.8 License: GPL-3.0-or-later Copyright: 2017 Jonathan Carter +Files: 3rdparty/kdsingleapplication/* +License: MIT +Copyright: Copyright (C) 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@ kdab.com + ### BUILD ARTIFACTS / NOT SOURCE # # QRC Files are basically build artifacts diff --git a/3rdparty/kdsingleapplication/CMakeLists.txt b/3rdparty/kdsingleapplication/CMakeLists.txt new file mode 100644 index 000000000..c345a2b6a --- /dev/null +++ b/3rdparty/kdsingleapplication/CMakeLists.txt @@ -0,0 +1,65 @@ +set(KDSINGLEAPPLICATION_SRCS + kdsingleapplication.cpp + kdsingleapplication_localsocket.cpp) + +set(KDSINGLEAPPLICATION_INSTALLABLE_INCLUDES + kdsingleapplication.h + kdsingleapplication_lib.h + ) + +set(KDSINGLEAPPLICATION_NON_INSTALLABLE_INCLUDES + kdsingleapplication_localsocket_p.h + ) + +if (${PROJECT_NAME}_STATIC) + add_library(kdsingleapplication STATIC ${KDSINGLEAPPLICATION_INSTALLABLE_INCLUDES} ${KDSINGLEAPPLICATION_SRCS}) + target_compile_definitions(kdsingleapplication PUBLIC KDSINGLEAPPLICATION_STATIC_BUILD) +else() + add_library(kdsingleapplication SHARED ${KDSINGLEAPPLICATION_INSTALLABLE_INCLUDES} ${KDSINGLEAPPLICATION_SRCS}) + target_compile_definitions(kdsingleapplication PRIVATE KDSINGLEAPPLICATION_SHARED_BUILD) +endif() + +include(GNUInstallDirs) + +set(KDSINGLEAPPLICATION_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}/kdsingleapplication) + +target_include_directories(kdsingleapplication + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +if (WIN32) + target_link_libraries(kdsingleapplication kernel32) +endif() +target_link_libraries(kdsingleapplication Qt5::Core Qt5::Network) + +install (FILES ${KDSINGLEAPPLICATION_INSTALLABLE_INCLUDES} DESTINATION ${KDSINGLEAPPLICATION_INCLUDEDIR}) + +install (TARGETS kdsingleapplication + EXPORT kdsingleapplicationTargets + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + KDSingleApplicationConfigVersion.cmake + VERSION ${PACKAGE_VERSION} + COMPATIBILITY AnyNewerVersion + ) + +install(EXPORT kdsingleapplicationTargets + FILE KDSingleApplicationTargets.cmake + NAMESPACE KDAB:: + DESTINATION lib/cmake/KDSingleApplication + ) +configure_file(KDSingleApplicationConfig.cmake.in KDSingleApplicationConfig.cmake @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KDSingleApplicationConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/KDSingleApplicationConfigVersion.cmake" + DESTINATION lib/cmake/KDSingleApplication + ) diff --git a/3rdparty/kdsingleapplication/KDSingleApplicationConfig.cmake.in b/3rdparty/kdsingleapplication/KDSingleApplicationConfig.cmake.in new file mode 100644 index 000000000..0bfc1646b --- /dev/null +++ b/3rdparty/kdsingleapplication/KDSingleApplicationConfig.cmake.in @@ -0,0 +1,7 @@ +include(CMakeFindDependencyMacro) + +find_dependency(Qt5Widgets REQUIRED) +find_dependency(Qt5Network REQUIRED) + +# Add the targets file +include("${CMAKE_CURRENT_LIST_DIR}/KDSingleApplicationTargets.cmake") diff --git a/3rdparty/kdsingleapplication/LICENSE.MIT.txt b/3rdparty/kdsingleapplication/LICENSE.MIT.txt new file mode 100644 index 000000000..de636926c --- /dev/null +++ b/3rdparty/kdsingleapplication/LICENSE.MIT.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (C) 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + +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. + diff --git a/3rdparty/kdsingleapplication/kdsingleapplication.cpp b/3rdparty/kdsingleapplication/kdsingleapplication.cpp new file mode 100644 index 000000000..bd876c6e2 --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication.cpp @@ -0,0 +1,123 @@ +/* + MIT License + + Copyright (C) 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "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 000000000..c2ef5e495 --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication.h @@ -0,0 +1,62 @@ +/* + MIT License + + Copyright (C) 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef 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 000000000..5749b22ce --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication_lib.h @@ -0,0 +1,37 @@ +/* + MIT License + + Copyright (C) 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef 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 000000000..32492db23 --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication_localsocket.cpp @@ -0,0 +1,304 @@ +/* + MIT License + + Copyright (C) 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "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) + 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 + return; + } + + 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()) + 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())) + return false; + } + + qCDebug(kdsaLocalSocket) << "Bytes written, now disconnecting" << "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired(); + + socket.disconnectFromServer(); + + if (socket.state() == QLocalSocket::UnconnectedState) + return true; + + if (!socket.waitForDisconnected(deadline.remainingTime())) + return false; + + qCDebug(kdsaLocalSocket) << "Disconnected -- success!"; + + return true; +} + +void KDSingleApplicationLocalSocket::handleNewConnection() +{ + Q_ASSERT(m_localServer); + + QLocalSocket *socket = m_localServer->nextPendingConnection(); + + qCDebug(kdsaLocalSocket) << "Got new connection on" << m_socketName << "state" << socket->state(); + + Connection c(socket); + + c.readDataConnection = QObjectConnectionHolder( + connect(c.socket.get(), &QLocalSocket::readyRead, + this, &KDSingleApplicationLocalSocket::readDataFromSecondary)); + + c.secondaryDisconnectedConnection = QObjectConnectionHolder( + connect(c.socket.get(), &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 000000000..1dd47cabd --- /dev/null +++ b/3rdparty/kdsingleapplication/kdsingleapplication_localsocket_p.h @@ -0,0 +1,133 @@ +/* + MIT License + + Copyright (C) 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef 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/kdsingleapplication/src.pro b/3rdparty/kdsingleapplication/src.pro new file mode 100644 index 000000000..28883fa56 --- /dev/null +++ b/3rdparty/kdsingleapplication/src.pro @@ -0,0 +1,20 @@ +include(../common.pri) + +TEMPLATE = lib +TARGET = kdsingleapplication +QT = core network +CONFIG += static + +SOURCES += \ + kdsingleapplication.cpp \ + kdsingleapplication_localsocket.cpp \ + +HEADERS += \ + kdsingleapplication.h \ + kdsingleapplication_lib.h \ + kdsingleapplication_localsocket_p.h \ + +DEFINES += \ + KDSINGLEAPPLICATION_BUILD + +win32:LIBS += -lkernel32