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
@@ -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