#pragma once #include #include #include #include #include #include class LogosAPI; class QRemoteObjectNode; class QAbstractItemModelReplica; class LogosViewReplicaFactory; class QPluginLoader; // ── LogosQmlBridge ─────────────────────────────────────────────────────────── // // Single entry point for QML to talk to modules. // // Non-view (backend) modules — go through LogosAPI IPC: // logos.callModule(name, method, [args]) // sync, returns JSON // logos.callModuleAsync(name, method, [args], cb, ms) // async, JSON payload // // View modules — each view module ships a typed replica factory plugin // (generated by logos_module() from a .rep file). The host loads it and // exposes the typed replica directly to QML, so this API is dead simple: // // property var backend: logos.module("my_view_module") // Text { text: backend.someProperty } // Button { onClicked: backend.someSlot() } // Connections { target: backend; function onSomeSignal() { ... } } // // // Enums declared in the .rep are available under the QML URI the // // factory registered (typically "Logos."): // import Logos.MyViewModule 1.0 // Text { text: backend.status === MyViewModule.Active ? "on" : "off" } // // // QAbstractItemModel* Q_PROPERTYs are still remoted separately: // ListView { model: logos.model("my_view_module", "items") } // class LogosQmlBridge : public QObject { Q_OBJECT public: explicit LogosQmlBridge(LogosAPI* api, QObject* parent = nullptr); // ── Backend (non-view) module calls via LogosAPI IPC ──────────────── Q_INVOKABLE QString callModule(const QString& module, const QString& method, const QVariantList& args = QVariantList()); // timeoutMs: if > 0, the callback is invoked with an error payload when // no reply arrives within that many milliseconds. Default 30000 (30s); // pass 0 to disable. Q_INVOKABLE void callModuleAsync(const QString& module, const QString& method, const QVariantList& args, QJSValue callback, int timeoutMs = 30000); // ── View module API ───────────────────────────────────────────────── // Returns the typed replica for a view module. Safe to call before the // replica is Valid — QML will see default-constructed values until then // and property NOTIFYs fire once the source meta arrives. Q_INVOKABLE QObject* module(const QString& moduleName); // Returns a QAbstractItemModelReplica* for a QAbstractItemModel* // Q_PROPERTY on the view module. ui-host auto-remotes every such // property as "/". Q_INVOKABLE QObject* model(const QString& moduleName, const QString& modelName); // True once the view module's replica is Valid (source meta received). Q_INVOKABLE bool isViewModuleReady(const QString& moduleName) const; // Watch a QRemoteObjectPendingCall returned by a replica slot call and // invoke callbacks with the result. Replaces QtRemoteObjects.watch() so // QML plugins don't need to import QtRemoteObjects (keeping the sandbox // locked down). // // Usage: // logos.watch(backend.add(1, 2), // function(value) { display.text = value }, // function(error) { console.log(error) }) Q_INVOKABLE void watch(const QVariant& pendingCall, QJSValue onSuccess, QJSValue onError = QJSValue()); // Called by the host application during setup. void setViewModuleSocket(const QString& moduleName, const QString& socketName); void setViewReplicaPlugin(const QString& moduleName, const QString& pluginPath); // Called when the view module's child process exits unexpectedly. void notifyViewModuleCrashed(const QString& moduleName); // ── Test-friendly helpers ─────────────────────────────────────────── // // Stateless serializer for a QVariant result into the JSON string // shape QML consumers expect. Exposed as a public static method so // unit tests (and callers who want the same format without going // through callModule()) can exercise it directly. static QString serializeResultForTesting(const QVariant& result); // Introspection helpers used by unit tests to assert routing state // without needing a real QRemoteObjectNode. bool hasViewModuleSocket(const QString& moduleName) const; QString viewModuleSocket(const QString& moduleName) const; QString viewReplicaPluginPath(const QString& moduleName) const; // Subscribe to events emitted by core (non-view) modules. When the // module emits an event, the moduleEventReceived signal fires. // // Usage: // Component.onCompleted: logos.onModuleEvent("calc_module", "resultReady") // Connections { // target: logos // function onModuleEventReceived(moduleName, eventName, data) { // if (eventName === "resultReady") // console.log("Got result:", JSON.stringify(data)) // } // } // // Returns true if the subscription succeeded. Q_INVOKABLE bool onModuleEvent(const QString& moduleName, const QString& eventName); signals: void viewModuleReadyChanged(const QString& moduleName, bool ready); void viewModuleCrashed(const QString& moduleName); void moduleEventReceived(const QString& moduleName, const QString& eventName, const QVariantList& data); private: void dropViewModuleCaches(const QString& moduleName); QRemoteObjectNode* getOrCreateNode(const QString& moduleName); LogosViewReplicaFactory* loadFactory(const QString& moduleName); LogosAPI* m_logosAPI; // Per-view-module state QMap m_viewModuleSockets; QMap m_replicaPluginPaths; QMap m_replicaNodes; QMap m_factoryLoaders; QMap m_factories; QMap m_replicas; QMap m_modelReplicas; };