Files
logos-basecamp/src/MainUIBackend.cpp
T

721 lines
22 KiB
C++
Raw Normal View History

2025-12-10 10:57:16 -05:00
#include "MainUIBackend.h"
2026-04-06 11:00:30 -03:00
#include "PluginLoader.h"
2026-03-18 14:17:05 +01:00
#include "LogosBasecampPaths.h"
2025-12-10 10:57:16 -05:00
#include <QDebug>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
2025-12-10 10:57:16 -05:00
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
2026-01-02 12:31:56 -05:00
#include <QLibraryInfo>
#include <QTimer>
2026-01-02 10:40:56 -05:00
#include <QQmlContext>
#include <QQuickWidget>
#include <QQmlEngine>
#include <QQmlError>
#include <QUrl>
2026-02-17 15:14:58 +01:00
#include <QIcon>
#include <QStandardPaths>
#include <QPointer>
2026-01-16 13:42:23 -05:00
#include <QFileDialog>
#include <memory>
#include <ViewModuleHost.h>
2025-12-10 10:57:16 -05:00
#include "logos_sdk.h"
#include "token_manager.h"
extern "C" {
char* logos_core_get_module_stats();
char* logos_core_process_plugin(const char* plugin_path);
2026-03-18 10:53:38 -04:00
char** logos_core_get_known_plugins();
char** logos_core_get_loaded_plugins();
int logos_core_load_plugin_with_dependencies(const char* plugin_name);
int logos_core_unload_plugin(const char* plugin_name);
void logos_core_refresh_plugins();
2025-12-10 10:57:16 -05:00
}
MainUIBackend::MainUIBackend(LogosAPI* logosAPI, QObject* parent)
: QObject(parent)
, m_currentActiveSectionIndex(0)
2025-12-10 10:57:16 -05:00
, m_logosAPI(logosAPI)
, m_ownsLogosAPI(false)
, m_statsTimer(nullptr)
2026-02-27 15:05:08 +01:00
, m_currentVisibleApp("")
2025-12-10 10:57:16 -05:00
{
if (!m_logosAPI) {
m_logosAPI = new LogosAPI("core", this);
m_ownsLogosAPI = true;
}
2026-04-06 11:00:30 -03:00
m_pluginLoader = new PluginLoader(m_logosAPI, this);
connect(m_pluginLoader, &PluginLoader::pluginLoaded,
this, &MainUIBackend::onPluginLoaded);
connect(m_pluginLoader, &PluginLoader::pluginLoadFailed,
this, &MainUIBackend::onPluginLoadFailed);
connect(m_pluginLoader, &PluginLoader::loadingChanged,
this, &MainUIBackend::loadingModulesChanged);
2025-12-10 10:57:16 -05:00
initializeSections();
2025-12-10 10:57:16 -05:00
m_statsTimer = new QTimer(this);
connect(m_statsTimer, &QTimer::timeout, this, &MainUIBackend::updateModuleStats);
m_statsTimer->start(2000);
refreshCoreModules();
2026-01-13 16:42:24 -05:00
subscribeToPackageInstallationEvents();
// Initial async fetch of UI plugin metadata (emits uiModulesChanged + launcherAppsChanged when done)
fetchUiPluginMetadata();
2026-01-13 16:42:24 -05:00
2025-12-10 10:57:16 -05:00
qDebug() << "MainUIBackend created";
}
MainUIBackend::~MainUIBackend()
{
QStringList moduleNames = m_loadedUiModules.keys();
for (const QString& name : m_qmlPluginWidgets.keys()) {
if (!moduleNames.contains(name)) {
moduleNames.append(name);
}
}
2025-12-10 10:57:16 -05:00
for (const QString& name : moduleNames) {
unloadUiModule(name);
}
}
2026-01-13 16:42:24 -05:00
void MainUIBackend::subscribeToPackageInstallationEvents()
{
if (!m_logosAPI) {
return;
}
LogosAPIClient* client = m_logosAPI->getClient("package_manager");
if (!client || !client->isConnected()) {
return;
}
LogosModules logos(m_logosAPI);
// Configure the package_manager module's directories so it knows where to install
logos.package_manager.setEmbeddedModulesDirectory(LogosBasecampPaths::embeddedModulesDirectory());
logos.package_manager.setUserModulesDirectory(LogosBasecampPaths::modulesDirectory());
logos.package_manager.setEmbeddedUiPluginsDirectory(LogosBasecampPaths::embeddedPluginsDirectory());
logos.package_manager.setUserUiPluginsDirectory(LogosBasecampPaths::pluginsDirectory());
logos.package_manager.on("corePluginFileInstalled", [this](const QVariantList& data) {
if (data.isEmpty()) return;
qDebug() << "Core module file installed:" << data[0].toString();
QTimer::singleShot(100, this, [this]() {
refreshCoreModules();
});
2026-01-13 16:42:24 -05:00
});
logos.package_manager.on("uiPluginFileInstalled", [this](const QVariantList& data) {
if (data.isEmpty()) return;
qDebug() << "UI plugin file installed:" << data[0].toString();
QTimer::singleShot(100, this, [this]() {
fetchUiPluginMetadata();
});
});
2026-01-13 16:42:24 -05:00
}
void MainUIBackend::initializeSections()
2025-12-10 10:57:16 -05:00
{
auto makeSection = [](const QString& name, const QString& iconPath, const QString& type) {
QVariantMap section;
section["name"] = name;
section["iconPath"] = iconPath;
section["type"] = type;
return section;
};
m_sections = QVariantList{
makeSection("Apps", "qrc:/icons/tent.png", "workspace"),
makeSection("Dashboard", "qrc:/icons/dashboard.png", "view"),
makeSection("Modules", "qrc:/icons/module.png", "view"),
makeSection("Settings", "qrc:/icons/settings.png", "view")
};
2025-12-10 10:57:16 -05:00
}
int MainUIBackend::currentActiveSectionIndex() const
2025-12-10 10:57:16 -05:00
{
return m_currentActiveSectionIndex;
2025-12-10 10:57:16 -05:00
}
void MainUIBackend::setCurrentActiveSectionIndex(int index)
2025-12-10 10:57:16 -05:00
{
// Valid indices: 0-3 (Apps, Dashboard, Modules, Settings)
if (m_currentActiveSectionIndex != index && index >= 0 && index < m_sections.size()) {
m_currentActiveSectionIndex = index;
emit currentActiveSectionIndexChanged();
// Check if we're navigating to Modules view
const QVariantMap section = m_sections[index].toMap();
const QString name = section.value("name").toString();
if (name == "Modules") {
fetchUiPluginMetadata();
2025-12-10 10:57:16 -05:00
refreshCoreModules();
}
}
}
QVariantList MainUIBackend::sections() const
2025-12-10 10:57:16 -05:00
{
return m_sections;
2025-12-10 10:57:16 -05:00
}
QVariantList MainUIBackend::uiModules() const
{
QVariantList modules;
QStringList availablePlugins = findAvailableUiPlugins();
for (const QString& pluginName : availablePlugins) {
QVariantMap module;
module["name"] = pluginName;
module["isLoaded"] = m_loadedUiModules.contains(pluginName) || m_qmlPluginWidgets.contains(pluginName);
2025-12-10 10:57:16 -05:00
module["isMainUi"] = (pluginName == "main_ui");
2026-02-26 11:05:34 -03:00
module["iconPath"] = getPluginIconPath(pluginName);
2025-12-10 10:57:16 -05:00
modules.append(module);
}
return modules;
}
void MainUIBackend::loadUiModule(const QString& moduleName)
{
qDebug() << "Loading UI module:" << moduleName;
2026-04-06 11:00:30 -03:00
if (m_loadedUiModules.contains(moduleName) || m_qmlPluginWidgets.contains(moduleName)) {
2025-12-10 10:57:16 -05:00
qDebug() << "Module" << moduleName << "is already loaded";
activateApp(moduleName);
return;
}
if (isQmlPlugin(moduleName)) {
const QVariantMap& meta = m_uiPluginMetadata.value(moduleName);
PluginLoadRequest request;
request.name = moduleName;
request.type = UIPluginType::UiQml;
request.installDir = meta.value("installDir").toString();
request.qmlViewPath = resolveQmlViewPath(meta);
request.iconPath = getPluginIconPath(moduleName, true);
if (hasBackendPlugin(moduleName))
request.mainFilePath = meta.value("mainFilePath").toString();
request.coreDependencies = meta.value("dependencies").toList();
m_pluginLoader->load(request);
2026-04-06 11:00:30 -03:00
return;
}
loadLegacyUiModule(moduleName);
2026-04-06 11:00:30 -03:00
}
void MainUIBackend::onPluginLoaded(const QString& name, QWidget* widget,
IComponent* component, UIPluginType type,
ViewModuleHost* viewHost)
2026-04-06 11:00:30 -03:00
{
if (component)
m_loadedUiModules[name] = component;
if (type != UIPluginType::Legacy)
2026-04-06 11:00:30 -03:00
m_qmlPluginWidgets[name] = qobject_cast<QQuickWidget*>(widget);
if (viewHost)
m_viewModuleHosts[name] = viewHost;
2026-04-06 11:00:30 -03:00
m_uiModuleWidgets[name] = widget;
m_loadedApps.insert(name);
2025-12-10 10:57:16 -05:00
emit uiModulesChanged();
emit launcherAppsChanged();
2026-04-06 11:00:30 -03:00
emit pluginWindowRequested(widget, name);
2025-12-10 10:57:16 -05:00
emit navigateToApps();
2026-04-06 11:00:30 -03:00
qDebug() << "Successfully loaded UI module:" << name;
}
void MainUIBackend::onPluginLoadFailed(const QString& name, const QString& error)
{
qWarning() << "Failed to load UI module" << name << ":" << error;
}
QStringList MainUIBackend::loadingModules() const
{
return m_pluginLoader->loadingPlugins();
2025-12-10 10:57:16 -05:00
}
void MainUIBackend::unloadUiModule(const QString& moduleName)
{
qDebug() << "Unloading UI module:" << moduleName;
bool isQml = m_qmlPluginWidgets.contains(moduleName);
bool isCpp = m_loadedUiModules.contains(moduleName);
if (!isQml && !isCpp) {
2025-12-10 10:57:16 -05:00
qDebug() << "Module" << moduleName << "is not loaded";
return;
}
QWidget* widget = m_uiModuleWidgets.value(moduleName);
IComponent* component = m_loadedUiModules.value(moduleName);
if (widget) {
emit pluginWindowRemoveRequested(widget);
}
if (component && widget) {
component->destroyWidget(widget);
}
if (isQml && widget) {
widget->deleteLater();
}
2025-12-10 10:57:16 -05:00
// Stop view module host process if this was a view module
if (m_viewModuleHosts.contains(moduleName)) {
m_viewModuleHosts[moduleName]->stop();
delete m_viewModuleHosts.take(moduleName);
}
2025-12-10 10:57:16 -05:00
m_loadedUiModules.remove(moduleName);
m_uiModuleWidgets.remove(moduleName);
m_qmlPluginWidgets.remove(moduleName);
2025-12-10 10:57:16 -05:00
m_loadedApps.remove(moduleName);
2025-12-10 10:57:16 -05:00
emit uiModulesChanged();
emit launcherAppsChanged();
2025-12-10 10:57:16 -05:00
qDebug() << "Successfully unloaded UI module:" << moduleName;
}
void MainUIBackend::activateApp(const QString& appName)
{
QWidget* widget = m_uiModuleWidgets.value(appName);
if (widget) {
emit pluginWindowActivateRequested(widget);
emit navigateToApps();
}
}
2026-02-27 15:05:08 +01:00
void MainUIBackend::setCurrentVisibleApp(const QString& pluginName)
{
if (m_currentVisibleApp != pluginName) {
m_currentVisibleApp = pluginName;
emit currentVisibleAppChanged();
emit launcherAppsChanged();
}
}
QString MainUIBackend::currentVisibleApp() const
{
return m_currentVisibleApp;
}
2025-12-10 10:57:16 -05:00
void MainUIBackend::onPluginWindowClosed(const QString& pluginName)
{
qDebug() << "Plugin window closed:" << pluginName;
// Called when user closes the plugin window (tab X or subwindow close). The MDI
// subwindow and plugin widget are already destroyed
2025-12-10 10:57:16 -05:00
if (m_loadedUiModules.contains(pluginName)) {
m_loadedUiModules.remove(pluginName);
m_uiModuleWidgets.remove(pluginName);
m_loadedApps.remove(pluginName);
emit uiModulesChanged();
emit launcherAppsChanged();
} else if (m_qmlPluginWidgets.contains(pluginName)) {
// Stop view module host process if applicable
if (m_viewModuleHosts.contains(pluginName)) {
m_viewModuleHosts[pluginName]->stop();
delete m_viewModuleHosts.take(pluginName);
}
m_qmlPluginWidgets.remove(pluginName);
m_uiModuleWidgets.remove(pluginName);
m_loadedApps.remove(pluginName);
2025-12-10 10:57:16 -05:00
emit uiModulesChanged();
emit launcherAppsChanged();
}
}
QVariantList MainUIBackend::coreModules() const
{
QVariantList modules;
2026-03-18 10:53:38 -04:00
// Build the set of loaded plugins for status checking
QStringList loadedPlugins;
char** loaded = logos_core_get_loaded_plugins();
if (loaded) {
for (char** p = loaded; *p != nullptr; ++p) {
loadedPlugins << QString::fromUtf8(*p);
delete[] *p;
}
delete[] loaded;
}
char** known = logos_core_get_known_plugins();
if (!known) {
2025-12-10 10:57:16 -05:00
return modules;
}
2026-03-18 10:53:38 -04:00
for (char** p = known; *p != nullptr; ++p) {
QString name = QString::fromUtf8(*p);
delete[] *p;
2025-12-10 10:57:16 -05:00
QVariantMap module;
module["name"] = name;
2026-03-18 10:53:38 -04:00
module["isLoaded"] = loadedPlugins.contains(name);
2025-12-10 10:57:16 -05:00
if (m_moduleStats.contains(name)) {
module["cpu"] = m_moduleStats[name]["cpu"];
module["memory"] = m_moduleStats[name]["memory"];
} else {
module["cpu"] = "0.0";
module["memory"] = "0.0";
}
2026-03-18 10:53:38 -04:00
2025-12-10 10:57:16 -05:00
modules.append(module);
}
2026-03-18 10:53:38 -04:00
delete[] known;
2025-12-10 10:57:16 -05:00
return modules;
}
void MainUIBackend::loadCoreModule(const QString& moduleName)
{
qDebug() << "Loading core module:" << moduleName;
2026-03-18 10:53:38 -04:00
bool success = logos_core_load_plugin_with_dependencies(moduleName.toUtf8().constData()) == 1;
2025-12-10 10:57:16 -05:00
if (success) {
qDebug() << "Successfully loaded core module:" << moduleName;
emit coreModulesChanged();
} else {
qDebug() << "Failed to load core module:" << moduleName;
}
}
void MainUIBackend::unloadCoreModule(const QString& moduleName)
{
qDebug() << "Unloading core module:" << moduleName;
2026-03-18 10:53:38 -04:00
bool success = logos_core_unload_plugin(moduleName.toUtf8().constData()) == 1;
2025-12-10 10:57:16 -05:00
if (success) {
qDebug() << "Successfully unloaded core module:" << moduleName;
emit coreModulesChanged();
} else {
qDebug() << "Failed to unload core module:" << moduleName;
}
}
void MainUIBackend::refreshCoreModules()
{
// Re-scan all plugin directories via logos_core C API
logos_core_refresh_plugins();
2026-02-26 11:05:34 -03:00
2025-12-10 10:57:16 -05:00
emit coreModulesChanged();
}
QString MainUIBackend::getCoreModuleMethods(const QString& moduleName)
{
if (!m_logosAPI) {
return "[]";
}
LogosAPIClient* client = m_logosAPI->getClient(moduleName);
if (!client || !client->isConnected()) {
return "[]";
}
QVariant result = client->invokeRemoteMethod(moduleName, "getMethods");
if (result.canConvert<QJsonArray>()) {
QJsonArray methods = result.toJsonArray();
QJsonDocument doc(methods);
return doc.toJson(QJsonDocument::Compact);
}
return "[]";
}
QString MainUIBackend::callCoreModuleMethod(const QString& moduleName, const QString& methodName, const QString& argsJson)
{
if (!m_logosAPI) {
return "{\"error\": \"LogosAPI not available\"}";
}
LogosAPIClient* client = m_logosAPI->getClient(moduleName);
if (!client || !client->isConnected()) {
return "{\"error\": \"Module not connected\"}";
}
QJsonDocument argsDoc = QJsonDocument::fromJson(argsJson.toUtf8());
QJsonArray argsArray = argsDoc.array();
QVariantList args;
for (const QJsonValue& val : argsArray) {
args.append(val.toVariant());
}
QVariant result;
if (args.isEmpty()) {
result = client->invokeRemoteMethod(moduleName, methodName);
} else if (args.size() == 1) {
result = client->invokeRemoteMethod(moduleName, methodName, args[0]);
} else if (args.size() == 2) {
result = client->invokeRemoteMethod(moduleName, methodName, args[0], args[1]);
} else if (args.size() == 3) {
result = client->invokeRemoteMethod(moduleName, methodName, args[0], args[1], args[2]);
} else {
return "{\"error\": \"Too many arguments\"}";
}
QJsonObject wrapper;
wrapper["result"] = QJsonValue::fromVariant(result);
QJsonDocument resultDoc(wrapper);
return resultDoc.toJson(QJsonDocument::Compact);
}
QVariantList MainUIBackend::launcherApps() const
{
QVariantList apps;
QStringList availablePlugins = findAvailableUiPlugins();
for (const QString& pluginName : availablePlugins) {
if (pluginName == "main_ui") {
continue;
}
QVariantMap app;
app["name"] = pluginName;
app["isLoaded"] = m_loadedApps.contains(pluginName);
2026-02-26 11:05:34 -03:00
app["iconPath"] = getPluginIconPath(pluginName);
2025-12-10 10:57:16 -05:00
apps.append(app);
}
return apps;
}
void MainUIBackend::onAppLauncherClicked(const QString& appName)
{
qDebug() << "App launcher clicked:" << appName;
2026-02-27 15:05:08 +01:00
setCurrentVisibleApp(appName);
2025-12-10 10:57:16 -05:00
if (m_loadedApps.contains(appName)) {
activateApp(appName);
} else {
loadUiModule(appName);
}
}
2026-01-14 11:38:01 -05:00
2026-01-16 13:42:23 -05:00
void MainUIBackend::openInstallPluginDialog()
{
2026-02-26 11:05:34 -03:00
QString filter = "LGX Package (*.lgx);;All Files (*)";
2026-01-16 13:42:23 -05:00
QString filePath = QFileDialog::getOpenFileName(nullptr, tr("Select Plugin to Install"), QString(), filter);
2026-02-26 11:05:34 -03:00
2026-01-16 13:42:23 -05:00
if (!filePath.isEmpty()) {
installPluginFromPath(filePath);
}
}
2026-01-14 11:38:01 -05:00
void MainUIBackend::installPluginFromPath(const QString& filePath)
{
2026-02-26 11:05:34 -03:00
LogosModules logos(m_logosAPI);
QPointer<MainUIBackend> self(this);
logos.package_manager.installPluginAsync(filePath, false, [self](QVariant result) {
if (!self) return;
Q_UNUSED(result);
self->refreshCoreModules();
self->fetchUiPluginMetadata();
});
2026-01-14 11:38:01 -05:00
}
2026-02-04 10:11:55 -05:00
2026-02-26 11:05:34 -03:00
QString MainUIBackend::getPluginType(const QString& name) const
{
const auto it = m_uiPluginMetadata.constFind(name);
if (it != m_uiPluginMetadata.cend()) {
return it->value("type").toString();
2026-02-26 11:05:34 -03:00
}
return QString();
2026-02-26 11:05:34 -03:00
}
bool MainUIBackend::isQmlPlugin(const QString& name) const
{
return getPluginType(name) == QStringLiteral("ui_qml");
}
QString MainUIBackend::resolveQmlViewPath(const QVariantMap& meta) const
{
// ui_qml contract: "view" is the QML entry point, relative to installDir.
const QString installDir = meta.value("installDir").toString();
const QString viewField = meta.value("view").toString();
if (viewField.isEmpty()) return QString();
return QDir(installDir).filePath(viewField);
}
void MainUIBackend::loadLegacyUiModule(const QString& moduleName)
{
if (m_pluginLoader->isLoading(moduleName)) {
qDebug() << "Module" << moduleName << "is already loading";
return;
}
PluginLoadRequest request;
request.name = moduleName;
request.type = UIPluginType::Legacy;
request.pluginPath = getPluginPath(moduleName);
request.iconPath = getPluginIconPath(moduleName, true);
if (m_uiPluginMetadata.contains(moduleName)) {
request.coreDependencies = m_uiPluginMetadata[moduleName].value("dependencies").toList();
}
m_pluginLoader->load(request);
}
bool MainUIBackend::hasBackendPlugin(const QString& name) const
{
// True iff the ui_qml plugin ships a backend Qt plugin lib alongside its
// QML view. For QML-only ui_qml modules, mainFilePath is empty (no
// backend). When a backend is present, mainFilePath points at the .so/.dylib.
if (!isQmlPlugin(name)) return false;
const QString mainPath = m_uiPluginMetadata.value(name).value("mainFilePath").toString();
if (mainPath.isEmpty()) return false;
return mainPath.endsWith(QStringLiteral(".so"), Qt::CaseInsensitive)
|| mainPath.endsWith(QStringLiteral(".dylib"), Qt::CaseInsensitive)
|| mainPath.endsWith(QStringLiteral(".dll"), Qt::CaseInsensitive);
}
2025-12-10 10:57:16 -05:00
QStringList MainUIBackend::findAvailableUiPlugins() const
{
return m_uiPluginMetadata.keys();
}
void MainUIBackend::fetchUiPluginMetadata()
{
if (!m_logosAPI) return;
LogosModules logos(m_logosAPI);
QPointer<MainUIBackend> self(this);
logos.package_manager.getInstalledUiPluginsAsync([self](QVariantList uiPlugins) {
if (!self) return;
self->m_uiPluginMetadata.clear();
for (const QVariant& item : uiPlugins) {
QVariantMap pluginInfo = item.toMap();
QString name = pluginInfo.value("name").toString();
if (name.isEmpty()) continue;
// ui_qml requires "view" (the QML entry point); "main" is optional.
// Other types require "mainFilePath" (the backend lib).
const QString type = pluginInfo.value("type").toString();
if (type == QStringLiteral("ui_qml")) {
if (pluginInfo.value("view").toString().isEmpty()) continue;
} else {
if (pluginInfo.value("mainFilePath").toString().isEmpty()) continue;
}
self->m_uiPluginMetadata[name] = pluginInfo;
}
emit self->uiModulesChanged();
emit self->launcherAppsChanged();
});
2025-12-10 10:57:16 -05:00
}
QString MainUIBackend::getPluginPath(const QString& name) const
{
// Only used by loadLegacyUiModule (type "ui"); ui_qml uses installDir + view.
const auto it = m_uiPluginMetadata.constFind(name);
if (it != m_uiPluginMetadata.cend()) {
return it->value("mainFilePath").toString();
}
return QString();
2025-12-10 10:57:16 -05:00
}
2026-02-26 11:05:34 -03:00
QString MainUIBackend::getPluginIconPath(const QString& pluginName, bool forWidgetIcon) const
2025-12-10 10:57:16 -05:00
{
if (!m_uiPluginMetadata.contains(pluginName)) {
return "";
}
const QVariantMap& meta = m_uiPluginMetadata[pluginName];
QString iconPath = meta.value("icon").toString();
QString installDir = meta.value("installDir").toString();
2026-02-17 15:14:58 +01:00
if (iconPath.isEmpty()) {
return "";
}
QDir pluginDir(installDir);
2026-02-26 11:05:34 -03:00
QString filePath = pluginDir.filePath(iconPath.startsWith(":/") ? iconPath.mid(2) : iconPath);
2026-02-17 15:14:58 +01:00
bool exists = QFile::exists(filePath);
if (forWidgetIcon) {
if (exists) {
return filePath;
}
2026-02-17 15:14:58 +01:00
if (iconPath.startsWith(":/")) {
qWarning() << "Plugin icon not on disk, using resource path; expected:" << filePath;
return iconPath;
}
qWarning() << "Plugin icon not found, expected:" << filePath;
return QString();
2025-12-10 10:57:16 -05:00
}
2026-02-17 15:14:58 +01:00
return exists ? QUrl::fromLocalFile(filePath).toString() : (iconPath.startsWith(":/") ? "qrc" + iconPath : QString());
2025-12-10 10:57:16 -05:00
}
void MainUIBackend::updateModuleStats()
{
char* stats_json = logos_core_get_module_stats();
if (!stats_json) {
return;
}
QString jsonStr = QString::fromUtf8(stats_json);
QJsonDocument doc = QJsonDocument::fromJson(stats_json);
free(stats_json);
if (doc.isNull()) {
qWarning() << "Failed to parse module stats JSON";
return;
}
QJsonArray modulesArray;
if (doc.isArray()) {
modulesArray = doc.array();
} else if (doc.isObject()) {
QJsonObject root = doc.object();
modulesArray = root["modules"].toArray();
}
for (const QJsonValue& val : modulesArray) {
QJsonObject moduleObj = val.toObject();
QString name = moduleObj["name"].toString();
if (!name.isEmpty()) {
QVariantMap stats;
double cpu = moduleObj["cpu_percent"].toDouble();
if (cpu == 0) cpu = moduleObj["cpu"].toDouble();
double memory = moduleObj["memory_mb"].toDouble();
if (memory == 0) memory = moduleObj["memory"].toDouble();
if (memory == 0) memory = moduleObj["memory_MB"].toDouble();
stats["cpu"] = QString::number(cpu, 'f', 1);
stats["memory"] = QString::number(memory, 'f', 1);
m_moduleStats[name] = stats;
}
}
emit coreModulesChanged();
}