feat: allow applets to be shared modules

This commit is contained in:
2026-04-24 14:50:08 +02:00
parent 00c3e37ecf
commit 58d2e773fd
14 changed files with 405 additions and 10 deletions
+18 -4
View File
@@ -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
@@ -0,0 +1,5 @@
[applets.printers]
label = "Printers"
library = "0:/os/libprintersapplet.lib"
icon = "0:/icons/printer.svg"
enabled = true
+37
View File
@@ -0,0 +1,37 @@
/*
* desktop_applet.hpp
* Shared-library desktop applet ABI for MontaukOS
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
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
+69
View File
@@ -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)
@@ -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:
@@ -0,0 +1,28 @@
/*
* libprintersapplet.cpp
* Shared desktop applet that launches the Printers configuration tool
* Copyright (c) 2026 Daniel Hammer
*/
#include <gui/desktop_applet.hpp>
#include <montauk/syscall.h>
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;
}
+1 -1
View File
@@ -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))
+12
View File
@@ -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
// ============================================================================
@@ -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;
};
@@ -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);
@@ -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;
}
+151
View File
@@ -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 <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 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;
}
@@ -0,0 +1,37 @@
/*
* desktop_applet.hpp
* Shared-library desktop applet ABI for MontaukOS
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
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