/* appreg.c — 应用注册表 + 桌面图标(M0 自 main.c 平移) * * 注册来源:内置 / LINUXAPP.CNF / PEAPPS.CNF / user/desktop/*.lnk / STARTMENU.CNF */ #include "desktop.h" #include "../UTSM/include/utsm/fluent_ui.h" /* M3: desktop_app_icon 图标映射 */ /* ---- 全局状态 ---- */ app_descriptor g_apps[MAX_APPS]; int g_app_count = 0; desktop_icon g_icons[MAX_ICONS]; int g_icon_count = 0; /* ============================================================ * Winux-Kate 应用图标(40×40,青色几何图形) * ============================================================ */ void draw_circle_outline(du_context *ctx, int cx, int cy, int r, u32 color) { for (int dy = -r; dy <= r; dy++) { for (int dx = -r; dx <= r; dx++) { int d2 = dx * dx + dy * dy; if (d2 <= r * r && d2 > (r - 1) * (r - 1)) { du_pixel(ctx, cx + dx, cy + dy, color); } } } } static void draw_icon_terminal(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); du_fill_rect(ctx, cx + 8, cy + 12, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 10, cy + 14, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 12, cy + 16, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 10, cy + 18, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 8, cy + 20, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 16, cy + 22, 8, 2, KS_ACCENT); } static void draw_icon_editor(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); int widths[] = {24, 20, 22, 14, 20, 18}; for (int i = 0; i < 6; i++) { du_fill_rect(ctx, cx + 8, cy + 8 + i * 4, widths[i], 2, (i == 0) ? KS_ACCENT2 : KS_ACCENT); } } static void draw_icon_folder(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_BORDER, 2); du_fill_rect(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT_DIM); du_rect_outline(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT, 1); du_fill_rect(ctx, cx + 6, cy + 14, 28, 16, KS_BG_SECONDARY); du_rect_outline(ctx, cx + 6, cy + 14, 28, 16, KS_ACCENT, 1); du_fill_rect(ctx, cx + 6, cy + 14, 28, 1, KS_ACCENT); du_fill_rect(ctx, cx + 10, cy + 19, 12, 1, KS_ACCENT2); du_fill_rect(ctx, cx + 10, cy + 23, 16, 1, KS_TEXT_DIM); du_fill_rect(ctx, cx + 10, cy + 26, 10, 1, KS_TEXT_DIM); } static void draw_icon_calc(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); du_fill_rect(ctx, cx + 6, cy + 6, 28, 8, KS_BG_PRIMARY); du_rect_outline(ctx, cx + 6, cy + 6, 28, 8, KS_ACCENT2, 1); du_fill_rect(ctx, cx + 28, cy + 8, 4, 4, KS_ACCENT2); for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) { int bx = cx + 6 + c * 9; int by = cy + 18 + r * 7; du_fill_rect(ctx, bx, by, 7, 5, KS_ACCENT_DIM); du_rect_outline(ctx, bx, by, 7, 5, KS_ACCENT, 1); } } } static void draw_icon_browser(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); int ccx = cx + 20, ccy = cy + 20, r = 11; draw_circle_outline(ctx, ccx, ccy, r, KS_ACCENT); du_fill_rect(ctx, ccx - r, ccy, 2 * r + 1, 1, KS_ACCENT); du_fill_rect(ctx, ccx, ccy - r, 1, 2 * r + 1, KS_ACCENT); du_pixel(ctx, ccx, ccy, KS_ACCENT2); } void draw_app_icon(du_context *ctx, int app_id, int cx, int cy) { const char *n = (app_id >= 0 && app_id < g_app_count) ? g_apps[app_id].name : 0; if (!n) { draw_icon_terminal(ctx, cx, cy); return; } if (n[0]=='e' && n[1]=='d') { draw_icon_editor(ctx, cx, cy); return; } if (n[0]=='p' && n[1]=='r') { draw_icon_editor(ctx, cx, cy); return; } if ((n[0]=='f' && n[1]=='i') || (n[0]=='f' && n[1]=='o')) { draw_icon_folder(ctx, cx, cy); return; } if (n[0]=='c' && n[1]=='a') { draw_icon_calc(ctx, cx, cy); return; } if (n[0]=='b' && n[1]=='r') { draw_icon_browser(ctx, cx, cy); return; } draw_icon_terminal(ctx, cx, cy); } /* M3: app_id → Fluent 线性图标(开始菜单瓦片 / 任务栏共用映射)。 * 返回值即 wf_icon_id(int 传递避免 desktop.h 依赖 fluent_ui.h)。 */ int desktop_app_icon(int app_id) { const char *n = (app_id >= 0 && app_id < g_app_count) ? g_apps[app_id].name : 0; if (!n) return WF_ICON_TERMINAL; if (n[0]=='s' && n[1]=='h') return WF_ICON_TERMINAL; /* shell */ if (n[0]=='c' && n[1]=='m' && n[2]=='d') return WF_ICON_TERMINAL; if (n[0]=='e' && n[1]=='d') return WF_ICON_EDITOR; /* editor */ if (n[0]=='p' && n[1]=='r') return WF_ICON_EDITOR; /* proedit */ if (n[0]=='f' && n[1]=='i') return WF_ICON_FOLDER; /* fileman */ if (n[0]=='c' && n[1]=='a' && n[2]=='l') return WF_ICON_CALC; if (n[0]=='b' && n[1]=='r') return WF_ICON_BROWSER; /* browser */ if (n[0]=='s' && n[1]=='e') return WF_ICON_SETTINGS; /* settings */ if (n[0]=='v' || n[0]=='k') return WF_ICON_BROWSER; /* vscode/kde */ if (n[0]=='s' && n[1]=='y' && n[2]=='s') return WF_ICON_INFO; /* sysinfo */ if (n[0]=='c' && n[1]=='l') return WF_ICON_CLOCK; /* clock */ if (n[0]=='p' && n[1]=='a') return WF_ICON_PICTURE; /* paint */ if (n[0]=='n' && n[1]=='o') return WF_ICON_EDITOR; /* notes */ if (n[0]=='d' && n[1]=='i') return WF_ICON_DISK; /* diskinfo */ return WF_ICON_APPS; } /* ============================================================ * 应用注册 * ============================================================ */ /* ---- VSCode Phase 5: LINUXAPP.CNF Linux 应用注册表 ---- * FAT32 根目录 LINUXAPP.CNF(8.3: LINUXAPPCNF),每行一个 Linux guest 应用: * NAME|DISPLAY|/guest/path|args(args 可含空格,可省略) * '#' 开头为注释。解析结果存静态池,app_descriptor.linux_path 指入。 */ #define LINUXAPP_MAX 8 static char g_lapp_name[LINUXAPP_MAX][12]; static char g_lapp_disp[LINUXAPP_MAX][16]; static char g_lapp_path[LINUXAPP_MAX][96]; static char g_lapp_args[LINUXAPP_MAX][96]; int g_vscode_app_id = -1; /* 第一个名为 "vscode" 的注册项 */ /* 字段拷贝:src[0..n) → dst(NUL 结尾),返回拷贝长度 */ static int lapp_field_copy(char *dst, int dst_cap, const char *src, int n) { if (n >= dst_cap) n = dst_cap - 1; for (int i = 0; i < n; i++) dst[i] = src[i]; dst[n] = 0; return n; } #define UDESK_MAX 12 static char g_ud_name[UDESK_MAX][12]; static char g_ud_disp[UDESK_MAX][48]; static char g_ud_path[UDESK_MAX][96]; static char g_ud_elf[UDESK_MAX][12]; static char g_ud_files[UDESK_MAX][64]; static int g_ud_nfiles; /* 桌面图标按 user/desktop 目录枚举顺序排,不按内置表下标。 */ static int g_desk_order[UDESK_MAX]; static int g_desk_n; static int udesk_name_taken(const char *n) { int i; for (i = 0; i < g_app_count; i++) { if (g_apps[i].name && fat32_lfn_streq_ci(g_apps[i].name, n)) return 1; } return 0; } static int udesk_collect_cb(const f32_entry *e, void *user) { (void)user; const char *nm = (e->has_lfn && e->long_name[0]) ? e->long_name : e->name; if (e->is_dir || !nm[0]) return 0; if (!fat32_lfn_ends_with_ci(nm, ".lnk")) return 0; if (g_ud_nfiles >= UDESK_MAX) return 1; lapp_field_copy(g_ud_files[g_ud_nfiles], 64, nm, kstrlen(nm)); g_ud_nfiles++; return 0; } static int udesk_ascii_ok(const char *s) { if (!s || !s[0]) return 0; while (*s) { if ((unsigned char)*s > 0x7E) return 0; s++; } return 1; } static int kind_word(const char *kind, const char *word) { int i = 0; while (word[i]) { char c = kind[i]; char w = word[i]; if (c >= 'a' && c <= 'z') c = (char)(c - 32); if (w >= 'a' && w <= 'z') w = (char)(w - 32); if (c != w) return 0; i++; } return kind[i] == 0 || kind[i] == ' '; } static int find_app_by_name(const char *name) { int a, i; if (!name || !name[0]) return -1; for (a = 0; a < g_app_count; a++) { const char *nm = g_apps[a].name; if (!nm) continue; i = 0; while (name[i] && nm[i] && name[i] == nm[i]) i++; if (name[i] == 0 && nm[i] == 0) return a; } return -1; } static void desk_place(int app_id) { int i; if (app_id < 0 || app_id >= g_app_count) return; for (i = 0; i < g_desk_n; i++) { if (g_desk_order[i] == app_id) return; } if (g_desk_n >= UDESK_MAX) return; g_apps[app_id].user_desktop = 1; g_desk_order[g_desk_n++] = app_id; } /* 快捷方式种类的第一个词对应内置应用。路径以 '/' 开头不能改写种类。 */ static const char *kind_builtin_name(const char *kind) { if (kind_word(kind, "FILEMAN")) return "fileman"; if (kind_word(kind, "EDITOR")) return "editor"; if (kind_word(kind, "BROWSER")) return "browser"; if (kind_word(kind, "SHELL")) return "shell"; if (kind_word(kind, "CALC")) return "calc"; if (kind_word(kind, "CMD")) return "cmd"; if (kind_word(kind, "PROEDIT")) return "proedit"; if (kind_word(kind, "SETTINGS")) return "settings"; return 0; } static int bind_desktop_builtin(int slot, const char *name) { int a = find_app_by_name(name); if (a < 0) return 0; g_apps[a].display_name = g_ud_disp[slot]; desk_place(a); slog("desktop shortcut -> builtin"); slog(g_ud_disp[slot]); return 1; } static void udesk_parse_lnk(int slot, const u8 *data, u32 size) { const char *lines[4]; int nlen[4]; int n = 0, i; u32 pos = 0; char kind[16]; if (slot < 0 || slot >= UDESK_MAX) return; while (pos < size && n < 4) { u32 eol = pos; while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++; lines[n] = (const char *)data + pos; nlen[n] = (int)(eol - pos); n++; while (eol < size && (data[eol] == '\n' || data[eol] == '\r')) eol++; pos = eol; } if (n < 2) return; if (n >= 4 && nlen[3] > 0) lapp_field_copy(g_ud_name[slot], 12, lines[3], nlen[3]); else lapp_field_copy(g_ud_name[slot], 12, lines[0], nlen[0]); if (udesk_ascii_ok(g_ud_name[slot]) == 0) { g_ud_name[slot][0] = 'a' + (char)slot; g_ud_name[slot][1] = 0; } /* 第一行是桌面标签,允许 UTF-8(文件管理 / 终端 等) */ if (nlen[0] > 0) lapp_field_copy(g_ud_disp[slot], 48, lines[0], nlen[0]); else kstrcpy(g_ud_disp[slot], g_ud_name[slot], 48); lapp_field_copy(kind, 16, lines[1], nlen[1]); g_ud_path[slot][0] = 0; g_ud_elf[slot][0] = 0; /* 种类优先。第三行路径经常以 '/' 开头,不能因此把 ELF / NATIVE 当成 Linux。 */ if (kind_word(kind, "LINUX")) { if (n >= 3) lapp_field_copy(g_ud_path[slot], 96, lines[2], nlen[2]); } else if (kind_word(kind, "NATIVE")) { if (bind_desktop_builtin(slot, g_ud_name[slot])) return; } else { const char *bn = kind_builtin_name(kind); if (bn && bind_desktop_builtin(slot, bn)) return; if (bind_desktop_builtin(slot, g_ud_name[slot])) return; if (nlen[1] >= 11) lapp_field_copy(g_ud_elf[slot], 12, lines[1], 11); else f32_name_to_83_w(kind, g_ud_elf[slot]); g_ud_elf[slot][11] = 0; } if (udesk_name_taken(g_ud_name[slot])) return; if (!kind_word(kind, "NATIVE") && !g_ud_path[slot][0] && !g_ud_elf[slot][0]) return; if (g_app_count >= MAX_APPS) return; { int id = g_app_count; g_apps[id] = (app_descriptor){ g_ud_name[slot], g_ud_disp[slot], 720, 480, 0, 0, 0, 0, g_ud_elf[slot][0] ? g_ud_elf[slot] : 0, g_ud_path[slot][0] ? g_ud_path[slot] : 0, 0, 0, 0, 0 }; g_app_count++; desk_place(id); } slog("user/desktop icon:"); slog(g_ud_disp[slot]); (void)i; } static void register_user_desktop_icons(void) { u32 dclus = 0; int i; g_ud_nfiles = 0; g_desk_n = 0; if (f32_find_path_dir_lfn_w("user/desktop", &dclus) != 0) { slog("user/desktop missing, skip shared icons"); return; } if (f32_list_dir_w(dclus, udesk_collect_cb, 0) != 0) return; for (i = 0; i < g_ud_nfiles && g_app_count < MAX_APPS; i++) { char path[96]; u8 *data = 0; u32 size = 0; kstrcpy(path, "user/desktop/", 96); kstrcat(path, g_ud_files[i], 96); if (f32_read_path_lfn_w(path, &data, &size) != 0 || !data || !size) continue; udesk_parse_lnk(i, data, size); } } static void register_linux_apps(void) { u8 *data = 0; u32 size = 0; char n11[11]; if (f32_name_to_83_w("LINUXAPP.CNF", n11) != 0) return; if (f32_read_root_file_w(n11, &data, &size) != 0) return; /* 无配置文件:跳过 */ int li = 0; /* 已注册数量 */ u32 pos = 0; while (pos < size && li < LINUXAPP_MAX && g_app_count < MAX_APPS) { /* 取一行 */ u32 eol = pos; while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++; u32 line_len = eol - pos; /* 跳过到下一行 */ u32 next = eol; while (next < size && (data[next] == '\n' || data[next] == '\r')) next++; if (line_len == 0 || data[pos] == '#') { pos = next; continue; } /* 按 '|' 切 4 段:NAME|DISPLAY|PATH|ARGS */ const char *line = (const char *)data + pos; int f0 = -1, f1 = -1, f2 = -1; /* 三个分隔符位置 */ for (u32 i = 0; i < line_len; i++) { if (line[i] == '|') { if (f0 < 0) f0 = (int)i; else if (f1 < 0) f1 = (int)i; else if (f2 < 0) { f2 = (int)i; break; } } } if (f0 > 0 && f1 > f0) { int path_beg = f1 + 1; int path_end = (f2 > path_beg) ? f2 : (int)line_len; int args_beg = (f2 > path_beg) ? f2 + 1 : (int)line_len; lapp_field_copy(g_lapp_name[li], 12, line, f0); lapp_field_copy(g_lapp_disp[li], 16, line + f0 + 1, f1 - f0 - 1); lapp_field_copy(g_lapp_path[li], 96, line + path_beg, path_end - path_beg); if (args_beg < (int)line_len) lapp_field_copy(g_lapp_args[li], 96, line + args_beg, (int)line_len - args_beg); else g_lapp_args[li][0] = 0; if (g_lapp_path[li][0] == '/') { g_apps[g_app_count] = (app_descriptor){ g_lapp_name[li], g_lapp_disp[li], 1024, 768, 0, 0, 0, 0, 0, g_lapp_path[li], g_lapp_args[li] }; /* 记录 VSCode app id(IDE attach 共享启动路径用)。 * P7.4 匹配收紧:精确匹配 "VSCODE"(第 7 字符须为 NUL), * VSCODE2 等多实例条目不占用共享启动入口。 */ if (g_vscode_app_id < 0 && g_lapp_name[li][0] == 'V' && g_lapp_name[li][1] == 'S' && g_lapp_name[li][2] == 'C' && g_lapp_name[li][3] == 'O' && g_lapp_name[li][4] == 'D' && g_lapp_name[li][5] == 'E' && g_lapp_name[li][6] == 0) g_vscode_app_id = g_app_count; g_app_count++; li++; } } pos = next; } } /* ---- P5d: PEAPPS.CNF Windows PE 应用注册表 ---- * FAT32 根目录 PEAPPS.CNF(8.3: PEAPPS CNF),每行一个 Windows PE 应用: * NAME|DISPLAY|path/file.exe|args('/' 分隔长路径,args 可含空格可省略) * '#' 开头为注释。解析结果存静态池,app_descriptor.pe_file 指入。 * 仅适用于 GUI PE(消息循环驱动 pump);纯控制台 PE 不会触发消息空转, * 窗口模式下桌面将无响应直到进程退出——控制台程序请走 CMD。 */ #define PEAPP_MAX 4 static char g_papp_name[PEAPP_MAX][12]; static char g_papp_disp[PEAPP_MAX][16]; static char g_papp_file[PEAPP_MAX][96]; static char g_papp_args[PEAPP_MAX][96]; static void register_pe_apps(void) { u8 *data = 0; u32 size = 0; char n11[11]; if (f32_name_to_83_w("PEAPPS.CNF", n11) != 0) return; if (f32_read_root_file_w(n11, &data, &size) != 0) return; /* 无配置文件:跳过 */ int pi = 0; u32 pos = 0; while (pos < size && pi < PEAPP_MAX && g_app_count < MAX_APPS) { u32 eol = pos; while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++; u32 line_len = eol - pos; u32 next = eol; while (next < size && (data[next] == '\n' || data[next] == '\r')) next++; if (line_len == 0 || data[pos] == '#') { pos = next; continue; } /* 按 '|' 切 4 段:NAME|DISPLAY|FILE|ARGS */ const char *line = (const char *)data + pos; int f0 = -1, f1 = -1, f2 = -1; for (u32 i = 0; i < line_len; i++) { if (line[i] == '|') { if (f0 < 0) f0 = (int)i; else if (f1 < 0) f1 = (int)i; else if (f2 < 0) { f2 = (int)i; break; } } } if (f0 > 0 && f1 > f0) { int file_beg = f1 + 1; int file_end = (f2 > file_beg) ? f2 : (int)line_len; int args_beg = (f2 > file_beg) ? f2 + 1 : (int)line_len; lapp_field_copy(g_papp_name[pi], 12, line, f0); lapp_field_copy(g_papp_disp[pi], 16, line + f0 + 1, f1 - f0 - 1); lapp_field_copy(g_papp_file[pi], 96, line + file_beg, file_end - file_beg); if (args_beg < (int)line_len) lapp_field_copy(g_papp_args[pi], 96, line + args_beg, (int)line_len - args_beg); else g_papp_args[pi][0] = 0; if (g_papp_file[pi][0]) { g_apps[g_app_count] = (app_descriptor){ g_papp_name[pi], g_papp_disp[pi], 720, 540, 0, 0, pe_win_on_draw, 0, 0, 0, 0, g_papp_file[pi], g_papp_args[pi] }; g_app_count++; pi++; } } pos = next; } } /* ============================================================ * M3: STARTMENU.CNF 开始菜单注册表 * FAT32 根目录 STARTMENU.CNF(8.3: STARTMENUCNF),每行一个菜单项: * NAME|显示名|path|args(args 可省略) * path 四种: * builtin: → 已注册内置应用(editor/calc/fileman/settings/...) * program/xxx.exe → GUI PE 应用(run_windowed 桌面嵌入,g++ 编译) * bin/xxx.exe → PE 应用(同上;CLI exe 请从 cmd 运行,不入菜单) * bin/xxx.elf → ELF 工具(按路径全屏加载) * /guest/path → Linux guest 应用(exec_async) * 显示名允许 UTF-8 中文(瓦片标签);NAME 为 ASCII(窗口标题)。 * 缺失/为空时回退:全部已注册应用进开始菜单。 * ============================================================ */ int g_sm_ids[SM_MAX_APPS]; char g_sm_labels[SM_MAX_APPS][48]; int g_sm_count = 0; static char g_smn_name[SM_MAX_APPS][12]; static char g_smn_path[SM_MAX_APPS][96]; static char g_smn_args[SM_MAX_APPS][96]; static void sm_add(int app_id, const char *label) { if (g_sm_count >= SM_MAX_APPS) return; if (app_id < 0 || app_id >= g_app_count) return; g_sm_ids[g_sm_count] = app_id; if (label && label[0]) lapp_field_copy(g_sm_labels[g_sm_count], 48, label, kstrlen(label)); else kstrcpy(g_sm_labels[g_sm_count], g_apps[app_id].display_name, 48); g_sm_count++; } static int str_eq(const char *a, const char *b) { int i = 0; while (a[i] && b[i] && a[i] == b[i]) i++; return a[i] == 0 && b[i] == 0; } static int str_ends_with_ci(const char *s, const char *suffix) { int sl = kstrlen(s), fl = kstrlen(suffix); if (sl < fl) return 0; for (int i = 0; i < fl; i++) { char c = s[sl - fl + i], d = suffix[i]; if (c >= 'a' && c <= 'z') c = (char)(c - 32); if (d >= 'a' && d <= 'z') d = (char)(d - 32); if (c != d) return 0; } return 1; } /* 已有同 pe_file / elf_path / linux_path / name 的注册项 → 复用 id */ static int sm_find_existing(const char *name, const char *path) { for (int i = 0; i < g_app_count; i++) { const app_descriptor *a = &g_apps[i]; if (a->pe_file && str_eq(a->pe_file, path)) return i; if (a->elf_path && str_eq(a->elf_path, path)) return i; if (a->linux_path && str_eq(a->linux_path, path)) return i; } return find_app_by_name(name); } static void sm_register_entry(int slot) { const char *name = g_smn_name[slot]; const char *path = g_smn_path[slot]; const char *args = g_smn_args[slot]; const char *label = g_sm_labels[slot]; if (!name[0] || !path[0]) return; /* builtin: → 绑定已注册应用 */ if (path[0]=='b' && path[1]=='u' && path[2]=='i' && path[3]=='l' && path[4]=='t' && path[5]=='i' && path[6]=='n' && path[7]==':') { int a = find_app_by_name(path + 8); if (a >= 0) { sm_add(a, label); } else { slog("[startmenu] builtin not found:"); slog(path + 8); } return; } /* /guest → Linux guest 应用 */ if (path[0] == '/') { int a = sm_find_existing(name, path); if (a >= 0) { sm_add(a, label); return; } if (g_app_count >= MAX_APPS) return; int id = g_app_count; g_apps[id] = (app_descriptor){ name, name, 1024, 768, 0, 0, 0, 0, 0, path, args[0] ? args : 0 }; g_app_count++; sm_add(id, label); return; } /* *.exe → PE 应用(GUI,run_windowed) */ if (str_ends_with_ci(path, ".exe")) { int a = sm_find_existing(name, path); if (a >= 0) { sm_add(a, label); return; } if (g_app_count >= MAX_APPS) return; int id = g_app_count; g_apps[id] = (app_descriptor){ name, name, 760, 560, 0, 0, pe_win_on_draw, 0, 0, 0, 0, path, args[0] ? args : 0 }; g_app_count++; sm_add(id, label); return; } /* *.elf → 按路径加载的外部 ELF 工具(bin/xxx.elf) */ if (str_ends_with_ci(path, ".elf")) { int a = sm_find_existing(name, path); if (a >= 0) { sm_add(a, label); return; } if (g_app_count >= MAX_APPS) return; int id = g_app_count; g_apps[id] = (app_descriptor){ name, name, 800, 560, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; g_apps[id].elf_path = path; g_app_count++; sm_add(id, label); return; } /* 兜底:按 NAME 找已注册应用 */ { int a = find_app_by_name(name); if (a >= 0) sm_add(a, label); } } static void register_startmenu_apps(void) { u8 *data = 0; u32 size = 0; char n11[11]; g_sm_count = 0; if (f32_name_to_83_w("STARTMENU.CNF", n11) == 0 && f32_read_root_file_w(n11, &data, &size) == 0 && data && size) { int slot = 0; u32 pos = 0; while (pos < size && slot < SM_MAX_APPS) { u32 eol = pos; while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++; u32 line_len = eol - pos; u32 next = eol; while (next < size && (data[next] == '\n' || data[next] == '\r')) next++; if (line_len == 0 || data[pos] == '#') { pos = next; continue; } /* 按 '|' 切 4 段:NAME|DISPLAY|PATH|ARGS */ const char *line = (const char *)data + pos; int f0 = -1, f1 = -1, f2 = -1; for (u32 i = 0; i < line_len; i++) { if (line[i] == '|') { if (f0 < 0) f0 = (int)i; else if (f1 < 0) f1 = (int)i; else if (f2 < 0) { f2 = (int)i; break; } } } if (f0 > 0 && f1 > f0) { int path_beg = f1 + 1; int path_end = (f2 > path_beg) ? f2 : (int)line_len; int args_beg = (f2 > path_beg) ? f2 + 1 : (int)line_len; lapp_field_copy(g_smn_name[slot], 12, line, f0); lapp_field_copy(g_sm_labels[slot], 48, line + f0 + 1, f1 - f0 - 1); lapp_field_copy(g_smn_path[slot], 96, line + path_beg, path_end - path_beg); if (args_beg < (int)line_len) lapp_field_copy(g_smn_args[slot], 96, line + args_beg, (int)line_len - args_beg); else g_smn_args[slot][0] = 0; slot++; } pos = next; } for (int i = 0; i < slot; i++) sm_register_entry(i); slog_num("[startmenu] config entries=", g_sm_count); return; } /* 无配置文件:全部已注册应用入菜单 */ for (int i = 0; i < g_app_count && g_sm_count < SM_MAX_APPS; i++) sm_add(i, g_apps[i].display_name); slog("[startmenu] no config, fallback to all apps"); } /* ============================================================ * 应用注册 * ============================================================ */ void register_apps(void) { /* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/ g_apps[0] = (app_descriptor){ "shell", "Terminal", 720, 480, 0, 0, 0, 0, "SHELL ELF" }; g_app_count++; /* 命令行2:cmd.elf(Windows 风格命令行 + PE/EXE 执行)*/ g_apps[1] = (app_descriptor){ "cmd", "CMD", 720, 480, 0, 0, 0, 0, "CMD ELF" }; g_app_count++; g_apps[2] = (app_descriptor){ "editor", "Editor", 600, 450, editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy, 0 }; g_app_count++; g_apps[3] = (app_descriptor){ "fileman", "Files", 880, 560, fileman_on_create, fileman_on_event, fileman_on_draw, fileman_on_destroy, 0 }; g_app_count++; g_apps[4] = (app_descriptor){ "calc", "Calculator", 280, 400, calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy, 0 }; g_app_count++; g_apps[5] = (app_descriptor){ "browser", "Browser", 700, 500, 0, 0, 0, 0, "BROWSER ELF" }; g_app_count++; /* ProEdit — 增强版文本编辑器(语法高亮、设置、目录浏览)*/ g_apps[6] = (app_descriptor){ "proedit", "ProEdit", 750, 550, 0, 0, 0, 0, "PROEDIT ELF" }; g_app_count++; /* Settings — 系统设置(内核 FUCK 配置 + 网络 NETCONF 配置)*/ g_apps[7] = (app_descriptor){ "settings", "Settings", 760, 540, 0, 0, 0, 0, "SETTINGSELF" }; g_app_count++; /* VSCode Phase 5: Linux guest 应用(VSCode 等),来自 LINUXAPP.CNF */ register_linux_apps(); /* P5d: Windows PE 应用,来自 PEAPPS.CNF */ register_pe_apps(); register_user_desktop_icons(); /* M3: 开始菜单注册(STARTMENU.CNF;须在全部来源注册后跑以支持 builtin:/path 复用) */ register_startmenu_apps(); } /* M2: 桌面图标 = user/desktop/*.lnk 用户快捷方式(user_desktop 标记)。 * 内置应用(editor/calc/settings…)不落桌面——经任务栏/开始菜单启动。 * 布局沿用 canvas padding 24px,列宽 104px,gap 18px,左→右换行。 */ static void place_desktop_icon(int app_id, int *x, int *y, int pad, int max_x) { if (g_icon_count >= MAX_ICONS) return; if (app_id < 0 || app_id >= g_app_count) return; if (*x > pad && *x + KATE_ICON_W > max_x) { *x = pad; *y += KATE_ICON_H + KATE_ICON_GAP; } g_icons[g_icon_count].app_id = app_id; g_icons[g_icon_count].x = *x; g_icons[g_icon_count].y = *y; g_icons[g_icon_count].label = g_apps[app_id].display_name; g_icons[g_icon_count].selected = 0; g_icon_count++; *x += KATE_ICON_W + KATE_ICON_GAP; } void setup_desktop_icons(void) { int pad = KATE_ICON_PAD; /* 24 */ int x = pad; int y = g_fluent ? (pad + 8) : (KATE_TOPBAR_H + KATE_PAGE_PAD + pad); int max_x = (int)g_fb_w - pad; /* canvas 右缘 */ int k; if (g_desk_n > 0) { for (k = 0; k < g_desk_n; k++) place_desktop_icon(g_desk_order[k], &x, &y, pad, max_x); return; } for (int i = 0; i < g_app_count; i++) { if (g_icon_count >= MAX_ICONS) break; if (!g_apps[i].user_desktop) continue; /* M2: 只显示 .lnk 快捷方式 */ if (x > pad && x + KATE_ICON_W > max_x) { x = pad; y += KATE_ICON_H + KATE_ICON_GAP; } g_icons[g_icon_count].app_id = i; g_icons[g_icon_count].x = x; g_icons[g_icon_count].y = y; g_icons[g_icon_count].label = g_apps[i].display_name; g_icons[g_icon_count].selected = 0; g_icon_count++; x += KATE_ICON_W + KATE_ICON_GAP; } }