#pragma once #include #include #include #include #include #include "logos_api.h" class QTimer; // Forward-declared, not included: logos_qt_host_core.h pulls in nlohmann/json, // and MainUIBackend, UIPluginManager, PackageCoordinator and PluginLoader all // include this header without touching the facade. namespace logos { namespace qt { class QtLogosCore; } } // CoreModuleManager — the app's module-management surface over the SDK facade. // // Every call into liblogos (known/loaded module lists, load/unload, cascade // unload, stats) funnels through this class; UIPluginManager, PackageManager // and MainUIBackend never touch the C API directly. // // The char*/char** marshalling and the `delete[]`-not-`free()` ownership rule // live once, in logos-qt-sdk's `logos::qt::QtLogosCore` // (`logos_qt_host_core.h`) over logos-cpp-sdk's `logos::host::LogosCore`. // Declaring that ABI a second time in the same image is an ODR hazard with no // diagnostic. // // What stays here is what the facade has no basis to decide: the poll interval, // which thread the timer lives on, when "the module set changed" is announced, // and the QML-facing key names. The poller is a single 2s QTimer that reads // per-module CPU/memory and emits coreModulesChanged() so the Modules tab // re-reads via Q_PROPERTY. class CoreModuleManager : public QObject { Q_OBJECT public: // `core` is the process-wide facade, owned by main() and outliving this // object. Must not be null. explicit CoreModuleManager(LogosAPI* logosAPI, logos::qt::QtLogosCore* core, QObject* parent = nullptr); ~CoreModuleManager() override; // Thin wrappers over QtLogosCore. Callers never see a raw C string. QStringList knownModules() const; QStringList loadedModules() const; // Loads with forward dependencies resolved. Returns true on success. bool loadModule(const QString& name); // Returns true on success. Does NOT cascade — see // unloadModuleWithDependents. bool unloadModule(const QString& name); // Tears down `name` and every currently-loaded module that depends on it, // leaves-first. False if any step failed — the cascade may still have made // progress, so callers should refresh their UI state. bool unloadModuleWithDependents(const QString& name); // Cached as of the last timer tick, so up to ~2s stale; empty for modules // the poller hasn't seen yet. QVariantMap moduleStats(const QString& name) const; // Re-scans every plugin directory, then emits coreModulesChanged(). Called // by the Modules tab's Reload button and after install/uninstall reshapes // the known set. Q_INVOKABLE void refresh(); // Introspection, serialised to JSON for QML. On failure getMethods/ // getEvents return "[]" and callMethod returns error JSON; a disconnected // module is a normal transient state, not an error. Q_INVOKABLE QString getMethods(const QString& moduleName); Q_INVOKABLE QString getEvents(const QString& moduleName); Q_INVOKABLE QString callMethod(const QString& moduleName, const QString& methodName, const QString& argsJson); signals: // Emitted by refresh() and after every stats tick. MainUIBackend forwards // it into its own same-named signal, which is what QML binds to. void coreModulesChanged(); private slots: void updateModuleStats(); private: LogosAPI* m_logosAPI; // not owned logos::qt::QtLogosCore* m_core; // not owned; owned by main() QTimer* m_statsTimer; // owned (parent=this) QMap m_moduleStats; };