feat(panic): FUCK [panic] mode=strict|fallback + realtest image variant

- UTSM panic_full returns in fallback mode (logs [PANIC:FALLBACK] + code + hit
  count); panic_halt keeps always-halt sites (CPU exception, VMM-E11 LA57,
  VMM-E15 DSK jump, UTSM-E42 test hook). Call sites given safe continuations.
- Mode passed to DSK/FirstInit/login/desktop via DSK_BOOT_FLAG_PANIC_FALLBACK;
  DKM kernel_api.panic / kern.log.panic return to driver; ext4 fatal aborts the
  op via setjmp/longjmp and DSK falls back to FAT32 ESP.
- build.ps1/Makefile: dev=strict, release=fallback, new realtest (release
  options + strict); default -Variant all; serialized iso packing under -j.
- Fix '*/' inside comments (dkm.h, kernel_api.h, bus.c) that broke the build.
- Includes pending work: dkm_bus service bus, mt7920/wlan drivers, desktop
  files/startmenu/prank, pe-tools, diskinfo, docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Linehu
2026-10-12 23:57:19 +08:00
co-authored by Cursor
parent a7e5b2bc7a
commit 8554a82677
84 changed files with 7325 additions and 484 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld
C_SOURCES := main.c input.c wm.c compositor.c apps_builtin.c appreg.c \
launcher.c lxc.c shell.c pages.c f32io.c ascii_font.c zhfont.c \
wallpaper.c taskbar.c textures.c
wallpaper.c taskbar.c textures.c prank.c files.c startmenu.c
OBJECTS := $(C_SOURCES:.c=.o)
.PHONY: all clean
+1 -1
View File
@@ -26,7 +26,7 @@ LDFLAGS := $(SDL_LIBS) -lm
TARGET := desktop_host
SRCS := main.c input.c wm.c compositor.c apps_builtin.c appreg.c \
launcher.c lxc.c shell.c pages.c f32io.c ascii_font.c \
zhfont.c host_main.c
zhfont.c host_main.c prank.c files.c
OBJS := $(SRCS:.c=.o)
.PHONY: all run clean
+345 -26
View File
@@ -1,8 +1,9 @@
/* appreg.c — 应用注册表 + 桌面图标(M0 自 main.c 平移)
*
* 注册来源:内置 / LINUXAPP.CNF / PEAPPS.CNF / user/desktop/*.lnk
* 注册来源:内置 / 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];
@@ -87,14 +88,38 @@ static void draw_icon_browser(du_context *ctx, int cx, int cy) {
}
void draw_app_icon(du_context *ctx, int app_id, int cx, int cy) {
switch (app_id) {
case 0: draw_icon_terminal(ctx, cx, cy); break;
case 1: draw_icon_editor(ctx, cx, cy); break;
case 2: draw_icon_folder(ctx, cx, cy); break;
case 3: draw_icon_calc(ctx, cx, cy); break;
case 4: draw_icon_browser(ctx, cx, cy); break;
default: draw_icon_terminal(ctx, cx, cy); break;
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;
}
/* ============================================================
@@ -122,11 +147,14 @@ static int lapp_field_copy(char *dst, int dst_cap, const char *src, int n) {
#define UDESK_MAX 12
static char g_ud_name[UDESK_MAX][12];
static char g_ud_disp[UDESK_MAX][16];
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;
@@ -156,6 +184,66 @@ static int udesk_ascii_ok(const char *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];
@@ -181,19 +269,24 @@ static void udesk_parse_lnk(int slot, const u8 *data, u32 size) {
g_ud_name[slot][0] = 'a' + (char)slot;
g_ud_name[slot][1] = 0;
}
if (nlen[0] > 0 && udesk_ascii_ok(lines[0]))
lapp_field_copy(g_ud_disp[slot], 16, lines[0], nlen[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], 16);
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;
if (fat32_lfn_streq_ci(kind, "LINUX") || (n >= 3 && lines[2][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 (fat32_lfn_streq_ci(kind, "NATIVE")) {
return;
} 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
@@ -201,16 +294,20 @@ static void udesk_parse_lnk(int slot, const u8 *data, u32 size) {
g_ud_elf[slot][11] = 0;
}
if (udesk_name_taken(g_ud_name[slot])) return;
if (!g_ud_path[slot][0] && !g_ud_elf[slot][0]) return;
if (!kind_word(kind, "NATIVE") && !g_ud_path[slot][0] && !g_ud_elf[slot][0]) return;
if (g_app_count >= MAX_APPS) return;
g_apps[g_app_count] = (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,
1 /* user_desktop:桌面只显示 .lnk */
};
g_app_count++;
{
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;
@@ -220,6 +317,7 @@ 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;
@@ -369,6 +467,203 @@ static void register_pe_apps(void) {
}
}
/* ============================================================
* M3: STARTMENU.CNF 开始菜单注册表
* FAT32 根目录 STARTMENU.CNF(8.3: STARTMENUCNF),每行一个菜单项:
* NAME|显示名|path|args(args 可省略)
* path 四种:
* builtin:<name> → 已注册内置应用(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:<name> → 绑定已注册应用 */
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){
@@ -388,8 +683,8 @@ void register_apps(void) {
};
g_app_count++;
g_apps[3] = (app_descriptor){
"fileman", "Files", 500, 400,
0, 0, 0, 0, "FILEMAN ELF"
"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){
@@ -419,16 +714,40 @@ void register_apps(void) {
/* 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 快捷方式 */
+62
View File
@@ -390,6 +390,68 @@ void editor_on_draw(void *state, app_ctx *ctx) {
}
void editor_on_destroy(void *state) { (void)state; }
/* 文件资源管理器把文本文件送进窗口编辑器(单实例 ADDR_EDWIN)。 */
void editor_open_text(const char *disp, const u8 *data, u32 size) {
int id = -1;
int found = 0;
int ascii = 1;
editor_state *s;
u32 cap = EDITOR_BUF_SIZE - 1;
if (!disp) disp = "";
if (!data) { data = (const u8 *)""; size = 0; }
for (int i = 0; i < g_app_count; i++) {
const char *n = g_apps[i].name;
if (!n || !g_apps[i].on_create) continue;
if (n[0]=='e' && n[1]=='d' && n[2]=='i' && n[3]=='t' &&
n[4]=='o' && n[5]=='r' && n[6]==0) { id = i; break; }
}
if (id < 0) {
slog("fileman: editor missing");
return;
}
launch_app(id);
for (int i = 0; i < g_win_count; i++) {
if (g_windows[i].app_id == id && g_windows[i].visible) { found = 1; break; }
}
if (!found) return;
s = (editor_state *)ADDR_EDWIN;
if (size > cap) size = cap;
s->text_len = 0;
for (u32 k = 0; k < size && s->text_len < (int)cap; k++) {
char c = (char)data[k];
if (c == '\r') continue;
if ((unsigned char)c >= 32 || c == '\n' || c == '\t')
s->text[s->text_len++] = c;
}
s->text[s->text_len] = 0;
s->cursor_pos = 0;
s->cursor_line = 0;
s->cursor_col = 0;
s->scroll_y = 0;
s->modified = 0;
s->has_file = 0;
s->disp[0] = 0;
for (int k = 0; disp[k]; k++) {
if ((unsigned char)disp[k] < 32 || (unsigned char)disp[k] > 126) ascii = 0;
}
for (int i = 0; i < g_win_count; i++) {
desktop_window *w = &g_windows[i];
int p = 0;
const char *prefix = "Editor";
if (w->app_id != id || !w->visible) continue;
while (prefix[p] && p < 46) { w->title[p] = prefix[p]; p++; }
if (ascii && disp[0]) {
const char *sep = " - ";
while (*sep && p < 46) w->title[p++] = *sep++;
for (int k = 0; disp[k] && p < 47; k++) w->title[p++] = disp[k];
}
w->title[p] = 0;
w->dirty = 1;
}
}
/* ============================================================
* 计算器 (Calculator)
* ============================================================ */
+6
View File
@@ -171,6 +171,9 @@ void redraw_all(void) {
/* 亮度调整 */
apply_brightness();
/* 整蛊叠加层(在光标之前,覆盖页面与任务栏) */
prank_draw(&g_fb);
/* 鼠标光标 */
cursor_save_bg(g_mouse_x, g_mouse_y);
cursor_draw(g_mouse_x, g_mouse_y);
@@ -216,6 +219,9 @@ static void redraw_all_fluent(void) {
/* 亮度调整 */
apply_brightness();
/* 整蛊叠加层(在光标之前,覆盖页面与任务栏) */
prank_draw(&g_fb);
/* 鼠标光标 */
cursor_save_bg(g_mouse_x, g_mouse_y);
cursor_draw(g_mouse_x, g_mouse_y);
+54 -1
View File
@@ -15,6 +15,8 @@
* ascii_font.c ASCII 位图字体单副本
* zhfont.c 中文位图字库(.dbf)加载与渲染(M1)
* wallpaper.c 壁纸背景层缓存(M1)
* files.c 窗口化文件资源管理器(目录遍历 + 打开文本)
* startmenu.c M3 Win11 开始菜单(搜索 + 应用瓦片 + 电源行)
*
* 依赖单向:main → shell → (pages/appreg/launcher/lxc) → wm/compositor → input。
* 全局状态按"最 owning 模块定义、此处 extern"原则组织。
@@ -100,7 +102,7 @@ static __inline__ u64 rdtsc(void) {
#define MAX_CPAGES 4 /* 自定义页上限 */
#define MAX_FILES 24 /* FILES 面板条目上限 */
#define MAX_ICONS 24
#define MAX_APPS 24
#define MAX_APPS 32 /* M3: 开始菜单注册 + 内置/LINUXAPP/PEAPPS/lnk 并存 */
/* 应用固定状态地址(单实例策略 → 每类应用唯一实例) */
#define ADDR_TERM1 0x4000000ULL
@@ -109,6 +111,7 @@ static __inline__ u64 rdtsc(void) {
#define ADDR_EDDASH 0x5000000ULL
#define ADDR_EDWIN 0x5100000ULL
#define ADDR_CALC 0x6000000ULL
#define ADDR_FILEMAN 0x6800000ULL /* 文件资源管理器状态(calc 与 sprite 之间) */
/* 双缓冲:渲染到 sprite buffer,完成后一次性 flip 到真实 framebuffer */
#define SPRITE_BUF_ADDR 0x7000000ULL /* 112MB,在 identity-mapped 区域 */
@@ -187,6 +190,9 @@ typedef struct {
const char *pe_args;
/* M2: 1 = 来自 user/desktop/*.lnk 的用户快捷方式(桌面只显示这些) */
int user_desktop;
/* M3: 按路径启动的外部 ELF(STARTMENU.CNF 的 bin/xxx.elf 条目)。
* 与 elf_name(FAT32 根 8.3 名)互斥,launch_app 优先走 elf_path。 */
const char *elf_path;
} app_descriptor;
/* 桌面图标 */
@@ -326,6 +332,12 @@ extern desktop_icon g_icons[MAX_ICONS];
extern int g_icon_count;
extern int g_vscode_app_id; /* 第一个名为 "vscode" 的注册项 */
/* ---- appreg.c:M3 开始菜单注册表(STARTMENU.CNF,缺失时回退全量) ---- */
#define SM_MAX_APPS 24 /* 开始菜单条目上限 */
extern int g_sm_ids[SM_MAX_APPS]; /* → g_apps 下标 */
extern char g_sm_labels[SM_MAX_APPS][48];/* 瓦片中文标签(UTF-8) */
extern int g_sm_count;
/* ---- launcher.c:PE 运行时 ---- */
extern int g_pe_running;
extern int g_pe_win_id;
@@ -463,15 +475,25 @@ void *calc_on_create(app_ctx *ctx);
void calc_on_event(void *state, app_ctx *ctx, desktop_event *ev);
void calc_on_draw(void *state, app_ctx *ctx);
void calc_on_destroy(void *state);
void editor_open_text(const char *disp, const u8 *data, u32 size);
/* ---- files.c:窗口化文件资源管理器 ---- */
void *fileman_on_create(app_ctx *ctx);
void fileman_on_event(void *state, app_ctx *ctx, desktop_event *ev);
void fileman_on_draw(void *state, app_ctx *ctx);
void fileman_on_destroy(void *state);
/* ---- appreg.c ---- */
void register_apps(void);
void setup_desktop_icons(void);
void draw_app_icon(du_context *ctx, int app_id, int cx, int cy);
void draw_circle_outline(du_context *ctx, int cx, int cy, int r, u32 color);
/* M3: app_id → Fluent 图标(返回 wf_icon_id,int 传递避免头依赖) */
int desktop_app_icon(int app_id);
/* ---- launcher.c ---- */
void launch_external_elf(const char *name11);
void launch_external_elf_path(const char *path);
void fill_app_ctx(app_ctx *ctx, desktop_window *win);
void launch_app(int app_id);
void launch_pe_app(int app_id);
@@ -510,9 +532,21 @@ void taskbar_draw(void);
int taskbar_click(int mx, int my);
int taskbar_init(int (*read_to)(const char *, unsigned char *,
unsigned int, unsigned int *));
/* ---- startmenu.c(M3 开始菜单,taskbar 开始按钮弹出) ---- */
void startmenu_open(void);
void startmenu_close(void);
void startmenu_toggle(void);
void startmenu_draw(void); /* taskbar_draw 末尾叠加 */
int startmenu_click(int mx, int my); /* 1=已消费(面板内) */
int startmenu_key(u8 sc, int shift); /* 1=已消费(模态输入) */
extern int g_sm_open;
/* ---- taskbar.c WiFi 快捷卡片 ---- */
extern int g_wifi_open; /* 卡片是否展开 */
int wifi_key(u8 sc, int shift); /* shell.c handle_key 路由;1=已消费 */
/* ---- taskbar.c 快速设置卡片(音量/亮度) ---- */
extern int g_qs_open;
/* M2: logo 纹理 */
extern int g_tb2_start_rect[4];
extern int g_tb2_tv_rect[4];
@@ -523,6 +557,25 @@ extern int g_tb2_vol_rect[4];
extern int g_tb2_pwr_rect[4];
extern int g_tb2_clk_rect[4];
/* ---- prank.c:整蛊系统(FUCK [prank] 段控制) ---- */
extern int g_prank_enabled; /* 总开关 */
extern int g_prank_boot_demo; /* 进桌面后演示秒数,0=关 */
extern int g_prank_interval; /* 使用中自动整蛊间隔(秒),0=关 */
extern int g_prank_fuck; /* FUCK 跟随鼠标遮屏 */
extern int g_prank_bsod; /* 假蓝屏 */
extern int g_prank_invert; /* 反色闪屏 */
extern int g_prank_hang; /* 假"未响应"对话框 */
extern int g_prank_garble; /* 打字乱码 */
extern int g_prank_fly; /* 任务栏图标乱飞 */
extern int g_prank_shutdown; /* 假"系统即将关机"倒计时 */
void prank_config_line(const char *line, int len);
void prank_boot_demo_start(void);
void prank_tick(void);
int prank_active(void);
int prank_wants_redraw(void);
u8 prank_filter_key(u8 sc);
void prank_draw(du_context *ctx);
/* ---- main.c ---- */
void dsk_entry(const dsk_boot_context *ctx);
+4
View File
@@ -36,6 +36,10 @@ int f32_read_path_lfn_w(const char *path, u8 **out_data, u32 *out_size) {
return f32_read_path_lfn(path, out_data, out_size);
}
int f32_read_file_by_clus_w(u32 clus, u32 size, u8 *out_buf, u32 buf_cap) {
return f32_read_file_by_clus(clus, size, out_buf, buf_cap);
}
int f32_read_path_lfn_to_w(const char *path, u8 *dst, u32 cap, u32 *out_size) {
int rc = f32_read_path_lfn_to(path, dst, cap, out_size);
if (rc != 0) slog_num("[f32io] read_to rc=", rc); /* M1 调试:定位失败步骤 */
+2 -1
View File
@@ -26,10 +26,11 @@ int f32_read_root_file_w(const char *name11, u8 **out_data, u32 *out_size);
int f32_write_root_file_w(const char *name11, const u8 *data, u32 size);
int f32_list_root_w(f32_list_cb_fn emit, void *user_data);
/* 目录列举 / 路径定位 / 长路径读取 */
/* 目录列举 / 路径定位 / 长路径读取 / 按簇读文件 */
int f32_list_dir_w(u32 dir_clus, f32_list_entry_fn emit, void *user_data);
int f32_find_path_dir_lfn_w(const char *path, u32 *out_clus);
int f32_read_path_lfn_w(const char *path, u8 **out_data, u32 *out_size);
int f32_read_file_by_clus_w(u32 clus, u32 size, u8 *out_buf, u32 buf_cap);
/* 流式直读到指定缓冲(绕过 f32_data 4MB 上限,zhfont 字库加载用) */
int f32_read_path_lfn_to_w(const char *path, u8 *dst, u32 cap, u32 *out_size);
+586
View File
@@ -0,0 +1,586 @@
/* files.c — 窗口化文件资源管理器
*
* 在桌面窗口里遍历 FAT32(含子目录与长文件名)。
* 单击选中,双击或 Enter 进入目录;文本文件交给内置编辑器。
* 左侧地点:此电脑 / 用户 / 桌面 / 系统。
*/
#include "desktop.h"
#include "zhfont.h"
#define FM_MAX 160
#define FM_NAME 64
#define FM_DEPTH 12
#define FM_TB 34
#define FM_HDR 22
#define FM_ST 22
#define FM_ROW 24
#define FM_SIDE 132
#define FM_READ_MAX 60000u
typedef struct {
char name[FM_NAME];
u32 clus;
u32 size;
u8 is_dir;
u8 is_up;
} fm_ent;
typedef struct {
fm_ent ents[FM_MAX];
int count;
int depth;
int sel;
int scroll;
int last_row;
u64 last_tsc;
u32 path_clus[FM_DEPTH];
char path_name[FM_DEPTH][FM_NAME];
char path_disp[160];
char status[96];
} fm_state;
static u8 g_fm_blob[FM_READ_MAX];
typedef struct {
u32 bg, side, bar, line, text, dim, sel, accent, folder;
} fm_pal;
static void fm_colors(fm_pal *p) {
if (g_fluent) {
p->bg = 0xFF1E1E1Eu;
p->side = 0xFF252526u;
p->bar = 0xFF2D2D2Du;
p->line = 0xFF3A3A3Au;
p->text = 0xFFF3F3F3u;
p->dim = 0xFF9A9A9Au;
p->sel = 0xFF094771u;
p->accent = 0xFF60CDFFu;
p->folder = 0xFFE8B84Au;
} else {
p->bg = KS_BG_PRIMARY;
p->side = KS_BG_SECONDARY;
p->bar = KS_BG_TERTIARY;
p->line = KS_BORDER;
p->text = KS_TEXT_PRIMARY;
p->dim = KS_TEXT_DIM;
p->sel = KS_ACCENT_DIM;
p->accent = KS_ACCENT;
p->folder = KS_ACCENT2;
}
}
static void fm_copy(char *dst, int cap, const char *src) {
int i = 0;
if (cap < 1) return;
while (src[i] && i < cap - 1) { dst[i] = src[i]; i++; }
dst[i] = 0;
}
static void fm_set_status(fm_state *s, const char *msg) {
fm_copy(s->status, (int)sizeof(s->status), msg);
}
static void fm_fit(char *dst, int cap, const char *src, int max_px) {
fm_copy(dst, cap, src);
int n = kstrlen(dst);
while (n > 1 && zh_string_width(dst, 0) > max_px) {
dst[--n] = 0;
while (n > 0 && ((unsigned char)dst[n - 1] & 0xC0) == 0x80) dst[--n] = 0;
}
}
static int fm_name_cmp(const char *a, const char *b) {
while (*a && *b) {
unsigned char ca = (unsigned char)*a, cb = (unsigned char)*b;
if (ca >= 'a' && ca <= 'z') ca = (unsigned char)(ca - 32);
if (cb >= 'a' && cb <= 'z') cb = (unsigned char)(cb - 32);
if (ca != cb) return ca < cb ? -1 : 1;
a++; b++;
}
if (*a) return 1;
if (*b) return -1;
return 0;
}
static void fm_sort(fm_state *s) {
for (int i = 1; i < s->count; i++) {
fm_ent key = s->ents[i];
int j = i - 1;
while (j >= 0) {
int dir_first = (s->ents[j].is_dir != key.is_dir)
? (key.is_dir && !s->ents[j].is_dir)
: (fm_name_cmp(key.name, s->ents[j].name) < 0);
if (!dir_first) break;
s->ents[j + 1] = s->ents[j];
j--;
}
s->ents[j + 1] = key;
}
}
static void fm_build_path(fm_state *s) {
int p = 0;
s->path_disp[p++] = '/';
for (int d = 0; d < s->depth && p < (int)sizeof(s->path_disp) - 2; d++) {
const char *n = s->path_name[d];
while (*n && p < (int)sizeof(s->path_disp) - 2) s->path_disp[p++] = *n++;
s->path_disp[p++] = '/';
}
s->path_disp[p] = 0;
}
static void fm_status_count(fm_state *s) {
int n = 0;
char num[12];
int p = 0;
for (int i = 0; i < s->count; i++) if (!s->ents[i].is_up) n++;
u32dec((u32)n, num);
while (num[p] && p < 80) { s->status[p] = num[p]; p++; }
{
const char *b = " 个项目";
while (*b && p < (int)sizeof(s->status) - 1) s->status[p++] = *b++;
}
s->status[p] = 0;
}
static int fm_collect(const f32_entry *e, void *ud) {
fm_state *s = (fm_state *)ud;
const char *nm;
fm_ent *f;
if (s->count >= FM_MAX) return 1;
nm = (e->has_lfn && e->long_name[0]) ? e->long_name : e->name;
if (!nm || !nm[0]) return 0;
f = &s->ents[s->count];
fm_copy(f->name, FM_NAME, nm);
f->clus = e->clus;
f->size = e->size;
f->is_dir = e->is_dir ? 1 : 0;
f->is_up = 0;
s->count++;
return 0;
}
static void fm_insert_up(fm_state *s) {
if (s->depth <= 0) return;
if (s->count >= FM_MAX) s->count = FM_MAX - 1;
for (int i = s->count; i > 0; i--) s->ents[i] = s->ents[i - 1];
fm_copy(s->ents[0].name, FM_NAME, "..");
s->ents[0].clus = 0;
s->ents[0].size = 0;
s->ents[0].is_dir = 1;
s->ents[0].is_up = 1;
s->count++;
}
static int fm_reload(fm_state *s) {
u32 clus;
s->count = 0;
s->sel = 0;
s->scroll = 0;
s->last_row = -1;
fm_build_path(s);
if (!g_block_read) {
fm_set_status(s, "无块设备");
return -1;
}
clus = (s->depth == 0) ? 0 : s->path_clus[s->depth - 1];
if (f32_list_dir_w(clus, fm_collect, s) != 0) {
s->count = 0;
fm_set_status(s, "无法读取目录");
slog("fileman list failed");
return -1;
}
fm_sort(s);
fm_insert_up(s);
fm_status_count(s);
return 0;
}
static void fm_up(fm_state *s) {
if (s->depth <= 0) return;
s->depth--;
fm_reload(s);
}
static int fm_enter_idx(fm_state *s, int idx) {
char name[FM_NAME];
u32 clus;
if (idx < 0 || idx >= s->count) return -1;
if (s->ents[idx].is_up) { fm_up(s); return 0; }
if (!s->ents[idx].is_dir) return -1;
if (s->depth >= FM_DEPTH) { fm_set_status(s, "目录太深"); return -1; }
clus = s->ents[idx].clus;
if (clus < 2) { fm_set_status(s, "无法进入目录"); return -1; }
fm_copy(name, FM_NAME, s->ents[idx].name);
s->path_clus[s->depth] = clus;
fm_copy(s->path_name[s->depth], FM_NAME, name);
s->depth++;
if (fm_reload(s) != 0) {
s->depth--;
fm_reload(s);
fm_set_status(s, "无法读取目录");
return -1;
}
slog("fileman enter dir");
return 0;
}
static int fm_enter_named(fm_state *s, const char *name) {
for (int i = 0; i < s->count; i++) {
if (s->ents[i].is_up || !s->ents[i].is_dir) continue;
if (fat32_lfn_streq_ci(s->ents[i].name, name))
return fm_enter_idx(s, i);
}
return -1;
}
static void fm_place(fm_state *s, int place) {
const char *a = 0;
const char *b = 0;
s->depth = 0;
if (fm_reload(s) != 0) return;
if (place == 1) a = "user";
else if (place == 2) { a = "user"; b = "desktop"; }
else if (place == 3) a = "system";
if (a && fm_enter_named(s, a) != 0) { fm_set_status(s, "路径不存在"); return; }
if (b && fm_enter_named(s, b) != 0) fm_set_status(s, "路径不存在");
}
static int fm_place_of(const fm_state *s) {
if (s->depth == 0) return 0;
if (s->depth == 1 && fat32_lfn_streq_ci(s->path_name[0], "user")) return 1;
if (s->depth == 1 && fat32_lfn_streq_ci(s->path_name[0], "system")) return 3;
if (s->depth == 2 && fat32_lfn_streq_ci(s->path_name[0], "user") &&
fat32_lfn_streq_ci(s->path_name[1], "desktop")) return 2;
return -1;
}
static int fm_looks_text(const u8 *p, u32 n) {
u32 sample, bad = 0;
if (n == 0) return 1;
sample = n > 512 ? 512 : n;
for (u32 i = 0; i < sample; i++) {
u8 c = p[i];
if (c == 0) return 0;
if (c < 9 || (c > 13 && c < 32) || c == 127) bad++;
}
return bad * 10 < sample;
}
static void fm_open_file(fm_state *s, int idx) {
fm_ent *e;
u32 n;
if (idx < 0 || idx >= s->count) return;
e = &s->ents[idx];
if (e->size == 0) {
editor_open_text(e->name, (const u8 *)"", 0);
fm_set_status(s, "已在编辑器中打开");
slog("fileman open file");
return;
}
if (e->clus < 2) { fm_set_status(s, "读取失败"); return; }
n = e->size;
if (n > FM_READ_MAX) n = FM_READ_MAX;
for (u32 i = 0; i < n; i++) g_fm_blob[i] = 0;
if (f32_read_file_by_clus_w(e->clus, e->size, g_fm_blob, FM_READ_MAX) != 0) {
fm_set_status(s, "读取失败");
slog("fileman read failed");
return;
}
if (!fm_looks_text(g_fm_blob, n)) {
fm_set_status(s, "二进制文件,未打开");
return;
}
editor_open_text(e->name, g_fm_blob, n);
if (e->size > n) fm_set_status(s, "已打开(内容已截断)");
else fm_set_status(s, "已在编辑器中打开");
slog("fileman open file");
}
static void fm_activate(fm_state *s, int idx) {
if (idx < 0 || idx >= s->count) return;
s->sel = idx;
if (s->ents[idx].is_dir || s->ents[idx].is_up) fm_enter_idx(s, idx);
else fm_open_file(s, idx);
}
static void fm_reveal(fm_state *s, int vis) {
if (vis < 1) vis = 1;
if (s->count <= 0) { s->sel = 0; s->scroll = 0; return; }
if (s->sel < 0) s->sel = 0;
if (s->sel >= s->count) s->sel = s->count - 1;
if (s->sel < s->scroll) s->scroll = s->sel;
if (s->sel >= s->scroll + vis) s->scroll = s->sel - vis + 1;
if (s->scroll < 0) s->scroll = 0;
}
static void fm_geom(const app_ctx *ctx, int *side, int *lx, int *ly,
int *lw, int *lh, int *vis) {
int sidew = (ctx->client_w < 420) ? 0 : FM_SIDE;
int body = ctx->client_h - FM_TB - FM_ST;
if (body < FM_HDR + FM_ROW) body = FM_HDR + FM_ROW;
*side = sidew;
*lx = ctx->client_x + sidew;
*ly = ctx->client_y + FM_TB + FM_HDR;
*lw = ctx->client_w - sidew;
*lh = body - FM_HDR;
*vis = *lh / FM_ROW;
if (*vis < 1) *vis = 1;
}
static void fm_size_str(char *out, u32 sz) {
char num[12];
const char *unit;
u32 v;
int j = 0;
if (sz < 1024u) { v = sz; unit = " B"; }
else if (sz < 1024u * 1024u) { v = sz / 1024u; unit = " K"; }
else { v = sz / (1024u * 1024u); unit = " M"; }
u32dec(v, num);
while (num[j] && j < 12) { out[j] = num[j]; j++; }
while (*unit && j < 15) out[j++] = *unit++;
out[j] = 0;
}
static const char *fm_kind(const fm_ent *e) {
if (e->is_up) return "上级";
if (e->is_dir) return "文件夹";
if (fat32_lfn_ends_with_ci(e->name, ".txt") ||
fat32_lfn_ends_with_ci(e->name, ".md") ||
fat32_lfn_ends_with_ci(e->name, ".cnf") ||
fat32_lfn_ends_with_ci(e->name, ".json") ||
fat32_lfn_ends_with_ci(e->name, ".ini") ||
fat32_lfn_ends_with_ci(e->name, ".log") ||
fat32_lfn_ends_with_ci(e->name, ".h") ||
fat32_lfn_ends_with_ci(e->name, ".c"))
return "文本";
if (fat32_lfn_ends_with_ci(e->name, ".elf") ||
fat32_lfn_ends_with_ci(e->name, ".exe") ||
fat32_lfn_ends_with_ci(e->name, ".drv"))
return "程序";
if (fat32_lfn_ends_with_ci(e->name, ".png") ||
fat32_lfn_ends_with_ci(e->name, ".rgba"))
return "图片";
return "文件";
}
static int fm_hit_row(const app_ctx *ctx, int my, int scroll) {
int side, lx, ly, lw, lh, vis, row;
fm_geom(ctx, &side, &lx, &ly, &lw, &lh, &vis);
(void)side; (void)lx; (void)lw;
if (my < ly) return -1;
row = (my - ly) / FM_ROW;
if (row < 0 || row >= vis) return -1;
return scroll + row;
}
void *fileman_on_create(app_ctx *ctx) {
fm_state *s = (fm_state *)ADDR_FILEMAN;
const char *t = "Files";
u8 *raw = (u8 *)s;
for (u32 i = 0; i < (u32)sizeof(fm_state); i++) raw[i] = 0;
s->last_row = -1;
fm_reload(s);
for (int i = 0; i < 47; i++) ctx->win->title[i] = 0;
for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i];
slog("fileman window");
return s;
}
void fileman_on_destroy(void *state) { (void)state; }
void fileman_on_event(void *state, app_ctx *ctx, desktop_event *ev) {
fm_state *s = (fm_state *)state;
int side, lx, ly, lw, lh, vis;
int cx = ctx->client_x;
int cy = ctx->client_y;
fm_geom(ctx, &side, &lx, &ly, &lw, &lh, &vis);
if (ev->type == EV_KEY_DOWN) {
u8 sc = ev->scancode;
if (sc == 0x48) {
if (s->sel > 0) s->sel--;
} else if (sc == 0x50) {
if (s->sel < s->count - 1) s->sel++;
} else if (sc == 0x49) {
s->sel -= vis;
if (s->sel < 0) s->sel = 0;
} else if (sc == 0x51) {
s->sel += vis;
if (s->count > 0 && s->sel >= s->count) s->sel = s->count - 1;
} else if (sc == 0x47) {
s->sel = 0;
} else if (sc == 0x4F) {
s->sel = (s->count > 0) ? s->count - 1 : 0;
} else if (sc == 0x0E || sc == 0x4B) {
fm_up(s);
} else if (sc == 0x1C || sc == 0x4D) {
fm_activate(s, s->sel);
} else if (sc == 0x3F) {
int keep = s->sel;
fm_reload(s);
s->sel = keep;
if (s->count > 0 && s->sel >= s->count) s->sel = s->count - 1;
}
fm_reveal(s, vis);
ctx->win->dirty = 1;
return;
}
if (ev->type != EV_MOUSE_DOWN || ev->button != 0) return;
/* 上级 */
{
int bw = zh_string_width("上级", 0) + 16;
if (ev->mx >= cx + 8 && ev->mx < cx + 8 + bw &&
ev->my >= cy + 6 && ev->my < cy + 6 + 22) {
fm_up(s);
ctx->win->dirty = 1;
return;
}
}
/* 地点 */
if (side > 0 && ev->mx >= cx && ev->mx < cx + side &&
ev->my >= cy + FM_TB && ev->my < cy + ctx->client_h - FM_ST) {
int rel = ev->my - (cy + FM_TB + 8);
int slot = rel / 28;
if (rel >= 0 && slot >= 0 && slot < 4) fm_place(s, slot);
ctx->win->dirty = 1;
return;
}
/* 滚动条 */
if (s->count > vis && ev->mx >= lx + lw - 10 && ev->mx < lx + lw &&
ev->my >= ly && ev->my < ly + lh) {
int max_scroll = s->count - vis;
int rel = ev->my - ly;
if (lh > 0 && max_scroll > 0)
s->scroll = rel * max_scroll / lh;
if (s->scroll < 0) s->scroll = 0;
if (s->scroll > max_scroll) s->scroll = max_scroll;
ctx->win->dirty = 1;
return;
}
/* 列表行:单击选中,双击打开 */
if (ev->mx >= lx && ev->mx < lx + lw - 10) {
int idx = fm_hit_row(ctx, ev->my, s->scroll);
if (idx >= 0 && idx < s->count) {
int dbl = 0;
u64 now = rdtsc();
if (s->last_row == idx) {
if (!g_tsc_per_sec || (now - s->last_tsc) < g_tsc_per_sec / 2)
dbl = 1;
}
s->last_row = idx;
s->last_tsc = now;
s->sel = idx;
if (dbl) fm_activate(s, idx);
ctx->win->dirty = 1;
}
}
}
void fileman_on_draw(void *state, app_ctx *ctx) {
fm_state *s = (fm_state *)state;
fm_pal pal;
int side, lx, ly, lw, lh, vis;
int cx = ctx->client_x;
int cy = ctx->client_y;
int cw = ctx->client_w;
int ch = ctx->client_h;
static const char *places[4] = { "此电脑", "用户", "桌面", "系统" };
int place = fm_place_of(s);
int up_w;
char fitted[FM_NAME];
fm_colors(&pal);
fm_geom(ctx, &side, &lx, &ly, &lw, &lh, &vis);
if (s->scroll < 0) s->scroll = 0;
if (s->count > vis && s->scroll > s->count - vis) s->scroll = s->count - vis;
if (s->count <= vis) s->scroll = 0;
du_fill_rect(&g_fb, cx, cy, cw, ch, pal.bg);
/* 工具栏 */
du_fill_rect(&g_fb, cx, cy, cw, FM_TB, pal.bar);
du_divider_h(&g_fb, cx, cy + FM_TB, cw, pal.line);
up_w = zh_string_width("上级", 0) + 16;
du_fill_rounded_rect(&g_fb, cx + 8, cy + 6, up_w, 22,
(s->depth > 0) ? pal.side : pal.bg, 4);
zh_draw_string(&g_fb, "上级", cx + 16, cy + 8,
(s->depth > 0) ? pal.text : pal.dim, 0);
fm_fit(fitted, (int)sizeof(fitted), s->path_disp[0] ? s->path_disp : "/",
cw - (up_w + 28));
zh_draw_string(&g_fb, fitted, cx + 8 + up_w + 10, cy + 8, pal.accent, 0);
/* 地点 */
if (side > 0) {
du_fill_rect(&g_fb, cx, cy + FM_TB, side, ch - FM_TB - FM_ST, pal.side);
du_fill_rect(&g_fb, cx + side - 1, cy + FM_TB, 1, ch - FM_TB - FM_ST, pal.line);
for (int i = 0; i < 4; i++) {
int y = cy + FM_TB + 8 + i * 28;
if (i == place)
du_fill_rect(&g_fb, cx + 6, y, side - 14, 24, pal.sel);
zh_draw_string(&g_fb, places[i], cx + 14, y + 3,
(i == place) ? pal.text : pal.dim, 0);
}
}
/* 列头 */
du_fill_rect(&g_fb, lx, cy + FM_TB, lw, FM_HDR, pal.bar);
zh_draw_string(&g_fb, "名称", lx + 28, cy + FM_TB + 2, pal.dim, 0);
zh_draw_string(&g_fb, "大小", lx + lw - 150, cy + FM_TB + 2, pal.dim, 0);
zh_draw_string(&g_fb, "类型", lx + lw - 64, cy + FM_TB + 2, pal.dim, 0);
du_divider_h(&g_fb, lx, ly, lw, pal.line);
if (s->count == 0) {
const char *empty = s->status[0] ? s->status : "此文件夹为空";
zh_draw_string(&g_fb, empty, lx + 16, ly + 12, pal.dim, 0);
}
for (int i = 0; i < vis; i++) {
int idx = s->scroll + i;
int y, name_w;
char shown[FM_NAME];
char sz[16];
if (idx >= s->count) break;
y = ly + i * FM_ROW;
if (idx == s->sel)
du_fill_rect(&g_fb, lx, y, lw - 10, FM_ROW, pal.sel);
if (s->ents[idx].is_dir) {
du_fill_rounded_rect(&g_fb, lx + 8, y + 6, 14, 10, pal.folder, 2);
} else {
du_fill_rect(&g_fb, lx + 8, y + 5, 12, 14, pal.line);
du_rect_outline(&g_fb, lx + 8, y + 5, 12, 14, pal.dim, 1);
}
name_w = lw - 28 - 160;
if (name_w < 40) name_w = 40;
fm_fit(shown, FM_NAME, s->ents[idx].name, name_w);
zh_draw_string(&g_fb, shown, lx + 28, y + 3, pal.text, 0);
if (!s->ents[idx].is_dir && !s->ents[idx].is_up) {
fm_size_str(sz, s->ents[idx].size);
zh_draw_string(&g_fb, sz, lx + lw - 150, y + 3, pal.dim, 0);
}
zh_draw_string(&g_fb, fm_kind(&s->ents[idx]), lx + lw - 64, y + 3, pal.dim, 0);
}
if (s->count > vis && lh > 0) {
int max_scroll = s->count - vis;
int thumb_h = lh * vis / s->count;
int thumb_y;
if (thumb_h < 16) thumb_h = 16;
if (thumb_h > lh) thumb_h = lh;
thumb_y = ly;
if (max_scroll > 0 && lh > thumb_h)
thumb_y = ly + (lh - thumb_h) * s->scroll / max_scroll;
du_fill_rect(&g_fb, lx + lw - 8, ly, 6, lh, pal.line);
du_fill_rounded_rect(&g_fb, lx + lw - 8, thumb_y, 6, thumb_h, pal.dim, 2);
}
/* 状态栏 */
du_fill_rect(&g_fb, cx, cy + ch - FM_ST, cw, FM_ST, pal.bar);
du_divider_h(&g_fb, cx, cy + ch - FM_ST, cw, pal.line);
zh_draw_string(&g_fb, s->status, cx + 10, cy + ch - FM_ST + 2, pal.dim, 0);
}
+32 -11
View File
@@ -21,6 +21,23 @@ typedef struct { u64 offset; u64 info; i64 addend; } dt_elf64_rela;
static u8 g_elf_image[1048576]; /* 1 MiB:settings.elf 内存映像 ~493KB(中文字体位图 BSS),256KB 不够 */
/* ELF 数据 → 加载执行 → 收尾(launch_external_elf / _path 共用) */
static void launch_elf_data(u8 *data, u32 size) {
void *entry = 0;
if (dt_load_elf(data, size, &entry) != 0) {
slog("elf load failed");
return;
}
slog("launching external elf");
void (*elf_entry)(const dsk_boot_context *) = (void (*)(const dsk_boot_context *))entry;
elf_entry(g_boot_ctx);
g_cursor_saved = 0;
g_cursor_old_x = -1; g_cursor_old_y = -1;
files_refresh(); /* 外部应用可能改动了磁盘 */
redraw_all();
slog("external elf returned");
}
static int dt_load_elf(u8 *data, u32 data_size, void **entry_out) {
const dt_elf64_ehdr *eh = (const dt_elf64_ehdr *)data;
if (data_size < sizeof(dt_elf64_ehdr)) return -1;
@@ -85,19 +102,18 @@ void launch_external_elf(const char *name11) {
slog("elf not found");
return;
}
void *entry = 0;
if (dt_load_elf(data, size, &entry) != 0) {
slog("elf load failed");
launch_elf_data(data, size);
}
/* M3: 按路径启动外部 ELF(STARTMENU.CNF 的 bin/xxx.elf 条目) */
void launch_external_elf_path(const char *path) {
u8 *data = 0; u32 size = 0;
if (f32_read_path_lfn_w(path, &data, &size) != 0) {
slog("elf path not found");
slog(path);
return;
}
slog("launching external elf");
void (*elf_entry)(const dsk_boot_context *) = (void (*)(const dsk_boot_context *))entry;
elf_entry(g_boot_ctx);
g_cursor_saved = 0;
g_cursor_old_x = -1; g_cursor_old_y = -1;
files_refresh(); /* 外部应用可能改动了磁盘 */
redraw_all();
slog("external elf returned");
launch_elf_data(data, size);
}
/* ============================================================
@@ -134,6 +150,11 @@ void launch_app(int app_id) {
return;
}
/* M3: STARTMENU.CNF 的 bin/xxx.elf → 按路径全屏加载 */
if (app->elf_path) {
launch_external_elf_path(app->elf_path);
return;
}
if (app->elf_name) {
launch_external_elf(app->elf_name);
return;
+34 -6
View File
@@ -50,8 +50,8 @@ static void load_fuck_config(void) {
slog("[cfg] FUCK not found, defaults");
return;
}
/* 逐行找 [desktop] 段的 fluent= 键 */
int in_dsk = 0;
/* 逐行找 [desktop] 段的 fluent= 键;[prank] 段交给 prank_config_line */
int in_dsk = 0, in_prank = 0;
u32 pos = 0;
while (pos < size) {
u32 eol = pos;
@@ -62,6 +62,11 @@ static void load_fuck_config(void) {
if (len > 1 && line[0] == '[') {
in_dsk = (len >= 9 && line[1] == 'd' && line[2] == 's' &&
line[3] == 'k' && line[4] == ']');
in_prank = (len >= 7 && line[1] == 'p' && line[2] == 'r' &&
line[3] == 'a' && line[4] == 'n' && line[5] == 'k' &&
line[6] == ']');
} else if (in_prank) {
prank_config_line(line, (int)len);
} else if (in_dsk && len >= 7 &&
line[0]=='f' && line[1]=='l' && line[2]=='u' &&
line[3]=='e' && line[4]=='n' && line[5]=='t' && line[6]=='=') {
@@ -134,9 +139,16 @@ static void crossfade_login_to_desktop(void) {
* 每个插桩点唯一 symbol(DESK-E<nn>)。
* ============================================================ */
/* DSK_BOOT_FLAG_PANIC_FALLBACK:desktop_fatal 只留串口坐标后返回,调用点走备用路径 */
static int g_panic_fallback;
static void desktop_fatal(const char *sym) {
/* 严格错误策略:串口留档 + 深红 fatal 屏(ASCII 18px 直写真实 fb)+ 停机 */
slog(sym);
if (g_panic_fallback) {
slog("[FALLBACK] fatal point hit, taking backup path");
return;
}
if (g_real_fb && g_fb_w && g_fb_h) {
u32 *fb = (u32 *)g_real_fb;
u64 pitch_u32 = g_fb_pitch / 4;
@@ -176,8 +188,10 @@ void dsk_entry(const dsk_boot_context *ctx) {
if (!ctx || ctx->magic != 0x44534B31424F4F54ULL) {
desktop_fatal("DSK-E01-BADCONTEXT");
return; /* fallback 不可达(无上下文 = 标志位读不到,按 strict 停机) */
}
g_boot_ctx = ctx;
g_panic_fallback = (ctx->flags & DSK_BOOT_FLAG_PANIC_FALLBACK) ? 1 : 0;
g_fb_addr = ctx->framebuffer_address;
g_fb_w = ctx->framebuffer_width;
@@ -227,11 +241,16 @@ void dsk_entry(const dsk_boot_context *ctx) {
} else {
slog_num("[zhfont] dbf load failed rc=", zrc);
desktop_fatal("DESK-E04 FONT PACK MISSING");
/* fallback:中文字形缺失时 zhfont 画空框占位,ASCII 正常 */
}
}
/* M2: FUCK 配置([desktop] fluent=)→ 壁纸初始化(Fluent 路径需要) */
load_fuck_config();
if (g_prank_enabled) {
slog(g_prank_boot_demo > 0 ? "[cfg] prank enabled (boot demo on)"
: "[cfg] prank enabled");
}
if (g_fluent) {
int wrc = wallpaper_init(f32_read_path_lfn_to_w, (int)g_fb_w, (int)g_fb_h);
if (wrc == 0) {
@@ -240,15 +259,17 @@ void dsk_entry(const dsk_boot_context *ctx) {
slog_num("[wallpaper] load failed rc=", wrc);
/* 严格错误策略:材质包(壁纸)缺失不降级,fatal 屏 + 错误码 */
desktop_fatal("DESK-E02 TEXTURE PACK MISSING");
g_fluent = 0; /* fallback:回落经典(非 Fluent)桌面 */
}
/* M2: 开始菜单 logo(textures/startMenuLogo.rgba → 0x8700000) */
if (taskbar_init(f32_read_path_lfn_to_w) == 0)
if (g_fluent && taskbar_init(f32_read_path_lfn_to_w) == 0)
slog("[taskbar] startMenu logo loaded");
else {
else if (g_fluent) {
/* 严格错误策略:材质包(开始菜单 logo)缺失不降级 */
desktop_fatal("DESK-E03 STARTMENU LOGO MISSING");
g_fluent = 0; /* fallback:回落经典(非 Fluent)桌面 */
}
g_page = 3; /* Fluent 默认进 DESKTOP 页 */
if (g_fluent) g_page = 3; /* Fluent 默认进 DESKTOP 页 */
}
tsc_calibrate();
@@ -292,6 +313,8 @@ void dsk_entry(const dsk_boot_context *ctx) {
g_booted = 1;
slog("desktop ready");
/* 整蛊系统:开机进桌面后按 FUCK [prank] boot_demo_sec 串烧演示一遍 */
prank_boot_demo_start();
desktop_launch_pending_native();
/* 主事件循环 */
@@ -303,6 +326,10 @@ void dsk_entry(const dsk_boot_context *ctx) {
int need_redraw = 0;
int moved = 0;
/* 整蛊系统:推进调度;可视类整蛊播放期间强制全屏重绘 */
prank_tick();
if (prank_wants_redraw()) need_redraw = 1;
/* P8.1: 自动启动测试(先 ping guest,不可用直接报告,不浪费 3s 等待) */
if (g_tsc_per_sec && g_autotest_state == 0) {
g_autotest_tsc = rdtsc();
@@ -403,7 +430,8 @@ void dsk_entry(const dsk_boot_context *ctx) {
u8 st = inb(0x64);
if (!(st & 1) || (st & 0x20)) break;
u8 sc = inb(0x60);
if (handle_key(sc)) need_redraw = 1;
/* 整蛊"打字乱码":按键可打印码被随机重映射(特殊键/释放码放行) */
if (handle_key(prank_filter_key(sc))) need_redraw = 1;
}
/* RTC 秒 tick:DASHBOARD 状态栏时钟/UP 计时刷新 */
+524
View File
@@ -0,0 +1,524 @@
/* prank.c — Deshab 整蛊系统("致力于打造极差的用户体验")
*
* 设计目标:在用户正常使用桌面时,不定时播放一段"恶作剧"叠加层,
* 制造"这系统是不是坏了"的错觉,随后若无其事地消失。
*
* 已实现整蛊(全部由 FUCK [prank] 段控制,见 build/configs/<variant>/FUCK):
* PK_FUCK — 屏幕压暗,特大 "FUCK" 跟着鼠标走,最后淡出(用户点名要的)
* PK_BSOD — 假蓝屏(Win 风格 + 哭泣脸 + 伪造 Stop code)
* PK_INVERT — 全屏反色闪烁
* PK_HANG — 假"程序未响应"对话框(带等待点点动画)
* PK_GARBLE — 打字乱码:按键被随机映射到别的字母/数字
* PK_FLY — 任务栏图标乱飞(任务栏溶解,应用图标四散飞出)
* PK_SHUTDOWN — 假"系统即将关机"倒计时(数到 0 什么也不发生)
*
* 触发方式:
* 1) 开机进桌面后 boot_demo_sec 秒的"串烧演示"(用于验证效果)
* 2) 使用中每隔 interval_sec 秒随机播放一个已启用的整蛊
*
* 渲染:只往 sprite buffer(g_fb.fb) 上叠画,绝不直接写真实 fb;
* 由 compositor 在光标之前调用 prank_draw()。运行时用 TSC 计时,
* 遵守 -mno-sse/-msoft-float,全程整数运算。
*/
#include "desktop.h"
#include "zhfont.h" /* PK_SHUTDOWN 的中文文案 */
/* ============================================================
* 配置(FUCK [prank];extern 声明见 desktop.h)
* 默认全部打开:无 FUCK 或缺键时也整蛊。FUCK 里写 0 才单独关掉。
* ============================================================ */
int g_prank_enabled = 1; /* 总开关,enable=0 关闭 */
int g_prank_boot_demo = 5; /* 进桌面后演示秒数,0=关 */
int g_prank_interval = 60; /* 使用中自动整蛊间隔(秒),0=关 */
int g_prank_fuck = 1; /* FUCK 跟随鼠标遮屏 */
int g_prank_bsod = 1; /* 假蓝屏 */
int g_prank_invert = 1; /* 反色闪屏 */
int g_prank_hang = 1; /* 假"未响应"对话框 */
int g_prank_garble = 1; /* 打字乱码 */
int g_prank_fly = 1; /* 任务栏图标乱飞 */
int g_prank_shutdown = 1; /* 假"系统即将关机"倒计时 */
/* ============================================================
* 运行时状态
* ============================================================ */
enum { PK_NONE = 0, PK_FUCK, PK_BSOD, PK_INVERT, PK_HANG,
PK_GARBLE, PK_FLY, PK_SHUTDOWN };
#define PK_KIND_MAX 7
#define PK_MS_FUCK 2600u
#define PK_MS_BSOD 3000u
#define PK_MS_INVERT 1500u
#define PK_MS_HANG 2600u
#define PK_MS_GARBLE 5000u
#define PK_MS_FLY 2600u
#define PK_MS_SHUTDOWN 6000u
static int s_kind = PK_NONE;
static u64 s_start = 0;
static u64 s_dur = 0;
static u64 s_next = 0; /* 下次自动整蛊的 TSC 时刻 */
static int s_armed = 0;
static int s_demo = 0; /* 1 = 开机演示串烧进行中 */
static int s_q[PK_KIND_MAX];
static int s_qn = 0;
static int s_qi = 0;
static int s_demo_ms = 0;
static u32 s_rng = 0x12345678u;
static u32 s_seed = 0; /* 本次整蛊的随机种子(图标飞散用) */
/* ============================================================
* 小工具
* ============================================================ */
static u64 ms_to_tsc(u32 ms) {
if (g_tsc_per_sec) return g_tsc_per_sec * (u64)ms / 1000u;
return (u64)ms * 1000000u; /* 未校准兜底(约数) */
}
static unsigned int rng_next(void) {
s_rng = s_rng * 1664525u + 1013904223u;
return s_rng;
}
static int key_is(const char *k, int kl, const char *name) {
int i = 0;
while (name[i]) i++;
if (i != kl) return 0;
for (i = 0; i < kl; i++) if (k[i] != name[i]) return 0;
return 1;
}
static int parse_int(const char *s, int len) {
int v = 0, neg = 0, i = 0;
if (len > 0 && s[0] == '-') { neg = 1; i = 1; }
for (; i < len && s[i] >= '0' && s[i] <= '9'; i++) v = v * 10 + (s[i] - '0');
return neg ? -v : v;
}
static u32 kind_ms(int k) {
switch (k) {
case PK_FUCK: return PK_MS_FUCK;
case PK_BSOD: return PK_MS_BSOD;
case PK_INVERT: return PK_MS_INVERT;
case PK_HANG: return PK_MS_HANG;
case PK_GARBLE: return PK_MS_GARBLE;
case PK_FLY: return PK_MS_FLY;
case PK_SHUTDOWN: return PK_MS_SHUTDOWN;
default: return 1000u;
}
}
static void start_prank(int kind, u32 ms) {
s_kind = kind;
s_start = rdtsc();
s_dur = ms_to_tsc(ms);
if (s_dur == 0) s_dur = 1;
s_seed = (u32)rdtsc() ^ (rng_next() << 8);
}
/* ============================================================
* FUCK [prank] 单行解析("key=value",注释/空行由 main 过滤)
* ============================================================ */
void prank_config_line(const char *line, int len) {
int eq = -1;
for (int i = 0; i < len; i++) if (line[i] == '=') { eq = i; break; }
if (eq <= 0) return;
const char *k = line;
int kl = eq;
const char *v = line + eq + 1;
int vl = len - eq - 1;
while (vl > 0 && (v[vl - 1] == ' ' || v[vl - 1] == '\t' || v[vl - 1] == '\r')) vl--;
int val = parse_int(v, vl);
if (key_is(k, kl, "enable")) g_prank_enabled = val ? 1 : 0;
else if (key_is(k, kl, "boot_demo_sec")) g_prank_boot_demo = val;
else if (key_is(k, kl, "interval_sec")) g_prank_interval = val;
else if (key_is(k, kl, "fuck_cursor")) g_prank_fuck = val ? 1 : 0;
else if (key_is(k, kl, "fake_bsod")) g_prank_bsod = val ? 1 : 0;
else if (key_is(k, kl, "invert_flash")) g_prank_invert = val ? 1 : 0;
else if (key_is(k, kl, "fake_hang")) g_prank_hang = val ? 1 : 0;
else if (key_is(k, kl, "garble_typing")) g_prank_garble = val ? 1 : 0;
else if (key_is(k, kl, "taskbar_fly")) g_prank_fly = val ? 1 : 0;
else if (key_is(k, kl, "fake_shutdown")) g_prank_shutdown = val ? 1 : 0;
}
/* ============================================================
* 调度
* ============================================================ */
/* 收集当前启用的整蛊种类(写入 out,返回个数,≤ PK_KIND_MAX)。 */
static int collect_kinds(int *out) {
int n = 0;
if (g_prank_fuck) out[n++] = PK_FUCK;
if (g_prank_bsod) out[n++] = PK_BSOD;
if (g_prank_invert) out[n++] = PK_INVERT;
if (g_prank_hang) out[n++] = PK_HANG;
if (g_prank_garble) out[n++] = PK_GARBLE;
if (g_prank_fly) out[n++] = PK_FLY;
if (g_prank_shutdown) out[n++] = PK_SHUTDOWN;
return n;
}
/* 开机进桌面后的串烧演示:把已启用的整蛊平分 boot_demo_sec 秒依次播放。 */
void prank_boot_demo_start(void) {
if (!g_prank_enabled || g_prank_boot_demo <= 0) return;
s_qn = collect_kinds(s_q);
if (s_qn == 0) return;
u32 each = (u32)g_prank_boot_demo * 1000u / (u32)s_qn;
if (each < 300u) each = 300u;
s_rng ^= (u32)rdtsc();
s_demo = 1;
s_qi = 0;
s_demo_ms = (int)each;
s_armed = 0;
start_prank(s_q[0], each);
slog("[prank] boot demo start");
}
void prank_tick(void) {
if (!g_prank_enabled) {
s_kind = PK_NONE;
s_demo = 0;
s_armed = 0;
return;
}
if (!g_tsc_per_sec) return; /* TSC 未校准前不动 */
u64 now = rdtsc();
if (s_kind != PK_NONE) {
if (now - s_start >= s_dur) {
s_kind = PK_NONE;
if (s_demo) {
s_qi++;
if (s_qi < s_qn) start_prank(s_q[s_qi], (u32)s_demo_ms);
else { s_demo = 0; s_armed = 0; slog("[prank] boot demo done"); }
} else {
s_armed = 0;
}
}
return;
}
if (s_demo) return;
if (g_prank_interval <= 0) return;
if (!s_armed) {
s_next = now + ms_to_tsc((u32)g_prank_interval * 1000u);
s_armed = 1;
return;
}
if (now < s_next) return;
int cand[PK_KIND_MAX];
int n = collect_kinds(cand);
if (n == 0) { s_armed = 0; return; }
int k = cand[(rng_next() >> 16) % (u32)n];
start_prank(k, kind_ms(k));
s_armed = 0;
}
int prank_active(void) { return s_kind != PK_NONE; }
/* 是否需要在整蛊期间强制全屏重绘(打字乱码纯作用于输入,无需重绘)。 */
int prank_wants_redraw(void) {
return s_kind != PK_NONE && s_kind != PK_GARBLE;
}
/* 打字乱码:把可打印键的按下码随机映射到另一个可打印码。
* 释放码与特殊键(Esc/回车/退格/Tab/方向键/修饰键/E0 前缀)原样放行。 */
u8 prank_filter_key(u8 sc) {
if (s_kind != PK_GARBLE) return sc;
if (sc & 0x80) return sc; /* 释放码不动 */
static const u8 keys[] = {
0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B, /* 1..0 */
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, /* qwertyuiop */
0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26, /* asdfghjkl */
0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32, /* zxcvbnm */
0x39 /* space */
};
int n = (int)(sizeof(keys) / sizeof(keys[0]));
int idx = -1;
for (int i = 0; i < n; i++) if (keys[i] == sc) { idx = i; break; }
if (idx < 0) return sc; /* 非可打印键原样 */
return keys[rng_next() % (unsigned int)n];
}
/* ============================================================
* 绘制原语(全部只作用于 sprite buffer)
* ============================================================ */
/* 全屏压暗:每通道 * (256-a)/256 */
static void blend_dark(du_context *ctx, u32 a) {
if (a == 0) return;
if (a > 255) a = 255;
u32 ia = 256u - a;
for (u64 y = 0; y < ctx->height; y++) {
u32 *line = (u32 *)((u8 *)ctx->fb + y * ctx->pitch);
for (u64 x = 0; x < ctx->width; x++) {
u32 c = line[x];
u32 r = ((c & 0xFF) * ia) >> 8;
u32 g = (((c >> 8) & 0xFF) * ia) >> 8;
u32 b = (((c >> 16) & 0xFF) * ia) >> 8;
line[x] = 0xFF000000u | (b << 16) | (g << 8) | r;
}
}
}
/* 全屏反色(保留 alpha) */
static void invert_all(du_context *ctx) {
for (u64 y = 0; y < ctx->height; y++) {
u32 *line = (u32 *)((u8 *)ctx->fb + y * ctx->pitch);
for (u64 x = 0; x < ctx->width; x++) line[x] ^= 0x00FFFFFFu;
}
}
/* 放大版 ASCII 字符串:以 (cx,cy) 为中心,每字形像素放大 scale 倍。
* alpha 0..255 控制整体不透明度。 */
static void draw_scale_text(du_context *ctx, const char *s, int cx, int cy,
int scale, u32 fg, int alpha) {
if (scale < 1) scale = 1;
if (alpha < 0) alpha = 0;
if (alpha > 255) alpha = 255;
if (alpha == 0) return;
int n = kstrlen(s);
if (n <= 0) return;
int gw = g_ascii_w, gh = g_ascii_h;
int step = (gw + 1) * scale;
int total_w = n * step - scale;
int x0 = cx - total_w / 2;
int y0 = cy - gh * scale / 2;
for (int i = 0; i < n; i++) {
unsigned char ch = (unsigned char)s[i];
if (ch < 32 || ch > 126) continue;
const unsigned char *g = g_ascii[ch - 32];
int gx = x0 + i * step;
for (int r = 0; r < gh; r++) {
for (int c = 0; c < gw; c++) {
unsigned char ga = g[r * gw + c];
if (!ga) continue;
int aa = (int)ga * alpha / 255;
if (aa <= 0) continue;
for (int sy = 0; sy < scale; sy++) {
int py = y0 + r * scale + sy;
if (py < 0 || (u64)py >= ctx->height) continue;
u32 *line = (u32 *)((u8 *)ctx->fb + (u64)py * ctx->pitch);
for (int sx = 0; sx < scale; sx++) {
int px = gx + c * scale + sx;
if (px < 0 || (u64)px >= ctx->width) continue;
line[px] = du_blend(line[px], fg, (u32)aa);
}
}
}
}
}
}
/* ============================================================
* 各整蛊的绘制
* ============================================================ */
/* PK_FUCK — 屏幕压暗 + 特大 "FUCK" 跟着鼠标 + 淡出 */
static void draw_fuck(du_context *ctx, int pct) {
int a;
if (pct < 15) a = pct * 170 / 15; /* 淡入压暗 */
else if (pct > 85) a = (100 - pct) * 170 / 15; /* 淡出解除 */
else a = 170;
if (a < 0) a = 0;
blend_dark(ctx, (u32)a);
int alpha = 255;
if (pct < 10) alpha = pct * 255 / 10;
else if (pct > 85) alpha = (100 - pct) * 255 / 15;
if (alpha < 0) alpha = 0;
/* 阴影 + 主体:中心跟随鼠标 */
draw_scale_text(ctx, "FUCK", g_mouse_x + 7, g_mouse_y + 7, 8, 0xFF000000u, alpha / 2);
draw_scale_text(ctx, "FUCK", g_mouse_x, g_mouse_y, 8, 0xFFFF1744u, alpha);
}
/* PK_BSOD — 假蓝屏 */
static void draw_bsod(du_context *ctx, int pct) {
int W = (int)ctx->width, H = (int)ctx->height;
du_fill_bg_solid(ctx, 0xFF0078D7u);
int x = W / 8; if (x < 80) x = 80;
int y = H / 2 - 150;
if (y < 40) y = 40;
draw_scale_text(ctx, ":(", x + 90, y + 40, 5, 0xFFFFFFFFu, 255);
const char *l1 = "Your Deshab ran into a problem and needs to restart.";
const char *l2 = "We're just collecting some error info, and then";
const char *l3 = "we'll restart for you.";
int ty = y + 110;
du_draw_string(ctx, l1, x, ty, 0xFFFFFFFFu, 0xFF0078D7u, DU_ASCII_STEP); ty += 34;
du_draw_string(ctx, l2, x, ty, 0xFFFFFFFFu, 0xFF0078D7u, DU_ASCII_STEP); ty += 22;
du_draw_string(ctx, l3, x, ty, 0xFFFFFFFFu, 0xFF0078D7u, DU_ASCII_STEP); ty += 50;
/* "(NN% complete)" */
int n = pct; if (n < 0) n = 0; if (n > 100) n = 100;
char pb[40]; int i = 0;
pb[i++] = '(';
if (n >= 100) { pb[i++] = '1'; pb[i++] = '0'; pb[i++] = '0'; }
else { pb[i++] = (char)('0' + n / 10); pb[i++] = (char)('0' + n % 10); }
const char *suf = "% complete)";
while (*suf) pb[i++] = *suf++;
pb[i] = 0;
du_draw_string(ctx, pb, x, ty, 0xFFFFFFFFu, 0xFF0078D7u, DU_ASCII_STEP); ty += 44;
du_draw_string(ctx, "Stop code: FUCKED_BY_DESIGN", x, ty,
0xFFFFFFFFu, 0xFF0078D7u, DU_ASCII_STEP);
}
/* PK_INVERT — 全屏反色 */
static void draw_invert(du_context *ctx) {
invert_all(ctx);
}
/* PK_HANG — 假"程序未响应"对话框 */
static void draw_hang(du_context *ctx, int pct) {
int W = (int)ctx->width, H = (int)ctx->height;
blend_dark(ctx, 110);
int w = 520, h = 200;
int x = (W - w) / 2, y = (H - h) / 2;
/* 阴影 + 卡片 */
du_fill_rect(ctx, x + 8, y + 8, w, h, 0xFF6B7280u);
du_fill_rect(ctx, x, y, w, h, 0xFFF3F4F6u);
du_rect_outline(ctx, x, y, w, h, 0xFF9CA3AFu, 0);
/* 标题栏 */
du_fill_rect(ctx, x, y, w, 34, 0xFFE5E7EBu);
du_draw_string(ctx, "Deshab - Not Responding", x + 12, y + 8,
0xFF111827u, 0xFFE5E7EBu, DU_ASCII_STEP);
/* 正文 */
du_draw_string(ctx, "This program is not responding.", x + 20, y + 56,
0xFF1F2937u, 0xFFF3F4F6u, DU_ASCII_STEP);
du_draw_string(ctx, "It may respond if you wait a bit.", x + 20, y + 80,
0xFF1F2937u, 0xFFF3F4F6u, DU_ASCII_STEP);
du_draw_string(ctx, "Do you want to close it?", x + 20, y + 104,
0xFF1F2937u, 0xFFF3F4F6u, DU_ASCII_STEP);
/* 等待点点动画(0..3 个点循环) */
int dots = (pct / 12) % 4;
char db[8]; db[0] = '.'; db[1] = '.'; db[2] = '.'; db[3] = '.'; db[4] = 0;
db[dots] = 0;
du_draw_string(ctx, "Waiting", x + 20, y + 138, 0xFF6B7280u, 0xFFF3F4F6u, DU_ASCII_STEP);
du_draw_string(ctx, db, x + 20 + 7 * (int)DU_ASCII_STEP, y + 138,
0xFF6B7280u, 0xFFF3F4F6u, DU_ASCII_STEP);
/* 按钮 */
int bw = 120, bh = 34, by = y + h - 54;
int bx2 = x + w - 20 - bw, bx1 = bx2 - bw - 12;
du_fill_rect(ctx, bx1, by, bw, bh, 0xFFE5E7EBu);
du_rect_outline(ctx, bx1, by, bw, bh, 0xFF9CA3AFu, 0);
du_draw_string(ctx, "Wait", bx1 + (bw - 4 * (int)DU_ASCII_STEP) / 2, by + 8,
0xFF111827u, 0xFFE5E7EBu, DU_ASCII_STEP);
du_fill_rect(ctx, bx2, by, bw, bh, 0xFF2563EBu);
du_draw_string(ctx, "Close", bx2 + (bw - 5 * (int)DU_ASCII_STEP) / 2, by + 8,
0xFFFFFFFFu, 0xFF2563EBu, DU_ASCII_STEP);
}
/* PK_FLY — 任务栏图标乱飞:底部任务栏"溶解",应用图标四散飞出屏幕 */
static void draw_fly(du_context *ctx, int pct) {
int W = (int)ctx->width, H = (int)ctx->height;
blend_dark(ctx, 90);
/* 任务栏条带溶解为深色(视觉上任务栏没了) */
int strip_h = 72;
int sy = H - strip_h; if (sy < 0) sy = 0;
du_fill_rect(ctx, 0, sy, W, strip_h, 0xFF14161Au);
/* 应用图标从任务栏位置四散飞出 */
int n = g_app_count; if (n < 6) n = 6; if (n > 8) n = 8;
int base_y = H - 40;
for (int i = 0; i < n; i++) {
u32 r = s_seed ^ ((u32)i * 2654435761u);
int vx = (int)(r & 0xFFu) - 128; /* -128..127 */
vx = vx * 3; /* -384..381 px */
int vy = -(int)(((r >> 8) & 0xFFu) * 3u + 120u); /* 向上飞 */
int start_x = W / 2 + (i - n / 2) * 56;
int x = start_x + vx * pct / 100;
int y = base_y + vy * pct / 100;
int appid = g_app_count > 0 ? (i % g_app_count) : 0;
du_fill_rect(ctx, x + 3, y + 3, 40, 40, 0xFF000000u); /* 阴影 */
draw_app_icon(ctx, appid, x, y);
}
}
/* PK_SHUTDOWN — 假"系统即将关机"倒计时 */
static void draw_shutdown(du_context *ctx, int pct) {
int W = (int)ctx->width, H = (int)ctx->height;
du_fill_bg_solid(ctx, 0xFF0A0E14u);
int remain = 6 - (pct * 6 / 100);
if (remain < 0) remain = 0;
/* 中文标题(16px 位图字库;24px 未懒加载前不使用) */
const char *zh_title = "系统即将关机";
int zw = zh_string_width(zh_title, 0);
zh_draw_string(ctx, zh_title, (W - zw) / 2, H / 2 - 130, 0xFFFFD24Au, 0);
/* 大号倒计时数字 */
char num[4];
num[0] = (char)('0' + remain);
num[1] = 0;
draw_scale_text(ctx, num, W / 2, H / 2 + 10, 10, 0xFFFFFFFFu, 255);
/* 中英提示 */
const char *zh_sub = "请立即保存您的工作";
int zw2 = zh_string_width(zh_sub, 0);
zh_draw_string(ctx, zh_sub, (W - zw2) / 2, H / 2 + 120, 0xFFB0B6C0u, 0);
const char *en = "System will shut down automatically.";
int ew = kstrlen(en) * (int)DU_ASCII_STEP;
du_draw_string(ctx, en, (W - ew) / 2, H / 2 + 152,
0xFF9AA0A8u, 0xFF0A0E14u, DU_ASCII_STEP);
/* 底部进度条(时间越少越红) */
int bw = W / 3, bx = (W - bw) / 2, byy = H - 80;
du_fill_rect(ctx, bx, byy, bw, 8, 0xFF2A2F38u);
int fw = bw * (100 - pct) / 100;
if (fw > 0) du_fill_rect(ctx, bx, byy, fw, 8, 0xFFE5484Du);
}
/* ============================================================
* 对外绘制入口(compositor 在光标之前调用)
* ============================================================ */
void prank_draw(du_context *ctx) {
if (s_kind == PK_NONE) return;
u64 el = rdtsc() - s_start;
int pct = (int)(el * 100u / (s_dur ? s_dur : 1u));
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
switch (s_kind) {
case PK_FUCK: draw_fuck(ctx, pct); break;
case PK_BSOD: draw_bsod(ctx, pct); break;
case PK_INVERT: draw_invert(ctx); break;
case PK_HANG: draw_hang(ctx, pct); break;
case PK_GARBLE: break; /* 纯输入效果,无叠加层 */
case PK_FLY: draw_fly(ctx, pct); break;
case PK_SHUTDOWN: draw_shutdown(ctx, pct); break;
default: break;
}
}
+6
View File
@@ -478,6 +478,12 @@ int handle_key(u8 sc) {
if (sc == 0xB8) { g_alt = 0; return 0; }
if (sc & 0x80) { g_e0 = 0; return 0; } /* 其他释放码忽略 */
/* M3 开始菜单(模态:Esc 关闭 / 搜索输入 / Enter 启动) */
if (g_sm_open) return startmenu_key(sc, g_shift);
/* 快速设置卡片(Esc 收起) */
if (g_qs_open && sc == 0x01) { g_qs_open = 0; return 1; }
/* WiFi 快捷卡片(模态:Esc 收起 / 密码模式接管输入) */
if (g_wifi_open) return wifi_key(sc, g_shift);
+394
View File
@@ -0,0 +1,394 @@
/* startmenu.c — M3 Win11 开始菜单
*
* 任务栏开始按钮弹出:搜索框 + "已固定" 应用瓦片网格 + 底部用户/电源行。
* 条目来自 appreg.c 的 g_sm_ids/g_sm_labels(STARTMENU.CNF,缺失回退全量)。
*
* 布局(浅色主题,深色壁纸衬托亚克力):
* 面板 520 × min(600, H-128),锚在开始按钮上方
* [搜索框 36px] [已固定 标签] [瓦片网格 6 列 × N 行]
* [分隔线] [用户 | 电源]
*
* 交互:
* 搜索:ASCII 输入实时过滤(display/name 不区分大小写子串),
* 过滤态切 du_wf_list_item 列表,Up/Down + Enter 键盘启动
* 瓦片:点击 → launch_app + 关闭
* 电源:g_quit(与托盘电源一致);Esc/点击面板外关闭
*
* 绘制挂在 taskbar_draw 末尾(叠加在任务栏之上);
* 点击/键盘经 taskbar_click / handle_key 路由(模态,参考 WiFi 卡片)。
*/
#include "desktop.h"
#include "ascii_font.h" /* 顺序敏感:先于 fluent_ui.h */
#include "../UTSM/include/utsm/fluent_ui.h"
#include "zhfont.h"
/* ---- 全局状态 ---- */
int g_sm_open = 0;
#define SM_PANEL_W 520
#define SM_PANEL_MIN_H 380
#define SM_SEARCH_MAX 28 /* 搜索串字节上限(ASCII) */
#define SM_COLS 6
#define SM_TILE_H 84
#define SM_TILE_GAP 4
#define SM_GRID_PAD 16
#define SM_BOTTOM_H 56
static char g_sm_search[SM_SEARCH_MAX + 1];
static int g_sm_search_len;
static int g_sm_sel; /* 过滤列表选中行 */
static int g_sm_panel_rect[4];
static int g_sm_search_rect[4];
static int g_sm_tile_rect[SM_MAX_APPS][4];
static int g_sm_pwr_rect[4];
static int g_sm_user_rect[4];
/* 过滤结果(g_sm 下标),-1 结尾 */
static int g_sm_filter[SM_MAX_APPS + 1];
static int g_sm_filter_n;
/* 网格实际显示的条目数(容量截断时串口留档) */
static int g_sm_grid_shown;
static int g_sm_grid_dropped;
/* 当前模式实际绘制(可点击)的行数:grid=瓦片数 / list=过滤行数 */
static int g_sm_rows_visible;
static const wf_theme *sm_theme(void) { return &g_wf_light; }
static wf_btn_state sm_hover_state(const int r[4]) {
if (g_mouse_x >= r[0] && g_mouse_x < r[0] + r[2] &&
g_mouse_y >= r[1] && g_mouse_y < r[1] + r[3])
return WF_BTN_HOVER;
return WF_BTN_NORMAL;
}
/* ============================================================
* 开 / 关 / 过滤
* ============================================================ */
static void sm_filter_rebuild(void) {
g_sm_filter_n = 0;
for (int i = 0; i < g_sm_count && g_sm_filter_n < SM_MAX_APPS; i++) {
if (g_sm_search_len == 0) {
g_sm_filter[g_sm_filter_n++] = i;
continue;
}
/* 不区分大小写 ASCII 子串匹配:display 标签 + 内部名 */
const char *hay[2] = { g_sm_labels[i],
g_apps[g_sm_ids[i]].name };
int hit = 0;
for (int h = 0; h < 2 && !hit; h++) {
const char *s = hay[h];
if (!s) continue;
for (int b = 0; s[b] && !hit; b++) {
int m = 1;
for (int k = 0; k < g_sm_search_len; k++) {
char c1 = s[b + k], c2 = g_sm_search[k];
if (!c1) { m = 0; break; }
if (c1 >= 'a' && c1 <= 'z') c1 = (char)(c1 - 32);
if (c2 >= 'a' && c2 <= 'z') c2 = (char)(c2 - 32);
if (c1 != c2) { m = 0; break; }
}
if (m) hit = 1;
}
}
if (hit) g_sm_filter[g_sm_filter_n++] = i;
}
g_sm_filter[g_sm_filter_n] = -1;
if (g_sm_sel >= g_sm_filter_n) g_sm_sel = 0;
}
void startmenu_open(void) {
g_sm_open = 1;
g_sm_search[0] = 0;
g_sm_search_len = 0;
g_sm_sel = 0;
sm_filter_rebuild();
g_wifi_open = 0; /* 互斥:收起托盘弹卡 */
g_qs_open = 0;
slog("[startmenu] open");
}
void startmenu_close(void) {
if (!g_sm_open) return;
g_sm_open = 0;
slog("[startmenu] close");
}
void startmenu_toggle(void) {
if (g_sm_open) startmenu_close();
else startmenu_open();
}
/* 瓦片标签截断:zh 16px ASCII 半宽 12 / CJK 全宽 16 → 瓦片 ~80px 最多 4 个 CJK */
static void sm_label_clip(char *dst, int cap, const char *src) {
int w = 0, o = 0;
for (int i = 0; src[i] && o < cap - 4; ) {
unsigned char c = (unsigned char)src[i];
int cw = (c < 0x80) ? 12 : 16; /* ASCII 半宽 12px / CJK 全宽 16px */
if (w + cw > 68) break;
dst[o++] = src[i++];
if (c >= 0x80) { /* UTF-8 续字节整组拷贝 */
int cont = (c >= 0xF0) ? 3 : (c >= 0xE0) ? 2 : 1;
while (cont-- > 0 && src[i] && o < cap - 4)
dst[o++] = src[i++];
}
w += cw;
}
dst[o] = 0;
}
/* ============================================================
* 绘制(taskbar_draw 末尾调用)
* ============================================================ */
void startmenu_draw(void) {
if (!g_sm_open) return;
const wf_theme *t = sm_theme();
int W = (int)g_fb_w, H = (int)g_fb_h;
/* ---- 面板几何:开始按钮上方 ---- */
int ph = H - 64 - 8 - 24; /* 任务栏(48+8gap) 上方 8 间隙 + 顶部余量 */
if (ph > 600) ph = 600;
if (ph < SM_PANEL_MIN_H) ph = SM_PANEL_MIN_H;
int px = g_tb2_start_rect[0] - 8;
if (px < 8) px = 8;
if (px + SM_PANEL_W > W - 8) px = W - 8 - SM_PANEL_W;
int py = H - 64 - ph; /* 任务栏区(56) 之上贴住 */
if (py < 8) py = 8;
g_sm_panel_rect[0] = px; g_sm_panel_rect[1] = py;
g_sm_panel_rect[2] = SM_PANEL_W; g_sm_panel_rect[3] = ph;
/* ---- 卡片底 ---- */
du_wf_shadow(&g_fb, px, py, SM_PANEL_W, ph, (du_u32)t->radius_lg, t);
du_wf_acrylic(&g_fb, px, py, SM_PANEL_W, ph, 235, (du_u32)t->radius_lg, t);
/* ---- 搜索框 ---- */
g_sm_search_rect[0] = px + 16; g_sm_search_rect[1] = py + 16;
g_sm_search_rect[2] = SM_PANEL_W - 32; g_sm_search_rect[3] = 36;
{
/* blink:RTC 秒奇偶交替 */
int yy, mo, dd, hh, mi, ss;
rtc_read(&yy, &mo, &dd, &hh, &mi, &ss);
du_wf_textbox(&g_fb, px + 16, py + 16, SM_PANEL_W - 32,
g_sm_search[0] ? g_sm_search : "search",
ss & 1, t, 1);
if (!g_sm_search[0])
du_wf_icon(&g_fb, WF_ICON_SEARCH,
px + SM_PANEL_W - 40, py + 22, 22, t->text_dim);
}
/* ---- 过滤列表模式(有搜索串)---- */
if (g_sm_search_len > 0) {
zh_draw_string(&g_fb, "搜索结果", px + 16, py + 66,
t->text_dim, 0);
int y = py + 92;
int max_rows = (py + ph - SM_BOTTOM_H - y) / 36;
if (max_rows > SM_MAX_APPS) max_rows = SM_MAX_APPS;
int rows = g_sm_filter_n;
if (rows > max_rows) rows = max_rows;
g_sm_rows_visible = rows;
for (int i = 0; i < rows; i++) {
int e = g_sm_filter[i];
int id = g_sm_ids[e];
char label[52];
sm_label_clip(label, 52, g_sm_labels[e]);
/* zh 列表项:底行 + 名字(ASCII name 右侧小字) */
int rowy = y + i * 36;
if (g_sm_sel == i)
du_wf_overlay(&g_fb, px + 8, rowy, SM_PANEL_W - 16, 36, 0x28000000u);
else if (sm_hover_state((int[4]){px + 8, rowy, SM_PANEL_W - 16, 36})
== WF_BTN_HOVER)
du_wf_overlay(&g_fb, px + 8, rowy, SM_PANEL_W - 16, 36, 0x14000000u);
if (g_sm_sel == i)
du_fill_rect(&g_fb, px + 8, rowy + 8, 3, 20, t->accent);
du_wf_icon(&g_fb, (wf_icon_id)desktop_app_icon(id),
px + 20, rowy + 7, 22, t->text_primary);
zh_draw_string(&g_fb, label, px + 54, rowy + 10,
t->text_primary, 0);
du_draw_string(&g_fb, g_apps[id].name,
px + SM_PANEL_W - 16 - kstrlen(g_apps[id].name) * 8,
rowy + 12, t->text_dim, 0, DU_ASCII_STEP);
/* 行命中矩形(点击用) */
g_sm_tile_rect[i][0] = px + 8; g_sm_tile_rect[i][1] = rowy;
g_sm_tile_rect[i][2] = SM_PANEL_W - 16; g_sm_tile_rect[i][3] = 36;
}
if (g_sm_filter_n == 0)
zh_draw_string(&g_fb, "无匹配应用", px + 24, y + 8, t->text_dim, 0);
goto bottom_bar;
}
/* ---- 瓦片网格模式 ---- */
zh_draw_string(&g_fb, "已固定", px + 16, py + 66, t->text_dim, 0);
{
int grid_y = py + 92;
int grid_h = py + ph - SM_BOTTOM_H - grid_y;
int rows_cap = grid_h / (SM_TILE_H + SM_TILE_GAP);
int cap = rows_cap * SM_COLS;
if (cap > SM_MAX_APPS) cap = SM_MAX_APPS;
g_sm_grid_shown = g_sm_count;
if (g_sm_grid_shown > cap) {
g_sm_grid_dropped = g_sm_count - cap;
g_sm_grid_shown = cap;
} else {
g_sm_grid_dropped = 0;
}
g_sm_rows_visible = g_sm_grid_shown;
int tile_w = (SM_PANEL_W - SM_GRID_PAD * 2 - (SM_COLS - 1) * SM_TILE_GAP)
/ SM_COLS;
for (int i = 0; i < g_sm_grid_shown; i++) {
int col = i % SM_COLS, row = i / SM_COLS;
int tx = px + SM_GRID_PAD + col * (tile_w + SM_TILE_GAP);
int ty = grid_y + row * (SM_TILE_H + SM_TILE_GAP);
g_sm_tile_rect[i][0] = tx; g_sm_tile_rect[i][1] = ty;
g_sm_tile_rect[i][2] = tile_w; g_sm_tile_rect[i][3] = SM_TILE_H;
int id = g_sm_ids[i];
du_wf_app_tile(&g_fb, tx, ty, tile_w, SM_TILE_H,
(wf_icon_id)desktop_app_icon(id), t,
sm_hover_state(g_sm_tile_rect[i]), 0);
char label[52];
sm_label_clip(label, 52, g_sm_labels[i]);
zh_draw_string(&g_fb, label,
tx + (tile_w - zh_string_width(label, 0)) / 2,
ty + SM_TILE_H - 30, t->text_primary, 0);
}
if (g_sm_grid_dropped > 0) {
char buf[24];
buf[0] = '+';
int n = u32dec((u32)g_sm_grid_dropped, buf + 1);
buf[n + 1] = 0;
zh_draw_string(&g_fb, buf, px + SM_PANEL_W - 60,
py + 66, t->text_dim, 0);
}
}
bottom_bar:
/* ---- 底部:分隔线 + 用户 + 电源 ---- */
{
int by = py + ph - SM_BOTTOM_H;
du_wf_separator(&g_fb, px + 16, by, SM_PANEL_W - 32, t);
/* 用户行 */
g_sm_user_rect[0] = px + 12; g_sm_user_rect[1] = by + 8;
g_sm_user_rect[2] = 220; g_sm_user_rect[3] = 40;
du_wf_icon_button(&g_fb, px + 12, by + 8, 40, t,
sm_hover_state(g_sm_user_rect));
du_wf_icon(&g_fb, WF_ICON_USER, px + 20, by + 16, 24, t->text_primary);
zh_draw_string(&g_fb, "Deshab 用户", px + 60, by + 16,
t->text_primary, 0);
/* 电源钮 */
g_sm_pwr_rect[0] = px + SM_PANEL_W - 52; g_sm_pwr_rect[1] = by + 8;
g_sm_pwr_rect[2] = 40; g_sm_pwr_rect[3] = 40;
du_wf_icon_button(&g_fb, px + SM_PANEL_W - 52, by + 8, 40, t,
sm_hover_state(g_sm_pwr_rect));
du_wf_icon(&g_fb, WF_ICON_POWER, px + SM_PANEL_W - 44, by + 16, 24,
t->text_primary);
}
}
/* ============================================================
* 点击(taskbar_click 调用;返回 1 = 已消费)
* ============================================================ */
int startmenu_click(int mx, int my) {
if (!g_sm_open) return 0;
if (!rect_hit(g_sm_panel_rect, mx, my)) {
startmenu_close(); /* 点面板外:关闭并放行点击 */
return 0;
}
/* 电源 → 退出桌面(与托盘电源一致) */
if (rect_hit(g_sm_pwr_rect, mx, my)) {
startmenu_close();
g_quit = 1;
return 1;
}
/* 用户行(预留:M7 用户菜单) */
if (rect_hit(g_sm_user_rect, mx, my)) return 1;
/* 瓦片 / 过滤行 → 启动(只认实际绘制的行,防越界脏矩形) */
int n = g_sm_rows_visible;
for (int i = 0; i < n; i++) {
if (!rect_hit(g_sm_tile_rect[i], mx, my)) continue;
int e = g_sm_filter[i];
slog("[startmenu] launch:");
slog(g_apps[g_sm_ids[e]].name);
startmenu_close();
launch_app(g_sm_ids[e]);
return 1;
}
return 1; /* 面板内空白:吞掉 */
}
/* ============================================================
* 键盘(handle_key 调用;返回 1 = 已消费)
* ============================================================ */
int startmenu_key(u8 sc, int shift) {
if (!g_sm_open) return 0;
if (sc == 0x01) { startmenu_close(); return 1; } /* Esc */
if (sc == 0x0E) { /* Backspace */
if (g_sm_search_len > 0) {
g_sm_search[--g_sm_search_len] = 0;
sm_filter_rebuild();
}
return 1;
}
if (sc == 0x1C) { /* Enter 启动选中 */
if (g_sm_search_len > 0 && g_sm_filter_n > 0) {
int e = g_sm_filter[g_sm_sel];
slog("[startmenu] launch(kbd):");
slog(g_apps[g_sm_ids[e]].name);
startmenu_close();
launch_app(g_sm_ids[e]);
} else if (g_sm_grid_shown > 0) {
int id = g_sm_ids[0];
startmenu_close();
launch_app(id);
}
return 1;
}
if (g_sm_search_len > 0) { /* 过滤列表导航 */
if (sc == 0x48) { /* Up */
if (g_sm_sel > 0) g_sm_sel--;
return 1;
}
if (sc == 0x50) { /* Down */
if (g_sm_sel < g_sm_filter_n - 1) g_sm_sel++;
return 1;
}
}
/* 可打印字符 → 搜索串 */
if (sc >= 58) return 1;
{
static const char normal[58] = {
0, 27, '1','2','3','4','5','6','7','8','9','0','-','=', 8, '\t',
'q','w','e','r','t','y','u','i','o','p','[',']','\n',0,
'a','s','d','f','g','h','j','k','l',';','\'', '`',0,'\\',
'z','x','c','v','b','n','m',',','.','/',0,'*',0,' '
};
static const char shifted[58] = {
0, 27, '!','@','#','$','%','^','&','*','(',')','_','+', 8, '\t',
'Q','W','E','R','T','Y','U','I','O','P','{','}','\n',0,
'A','S','D','F','G','H','J','K','L',':','"','~',0,'|',
'Z','X','C','V','B','N','M','<','>','?',0,'*',0,' '
};
char c = shift ? shifted[sc] : normal[sc];
if (c && c != '\n' && c != 27 && c != 8 && c != '\t') {
if (g_sm_search_len < SM_SEARCH_MAX) {
g_sm_search[g_sm_search_len++] = c;
g_sm_search[g_sm_search_len] = 0;
g_sm_sel = 0;
sm_filter_rebuild();
}
}
}
return 1;
}
+345 -8
View File
@@ -54,6 +54,9 @@ static wf_btn_state tb_hover_state(const int r[4]);
#define WIFI_NETAPI_IS_WIRELESS 0x40
#define WIFI_NETAPI_CONNECT 0x48
#define WIFI_NETAPI_DEVICE_COUNT 0x08
#define WIFI_NETAPI_DEVICE_INFO 0x10
#define TRAY_NET_LINK_UP (1u << 0)
#define TRAY_NET_WIRELESS (1u << 3)
#define WIFI_MAX_ROWS 8 /* 卡片内最多显示行数 */
#define WIFI_PW_MAX 63
@@ -79,6 +82,7 @@ int g_wifi_open = 0;
static int g_wifi_mode; /* 0=列表 1=密码输入 */
static int g_wifi_dev = -1; /* 无线设备索引,-1 = 无 */
static int g_wifi_scanning;
static int g_wifi_guest; /* 1 = 结果来自 Linux 客户机 iw,不是原生网卡 */
static int g_wifi_n; /* 缓存的扫描结果数 */
static int g_wifi_sel = -1;
static char g_wifi_sel_ssid[33];
@@ -103,6 +107,43 @@ static void *wifi_slot(u32 slot_off) {
return (void *)(unsigned long)fn;
}
/* 与 dkm_net_device_info 同布局:name 指针、6 字节 MAC、对齐后的 flags。 */
typedef struct tray_netdev {
const char *name;
u8 mac[6];
u32 flags;
} tray_netdev;
typedef int (*tray_info_fn)(u32 index, tray_netdev *out);
static int g_tray_n, g_tray_up, g_tray_wl;
/* 扫已注册网卡。返回 0 = 计数有效。链路起来的有线口在 QEMU e1000 上应为 1。 */
static int tray_net_query(void) {
wifi_fn_device_count cnt = (wifi_fn_device_count)wifi_slot(WIFI_NETAPI_DEVICE_COUNT);
tray_info_fn info = (tray_info_fn)wifi_slot(WIFI_NETAPI_DEVICE_INFO);
if (!cnt) return -1;
u32 n = cnt();
if (n > 8) n = 8;
int up = 0, wl = 0;
for (u32 i = 0; i < n; i++) {
tray_netdev d;
d.flags = 0;
if (info && info(i, &d) == 0) {
if (d.flags & TRAY_NET_LINK_UP) up++;
if (d.flags & TRAY_NET_WIRELESS) wl++;
}
}
if (n != (u32)g_tray_n || up != g_tray_up || wl != g_tray_wl) {
g_tray_n = (int)n;
g_tray_up = up;
g_tray_wl = wl;
slog_num("[tray] net devices=", g_tray_n);
slog_num("[tray] net link_up=", g_tray_up);
slog_num("[tray] net wireless=", g_tray_wl);
}
return 0;
}
/* 找无线设备;返回 0 = 已定位 g_wifi_dev */
static int wifi_dev_find(void) {
wifi_fn_device_count cnt = (wifi_fn_device_count)wifi_slot(WIFI_NETAPI_DEVICE_COUNT);
@@ -129,6 +170,176 @@ static void wifi_results_pull(void) {
g_wifi_n = n;
}
static void wifi_status_set(const char *m) {
int i = 0;
for (; m[i] && i < 63; i++) g_wifi_status[i] = m[i];
g_wifi_status[i] = 0;
}
static int wifi_line_eq_prefix(const char *s, const char *p) {
while (*p) {
if (*s++ != *p++) return 0;
}
return 1;
}
static int wifi_parse_i(const char *s, int *out) {
int sign = 1;
if (*s == '-') { sign = -1; s++; }
if (*s < '0' || *s > '9') return -1;
int v = 0;
while (*s >= '0' && *s <= '9') {
v = v * 10 + (*s - '0');
s++;
}
*out = v * sign;
return 0;
}
static int wifi_hex_nibble(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
/* 把一行 iw scan 文本并进当前 BSS。返回 1 表示遇到新的 BSS 头。 */
static int g_guest_have;
static char g_guest_ssid[33];
static u8 g_guest_bssid[6];
static u8 g_guest_ch;
static signed char g_guest_rssi;
static u8 g_guest_sec;
static void wifi_guest_commit(void) {
if (!g_guest_have || !g_guest_ssid[0] || g_wifi_n >= 32) {
g_guest_have = 0;
return;
}
wifi_scan_item *it = &g_wifi_items[g_wifi_n];
int i = 0;
for (; i < 32 && g_guest_ssid[i]; i++) it->ssid[i] = g_guest_ssid[i];
it->ssid[i] = 0;
for (i = 0; i < 6; i++) it->bssid[i] = g_guest_bssid[i];
it->channel = g_guest_ch;
it->rssi = g_guest_rssi;
it->security = g_guest_sec;
g_wifi_n++;
g_guest_have = 0;
}
static void wifi_guest_line(const char *s) {
while (*s == ' ' || *s == '\t') s++;
if (wifi_line_eq_prefix(s, "BSS ")) {
wifi_guest_commit();
g_guest_have = 1;
g_guest_ssid[0] = 0;
g_guest_ch = 0;
g_guest_rssi = -90;
g_guest_sec = 0;
for (int i = 0; i < 6; i++) g_guest_bssid[i] = 0;
const char *p = s + 4;
for (int i = 0; i < 6; i++) {
int hi = wifi_hex_nibble(p[0]);
int lo = wifi_hex_nibble(p[1]);
if (hi < 0 || lo < 0) { g_guest_have = 0; return; }
g_guest_bssid[i] = (u8)((hi << 4) | lo);
p += 2;
if (i < 5) {
if (*p != ':') { g_guest_have = 0; return; }
p++;
}
}
return;
}
if (!g_guest_have) return;
if (wifi_line_eq_prefix(s, "SSID:")) {
s += 5;
while (*s == ' ') s++;
int i = 0;
for (; s[i] && s[i] != '\n' && i < 32; i++) g_guest_ssid[i] = s[i];
g_guest_ssid[i] = 0;
return;
}
if (wifi_line_eq_prefix(s, "signal:")) {
s += 7;
while (*s == ' ') s++;
int dbm = 0;
if (wifi_parse_i(s, &dbm) == 0) {
if (dbm < -128) dbm = -128;
if (dbm > 127) dbm = 127;
g_guest_rssi = (signed char)dbm;
}
return;
}
if (wifi_line_eq_prefix(s, "freq:")) {
s += 5;
while (*s == ' ') s++;
int freq = 0;
if (wifi_parse_i(s, &freq) == 0) {
int ch = 0;
if (freq >= 2412 && freq <= 2472) ch = (freq - 2407) / 5;
else if (freq >= 5180 && freq <= 5885) ch = (freq - 5000) / 5;
if (ch > 0 && ch < 256) g_guest_ch = (u8)ch;
}
return;
}
if (wifi_line_eq_prefix(s, "RSN:")) g_guest_sec = 3; /* WPA2 */
else if (wifi_line_eq_prefix(s, "WPA:") && g_guest_sec == 0) g_guest_sec = 2;
}
/* 客户机里跑 iw。没有 wlan0 或客户机没起来时返回负值,不填假热点。 */
static int wifi_guest_scan(void) {
g_wifi_guest = 0;
g_wifi_n = 0;
g_guest_have = 0;
if (!g_lxc_svc || !g_lxc_svc->is_available || !g_lxc_svc->exec || !g_lxc_svc->file_read)
return -1;
if (!g_lxc_svc->is_available()) return -1;
const char *argv[] = {
"sh", "-c",
"if ! iw dev wlan0 info >/dev/null 2>&1; then echo NOIF; exit 0; fi; "
"iw dev wlan0 scan > /tmp/deshab-wifi.txt 2>/tmp/deshab-wifi.err; echo OK"
};
char head[8];
for (int i = 0; i < 8; i++) head[i] = 0;
u64 ec = 0;
int rc = g_lxc_svc->exec("/bin/sh", 3, argv, head, 7, 0, &ec);
slog_num("[wifi] guest iw rc=", rc);
if (rc != 0) return -2;
if (head[0] == 'N' && head[1] == 'O') return -3;
if (head[0] != 'O') return -2;
static char chunk[2048];
char line[192];
int ln = 0;
u64 off = 0;
for (int part = 0; part < 32; part++) {
u64 got = 0;
int fr = g_lxc_svc->file_read("/tmp/deshab-wifi.txt", off, chunk, sizeof(chunk), &got, 0);
if (fr != 0 || got == 0) break;
for (u64 i = 0; i < got; i++) {
char c = chunk[i];
if (c == '\n' || ln >= (int)sizeof(line) - 1) {
line[ln] = 0;
if (ln) wifi_guest_line(line);
ln = 0;
} else if (c != '\r') {
line[ln++] = c;
}
}
off += got;
}
if (ln) {
line[ln] = 0;
wifi_guest_line(line);
}
wifi_guest_commit();
g_wifi_guest = 1;
slog_num("[wifi] guest aps=", g_wifi_n);
return 0;
}
/* 打开卡片并踢一脚扫描 */
static void wifi_card_open(void) {
g_wifi_open = 1;
@@ -137,13 +348,32 @@ static void wifi_card_open(void) {
g_wifi_pw[0] = 0;
g_wifi_status[0] = 0;
g_wifi_n = 0;
g_wifi_guest = 0;
g_wifi_scanning = 1;
tray_net_query();
if (wifi_dev_find() != 0) {
g_wifi_scanning = 0;
const char *m = "未检测到无线网卡";
int i = 0;
for (; m[i]; i++) g_wifi_status[i] = m[i];
g_wifi_status[i] = 0;
int grc = wifi_guest_scan();
if (grc == 0) {
if (g_wifi_n == 0) wifi_status_set("未发现网络");
else wifi_status_set("来自客户机");
return;
}
if (grc == -3) {
wifi_status_set("客户机无无线接口");
slog("[wifi] guest has no wlan0");
return;
}
if (grc == -2) {
wifi_status_set("客户机扫描失败");
return;
}
if (g_tray_up > 0) {
wifi_status_set("有线已连接");
slog("[wifi] wired link up, no wireless device");
return;
}
wifi_status_set("未检测到无线网卡");
slog("[wifi] no wireless device");
return;
}
@@ -160,6 +390,11 @@ static void wifi_card_open(void) {
/* 发起连接(当前选中网络 + 密码) */
static void wifi_connect_go(void) {
if (g_wifi_guest) {
wifi_status_set("客户机关联未实现");
slog("[wifi] guest assoc not implemented");
return;
}
wifi_fn_connect conn = (wifi_fn_connect)wifi_slot(WIFI_NETAPI_CONNECT);
if (!conn || g_wifi_dev < 0) return;
const char *m = "连接中...";
@@ -244,7 +479,7 @@ static void wifi_draw(void) {
y += 42;
} else {
/* ---- 列表模式 ---- */
if (g_wifi_dev < 0) {
if (g_wifi_dev < 0 && g_wifi_n == 0) {
zh_draw_string(&g_fb, g_wifi_status[0] ? g_wifi_status : "无无线设备",
cx + 14, y + 8, t->text_dim, 0);
y += 34;
@@ -371,6 +606,85 @@ int wifi_key(u8 sc, int shift) {
return 1;
}
/* ============================================================
* 快速设置卡片(托盘音量按钮弹出:音量/亮度滑块)
*
* M6 提前落地最小版:du_wf_slider 组件实战。音量接 g_vol(状态),
* 亮度接 g_bright(apply_brightness 真实调暗)。拖拽复用主循环
* g_drag_slider 通道(卡片绘制时把自己的轨道矩形写进 g_vol_rect/
* g_bri_rect,主循环 slider_set 即可作用到卡片滑块)。
* ============================================================ */
int g_qs_open = 0;
static int g_qs_card_rect[4];
static int g_qs_vol_rect[4]; /* 音量滑块命中区(镜像到 g_vol_rect) */
static int g_qs_bri_rect[4]; /* 亮度滑块命中区(镜像到 g_bri_rect) */
static void qs_draw(void) {
if (!g_qs_open) return;
const wf_theme *t = tb_theme();
int W = (int)g_fb_w;
const int CW = 300, CH = 46 + 2 * 58 + 8;
int cx = g_tb2_vol_rect[0] + g_tb2_vol_rect[2] - CW;
if (cx < 8) cx = 8;
if (cx + CW > W - 8) cx = W - 8 - CW;
int cy = (int)g_fb_h - TB_H - TB_BOTTOM_GAP - CH - 8;
g_qs_card_rect[0] = cx; g_qs_card_rect[1] = cy;
g_qs_card_rect[2] = CW; g_qs_card_rect[3] = CH;
du_wf_card(&g_fb, cx, cy, CW, CH, 235, t);
zh_draw_string(&g_fb, "快速设置", cx + 14, cy + 12, t->text_primary, 0);
int y = cy + 46;
/* 音量行:标签 + 百分比 + 滑块 */
{
char vb[8];
int n = u32dec((u32)g_vol, vb);
vb[n] = '%'; vb[n + 1] = 0;
zh_draw_string(&g_fb, "音量", cx + 14, y, t->text_secondary, 0);
du_draw_string(&g_fb, vb, cx + CW - 14 - (n + 1) * 8, y,
t->text_primary, 0, DU_ASCII_STEP);
du_wf_slider(&g_fb, cx + 14, y + 22, CW - 28, g_vol, t, g_qs_vol_rect);
g_vol_rect[0] = g_qs_vol_rect[0]; g_vol_rect[1] = g_qs_vol_rect[1];
g_vol_rect[2] = g_qs_vol_rect[2]; g_vol_rect[3] = g_qs_vol_rect[3];
y += 58;
}
/* 亮度行(真实调暗:apply_brightness 每帧消费 g_bright) */
{
char vb[8];
int n = u32dec((u32)g_bright, vb);
vb[n] = '%'; vb[n + 1] = 0;
zh_draw_string(&g_fb, "亮度", cx + 14, y, t->text_secondary, 0);
du_draw_string(&g_fb, vb, cx + CW - 14 - (n + 1) * 8, y,
t->text_primary, 0, DU_ASCII_STEP);
du_wf_slider(&g_fb, cx + 14, y + 22, CW - 28, g_bright, t, g_qs_bri_rect);
g_bri_rect[0] = g_qs_bri_rect[0]; g_bri_rect[1] = g_qs_bri_rect[1];
g_bri_rect[2] = g_qs_bri_rect[2]; g_bri_rect[3] = g_qs_bri_rect[3];
}
}
/* 卡片点击(返回 1 = 已消费;点外部收起并放行) */
static int qs_click(int mx, int my) {
if (!g_qs_open) return 0;
if (!rect_hit(g_qs_card_rect, mx, my)) {
g_qs_open = 0;
return 0;
}
if (rect_hit(g_qs_vol_rect, mx, my)) {
g_drag_slider = 1;
slider_set(g_vol_rect, &g_vol, mx);
return 1;
}
if (rect_hit(g_qs_bri_rect, mx, my)) {
g_drag_slider = 2;
slider_set(g_bri_rect, &g_bright, mx);
return 1;
}
return 1;
}
/* ---- 开始菜单 logo(textures/startMenuLogo.rgba,加载失败回退字形) ---- */
static int g_smlogo_ok = 0;
@@ -507,7 +821,15 @@ void taskbar_draw(void) {
g_tb2_net_rect[0] = x; g_tb2_net_rect[1] = iy;
g_tb2_net_rect[2] = TB_BTN; g_tb2_net_rect[3] = TB_BTN;
du_wf_icon_button(&g_fb, x, iy, TB_BTN, t, tb_hover_state(g_tb2_net_rect));
du_wf_icon(&g_fb, WF_ICON_NETWORK, x + (TB_BTN - 20) / 2, iy + (TB_BTN - 20) / 2, 20, t->text_primary);
tray_net_query();
{
u32 col = g_tray_up > 0 ? t->accent : t->text_dim;
int ix = x + (TB_BTN - 20) / 2;
int iy2 = iy + (TB_BTN - 20) / 2;
du_wf_icon(&g_fb, WF_ICON_NETWORK, ix, iy2, 20, col);
if (g_tray_n == 0 || g_tray_up == 0)
du_wf_line(&g_fb, ix + 2, iy2 + 18, ix + 18, iy2 + 2, 2, col);
}
x += TB_BTN + 2;
g_tb2_vol_rect[0] = x; g_tb2_vol_rect[1] = iy;
@@ -533,8 +855,12 @@ void taskbar_draw(void) {
g_tb2_tray_rect[0] = tx; g_tb2_tray_rect[1] = by;
g_tb2_tray_rect[2] = tray_w; g_tb2_tray_rect[3] = TB_H;
/* 快速设置卡片(音量/亮度) */
qs_draw();
/* WiFi 快捷卡片(最顶) */
wifi_draw();
/* M3: 开始菜单(叠加在一切之上) */
startmenu_draw();
}
/* ============================================================
@@ -542,9 +868,14 @@ void taskbar_draw(void) {
* ============================================================ */
int taskbar_click(int mx, int my) {
/* M3: 开始菜单(打开时优先消费;点面板外关闭并放行) */
if (g_sm_open) {
if (startmenu_click(mx, my)) return 1;
if (rect_hit(g_tb2_start_rect, mx, my)) return 1; /* 已在 click 内关闭 */
}
/* 主任务栏 */
if (rect_hit(g_tb2_start_rect, mx, my)) {
slog("[taskbar] start button (start menu: M3)");
startmenu_toggle();
return 1;
}
if (rect_hit(g_tb2_tv_rect, mx, my)) {
@@ -567,6 +898,8 @@ int taskbar_click(int mx, int my) {
}
/* WiFi 卡片(打开时优先消费;点外部收起并放行点击) */
if (g_wifi_open) wifi_click(mx, my);
/* 快速设置卡片(打开时优先消费;点外部收起并放行点击) */
if (g_qs_open) qs_click(mx, my);
/* 托盘 */
if (rect_hit(g_tb2_pwr_rect, mx, my)) {
@@ -575,12 +908,16 @@ int taskbar_click(int mx, int my) {
}
if (rect_hit(g_tb2_net_rect, mx, my)) {
/* 网络:弹出/收起 WiFi 快捷卡片(扫描 → 选网 → 密码 → 连接) */
g_qs_open = 0;
if (g_wifi_open) g_wifi_open = 0;
else wifi_card_open();
return 1;
}
if (rect_hit(g_tb2_vol_rect, mx, my)) {
slog("[taskbar] volume (quick settings: M6)");
/* 音量:弹出/收起快速设置卡片(音量/亮度滑块) */
g_wifi_open = 0;
if (g_qs_open) g_qs_open = 0;
else g_qs_open = 1;
return 1;
}
if (rect_hit(g_tb2_clk_rect, mx, my)) {
+7 -4
View File
@@ -81,8 +81,9 @@ desktop_window *win_create(int app_id, const char *title, int x, int y, int w, i
void win_destroy(desktop_window *w) {
int idx = -1;
int id = w->id;
for (int i = 0; i < g_win_count; i++) {
if (g_windows[i].id == w->id) { idx = i; break; }
if (g_windows[i].id == id) { idx = i; break; }
}
if (idx < 0) return;
@@ -92,9 +93,10 @@ void win_destroy(desktop_window *w) {
if (w->app_id >= 0 && w->app_id < g_app_count && g_apps[w->app_id].on_destroy && w->app_state) {
g_apps[w->app_id].on_destroy(w->app_state);
}
/* 挪位之后 w 指向的是下一扇窗口,关闭判断必须用挪位前记下的 id。 */
for (int i = idx; i < g_win_count - 1; i++) g_windows[i] = g_windows[i+1];
g_win_count--;
if (g_focused_win == w->id) {
if (g_focused_win == id) {
g_focused_win = -1;
for (int i = g_win_count - 1; i >= 0; i--) {
if (g_windows[i].ws == g_ws_cur && g_windows[i].visible && !g_windows[i].minimized) {
@@ -148,7 +150,8 @@ static int wm_fluent_hit_btn(desktop_window *w, int mx, int my, int btn) {
static int g_wm_btn_hover = 0;
int win_hit_close_btn(desktop_window *w, int mx, int my) {
if (g_fluent) return wm_fluent_hit_btn(w, mx, my, 3);
/* 绘制顺序是 min=*3、max=*2、close=*1(最右)。命中必须跟绘制同一序号。 */
if (g_fluent) return wm_fluent_hit_btn(w, mx, my, 1);
int bx = w->x + w->w - CLOSE_BTN_OFFSET;
int by = w->y + (KATE_TITLEBAR_H - CLOSE_BTN_SIZE) / 2;
return mx >= bx && mx < bx + CLOSE_BTN_SIZE &&
@@ -156,7 +159,7 @@ int win_hit_close_btn(desktop_window *w, int mx, int my) {
}
int win_hit_min_btn(desktop_window *w, int mx, int my) {
return g_fluent ? wm_fluent_hit_btn(w, mx, my, 1) : 0;
return g_fluent ? wm_fluent_hit_btn(w, mx, my, 3) : 0;
}
int win_hit_max_btn(desktop_window *w, int mx, int my) {