Files
Deshab-OS/CODE/desktop/main.c
T
xyou a26866e339 feat: B7 APIC/IOAPIC接管 + NVMe写路径 + virtio_net virtqueue + PE兼容层修复 + QEMU测试套件全自动化
中断路由 (B7):
- apic.drv: LAPIC enable + IOAPIC重定向表编程(MADT ISO感知) + apic_route/apic_route_legacy/apic_diag开关
- UTSM: LAPIC EOI钩子(register_apic_eoi, ABI尾部追加) + IDT 0-255全覆盖 + spurious 0xFF特判
- FUCK: apic_route=0默认PIC(零回归) / =1 IOAPIC->LAPIC接管(失败完整降级) / legacy放开IRQ1/12/11

存储:
- nvme.drv: 写路径 + PRP list + 读回校验 + MSI/MSI-X中断驱动(nvme_msi开关, 回退纯轮询)
- build.ps1: apic.drv纳入构建(原为预置二进制)

网络:
- virtio_net: virtqueue RX/TX + netdev注册 + DHCP自测(slirp真实OFFER)

PE兼容层:
- pe_shim: guest->host指针转换(g_emu_mem_base + gp()) + shim fn32列补齐
- gen_pe.py: PE32+ RVA偏置修复(BUG-004) + cmd.exe生成
- desktop_app.h: da_mouse_poll先判AUX位 + da_mouse_init冲刷残留(BUG-003/008)
- shell: Ctrl修饰键跟踪(BUG-001) + g_run_image扩至1MB(BUG-002)

QEMU测试套件:
- 9用例全自动化(Run-All.ps1): boot-regression/driver-matrix/linux-compat/pe-compat/deaicup-e2e/userapps-matrix/network-e2e/performance-baseline/x86emu32-coverage
- QemuTest.ps1公共库: 串口断言/monitor sendkey/screendump/HTTP服务器/并发隔离

