223 lines
8.2 KiB
C++
223 lines
8.2 KiB
C++
/*
|
|
* desktop_applets.cpp
|
|
* Desktop applet registry and launch helpers
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include "desktop_internal.hpp"
|
|
#include <gui/desktop_applet.hpp>
|
|
#include <libloader/libloader.h>
|
|
#include <montauk/config.h>
|
|
|
|
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;
|
|
}
|
|
}
|