feat: system settings stored as applets in virtual folder
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -10,24 +10,17 @@
|
||||
#include "../../wallpaper.hpp"
|
||||
#include <gui/mtk.hpp>
|
||||
#include <montauk/config.h>
|
||||
#include <montauk/user.h>
|
||||
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+60
-40
@@ -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
|
||||
@@ -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 <gui/mtk.hpp>
|
||||
#include <montauk/user.h>
|
||||
|
||||
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
|
||||
+44
-19
@@ -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
|
||||
@@ -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 },
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* desktop_builtin.cpp
|
||||
* Shared builtin applet metadata and dispatch helpers
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "desktop_internal.hpp"
|
||||
#include <montauk/user.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -7,35 +7,6 @@
|
||||
#include "desktop_internal.hpp"
|
||||
#include <montauk/user.h>
|
||||
|
||||
// ============================================================================
|
||||
// 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') {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user