/* Deshab Desktop Manager — Winux-Kate 结构完整复刻 * * 布局结构与 D:\Code\Winux-Kate 完全一致: * app-shell * ├── topbar (34px):品牌 + 工作区切换 + 页面切换 + PG 时钟 * page-host * │ ├── 1·DASHBOARD:TERM-01 / TERM-02 / EDITOR / FILES 四面板 + StatusBar * │ ├── 2·IDE:标签栏 + 编辑宿主 * │ ├── 3·DESKTOP:图标网格 + 窗口 + 任务栏 * │ └── 4+/CUSTOM:外部 .elf 全屏宿主(双击启动) * └── task-view overlay(工作区卡片) * * 新 UI 特性(Kate 之外): * - RTC 实时时钟(秒级)+ 开机至今 UP 计时 * - VOL / BRIGHT 可拖滑块(BRIGHT 真实调暗全屏) * - 开机打字机 Boot 屏 * - FILES 面板双击文件载入 EDITOR(FAT32 真实读写 + SAVE 回写) * - 窗口单实例聚焦策略 */ #include "../UTSM/include/utsm/dsk.h" #include "../UTSM/include/utsm/linux_compat.h" /* VSCode Phase 2: input_forward_* */ #include "../UTSM/include/utsm/pe.h" /* P5d: PE 窗口模式 run_windowed/inject_* */ /* block 设备函数类型(从 kernel_api + 0xA8 获取 block_api,read @ +0x10, write @ +0x18) */ typedef int (*desktop_block_read_fn)(u32 index, u64 lba, u32 count, void *buffer); typedef int (*desktop_block_write_fn)(u32 index, u64 lba, u32 count, const void *buffer); /* 先包含 ascii_bitmaps.c(定义 g_ascii),再包含 deshab_ui.h(引用 g_ascii) */ #include "../firstInit/ascii_bitmaps.c" #include "../UTSM/include/utsm/deshab_ui.h" /* 用户态 FAT32 读写。P5d:PE exe 可达数 MB,数据缓冲同 cmd.elf 提到 4MB。 */ #define F32_DATA_BYTES (4u * 1024u * 1024u) #include "../tools/fat32_io.h" typedef unsigned char u8; typedef signed char i8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned long long u64; typedef long long i64; #define COM1 0x3F8 static __inline__ void outb(u16 port, u8 value) { __asm__ volatile("outb %0,%1"::"a"(value),"Nd"(port)); } static __inline__ u8 inb(u16 port) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(port)); return v; } static void sputc(char c) { for (unsigned int i=0; i<100000; i++) { if (inb(COM1+5)&0x20) break; } outb(COM1, (unsigned char)c); } static void swrite(const char *s) { while(*s) { if(*s=='\n')sputc('\r'); sputc(*s++); } } static void slog(const char *s) { swrite("[desktop] "); swrite(s); swrite("\n"); } static int kstrlen(const char *s) { int n = 0; while (s[n]) n++; return n; } static void kstrcpy(char *d, const char *s, int cap) { int i = 0; for (; i < cap - 1 && s[i]; i++) d[i] = s[i]; d[i] = 0; } static void kstrcat(char *d, const char *s, int cap) { int n = kstrlen(d); if (n < cap) kstrcpy(d + n, s, cap - n); } /* P7.9: u32 -> decimal string(返回长度) */ static int u32dec(u32 v, char *out) { char tmp[10]; int n = 0; if (v == 0) { out[0] = '0'; out[1] = 0; return 1; } while (v > 0) { tmp[n++] = '0' + (char)(v % 10); v /= 10; } for (int i = 0; i < n; i++) out[i] = tmp[n - 1 - i]; out[n] = 0; return n; } static u64 rdtsc(void) { u32 lo, hi; __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi)); return ((u64)hi << 32) | lo; } /* 前向声明 */ static void redraw_all(void); static int ps2_mouse_poll(void); static void draw_statusbar_full(void); static void files_refresh(void); /* ============================================================ * 常量(Winux-Kate 布局) * ============================================================ */ #define MAX_WINDOWS 8 #define BORDER_W 1 #define CURSOR_SIZE 24 #define ICON_W KATE_ICON_W /* 104 */ #define ICON_H KATE_ICON_H /* 90 */ #define ICON_GAP KATE_ICON_GAP /* 18 */ #define ICON_PAD KATE_ICON_PAD /* 24 */ #define CLOSE_BTN_SIZE 16 #define CLOSE_BTN_OFFSET 22 #define MAX_WS 6 /* 工作区上限 */ #define MAX_CPAGES 4 /* 自定义页上限 */ #define MAX_FILES 24 /* FILES 面板条目上限 */ /* 应用固定状态地址(单实例策略 → 每类应用唯一实例) */ #define ADDR_TERM1 0x4000000ULL #define ADDR_TERM2 0x4100000ULL #define ADDR_TERMWIN 0x4200000ULL #define ADDR_EDDASH 0x5000000ULL #define ADDR_EDWIN 0x5100000ULL #define ADDR_CALC 0x6000000ULL /* ============================================================ * 事件系统 * ============================================================ */ typedef enum { EV_MOUSE_MOVE = 1, EV_MOUSE_DOWN, EV_MOUSE_UP, EV_KEY_DOWN, EV_KEY_UP, } ev_type; typedef struct { ev_type type; int mx, my; int button; /* 0=左, 1=右 */ u8 scancode; int shift, ctrl, alt; } desktop_event; /* ============================================================ * 窗口 * ============================================================ */ typedef struct desktop_window { int id; int x, y, w, h; int visible; int focused; int minimized; char title[48]; int app_id; void *app_state; int closing; int dirty; int ws; /* 所属工作区 */ } desktop_window; typedef struct desktop_window desktop_win; typedef struct app_ctx { du_context *fb; desktop_win *win; int client_x, client_y, client_w, client_h; int should_exit; desktop_block_read_fn block_read; desktop_block_write_fn block_write; const void *kernel_api; } app_ctx; typedef void *(*app_create_fn)(app_ctx *ctx); typedef void (*app_event_fn)(void *state, app_ctx *ctx, desktop_event *ev); typedef void (*app_draw_fn)(void *state, app_ctx *ctx); typedef void (*app_destroy_fn)(void *state); typedef struct { const char *name; const char *display_name; u32 default_w, default_h; app_create_fn on_create; app_event_fn on_event; app_draw_fn on_draw; app_destroy_fn on_destroy; const char *elf_name; /* VSCode Phase 5: Linux guest 应用(来自 LINUXAPP.CNF)。 * linux_path 非 0 时 launch_app 走 launch_linux_app → exec_async。 */ const char *linux_path; const char *linux_args; /* P5d: Windows PE 应用(来自 PEAPPS.CNF)。 * pe_file 非 0 时 launch_app 走 launch_pe_app → run_windowed 桌面嵌入。 */ const char *pe_file; const char *pe_args; } app_descriptor; /* 桌面图标 */ typedef struct { int app_id; int x, y; const char *label; int selected; } desktop_icon; #define MAX_ICONS 16 /* P7.4: 与 MAX_APPS 对齐(多实例 VSCODE2 等占位) */ /* 工作区 */ typedef struct { char name[16]; int page; /* 该工作区离开时的页面 */ } workspace; /* 自定义页 */ typedef struct { char name[12]; char elf11[11]; int id; /* 页面号 5..8 */ } cpage; /* FILES 面板条目 */ typedef struct { char name11[11]; char disp[13]; u32 size; } fentry; /* ============================================================ * 全局状态 * ============================================================ */ static du_context g_fb; static u64 g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch; static const void *g_kernel_api; /* 双缓冲:渲染到 sprite buffer,完成后一次性 flip 到真实 framebuffer */ #define SPRITE_BUF_ADDR 0x7000000ULL /* 112MB,在 identity-mapped 区域 */ static u8 *g_real_fb = 0; /* 真实 framebuffer 地址 */ static desktop_block_read_fn g_block_read; static desktop_block_write_fn g_block_write; static const dsk_boot_context *g_boot_ctx = 0; /* VSCode Phase 2: Linux guest 输入转发。 * g_lxc_svc 由 dsk_entry() 从 boot context reserved[5] 读取; * g_input_forward_enabled 在 IDE 标签 attach/detach 时联动置位, * 开启后 PS/2 鼠标键盘事件经 virtio-input 注入 Linux guest(VSCode 运行其中)。 */ static const linux_compat_service *g_lxc_svc = 0; static int g_input_forward_enabled = 0; /* P5d: PE 窗口桌面嵌入状态。 * g_pe_svc 由 dsk_entry() 从 boot context reserved[4] 读取(magic 校验)。 * launch_pe_app 创建受管窗口后阻塞在 pe_service.run_windowed; * PE 消息空转时 shim 回调 pe_pump_cb 驱动 desktop 一帧。 * 单实例:run_windowed 阻塞期间拒绝启动第二个 PE 窗口(pump 重入安全)。 */ static const pe_service *g_pe_svc = 0; static int g_pe_running = 0; static int g_pe_win_id = -1; static u8 *g_pe_surf = 0; static int g_pe_surf_w = 0, g_pe_surf_h = 0; static int g_pe_state_dummy = 0; /* draw_desktop_page 要求 app_state 非空 */ #define PE_SURFACE_ADDR 0x7800000ULL /* 120MB:sprite buf(112MB, ≤8.3MB) 之上 */ #define PE_SURFACE_MAX (8u * 1024u * 1024u) #define PE_WM_CLOSE 0x0010u /* shim Win32 WM_CLOSE */ /* 鼠标状态 */ static int g_mouse_x = 400, g_mouse_y = 300; static int g_mouse_btn = 0; /* bit0=左 bit1=右 */ static int g_left_pressed = 0, g_left_released = 0, g_right_pressed = 0; static u8 g_mouse_buf[3]; static int g_mouse_idx = 0; static int g_mouse_has_pkt = 0; /* 光标背景保存 */ static u32 g_cursor_save[CURSOR_SIZE * CURSOR_SIZE]; static int g_cursor_saved = 0; static int g_cursor_old_x = -1, g_cursor_old_y = -1; /* 键盘状态 */ static int g_shift = 0, g_ctrl = 0, g_alt = 0; static int g_e0 = 0; /* 窗口列表 */ static desktop_window g_windows[MAX_WINDOWS]; static int g_win_count = 0; static int g_next_win_id = 1; static int g_focused_win = -1; /* 拖拽状态 */ static int g_dragging = 0; static int g_drag_win = -1; static int g_drag_off_x, g_drag_off_y; /* 应用表 */ #define MAX_APPS 16 static app_descriptor g_apps[MAX_APPS]; static int g_app_count = 0; static desktop_icon g_icons[MAX_ICONS]; static int g_icon_count = 0; /* ---- Kate 外壳状态 ---- */ static int g_booted = 0; static int g_page = 1; /* 1..3 内置, 4..8 自定义 */ static int g_taskview = 0; static int g_dash_focus = 0; /* 0=t1 1=t2 2=ed 3=files */ static workspace g_ws[MAX_WS]; static int g_ws_count = 1; static int g_ws_cur = 0; static cpage g_cpages[MAX_CPAGES]; static int g_cpage_count = 0; /* 右键菜单(移到工作区) */ static int g_ctx_open = 0; static int g_ctx_x = 0, g_ctx_y = 0; static int g_ctx_win = -1; /* 状态栏滑块 */ static int g_vol = 65; static int g_bright = 100; static int g_bright_applied = 100; /* 上次应用的亮度值(避免重复处理) */ static int g_drag_slider = 0; /* 1=vol 2=bright */ static int g_vol_rect[4]; /* x,y,w,h 滑轨 */ static int g_bri_rect[4]; static int g_quit = 0; /* EXIT 按钮 → 返回 DSK */ /* 时钟 */ static int g_rtc_boot_sec = 0; /* 开机时刻(秒级当天秒) */ static int g_last_sec = -1; static u64 g_tsc_per_sec = 0; /* FILES 面板 */ static fentry g_files[MAX_FILES]; static int g_file_count = 0; static int g_file_sel = -1; static int g_files_loaded = 0; /* 双击检测 */ static u64 g_last_click_tsc = 0; static int g_last_click_x = -100, g_last_click_y = -100; /* 面板/控件命中矩形(绘制时更新,点击时使用) */ static int g_dash_rect[4][4]; /* 4 面板 x,y,w,h */ static int g_save_rect[4]; static int g_pg_rect[8][4]; /* 页面切换按钮 */ static int g_pg_rm_rect[8][4]; static int g_pgadd_rect[4]; static int g_tvbtn_rect[4]; /* 任务视图按钮 */ static int g_ws_rect[MAX_WS][4]; static int g_wsadd_rect[4]; static int g_tb_rect[MAX_WINDOWS][4]; /* 任务栏项 */ static int g_tb_close_rect[MAX_WINDOWS][4]; static int g_tb_quit_rect[4]; static int g_tb_adopt_rect[4]; /* 收纳窗口按钮 */ static int g_tv_card[MAX_WS + 1][4]; /* 任务视图卡片(+add) */ static int g_tv_close[MAX_WS][4]; static int g_ctx_rect[4]; static int g_ctx_item[MAX_WS + 1][4]; static int g_ctx_item_ws[MAX_WS + 1]; /* 菜单行 → 真实工作区索引,-1=新建 */ static int g_ide_host[4]; static int g_ide_tabx[4]; /* IDE 标签 × */ static int g_ide_new_rect[4]; /* IDE "+ NEW IDE" 按钮 */ static int g_cp_host[4]; /* 自定义页宿主 */ /* ============================================================ * 脏矩形渲染(性能优化:只更新变化区域) * ============================================================ */ #define MAX_DIRTY_RECTS 16 typedef struct { int x, y, w, h; int active; } dirty_rect; static dirty_rect g_dirty_rects[MAX_DIRTY_RECTS]; static int g_dirty_count = 0; static int g_full_redraw = 1; /* 初始全屏重绘 */ /* 标记脏区域 */ __attribute__((unused)) static void mark_dirty(int x, int y, int w, int h) { if (g_dirty_count >= MAX_DIRTY_RECTS) { /* 溢出时合并为全屏重绘 */ g_full_redraw = 1; return; } g_dirty_rects[g_dirty_count].x = x; g_dirty_rects[g_dirty_count].y = y; g_dirty_rects[g_dirty_count].w = w; g_dirty_rects[g_dirty_count].h = h; g_dirty_rects[g_dirty_count].active = 1; g_dirty_count++; } /* 合并所有脏矩形为一个包围盒 */ static void merge_dirty_rects(int *out_x, int *out_y, int *out_w, int *out_h) { if (g_dirty_count == 0) { *out_x = *out_y = *out_w = *out_h = 0; return; } int min_x = (int)g_fb_w, min_y = (int)g_fb_h; int max_x = 0, max_y = 0; for (int i = 0; i < g_dirty_count; i++) { if (!g_dirty_rects[i].active) continue; int x1 = g_dirty_rects[i].x; int y1 = g_dirty_rects[i].y; int x2 = x1 + g_dirty_rects[i].w; int y2 = y1 + g_dirty_rects[i].h; if (x1 < min_x) min_x = x1; if (y1 < min_y) min_y = y1; if (x2 > max_x) max_x = x2; if (y2 > max_y) max_y = y2; } *out_x = min_x; *out_y = min_y; *out_w = max_x - min_x; *out_h = max_y - min_y; } /* 清除所有脏矩形 */ static void clear_dirty_rects(void) { g_dirty_count = 0; g_full_redraw = 0; } /* ============================================================ * 箭头光标形状(24×24,1=前景 0=透明) * ============================================================ */ static const u8 cursor_shape[24][24] = { {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, }; /* ============================================================ * RTC 时钟 * ============================================================ */ static int bcd2bin(u8 v) { return (v >> 4) * 10 + (v & 0xF); } static void rtc_read(int *yy, int *mo, int *dd, int *hh, int *mi, int *ss) { outb(0x70, 0x00); *ss = bcd2bin(inb(0x71)); outb(0x70, 0x02); *mi = bcd2bin(inb(0x71)); outb(0x70, 0x04); *hh = bcd2bin(inb(0x71)); outb(0x70, 0x07); *dd = bcd2bin(inb(0x71)); outb(0x70, 0x08); *mo = bcd2bin(inb(0x71)); outb(0x70, 0x09); *yy = bcd2bin(inb(0x71)); } static int rtc_day_sec(void) { int y, mo, d, h, mi, s; rtc_read(&y, &mo, &d, &h, &mi, &s); return h * 3600 + mi * 60 + s; } /* 格式化 HH:MM:SS */ static void fmt_hms(char *out, int sec) { int h = sec / 3600, m = (sec / 60) % 60, s = sec % 60; out[0] = '0' + h / 10; out[1] = '0' + h % 10; out[2] = ':'; out[3] = '0' + m / 10; out[4] = '0' + m % 10; out[5] = ':'; out[6] = '0' + s / 10; out[7] = '0' + s % 10; out[8] = 0; } /* 校准 TSC(约 1 秒):用于双击间隔判定 */ static void tsc_calibrate(void) { int s0 = rtc_day_sec(); while (rtc_day_sec() == s0) __asm__("pause"); u64 t0 = rdtsc(); int s1 = rtc_day_sec(); while (rtc_day_sec() == s1) __asm__("pause"); g_tsc_per_sec = rdtsc() - t0; } /* ============================================================ * Winux-Kate 应用图标(40×40,青色几何图形) * ============================================================ */ static void draw_circle_outline(du_context *ctx, int cx, int cy, int r, u32 color) { for (int dy = -r; dy <= r; dy++) { for (int dx = -r; dx <= r; dx++) { int d2 = dx * dx + dy * dy; if (d2 <= r * r && d2 > (r - 1) * (r - 1)) { du_pixel(ctx, cx + dx, cy + dy, color); } } } } static void draw_icon_terminal(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); du_fill_rect(ctx, cx + 8, cy + 12, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 10, cy + 14, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 12, cy + 16, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 10, cy + 18, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 8, cy + 20, 2, 2, KS_ACCENT2); du_fill_rect(ctx, cx + 16, cy + 22, 8, 2, KS_ACCENT); } static void draw_icon_editor(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); int widths[] = {24, 20, 22, 14, 20, 18}; for (int i = 0; i < 6; i++) { du_fill_rect(ctx, cx + 8, cy + 8 + i * 4, widths[i], 2, (i == 0) ? KS_ACCENT2 : KS_ACCENT); } } static void draw_icon_folder(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_BORDER, 2); du_fill_rect(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT_DIM); du_rect_outline(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT, 1); du_fill_rect(ctx, cx + 6, cy + 14, 28, 16, KS_BG_SECONDARY); du_rect_outline(ctx, cx + 6, cy + 14, 28, 16, KS_ACCENT, 1); du_fill_rect(ctx, cx + 6, cy + 14, 28, 1, KS_ACCENT); du_fill_rect(ctx, cx + 10, cy + 19, 12, 1, KS_ACCENT2); du_fill_rect(ctx, cx + 10, cy + 23, 16, 1, KS_TEXT_DIM); du_fill_rect(ctx, cx + 10, cy + 26, 10, 1, KS_TEXT_DIM); } static void draw_icon_calc(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); du_fill_rect(ctx, cx + 6, cy + 6, 28, 8, KS_BG_PRIMARY); du_rect_outline(ctx, cx + 6, cy + 6, 28, 8, KS_ACCENT2, 1); du_fill_rect(ctx, cx + 28, cy + 8, 4, 4, KS_ACCENT2); for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) { int bx = cx + 6 + c * 9; int by = cy + 18 + r * 7; du_fill_rect(ctx, bx, by, 7, 5, KS_ACCENT_DIM); du_rect_outline(ctx, bx, by, 7, 5, KS_ACCENT, 1); } } } static void draw_icon_browser(du_context *ctx, int cx, int cy) { du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY); du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2); int ccx = cx + 20, ccy = cy + 20, r = 11; draw_circle_outline(ctx, ccx, ccy, r, KS_ACCENT); du_fill_rect(ctx, ccx - r, ccy, 2 * r + 1, 1, KS_ACCENT); du_fill_rect(ctx, ccx, ccy - r, 1, 2 * r + 1, KS_ACCENT); du_pixel(ctx, ccx, ccy, KS_ACCENT2); } static 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; } } /* ============================================================ * 光标管理 * ============================================================ */ static void cursor_save_bg(int mx, int my) { for (int r = 0; r < CURSOR_SIZE; r++) { for (int c = 0; c < CURSOR_SIZE; c++) { int x = mx + c, y = my + r; if (x >= 0 && (u64)x < g_fb_w && y >= 0 && (u64)y < g_fb_h) { u32 *line = (u32 *)((u8 *)g_fb.fb + (u64)y * g_fb.pitch); g_cursor_save[r * CURSOR_SIZE + c] = line[x]; } else { g_cursor_save[r * CURSOR_SIZE + c] = 0; } } } g_cursor_saved = 1; g_cursor_old_x = mx; g_cursor_old_y = my; } static void cursor_restore_bg(void) { if (!g_cursor_saved) return; for (int r = 0; r < CURSOR_SIZE; r++) { for (int c = 0; c < CURSOR_SIZE; c++) { int x = g_cursor_old_x + c, y = g_cursor_old_y + r; if (x >= 0 && (u64)x < g_fb_w && y >= 0 && (u64)y < g_fb_h) { u32 *line = (u32 *)((u8 *)g_fb.fb + (u64)y * g_fb.pitch); line[x] = g_cursor_save[r * CURSOR_SIZE + c]; } } } g_cursor_saved = 0; } static void cursor_draw(int mx, int my) { for (int r = 0; r < CURSOR_SIZE; r++) { for (int c = 0; c < CURSOR_SIZE; c++) { if (cursor_shape[r][c]) { du_pixel(&g_fb, mx + c, my + r, KS_ACCENT); } } } } /* ---- 双缓冲 flip ---- */ /* 将 sprite buffer 完整复制到真实 framebuffer */ static void flip_buffer(void) { if (!g_real_fb) return; u64 total = g_fb_h * g_fb_pitch / 4; u32 *dst = (u32 *)g_real_fb; u32 *src = (u32 *)SPRITE_BUF_ADDR; for (u64 i = 0; i < total; i++) dst[i] = src[i]; } /* 仅复制指定矩形区域(用于鼠标移动时的局部更新) */ static void flip_rect(int x, int y, int w, int h) { if (!g_real_fb) return; for (int r = 0; r < h; r++) { int ry = y + r; if (ry < 0 || (u64)ry >= g_fb_h) continue; u32 *dst = (u32 *)(g_real_fb + (u64)ry * g_fb_pitch); u32 *src = (u32 *)((u8 *)SPRITE_BUF_ADDR + (u64)ry * g_fb_pitch); for (int c = 0; c < w; c++) { int rx = x + c; if (rx < 0 || (u64)rx >= g_fb_w) continue; dst[rx] = src[rx]; } } } /* 非阻塞延时:动画期间持续轮询鼠标,保持光标响应。 */ __attribute__((unused)) static void anim_delay_poll_mouse(u32 ms) { for (u32 i = 0; i < ms; i++) { for (volatile u32 j = 0; j < 50000; j++) { __asm__ volatile("pause"); } if (ps2_mouse_poll()) { int old_x = g_cursor_old_x, old_y = g_cursor_old_y; cursor_restore_bg(); flip_rect(old_x, old_y, CURSOR_SIZE, CURSOR_SIZE); cursor_save_bg(g_mouse_x, g_mouse_y); cursor_draw(g_mouse_x, g_mouse_y); flip_rect(g_mouse_x, g_mouse_y, CURSOR_SIZE, CURSOR_SIZE); } } } /* ============================================================ * 窗口管理 * ============================================================ */ static desktop_window *win_find(int id) { for (int i = 0; i < g_win_count; i++) { if (g_windows[i].id == id) return &g_windows[i]; } return 0; } static desktop_window *win_find_at(int mx, int my) { for (int i = g_win_count - 1; i >= 0; i--) { desktop_window *w = &g_windows[i]; if (!w->visible || w->minimized) continue; if (w->ws != g_ws_cur) continue; if (mx >= w->x && mx < w->x + w->w && my >= w->y && my < w->y + w->h) { return w; } } return 0; } static void win_bring_to_front(desktop_window *w) { int idx = -1; for (int i = 0; i < g_win_count; i++) { if (g_windows[i].id == w->id) { idx = i; break; } } if (idx < 0 || idx == g_win_count - 1) return; desktop_window tmp = g_windows[idx]; for (int i = idx; i < g_win_count - 1; i++) { g_windows[i] = g_windows[i+1]; } g_windows[g_win_count - 1] = tmp; } static void win_focus(desktop_window *w) { int id = w->id; /* bring_to_front 会重排数组,先保存 id 避免悬空指针 */ win_bring_to_front(w); for (int i = 0; i < g_win_count; i++) g_windows[i].focused = (g_windows[i].id == id); g_focused_win = id; if (g_win_count > 0) g_windows[g_win_count - 1].minimized = 0; } static desktop_window *win_create(int app_id, const char *title, int x, int y, int w, int h) { if (g_win_count >= MAX_WINDOWS) return 0; desktop_window *win = &g_windows[g_win_count++]; int id = g_next_win_id++; win->id = id; win->x = x; win->y = y; win->w = w; win->h = h; win->visible = 1; win->focused = 1; win->minimized = 0; win->app_id = app_id; win->app_state = 0; win->closing = 0; win->dirty = 1; win->ws = g_ws_cur; for (int i = 0; i < 47 && title[i]; i++) win->title[i] = title[i]; win->title[47] = 0; for (int i = 0; i < g_win_count - 1; i++) g_windows[i].focused = 0; g_focused_win = id; return win; } static void win_destroy(desktop_window *w) { int idx = -1; for (int i = 0; i < g_win_count; i++) { if (g_windows[i].id == w->id) { idx = i; break; } } if (idx < 0) return; /* 直接隐藏窗口(去掉关闭动画以避免卡顿和闪屏) */ w->visible = 0; 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); } 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) { 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) { g_focused_win = g_windows[i].id; g_windows[i].focused = 1; break; } } } } static int win_hit_titlebar(desktop_window *w, int mx, int my) { return mx >= w->x && mx < w->x + w->w && my >= w->y && my < w->y + KATE_TITLEBAR_H; } /* P5d: 关闭请求。运行中的 PE 窗口不能直接 win_destroy(run_windowed 阻塞中, * pump 持有窗口 id)——按 pe_window_host 协作契约 inject WM_CLOSE, * PE 自愿退出后由 launch_pe_app 收尾销毁。 */ static void win_close_request(desktop_window *w) { if (g_pe_running && w->id == g_pe_win_id && g_pe_svc && g_pe_svc->inject_input) { g_pe_svc->inject_input(PE_WM_CLOSE, 0, 0, 0, 0); return; } win_destroy(w); } static int win_hit_close_btn(desktop_window *w, int mx, int my) { 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 && my >= by && my < by + CLOSE_BTN_SIZE; } static int ws_win_count(int ws) { int n = 0; for (int i = 0; i < g_win_count; i++) if (g_windows[i].ws == ws && g_windows[i].visible) n++; return n; } /* ============================================================ * 窗口装饰绘制(Winux-Kate 风格) * ============================================================ */ static void draw_window_frame(desktop_window *w) { if (!w->visible || w->minimized) return; u32 border = w->focused ? KS_BORDER_FOCUS : KS_BORDER; if (w->focused) { du_kate_glow_border(&g_fb, w->x, w->y, w->w, w->h, KS_ACCENT); } du_fill_rounded_rect(&g_fb, w->x, w->y, w->w, w->h, KS_BG_SECONDARY, 2); du_fill_rect_gradient(&g_fb, w->x + 1, w->y + 1, w->w - 2, KATE_TITLEBAR_H - 1, 0xFF0A3050u, KS_BG_SECONDARY); du_divider_h(&g_fb, w->x, w->y + KATE_TITLEBAR_H, w->w, KS_BORDER); du_fill_rect(&g_fb, w->x + 6, w->y + KATE_TITLEBAR_H / 2 - 2, 4, 4, KS_ACCENT2); du_draw_string(&g_fb, w->title, w->x + 14, w->y + (KATE_TITLEBAR_H - (i64)DU_ASCII_LINE_H) / 2 + 1, KS_ACCENT, 0xFF0A3050u, DU_ASCII_STEP); int bx = w->x + w->w - CLOSE_BTN_OFFSET; int by = w->y + (KATE_TITLEBAR_H - CLOSE_BTN_SIZE) / 2; du_rect_outline(&g_fb, bx, by, CLOSE_BTN_SIZE, CLOSE_BTN_SIZE, KS_DANGER, 1); du_draw_string(&g_fb, "X", bx + 3, by - 1, KS_DANGER, KS_BG_SECONDARY, DU_ASCII_STEP); du_rect_outline(&g_fb, w->x, w->y, w->w, w->h, border, 2); } /* ============================================================ * PS/2 鼠标处理 * ============================================================ */ static int ps2_wait_write(void) { for (int t = 0; t < 200000; t++) { if (!(inb(0x64) & 0x02)) return 0; __asm__ volatile("pause"); } return -1; } static int ps2_wait_read(void) { for (int t = 0; t < 200000; t++) { if (inb(0x64) & 0x01) return 0; __asm__ volatile("pause"); } return -1; } static void ps2_drain(void) { for (int i = 0; i < 16; i++) { if (!(inb(0x64) & 0x01)) break; inb(0x60); } } static void ps2_mouse_init(void) { ps2_drain(); ps2_wait_write(); outb(0x64, 0xA8); ps2_wait_write(); outb(0x64, 0x20); ps2_wait_read(); u8 cfg = inb(0x60); cfg &= ~0x20; cfg |= 0x02; cfg |= 0x40; ps2_wait_write(); outb(0x64, 0x60); ps2_wait_write(); outb(0x60, cfg); ps2_wait_write(); outb(0x64, 0xD4); ps2_wait_write(); outb(0x60, 0xFF); for (int i = 0; i < 8; i++) { if (ps2_wait_read() != 0) break; inb(0x60); } ps2_wait_write(); outb(0x64, 0xD4); ps2_wait_write(); outb(0x60, 0xF4); for (int i = 0; i < 4; i++) { if (ps2_wait_read() != 0) break; inb(0x60); } ps2_drain(); g_mouse_idx = 0; g_mouse_has_pkt = 0; slog("mouse init ok"); } static int ps2_mouse_poll(void) { u8 st = inb(0x64); if (!(st & 1)) return 0; if (!(st & 0x20)) return 0; u8 data = inb(0x60); g_mouse_buf[g_mouse_idx++] = data; if (g_mouse_idx < 3) return 0; g_mouse_idx = 0; if (!(g_mouse_buf[0] & 0x08)) return 0; int dx = (int)(i8)g_mouse_buf[1]; int dy = (int)(i8)g_mouse_buf[2]; dy = -dy; g_mouse_x += dx; g_mouse_y += dy; if (g_mouse_x < 0) g_mouse_x = 0; if (g_mouse_y < 0) g_mouse_y = 0; if (g_mouse_x >= (int)g_fb_w - CURSOR_SIZE) g_mouse_x = (int)g_fb_w - CURSOR_SIZE; if (g_mouse_y >= (int)g_fb_h - CURSOR_SIZE) g_mouse_y = (int)g_fb_h - CURSOR_SIZE; int old_btn = g_mouse_btn; g_mouse_btn = g_mouse_buf[0] & 0x03; if ((g_mouse_btn & 1) && !(old_btn & 1)) g_left_pressed = 1; if (!(g_mouse_btn & 1) && (old_btn & 1)) g_left_released = 1; if ((g_mouse_btn & 2) && !(old_btn & 2)) g_right_pressed = 1; if (dx == 0 && dy == 0 && g_mouse_btn == old_btn) return 0; /* VSCode Phase 2: IDE attached 时把相对位移 + 按钮状态镜像到 Linux guest。 * dy 已在上方取反为屏幕坐标系(正=下),与 Linux REL_Y 约定一致; * input_forward_mouse 内部与上次状态比较,仅产生变化按钮事件 + SYN。 * 即使宿主光标已抵边界被钳位,原始 dx/dy 仍转发,guest 光标自由移动。 */ if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_mouse) { g_lxc_svc->input_forward_mouse(dx, dy, (u8)g_mouse_btn); } g_mouse_has_pkt = 1; return 1; } /* ============================================================ * 键盘扫描码 → ASCII * ============================================================ */ static char scan_to_ascii(u8 sc, int shift) { 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,' ' }; if (sc >= 58) return 0; return shift ? shifted[sc] : normal[sc]; } /* VSCode Phase 2: PS/2 set-1 扫描码 → Linux keycode(linux/input-event-codes.h)。 * base : 已去除 0x80 release 位的基础扫描码(0x01..0x39 主键区 + 少数扩展) * e0 : 上一字节为 0xE0 前缀时置 1,选取扩展键变体 * 返回 0 表示该扫描码无映射(调用方应丢弃)。 * * 巧合:主键区 0x01..0x39 的 Linux keycode 与扫描码数值完全一致 * (IBM XT 布局历史遗留),故直接透传;仅 E0 扩展键需单独查表。 */ static u16 scancode_to_linux_keycode(u8 base, int e0) { if (e0) { switch (base) { case 0x1D: return 97; /* KEY_RIGHTCTRL */ case 0x38: return 100; /* KEY_RIGHTALT */ case 0x48: return 103; /* KEY_UP */ case 0x4B: return 105; /* KEY_LEFT */ case 0x4D: return 106; /* KEY_RIGHT */ case 0x50: return 108; /* KEY_DOWN */ case 0x52: return 110; /* KEY_INSERT */ case 0x53: return 111; /* KEY_DELETE */ case 0x47: return 102; /* KEY_HOME */ case 0x4F: return 107; /* KEY_END */ case 0x49: return 104; /* KEY_PAGEUP */ case 0x51: return 109; /* KEY_PAGEDOWN */ default: return 0; } } /* 主键区:0x01..0x39 透传,超出范围无映射 */ if (base >= 0x01 && base <= 0x39) return (u16)base; return 0; } /* ============================================================ * 终端 (Bash) — 面板/窗口共用核心 * ============================================================ */ #define TERM_MAX_COLS 80 #define TERM_MAX_ROWS 40 #define TERM_MAX_CHARS (TERM_MAX_COLS * TERM_MAX_ROWS) typedef struct { u8 ch[TERM_MAX_CHARS]; u32 fg[TERM_MAX_CHARS]; int cols, rows; int cur_col, cur_row; char input_buf[256]; int input_len, input_cursor; int prompt_len; /* 性能优化:脏行标记(增量渲染) */ u8 dirty_rows[TERM_MAX_ROWS]; /* 标记哪些行需要重绘 */ int any_dirty; /* 是否有任何脏行 */ } bash_state; static const char *BASH_PROMPT = "deshab# "; static void bash_reset(bash_state *s, int w, int h) { for (int i = 0; i < TERM_MAX_CHARS; i++) { s->ch[i] = ' '; s->fg[i] = KS_TEXT_PRIMARY; } s->cols = (w - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_STEP; s->rows = (h - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_LINE_H; if (s->cols > TERM_MAX_COLS) s->cols = TERM_MAX_COLS; if (s->rows > TERM_MAX_ROWS) s->rows = TERM_MAX_ROWS; if (s->cols < 10) s->cols = 10; if (s->rows < 3) s->rows = 3; s->cur_col = 0; s->cur_row = 0; s->input_len = 0; s->input_cursor = 0; s->prompt_len = kstrlen(BASH_PROMPT); /* 初始化脏行标记:所有行标记为脏,确保首次渲染 */ for (int i = 0; i < TERM_MAX_ROWS; i++) s->dirty_rows[i] = 1; s->any_dirty = 1; } static void bash_putc(bash_state *s, char c, u32 color) { if (c == '\n') { s->dirty_rows[s->cur_row] = 1; /* 标记当前行为脏 */ s->any_dirty = 1; s->cur_col = 0; s->cur_row++; return; } if (s->cur_row >= s->rows) { /* 滚动:所有行都需要重绘 */ for (int r = 1; r < s->rows; r++) { for (int c2 = 0; c2 < s->cols; c2++) { s->ch[(r-1)*TERM_MAX_COLS+c2] = s->ch[r*TERM_MAX_COLS+c2]; s->fg[(r-1)*TERM_MAX_COLS+c2] = s->fg[r*TERM_MAX_COLS+c2]; } } for (int c2 = 0; c2 < s->cols; c2++) { s->ch[(s->rows-1)*TERM_MAX_COLS+c2] = ' '; } s->cur_row = s->rows - 1; /* 标记所有行为脏 */ for (int r = 0; r < s->rows; r++) s->dirty_rows[r] = 1; s->any_dirty = 1; } if (s->cur_col >= s->cols) { s->cur_col = 0; s->cur_row++; } int idx = s->cur_row * TERM_MAX_COLS + s->cur_col; s->ch[idx] = (u8)c; s->fg[idx] = color; s->cur_col++; /* 标记当前行为脏 */ s->dirty_rows[s->cur_row] = 1; s->any_dirty = 1; } static void bash_puts(bash_state *s, const char *str, u32 color) { while (*str) bash_putc(s, *str++, color); } static void bash_execute(bash_state *s, const char *cmd) { while (*cmd == ' ') cmd++; bash_puts(s, BASH_PROMPT, KS_ACCENT2); bash_puts(s, cmd, KS_TEXT_PRIMARY); bash_putc(s, '\n', 0); if (*cmd == 0) return; if (cmd[0]=='h'&&cmd[1]=='e'&&cmd[2]=='l'&&cmd[3]=='p') { bash_puts(s, " help clear echo version\n", KS_ACCENT2); bash_puts(s, " uname about reboot halt\n", KS_ACCENT2); bash_puts(s, " pwd whoami id\n", KS_ACCENT2); } else if (cmd[0]=='c'&&cmd[1]=='l'&&cmd[2]=='e'&&cmd[3]=='a'&&cmd[4]=='r') { for (int i = 0; i < TERM_MAX_CHARS; i++) { s->ch[i] = ' '; } s->cur_col = 0; s->cur_row = 0; } else if (cmd[0]=='v'&&cmd[1]=='e'&&cmd[2]=='r') { bash_puts(s, "Deshab OS v0.1.0 (Winux-Kate shell)\n", KS_ACCENT2); } else if (cmd[0]=='u'&&cmd[1]=='n'&&cmd[2]=='a') { bash_puts(s, "Deshab\n", KS_TEXT_PRIMARY); } else if (cmd[0]=='a'&&cmd[1]=='b'&&cmd[2]=='o'&&cmd[3]=='u'&&cmd[4]=='t') { bash_puts(s, "Deshab OS - SAS-R0 Kernel\n", KS_ACCENT); bash_puts(s, "Winux-Kate UI Structure\n", KS_ACCENT2); } else if (cmd[0]=='e'&&cmd[1]=='c'&&cmd[2]=='h'&&cmd[3]=='o') { const char *arg = cmd + 4; while (*arg == ' ') arg++; bash_puts(s, arg, KS_TEXT_PRIMARY); bash_putc(s, '\n', 0); } else if (cmd[0]=='p'&&cmd[1]=='w'&&cmd[2]=='d') { bash_puts(s, "/\n", KS_TEXT_PRIMARY); } else if (cmd[0]=='w'&&cmd[1]=='h'&&cmd[2]=='o') { bash_puts(s, "root\n", KS_TEXT_PRIMARY); } else if (cmd[0]=='i'&&cmd[1]=='d') { bash_puts(s, "uid=0(root) gid=0(root)\n", KS_TEXT_PRIMARY); } else if (cmd[0]=='r'&&cmd[1]=='e'&&cmd[2]=='b') { bash_puts(s, "Rebooting...\n", KS_ACCENT2); outb(0x64, 0xFE); for(;;) __asm__("hlt"); } else if (cmd[0]=='h'&&cmd[1]=='a'&&cmd[2]=='l'&&cmd[3]=='t') { bash_puts(s, "Halted.\n", KS_ACCENT2); for(;;) __asm__("hlt"); } else { bash_puts(s, "unknown: ", KS_DANGER); bash_puts(s, cmd, KS_DANGER); bash_putc(s, '\n', 0); } } static void bash_key(bash_state *s, u8 sc, int shift) { if (sc == 0x1C) { s->input_buf[s->input_len] = 0; bash_execute(s, s->input_buf); s->input_len = 0; s->input_cursor = 0; } else if (sc == 0x0E) { if (s->input_cursor > 0) { for (int i = s->input_cursor-1; i < s->input_len-1; i++) s->input_buf[i] = s->input_buf[i+1]; s->input_len--; s->input_cursor--; } } else { char c = scan_to_ascii(sc, shift); if (c && c >= 32 && c <= 126 && s->input_len < 255) { for (int i = s->input_len; i > s->input_cursor; i--) s->input_buf[i] = s->input_buf[i-1]; s->input_buf[s->input_cursor++] = c; s->input_len++; } } } /* 绘制终端到指定矩形区域(面板或窗口客户区) * 性能优化:增量渲染,只绘制脏行 */ static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) { /* 快速退出:终端内容未修改,跳过绘制 */ if (!s->any_dirty) return; du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY); for (int r = 0; r < s->rows; r++) { /* 增量渲染:只绘制脏行 */ if (!s->dirty_rows[r]) continue; for (int c = 0; c < s->cols; c++) { int idx = r * TERM_MAX_COLS + c; if (s->ch[idx] == ' ') continue; int px = cx + (int)DU_SPACE_SM + c * (int)DU_ASCII_STEP; int py = cy + (int)DU_SPACE_SM + r * (int)DU_ASCII_LINE_H; if (px + (int)DU_ASCII_CELL_W > cx + cw) continue; if (py + (int)DU_ASCII_CELL_H > cy + ch) continue; du_draw_char(&g_fb, s->ch[idx], px, py, s->fg[idx], KS_BG_PRIMARY); } /* 清除脏行标记 */ s->dirty_rows[r] = 0; } int input_y = cy + (int)DU_SPACE_SM + s->cur_row * (int)DU_ASCII_LINE_H; if (input_y + (int)DU_ASCII_CELL_H <= cy + ch) { du_draw_string(&g_fb, BASH_PROMPT, cx + (int)DU_SPACE_SM, input_y, KS_ACCENT2, KS_BG_PRIMARY, DU_ASCII_STEP); du_draw_string(&g_fb, s->input_buf, cx + (int)DU_SPACE_SM + s->prompt_len * (int)DU_ASCII_STEP, input_y, KS_TEXT_PRIMARY, KS_BG_PRIMARY, DU_ASCII_STEP); int cursor_x = cx + (int)DU_SPACE_SM + (s->prompt_len + s->input_cursor) * (int)DU_ASCII_STEP; du_fill_rect(&g_fb, cursor_x, input_y + (int)DU_ASCII_CELL_H - 3, (int)DU_ASCII_CELL_W, 2, KS_ACCENT); } /* 清除全局脏标记 */ s->any_dirty = 0; } /* ---- 窗口适配 ---- */ __attribute__((unused)) static void *bash_on_create(app_ctx *ctx) { bash_state *s = (bash_state *)ADDR_TERMWIN; bash_reset(s, ctx->client_w, ctx->client_h); bash_puts(s, "Deshab Bash v0.1\nType help for commands\n\n", KS_TEXT_PRIMARY); return s; } __attribute__((unused)) static void bash_on_event(void *state, app_ctx *ctx, desktop_event *ev) { if (ev->type != EV_KEY_DOWN) return; bash_key((bash_state *)state, ev->scancode, ev->shift); ctx->win->dirty = 1; } __attribute__((unused)) static void bash_on_draw(void *state, app_ctx *ctx) { bash_draw_to((bash_state *)state, ctx->client_x, ctx->client_y, ctx->client_w, ctx->client_h); } __attribute__((unused)) static void bash_on_destroy(void *state) { (void)state; } /* ============================================================ * 文本编辑器 (Editor) — 面板/IDE/窗口共用核心 * ============================================================ */ #define EDITOR_BUF_SIZE 65536 typedef struct { char text[EDITOR_BUF_SIZE]; int text_len; int cursor_pos; int scroll_y; int cursor_line; int cursor_col; int modified; int has_file; /* 关联了磁盘文件 */ char name11[11]; /* FAT32 8.3 名 */ char disp[13]; /* 显示名 */ } editor_state; static void editor_recalc_cursor(editor_state *s) { s->cursor_line = 0; s->cursor_col = 0; for (int i = 0; i < s->cursor_pos && i < s->text_len; i++) { if (s->text[i] == '\n') { s->cursor_line++; s->cursor_col = 0; } else s->cursor_col++; } } static int editor_line_start(editor_state *s, int line) { int l = 0; for (int i = 0; i < s->text_len; i++) { if (l == line) return i; if (s->text[i] == '\n') l++; } return s->text_len; } static void editor_reset(editor_state *s, const char *sample) { s->text_len = 0; s->cursor_pos = 0; s->scroll_y = 0; s->modified = 0; s->has_file = 0; s->disp[0] = 0; while (*sample && s->text_len < EDITOR_BUF_SIZE - 1) { s->text[s->text_len++] = *sample++; } s->cursor_pos = s->text_len; editor_recalc_cursor(s); } static void editor_key(editor_state *s, u8 sc, int shift) { if (sc == 0x1C) { if (s->text_len < EDITOR_BUF_SIZE - 1) { for (int i = s->text_len; i > s->cursor_pos; i--) s->text[i] = s->text[i-1]; s->text[s->cursor_pos] = '\n'; s->text_len++; s->cursor_pos++; s->modified = 1; } } else if (sc == 0x0E) { if (s->cursor_pos > 0) { for (int i = s->cursor_pos - 1; i < s->text_len - 1; i++) s->text[i] = s->text[i+1]; s->text_len--; s->cursor_pos--; s->modified = 1; } } else if (sc == 0x48) { if (s->cursor_line > 0) { int prev_start = editor_line_start(s, s->cursor_line - 1); int col = s->cursor_col; s->cursor_pos = prev_start; for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++) s->cursor_pos++; } } else if (sc == 0x50) { int next_start = editor_line_start(s, s->cursor_line + 1); if (next_start < s->text_len || s->cursor_line < 999) { int col = s->cursor_col; s->cursor_pos = next_start; for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++) s->cursor_pos++; } } else if (sc == 0x4B) { if (s->cursor_pos > 0) s->cursor_pos--; } else if (sc == 0x4D) { if (s->cursor_pos < s->text_len) s->cursor_pos++; } else if (sc == 0x47) { s->cursor_pos = editor_line_start(s, s->cursor_line); } else if (sc == 0x4F) { s->cursor_pos = editor_line_start(s, s->cursor_line); while (s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n') s->cursor_pos++; } else { char c = scan_to_ascii(sc, shift); if (c && c >= 32 && c <= 126 && s->text_len < EDITOR_BUF_SIZE - 1) { for (int i = s->text_len; i > s->cursor_pos; i--) s->text[i] = s->text[i-1]; s->text[s->cursor_pos] = c; s->text_len++; s->cursor_pos++; s->modified = 1; } } editor_recalc_cursor(s); } /* 绘制编辑器到指定矩形区域 */ static void editor_draw_to(editor_state *s, int cx, int cy, int cw, int ch) { int line_num_w = 5 * (int)DU_ASCII_STEP + (int)DU_SPACE_SM; int text_area_x = cx + line_num_w; int text_area_w = cw - line_num_w; int visible_rows = (ch - 20) / (int)DU_ASCII_LINE_H; int visible_cols = text_area_w / (int)DU_ASCII_STEP; if (visible_rows < 1) visible_rows = 1; if (visible_cols < 4) visible_cols = 4; du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY); if (s->cursor_line < s->scroll_y) s->scroll_y = s->cursor_line; if (s->cursor_line >= s->scroll_y + visible_rows) s->scroll_y = s->cursor_line - visible_rows + 1; int line = 0; int pos = 0; for (int i = 0; i < s->text_len || line <= s->cursor_line; ) { if (line >= s->scroll_y + visible_rows) break; if (line >= s->scroll_y) { int screen_y = cy + (line - s->scroll_y) * (int)DU_ASCII_LINE_H; char ln[8]; ln[0] = '0' + ((line+1)/100)%10; ln[1] = '0' + ((line+1)/10)%10; ln[2] = '0' + (line+1)%10; ln[3] = 0; du_draw_string(&g_fb, ln, cx + 2, screen_y, KS_TEXT_DIM, KS_BG_SECONDARY, DU_ASCII_STEP); du_fill_rect(&g_fb, cx + line_num_w - 2, screen_y, 1, (int)DU_ASCII_CELL_H, KS_BORDER_DIM); int col = 0; while (pos < s->text_len && s->text[pos] != '\n' && col < visible_cols) { du_draw_char(&g_fb, s->text[pos], text_area_x + col * (int)DU_ASCII_STEP, screen_y, KS_TEXT_PRIMARY, KS_BG_SECONDARY); col++; pos++; } if (pos < s->text_len && s->text[pos] == '\n') pos++; } else { while (pos < s->text_len && s->text[pos] != '\n') pos++; if (pos < s->text_len) pos++; } line++; if (pos >= s->text_len && line > s->cursor_line) break; } /* 光标(青色竖条) */ if (s->cursor_line >= s->scroll_y && s->cursor_line < s->scroll_y + visible_rows) { int cursor_y = cy + (s->cursor_line - s->scroll_y) * (int)DU_ASCII_LINE_H; int cursor_x = text_area_x + s->cursor_col * (int)DU_ASCII_STEP; du_fill_rect(&g_fb, cursor_x, cursor_y, 2, (int)DU_ASCII_CELL_H, KS_ACCENT); } /* 底部迷你状态栏 */ int status_y = cy + ch - 20; du_fill_rect(&g_fb, cx, status_y, cw, 20, KS_BG_TERTIARY); du_divider_h(&g_fb, cx, status_y, cw, KS_BORDER); char status[64]; int sp = 0; const char *p1 = "L:"; while(*p1) status[sp++] = *p1++; status[sp++] = '0' + (s->cursor_line+1)/10%10; status[sp++] = '0' + (s->cursor_line+1)%10; const char *p2 = " C:"; while(*p2) status[sp++] = *p2++; status[sp++] = '0' + (s->cursor_col+1)/10%10; status[sp++] = '0' + (s->cursor_col+1)%10; if (s->modified) { const char *m = " *"; while(*m) status[sp++] = *m++; } if (s->has_file) { const char *m = " "; while(*m) status[sp++] = *m++; for (int i = 0; s->disp[i] && sp < 60; i++) status[sp++] = s->disp[i]; } status[sp] = 0; du_draw_string(&g_fb, status, cx + 4, status_y + 1, KS_TEXT_DIM, KS_BG_TERTIARY, DU_ASCII_STEP); } /* ---- 窗口适配 ---- */ static void *editor_on_create(app_ctx *ctx) { editor_state *s = (editor_state *)ADDR_EDWIN; editor_reset(s, "# Welcome to Deshab Editor\n#\n\nStart typing here...\n"); for (int i = 0; i < 47; i++) ctx->win->title[i] = 0; const char *t = "Editor - untitled"; for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i]; return s; } static void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev) { if (ev->type != EV_KEY_DOWN) return; editor_key((editor_state *)state, ev->scancode, ev->shift); ctx->win->dirty = 1; } static void editor_on_draw(void *state, app_ctx *ctx) { editor_draw_to((editor_state *)state, ctx->client_x, ctx->client_y, ctx->client_w, ctx->client_h); } static void editor_on_destroy(void *state) { (void)state; } /* ============================================================ * 计算器 (Calculator) * ============================================================ */ typedef struct { char display[32]; int display_len; i64 accumulator; i64 current; int op; int new_number; } calc_state; static void calc_update_display(calc_state *s) { s->display_len = 0; i64 val = s->current; if (val < 0) { s->display[s->display_len++] = '-'; val = -val; } if (val == 0) { s->display[s->display_len++] = '0'; } else { char tmp[20]; int tl = 0; while (val > 0) { tmp[tl++] = '0' + (int)(val % 10); val /= 10; } for (int i = tl-1; i >= 0; i--) s->display[s->display_len++] = tmp[i]; } s->display[s->display_len] = 0; } static void *calc_on_create(app_ctx *ctx) { calc_state *s = (calc_state *)ADDR_CALC; s->accumulator = 0; s->current = 0; s->op = 0; s->new_number = 1; calc_update_display(s); const char *t = "Calculator"; for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i]; return s; } static void calc_on_event(void *state, app_ctx *ctx, desktop_event *ev) { calc_state *s = (calc_state *)state; if (ev->type != EV_KEY_DOWN) return; char c = scan_to_ascii(ev->scancode, ev->shift); if (c >= '0' && c <= '9') { if (s->new_number) { s->current = 0; s->new_number = 0; } s->current = s->current * 10 + (c - '0'); } else if (c == '+') { s->accumulator = s->current; s->op = 1; s->new_number = 1; } else if (c == '-') { s->accumulator = s->current; s->op = 2; s->new_number = 1; } else if (c == '*') { s->accumulator = s->current; s->op = 3; s->new_number = 1; } else if (c == '/') { s->accumulator = s->current; s->op = 4; s->new_number = 1; } else if (c == '=' || c == '\n') { switch (s->op) { case 1: s->current = s->accumulator + s->current; break; case 2: s->current = s->accumulator - s->current; break; case 3: s->current = s->accumulator * s->current; break; case 4: s->current = (s->current != 0) ? s->accumulator / s->current : 0; break; } s->op = 0; s->new_number = 1; } else if (c == 'c' || c == 'C') { s->current = 0; s->accumulator = 0; s->op = 0; s->new_number = 1; } calc_update_display(s); ctx->win->dirty = 1; } static void calc_on_draw(void *state, app_ctx *ctx) { calc_state *s = (calc_state *)state; int cx = ctx->client_x; int cy = ctx->client_y; int cw = ctx->client_w; int ch = ctx->client_h; du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY); int disp_h = 40; du_fill_rounded_rect(&g_fb, cx + 8, cy + 8, cw - 16, disp_h, KS_BG_PRIMARY, DU_RADIUS_MD); du_rect_outline(&g_fb, cx + 8, cy + 8, cw - 16, disp_h, KS_ACCENT, DU_RADIUS_MD); int text_x = cx + cw - 16 - s->display_len * (int)DU_ASCII_STEP - 8; du_draw_string(&g_fb, s->display, text_x, cy + 16, KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP); const char *btn_labels[] = { "C", "+/-", "%", "/", "7", "8", "9", "*", "4", "5", "6", "-", "1", "2", "3", "+", "0", ".", "=", 0 }; int btn_w = (cw - 16 - 12) / 4; int btn_h = 32; int btn_y = cy + 8 + disp_h + 8; for (int i = 0; i < 19; i++) { int row = i / 4, col = i % 4; int bx = cx + 8 + col * (btn_w + 3); int by = btn_y + row * (btn_h + 3); if (by + btn_h > cy + ch - 4) break; u32 bg = (i < 4) ? KS_ACCENT_DIM : (i % 4 == 3) ? KS_ACCENT : (i == 18) ? KS_ACCENT2 : KS_BG_TERTIARY; u32 fg = ((i % 4 == 3) || (i == 18)) ? KS_TEXT_INVERT : KS_TEXT_PRIMARY; du_fill_rounded_rect(&g_fb, bx, by, btn_w, btn_h, bg, DU_RADIUS_SM); du_rect_outline(&g_fb, bx, by, btn_w, btn_h, KS_BORDER, DU_RADIUS_SM); du_draw_string(&g_fb, btn_labels[i], bx + (btn_w - 1*(int)DU_ASCII_STEP)/2, by + (btn_h - (int)DU_ASCII_CELL_H)/2, fg, bg, DU_ASCII_STEP); } } static void calc_on_destroy(void *state) { (void)state; } /* ============================================================ * 应用注册 * ============================================================ */ /* ---- VSCode Phase 5: LINUXAPP.CNF Linux 应用注册表 ---- * FAT32 根目录 LINUXAPP.CNF(8.3: LINUXAPPCNF),每行一个 Linux guest 应用: * NAME|DISPLAY|/guest/path|args(args 可含空格,可省略) * '#' 开头为注释。解析结果存静态池,app_descriptor.linux_path 指入。 */ #define LINUXAPP_MAX 4 static char g_lapp_name[LINUXAPP_MAX][12]; static char g_lapp_disp[LINUXAPP_MAX][16]; static char g_lapp_path[LINUXAPP_MAX][96]; static char g_lapp_args[LINUXAPP_MAX][96]; static int g_vscode_app_id = -1; /* 第一个名为 "vscode" 的注册项 */ /* 字段拷贝:src[0..n) → dst(NUL 结尾),返回拷贝长度 */ static int lapp_field_copy(char *dst, int dst_cap, const char *src, int n) { if (n >= dst_cap) n = dst_cap - 1; for (int i = 0; i < n; i++) dst[i] = src[i]; dst[n] = 0; return n; } static void register_linux_apps(void) { u8 *data = 0; u32 size = 0; char n11[11]; if (f32_name_to_83("LINUXAPP.CNF", n11) != 0) return; if (f32_read_root_file(n11, &data, &size) != 0) return; /* 无配置文件:跳过 */ int li = 0; /* 已注册数量 */ u32 pos = 0; while (pos < size && li < LINUXAPP_MAX && g_app_count < MAX_APPS) { /* 取一行 */ u32 eol = pos; while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++; u32 line_len = eol - pos; /* 跳过到下一行 */ u32 next = eol; while (next < size && (data[next] == '\n' || data[next] == '\r')) next++; if (line_len == 0 || data[pos] == '#') { pos = next; continue; } /* 按 '|' 切 4 段:NAME|DISPLAY|PATH|ARGS */ const char *line = (const char *)data + pos; int f0 = -1, f1 = -1, f2 = -1; /* 三个分隔符位置 */ for (u32 i = 0; i < line_len; i++) { if (line[i] == '|') { if (f0 < 0) f0 = (int)i; else if (f1 < 0) f1 = (int)i; else if (f2 < 0) { f2 = (int)i; break; } } } if (f0 > 0 && f1 > f0) { int path_beg = f1 + 1; int path_end = (f2 > path_beg) ? f2 : (int)line_len; int args_beg = (f2 > path_beg) ? f2 + 1 : (int)line_len; lapp_field_copy(g_lapp_name[li], 12, line, f0); lapp_field_copy(g_lapp_disp[li], 16, line + f0 + 1, f1 - f0 - 1); lapp_field_copy(g_lapp_path[li], 96, line + path_beg, path_end - path_beg); if (args_beg < (int)line_len) lapp_field_copy(g_lapp_args[li], 96, line + args_beg, (int)line_len - args_beg); else g_lapp_args[li][0] = 0; if (g_lapp_path[li][0] == '/') { g_apps[g_app_count] = (app_descriptor){ g_lapp_name[li], g_lapp_disp[li], 1024, 768, 0, 0, 0, 0, 0, g_lapp_path[li], g_lapp_args[li] }; /* 记录 VSCode app id(IDE attach 共享启动路径用)。 * P7.4 匹配收紧:精确匹配 "VSCODE"(第 7 字符须为 NUL), * VSCODE2 等多实例条目不占用共享启动入口。 */ if (g_vscode_app_id < 0 && g_lapp_name[li][0] == 'V' && g_lapp_name[li][1] == 'S' && g_lapp_name[li][2] == 'C' && g_lapp_name[li][3] == 'O' && g_lapp_name[li][4] == 'D' && g_lapp_name[li][5] == 'E' && g_lapp_name[li][6] == 0) g_vscode_app_id = g_app_count; g_app_count++; li++; } } pos = next; } } /* ---- P5d: PEAPPS.CNF Windows PE 应用注册表 ---- * FAT32 根目录 PEAPPS.CNF(8.3: PEAPPS CNF),每行一个 Windows PE 应用: * NAME|DISPLAY|path/file.exe|args('/' 分隔长路径,args 可含空格可省略) * '#' 开头为注释。解析结果存静态池,app_descriptor.pe_file 指入。 * 仅适用于 GUI PE(消息循环驱动 pump);纯控制台 PE 不会触发消息空转, * 窗口模式下桌面将无响应直到进程退出——控制台程序请走 CMD。 */ #define PEAPP_MAX 4 static char g_papp_name[PEAPP_MAX][12]; static char g_papp_disp[PEAPP_MAX][16]; static char g_papp_file[PEAPP_MAX][96]; static char g_papp_args[PEAPP_MAX][96]; static void pe_win_on_draw(void *state, app_ctx *ctx); /* 定义于 P5d 运行时段 */ static void launch_pe_app(int app_id); static void register_pe_apps(void) { u8 *data = 0; u32 size = 0; char n11[11]; if (f32_name_to_83("PEAPPS.CNF", n11) != 0) return; if (f32_read_root_file(n11, &data, &size) != 0) return; /* 无配置文件:跳过 */ int pi = 0; u32 pos = 0; while (pos < size && pi < PEAPP_MAX && g_app_count < MAX_APPS) { u32 eol = pos; while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++; u32 line_len = eol - pos; u32 next = eol; while (next < size && (data[next] == '\n' || data[next] == '\r')) next++; if (line_len == 0 || data[pos] == '#') { pos = next; continue; } /* 按 '|' 切 4 段:NAME|DISPLAY|FILE|ARGS */ const char *line = (const char *)data + pos; int f0 = -1, f1 = -1, f2 = -1; for (u32 i = 0; i < line_len; i++) { if (line[i] == '|') { if (f0 < 0) f0 = (int)i; else if (f1 < 0) f1 = (int)i; else if (f2 < 0) { f2 = (int)i; break; } } } if (f0 > 0 && f1 > f0) { int file_beg = f1 + 1; int file_end = (f2 > file_beg) ? f2 : (int)line_len; int args_beg = (f2 > file_beg) ? f2 + 1 : (int)line_len; lapp_field_copy(g_papp_name[pi], 12, line, f0); lapp_field_copy(g_papp_disp[pi], 16, line + f0 + 1, f1 - f0 - 1); lapp_field_copy(g_papp_file[pi], 96, line + file_beg, file_end - file_beg); if (args_beg < (int)line_len) lapp_field_copy(g_papp_args[pi], 96, line + args_beg, (int)line_len - args_beg); else g_papp_args[pi][0] = 0; if (g_papp_file[pi][0]) { g_apps[g_app_count] = (app_descriptor){ g_papp_name[pi], g_papp_disp[pi], 720, 540, 0, 0, pe_win_on_draw, 0, 0, 0, 0, g_papp_file[pi], g_papp_args[pi] }; g_app_count++; pi++; } } pos = next; } } static void register_apps(void) { /* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/ g_apps[0] = (app_descriptor){ "shell", "Terminal", 720, 480, 0, 0, 0, 0, "SHELL ELF" }; g_app_count++; /* 命令行2:cmd.elf(Windows 风格命令行 + PE/EXE 执行)*/ g_apps[1] = (app_descriptor){ "cmd", "CMD", 720, 480, 0, 0, 0, 0, "CMD ELF" }; g_app_count++; g_apps[2] = (app_descriptor){ "editor", "Editor", 600, 450, editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy, 0 }; g_app_count++; g_apps[3] = (app_descriptor){ "fileman", "Files", 500, 400, 0, 0, 0, 0, "FILEMAN ELF" }; g_app_count++; g_apps[4] = (app_descriptor){ "calc", "Calculator", 280, 400, calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy, 0 }; g_app_count++; g_apps[5] = (app_descriptor){ "browser", "Browser", 700, 500, 0, 0, 0, 0, "BROWSER ELF" }; g_app_count++; /* ProEdit — 增强版文本编辑器(语法高亮、设置、目录浏览)*/ g_apps[6] = (app_descriptor){ "proedit", "ProEdit", 750, 550, 0, 0, 0, 0, "PROEDIT ELF" }; g_app_count++; /* Settings — 系统设置(内核 FUCK 配置 + 网络 NETCONF 配置)*/ g_apps[7] = (app_descriptor){ "settings", "Settings", 760, 540, 0, 0, 0, 0, "SETTINGSELF" }; g_app_count++; /* VSCode Phase 5: Linux guest 应用(VSCode 等),来自 LINUXAPP.CNF */ register_linux_apps(); /* P5d: Windows PE 应用,来自 PEAPPS.CNF */ register_pe_apps(); } /* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px, * 左→右排列,超出画布宽度则换行(align-content: start) * * 注意:Terminal 和 CMD 已在第一页 Dashboard(TERM-01 / TERM-02), * 桌面不再重复显示其图标。 */ static void setup_desktop_icons(void) { int pad = KATE_ICON_PAD; /* 24 */ int x = pad; int y = KATE_TOPBAR_H + KATE_PAGE_PAD + pad; int max_x = (int)g_fb_w - pad; /* canvas 右缘 */ for (int i = 0; i < g_app_count; i++) { if (g_icon_count >= MAX_ICONS) break; /* P7.4: Linux 应用(linux_path)也要出图标——否则 VSCODE 不可见 */ if (!g_apps[i].on_create && !g_apps[i].elf_name && !g_apps[i].linux_path && !g_apps[i].pe_file) continue; /* 跳过 Terminal 和 CMD(已在 Dashboard 第一页) */ if (g_apps[i].name[0] == 's' && g_apps[i].name[1] == 'h' && g_apps[i].name[2] == 'e') continue; if (g_apps[i].name[0] == 'c' && g_apps[i].name[1] == 'm' && g_apps[i].name[2] == 'd') continue; if (x > pad && x + KATE_ICON_W > max_x) { x = pad; y += KATE_ICON_H + KATE_ICON_GAP; } g_icons[g_icon_count].app_id = i; g_icons[g_icon_count].x = x; g_icons[g_icon_count].y = y; g_icons[g_icon_count].label = g_apps[i].display_name; g_icons[g_icon_count].selected = 0; g_icon_count++; x += KATE_ICON_W + KATE_ICON_GAP; } } /* ============================================================ * 外部 ELF 加载 * ============================================================ */ #define DT_ELFCLASS64 2 #define DT_EM_X86_64 62 #define DT_PT_LOAD 1 #define DT_PT_DYNAMIC 2 #define DT_DT_RELA 7 #define DT_DT_RELASZ 8 #define DT_R_X86_64_RELATIVE 8 typedef struct { u8 ident[16]; u16 type,machine; u32 ver; u64 entry,phoff,shoff; u32 flags; u16 ehsize,phentsize,phnum,shentsize,shnum,shstrndx; } dt_elf64_ehdr; typedef struct { u32 type,flags; u64 offset,vaddr,paddr,filesz,memsz,align; } dt_elf64_phdr; typedef struct { i64 tag; u64 val; } dt_elf64_dyn; typedef struct { u64 offset; u64 info; i64 addend; } dt_elf64_rela; static u8 g_elf_image[1048576]; /* 1 MiB:settings.elf 内存映像 ~493KB(中文字体位图 BSS),256KB 不够 */ 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; if (eh->ident[0] != 0x7F || eh->ident[4] != DT_ELFCLASS64) return -2; if (eh->machine != DT_EM_X86_64) return -3; u64 min_vaddr = ~0ULL, max_vaddr = 0; u32 lc = 0; for (u16 i = 0; i < eh->phnum; i++) { const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize); if (ph->type != DT_PT_LOAD) continue; if (ph->filesz > ph->memsz) return -4; if (ph->vaddr < min_vaddr) min_vaddr = ph->vaddr; if (ph->vaddr + ph->memsz > max_vaddr) max_vaddr = ph->vaddr + ph->memsz; lc++; } if (!lc || min_vaddr == ~0ULL) return -5; u64 isize = (max_vaddr - min_vaddr + 0xFFF) & ~0xFFFULL; if (isize > sizeof(g_elf_image)) return -6; u8 *image = g_elf_image; for (u64 i = 0; i < isize; i++) image[i] = 0; for (u16 i = 0; i < eh->phnum; i++) { const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize); if (ph->type != DT_PT_LOAD) continue; u64 off = ph->vaddr - min_vaddr; for (u64 j = 0; j < ph->filesz; j++) image[off + j] = data[ph->offset + j]; } for (u16 i = 0; i < eh->phnum; i++) { const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize); if (ph->type != DT_PT_DYNAMIC) continue; const dt_elf64_dyn *dyn = (const dt_elf64_dyn *)(data + ph->offset); u64 rela_off = 0, rela_sz = 0; for (u32 j = 0; j < ph->filesz / sizeof(dt_elf64_dyn); j++) { if (dyn[j].tag == DT_DT_RELA) rela_off = dyn[j].val; else if (dyn[j].tag == DT_DT_RELASZ) rela_sz = dyn[j].val; else if (dyn[j].tag == 0) break; } if (rela_off && rela_sz) { const dt_elf64_rela *r = (const dt_elf64_rela *)(data + rela_off); u32 rn = (u32)(rela_sz / sizeof(dt_elf64_rela)); u64 load_bias = (u64)image - min_vaddr; /* 与 DSK loader 一致 */ for (u32 j = 0; j < rn; j++) { u32 type = (u32)(r[j].info & 0xFFFFFFFF); if (type == DT_R_X86_64_RELATIVE) { /* 原 BUG: val = min_vaddr + addend,指针被写成低地址绝对值, * 导致 settings.elf 等模块内 .data 中的字符串指针全部失效 */ if (r[j].offset < min_vaddr || r[j].offset >= min_vaddr + isize) continue; u64 target = r[j].offset - min_vaddr; u64 val = load_bias + (u64)r[j].addend; *(u64 *)(image + target) = val; } } } } *entry_out = (void *)(image + (eh->entry - min_vaddr)); return 0; } static void launch_external_elf(const char *name11) { u8 *data = 0; u32 size = 0; if (f32_read_root_file(name11, &data, &size) != 0) { slog("elf not found"); return; } 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 void fill_app_ctx(app_ctx *ctx, desktop_window *win) { ctx->fb = &g_fb; ctx->win = win; ctx->client_x = win->x + BORDER_W; ctx->client_y = win->y + KATE_TITLEBAR_H; ctx->client_w = win->w - 2 * BORDER_W; ctx->client_h = win->h - KATE_TITLEBAR_H - BORDER_W; ctx->should_exit = 0; ctx->block_read = g_block_read; ctx->block_write = g_block_write; ctx->kernel_api = g_kernel_api; } static void launch_linux_app(int app_id); /* VSCode Phase 5(定义于 IDE 区) */ static void launch_app(int app_id) { if (app_id < 0 || app_id >= g_app_count) return; app_descriptor *app = &g_apps[app_id]; /* VSCode Phase 5: Linux guest 应用走 exec_async 共享启动路径 */ if (app->linux_path) { launch_linux_app(app_id); return; } /* P5d: Windows PE 应用走 run_windowed 桌面嵌入路径 */ if (app->pe_file) { launch_pe_app(app_id); return; } if (app->elf_name) { launch_external_elf(app->elf_name); return; } if (!app->on_create) return; /* 单实例:已有同类窗口则聚焦并迁移到当前工作区 */ for (int i = 0; i < g_win_count; i++) { if (g_windows[i].app_id == app_id && g_windows[i].visible) { g_windows[i].ws = g_ws_cur; win_focus(&g_windows[i]); slog("focus existing app window"); return; } } int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_TASKBAR_H; int wx = ICON_PAD + g_win_count * 30; int wy = top + 8 + g_win_count * 30; if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = ICON_PAD; if (wy + (int)app->default_h > bot - 20) wy = top + 8; desktop_window *win = win_create(app_id, app->display_name, wx, wy, (int)app->default_w, (int)app->default_h); if (!win) return; app_ctx ctx; fill_app_ctx(&ctx, win); win->app_state = app->on_create(&ctx); win->dirty = 1; /* 直接显示窗口(去掉打开动画以避免卡顿和闪屏) */ slog("app launched"); } /* ============================================================ * FILES 面板(FAT32 真实读写) * ============================================================ */ static int files_list_cb(const char *name, u32 size, u8 attr, void *ud) { (void)ud; if (attr & 0x10) return 0; /* 跳过子目录 */ if (g_file_count >= MAX_FILES) return 1; /* 停止 */ /* 只显示 user 文件:跳过 .ELF 可执行文件和系统模块 */ int nl = kstrlen(name); if (nl >= 4 && name[nl-4] == '.' && name[nl-3] == 'E' && name[nl-2] == 'L' && name[nl-1] == 'F') return 0; /* FIRSTINI* (FirstInit.elf,Name83=FIRSTINIT → 显示名 FIRSTINI.T) */ if (nl >= 8 && name[0] == 'F' && name[1] == 'I' && name[2] == 'R' && name[3] == 'S' && name[4] == 'T' && name[5] == 'I' && name[6] == 'N' && name[7] == 'I') return 0; fentry *f = &g_files[g_file_count]; f->size = size; kstrcpy(f->disp, name, 13); /* disp (NAME.EXT) → name11 (NAME EXT) */ for (int i = 0; i < 11; i++) f->name11[i] = ' '; int i = 0, o = 0; while (name[i] && name[i] != '.' && o < 8) f->name11[o++] = name[i++]; while (name[i] && name[i] != '.') i++; if (name[i] == '.') { i++; o = 8; while (name[i] && o < 11) f->name11[o++] = name[i++]; } g_file_count++; return 0; } static void files_refresh(void) { g_file_count = 0; g_file_sel = -1; if (!g_block_read) { g_files_loaded = 0; return; } if (f32_list_root(files_list_cb, 0) == 0) g_files_loaded = 1; else g_files_loaded = 0; } /* 双击打开文件 → 载入 DASHBOARD EDITOR */ static void files_open(int idx) { if (idx < 0 || idx >= g_file_count) return; editor_state *ed = (editor_state *)ADDR_EDDASH; fentry *f = &g_files[idx]; u8 *data = 0; u32 size = 0; if (f32_read_root_file(f->name11, &data, &size) != 0) { slog("file open failed"); return; } ed->text_len = 0; u32 cap = EDITOR_BUF_SIZE - 1; if (size > cap) size = cap; for (u32 i = 0; i < size; i++) { char c = (char)data[i]; if ((u8)c >= 32 || c == '\n' || c == '\t') ed->text[ed->text_len++] = c; } for (int i = 0; i < 11; i++) ed->name11[i] = f->name11[i]; kstrcpy(ed->disp, f->disp, 13); ed->has_file = 1; ed->modified = 0; ed->scroll_y = 0; ed->cursor_pos = 0; editor_recalc_cursor(ed); g_dash_focus = 2; slog("file loaded into editor"); } /* EDITOR SAVE 回写磁盘 */ static void editor_save(void) { editor_state *ed = (editor_state *)ADDR_EDDASH; if (!ed->has_file) return; if (f32_write_root_file(ed->name11, (const u8 *)ed->text, (u32)ed->text_len) == 0) { ed->modified = 0; slog("file saved"); } else { slog("file save failed"); } } /* ============================================================ * 通用小绘制 * ============================================================ */ /* 矩形内居中文字 */ static void draw_centered(const char *s, int cx, int y, u32 fg, u32 bg) { int l = kstrlen(s); du_draw_string(&g_fb, s, cx - l * (int)DU_ASCII_STEP / 2, y, fg, bg, DU_ASCII_STEP); } /* 虚线矩形(ide-host / custom host) */ static void draw_dashed_rect(int x, int y, int w, int h, u32 color) { for (int i = x; i < x + w; i += 8) { du_fill_rect(&g_fb, i, y, 4, 1, color); du_fill_rect(&g_fb, i, y + h - 1, 4, 1, color); } for (int i = y; i < y + h; i += 8) { du_fill_rect(&g_fb, x, i, 1, 4, color); du_fill_rect(&g_fb, x + w - 1, i, 1, 4, color); } } /* ============================================================ * Boot 屏(打字机) * ============================================================ */ static const char *BOOT_LINES[] = { "DESHAB SHELL v0.1.0", "initializing UTSM sealed memory...", "mounting FAT32 root filesystem............[ OK ]", "loading window manager (SAS-R0 Ring0).....[ OK ]", "registering PS/2 input drivers............[ OK ]", "probing AHCI block device.................[ OK ]", "resolving desktop shortcuts...............[ OK ]", "desktop replaced by Winux-Kate shell......[ OK ]", "shell ready.", "DEAICUP STUDIO", }; #define BOOT_LINE_COUNT 10 static void busy_delay(u32 cycles) { for (volatile u32 i = 0; i < cycles; i++) __asm__("pause"); } static void boot_screen(void) { int W = (int)g_fb_w, H = (int)g_fb_h; du_fill_bg_solid(&g_fb, KS_BG_PRIMARY); flip_buffer(); int lw = 46 * (int)DU_ASCII_STEP; int lx = (W - lw) / 2; int ty = H / 2 - 180; /* boot-title */ const char *title = "DESHAB"; int tl = kstrlen(title); for (int i = 0; i < tl; i++) { char t[2] = { title[i], 0 }; du_draw_string(&g_fb, t, W / 2 - tl * (int)DU_ASCII_STEP + i * (int)DU_ASCII_STEP * 2, ty, KS_ACCENT, 0, DU_ASCII_STEP * 2); flip_buffer(); busy_delay(2000000); } du_divider_h(&g_fb, W / 2 - 120, ty + 30, 240, KS_ACCENT); /* boot-subtitle(对应 Kate "由 Deaicup 工作室制作") */ draw_centered("DEAICUP STUDIO", W / 2, ty + 44, KS_ACCENT2, 0); flip_buffer(); busy_delay(2500000); /* boot-lines 打字机;进度条固定在全部行下方(对应 Kate .boot-bar) */ int ly = ty + 76; int bar_y = ly + BOOT_LINE_COUNT * ((int)DU_ASCII_LINE_H + 2) + 12; int bw = 260; for (int li = 0; li < BOOT_LINE_COUNT; li++) { const char *s = BOOT_LINES[li]; u32 fg = (li == 1 || li == BOOT_LINE_COUNT - 1) ? KS_TEXT_DIM : KS_ACCENT2; du_draw_string(&g_fb, ">", lx, ly, KS_ACCENT, 0, DU_ASCII_STEP); for (int ci = 0; s[ci]; ci++) { char t[2] = { s[ci], 0 }; du_draw_string(&g_fb, t, lx + (ci + 1) * (int)DU_ASCII_STEP, ly, fg, 0, DU_ASCII_STEP); flip_buffer(); busy_delay(125000); } ly += (int)DU_ASCII_LINE_H + 2; /* boot-bar:渐变填充(accent → accent2) */ int fill = bw * (li + 1) / BOOT_LINE_COUNT; du_rect_outline(&g_fb, W / 2 - bw / 2, bar_y, bw, 6, KS_BORDER, 1); if (fill > 2) du_fill_rect_gradient(&g_fb, W / 2 - bw / 2 + 1, bar_y + 1, fill - 2, 4, KS_ACCENT, KS_ACCENT2); flip_buffer(); } /* boot-hint + credit(对应 Kate "SYSTEM ONLINE" + "© 2026 Deaicup Studio") */ draw_centered("SYSTEM ONLINE", W / 2, H - 64, KS_ACCENT2, 0); draw_centered("(C) 2026 DEAICUP STUDIO", W / 2, H - 40, KS_TEXT_DIM, 0); flip_buffer(); busy_delay(10000000); } /* ============================================================ * 顶栏(brand + WorkspaceSwitcher + PageSwitcher + PG 时钟) * ============================================================ */ static void draw_topbar(void) { int W = (int)g_fb_w; du_fill_rect_gradient(&g_fb, 0, 0, W, KATE_TOPBAR_H, 0xFF0A2840u, KS_BG_PRIMARY); du_divider_h(&g_fb, 0, KATE_TOPBAR_H, W, KS_BORDER); int x = 12; int ty = (KATE_TOPBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1; /* brand:方块点 + DESHAB + brand-credit(对应 Kate "由 Deaicup 工作室制作") */ du_fill_rect(&g_fb, x, KATE_TOPBAR_H / 2 - 3, 6, 6, KS_ACCENT); du_draw_string(&g_fb, "DESHAB", x + 12, ty, KS_ACCENT, 0, DU_ASCII_STEP + 2); x += 12 + 6 * ((int)DU_ASCII_STEP + 2) + 10; du_draw_string(&g_fb, "DEAICUP STUDIO", x, ty + 2, KS_ACCENT2, 0, DU_ASCII_STEP); x += 14 * (int)DU_ASCII_STEP + 14; /* 分隔线 + 任务视图按钮(三横线图标) */ du_divider_v(&g_fb, x - 6, 6, KATE_TOPBAR_H - 12, KS_BORDER); g_tvbtn_rect[0] = x; g_tvbtn_rect[1] = 5; g_tvbtn_rect[2] = 28; g_tvbtn_rect[3] = 24; { int hover = g_mouse_x >= x && g_mouse_x < x + 28 && g_mouse_y >= 5 && g_mouse_y < 29; if (hover) du_fill_rect(&g_fb, x, 5, 28, 24, KS_ACCENT_DIM); du_rect_outline(&g_fb, x, 5, 28, 24, KS_BORDER, 1); du_fill_rect(&g_fb, x + 6, 11, 16, 2, KS_ACCENT); du_fill_rect(&g_fb, x + 6, 16, 16, 2, KS_ACCENT); du_fill_rect(&g_fb, x + 6, 21, 16, 2, KS_ACCENT); } x += 34; /* 工作区按钮 "name|n" */ for (int i = 0; i < g_ws_count; i++) { char label[24]; kstrcpy(label, g_ws[i].name, 18); kstrcat(label, "|", 24); int cnt = ws_win_count(i); char cn[4]; cn[0] = '0' + (char)(cnt % 10); cn[1] = 0; kstrcat(label, cn, 24); int bw = kstrlen(label) * (int)DU_ASCII_STEP + 16; int active = (i == g_ws_cur); int hover = g_mouse_x >= x && g_mouse_x < x + bw && g_mouse_y >= 5 && g_mouse_y < 29; if (active) { du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT2); du_kate_glow_border(&g_fb, x, 5, bw, 24, KS_ACCENT2); } else if (hover) { du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM); } du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT2 : KS_BORDER, 1); du_draw_string(&g_fb, label, x + 8, ty, active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP); g_ws_rect[i][0] = x; g_ws_rect[i][1] = 5; g_ws_rect[i][2] = bw; g_ws_rect[i][3] = 24; x += bw + 6; } /* ws-add "+" */ g_wsadd_rect[0] = x; g_wsadd_rect[1] = 5; g_wsadd_rect[2] = 26; g_wsadd_rect[3] = 24; { int hover = g_mouse_x >= x && g_mouse_x < x + 26 && g_mouse_y >= 5 && g_mouse_y < 29; if (hover) du_fill_rect(&g_fb, x, 5, 26, 24, KS_ACCENT_DIM); du_rect_outline(&g_fb, x, 5, 26, 24, KS_ACCENT2, 1); du_draw_string(&g_fb, "+", x + 7, ty - 1, KS_ACCENT2, 0, DU_ASCII_STEP); } x += 32; /* 分隔线 */ du_divider_v(&g_fb, x - 4, 6, KATE_TOPBAR_H - 12, KS_BORDER); x += 4; /* 页面切换:精简按钮文字 */ static const char *pg_names[3] = { "DASH", "IDE", "DESK" }; for (int i = 0; i < 3; i++) { int bw = kstrlen(pg_names[i]) * (int)DU_ASCII_STEP + 24; int active = (g_page == i + 1); int hover = g_mouse_x >= x && g_mouse_x < x + bw && g_mouse_y >= 5 && g_mouse_y < 29; if (active) { du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT); du_kate_glow_border(&g_fb, x, 5, bw, 24, KS_ACCENT); } else if (hover) { du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM); } du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT : KS_BORDER, 1); du_draw_string(&g_fb, pg_names[i], x + 12, ty, active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP); g_pg_rect[i][0] = x; g_pg_rect[i][1] = 5; g_pg_rect[i][2] = bw; g_pg_rect[i][3] = 24; x += bw + 8; } /* 自定义页 */ for (int i = 0; i < g_cpage_count; i++) { char label[20]; label[0] = '0' + (char)g_cpages[i].id; label[1] = '.'; label[2] = 0; kstrcat(label, g_cpages[i].name, 20); int bw = kstrlen(label) * (int)DU_ASCII_STEP + 28; int active = (g_page == g_cpages[i].id); int hover = g_mouse_x >= x && g_mouse_x < x + bw && g_mouse_y >= 5 && g_mouse_y < 29; if (active) { du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT); } else if (hover) { du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM); } du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT : KS_BORDER, 1); du_draw_string(&g_fb, label, x + 6, ty, active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP); du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_DANGER, 0, DU_ASCII_STEP); g_pg_rect[3 + i][0] = x; g_pg_rect[3 + i][1] = 5; g_pg_rect[3 + i][2] = bw; g_pg_rect[3 + i][3] = 24; g_pg_rm_rect[3 + i][0] = x + bw - 20; g_pg_rm_rect[3 + i][1] = 5; g_pg_rm_rect[3 + i][2] = 20; g_pg_rm_rect[3 + i][3] = 24; x += bw + 6; } /* pg-add "+" */ if (g_cpage_count < MAX_CPAGES) { g_pgadd_rect[0] = x; g_pgadd_rect[1] = 5; g_pgadd_rect[2] = 26; g_pgadd_rect[3] = 24; int hover = g_mouse_x >= x && g_mouse_x < x + 26 && g_mouse_y >= 5 && g_mouse_y < 29; if (hover) du_fill_rect(&g_fb, x, 5, 26, 24, KS_ACCENT_DIM); du_rect_outline(&g_fb, x, 5, 26, 24, KS_ACCENT2, 1); du_draw_string(&g_fb, "+", x + 7, ty - 1, KS_ACCENT2, 0, DU_ASCII_STEP); } else { g_pgadd_rect[2] = 0; } /* 右侧:credit-top + PG 时钟(对应 Kate "© Deaicup Studio" + "PG n") */ char pgclk[8]; pgclk[0] = 'P'; pgclk[1] = 'G'; pgclk[2] = ' '; pgclk[3] = '0' + (char)(g_page % 10); pgclk[4] = 0; const char *credit = "(C) DEAICUP STUDIO"; int cl = kstrlen(credit) + 1 + kstrlen(pgclk); du_draw_string(&g_fb, credit, W - 12 - cl * (int)DU_ASCII_STEP, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); du_draw_string(&g_fb, pgclk, W - 12 - kstrlen(pgclk) * (int)DU_ASCII_STEP, ty, KS_ACCENT2, 0, DU_ASCII_STEP); } /* ============================================================ * 滑块(VOL / BRIGHT) * ============================================================ */ static void draw_slider(int x, int y, int w, int val, int rect_out[4]) { du_fill_rect(&g_fb, x, y + 5, w, 3, KS_BG_TERTIARY); du_rect_outline(&g_fb, x, y + 4, w, 5, KS_BORDER, 1); int fw = w * val / 100; if (fw > 0) du_fill_rect(&g_fb, x, y + 5, fw, 3, KS_ACCENT); int kx = x + fw - 3; if (kx < x) kx = x; if (kx > x + w - 6) kx = x + w - 6; du_fill_rect(&g_fb, kx, y, 6, 13, KS_ACCENT); du_rect_outline(&g_fb, kx, y, 6, 13, KS_ACCENT, 1); rect_out[0] = x; rect_out[1] = y - 2; rect_out[2] = w; rect_out[3] = 17; } /* ============================================================ * 状态栏(DASHBOARD 底部 32px) * ============================================================ */ static void draw_statusbar_full(void) { int W = (int)g_fb_w; int y = (int)g_fb_h - KATE_STATUSBAR_H; du_fill_rect(&g_fb, 0, y, W, KATE_STATUSBAR_H, 0xFF051828u); du_divider_h(&g_fb, 0, y, W, KS_BORDER); int x = 12; int ty = y + (KATE_STATUSBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1; /* 时钟(日期 · 秒级时间,accent2) */ int yy, mo, dd, hh, mi, ss; rtc_read(&yy, &mo, &dd, &hh, &mi, &ss); char clk[32]; int cp2 = 0; clk[cp2++] = '2'; clk[cp2++] = '0'; clk[cp2++] = '0' + (char)(yy / 10); clk[cp2++] = '0' + (char)(yy % 10); clk[cp2++] = '-'; clk[cp2++] = '0' + (char)(mo / 10); clk[cp2++] = '0' + (char)(mo % 10); clk[cp2++] = '-'; clk[cp2++] = '0' + (char)(dd / 10); clk[cp2++] = '0' + (char)(dd % 10); clk[cp2++] = ' '; clk[cp2++] = '.'; clk[cp2++] = ' '; char hms[9]; fmt_hms(hms, hh * 3600 + mi * 60 + ss); for (int i = 0; hms[i]; i++) clk[cp2++] = hms[i]; clk[cp2] = 0; du_draw_string(&g_fb, clk, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP); x += cp2 * (int)DU_ASCII_STEP + 18; /* UP 计时 */ { int up = hh * 3600 + mi * 60 + ss - g_rtc_boot_sec; if (up < 0) up += 86400; char upb[16]; kstrcpy(upb, "UP ", 16); char uh[9]; fmt_hms(uh, up); kstrcat(upb, uh, 16); du_draw_string(&g_fb, upb, x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); x += kstrlen(upb) * (int)DU_ASCII_STEP + 18; } /* VOL 滑块 */ du_draw_string(&g_fb, "VOL", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); x += 3 * (int)DU_ASCII_STEP + 6; draw_slider(x, y + 9, 90, g_vol, g_vol_rect); x += 90 + 8; { char vb[6]; vb[0] = '0' + (char)(g_vol / 100 % 10); vb[1] = '0' + (char)(g_vol / 10 % 10); vb[2] = '0' + (char)(g_vol % 10); vb[3] = '%'; vb[4] = 0; const char *vp = (g_vol >= 100) ? vb : (g_vol >= 10 ? vb + 1 : vb + 2); du_draw_string(&g_fb, vp, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP); x += 4 * (int)DU_ASCII_STEP + 14; } /* BRIGHT 滑块 */ du_draw_string(&g_fb, "BRIGHT", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); x += 6 * (int)DU_ASCII_STEP + 6; draw_slider(x, y + 9, 90, g_bright, g_bri_rect); x += 90 + 8; { char vb[6]; vb[0] = '0' + (char)(g_bright / 100 % 10); vb[1] = '0' + (char)(g_bright / 10 % 10); vb[2] = '0' + (char)(g_bright % 10); vb[3] = '%'; vb[4] = 0; const char *vp = (g_bright >= 100) ? vb : (g_bright >= 10 ? vb + 1 : vb + 2); du_draw_string(&g_fb, vp, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP); x += 4 * (int)DU_ASCII_STEP + 14; } /* BT / WIFI */ du_draw_string(&g_fb, "BT OFF", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); x += 6 * (int)DU_ASCII_STEP + 14; du_draw_string(&g_fb, "WIFI --", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); /* 右侧 hint */ const char *hint = "Ctrl+Tab PAGE"; du_draw_string(&g_fb, hint, W - 12 - kstrlen(hint) * (int)DU_ASCII_STEP, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); } /* ============================================================ * 页面绘制 * ============================================================ */ static void draw_desktop_icons(void) { /* desktop-empty(Kate:无快捷方式提示) */ if (g_icon_count == 0) { du_draw_string(&g_fb, "NO SHORTCUTS ON DESKTOP", KATE_ICON_PAD, KATE_TOPBAR_H + KATE_PAGE_PAD + KATE_ICON_PAD + 16, KS_TEXT_DIM, 0, DU_ASCII_STEP); return; } for (int i = 0; i < g_icon_count; i++) { desktop_icon *ic = &g_icons[i]; du_kate_desktop_icon(&g_fb, ic->x, ic->y, ic->label, ic->selected); int ix = ic->x + (KATE_ICON_W - KATE_ICON_IMG) / 2; int iy = ic->y + 10; draw_app_icon(&g_fb, ic->app_id, ix, iy); } } /* ---- 1·DASHBOARD ---- */ static void dash_layout(void) { int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_STATUSBAR_H; int pad = 8, gap = 8; int avail_w = (int)g_fb_w - 2 * pad; int avail_h = bot - top - 2 * pad; int left_w = avail_w * 35 / 100; /* 终端面板:42%→35% */ int right_x = pad + left_w + gap; int right_w = (int)g_fb_w - pad - right_x; int row_h = (avail_h - gap) / 2; g_dash_rect[0][0] = pad; g_dash_rect[0][1] = top + pad; g_dash_rect[0][2] = left_w; g_dash_rect[0][3] = row_h; g_dash_rect[1][0] = pad; g_dash_rect[1][1] = top + pad + row_h + gap; g_dash_rect[1][2] = left_w; g_dash_rect[1][3] = row_h; g_dash_rect[2][0] = right_x; g_dash_rect[2][1] = top + pad; g_dash_rect[2][2] = right_w; g_dash_rect[2][3] = row_h; g_dash_rect[3][0] = right_x; g_dash_rect[3][1] = top + pad + row_h + gap; g_dash_rect[3][2] = right_w; g_dash_rect[3][3] = row_h; } static void draw_dashboard(void) { dash_layout(); const char *headers[4] = { "TERM-01", "TERM-02", 0, "FILES" }; char ed_hdr[40]; editor_state *ed = (editor_state *)ADDR_EDDASH; kstrcpy(ed_hdr, "EDITOR . ", 40); kstrcat(ed_hdr, ed->has_file ? ed->disp : "untitled", 40); headers[2] = ed_hdr; for (int i = 0; i < 4; i++) { int x = g_dash_rect[i][0], y = g_dash_rect[i][1]; int w = g_dash_rect[i][2], h = g_dash_rect[i][3]; du_kate_panel(&g_fb, x, y, w, h, headers[i]); if (g_dash_focus == i) { du_rect_outline(&g_fb, x, y, w, h, KS_ACCENT, 2); } } /* EDITOR 头 SAVE 按钮(仅关联文件后可用) */ { int ex = g_dash_rect[2][0], ey = g_dash_rect[2][1], ew = g_dash_rect[2][2]; int bw = 52, bh = 18; int bx = ex + ew - bw - 6, by = ey + 3; g_save_rect[0] = bx; g_save_rect[1] = by; g_save_rect[2] = bw; g_save_rect[3] = bh; u32 fg = ed->has_file ? KS_ACCENT : KS_TEXT_DIM; int hover = ed->has_file && g_mouse_x >= bx && g_mouse_x < bx + bw && g_mouse_y >= by && g_mouse_y < by + bh; if (hover) du_fill_rect(&g_fb, bx, by, bw, bh, KS_ACCENT_DIM); du_rect_outline(&g_fb, bx, by, bw, bh, ed->has_file ? KS_BORDER : KS_BORDER_DIM, 1); du_draw_string(&g_fb, "SAVE", bx + 5, by + 1, fg, 0, DU_ASCII_STEP); } /* 面板内容 */ bash_draw_to((bash_state *)ADDR_TERM1, g_dash_rect[0][0] + 2, g_dash_rect[0][1] + KATE_PANEL_HEADER_H + 2, g_dash_rect[0][2] - 4, g_dash_rect[0][3] - KATE_PANEL_HEADER_H - 4); bash_draw_to((bash_state *)ADDR_TERM2, g_dash_rect[1][0] + 2, g_dash_rect[1][1] + KATE_PANEL_HEADER_H + 2, g_dash_rect[1][2] - 4, g_dash_rect[1][3] - KATE_PANEL_HEADER_H - 4); editor_draw_to(ed, g_dash_rect[2][0] + 2, g_dash_rect[2][1] + KATE_PANEL_HEADER_H + 2, g_dash_rect[2][2] - 4, g_dash_rect[2][3] - KATE_PANEL_HEADER_H - 4); /* FILES 面板内容 */ { int fx = g_dash_rect[3][0] + 2, fy = g_dash_rect[3][1] + KATE_PANEL_HEADER_H + 2; int fw = g_dash_rect[3][2] - 4, fh = g_dash_rect[3][3] - KATE_PANEL_HEADER_H - 4; du_fill_rect(&g_fb, fx, fy, fw, fh, KS_BG_PRIMARY); char path_buf[32]; kstrcpy(path_buf, "FAT32:/USER (", 32); int pl = kstrlen(path_buf); if (g_file_count >= 10) path_buf[pl++] = '0' + (char)(g_file_count / 10); path_buf[pl++] = '0' + (char)(g_file_count % 10); path_buf[pl++] = ')'; path_buf[pl] = 0; du_draw_string(&g_fb, path_buf, fx + 8, fy + 3, KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP); du_divider_h(&g_fb, fx, fy + 22, fw, KS_BORDER); int row_h = 22; int max_rows = (fh - 24) / row_h; for (int i = 0; i < g_file_count && i < max_rows; i++) { int ry = fy + 24 + i * row_h; if (i == g_file_sel) { du_fill_rect(&g_fb, fx, ry, fw, row_h, KS_ACCENT_DIM); } int hover = (g_mouse_x >= fx && g_mouse_x < fx + fw && g_mouse_y >= ry && g_mouse_y < ry + row_h); if (hover && i != g_file_sel) { du_fill_rect(&g_fb, fx, ry, fw, row_h, KS_ACCENT_DIM); } u32 fg = (i == g_file_sel) ? KS_ACCENT : KS_TEXT_PRIMARY; du_draw_string(&g_fb, g_files[i].disp, fx + 8, ry + 2, fg, 0, DU_ASCII_STEP); char sz[12]; u32 kb = g_files[i].size / 1024; int sp = 0; if (kb >= 100) { sz[sp++] = '0' + (char)(kb / 100 % 10); } if (kb >= 10) { sz[sp++] = '0' + (char)(kb / 10 % 10); } sz[sp++] = '0' + (char)(kb % 10); sz[sp++] = 'K'; sz[sp] = 0; du_draw_string(&g_fb, sz, fx + fw - sp * (int)DU_ASCII_STEP - 8, ry + 2, KS_TEXT_DIM, 0, DU_ASCII_STEP); } if (!g_files_loaded) { du_draw_string(&g_fb, "NO BLOCK DEVICE", fx + 8, fy + 28, KS_DANGER, KS_BG_PRIMARY, DU_ASCII_STEP); } else if (g_file_count == 0) { du_draw_string(&g_fb, "(empty)", fx + 8, fy + 28, KS_TEXT_DIM, KS_BG_PRIMARY, DU_ASCII_STEP); } } draw_statusbar_full(); } /* ---- 2·IDE ---- */ static int g_ide_attached = 0; static void page_switch(int p); /* 定义于工作区段,launch_linux_app 前向引用 */ /* VSCode Phase 6: guest GUI 状态机。 * 0 = 未运行(IDE host 显示启动提示) * 1 = 启动中(X server/VSCode spawn 已发出,等 scanout 上线) * 2 = 运行中(scanout enabled,blit guest 帧) */ static int g_vscode_state = 0; /* P8.1: VSCode 启动插桩--分阶段状态跟踪 + 串口日志 + 屏幕显示。 * g_launch_phase: 0=idle 1=X启动 2=X socket等待 3=app spawn 4=pgrep验证 5=等scanout 6=running 7=失败 * g_launch_status: 屏幕显示的状态字符串(IDE host 区域) */ static int g_launch_phase = 0; static char g_launch_status[64] = ""; static u64 g_launch_tsc = 0; /* 启动开始 TSC(测各阶段耗时) */ static int g_launch_exec_rc = 0; /* exec_async 返回码 */ static int g_launch_pgrep_rc = -1; /* pgrep 验证结果(0=找到, 1=未找到)*/ /* P7.4 多实例:IDE tab 动态标签——显示最近启动实例的 DISPLAY 名 * (单 X :0 共享 scanout,多 VSCode 进程同屏;标签指示最后 attach 者)。 */ static char g_ide_tab_label[16] = "VSCODE"; /* P7.9: 性能 profiling(TSC 计时,每秒更新 FPS 显示)。 * 在 IDE 页面叠加显示 FPS / blit 耗时 / dirty 跳过率。 */ static u64 g_prof_frames = 0; /* 自上次 FPS 更新以来的帧数 */ static u64 g_prof_fps_tsc = 0; /* 上次 FPS 计算的 TSC */ static int g_prof_fps = 0; /* 当前 FPS(每秒更新) */ static u64 g_prof_blit_total = 0; /* 累计 blit TSC */ static u64 g_prof_blit_count = 0; /* blit 调用次数 */ static u64 g_prof_blit_skip = 0; /* dirty=0 跳过的次数 */ static u64 g_prof_blit_full = 0; /* full repaint 次数 */ static int g_prof_last_blit_us = 0; /* 上次 blit 耗时(微秒) */ /* P7.3: attach/detach 时使 scanout 缓存失效并请求全量重绘(定义于 blit 区) */ static void ide_attach_invalidate(void); /* VSCode Phase 3: IDE attach 时在 Linux guest 中启动 X server(Xfbdev)。 * Xfbdev 通过 virtio-gpu 2D 设备合成帧到 surface pool scanout 影子缓冲, * desktop 每帧 blit 到 IDE host 区域。exec 用 sh -c "... &" 后台启动, * shell 立即退出被 daemon 回收,Xfbdev 由 init 收养继续运行。 * Phase 6: pgrep 守卫保证幂等(多实例/重复 attach 不重起 X)。 */ /* P8.1: slog + 数字(用于 rc/ec 日志,负数正确显示前导 -) */ static void slog_num(const char *prefix, int rc) { char buf[48]; kstrcpy(buf, prefix, 48); int n = kstrlen(buf); if (rc < 0) { buf[n++] = '-'; rc = -rc; } u32dec((u32)rc, buf + n); slog(buf); } static void ide_launch_guest_x(void) { if (!g_lxc_svc || !g_lxc_svc->exec) { slog("ide: lxc exec unavailable"); return; } slog("[launch] phase 1: launching Xfbdev"); g_launch_phase = 1; kstrcpy(g_launch_status, "LAUNCHING X SERVER . . .", 64); const char *argv[] = { "sh", "-c", "pgrep -x Xfbdev >/dev/null 2>&1 || " "Xfbdev :0 -geometry 1024x768 -depth 32 >/dev/null 2>&1 &" }; u64 exit_code = 0; int rc = g_lxc_svc->exec("/bin/sh", 3, argv, 0, 0, 0, &exit_code); slog_num("[launch] Xfbdev exec rc=", rc); if (rc != 0) { slog("[launch] WARNING: Xfbdev launch rc!=0 (may still be OK if already running)"); } } /* P8.1 Fix: lxc guest ping -- exec "echo OK" 快速检测 Linux guest 是否运行。 * 返回 0=guest 可用,非 0=不可用(VMX 未启用/guest 未 park/IPC 不通)。 */ static int lxc_guest_ping(void) { if (!g_lxc_svc || !g_lxc_svc->exec) return -1; const char *pargv[] = { "sh", "-c", "echo OK" }; char out[8] = {0}; u64 ec = 0; int rc = g_lxc_svc->exec("/bin/sh", 3, pargv, out, 7, 0, &ec); if (rc != 0) return rc; if (out[0] == 'O' && out[1] == 'K') return 0; return -1; } /* VSCode Phase 6: 共享 guest GUI 启动路径(IDE +NEW/双击 host 与桌面图标共用)。 * 首次调用确保 X server 在线(幂等启动 + 有界等待 X socket),然后 * exec_async 目标程序(daemon fork+setsid+detach,立即返回)。 * 后续调用(多实例)跳过 X 准备直接 spawn。path 为 NULL 时仅确保 X 在线。 * P8.1 Fix: 启动前 ping 检测 guest 可用性;失败时快速短路,不浪费 5s X socket 等待。 */ static void guest_gui_launch(const char *path, int argc, const char *const *argv) { if (!g_lxc_svc) { slog("[launch] lxc service null"); g_launch_phase = 7; kstrcpy(g_launch_status, "LXC SERVICE UNAVAILABLE", 64); return; } if (g_vscode_state == 0) { g_vscode_state = 1; /* 启动中 */ g_launch_tsc = rdtsc(); slog("[launch] === guest GUI launch sequence start ==="); /* P8.1 Fix: Phase 0 - ping guest 检测可用性 */ g_launch_phase = 0; kstrcpy(g_launch_status, "CHECKING GUEST . . .", 64); int prc = lxc_guest_ping(); slog_num("[launch] guest ping rc=", prc); if (prc != 0) { slog("[launch] FAIL: Linux guest not running (VMX/guest unavailable)"); g_vscode_state = 0; /* 回到未启动状态 */ g_launch_phase = 7; kstrcpy(g_launch_status, "GUEST NOT RUNNING - NEED KVM/VMX", 64); return; } slog("[launch] guest ping OK - Linux guest is running"); /* Phase 1: launch X server */ ide_launch_guest_x(); /* Phase 2: wait for X socket (bounded ~5s) */ g_launch_phase = 2; kstrcpy(g_launch_status, "WAITING FOR X SOCKET . . .", 64); slog("[launch] phase 2: waiting for X socket"); if (g_lxc_svc->exec) { const char *wargv[] = { "sh", "-c", "i=0; while [ ! -S /tmp/.X11-unix/X0 ] && [ $i -lt 25 ]; " "do sleep 0.2; i=$((i+1)); done; [ -S /tmp/.X11-unix/X0 ] && echo OK || echo FAIL" }; /* 用 exec + stdout 捕获验证 X socket 是否就绪 */ char xout[16] = {0}; u64 ec = 0; int rc = g_lxc_svc->exec("/bin/sh", 3, wargv, xout, 15, 0, &ec); slog_num("[launch] X socket wait rc=", rc); if (xout[0] == 'O' && xout[1] == 'K') { slog("[launch] X socket READY (/tmp/.X11-unix/X0 exists)"); } else { slog("[launch] WARNING: X socket NOT ready after ~5s timeout"); kstrcpy(g_launch_status, "X SOCKET TIMEOUT - RETRYING SPAWN", 64); } } } /* Phase 3: exec_async spawn the app */ if (path && g_lxc_svc->exec_async) { g_launch_phase = 3; kstrcpy(g_launch_status, "SPAWNING APPLICATION . . .", 64); slog("[launch] phase 3: exec_async spawn"); slog(path); int rc = g_lxc_svc->exec_async(path, argc, argv); g_launch_exec_rc = rc; slog_num("[launch] exec_async rc=", rc); if (rc == 0) { slog("[launch] exec_async OK - process spawned"); } else { slog("[launch] exec_async FAILED - process not spawned"); g_vscode_state = 0; /* P8.1 Fix: 回到未启动,UI 显示错误而非永远 STARTING */ g_launch_phase = 7; kstrcpy(g_launch_status, "SPAWN FAILED - CHECK SERIAL LOG", 64); return; } /* Phase 4: pgrep verify process exists (bounded wait ~1s) */ g_launch_phase = 4; kstrcpy(g_launch_status, "VERIFYING PROCESS . . .", 64); slog("[launch] phase 4: pgrep verify"); if (g_lxc_svc->exec) { /* 用 basename 做 pgrep(code -> pgrep code) */ const char *pargv[] = { "sh", "-c", "sleep 0.5; pgrep -x code >/dev/null 2>&1 && echo FOUND || echo NOPE" }; char pout[16] = {0}; u64 pec = 0; int prc = g_lxc_svc->exec("/bin/sh", 3, pargv, pout, 15, 0, &pec); if (pout[0] == 'F') { g_launch_pgrep_rc = 0; slog("[launch] pgrep: VSCode process FOUND"); } else if (pout[0] == 'N') { g_launch_pgrep_rc = 1; slog("[launch] pgrep: VSCode process NOT found (may still be starting)"); } else { g_launch_pgrep_rc = -1; slog_num("[launch] pgrep: no output, exec rc=", prc); } } /* Phase 5: wait for scanout */ g_launch_phase = 5; kstrcpy(g_launch_status, "WAITING FOR SCANOUT . . .", 64); slog("[launch] phase 5: waiting for scanout to come online"); } else if (path && !g_lxc_svc->exec_async) { slog("[launch] exec_async unavailable - cannot spawn app"); g_launch_phase = 7; kstrcpy(g_launch_status, "EXEC_ASYNC UNAVAILABLE", 64); } } /* VSCode Phase 5/6: 桌面图标启动 Linux 应用(LINUXAPP.CNF 注册项)。 * 解析 args(空格分隔)后经共享路径 exec_async,并切到 IDE 页 attach。 */ static void launch_linux_app(int app_id) { if (app_id < 0 || app_id >= g_app_count) return; app_descriptor *app = &g_apps[app_id]; if (!app->linux_path) return; if (!g_lxc_svc || !g_lxc_svc->exec_async) { slog("linuxapp: lxc service unavailable"); return; } /* argv: [name, ...args](args 在本地缓冲内原地切分) */ const char *argv[16]; int argc = 0; argv[argc++] = app->name; char abuf[96]; if (app->linux_args && app->linux_args[0]) { int n = kstrlen(app->linux_args); if (n > (int)sizeof(abuf) - 1) n = (int)sizeof(abuf) - 1; for (int i = 0; i < n; i++) abuf[i] = app->linux_args[i]; abuf[n] = 0; char *p = abuf; while (*p && argc < 15) { while (*p == ' ') p++; if (!*p) break; argv[argc++] = p; while (*p && *p != ' ') p++; if (*p) *p++ = 0; } } guest_gui_launch(app->linux_path, argc, argv); /* P7.4: tab 标签跟随最近启动实例(DISPLAY 名,≤15 字符) */ { int i = 0; const char *dn = app->display_name; while (dn && dn[i] && i + 1 < (int)sizeof(g_ide_tab_label)) { g_ide_tab_label[i] = dn[i]; i++; } g_ide_tab_label[i] = 0; } /* 切到 IDE 页并 attach(VSCode 窗口出现在 IDE host 区域) */ g_ide_attached = 1; g_input_forward_enabled = 1; ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */ page_switch(2); slog("linuxapp launched -> ide attached"); } /* VSCode Phase 6: IDE attach 共享入口——启动(或复用)VSCode。 */ static void vscode_launch(void) { if (g_vscode_app_id >= 0) { launch_linux_app(g_vscode_app_id); return; } /* 未注册 VSCode(CNF 缺失):仅确保 X server 在线,保持 Phase 3 行为 */ guest_gui_launch(0, 0, 0); } /* VSCode Phase 3/7: 把 virtio-gpu scanout 影子缓冲 blit 到 IDE host 区域。 * scanout 为 1024×768 XRGB8888(stride=4096),与 framebuffer 同为 32bpp。 * Phase 7 dirty rect 优化:主循环每帧把 scanout 状态缓存进 g_so(查询会 * 清零 UTSM 侧 dirty),blit 只拷贝累积 dirty rect(scanout 坐标系并集), * sprite 双缓冲持久保留未变区域;attach/首帧/状态切换经 g_so_full_repaint * 全量重绘一次。 */ static struct linux_compat_scanout_info g_so; static int g_so_valid = 0; /* 1 = g_so 为本帧主循环 poll 的缓存 */ static int g_so_full_repaint = 1; /* 1 = 下次 blit 全量拷贝(初始/attach/上线) */ /* attach/detach 时使 scanout 缓存失效并请求全量重绘 */ static void ide_attach_invalidate(void) { g_so_full_repaint = 1; g_so_valid = 0; } /* 获取 scanout 状态:优先本帧缓存;缓存无效时查询并缓存。 */ static const struct linux_compat_scanout_info *ide_scanout_state(void) { if (g_so_valid) return &g_so; if (g_lxc_svc && g_lxc_svc->gpu_get_scanout_info && g_lxc_svc->gpu_get_scanout_info(&g_so) == 0) { g_so_valid = 1; return &g_so; } return 0; } static void blit_scanout_to_ide(int hx, int hy, int hw, int hh) { const struct linux_compat_scanout_info *si = ide_scanout_state(); if (!si || !si->enabled || !si->host_vaddr) { /* scanout 未就绪:X server 尚未 SET_SCANOUT,显示等待提示 */ du_fill_rect(&g_fb, hx, hy, hw, hh, 0xFF051828u); draw_centered("WAITING FOR GUEST X SERVER . . .", hx + hw / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0); return; } int sw = (int)si->width, sh = (int)si->height; int sstride = (int)si->stride; /* bytes/row */ int dstride = (int)g_fb_pitch; /* bytes/row */ u8 *src = (u8 *)si->host_vaddr; u8 *dst_base = (u8 *)g_fb.fb; /* 拷贝区域:默认仅累积 dirty rect;full repaint 时整帧 */ int full = g_so_full_repaint; int cx = 0, cy = 0, cw = sw, ch = sh; if (!full) { if (!si->dirty || si->dirty_w == 0 || si->dirty_h == 0) { g_prof_blit_skip++; /* P7.9: dirty=0 零拷贝跳过 */ return; } cx = (int)si->dirty_x; cy = (int)si->dirty_y; cw = (int)si->dirty_w; ch = (int)si->dirty_h; } g_so_full_repaint = 0; /* P7.9: TSC 计时 blit */ u64 _tsc0 = rdtsc(); if (full) g_prof_blit_full++; /* clamp 到 host 区域与 scanout 边界 */ if (cx + cw > hw) cw = hw - cx; if (cy + ch > hh) ch = hh - cy; if (cw > sw - cx) cw = sw - cx; if (ch > sh - cy) ch = sh - cy; if (cw <= 0 || ch <= 0) return; for (int row = 0; row < ch; row++) { u32 *s = (u32 *)(src + (u64)(cy + row) * sstride + (u64)cx * 4); u32 *d = (u32 *)(dst_base + (u64)(hy + cy + row) * dstride + (u64)(hx + cx) * 4); for (int col = 0; col < cw; col++) d[col] = s[col]; } /* 全量重绘时,scanout 未覆盖的 host 剩余区域填深色背景 */ if (full) { int cov_w = (hw < sw) ? hw : sw; int cov_h = (hh < sh) ? hh : sh; if (cov_w < hw) du_fill_rect(&g_fb, hx + cov_w, hy, hw - cov_w, cov_h, 0xFF051828u); if (cov_h < hh) du_fill_rect(&g_fb, hx, hy + cov_h, hw, hh - cov_h, 0xFF051828u); } /* P7.6: 叠加 guest 硬件光标(ARGB alpha-blend 到 framebuffer)。 * 光标位图 row stride = 64(virtio-gpu 规范 cursor max 64x64)。 * 坐标从 scanout 坐标系映射到 framebuffer:(hx + cursor_x - hot_x, ...) */ if (si->cursor_visible && si->cursor_bitmap) { int cw = (int)si->cursor_w, ch = (int)si->cursor_h; if (cw > 0 && ch > 0) { int fx = hx + (int)si->cursor_x - (int)si->cursor_hot_x; int fy = hy + (int)si->cursor_y - (int)si->cursor_hot_y; u32 *cbmp = (u32 *)si->cursor_bitmap; for (int row = 0; row < ch; row++) { int dy = fy + row; if (dy < 0 || (u64)dy >= g_fb_h) continue; u32 *d = (u32 *)((u8 *)g_fb.fb + (u64)dy * (u64)g_fb_pitch); const u32 *s = &cbmp[row * 64]; /* row stride = VGPU_CURSOR_MAX */ for (int col = 0; col < cw; col++) { int dx = fx + col; if (dx < 0 || (u64)dx >= g_fb_w) continue; u32 src = s[col]; u32 a = (src >> 24) & 0xFF; if (a == 0) continue; if (a == 255) { d[dx] = src; continue; } u32 dst = d[dx]; u32 rr = (((src >> 16) & 0xFF) * a + ((dst >> 16) & 0xFF) * (255 - a)) / 255; u32 gg = (((src >> 8) & 0xFF) * a + ((dst >> 8) & 0xFF) * (255 - a)) / 255; u32 bb = ( (src & 0xFF) * a + ( dst & 0xFF) * (255 - a)) / 255; d[dx] = 0xFF000000u | (rr << 16) | (gg << 8) | bb; } } } } /* P7.9: 记录 blit 耗时 */ g_prof_blit_total += rdtsc() - _tsc0; g_prof_blit_count++; if (g_tsc_per_sec) g_prof_last_blit_us = (int)((rdtsc() - _tsc0) * 1000000 / g_tsc_per_sec); } static void draw_ide(void) { int W = (int)g_fb_w; int top = KATE_TOPBAR_H; /* ide-tabs(36px):dot + 实例标签 + "+ NEW IDE" + ide-hint(Kate 结构) */ du_fill_rect(&g_fb, 0, top, W, 36, 0xFF051828u); du_divider_h(&g_fb, 0, top + 36, W, KS_BORDER); int x = 12; int ty = top + (36 - (int)DU_ASCII_LINE_H) / 2 + 1; /* .dot(8×8 accent2 发光点) */ du_fill_rect(&g_fb, x, top + 14, 8, 8, KS_ACCENT2); x += 16; /* 实例标签(Kate .ide-tab):P7.4 动态标签 = 最近启动实例 DISPLAY 名 */ { const char *label = g_ide_tab_label; int bw = kstrlen(label) * (int)DU_ASCII_STEP + 32; if (g_ide_attached) { du_fill_rect(&g_fb, x, top + 5, bw, 26, KS_ACCENT); du_kate_glow_border(&g_fb, x, top + 5, bw, 26, KS_ACCENT); } du_rect_outline(&g_fb, x, top + 5, bw, 26, g_ide_attached ? KS_ACCENT : KS_BORDER, 1); du_draw_string(&g_fb, label, x + 8, ty, g_ide_attached ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP); du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_DANGER, 0, DU_ASCII_STEP); g_ide_tabx[0] = x + bw - 20; g_ide_tabx[1] = top + 5; g_ide_tabx[2] = 20; g_ide_tabx[3] = 26; x += bw + 8; } /* "+ NEW IDE" 按钮(Kate .btn,透明底 + 边框 + accent 字) */ { const char *nb = "+ NEW IDE"; int bw = kstrlen(nb) * (int)DU_ASCII_STEP + 20; g_ide_new_rect[0] = x; g_ide_new_rect[1] = top + 7; g_ide_new_rect[2] = bw; g_ide_new_rect[3] = 22; int hover = g_mouse_x >= x && g_mouse_x < x + bw && g_mouse_y >= top + 7 && g_mouse_y < top + 29; if (hover) du_fill_rect(&g_fb, x, top + 7, bw, 22, KS_ACCENT_DIM); du_rect_outline(&g_fb, x, top + 7, bw, 22, KS_BORDER, 1); du_draw_string(&g_fb, nb, x + 10, ty, KS_ACCENT, 0, DU_ASCII_STEP); x += bw + 8; } /* ide-hint */ du_draw_string(&g_fb, "CTRL+SHIFT+TAB CTX . x DETACH . DBL-CLICK HOST ATTACH", x + 8, ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP); /* page-credit(对应 Kate "由 Deaicup 工作室制作") */ du_draw_string(&g_fb, "DEAICUP STUDIO", W - 12 - 14 * (int)DU_ASCII_STEP, ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP); /* ide-host(虚线,margin 8px) */ int hx = 8, hy = top + 36 + 8; int hw = W - 16, hh = (int)g_fb_h - hy - 8; g_ide_host[0] = hx; g_ide_host[1] = hy; g_ide_host[2] = hw; g_ide_host[3] = hh; draw_dashed_rect(hx, hy, hw, hh, KS_BORDER); if (g_ide_attached) { if (g_input_forward_enabled) { /* VSCode Phase 6 状态机:启动中探测 scanout 上线 → 运行中 */ if (g_vscode_state == 1) { const struct linux_compat_scanout_info *si = ide_scanout_state(); if (si && si->enabled) { g_vscode_state = 2; g_launch_phase = 6; g_so_full_repaint = 1; /* 上线首帧全量重绘 */ /* P8.1: scanout 上线日志 + 耗时 */ slog("[launch] phase 6: SCANOUT ONLINE"); if (g_tsc_per_sec && g_launch_tsc) { u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec; slog_num("[launch] total launch time ms=", (int)ms); } { char sbuf[48]; kstrcpy(sbuf, "scanout: ", 48); u32dec(si->width, sbuf + kstrlen(sbuf)); kstrcat(sbuf, "x", 48); u32dec(si->height, sbuf + kstrlen(sbuf)); slog(sbuf); } } } if (g_vscode_state == 2) { /* 运行中:blit guest 合成帧 */ blit_scanout_to_ide(hx + 2, hy + 2, hw - 4, hh - 4); } else { /* P8.1: 启动中--显示分阶段状态 + 详细信息 */ du_fill_rect(&g_fb, hx + 2, hy + 2, hw - 4, hh - 4, 0xFF051828u); draw_centered(g_launch_status[0] ? g_launch_status : "STARTING VSCODE . . .", W / 2, hy + hh / 2 - 20, KS_TEXT_DIM, 0); /* 详细信息行(phase/exec_rc/pgrep/耗时) */ { char dbuf[80]; int dp = 0; kstrcpy(dbuf + dp, "PHASE:", 80); dp = kstrlen(dbuf); dp += u32dec((u32)g_launch_phase, dbuf + dp); if (g_launch_exec_rc != 0) { kstrcpy(dbuf + dp, " RC:", 80); dp = kstrlen(dbuf); dp += u32dec((u32)(g_launch_exec_rc < 0 ? (u32)(-g_launch_exec_rc) : (u32)g_launch_exec_rc), dbuf + dp); } if (g_launch_pgrep_rc >= 0) { kstrcpy(dbuf + dp, " PGREP:", 80); dp = kstrlen(dbuf); kstrcpy(dbuf + dp, g_launch_pgrep_rc == 0 ? "FOUND" : "MISSING", 80); dp = kstrlen(dbuf); } if (g_tsc_per_sec && g_launch_tsc) { u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec; kstrcpy(dbuf + dp, " T:", 80); dp = kstrlen(dbuf); dp += u32dec((u32)ms, dbuf + dp); kstrcpy(dbuf + dp, "ms", 80); } draw_centered(dbuf, W / 2, hy + hh / 2 + 4, KS_TEXT_DIM, 0); } } } else { editor_draw_to((editor_state *)ADDR_EDDASH, hx + 2, hy + 2, hw - 4, hh - 4); } } else { /* embed-empty(Kate:未启动 VSCode 提示) */ draw_centered("VSCODE NOT RUNNING . DOUBLE-CLICK OR + NEW IDE TO START", W / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0); } /* P7.9: 性能 profiling 叠加(IDE 页面左下角) */ if (g_ide_attached && g_input_forward_enabled) { char buf[80]; int p = 0; kstrcpy(buf + p, "FPS:", 80 - p); p = kstrlen(buf); p += u32dec((u32)g_prof_fps, buf + p); kstrcpy(buf + p, " BLT:", 80 - p); p = kstrlen(buf); p += u32dec((u32)g_prof_last_blit_us, buf + p); kstrcpy(buf + p, "us SKP:", 80 - p); p = kstrlen(buf); p += u32dec((u32)g_prof_blit_skip, buf + p); kstrcpy(buf + p, " FUL:", 80 - p); p = kstrlen(buf); p += u32dec((u32)g_prof_blit_full, buf + p); du_draw_string(&g_fb, buf, hx + 6, hy + hh - 16, KS_TEXT_DIM, 0, DU_ASCII_STEP); } } /* ---- 3·DESKTOP ---- */ static void draw_desktop_page(void) { /* 图标网格 */ draw_desktop_icons(); /* 窗口(当前工作区,从底到顶) */ for (int i = 0; i < g_win_count; i++) { desktop_window *w = &g_windows[i]; if (w->minimized || !w->visible) continue; if (w->ws != g_ws_cur) continue; draw_window_frame(w); if (w->app_id >= 0 && w->app_id < g_app_count && g_apps[w->app_id].on_draw && w->app_state) { app_ctx ctx; fill_app_ctx(&ctx, w); g_apps[w->app_id].on_draw(w->app_state, &ctx); } du_kate_scanlines(&g_fb, w->x + 1, w->y + KATE_TITLEBAR_H, w->w - 2, w->h - KATE_TITLEBAR_H - 1); } /* 任务栏(40px) */ int W = (int)g_fb_w; int tb_y = (int)g_fb_h - KATE_TASKBAR_H; du_fill_rect_gradient(&g_fb, 0, tb_y, W, KATE_TASKBAR_H, 0xFF0A2840u, KS_BG_PRIMARY); du_divider_h(&g_fb, 0, tb_y, W, KS_BORDER); int x = 8; int ty = tb_y + (KATE_TASKBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1; /* tb-item per window(当前工作区) */ for (int i = 0; i < g_win_count; i++) { desktop_window *w = &g_windows[i]; if (!w->visible || w->ws != g_ws_cur) { g_tb_rect[i][2] = 0; g_tb_close_rect[i][2] = 0; continue; } char t[16]; int tl = 0; while (w->title[tl] && tl < 11) { t[tl] = w->title[tl]; tl++; } if (w->title[tl]) { t[tl-1] = '.'; t[tl] = '.'; tl++; } t[tl] = 0; int bw = tl * (int)DU_ASCII_STEP + 34; if (bw > 150) bw = 150; int active = w->focused; int hover = g_mouse_x >= x && g_mouse_x < x + bw && g_mouse_y >= tb_y + 6 && g_mouse_y < tb_y + 34; if (active) du_fill_rect(&g_fb, x, tb_y + 6, bw, 28, KS_ACCENT_DIM); else if (hover) du_fill_rect(&g_fb, x, tb_y + 6, bw, 28, KS_ACCENT_DIM); du_rect_outline(&g_fb, x, tb_y + 6, bw, 28, active ? KS_ACCENT : KS_BORDER, 1); du_draw_string(&g_fb, t, x + 6, ty, active ? KS_ACCENT : KS_TEXT_PRIMARY, 0, DU_ASCII_STEP); du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); g_tb_rect[i][0] = x; g_tb_rect[i][1] = tb_y + 6; g_tb_rect[i][2] = bw; g_tb_rect[i][3] = 28; g_tb_close_rect[i][0] = x + bw - 20; g_tb_close_rect[i][1] = tb_y + 6; g_tb_close_rect[i][2] = 20; g_tb_close_rect[i][3] = 28; x += bw + 6; if (x > W - 260) break; } /* Winux-Kate 任务栏右组(margin-left:auto): * [+ ADOPT 虚框] [tb-credit 署名] [EXIT 危险色] */ const char *credit = "DEAICUP STUDIO"; const char *quit = "EXIT"; const char *adopt = "+ ADOPT"; int qw = 60; int qx = W - qw - 8; int credit_w = kstrlen(credit) * (int)DU_ASCII_STEP; int cx = qx - 8 - credit_w; int aw = kstrlen(adopt) * (int)DU_ASCII_STEP + 16; int ax = cx - 8 - aw; /* tb-adopt:虚线 accent 边框 */ g_tb_adopt_rect[0] = ax; g_tb_adopt_rect[1] = tb_y + 6; g_tb_adopt_rect[2] = aw; g_tb_adopt_rect[3] = 28; { int hover = g_mouse_x >= ax && g_mouse_x < ax + aw && g_mouse_y >= tb_y + 6 && g_mouse_y < tb_y + 34; if (hover) du_fill_rect(&g_fb, ax, tb_y + 6, aw, 28, KS_ACCENT_DIM); draw_dashed_rect(ax, tb_y + 6, aw, 28, KS_ACCENT); du_draw_string(&g_fb, adopt, ax + 8, ty, KS_ACCENT, 0, DU_ASCII_STEP); } /* tb-credit */ du_draw_string(&g_fb, credit, cx, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP); /* tb-quit */ g_tb_quit_rect[0] = qx; g_tb_quit_rect[1] = tb_y + 6; g_tb_quit_rect[2] = qw; g_tb_quit_rect[3] = 28; { int hover = g_mouse_x >= qx && g_mouse_x < qx + qw && g_mouse_y >= tb_y + 6 && g_mouse_y < tb_y + 34; if (hover) du_fill_rect(&g_fb, qx, tb_y + 6, qw, 28, 0x30FF4D6Du); du_rect_outline(&g_fb, qx, tb_y + 6, qw, 28, KS_DANGER, 1); du_draw_string(&g_fb, quit, qx + (qw - 4 * (int)DU_ASCII_STEP) / 2, ty, KS_DANGER, 0, DU_ASCII_STEP); } } /* ---- 5+/CUSTOM ---- */ static int g_cp_new_rect[4]; /* 自定义页 "+ NEW INSTANCE" 按钮 */ static void draw_custom_page(int cp_idx) { int W = (int)g_fb_w; int top = KATE_TOPBAR_H; cpage *cp = &g_cpages[cp_idx]; /* ide-tabs 栏(36px):dot + 页名 + "+ NEW INSTANCE" + ide-hint(Kate 结构) */ du_fill_rect(&g_fb, 0, top, W, 36, 0xFF051828u); du_divider_h(&g_fb, 0, top + 36, W, KS_BORDER); int ty = top + (36 - (int)DU_ASCII_LINE_H) / 2 + 1; /* .dot + 页名 */ du_fill_rect(&g_fb, 12, top + 14, 8, 8, KS_ACCENT2); du_draw_string(&g_fb, cp->name, 28, ty, KS_ACCENT, 0, DU_ASCII_STEP); int x = 28 + kstrlen(cp->name) * (int)DU_ASCII_STEP + 12; /* "+ NEW INSTANCE" 按钮(对应 Kate custom launch_custom_new) */ { const char *nb = "+ NEW INSTANCE"; int bw = kstrlen(nb) * (int)DU_ASCII_STEP + 20; g_cp_new_rect[0] = x; g_cp_new_rect[1] = top + 7; g_cp_new_rect[2] = bw; g_cp_new_rect[3] = 22; int hover = g_mouse_x >= x && g_mouse_x < x + bw && g_mouse_y >= top + 7 && g_mouse_y < top + 29; if (hover) du_fill_rect(&g_fb, x, top + 7, bw, 22, KS_ACCENT_DIM); du_rect_outline(&g_fb, x, top + 7, bw, 22, KS_BORDER, 1); du_draw_string(&g_fb, nb, x + 10, ty, KS_ACCENT, 0, DU_ASCII_STEP); x += bw + 8; } /* ide-hint */ du_draw_string(&g_fb, "DBL-CLICK HOST LAUNCH . EXTERNAL ELF FULLSCREEN", x + 8, ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP); /* page-credit */ du_draw_string(&g_fb, "DEAICUP STUDIO", W - 12 - 14 * (int)DU_ASCII_STEP, ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP); /* ide-host(虚线,margin 8px) */ int hx = 8, hy = top + 36 + 8; int hw = W - 16, hh = (int)g_fb_h - hy - 8; g_cp_host[0] = hx; g_cp_host[1] = hy; g_cp_host[2] = hw; g_cp_host[3] = hh; draw_dashed_rect(hx, hy, hw, hh, KS_BORDER); /* embed-empty(Kate:正在启动提示) */ char msg[40]; kstrcpy(msg, "DOUBLE-CLICK TO LAUNCH ", 40); kstrcat(msg, cp->name, 40); draw_centered(msg, hx + hw / 2, hy + hh / 2 - 12, KS_TEXT_DIM, 0); char sub[40]; kstrcpy(sub, "ELF: ", 40); { char disp[13]; f32_name_from_83(cp->elf11, disp); kstrcat(sub, disp, 40); } draw_centered(sub, hx + hw / 2, hy + hh / 2 + 12, KS_ACCENT, 0); } /* ============================================================ * 任务视图 overlay * ============================================================ */ static void draw_taskview(void) { int W = (int)g_fb_w; /* 背景暗化(全屏 blend,模态 overlay 下文字可读性优先) */ for (u64 ry = 0; ry < g_fb_h; ry++) { u32 *line = (u32 *)((u8 *)g_fb.fb + ry * g_fb_pitch); for (u64 rx = 0; rx < g_fb_w; rx++) { line[rx] = du_blend(line[rx], KS_BG_PRIMARY, 220); } } /* tv-header */ du_draw_string(&g_fb, "TASK VIEW", 60, 44, KS_ACCENT, 0, DU_ASCII_STEP + 4); du_draw_string(&g_fb, "CLICK TO SWITCH . ESC CLOSE . x CLOSE WS", 60 + 9 * ((int)DU_ASCII_STEP + 4) + 20, 48, KS_TEXT_DIM, 0, DU_ASCII_STEP); du_divider_h(&g_fb, 60, 76, W - 120, KS_BORDER); /* tv-grid 卡片 */ int cw = 260, ch = 170, gap = 20; int cols = (W - 120 + gap) / (cw + gap); if (cols < 1) cols = 1; for (int i = 0; i <= g_ws_count; i++) { int row = i / cols, col = i % cols; int x = 60 + col * (cw + gap); int y = 96 + row * (ch + gap); g_tv_card[i][0] = x; g_tv_card[i][1] = y; g_tv_card[i][2] = cw; g_tv_card[i][3] = ch; if (i == g_ws_count) { /* 新建工作区卡片(虚线 +) */ draw_dashed_rect(x, y, cw, ch, KS_ACCENT2); du_draw_string(&g_fb, "+", x + cw / 2 - 6, y + ch / 2 - 24, KS_ACCENT2, 0, DU_ASCII_STEP); draw_centered("NEW WORKSPACE", x + cw / 2, y + ch / 2 + 8, KS_ACCENT2, 0); continue; } int active = (i == g_ws_cur); int hover = g_mouse_x >= x && g_mouse_x < x + cw && g_mouse_y >= y && g_mouse_y < y + ch; du_fill_rect(&g_fb, x, y, cw, ch, KS_BG_SECONDARY); du_rect_outline(&g_fb, x, y, cw, ch, active ? KS_ACCENT2 : (hover ? KS_ACCENT : KS_BORDER), 1); if (active) du_kate_glow_border(&g_fb, x, y, cw, ch, KS_ACCENT2); /* tv-card-top */ du_draw_string(&g_fb, g_ws[i].name, x + 12, y + 10, KS_ACCENT, 0, DU_ASCII_STEP); du_divider_h(&g_fb, x, y + 34, cw, KS_BORDER); /* × close */ g_tv_close[i][0] = x + cw - 26; g_tv_close[i][1] = y + 4; g_tv_close[i][2] = 22; g_tv_close[i][3] = 26; du_draw_string(&g_fb, "x", x + cw - 20, y + 8, KS_TEXT_DIM, 0, DU_ASCII_STEP); /* tv-stat */ char nbuf[4]; int wc = ws_win_count(i); nbuf[0] = '0' + (char)(wc % 10); nbuf[1] = 0; draw_centered(nbuf, x + cw / 4, y + 70, KS_ACCENT2, 0); draw_centered("WINS", x + cw / 4, y + 96, KS_TEXT_DIM, 0); char pbuf[6]; kstrcpy(pbuf, "PG", 6); pbuf[2] = '0' + (char)(g_ws[i].page % 10); pbuf[3] = 0; draw_centered(pbuf, x + cw * 3 / 4, y + 70, KS_ACCENT2, 0); draw_centered("PAGE", x + cw * 3 / 4, y + 96, KS_TEXT_DIM, 0); /* badge CURRENT */ if (active) { int bx = x + cw - 84, by = y + 6; du_fill_rect(&g_fb, bx, by, 56, 14, KS_ACCENT2); du_draw_string(&g_fb, "CURRENT", bx + 2, by - 1, KS_TEXT_INVERT, 0, DU_ASCII_STEP); } } } /* ============================================================ * 右键菜单(移到工作区) * ============================================================ */ /* 构建"移到工作区"列表(排除当前工作区,与 Winux-Kate 一致),返回行数 */ static int ctx_list_build(int *map) { int n = 0; for (int i = 0; i < g_ws_count; i++) { if (i == g_ws_cur) continue; map[n++] = i; } return n; } static void draw_ctx_menu(void) { if (!g_ctx_open) return; int lmap[MAX_WS]; int ln = ctx_list_build(lmap); int rows = (ln ? ln : 1) + 1; /* 列表行(或空态行) + 新建行 */ int mw = 200; int mh = 24 + rows * 22 + 8; int x = g_ctx_x, y = g_ctx_y; if (x + mw > (int)g_fb_w) x = (int)g_fb_w - mw - 4; if (y + mh > (int)g_fb_h) y = (int)g_fb_h - mh - 4; g_ctx_rect[0] = x; g_ctx_rect[1] = y; g_ctx_rect[2] = mw; g_ctx_rect[3] = mh; du_fill_rect(&g_fb, x, y, mw, mh, 0xFF050A18u); du_rect_outline(&g_fb, x, y, mw, mh, KS_ACCENT, 1); du_kate_glow_border(&g_fb, x, y, mw, mh, KS_ACCENT); du_draw_string(&g_fb, "MOVE TO WORKSPACE", x + 12, y + 5, KS_ACCENT, 0, DU_ASCII_STEP); du_divider_h(&g_fb, x, y + 22, mw, KS_BORDER); int iy = y + 26; int row = 0; if (ln == 0) { /* 空态(不可点击) */ du_draw_string(&g_fb, "NO OTHER WORKSPACE", x + 12, iy + 2, KS_TEXT_DIM, 0, DU_ASCII_STEP); g_ctx_item[0][2] = 0; /* 禁用命中 */ g_ctx_item_ws[0] = -2; iy += 22; row = 1; } for (int r = 0; r < ln; r++) { int wi = lmap[r]; int hover = g_mouse_x >= x && g_mouse_x < x + mw && g_mouse_y >= iy && g_mouse_y < iy + 22; if (hover) du_fill_rect(&g_fb, x + 1, iy, mw - 2, 22, KS_ACCENT_DIM); du_draw_string(&g_fb, g_ws[wi].name, x + 12, iy + 2, hover ? KS_ACCENT : KS_TEXT_PRIMARY, 0, DU_ASCII_STEP); char cn[4]; cn[0] = '0' + (char)(ws_win_count(wi) % 10); cn[1] = 0; du_draw_string(&g_fb, cn, x + mw - 24, iy + 2, KS_TEXT_DIM, 0, DU_ASCII_STEP); g_ctx_item[row][0] = x; g_ctx_item[row][1] = iy; g_ctx_item[row][2] = mw; g_ctx_item[row][3] = 22; g_ctx_item_ws[row] = wi; iy += 22; row++; } /* + NEW WORKSPACE */ du_divider_h(&g_fb, x, iy, mw, KS_BORDER); int hover = g_mouse_x >= x && g_mouse_x < x + mw && g_mouse_y >= iy && g_mouse_y < iy + 22; if (hover) du_fill_rect(&g_fb, x + 1, iy, mw - 2, 22, KS_ACCENT_DIM); du_draw_string(&g_fb, "+ NEW WORKSPACE", x + 12, iy + 2, KS_ACCENT2, 0, DU_ASCII_STEP); g_ctx_item[row][0] = x; g_ctx_item[row][1] = iy; g_ctx_item[row][2] = mw; g_ctx_item[row][3] = 22; g_ctx_item_ws[row] = -1; /* 清除残余行命中 */ for (int r = row + 1; r <= MAX_WS; r++) g_ctx_item[r][2] = 0; } /* ============================================================ * 全屏重绘 * ============================================================ */ /* 全局亮度(BRIGHT 滑块真实调暗) * 性能优化:仅在亮度变化时处理,避免每帧全屏遍历 */ static void apply_brightness(void) { /* 快速退出:亮度100%无需调整 */ if (g_bright >= 100) { g_bright_applied = 100; return; } /* 快速退出:亮度未变化,跳过处理 */ if (g_bright == g_bright_applied) return; /* 记录当前应用的亮度值 */ g_bright_applied = g_bright; u32 f = (u32)g_bright; for (u64 y = 0; y < g_fb_h; y++) { u32 *line = (u32 *)((u8 *)g_fb.fb + y * g_fb_pitch); for (u64 x = 0; x < g_fb_w; x++) { u32 c = line[x]; u32 b = ((c >> 16) & 0xFF) * f / 100; u32 g = ((c >> 8) & 0xFF) * f / 100; u32 r = (c & 0xFF) * f / 100; line[x] = 0xFF000000u | (b << 16) | (g << 8) | r; } } } static void redraw_all(void) { /* 深空黑渐变背景 */ du_fill_bg_gradient(&g_fb, KS_BG_PRIMARY, KS_BG_TERTIARY); /* 顶栏 */ draw_topbar(); /* 页面 */ if (g_page == 1) draw_dashboard(); else if (g_page == 2) draw_ide(); else if (g_page == 3) draw_desktop_page(); else { int idx = -1; for (int i = 0; i < g_cpage_count; i++) if (g_cpages[i].id == g_page) { idx = i; break; } if (idx >= 0) draw_custom_page(idx); else g_page = 1; } /* 任务视图 overlay */ if (g_taskview) draw_taskview(); /* 右键菜单(最顶) */ if (g_ctx_open) draw_ctx_menu(); /* 亮度调整 */ apply_brightness(); /* 鼠标光标 */ cursor_save_bg(g_mouse_x, g_mouse_y); cursor_draw(g_mouse_x, g_mouse_y); } /* ============================================================ * 工作区 / 页面管理 * ============================================================ */ static void ws_switch(int id) { if (id < 0 || id >= g_ws_count || id == g_ws_cur) { g_taskview = 0; return; } g_ws[g_ws_cur].page = g_page; /* 记住离开时的页面 */ g_ws_cur = id; g_page = g_ws[id].page; g_taskview = 0; } static void ws_create(void) { if (g_ws_count >= MAX_WS) return; char *nm = g_ws[g_ws_count].name; nm[0] = 'W'; nm[1] = 'S'; nm[2] = '-'; nm[3] = '0' + (char)(g_ws_count + 1); nm[4] = 0; g_ws[g_ws_count].page = 1; g_ws_count++; } /* 关闭工作区:窗口释放到关闭后的当前工作区(Kate release 语义) */ static void ws_close(int id) { if (g_ws_count <= 1 || id < 0 || id >= g_ws_count) return; int cur_after = g_ws_cur; if (g_ws_cur == id) cur_after = (id > 0) ? id - 1 : 0; else if (g_ws_cur > id) cur_after = g_ws_cur - 1; for (int i = 0; i < g_win_count; i++) { if (g_windows[i].ws == id) g_windows[i].ws = cur_after; else if (g_windows[i].ws > id) g_windows[i].ws--; } for (int i = id; i < g_ws_count - 1; i++) g_ws[i] = g_ws[i + 1]; g_ws_count--; g_ws_cur = cur_after; g_taskview = 0; } static void page_switch(int p) { if (p < 1 || p > 3 + g_cpage_count) return; g_page = p; } /* 自定义页:为尚未挂载的外部 .elf 应用新建页面 */ static void cpage_add(void) { if (g_cpage_count >= MAX_CPAGES) return; for (int a = 0; a < g_app_count; a++) { if (!g_apps[a].elf_name) continue; int dup = 0; for (int i = 0; i < g_cpage_count; i++) if (f32_neq11(g_cpages[i].elf11, g_apps[a].elf_name)) { dup = 1; break; } if (dup) continue; cpage *cp = &g_cpages[g_cpage_count]; int n = kstrlen(g_apps[a].display_name); if (n > 11) n = 11; for (int i = 0; i < n; i++) cp->name[i] = g_apps[a].display_name[i]; cp->name[n] = 0; for (int i = 0; i < 11; i++) cp->elf11[i] = g_apps[a].elf_name[i]; cp->id = 4 + g_cpage_count; g_cpage_count++; g_page = cp->id; return; } } static void cpage_remove(int idx) { if (idx < 0 || idx >= g_cpage_count) return; int removed_page = g_cpages[idx].id; for (int i = idx; i < g_cpage_count - 1; i++) g_cpages[i] = g_cpages[i + 1]; g_cpage_count--; for (int i = 0; i < g_cpage_count; i++) g_cpages[i].id = 4 + i; if (g_page == removed_page) g_page = 1; else if (g_page > removed_page) g_page--; } /* ============================================================ * 事件处理 * ============================================================ */ static int rect_hit(const int r[4], int mx, int my) { return r[2] > 0 && mx >= r[0] && mx < r[0] + r[2] && my >= r[1] && my < r[1] + r[3]; } static void slider_set(const int rect[4], int *val, int mx) { int v = (mx - rect[0]) * 100 / rect[2]; if (v < 0) v = 0; if (v > 100) v = 100; *val = v; } /* 双击判定(TSC 半秒 + 8px 容差),不更新状态 */ static int dbl_check(int mx, int my) { if (!g_tsc_per_sec) return 0; u64 now = rdtsc(); int dx = mx - g_last_click_x, dy = my - g_last_click_y; return (now - g_last_click_tsc) < g_tsc_per_sec / 2 && dx > -8 && dx < 8 && dy > -8 && dy < 8; } /* ---- 1·DASHBOARD 点击 ---- */ static int dash_click(int mx, int my, int dbl) { editor_state *ed = (editor_state *)ADDR_EDDASH; /* SAVE 按钮 */ if (ed->has_file && rect_hit(g_save_rect, mx, my)) { editor_save(); return 1; } /* 面板聚焦 */ for (int i = 0; i < 4; i++) { if (!rect_hit(g_dash_rect[i], mx, my)) continue; g_dash_focus = i; if (i == 3) { /* FILES 行命中 / 双击打开 */ int fx = g_dash_rect[3][0] + 2; int fy = g_dash_rect[3][1] + KATE_PANEL_HEADER_H + 2; int fw = g_dash_rect[3][2] - 4; int fh = g_dash_rect[3][3] - KATE_PANEL_HEADER_H - 4; if (mx >= fx && mx < fx + fw && my >= fy + 24) { int max_rows = (fh - 24) / 22; int row = (my - fy - 24) / 22; if (row >= 0 && row < max_rows && row < g_file_count) { if (dbl && row == g_file_sel) files_open(row); else g_file_sel = row; } } } return 1; } /* 状态栏滑块 */ if (rect_hit(g_vol_rect, mx, my)) { g_drag_slider = 1; slider_set(g_vol_rect, &g_vol, mx); return 1; } if (rect_hit(g_bri_rect, mx, my)) { g_drag_slider = 2; slider_set(g_bri_rect, &g_bright, mx); return 1; } return 0; } /* ---- 2·IDE 点击 ---- */ static int ide_click(int mx, int my, int dbl) { if (g_ide_attached && rect_hit(g_ide_tabx, mx, my)) { g_ide_attached = 0; /* VSCode Phase 2: detach 时停止向 guest 转发输入 */ g_input_forward_enabled = 0; ide_attach_invalidate(); /* P7.3: detach 后下次 attach 全量重绘 */ slog("ide detached, input forwarding off"); return 1; } if (!g_ide_attached && rect_hit(g_ide_new_rect, mx, my)) { g_ide_attached = 1; ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */ /* attach 时仅当 Linux guest 服务可用才开启转发;否则降级为本地 editor */ g_input_forward_enabled = (g_lxc_svc != 0); slog(g_input_forward_enabled ? "ide attached, forwarding to guest" : "ide attached, lxc unavailable (local editor)"); /* VSCode Phase 6: 共享启动路径(X server + VSCode) */ if (g_input_forward_enabled) vscode_launch(); return 1; } if (!g_ide_attached && dbl && rect_hit(g_ide_host, mx, my)) { g_ide_attached = 1; ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */ g_input_forward_enabled = (g_lxc_svc != 0); slog(g_input_forward_enabled ? "ide attached (dbl), forwarding to guest" : "ide attached (dbl), lxc unavailable (local editor)"); /* VSCode Phase 6: 共享启动路径(X server + VSCode) */ if (g_input_forward_enabled) vscode_launch(); return 1; } return 0; } /* ---- 4·DESKTOP 点击 ---- */ static int desktop_click(int mx, int my, int dbl) { int tb_y = (int)g_fb_h - KATE_TASKBAR_H; /* 任务栏 */ if (my >= tb_y) { if (rect_hit(g_tb_quit_rect, mx, my)) { g_quit = 1; return 1; } if (rect_hit(g_tb_adopt_rect, mx, my)) { /* 收纳窗口:全部工作区窗口收归当前工作区并还原最小化 */ for (int i = 0; i < g_win_count; i++) { g_windows[i].ws = g_ws_cur; g_windows[i].minimized = 0; } return 1; } for (int i = 0; i < g_win_count; i++) { if (!g_tb_rect[i][2] || !rect_hit(g_tb_rect[i], mx, my)) continue; if (rect_hit(g_tb_close_rect[i], mx, my)) { win_close_request(&g_windows[i]); } else { win_focus(&g_windows[i]); } return 1; } return 0; } /* 窗口 */ desktop_window *w = win_find_at(mx, my); if (w) { int wid = w->id; win_focus(w); w = win_find(wid); /* focus 重排后重新定位 */ if (!w) return 1; if (win_hit_close_btn(w, mx, my)) { win_close_request(w); return 1; } if (win_hit_titlebar(w, mx, my)) { g_dragging = 1; g_drag_win = w->id; g_drag_off_x = mx - w->x; g_drag_off_y = my - w->y; return 1; } /* 客户区事件 → 应用 */ if (w->app_id >= 0 && w->app_id < g_app_count && g_apps[w->app_id].on_event && w->app_state) { desktop_event dev; dev.type = EV_MOUSE_DOWN; dev.mx = mx; dev.my = my; dev.button = 0; dev.scancode = 0; dev.shift = g_shift; dev.ctrl = g_ctrl; dev.alt = g_alt; app_ctx actx; fill_app_ctx(&actx, w); g_apps[w->app_id].on_event(w->app_state, &actx, &dev); } return 1; } /* 图标(单击选中 / 双击启动) */ for (int i = 0; i < g_icon_count; i++) { int ix = g_icons[i].x, iy = g_icons[i].y; if (mx >= ix && mx < ix + ICON_W && my >= iy && my < iy + ICON_H) { if (dbl && g_icons[i].selected) { launch_app(g_icons[i].app_id); } else { for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0; g_icons[i].selected = 1; } return 1; } } /* 空白:取消选择 */ for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0; return 1; } /* ---- 左键按下 ---- */ static int on_left_press(void) { int mx = g_mouse_x, my = g_mouse_y; int dbl = dbl_check(mx, my); g_last_click_tsc = rdtsc(); g_last_click_x = mx; g_last_click_y = my; /* 任务视图 overlay(模态) */ if (g_taskview) { for (int i = 0; i <= g_ws_count; i++) { if (!rect_hit(g_tv_card[i], mx, my)) continue; if (i == g_ws_count) ws_create(); else if (rect_hit(g_tv_close[i], mx, my)) ws_close(i); else ws_switch(i); return 1; } g_taskview = 0; /* 背景点击关闭 */ return 1; } /* 右键菜单(模态) */ if (g_ctx_open) { if (rect_hit(g_ctx_rect, mx, my)) { for (int i = 0; i <= MAX_WS; i++) { if (!rect_hit(g_ctx_item[i], mx, my)) continue; int wi = g_ctx_item_ws[i]; if (wi == -1) { /* + NEW WORKSPACE:新建并迁移 */ ws_create(); desktop_window *w = win_find(g_ctx_win); if (w && g_ws_count > 0) w->ws = g_ws_count - 1; } else if (wi >= 0) { desktop_window *w = win_find(g_ctx_win); if (w) w->ws = wi; } break; } } g_ctx_open = 0; return 1; } /* 顶栏 */ if (my < KATE_TOPBAR_H) { if (rect_hit(g_tvbtn_rect, mx, my)) { g_taskview = 1; return 1; } for (int i = 0; i < g_ws_count; i++) if (rect_hit(g_ws_rect[i], mx, my)) { ws_switch(i); return 1; } if (rect_hit(g_wsadd_rect, mx, my)) { ws_create(); return 1; } for (int i = 0; i < 3 + g_cpage_count; i++) { if (i >= 3 && rect_hit(g_pg_rm_rect[i], mx, my)) { cpage_remove(i - 3); return 1; } if (rect_hit(g_pg_rect[i], mx, my)) { page_switch(i + 1); return 1; } } if (rect_hit(g_pgadd_rect, mx, my)) { cpage_add(); return 1; } return 0; } /* 页面内容 */ if (g_page == 1) return dash_click(mx, my, dbl); if (g_page == 2) return ide_click(mx, my, dbl); if (g_page == 3) return desktop_click(mx, my, dbl); if (g_page >= 4) { int launch = 0; if (rect_hit(g_cp_new_rect, mx, my)) launch = 1; if (dbl && rect_hit(g_cp_host, mx, my)) launch = 1; if (launch) { for (int i = 0; i < g_cpage_count; i++) { if (g_cpages[i].id != g_page) continue; launch_external_elf(g_cpages[i].elf11); break; } return 1; } return 0; } return 0; } /* ---- 右键按下:窗口/任务栏项 → 移到工作区菜单 ---- */ static int on_right_press(void) { int mx = g_mouse_x, my = g_mouse_y; if (g_taskview) { g_taskview = 0; return 1; } if (g_ctx_open) { g_ctx_open = 0; return 1; } if (g_page != 3) return 0; int tb_y = (int)g_fb_h - KATE_TASKBAR_H; if (my >= tb_y) { for (int i = 0; i < g_win_count; i++) { if (!g_tb_rect[i][2] || !rect_hit(g_tb_rect[i], mx, my)) continue; int lmap[MAX_WS]; int ln = ctx_list_build(lmap); int mh = 24 + ((ln ? ln : 1) + 1) * 22 + 8; g_ctx_win = g_windows[i].id; g_ctx_x = mx; g_ctx_y = my - mh; /* 菜单向上展开 */ if (g_ctx_y < KATE_TOPBAR_H) g_ctx_y = my; g_ctx_open = 1; return 1; } return 0; } desktop_window *w = win_find_at(mx, my); if (w) { g_ctx_win = w->id; g_ctx_x = mx; g_ctx_y = my; g_ctx_open = 1; return 1; } return 0; } /* ---- 键盘 ---- */ static int handle_key(u8 sc) { /* VSCode Phase 2: IDE attached 且在 IDE 页时,把按键序列转发到 Linux guest。 * 0xE0 前缀字节本身不转发(无对应 keycode),仅靠 g_e0 标志让下一字节选取扩展键变体。 * 转发在修饰键跟踪与页面路由之前完成,保证 VSCode 收到完整 press/release * 序列(含 Shift/Ctrl/Alt/方向键)。宿主修饰键跟踪继续运行,Ctrl+Tab 切页、 * Ctrl+S 等热键仍可在宿主侧生效,便于用户随时切离 IDE 页。 */ if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_keyboard && sc != 0xE0 && g_page == 2) { u8 base = (u8)(sc & 0x7F); u16 code = scancode_to_linux_keycode(base, g_e0); if (code) { u32 value = (sc & 0x80) ? 0 : 1; /* 0x80 位 = release */ g_lxc_svc->input_forward_keyboard(code, value); } } if (sc == 0xE0) { g_e0 = 1; return 0; } if (sc == 0x2A || sc == 0x36) { g_shift = 1; return 0; } if (sc == 0xAA || sc == 0xB6) { g_shift = 0; return 0; } if (sc == 0x1D) { g_ctrl = 1; g_e0 = 0; return 0; } if (sc == 0x9D) { g_ctrl = 0; return 0; } if (sc == 0x38) { g_alt = 1; g_e0 = 0; return 0; } if (sc == 0xB8) { g_alt = 0; return 0; } if (sc & 0x80) { g_e0 = 0; return 0; } /* 其他释放码忽略 */ /* Esc:关闭 overlay */ if (sc == 0x01) { if (g_taskview) { g_taskview = 0; return 1; } if (g_ctx_open) { g_ctx_open = 0; return 1; } return 0; } if (g_taskview || g_ctx_open) return 0; /* 模态期间吞掉按键 */ /* Ctrl+Tab 切页 / Ctrl+Shift+Tab 上下文菜单(DESKTOP 页) */ if (g_ctrl && sc == 0x0F) { if (g_shift) { /* Ctrl+Shift+Tab:对当前聚焦窗口打开"移到工作区"菜单 */ if (g_page == 3 && win_find(g_focused_win)) { g_ctx_win = g_focused_win; g_ctx_x = g_mouse_x; g_ctx_y = g_mouse_y; g_ctx_open = 1; return 1; } return 0; } int maxp = 3 + g_cpage_count; int p = g_page + 1; if (p > maxp) p = 1; page_switch(p); return 1; } if (g_ctrl && sc == 0x1F) { editor_save(); return 1; } /* 页面路由 */ if (g_page == 1) { if (g_dash_focus <= 1) { bash_key((bash_state *)(g_dash_focus == 0 ? ADDR_TERM1 : ADDR_TERM2), sc, g_shift); return 1; } if (g_dash_focus == 2) { editor_key((editor_state *)ADDR_EDDASH, sc, g_shift); return 1; } /* FILES 面板导航 */ if (sc == 0x48) { if (g_file_sel > 0) g_file_sel--; return 1; } if (sc == 0x50) { if (g_file_sel < g_file_count - 1) g_file_sel++; return 1; } if (sc == 0x1C) { files_open(g_file_sel); return 1; } return 0; } if (g_page == 2) { if (g_ide_attached) { if (g_input_forward_enabled) { /* VSCode Phase 2: 键盘已转发到 guest 中的 VSCode, * 本地 editor 不再重复处理,避免双输入。 */ return 1; } editor_key((editor_state *)ADDR_EDDASH, sc, g_shift); return 1; } return 0; } if (g_page == 3) { desktop_window *fw = win_find(g_focused_win); if (fw && fw->ws == g_ws_cur && fw->app_id >= 0 && fw->app_id < g_app_count && g_apps[fw->app_id].on_event && fw->app_state) { desktop_event dev; dev.type = EV_KEY_DOWN; dev.mx = g_mouse_x; dev.my = g_mouse_y; dev.button = 0; dev.scancode = sc; dev.shift = g_shift; dev.ctrl = g_ctrl; dev.alt = g_alt; app_ctx actx; fill_app_ctx(&actx, fw); g_apps[fw->app_id].on_event(fw->app_state, &actx, &dev); return 1; } } return 0; } /* ============================================================ * P5d: PE 窗口运行时(run_windowed 桌面嵌入) * * 协作模型(契约见 utsm/pe.h pe_window_host): * launch_pe_app 创建受管窗口并把客户区大小的内存 surface 交给 shim, * 随后阻塞在 pe_service.run_windowed。PE 消息空转(GetMessage/ * PeekMessage 无消息)时 shim 回调 pe_pump_cb,由它驱动 desktop 一帧: * 采集 PS/2 输入(PE 窗口聚焦时经 inject_* 注入,否则走桌面正常路径)、 * redraw_all + flip。关闭 = inject WM_CLOSE(win_close_request), * PE 自愿退出后 run_windowed 返回,launch_pe_app 收尾销毁窗口。 * * 注意:pump 运行在 PE 的 1MB 大栈上,desktop 帧代码栈用量远小于此; * pump 内禁止再次调用 run/run_windowed(launch_pe_app 以 g_pe_running 拒绝)。 * ============================================================ */ /* PE 窗口 on_draw:把 shim 渲染的 surface blit 到客户区(32bpp 行拷贝) */ static void pe_win_on_draw(void *state, app_ctx *ctx) { (void)state; if (!g_pe_surf) return; int w = (g_pe_surf_w < ctx->client_w) ? g_pe_surf_w : ctx->client_w; int h = (g_pe_surf_h < ctx->client_h) ? g_pe_surf_h : ctx->client_h; if (w <= 0 || h <= 0) return; u8 *dst0 = (u8 *)g_fb.fb + (u64)ctx->client_y * g_fb_pitch + (u64)ctx->client_x * 4; u32 sp = (u32)g_pe_surf_w * 4; for (int r = 0; r < h; r++) { u32 *d = (u32 *)(dst0 + (u64)r * g_fb_pitch); const u32 *s = (const u32 *)(g_pe_surf + (u64)r * sp); for (int c = 0; c < w; c++) d[c] = s[c] | 0xFF000000u; } } /* PE 消息空转让出点:驱动 desktop 一帧。返回 0 继续,非 0 请求 PE 退出。 */ static int pe_pump_cb(void *ud) { int win_id = (int)(u64)ud; int moved = 0; while (ps2_mouse_poll()) moved = 1; desktop_window *pw = win_find(win_id); if (!pw) return 1; /* 窗口已销毁:请求 PE 退出 */ int pe_focused = (g_page == 3 && pw->focused && pw->ws == g_ws_cur && !g_taskview && !g_ctx_open); /* 鼠标注入:PE 窗口聚焦且光标在客户区内 → surface 局部坐标 + 按钮位 */ int cx0 = pw->x + BORDER_W, cy0 = pw->y + KATE_TITLEBAR_H; int ccw = pw->w - 2 * BORDER_W, cch = pw->h - KATE_TITLEBAR_H - BORDER_W; if (pe_focused && g_pe_svc->inject_pointer && g_mouse_x >= cx0 && g_mouse_x < cx0 + ccw && g_mouse_y >= cy0 && g_mouse_y < cy0 + cch) { g_pe_svc->inject_pointer(g_mouse_x - cx0, g_mouse_y - cy0, (u32)(g_mouse_btn & 3)); } /* 点击/释放走桌面正常路径(聚焦、标题栏拖拽、关闭按钮→win_close_request、 * 其他窗口/图标)。双击其他 PE 图标会被 launch_pe_app 的 g_pe_running 拒绝。 */ if (g_left_pressed) { g_left_pressed = 0; on_left_press(); } if (g_right_pressed) { g_right_pressed = 0; on_right_press(); } if (g_left_released) { g_left_released = 0; if (g_dragging || g_drag_slider) { g_dragging = 0; g_drag_win = -1; g_drag_slider = 0; } } pw = win_find(win_id); /* 点击可能重排窗口数组 */ if (!pw) return 1; pe_focused = (g_page == 3 && pw->focused && pw->ws == g_ws_cur && !g_taskview && !g_ctx_open); /* 窗口/滑块拖拽(与主循环相同的钳位) */ if (moved && g_dragging && g_drag_win == win_id) { pw->x = g_mouse_x - g_drag_off_x; pw->y = g_mouse_y - g_drag_off_y; if (pw->y < KATE_TOPBAR_H) pw->y = KATE_TOPBAR_H; if (pw->x < 0 - pw->w + 40) pw->x = 0 - pw->w + 40; if (pw->x > (int)g_fb_w - 40) pw->x = (int)g_fb_w - 40; if (pw->y > (int)g_fb_h - KATE_TASKBAR_H - 20) pw->y = (int)g_fb_h - KATE_TASKBAR_H - 20; } if (moved && g_drag_slider == 1) slider_set(g_vol_rect, &g_vol, g_mouse_x); if (moved && g_drag_slider == 2) slider_set(g_bri_rect, &g_bright, g_mouse_x); /* 键盘:PE 聚焦时注入(shim 内部合成 KEYUP/CHAR/修饰键), * Ctrl+Tab 保留给宿主切页;否则走桌面正常路径。 */ for (;;) { u8 st = inb(0x64); if (!(st & 1) || (st & 0x20)) break; u8 sc = inb(0x60); if (pe_focused && g_pe_svc->inject_scancode) { if (sc == 0xE0) { g_e0 = 1; continue; } /* 宿主侧同步跟踪修饰键(Ctrl+Tab 切页判定用) */ if (sc == 0x2A || sc == 0x36) g_shift = 1; if (sc == 0xAA || sc == 0xB6) g_shift = 0; if (sc == 0x1D) g_ctrl = 1; if (sc == 0x9D) g_ctrl = 0; if (g_ctrl && sc == 0x0F) { /* 切页前补修饰键释放,防止 PE 侧卡键 */ g_pe_svc->inject_scancode(0x9D, 0); g_pe_svc->inject_scancode(0xAA, 0); g_pe_svc->inject_scancode(0xB8, 0); g_e0 = 0; handle_key(sc); continue; } g_pe_svc->inject_scancode(sc, g_e0); g_e0 = 0; } else { handle_key(sc); } } if (g_quit) return 1; /* 任务栏 QUIT:请求 PE 退出 */ /* 驱动一帧(redraw_all 内含 PE surface blit 与光标绘制) */ redraw_all(); flip_buffer(); return 0; } /* 启动 PE 应用:读 exe → 建窗口 → run_windowed 阻塞运行 → 退出后销毁窗口 */ static void launch_pe_app(int app_id) { app_descriptor *app = &g_apps[app_id]; if (!g_pe_svc || !g_pe_svc->run_windowed) { slog("pe: service unavailable"); return; } if (g_pe_running) { slog("pe: another PE window is running"); return; } u8 *data = 0; u32 size = 0; if (f32_read_path_lfn(app->pe_file, &data, &size) != 0) { slog("pe: exe not found"); return; } /* 窗口布局:与 launch_app 相同的级联策略 */ int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_TASKBAR_H; int wx = ICON_PAD + g_win_count * 30; int wy = top + 8 + g_win_count * 30; if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = ICON_PAD; if (wy + (int)app->default_h > bot - 20) wy = top + 8; desktop_window *win = win_create(app_id, app->display_name, wx, wy, (int)app->default_w, (int)app->default_h); if (!win) return; int win_id = win->id; int cw = (int)app->default_w - 2 * BORDER_W; int ch = (int)app->default_h - KATE_TITLEBAR_H - BORDER_W; if (cw <= 0 || ch <= 0 || (u64)cw * (u64)ch * 4 > PE_SURFACE_MAX) { win_destroy(win); slog("pe: client area too large for surface"); return; } g_pe_surf = (u8 *)PE_SURFACE_ADDR; g_pe_surf_w = cw; g_pe_surf_h = ch; /* 初始化为深色底:PE 窗口未覆盖区域不残留脏数据 */ { u32 *p = (u32 *)g_pe_surf; u64 n = (u64)cw * (u64)ch; for (u64 i = 0; i < n; i++) p[i] = 0xFF101418u; } win->app_state = &g_pe_state_dummy; /* draw_desktop_page 要求非空才调 on_draw */ g_pe_win_id = win_id; g_pe_running = 1; slog("pe: run_windowed"); /* cmdline:"file.exe args"(GetCommandLineA 返回值) */ static char cmdline[160]; kstrcpy(cmdline, app->pe_file, (int)sizeof(cmdline)); if (app->pe_args && app->pe_args[0]) { kstrcat(cmdline, " ", (int)sizeof(cmdline)); kstrcat(cmdline, app->pe_args, (int)sizeof(cmdline)); } pe_window_host host; host.pump = pe_pump_cb; host.ud = (void *)(u64)win_id; /* 传 id:窗口数组重排不悬空 */ host.surface = g_pe_surf; host.width = (u32)cw; host.height = (u32)ch; host.pitch = (u32)cw * 4; u64 exit_code = 0; g_pe_svc->run_windowed(data, size, cmdline, &exit_code, &host); g_pe_running = 0; g_pe_win_id = -1; g_pe_surf = 0; /* PE 已退出:销毁宿主窗口并全屏重绘 */ desktop_window *w2 = win_find(win_id); if (w2) win_destroy(w2); files_refresh(); /* PE 可能改动了磁盘 */ g_full_redraw = 1; redraw_all(); flip_buffer(); slog("pe: exited"); } /* ============================================================ * 主入口 * ============================================================ */ __attribute__((visibility("default"))) void dsk_entry(const dsk_boot_context *ctx) { __asm__ volatile("cli"); slog("boot"); if (!ctx || ctx->magic != 0x44534B31424F4F54ULL) { slog("bad context"); for (;;) __asm__("hlt"); } g_boot_ctx = ctx; g_fb_addr = ctx->framebuffer_address; g_fb_w = ctx->framebuffer_width; g_fb_h = ctx->framebuffer_height; g_fb_pitch = ctx->framebuffer_pitch; g_kernel_api = (const void *)ctx->dkm_kernel_api; /* block API:kernel_api + 0xA8 → block_api 结构,read @ +0x10, write @ +0x18 */ { u64 blk = *(u64 *)(ctx->dkm_kernel_api + 0xA8); g_block_read = blk ? (desktop_block_read_fn)*(u64 *)(blk + 16) : 0; g_block_write = blk ? (desktop_block_write_fn)*(u64 *)(blk + 24) : 0; } /* VSCode Phase 2: Linux 兼容层服务指针(dsk_loader 在 reserved[5] 写入)。 * 为 NULL 表示 Linux guest 未启动 / IPC 未就绪,IDE attach 时降级为本地 editor。 */ g_lxc_svc = (const linux_compat_service *)ctx->reserved[5]; if (g_lxc_svc && g_lxc_svc->magic == LINUX_COMPAT_MAGIC) { slog("lxc service available"); } else { g_lxc_svc = 0; slog("lxc service unavailable (IDE will use local editor)"); } /* P5d: PE 兼容层服务指针(dsk_loader 在 reserved[4] 写入)。 * 为 NULL 表示 PE 服务未注册,PEAPPS.CNF 应用启动时降级报错。 */ g_pe_svc = (const pe_service *)ctx->reserved[4]; if (g_pe_svc && g_pe_svc->magic == PE_SERVICE_MAGIC) { slog("pe service available"); } else { g_pe_svc = 0; slog("pe service unavailable"); } du_context_init(&g_fb, g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch); /* 双缓冲:保存真实 fb,切换到 sprite buffer 渲染 */ g_real_fb = (u8 *)g_fb_addr; g_fb.fb = (du_u32 *)SPRITE_BUF_ADDR; f32_init(g_block_read, g_block_write); tsc_calibrate(); g_rtc_boot_sec = rtc_day_sec(); g_last_sec = g_rtc_boot_sec; ps2_mouse_init(); register_apps(); setup_desktop_icons(); /* 工作区 0 */ kstrcpy(g_ws[0].name, "WS-1", 16); g_ws[0].page = 1; g_ws_count = 1; g_ws_cur = 0; /* DASHBOARD 面板共享状态 */ { bash_state *t1 = (bash_state *)ADDR_TERM1; bash_reset(t1, 640, 280); bash_puts(t1, "Deshab Bash v0.1 - TERM-01\nType help for commands\n\n", KS_TEXT_PRIMARY); bash_state *t2 = (bash_state *)ADDR_TERM2; bash_reset(t2, 640, 280); bash_puts(t2, "Deshab Bash v0.1 - TERM-02\nType help for commands\n\n", KS_TEXT_PRIMARY); editor_reset((editor_state *)ADDR_EDDASH, "Welcome to Deshab Editor\n" "\n" "- Double-click a file in FILES panel to open it here\n" "- SAVE button / Ctrl+S writes back to FAT32 disk\n" "- IDE page hosts this same editor full-screen\n"); } files_refresh(); boot_screen(); g_booted = 1; redraw_all(); slog("desktop ready"); /* 主事件循环 */ /* P8.1: dev 自动 VSCode 启动测试--无需 GUI 交互即可验证 launch 链路 */ static int g_autotest_state = 0; static u64 g_autotest_tsc = 0; while (!g_quit) { int need_redraw = 0; int moved = 0; /* P8.1: 自动启动测试(先 ping guest,不可用直接报告,不浪费 3s 等待) */ if (g_tsc_per_sec && g_autotest_state == 0) { g_autotest_tsc = rdtsc(); g_autotest_state = 1; /* P8.1 Fix: 先检测 guest 可用性 */ int prc = lxc_guest_ping(); if (prc != 0) { slog("[autotest] SKIP: Linux guest not running (no VMX/KVM)"); slog("[autotest] To test VSCode: use WSL2+KVM or Linux host with KVM"); g_autotest_state = 3; /* 跳过测试 */ } else { slog("[autotest] guest OK, waiting 3s before vscode_launch"); } } if (g_autotest_state == 1 && g_tsc_per_sec) { u64 elapsed = rdtsc() - g_autotest_tsc; if (elapsed >= (u64)g_tsc_per_sec * 3) { slog("[autotest] triggering vscode_launch"); g_autotest_state = 2; vscode_launch(); } } if (g_autotest_state == 2 && g_tsc_per_sec) { u64 elapsed = rdtsc() - g_autotest_tsc; /* 15s 后报告结果 */ if (elapsed >= (u64)g_tsc_per_sec * 15) { if (g_vscode_state == 2) { slog("[autotest] SUCCESS: scanout online, VSCode running"); } else if (g_vscode_state == 1) { slog("[autotest] TIMEOUT: still launching"); slog_num("[autotest] phase=", g_launch_phase); } else { slog("[autotest] FAIL: vscode_state=0"); slog_num("[autotest] phase=", g_launch_phase); } g_autotest_state = 3; } } /* 鼠标:排空所有数据包 */ while (ps2_mouse_poll()) moved = 1; if (g_left_pressed) { g_left_pressed = 0; if (on_left_press()) need_redraw = 1; } if (g_right_pressed) { g_right_pressed = 0; if (on_right_press()) need_redraw = 1; } if (g_left_released) { g_left_released = 0; if (g_dragging || g_drag_slider) { g_dragging = 0; g_drag_win = -1; g_drag_slider = 0; need_redraw = 1; } } /* 窗口拖拽 */ if (moved && g_dragging && g_drag_win >= 0) { desktop_window *w = win_find(g_drag_win); if (w) { w->x = g_mouse_x - g_drag_off_x; w->y = g_mouse_y - g_drag_off_y; if (w->y < KATE_TOPBAR_H) w->y = KATE_TOPBAR_H; if (w->x < 0 - w->w + 40) w->x = 0 - w->w + 40; if (w->x > (int)g_fb_w - 40) w->x = (int)g_fb_w - 40; if (w->y > (int)g_fb_h - KATE_TASKBAR_H - 20) w->y = (int)g_fb_h - KATE_TASKBAR_H - 20; } need_redraw = 1; } /* 滑块拖拽 */ if (moved && g_drag_slider == 1) { slider_set(g_vol_rect, &g_vol, g_mouse_x); need_redraw = 1; } if (moved && g_drag_slider == 2) { slider_set(g_bri_rect, &g_bright, g_mouse_x); need_redraw = 1; } /* 键盘:排空 */ for (;;) { u8 st = inb(0x64); if (!(st & 1) || (st & 0x20)) break; u8 sc = inb(0x60); if (handle_key(sc)) need_redraw = 1; } /* RTC 秒 tick:DASHBOARD 状态栏时钟/UP 计时刷新 */ { int sec = rtc_day_sec(); if (sec != g_last_sec) { g_last_sec = sec; if (g_page == 1 && !g_taskview) need_redraw = 1; } } /* VSCode Phase 3/7: IDE attached 时轮询 guest scanout dirty, * 有新帧则触发重绘以 blit 到 framebuffer。查询结果缓存进 g_so * (查询清零 UTSM 侧 dirty + dirty rect),blit 据此局部拷贝。 */ if (g_page == 2 && g_ide_attached && g_input_forward_enabled && g_lxc_svc && g_lxc_svc->gpu_get_scanout_info) { if (g_lxc_svc->gpu_get_scanout_info(&g_so) == 0) { g_so_valid = 1; if (g_so.dirty) need_redraw = 1; } } /* P7.9: FPS 统计(每秒更新,基于 TSC) */ if (g_tsc_per_sec && g_prof_fps_tsc) { u64 elapsed = rdtsc() - g_prof_fps_tsc; if (elapsed >= g_tsc_per_sec) { g_prof_fps = (int)(g_prof_frames * g_tsc_per_sec / elapsed); g_prof_frames = 0; g_prof_fps_tsc = rdtsc(); if (g_page == 2) need_redraw = 1; /* 刷新叠加显示 */ } } else if (g_tsc_per_sec) { g_prof_fps_tsc = rdtsc(); } if (need_redraw && g_page == 2) g_prof_frames++; if (need_redraw) { /* 脏矩形渲染:合并脏矩形,只 flip 变化区域 */ if (g_full_redraw || g_dirty_count == 0) { /* 全屏重绘 */ redraw_all(); flip_buffer(); } else { /* 合并脏矩形 */ int dx, dy, dw, dh; merge_dirty_rects(&dx, &dy, &dw, &dh); /* 重绘并 flip 脏区域 */ if (dw > 0 && dh > 0) { /* 清除脏矩形标记 */ clear_dirty_rects(); /* 重绘整个屏幕(简化:暂不支持局部重绘) */ redraw_all(); /* Flip 合并后的脏区域 */ flip_rect(dx, dy, dw, dh); } } } else if (moved) { /* 仅光标移动:局部 flip(双缓冲 → 无闪烁) */ int old_x = g_cursor_old_x, old_y = g_cursor_old_y; cursor_restore_bg(); flip_rect(old_x, old_y, CURSOR_SIZE, CURSOR_SIZE); cursor_save_bg(g_mouse_x, g_mouse_y); cursor_draw(g_mouse_x, g_mouse_y); flip_rect(g_mouse_x, g_mouse_y, CURSOR_SIZE, CURSOR_SIZE); } __asm__("pause"); } slog("exit"); /* 返回到 DSK */ }