/* * desktop_applets.cpp * Desktop applet registry and launch helpers * Copyright (c) 2026 Daniel Hammer */ #include "desktop_internal.hpp" #include #include #include namespace { using gui::desktop_applet::Descriptor; using gui::desktop_applet::OpenFn; using gui::desktop_applet::QueryFn; static constexpr const char* DEFAULT_APPLET_ICON = "0:/icons/preferences-system.svg"; static constexpr const char* APPLET_CONFIG_NAME = "desktop-applets"; static constexpr const char* APPLET_TABLE_PREFIX = "applets."; static constexpr uint32_t SYSTEM_CONFIGURATION_CAP = gui::desktop_applet::CAP_SYSTEM_CONFIGURATION; static void clear_applet_entry(DesktopAppletEntry* entry) { if (!entry) return; montauk::memset(entry, 0, sizeof(*entry)); entry->kind = DESKTOP_APPLET_NONE; entry->builtin_id = DESKTOP_BUILTIN_NONE; } static bool str_ends_with(const char* s, const char* suffix) { if (!s || !suffix) return false; int slen = montauk::slen(s); int tlen = montauk::slen(suffix); if (tlen > slen) return false; for (int i = 0; i < tlen; i++) { if (s[slen - tlen + i] != suffix[i]) return false; } return true; } static const char* applet_field_string(const montauk::toml::Doc& doc, const char* applet_key, const char* field, const char* def = "") { char key[256]; snprintf(key, sizeof(key), "%s.%s", applet_key, field); return doc.get_string(key, def); } static bool applet_field_bool(const montauk::toml::Doc& doc, const char* applet_key, const char* field, bool def = false) { char key[256]; snprintf(key, sizeof(key), "%s.%s", applet_key, field); return doc.get_bool(key, def); } static const char* applet_id_from_table_key(const char* table_key) { if (!table_key || !montauk::starts_with(table_key, APPLET_TABLE_PREFIX)) return nullptr; const char* applet_id = table_key + montauk::slen(APPLET_TABLE_PREFIX); return applet_id[0] ? applet_id : nullptr; } static bool resolve_shared_applet(LibHandle* lib, uint32_t required_capability, const Descriptor** out_desc, OpenFn* out_open) { if (out_desc) *out_desc = nullptr; if (out_open) *out_open = nullptr; if (!lib) return false; QueryFn query = (QueryFn)libloader::dlsym(lib, gui::desktop_applet::QUERY_SYMBOL); OpenFn open = (OpenFn)libloader::dlsym(lib, gui::desktop_applet::OPEN_SYMBOL); if (!query || !open) return false; const Descriptor* desc = query(); if (!desc || desc->abi_version != gui::desktop_applet::ABI_VERSION) return false; if ((desc->capabilities & required_capability) == 0) return false; if (!desc->applet_id || !desc->applet_id[0]) return false; if (!desc->display_name || !desc->display_name[0]) return false; if (out_desc) *out_desc = desc; if (out_open) *out_open = open; return true; } static bool load_shared_applet_metadata(const montauk::toml::Doc& doc, const char* table_key, const char* library, DesktopAppletEntry* out) { if (!out || !library || !library[0]) return false; LibHandle* lib = libloader::dlopen(library); if (!lib) return false; const Descriptor* desc = nullptr; bool ok = resolve_shared_applet(lib, SYSTEM_CONFIGURATION_CAP, &desc, nullptr); if (ok) { clear_applet_entry(out); out->kind = DESKTOP_APPLET_SHARED_LIBRARY; montauk::strncpy(out->id, desc->applet_id, sizeof(out->id)); montauk::strncpy(out->library_path, library, sizeof(out->library_path)); const char* label_override = applet_field_string(doc, table_key, "label", ""); const char* icon_override = applet_field_string(doc, table_key, "icon", ""); const char* icon = icon_override[0] ? icon_override : (desc->icon_path && desc->icon_path[0] ? desc->icon_path : DEFAULT_APPLET_ICON); montauk::strncpy(out->label, label_override[0] ? label_override : desc->display_name, sizeof(out->label)); montauk::strncpy(out->icon_path, icon, sizeof(out->icon_path)); } libloader::dlclose(lib); return ok; } static void sort_applets(DesktopAppletEntry* entries, int count) { if (!entries || count <= 1) return; for (int i = 1; i < count; i++) { DesktopAppletEntry tmp = entries[i]; int j = i - 1; while (j >= 0 && str_compare_ci(tmp.label, entries[j].label) < 0) { entries[j + 1] = entries[j]; j--; } entries[j + 1] = tmp; } } static bool launch_shared_applet(DesktopState* ds, const char* lib_path, uint32_t required_capability) { if (!ds || !lib_path || !lib_path[0]) return false; LibHandle* lib = libloader::dlopen(lib_path); if (!lib) return false; OpenFn open = nullptr; bool ok = resolve_shared_applet(lib, required_capability, nullptr, &open); if (ok) { gui::desktop_applet::Host host = {}; montauk::strncpy(host.current_user, ds->current_user, sizeof(host.current_user)); montauk::strncpy(host.home_dir, ds->home_dir, sizeof(host.home_dir)); montauk::strncpy(host.user_config_dir, ds->user_config_dir, sizeof(host.user_config_dir)); host.is_admin = ds->is_admin; ok = open(&host); } libloader::dlclose(lib); return ok; } } // namespace int desktop_list_system_configuration_applets(DesktopState* ds, DesktopAppletEntry* out, int max_entries) { (void)ds; if (!out || max_entries <= 0) return 0; int written = 0; int builtin_count = 0; const DesktopBuiltinEntry* builtins = desktop_builtin_registry(&builtin_count); for (int i = 0; i < builtin_count && written < max_entries; i++) { if (!builtins[i].system_configuration_id) continue; clear_applet_entry(&out[written]); out[written].kind = DESKTOP_APPLET_BUILTIN; out[written].builtin_id = builtins[i].id; montauk::strncpy(out[written].id, builtins[i].system_configuration_id, sizeof(out[written].id)); montauk::strncpy(out[written].label, builtins[i].label, sizeof(out[written].label)); if (builtins[i].icon_path) { montauk::strncpy(out[written].icon_path, builtins[i].icon_path, sizeof(out[written].icon_path)); } written++; } auto doc = montauk::config::load(APPLET_CONFIG_NAME); for (int i = 0; i < doc.entries.count && written < max_entries; i++) { montauk::toml::Value* item = doc.entries.items[i]; if (!item || item->type != montauk::toml::Type::Table || !item->key) continue; if (!applet_id_from_table_key(item->key)) continue; if (!applet_field_bool(doc, item->key, "enabled", true)) continue; const char* library = applet_field_string(doc, item->key, "library"); if (!library[0] || !str_ends_with(library, ".lib")) continue; if (load_shared_applet_metadata(doc, item->key, library, &out[written])) { written++; } } doc.destroy(); sort_applets(out, written); return written; } bool desktop_launch_system_configuration_applet(DesktopState* ds, const DesktopAppletEntry* applet) { if (!ds || !applet) return false; switch (applet->kind) { case DESKTOP_APPLET_BUILTIN: if (applet->builtin_id == DESKTOP_BUILTIN_NONE) return false; desktop_launch_builtin(ds, applet->builtin_id); return true; case DESKTOP_APPLET_SHARED_LIBRARY: return launch_shared_applet(ds, applet->library_path, SYSTEM_CONFIGURATION_CAP); default: return false; } }