diff --git a/example/AppInfo.h b/example/AppInfo.h index 1c09604..64f27fa 100644 --- a/example/AppInfo.h +++ b/example/AppInfo.h @@ -13,6 +13,7 @@ class AppInfo : public QObject public: explicit AppInfo(QObject *parent = nullptr); Q_INVOKABLE void changeLang(const QString& locale); + Q_SIGNAL void activeWindow(); }; #endif // APPINFO_H diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 461e98e..467dbf1 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -30,6 +30,7 @@ set(SOURCES lang/Lang.cpp lang/Zh.cpp lang/En.cpp + IPC.cpp ) set(HEADERS @@ -39,6 +40,7 @@ set(HEADERS lang/Lang.h lang/Zh.h lang/En.h + IPC.h ) set(RESOURCES diff --git a/example/IPC.cpp b/example/IPC.cpp new file mode 100644 index 0000000..68533e9 --- /dev/null +++ b/example/IPC.cpp @@ -0,0 +1,255 @@ +#include "IPC.h" + +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +IPC::IPC(uint32_t profileId) + : profileId{profileId} + , globalMemory{"ipc-" IPC_PROTOCOL_VERSION} +{ + qRegisterMetaType("IPCEventHandler"); + timer.setInterval(EVENT_TIMER_MS); + timer.setSingleShot(true); + connect(&timer, &QTimer::timeout, this, &IPC::processEvents); + std::default_random_engine randEngine((std::random_device())()); + std::uniform_int_distribution distribution; + globalId = distribution(randEngine); + qDebug() << "Our global IPC ID is " << globalId; + if (globalMemory.create(sizeof(IPCMemory))) { + if (globalMemory.lock()) { + IPCMemory* mem = global(); + memset(mem, 0, sizeof(IPCMemory)); + mem->globalId = globalId; + mem->lastProcessed = time(nullptr); + globalMemory.unlock(); + } else { + qWarning() << "Couldn't lock to take ownership"; + } + } else if (globalMemory.attach()) { + qDebug() << "Attaching to the global shared memory"; + } else { + qDebug() << "Failed to attach to the global shared memory, giving up. Error:" + << globalMemory.error(); + return; + } + + processEvents(); +} + +IPC::~IPC() +{ + if (!globalMemory.lock()) { + qWarning() << "Failed to lock in ~IPC"; + return; + } + + if (isCurrentOwnerNoLock()) { + global()->globalId = 0; + } + globalMemory.unlock(); +} + +time_t IPC::postEvent(const QString& name, const QByteArray& data, uint32_t dest) +{ + QByteArray binName = name.toUtf8(); + if (binName.length() > (int32_t)sizeof(IPCEvent::name)) { + return 0; + } + + if (data.length() > (int32_t)sizeof(IPCEvent::data)) { + return 0; + } + + if (!globalMemory.lock()) { + qDebug() << "Failed to lock in postEvent()"; + return 0; + } + + IPCEvent* evt = nullptr; + IPCMemory* mem = global(); + time_t result = 0; + + for (uint32_t i = 0; !evt && i < EVENT_QUEUE_SIZE; ++i) { + if (mem->events[i].posted == 0) { + evt = &mem->events[i]; + } + } + + if (evt) { + memset(evt, 0, sizeof(IPCEvent)); + memcpy(evt->name, binName.constData(), binName.length()); + memcpy(evt->data, data.constData(), data.length()); + mem->lastEvent = evt->posted = result = qMax(mem->lastEvent + 1, time(nullptr)); + evt->dest = dest; + evt->sender = GetCurrentProcessId(); + qDebug() << "postEvent " << name << "to" << dest; + } + globalMemory.unlock(); + return result; +} + +bool IPC::isCurrentOwner() +{ + if (globalMemory.lock()) { + const bool isOwner = isCurrentOwnerNoLock(); + globalMemory.unlock(); + return isOwner; + } else { + qWarning() << "isCurrentOwner failed to lock, returning false"; + return false; + } +} + +void IPC::registerEventHandler(const QString& name, IPCEventHandler handler) +{ + eventHandlers[name] = handler; +} + +bool IPC::isEventAccepted(time_t time) +{ + bool result = false; + if (!globalMemory.lock()) { + return result; + } + + if (difftime(global()->lastProcessed, time) > 0) { + IPCMemory* mem = global(); + for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; ++i) { + if (mem->events[i].posted == time && mem->events[i].processed) { + result = mem->events[i].accepted; + break; + } + } + } + globalMemory.unlock(); + + return result; +} + +bool IPC::waitUntilAccepted(time_t postTime, int32_t timeout /*=-1*/) +{ + bool result = false; + time_t start = time(nullptr); + forever + { + result = isEventAccepted(postTime); + if (result || (timeout > 0 && difftime(time(nullptr), start) >= timeout)) { + break; + } + + qApp->processEvents(); + QThread::msleep(0); + } + return result; +} + +bool IPC::isAttached() const +{ + return globalMemory.isAttached(); +} + +void IPC::setProfileId(uint32_t profileId) +{ + this->profileId = profileId; +} + +IPC::IPCEvent* IPC::fetchEvent() +{ + IPCMemory* mem = global(); + for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; ++i) { + IPCEvent* evt = &mem->events[i]; + if ((evt->processed && difftime(time(nullptr), evt->processed) > EVENT_GC_TIMEOUT) + || (!evt->processed && difftime(time(nullptr), evt->posted) > EVENT_GC_TIMEOUT)) { + memset(evt, 0, sizeof(IPCEvent)); + } + + if (evt->posted && !evt->processed && evt->sender != GetCurrentProcessId() + && (evt->dest == profileId || (evt->dest == 0 && isCurrentOwnerNoLock()))) { + return evt; + } + } + + return nullptr; +} + +bool IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg) +{ + bool result = false; + if (QThread::currentThread() == qApp->thread()) { + result = handler(arg); + } else { + QMetaObject::invokeMethod(this, "runEventHandler", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), Q_ARG(IPCEventHandler, handler), + Q_ARG(const QByteArray&, arg)); + } + return result; +} + +void IPC::processEvents() +{ + if (!globalMemory.lock()) { + timer.start(); + return; + } + + IPCMemory* mem = global(); + + if (mem->globalId == globalId) { + mem->lastProcessed = time(nullptr); + } else { + if (difftime(time(nullptr), mem->lastProcessed) >= OWNERSHIP_TIMEOUT_S) { + qDebug() << "Previous owner timed out, taking ownership" << mem->globalId << "->" + << globalId; + memset(mem, 0, sizeof(IPCMemory)); + mem->globalId = globalId; + mem->lastProcessed = time(nullptr); + } + } + + while (IPCEvent* evt = fetchEvent()) { + QString name = QString::fromUtf8(evt->name); + auto it = eventHandlers.find(name); + if (it != eventHandlers.end()) { + evt->accepted = runEventHandler(it.value(), evt->data); + qDebug() << "Processed event:" << name << "posted:" << evt->posted + << "accepted:" << evt->accepted; + if (evt->dest == 0) { + if (evt->accepted) { + evt->processed = time(nullptr); + } + } else { + evt->processed = time(nullptr); + } + } else { + qDebug() << "Received event:" << name << "without handler"; + qDebug() << "Available handlers:" << eventHandlers.keys(); + } + } + + globalMemory.unlock(); + timer.start(); +} + +bool IPC::isCurrentOwnerNoLock() +{ + const void* const data = globalMemory.data(); + if (!data) { + qWarning() << "isCurrentOwnerNoLock failed to access the memory, returning false"; + return false; + } + return (*static_cast(data) == globalId); +} + +IPC::IPCMemory* IPC::global() +{ + return static_cast(globalMemory.data()); +} diff --git a/example/IPC.h b/example/IPC.h new file mode 100644 index 0000000..5f0077c --- /dev/null +++ b/example/IPC.h @@ -0,0 +1,75 @@ +#ifndef IPC_H +#define IPC_H + +#include +#include +#include +#include +#include +#include + +using IPCEventHandler = std::function; + +#define IPC_PROTOCOL_VERSION "1" + +class IPC : public QObject +{ + Q_OBJECT + +protected: + static const int EVENT_TIMER_MS = 1000; + static const int EVENT_GC_TIMEOUT = 5; + static const int EVENT_QUEUE_SIZE = 32; + static const int OWNERSHIP_TIMEOUT_S = 5; + +public: + IPC(uint32_t profileId); + ~IPC(); + + struct IPCEvent + { + uint32_t dest; + int32_t sender; + char name[16]; + char data[128]; + time_t posted; + time_t processed; + uint32_t flags; + bool accepted; + bool global; + }; + + struct IPCMemory + { + uint64_t globalId; + time_t lastEvent; + time_t lastProcessed; + IPCEvent events[IPC::EVENT_QUEUE_SIZE]; + }; + + time_t postEvent(const QString& name, const QByteArray& data = QByteArray(), uint32_t dest = 0); + bool isCurrentOwner(); + void registerEventHandler(const QString& name, IPCEventHandler handler); + bool isEventAccepted(time_t time); + bool waitUntilAccepted(time_t time, int32_t timeout = -1); + bool isAttached() const; + +public slots: + void setProfileId(uint32_t profileId); + +private: + IPCMemory* global(); + bool runEventHandler(IPCEventHandler handler, const QByteArray& arg); + IPCEvent* fetchEvent(); + void processEvents(); + bool isCurrentOwnerNoLock(); + +private: + QTimer timer; + uint64_t globalId; + uint32_t profileId; + QSharedMemory globalMemory; + QMap eventHandlers; +}; + +#endif // IPC_H diff --git a/example/example.pro b/example/example.pro index 624ad33..d00dd62 100644 --- a/example/example.pro +++ b/example/example.pro @@ -8,7 +8,8 @@ HEADERS += \ lang/Zh.h \ stdafx.h \ ChatController.h \ - AppInfo.h + AppInfo.h \ + IPC.h SOURCES += \ ChatController.cpp \ @@ -16,7 +17,8 @@ SOURCES += \ lang/En.cpp \ lang/Lang.cpp \ lang/Zh.cpp \ - main.cpp + main.cpp \ + IPC.cpp RESOURCES += qml.qrc diff --git a/example/main.cpp b/example/main.cpp index 9c5b854..5ab1b8a 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -7,6 +7,7 @@ #include "lang/Lang.h" #include "AppInfo.h" #include "ChatController.h" +#include "IPC.h" int main(int argc, char *argv[]) { @@ -19,12 +20,25 @@ int main(int argc, char *argv[]) QGuiApplication::setOrganizationName("ZhuZiChu"); QGuiApplication::setOrganizationDomain("https://zhuzichu520.github.io"); QGuiApplication::setApplicationName("FluentUI"); -// QQuickWindow::setGraphicsApi(QSGRendererInterface::Software); + // QQuickWindow::setGraphicsApi(QSGRendererInterface::Software); QGuiApplication app(argc, argv); + AppInfo* appInfo = new AppInfo(); + IPC ipc(0); + QString activeWindowEvent = "activeWindow"; + if(!ipc.isCurrentOwner()){ + ipc.postEvent(activeWindowEvent,QString().toUtf8(),0); + delete appInfo; + return 0; + } + if(ipc.isAttached()){ + ipc.registerEventHandler(activeWindowEvent,[&appInfo](const QByteArray& data){ + Q_EMIT appInfo->activeWindow(); + return true; + }); + } app.setQuitOnLastWindowClosed(false); QQmlApplicationEngine engine; qmlRegisterType("Controller",1,0,"ChatController"); - AppInfo* appInfo = new AppInfo(); QQmlContext * context = engine.rootContext(); Lang* lang = appInfo->lang(); context->setContextProperty("lang",lang); @@ -34,10 +48,10 @@ int main(int argc, char *argv[]) context->setContextProperty("appInfo",appInfo); const QUrl url(QStringLiteral("qrc:/App.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, - &app, [url](QObject *obj, const QUrl &objUrl) { - if (!obj && url == objUrl) - QCoreApplication::exit(-1); - }, Qt::QueuedConnection); + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); engine.load(url); return app.exec(); } diff --git a/example/page/MainPage.qml b/example/page/MainPage.qml index 08b17cd..4380381 100644 --- a/example/page/MainPage.qml +++ b/example/page/MainPage.qml @@ -20,6 +20,15 @@ FluWindow { event.accepted = false } + Connections{ + target: appInfo + function onActiveWindow(){ + window.show() + window.raise() + window.requestActivate() + } + } + FluAppBar{ id:appbar z:9