refactor: unify desktop apps and Settings under item catalog
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,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
|
||||
*/
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* MontaukOS Network configuration applet
|
||||
* MontaukOS Network configuration app
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* MontaukOS Time Zone configuration applet
|
||||
* MontaukOS Time Zone configuration app
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user