文档:
- CLAUDE.md: APIC/IOAPIC能力 + kernel_api暴露 + IRQ后端升级状态
- LESSONS_LEARNED.md: B7 LAPIC/IOAPIC经验教训(2条)
- Bug记录.txt: BUG-20260801-001~008 + BUG-20260803-009(9条)
- test-cases/*.md: 全部补充自动化脚本小节
- 桌面设计.md: .lnk快捷方式 + LINUXAPP.CNF Linux应用图标
- 系统开发策划.md: NVMe/virtio_net标完成 + B2/B7标已解决 + M2进度列
2026-08-04 18:08:08 +08:00

3705 lines
134 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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"
/* Linux 兼容层服务接口(reserved[5],exec/file_list/file_read) */
#include "../UTSM/include/utsm/linux_compat.h"
/* 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 读写 */
#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; }
/* Linux 桌面应用配置契约(da_linuxapp_parse / DA_LINUXAPP_* 常量)。
* DA_NO_PORTIO:本文件已自带 outb/inb,跳过头文件内同名定义。 */
#define DA_NO_PORTIO
#include "../tools/desktop_app.h"
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);
}
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);
static void shortcuts_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;
/* Linux 兼容层命令(Phase 5,如 "/usr/bin/neofetch")。与 elf_name 互斥:
* 两者同时设置时 elf_name 优先(走外部 ELF 全屏宿主),linux_cmd 被忽略;
* 仅设置 linux_cmd 时必须提供 on_create(内置终端窗口承载执行)。 */
const char *linux_cmd;
} app_descriptor;
/* 桌面图标 */
typedef struct {
int app_id;
int x, y;
const char *label;
int selected;
} desktop_icon;
/* 桌面快捷方式(从 SYSTEM/user/desktop 目录加载 .lnk 文件) */
typedef struct {
char display_name[24]; /* 显示名称(UTF-8) */
char elf_name[11]; /* ELF 文件名(FAT32 8.3) */
char icon_type[12]; /* 图标类型:editor/folder/browser/terminal */
int x, y; /* 桌面位置 */
int selected; /* 选中状态 */
} desktop_shortcut;
#define MAX_ICONS 24 /* 桌面图标槽(内置应用 + 用户快捷方式) */
/* 工作区 */
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;
/* 鼠标状态 */
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;
/* 应用表(16 = 6 内置 + DA_LINUXAPP_MAX 8 个 Linux 应用 + 余量) */
#define MAX_APPS 16
static app_descriptor g_apps[MAX_APPS];
static int g_app_count = 0;
/* Linux 兼容层服务运行态(register_linux_apps 设置):0 时 Linux 图标置灰,
* 双击仍开终端窗口并提示不可用原因(lnx_on_create 运行时复查)。 */
static int g_lnx_svc_up = 0;
static desktop_icon g_icons[MAX_ICONS];
static int g_icon_count = 0;
/* 用户快捷方式(从 SYSTEM/user/desktop 加载) */
static desktop_shortcut g_shortcuts[MAX_ICONS];
static int g_shortcut_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);
}
/* Linux 应用图标:暗底终端 + 霓虹绿 >_ 提示符(与 bash 终端同色板)。
* 服务不可用时 accent 传 KS_TEXT_DIM(置灰,点击仍给原因提示)。 */
static void draw_icon_linux(du_context *ctx, int cx, int cy, u32 accent) {
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_PRIMARY);
du_rect_outline(ctx, cx, cy, 40, 40, accent, 2);
/* '>' 折线 */
du_fill_rect(ctx, cx + 8, cy + 12, 2, 2, accent);
du_fill_rect(ctx, cx + 10, cy + 14, 2, 2, accent);
du_fill_rect(ctx, cx + 12, cy + 16, 2, 2, accent);
du_fill_rect(ctx, cx + 10, cy + 18, 2, 2, accent);
du_fill_rect(ctx, cx + 8, cy + 20, 2, 2, accent);
/* '_' 光标块 */
du_fill_rect(ctx, cx + 16, cy + 22, 8, 2, accent);
}
static void draw_app_icon(du_context *ctx, int app_id, int cx, int cy) {
/* Linux 应用走专属图标(暗底绿提示符),其余保持原有映射 */
if (app_id >= 0 && app_id < g_app_count && g_apps[app_id].linux_cmd) {
draw_icon_linux(ctx, cx, cy, g_lnx_svc_up ? KS_ACCENT2 : KS_TEXT_DIM);
return;
}
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;
}
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;
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];
}
/* ============================================================
* 终端 (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; }
/* ============================================================
* Linux 兼容层应用 (Phase 5)
* 图标 → 终端风格窗口 → linux_compat_service.exec 同步执行。
* exec 是同步阻塞调用(park-and-resume 到 Linux guest 完成才返回),
* 无 out_cb 流式回调:先绘制 running 状态再阻塞,返回后一次性
* 把 stdout 缓冲追加进终端并显示 [exit N]。
* ============================================================ */
static const linux_compat_service *g_lxc_svc = 0;
#define LNX_MAX_WINS 8 /* 与 MAX_WINDOWS 对齐:任何窗口都可能是 Linux 终端 */
#define LNX_OUT_CAP 16384 /* exec stdout 收集缓冲 */
typedef struct {
bash_state term; /* 复用 bash 终端字符网格(无输入行) */
int finished; /* 0=running 1=已返回 */
int rc; /* exec 返回值(0 成功,负值失败) */
u64 exit_code; /* guest 进程退出码 */
} lnx_state;
static lnx_state g_lnx_states[LNX_MAX_WINS];
static int g_lnx_used[LNX_MAX_WINS];
static char g_lnx_out[LNX_OUT_CAP]; /* 同步单发,可全窗口共享 */
/* Linux 应用注册名/命令持久池(f32_data 会被后续文件操作复用,必须拷出) */
static char g_lnx_name_pool[DA_LINUXAPP_MAX][12];
static char g_lnx_cmd_pool[DA_LINUXAPP_MAX][64];
static char g_lnx_id_pool[DA_LINUXAPP_MAX][8];
/* 追加无符号十进制到终端 */
static void lnx_put_u64(bash_state *t, u64 v, u32 color) {
char tmp[20];
int n = 0;
if (v == 0) tmp[n++] = '0';
while (v && n < 20) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; }
while (n > 0) bash_putc(t, tmp[--n], color);
}
static void lnx_put_i64(bash_state *t, i64 v, u32 color) {
if (v < 0) { bash_putc(t, '-', color); lnx_put_u64(t, (u64)(-v), color); }
else lnx_put_u64(t, (u64)v, color);
}
/* 追加 exec 输出到终端:剥离 ANSI CSI 转义序列,忽略 \r,\t 展开为 2 空格,
* 其余控制字符丢弃(终端字体仅 ASCII,非 ASCII 字节由 du_draw_char 钳为空格)。 */
static void lnx_append(bash_state *t, const char *buf, u64 len, u32 color) {
int esc = 0;
for (u64 i = 0; i < len; i++) {
char c = buf[i];
if (esc) {
/* CSI: ESC [ 参数... 结束字节(0x40-0x7E);其它 ESC 序列同理吞掉 */
if (c >= 0x40 && c <= 0x7E) esc = 0;
continue;
}
if (c == 0x1B) { esc = 1; continue; }
if (c == '\r') continue;
if (c == '\t') { bash_putc(t, ' ', color); bash_putc(t, ' ', color); continue; }
if ((u8)c < 32 && c != '\n') continue;
bash_putc(t, c, color);
}
}
/* 终端内容绘制:全量重绘(与 editor_draw_to 同策略),无输入行 */
static void lnx_draw_to(lnx_state *s, int cx, int cy, int cw, int ch) {
bash_state *t = &s->term;
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY);
for (int r = 0; r < t->rows; r++) {
for (int c = 0; c < t->cols; c++) {
int idx = r * TERM_MAX_COLS + c;
if (t->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, (char)t->ch[idx], px, py, t->fg[idx], KS_BG_PRIMARY);
}
}
}
/* ---- 窗口适配 ---- */
static void *lnx_on_create(app_ctx *ctx) {
int slot = -1;
for (int i = 0; i < LNX_MAX_WINS; i++) {
if (!g_lnx_used[i]) { slot = i; break; }
}
if (slot < 0) return 0;
g_lnx_used[slot] = 1;
lnx_state *s = &g_lnx_states[slot];
app_descriptor *app = &g_apps[ctx->win->app_id];
bash_reset(&s->term, ctx->client_w, ctx->client_h);
s->finished = 0;
s->rc = 0;
s->exit_code = 0;
/* 标题:显示名 + Linux 标记 */
{
for (int i = 0; i < 48; i++) ctx->win->title[i] = 0;
char t[48];
int ti = 0;
const char *d = app->display_name;
while (d[ti] && ti < 38) { t[ti] = d[ti]; ti++; }
const char *suf = " . Linux";
int j = 0;
while (suf[j] && ti < 47) { t[ti++] = suf[j++]; }
t[ti] = 0;
kstrcpy(ctx->win->title, t, 48);
}
/* 命令回显 */
bash_puts(&s->term, "$ ", KS_ACCENT2);
bash_puts(&s->term, app->linux_cmd, KS_TEXT_PRIMARY);
bash_putc(&s->term, '\n', 0);
/* 服务运行时复查(注册后 guest 仍可能不可用) */
if (!g_lxc_svc || !g_lxc_svc->is_available()) {
bash_puts(&s->term, "linux: compat service unavailable\n", KS_DANGER);
bash_puts(&s->term, "[exit -1]\n", KS_DANGER);
s->finished = 1;
s->rc = -1;
return s;
}
/* running 状态提示:先呈现窗口再进入同步阻塞 exec */
bash_puts(&s->term, "running... (sync exec, UI will freeze)\n", KS_TEXT_DIM);
ctx->win->app_state = s; /* 让 redraw_all 能看到本窗口内容 */
redraw_all();
flip_buffer();
/* 分词:linux_cmd → path + argv(就地切分副本) */
char cmdline[96];
kstrcpy(cmdline, app->linux_cmd, 96);
const char *argv[8];
int argc = 0;
char *p = cmdline;
while (*p && argc < 8) {
while (*p == ' ') p++;
if (!*p) break;
argv[argc++] = p;
while (*p && *p != ' ') p++;
if (*p) *p++ = 0;
}
if (argc == 0) {
bash_puts(&s->term, "linux: empty command\n", KS_DANGER);
s->finished = 1;
s->rc = -1;
return s;
}
/* 绝对路径直接使用;裸命令补 /bin/ 前缀(与 shell linux 命令一致) */
char path[96];
if (argv[0][0] == '/') {
kstrcpy(path, argv[0], 96);
} else {
kstrcpy(path, "/bin/", 96);
kstrcat(path, argv[0], 96);
}
u64 out_len = 0, exit_code = 0;
slog("linux exec");
int rc = g_lxc_svc->exec(path, argc, argv,
g_lnx_out, LNX_OUT_CAP - 1,
&out_len, &exit_code);
s->rc = rc;
s->exit_code = exit_code;
s->finished = 1;
if (rc != 0) {
bash_puts(&s->term, "linux: exec failed (rc=", KS_DANGER);
lnx_put_i64(&s->term, rc, KS_DANGER);
bash_puts(&s->term, ")\n", KS_DANGER);
bash_puts(&s->term, "[exit -1]\n", KS_DANGER);
return s;
}
/* 输出回显(一次性追加;exec 无流式回调) */
if (out_len > 0) {
lnx_append(&s->term, g_lnx_out, out_len, KS_TEXT_PRIMARY);
if (g_lnx_out[out_len - 1] != '\n') bash_putc(&s->term, '\n', 0);
if (out_len >= LNX_OUT_CAP - 1)
bash_puts(&s->term, "[output truncated]\n", KS_TEXT_DIM);
}
/* 退出码:0 绿 / 非 0 琥珀 */
u32 ec_color = (exit_code == 0) ? KS_ACCENT2 : KS_WARN;
bash_puts(&s->term, "[exit ", ec_color);
lnx_put_u64(&s->term, exit_code, ec_color);
bash_puts(&s->term, "]\n", ec_color);
return s;
}
static void lnx_on_draw(void *state, app_ctx *ctx) {
lnx_draw_to((lnx_state *)state, ctx->client_x, ctx->client_y,
ctx->client_w, ctx->client_h);
}
static void lnx_on_destroy(void *state) {
lnx_state *s = (lnx_state *)state;
for (int i = 0; i < LNX_MAX_WINS; i++) {
if (&g_lnx_states[i] == s) { g_lnx_used[i] = 0; break; }
}
}
/* ---- Linux 应用注册 ----
* 无论服务是否可用都注册图标:可用时双击即同步 exec;不可用(VMX 缺失/
* guest 未驻留)时图标置灰(g_lnx_svc_up=0),双击由 lnx_on_create 运行时
* 复查并显示 "linux: compat service unavailable [exit -1]",绝不 panic。
* 配置:FAT32 根目录 LINUXAPP.CNF(8.3 名 LINUXAPPCNF),每行 显示名|linux命令;
* 文件缺失或无有效行时回退内置默认列表(echo hello / uname -a / ls -l /,
* 三条均为 Arch rootfs 与 initramfs busybox 环境共有的可执行命令)。 */
static void register_linux_apps(void) {
g_lnx_svc_up = (g_lxc_svc && g_lxc_svc->is_available()) ? 1 : 0;
if (!g_lxc_svc) slog("linux apps: service absent (icons greyed)");
else if (!g_lnx_svc_up) slog("linux apps: service unavailable (icons greyed)");
da_linux_app ents[DA_LINUXAPP_MAX];
int n = 0;
u8 *data = 0;
u32 size = 0;
if (f32_read_root_file(DA_LINUXAPP_CNF_83, &data, &size) == 0 && size > 0) {
n = da_linuxapp_parse((const char *)data, size, ents, DA_LINUXAPP_MAX);
}
if (n <= 0) {
/* 内置默认保底(guest 中确定存在的命令) */
kstrcpy(g_lnx_name_pool[0], "HELLO", 12);
kstrcpy(g_lnx_cmd_pool[0], "/bin/echo hello from deshab linux", 64);
kstrcpy(g_lnx_name_pool[1], "UNAME", 12);
kstrcpy(g_lnx_cmd_pool[1], "/bin/uname -a", 64);
kstrcpy(g_lnx_name_pool[2], "LS", 12);
kstrcpy(g_lnx_cmd_pool[2], "/bin/ls -l /", 64);
n = 3;
slog("linux apps: builtin defaults");
} else {
for (int i = 0; i < n; i++) {
kstrcpy(g_lnx_name_pool[i], ents[i].name, 12);
kstrcpy(g_lnx_cmd_pool[i], ents[i].cmd, 64);
}
slog("linux apps: loaded LINUXAPP.CNF");
}
for (int i = 0; i < n && g_app_count < MAX_APPS; i++) {
char *id = g_lnx_id_pool[i];
id[0] = 'l'; id[1] = 'n'; id[2] = 'x'; id[3] = (char)('0' + i); id[4] = 0;
g_apps[g_app_count] = (app_descriptor){
id, g_lnx_name_pool[i], 720, 480,
lnx_on_create, 0, lnx_on_draw, lnx_on_destroy,
0, g_lnx_cmd_pool[i]
};
g_app_count++;
{
char msg[64];
kstrcpy(msg, "linux app registered: ", 64);
kstrcat(msg, g_lnx_name_pool[i], 64);
slog(msg);
}
}
slog("linux apps registered");
}
/* ============================================================
* 文本编辑器 (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; }
/* ============================================================
* 应用注册
* ============================================================ */
static void register_apps(void) {
/* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/
g_apps[0] = (app_descriptor){
"shell", "Terminal", 720, 480,
0, 0, 0, 0, "SHELL ELF", 0
};
g_app_count++;
/* 命令行2:cmd.elf(Windows 风格命令行 + PE/EXE 执行)*/
g_apps[1] = (app_descriptor){
"cmd", "CMD", 720, 480,
0, 0, 0, 0, "CMD ELF", 0
};
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, 0
};
g_app_count++;
g_apps[3] = (app_descriptor){
"fileman", "Files", 500, 400,
0, 0, 0, 0, "FILEMAN ELF", 0
};
g_app_count++;
g_apps[4] = (app_descriptor){
"calc", "Calculator", 280, 400,
calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy, 0, 0
};
g_app_count++;
g_apps[5] = (app_descriptor){
"browser", "Browser", 700, 500,
0, 0, 0, 0, "BROWSER ELF", 0
};
g_app_count++;
/* Linux 兼容层应用:无论服务是否可用都注册图标(不可用时置灰,
* 双击仍开终端窗口提示原因,见 register_linux_apps / lnx_on_create) */
register_linux_apps();
}
/* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px,
* 左→右排列,超出画布宽度则换行(align-content: start)
*
* 桌面图标来源:
* 1. 用户快捷方式(SYSTEM/user/desktop 目录)
* 2. 内置应用(editor, browser 等)
*/
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 右缘 */
/* 先加载用户快捷方式 */
shortcuts_refresh();
for (int i = 0; i < g_shortcut_count && g_icon_count < MAX_ICONS; i++) {
if (x > pad && x + KATE_ICON_W > max_x) {
x = pad;
y += KATE_ICON_H + KATE_ICON_GAP;
}
/* 将快捷方式转换为桌面图标(app_id = -1 表示快捷方式) */
g_icons[g_icon_count].app_id = -1; /* 标记为快捷方式 */
g_icons[g_icon_count].x = x;
g_icons[g_icon_count].y = y;
g_icons[g_icon_count].label = g_shortcuts[i].display_name;
g_icons[g_icon_count].selected = 0;
g_shortcuts[i].x = x; /* 记录位置用于点击检测 */
g_shortcuts[i].y = y;
g_icon_count++;
x += KATE_ICON_W + KATE_ICON_GAP;
}
/* 再追加 Linux 应用图标(register_linux_apps 已登记进 g_apps,带 linux_cmd)。
* 服务不可用时图标仍上架但置灰(draw_app_icon 查 g_lnx_svc_up),
* 双击开终端窗口提示原因,与可用时同一入口。 */
for (int a = 0; a < g_app_count && g_icon_count < MAX_ICONS; a++) {
if (!g_apps[a].linux_cmd) 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 = a;
g_icons[g_icon_count].x = x;
g_icons[g_icon_count].y = y;
g_icons[g_icon_count].label = g_apps[a].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;
/* 外部工具 ELF 加载缓冲。fileman/editor 等工具 include fat32_io.h
* 带 ~388KB BSS(f32_disk+f32_data),其 .elf 的 memsz 可达 ~470KB。
* 1MB 容量覆盖当前所有工具 ELF 并留余量。BSS 是 NOBITS 不占 desktop.elf 文件空间。 */
static u8 g_elf_image[1048576];
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));
for (u32 j = 0; j < rn; j++) {
u32 type = (u32)(r[j].info & 0xFFFFFFFF);
if (type == DT_R_X86_64_RELATIVE) {
u64 target = r[j].offset - min_vaddr;
u64 val = min_vaddr + (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_app(int app_id) {
if (app_id < 0 || app_id >= g_app_count) return;
app_descriptor *app = &g_apps[app_id];
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;
}
/* 加载桌面快捷方式(从 SYSTEM/user/desktop 目录读取 .lnk 文件)
* 快捷方式文件格式(纯文本,UTF-8):
* 第1行:显示名称(如 "编辑器")
* 第2行:ELF文件名(FAT32 8.3,如 "EDITOR ELF")
* 第3行:图标类型(如 "editor")
*/
static void shortcuts_refresh(void) {
g_shortcut_count = 0;
if (!g_block_read) return;
/* 读取 desktop 目录下的 .lnk 文件 */
/* 简化实现:直接读取预定义的快捷方式 */
/* 内置快捷方式列表(暂时硬编码,后续可改为读取.lnk文件) */
static const struct {
const char *name;
const char *elf;
const char *icon;
} builtin_shortcuts[] = {
{ "编辑器", "EDITOR ELF", "editor" },
{ "文件管理", "FILEMAN ELF", "folder" },
{ "浏览器", "BROWSER ELF", "browser" },
{ "终端", "SHELL ELF", "terminal" },
};
int count = sizeof(builtin_shortcuts) / sizeof(builtin_shortcuts[0]);
for (int i = 0; i < count && g_shortcut_count < MAX_ICONS; i++) {
desktop_shortcut *s = &g_shortcuts[g_shortcut_count];
kstrcpy(s->display_name, builtin_shortcuts[i].name, 24);
kstrcpy(s->elf_name, builtin_shortcuts[i].elf, 11);
kstrcpy(s->icon_type, builtin_shortcuts[i].icon, 12);
s->x = 0; s->y = 0;
s->selected = 0;
g_shortcut_count++;
}
}
/* 双击打开文件 → 载入 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;
/* 根据图标来源绘制不同图标 */
if (ic->app_id >= 0) {
/* 内置应用图标 */
draw_app_icon(&g_fb, ic->app_id, ix, iy);
} else if (i < g_shortcut_count) {
/* 快捷方式图标(根据 icon_type 选择) */
const char *icon = g_shortcuts[i].icon_type;
if (icon[0] == 'e' && icon[1] == 'd') draw_icon_editor(&g_fb, ix, iy);
else if (icon[0] == 'f') draw_icon_folder(&g_fb, ix, iy);
else if (icon[0] == 'b') draw_icon_browser(&g_fb, ix, iy);
else if (icon[0] == 't') draw_icon_terminal(&g_fb, ix, iy);
else draw_icon_editor(&g_fb, 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 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;
/* 实例标签 EDITOR ×(Kate .ide-tab) */
{
const char *label = "EDITOR";
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) {
editor_draw_to((editor_state *)ADDR_EDDASH, hx + 2, hy + 2, hw - 4, hh - 4);
} else {
/* embed-empty(Kate:未启动 VSCode 提示) */
draw_centered("EDITOR NOT ATTACHED . DOUBLE-CLICK OR + NEW IDE",
W / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0);
}
}
/* ---- 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; return 1; }
if (!g_ide_attached && rect_hit(g_ide_new_rect, mx, my)) { g_ide_attached = 1; return 1; }
if (!g_ide_attached && dbl && rect_hit(g_ide_host, mx, my)) {
g_ide_attached = 1;
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_destroy(&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_destroy(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) {
/* 根据图标来源选择启动方式 */
if (g_icons[i].app_id >= 0) {
/* 内置应用 */
launch_app(g_icons[i].app_id);
} else if (i < g_shortcut_count) {
/* 快捷方式:加载外部 ELF */
slog("launching shortcut");
launch_external_elf(g_shortcuts[i].elf_name);
}
} 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) {
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) {
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;
}
/* ============================================================
* 主入口
* ============================================================ */
__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;
}
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);
/* Linux 兼容层服务(reserved[5],magic 校验)—— 需在 register_apps 前就绪 */
{
u64 lxc_addr = ctx->reserved[5];
if (lxc_addr) {
const linux_compat_service *lxc = (const linux_compat_service *)lxc_addr;
if (lxc->magic == LINUX_COMPAT_MAGIC) {
g_lxc_svc = lxc;
slog("linux_compat service ok");
} else {
slog("linux_compat magic mismatch");
}
} else {
slog("linux_compat service absent");
}
}
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");
/* 主事件循环 */
while (!g_quit) {
int need_redraw = 0;
int moved = 0;
/* 鼠标:排空所有数据包 */
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;
}
}
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 */
}