From d912a636e9327337c1f1ddadff9b51239e1fa4a6 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Thu, 23 Apr 2026 15:36:03 +0200 Subject: [PATCH] feat: system settings stored as applets in virtual folder --- programs/include/gui/desktop.hpp | 3 + programs/src/desktop/Makefile | 2 +- programs/src/desktop/apps/apps_common.hpp | 36 +++ .../src/desktop/apps/filemanager/actions.cpp | 26 +- programs/src/desktop/apps/filemanager/app.cpp | 30 ++- .../apps/filemanager/filemanager_internal.hpp | 65 ++++- .../desktop/apps/filemanager/filesystem.cpp | 223 +++++++++++------- .../desktop/apps/filemanager/navigation.cpp | 119 ++++++---- .../src/desktop/apps/filemanager/render.cpp | 73 +++--- programs/src/desktop/apps/settings/app.cpp | 10 +- .../apps/settings/settings_internal.hpp | 26 +- programs/src/desktop/apps/usermanager/app.cpp | 69 ++++++ .../{settings => usermanager}/dialogs.cpp | 100 ++++---- .../apps/usermanager/usermanager_internal.hpp | 48 ++++ .../apps/{settings => usermanager}/users.cpp | 63 +++-- programs/src/desktop/compose.cpp | 4 +- programs/src/desktop/desktop_builtin.cpp | 103 ++++++++ programs/src/desktop/desktop_internal.hpp | 8 +- programs/src/desktop/input.cpp | 50 +--- programs/src/desktop/main.cpp | 47 ++-- scripts/copy_icons.sh | 1 + 21 files changed, 747 insertions(+), 359 deletions(-) create mode 100644 programs/src/desktop/apps/usermanager/app.cpp rename programs/src/desktop/apps/{settings => usermanager}/dialogs.cpp (85%) create mode 100644 programs/src/desktop/apps/usermanager/usermanager_internal.hpp rename programs/src/desktop/apps/{settings => usermanager}/users.cpp (66%) create mode 100644 programs/src/desktop/desktop_builtin.cpp diff --git a/programs/include/gui/desktop.hpp b/programs/include/gui/desktop.hpp index e194b2f..72248be 100644 --- a/programs/include/gui/desktop.hpp +++ b/programs/include/gui/desktop.hpp @@ -131,6 +131,9 @@ struct DesktopState { SvgIcon icon_home_folder_lg; SvgIcon icon_apps; SvgIcon icon_apps_lg; + SvgIcon icon_system_tools_menu; + SvgIcon icon_system_tools; + SvgIcon icon_system_tools_lg; // Special user folder icons (16x16 and 48x48) static constexpr int SPECIAL_FOLDER_COUNT = 6; diff --git a/programs/src/desktop/Makefile b/programs/src/desktop/Makefile index 6446377..ca9a7b1 100644 --- a/programs/src/desktop/Makefile +++ b/programs/src/desktop/Makefile @@ -68,7 +68,7 @@ LDFLAGS := \ # ---- C++ source files ---- -CORE_SRCS := main.cpp window.cpp panel.cpp compose.cpp input.cpp dialogs.cpp launcher.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 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)) diff --git a/programs/src/desktop/apps/apps_common.hpp b/programs/src/desktop/apps/apps_common.hpp index 7adf4d6..8eafc8c 100644 --- a/programs/src/desktop/apps/apps_common.hpp +++ b/programs/src/desktop/apps/apps_common.hpp @@ -155,6 +155,41 @@ inline void format_size(char* buf, int size) { } } +// ============================================================================ +// Desktop builtins +// ============================================================================ + +enum DesktopBuiltinId : int { + DESKTOP_BUILTIN_NONE = -1, + DESKTOP_BUILTIN_FILES = 1, + DESKTOP_BUILTIN_DESKTOP_SETTINGS = 11, + DESKTOP_BUILTIN_REBOOT = 12, + DESKTOP_BUILTIN_SHUTDOWN = 14, + DESKTOP_BUILTIN_LOG_OUT = 16, + DESKTOP_BUILTIN_LOCK_SCREEN = 17, + DESKTOP_BUILTIN_SLEEP = 18, + DESKTOP_BUILTIN_USER_MANAGEMENT = 19, + DESKTOP_BUILTIN_SYSTEM_CONFIGURATION = 20, +}; + +inline constexpr const char* FILEMANAGER_PATH_SYSTEM_CONFIGURATION = + "virtual://system-configuration"; + +struct DesktopBuiltinEntry { + DesktopBuiltinId id; + const char* label; + int launcher_category; + bool launcher_footer; + bool system_tools_applet; + const char* icon_path; +}; + +const DesktopBuiltinEntry* desktop_builtin_registry(int* count); +const DesktopBuiltinEntry* desktop_builtin_entry(DesktopBuiltinId id); +SvgIcon* desktop_builtin_menu_icon(DesktopState* ds, DesktopBuiltinId id); +void desktop_launch_builtin(DesktopState* ds, DesktopBuiltinId id); +void desktop_lock_screen(DesktopState* ds); + // ============================================================================ // Forward declarations for desktop launch entry points // ============================================================================ @@ -167,6 +202,7 @@ void open_calculator(DesktopState* ds); void open_texteditor(DesktopState* ds); void open_klog(DesktopState* ds); void open_settings(DesktopState* ds); +void open_user_manager(DesktopState* ds); void open_reboot_dialog(DesktopState* ds); void open_wordprocessor(DesktopState* ds); void open_shutdown_dialog(DesktopState* ds); diff --git a/programs/src/desktop/apps/filemanager/actions.cpp b/programs/src/desktop/apps/filemanager/actions.cpp index 8dfa2f0..4ec0b28 100644 --- a/programs/src/desktop/apps/filemanager/actions.cpp +++ b/programs/src/desktop/apps/filemanager/actions.cpp @@ -10,7 +10,8 @@ namespace filemanager { void filemanager_do_copy(FileManagerState* fm) { if (fm->selected < 0 || fm->selected >= fm->entry_count) return; - if (fm->at_drives_root || fm->entry_types[fm->selected] == 3) return; + if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm) || + fm->entry_types[fm->selected] == FM_ENTRY_DRIVE) return; filemanager_build_fullpath(fm->clipboard_path, 256, fm->current_path, fm->entry_names[fm->selected]); @@ -20,7 +21,8 @@ void filemanager_do_copy(FileManagerState* fm) { void filemanager_do_cut(FileManagerState* fm) { if (fm->selected < 0 || fm->selected >= fm->entry_count) return; - if (fm->at_drives_root || fm->entry_types[fm->selected] == 3) return; + if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm) || + fm->entry_types[fm->selected] == FM_ENTRY_DRIVE) return; filemanager_build_fullpath(fm->clipboard_path, 256, fm->current_path, fm->entry_names[fm->selected]); @@ -29,7 +31,7 @@ void filemanager_do_cut(FileManagerState* fm) { } void filemanager_do_paste(FileManagerState* fm) { - if (!fm->clipboard_has_data || fm->at_drives_root) return; + if (!fm->clipboard_has_data || filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) return; const char* basename = path_basename(fm->clipboard_path); char dst[512]; @@ -59,7 +61,8 @@ void filemanager_do_paste(FileManagerState* fm) { void filemanager_start_rename(FileManagerState* fm) { if (fm->selected < 0 || fm->selected >= fm->entry_count) return; - if (fm->at_drives_root || fm->entry_types[fm->selected] == 3) return; + if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm) || + fm->entry_types[fm->selected] == FM_ENTRY_DRIVE) return; fm->rename_active = true; fm->rename_idx = fm->selected; @@ -100,7 +103,7 @@ void filemanager_cancel_rename(FileManagerState* fm) { } void filemanager_new_folder(FileManagerState* fm) { - if (fm->at_drives_root) return; + if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) return; // Find a unique name char name[64] = "New Folder"; @@ -130,7 +133,8 @@ void filemanager_new_folder(FileManagerState* fm) { void filemanager_delete_selected(FileManagerState* fm) { if (fm->selected < 0 || fm->selected >= fm->entry_count) return; - if (fm->at_drives_root || fm->entry_types[fm->selected] == 3) return; + if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm) || + fm->entry_types[fm->selected] == FM_ENTRY_DRIVE) return; char fullpath[512]; filemanager_build_fullpath(fullpath, 512, @@ -152,8 +156,10 @@ void filemanager_open_ctx_menu(FileManagerState* fm, int local_x, int local_y, i if (target_idx >= 0 && target_idx < fm->entry_count) { int tt = fm->entry_types[target_idx]; - if ((fm->at_drives_root && (tt == 3 || tt == 4 || tt == 5 || tt == 7)) || - (fm->at_apps_view && tt == 6)) { + 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_TOOLS_ROOT)) + || (filemanager_is_virtual_view(fm) && tt == FM_ENTRY_VIRTUAL_APP)) { // Drive, Home, Apps shortcut, or app entry: only Open fm->ctx_items[fm->ctx_item_count++] = CTX_OPEN; } else { @@ -165,6 +171,10 @@ void filemanager_open_ctx_menu(FileManagerState* fm, int local_x, int local_y, i } } else { // Background click + if (filemanager_is_virtual_view(fm)) { + fm->ctx_open = false; + return; + } if (fm->clipboard_has_data) fm->ctx_items[fm->ctx_item_count++] = CTX_PASTE; fm->ctx_items[fm->ctx_item_count++] = CTX_NEW_FOLDER; diff --git a/programs/src/desktop/apps/filemanager/app.cpp b/programs/src/desktop/apps/filemanager/app.cpp index 24caf03..addfac1 100644 --- a/programs/src/desktop/apps/filemanager/app.cpp +++ b/programs/src/desktop/apps/filemanager/app.cpp @@ -8,6 +8,17 @@ namespace filemanager { +static bool filemanager_open_virtual_path(FileManagerState* fm, const char* path) { + if (!fm || !path) return false; + + if (montauk::streq(path, FILEMANAGER_PATH_SYSTEM_CONFIGURATION)) { + filemanager_read_virtual_view(fm, FM_VIRTUAL_VIEW_SYSTEM_TOOLS); + return true; + } + + return false; +} + static void open_filemanager_internal(DesktopState* ds, const char* initial_path) { int idx = desktop_create_window(ds, "Files", 150, 120, 560, 420); if (idx < 0) return; @@ -27,13 +38,17 @@ static void open_filemanager_internal(DesktopState* ds, const char* initial_path ensure_filemanager_icons_loaded(ds); if (initial_path && initial_path[0] != '\0') { - int probe_fd = montauk::open(initial_path); - if (probe_fd >= 0) { - montauk::close(probe_fd); - montauk::strncpy(fm->current_path, initial_path, sizeof(fm->current_path)); - filemanager_read_dir(fm); + if (filemanager_open_virtual_path(fm, initial_path)) { + // Virtual view already loaded. } else { - filemanager_read_drives(fm); + int probe_fd = montauk::open(initial_path); + if (probe_fd >= 0) { + montauk::close(probe_fd); + montauk::strncpy(fm->current_path, initial_path, sizeof(fm->current_path)); + filemanager_read_dir(fm); + } else { + filemanager_read_drives(fm); + } } } else { filemanager_read_drives(fm); @@ -59,6 +74,9 @@ void ensure_filemanager_icons_loaded(DesktopState* ds) { ds->icon_home_folder_lg = svg_load("0:/icons/folder-blue-home.svg", 48, 48, defColor); ds->icon_apps = svg_load("0:/icons/folder-blue-development.svg", 16, 16, defColor); ds->icon_apps_lg = svg_load("0:/icons/folder-blue-development.svg", 48, 48, defColor); + ds->icon_system_tools_menu = svg_load("0:/icons/preferences-system.svg", 20, 20, defColor); + ds->icon_system_tools = svg_load("0:/icons/preferences-system.svg", 16, 16, defColor); + ds->icon_system_tools_lg = svg_load("0:/icons/preferences-system.svg", 48, 48, defColor); for (int sf = 0; sf < filemanager::SF_COUNT; sf++) { char icon_path[128]; diff --git a/programs/src/desktop/apps/filemanager/filemanager_internal.hpp b/programs/src/desktop/apps/filemanager/filemanager_internal.hpp index 00e3876..7332706 100644 --- a/programs/src/desktop/apps/filemanager/filemanager_internal.hpp +++ b/programs/src/desktop/apps/filemanager/filemanager_internal.hpp @@ -12,13 +12,43 @@ namespace filemanager { inline constexpr int FM_MAX_DRIVES = 16; +enum FileManagerEntryType : int { + FM_ENTRY_FILE = 0, + FM_ENTRY_DIR = 1, + FM_ENTRY_EXEC = 2, + FM_ENTRY_DRIVE = 3, + FM_ENTRY_HOME = 4, + FM_ENTRY_APPS_ROOT = 5, + FM_ENTRY_VIRTUAL_APP = 6, + FM_ENTRY_SPECIAL_DIR = 7, + FM_ENTRY_SYSTEM_TOOLS_ROOT = 8, +}; + +enum FileManagerVirtualViewKind : uint8_t { + FM_VIRTUAL_VIEW_NONE = 0, + FM_VIRTUAL_VIEW_APPS, + FM_VIRTUAL_VIEW_SYSTEM_TOOLS, +}; + +struct FileManagerLocation { + char path[256]; + FileManagerVirtualViewKind virtual_view; +}; + +struct FileManagerVirtualEntry { + int external_app_index; + DesktopBuiltinId builtin_id; + SvgIcon icon_lg; + SvgIcon icon_sm; +}; + struct FileManagerState { char current_path[256]; - char history[16][256]; + FileManagerLocation history[16]; int history_pos; int history_count; char entry_names[64][128]; - int entry_types[64]; // 0=file, 1=dir, 2=exec, 3=drive, 4=home, 5=apps, 6=app, 7=special_dir + int entry_types[64]; int entry_sizes[64]; int entry_count; int selected; @@ -29,8 +59,7 @@ struct FileManagerState { Scrollbar scrollbar; DesktopState* desktop; bool grid_view; - bool at_drives_root; - int drive_indices[FM_MAX_DRIVES]; // which drive number each entry maps to + int drive_indices[64]; // drive number or special-folder index for Computer view entries // Clipboard char clipboard_path[256]; @@ -58,12 +87,10 @@ struct FileManagerState { int pathbar_cursor; int pathbar_len; - // Apps view - bool at_apps_view; - int app_map[64]; // entry index -> external_apps[] index - SvgIcon app_icons_lg[64]; // 48x48 icons for grid view - SvgIcon app_icons_sm[64]; // 16x16 icons for list view - int app_icon_count; + // Virtual views + FileManagerVirtualViewKind virtual_view; + FileManagerVirtualEntry virtual_entries[64]; + int virtual_entry_count; }; inline constexpr int FM_TOOLBAR_H = 32; @@ -100,6 +127,22 @@ inline constexpr int CTX_NEW_FOLDER = 6; inline constexpr int CTX_MENU_W = 140; inline constexpr int CTX_ITEM_H = 24; +inline bool filemanager_is_virtual_view(const FileManagerState* fm) { + return fm && fm->virtual_view != FM_VIRTUAL_VIEW_NONE; +} + +inline bool filemanager_is_computer_view(const FileManagerState* fm) { + return fm && !filemanager_is_virtual_view(fm) && fm->current_path[0] == '\0'; +} + +inline const char* filemanager_virtual_view_label(FileManagerVirtualViewKind view) { + switch (view) { + case FM_VIRTUAL_VIEW_APPS: return "Apps"; + case FM_VIRTUAL_VIEW_SYSTEM_TOOLS: return "System Configuration"; + default: return "Computer"; + } +} + int special_folder_index(const char* name); const char* ctx_label(int action); @@ -119,7 +162,9 @@ const char* path_basename(const char* path); void filemanager_read_drives(FileManagerState* fm); void filemanager_read_dir(FileManagerState* fm); void filemanager_free_app_icons(FileManagerState* fm); +void filemanager_read_virtual_view(FileManagerState* fm, FileManagerVirtualViewKind view); void filemanager_read_apps(FileManagerState* fm); +void filemanager_read_system_tools(FileManagerState* fm); bool filemanager_delete_recursive(const char* path); bool filemanager_copy_file(const char* src, const char* dst); diff --git a/programs/src/desktop/apps/filemanager/filesystem.cpp b/programs/src/desktop/apps/filemanager/filesystem.cpp index 9feadc1..9dfcf77 100644 --- a/programs/src/desktop/apps/filemanager/filesystem.cpp +++ b/programs/src/desktop/apps/filemanager/filesystem.cpp @@ -9,33 +9,92 @@ namespace filemanager { -void filemanager_read_drives(FileManagerState* fm) { +static int filemanager_find_external_app(DesktopState* ds, const char* bin_path) { + if (!ds || !bin_path) return -1; + for (int x = 0; x < ds->external_app_count; x++) { + if (montauk::streq(ds->external_apps[x].binary_path, bin_path)) return x; + } + return -1; +} + +static void filemanager_reset_list_state(FileManagerState* fm) { + fm->selected = -1; + fm->scroll_offset = 0; + fm->scrollbar.scroll_offset = 0; + fm->last_click_item = -1; + fm->last_click_time = 0; +} + +static void filemanager_reset_virtual_entry(FileManagerVirtualEntry* entry) { + if (!entry) return; + entry->external_app_index = -1; + entry->builtin_id = DESKTOP_BUILTIN_NONE; + entry->icon_lg = {}; + entry->icon_sm = {}; +} + +static void filemanager_add_root_entry(FileManagerState* fm, + const char* name, + FileManagerEntryType type, + int drive_index) { + if (!fm || fm->entry_count >= 64) return; + + int idx = fm->entry_count++; + montauk::strncpy(fm->entry_names[idx], name, 63); + fm->entry_types[idx] = type; + fm->entry_sizes[idx] = 0; + fm->is_dir[idx] = true; + fm->drive_indices[idx] = drive_index; +} + +static void filemanager_add_virtual_entry(FileManagerState* fm, + const char* name, + const char* icon_path, + int external_index, + DesktopBuiltinId builtin_id) { + if (!fm || fm->entry_count >= 64) return; + + int idx = fm->entry_count; + montauk::strncpy(fm->entry_names[idx], name, 63); + fm->entry_types[idx] = FM_ENTRY_VIRTUAL_APP; + fm->entry_sizes[idx] = 0; + fm->is_dir[idx] = false; + fm->drive_indices[idx] = -1; + filemanager_reset_virtual_entry(&fm->virtual_entries[idx]); + fm->virtual_entries[idx].external_app_index = external_index; + fm->virtual_entries[idx].builtin_id = builtin_id; + + if (icon_path && icon_path[0]) { + Color defColor = colors::ICON_COLOR; + fm->virtual_entries[idx].icon_lg = svg_load(icon_path, 48, 48, defColor); + fm->virtual_entries[idx].icon_sm = svg_load(icon_path, 16, 16, defColor); + } + + fm->entry_count++; + fm->virtual_entry_count = fm->entry_count; +} + +static void filemanager_begin_virtual_view(FileManagerState* fm, FileManagerVirtualViewKind view) { + filemanager_free_app_icons(fm); + fm->current_path[0] = '\0'; + fm->virtual_view = view; fm->entry_count = 0; - fm->at_drives_root = true; +} + +void filemanager_read_drives(FileManagerState* fm) { + filemanager_free_app_icons(fm); + fm->entry_count = 0; + fm->virtual_view = FM_VIRTUAL_VIEW_NONE; fm->current_path[0] = '\0'; // Home folder entry (if we have a valid home directory) DesktopState* ds = fm->desktop; if (ds && ds->home_dir[0] != '\0') { - int i = fm->entry_count; - montauk::strncpy(fm->entry_names[i], "Home", 63); - fm->entry_types[i] = 4; // home - fm->entry_sizes[i] = 0; - fm->is_dir[i] = true; - fm->drive_indices[i] = -1; - fm->entry_count++; + filemanager_add_root_entry(fm, "Home", FM_ENTRY_HOME, -1); } // Apps entry - { - int i = fm->entry_count; - montauk::strncpy(fm->entry_names[i], "Apps", 63); - fm->entry_types[i] = 5; // apps - fm->entry_sizes[i] = 0; - fm->is_dir[i] = true; - fm->drive_indices[i] = -1; - fm->entry_count++; - } + filemanager_add_root_entry(fm, "Apps", FM_ENTRY_APPS_ROOT, -1); // Special user folders (Documents, Desktop, Music, etc.) - only if they exist if (ds && ds->home_dir[0] != '\0') { @@ -51,24 +110,20 @@ void filemanager_read_drives(FileManagerState* fm) { int probe_fd = montauk::open(probe_path); if (probe_fd >= 0) { montauk::close(probe_fd); - int i = fm->entry_count; - montauk::strncpy(fm->entry_names[i], sf_names[sf], 63); - fm->entry_types[i] = 7; // special_dir - fm->entry_sizes[i] = 0; - fm->is_dir[i] = true; - fm->drive_indices[i] = sf; // special folder index - fm->entry_count++; + filemanager_add_root_entry(fm, sf_names[sf], FM_ENTRY_SPECIAL_DIR, sf); } } } + // System configuration entry + filemanager_add_root_entry(fm, "System Configuration", FM_ENTRY_SYSTEM_TOOLS_ROOT, -1); + // Drive entries int drives[FM_MAX_DRIVES]; int driveCount = montauk::drivelist(drives, FM_MAX_DRIVES); for (int di = 0; di < driveCount; di++) { int d = drives[di]; - int i = fm->entry_count; char probe[8]; if (d < 10) { probe[0] = '0' + d; @@ -91,28 +146,22 @@ void filemanager_read_drives(FileManagerState* fm) { str_append(label, volume_label, 64); str_append(label, "]", 64); } - montauk::strncpy(fm->entry_names[i], label, 63); - fm->entry_types[i] = 3; // drive - fm->entry_sizes[i] = 0; - fm->is_dir[i] = true; - fm->drive_indices[i] = d; - fm->entry_count++; + filemanager_add_root_entry(fm, label, FM_ENTRY_DRIVE, d); if (fm->entry_count >= 64) break; } - fm->selected = -1; - fm->scroll_offset = 0; - fm->scrollbar.scroll_offset = 0; - fm->last_click_item = -1; - fm->last_click_time = 0; + filemanager_reset_list_state(fm); } void filemanager_read_dir(FileManagerState* fm) { - fm->at_drives_root = false; - if (fm->at_apps_view) { - fm->at_apps_view = false; - filemanager_free_app_icons(fm); + if (fm->current_path[0] == '\0') { + filemanager_read_drives(fm); + return; } + + filemanager_free_app_icons(fm); + fm->virtual_view = FM_VIRTUAL_VIEW_NONE; + const char* names[64]; fm->entry_count = montauk::readdir(fm->current_path, names, 64); if (fm->entry_count < 0) fm->entry_count = 0; @@ -210,33 +259,36 @@ void filemanager_read_dir(FileManagerState* fm) { fm->is_dir[j + 1] = tmp_isdir; } - fm->selected = -1; - fm->scroll_offset = 0; - fm->scrollbar.scroll_offset = 0; - fm->last_click_item = -1; - fm->last_click_time = 0; + filemanager_reset_list_state(fm); } void filemanager_free_app_icons(FileManagerState* fm) { - for (int i = 0; i < fm->app_icon_count; i++) { - if (fm->app_icons_lg[i].pixels) { - montauk::mfree(fm->app_icons_lg[i].pixels); - fm->app_icons_lg[i].pixels = nullptr; + if (!fm) return; + + for (int i = 0; i < fm->virtual_entry_count; i++) { + if (fm->virtual_entries[i].icon_lg.pixels) { + montauk::mfree(fm->virtual_entries[i].icon_lg.pixels); + fm->virtual_entries[i].icon_lg.pixels = nullptr; } - if (fm->app_icons_sm[i].pixels) { - montauk::mfree(fm->app_icons_sm[i].pixels); - fm->app_icons_sm[i].pixels = nullptr; + if (fm->virtual_entries[i].icon_sm.pixels) { + montauk::mfree(fm->virtual_entries[i].icon_sm.pixels); + fm->virtual_entries[i].icon_sm.pixels = nullptr; } + filemanager_reset_virtual_entry(&fm->virtual_entries[i]); + } + fm->virtual_entry_count = 0; +} + +void filemanager_read_virtual_view(FileManagerState* fm, FileManagerVirtualViewKind view) { + switch (view) { + case FM_VIRTUAL_VIEW_APPS: filemanager_read_apps(fm); break; + case FM_VIRTUAL_VIEW_SYSTEM_TOOLS: filemanager_read_system_tools(fm); break; + default: filemanager_read_drives(fm); break; } - fm->app_icon_count = 0; } void filemanager_read_apps(FileManagerState* fm) { - filemanager_free_app_icons(fm); - - fm->at_drives_root = false; - fm->at_apps_view = true; - fm->entry_count = 0; + filemanager_begin_virtual_view(fm, FM_VIRTUAL_VIEW_APPS); DesktopState* ds = fm->desktop; if (!ds) return; @@ -283,45 +335,36 @@ void filemanager_read_apps(FileManagerState* fm) { const char* binary = doc.get_string("app.binary", ""); const char* icon_file = doc.get_string("app.icon", ""); - int idx = fm->entry_count; - montauk::strncpy(fm->entry_names[idx], name, 63); - fm->entry_types[idx] = 6; // app entry - fm->entry_sizes[idx] = 0; - fm->is_dir[idx] = false; - - // Store binary path in drive_indices as an app_map index - fm->app_map[idx] = -1; - // Find matching external_app by binary path char bin_path[128]; snprintf(bin_path, 128, "0:/apps/%s/%s", dirname, binary); - for (int x = 0; x < ds->external_app_count; x++) { - if (montauk::streq(ds->external_apps[x].binary_path, bin_path)) { - fm->app_map[idx] = x; - break; - } + char icon_path[128]; + icon_path[0] = '\0'; + if (icon_file[0]) { + snprintf(icon_path, 128, "0:/apps/%s/%s", dirname, icon_file); } - // Load icons at file manager sizes - fm->app_icons_lg[idx] = {}; - fm->app_icons_sm[idx] = {}; - if (icon_file[0]) { - char icon_path[128]; - snprintf(icon_path, 128, "0:/apps/%s/%s", dirname, icon_file); - Color defColor = colors::ICON_COLOR; - fm->app_icons_lg[idx] = svg_load(icon_path, 48, 48, defColor); - fm->app_icons_sm[idx] = svg_load(icon_path, 16, 16, defColor); - } + filemanager_add_virtual_entry(fm, name, icon_path, + filemanager_find_external_app(ds, bin_path), + DESKTOP_BUILTIN_NONE); doc.destroy(); - fm->entry_count++; - fm->app_icon_count = fm->entry_count; } - fm->selected = -1; - fm->scroll_offset = 0; - fm->scrollbar.scroll_offset = 0; - fm->last_click_item = -1; - fm->last_click_time = 0; + filemanager_reset_list_state(fm); +} + +void filemanager_read_system_tools(FileManagerState* fm) { + filemanager_begin_virtual_view(fm, FM_VIRTUAL_VIEW_SYSTEM_TOOLS); + + int builtin_count = 0; + const DesktopBuiltinEntry* builtins = desktop_builtin_registry(&builtin_count); + 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); + } + + filemanager_reset_list_state(fm); } bool filemanager_delete_recursive(const char* path) { diff --git a/programs/src/desktop/apps/filemanager/navigation.cpp b/programs/src/desktop/apps/filemanager/navigation.cpp index fb9e0b6..5aaff13 100644 --- a/programs/src/desktop/apps/filemanager/navigation.cpp +++ b/programs/src/desktop/apps/filemanager/navigation.cpp @@ -8,14 +8,54 @@ namespace filemanager { -void filemanager_push_history(FileManagerState* fm) { - // Don't push if same as current position - if (fm->history_count > 0 && fm->history_pos >= 0) { - if (montauk::streq(fm->history[fm->history_pos], fm->current_path)) return; +static void filemanager_store_location(FileManagerLocation* location, + const char* path, + FileManagerVirtualViewKind virtual_view) { + if (!location) return; + montauk::strncpy(location->path, path ? path : "", sizeof(location->path)); + location->virtual_view = virtual_view; +} + +static bool filemanager_location_matches_current(const FileManagerState* fm, + const FileManagerLocation& location) { + return fm + && location.virtual_view == fm->virtual_view + && montauk::streq(location.path, fm->current_path); +} + +static void filemanager_restore_location(FileManagerState* fm, const FileManagerLocation& location) { + montauk::strncpy(fm->current_path, location.path, sizeof(fm->current_path)); + + if (location.virtual_view != FM_VIRTUAL_VIEW_NONE) { + filemanager_read_virtual_view(fm, location.virtual_view); + } else if (location.path[0] == '\0') { + filemanager_read_drives(fm); + } else { + filemanager_read_dir(fm); } +} + +void filemanager_push_history(FileManagerState* fm) { + if (!fm) return; + + if (fm->history_count > 0 && fm->history_pos >= 0) { + if (filemanager_location_matches_current(fm, fm->history[fm->history_pos])) return; + } + + if (fm->history_pos < fm->history_count - 1) { + fm->history_count = fm->history_pos + 1; + } + + if (fm->history_count >= 16) { + for (int i = 1; i < 16; i++) { + fm->history[i - 1] = fm->history[i]; + } + fm->history_count = 15; + fm->history_pos = 14; + } + fm->history_pos++; - if (fm->history_pos >= 16) fm->history_pos = 15; - montauk::strcpy(fm->history[fm->history_pos], fm->current_path); + filemanager_store_location(&fm->history[fm->history_pos], fm->current_path, fm->virtual_view); fm->history_count = fm->history_pos + 1; } @@ -25,17 +65,14 @@ void filemanager_navigate(FileManagerState* fm, const char* name) { str_append(fm->current_path, "/", 256); } str_append(fm->current_path, name, 256); - filemanager_push_history(fm); filemanager_read_dir(fm); + filemanager_push_history(fm); } void filemanager_go_up(FileManagerState* fm) { - if (fm->at_drives_root) return; + if (filemanager_is_computer_view(fm)) return; - // From apps view, go back to Computer - if (fm->at_apps_view) { - fm->at_apps_view = false; - filemanager_free_app_icons(fm); + if (filemanager_is_virtual_view(fm)) { filemanager_read_drives(fm); filemanager_push_history(fm); return; @@ -46,7 +83,6 @@ void filemanager_go_up(FileManagerState* fm) { // At drive root (e.g. "0:/" or "10:/") -- go to drives view bool is_drive_root = false; if (len >= 3 && fm->current_path[len - 1] == '/') { - // Check if path is just "N:/" or "NN:/" int colon = -1; for (int i = 0; i < len; i++) { if (fm->current_path[i] == ':') { colon = i; break; } @@ -69,24 +105,19 @@ void filemanager_go_up(FileManagerState* fm) { if (fm->current_path[i] == '/') { last_slash = i; break; } } if (last_slash >= 0) { - // Keep the slash only if it's the root slash (right after "N:") if (last_slash > 0 && fm->current_path[last_slash - 1] == ':') { fm->current_path[last_slash + 1] = '\0'; } else { fm->current_path[last_slash] = '\0'; } } - filemanager_push_history(fm); filemanager_read_dir(fm); + filemanager_push_history(fm); } void filemanager_navigate_to_history(FileManagerState* fm) { - montauk::strcpy(fm->current_path, fm->history[fm->history_pos]); - if (fm->current_path[0] == '\0') { - filemanager_read_drives(fm); - } else { - filemanager_read_dir(fm); - } + if (!fm || fm->history_pos < 0 || fm->history_pos >= fm->history_count) return; + filemanager_restore_location(fm, fm->history[fm->history_pos]); } void filemanager_go_back(FileManagerState* fm) { @@ -102,17 +133,13 @@ void filemanager_go_forward(FileManagerState* fm) { } void filemanager_go_home(FileManagerState* fm) { - if (fm->at_apps_view) { - fm->at_apps_view = false; - filemanager_free_app_icons(fm); - } filemanager_read_drives(fm); filemanager_push_history(fm); } void filemanager_start_pathbar(FileManagerState* fm) { fm->pathbar_editing = true; - if (fm->at_drives_root) { + if (filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)) { fm->pathbar_buf[0] = '\0'; fm->pathbar_len = 0; } else { @@ -134,15 +161,14 @@ void filemanager_commit_pathbar(FileManagerState* fm) { return; } montauk::strcpy(fm->current_path, fm->pathbar_buf); - filemanager_push_history(fm); filemanager_read_dir(fm); + filemanager_push_history(fm); } void filemanager_open_entry(FileManagerState* fm, int idx) { if (idx < 0 || idx >= fm->entry_count) return; - // Special user folder in Computer view - if (fm->at_drives_root && fm->entry_types[idx] == 7) { + if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_SPECIAL_DIR) { DesktopState* ds = fm->desktop; if (ds && ds->home_dir[0] != '\0') { montauk::strcpy(fm->current_path, ds->home_dir); @@ -150,23 +176,33 @@ void filemanager_open_entry(FileManagerState* fm, int idx) { if (plen > 0 && fm->current_path[plen - 1] != '/') str_append(fm->current_path, "/", 256); str_append(fm->current_path, fm->entry_names[idx], 256); - filemanager_push_history(fm); filemanager_read_dir(fm); + filemanager_push_history(fm); } return; } - // Apps shortcut in Computer view - if (fm->at_drives_root && fm->entry_types[idx] == 5) { - filemanager_read_apps(fm); + if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_APPS_ROOT) { + filemanager_read_virtual_view(fm, FM_VIRTUAL_VIEW_APPS); + filemanager_push_history(fm); return; } - // App entry in apps view - launch it - if (fm->at_apps_view && fm->entry_types[idx] == 6) { + if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_SYSTEM_TOOLS_ROOT) { + filemanager_read_virtual_view(fm, FM_VIRTUAL_VIEW_SYSTEM_TOOLS); + filemanager_push_history(fm); + return; + } + + if (filemanager_is_virtual_view(fm) && fm->entry_types[idx] == FM_ENTRY_VIRTUAL_APP) { DesktopState* ds = fm->desktop; - if (ds && fm->app_map[idx] >= 0 && fm->app_map[idx] < ds->external_app_count) { - const ExternalApp& app = ds->external_apps[fm->app_map[idx]]; + const FileManagerVirtualEntry& entry = fm->virtual_entries[idx]; + if (ds && 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) { + const ExternalApp& app = ds->external_apps[entry.external_app_index]; if (app.launch_with_home) { montauk::spawn(app.binary_path, ds->home_dir); } else { @@ -176,18 +212,17 @@ void filemanager_open_entry(FileManagerState* fm, int idx) { return; } - if (fm->at_drives_root && fm->entry_types[idx] == 4) { - // Home folder + if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_HOME) { DesktopState* ds = fm->desktop; if (ds && ds->home_dir[0] != '\0') { montauk::strcpy(fm->current_path, ds->home_dir); - filemanager_push_history(fm); filemanager_read_dir(fm); + filemanager_push_history(fm); } return; } - if (fm->at_drives_root && fm->entry_types[idx] == 3) { + if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_DRIVE) { int d = fm->drive_indices[idx]; char dpath[8]; if (d < 10) { @@ -196,8 +231,8 @@ void filemanager_open_entry(FileManagerState* fm, int idx) { dpath[0] = '1'; dpath[1] = '0' + (d - 10); dpath[2] = ':'; dpath[3] = '/'; dpath[4] = '\0'; } montauk::strcpy(fm->current_path, dpath); - filemanager_push_history(fm); filemanager_read_dir(fm); + filemanager_push_history(fm); return; } diff --git a/programs/src/desktop/apps/filemanager/render.cpp b/programs/src/desktop/apps/filemanager/render.cpp index 145443d..a44609d 100644 --- a/programs/src/desktop/apps/filemanager/render.cpp +++ b/programs/src/desktop/apps/filemanager/render.cpp @@ -202,9 +202,10 @@ void filemanager_draw_header(Canvas& c, FileManagerState* fm, Color toolbar_colo // Action buttons: Copy, Cut, Paste, Rename, New Folder, Delete bool has_sel = fm->selected >= 0 && fm->selected < fm->entry_count - && !fm->at_drives_root && !fm->at_apps_view - && fm->entry_types[fm->selected] != 3; - bool has_clip = fm->clipboard_has_data && !fm->at_drives_root && !fm->at_apps_view; + && !filemanager_is_computer_view(fm) && !filemanager_is_virtual_view(fm) + && fm->entry_types[fm->selected] != FM_ENTRY_DRIVE; + bool has_clip = fm->clipboard_has_data && !filemanager_is_computer_view(fm) + && !filemanager_is_virtual_view(fm); Color dim_bg = Color::from_rgb(0xF0, 0xF0, 0xF0); // Icon toolbar buttons: Copy, Cut, Paste, Rename, New Folder, Delete @@ -214,7 +215,8 @@ void filemanager_draw_header(Canvas& c, FileManagerState* fm, Color toolbar_colo { 188, ds ? &ds->icon_cut : nullptr, has_sel }, { 216, ds ? &ds->icon_paste : nullptr, has_clip }, { 244, ds ? &ds->icon_rename : nullptr, has_sel }, - { 272, ds ? &ds->icon_folder_new : nullptr, !fm->at_drives_root && !fm->at_apps_view }, + { 272, ds ? &ds->icon_folder_new : nullptr, + !filemanager_is_computer_view(fm) && !filemanager_is_virtual_view(fm) }, { 300, ds ? &ds->icon_delete : nullptr, has_sel }, }; @@ -252,8 +254,8 @@ void filemanager_draw_header(Canvas& c, FileManagerState* fm, Color toolbar_colo } else { c.fill_rect(0, pathbar_y, c.w, FM_PATHBAR_H, Color::from_rgb(0xF0, 0xF0, 0xF0)); const char* pathbar_text = fm->current_path; - if (fm->at_drives_root) pathbar_text = "Computer"; - else if (fm->at_apps_view) pathbar_text = "Apps"; + if (filemanager_is_computer_view(fm)) pathbar_text = "Computer"; + else if (filemanager_is_virtual_view(fm)) pathbar_text = filemanager_virtual_view_label(fm->virtual_view); c.text(8, pathbar_y + 4, pathbar_text, colors::TEXT_COLOR); } @@ -313,22 +315,27 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) { int icon_y = cell_y + FM_GRID_PAD; // Check for special folder icon (type 7 in Computer, or type 1 dir with matching name) int sfi = -1; - if (fm->entry_types[i] == 7) sfi = fm->drive_indices[i]; - else if (ds && fm->entry_types[i] == 1) sfi = special_folder_index(fm->entry_names[i]); + if (fm->entry_types[i] == FM_ENTRY_SPECIAL_DIR) sfi = fm->drive_indices[i]; + else if (ds && fm->entry_types[i] == FM_ENTRY_DIR) sfi = special_folder_index(fm->entry_names[i]); 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] == 6 && i < fm->app_icon_count && fm->app_icons_lg[i].pixels) { - c.icon(icon_x, icon_y, fm->app_icons_lg[i]); - } else if (ds && fm->entry_types[i] == 5 && ds->icon_apps_lg.pixels) { + } else if (fm->entry_types[i] == FM_ENTRY_VIRTUAL_APP + && i < fm->virtual_entry_count + && fm->virtual_entries[i].icon_lg.pixels) { + c.icon(icon_x, icon_y, fm->virtual_entries[i].icon_lg); + } else if (ds && fm->entry_types[i] == FM_ENTRY_SYSTEM_TOOLS_ROOT + && ds->icon_system_tools_lg.pixels) { + c.icon(icon_x, icon_y, ds->icon_system_tools_lg); + } else if (ds && fm->entry_types[i] == FM_ENTRY_APPS_ROOT && ds->icon_apps_lg.pixels) { c.icon(icon_x, icon_y, ds->icon_apps_lg); - } else if (ds && fm->entry_types[i] == 4 && ds->icon_home_folder_lg.pixels) { + } else if (ds && fm->entry_types[i] == FM_ENTRY_HOME && ds->icon_home_folder_lg.pixels) { c.icon(icon_x, icon_y, ds->icon_home_folder_lg); - } else if (ds && fm->entry_types[i] == 3 && ds->icon_drive_lg.pixels) { + } else if (ds && fm->entry_types[i] == FM_ENTRY_DRIVE && ds->icon_drive_lg.pixels) { c.icon(icon_x, icon_y, ds->icon_drive_lg); - } else if (ds && fm->entry_types[i] == 1 && ds->icon_folder_lg.pixels) { + } else if (ds && fm->entry_types[i] == FM_ENTRY_DIR && ds->icon_folder_lg.pixels) { c.icon(icon_x, icon_y, ds->icon_folder_lg); - } else if (ds && fm->entry_types[i] == 2 && ds->icon_exec_lg.pixels) { + } else if (ds && fm->entry_types[i] == FM_ENTRY_EXEC && ds->icon_exec_lg.pixels) { c.icon(icon_x, icon_y, ds->icon_exec_lg); } else if (ds && ds->icon_file_lg.pixels) { c.icon(icon_x, icon_y, ds->icon_file_lg); @@ -438,23 +445,28 @@ void filemanager_on_draw(Window* win, Framebuffer& fb) { int ico_y = iy + (FM_ITEM_H - 16) / 2; // Check for special folder icon int sfi_sm = -1; - if (fm->entry_types[i] == 7) sfi_sm = fm->drive_indices[i]; - else if (ds && fm->entry_types[i] == 1) sfi_sm = special_folder_index(fm->entry_names[i]); + if (fm->entry_types[i] == FM_ENTRY_SPECIAL_DIR) sfi_sm = fm->drive_indices[i]; + else if (ds && fm->entry_types[i] == FM_ENTRY_DIR) sfi_sm = special_folder_index(fm->entry_names[i]); 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] == 6 && i < fm->app_icon_count && fm->app_icons_sm[i].pixels) { - c.icon(ico_x, ico_y, fm->app_icons_sm[i]); - } else if (ds && fm->entry_types[i] == 5 && ds->icon_apps.pixels) { + } else if (fm->entry_types[i] == FM_ENTRY_VIRTUAL_APP + && i < fm->virtual_entry_count + && fm->virtual_entries[i].icon_sm.pixels) { + c.icon(ico_x, ico_y, fm->virtual_entries[i].icon_sm); + } else if (ds && fm->entry_types[i] == FM_ENTRY_SYSTEM_TOOLS_ROOT + && ds->icon_system_tools.pixels) { + c.icon(ico_x, ico_y, ds->icon_system_tools); + } else if (ds && fm->entry_types[i] == FM_ENTRY_APPS_ROOT && ds->icon_apps.pixels) { c.icon(ico_x, ico_y, ds->icon_apps); - } else if (ds && fm->entry_types[i] == 4 && ds->icon_home_folder.pixels) { + } else if (ds && fm->entry_types[i] == FM_ENTRY_HOME && ds->icon_home_folder.pixels) { c.icon(ico_x, ico_y, ds->icon_home_folder); - } else if (ds && fm->entry_types[i] == 3 && ds->icon_drive.pixels) { + } else if (ds && fm->entry_types[i] == FM_ENTRY_DRIVE && ds->icon_drive.pixels) { c.icon(ico_x, ico_y, ds->icon_drive); - } else if (ds && fm->entry_types[i] == 1 && ds->icon_folder.pixels) { + } else if (ds && fm->entry_types[i] == FM_ENTRY_DIR && ds->icon_folder.pixels) { c.icon(ico_x, ico_y, ds->icon_folder); - } else if (ds && fm->entry_types[i] == 2 && ds->icon_exec.pixels) { + } else if (ds && fm->entry_types[i] == FM_ENTRY_EXEC && ds->icon_exec.pixels) { c.icon(ico_x, ico_y, ds->icon_exec); } else if (ds && ds->icon_file.pixels) { c.icon(ico_x, ico_y, ds->icon_file); @@ -525,12 +537,13 @@ 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] == 6) type_str = "App"; - else if (fm->entry_types[i] == 5) type_str = "Apps"; - else if (fm->entry_types[i] == 4) type_str = "Home"; - else if (fm->entry_types[i] == 3) type_str = "Drive"; - else if (fm->entry_types[i] == 1) type_str = "Dir"; - else if (fm->entry_types[i] == 2) type_str = "Exec"; + if (fm->entry_types[i] == FM_ENTRY_VIRTUAL_APP) type_str = "App"; + else if (fm->entry_types[i] == FM_ENTRY_APPS_ROOT) type_str = "Apps"; + else if (fm->entry_types[i] == FM_ENTRY_SYSTEM_TOOLS_ROOT) type_str = "System"; + else if (fm->entry_types[i] == FM_ENTRY_HOME) type_str = "Home"; + else if (fm->entry_types[i] == FM_ENTRY_DRIVE) type_str = "Drive"; + else if (fm->entry_types[i] == FM_ENTRY_DIR) type_str = "Dir"; + else if (fm->entry_types[i] == FM_ENTRY_EXEC) type_str = "Exec"; c.text(type_col_x, ty, type_str, dim); } } diff --git a/programs/src/desktop/apps/settings/app.cpp b/programs/src/desktop/apps/settings/app.cpp index c5bbec9..7eaedb6 100644 --- a/programs/src/desktop/apps/settings/app.cpp +++ b/programs/src/desktop/apps/settings/app.cpp @@ -25,8 +25,7 @@ static void settings_on_draw(Window* win, Framebuffer& fb) { switch (st->active_tab) { case 0: settings_draw_appearance(content, st); break; case 1: settings_draw_display(content, st); break; - case 2: settings_draw_users(content, st); break; - case 3: settings_draw_about(content, st); break; + case 2: settings_draw_about(content, st); break; } } @@ -56,9 +55,6 @@ static void settings_on_mouse(Window* win, MouseEvent& ev) { case 1: settings_handle_display_click(st, mx, cy); break; - case 2: - settings_handle_users_click(win, st, mx, cy); - break; default: break; } @@ -80,7 +76,7 @@ static void settings_on_close(Window* win) { } // namespace settings_app void open_settings(DesktopState* ds) { - int idx = desktop_create_window(ds, "Settings", 200, 100, 480, 420); + int idx = desktop_create_window(ds, "Desktop Settings", 200, 100, 480, 420); if (idx < 0) return; Window* win = &ds->windows[idx]; @@ -93,8 +89,6 @@ void open_settings(DesktopState* ds) { st->uptime_ms = montauk::get_milliseconds(); st->wp_scanned = false; st->wp_files.count = 0; - st->selected_user = -1; - st->users_loaded = false; st->status_msg[0] = '\0'; win->app_data = st; diff --git a/programs/src/desktop/apps/settings/settings_internal.hpp b/programs/src/desktop/apps/settings/settings_internal.hpp index 1d24dd6..ebee8e1 100644 --- a/programs/src/desktop/apps/settings/settings_internal.hpp +++ b/programs/src/desktop/apps/settings/settings_internal.hpp @@ -10,24 +10,17 @@ #include "../../wallpaper.hpp" #include #include -#include namespace settings_app { struct SettingsState { DesktopState* desktop; - int active_tab; // 0=Appearance, 1=Display, 2=Users, 3=About + int active_tab; // 0=Appearance, 1=Display, 2=About Montauk::SysInfo sys_info; uint64_t uptime_ms; WallpaperFileList wp_files; bool wp_scanned; - // Users tab - montauk::user::UserInfo users[16]; - int user_count; - int selected_user; // index into users[], or -1 - bool users_loaded; - char status_msg[128]; uint64_t status_time; }; @@ -73,13 +66,10 @@ inline const Color accent_palette[SWATCH_COUNT] = { }; inline constexpr int TAB_BAR_H = 36; -inline constexpr int TAB_COUNT = 4; -inline constexpr const char* tab_labels[TAB_COUNT] = { "Appearance", "Display", "Users", "About" }; +inline constexpr int TAB_COUNT = 3; +inline constexpr const char* tab_labels[TAB_COUNT] = { "Appearance", "Display", "About" }; inline constexpr int WP_ITEM_H = 24; -inline constexpr int USER_BTN_H = 30; -inline constexpr int USER_BTN_W = 100; -inline constexpr int USER_FIELD_H = 32; inline mtk::Theme settings_theme(const SettingsState* st) { return mtk::make_theme(st->desktop->settings.accent_color); @@ -98,19 +88,9 @@ bool settings_handle_appearance_click(Window* win, SettingsState* st, int mx, in void settings_draw_display(Canvas& c, SettingsState* st); bool settings_handle_display_click(SettingsState* st, int mx, int cy); -void settings_draw_users(Canvas& c, SettingsState* st); -bool settings_handle_users_click(Window* win, SettingsState* st, int mx, int cy); - void settings_draw_about(Canvas& c, SettingsState* st); void settings_persist(SettingsState* st); void settings_persist_tz(SettingsState* st); -void users_reload(SettingsState* st); -int user_row_height(); - -void open_add_user_dialog(SettingsState* parent); -void open_change_pwd_dialog(SettingsState* parent); -void open_delete_user_dialog(SettingsState* parent); - } // namespace settings_app diff --git a/programs/src/desktop/apps/usermanager/app.cpp b/programs/src/desktop/apps/usermanager/app.cpp new file mode 100644 index 0000000..2f97c96 --- /dev/null +++ b/programs/src/desktop/apps/usermanager/app.cpp @@ -0,0 +1,69 @@ +/* + * app.cpp + * User management applet launcher and desktop integration entry points + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "usermanager_internal.hpp" + +namespace usermanager_app { + +static void usermanager_on_draw(Window* win, Framebuffer& fb) { + (void)fb; + UserManagerState* st = (UserManagerState*)win->app_data; + if (!st) return; + + Canvas c(win); + usermanager_draw(c, st); +} + +static void usermanager_on_mouse(Window* win, MouseEvent& ev) { + UserManagerState* st = (UserManagerState*)win->app_data; + if (!st) return; + + mtk::DesktopHost host(win); + int mx = 0; + int my = 0; + if (!host.map_mouse(ev, &mx, &my)) return; + + usermanager_update_mouse(st, mx, my); + if (ev.left_pressed()) { + usermanager_handle_click(win, st, mx, my); + } +} + +static void usermanager_on_key(Window* win, const Montauk::KeyEvent& key) { + (void)win; + (void)key; +} + +static void usermanager_on_close(Window* win) { + if (win->app_data) { + montauk::mfree(win->app_data); + win->app_data = nullptr; + } +} + +} // namespace usermanager_app + +void open_user_manager(DesktopState* ds) { + int idx = desktop_create_window(ds, "User Management", 200, 100, 480, 384); + if (idx < 0) return; + + Window* win = &ds->windows[idx]; + usermanager_app::UserManagerState* st = + (usermanager_app::UserManagerState*)montauk::malloc(sizeof(usermanager_app::UserManagerState)); + montauk::memset(st, 0, sizeof(usermanager_app::UserManagerState)); + st->desktop = ds; + st->selected_user = -1; + st->users_loaded = false; + st->status_msg[0] = '\0'; + st->mouse_x = -1; + st->mouse_y = -1; + + win->app_data = st; + win->on_draw = usermanager_app::usermanager_on_draw; + win->on_mouse = usermanager_app::usermanager_on_mouse; + win->on_key = usermanager_app::usermanager_on_key; + win->on_close = usermanager_app::usermanager_on_close; +} diff --git a/programs/src/desktop/apps/settings/dialogs.cpp b/programs/src/desktop/apps/usermanager/dialogs.cpp similarity index 85% rename from programs/src/desktop/apps/settings/dialogs.cpp rename to programs/src/desktop/apps/usermanager/dialogs.cpp index 42fe45f..4c66c36 100644 --- a/programs/src/desktop/apps/settings/dialogs.cpp +++ b/programs/src/desktop/apps/usermanager/dialogs.cpp @@ -1,12 +1,19 @@ /* * dialogs.cpp - * User-management dialogs for the embedded desktop settings app + * User-management dialogs for the embedded desktop user management applet * Copyright (c) 2026 Daniel Hammer */ -#include "settings_internal.hpp" +#include "usermanager_internal.hpp" -namespace settings_app { +namespace usermanager_app { + +static mtk::WidgetState dialog_button_state(int mouse_x, int mouse_y, + const Rect& rect, + bool enabled = true, + bool active = false) { + return mtk::widget_state(active, rect.contains(mouse_x, mouse_y), enabled); +} static void dialog_append(char* buf, int* len, int max, char ch) { if (*len < max - 1) { @@ -40,14 +47,12 @@ static Rect dialog_secondary_button_rect(int pad, int content_h) { return {pad + 88, content_h - USER_BTN_H - pad, 80, USER_BTN_H}; } -// ============================================================================ -// Add User Dialog -// ============================================================================ - struct AddUserDialogState { DesktopState* ds; - SettingsState* parent; + UserManagerState* parent; Color accent; + int mouse_x; + int mouse_y; int field; char username[32]; int username_len; char display[64]; int display_len; @@ -57,6 +62,7 @@ struct AddUserDialogState { }; static void adduser_on_draw(Window* win, Framebuffer& fb) { + (void)fb; AddUserDialogState* st = (AddUserDialogState*)win->app_data; if (!st) return; @@ -85,9 +91,11 @@ static void adduser_on_draw(Window* win, Framebuffer& fb) { Rect user_btn = {pad + 60, y, 60, USER_BTN_H}; Rect admin_btn = {pad + 128, y, 60, USER_BTN_H}; mtk::draw_button(c, user_btn, "User", mtk::BUTTON_SECONDARY, - mtk::widget_state(!st->role_admin), theme); + dialog_button_state(st->mouse_x, st->mouse_y, user_btn, true, !st->role_admin), + theme); mtk::draw_button(c, admin_btn, "Admin", mtk::BUTTON_SECONDARY, - mtk::widget_state(st->role_admin), theme); + dialog_button_state(st->mouse_x, st->mouse_y, admin_btn, true, st->role_admin), + theme); y += USER_BTN_H + 14; if (st->error[0]) { @@ -95,11 +103,14 @@ static void adduser_on_draw(Window* win, Framebuffer& fb) { y += sfh + 6; } + Rect create_btn = dialog_primary_button_rect(pad, c.h); + Rect cancel_btn = dialog_secondary_button_rect(pad, c.h); mtk::draw_modal_actions(c, - dialog_primary_button_rect(pad, c.h), "Create", - mtk::BUTTON_PRIMARY, mtk::widget_state(), - dialog_secondary_button_rect(pad, c.h), "Cancel", - mtk::widget_state(), + create_btn, "Create", + mtk::BUTTON_PRIMARY, + dialog_button_state(st->mouse_x, st->mouse_y, create_btn), + cancel_btn, "Cancel", + dialog_button_state(st->mouse_x, st->mouse_y, cancel_btn), theme); } @@ -117,7 +128,7 @@ static void adduser_submit(AddUserDialogState* st) { const char* role = st->role_admin ? "admin" : "user"; if (montauk::user::create_user(st->username, dname, st->password, role)) { st->parent->users_loaded = false; - settings_set_status(st->parent, "User created"); + usermanager_set_status(st->parent, "User created"); dialog_close_self(st->ds, st); } else { montauk::strcpy(st->error, "Failed (username taken?)"); @@ -126,12 +137,15 @@ static void adduser_submit(AddUserDialogState* st) { static void adduser_on_mouse(Window* win, MouseEvent& ev) { AddUserDialogState* st = (AddUserDialogState*)win->app_data; - if (!st || !ev.left_pressed()) return; + if (!st) return; mtk::DesktopHost host(win); int mx = 0; int my = 0; if (!host.map_mouse(ev, &mx, &my)) return; + st->mouse_x = mx; + st->mouse_y = my; + if (!ev.left_pressed()) return; mtk::Theme theme = mtk::make_theme(st->accent); int pad = 16; @@ -213,7 +227,7 @@ static void adduser_on_close(Window* win) { } } -void open_add_user_dialog(SettingsState* parent) { +void open_add_user_dialog(UserManagerState* parent) { DesktopState* ds = parent->desktop; int w = 340; int h = 340; @@ -228,6 +242,8 @@ void open_add_user_dialog(SettingsState* parent) { st->ds = ds; st->parent = parent; st->accent = ds->settings.accent_color; + st->mouse_x = -1; + st->mouse_y = -1; win->app_data = st; win->on_draw = adduser_on_draw; @@ -236,14 +252,12 @@ void open_add_user_dialog(SettingsState* parent) { win->on_close = adduser_on_close; } -// ============================================================================ -// Change Password Dialog -// ============================================================================ - struct ChPwdDialogState { DesktopState* ds; - SettingsState* parent; + UserManagerState* parent; Color accent; + int mouse_x; + int mouse_y; char username[32]; char display_name[64]; int field; @@ -253,6 +267,7 @@ struct ChPwdDialogState { }; static void chpwd_on_draw(Window* win, Framebuffer& fb) { + (void)fb; ChPwdDialogState* st = (ChPwdDialogState*)win->app_data; if (!st) return; @@ -282,11 +297,14 @@ static void chpwd_on_draw(Window* win, Framebuffer& fb) { c.text(pad, y, st->error, theme.danger); } + Rect save_btn = dialog_primary_button_rect(pad, c.h); + Rect cancel_btn = dialog_secondary_button_rect(pad, c.h); mtk::draw_modal_actions(c, - dialog_primary_button_rect(pad, c.h), "Save", - mtk::BUTTON_PRIMARY, mtk::widget_state(), - dialog_secondary_button_rect(pad, c.h), "Cancel", - mtk::widget_state(), + save_btn, "Save", + mtk::BUTTON_PRIMARY, + dialog_button_state(st->mouse_x, st->mouse_y, save_btn), + cancel_btn, "Cancel", + dialog_button_state(st->mouse_x, st->mouse_y, cancel_btn), theme); } @@ -301,18 +319,21 @@ static void chpwd_submit(ChPwdDialogState* st) { } montauk::user::change_password(st->username, st->new_pwd); - settings_set_status(st->parent, "Password changed"); + usermanager_set_status(st->parent, "Password changed"); dialog_close_self(st->ds, st); } static void chpwd_on_mouse(Window* win, MouseEvent& ev) { ChPwdDialogState* st = (ChPwdDialogState*)win->app_data; - if (!st || !ev.left_pressed()) return; + if (!st) return; mtk::DesktopHost host(win); int mx = 0; int my = 0; if (!host.map_mouse(ev, &mx, &my)) return; + st->mouse_x = mx; + st->mouse_y = my; + if (!ev.left_pressed()) return; mtk::Theme theme = mtk::make_theme(st->accent); int pad = 16; @@ -372,7 +393,7 @@ static void chpwd_on_close(Window* win) { } } -void open_change_pwd_dialog(SettingsState* parent) { +void open_change_pwd_dialog(UserManagerState* parent) { if (parent->selected_user < 0) return; DesktopState* ds = parent->desktop; @@ -389,6 +410,8 @@ void open_change_pwd_dialog(SettingsState* parent) { st->ds = ds; st->parent = parent; st->accent = ds->settings.accent_color; + st->mouse_x = -1; + st->mouse_y = -1; montauk::strncpy(st->username, parent->users[parent->selected_user].username, 31); montauk::strncpy(st->display_name, parent->users[parent->selected_user].display_name, 63); @@ -399,24 +422,21 @@ void open_change_pwd_dialog(SettingsState* parent) { win->on_close = chpwd_on_close; } -// ============================================================================ -// Delete User Dialog -// ============================================================================ - struct DeleteUserDialogState { DesktopState* ds; - SettingsState* parent; + UserManagerState* parent; char username[32]; bool hover_delete; bool hover_cancel; }; static void deluser_on_draw(Window* win, Framebuffer& fb) { + (void)fb; DeleteUserDialogState* st = (DeleteUserDialogState*)win->app_data; if (!st) return; Canvas c(win); - mtk::Theme theme = settings_theme(st->parent); + mtk::Theme theme = usermanager_theme(st->parent); c.fill(theme.window_bg); int sfh = system_font_height(); @@ -476,7 +496,7 @@ static void deluser_on_mouse(Window* win, MouseEvent& ev) { montauk::user::delete_user(st->username); st->parent->users_loaded = false; st->parent->selected_user = -1; - settings_set_status(st->parent, "User deleted"); + usermanager_set_status(st->parent, "User deleted"); dialog_close_self(st->ds, st); return; } @@ -495,7 +515,7 @@ static void deluser_on_key(Window* win, const Montauk::KeyEvent& key) { montauk::user::delete_user(st->username); st->parent->users_loaded = false; st->parent->selected_user = -1; - settings_set_status(st->parent, "User deleted"); + usermanager_set_status(st->parent, "User deleted"); dialog_close_self(st->ds, st); } if (key.scancode == 0x01) { @@ -510,12 +530,12 @@ static void deluser_on_close(Window* win) { } } -void open_delete_user_dialog(SettingsState* parent) { +void open_delete_user_dialog(UserManagerState* parent) { if (parent->selected_user < 0) return; DesktopState* ds = parent->desktop; if (montauk::streq(parent->users[parent->selected_user].username, ds->current_user)) { - settings_set_status(parent, "Cannot delete current user"); + usermanager_set_status(parent, "Cannot delete current user"); return; } @@ -540,4 +560,4 @@ void open_delete_user_dialog(SettingsState* parent) { win->on_close = deluser_on_close; } -} // namespace settings_app +} // namespace usermanager_app diff --git a/programs/src/desktop/apps/usermanager/usermanager_internal.hpp b/programs/src/desktop/apps/usermanager/usermanager_internal.hpp new file mode 100644 index 0000000..0ead6fa --- /dev/null +++ b/programs/src/desktop/apps/usermanager/usermanager_internal.hpp @@ -0,0 +1,48 @@ +/* + * usermanager_internal.hpp + * Shared declarations for the embedded desktop user management applet + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once + +#include "../apps_common.hpp" +#include +#include + +namespace usermanager_app { + +struct UserManagerState { + DesktopState* desktop; + montauk::user::UserInfo users[16]; + int user_count; + int selected_user; + bool users_loaded; + char status_msg[128]; + uint64_t status_time; + int mouse_x; + int mouse_y; +}; + +inline constexpr int USER_BTN_H = 30; +inline constexpr int USER_BTN_W = 100; +inline constexpr int USER_FIELD_H = 32; + +inline mtk::Theme usermanager_theme(const UserManagerState* st) { + return mtk::make_theme(st->desktop->settings.accent_color); +} + +bool usermanager_status_visible(UserManagerState* st); +void usermanager_set_status(UserManagerState* st, const char* msg); +void usermanager_reload(UserManagerState* st); +int user_row_height(); +void usermanager_update_mouse(UserManagerState* st, int mx, int my); + +void usermanager_draw(Canvas& c, UserManagerState* st); +bool usermanager_handle_click(Window* win, UserManagerState* st, int mx, int my); + +void open_add_user_dialog(UserManagerState* parent); +void open_change_pwd_dialog(UserManagerState* parent); +void open_delete_user_dialog(UserManagerState* parent); + +} // namespace usermanager_app diff --git a/programs/src/desktop/apps/settings/users.cpp b/programs/src/desktop/apps/usermanager/users.cpp similarity index 66% rename from programs/src/desktop/apps/settings/users.cpp rename to programs/src/desktop/apps/usermanager/users.cpp index 4b770ab..739e6c8 100644 --- a/programs/src/desktop/apps/settings/users.cpp +++ b/programs/src/desktop/apps/usermanager/users.cpp @@ -1,32 +1,57 @@ /* * users.cpp - * Users tab for the embedded desktop settings app + * User management view for the embedded desktop user management applet * Copyright (c) 2026 Daniel Hammer */ -#include "settings_internal.hpp" +#include "usermanager_internal.hpp" -namespace settings_app { +namespace usermanager_app { -void users_reload(SettingsState* st) { +static mtk::WidgetState button_state(const UserManagerState* st, + const Rect& rect, + bool enabled, + bool active = false) { + return mtk::widget_state(active, rect.contains(st->mouse_x, st->mouse_y), enabled); +} + +void usermanager_reload(UserManagerState* st) { st->user_count = montauk::user::load_users(st->users, 16); st->users_loaded = true; } +bool usermanager_status_visible(UserManagerState* st) { + return st->status_msg[0] && + (montauk::get_milliseconds() - st->status_time < 4000); +} + +void usermanager_set_status(UserManagerState* st, const char* msg) { + montauk::strncpy(st->status_msg, msg ? msg : "", (int)sizeof(st->status_msg)); + st->status_time = montauk::get_milliseconds(); +} + int user_row_height() { return system_font_height() * 2 + 12; } -void settings_draw_users(Canvas& c, SettingsState* st) { - if (!st->users_loaded) users_reload(st); +void usermanager_update_mouse(UserManagerState* st, int mx, int my) { + if (!st) return; + st->mouse_x = mx; + st->mouse_y = my; +} - mtk::Theme theme = settings_theme(st); +void usermanager_draw(Canvas& c, UserManagerState* st) { + if (!st->users_loaded) usermanager_reload(st); + + mtk::Theme theme = usermanager_theme(st); int x = 16; int y = 20; int sfh = system_font_height(); int content_w = c.w - 2 * x; int row_h = user_row_height(); + c.fill(theme.window_bg); + if (!st->desktop->is_admin) { c.text(x, y, "Admin access required to manage users.", theme.text_muted); return; @@ -74,7 +99,7 @@ void settings_draw_users(Canvas& c, SettingsState* st) { mtk::draw_separator(c, x, y, content_w, theme); y += 16; - if (settings_status_visible(st)) { + if (usermanager_status_visible(st)) { c.text(x, y, st->status_msg, theme.accent); y += sfh + 8; } @@ -85,16 +110,16 @@ void settings_draw_users(Canvas& c, SettingsState* st) { bool has_selection = st->selected_user >= 0; mtk::draw_button(c, add_btn, "Add User", mtk::BUTTON_PRIMARY, - mtk::widget_state(), theme); + button_state(st, add_btn, true), theme); mtk::draw_button(c, change_btn, "Change Pwd", mtk::BUTTON_SECONDARY, - mtk::widget_state(false, false, has_selection), theme); + button_state(st, change_btn, has_selection), theme); mtk::draw_button(c, delete_btn, "Delete", mtk::BUTTON_DANGER, - mtk::widget_state(false, false, has_selection), theme); + button_state(st, delete_btn, has_selection), theme); } -bool settings_handle_users_click(Window* win, SettingsState* st, int mx, int cy) { +bool usermanager_handle_click(Window* win, UserManagerState* st, int mx, int my) { if (!st->desktop->is_admin) return false; - if (!st->users_loaded) users_reload(st); + if (!st->users_loaded) usermanager_reload(st); int x = 16; int sfh = system_font_height(); @@ -104,7 +129,7 @@ bool settings_handle_users_click(Window* win, SettingsState* st, int mx, int cy) for (int i = 0; i < st->user_count; i++) { Rect row = {x, y, content_w, row_h}; - if (row.contains(mx, cy)) { + if (row.contains(mx, my)) { st->selected_user = (st->selected_user == i) ? -1 : i; return true; } @@ -112,22 +137,22 @@ bool settings_handle_users_click(Window* win, SettingsState* st, int mx, int cy) } y += 8 + 16; - if (settings_status_visible(st)) { + if (usermanager_status_visible(st)) { y += sfh + 8; } Rect add_btn = {x, y, 90, USER_BTN_H}; Rect change_btn = {x + 98, y, USER_BTN_W, USER_BTN_H}; Rect delete_btn = {x + 98 + USER_BTN_W + 8, y, 70, USER_BTN_H}; - if (add_btn.contains(mx, cy)) { + if (add_btn.contains(mx, my)) { open_add_user_dialog(st); return true; } - if (change_btn.contains(mx, cy)) { + if (change_btn.contains(mx, my)) { if (st->selected_user >= 0) open_change_pwd_dialog(st); return true; } - if (delete_btn.contains(mx, cy)) { + if (delete_btn.contains(mx, my)) { if (st->selected_user >= 0) open_delete_user_dialog(st); return true; } @@ -135,4 +160,4 @@ bool settings_handle_users_click(Window* win, SettingsState* st, int mx, int cy) return false; } -} // namespace settings_app +} // namespace usermanager_app diff --git a/programs/src/desktop/compose.cpp b/programs/src/desktop/compose.cpp index 37094d0..36fcc5a 100644 --- a/programs/src/desktop/compose.cpp +++ b/programs/src/desktop/compose.cpp @@ -233,6 +233,8 @@ void gui::desktop_compose(DesktopState* ds) { if (cmx + CTX_MENU_W > sw) cmx = sw - CTX_MENU_W; if (cmy + cmh > sh) cmy = sh - cmh; + ensure_filemanager_icons_loaded(ds); + draw_shadow(fb, cmx, cmy, CTX_MENU_W, cmh, 4, colors::SHADOW); fill_rounded_rect(fb, cmx, cmy, CTX_MENU_W, cmh, 8, colors::MENU_BG); draw_rect(fb, cmx, cmy, CTX_MENU_W, cmh, colors::BORDER); @@ -241,7 +243,7 @@ void gui::desktop_compose(DesktopState* ds) { CtxItem ctx_items[CTX_ITEM_COUNT] = { { "Terminal", &ds->icon_terminal }, { "Files", &ds->icon_filemanager }, - { "About", &ds->icon_settings }, + { "System Configuration", &ds->icon_system_tools_menu }, { "Sleep", &ds->icon_sleep }, { "Reboot", &ds->icon_reboot }, { "Shutdown", &ds->icon_shutdown }, diff --git a/programs/src/desktop/desktop_builtin.cpp b/programs/src/desktop/desktop_builtin.cpp new file mode 100644 index 0000000..9d70846 --- /dev/null +++ b/programs/src/desktop/desktop_builtin.cpp @@ -0,0 +1,103 @@ +/* + * desktop_builtin.cpp + * Shared builtin applet metadata and dispatch helpers + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "desktop_internal.hpp" +#include + +static const DesktopBuiltinEntry builtin_entries[] = { + { DESKTOP_BUILTIN_FILES, "Files", 0, false, false, "0:/icons/system-file-manager.svg" }, + { DESKTOP_BUILTIN_DESKTOP_SETTINGS, "Desktop Settings", -1, false, true, + "0:/icons/preferences-desktop-apps.svg" }, + { DESKTOP_BUILTIN_SYSTEM_CONFIGURATION, "System Configuration", -1, true, false, + "0:/icons/preferences-system.svg" }, + { DESKTOP_BUILTIN_USER_MANAGEMENT, "User Management", -1, false, true, + "0:/icons/user-home.svg" }, + { DESKTOP_BUILTIN_LOCK_SCREEN, "Lock Screen", -1, true, false, nullptr }, + { DESKTOP_BUILTIN_LOG_OUT, "Log Out", -1, true, false, nullptr }, + { DESKTOP_BUILTIN_SLEEP, "Sleep", -1, true, false, nullptr }, + { DESKTOP_BUILTIN_REBOOT, "Reboot", -1, true, false, nullptr }, + { DESKTOP_BUILTIN_SHUTDOWN, "Shutdown", -1, true, false, nullptr }, +}; + +static constexpr int builtin_entry_count = sizeof(builtin_entries) / sizeof(builtin_entries[0]); + +const DesktopBuiltinEntry* desktop_builtin_registry(int* count) { + if (count) *count = builtin_entry_count; + return builtin_entries; +} + +const DesktopBuiltinEntry* desktop_builtin_entry(DesktopBuiltinId id) { + for (int i = 0; i < builtin_entry_count; i++) { + if (builtin_entries[i].id == id) return &builtin_entries[i]; + } + return nullptr; +} + +SvgIcon* desktop_builtin_menu_icon(DesktopState* ds, DesktopBuiltinId id) { + if (!ds) return nullptr; + + switch (id) { + case DESKTOP_BUILTIN_FILES: return &ds->icon_filemanager; + case DESKTOP_BUILTIN_DESKTOP_SETTINGS: return &ds->icon_settings; + case DESKTOP_BUILTIN_SYSTEM_CONFIGURATION: + ensure_filemanager_icons_loaded(ds); + return &ds->icon_system_tools_menu; + case DESKTOP_BUILTIN_LOCK_SCREEN: return &ds->icon_lock; + case DESKTOP_BUILTIN_LOG_OUT: return &ds->icon_logout; + case DESKTOP_BUILTIN_SLEEP: return &ds->icon_sleep; + case DESKTOP_BUILTIN_REBOOT: return &ds->icon_reboot; + case DESKTOP_BUILTIN_SHUTDOWN: return &ds->icon_shutdown; + default: return nullptr; + } +} + +void desktop_lock_screen(DesktopState* ds) { + if (!ds) return; + + ds->screen_locked = true; + ds->lock_password[0] = '\0'; + ds->lock_password_len = 0; + ds->lock_error[0] = '\0'; + ds->lock_show_error = false; + ds->app_menu_open = false; + desktop_close_launcher(ds); + ds->ctx_menu_open = false; + ds->net_popup_open = false; + ds->vol_popup_open = false; + + // Cache display name for lock screen rendering + montauk::strncpy(ds->lock_display_name, ds->current_user, sizeof(ds->lock_display_name)); + montauk::user::UserInfo users[16]; + int count = montauk::user::load_users(users, 16); + for (int i = 0; i < count; i++) { + if (montauk::streq(users[i].username, ds->current_user)) { + if (users[i].display_name[0]) { + montauk::strncpy(ds->lock_display_name, users[i].display_name, + sizeof(ds->lock_display_name)); + } + break; + } + } +} + +void desktop_launch_builtin(DesktopState* ds, DesktopBuiltinId id) { + if (!ds) return; + + switch (id) { + case DESKTOP_BUILTIN_FILES: open_filemanager(ds); break; + case DESKTOP_BUILTIN_DESKTOP_SETTINGS: open_settings(ds); break; + case DESKTOP_BUILTIN_SYSTEM_CONFIGURATION: + open_filemanager_path(ds, FILEMANAGER_PATH_SYSTEM_CONFIGURATION); + break; + case DESKTOP_BUILTIN_USER_MANAGEMENT: open_user_manager(ds); break; + case DESKTOP_BUILTIN_REBOOT: open_reboot_dialog(ds); break; + case DESKTOP_BUILTIN_SHUTDOWN: open_shutdown_dialog(ds); break; + case DESKTOP_BUILTIN_LOG_OUT: montauk::exit(0); break; + case DESKTOP_BUILTIN_LOCK_SCREEN: desktop_lock_screen(ds); break; + case DESKTOP_BUILTIN_SLEEP: open_sleep_dialog(ds); break; + default: break; + } +} diff --git a/programs/src/desktop/desktop_internal.hpp b/programs/src/desktop/desktop_internal.hpp index 555fc4e..1a7a08c 100644 --- a/programs/src/desktop/desktop_internal.hpp +++ b/programs/src/desktop/desktop_internal.hpp @@ -22,7 +22,7 @@ static constexpr int MENU_DIV_H = 10; struct MenuRow { bool is_category; char label[48]; - int app_id; // embedded dispatch ID, or -1 for categories/dividers + DesktopBuiltinId app_id; // desktop builtin ID, or DESKTOP_BUILTIN_NONE for categories/dividers bool external; // true = spawn binary_path char binary_path[128]; // full VFS path for external apps bool launch_with_home; // true = pass home_dir as first argument @@ -74,7 +74,7 @@ inline int menu_add_category(const char* label) { MenuRow& r = menu_rows[menu_row_count++]; r.is_category = true; montauk::strncpy(r.label, label, sizeof(r.label)); - r.app_id = -1; + r.app_id = DESKTOP_BUILTIN_NONE; r.external = false; r.binary_path[0] = '\0'; r.launch_with_home = false; @@ -82,7 +82,7 @@ inline int menu_add_category(const char* label) { return menu_row_count - 1; } -inline int menu_add_embedded(const char* label, int app_id, SvgIcon* icon) { +inline int menu_add_embedded(const char* label, DesktopBuiltinId app_id, SvgIcon* icon) { if (menu_row_count >= MAX_MENU_ROWS) return -1; MenuRow& r = menu_rows[menu_row_count++]; r.is_category = false; @@ -100,7 +100,7 @@ inline int menu_add_external(const char* label, const char* binary, SvgIcon* ico MenuRow& r = menu_rows[menu_row_count++]; r.is_category = false; montauk::strncpy(r.label, label, sizeof(r.label)); - r.app_id = -1; + r.app_id = DESKTOP_BUILTIN_NONE; r.external = true; montauk::strncpy(r.binary_path, binary, sizeof(r.binary_path)); r.launch_with_home = launch_with_home; diff --git a/programs/src/desktop/input.cpp b/programs/src/desktop/input.cpp index 71a9ffb..a531d0f 100644 --- a/programs/src/desktop/input.cpp +++ b/programs/src/desktop/input.cpp @@ -7,35 +7,6 @@ #include "desktop_internal.hpp" #include -// ============================================================================ -// Lock Screen Input -// ============================================================================ - -static void lock_screen(DesktopState* ds) { - ds->screen_locked = true; - ds->lock_password[0] = '\0'; - ds->lock_password_len = 0; - ds->lock_error[0] = '\0'; - ds->lock_show_error = false; - ds->app_menu_open = false; - desktop_close_launcher(ds); - ds->ctx_menu_open = false; - ds->net_popup_open = false; - ds->vol_popup_open = false; - - // Cache display name for lock screen rendering - montauk::strncpy(ds->lock_display_name, ds->current_user, sizeof(ds->lock_display_name)); - montauk::user::UserInfo users[16]; - int count = montauk::user::load_users(users, 16); - for (int i = 0; i < count; i++) { - if (montauk::streq(users[i].username, ds->current_user)) { - if (users[i].display_name[0]) - montauk::strncpy(ds->lock_display_name, users[i].display_name, sizeof(ds->lock_display_name)); - break; - } - } -} - static bool try_unlock(DesktopState* ds) { ds->lock_show_error = false; @@ -175,10 +146,10 @@ void gui::desktop_handle_mouse(DesktopState* ds) { switch (item_idx) { case 0: open_terminal(ds); break; case 1: open_filemanager(ds); break; - case 2: open_settings(ds); break; - case 3: open_sleep_dialog(ds); break; - case 4: open_reboot_dialog(ds); break; - case 5: open_shutdown_dialog(ds); break; + case 2: desktop_launch_builtin(ds, DESKTOP_BUILTIN_SYSTEM_CONFIGURATION); break; + case 3: desktop_launch_builtin(ds, DESKTOP_BUILTIN_SLEEP); break; + case 4: desktop_launch_builtin(ds, DESKTOP_BUILTIN_REBOOT); break; + case 5: desktop_launch_builtin(ds, DESKTOP_BUILTIN_SHUTDOWN); break; } return; } @@ -351,16 +322,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) { montauk::spawn(row.binary_path); } } else { - // Dispatch desktop-owned launcher entry - switch (row.app_id) { - case 1: open_filemanager(ds); break; - case 11: open_settings(ds); break; - case 12: open_reboot_dialog(ds); break; - case 14: open_shutdown_dialog(ds); break; - case 16: montauk::exit(0); break; // Log Out - case 17: lock_screen(ds); break; // Lock Screen - case 18: open_sleep_dialog(ds); break; // Sleep - } + desktop_launch_builtin(ds, row.app_id); } ds->app_menu_open = false; } @@ -746,7 +708,7 @@ void gui::desktop_handle_keyboard(DesktopState* ds, const Montauk::KeyEvent& key // Global shortcuts (only on key press) if (key.pressed && key.ctrl && key.alt) { if (key.ascii == 'l' || key.ascii == 'L') { - lock_screen(ds); + desktop_lock_screen(ds); return; } if (key.ascii == 't' || key.ascii == 'T') { diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index dace1bb..9b0ef4c 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -110,39 +110,20 @@ void desktop_scan_apps(DesktopState* ds) { static const char* CATEGORY_NAMES[] = { "Applications", "Internet", "System", "Games" }; static constexpr int NUM_CATEGORIES = 4; -// Embedded app definitions: { display_name, app_id, category_index } -struct EmbeddedAppDef { - const char* name; - int app_id; - int category; // index into CATEGORY_NAMES -}; - -static const EmbeddedAppDef embedded_apps[] = { - { "Files", 1, 0 }, -}; - -static constexpr int NUM_EMBEDDED = sizeof(embedded_apps) / sizeof(embedded_apps[0]); - -// Resolve embedded app_id to an icon pointer in DesktopState -static SvgIcon* icon_for_embedded(DesktopState* ds, int app_id) { - switch (app_id) { - case 1: return &ds->icon_filemanager; - default: return nullptr; - } -} - void desktop_build_menu(DesktopState* ds) { menu_row_count = 0; + int builtin_count = 0; + const DesktopBuiltinEntry* builtins = desktop_builtin_registry(&builtin_count); // Build each category for (int cat = 0; cat < NUM_CATEGORIES; cat++) { menu_add_category(CATEGORY_NAMES[cat]); - // Add embedded apps in this category - for (int e = 0; e < NUM_EMBEDDED; e++) { - if (embedded_apps[e].category == cat) { - menu_add_embedded(embedded_apps[e].name, embedded_apps[e].app_id, - icon_for_embedded(ds, embedded_apps[e].app_id)); + // Add desktop builtins in this category + for (int b = 0; b < builtin_count; b++) { + if (builtins[b].launcher_category == cat) { + menu_add_embedded(builtins[b].label, builtins[b].id, + desktop_builtin_menu_icon(ds, builtins[b].id)); } } @@ -158,12 +139,12 @@ void desktop_build_menu(DesktopState* ds) { // Divider + always-visible entries menu_add_category(""); // divider (cat 4, always expanded) - menu_add_embedded("Settings", 11, &ds->icon_settings); - menu_add_embedded("Lock Screen", 17, &ds->icon_lock); - menu_add_embedded("Log Out", 16, &ds->icon_logout); - menu_add_embedded("Sleep", 18, &ds->icon_sleep); - menu_add_embedded("Reboot", 12, &ds->icon_reboot); - menu_add_embedded("Shutdown", 14, &ds->icon_shutdown); + for (int b = 0; b < builtin_count; b++) { + if (builtins[b].launcher_footer) { + menu_add_embedded(builtins[b].label, builtins[b].id, + desktop_builtin_menu_icon(ds, builtins[b].id)); + } + } } // ============================================================================ @@ -211,7 +192,7 @@ void gui::desktop_init(DesktopState* ds) { ds->icon_exec_lg = svg_load("0:/icons/utilities-terminal.svg", 48, 48, defColor); // drive icons loaded lazily by file manager to reduce startup heap pressure - ds->icon_settings = svg_load("0:/icons/help-about.svg", 20, 20, defColor); + ds->icon_settings = svg_load("0:/icons/preferences-desktop-apps.svg", 20, 20, defColor); ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor); ds->icon_shutdown = svg_load("0:/icons/system-shutdown.svg", 20, 20, defColor); ds->icon_sleep = svg_load("0:/icons/sleep.svg", 20, 20, defColor); diff --git a/scripts/copy_icons.sh b/scripts/copy_icons.sh index dca1651..6a81ff5 100755 --- a/scripts/copy_icons.sh +++ b/scripts/copy_icons.sh @@ -51,6 +51,7 @@ ICONS=( "apps/scalable/utilities-terminal.svg" "apps/scalable/system-file-manager.svg" "apps/scalable/preferences-desktop-apps.svg" + "categories/scalable/preferences-system.svg" "apps/scalable/accessories-calculator.svg" "apps/scalable/web-browser.svg" "places/scalable/folder.svg"