From 58d2e773fd5c9711cdfeba9eea7e25bbfb60e504 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Fri, 24 Apr 2026 14:50:08 +0200 Subject: [PATCH] feat: allow applets to be shared modules --- programs/GNUmakefile | 22 ++- programs/data/config/desktop-applets.toml | 5 + programs/include/gui/desktop_applet.hpp | 37 +++++ programs/libs/libprintersapplet/Makefile | 69 ++++++++ .../obj/src/libprintersapplet.d | 6 + .../obj/src/libprintersapplet.o | Bin 0 -> 17392 bytes .../src/libprintersapplet.cpp | 28 ++++ programs/src/desktop/Makefile | 2 +- programs/src/desktop/apps/apps_common.hpp | 12 ++ .../apps/filemanager/filemanager_internal.hpp | 9 ++ .../desktop/apps/filemanager/filesystem.cpp | 27 +++- .../desktop/apps/filemanager/navigation.cpp | 10 +- programs/src/desktop/shared_applets.cpp | 151 ++++++++++++++++++ .../sysroot/include/gui/desktop_applet.hpp | 37 +++++ 14 files changed, 405 insertions(+), 10 deletions(-) create mode 100644 programs/data/config/desktop-applets.toml create mode 100644 programs/include/gui/desktop_applet.hpp create mode 100644 programs/libs/libprintersapplet/Makefile create mode 100644 programs/libs/libprintersapplet/obj/src/libprintersapplet.d create mode 100644 programs/libs/libprintersapplet/obj/src/libprintersapplet.o create mode 100644 programs/libs/libprintersapplet/src/libprintersapplet.cpp create mode 100644 programs/src/desktop/shared_applets.cpp create mode 100644 template/sysroot/include/gui/desktop_applet.hpp diff --git a/programs/GNUmakefile b/programs/GNUmakefile index 42dcb26..e16a294 100644 --- a/programs/GNUmakefile +++ b/programs/GNUmakefile @@ -78,12 +78,17 @@ WWWDST := $(patsubst $(WWWDIR)/%,$(BINDIR)/www/%,$(WWWSRC)) # CA certificate bundle. CA_CERTS := $(BINDIR)/etc/ca-certificates.crt +# System config TOML files bundled into bin/config/. +CONFIGDIR := data/config +CONFIGSRC := $(wildcard $(CONFIGDIR)/*.toml) +CONFIGDST := $(patsubst $(CONFIGDIR)/%,$(BINDIR)/config/%,$(CONFIGSRC)) + # Common shared assets (wallpapers, etc.) COMMONKEEP := $(BINDIR)/common/.keep -.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs icons fonts bearssl libc tls libjpeg libjpegwrite install-apps libloader libs libhello test_dialogs test_dl crashpad +.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader libs libhello test_dialogs test_dl crashpad -all: bearssl libc libjpeg libjpegwrite tls libloader libs libhello $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs login desktop shell icons fonts install-apps test_dialogs test_dl crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(COMMONKEEP) +all: bearssl libc libjpeg libjpegwrite tls libloader libs libhello $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs login desktop shell icons fonts install-apps test_dialogs test_dl crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(COMMONKEEP) # Build BearSSL static library (cross-compiled for freestanding x86_64). BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc) @@ -111,6 +116,7 @@ libloader: libc # Build shared libraries (libmath.lib etc). libs: libloader $(MAKE) -C libs/libmath + $(MAKE) -C libs/libprintersapplet # Build libhello shared GUI library. libhello: libloader @@ -216,8 +222,8 @@ login: bearssl libc shell: $(MAKE) -C src/shell -# Build desktop via its own Makefile (depends on libc, libjpeg, bearssl for user.h, and dialogs for duplicate-session warnings). -desktop: libc libjpeg bearssl libloader dialogs +# Build desktop via its own Makefile (depends on libc, libjpeg, bearssl for user.h, dialogs, and registry config files). +desktop: libc libjpeg bearssl libloader dialogs configs $(MAKE) -C src/desktop # Copy SVG icons for the desktop into bin/icons/. @@ -228,6 +234,9 @@ icons: fonts: ../scripts/copy_fonts.sh +# Copy default system config files into bin/config/. +configs: $(CONFIGDST) + # Build paint standalone GUI tool (depends on libc). paint: libc $(MAKE) -C src/paint @@ -308,6 +317,11 @@ $(CA_CERTS): data/ca-certificates.crt mkdir -p $(BINDIR)/etc cp $< $@ +# Copy system config files into bin/config/. +$(BINDIR)/config/%: $(CONFIGDIR)/% + mkdir -p $(BINDIR)/config + cp $< $@ + # Ensure common assets directory exists. $(COMMONKEEP): mkdir -p $(BINDIR)/common diff --git a/programs/data/config/desktop-applets.toml b/programs/data/config/desktop-applets.toml new file mode 100644 index 0000000..74cc874 --- /dev/null +++ b/programs/data/config/desktop-applets.toml @@ -0,0 +1,5 @@ +[applets.printers] +label = "Printers" +library = "0:/os/libprintersapplet.lib" +icon = "0:/icons/printer.svg" +enabled = true diff --git a/programs/include/gui/desktop_applet.hpp b/programs/include/gui/desktop_applet.hpp new file mode 100644 index 0000000..a82b549 --- /dev/null +++ b/programs/include/gui/desktop_applet.hpp @@ -0,0 +1,37 @@ +/* + * desktop_applet.hpp + * Shared-library desktop applet ABI for MontaukOS + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once + +#include + +namespace gui::desktop_applet { + +inline constexpr uint32_t ABI_VERSION = 1; +inline constexpr uint32_t CAP_SYSTEM_CONFIGURATION = 1u << 0; + +struct Host { + char current_user[32]; + char home_dir[128]; + char user_config_dir[128]; + bool is_admin; +}; + +struct Descriptor { + uint32_t abi_version; + uint32_t capabilities; + const char* applet_id; + const char* display_name; + const char* icon_path; +}; + +using QueryFn = const Descriptor* (*)(); +using OpenFn = bool (*)(const Host* host); + +inline constexpr const char* QUERY_SYMBOL = "desktop_applet_query_v1"; +inline constexpr const char* OPEN_SYMBOL = "desktop_applet_open_v1"; + +} // namespace gui::desktop_applet diff --git a/programs/libs/libprintersapplet/Makefile b/programs/libs/libprintersapplet/Makefile new file mode 100644 index 0000000..8ef4269 --- /dev/null +++ b/programs/libs/libprintersapplet/Makefile @@ -0,0 +1,69 @@ +# Makefile for the Printers shared desktop applet +# Copyright (c) 2026 Daniel Hammer + +MAKEFLAGS += -rR +.SUFFIXES: + +TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf- +ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),) + CXX := $(TOOLCHAIN_PREFIX)g++ + LD := $(TOOLCHAIN_PREFIX)ld +else + CXX := g++ + LD := ld +endif + +OUTDIR := ../../bin/os +OBJDIR := obj +TARGET := $(OUTDIR)/libprintersapplet.lib + +CXXFLAGS := \ + -std=gnu++20 \ + -g -O2 -pipe \ + -Wall \ + -Wextra \ + -nostdinc \ + -ffreestanding \ + -fno-stack-protector \ + -fno-stack-check \ + -fPIC \ + -fno-rtti \ + -fno-exceptions \ + -ffunction-sections \ + -fdata-sections \ + -m64 \ + -march=x86-64 \ + -mno-red-zone \ + -mcmodel=small \ + -MMD -MP \ + -I ../../include \ + -isystem ../../include/libc \ + -isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \ + -isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include + +LDFLAGS := \ + -shared \ + --build-id=none \ + --hash-style=sysv \ + -m elf_x86_64 \ + -z max-page-size=0x1000 + +SRCS := src/libprintersapplet.cpp +OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o)) + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJS) Makefile + mkdir -p $(OUTDIR) + $(LD) $(LDFLAGS) $(OBJS) -o $@ + +$(OBJDIR)/%.o: %.cpp Makefile + mkdir -p $(dir $@) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +-include $(OBJS:.o=.d) + +clean: + rm -rf $(OBJDIR) $(TARGET) diff --git a/programs/libs/libprintersapplet/obj/src/libprintersapplet.d b/programs/libs/libprintersapplet/obj/src/libprintersapplet.d new file mode 100644 index 0000000..78abc7d --- /dev/null +++ b/programs/libs/libprintersapplet/obj/src/libprintersapplet.d @@ -0,0 +1,6 @@ +obj/src/libprintersapplet.o: src/libprintersapplet.cpp \ + ../../include/gui/desktop_applet.hpp ../../include/montauk/syscall.h \ + ../../include/Api/Syscall.hpp +../../include/gui/desktop_applet.hpp: +../../include/montauk/syscall.h: +../../include/Api/Syscall.hpp: diff --git a/programs/libs/libprintersapplet/obj/src/libprintersapplet.o b/programs/libs/libprintersapplet/obj/src/libprintersapplet.o new file mode 100644 index 0000000000000000000000000000000000000000..7f5df688232e873191579a918afe0fc480361c73 GIT binary patch literal 17392 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 DXq>vr literal 0 HcmV?d00001 diff --git a/programs/libs/libprintersapplet/src/libprintersapplet.cpp b/programs/libs/libprintersapplet/src/libprintersapplet.cpp new file mode 100644 index 0000000..2d98655 --- /dev/null +++ b/programs/libs/libprintersapplet/src/libprintersapplet.cpp @@ -0,0 +1,28 @@ +/* + * libprintersapplet.cpp + * Shared desktop applet that launches the Printers configuration tool + * Copyright (c) 2026 Daniel Hammer +*/ + +#include +#include + +using gui::desktop_applet::Descriptor; +using gui::desktop_applet::Host; + +static constexpr Descriptor kAppletDescriptor = { + gui::desktop_applet::ABI_VERSION, + gui::desktop_applet::CAP_SYSTEM_CONFIGURATION, + "printers", + "Printers", + "0:/icons/printer.svg", +}; + +extern "C" const Descriptor* desktop_applet_query_v1() { + return &kAppletDescriptor; +} + +extern "C" bool desktop_applet_open_v1(const Host* host) { + (void)host; + return montauk::spawn("0:/apps/printers/printers.elf") >= 0; +} diff --git a/programs/src/desktop/Makefile b/programs/src/desktop/Makefile index ca9a7b1..cc60486 100644 --- a/programs/src/desktop/Makefile +++ b/programs/src/desktop/Makefile @@ -68,7 +68,7 @@ LDFLAGS := \ # ---- C++ source files ---- -CORE_SRCS := main.cpp window.cpp panel.cpp compose.cpp input.cpp dialogs.cpp launcher.cpp desktop_builtin.cpp font_data.cpp stb_truetype_impl.cpp +CORE_SRCS := main.cpp window.cpp panel.cpp compose.cpp input.cpp dialogs.cpp launcher.cpp desktop_builtin.cpp shared_applets.cpp font_data.cpp stb_truetype_impl.cpp APP_SRCS := $(sort $(shell find apps -name '*.cpp' -print)) SRCS := $(CORE_SRCS) $(APP_SRCS) OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o)) diff --git a/programs/src/desktop/apps/apps_common.hpp b/programs/src/desktop/apps/apps_common.hpp index 8eafc8c..092faad 100644 --- a/programs/src/desktop/apps/apps_common.hpp +++ b/programs/src/desktop/apps/apps_common.hpp @@ -190,6 +190,18 @@ SvgIcon* desktop_builtin_menu_icon(DesktopState* ds, DesktopBuiltinId id); void desktop_launch_builtin(DesktopState* ds, DesktopBuiltinId id); void desktop_lock_screen(DesktopState* ds); +struct DesktopSharedAppletEntry { + char id[48]; + char label[64]; + char icon_path[128]; + char library_path[128]; +}; + +int desktop_list_system_configuration_applets(DesktopState* ds, + DesktopSharedAppletEntry* out, + int max_entries); +bool desktop_launch_shared_applet(DesktopState* ds, const char* lib_path); + // ============================================================================ // Forward declarations for desktop launch entry points // ============================================================================ diff --git a/programs/src/desktop/apps/filemanager/filemanager_internal.hpp b/programs/src/desktop/apps/filemanager/filemanager_internal.hpp index 7332706..8ac9afe 100644 --- a/programs/src/desktop/apps/filemanager/filemanager_internal.hpp +++ b/programs/src/desktop/apps/filemanager/filemanager_internal.hpp @@ -30,14 +30,23 @@ enum FileManagerVirtualViewKind : uint8_t { FM_VIRTUAL_VIEW_SYSTEM_TOOLS, }; +enum FileManagerVirtualEntryKind : uint8_t { + FM_VIRTUAL_ENTRY_NONE = 0, + FM_VIRTUAL_ENTRY_EXTERNAL_APP, + FM_VIRTUAL_ENTRY_BUILTIN, + FM_VIRTUAL_ENTRY_SHARED_APPLET, +}; + struct FileManagerLocation { char path[256]; FileManagerVirtualViewKind virtual_view; }; struct FileManagerVirtualEntry { + FileManagerVirtualEntryKind kind; int external_app_index; DesktopBuiltinId builtin_id; + char shared_applet_path[128]; SvgIcon icon_lg; SvgIcon icon_sm; }; diff --git a/programs/src/desktop/apps/filemanager/filesystem.cpp b/programs/src/desktop/apps/filemanager/filesystem.cpp index 9dfcf77..93498b3 100644 --- a/programs/src/desktop/apps/filemanager/filesystem.cpp +++ b/programs/src/desktop/apps/filemanager/filesystem.cpp @@ -27,8 +27,10 @@ static void filemanager_reset_list_state(FileManagerState* fm) { static void filemanager_reset_virtual_entry(FileManagerVirtualEntry* entry) { if (!entry) return; + entry->kind = FM_VIRTUAL_ENTRY_NONE; entry->external_app_index = -1; entry->builtin_id = DESKTOP_BUILTIN_NONE; + entry->shared_applet_path[0] = '\0'; entry->icon_lg = {}; entry->icon_sm = {}; } @@ -50,8 +52,10 @@ static void filemanager_add_root_entry(FileManagerState* fm, static void filemanager_add_virtual_entry(FileManagerState* fm, const char* name, const char* icon_path, + FileManagerVirtualEntryKind kind, int external_index, - DesktopBuiltinId builtin_id) { + DesktopBuiltinId builtin_id, + const char* shared_applet_path) { if (!fm || fm->entry_count >= 64) return; int idx = fm->entry_count; @@ -61,8 +65,14 @@ static void filemanager_add_virtual_entry(FileManagerState* fm, fm->is_dir[idx] = false; fm->drive_indices[idx] = -1; filemanager_reset_virtual_entry(&fm->virtual_entries[idx]); + fm->virtual_entries[idx].kind = kind; fm->virtual_entries[idx].external_app_index = external_index; fm->virtual_entries[idx].builtin_id = builtin_id; + if (shared_applet_path && shared_applet_path[0]) { + montauk::strncpy(fm->virtual_entries[idx].shared_applet_path, + shared_applet_path, + sizeof(fm->virtual_entries[idx].shared_applet_path)); + } if (icon_path && icon_path[0]) { Color defColor = colors::ICON_COLOR; @@ -344,8 +354,9 @@ void filemanager_read_apps(FileManagerState* fm) { } filemanager_add_virtual_entry(fm, name, icon_path, + FM_VIRTUAL_ENTRY_EXTERNAL_APP, filemanager_find_external_app(ds, bin_path), - DESKTOP_BUILTIN_NONE); + DESKTOP_BUILTIN_NONE, nullptr); doc.destroy(); } @@ -361,7 +372,17 @@ void filemanager_read_system_tools(FileManagerState* fm) { for (int i = 0; i < builtin_count; i++) { if (!builtins[i].system_tools_applet) continue; filemanager_add_virtual_entry(fm, builtins[i].label, builtins[i].icon_path, - -1, builtins[i].id); + FM_VIRTUAL_ENTRY_BUILTIN, + -1, builtins[i].id, nullptr); + } + + DesktopSharedAppletEntry applets[32]; + int applet_count = desktop_list_system_configuration_applets(fm->desktop, applets, 32); + for (int i = 0; i < applet_count && fm->entry_count < 64; i++) { + filemanager_add_virtual_entry(fm, applets[i].label, applets[i].icon_path, + FM_VIRTUAL_ENTRY_SHARED_APPLET, + -1, DESKTOP_BUILTIN_NONE, + applets[i].library_path); } filemanager_reset_list_state(fm); diff --git a/programs/src/desktop/apps/filemanager/navigation.cpp b/programs/src/desktop/apps/filemanager/navigation.cpp index 5aaff13..642d5bd 100644 --- a/programs/src/desktop/apps/filemanager/navigation.cpp +++ b/programs/src/desktop/apps/filemanager/navigation.cpp @@ -197,17 +197,23 @@ void filemanager_open_entry(FileManagerState* fm, int idx) { if (filemanager_is_virtual_view(fm) && fm->entry_types[idx] == FM_ENTRY_VIRTUAL_APP) { DesktopState* ds = fm->desktop; const FileManagerVirtualEntry& entry = fm->virtual_entries[idx]; - if (ds && entry.builtin_id != DESKTOP_BUILTIN_NONE) { + if (ds && entry.kind == FM_VIRTUAL_ENTRY_BUILTIN + && entry.builtin_id != DESKTOP_BUILTIN_NONE) { desktop_launch_builtin(ds, entry.builtin_id); return; } - if (ds && entry.external_app_index >= 0 && entry.external_app_index < ds->external_app_count) { + if (ds && entry.kind == FM_VIRTUAL_ENTRY_EXTERNAL_APP + && entry.external_app_index >= 0 + && entry.external_app_index < ds->external_app_count) { const ExternalApp& app = ds->external_apps[entry.external_app_index]; if (app.launch_with_home) { montauk::spawn(app.binary_path, ds->home_dir); } else { montauk::spawn(app.binary_path); } + } else if (ds && entry.kind == FM_VIRTUAL_ENTRY_SHARED_APPLET + && entry.shared_applet_path[0] != '\0') { + desktop_launch_shared_applet(ds, entry.shared_applet_path); } return; } diff --git a/programs/src/desktop/shared_applets.cpp b/programs/src/desktop/shared_applets.cpp new file mode 100644 index 0000000..7d3da42 --- /dev/null +++ b/programs/src/desktop/shared_applets.cpp @@ -0,0 +1,151 @@ +/* + * shared_applets.cpp + * Shared-library desktop applet discovery 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 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, + 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->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 void sort_shared_applets(DesktopSharedAppletEntry* entries, int count) { + if (!entries || count <= 1) return; + + for (int i = 1; i < count; i++) { + DesktopSharedAppletEntry 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; + } +} + +} // namespace + +int desktop_list_system_configuration_applets(DesktopState* ds, + DesktopSharedAppletEntry* out, + int max_entries) { + (void)ds; + if (!out || max_entries <= 0) return 0; + + auto doc = montauk::config::load(APPLET_CONFIG_NAME); + int written = 0; + 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; + + const char* applet_id = applet_id_from_table_key(item->key); + if (!applet_id) 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; + + const char* label = applet_field_string(doc, item->key, "label", applet_id); + const char* icon = applet_field_string(doc, item->key, "icon", DEFAULT_APPLET_ICON); + + montauk::memset(&out[written], 0, sizeof(out[written])); + montauk::strncpy(out[written].id, applet_id, sizeof(out[written].id)); + montauk::strncpy(out[written].label, label[0] ? label : library, sizeof(out[written].label)); + montauk::strncpy(out[written].icon_path, + icon[0] ? icon : DEFAULT_APPLET_ICON, + sizeof(out[written].icon_path)); + montauk::strncpy(out[written].library_path, library, sizeof(out[written].library_path)); + written++; + } + + doc.destroy(); + sort_shared_applets(out, written); + return written; +} + +bool desktop_launch_shared_applet(DesktopState* ds, const char* lib_path) { + if (!ds || !lib_path || !lib_path[0]) return false; + + LibHandle* lib = libloader::dlopen(lib_path); + if (!lib) return false; + + const Descriptor* desc = nullptr; + OpenFn open = nullptr; + bool ok = resolve_shared_applet(lib, &desc, &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; +} diff --git a/template/sysroot/include/gui/desktop_applet.hpp b/template/sysroot/include/gui/desktop_applet.hpp new file mode 100644 index 0000000..a82b549 --- /dev/null +++ b/template/sysroot/include/gui/desktop_applet.hpp @@ -0,0 +1,37 @@ +/* + * desktop_applet.hpp + * Shared-library desktop applet ABI for MontaukOS + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once + +#include + +namespace gui::desktop_applet { + +inline constexpr uint32_t ABI_VERSION = 1; +inline constexpr uint32_t CAP_SYSTEM_CONFIGURATION = 1u << 0; + +struct Host { + char current_user[32]; + char home_dir[128]; + char user_config_dir[128]; + bool is_admin; +}; + +struct Descriptor { + uint32_t abi_version; + uint32_t capabilities; + const char* applet_id; + const char* display_name; + const char* icon_path; +}; + +using QueryFn = const Descriptor* (*)(); +using OpenFn = bool (*)(const Host* host); + +inline constexpr const char* QUERY_SYMBOL = "desktop_applet_query_v1"; +inline constexpr const char* OPEN_SYMBOL = "desktop_applet_open_v1"; + +} // namespace gui::desktop_applet