2026-07-28 12:00:02 +08:00
|
|
|
|
/* Deshab Desktop Manager — Winux-Kate 结构完整复刻
|
2026-07-03 21:13:04 +08:00
|
|
|
|
*
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* 布局结构与 D:\Code\Winux-Kate 完全一致:
|
|
|
|
|
|
* app-shell
|
|
|
|
|
|
* ├── topbar (34px):品牌 + 工作区切换 + 页面切换 + PG 时钟
|
2026-07-28 14:35:14 +08:00
|
|
|
|
* page-host
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* │ ├── 1·DASHBOARD:TERM-01 / TERM-02 / EDITOR / FILES 四面板 + StatusBar
|
|
|
|
|
|
* │ ├── 2·IDE:标签栏 + 编辑宿主
|
2026-07-28 14:35:14 +08:00
|
|
|
|
* │ ├── 3·DESKTOP:图标网格 + 窗口 + 任务栏
|
|
|
|
|
|
* │ └── 4+/CUSTOM:外部 .elf 全屏宿主(双击启动)
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* └── task-view overlay(工作区卡片)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 新 UI 特性(Kate 之外):
|
|
|
|
|
|
* - RTC 实时时钟(秒级)+ 开机至今 UP 计时
|
|
|
|
|
|
* - VOL / BRIGHT 可拖滑块(BRIGHT 真实调暗全屏)
|
|
|
|
|
|
* - 开机打字机 Boot 屏
|
|
|
|
|
|
* - FILES 面板双击文件载入 EDITOR(FAT32 真实读写 + SAVE 回写)
|
|
|
|
|
|
* - 窗口单实例聚焦策略
|
2026-07-03 21:13:04 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "../UTSM/include/utsm/dsk.h"
|
2026-08-09 22:14:01 +08:00
|
|
|
|
#include "../UTSM/include/utsm/linux_compat.h" /* VSCode Phase 2: input_forward_* */
|
|
|
|
|
|
#include "../UTSM/include/utsm/pe.h" /* P5d: PE 窗口模式 run_windowed/inject_* */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
/* 先包含 ascii_bitmaps.c(定义 g_ascii),再包含 deshab_ui.h(引用 g_ascii) */
|
|
|
|
|
|
#include "../firstInit/ascii_bitmaps.c"
|
|
|
|
|
|
#include "../UTSM/include/utsm/deshab_ui.h"
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* 用户态 FAT32 读写。P5d:PE exe 可达数 MB,数据缓冲同 cmd.elf 提到 4MB。 */
|
|
|
|
|
|
#define F32_DATA_BYTES (4u * 1024u * 1024u)
|
2026-07-28 12:00:02 +08:00
|
|
|
|
#include "../tools/fat32_io.h"
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
typedef unsigned char u8;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
typedef signed char i8;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
typedef unsigned short u16;
|
|
|
|
|
|
typedef unsigned int u32;
|
|
|
|
|
|
typedef unsigned long long u64;
|
|
|
|
|
|
typedef long long i64;
|
|
|
|
|
|
|
|
|
|
|
|
#define COM1 0x3F8
|
|
|
|
|
|
|
|
|
|
|
|
static __inline__ void outb(u16 port, u8 value) { __asm__ volatile("outb %0,%1"::"a"(value),"Nd"(port)); }
|
|
|
|
|
|
static __inline__ u8 inb(u16 port) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(port)); return v; }
|
|
|
|
|
|
|
|
|
|
|
|
static void sputc(char c) {
|
|
|
|
|
|
for (unsigned int i=0; i<100000; i++) { if (inb(COM1+5)&0x20) break; }
|
|
|
|
|
|
outb(COM1, (unsigned char)c);
|
|
|
|
|
|
}
|
|
|
|
|
|
static void swrite(const char *s) { while(*s) { if(*s=='\n')sputc('\r'); sputc(*s++); } }
|
|
|
|
|
|
static void slog(const char *s) { swrite("[desktop] "); swrite(s); swrite("\n"); }
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* P7.9: u32 -> decimal string(返回长度) */
|
|
|
|
|
|
static int u32dec(u32 v, char *out) {
|
|
|
|
|
|
char tmp[10];
|
|
|
|
|
|
int n = 0;
|
|
|
|
|
|
if (v == 0) { out[0] = '0'; out[1] = 0; return 1; }
|
|
|
|
|
|
while (v > 0) { tmp[n++] = '0' + (char)(v % 10); v /= 10; }
|
|
|
|
|
|
for (int i = 0; i < n; i++) out[i] = tmp[n - 1 - i];
|
|
|
|
|
|
out[n] = 0;
|
|
|
|
|
|
return n;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static u64 rdtsc(void) {
|
|
|
|
|
|
u32 lo, hi;
|
|
|
|
|
|
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
|
|
|
|
|
return ((u64)hi << 32) | lo;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-26 21:07:36 +08:00
|
|
|
|
/* 前向声明 */
|
|
|
|
|
|
static void redraw_all(void);
|
|
|
|
|
|
static int ps2_mouse_poll(void);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void draw_statusbar_full(void);
|
|
|
|
|
|
static void files_refresh(void);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
/* ============================================================
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* 常量(Winux-Kate 布局)
|
2026-07-03 21:13:04 +08:00
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
#define MAX_WINDOWS 8
|
2026-07-28 12:00:02 +08:00
|
|
|
|
#define BORDER_W 1
|
2026-07-03 21:13:04 +08:00
|
|
|
|
#define CURSOR_SIZE 24
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
#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
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 事件系统
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
int mx, my;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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];
|
2026-07-28 12:00:02 +08:00
|
|
|
|
int app_id;
|
|
|
|
|
|
void *app_state;
|
|
|
|
|
|
int closing;
|
|
|
|
|
|
int dirty;
|
|
|
|
|
|
int ws; /* 所属工作区 */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} 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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
desktop_block_read_fn block_read;
|
|
|
|
|
|
desktop_block_write_fn block_write;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
const char *elf_name;
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* VSCode Phase 5: Linux guest 应用(来自 LINUXAPP.CNF)。
|
|
|
|
|
|
* linux_path 非 0 时 launch_app 走 launch_linux_app → exec_async。 */
|
|
|
|
|
|
const char *linux_path;
|
|
|
|
|
|
const char *linux_args;
|
|
|
|
|
|
/* P5d: Windows PE 应用(来自 PEAPPS.CNF)。
|
|
|
|
|
|
* pe_file 非 0 时 launch_app 走 launch_pe_app → run_windowed 桌面嵌入。 */
|
|
|
|
|
|
const char *pe_file;
|
|
|
|
|
|
const char *pe_args;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} app_descriptor;
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 桌面图标 */
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
|
int app_id;
|
|
|
|
|
|
int x, y;
|
|
|
|
|
|
const char *label;
|
|
|
|
|
|
int selected;
|
|
|
|
|
|
} desktop_icon;
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
#define MAX_ICONS 16 /* P7.4: 与 MAX_APPS 对齐(多实例 VSCODE2 等占位) */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
|
|
|
|
|
|
/* 工作区 */
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 全局状态
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
|
|
|
|
|
|
/* 双缓冲:渲染到 sprite buffer,完成后一次性 flip 到真实 framebuffer */
|
|
|
|
|
|
#define SPRITE_BUF_ADDR 0x7000000ULL /* 112MB,在 identity-mapped 区域 */
|
|
|
|
|
|
static u8 *g_real_fb = 0; /* 真实 framebuffer 地址 */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* VSCode Phase 2: Linux guest 输入转发。
|
|
|
|
|
|
* g_lxc_svc 由 dsk_entry() 从 boot context reserved[5] 读取;
|
|
|
|
|
|
* g_input_forward_enabled 在 IDE 标签 attach/detach 时联动置位,
|
|
|
|
|
|
* 开启后 PS/2 鼠标键盘事件经 virtio-input 注入 Linux guest(VSCode 运行其中)。 */
|
|
|
|
|
|
static const linux_compat_service *g_lxc_svc = 0;
|
|
|
|
|
|
static int g_input_forward_enabled = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/* P5d: PE 窗口桌面嵌入状态。
|
|
|
|
|
|
* g_pe_svc 由 dsk_entry() 从 boot context reserved[4] 读取(magic 校验)。
|
|
|
|
|
|
* launch_pe_app 创建受管窗口后阻塞在 pe_service.run_windowed;
|
|
|
|
|
|
* PE 消息空转时 shim 回调 pe_pump_cb 驱动 desktop 一帧。
|
|
|
|
|
|
* 单实例:run_windowed 阻塞期间拒绝启动第二个 PE 窗口(pump 重入安全)。 */
|
|
|
|
|
|
static const pe_service *g_pe_svc = 0;
|
|
|
|
|
|
static int g_pe_running = 0;
|
|
|
|
|
|
static int g_pe_win_id = -1;
|
|
|
|
|
|
static u8 *g_pe_surf = 0;
|
|
|
|
|
|
static int g_pe_surf_w = 0, g_pe_surf_h = 0;
|
|
|
|
|
|
static int g_pe_state_dummy = 0; /* draw_desktop_page 要求 app_state 非空 */
|
|
|
|
|
|
|
|
|
|
|
|
#define PE_SURFACE_ADDR 0x7800000ULL /* 120MB:sprite buf(112MB, ≤8.3MB) 之上 */
|
|
|
|
|
|
#define PE_SURFACE_MAX (8u * 1024u * 1024u)
|
|
|
|
|
|
#define PE_WM_CLOSE 0x0010u /* shim Win32 WM_CLOSE */
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
/* 鼠标状态 */
|
|
|
|
|
|
static int g_mouse_x = 400, g_mouse_y = 300;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static int g_mouse_btn = 0; /* bit0=左 bit1=右 */
|
|
|
|
|
|
static int g_left_pressed = 0, g_left_released = 0, g_right_pressed = 0;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-08-05 13:15:46 +08:00
|
|
|
|
/* 应用表 */
|
2026-08-09 22:14:01 +08:00
|
|
|
|
#define MAX_APPS 16
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static app_descriptor g_apps[MAX_APPS];
|
|
|
|
|
|
static int g_app_count = 0;
|
|
|
|
|
|
|
|
|
|
|
|
static desktop_icon g_icons[MAX_ICONS];
|
|
|
|
|
|
static int g_icon_count = 0;
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* ---- Kate 外壳状态 ---- */
|
|
|
|
|
|
static int g_booted = 0;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
static int g_page = 1; /* 1..3 内置, 4..8 自定义 */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
2026-07-28 21:11:10 +08:00
|
|
|
|
static int g_bright_applied = 100; /* 上次应用的亮度值(避免重复处理) */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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]; /* 自定义页宿主 */
|
|
|
|
|
|
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 脏矩形渲染(性能优化:只更新变化区域)
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 箭头光标形状(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},
|
2026-07-26 21:07:36 +08:00
|
|
|
|
{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},
|
2026-07-03 21:13:04 +08:00
|
|
|
|
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
|
|
|
|
|
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
|
|
|
|
|
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* 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,青色几何图形)
|
2026-07-03 21:13:04 +08:00
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static void draw_icon_terminal(du_context *ctx, int cx, int cy) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static void draw_icon_editor(du_context *ctx, int cx, int cy) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void draw_icon_folder(du_context *ctx, int cx, int cy) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static void draw_icon_calc(du_context *ctx, int cx, int cy) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
for (int r = 0; r < 3; r++) {
|
|
|
|
|
|
for (int c = 0; c < 3; c++) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void draw_icon_browser(du_context *ctx, int cx, int cy) {
|
|
|
|
|
|
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
|
|
|
|
|
du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2);
|
|
|
|
|
|
int ccx = cx + 20, ccy = cy + 20, r = 11;
|
|
|
|
|
|
draw_circle_outline(ctx, ccx, ccy, r, KS_ACCENT);
|
|
|
|
|
|
du_fill_rect(ctx, ccx - r, ccy, 2 * r + 1, 1, KS_ACCENT);
|
|
|
|
|
|
du_fill_rect(ctx, ccx, ccy - r, 1, 2 * r + 1, KS_ACCENT);
|
|
|
|
|
|
du_pixel(ctx, ccx, ccy, KS_ACCENT2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void draw_app_icon(du_context *ctx, int app_id, int cx, int cy) {
|
|
|
|
|
|
switch (app_id) {
|
|
|
|
|
|
case 0: draw_icon_terminal(ctx, cx, cy); break;
|
|
|
|
|
|
case 1: draw_icon_editor(ctx, cx, cy); break;
|
|
|
|
|
|
case 2: draw_icon_folder(ctx, cx, cy); break;
|
|
|
|
|
|
case 3: draw_icon_calc(ctx, cx, cy); break;
|
|
|
|
|
|
case 4: draw_icon_browser(ctx, cx, cy); break;
|
|
|
|
|
|
default: draw_icon_terminal(ctx, cx, cy); break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 光标管理
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
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]) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_pixel(&g_fb, mx + c, my + r, KS_ACCENT);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* ---- 双缓冲 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];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 非阻塞延时:动画期间持续轮询鼠标,保持光标响应。 */
|
2026-07-28 14:35:14 +08:00
|
|
|
|
__attribute__((unused))
|
2026-07-26 21:07:36 +08:00
|
|
|
|
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()) {
|
2026-07-28 14:35:14 +08:00
|
|
|
|
int old_x = g_cursor_old_x, old_y = g_cursor_old_y;
|
2026-07-26 21:07:36 +08:00
|
|
|
|
cursor_restore_bg();
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_rect(old_x, old_y, CURSOR_SIZE, CURSOR_SIZE);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
cursor_save_bg(g_mouse_x, g_mouse_y);
|
|
|
|
|
|
cursor_draw(g_mouse_x, g_mouse_y);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_rect(g_mouse_x, g_mouse_y, CURSOR_SIZE, CURSOR_SIZE);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 窗口管理
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
if (w->ws != g_ws_cur) continue;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
win->ws = g_ws_cur;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* 直接隐藏窗口(去掉关闭动画以避免卡顿和闪屏) */
|
|
|
|
|
|
w->visible = 0;
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int win_hit_titlebar(desktop_window *w, int mx, int my) {
|
|
|
|
|
|
return mx >= w->x && mx < w->x + w->w &&
|
2026-07-28 12:00:02 +08:00
|
|
|
|
my >= w->y && my < w->y + KATE_TITLEBAR_H;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* P5d: 关闭请求。运行中的 PE 窗口不能直接 win_destroy(run_windowed 阻塞中,
|
|
|
|
|
|
* pump 持有窗口 id)——按 pe_window_host 协作契约 inject WM_CLOSE,
|
|
|
|
|
|
* PE 自愿退出后由 launch_pe_app 收尾销毁。 */
|
|
|
|
|
|
static void win_close_request(desktop_window *w) {
|
|
|
|
|
|
if (g_pe_running && w->id == g_pe_win_id && g_pe_svc && g_pe_svc->inject_input) {
|
|
|
|
|
|
g_pe_svc->inject_input(PE_WM_CLOSE, 0, 0, 0, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
win_destroy(w);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static int win_hit_close_btn(desktop_window *w, int mx, int my) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* 窗口装饰绘制(Winux-Kate 风格)
|
2026-07-03 21:13:04 +08:00
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
static void draw_window_frame(desktop_window *w) {
|
|
|
|
|
|
if (!w->visible || w->minimized) return;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
u32 border = w->focused ? KS_BORDER_FOCUS : KS_BORDER;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
if (w->focused) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_kate_glow_border(&g_fb, w->x, w->y, w->w, w->h, KS_ACCENT);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_fill_rounded_rect(&g_fb, w->x, w->y, w->w, w->h, KS_BG_SECONDARY, 2);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_fill_rect(&g_fb, w->x + 6, w->y + KATE_TITLEBAR_H / 2 - 2, 4, 4, KS_ACCENT2);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_rect_outline(&g_fb, w->x, w->y, w->w, w->h, border, 2);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* PS/2 鼠标处理
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
2026-07-26 21:07:36 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static void ps2_mouse_init(void) {
|
2026-07-26 21:07:36 +08:00
|
|
|
|
ps2_drain();
|
|
|
|
|
|
|
|
|
|
|
|
ps2_wait_write();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
outb(0x64, 0xA8);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
|
|
|
|
|
ps2_wait_write();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
outb(0x64, 0x20);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
ps2_wait_read();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
u8 cfg = inb(0x60);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
cfg &= ~0x20;
|
|
|
|
|
|
cfg |= 0x02;
|
|
|
|
|
|
cfg |= 0x40;
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
|
|
|
|
|
ps2_wait_write();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
outb(0x64, 0x60);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
ps2_wait_write();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
outb(0x60, cfg);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
|
|
|
|
|
ps2_wait_write();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
outb(0x64, 0xD4);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
ps2_wait_write();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
outb(0x60, 0xFF);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
|
|
|
|
if (ps2_wait_read() != 0) break;
|
|
|
|
|
|
inb(0x60);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ps2_wait_write();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
outb(0x64, 0xD4);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
ps2_wait_write();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
outb(0x60, 0xF4);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
slog("mouse init ok");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int ps2_mouse_poll(void) {
|
|
|
|
|
|
u8 st = inb(0x64);
|
|
|
|
|
|
if (!(st & 1)) return 0;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
if (!(st & 0x20)) return 0;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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];
|
2026-07-28 12:00:02 +08:00
|
|
|
|
dy = -dy;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-07-26 21:07:36 +08:00
|
|
|
|
int old_btn = g_mouse_btn;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
2026-07-26 21:07:36 +08:00
|
|
|
|
if (dx == 0 && dy == 0 && g_mouse_btn == old_btn) return 0;
|
2026-08-09 22:14:01 +08:00
|
|
|
|
|
|
|
|
|
|
/* VSCode Phase 2: IDE attached 时把相对位移 + 按钮状态镜像到 Linux guest。
|
|
|
|
|
|
* dy 已在上方取反为屏幕坐标系(正=下),与 Linux REL_Y 约定一致;
|
|
|
|
|
|
* input_forward_mouse 内部与上次状态比较,仅产生变化按钮事件 + SYN。
|
|
|
|
|
|
* 即使宿主光标已抵边界被钳位,原始 dx/dy 仍转发,guest 光标自由移动。 */
|
|
|
|
|
|
if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_mouse) {
|
|
|
|
|
|
g_lxc_svc->input_forward_mouse(dx, dy, (u8)g_mouse_btn);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* VSCode Phase 2: PS/2 set-1 扫描码 → Linux keycode(linux/input-event-codes.h)。
|
|
|
|
|
|
* base : 已去除 0x80 release 位的基础扫描码(0x01..0x39 主键区 + 少数扩展)
|
|
|
|
|
|
* e0 : 上一字节为 0xE0 前缀时置 1,选取扩展键变体
|
|
|
|
|
|
* 返回 0 表示该扫描码无映射(调用方应丢弃)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 巧合:主键区 0x01..0x39 的 Linux keycode 与扫描码数值完全一致
|
|
|
|
|
|
* (IBM XT 布局历史遗留),故直接透传;仅 E0 扩展键需单独查表。 */
|
|
|
|
|
|
static u16 scancode_to_linux_keycode(u8 base, int e0) {
|
|
|
|
|
|
if (e0) {
|
|
|
|
|
|
switch (base) {
|
|
|
|
|
|
case 0x1D: return 97; /* KEY_RIGHTCTRL */
|
|
|
|
|
|
case 0x38: return 100; /* KEY_RIGHTALT */
|
|
|
|
|
|
case 0x48: return 103; /* KEY_UP */
|
|
|
|
|
|
case 0x4B: return 105; /* KEY_LEFT */
|
|
|
|
|
|
case 0x4D: return 106; /* KEY_RIGHT */
|
|
|
|
|
|
case 0x50: return 108; /* KEY_DOWN */
|
|
|
|
|
|
case 0x52: return 110; /* KEY_INSERT */
|
|
|
|
|
|
case 0x53: return 111; /* KEY_DELETE */
|
|
|
|
|
|
case 0x47: return 102; /* KEY_HOME */
|
|
|
|
|
|
case 0x4F: return 107; /* KEY_END */
|
|
|
|
|
|
case 0x49: return 104; /* KEY_PAGEUP */
|
|
|
|
|
|
case 0x51: return 109; /* KEY_PAGEDOWN */
|
|
|
|
|
|
default: return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 主键区:0x01..0x39 透传,超出范围无映射 */
|
|
|
|
|
|
if (base >= 0x01 && base <= 0x39) return (u16)base;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
/* ============================================================
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* 终端 (Bash) — 面板/窗口共用核心
|
2026-07-03 21:13:04 +08:00
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
#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;
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 性能优化:脏行标记(增量渲染) */
|
|
|
|
|
|
u8 dirty_rows[TERM_MAX_ROWS]; /* 标记哪些行需要重绘 */
|
|
|
|
|
|
int any_dirty; /* 是否有任何脏行 */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} bash_state;
|
|
|
|
|
|
|
|
|
|
|
|
static const char *BASH_PROMPT = "deshab# ";
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void bash_reset(bash_state *s, int w, int h) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
for (int i = 0; i < TERM_MAX_CHARS; i++) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
s->ch[i] = ' '; s->fg[i] = KS_TEXT_PRIMARY;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
if (s->cols > TERM_MAX_COLS) s->cols = TERM_MAX_COLS;
|
|
|
|
|
|
if (s->rows > TERM_MAX_ROWS) s->rows = TERM_MAX_ROWS;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
if (s->cols < 10) s->cols = 10;
|
|
|
|
|
|
if (s->rows < 3) s->rows = 3;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
s->cur_col = 0; s->cur_row = 0;
|
|
|
|
|
|
s->input_len = 0; s->input_cursor = 0;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
s->prompt_len = kstrlen(BASH_PROMPT);
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 初始化脏行标记:所有行标记为脏,确保首次渲染 */
|
|
|
|
|
|
for (int i = 0; i < TERM_MAX_ROWS; i++) s->dirty_rows[i] = 1;
|
|
|
|
|
|
s->any_dirty = 1;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void bash_putc(bash_state *s, char c, u32 color) {
|
2026-07-28 21:11:10 +08:00
|
|
|
|
if (c == '\n') {
|
|
|
|
|
|
s->dirty_rows[s->cur_row] = 1; /* 标记当前行为脏 */
|
|
|
|
|
|
s->any_dirty = 1;
|
|
|
|
|
|
s->cur_col = 0; s->cur_row++;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
if (s->cur_row >= s->rows) {
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 滚动:所有行都需要重绘 */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 标记所有行为脏 */
|
|
|
|
|
|
for (int r = 0; r < s->rows; r++) s->dirty_rows[r] = 1;
|
|
|
|
|
|
s->any_dirty = 1;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
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++;
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 标记当前行为脏 */
|
|
|
|
|
|
s->dirty_rows[s->cur_row] = 1;
|
|
|
|
|
|
s->any_dirty = 1;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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++;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, BASH_PROMPT, KS_ACCENT2);
|
|
|
|
|
|
bash_puts(s, cmd, KS_TEXT_PRIMARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
bash_putc(s, '\n', 0);
|
|
|
|
|
|
|
|
|
|
|
|
if (*cmd == 0) return;
|
|
|
|
|
|
if (cmd[0]=='h'&&cmd[1]=='e'&&cmd[2]=='l'&&cmd[3]=='p') {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} 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') {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, "Deshab OS v0.1.0 (Winux-Kate shell)\n", KS_ACCENT2);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} else if (cmd[0]=='u'&&cmd[1]=='n'&&cmd[2]=='a') {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, "Deshab\n", KS_TEXT_PRIMARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} else if (cmd[0]=='a'&&cmd[1]=='b'&&cmd[2]=='o'&&cmd[3]=='u'&&cmd[4]=='t') {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, "Deshab OS - SAS-R0 Kernel\n", KS_ACCENT);
|
|
|
|
|
|
bash_puts(s, "Winux-Kate UI Structure\n", KS_ACCENT2);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} else if (cmd[0]=='e'&&cmd[1]=='c'&&cmd[2]=='h'&&cmd[3]=='o') {
|
|
|
|
|
|
const char *arg = cmd + 4;
|
|
|
|
|
|
while (*arg == ' ') arg++;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, arg, KS_TEXT_PRIMARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
bash_putc(s, '\n', 0);
|
|
|
|
|
|
} else if (cmd[0]=='p'&&cmd[1]=='w'&&cmd[2]=='d') {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, "/\n", KS_TEXT_PRIMARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} else if (cmd[0]=='w'&&cmd[1]=='h'&&cmd[2]=='o') {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, "root\n", KS_TEXT_PRIMARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} else if (cmd[0]=='i'&&cmd[1]=='d') {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, "uid=0(root) gid=0(root)\n", KS_TEXT_PRIMARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} else if (cmd[0]=='r'&&cmd[1]=='e'&&cmd[2]=='b') {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, "Rebooting...\n", KS_ACCENT2);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
outb(0x64, 0xFE);
|
|
|
|
|
|
for(;;) __asm__("hlt");
|
|
|
|
|
|
} else if (cmd[0]=='h'&&cmd[1]=='a'&&cmd[2]=='l'&&cmd[3]=='t') {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, "Halted.\n", KS_ACCENT2);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
for(;;) __asm__("hlt");
|
|
|
|
|
|
} else {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
bash_puts(s, "unknown: ", KS_DANGER);
|
|
|
|
|
|
bash_puts(s, cmd, KS_DANGER);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
bash_putc(s, '\n', 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void bash_key(bash_state *s, u8 sc, int shift) {
|
|
|
|
|
|
if (sc == 0x1C) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
s->input_buf[s->input_len] = 0;
|
|
|
|
|
|
bash_execute(s, s->input_buf);
|
|
|
|
|
|
s->input_len = 0; s->input_cursor = 0;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} else if (sc == 0x0E) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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 {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
char c = scan_to_ascii(sc, shift);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 绘制终端到指定矩形区域(面板或窗口客户区)
|
|
|
|
|
|
* 性能优化:增量渲染,只绘制脏行 */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) {
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 快速退出:终端内容未修改,跳过绘制 */
|
|
|
|
|
|
if (!s->any_dirty) return;
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
for (int r = 0; r < s->rows; r++) {
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 增量渲染:只绘制脏行 */
|
|
|
|
|
|
if (!s->dirty_rows[r]) continue;
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
for (int c = 0; c < s->cols; c++) {
|
|
|
|
|
|
int idx = r * TERM_MAX_COLS + c;
|
|
|
|
|
|
if (s->ch[idx] == ' ') continue;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
2026-07-28 21:11:10 +08:00
|
|
|
|
|
|
|
|
|
|
/* 清除脏行标记 */
|
|
|
|
|
|
s->dirty_rows[r] = 0;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int input_y = cy + (int)DU_SPACE_SM + s->cur_row * (int)DU_ASCII_LINE_H;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-07-28 21:11:10 +08:00
|
|
|
|
|
|
|
|
|
|
/* 清除全局脏标记 */
|
|
|
|
|
|
s->any_dirty = 0;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* ---- 窗口适配 ---- */
|
2026-07-28 14:35:14 +08:00
|
|
|
|
__attribute__((unused))
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-28 14:35:14 +08:00
|
|
|
|
__attribute__((unused))
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-28 14:35:14 +08:00
|
|
|
|
__attribute__((unused))
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
2026-07-28 14:35:14 +08:00
|
|
|
|
__attribute__((unused))
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void bash_on_destroy(void *state) { (void)state; }
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* 文本编辑器 (Editor) — 面板/IDE/窗口共用核心
|
2026-07-03 21:13:04 +08:00
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
#define EDITOR_BUF_SIZE 65536
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
|
char text[EDITOR_BUF_SIZE];
|
|
|
|
|
|
int text_len;
|
|
|
|
|
|
int cursor_pos;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
int scroll_y;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
int cursor_line;
|
|
|
|
|
|
int cursor_col;
|
|
|
|
|
|
int modified;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
int has_file; /* 关联了磁盘文件 */
|
|
|
|
|
|
char name11[11]; /* FAT32 8.3 名 */
|
|
|
|
|
|
char disp[13]; /* 显示名 */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
} 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void editor_reset(editor_state *s, const char *sample) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
s->text_len = 0; s->cursor_pos = 0;
|
|
|
|
|
|
s->scroll_y = 0; s->modified = 0;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
s->has_file = 0;
|
|
|
|
|
|
s->disp[0] = 0;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void editor_key(editor_state *s, u8 sc, int shift) {
|
|
|
|
|
|
if (sc == 0x1C) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} else if (sc == 0x0E) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} else if (sc == 0x48) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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++;
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} 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) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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++;
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} else if (sc == 0x4B) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
if (s->cursor_pos > 0) s->cursor_pos--;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} else if (sc == 0x4D) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
if (s->cursor_pos < s->text_len) s->cursor_pos++;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} else if (sc == 0x47) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
s->cursor_pos = editor_line_start(s, s->cursor_line);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} else if (sc == 0x4F) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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 {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
char c = scan_to_ascii(sc, shift);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 绘制编辑器到指定矩形区域 */
|
|
|
|
|
|
static void editor_draw_to(editor_state *s, int cx, int cy, int cw, int ch) {
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
int visible_rows = (ch - 20) / (int)DU_ASCII_LINE_H;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
int visible_cols = text_area_w / (int)DU_ASCII_STEP;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
if (visible_rows < 1) visible_rows = 1;
|
|
|
|
|
|
if (visible_cols < 4) visible_cols = 4;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
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,
|
2026-07-28 12:00:02 +08:00
|
|
|
|
KS_TEXT_DIM, KS_BG_SECONDARY, DU_ASCII_STEP);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
du_fill_rect(&g_fb, cx + line_num_w - 2, screen_y, 1,
|
2026-07-28 12:00:02 +08:00
|
|
|
|
(int)DU_ASCII_CELL_H, KS_BORDER_DIM);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
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,
|
2026-07-28 12:00:02 +08:00
|
|
|
|
KS_TEXT_PRIMARY, KS_BG_SECONDARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 光标(青色竖条) */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_fill_rect(&g_fb, cursor_x, cursor_y, 2, (int)DU_ASCII_CELL_H, KS_ACCENT);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 底部迷你状态栏 */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
int status_y = cy + ch - 20;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_fill_rect(&g_fb, cx, status_y, cw, 20, KS_BG_TERTIARY);
|
|
|
|
|
|
du_divider_h(&g_fb, cx, status_y, cw, KS_BORDER);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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++; }
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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];
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
status[sp] = 0;
|
|
|
|
|
|
du_draw_string(&g_fb, status, cx + 4, status_y + 1,
|
2026-07-28 12:00:02 +08:00
|
|
|
|
KS_TEXT_DIM, KS_BG_TERTIARY, DU_ASCII_STEP);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* ---- 窗口适配 ---- */
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static void editor_on_destroy(void *state) { (void)state; }
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* 计算器 (Calculator)
|
2026-07-03 21:13:04 +08:00
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
|
char display[32];
|
|
|
|
|
|
int display_len;
|
|
|
|
|
|
i64 accumulator;
|
|
|
|
|
|
i64 current;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
int op;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
calc_state *s = (calc_state *)ADDR_CALC;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
int disp_h = 40;
|
|
|
|
|
|
du_fill_rounded_rect(&g_fb, cx + 8, cy + 8, cw - 16, disp_h,
|
2026-07-28 12:00:02 +08:00
|
|
|
|
KS_BG_PRIMARY, DU_RADIUS_MD);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
du_rect_outline(&g_fb, cx + 8, cy + 8, cw - 16, disp_h,
|
2026-07-28 12:00:02 +08:00
|
|
|
|
KS_ACCENT, DU_RADIUS_MD);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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,
|
2026-07-28 12:00:02 +08:00
|
|
|
|
KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
du_fill_rounded_rect(&g_fb, bx, by, btn_w, btn_h, bg, DU_RADIUS_SM);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_rect_outline(&g_fb, bx, by, btn_w, btn_h, KS_BORDER, DU_RADIUS_SM);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
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,
|
2026-07-28 12:00:02 +08:00
|
|
|
|
fg, bg, DU_ASCII_STEP);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void calc_on_destroy(void *state) { (void)state; }
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 应用注册
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* ---- VSCode Phase 5: LINUXAPP.CNF Linux 应用注册表 ----
|
|
|
|
|
|
* FAT32 根目录 LINUXAPP.CNF(8.3: LINUXAPPCNF),每行一个 Linux guest 应用:
|
|
|
|
|
|
* NAME|DISPLAY|/guest/path|args(args 可含空格,可省略)
|
|
|
|
|
|
* '#' 开头为注释。解析结果存静态池,app_descriptor.linux_path 指入。 */
|
|
|
|
|
|
#define LINUXAPP_MAX 4
|
|
|
|
|
|
static char g_lapp_name[LINUXAPP_MAX][12];
|
|
|
|
|
|
static char g_lapp_disp[LINUXAPP_MAX][16];
|
|
|
|
|
|
static char g_lapp_path[LINUXAPP_MAX][96];
|
|
|
|
|
|
static char g_lapp_args[LINUXAPP_MAX][96];
|
|
|
|
|
|
static int g_vscode_app_id = -1; /* 第一个名为 "vscode" 的注册项 */
|
|
|
|
|
|
|
|
|
|
|
|
/* 字段拷贝:src[0..n) → dst(NUL 结尾),返回拷贝长度 */
|
|
|
|
|
|
static int lapp_field_copy(char *dst, int dst_cap, const char *src, int n) {
|
|
|
|
|
|
if (n >= dst_cap) n = dst_cap - 1;
|
|
|
|
|
|
for (int i = 0; i < n; i++) dst[i] = src[i];
|
|
|
|
|
|
dst[n] = 0;
|
|
|
|
|
|
return n;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void register_linux_apps(void) {
|
|
|
|
|
|
u8 *data = 0;
|
|
|
|
|
|
u32 size = 0;
|
|
|
|
|
|
char n11[11];
|
|
|
|
|
|
if (f32_name_to_83("LINUXAPP.CNF", n11) != 0) return;
|
|
|
|
|
|
if (f32_read_root_file(n11, &data, &size) != 0) return; /* 无配置文件:跳过 */
|
|
|
|
|
|
|
|
|
|
|
|
int li = 0; /* 已注册数量 */
|
|
|
|
|
|
u32 pos = 0;
|
|
|
|
|
|
while (pos < size && li < LINUXAPP_MAX && g_app_count < MAX_APPS) {
|
|
|
|
|
|
/* 取一行 */
|
|
|
|
|
|
u32 eol = pos;
|
|
|
|
|
|
while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++;
|
|
|
|
|
|
u32 line_len = eol - pos;
|
|
|
|
|
|
/* 跳过到下一行 */
|
|
|
|
|
|
u32 next = eol;
|
|
|
|
|
|
while (next < size && (data[next] == '\n' || data[next] == '\r')) next++;
|
|
|
|
|
|
|
|
|
|
|
|
if (line_len == 0 || data[pos] == '#') { pos = next; continue; }
|
|
|
|
|
|
|
|
|
|
|
|
/* 按 '|' 切 4 段:NAME|DISPLAY|PATH|ARGS */
|
|
|
|
|
|
const char *line = (const char *)data + pos;
|
|
|
|
|
|
int f0 = -1, f1 = -1, f2 = -1; /* 三个分隔符位置 */
|
|
|
|
|
|
for (u32 i = 0; i < line_len; i++) {
|
|
|
|
|
|
if (line[i] == '|') {
|
|
|
|
|
|
if (f0 < 0) f0 = (int)i;
|
|
|
|
|
|
else if (f1 < 0) f1 = (int)i;
|
|
|
|
|
|
else if (f2 < 0) { f2 = (int)i; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (f0 > 0 && f1 > f0) {
|
|
|
|
|
|
int path_beg = f1 + 1;
|
|
|
|
|
|
int path_end = (f2 > path_beg) ? f2 : (int)line_len;
|
|
|
|
|
|
int args_beg = (f2 > path_beg) ? f2 + 1 : (int)line_len;
|
|
|
|
|
|
|
|
|
|
|
|
lapp_field_copy(g_lapp_name[li], 12, line, f0);
|
|
|
|
|
|
lapp_field_copy(g_lapp_disp[li], 16, line + f0 + 1, f1 - f0 - 1);
|
|
|
|
|
|
lapp_field_copy(g_lapp_path[li], 96, line + path_beg, path_end - path_beg);
|
|
|
|
|
|
if (args_beg < (int)line_len)
|
|
|
|
|
|
lapp_field_copy(g_lapp_args[li], 96, line + args_beg, (int)line_len - args_beg);
|
|
|
|
|
|
else
|
|
|
|
|
|
g_lapp_args[li][0] = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (g_lapp_path[li][0] == '/') {
|
|
|
|
|
|
g_apps[g_app_count] = (app_descriptor){
|
|
|
|
|
|
g_lapp_name[li], g_lapp_disp[li], 1024, 768,
|
|
|
|
|
|
0, 0, 0, 0, 0,
|
|
|
|
|
|
g_lapp_path[li], g_lapp_args[li]
|
|
|
|
|
|
};
|
|
|
|
|
|
/* 记录 VSCode app id(IDE attach 共享启动路径用)。
|
|
|
|
|
|
* P7.4 匹配收紧:精确匹配 "VSCODE"(第 7 字符须为 NUL),
|
|
|
|
|
|
* VSCODE2 等多实例条目不占用共享启动入口。 */
|
|
|
|
|
|
if (g_vscode_app_id < 0 &&
|
|
|
|
|
|
g_lapp_name[li][0] == 'V' && g_lapp_name[li][1] == 'S' &&
|
|
|
|
|
|
g_lapp_name[li][2] == 'C' && g_lapp_name[li][3] == 'O' &&
|
|
|
|
|
|
g_lapp_name[li][4] == 'D' && g_lapp_name[li][5] == 'E' &&
|
|
|
|
|
|
g_lapp_name[li][6] == 0)
|
|
|
|
|
|
g_vscode_app_id = g_app_count;
|
|
|
|
|
|
g_app_count++;
|
|
|
|
|
|
li++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
pos = next;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- P5d: PEAPPS.CNF Windows PE 应用注册表 ----
|
|
|
|
|
|
* FAT32 根目录 PEAPPS.CNF(8.3: PEAPPS CNF),每行一个 Windows PE 应用:
|
|
|
|
|
|
* NAME|DISPLAY|path/file.exe|args('/' 分隔长路径,args 可含空格可省略)
|
|
|
|
|
|
* '#' 开头为注释。解析结果存静态池,app_descriptor.pe_file 指入。
|
|
|
|
|
|
* 仅适用于 GUI PE(消息循环驱动 pump);纯控制台 PE 不会触发消息空转,
|
|
|
|
|
|
* 窗口模式下桌面将无响应直到进程退出——控制台程序请走 CMD。 */
|
|
|
|
|
|
#define PEAPP_MAX 4
|
|
|
|
|
|
static char g_papp_name[PEAPP_MAX][12];
|
|
|
|
|
|
static char g_papp_disp[PEAPP_MAX][16];
|
|
|
|
|
|
static char g_papp_file[PEAPP_MAX][96];
|
|
|
|
|
|
static char g_papp_args[PEAPP_MAX][96];
|
|
|
|
|
|
|
|
|
|
|
|
static void pe_win_on_draw(void *state, app_ctx *ctx); /* 定义于 P5d 运行时段 */
|
|
|
|
|
|
static void launch_pe_app(int app_id);
|
|
|
|
|
|
|
|
|
|
|
|
static void register_pe_apps(void) {
|
|
|
|
|
|
u8 *data = 0;
|
|
|
|
|
|
u32 size = 0;
|
|
|
|
|
|
char n11[11];
|
|
|
|
|
|
if (f32_name_to_83("PEAPPS.CNF", n11) != 0) return;
|
|
|
|
|
|
if (f32_read_root_file(n11, &data, &size) != 0) return; /* 无配置文件:跳过 */
|
|
|
|
|
|
|
|
|
|
|
|
int pi = 0;
|
|
|
|
|
|
u32 pos = 0;
|
|
|
|
|
|
while (pos < size && pi < PEAPP_MAX && g_app_count < MAX_APPS) {
|
|
|
|
|
|
u32 eol = pos;
|
|
|
|
|
|
while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++;
|
|
|
|
|
|
u32 line_len = eol - pos;
|
|
|
|
|
|
u32 next = eol;
|
|
|
|
|
|
while (next < size && (data[next] == '\n' || data[next] == '\r')) next++;
|
|
|
|
|
|
|
|
|
|
|
|
if (line_len == 0 || data[pos] == '#') { pos = next; continue; }
|
|
|
|
|
|
|
|
|
|
|
|
/* 按 '|' 切 4 段:NAME|DISPLAY|FILE|ARGS */
|
|
|
|
|
|
const char *line = (const char *)data + pos;
|
|
|
|
|
|
int f0 = -1, f1 = -1, f2 = -1;
|
|
|
|
|
|
for (u32 i = 0; i < line_len; i++) {
|
|
|
|
|
|
if (line[i] == '|') {
|
|
|
|
|
|
if (f0 < 0) f0 = (int)i;
|
|
|
|
|
|
else if (f1 < 0) f1 = (int)i;
|
|
|
|
|
|
else if (f2 < 0) { f2 = (int)i; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (f0 > 0 && f1 > f0) {
|
|
|
|
|
|
int file_beg = f1 + 1;
|
|
|
|
|
|
int file_end = (f2 > file_beg) ? f2 : (int)line_len;
|
|
|
|
|
|
int args_beg = (f2 > file_beg) ? f2 + 1 : (int)line_len;
|
|
|
|
|
|
|
|
|
|
|
|
lapp_field_copy(g_papp_name[pi], 12, line, f0);
|
|
|
|
|
|
lapp_field_copy(g_papp_disp[pi], 16, line + f0 + 1, f1 - f0 - 1);
|
|
|
|
|
|
lapp_field_copy(g_papp_file[pi], 96, line + file_beg, file_end - file_beg);
|
|
|
|
|
|
if (args_beg < (int)line_len)
|
|
|
|
|
|
lapp_field_copy(g_papp_args[pi], 96, line + args_beg, (int)line_len - args_beg);
|
|
|
|
|
|
else
|
|
|
|
|
|
g_papp_args[pi][0] = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (g_papp_file[pi][0]) {
|
|
|
|
|
|
g_apps[g_app_count] = (app_descriptor){
|
|
|
|
|
|
g_papp_name[pi], g_papp_disp[pi], 720, 540,
|
|
|
|
|
|
0, 0, pe_win_on_draw, 0, 0, 0, 0,
|
|
|
|
|
|
g_papp_file[pi], g_papp_args[pi]
|
|
|
|
|
|
};
|
|
|
|
|
|
g_app_count++;
|
|
|
|
|
|
pi++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
pos = next;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static void register_apps(void) {
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/
|
2026-07-03 21:13:04 +08:00
|
|
|
|
g_apps[0] = (app_descriptor){
|
2026-07-28 14:35:14 +08:00
|
|
|
|
"shell", "Terminal", 720, 480,
|
2026-08-05 13:15:46 +08:00
|
|
|
|
0, 0, 0, 0, "SHELL ELF"
|
2026-07-03 21:13:04 +08:00
|
|
|
|
};
|
|
|
|
|
|
g_app_count++;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* 命令行2:cmd.elf(Windows 风格命令行 + PE/EXE 执行)*/
|
2026-07-03 21:13:04 +08:00
|
|
|
|
g_apps[1] = (app_descriptor){
|
2026-07-28 14:35:14 +08:00
|
|
|
|
"cmd", "CMD", 720, 480,
|
2026-08-05 13:15:46 +08:00
|
|
|
|
0, 0, 0, 0, "CMD ELF"
|
2026-07-28 14:35:14 +08:00
|
|
|
|
};
|
|
|
|
|
|
g_app_count++;
|
|
|
|
|
|
g_apps[2] = (app_descriptor){
|
2026-07-03 21:13:04 +08:00
|
|
|
|
"editor", "Editor", 600, 450,
|
2026-08-05 13:15:46 +08:00
|
|
|
|
editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy, 0
|
2026-07-03 21:13:04 +08:00
|
|
|
|
};
|
|
|
|
|
|
g_app_count++;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
g_apps[3] = (app_descriptor){
|
2026-07-03 21:13:04 +08:00
|
|
|
|
"fileman", "Files", 500, 400,
|
2026-08-05 13:15:46 +08:00
|
|
|
|
0, 0, 0, 0, "FILEMAN ELF"
|
2026-07-03 21:13:04 +08:00
|
|
|
|
};
|
|
|
|
|
|
g_app_count++;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
g_apps[4] = (app_descriptor){
|
2026-07-03 21:13:04 +08:00
|
|
|
|
"calc", "Calculator", 280, 400,
|
2026-08-05 13:15:46 +08:00
|
|
|
|
calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy, 0
|
2026-07-28 12:00:02 +08:00
|
|
|
|
};
|
|
|
|
|
|
g_app_count++;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
g_apps[5] = (app_descriptor){
|
2026-07-28 12:00:02 +08:00
|
|
|
|
"browser", "Browser", 700, 500,
|
2026-08-05 13:15:46 +08:00
|
|
|
|
0, 0, 0, 0, "BROWSER ELF"
|
|
|
|
|
|
};
|
|
|
|
|
|
g_app_count++;
|
|
|
|
|
|
/* ProEdit — 增强版文本编辑器(语法高亮、设置、目录浏览)*/
|
|
|
|
|
|
g_apps[6] = (app_descriptor){
|
|
|
|
|
|
"proedit", "ProEdit", 750, 550,
|
|
|
|
|
|
0, 0, 0, 0, "PROEDIT ELF"
|
2026-07-03 21:13:04 +08:00
|
|
|
|
};
|
|
|
|
|
|
g_app_count++;
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* Settings — 系统设置(内核 FUCK 配置 + 网络 NETCONF 配置)*/
|
|
|
|
|
|
g_apps[7] = (app_descriptor){
|
|
|
|
|
|
"settings", "Settings", 760, 540,
|
|
|
|
|
|
0, 0, 0, 0, "SETTINGSELF"
|
|
|
|
|
|
};
|
|
|
|
|
|
g_app_count++;
|
|
|
|
|
|
/* VSCode Phase 5: Linux guest 应用(VSCode 等),来自 LINUXAPP.CNF */
|
|
|
|
|
|
register_linux_apps();
|
|
|
|
|
|
/* P5d: Windows PE 应用,来自 PEAPPS.CNF */
|
|
|
|
|
|
register_pe_apps();
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px,
|
2026-07-28 21:11:10 +08:00
|
|
|
|
* 左→右排列,超出画布宽度则换行(align-content: start)
|
|
|
|
|
|
*
|
2026-08-05 13:15:46 +08:00
|
|
|
|
* 注意:Terminal 和 CMD 已在第一页 Dashboard(TERM-01 / TERM-02),
|
|
|
|
|
|
* 桌面不再重复显示其图标。
|
2026-07-28 21:11:10 +08:00
|
|
|
|
*/
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static void setup_desktop_icons(void) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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 右缘 */
|
2026-08-05 13:15:46 +08:00
|
|
|
|
for (int i = 0; i < g_app_count; i++) {
|
2026-08-09 22:14:01 +08:00
|
|
|
|
if (g_icon_count >= MAX_ICONS) break;
|
|
|
|
|
|
/* P7.4: Linux 应用(linux_path)也要出图标——否则 VSCODE 不可见 */
|
|
|
|
|
|
if (!g_apps[i].on_create && !g_apps[i].elf_name &&
|
|
|
|
|
|
!g_apps[i].linux_path && !g_apps[i].pe_file) continue;
|
2026-08-05 13:15:46 +08:00
|
|
|
|
/* 跳过 Terminal 和 CMD(已在 Dashboard 第一页) */
|
|
|
|
|
|
if (g_apps[i].name[0] == 's' && g_apps[i].name[1] == 'h' && g_apps[i].name[2] == 'e') continue;
|
|
|
|
|
|
if (g_apps[i].name[0] == 'c' && g_apps[i].name[1] == 'm' && g_apps[i].name[2] == 'd') continue;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
if (x > pad && x + KATE_ICON_W > max_x) {
|
|
|
|
|
|
x = pad;
|
|
|
|
|
|
y += KATE_ICON_H + KATE_ICON_GAP;
|
|
|
|
|
|
}
|
2026-08-05 13:15:46 +08:00
|
|
|
|
g_icons[g_icon_count].app_id = i;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
g_icons[g_icon_count].x = x;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
g_icons[g_icon_count].y = y;
|
2026-08-05 13:15:46 +08:00
|
|
|
|
g_icons[g_icon_count].label = g_apps[i].display_name;
|
2026-08-04 18:08:08 +08:00
|
|
|
|
g_icons[g_icon_count].selected = 0;
|
|
|
|
|
|
g_icon_count++;
|
|
|
|
|
|
x += KATE_ICON_W + KATE_ICON_GAP;
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 外部 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;
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
static u8 g_elf_image[1048576]; /* 1 MiB:settings.elf 内存映像 ~493KB(中文字体位图 BSS),256KB 不够 */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
|
|
|
|
|
|
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++;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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));
|
2026-08-09 22:14:01 +08:00
|
|
|
|
u64 load_bias = (u64)image - min_vaddr; /* 与 DSK loader 一致 */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
for (u32 j = 0; j < rn; j++) {
|
|
|
|
|
|
u32 type = (u32)(r[j].info & 0xFFFFFFFF);
|
|
|
|
|
|
if (type == DT_R_X86_64_RELATIVE) {
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* 原 BUG: val = min_vaddr + addend,指针被写成低地址绝对值,
|
|
|
|
|
|
* 导致 settings.elf 等模块内 .data 中的字符串指针全部失效 */
|
|
|
|
|
|
if (r[j].offset < min_vaddr || r[j].offset >= min_vaddr + isize) continue;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
u64 target = r[j].offset - min_vaddr;
|
2026-08-09 22:14:01 +08:00
|
|
|
|
u64 val = load_bias + (u64)r[j].addend;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
*(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");
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* 应用启动(单实例聚焦策略)
|
2026-07-03 21:13:04 +08:00
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
static void launch_linux_app(int app_id); /* VSCode Phase 5(定义于 IDE 区) */
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
static void launch_app(int app_id) {
|
|
|
|
|
|
if (app_id < 0 || app_id >= g_app_count) return;
|
|
|
|
|
|
app_descriptor *app = &g_apps[app_id];
|
2026-07-28 12:00:02 +08:00
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* VSCode Phase 5: Linux guest 应用走 exec_async 共享启动路径 */
|
|
|
|
|
|
if (app->linux_path) {
|
|
|
|
|
|
launch_linux_app(app_id);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* P5d: Windows PE 应用走 run_windowed 桌面嵌入路径 */
|
|
|
|
|
|
if (app->pe_file) {
|
|
|
|
|
|
launch_pe_app(app_id);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
if (app->elf_name) {
|
|
|
|
|
|
launch_external_elf(app->elf_name);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
if (!app->on_create) return;
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 单实例:已有同类窗口则聚焦并迁移到当前工作区 */
|
|
|
|
|
|
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;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
fill_app_ctx(&ctx, win);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
win->app_state = app->on_create(&ctx);
|
|
|
|
|
|
win->dirty = 1;
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* 直接显示窗口(去掉打开动画以避免卡顿和闪屏) */
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
slog("app launched");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* FILES 面板(FAT32 真实读写)
|
2026-07-03 21:13:04 +08:00
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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; /* 停止 */
|
2026-07-28 14:35:14 +08:00
|
|
|
|
|
|
|
|
|
|
/* 只显示 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;
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-26 21:07:36 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 双击打开文件 → 载入 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];
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
u8 *data = 0; u32 size = 0;
|
|
|
|
|
|
if (f32_read_root_file(f->name11, &data, &size) != 0) {
|
|
|
|
|
|
slog("file open failed");
|
|
|
|
|
|
return;
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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");
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 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");
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
2026-07-28 12:00:02 +08:00
|
|
|
|
* 通用小绘制
|
2026-07-03 21:13:04 +08:00
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 矩形内居中文字 */
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* 虚线矩形(ide-host / custom host) */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* Boot 屏(打字机)
|
|
|
|
|
|
* ============================================================ */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void busy_delay(u32 cycles) {
|
|
|
|
|
|
for (volatile u32 i = 0; i < cycles; i++) __asm__("pause");
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_buffer();
|
2026-07-28 12:00:02 +08:00
|
|
|
|
|
|
|
|
|
|
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);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_buffer();
|
|
|
|
|
|
busy_delay(2000000);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
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);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_buffer();
|
|
|
|
|
|
busy_delay(2500000);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
|
|
|
|
|
|
/* 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);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_buffer();
|
|
|
|
|
|
busy_delay(125000);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
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);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_buffer();
|
2026-07-28 12:00:02 +08:00
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 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);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_buffer();
|
|
|
|
|
|
busy_delay(10000000);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 顶栏(brand + WorkspaceSwitcher + PageSwitcher + PG 时钟)
|
|
|
|
|
|
* ============================================================ */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 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;
|
|
|
|
|
|
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 页面切换:精简按钮文字 */
|
|
|
|
|
|
static const char *pg_names[3] = { "DASH", "IDE", "DESK" };
|
2026-07-28 14:35:14 +08:00
|
|
|
|
for (int i = 0; i < 3; i++) {
|
2026-07-28 21:11:10 +08:00
|
|
|
|
int bw = kstrlen(pg_names[i]) * (int)DU_ASCII_STEP + 24;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-28 21:11:10 +08:00
|
|
|
|
du_draw_string(&g_fb, pg_names[i], x + 12, ty,
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
2026-07-28 21:11:10 +08:00
|
|
|
|
x += bw + 8;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
/* 自定义页 */
|
|
|
|
|
|
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);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
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;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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 */
|
2026-07-28 14:35:14 +08:00
|
|
|
|
const char *hint = "Ctrl+Tab PAGE";
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
2026-08-05 13:15:46 +08:00
|
|
|
|
draw_app_icon(&g_fb, ic->app_id, ix, iy);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- 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;
|
2026-07-28 21:11:10 +08:00
|
|
|
|
int left_w = avail_w * 35 / 100; /* 终端面板:42%→35% */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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];
|
2026-07-28 14:35:14 +08:00
|
|
|
|
kstrcpy(path_buf, "FAT32:/USER (", 32);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
2026-08-09 22:14:01 +08:00
|
|
|
|
static void page_switch(int p); /* 定义于工作区段,launch_linux_app 前向引用 */
|
|
|
|
|
|
|
|
|
|
|
|
/* VSCode Phase 6: guest GUI 状态机。
|
|
|
|
|
|
* 0 = 未运行(IDE host 显示启动提示)
|
|
|
|
|
|
* 1 = 启动中(X server/VSCode spawn 已发出,等 scanout 上线)
|
|
|
|
|
|
* 2 = 运行中(scanout enabled,blit guest 帧) */
|
|
|
|
|
|
static int g_vscode_state = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/* P8.1: VSCode 启动插桩--分阶段状态跟踪 + 串口日志 + 屏幕显示。
|
|
|
|
|
|
* g_launch_phase: 0=idle 1=X启动 2=X socket等待 3=app spawn 4=pgrep验证 5=等scanout 6=running 7=失败
|
|
|
|
|
|
* g_launch_status: 屏幕显示的状态字符串(IDE host 区域) */
|
|
|
|
|
|
static int g_launch_phase = 0;
|
|
|
|
|
|
static char g_launch_status[64] = "";
|
|
|
|
|
|
static u64 g_launch_tsc = 0; /* 启动开始 TSC(测各阶段耗时) */
|
|
|
|
|
|
static int g_launch_exec_rc = 0; /* exec_async 返回码 */
|
|
|
|
|
|
static int g_launch_pgrep_rc = -1; /* pgrep 验证结果(0=找到, 1=未找到)*/
|
|
|
|
|
|
|
|
|
|
|
|
/* P7.4 多实例:IDE tab 动态标签——显示最近启动实例的 DISPLAY 名
|
|
|
|
|
|
* (单 X :0 共享 scanout,多 VSCode 进程同屏;标签指示最后 attach 者)。 */
|
|
|
|
|
|
static char g_ide_tab_label[16] = "VSCODE";
|
|
|
|
|
|
|
|
|
|
|
|
/* P7.9: 性能 profiling(TSC 计时,每秒更新 FPS 显示)。
|
|
|
|
|
|
* 在 IDE 页面叠加显示 FPS / blit 耗时 / dirty 跳过率。 */
|
|
|
|
|
|
static u64 g_prof_frames = 0; /* 自上次 FPS 更新以来的帧数 */
|
|
|
|
|
|
static u64 g_prof_fps_tsc = 0; /* 上次 FPS 计算的 TSC */
|
|
|
|
|
|
static int g_prof_fps = 0; /* 当前 FPS(每秒更新) */
|
|
|
|
|
|
static u64 g_prof_blit_total = 0; /* 累计 blit TSC */
|
|
|
|
|
|
static u64 g_prof_blit_count = 0; /* blit 调用次数 */
|
|
|
|
|
|
static u64 g_prof_blit_skip = 0; /* dirty=0 跳过的次数 */
|
|
|
|
|
|
static u64 g_prof_blit_full = 0; /* full repaint 次数 */
|
|
|
|
|
|
static int g_prof_last_blit_us = 0; /* 上次 blit 耗时(微秒) */
|
|
|
|
|
|
|
|
|
|
|
|
/* P7.3: attach/detach 时使 scanout 缓存失效并请求全量重绘(定义于 blit 区) */
|
|
|
|
|
|
static void ide_attach_invalidate(void);
|
|
|
|
|
|
|
|
|
|
|
|
/* VSCode Phase 3: IDE attach 时在 Linux guest 中启动 X server(Xfbdev)。
|
|
|
|
|
|
* Xfbdev 通过 virtio-gpu 2D 设备合成帧到 surface pool scanout 影子缓冲,
|
|
|
|
|
|
* desktop 每帧 blit 到 IDE host 区域。exec 用 sh -c "... &" 后台启动,
|
|
|
|
|
|
* shell 立即退出被 daemon 回收,Xfbdev 由 init 收养继续运行。
|
|
|
|
|
|
* Phase 6: pgrep 守卫保证幂等(多实例/重复 attach 不重起 X)。 */
|
|
|
|
|
|
/* P8.1: slog + 数字(用于 rc/ec 日志,负数正确显示前导 -) */
|
|
|
|
|
|
static void slog_num(const char *prefix, int rc) {
|
|
|
|
|
|
char buf[48];
|
|
|
|
|
|
kstrcpy(buf, prefix, 48);
|
|
|
|
|
|
int n = kstrlen(buf);
|
|
|
|
|
|
if (rc < 0) { buf[n++] = '-'; rc = -rc; }
|
|
|
|
|
|
u32dec((u32)rc, buf + n);
|
|
|
|
|
|
slog(buf);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void ide_launch_guest_x(void) {
|
|
|
|
|
|
if (!g_lxc_svc || !g_lxc_svc->exec) { slog("ide: lxc exec unavailable"); return; }
|
|
|
|
|
|
slog("[launch] phase 1: launching Xfbdev");
|
|
|
|
|
|
g_launch_phase = 1;
|
|
|
|
|
|
kstrcpy(g_launch_status, "LAUNCHING X SERVER . . .", 64);
|
|
|
|
|
|
const char *argv[] = {
|
|
|
|
|
|
"sh", "-c",
|
|
|
|
|
|
"pgrep -x Xfbdev >/dev/null 2>&1 || "
|
|
|
|
|
|
"Xfbdev :0 -geometry 1024x768 -depth 32 >/dev/null 2>&1 &"
|
|
|
|
|
|
};
|
|
|
|
|
|
u64 exit_code = 0;
|
|
|
|
|
|
int rc = g_lxc_svc->exec("/bin/sh", 3, argv, 0, 0, 0, &exit_code);
|
|
|
|
|
|
slog_num("[launch] Xfbdev exec rc=", rc);
|
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
|
slog("[launch] WARNING: Xfbdev launch rc!=0 (may still be OK if already running)");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* P8.1 Fix: lxc guest ping -- exec "echo OK" 快速检测 Linux guest 是否运行。
|
|
|
|
|
|
* 返回 0=guest 可用,非 0=不可用(VMX 未启用/guest 未 park/IPC 不通)。 */
|
|
|
|
|
|
static int lxc_guest_ping(void) {
|
|
|
|
|
|
if (!g_lxc_svc || !g_lxc_svc->exec) return -1;
|
|
|
|
|
|
const char *pargv[] = { "sh", "-c", "echo OK" };
|
|
|
|
|
|
char out[8] = {0};
|
|
|
|
|
|
u64 ec = 0;
|
|
|
|
|
|
int rc = g_lxc_svc->exec("/bin/sh", 3, pargv, out, 7, 0, &ec);
|
|
|
|
|
|
if (rc != 0) return rc;
|
|
|
|
|
|
if (out[0] == 'O' && out[1] == 'K') return 0;
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* VSCode Phase 6: 共享 guest GUI 启动路径(IDE +NEW/双击 host 与桌面图标共用)。
|
|
|
|
|
|
* 首次调用确保 X server 在线(幂等启动 + 有界等待 X socket),然后
|
|
|
|
|
|
* exec_async 目标程序(daemon fork+setsid+detach,立即返回)。
|
|
|
|
|
|
* 后续调用(多实例)跳过 X 准备直接 spawn。path 为 NULL 时仅确保 X 在线。
|
|
|
|
|
|
* P8.1 Fix: 启动前 ping 检测 guest 可用性;失败时快速短路,不浪费 5s X socket 等待。 */
|
|
|
|
|
|
static void guest_gui_launch(const char *path, int argc, const char *const *argv) {
|
|
|
|
|
|
if (!g_lxc_svc) {
|
|
|
|
|
|
slog("[launch] lxc service null");
|
|
|
|
|
|
g_launch_phase = 7;
|
|
|
|
|
|
kstrcpy(g_launch_status, "LXC SERVICE UNAVAILABLE", 64);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (g_vscode_state == 0) {
|
|
|
|
|
|
g_vscode_state = 1; /* 启动中 */
|
|
|
|
|
|
g_launch_tsc = rdtsc();
|
|
|
|
|
|
slog("[launch] === guest GUI launch sequence start ===");
|
|
|
|
|
|
|
|
|
|
|
|
/* P8.1 Fix: Phase 0 - ping guest 检测可用性 */
|
|
|
|
|
|
g_launch_phase = 0;
|
|
|
|
|
|
kstrcpy(g_launch_status, "CHECKING GUEST . . .", 64);
|
|
|
|
|
|
int prc = lxc_guest_ping();
|
|
|
|
|
|
slog_num("[launch] guest ping rc=", prc);
|
|
|
|
|
|
if (prc != 0) {
|
|
|
|
|
|
slog("[launch] FAIL: Linux guest not running (VMX/guest unavailable)");
|
|
|
|
|
|
g_vscode_state = 0; /* 回到未启动状态 */
|
|
|
|
|
|
g_launch_phase = 7;
|
|
|
|
|
|
kstrcpy(g_launch_status, "GUEST NOT RUNNING - NEED KVM/VMX", 64);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
slog("[launch] guest ping OK - Linux guest is running");
|
|
|
|
|
|
|
|
|
|
|
|
/* Phase 1: launch X server */
|
|
|
|
|
|
ide_launch_guest_x();
|
|
|
|
|
|
|
|
|
|
|
|
/* Phase 2: wait for X socket (bounded ~5s) */
|
|
|
|
|
|
g_launch_phase = 2;
|
|
|
|
|
|
kstrcpy(g_launch_status, "WAITING FOR X SOCKET . . .", 64);
|
|
|
|
|
|
slog("[launch] phase 2: waiting for X socket");
|
|
|
|
|
|
if (g_lxc_svc->exec) {
|
|
|
|
|
|
const char *wargv[] = {
|
|
|
|
|
|
"sh", "-c",
|
|
|
|
|
|
"i=0; while [ ! -S /tmp/.X11-unix/X0 ] && [ $i -lt 25 ]; "
|
|
|
|
|
|
"do sleep 0.2; i=$((i+1)); done; [ -S /tmp/.X11-unix/X0 ] && echo OK || echo FAIL"
|
|
|
|
|
|
};
|
|
|
|
|
|
/* 用 exec + stdout 捕获验证 X socket 是否就绪 */
|
|
|
|
|
|
char xout[16] = {0};
|
|
|
|
|
|
u64 ec = 0;
|
|
|
|
|
|
int rc = g_lxc_svc->exec("/bin/sh", 3, wargv, xout, 15, 0, &ec);
|
|
|
|
|
|
slog_num("[launch] X socket wait rc=", rc);
|
|
|
|
|
|
if (xout[0] == 'O' && xout[1] == 'K') {
|
|
|
|
|
|
slog("[launch] X socket READY (/tmp/.X11-unix/X0 exists)");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
slog("[launch] WARNING: X socket NOT ready after ~5s timeout");
|
|
|
|
|
|
kstrcpy(g_launch_status, "X SOCKET TIMEOUT - RETRYING SPAWN", 64);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Phase 3: exec_async spawn the app */
|
|
|
|
|
|
if (path && g_lxc_svc->exec_async) {
|
|
|
|
|
|
g_launch_phase = 3;
|
|
|
|
|
|
kstrcpy(g_launch_status, "SPAWNING APPLICATION . . .", 64);
|
|
|
|
|
|
slog("[launch] phase 3: exec_async spawn");
|
|
|
|
|
|
slog(path);
|
|
|
|
|
|
int rc = g_lxc_svc->exec_async(path, argc, argv);
|
|
|
|
|
|
g_launch_exec_rc = rc;
|
|
|
|
|
|
slog_num("[launch] exec_async rc=", rc);
|
|
|
|
|
|
if (rc == 0) {
|
|
|
|
|
|
slog("[launch] exec_async OK - process spawned");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
slog("[launch] exec_async FAILED - process not spawned");
|
|
|
|
|
|
g_vscode_state = 0; /* P8.1 Fix: 回到未启动,UI 显示错误而非永远 STARTING */
|
|
|
|
|
|
g_launch_phase = 7;
|
|
|
|
|
|
kstrcpy(g_launch_status, "SPAWN FAILED - CHECK SERIAL LOG", 64);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Phase 4: pgrep verify process exists (bounded wait ~1s) */
|
|
|
|
|
|
g_launch_phase = 4;
|
|
|
|
|
|
kstrcpy(g_launch_status, "VERIFYING PROCESS . . .", 64);
|
|
|
|
|
|
slog("[launch] phase 4: pgrep verify");
|
|
|
|
|
|
if (g_lxc_svc->exec) {
|
|
|
|
|
|
/* 用 basename 做 pgrep(code -> pgrep code) */
|
|
|
|
|
|
const char *pargv[] = {
|
|
|
|
|
|
"sh", "-c",
|
|
|
|
|
|
"sleep 0.5; pgrep -x code >/dev/null 2>&1 && echo FOUND || echo NOPE"
|
|
|
|
|
|
};
|
|
|
|
|
|
char pout[16] = {0};
|
|
|
|
|
|
u64 pec = 0;
|
|
|
|
|
|
int prc = g_lxc_svc->exec("/bin/sh", 3, pargv, pout, 15, 0, &pec);
|
|
|
|
|
|
if (pout[0] == 'F') {
|
|
|
|
|
|
g_launch_pgrep_rc = 0;
|
|
|
|
|
|
slog("[launch] pgrep: VSCode process FOUND");
|
|
|
|
|
|
} else if (pout[0] == 'N') {
|
|
|
|
|
|
g_launch_pgrep_rc = 1;
|
|
|
|
|
|
slog("[launch] pgrep: VSCode process NOT found (may still be starting)");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
g_launch_pgrep_rc = -1;
|
|
|
|
|
|
slog_num("[launch] pgrep: no output, exec rc=", prc);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Phase 5: wait for scanout */
|
|
|
|
|
|
g_launch_phase = 5;
|
|
|
|
|
|
kstrcpy(g_launch_status, "WAITING FOR SCANOUT . . .", 64);
|
|
|
|
|
|
slog("[launch] phase 5: waiting for scanout to come online");
|
|
|
|
|
|
} else if (path && !g_lxc_svc->exec_async) {
|
|
|
|
|
|
slog("[launch] exec_async unavailable - cannot spawn app");
|
|
|
|
|
|
g_launch_phase = 7;
|
|
|
|
|
|
kstrcpy(g_launch_status, "EXEC_ASYNC UNAVAILABLE", 64);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* VSCode Phase 5/6: 桌面图标启动 Linux 应用(LINUXAPP.CNF 注册项)。
|
|
|
|
|
|
* 解析 args(空格分隔)后经共享路径 exec_async,并切到 IDE 页 attach。 */
|
|
|
|
|
|
static void launch_linux_app(int app_id) {
|
|
|
|
|
|
if (app_id < 0 || app_id >= g_app_count) return;
|
|
|
|
|
|
app_descriptor *app = &g_apps[app_id];
|
|
|
|
|
|
if (!app->linux_path) return;
|
|
|
|
|
|
if (!g_lxc_svc || !g_lxc_svc->exec_async) {
|
|
|
|
|
|
slog("linuxapp: lxc service unavailable");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* argv: [name, ...args](args 在本地缓冲内原地切分) */
|
|
|
|
|
|
const char *argv[16];
|
|
|
|
|
|
int argc = 0;
|
|
|
|
|
|
argv[argc++] = app->name;
|
|
|
|
|
|
char abuf[96];
|
|
|
|
|
|
if (app->linux_args && app->linux_args[0]) {
|
|
|
|
|
|
int n = kstrlen(app->linux_args);
|
|
|
|
|
|
if (n > (int)sizeof(abuf) - 1) n = (int)sizeof(abuf) - 1;
|
|
|
|
|
|
for (int i = 0; i < n; i++) abuf[i] = app->linux_args[i];
|
|
|
|
|
|
abuf[n] = 0;
|
|
|
|
|
|
char *p = abuf;
|
|
|
|
|
|
while (*p && argc < 15) {
|
|
|
|
|
|
while (*p == ' ') p++;
|
|
|
|
|
|
if (!*p) break;
|
|
|
|
|
|
argv[argc++] = p;
|
|
|
|
|
|
while (*p && *p != ' ') p++;
|
|
|
|
|
|
if (*p) *p++ = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
guest_gui_launch(app->linux_path, argc, argv);
|
|
|
|
|
|
|
|
|
|
|
|
/* P7.4: tab 标签跟随最近启动实例(DISPLAY 名,≤15 字符) */
|
|
|
|
|
|
{
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
const char *dn = app->display_name;
|
|
|
|
|
|
while (dn && dn[i] && i + 1 < (int)sizeof(g_ide_tab_label)) {
|
|
|
|
|
|
g_ide_tab_label[i] = dn[i];
|
|
|
|
|
|
i++;
|
|
|
|
|
|
}
|
|
|
|
|
|
g_ide_tab_label[i] = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 切到 IDE 页并 attach(VSCode 窗口出现在 IDE host 区域) */
|
|
|
|
|
|
g_ide_attached = 1;
|
|
|
|
|
|
g_input_forward_enabled = 1;
|
|
|
|
|
|
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
|
|
|
|
|
page_switch(2);
|
|
|
|
|
|
slog("linuxapp launched -> ide attached");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* VSCode Phase 6: IDE attach 共享入口——启动(或复用)VSCode。 */
|
|
|
|
|
|
static void vscode_launch(void) {
|
|
|
|
|
|
if (g_vscode_app_id >= 0) {
|
|
|
|
|
|
launch_linux_app(g_vscode_app_id);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 未注册 VSCode(CNF 缺失):仅确保 X server 在线,保持 Phase 3 行为 */
|
|
|
|
|
|
guest_gui_launch(0, 0, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* VSCode Phase 3/7: 把 virtio-gpu scanout 影子缓冲 blit 到 IDE host 区域。
|
|
|
|
|
|
* scanout 为 1024×768 XRGB8888(stride=4096),与 framebuffer 同为 32bpp。
|
|
|
|
|
|
* Phase 7 dirty rect 优化:主循环每帧把 scanout 状态缓存进 g_so(查询会
|
|
|
|
|
|
* 清零 UTSM 侧 dirty),blit 只拷贝累积 dirty rect(scanout 坐标系并集),
|
|
|
|
|
|
* sprite 双缓冲持久保留未变区域;attach/首帧/状态切换经 g_so_full_repaint
|
|
|
|
|
|
* 全量重绘一次。 */
|
|
|
|
|
|
static struct linux_compat_scanout_info g_so;
|
|
|
|
|
|
static int g_so_valid = 0; /* 1 = g_so 为本帧主循环 poll 的缓存 */
|
|
|
|
|
|
static int g_so_full_repaint = 1; /* 1 = 下次 blit 全量拷贝(初始/attach/上线) */
|
|
|
|
|
|
|
|
|
|
|
|
/* attach/detach 时使 scanout 缓存失效并请求全量重绘 */
|
|
|
|
|
|
static void ide_attach_invalidate(void) {
|
|
|
|
|
|
g_so_full_repaint = 1;
|
|
|
|
|
|
g_so_valid = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 获取 scanout 状态:优先本帧缓存;缓存无效时查询并缓存。 */
|
|
|
|
|
|
static const struct linux_compat_scanout_info *ide_scanout_state(void) {
|
|
|
|
|
|
if (g_so_valid) return &g_so;
|
|
|
|
|
|
if (g_lxc_svc && g_lxc_svc->gpu_get_scanout_info &&
|
|
|
|
|
|
g_lxc_svc->gpu_get_scanout_info(&g_so) == 0) {
|
|
|
|
|
|
g_so_valid = 1;
|
|
|
|
|
|
return &g_so;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void blit_scanout_to_ide(int hx, int hy, int hw, int hh) {
|
|
|
|
|
|
const struct linux_compat_scanout_info *si = ide_scanout_state();
|
|
|
|
|
|
if (!si || !si->enabled || !si->host_vaddr) {
|
|
|
|
|
|
/* scanout 未就绪:X server 尚未 SET_SCANOUT,显示等待提示 */
|
|
|
|
|
|
du_fill_rect(&g_fb, hx, hy, hw, hh, 0xFF051828u);
|
|
|
|
|
|
draw_centered("WAITING FOR GUEST X SERVER . . .",
|
|
|
|
|
|
hx + hw / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int sw = (int)si->width, sh = (int)si->height;
|
|
|
|
|
|
int sstride = (int)si->stride; /* bytes/row */
|
|
|
|
|
|
int dstride = (int)g_fb_pitch; /* bytes/row */
|
|
|
|
|
|
u8 *src = (u8 *)si->host_vaddr;
|
|
|
|
|
|
u8 *dst_base = (u8 *)g_fb.fb;
|
|
|
|
|
|
|
|
|
|
|
|
/* 拷贝区域:默认仅累积 dirty rect;full repaint 时整帧 */
|
|
|
|
|
|
int full = g_so_full_repaint;
|
|
|
|
|
|
int cx = 0, cy = 0, cw = sw, ch = sh;
|
|
|
|
|
|
if (!full) {
|
|
|
|
|
|
if (!si->dirty || si->dirty_w == 0 || si->dirty_h == 0) {
|
|
|
|
|
|
g_prof_blit_skip++; /* P7.9: dirty=0 零拷贝跳过 */
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
cx = (int)si->dirty_x; cy = (int)si->dirty_y;
|
|
|
|
|
|
cw = (int)si->dirty_w; ch = (int)si->dirty_h;
|
|
|
|
|
|
}
|
|
|
|
|
|
g_so_full_repaint = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/* P7.9: TSC 计时 blit */
|
|
|
|
|
|
u64 _tsc0 = rdtsc();
|
|
|
|
|
|
if (full) g_prof_blit_full++;
|
|
|
|
|
|
|
|
|
|
|
|
/* clamp 到 host 区域与 scanout 边界 */
|
|
|
|
|
|
if (cx + cw > hw) cw = hw - cx;
|
|
|
|
|
|
if (cy + ch > hh) ch = hh - cy;
|
|
|
|
|
|
if (cw > sw - cx) cw = sw - cx;
|
|
|
|
|
|
if (ch > sh - cy) ch = sh - cy;
|
|
|
|
|
|
if (cw <= 0 || ch <= 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
for (int row = 0; row < ch; row++) {
|
|
|
|
|
|
u32 *s = (u32 *)(src + (u64)(cy + row) * sstride + (u64)cx * 4);
|
|
|
|
|
|
u32 *d = (u32 *)(dst_base + (u64)(hy + cy + row) * dstride
|
|
|
|
|
|
+ (u64)(hx + cx) * 4);
|
|
|
|
|
|
for (int col = 0; col < cw; col++) d[col] = s[col];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 全量重绘时,scanout 未覆盖的 host 剩余区域填深色背景 */
|
|
|
|
|
|
if (full) {
|
|
|
|
|
|
int cov_w = (hw < sw) ? hw : sw;
|
|
|
|
|
|
int cov_h = (hh < sh) ? hh : sh;
|
|
|
|
|
|
if (cov_w < hw)
|
|
|
|
|
|
du_fill_rect(&g_fb, hx + cov_w, hy, hw - cov_w, cov_h, 0xFF051828u);
|
|
|
|
|
|
if (cov_h < hh)
|
|
|
|
|
|
du_fill_rect(&g_fb, hx, hy + cov_h, hw, hh - cov_h, 0xFF051828u);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* P7.6: 叠加 guest 硬件光标(ARGB alpha-blend 到 framebuffer)。
|
|
|
|
|
|
* 光标位图 row stride = 64(virtio-gpu 规范 cursor max 64x64)。
|
|
|
|
|
|
* 坐标从 scanout 坐标系映射到 framebuffer:(hx + cursor_x - hot_x, ...) */
|
|
|
|
|
|
if (si->cursor_visible && si->cursor_bitmap) {
|
|
|
|
|
|
int cw = (int)si->cursor_w, ch = (int)si->cursor_h;
|
|
|
|
|
|
if (cw > 0 && ch > 0) {
|
|
|
|
|
|
int fx = hx + (int)si->cursor_x - (int)si->cursor_hot_x;
|
|
|
|
|
|
int fy = hy + (int)si->cursor_y - (int)si->cursor_hot_y;
|
|
|
|
|
|
u32 *cbmp = (u32 *)si->cursor_bitmap;
|
|
|
|
|
|
for (int row = 0; row < ch; row++) {
|
|
|
|
|
|
int dy = fy + row;
|
|
|
|
|
|
if (dy < 0 || (u64)dy >= g_fb_h) continue;
|
|
|
|
|
|
u32 *d = (u32 *)((u8 *)g_fb.fb + (u64)dy * (u64)g_fb_pitch);
|
|
|
|
|
|
const u32 *s = &cbmp[row * 64]; /* row stride = VGPU_CURSOR_MAX */
|
|
|
|
|
|
for (int col = 0; col < cw; col++) {
|
|
|
|
|
|
int dx = fx + col;
|
|
|
|
|
|
if (dx < 0 || (u64)dx >= g_fb_w) continue;
|
|
|
|
|
|
u32 src = s[col];
|
|
|
|
|
|
u32 a = (src >> 24) & 0xFF;
|
|
|
|
|
|
if (a == 0) continue;
|
|
|
|
|
|
if (a == 255) { d[dx] = src; continue; }
|
|
|
|
|
|
u32 dst = d[dx];
|
|
|
|
|
|
u32 rr = (((src >> 16) & 0xFF) * a + ((dst >> 16) & 0xFF) * (255 - a)) / 255;
|
|
|
|
|
|
u32 gg = (((src >> 8) & 0xFF) * a + ((dst >> 8) & 0xFF) * (255 - a)) / 255;
|
|
|
|
|
|
u32 bb = ( (src & 0xFF) * a + ( dst & 0xFF) * (255 - a)) / 255;
|
|
|
|
|
|
d[dx] = 0xFF000000u | (rr << 16) | (gg << 8) | bb;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* P7.9: 记录 blit 耗时 */
|
|
|
|
|
|
g_prof_blit_total += rdtsc() - _tsc0;
|
|
|
|
|
|
g_prof_blit_count++;
|
|
|
|
|
|
if (g_tsc_per_sec)
|
|
|
|
|
|
g_prof_last_blit_us = (int)((rdtsc() - _tsc0) * 1000000 / g_tsc_per_sec);
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* 实例标签(Kate .ide-tab):P7.4 动态标签 = 最近启动实例 DISPLAY 名 */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
{
|
2026-08-09 22:14:01 +08:00
|
|
|
|
const char *label = g_ide_tab_label;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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) {
|
2026-08-09 22:14:01 +08:00
|
|
|
|
if (g_input_forward_enabled) {
|
|
|
|
|
|
/* VSCode Phase 6 状态机:启动中探测 scanout 上线 → 运行中 */
|
|
|
|
|
|
if (g_vscode_state == 1) {
|
|
|
|
|
|
const struct linux_compat_scanout_info *si = ide_scanout_state();
|
|
|
|
|
|
if (si && si->enabled) {
|
|
|
|
|
|
g_vscode_state = 2;
|
|
|
|
|
|
g_launch_phase = 6;
|
|
|
|
|
|
g_so_full_repaint = 1; /* 上线首帧全量重绘 */
|
|
|
|
|
|
/* P8.1: scanout 上线日志 + 耗时 */
|
|
|
|
|
|
slog("[launch] phase 6: SCANOUT ONLINE");
|
|
|
|
|
|
if (g_tsc_per_sec && g_launch_tsc) {
|
|
|
|
|
|
u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec;
|
|
|
|
|
|
slog_num("[launch] total launch time ms=", (int)ms);
|
|
|
|
|
|
}
|
|
|
|
|
|
{
|
|
|
|
|
|
char sbuf[48];
|
|
|
|
|
|
kstrcpy(sbuf, "scanout: ", 48);
|
|
|
|
|
|
u32dec(si->width, sbuf + kstrlen(sbuf));
|
|
|
|
|
|
kstrcat(sbuf, "x", 48);
|
|
|
|
|
|
u32dec(si->height, sbuf + kstrlen(sbuf));
|
|
|
|
|
|
slog(sbuf);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (g_vscode_state == 2) {
|
|
|
|
|
|
/* 运行中:blit guest 合成帧 */
|
|
|
|
|
|
blit_scanout_to_ide(hx + 2, hy + 2, hw - 4, hh - 4);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
/* P8.1: 启动中--显示分阶段状态 + 详细信息 */
|
|
|
|
|
|
du_fill_rect(&g_fb, hx + 2, hy + 2, hw - 4, hh - 4, 0xFF051828u);
|
|
|
|
|
|
draw_centered(g_launch_status[0] ? g_launch_status :
|
|
|
|
|
|
"STARTING VSCODE . . .",
|
|
|
|
|
|
W / 2, hy + hh / 2 - 20, KS_TEXT_DIM, 0);
|
|
|
|
|
|
/* 详细信息行(phase/exec_rc/pgrep/耗时) */
|
|
|
|
|
|
{
|
|
|
|
|
|
char dbuf[80];
|
|
|
|
|
|
int dp = 0;
|
|
|
|
|
|
kstrcpy(dbuf + dp, "PHASE:", 80); dp = kstrlen(dbuf);
|
|
|
|
|
|
dp += u32dec((u32)g_launch_phase, dbuf + dp);
|
|
|
|
|
|
if (g_launch_exec_rc != 0) {
|
|
|
|
|
|
kstrcpy(dbuf + dp, " RC:", 80); dp = kstrlen(dbuf);
|
|
|
|
|
|
dp += u32dec((u32)(g_launch_exec_rc < 0 ?
|
|
|
|
|
|
(u32)(-g_launch_exec_rc) : (u32)g_launch_exec_rc),
|
|
|
|
|
|
dbuf + dp);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (g_launch_pgrep_rc >= 0) {
|
|
|
|
|
|
kstrcpy(dbuf + dp, " PGREP:", 80); dp = kstrlen(dbuf);
|
|
|
|
|
|
kstrcpy(dbuf + dp, g_launch_pgrep_rc == 0 ? "FOUND" : "MISSING",
|
|
|
|
|
|
80);
|
|
|
|
|
|
dp = kstrlen(dbuf);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (g_tsc_per_sec && g_launch_tsc) {
|
|
|
|
|
|
u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec;
|
|
|
|
|
|
kstrcpy(dbuf + dp, " T:", 80); dp = kstrlen(dbuf);
|
|
|
|
|
|
dp += u32dec((u32)ms, dbuf + dp);
|
|
|
|
|
|
kstrcpy(dbuf + dp, "ms", 80);
|
|
|
|
|
|
}
|
|
|
|
|
|
draw_centered(dbuf, W / 2, hy + hh / 2 + 4, KS_TEXT_DIM, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
editor_draw_to((editor_state *)ADDR_EDDASH, hx + 2, hy + 2, hw - 4, hh - 4);
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
/* embed-empty(Kate:未启动 VSCode 提示) */
|
2026-08-09 22:14:01 +08:00
|
|
|
|
draw_centered("VSCODE NOT RUNNING . DOUBLE-CLICK OR + NEW IDE TO START",
|
2026-07-28 12:00:02 +08:00
|
|
|
|
W / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0);
|
|
|
|
|
|
}
|
2026-08-09 22:14:01 +08:00
|
|
|
|
|
|
|
|
|
|
/* P7.9: 性能 profiling 叠加(IDE 页面左下角) */
|
|
|
|
|
|
if (g_ide_attached && g_input_forward_enabled) {
|
|
|
|
|
|
char buf[80];
|
|
|
|
|
|
int p = 0;
|
|
|
|
|
|
kstrcpy(buf + p, "FPS:", 80 - p); p = kstrlen(buf);
|
|
|
|
|
|
p += u32dec((u32)g_prof_fps, buf + p);
|
|
|
|
|
|
kstrcpy(buf + p, " BLT:", 80 - p); p = kstrlen(buf);
|
|
|
|
|
|
p += u32dec((u32)g_prof_last_blit_us, buf + p);
|
|
|
|
|
|
kstrcpy(buf + p, "us SKP:", 80 - p); p = kstrlen(buf);
|
|
|
|
|
|
p += u32dec((u32)g_prof_blit_skip, buf + p);
|
|
|
|
|
|
kstrcpy(buf + p, " FUL:", 80 - p); p = kstrlen(buf);
|
|
|
|
|
|
p += u32dec((u32)g_prof_blit_full, buf + p);
|
|
|
|
|
|
du_draw_string(&g_fb, buf, hx + 6, hy + hh - 16,
|
|
|
|
|
|
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* ---- 3·DESKTOP ---- */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 全屏重绘
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 全局亮度(BRIGHT 滑块真实调暗)
|
|
|
|
|
|
* 性能优化:仅在亮度变化时处理,避免每帧全屏遍历 */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
static void apply_brightness(void) {
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 快速退出:亮度100%无需调整 */
|
|
|
|
|
|
if (g_bright >= 100) {
|
|
|
|
|
|
g_bright_applied = 100;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 快速退出:亮度未变化,跳过处理 */
|
|
|
|
|
|
if (g_bright == g_bright_applied) return;
|
|
|
|
|
|
|
|
|
|
|
|
/* 记录当前应用的亮度值 */
|
|
|
|
|
|
g_bright_applied = g_bright;
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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();
|
2026-07-28 14:35:14 +08:00
|
|
|
|
else if (g_page == 3) draw_desktop_page();
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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) {
|
2026-07-28 14:35:14 +08:00
|
|
|
|
if (p < 1 || p > 3 + g_cpage_count) return;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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];
|
2026-07-28 14:35:14 +08:00
|
|
|
|
cp->id = 4 + g_cpage_count;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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--;
|
2026-07-28 14:35:14 +08:00
|
|
|
|
for (int i = 0; i < g_cpage_count; i++) g_cpages[i].id = 4 + i;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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) {
|
2026-08-09 22:14:01 +08:00
|
|
|
|
if (g_ide_attached && rect_hit(g_ide_tabx, mx, my)) {
|
|
|
|
|
|
g_ide_attached = 0;
|
|
|
|
|
|
/* VSCode Phase 2: detach 时停止向 guest 转发输入 */
|
|
|
|
|
|
g_input_forward_enabled = 0;
|
|
|
|
|
|
ide_attach_invalidate(); /* P7.3: detach 后下次 attach 全量重绘 */
|
|
|
|
|
|
slog("ide detached, input forwarding off");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!g_ide_attached && rect_hit(g_ide_new_rect, mx, my)) {
|
|
|
|
|
|
g_ide_attached = 1;
|
|
|
|
|
|
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
|
|
|
|
|
/* attach 时仅当 Linux guest 服务可用才开启转发;否则降级为本地 editor */
|
|
|
|
|
|
g_input_forward_enabled = (g_lxc_svc != 0);
|
|
|
|
|
|
slog(g_input_forward_enabled ? "ide attached, forwarding to guest"
|
|
|
|
|
|
: "ide attached, lxc unavailable (local editor)");
|
|
|
|
|
|
/* VSCode Phase 6: 共享启动路径(X server + VSCode) */
|
|
|
|
|
|
if (g_input_forward_enabled) vscode_launch();
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
if (!g_ide_attached && dbl && rect_hit(g_ide_host, mx, my)) {
|
|
|
|
|
|
g_ide_attached = 1;
|
2026-08-09 22:14:01 +08:00
|
|
|
|
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
|
|
|
|
|
g_input_forward_enabled = (g_lxc_svc != 0);
|
|
|
|
|
|
slog(g_input_forward_enabled ? "ide attached (dbl), forwarding to guest"
|
|
|
|
|
|
: "ide attached (dbl), lxc unavailable (local editor)");
|
|
|
|
|
|
/* VSCode Phase 6: 共享启动路径(X server + VSCode) */
|
|
|
|
|
|
if (g_input_forward_enabled) vscode_launch();
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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)) {
|
2026-08-09 22:14:01 +08:00
|
|
|
|
win_close_request(&g_windows[i]);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} 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;
|
2026-08-09 22:14:01 +08:00
|
|
|
|
if (win_hit_close_btn(w, mx, my)) { win_close_request(w); return 1; }
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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) {
|
2026-08-05 13:15:46 +08:00
|
|
|
|
launch_app(g_icons[i].app_id);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} 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; }
|
2026-07-28 14:35:14 +08:00
|
|
|
|
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);
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
if (g_page == 3) return desktop_click(mx, my, dbl);
|
|
|
|
|
|
if (g_page >= 4) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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; }
|
2026-07-28 14:35:14 +08:00
|
|
|
|
if (g_page != 3) return 0;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
|
|
|
|
|
|
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) {
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* VSCode Phase 2: IDE attached 且在 IDE 页时,把按键序列转发到 Linux guest。
|
|
|
|
|
|
* 0xE0 前缀字节本身不转发(无对应 keycode),仅靠 g_e0 标志让下一字节选取扩展键变体。
|
|
|
|
|
|
* 转发在修饰键跟踪与页面路由之前完成,保证 VSCode 收到完整 press/release
|
|
|
|
|
|
* 序列(含 Shift/Ctrl/Alt/方向键)。宿主修饰键跟踪继续运行,Ctrl+Tab 切页、
|
|
|
|
|
|
* Ctrl+S 等热键仍可在宿主侧生效,便于用户随时切离 IDE 页。 */
|
|
|
|
|
|
if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_keyboard &&
|
|
|
|
|
|
sc != 0xE0 && g_page == 2) {
|
|
|
|
|
|
u8 base = (u8)(sc & 0x7F);
|
|
|
|
|
|
u16 code = scancode_to_linux_keycode(base, g_e0);
|
|
|
|
|
|
if (code) {
|
|
|
|
|
|
u32 value = (sc & 0x80) ? 0 : 1; /* 0x80 位 = release */
|
|
|
|
|
|
g_lxc_svc->input_forward_keyboard(code, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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; /* 模态期间吞掉按键 */
|
|
|
|
|
|
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* Ctrl+Tab 切页 / Ctrl+Shift+Tab 上下文菜单(DESKTOP 页) */
|
2026-07-28 12:00:02 +08:00
|
|
|
|
if (g_ctrl && sc == 0x0F) {
|
|
|
|
|
|
if (g_shift) {
|
|
|
|
|
|
/* Ctrl+Shift+Tab:对当前聚焦窗口打开"移到工作区"菜单 */
|
2026-07-28 14:35:14 +08:00
|
|
|
|
if (g_page == 3 && win_find(g_focused_win)) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-07-28 14:35:14 +08:00
|
|
|
|
int maxp = 3 + g_cpage_count;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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) {
|
2026-08-09 22:14:01 +08:00
|
|
|
|
if (g_input_forward_enabled) {
|
|
|
|
|
|
/* VSCode Phase 2: 键盘已转发到 guest 中的 VSCode,
|
|
|
|
|
|
* 本地 editor 不再重复处理,避免双输入。 */
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
editor_key((editor_state *)ADDR_EDDASH, sc, g_shift);
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2026-07-28 14:35:14 +08:00
|
|
|
|
if (g_page == 3) {
|
2026-07-28 12:00:02 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* P5d: PE 窗口运行时(run_windowed 桌面嵌入)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 协作模型(契约见 utsm/pe.h pe_window_host):
|
|
|
|
|
|
* launch_pe_app 创建受管窗口并把客户区大小的内存 surface 交给 shim,
|
|
|
|
|
|
* 随后阻塞在 pe_service.run_windowed。PE 消息空转(GetMessage/
|
|
|
|
|
|
* PeekMessage 无消息)时 shim 回调 pe_pump_cb,由它驱动 desktop 一帧:
|
|
|
|
|
|
* 采集 PS/2 输入(PE 窗口聚焦时经 inject_* 注入,否则走桌面正常路径)、
|
|
|
|
|
|
* redraw_all + flip。关闭 = inject WM_CLOSE(win_close_request),
|
|
|
|
|
|
* PE 自愿退出后 run_windowed 返回,launch_pe_app 收尾销毁窗口。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 注意:pump 运行在 PE 的 1MB 大栈上,desktop 帧代码栈用量远小于此;
|
|
|
|
|
|
* pump 内禁止再次调用 run/run_windowed(launch_pe_app 以 g_pe_running 拒绝)。
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
/* PE 窗口 on_draw:把 shim 渲染的 surface blit 到客户区(32bpp 行拷贝) */
|
|
|
|
|
|
static void pe_win_on_draw(void *state, app_ctx *ctx) {
|
|
|
|
|
|
(void)state;
|
|
|
|
|
|
if (!g_pe_surf) return;
|
|
|
|
|
|
int w = (g_pe_surf_w < ctx->client_w) ? g_pe_surf_w : ctx->client_w;
|
|
|
|
|
|
int h = (g_pe_surf_h < ctx->client_h) ? g_pe_surf_h : ctx->client_h;
|
|
|
|
|
|
if (w <= 0 || h <= 0) return;
|
|
|
|
|
|
u8 *dst0 = (u8 *)g_fb.fb + (u64)ctx->client_y * g_fb_pitch
|
|
|
|
|
|
+ (u64)ctx->client_x * 4;
|
|
|
|
|
|
u32 sp = (u32)g_pe_surf_w * 4;
|
|
|
|
|
|
for (int r = 0; r < h; r++) {
|
|
|
|
|
|
u32 *d = (u32 *)(dst0 + (u64)r * g_fb_pitch);
|
|
|
|
|
|
const u32 *s = (const u32 *)(g_pe_surf + (u64)r * sp);
|
|
|
|
|
|
for (int c = 0; c < w; c++) d[c] = s[c] | 0xFF000000u;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* PE 消息空转让出点:驱动 desktop 一帧。返回 0 继续,非 0 请求 PE 退出。 */
|
|
|
|
|
|
static int pe_pump_cb(void *ud) {
|
|
|
|
|
|
int win_id = (int)(u64)ud;
|
|
|
|
|
|
int moved = 0;
|
|
|
|
|
|
while (ps2_mouse_poll()) moved = 1;
|
|
|
|
|
|
|
|
|
|
|
|
desktop_window *pw = win_find(win_id);
|
|
|
|
|
|
if (!pw) return 1; /* 窗口已销毁:请求 PE 退出 */
|
|
|
|
|
|
|
|
|
|
|
|
int pe_focused = (g_page == 3 && pw->focused && pw->ws == g_ws_cur &&
|
|
|
|
|
|
!g_taskview && !g_ctx_open);
|
|
|
|
|
|
|
|
|
|
|
|
/* 鼠标注入:PE 窗口聚焦且光标在客户区内 → surface 局部坐标 + 按钮位 */
|
|
|
|
|
|
int cx0 = pw->x + BORDER_W, cy0 = pw->y + KATE_TITLEBAR_H;
|
|
|
|
|
|
int ccw = pw->w - 2 * BORDER_W, cch = pw->h - KATE_TITLEBAR_H - BORDER_W;
|
|
|
|
|
|
if (pe_focused && g_pe_svc->inject_pointer &&
|
|
|
|
|
|
g_mouse_x >= cx0 && g_mouse_x < cx0 + ccw &&
|
|
|
|
|
|
g_mouse_y >= cy0 && g_mouse_y < cy0 + cch) {
|
|
|
|
|
|
g_pe_svc->inject_pointer(g_mouse_x - cx0, g_mouse_y - cy0,
|
|
|
|
|
|
(u32)(g_mouse_btn & 3));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 点击/释放走桌面正常路径(聚焦、标题栏拖拽、关闭按钮→win_close_request、
|
|
|
|
|
|
* 其他窗口/图标)。双击其他 PE 图标会被 launch_pe_app 的 g_pe_running 拒绝。 */
|
|
|
|
|
|
if (g_left_pressed) { g_left_pressed = 0; on_left_press(); }
|
|
|
|
|
|
if (g_right_pressed) { g_right_pressed = 0; on_right_press(); }
|
|
|
|
|
|
if (g_left_released) {
|
|
|
|
|
|
g_left_released = 0;
|
|
|
|
|
|
if (g_dragging || g_drag_slider) {
|
|
|
|
|
|
g_dragging = 0; g_drag_win = -1; g_drag_slider = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pw = win_find(win_id); /* 点击可能重排窗口数组 */
|
|
|
|
|
|
if (!pw) return 1;
|
|
|
|
|
|
pe_focused = (g_page == 3 && pw->focused && pw->ws == g_ws_cur &&
|
|
|
|
|
|
!g_taskview && !g_ctx_open);
|
|
|
|
|
|
|
|
|
|
|
|
/* 窗口/滑块拖拽(与主循环相同的钳位) */
|
|
|
|
|
|
if (moved && g_dragging && g_drag_win == win_id) {
|
|
|
|
|
|
pw->x = g_mouse_x - g_drag_off_x;
|
|
|
|
|
|
pw->y = g_mouse_y - g_drag_off_y;
|
|
|
|
|
|
if (pw->y < KATE_TOPBAR_H) pw->y = KATE_TOPBAR_H;
|
|
|
|
|
|
if (pw->x < 0 - pw->w + 40) pw->x = 0 - pw->w + 40;
|
|
|
|
|
|
if (pw->x > (int)g_fb_w - 40) pw->x = (int)g_fb_w - 40;
|
|
|
|
|
|
if (pw->y > (int)g_fb_h - KATE_TASKBAR_H - 20)
|
|
|
|
|
|
pw->y = (int)g_fb_h - KATE_TASKBAR_H - 20;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (moved && g_drag_slider == 1) slider_set(g_vol_rect, &g_vol, g_mouse_x);
|
|
|
|
|
|
if (moved && g_drag_slider == 2) slider_set(g_bri_rect, &g_bright, g_mouse_x);
|
|
|
|
|
|
|
|
|
|
|
|
/* 键盘:PE 聚焦时注入(shim 内部合成 KEYUP/CHAR/修饰键),
|
|
|
|
|
|
* Ctrl+Tab 保留给宿主切页;否则走桌面正常路径。 */
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
|
u8 st = inb(0x64);
|
|
|
|
|
|
if (!(st & 1) || (st & 0x20)) break;
|
|
|
|
|
|
u8 sc = inb(0x60);
|
|
|
|
|
|
if (pe_focused && g_pe_svc->inject_scancode) {
|
|
|
|
|
|
if (sc == 0xE0) { g_e0 = 1; continue; }
|
|
|
|
|
|
/* 宿主侧同步跟踪修饰键(Ctrl+Tab 切页判定用) */
|
|
|
|
|
|
if (sc == 0x2A || sc == 0x36) g_shift = 1;
|
|
|
|
|
|
if (sc == 0xAA || sc == 0xB6) g_shift = 0;
|
|
|
|
|
|
if (sc == 0x1D) g_ctrl = 1;
|
|
|
|
|
|
if (sc == 0x9D) g_ctrl = 0;
|
|
|
|
|
|
if (g_ctrl && sc == 0x0F) {
|
|
|
|
|
|
/* 切页前补修饰键释放,防止 PE 侧卡键 */
|
|
|
|
|
|
g_pe_svc->inject_scancode(0x9D, 0);
|
|
|
|
|
|
g_pe_svc->inject_scancode(0xAA, 0);
|
|
|
|
|
|
g_pe_svc->inject_scancode(0xB8, 0);
|
|
|
|
|
|
g_e0 = 0;
|
|
|
|
|
|
handle_key(sc);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
g_pe_svc->inject_scancode(sc, g_e0);
|
|
|
|
|
|
g_e0 = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
handle_key(sc);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (g_quit) return 1; /* 任务栏 QUIT:请求 PE 退出 */
|
|
|
|
|
|
|
|
|
|
|
|
/* 驱动一帧(redraw_all 内含 PE surface blit 与光标绘制) */
|
|
|
|
|
|
redraw_all();
|
|
|
|
|
|
flip_buffer();
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 启动 PE 应用:读 exe → 建窗口 → run_windowed 阻塞运行 → 退出后销毁窗口 */
|
|
|
|
|
|
static void launch_pe_app(int app_id) {
|
|
|
|
|
|
app_descriptor *app = &g_apps[app_id];
|
|
|
|
|
|
if (!g_pe_svc || !g_pe_svc->run_windowed) {
|
|
|
|
|
|
slog("pe: service unavailable");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (g_pe_running) {
|
|
|
|
|
|
slog("pe: another PE window is running");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
u8 *data = 0; u32 size = 0;
|
|
|
|
|
|
if (f32_read_path_lfn(app->pe_file, &data, &size) != 0) {
|
|
|
|
|
|
slog("pe: exe not found");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 窗口布局:与 launch_app 相同的级联策略 */
|
|
|
|
|
|
int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_TASKBAR_H;
|
|
|
|
|
|
int wx = ICON_PAD + g_win_count * 30;
|
|
|
|
|
|
int wy = top + 8 + g_win_count * 30;
|
|
|
|
|
|
if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = ICON_PAD;
|
|
|
|
|
|
if (wy + (int)app->default_h > bot - 20) wy = top + 8;
|
|
|
|
|
|
|
|
|
|
|
|
desktop_window *win = win_create(app_id, app->display_name,
|
|
|
|
|
|
wx, wy, (int)app->default_w, (int)app->default_h);
|
|
|
|
|
|
if (!win) return;
|
|
|
|
|
|
int win_id = win->id;
|
|
|
|
|
|
|
|
|
|
|
|
int cw = (int)app->default_w - 2 * BORDER_W;
|
|
|
|
|
|
int ch = (int)app->default_h - KATE_TITLEBAR_H - BORDER_W;
|
|
|
|
|
|
if (cw <= 0 || ch <= 0 || (u64)cw * (u64)ch * 4 > PE_SURFACE_MAX) {
|
|
|
|
|
|
win_destroy(win);
|
|
|
|
|
|
slog("pe: client area too large for surface");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
g_pe_surf = (u8 *)PE_SURFACE_ADDR;
|
|
|
|
|
|
g_pe_surf_w = cw; g_pe_surf_h = ch;
|
|
|
|
|
|
/* 初始化为深色底:PE 窗口未覆盖区域不残留脏数据 */
|
|
|
|
|
|
{
|
|
|
|
|
|
u32 *p = (u32 *)g_pe_surf;
|
|
|
|
|
|
u64 n = (u64)cw * (u64)ch;
|
|
|
|
|
|
for (u64 i = 0; i < n; i++) p[i] = 0xFF101418u;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
win->app_state = &g_pe_state_dummy; /* draw_desktop_page 要求非空才调 on_draw */
|
|
|
|
|
|
g_pe_win_id = win_id;
|
|
|
|
|
|
g_pe_running = 1;
|
|
|
|
|
|
slog("pe: run_windowed");
|
|
|
|
|
|
|
|
|
|
|
|
/* cmdline:"file.exe args"(GetCommandLineA 返回值) */
|
|
|
|
|
|
static char cmdline[160];
|
|
|
|
|
|
kstrcpy(cmdline, app->pe_file, (int)sizeof(cmdline));
|
|
|
|
|
|
if (app->pe_args && app->pe_args[0]) {
|
|
|
|
|
|
kstrcat(cmdline, " ", (int)sizeof(cmdline));
|
|
|
|
|
|
kstrcat(cmdline, app->pe_args, (int)sizeof(cmdline));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pe_window_host host;
|
|
|
|
|
|
host.pump = pe_pump_cb;
|
|
|
|
|
|
host.ud = (void *)(u64)win_id; /* 传 id:窗口数组重排不悬空 */
|
|
|
|
|
|
host.surface = g_pe_surf;
|
|
|
|
|
|
host.width = (u32)cw;
|
|
|
|
|
|
host.height = (u32)ch;
|
|
|
|
|
|
host.pitch = (u32)cw * 4;
|
|
|
|
|
|
|
|
|
|
|
|
u64 exit_code = 0;
|
|
|
|
|
|
g_pe_svc->run_windowed(data, size, cmdline, &exit_code, &host);
|
|
|
|
|
|
|
|
|
|
|
|
g_pe_running = 0;
|
|
|
|
|
|
g_pe_win_id = -1;
|
|
|
|
|
|
g_pe_surf = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/* PE 已退出:销毁宿主窗口并全屏重绘 */
|
|
|
|
|
|
desktop_window *w2 = win_find(win_id);
|
|
|
|
|
|
if (w2) win_destroy(w2);
|
|
|
|
|
|
files_refresh(); /* PE 可能改动了磁盘 */
|
|
|
|
|
|
g_full_redraw = 1;
|
|
|
|
|
|
redraw_all();
|
|
|
|
|
|
flip_buffer();
|
|
|
|
|
|
slog("pe: exited");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 主入口
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
__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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* VSCode Phase 2: Linux 兼容层服务指针(dsk_loader 在 reserved[5] 写入)。
|
|
|
|
|
|
* 为 NULL 表示 Linux guest 未启动 / IPC 未就绪,IDE attach 时降级为本地 editor。 */
|
|
|
|
|
|
g_lxc_svc = (const linux_compat_service *)ctx->reserved[5];
|
|
|
|
|
|
if (g_lxc_svc && g_lxc_svc->magic == LINUX_COMPAT_MAGIC) {
|
|
|
|
|
|
slog("lxc service available");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
g_lxc_svc = 0;
|
|
|
|
|
|
slog("lxc service unavailable (IDE will use local editor)");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* P5d: PE 兼容层服务指针(dsk_loader 在 reserved[4] 写入)。
|
|
|
|
|
|
* 为 NULL 表示 PE 服务未注册,PEAPPS.CNF 应用启动时降级报错。 */
|
|
|
|
|
|
g_pe_svc = (const pe_service *)ctx->reserved[4];
|
|
|
|
|
|
if (g_pe_svc && g_pe_svc->magic == PE_SERVICE_MAGIC) {
|
|
|
|
|
|
slog("pe service available");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
g_pe_svc = 0;
|
|
|
|
|
|
slog("pe service unavailable");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
du_context_init(&g_fb, g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* 双缓冲:保存真实 fb,切换到 sprite buffer 渲染 */
|
|
|
|
|
|
g_real_fb = (u8 *)g_fb_addr;
|
|
|
|
|
|
g_fb.fb = (du_u32 *)SPRITE_BUF_ADDR;
|
2026-07-28 12:00:02 +08:00
|
|
|
|
f32_init(g_block_read, g_block_write);
|
|
|
|
|
|
|
|
|
|
|
|
tsc_calibrate();
|
|
|
|
|
|
g_rtc_boot_sec = rtc_day_sec();
|
|
|
|
|
|
g_last_sec = g_rtc_boot_sec;
|
|
|
|
|
|
|
|
|
|
|
|
ps2_mouse_init();
|
|
|
|
|
|
|
|
|
|
|
|
register_apps();
|
|
|
|
|
|
setup_desktop_icons();
|
|
|
|
|
|
|
|
|
|
|
|
/* 工作区 0 */
|
|
|
|
|
|
kstrcpy(g_ws[0].name, "WS-1", 16);
|
|
|
|
|
|
g_ws[0].page = 1;
|
|
|
|
|
|
g_ws_count = 1;
|
|
|
|
|
|
g_ws_cur = 0;
|
|
|
|
|
|
|
|
|
|
|
|
/* DASHBOARD 面板共享状态 */
|
|
|
|
|
|
{
|
|
|
|
|
|
bash_state *t1 = (bash_state *)ADDR_TERM1;
|
|
|
|
|
|
bash_reset(t1, 640, 280);
|
|
|
|
|
|
bash_puts(t1, "Deshab Bash v0.1 - TERM-01\nType help for commands\n\n", KS_TEXT_PRIMARY);
|
|
|
|
|
|
|
|
|
|
|
|
bash_state *t2 = (bash_state *)ADDR_TERM2;
|
|
|
|
|
|
bash_reset(t2, 640, 280);
|
|
|
|
|
|
bash_puts(t2, "Deshab Bash v0.1 - TERM-02\nType help for commands\n\n", KS_TEXT_PRIMARY);
|
|
|
|
|
|
|
|
|
|
|
|
editor_reset((editor_state *)ADDR_EDDASH,
|
|
|
|
|
|
"Welcome to Deshab Editor\n"
|
|
|
|
|
|
"\n"
|
|
|
|
|
|
"- Double-click a file in FILES panel to open it here\n"
|
|
|
|
|
|
"- SAVE button / Ctrl+S writes back to FAT32 disk\n"
|
|
|
|
|
|
"- IDE page hosts this same editor full-screen\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
files_refresh();
|
|
|
|
|
|
|
|
|
|
|
|
boot_screen();
|
|
|
|
|
|
g_booted = 1;
|
|
|
|
|
|
|
|
|
|
|
|
redraw_all();
|
|
|
|
|
|
slog("desktop ready");
|
|
|
|
|
|
|
|
|
|
|
|
/* 主事件循环 */
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* P8.1: dev 自动 VSCode 启动测试--无需 GUI 交互即可验证 launch 链路 */
|
|
|
|
|
|
static int g_autotest_state = 0;
|
|
|
|
|
|
static u64 g_autotest_tsc = 0;
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
while (!g_quit) {
|
|
|
|
|
|
int need_redraw = 0;
|
|
|
|
|
|
int moved = 0;
|
|
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* P8.1: 自动启动测试(先 ping guest,不可用直接报告,不浪费 3s 等待) */
|
|
|
|
|
|
if (g_tsc_per_sec && g_autotest_state == 0) {
|
|
|
|
|
|
g_autotest_tsc = rdtsc();
|
|
|
|
|
|
g_autotest_state = 1;
|
|
|
|
|
|
/* P8.1 Fix: 先检测 guest 可用性 */
|
|
|
|
|
|
int prc = lxc_guest_ping();
|
|
|
|
|
|
if (prc != 0) {
|
|
|
|
|
|
slog("[autotest] SKIP: Linux guest not running (no VMX/KVM)");
|
|
|
|
|
|
slog("[autotest] To test VSCode: use WSL2+KVM or Linux host with KVM");
|
|
|
|
|
|
g_autotest_state = 3; /* 跳过测试 */
|
|
|
|
|
|
} else {
|
|
|
|
|
|
slog("[autotest] guest OK, waiting 3s before vscode_launch");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (g_autotest_state == 1 && g_tsc_per_sec) {
|
|
|
|
|
|
u64 elapsed = rdtsc() - g_autotest_tsc;
|
|
|
|
|
|
if (elapsed >= (u64)g_tsc_per_sec * 3) {
|
|
|
|
|
|
slog("[autotest] triggering vscode_launch");
|
|
|
|
|
|
g_autotest_state = 2;
|
|
|
|
|
|
vscode_launch();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (g_autotest_state == 2 && g_tsc_per_sec) {
|
|
|
|
|
|
u64 elapsed = rdtsc() - g_autotest_tsc;
|
|
|
|
|
|
/* 15s 后报告结果 */
|
|
|
|
|
|
if (elapsed >= (u64)g_tsc_per_sec * 15) {
|
|
|
|
|
|
if (g_vscode_state == 2) {
|
|
|
|
|
|
slog("[autotest] SUCCESS: scanout online, VSCode running");
|
|
|
|
|
|
} else if (g_vscode_state == 1) {
|
|
|
|
|
|
slog("[autotest] TIMEOUT: still launching");
|
|
|
|
|
|
slog_num("[autotest] phase=", g_launch_phase);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
slog("[autotest] FAIL: vscode_state=0");
|
|
|
|
|
|
slog_num("[autotest] phase=", g_launch_phase);
|
|
|
|
|
|
}
|
|
|
|
|
|
g_autotest_state = 3;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-28 12:00:02 +08:00
|
|
|
|
/* 鼠标:排空所有数据包 */
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-03 21:13:04 +08:00
|
|
|
|
|
2026-08-09 22:14:01 +08:00
|
|
|
|
/* VSCode Phase 3/7: IDE attached 时轮询 guest scanout dirty,
|
|
|
|
|
|
* 有新帧则触发重绘以 blit 到 framebuffer。查询结果缓存进 g_so
|
|
|
|
|
|
* (查询清零 UTSM 侧 dirty + dirty rect),blit 据此局部拷贝。 */
|
|
|
|
|
|
if (g_page == 2 && g_ide_attached && g_input_forward_enabled &&
|
|
|
|
|
|
g_lxc_svc && g_lxc_svc->gpu_get_scanout_info) {
|
|
|
|
|
|
if (g_lxc_svc->gpu_get_scanout_info(&g_so) == 0) {
|
|
|
|
|
|
g_so_valid = 1;
|
|
|
|
|
|
if (g_so.dirty) need_redraw = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* P7.9: FPS 统计(每秒更新,基于 TSC) */
|
|
|
|
|
|
if (g_tsc_per_sec && g_prof_fps_tsc) {
|
|
|
|
|
|
u64 elapsed = rdtsc() - g_prof_fps_tsc;
|
|
|
|
|
|
if (elapsed >= g_tsc_per_sec) {
|
|
|
|
|
|
g_prof_fps = (int)(g_prof_frames * g_tsc_per_sec / elapsed);
|
|
|
|
|
|
g_prof_frames = 0;
|
|
|
|
|
|
g_prof_fps_tsc = rdtsc();
|
|
|
|
|
|
if (g_page == 2) need_redraw = 1; /* 刷新叠加显示 */
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (g_tsc_per_sec) {
|
|
|
|
|
|
g_prof_fps_tsc = rdtsc();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (need_redraw && g_page == 2) g_prof_frames++;
|
|
|
|
|
|
|
2026-07-03 21:13:04 +08:00
|
|
|
|
if (need_redraw) {
|
2026-07-28 21:11:10 +08:00
|
|
|
|
/* 脏矩形渲染:合并脏矩形,只 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
} else if (moved) {
|
2026-07-28 14:35:14 +08:00
|
|
|
|
/* 仅光标移动:局部 flip(双缓冲 → 无闪烁) */
|
|
|
|
|
|
int old_x = g_cursor_old_x, old_y = g_cursor_old_y;
|
2026-07-26 21:07:36 +08:00
|
|
|
|
cursor_restore_bg();
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_rect(old_x, old_y, CURSOR_SIZE, CURSOR_SIZE);
|
2026-07-26 21:07:36 +08:00
|
|
|
|
cursor_save_bg(g_mouse_x, g_mouse_y);
|
|
|
|
|
|
cursor_draw(g_mouse_x, g_mouse_y);
|
2026-07-28 14:35:14 +08:00
|
|
|
|
flip_rect(g_mouse_x, g_mouse_y, CURSOR_SIZE, CURSOR_SIZE);
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__asm__("pause");
|
|
|
|
|
|
}
|
2026-07-28 12:00:02 +08:00
|
|
|
|
|
|
|
|
|
|
slog("exit");
|
|
|
|
|
|
/* 返回到 DSK */
|
2026-07-03 21:13:04 +08:00
|
|
|
|
}
|