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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user