feat: allow applets to be shared modules
This commit is contained in:
@@ -68,7 +68,7 @@ LDFLAGS := \
|
||||
|
||||
# ---- C++ source files ----
|
||||
|
||||
CORE_SRCS := main.cpp window.cpp panel.cpp compose.cpp input.cpp dialogs.cpp launcher.cpp desktop_builtin.cpp font_data.cpp stb_truetype_impl.cpp
|
||||
CORE_SRCS := main.cpp window.cpp panel.cpp compose.cpp input.cpp dialogs.cpp launcher.cpp desktop_builtin.cpp shared_applets.cpp font_data.cpp stb_truetype_impl.cpp
|
||||
APP_SRCS := $(sort $(shell find apps -name '*.cpp' -print))
|
||||
SRCS := $(CORE_SRCS) $(APP_SRCS)
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
@@ -190,6 +190,18 @@ SvgIcon* desktop_builtin_menu_icon(DesktopState* ds, DesktopBuiltinId id);
|
||||
void desktop_launch_builtin(DesktopState* ds, DesktopBuiltinId id);
|
||||
void desktop_lock_screen(DesktopState* ds);
|
||||
|
||||
struct DesktopSharedAppletEntry {
|
||||
char id[48];
|
||||
char label[64];
|
||||
char icon_path[128];
|
||||
char library_path[128];
|
||||
};
|
||||
|
||||
int desktop_list_system_configuration_applets(DesktopState* ds,
|
||||
DesktopSharedAppletEntry* out,
|
||||
int max_entries);
|
||||
bool desktop_launch_shared_applet(DesktopState* ds, const char* lib_path);
|
||||
|
||||
// ============================================================================
|
||||
// Forward declarations for desktop launch entry points
|
||||
// ============================================================================
|
||||
|
||||
@@ -30,14 +30,23 @@ enum FileManagerVirtualViewKind : uint8_t {
|
||||
FM_VIRTUAL_VIEW_SYSTEM_TOOLS,
|
||||
};
|
||||
|
||||
enum FileManagerVirtualEntryKind : uint8_t {
|
||||
FM_VIRTUAL_ENTRY_NONE = 0,
|
||||
FM_VIRTUAL_ENTRY_EXTERNAL_APP,
|
||||
FM_VIRTUAL_ENTRY_BUILTIN,
|
||||
FM_VIRTUAL_ENTRY_SHARED_APPLET,
|
||||
};
|
||||
|
||||
struct FileManagerLocation {
|
||||
char path[256];
|
||||
FileManagerVirtualViewKind virtual_view;
|
||||
};
|
||||
|
||||
struct FileManagerVirtualEntry {
|
||||
FileManagerVirtualEntryKind kind;
|
||||
int external_app_index;
|
||||
DesktopBuiltinId builtin_id;
|
||||
char shared_applet_path[128];
|
||||
SvgIcon icon_lg;
|
||||
SvgIcon icon_sm;
|
||||
};
|
||||
|
||||
@@ -27,8 +27,10 @@ static void filemanager_reset_list_state(FileManagerState* fm) {
|
||||
|
||||
static void filemanager_reset_virtual_entry(FileManagerVirtualEntry* entry) {
|
||||
if (!entry) return;
|
||||
entry->kind = FM_VIRTUAL_ENTRY_NONE;
|
||||
entry->external_app_index = -1;
|
||||
entry->builtin_id = DESKTOP_BUILTIN_NONE;
|
||||
entry->shared_applet_path[0] = '\0';
|
||||
entry->icon_lg = {};
|
||||
entry->icon_sm = {};
|
||||
}
|
||||
@@ -50,8 +52,10 @@ static void filemanager_add_root_entry(FileManagerState* fm,
|
||||
static void filemanager_add_virtual_entry(FileManagerState* fm,
|
||||
const char* name,
|
||||
const char* icon_path,
|
||||
FileManagerVirtualEntryKind kind,
|
||||
int external_index,
|
||||
DesktopBuiltinId builtin_id) {
|
||||
DesktopBuiltinId builtin_id,
|
||||
const char* shared_applet_path) {
|
||||
if (!fm || fm->entry_count >= 64) return;
|
||||
|
||||
int idx = fm->entry_count;
|
||||
@@ -61,8 +65,14 @@ static void filemanager_add_virtual_entry(FileManagerState* fm,
|
||||
fm->is_dir[idx] = false;
|
||||
fm->drive_indices[idx] = -1;
|
||||
filemanager_reset_virtual_entry(&fm->virtual_entries[idx]);
|
||||
fm->virtual_entries[idx].kind = kind;
|
||||
fm->virtual_entries[idx].external_app_index = external_index;
|
||||
fm->virtual_entries[idx].builtin_id = builtin_id;
|
||||
if (shared_applet_path && shared_applet_path[0]) {
|
||||
montauk::strncpy(fm->virtual_entries[idx].shared_applet_path,
|
||||
shared_applet_path,
|
||||
sizeof(fm->virtual_entries[idx].shared_applet_path));
|
||||
}
|
||||
|
||||
if (icon_path && icon_path[0]) {
|
||||
Color defColor = colors::ICON_COLOR;
|
||||
@@ -344,8 +354,9 @@ void filemanager_read_apps(FileManagerState* fm) {
|
||||
}
|
||||
|
||||
filemanager_add_virtual_entry(fm, name, icon_path,
|
||||
FM_VIRTUAL_ENTRY_EXTERNAL_APP,
|
||||
filemanager_find_external_app(ds, bin_path),
|
||||
DESKTOP_BUILTIN_NONE);
|
||||
DESKTOP_BUILTIN_NONE, nullptr);
|
||||
|
||||
doc.destroy();
|
||||
}
|
||||
@@ -361,7 +372,17 @@ void filemanager_read_system_tools(FileManagerState* fm) {
|
||||
for (int i = 0; i < builtin_count; i++) {
|
||||
if (!builtins[i].system_tools_applet) continue;
|
||||
filemanager_add_virtual_entry(fm, builtins[i].label, builtins[i].icon_path,
|
||||
-1, builtins[i].id);
|
||||
FM_VIRTUAL_ENTRY_BUILTIN,
|
||||
-1, builtins[i].id, nullptr);
|
||||
}
|
||||
|
||||
DesktopSharedAppletEntry applets[32];
|
||||
int applet_count = desktop_list_system_configuration_applets(fm->desktop, applets, 32);
|
||||
for (int i = 0; i < applet_count && fm->entry_count < 64; i++) {
|
||||
filemanager_add_virtual_entry(fm, applets[i].label, applets[i].icon_path,
|
||||
FM_VIRTUAL_ENTRY_SHARED_APPLET,
|
||||
-1, DESKTOP_BUILTIN_NONE,
|
||||
applets[i].library_path);
|
||||
}
|
||||
|
||||
filemanager_reset_list_state(fm);
|
||||
|
||||
@@ -197,17 +197,23 @@ void filemanager_open_entry(FileManagerState* fm, int idx) {
|
||||
if (filemanager_is_virtual_view(fm) && fm->entry_types[idx] == FM_ENTRY_VIRTUAL_APP) {
|
||||
DesktopState* ds = fm->desktop;
|
||||
const FileManagerVirtualEntry& entry = fm->virtual_entries[idx];
|
||||
if (ds && entry.builtin_id != DESKTOP_BUILTIN_NONE) {
|
||||
if (ds && entry.kind == FM_VIRTUAL_ENTRY_BUILTIN
|
||||
&& entry.builtin_id != DESKTOP_BUILTIN_NONE) {
|
||||
desktop_launch_builtin(ds, entry.builtin_id);
|
||||
return;
|
||||
}
|
||||
if (ds && entry.external_app_index >= 0 && entry.external_app_index < ds->external_app_count) {
|
||||
if (ds && entry.kind == FM_VIRTUAL_ENTRY_EXTERNAL_APP
|
||||
&& entry.external_app_index >= 0
|
||||
&& entry.external_app_index < ds->external_app_count) {
|
||||
const ExternalApp& app = ds->external_apps[entry.external_app_index];
|
||||
if (app.launch_with_home) {
|
||||
montauk::spawn(app.binary_path, ds->home_dir);
|
||||
} else {
|
||||
montauk::spawn(app.binary_path);
|
||||
}
|
||||
} else if (ds && entry.kind == FM_VIRTUAL_ENTRY_SHARED_APPLET
|
||||
&& entry.shared_applet_path[0] != '\0') {
|
||||
desktop_launch_shared_applet(ds, entry.shared_applet_path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* shared_applets.cpp
|
||||
* Shared-library desktop applet discovery and launch helpers
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "desktop_internal.hpp"
|
||||
#include <gui/desktop_applet.hpp>
|
||||
#include <libloader/libloader.h>
|
||||
#include <montauk/config.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using gui::desktop_applet::Descriptor;
|
||||
using gui::desktop_applet::OpenFn;
|
||||
using gui::desktop_applet::QueryFn;
|
||||
|
||||
static constexpr const char* DEFAULT_APPLET_ICON = "0:/icons/preferences-system.svg";
|
||||
static constexpr const char* APPLET_CONFIG_NAME = "desktop-applets";
|
||||
static constexpr const char* APPLET_TABLE_PREFIX = "applets.";
|
||||
|
||||
static bool str_ends_with(const char* s, const char* suffix) {
|
||||
if (!s || !suffix) return false;
|
||||
int slen = montauk::slen(s);
|
||||
int tlen = montauk::slen(suffix);
|
||||
if (tlen > slen) return false;
|
||||
for (int i = 0; i < tlen; i++) {
|
||||
if (s[slen - tlen + i] != suffix[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char* applet_field_string(const montauk::toml::Doc& doc,
|
||||
const char* applet_key,
|
||||
const char* field,
|
||||
const char* def = "") {
|
||||
char key[256];
|
||||
snprintf(key, sizeof(key), "%s.%s", applet_key, field);
|
||||
return doc.get_string(key, def);
|
||||
}
|
||||
|
||||
static bool applet_field_bool(const montauk::toml::Doc& doc,
|
||||
const char* applet_key,
|
||||
const char* field,
|
||||
bool def = false) {
|
||||
char key[256];
|
||||
snprintf(key, sizeof(key), "%s.%s", applet_key, field);
|
||||
return doc.get_bool(key, def);
|
||||
}
|
||||
|
||||
static const char* applet_id_from_table_key(const char* table_key) {
|
||||
if (!table_key || !montauk::starts_with(table_key, APPLET_TABLE_PREFIX)) return nullptr;
|
||||
const char* applet_id = table_key + montauk::slen(APPLET_TABLE_PREFIX);
|
||||
return applet_id[0] ? applet_id : nullptr;
|
||||
}
|
||||
|
||||
static bool resolve_shared_applet(LibHandle* lib,
|
||||
const Descriptor** out_desc,
|
||||
OpenFn* out_open) {
|
||||
if (out_desc) *out_desc = nullptr;
|
||||
if (out_open) *out_open = nullptr;
|
||||
if (!lib) return false;
|
||||
|
||||
QueryFn query = (QueryFn)libloader::dlsym(lib, gui::desktop_applet::QUERY_SYMBOL);
|
||||
OpenFn open = (OpenFn)libloader::dlsym(lib, gui::desktop_applet::OPEN_SYMBOL);
|
||||
if (!query || !open) return false;
|
||||
|
||||
const Descriptor* desc = query();
|
||||
if (!desc || desc->abi_version != gui::desktop_applet::ABI_VERSION) return false;
|
||||
if (!desc->applet_id || !desc->applet_id[0]) return false;
|
||||
if (!desc->display_name || !desc->display_name[0]) return false;
|
||||
|
||||
if (out_desc) *out_desc = desc;
|
||||
if (out_open) *out_open = open;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sort_shared_applets(DesktopSharedAppletEntry* entries, int count) {
|
||||
if (!entries || count <= 1) return;
|
||||
|
||||
for (int i = 1; i < count; i++) {
|
||||
DesktopSharedAppletEntry tmp = entries[i];
|
||||
int j = i - 1;
|
||||
while (j >= 0 && str_compare_ci(tmp.label, entries[j].label) < 0) {
|
||||
entries[j + 1] = entries[j];
|
||||
j--;
|
||||
}
|
||||
entries[j + 1] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int desktop_list_system_configuration_applets(DesktopState* ds,
|
||||
DesktopSharedAppletEntry* out,
|
||||
int max_entries) {
|
||||
(void)ds;
|
||||
if (!out || max_entries <= 0) return 0;
|
||||
|
||||
auto doc = montauk::config::load(APPLET_CONFIG_NAME);
|
||||
int written = 0;
|
||||
for (int i = 0; i < doc.entries.count && written < max_entries; i++) {
|
||||
montauk::toml::Value* item = doc.entries.items[i];
|
||||
if (!item || item->type != montauk::toml::Type::Table || !item->key) continue;
|
||||
|
||||
const char* applet_id = applet_id_from_table_key(item->key);
|
||||
if (!applet_id) continue;
|
||||
if (!applet_field_bool(doc, item->key, "enabled", true)) continue;
|
||||
|
||||
const char* library = applet_field_string(doc, item->key, "library");
|
||||
if (!library[0] || !str_ends_with(library, ".lib")) continue;
|
||||
|
||||
const char* label = applet_field_string(doc, item->key, "label", applet_id);
|
||||
const char* icon = applet_field_string(doc, item->key, "icon", DEFAULT_APPLET_ICON);
|
||||
|
||||
montauk::memset(&out[written], 0, sizeof(out[written]));
|
||||
montauk::strncpy(out[written].id, applet_id, sizeof(out[written].id));
|
||||
montauk::strncpy(out[written].label, label[0] ? label : library, sizeof(out[written].label));
|
||||
montauk::strncpy(out[written].icon_path,
|
||||
icon[0] ? icon : DEFAULT_APPLET_ICON,
|
||||
sizeof(out[written].icon_path));
|
||||
montauk::strncpy(out[written].library_path, library, sizeof(out[written].library_path));
|
||||
written++;
|
||||
}
|
||||
|
||||
doc.destroy();
|
||||
sort_shared_applets(out, written);
|
||||
return written;
|
||||
}
|
||||
|
||||
bool desktop_launch_shared_applet(DesktopState* ds, const char* lib_path) {
|
||||
if (!ds || !lib_path || !lib_path[0]) return false;
|
||||
|
||||
LibHandle* lib = libloader::dlopen(lib_path);
|
||||
if (!lib) return false;
|
||||
|
||||
const Descriptor* desc = nullptr;
|
||||
OpenFn open = nullptr;
|
||||
bool ok = resolve_shared_applet(lib, &desc, &open);
|
||||
if (ok) {
|
||||
gui::desktop_applet::Host host = {};
|
||||
montauk::strncpy(host.current_user, ds->current_user, sizeof(host.current_user));
|
||||
montauk::strncpy(host.home_dir, ds->home_dir, sizeof(host.home_dir));
|
||||
montauk::strncpy(host.user_config_dir, ds->user_config_dir, sizeof(host.user_config_dir));
|
||||
host.is_admin = ds->is_admin;
|
||||
ok = open(&host);
|
||||
}
|
||||
|
||||
libloader::dlclose(lib);
|
||||
return ok;
|
||||
}
|
||||
Reference in New Issue
Block a user