refactor: unify desktop apps and Settings under item catalog

This commit is contained in:
2026-07-29 13:59:46 +01:00
parent 84ea28a978
commit 75ae7ede56
38 changed files with 322 additions and 797 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 6
#define MONTAUK_BUILD_NUMBER 7
+3 -12
View File
@@ -89,9 +89,9 @@ WPDIR := data/wallpapers
WPSRC := $(wildcard $(WPDIR)/*.jpg)
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader libs crashpad check-syscalls gen-syscalls
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
all: bearssl libc libjpeg libjpegwrite tls libloader libs devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap 2048 doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap 2048 doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
# 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)
@@ -112,16 +112,10 @@ libc:
tls: bearssl libc
$(MAKE) -C lib/tls
# Build libloader static library (used by the applet shared libs).
# Build the shared-library loader used by runtime GUI components.
libloader: libc
$(MAKE) -C src/libloader
# Build shared libraries (applet .lib files).
libs: libloader
$(MAKE) -C libs/libnetworkapplet
$(MAKE) -C libs/libprintersapplet
$(MAKE) -C libs/libtimezonesapplet
# Build fetch via its own Makefile (depends on bearssl, libc, and tls).
fetch: bearssl libc tls
$(MAKE) -C src/fetch
@@ -487,6 +481,3 @@ clean:
$(MAKE) -C src/dialogs clean
$(MAKE) -C src/crashpad clean
$(MAKE) -C src/libloader clean
$(MAKE) -C libs/libnetworkapplet clean
$(MAKE) -C libs/libprintersapplet clean
$(MAKE) -C libs/libtimezonesapplet clean
-17
View File
@@ -1,17 +0,0 @@
[applets.printers]
label = "Printers"
library = "0:/os/printersapplet.lib"
icon = "0:/icons/printer.svg"
enabled = true
[applets.network]
label = "Network"
library = "0:/os/networkapplet.lib"
icon = "0:/icons/network-wired.svg"
enabled = true
[applets.timezone]
label = "Time Zone"
library = "0:/os/timezonesapplet.lib"
icon = "0:/icons/preferences-system-time.svg"
enabled = true
+23 -6
View File
@@ -18,15 +18,32 @@ namespace gui {
static constexpr int MAX_WINDOWS = 32;
static constexpr int PANEL_HEIGHT = 32;
// External app discovered from 0:/apps/ manifest
struct ExternalApp {
enum DesktopItemSection : uint8_t {
DESKTOP_ITEM_SECTION_HIDDEN = 0,
DESKTOP_ITEM_SECTION_APPS,
DESKTOP_ITEM_SECTION_SETTINGS,
};
enum DesktopItemLaunchKind : uint8_t {
DESKTOP_ITEM_LAUNCH_EXECUTABLE = 0,
DESKTOP_ITEM_LAUNCH_BUILTIN,
};
// Launchable desktop item discovered from an app manifest or supplied by the
// desktop's built-in registry.
struct DesktopItem {
char id[48];
char name[48];
char binary_path[128];
char icon_path[128];
char category[24];
SvgIcon icon;
DesktopItemSection section;
DesktopItemLaunchKind launch_kind;
int builtin_id;
bool menu_visible;
bool launch_with_home;
bool admin_only;
};
enum LauncherItemKind : uint8_t {
@@ -150,10 +167,10 @@ struct DesktopState {
SvgIcon icon_volume;
// External apps discovered from 0:/apps/ manifests (dynamically grown)
ExternalApp* external_apps;
int external_app_count;
int external_app_capacity;
// Desktop items discovered from 0:/apps/ manifests (dynamically grown).
DesktopItem* desktop_items;
int desktop_item_count;
int desktop_item_capacity;
bool ctx_menu_open;
int ctx_menu_x, ctx_menu_y;
-37
View File
@@ -1,37 +0,0 @@
/*
* 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
@@ -1,69 +0,0 @@
# Makefile for the Network shared desktop applet
# Copyright (c) 2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
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)/networkapplet.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/libnetworkapplet.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)
@@ -1,28 +0,0 @@
/*
* libnetworkapplet.cpp
* Shared desktop applet that launches the Network 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,
"network",
"Network",
"0:/icons/network-wired.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/network/network.elf") >= 0;
}
-69
View File
@@ -1,69 +0,0 @@
# 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-montauk-
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)/printersapplet.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)
@@ -1,6 +0,0 @@
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:
@@ -1,28 +0,0 @@
/*
* 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;
}
-69
View File
@@ -1,69 +0,0 @@
# Makefile for the Time Zone shared desktop applet
# Copyright (c) 2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
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)/timezonesapplet.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/libtimezonesapplet.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)
@@ -1,6 +0,0 @@
obj/src/libtimezonesapplet.o: src/libtimezonesapplet.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:
@@ -1,28 +0,0 @@
/*
* libtimezonesapplet.cpp
* Shared desktop applet that launches the Time Zone 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,
"timezone",
"Time Zone",
"0:/icons/preferences-system-time.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/timezone/timezone.elf") >= 0;
}
+1 -1
View File
@@ -56,7 +56,7 @@ LDFLAGS := \
# ---- C++ source files ----
CORE_SRCS := main.cpp window.cpp panel.cpp compose.cpp input.cpp dialogs.cpp launcher.cpp desktop_builtin.cpp desktop_applets.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 desktop_catalog.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))
+5 -20
View File
@@ -232,26 +232,11 @@ SvgIcon* desktop_builtin_menu_icon(DesktopState* ds, DesktopBuiltinId id);
void desktop_launch_builtin(DesktopState* ds, DesktopBuiltinId id);
void desktop_lock_screen(DesktopState* ds);
enum DesktopAppletKind : int {
DESKTOP_APPLET_NONE = 0,
DESKTOP_APPLET_BUILTIN,
DESKTOP_APPLET_SHARED_LIBRARY,
};
struct DesktopAppletEntry {
DesktopAppletKind kind;
char id[48];
char label[64];
char icon_path[128];
DesktopBuiltinId builtin_id;
char library_path[128];
};
int desktop_list_system_configuration_applets(DesktopState* ds,
DesktopAppletEntry* out,
int max_entries);
bool desktop_launch_system_configuration_applet(DesktopState* ds,
const DesktopAppletEntry* applet);
int desktop_list_item_indices(DesktopState* ds,
DesktopItemSection section,
int* out,
int max_entries);
bool desktop_launch_item(DesktopState* ds, const DesktopItem* item);
// ============================================================================
// Forward declarations for desktop launch entry points
@@ -200,7 +200,7 @@ void filemanager_open_ctx_menu(FileManagerState* fm, int local_x, int local_y, i
if ((filemanager_is_computer_view(fm)
&& (tt == FM_ENTRY_DRIVE || tt == FM_ENTRY_HOME || tt == FM_ENTRY_APPS_ROOT
|| tt == FM_ENTRY_SPECIAL_DIR || tt == FM_ENTRY_SYSTEM_CONFIGURATION_ROOT))
|| (filemanager_is_virtual_view(fm) && tt == FM_ENTRY_VIRTUAL_APP)) {
|| (filemanager_is_virtual_view(fm) && tt == FM_ENTRY_VIRTUAL_ITEM)) {
// Drive, Home, Apps shortcut, or app entry: only Open
fm->ctx_items[fm->ctx_item_count++] = CTX_OPEN;
fm->ctx_items[fm->ctx_item_count++] = CTX_PROPERTIES;
+11 -3
View File
@@ -8,12 +8,20 @@
namespace filemanager {
static constexpr const char* FM_PATH_SYSTEM_CONFIGURATION = "virtual://system-configuration";
static constexpr const char* FM_PATH_APPS = "virtual://apps";
static constexpr const char* FM_PATH_SETTINGS = "virtual://settings";
static constexpr const char* FM_PATH_LEGACY_SYSTEM_CONFIGURATION =
"virtual://system-configuration";
static bool filemanager_open_virtual_path(FileManagerState* fm, const char* path) {
if (!fm || !path) return false;
if (montauk::streq(path, FM_PATH_SYSTEM_CONFIGURATION)) {
if (montauk::streq(path, FM_PATH_APPS)) {
filemanager_read_virtual_view(fm, FM_VIRTUAL_VIEW_APPS);
return true;
}
if (montauk::streq(path, FM_PATH_SETTINGS)
|| montauk::streq(path, FM_PATH_LEGACY_SYSTEM_CONFIGURATION)) {
filemanager_read_virtual_view(fm, FM_VIRTUAL_VIEW_SYSTEM_CONFIGURATION);
return true;
}
@@ -108,5 +116,5 @@ void open_filemanager_path(DesktopState* ds, const char* path) {
}
void open_system_configuration(DesktopState* ds) {
filemanager::open_filemanager_internal(ds, filemanager::FM_PATH_SYSTEM_CONFIGURATION);
filemanager::open_filemanager_internal(ds, filemanager::FM_PATH_SETTINGS);
}
@@ -20,7 +20,7 @@ enum FileManagerEntryType : int {
FM_ENTRY_DRIVE = 3,
FM_ENTRY_HOME = 4,
FM_ENTRY_APPS_ROOT = 5,
FM_ENTRY_VIRTUAL_APP = 6,
FM_ENTRY_VIRTUAL_ITEM = 6,
FM_ENTRY_SPECIAL_DIR = 7,
FM_ENTRY_SYSTEM_CONFIGURATION_ROOT = 8,
};
@@ -31,21 +31,13 @@ enum FileManagerVirtualViewKind : uint8_t {
FM_VIRTUAL_VIEW_SYSTEM_CONFIGURATION,
};
enum FileManagerVirtualEntryKind : uint8_t {
FM_VIRTUAL_ENTRY_NONE = 0,
FM_VIRTUAL_ENTRY_EXTERNAL_APP,
FM_VIRTUAL_ENTRY_SYSTEM_CONFIGURATION_APPLET,
};
struct FileManagerLocation {
char path[256];
FileManagerVirtualViewKind virtual_view;
};
struct FileManagerVirtualEntry {
FileManagerVirtualEntryKind kind;
int external_app_index;
DesktopAppletEntry applet;
int desktop_item_index;
SvgIcon icon_lg;
SvgIcon icon_sm;
};
@@ -110,11 +110,7 @@ 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;
montauk::memset(&entry->applet, 0, sizeof(entry->applet));
entry->applet.kind = DESKTOP_APPLET_NONE;
entry->applet.builtin_id = DESKTOP_BUILTIN_NONE;
entry->desktop_item_index = -1;
entry->icon_lg = {};
entry->icon_sm = {};
}
@@ -140,19 +136,17 @@ static void filemanager_add_root_entry(FileManagerState* fm,
static int filemanager_add_virtual_entry(FileManagerState* fm,
const char* name,
const char* icon_path,
FileManagerVirtualEntryKind kind) {
const char* icon_path) {
if (!fm || !filemanager_ensure_entry_capacity(fm, fm->entry_count + 1)) return -1;
int idx = fm->entry_count;
montauk::strncpy(fm->entry_names[idx], name, 63);
fm->entry_types[idx] = FM_ENTRY_VIRTUAL_APP;
fm->entry_types[idx] = FM_ENTRY_VIRTUAL_ITEM;
fm->entry_sizes[idx] = 0;
fm->entry_mtimes[idx] = 0;
fm->is_dir[idx] = false;
fm->drive_indices[idx] = -1;
filemanager_reset_virtual_entry(&fm->virtual_entries[idx]);
fm->virtual_entries[idx].kind = kind;
if (icon_path && icon_path[0]) {
Color defColor = colors::ICON_COLOR;
@@ -165,24 +159,32 @@ static int filemanager_add_virtual_entry(FileManagerState* fm,
return idx;
}
static void filemanager_add_external_app_entry(FileManagerState* fm,
const ExternalApp& app,
int external_index) {
int idx = filemanager_add_virtual_entry(fm, app.name, app.icon_path,
FM_VIRTUAL_ENTRY_EXTERNAL_APP);
static void filemanager_add_desktop_item_entry(FileManagerState* fm,
const DesktopItem& item,
int desktop_item_index) {
int idx = filemanager_add_virtual_entry(fm, item.name, item.icon_path);
if (idx >= 0) {
fm->virtual_entries[idx].external_app_index = external_index;
fm->virtual_entries[idx].desktop_item_index = desktop_item_index;
}
}
static void filemanager_add_system_configuration_applet_entry(
FileManagerState* fm,
const DesktopAppletEntry& applet) {
int idx = filemanager_add_virtual_entry(fm, applet.label, applet.icon_path,
FM_VIRTUAL_ENTRY_SYSTEM_CONFIGURATION_APPLET);
if (idx >= 0) {
fm->virtual_entries[idx].applet = applet;
static void filemanager_add_catalog_section(FileManagerState* fm,
DesktopItemSection section) {
if (!fm || !fm->desktop || fm->desktop->desktop_item_count <= 0) return;
int capacity = fm->desktop->desktop_item_count;
int* item_indices = (int*)montauk::malloc((uint64_t)capacity * sizeof(int));
if (!item_indices) return;
int item_count = desktop_list_item_indices(
fm->desktop, section, item_indices, capacity);
for (int i = 0; i < item_count; i++) {
int item_index = item_indices[i];
filemanager_add_desktop_item_entry(
fm, fm->desktop->desktop_items[item_index], item_index);
}
montauk::mfree(item_indices);
}
static void filemanager_begin_virtual_view(FileManagerState* fm, FileManagerVirtualViewKind view) {
@@ -418,25 +420,14 @@ void filemanager_read_virtual_view(FileManagerState* fm, FileManagerVirtualViewK
void filemanager_read_apps(FileManagerState* fm) {
filemanager_begin_virtual_view(fm, FM_VIRTUAL_VIEW_APPS);
DesktopState* ds = fm->desktop;
if (!ds) return;
for (int i = 0; i < ds->external_app_count; i++) {
filemanager_add_external_app_entry(fm, ds->external_apps[i], i);
}
filemanager_add_catalog_section(fm, DESKTOP_ITEM_SECTION_APPS);
filemanager_reset_list_state(fm);
}
void filemanager_read_system_configuration(FileManagerState* fm) {
filemanager_begin_virtual_view(fm, FM_VIRTUAL_VIEW_SYSTEM_CONFIGURATION);
DesktopAppletEntry applets[32];
int applet_count = desktop_list_system_configuration_applets(fm->desktop, applets, 32);
for (int i = 0; i < applet_count; i++) {
filemanager_add_system_configuration_applet_entry(fm, applets[i]);
}
filemanager_add_catalog_section(fm, DESKTOP_ITEM_SECTION_SETTINGS);
filemanager_reset_list_state(fm);
}
@@ -242,20 +242,12 @@ void filemanager_open_entry(FileManagerState* fm, int idx) {
return;
}
if (filemanager_is_virtual_view(fm) && fm->entry_types[idx] == FM_ENTRY_VIRTUAL_APP) {
if (filemanager_is_virtual_view(fm) && fm->entry_types[idx] == FM_ENTRY_VIRTUAL_ITEM) {
DesktopState* ds = fm->desktop;
const FileManagerVirtualEntry& entry = fm->virtual_entries[idx];
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_SYSTEM_CONFIGURATION_APPLET) {
desktop_launch_system_configuration_applet(ds, &entry.applet);
if (ds && entry.desktop_item_index >= 0
&& entry.desktop_item_index < ds->desktop_item_count) {
desktop_launch_item(ds, &ds->desktop_items[entry.desktop_item_index]);
}
return;
}
@@ -145,9 +145,9 @@ static const char* props_type_label(const FileManagerState* fm, int idx) {
case FM_ENTRY_APPS_ROOT: return "Apps Folder";
case FM_ENTRY_SPECIAL_DIR: return "Folder";
case FM_ENTRY_SYSTEM_CONFIGURATION_ROOT: return "Settings";
case FM_ENTRY_VIRTUAL_APP:
case FM_ENTRY_VIRTUAL_ITEM:
if (fm->virtual_view == FM_VIRTUAL_VIEW_SYSTEM_CONFIGURATION)
return "Settings Applet";
return "Setting";
return "Application";
case FM_ENTRY_FILE:
default:
@@ -192,21 +192,23 @@ static void props_build_path(const FileManagerState* fm, int idx, char* out, int
} else if (type == FM_ENTRY_DRIVE) {
props_drive_path(fm->drive_indices[idx], out, out_len);
} else if (type == FM_ENTRY_APPS_ROOT) {
props_copy(out, out_len, "Apps");
props_copy(out, out_len, "virtual://apps");
} else if (type == FM_ENTRY_SYSTEM_CONFIGURATION_ROOT) {
props_copy(out, out_len, "virtual://system-configuration");
props_copy(out, out_len, "virtual://settings");
}
return;
}
if (type == FM_ENTRY_VIRTUAL_APP && idx < fm->virtual_entry_count) {
if (type == FM_ENTRY_VIRTUAL_ITEM && idx < fm->virtual_entry_count) {
const FileManagerVirtualEntry& entry = fm->virtual_entries[idx];
if (entry.kind == FM_VIRTUAL_ENTRY_EXTERNAL_APP && ds &&
entry.external_app_index >= 0 &&
entry.external_app_index < ds->external_app_count) {
props_copy(out, out_len, ds->external_apps[entry.external_app_index].binary_path);
} else if (entry.kind == FM_VIRTUAL_ENTRY_SYSTEM_CONFIGURATION_APPLET) {
props_copy(out, out_len, "Settings applet");
if (ds && entry.desktop_item_index >= 0
&& entry.desktop_item_index < ds->desktop_item_count) {
const DesktopItem& item = ds->desktop_items[entry.desktop_item_index];
if (item.launch_kind == DESKTOP_ITEM_LAUNCH_EXECUTABLE) {
props_copy(out, out_len, item.binary_path);
} else {
props_copy(out, out_len, item.id);
}
}
}
}
@@ -243,14 +245,12 @@ static void props_choose_icon(FilePropertiesDialogState* st,
if (sfi >= 0 && ds && ds->icon_special_folder_lg[sfi].pixels) {
props_set_shared_icon(st, &ds->icon_special_folder_lg[sfi]);
} else if (type == FM_ENTRY_VIRTUAL_APP && idx < fm->virtual_entry_count) {
} else if (type == FM_ENTRY_VIRTUAL_ITEM && idx < fm->virtual_entry_count) {
const FileManagerVirtualEntry& entry = fm->virtual_entries[idx];
if (entry.kind == FM_VIRTUAL_ENTRY_EXTERNAL_APP && ds &&
entry.external_app_index >= 0 &&
entry.external_app_index < ds->external_app_count) {
props_load_owned_icon(st, ds->external_apps[entry.external_app_index].icon_path);
} else if (entry.kind == FM_VIRTUAL_ENTRY_SYSTEM_CONFIGURATION_APPLET) {
props_load_owned_icon(st, entry.applet.icon_path);
if (ds && entry.desktop_item_index >= 0
&& entry.desktop_item_index < ds->desktop_item_count) {
props_load_owned_icon(
st, ds->desktop_items[entry.desktop_item_index].icon_path);
}
} else if (ds && type == FM_ENTRY_SYSTEM_CONFIGURATION_ROOT &&
ds->icon_system_configuration_lg.pixels) {
@@ -360,7 +360,7 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) {
if (sfi >= 0 && ds && ds->icon_special_folder_lg[sfi].pixels) {
c.icon(icon_x, icon_y, ds->icon_special_folder_lg[sfi]);
} else if (fm->entry_types[i] == FM_ENTRY_VIRTUAL_APP
} else if (fm->entry_types[i] == FM_ENTRY_VIRTUAL_ITEM
&& i < fm->virtual_entry_count
&& fm->virtual_entries[i].icon_lg.pixels) {
c.icon(icon_x, icon_y, fm->virtual_entries[i].icon_lg);
@@ -500,7 +500,7 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) {
if (ico_y < list_y) { /* clipped by header repaint */ }
else if (sfi_sm >= 0 && ds && ds->icon_special_folder[sfi_sm].pixels) {
c.icon(ico_x, ico_y, ds->icon_special_folder[sfi_sm]);
} else if (fm->entry_types[i] == FM_ENTRY_VIRTUAL_APP
} else if (fm->entry_types[i] == FM_ENTRY_VIRTUAL_ITEM
&& i < fm->virtual_entry_count
&& fm->virtual_entries[i].icon_sm.pixels) {
c.icon(ico_x, ico_y, fm->virtual_entries[i].icon_sm);
@@ -588,9 +588,9 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) {
// Type
if (type_col_x > 160 && ty >= list_y && ty + fm_sfh <= c.h) {
const char* type_str = "File";
if (fm->entry_types[i] == FM_ENTRY_VIRTUAL_APP) {
if (fm->entry_types[i] == FM_ENTRY_VIRTUAL_ITEM) {
type_str = fm->virtual_view == FM_VIRTUAL_VIEW_SYSTEM_CONFIGURATION
? "Applet"
? "Setting"
: "App";
}
else if (fm->entry_types[i] == FM_ENTRY_APPS_ROOT) type_str = "Apps";
-222
View File
@@ -1,222 +0,0 @@
/*
* 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 = "config-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;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* desktop_builtin.cpp
* Shared builtin applet metadata and dispatch helpers
* Shared built-in desktop item metadata and dispatch helpers
* Copyright (c) 2026 Daniel Hammer
*/
+63
View File
@@ -0,0 +1,63 @@
/*
* desktop_catalog.cpp
* Unified catalog and launch helpers for desktop items
* Copyright (c) 2026 Daniel Hammer
*/
#include "desktop_internal.hpp"
namespace {
static void sort_item_indices(DesktopState* ds, int* indices, int count) {
for (int i = 1; i < count; i++) {
int tmp = indices[i];
int j = i - 1;
while (j >= 0
&& str_compare_ci(ds->desktop_items[tmp].name,
ds->desktop_items[indices[j]].name) < 0) {
indices[j + 1] = indices[j];
j--;
}
indices[j + 1] = tmp;
}
}
} // namespace
int desktop_list_item_indices(DesktopState* ds,
DesktopItemSection section,
int* out,
int max_entries) {
if (!ds || !out || max_entries <= 0) return 0;
int written = 0;
for (int i = 0; i < ds->desktop_item_count && written < max_entries; i++) {
const DesktopItem& item = ds->desktop_items[i];
if (item.section != section) continue;
if (item.admin_only && !ds->is_admin) continue;
out[written++] = i;
}
sort_item_indices(ds, out, written);
return written;
}
bool desktop_launch_item(DesktopState* ds, const DesktopItem* item) {
if (!ds || !item) return false;
if (item->admin_only && !ds->is_admin) return false;
switch (item->launch_kind) {
case DESKTOP_ITEM_LAUNCH_BUILTIN:
if (item->builtin_id < 0) return false;
desktop_launch_builtin(ds, (DesktopBuiltinId)item->builtin_id);
return true;
case DESKTOP_ITEM_LAUNCH_EXECUTABLE:
if (!item->binary_path[0]) return false;
if (item->launch_with_home) {
return montauk::spawn(item->binary_path, ds->home_dir) >= 0;
}
return montauk::spawn(item->binary_path) >= 0;
default:
return false;
}
}
+10 -16
View File
@@ -199,16 +199,6 @@ static void launcher_add_item(DesktopState* ds, LauncherItemKind kind,
item->ref_index = ref_index;
}
static void desktop_launch_external_app(DesktopState* ds, const ExternalApp* app) {
if (!app) return;
if (app->launch_with_home) {
montauk::spawn(app->binary_path, ds->home_dir);
} else {
montauk::spawn(app->binary_path);
}
}
static void desktop_build_launcher_items(DesktopState* ds) {
ds->launcher_item_count = 0;
@@ -219,11 +209,15 @@ static void desktop_build_launcher_items(DesktopState* ds) {
&ds->icon_filemanager, -1, nullptr,
"file manager browser");
for (int i = 0; i < ds->external_app_count; i++) {
ExternalApp* app = &ds->external_apps[i];
SvgIcon* icon = app->icon.pixels ? &app->icon : &ds->icon_exec;
for (int i = 0; i < ds->desktop_item_count; i++) {
DesktopItem* item = &ds->desktop_items[i];
if (item->admin_only && !ds->is_admin) continue;
SvgIcon* icon = item->icon.pixels ? &item->icon : &ds->icon_exec;
const char* category = item->section == DESKTOP_ITEM_SECTION_SETTINGS
? "Settings"
: item->category;
launcher_add_item(ds, LAUNCHER_ITEM_EXTERNAL_APP,
app->name, app->category,
item->name, category,
icon, i, nullptr, nullptr);
}
@@ -341,8 +335,8 @@ static void launcher_activate_selected(DesktopState* ds) {
LauncherItem* item = &ds->launcher_items[item_idx];
switch (item->kind) {
case LAUNCHER_ITEM_EXTERNAL_APP:
if (item->ref_index >= 0 && item->ref_index < ds->external_app_count) {
desktop_launch_external_app(ds, &ds->external_apps[item->ref_index]);
if (item->ref_index >= 0 && item->ref_index < ds->desktop_item_count) {
desktop_launch_item(ds, &ds->desktop_items[item->ref_index]);
}
break;
case LAUNCHER_ITEM_BUILTIN_FILES:
+88 -29
View File
@@ -35,22 +35,46 @@ static void extract_basename(char* out, int outSz, const char* entry) {
out[base_len] = '\0';
}
// Return a slot for the next external app, doubling the array as needed.
// Return a slot for the next desktop item, doubling the array as needed.
// Returns nullptr if allocation failed.
static ExternalApp* next_external_app_slot(DesktopState* ds) {
if (ds->external_app_count >= ds->external_app_capacity) {
int new_cap = ds->external_app_capacity ? ds->external_app_capacity * 2 : 32;
auto* grown = (ExternalApp*)montauk::realloc(
ds->external_apps, (uint64_t)new_cap * sizeof(ExternalApp));
static DesktopItem* next_desktop_item_slot(DesktopState* ds) {
if (ds->desktop_item_count >= ds->desktop_item_capacity) {
int new_cap = ds->desktop_item_capacity ? ds->desktop_item_capacity * 2 : 32;
auto* grown = (DesktopItem*)montauk::realloc(
ds->desktop_items, (uint64_t)new_cap * sizeof(DesktopItem));
if (!grown) return nullptr;
ds->external_apps = grown;
ds->external_app_capacity = new_cap;
ds->desktop_items = grown;
ds->desktop_item_capacity = new_cap;
}
return &ds->desktop_items[ds->desktop_item_count];
}
static void desktop_add_builtin_catalog_items(DesktopState* ds) {
int builtin_count = 0;
const DesktopBuiltinEntry* builtins = desktop_builtin_registry(&builtin_count);
for (int i = 0; i < builtin_count; i++) {
if (!builtins[i].system_configuration_id) continue;
DesktopItem* item = next_desktop_item_slot(ds);
if (!item) return;
montauk::memset(item, 0, sizeof(*item));
montauk::strncpy(item->id, builtins[i].system_configuration_id, sizeof(item->id));
montauk::strncpy(item->name, builtins[i].label, sizeof(item->name));
if (builtins[i].icon_path) {
montauk::strncpy(item->icon_path, builtins[i].icon_path, sizeof(item->icon_path));
}
SvgIcon* icon = desktop_builtin_menu_icon(ds, builtins[i].id);
if (icon) item->icon = *icon;
item->section = DESKTOP_ITEM_SECTION_SETTINGS;
item->launch_kind = DESKTOP_ITEM_LAUNCH_BUILTIN;
item->builtin_id = (int)builtins[i].id;
ds->desktop_item_count++;
}
return &ds->external_apps[ds->external_app_count];
}
void desktop_scan_apps(DesktopState* ds) {
ds->external_app_count = 0;
ds->desktop_item_count = 0;
desktop_add_builtin_catalog_items(ds);
// Ensure the apps directory exists
montauk::fmkdir("0:/apps");
@@ -94,44 +118,78 @@ void desktop_scan_apps(DesktopState* ds) {
montauk::mfree(text);
// Read manifest fields
const char* id = doc.get_string("app.id", dirname);
const char* name = doc.get_string("app.name", "Unknown");
const char* binary = doc.get_string("app.binary", "");
const char* icon_file = doc.get_string("app.icon", "");
const char* category = doc.get_string("menu.category", "Applications");
bool visible = doc.get_bool("menu.visible", true);
bool launch_with_home = doc.get_bool("launch.pass_home_dir", true);
if (binary[0] == '\0') {
const char* section_name = doc.get_string("desktop.section", "apps");
bool admin_only = doc.get_bool("desktop.admin_only", false);
if (id[0] == '\0' || binary[0] == '\0') {
montauk::print("desktop: skipping app manifest with missing id or binary\n");
doc.destroy();
continue;
}
ExternalApp* app = next_external_app_slot(ds);
if (!app) {
bool duplicate = false;
for (int existing = 0; existing < ds->desktop_item_count; existing++) {
if (montauk::streq(ds->desktop_items[existing].id, id)) {
duplicate = true;
break;
}
}
if (duplicate) {
montauk::print("desktop: skipping app manifest with duplicate id\n");
doc.destroy();
continue;
}
DesktopItemSection section = DESKTOP_ITEM_SECTION_APPS;
if (montauk::streq(section_name, "settings")) {
section = DESKTOP_ITEM_SECTION_SETTINGS;
} else if (montauk::streq(section_name, "hidden")) {
section = DESKTOP_ITEM_SECTION_HIDDEN;
} else if (!montauk::streq(section_name, "apps")) {
montauk::print("desktop: skipping app manifest with invalid desktop.section\n");
doc.destroy();
continue;
}
DesktopItem* item = next_desktop_item_slot(ds);
if (!item) {
doc.destroy();
return;
}
montauk::memset(item, 0, sizeof(*item));
montauk::strncpy(app->name, name, sizeof(app->name));
montauk::strncpy(app->category, category, sizeof(app->category));
app->menu_visible = visible;
app->launch_with_home = launch_with_home;
montauk::strncpy(item->id, id, sizeof(item->id));
montauk::strncpy(item->name, name, sizeof(item->name));
montauk::strncpy(item->category, category, sizeof(item->category));
item->menu_visible = visible;
item->launch_with_home = launch_with_home;
item->admin_only = admin_only;
item->launch_kind = DESKTOP_ITEM_LAUNCH_EXECUTABLE;
item->builtin_id = -1;
item->section = section;
// Build full binary path: 0:/apps/<dir>/<binary>
snprintf(app->binary_path, sizeof(app->binary_path),
snprintf(item->binary_path, sizeof(item->binary_path),
"0:/apps/%s/%s", dirname, binary);
// Load icon from app directory
app->icon = {};
app->icon_path[0] = '\0';
item->icon = {};
item->icon_path[0] = '\0';
if (icon_file[0]) {
snprintf(app->icon_path, sizeof(app->icon_path),
snprintf(item->icon_path, sizeof(item->icon_path),
"0:/apps/%s/%s", dirname, icon_file);
Color defColor = colors::ICON_COLOR;
app->icon = svg_load(app->icon_path, 20, 20, defColor);
item->icon = svg_load(item->icon_path, 20, 20, defColor);
}
doc.destroy();
ds->external_app_count++;
ds->desktop_item_count++;
}
}
}
@@ -161,12 +219,13 @@ void desktop_build_menu(DesktopState* ds) {
}
}
// Add external apps in this category
for (int x = 0; x < ds->external_app_count; x++) {
ExternalApp* app = &ds->external_apps[x];
if (!app->menu_visible) continue;
if (montauk::streq(app->category, CATEGORY_NAMES[cat])) {
menu_add_external(app->name, app->binary_path, &app->icon, app->launch_with_home);
// Add manifest-backed items in this category.
for (int x = 0; x < ds->desktop_item_count; x++) {
DesktopItem* item = &ds->desktop_items[x];
if (!item->menu_visible || (item->admin_only && !ds->is_admin)) continue;
if (montauk::streq(item->category, CATEGORY_NAMES[cat])) {
menu_add_external(item->name, item->binary_path,
&item->icon, item->launch_with_home);
}
}
}
+1 -2
View File
@@ -138,8 +138,7 @@ Component g_components[] = {
{ "printing", "Printing support",
"Print spooler daemon (printd), printer setup app and print tools",
-1, "os/printd.elf",
{"os/printd.elf", "os/printctl.elf", "os/printersapplet.lib",
"apps/printers", nullptr},
{"os/printd.elf", "os/printctl.elf", "apps/printers", nullptr},
false, -1,
false, false, false, false, 0, 0 },
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* main.cpp
* MontaukOS Network configuration applet
* MontaukOS Network configuration app
* Copyright (c) 2026 Daniel Hammer
*/
+5
View File
@@ -1,4 +1,5 @@
[app]
id = "network"
name = "Network"
binary = "network.elf"
icon = "network-wired.svg"
@@ -6,3 +7,7 @@ icon = "network-wired.svg"
[menu]
category = "System"
visible = false
[desktop]
section = "settings"
admin_only = false
+5
View File
@@ -1,4 +1,5 @@
[app]
id = "printers"
name = "Printers"
binary = "printers.elf"
icon = "preferences-devices-printer.svg"
@@ -6,3 +7,7 @@ icon = "preferences-devices-printer.svg"
[menu]
category = "System"
visible = true
[desktop]
section = "settings"
admin_only = false
+1 -1
View File
@@ -1,6 +1,6 @@
/*
* main.cpp
* MontaukOS Time Zone configuration applet
* MontaukOS Time Zone configuration app
* Copyright (c) 2026 Daniel Hammer
*/
+5
View File
@@ -1,4 +1,5 @@
[app]
id = "timezone"
name = "Time Zone"
binary = "timezone.elf"
icon = "preferences-system-time.svg"
@@ -6,3 +7,7 @@ icon = "preferences-system-time.svg"
[menu]
category = "System"
visible = false
[desktop]
section = "settings"
admin_only = false
+20 -2
View File
@@ -841,18 +841,36 @@ External standalone apps can be discovered by the desktop through TOML manifest
```toml
[app]
id = "my-app"
name = "My App"
binary = "myapp"
binary = "myapp.elf"
icon = "myapp_icon.svg"
[menu]
category = "Applications"
visible = true
[desktop]
section = "apps"
admin_only = false
[launch]
pass_home_dir = true
```
**Categories:** `Applications`, `Internet`, `System`, `Games`
The desktop scans `0:/apps/` at startup and adds matching entries to the application menu. The `binary` field is the executable name (looked up in the system path).
The desktop scans `0:/apps/` at startup and builds one catalog used by the
application menu, launcher search, and the Apps and Settings virtual folders.
`app.id` must be unique; when omitted, the containing directory name is used.
The `binary` field names an executable in that app's bundle.
`desktop.section` accepts `apps` (the default), `settings`, or `hidden`.
Section placement is independent of `menu.visible`: a Settings item can be
hidden from the application menu while remaining directly searchable in the
launcher. Set `desktop.admin_only` to hide the item from non-administrator
users. `launch.pass_home_dir` controls whether the current user's home path is
passed as the executable's first argument.
---
+23 -6
View File
@@ -17,18 +17,34 @@ namespace gui {
static constexpr int MAX_WINDOWS = 8;
static constexpr int PANEL_HEIGHT = 32;
static constexpr int MAX_EXTERNAL_APPS = 32;
static constexpr int MAX_LAUNCHER_ITEMS = 64;
// External app discovered from 0:/apps/ manifest
struct ExternalApp {
enum DesktopItemSection : uint8_t {
DESKTOP_ITEM_SECTION_HIDDEN = 0,
DESKTOP_ITEM_SECTION_APPS,
DESKTOP_ITEM_SECTION_SETTINGS,
};
enum DesktopItemLaunchKind : uint8_t {
DESKTOP_ITEM_LAUNCH_EXECUTABLE = 0,
DESKTOP_ITEM_LAUNCH_BUILTIN,
};
// Launchable desktop item discovered from an app manifest or supplied by the
// desktop's built-in registry.
struct DesktopItem {
char id[48];
char name[48];
char binary_path[128];
char icon_path[128];
char category[24];
SvgIcon icon;
DesktopItemSection section;
DesktopItemLaunchKind launch_kind;
int builtin_id;
bool menu_visible;
bool launch_with_home;
bool admin_only;
};
enum LauncherItemKind : uint8_t {
@@ -152,9 +168,10 @@ struct DesktopState {
SvgIcon icon_volume;
// External apps discovered from 0:/apps/ manifests
ExternalApp external_apps[MAX_EXTERNAL_APPS];
int external_app_count;
// Desktop items discovered from 0:/apps/ manifests (dynamically grown).
DesktopItem* desktop_items;
int desktop_item_count;
int desktop_item_capacity;
bool ctx_menu_open;
int ctx_menu_x, ctx_menu_y;
@@ -1,37 +0,0 @@
/*
* 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