Files
Deshab-OS/CODE/desktop/startmenu.c
T
LinehuandCursor 8554a82677 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>
2026-10-12 23:57:19 +08:00

395 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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;
}