ohhhhhhhhhhh (exclude linux-extra-rootfs.img build artifact)
This commit is contained in:
@@ -7,7 +7,9 @@ OUT := $(ROOT)/SYSTEM/system/deshab64/desktop.elf
|
||||
CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I../UTSM/include -O2
|
||||
LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld
|
||||
|
||||
C_SOURCES := main.c
|
||||
C_SOURCES := main.c input.c wm.c compositor.c apps_builtin.c appreg.c \
|
||||
launcher.c lxc.c shell.c pages.c f32io.c ascii_font.c zhfont.c \
|
||||
wallpaper.c taskbar.c textures.c
|
||||
OBJECTS := $(C_SOURCES:.c=.o)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
@@ -24,7 +24,9 @@ CFLAGS := -DDESHAB_HOST -O2 -Wall -Wextra -g \
|
||||
LDFLAGS := $(SDL_LIBS) -lm
|
||||
|
||||
TARGET := desktop_host
|
||||
SRCS := main.c host_main.c
|
||||
SRCS := main.c input.c wm.c compositor.c apps_builtin.c appreg.c \
|
||||
launcher.c lxc.c shell.c pages.c f32io.c ascii_font.c \
|
||||
zhfont.c host_main.c
|
||||
OBJS := $(SRCS:.c=.o)
|
||||
|
||||
.PHONY: all run clean
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
/* appreg.c — 应用注册表 + 桌面图标(M0 自 main.c 平移)
|
||||
*
|
||||
* 注册来源:内置 / LINUXAPP.CNF / PEAPPS.CNF / user/desktop/*.lnk
|
||||
*/
|
||||
#include "desktop.h"
|
||||
|
||||
/* ---- 全局状态 ---- */
|
||||
app_descriptor g_apps[MAX_APPS];
|
||||
int g_app_count = 0;
|
||||
|
||||
desktop_icon g_icons[MAX_ICONS];
|
||||
int g_icon_count = 0;
|
||||
|
||||
/* ============================================================
|
||||
* Winux-Kate 应用图标(40×40,青色几何图形)
|
||||
* ============================================================ */
|
||||
|
||||
void draw_circle_outline(du_context *ctx, int cx, int cy, int r, u32 color) {
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
int d2 = dx * dx + dy * dy;
|
||||
if (d2 <= r * r && d2 > (r - 1) * (r - 1)) {
|
||||
du_pixel(ctx, cx + dx, cy + dy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_icon_terminal(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2);
|
||||
du_fill_rect(ctx, cx + 8, cy + 12, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 10, cy + 14, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 12, cy + 16, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 10, cy + 18, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 8, cy + 20, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 16, cy + 22, 8, 2, KS_ACCENT);
|
||||
}
|
||||
|
||||
static void draw_icon_editor(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2);
|
||||
int widths[] = {24, 20, 22, 14, 20, 18};
|
||||
for (int i = 0; i < 6; i++) {
|
||||
du_fill_rect(ctx, cx + 8, cy + 8 + i * 4, widths[i], 2,
|
||||
(i == 0) ? KS_ACCENT2 : KS_ACCENT);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_icon_folder(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_BORDER, 2);
|
||||
du_fill_rect(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT_DIM);
|
||||
du_rect_outline(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT, 1);
|
||||
du_fill_rect(ctx, cx + 6, cy + 14, 28, 16, KS_BG_SECONDARY);
|
||||
du_rect_outline(ctx, cx + 6, cy + 14, 28, 16, KS_ACCENT, 1);
|
||||
du_fill_rect(ctx, cx + 6, cy + 14, 28, 1, KS_ACCENT);
|
||||
du_fill_rect(ctx, cx + 10, cy + 19, 12, 1, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 10, cy + 23, 16, 1, KS_TEXT_DIM);
|
||||
du_fill_rect(ctx, cx + 10, cy + 26, 10, 1, KS_TEXT_DIM);
|
||||
}
|
||||
|
||||
static void draw_icon_calc(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2);
|
||||
du_fill_rect(ctx, cx + 6, cy + 6, 28, 8, KS_BG_PRIMARY);
|
||||
du_rect_outline(ctx, cx + 6, cy + 6, 28, 8, KS_ACCENT2, 1);
|
||||
du_fill_rect(ctx, cx + 28, cy + 8, 4, 4, KS_ACCENT2);
|
||||
for (int r = 0; r < 3; r++) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
int bx = cx + 6 + c * 9;
|
||||
int by = cy + 18 + r * 7;
|
||||
du_fill_rect(ctx, bx, by, 7, 5, KS_ACCENT_DIM);
|
||||
du_rect_outline(ctx, bx, by, 7, 5, KS_ACCENT, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_icon_browser(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2);
|
||||
int ccx = cx + 20, ccy = cy + 20, r = 11;
|
||||
draw_circle_outline(ctx, ccx, ccy, r, KS_ACCENT);
|
||||
du_fill_rect(ctx, ccx - r, ccy, 2 * r + 1, 1, KS_ACCENT);
|
||||
du_fill_rect(ctx, ccx, ccy - r, 1, 2 * r + 1, KS_ACCENT);
|
||||
du_pixel(ctx, ccx, ccy, KS_ACCENT2);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 应用注册
|
||||
* ============================================================ */
|
||||
|
||||
/* ---- 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 8
|
||||
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];
|
||||
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;
|
||||
}
|
||||
|
||||
#define UDESK_MAX 12
|
||||
static char g_ud_name[UDESK_MAX][12];
|
||||
static char g_ud_disp[UDESK_MAX][16];
|
||||
static char g_ud_path[UDESK_MAX][96];
|
||||
static char g_ud_elf[UDESK_MAX][12];
|
||||
static char g_ud_files[UDESK_MAX][64];
|
||||
static int g_ud_nfiles;
|
||||
|
||||
static int udesk_name_taken(const char *n) {
|
||||
int i;
|
||||
for (i = 0; i < g_app_count; i++) {
|
||||
if (g_apps[i].name && fat32_lfn_streq_ci(g_apps[i].name, n)) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int udesk_collect_cb(const f32_entry *e, void *user) {
|
||||
(void)user;
|
||||
const char *nm = (e->has_lfn && e->long_name[0]) ? e->long_name : e->name;
|
||||
if (e->is_dir || !nm[0]) return 0;
|
||||
if (!fat32_lfn_ends_with_ci(nm, ".lnk")) return 0;
|
||||
if (g_ud_nfiles >= UDESK_MAX) return 1;
|
||||
lapp_field_copy(g_ud_files[g_ud_nfiles], 64, nm, kstrlen(nm));
|
||||
g_ud_nfiles++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int udesk_ascii_ok(const char *s) {
|
||||
if (!s || !s[0]) return 0;
|
||||
while (*s) {
|
||||
if ((unsigned char)*s > 0x7E) return 0;
|
||||
s++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void udesk_parse_lnk(int slot, const u8 *data, u32 size) {
|
||||
const char *lines[4];
|
||||
int nlen[4];
|
||||
int n = 0, i;
|
||||
u32 pos = 0;
|
||||
char kind[16];
|
||||
if (slot < 0 || slot >= UDESK_MAX) return;
|
||||
while (pos < size && n < 4) {
|
||||
u32 eol = pos;
|
||||
while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++;
|
||||
lines[n] = (const char *)data + pos;
|
||||
nlen[n] = (int)(eol - pos);
|
||||
n++;
|
||||
while (eol < size && (data[eol] == '\n' || data[eol] == '\r')) eol++;
|
||||
pos = eol;
|
||||
}
|
||||
if (n < 2) return;
|
||||
if (n >= 4 && nlen[3] > 0)
|
||||
lapp_field_copy(g_ud_name[slot], 12, lines[3], nlen[3]);
|
||||
else
|
||||
lapp_field_copy(g_ud_name[slot], 12, lines[0], nlen[0]);
|
||||
if (udesk_ascii_ok(g_ud_name[slot]) == 0) {
|
||||
g_ud_name[slot][0] = 'a' + (char)slot;
|
||||
g_ud_name[slot][1] = 0;
|
||||
}
|
||||
if (nlen[0] > 0 && udesk_ascii_ok(lines[0]))
|
||||
lapp_field_copy(g_ud_disp[slot], 16, lines[0], nlen[0]);
|
||||
else
|
||||
kstrcpy(g_ud_disp[slot], g_ud_name[slot], 16);
|
||||
lapp_field_copy(kind, 16, lines[1], nlen[1]);
|
||||
g_ud_path[slot][0] = 0;
|
||||
g_ud_elf[slot][0] = 0;
|
||||
if (fat32_lfn_streq_ci(kind, "LINUX") || (n >= 3 && lines[2][0] == '/')) {
|
||||
if (n >= 3)
|
||||
lapp_field_copy(g_ud_path[slot], 96, lines[2], nlen[2]);
|
||||
} else if (fat32_lfn_streq_ci(kind, "NATIVE")) {
|
||||
return;
|
||||
} else {
|
||||
if (nlen[1] >= 11)
|
||||
lapp_field_copy(g_ud_elf[slot], 12, lines[1], 11);
|
||||
else
|
||||
f32_name_to_83_w(kind, g_ud_elf[slot]);
|
||||
g_ud_elf[slot][11] = 0;
|
||||
}
|
||||
if (udesk_name_taken(g_ud_name[slot])) return;
|
||||
if (!g_ud_path[slot][0] && !g_ud_elf[slot][0]) return;
|
||||
if (g_app_count >= MAX_APPS) return;
|
||||
g_apps[g_app_count] = (app_descriptor){
|
||||
g_ud_name[slot], g_ud_disp[slot], 720, 480,
|
||||
0, 0, 0, 0,
|
||||
g_ud_elf[slot][0] ? g_ud_elf[slot] : 0,
|
||||
g_ud_path[slot][0] ? g_ud_path[slot] : 0, 0, 0, 0,
|
||||
1 /* user_desktop:桌面只显示 .lnk */
|
||||
};
|
||||
g_app_count++;
|
||||
slog("user/desktop icon:");
|
||||
slog(g_ud_disp[slot]);
|
||||
(void)i;
|
||||
}
|
||||
|
||||
static void register_user_desktop_icons(void) {
|
||||
u32 dclus = 0;
|
||||
int i;
|
||||
g_ud_nfiles = 0;
|
||||
if (f32_find_path_dir_lfn_w("user/desktop", &dclus) != 0) {
|
||||
slog("user/desktop missing, skip shared icons");
|
||||
return;
|
||||
}
|
||||
if (f32_list_dir_w(dclus, udesk_collect_cb, 0) != 0) return;
|
||||
for (i = 0; i < g_ud_nfiles && g_app_count < MAX_APPS; i++) {
|
||||
char path[96];
|
||||
u8 *data = 0;
|
||||
u32 size = 0;
|
||||
kstrcpy(path, "user/desktop/", 96);
|
||||
kstrcat(path, g_ud_files[i], 96);
|
||||
if (f32_read_path_lfn_w(path, &data, &size) != 0 || !data || !size) continue;
|
||||
udesk_parse_lnk(i, data, size);
|
||||
}
|
||||
}
|
||||
|
||||
static void register_linux_apps(void) {
|
||||
u8 *data = 0;
|
||||
u32 size = 0;
|
||||
char n11[11];
|
||||
if (f32_name_to_83_w("LINUXAPP.CNF", n11) != 0) return;
|
||||
if (f32_read_root_file_w(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 register_pe_apps(void) {
|
||||
u8 *data = 0;
|
||||
u32 size = 0;
|
||||
char n11[11];
|
||||
if (f32_name_to_83_w("PEAPPS.CNF", n11) != 0) return;
|
||||
if (f32_read_root_file_w(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;
|
||||
}
|
||||
}
|
||||
|
||||
void register_apps(void) {
|
||||
/* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/
|
||||
g_apps[0] = (app_descriptor){
|
||||
"shell", "Terminal", 720, 480,
|
||||
0, 0, 0, 0, "SHELL ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* 命令行2:cmd.elf(Windows 风格命令行 + PE/EXE 执行)*/
|
||||
g_apps[1] = (app_descriptor){
|
||||
"cmd", "CMD", 720, 480,
|
||||
0, 0, 0, 0, "CMD ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
g_apps[2] = (app_descriptor){
|
||||
"editor", "Editor", 600, 450,
|
||||
editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy, 0
|
||||
};
|
||||
g_app_count++;
|
||||
g_apps[3] = (app_descriptor){
|
||||
"fileman", "Files", 500, 400,
|
||||
0, 0, 0, 0, "FILEMAN ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
g_apps[4] = (app_descriptor){
|
||||
"calc", "Calculator", 280, 400,
|
||||
calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy, 0
|
||||
};
|
||||
g_app_count++;
|
||||
g_apps[5] = (app_descriptor){
|
||||
"browser", "Browser", 700, 500,
|
||||
0, 0, 0, 0, "BROWSER ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* ProEdit — 增强版文本编辑器(语法高亮、设置、目录浏览)*/
|
||||
g_apps[6] = (app_descriptor){
|
||||
"proedit", "ProEdit", 750, 550,
|
||||
0, 0, 0, 0, "PROEDIT ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* Settings — 系统设置(内核 FUCK 配置 + 网络 NETCONF 配置)*/
|
||||
g_apps[7] = (app_descriptor){
|
||||
"settings", "Settings", 760, 540,
|
||||
0, 0, 0, 0, "SETTINGSELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* VSCode Phase 5: Linux guest 应用(VSCode 等),来自 LINUXAPP.CNF */
|
||||
register_linux_apps();
|
||||
/* P5d: Windows PE 应用,来自 PEAPPS.CNF */
|
||||
register_pe_apps();
|
||||
register_user_desktop_icons();
|
||||
}
|
||||
|
||||
/* M2: 桌面图标 = user/desktop/*.lnk 用户快捷方式(user_desktop 标记)。
|
||||
* 内置应用(editor/calc/settings…)不落桌面——经任务栏/开始菜单启动。
|
||||
* 布局沿用 canvas padding 24px,列宽 104px,gap 18px,左→右换行。 */
|
||||
void setup_desktop_icons(void) {
|
||||
int pad = KATE_ICON_PAD; /* 24 */
|
||||
int x = pad;
|
||||
int y = g_fluent ? (pad + 8) : (KATE_TOPBAR_H + KATE_PAGE_PAD + pad);
|
||||
int max_x = (int)g_fb_w - pad; /* canvas 右缘 */
|
||||
for (int i = 0; i < g_app_count; i++) {
|
||||
if (g_icon_count >= MAX_ICONS) break;
|
||||
if (!g_apps[i].user_desktop) continue; /* M2: 只显示 .lnk 快捷方式 */
|
||||
if (x > pad && x + KATE_ICON_W > max_x) {
|
||||
x = pad;
|
||||
y += KATE_ICON_H + KATE_ICON_GAP;
|
||||
}
|
||||
g_icons[g_icon_count].app_id = i;
|
||||
g_icons[g_icon_count].x = x;
|
||||
g_icons[g_icon_count].y = y;
|
||||
g_icons[g_icon_count].label = g_apps[i].display_name;
|
||||
g_icons[g_icon_count].selected = 0;
|
||||
g_icon_count++;
|
||||
x += KATE_ICON_W + KATE_ICON_GAP;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
/* apps_builtin.c — 内置应用 bash/editor/calc + FILES 面板(M0 自 main.c 平移)
|
||||
*/
|
||||
#include "desktop.h"
|
||||
|
||||
/* ---- FILES 面板全局状态 ---- */
|
||||
fentry g_files[MAX_FILES];
|
||||
int g_file_count = 0;
|
||||
int g_file_sel = -1;
|
||||
int g_files_loaded = 0;
|
||||
|
||||
/* ============================================================
|
||||
* 终端 (Bash) — 面板/窗口共用核心
|
||||
* ============================================================ */
|
||||
|
||||
const char *BASH_PROMPT = "deshab# ";
|
||||
|
||||
void bash_reset(bash_state *s, int w, int h) {
|
||||
for (int i = 0; i < TERM_MAX_CHARS; i++) {
|
||||
s->ch[i] = ' '; s->fg[i] = KS_TEXT_PRIMARY;
|
||||
}
|
||||
s->cols = (w - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_STEP;
|
||||
s->rows = (h - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_LINE_H;
|
||||
if (s->cols > TERM_MAX_COLS) s->cols = TERM_MAX_COLS;
|
||||
if (s->rows > TERM_MAX_ROWS) s->rows = TERM_MAX_ROWS;
|
||||
if (s->cols < 10) s->cols = 10;
|
||||
if (s->rows < 3) s->rows = 3;
|
||||
s->cur_col = 0; s->cur_row = 0;
|
||||
s->input_len = 0; s->input_cursor = 0;
|
||||
s->prompt_len = kstrlen(BASH_PROMPT);
|
||||
/* 初始化脏行标记:所有行标记为脏,确保首次渲染 */
|
||||
for (int i = 0; i < TERM_MAX_ROWS; i++) s->dirty_rows[i] = 1;
|
||||
s->any_dirty = 1;
|
||||
}
|
||||
|
||||
static void bash_putc(bash_state *s, char c, u32 color) {
|
||||
if (c == '\n') {
|
||||
s->dirty_rows[s->cur_row] = 1; /* 标记当前行为脏 */
|
||||
s->any_dirty = 1;
|
||||
s->cur_col = 0; s->cur_row++;
|
||||
return;
|
||||
}
|
||||
if (s->cur_row >= s->rows) {
|
||||
/* 滚动:所有行都需要重绘 */
|
||||
for (int r = 1; r < s->rows; r++) {
|
||||
for (int c2 = 0; c2 < s->cols; c2++) {
|
||||
s->ch[(r-1)*TERM_MAX_COLS+c2] = s->ch[r*TERM_MAX_COLS+c2];
|
||||
s->fg[(r-1)*TERM_MAX_COLS+c2] = s->fg[r*TERM_MAX_COLS+c2];
|
||||
}
|
||||
}
|
||||
for (int c2 = 0; c2 < s->cols; c2++) {
|
||||
s->ch[(s->rows-1)*TERM_MAX_COLS+c2] = ' ';
|
||||
}
|
||||
s->cur_row = s->rows - 1;
|
||||
/* 标记所有行为脏 */
|
||||
for (int r = 0; r < s->rows; r++) s->dirty_rows[r] = 1;
|
||||
s->any_dirty = 1;
|
||||
}
|
||||
if (s->cur_col >= s->cols) { s->cur_col = 0; s->cur_row++; }
|
||||
int idx = s->cur_row * TERM_MAX_COLS + s->cur_col;
|
||||
s->ch[idx] = (u8)c; s->fg[idx] = color;
|
||||
s->cur_col++;
|
||||
/* 标记当前行为脏 */
|
||||
s->dirty_rows[s->cur_row] = 1;
|
||||
s->any_dirty = 1;
|
||||
}
|
||||
|
||||
void bash_puts(bash_state *s, const char *str, u32 color) {
|
||||
while (*str) bash_putc(s, *str++, color);
|
||||
}
|
||||
|
||||
static void bash_execute(bash_state *s, const char *cmd) {
|
||||
while (*cmd == ' ') cmd++;
|
||||
bash_puts(s, BASH_PROMPT, KS_ACCENT2);
|
||||
bash_puts(s, cmd, KS_TEXT_PRIMARY);
|
||||
bash_putc(s, '\n', 0);
|
||||
|
||||
if (*cmd == 0) return;
|
||||
if (cmd[0]=='h'&&cmd[1]=='e'&&cmd[2]=='l'&&cmd[3]=='p') {
|
||||
bash_puts(s, " help clear echo version\n", KS_ACCENT2);
|
||||
bash_puts(s, " uname about reboot halt\n", KS_ACCENT2);
|
||||
bash_puts(s, " pwd whoami id\n", KS_ACCENT2);
|
||||
} else if (cmd[0]=='c'&&cmd[1]=='l'&&cmd[2]=='e'&&cmd[3]=='a'&&cmd[4]=='r') {
|
||||
for (int i = 0; i < TERM_MAX_CHARS; i++) { s->ch[i] = ' '; }
|
||||
s->cur_col = 0; s->cur_row = 0;
|
||||
} else if (cmd[0]=='v'&&cmd[1]=='e'&&cmd[2]=='r') {
|
||||
bash_puts(s, "Deshab OS v0.1.0 (Winux-Kate shell)\n", KS_ACCENT2);
|
||||
} else if (cmd[0]=='u'&&cmd[1]=='n'&&cmd[2]=='a') {
|
||||
bash_puts(s, "Deshab\n", KS_TEXT_PRIMARY);
|
||||
} else if (cmd[0]=='a'&&cmd[1]=='b'&&cmd[2]=='o'&&cmd[3]=='u'&&cmd[4]=='t') {
|
||||
bash_puts(s, "Deshab OS - SAS-R0 Kernel\n", KS_ACCENT);
|
||||
bash_puts(s, "Winux-Kate UI Structure\n", KS_ACCENT2);
|
||||
} else if (cmd[0]=='e'&&cmd[1]=='c'&&cmd[2]=='h'&&cmd[3]=='o') {
|
||||
const char *arg = cmd + 4;
|
||||
while (*arg == ' ') arg++;
|
||||
bash_puts(s, arg, KS_TEXT_PRIMARY);
|
||||
bash_putc(s, '\n', 0);
|
||||
} else if (cmd[0]=='p'&&cmd[1]=='w'&&cmd[2]=='d') {
|
||||
bash_puts(s, "/\n", KS_TEXT_PRIMARY);
|
||||
} else if (cmd[0]=='w'&&cmd[1]=='h'&&cmd[2]=='o') {
|
||||
bash_puts(s, "root\n", KS_TEXT_PRIMARY);
|
||||
} else if (cmd[0]=='i'&&cmd[1]=='d') {
|
||||
bash_puts(s, "uid=0(root) gid=0(root)\n", KS_TEXT_PRIMARY);
|
||||
} else if (cmd[0]=='r'&&cmd[1]=='e'&&cmd[2]=='b') {
|
||||
bash_puts(s, "Rebooting...\n", KS_ACCENT2);
|
||||
outb(0x64, 0xFE);
|
||||
for(;;) __asm__("hlt");
|
||||
} else if (cmd[0]=='h'&&cmd[1]=='a'&&cmd[2]=='l'&&cmd[3]=='t') {
|
||||
bash_puts(s, "Halted.\n", KS_ACCENT2);
|
||||
for(;;) __asm__("hlt");
|
||||
} else {
|
||||
bash_puts(s, "unknown: ", KS_DANGER);
|
||||
bash_puts(s, cmd, KS_DANGER);
|
||||
bash_putc(s, '\n', 0);
|
||||
}
|
||||
}
|
||||
|
||||
void bash_key(bash_state *s, u8 sc, int shift) {
|
||||
if (sc == 0x1C) {
|
||||
s->input_buf[s->input_len] = 0;
|
||||
bash_execute(s, s->input_buf);
|
||||
s->input_len = 0; s->input_cursor = 0;
|
||||
} else if (sc == 0x0E) {
|
||||
if (s->input_cursor > 0) {
|
||||
for (int i = s->input_cursor-1; i < s->input_len-1; i++)
|
||||
s->input_buf[i] = s->input_buf[i+1];
|
||||
s->input_len--; s->input_cursor--;
|
||||
}
|
||||
} else {
|
||||
char c = scan_to_ascii(sc, shift);
|
||||
if (c && c >= 32 && c <= 126 && s->input_len < 255) {
|
||||
for (int i = s->input_len; i > s->input_cursor; i--)
|
||||
s->input_buf[i] = s->input_buf[i-1];
|
||||
s->input_buf[s->input_cursor++] = c;
|
||||
s->input_len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 绘制终端到指定矩形区域(面板或窗口客户区)
|
||||
* 性能优化:增量渲染,只绘制脏行 */
|
||||
void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) {
|
||||
/* 快速退出:终端内容未修改,跳过绘制 */
|
||||
if (!s->any_dirty) return;
|
||||
|
||||
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY);
|
||||
|
||||
for (int r = 0; r < s->rows; r++) {
|
||||
/* 增量渲染:只绘制脏行 */
|
||||
if (!s->dirty_rows[r]) continue;
|
||||
|
||||
for (int c = 0; c < s->cols; c++) {
|
||||
int idx = r * TERM_MAX_COLS + c;
|
||||
if (s->ch[idx] == ' ') continue;
|
||||
int px = cx + (int)DU_SPACE_SM + c * (int)DU_ASCII_STEP;
|
||||
int py = cy + (int)DU_SPACE_SM + r * (int)DU_ASCII_LINE_H;
|
||||
if (px + (int)DU_ASCII_CELL_W > cx + cw) continue;
|
||||
if (py + (int)DU_ASCII_CELL_H > cy + ch) continue;
|
||||
du_draw_char(&g_fb, s->ch[idx], px, py, s->fg[idx], KS_BG_PRIMARY);
|
||||
}
|
||||
|
||||
/* 清除脏行标记 */
|
||||
s->dirty_rows[r] = 0;
|
||||
}
|
||||
|
||||
int input_y = cy + (int)DU_SPACE_SM + s->cur_row * (int)DU_ASCII_LINE_H;
|
||||
if (input_y + (int)DU_ASCII_CELL_H <= cy + ch) {
|
||||
du_draw_string(&g_fb, BASH_PROMPT,
|
||||
cx + (int)DU_SPACE_SM, input_y,
|
||||
KS_ACCENT2, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
du_draw_string(&g_fb, s->input_buf,
|
||||
cx + (int)DU_SPACE_SM + s->prompt_len * (int)DU_ASCII_STEP,
|
||||
input_y,
|
||||
KS_TEXT_PRIMARY, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
int cursor_x = cx + (int)DU_SPACE_SM + (s->prompt_len + s->input_cursor) * (int)DU_ASCII_STEP;
|
||||
du_fill_rect(&g_fb, cursor_x, input_y + (int)DU_ASCII_CELL_H - 3,
|
||||
(int)DU_ASCII_CELL_W, 2, KS_ACCENT);
|
||||
}
|
||||
|
||||
/* 清除全局脏标记 */
|
||||
s->any_dirty = 0;
|
||||
}
|
||||
|
||||
/* ---- 窗口适配 ---- */
|
||||
__attribute__((unused))
|
||||
static void *bash_on_create(app_ctx *ctx) {
|
||||
bash_state *s = (bash_state *)ADDR_TERMWIN;
|
||||
bash_reset(s, ctx->client_w, ctx->client_h);
|
||||
bash_puts(s, "Deshab Bash v0.1\nType help for commands\n\n", KS_TEXT_PRIMARY);
|
||||
return s;
|
||||
}
|
||||
__attribute__((unused))
|
||||
static void bash_on_event(void *state, app_ctx *ctx, desktop_event *ev) {
|
||||
if (ev->type != EV_KEY_DOWN) return;
|
||||
bash_key((bash_state *)state, ev->scancode, ev->shift);
|
||||
ctx->win->dirty = 1;
|
||||
}
|
||||
__attribute__((unused))
|
||||
static void bash_on_draw(void *state, app_ctx *ctx) {
|
||||
bash_draw_to((bash_state *)state, ctx->client_x, ctx->client_y,
|
||||
ctx->client_w, ctx->client_h);
|
||||
}
|
||||
__attribute__((unused))
|
||||
static void bash_on_destroy(void *state) { (void)state; }
|
||||
|
||||
/* ============================================================
|
||||
* 文本编辑器 (Editor) — 面板/IDE/窗口共用核心
|
||||
* ============================================================ */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void editor_reset(editor_state *s, const char *sample) {
|
||||
s->text_len = 0; s->cursor_pos = 0;
|
||||
s->scroll_y = 0; s->modified = 0;
|
||||
s->has_file = 0;
|
||||
s->disp[0] = 0;
|
||||
while (*sample && s->text_len < EDITOR_BUF_SIZE - 1) {
|
||||
s->text[s->text_len++] = *sample++;
|
||||
}
|
||||
s->cursor_pos = s->text_len;
|
||||
editor_recalc_cursor(s);
|
||||
}
|
||||
|
||||
void editor_key(editor_state *s, u8 sc, int shift) {
|
||||
if (sc == 0x1C) {
|
||||
if (s->text_len < EDITOR_BUF_SIZE - 1) {
|
||||
for (int i = s->text_len; i > s->cursor_pos; i--)
|
||||
s->text[i] = s->text[i-1];
|
||||
s->text[s->cursor_pos] = '\n';
|
||||
s->text_len++; s->cursor_pos++;
|
||||
s->modified = 1;
|
||||
}
|
||||
} else if (sc == 0x0E) {
|
||||
if (s->cursor_pos > 0) {
|
||||
for (int i = s->cursor_pos - 1; i < s->text_len - 1; i++)
|
||||
s->text[i] = s->text[i+1];
|
||||
s->text_len--; s->cursor_pos--;
|
||||
s->modified = 1;
|
||||
}
|
||||
} else if (sc == 0x48) {
|
||||
if (s->cursor_line > 0) {
|
||||
int prev_start = editor_line_start(s, s->cursor_line - 1);
|
||||
int col = s->cursor_col;
|
||||
s->cursor_pos = prev_start;
|
||||
for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++)
|
||||
s->cursor_pos++;
|
||||
}
|
||||
} else if (sc == 0x50) {
|
||||
int next_start = editor_line_start(s, s->cursor_line + 1);
|
||||
if (next_start < s->text_len || s->cursor_line < 999) {
|
||||
int col = s->cursor_col;
|
||||
s->cursor_pos = next_start;
|
||||
for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++)
|
||||
s->cursor_pos++;
|
||||
}
|
||||
} else if (sc == 0x4B) {
|
||||
if (s->cursor_pos > 0) s->cursor_pos--;
|
||||
} else if (sc == 0x4D) {
|
||||
if (s->cursor_pos < s->text_len) s->cursor_pos++;
|
||||
} else if (sc == 0x47) {
|
||||
s->cursor_pos = editor_line_start(s, s->cursor_line);
|
||||
} else if (sc == 0x4F) {
|
||||
s->cursor_pos = editor_line_start(s, s->cursor_line);
|
||||
while (s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n')
|
||||
s->cursor_pos++;
|
||||
} else {
|
||||
char c = scan_to_ascii(sc, shift);
|
||||
if (c && c >= 32 && c <= 126 && s->text_len < EDITOR_BUF_SIZE - 1) {
|
||||
for (int i = s->text_len; i > s->cursor_pos; i--)
|
||||
s->text[i] = s->text[i-1];
|
||||
s->text[s->cursor_pos] = c;
|
||||
s->text_len++; s->cursor_pos++;
|
||||
s->modified = 1;
|
||||
}
|
||||
}
|
||||
editor_recalc_cursor(s);
|
||||
}
|
||||
|
||||
/* 绘制编辑器到指定矩形区域 */
|
||||
void editor_draw_to(editor_state *s, int cx, int cy, int cw, int ch) {
|
||||
int line_num_w = 5 * (int)DU_ASCII_STEP + (int)DU_SPACE_SM;
|
||||
int text_area_x = cx + line_num_w;
|
||||
int text_area_w = cw - line_num_w;
|
||||
int visible_rows = (ch - 20) / (int)DU_ASCII_LINE_H;
|
||||
int visible_cols = text_area_w / (int)DU_ASCII_STEP;
|
||||
if (visible_rows < 1) visible_rows = 1;
|
||||
if (visible_cols < 4) visible_cols = 4;
|
||||
|
||||
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY);
|
||||
|
||||
if (s->cursor_line < s->scroll_y) s->scroll_y = s->cursor_line;
|
||||
if (s->cursor_line >= s->scroll_y + visible_rows) s->scroll_y = s->cursor_line - visible_rows + 1;
|
||||
|
||||
int line = 0;
|
||||
int pos = 0;
|
||||
for (int i = 0; i < s->text_len || line <= s->cursor_line; ) {
|
||||
if (line >= s->scroll_y + visible_rows) break;
|
||||
|
||||
if (line >= s->scroll_y) {
|
||||
int screen_y = cy + (line - s->scroll_y) * (int)DU_ASCII_LINE_H;
|
||||
|
||||
char ln[8];
|
||||
ln[0] = '0' + ((line+1)/100)%10;
|
||||
ln[1] = '0' + ((line+1)/10)%10;
|
||||
ln[2] = '0' + (line+1)%10;
|
||||
ln[3] = 0;
|
||||
du_draw_string(&g_fb, ln, cx + 2, screen_y,
|
||||
KS_TEXT_DIM, KS_BG_SECONDARY, DU_ASCII_STEP);
|
||||
|
||||
du_fill_rect(&g_fb, cx + line_num_w - 2, screen_y, 1,
|
||||
(int)DU_ASCII_CELL_H, KS_BORDER_DIM);
|
||||
|
||||
int col = 0;
|
||||
while (pos < s->text_len && s->text[pos] != '\n' && col < visible_cols) {
|
||||
du_draw_char(&g_fb, s->text[pos],
|
||||
text_area_x + col * (int)DU_ASCII_STEP,
|
||||
screen_y,
|
||||
KS_TEXT_PRIMARY, KS_BG_SECONDARY);
|
||||
col++; pos++;
|
||||
}
|
||||
if (pos < s->text_len && s->text[pos] == '\n') pos++;
|
||||
} else {
|
||||
while (pos < s->text_len && s->text[pos] != '\n') pos++;
|
||||
if (pos < s->text_len) pos++;
|
||||
}
|
||||
line++;
|
||||
if (pos >= s->text_len && line > s->cursor_line) break;
|
||||
}
|
||||
|
||||
/* 光标(青色竖条) */
|
||||
if (s->cursor_line >= s->scroll_y && s->cursor_line < s->scroll_y + visible_rows) {
|
||||
int cursor_y = cy + (s->cursor_line - s->scroll_y) * (int)DU_ASCII_LINE_H;
|
||||
int cursor_x = text_area_x + s->cursor_col * (int)DU_ASCII_STEP;
|
||||
du_fill_rect(&g_fb, cursor_x, cursor_y, 2, (int)DU_ASCII_CELL_H, KS_ACCENT);
|
||||
}
|
||||
|
||||
/* 底部迷你状态栏 */
|
||||
int status_y = cy + ch - 20;
|
||||
du_fill_rect(&g_fb, cx, status_y, cw, 20, KS_BG_TERTIARY);
|
||||
du_divider_h(&g_fb, cx, status_y, cw, KS_BORDER);
|
||||
char status[64];
|
||||
int sp = 0;
|
||||
const char *p1 = "L:"; while(*p1) status[sp++] = *p1++;
|
||||
status[sp++] = '0' + (s->cursor_line+1)/10%10;
|
||||
status[sp++] = '0' + (s->cursor_line+1)%10;
|
||||
const char *p2 = " C:"; while(*p2) status[sp++] = *p2++;
|
||||
status[sp++] = '0' + (s->cursor_col+1)/10%10;
|
||||
status[sp++] = '0' + (s->cursor_col+1)%10;
|
||||
if (s->modified) { const char *m = " *"; while(*m) status[sp++] = *m++; }
|
||||
if (s->has_file) {
|
||||
const char *m = " "; while(*m) status[sp++] = *m++;
|
||||
for (int i = 0; s->disp[i] && sp < 60; i++) status[sp++] = s->disp[i];
|
||||
}
|
||||
status[sp] = 0;
|
||||
du_draw_string(&g_fb, status, cx + 4, status_y + 1,
|
||||
KS_TEXT_DIM, KS_BG_TERTIARY, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* ---- 窗口适配 ---- */
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
void editor_on_destroy(void *state) { (void)state; }
|
||||
|
||||
/* ============================================================
|
||||
* 计算器 (Calculator)
|
||||
* ============================================================ */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void *calc_on_create(app_ctx *ctx) {
|
||||
calc_state *s = (calc_state *)ADDR_CALC;
|
||||
s->accumulator = 0; s->current = 0;
|
||||
s->op = 0; s->new_number = 1;
|
||||
calc_update_display(s);
|
||||
const char *t = "Calculator";
|
||||
for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i];
|
||||
return s;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void calc_on_draw(void *state, app_ctx *ctx) {
|
||||
calc_state *s = (calc_state *)state;
|
||||
int cx = ctx->client_x;
|
||||
int cy = ctx->client_y;
|
||||
int cw = ctx->client_w;
|
||||
int ch = ctx->client_h;
|
||||
|
||||
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY);
|
||||
|
||||
int disp_h = 40;
|
||||
du_fill_rounded_rect(&g_fb, cx + 8, cy + 8, cw - 16, disp_h,
|
||||
KS_BG_PRIMARY, DU_RADIUS_MD);
|
||||
du_rect_outline(&g_fb, cx + 8, cy + 8, cw - 16, disp_h,
|
||||
KS_ACCENT, DU_RADIUS_MD);
|
||||
int text_x = cx + cw - 16 - s->display_len * (int)DU_ASCII_STEP - 8;
|
||||
du_draw_string(&g_fb, s->display, text_x, cy + 16,
|
||||
KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
|
||||
const char *btn_labels[] = {
|
||||
"C", "+/-", "%", "/",
|
||||
"7", "8", "9", "*",
|
||||
"4", "5", "6", "-",
|
||||
"1", "2", "3", "+",
|
||||
"0", ".", "=", 0
|
||||
};
|
||||
int btn_w = (cw - 16 - 12) / 4;
|
||||
int btn_h = 32;
|
||||
int btn_y = cy + 8 + disp_h + 8;
|
||||
for (int i = 0; i < 19; i++) {
|
||||
int row = i / 4, col = i % 4;
|
||||
int bx = cx + 8 + col * (btn_w + 3);
|
||||
int by = btn_y + row * (btn_h + 3);
|
||||
if (by + btn_h > cy + ch - 4) break;
|
||||
u32 bg = (i < 4) ? KS_ACCENT_DIM :
|
||||
(i % 4 == 3) ? KS_ACCENT :
|
||||
(i == 18) ? KS_ACCENT2 :
|
||||
KS_BG_TERTIARY;
|
||||
u32 fg = ((i % 4 == 3) || (i == 18)) ? KS_TEXT_INVERT : KS_TEXT_PRIMARY;
|
||||
du_fill_rounded_rect(&g_fb, bx, by, btn_w, btn_h, bg, DU_RADIUS_SM);
|
||||
du_rect_outline(&g_fb, bx, by, btn_w, btn_h, KS_BORDER, DU_RADIUS_SM);
|
||||
du_draw_string(&g_fb, btn_labels[i],
|
||||
bx + (btn_w - 1*(int)DU_ASCII_STEP)/2,
|
||||
by + (btn_h - (int)DU_ASCII_CELL_H)/2,
|
||||
fg, bg, DU_ASCII_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
void calc_on_destroy(void *state) { (void)state; }
|
||||
|
||||
/* ============================================================
|
||||
* FILES 面板(FAT32 真实读写)
|
||||
* ============================================================ */
|
||||
|
||||
static int files_list_cb(const char *name, u32 size, u8 attr, void *ud) {
|
||||
(void)ud;
|
||||
if (attr & 0x10) return 0; /* 跳过子目录 */
|
||||
if (g_file_count >= MAX_FILES) return 1; /* 停止 */
|
||||
|
||||
/* 只显示 user 文件:跳过 .ELF 可执行文件和系统模块 */
|
||||
int nl = kstrlen(name);
|
||||
if (nl >= 4 && name[nl-4] == '.' && name[nl-3] == 'E' &&
|
||||
name[nl-2] == 'L' && name[nl-1] == 'F') return 0;
|
||||
/* FIRSTINI* (FirstInit.elf,Name83=FIRSTINIT → 显示名 FIRSTINI.T) */
|
||||
if (nl >= 8 && name[0] == 'F' && name[1] == 'I' && name[2] == 'R' &&
|
||||
name[3] == 'S' && name[4] == 'T' && name[5] == 'I' && name[6] == 'N' &&
|
||||
name[7] == 'I') return 0;
|
||||
|
||||
fentry *f = &g_files[g_file_count];
|
||||
f->size = size;
|
||||
kstrcpy(f->disp, name, 13);
|
||||
/* disp (NAME.EXT) → name11 (NAME EXT) */
|
||||
for (int i = 0; i < 11; i++) f->name11[i] = ' ';
|
||||
int i = 0, o = 0;
|
||||
while (name[i] && name[i] != '.' && o < 8) f->name11[o++] = name[i++];
|
||||
while (name[i] && name[i] != '.') i++;
|
||||
if (name[i] == '.') {
|
||||
i++;
|
||||
o = 8;
|
||||
while (name[i] && o < 11) f->name11[o++] = name[i++];
|
||||
}
|
||||
g_file_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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_w(files_list_cb, 0) == 0) g_files_loaded = 1;
|
||||
else g_files_loaded = 0;
|
||||
}
|
||||
|
||||
/* 双击打开文件 → 载入 DASHBOARD EDITOR */
|
||||
void files_open(int idx) {
|
||||
if (idx < 0 || idx >= g_file_count) return;
|
||||
editor_state *ed = (editor_state *)ADDR_EDDASH;
|
||||
fentry *f = &g_files[idx];
|
||||
|
||||
u8 *data = 0; u32 size = 0;
|
||||
if (f32_read_root_file_w(f->name11, &data, &size) != 0) {
|
||||
slog("file open failed");
|
||||
return;
|
||||
}
|
||||
ed->text_len = 0;
|
||||
u32 cap = EDITOR_BUF_SIZE - 1;
|
||||
if (size > cap) size = cap;
|
||||
for (u32 i = 0; i < size; i++) {
|
||||
char c = (char)data[i];
|
||||
if ((u8)c >= 32 || c == '\n' || c == '\t')
|
||||
ed->text[ed->text_len++] = c;
|
||||
}
|
||||
for (int i = 0; i < 11; i++) ed->name11[i] = f->name11[i];
|
||||
kstrcpy(ed->disp, f->disp, 13);
|
||||
ed->has_file = 1;
|
||||
ed->modified = 0;
|
||||
ed->scroll_y = 0;
|
||||
ed->cursor_pos = 0;
|
||||
editor_recalc_cursor(ed);
|
||||
g_dash_focus = 2;
|
||||
slog("file loaded into editor");
|
||||
}
|
||||
|
||||
/* EDITOR SAVE 回写磁盘 */
|
||||
void editor_save(void) {
|
||||
editor_state *ed = (editor_state *)ADDR_EDDASH;
|
||||
if (!ed->has_file) return;
|
||||
if (f32_write_root_file_w(ed->name11, (const u8 *)ed->text, (u32)ed->text_len) == 0) {
|
||||
ed->modified = 0;
|
||||
slog("file saved");
|
||||
} else {
|
||||
slog("file save failed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* ascii_font.c — ASCII 位图字体单副本
|
||||
*
|
||||
* 经 #define 重命名包含 firstInit/ascii_bitmaps.c(其内部为 static 定义),
|
||||
* 再以同名导出指针/值,使所有编译单元共享一份字形数据。
|
||||
*/
|
||||
#include "ascii_font.h"
|
||||
|
||||
#define g_ascii g_ascii_static
|
||||
#define g_ascii_w g_ascii_w_static
|
||||
#define g_ascii_h g_ascii_h_static
|
||||
#include "../firstInit/ascii_bitmaps.c"
|
||||
#undef g_ascii
|
||||
#undef g_ascii_w
|
||||
#undef g_ascii_h
|
||||
|
||||
const unsigned char (*const g_ascii)[11 * 18] = g_ascii_static;
|
||||
const int g_ascii_w = g_ascii_w_static;
|
||||
const int g_ascii_h = g_ascii_h_static;
|
||||
@@ -0,0 +1,18 @@
|
||||
/* ascii_font.h — Consolas 18px ASCII 位图字体(全程序单副本导出)
|
||||
*
|
||||
* 背景:ascii_bitmaps.c 以 static 数组定义 g_ascii(含它的编译单元各持一份
|
||||
* ~19KB 副本)。多模块拆分后由 ascii_font.c 经 #define 重命名技巧包含
|
||||
* ascii_bitmaps.c,导出唯一一份;deshab_ui.h 的 du_draw_char/du_draw_string
|
||||
* 引用这些 extern 符号。
|
||||
*
|
||||
* 用法:#include "ascii_font.h" 之后 #include <utsm/deshab_ui.h>。
|
||||
* firstInit 等旧代码仍直接 include ascii_bitmaps.c,不受影响。
|
||||
*/
|
||||
#ifndef DESHAB_ASCII_FONT_H
|
||||
#define DESHAB_ASCII_FONT_H
|
||||
|
||||
extern const unsigned char (*const g_ascii)[11 * 18];
|
||||
extern const int g_ascii_w;
|
||||
extern const int g_ascii_h;
|
||||
|
||||
#endif /* DESHAB_ASCII_FONT_H */
|
||||
@@ -0,0 +1,222 @@
|
||||
/* compositor.c — 脏矩形、双缓冲 flip、全屏重绘(M0 自 main.c 平移)
|
||||
* M2:Fluent 重绘路径(壁纸背景 + 无 topbar + 悬浮任务栏 overlay)。
|
||||
*/
|
||||
#include "desktop.h"
|
||||
#include "ascii_font.h"
|
||||
#include "../UTSM/include/utsm/fluent_ui.h"
|
||||
#include "wallpaper.h"
|
||||
|
||||
/* ---- 全局状态 ---- */
|
||||
static dirty_rect g_dirty_rects[MAX_DIRTY_RECTS];
|
||||
int g_dirty_count = 0;
|
||||
int g_full_redraw = 1; /* 初始全屏重绘 */
|
||||
|
||||
/* 标记脏区域 */
|
||||
__attribute__((unused))
|
||||
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++;
|
||||
}
|
||||
|
||||
/* 合并所有脏矩形为一个包围盒 */
|
||||
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;
|
||||
}
|
||||
|
||||
/* 清除所有脏矩形 */
|
||||
void clear_dirty_rects(void) {
|
||||
g_dirty_count = 0;
|
||||
g_full_redraw = 0;
|
||||
}
|
||||
|
||||
/* ---- 双缓冲 flip ---- */
|
||||
|
||||
/* 将 sprite buffer 完整复制到真实 framebuffer */
|
||||
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];
|
||||
}
|
||||
|
||||
/* 仅复制指定矩形区域(用于鼠标移动时的局部更新) */
|
||||
void flip_rect(int x, int y, int w, int h) {
|
||||
if (!g_real_fb) return;
|
||||
for (int r = 0; r < h; r++) {
|
||||
int ry = y + r;
|
||||
if (ry < 0 || (u64)ry >= g_fb_h) continue;
|
||||
u32 *dst = (u32 *)(g_real_fb + (u64)ry * g_fb_pitch);
|
||||
u32 *src = (u32 *)((u8 *)SPRITE_BUF_ADDR + (u64)ry * g_fb_pitch);
|
||||
for (int c = 0; c < w; c++) {
|
||||
int rx = x + c;
|
||||
if (rx < 0 || (u64)rx >= g_fb_w) continue;
|
||||
dst[rx] = src[rx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 非阻塞延时:动画期间持续轮询鼠标,保持光标响应。 */
|
||||
__attribute__((unused))
|
||||
void anim_delay_poll_mouse(u32 ms) {
|
||||
for (u32 i = 0; i < ms; i++) {
|
||||
for (volatile u32 j = 0; j < 50000; j++) {
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
if (ps2_mouse_poll()) {
|
||||
int old_x = g_cursor_old_x, old_y = g_cursor_old_y;
|
||||
cursor_restore_bg();
|
||||
flip_rect(old_x, old_y, CURSOR_SIZE, CURSOR_SIZE);
|
||||
cursor_save_bg(g_mouse_x, g_mouse_y);
|
||||
cursor_draw(g_mouse_x, g_mouse_y);
|
||||
flip_rect(g_mouse_x, g_mouse_y, CURSOR_SIZE, CURSOR_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 全屏重绘
|
||||
* ============================================================ */
|
||||
|
||||
/* 全局亮度(BRIGHT 滑块真实调暗)
|
||||
* 性能优化:仅在亮度变化时处理,避免每帧全屏遍历 */
|
||||
static void apply_brightness(void) {
|
||||
/* 快速退出:亮度100%无需调整 */
|
||||
if (g_bright >= 100) {
|
||||
g_bright_applied = 100;
|
||||
return;
|
||||
}
|
||||
|
||||
/* 快速退出:亮度未变化,跳过处理 */
|
||||
if (g_bright == g_bright_applied) return;
|
||||
|
||||
/* 记录当前应用的亮度值 */
|
||||
g_bright_applied = g_bright;
|
||||
|
||||
u32 f = (u32)g_bright;
|
||||
for (u64 y = 0; y < g_fb_h; y++) {
|
||||
u32 *line = (u32 *)((u8 *)g_fb.fb + y * g_fb_pitch);
|
||||
for (u64 x = 0; x < g_fb_w; x++) {
|
||||
u32 c = line[x];
|
||||
u32 b = ((c >> 16) & 0xFF) * f / 100;
|
||||
u32 g = ((c >> 8) & 0xFF) * f / 100;
|
||||
u32 r = (c & 0xFF) * f / 100;
|
||||
line[x] = 0xFF000000u | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void redraw_all_fluent(void);
|
||||
|
||||
void redraw_all(void) {
|
||||
if (g_fluent) {
|
||||
redraw_all_fluent();
|
||||
return;
|
||||
}
|
||||
|
||||
/* ---- KATE 路径(fluent=0 回退) ---- */
|
||||
/* 深空黑渐变背景 */
|
||||
du_fill_bg_gradient(&g_fb, KS_BG_PRIMARY, KS_BG_TERTIARY);
|
||||
|
||||
/* 顶栏 */
|
||||
draw_topbar();
|
||||
|
||||
/* 页面 */
|
||||
if (g_page == 1) draw_dashboard();
|
||||
else if (g_page == 2) draw_ide();
|
||||
else if (g_page == 3) draw_desktop_page();
|
||||
else {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < g_cpage_count; i++)
|
||||
if (g_cpages[i].id == g_page) { idx = i; break; }
|
||||
if (idx >= 0) draw_custom_page(idx);
|
||||
else g_page = 1;
|
||||
}
|
||||
|
||||
/* 任务视图 overlay */
|
||||
if (g_taskview) draw_taskview();
|
||||
|
||||
/* 右键菜单(最顶) */
|
||||
if (g_ctx_open) draw_ctx_menu();
|
||||
|
||||
/* 亮度调整 */
|
||||
apply_brightness();
|
||||
|
||||
/* 鼠标光标 */
|
||||
cursor_save_bg(g_mouse_x, g_mouse_y);
|
||||
cursor_draw(g_mouse_x, g_mouse_y);
|
||||
}
|
||||
|
||||
/* M2 Fluent 重绘:壁纸背景(缺失回退深色渐变)+ 页面 + 悬浮任务栏 */
|
||||
static void redraw_all_fluent(void) {
|
||||
/* 背景:壁纸层缓存直拷,失败回退渐变 */
|
||||
{
|
||||
wp_u32 *wp = wallpaper_layer();
|
||||
if (wp) {
|
||||
for (u64 y = 0; y < g_fb_h; y++) {
|
||||
u32 *line = (u32 *)((u8 *)g_fb.fb + y * g_fb_pitch);
|
||||
const u32 *srcline = wp + y * (u64)g_fb_w;
|
||||
for (u64 x = 0; x < g_fb_w; x++) line[x] = srcline[x];
|
||||
}
|
||||
} else {
|
||||
du_fill_bg_gradient(&g_fb, 0xFF0A0E1Cu, 0xFF16264Au);
|
||||
}
|
||||
}
|
||||
|
||||
/* 页面(无 topbar;DESKTOP 页绘制窗口,任务栏由 taskbar_draw 叠加) */
|
||||
if (g_page == 1) draw_dashboard();
|
||||
else if (g_page == 2) draw_ide();
|
||||
else if (g_page == 3) draw_desktop_page();
|
||||
else {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < g_cpage_count; i++)
|
||||
if (g_cpages[i].id == g_page) { idx = i; break; }
|
||||
if (idx >= 0) draw_custom_page(idx);
|
||||
else g_page = 3; /* Fluent 默认页 = DESKTOP */
|
||||
}
|
||||
|
||||
/* 任务视图 overlay */
|
||||
if (g_taskview) draw_taskview();
|
||||
|
||||
/* 右键菜单(最顶) */
|
||||
if (g_ctx_open) draw_ctx_menu();
|
||||
|
||||
/* 悬浮任务栏(DESKTOP 页;其他页也显示以保持导航) */
|
||||
taskbar_draw();
|
||||
|
||||
/* 亮度调整 */
|
||||
apply_brightness();
|
||||
|
||||
/* 鼠标光标 */
|
||||
cursor_save_bg(g_mouse_x, g_mouse_y);
|
||||
cursor_draw(g_mouse_x, g_mouse_y);
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
/* desktop.h — Deshab 桌面跨模块共享契约(M0 模块化拆分)
|
||||
*
|
||||
* 模块划分(M0:纯平移,零功能变更):
|
||||
* main.c 组合根:dsk_entry、服务探测、FUCK、boot 屏、主事件循环
|
||||
* input.c PS/2 鼠标键盘、光标、RTC/TSC 时钟
|
||||
* wm.c 窗口管理(win_*)+ 窗口装饰
|
||||
* compositor.c 脏矩形、双缓冲 flip、全屏重绘
|
||||
* apps_builtin.c 内置应用 bash/editor/calc + FILES 面板
|
||||
* appreg.c 应用注册表(内置/LINUXAPP/PEAPPS/user desktop)+ 图标绘制
|
||||
* launcher.c 统一启动器 + 外部 ELF 加载 + PE 窗口运行时
|
||||
* lxc.c Linux guest / IDE / VSCode 启动链路 + 性能 profiling
|
||||
* shell.c 工作区/页面状态机 + 任务视图/右键菜单 + 事件路由
|
||||
* pages.c KATE 风格页面绘制(topbar/dashboard/ide/custom/desktop)
|
||||
* f32io.c fat32_io.h 单编译单元封装(4MB 缓冲唯一持有者)
|
||||
* ascii_font.c ASCII 位图字体单副本
|
||||
* zhfont.c 中文位图字库(.dbf)加载与渲染(M1)
|
||||
* wallpaper.c 壁纸背景层缓存(M1)
|
||||
*
|
||||
* 依赖单向:main → shell → (pages/appreg/launcher/lxc) → wm/compositor → input。
|
||||
* 全局状态按"最 owning 模块定义、此处 extern"原则组织。
|
||||
*/
|
||||
#ifndef DESHAB_DESKTOP_H
|
||||
#define DESHAB_DESKTOP_H
|
||||
|
||||
#include "../UTSM/include/utsm/dsk.h"
|
||||
#include "../UTSM/include/utsm/linux_compat.h" /* VSCode Phase 2: input_forward_* */
|
||||
#include "../UTSM/include/utsm/pe.h" /* P5d: PE 窗口模式 run_windowed/inject_* */
|
||||
/* ascii_font.h 须在 deshab_ui.h 之前(g_ascii);du_context 在 app_ctx 里用到 */
|
||||
#include "ascii_font.h"
|
||||
#include "../UTSM/include/utsm/deshab_ui.h"
|
||||
|
||||
/* block 设备函数类型(从 kernel_api + 0xA8 获取 block_api,read @ +0x10, write @ +0x18) */
|
||||
typedef int (*desktop_block_read_fn)(u32 index, u64 lba, u32 count, void *buffer);
|
||||
typedef int (*desktop_block_write_fn)(u32 index, u64 lba, u32 count, const void *buffer);
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef signed char i8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
typedef long long i64;
|
||||
|
||||
#define COM1 0x3F8
|
||||
|
||||
/* ---- 小工具(inline,各模块共享) ---- */
|
||||
|
||||
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 __inline__ void sputc(char c) {
|
||||
for (unsigned int i = 0; i < 100000; i++) { if (inb(COM1+5)&0x20) break; }
|
||||
outb(COM1, (unsigned char)c);
|
||||
}
|
||||
static __inline__ void swrite(const char *s) { while(*s) { if(*s=='\n')sputc('\r'); sputc(*s++); } }
|
||||
static __inline__ void slog(const char *s) { swrite("[desktop] "); swrite(s); swrite("\n"); }
|
||||
|
||||
static __inline__ int kstrlen(const char *s) { int n = 0; while (s[n]) n++; return n; }
|
||||
static __inline__ 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 __inline__ void kstrcat(char *d, const char *s, int cap) {
|
||||
int n = kstrlen(d);
|
||||
if (n < cap) kstrcpy(d + n, s, cap - n);
|
||||
}
|
||||
|
||||
/* P7.9: u32 -> decimal string(返回长度) */
|
||||
static __inline__ int u32dec(u32 v, char *out) {
|
||||
char tmp[10];
|
||||
int n = 0;
|
||||
if (v == 0) { out[0] = '0'; out[1] = 0; return 1; }
|
||||
while (v > 0) { tmp[n++] = '0' + (char)(v % 10); v /= 10; }
|
||||
for (int i = 0; i < n; i++) out[i] = tmp[n - 1 - i];
|
||||
out[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static __inline__ u64 rdtsc(void) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
|
||||
/* ---- 常量(Winux-Kate 布局) ---- */
|
||||
|
||||
#define MAX_WINDOWS 8
|
||||
#define BORDER_W 1
|
||||
#define CURSOR_SIZE 24
|
||||
|
||||
#define ICON_W KATE_ICON_W /* 104 */
|
||||
#define ICON_H KATE_ICON_H /* 90 */
|
||||
#define ICON_GAP KATE_ICON_GAP /* 18 */
|
||||
#define ICON_PAD KATE_ICON_PAD /* 24 */
|
||||
|
||||
#define CLOSE_BTN_SIZE 16
|
||||
#define CLOSE_BTN_OFFSET 22
|
||||
|
||||
#define MAX_WS 6 /* 工作区上限 */
|
||||
#define MAX_CPAGES 4 /* 自定义页上限 */
|
||||
#define MAX_FILES 24 /* FILES 面板条目上限 */
|
||||
#define MAX_ICONS 24
|
||||
#define MAX_APPS 24
|
||||
|
||||
/* 应用固定状态地址(单实例策略 → 每类应用唯一实例) */
|
||||
#define ADDR_TERM1 0x4000000ULL
|
||||
#define ADDR_TERM2 0x4100000ULL
|
||||
#define ADDR_TERMWIN 0x4200000ULL
|
||||
#define ADDR_EDDASH 0x5000000ULL
|
||||
#define ADDR_EDWIN 0x5100000ULL
|
||||
#define ADDR_CALC 0x6000000ULL
|
||||
|
||||
/* 双缓冲:渲染到 sprite buffer,完成后一次性 flip 到真实 framebuffer */
|
||||
#define SPRITE_BUF_ADDR 0x7000000ULL /* 112MB,在 identity-mapped 区域 */
|
||||
|
||||
#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 */
|
||||
|
||||
/* ---- 事件系统 ---- */
|
||||
|
||||
typedef enum {
|
||||
EV_MOUSE_MOVE = 1,
|
||||
EV_MOUSE_DOWN,
|
||||
EV_MOUSE_UP,
|
||||
EV_KEY_DOWN,
|
||||
EV_KEY_UP,
|
||||
} ev_type;
|
||||
|
||||
typedef struct {
|
||||
ev_type type;
|
||||
int mx, my;
|
||||
int button; /* 0=左, 1=右 */
|
||||
u8 scancode;
|
||||
int shift, ctrl, alt;
|
||||
} desktop_event;
|
||||
|
||||
/* ---- 窗口 / 应用 ---- */
|
||||
|
||||
typedef struct desktop_window {
|
||||
int id;
|
||||
int x, y, w, h;
|
||||
int visible;
|
||||
int focused;
|
||||
int minimized;
|
||||
char title[48];
|
||||
int app_id;
|
||||
void *app_state;
|
||||
int closing;
|
||||
int dirty;
|
||||
int ws; /* 所属工作区 */
|
||||
} desktop_window;
|
||||
|
||||
typedef struct desktop_window desktop_win;
|
||||
|
||||
typedef struct app_ctx {
|
||||
du_context *fb;
|
||||
desktop_win *win;
|
||||
int client_x, client_y, client_w, client_h;
|
||||
int should_exit;
|
||||
desktop_block_read_fn block_read;
|
||||
desktop_block_write_fn block_write;
|
||||
const void *kernel_api;
|
||||
} app_ctx;
|
||||
|
||||
typedef void *(*app_create_fn)(app_ctx *ctx);
|
||||
typedef void (*app_event_fn)(void *state, app_ctx *ctx, desktop_event *ev);
|
||||
typedef void (*app_draw_fn)(void *state, app_ctx *ctx);
|
||||
typedef void (*app_destroy_fn)(void *state);
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *display_name;
|
||||
u32 default_w, default_h;
|
||||
app_create_fn on_create;
|
||||
app_event_fn on_event;
|
||||
app_draw_fn on_draw;
|
||||
app_destroy_fn on_destroy;
|
||||
const char *elf_name;
|
||||
/* VSCode Phase 5: Linux guest 应用(来自 LINUXAPP.CNF)。
|
||||
* linux_path 非 0 时 launch_app 走 launch_linux_app → exec_async。 */
|
||||
const char *linux_path;
|
||||
const char *linux_args;
|
||||
/* P5d: Windows PE 应用(来自 PEAPPS.CNF)。
|
||||
* pe_file 非 0 时 launch_app 走 launch_pe_app → run_windowed 桌面嵌入。 */
|
||||
const char *pe_file;
|
||||
const char *pe_args;
|
||||
/* M2: 1 = 来自 user/desktop/*.lnk 的用户快捷方式(桌面只显示这些) */
|
||||
int user_desktop;
|
||||
} app_descriptor;
|
||||
|
||||
/* 桌面图标 */
|
||||
typedef struct {
|
||||
int app_id;
|
||||
int x, y;
|
||||
const char *label;
|
||||
int selected;
|
||||
} desktop_icon;
|
||||
|
||||
/* 工作区 */
|
||||
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;
|
||||
|
||||
/* ---- 内置应用状态 ---- */
|
||||
|
||||
#define TERM_MAX_COLS 80
|
||||
#define TERM_MAX_ROWS 40
|
||||
#define TERM_MAX_CHARS (TERM_MAX_COLS * TERM_MAX_ROWS)
|
||||
|
||||
typedef struct {
|
||||
u8 ch[TERM_MAX_CHARS];
|
||||
u32 fg[TERM_MAX_CHARS];
|
||||
int cols, rows;
|
||||
int cur_col, cur_row;
|
||||
char input_buf[256];
|
||||
int input_len, input_cursor;
|
||||
int prompt_len;
|
||||
/* 性能优化:脏行标记(增量渲染) */
|
||||
u8 dirty_rows[TERM_MAX_ROWS]; /* 标记哪些行需要重绘 */
|
||||
int any_dirty; /* 是否有任何脏行 */
|
||||
} bash_state;
|
||||
|
||||
#define EDITOR_BUF_SIZE 65536
|
||||
|
||||
typedef struct {
|
||||
char text[EDITOR_BUF_SIZE];
|
||||
int text_len;
|
||||
int cursor_pos;
|
||||
int scroll_y;
|
||||
int cursor_line;
|
||||
int cursor_col;
|
||||
int modified;
|
||||
int has_file; /* 关联了磁盘文件 */
|
||||
char name11[11]; /* FAT32 8.3 名 */
|
||||
char disp[13]; /* 显示名 */
|
||||
} editor_state;
|
||||
|
||||
typedef struct {
|
||||
char display[32];
|
||||
int display_len;
|
||||
i64 accumulator;
|
||||
i64 current;
|
||||
int op;
|
||||
int new_number;
|
||||
} calc_state;
|
||||
|
||||
/* ---- 脏矩形 ---- */
|
||||
|
||||
#define MAX_DIRTY_RECTS 16
|
||||
|
||||
typedef struct {
|
||||
int x, y, w, h;
|
||||
int active;
|
||||
} dirty_rect;
|
||||
|
||||
/* ---- FAT32 单 TU 封装(f32io.c 持有 fat32_io.h 的 4MB 缓冲) ---- */
|
||||
#include "f32io.h"
|
||||
/* 纯字符串助手(无状态,可安全多 TU 包含) */
|
||||
#include "../tools/fat32_lfn.h"
|
||||
|
||||
/* ============================================================
|
||||
* 全局状态 extern(定义见各 owning 模块)
|
||||
* ============================================================ */
|
||||
|
||||
/* ---- main.c:渲染上下文 / 服务指针 ---- */
|
||||
extern du_context g_fb;
|
||||
extern u64 g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch;
|
||||
extern const void *g_kernel_api;
|
||||
extern u8 *g_real_fb;
|
||||
extern desktop_block_read_fn g_block_read;
|
||||
extern desktop_block_write_fn g_block_write;
|
||||
extern const dsk_boot_context *g_boot_ctx;
|
||||
extern const linux_compat_service *g_lxc_svc;
|
||||
extern int g_input_forward_enabled;
|
||||
extern const pe_service *g_pe_svc;
|
||||
|
||||
/* ---- input.c:输入 / 时钟 ---- */
|
||||
extern int g_mouse_x, g_mouse_y;
|
||||
extern int g_mouse_btn; /* bit0=左 bit1=右 */
|
||||
extern int g_left_pressed, g_left_released, g_right_pressed;
|
||||
extern int g_cursor_saved;
|
||||
extern int g_cursor_old_x, g_cursor_old_y;
|
||||
extern int g_shift, g_ctrl, g_alt, g_e0;
|
||||
extern int g_rtc_boot_sec; /* 开机时刻(秒级当天秒) */
|
||||
extern int g_last_sec;
|
||||
extern u64 g_tsc_per_sec;
|
||||
|
||||
/* ---- wm.c:窗口 ---- */
|
||||
extern desktop_window g_windows[MAX_WINDOWS];
|
||||
extern int g_win_count;
|
||||
extern int g_focused_win;
|
||||
extern int g_dragging;
|
||||
extern int g_drag_win;
|
||||
extern int g_drag_off_x, g_drag_off_y;
|
||||
|
||||
/* ---- compositor.c:脏矩形 ---- */
|
||||
extern int g_dirty_count;
|
||||
extern int g_full_redraw;
|
||||
|
||||
/* ---- apps_builtin.c:FILES 面板 ---- */
|
||||
extern fentry g_files[MAX_FILES];
|
||||
extern int g_file_count;
|
||||
extern int g_file_sel;
|
||||
extern int g_files_loaded;
|
||||
|
||||
/* ---- appreg.c:应用表 / 图标 ---- */
|
||||
extern app_descriptor g_apps[MAX_APPS];
|
||||
extern int g_app_count;
|
||||
extern desktop_icon g_icons[MAX_ICONS];
|
||||
extern int g_icon_count;
|
||||
extern int g_vscode_app_id; /* 第一个名为 "vscode" 的注册项 */
|
||||
|
||||
/* ---- launcher.c:PE 运行时 ---- */
|
||||
extern int g_pe_running;
|
||||
extern int g_pe_win_id;
|
||||
extern u8 *g_pe_surf;
|
||||
extern int g_pe_surf_w, g_pe_surf_h;
|
||||
|
||||
/* ---- lxc.c:IDE / guest / profiling ---- */
|
||||
extern int g_ide_attached;
|
||||
extern int g_vscode_state; /* 0=未运行 1=启动中 2=运行中 */
|
||||
extern int g_launch_phase;
|
||||
extern char g_launch_status[64];
|
||||
extern u64 g_launch_tsc;
|
||||
extern int g_launch_exec_rc;
|
||||
extern int g_launch_pgrep_rc;
|
||||
extern char g_ide_tab_label[16];
|
||||
extern u64 g_prof_frames;
|
||||
extern u64 g_prof_fps_tsc;
|
||||
extern int g_prof_fps;
|
||||
extern u64 g_prof_blit_total;
|
||||
extern u64 g_prof_blit_count;
|
||||
extern u64 g_prof_blit_skip;
|
||||
extern u64 g_prof_blit_full;
|
||||
extern int g_prof_last_blit_us;
|
||||
extern struct linux_compat_scanout_info g_so;
|
||||
extern int g_so_valid;
|
||||
extern int g_so_full_repaint;
|
||||
|
||||
/* ---- main.c:M2 Fluent 开关(FUCK [desktop] fluent=,缺省 1) ---- */
|
||||
extern int g_fluent;
|
||||
|
||||
/* ---- shell.c:外壳状态 / 命中矩形 ---- */
|
||||
extern int g_page; /* 1..3 内置, 4..8 自定义 */
|
||||
extern int g_taskview;
|
||||
extern int g_dash_focus; /* 0=t1 1=t2 2=ed 3=files */
|
||||
extern workspace g_ws[MAX_WS];
|
||||
extern int g_ws_count;
|
||||
extern int g_ws_cur;
|
||||
extern cpage g_cpages[MAX_CPAGES];
|
||||
extern int g_cpage_count;
|
||||
extern int g_ctx_open;
|
||||
extern int g_ctx_x, g_ctx_y;
|
||||
extern int g_ctx_win;
|
||||
extern int g_vol;
|
||||
extern int g_bright;
|
||||
extern int g_bright_applied;
|
||||
extern int g_drag_slider; /* 1=vol 2=bright */
|
||||
extern int g_vol_rect[4];
|
||||
extern int g_bri_rect[4];
|
||||
extern int g_quit; /* EXIT 按钮 → 返回 DSK */
|
||||
extern u64 g_last_click_tsc;
|
||||
extern int g_last_click_x, g_last_click_y;
|
||||
extern int g_dash_rect[4][4]; /* 4 面板 x,y,w,h */
|
||||
extern int g_save_rect[4];
|
||||
extern int g_pg_rect[8][4]; /* 页面切换按钮 */
|
||||
extern int g_pg_rm_rect[8][4];
|
||||
extern int g_pgadd_rect[4];
|
||||
extern int g_tvbtn_rect[4]; /* 任务视图按钮 */
|
||||
extern int g_ws_rect[MAX_WS][4];
|
||||
extern int g_wsadd_rect[4];
|
||||
extern int g_tb_rect[MAX_WINDOWS][4]; /* 任务栏项 */
|
||||
extern int g_tb_close_rect[MAX_WINDOWS][4];
|
||||
extern int g_tb_quit_rect[4];
|
||||
extern int g_tb_adopt_rect[4]; /* 收纳窗口按钮 */
|
||||
extern int g_tv_card[MAX_WS + 1][4]; /* 任务视图卡片(+add) */
|
||||
extern int g_tv_close[MAX_WS][4];
|
||||
extern int g_ctx_rect[4];
|
||||
extern int g_ctx_item[MAX_WS + 1][4];
|
||||
extern int g_ctx_item_ws[MAX_WS + 1]; /* 菜单行 → 真实工作区索引,-1=新建 */
|
||||
extern int g_ide_host[4];
|
||||
extern int g_ide_tabx[4]; /* IDE 标签 × */
|
||||
extern int g_ide_new_rect[4]; /* IDE "+ NEW IDE" 按钮 */
|
||||
extern int g_cp_host[4]; /* 自定义页宿主 */
|
||||
extern int g_cp_new_rect[4]; /* 自定义页 "+ NEW INSTANCE" 按钮 */
|
||||
|
||||
/* ============================================================
|
||||
* 跨模块函数原型
|
||||
* ============================================================ */
|
||||
|
||||
/* ---- input.c ---- */
|
||||
void rtc_read(int *yy, int *mo, int *dd, int *hh, int *mi, int *ss);
|
||||
int rtc_day_sec(void);
|
||||
void fmt_hms(char *out, int sec);
|
||||
void tsc_calibrate(void);
|
||||
void ps2_mouse_init(void);
|
||||
int ps2_mouse_poll(void);
|
||||
char scan_to_ascii(u8 sc, int shift);
|
||||
u16 scancode_to_linux_keycode(u8 base, int e0);
|
||||
void cursor_save_bg(int mx, int my);
|
||||
void cursor_restore_bg(void);
|
||||
void cursor_draw(int mx, int my);
|
||||
|
||||
/* ---- wm.c ---- */
|
||||
desktop_window *win_find(int id);
|
||||
desktop_window *win_find_at(int mx, int my);
|
||||
void win_focus(desktop_window *w);
|
||||
desktop_window *win_create(int app_id, const char *title, int x, int y, int w, int h);
|
||||
void win_destroy(desktop_window *w);
|
||||
int win_titlebar_h(void); /* M2:Fluent 36 / KATE 28 */
|
||||
int win_hit_titlebar(desktop_window *w, int mx, int my);
|
||||
void win_close_request(desktop_window *w);
|
||||
int win_hit_close_btn(desktop_window *w, int mx, int my);
|
||||
int win_hit_min_btn(desktop_window *w, int mx, int my); /* M2 Fluent */
|
||||
int win_hit_max_btn(desktop_window *w, int mx, int my); /* M2 Fluent */
|
||||
void wm_set_btn_hover(int btn); /* 0=无 1=min 2=max 3=close */
|
||||
int wm_detect_btn_hover(desktop_window *w, int mx, int my);
|
||||
int ws_win_count(int ws);
|
||||
void draw_window_frame(desktop_window *w);
|
||||
|
||||
/* ---- compositor.c ---- */
|
||||
void mark_dirty(int x, int y, int w, int h);
|
||||
void merge_dirty_rects(int *out_x, int *out_y, int *out_w, int *out_h);
|
||||
void clear_dirty_rects(void);
|
||||
void flip_buffer(void);
|
||||
void flip_rect(int x, int y, int w, int h);
|
||||
void anim_delay_poll_mouse(u32 ms);
|
||||
void redraw_all(void);
|
||||
|
||||
/* ---- apps_builtin.c ---- */
|
||||
extern const char *BASH_PROMPT;
|
||||
void bash_reset(bash_state *s, int w, int h);
|
||||
void bash_puts(bash_state *s, const char *str, u32 color);
|
||||
void bash_key(bash_state *s, u8 sc, int shift);
|
||||
void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch);
|
||||
void editor_reset(editor_state *s, const char *sample);
|
||||
void editor_key(editor_state *s, u8 sc, int shift);
|
||||
void editor_draw_to(editor_state *s, int cx, int cy, int cw, int ch);
|
||||
void editor_save(void);
|
||||
void files_refresh(void);
|
||||
void files_open(int idx);
|
||||
void *editor_on_create(app_ctx *ctx);
|
||||
void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev);
|
||||
void editor_on_draw(void *state, app_ctx *ctx);
|
||||
void editor_on_destroy(void *state);
|
||||
void *calc_on_create(app_ctx *ctx);
|
||||
void calc_on_event(void *state, app_ctx *ctx, desktop_event *ev);
|
||||
void calc_on_draw(void *state, app_ctx *ctx);
|
||||
void calc_on_destroy(void *state);
|
||||
|
||||
/* ---- appreg.c ---- */
|
||||
void register_apps(void);
|
||||
void setup_desktop_icons(void);
|
||||
void draw_app_icon(du_context *ctx, int app_id, int cx, int cy);
|
||||
void draw_circle_outline(du_context *ctx, int cx, int cy, int r, u32 color);
|
||||
|
||||
/* ---- launcher.c ---- */
|
||||
void launch_external_elf(const char *name11);
|
||||
void fill_app_ctx(app_ctx *ctx, desktop_window *win);
|
||||
void launch_app(int app_id);
|
||||
void launch_pe_app(int app_id);
|
||||
void pe_win_on_draw(void *state, app_ctx *ctx);
|
||||
|
||||
/* ---- lxc.c ---- */
|
||||
void slog_num(const char *prefix, int rc);
|
||||
int lxc_guest_ping(void);
|
||||
void guest_gui_launch(const char *path, int argc, const char *const *argv);
|
||||
void launch_linux_app(int app_id);
|
||||
void vscode_launch(void);
|
||||
void ide_attach_invalidate(void);
|
||||
const struct linux_compat_scanout_info *ide_scanout_state(void);
|
||||
void blit_scanout_to_ide(int hx, int hy, int hw, int hh);
|
||||
void desktop_launch_pending_native(void);
|
||||
|
||||
/* ---- shell.c ---- */
|
||||
void page_switch(int p);
|
||||
int ctx_list_build(int *map);
|
||||
int on_left_press(void);
|
||||
int on_right_press(void);
|
||||
int handle_key(u8 sc);
|
||||
void slider_set(const int rect[4], int *val, int mx);
|
||||
|
||||
/* ---- pages.c ---- */
|
||||
void draw_topbar(void);
|
||||
void draw_dashboard(void);
|
||||
void draw_ide(void);
|
||||
void draw_desktop_page(void);
|
||||
void draw_custom_page(int cp_idx);
|
||||
void draw_taskview(void);
|
||||
void draw_ctx_menu(void);
|
||||
|
||||
/* ---- taskbar.c(M2 Fluent 悬浮任务栏) ---- */
|
||||
void taskbar_draw(void);
|
||||
int taskbar_click(int mx, int my);
|
||||
int taskbar_init(int (*read_to)(const char *, unsigned char *,
|
||||
unsigned int, unsigned int *)); /* M2: logo 纹理 */
|
||||
extern int g_tb2_start_rect[4];
|
||||
extern int g_tb2_tv_rect[4];
|
||||
extern int g_tb2_app_rect[MAX_WINDOWS][4];
|
||||
extern int g_tb2_tray_rect[4];
|
||||
extern int g_tb2_net_rect[4];
|
||||
extern int g_tb2_vol_rect[4];
|
||||
extern int g_tb2_pwr_rect[4];
|
||||
extern int g_tb2_clk_rect[4];
|
||||
|
||||
/* ---- main.c ---- */
|
||||
void dsk_entry(const dsk_boot_context *ctx);
|
||||
|
||||
/* ---- 内联小绘制(依赖 g_fb extern,放头文件末尾) ---- */
|
||||
|
||||
/* 矩形命中 */
|
||||
static __inline__ 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 __inline__ void draw_centered(const char *s, int cx, int y, u32 fg, u32 bg) {
|
||||
int l = kstrlen(s);
|
||||
du_draw_string(&g_fb, s, cx - l * (int)DU_ASCII_STEP / 2, y, fg, bg, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* 虚线矩形(ide-host / custom host) */
|
||||
static __inline__ void draw_dashed_rect(int x, int y, int w, int h, u32 color) {
|
||||
for (int i = x; i < x + w; i += 8) {
|
||||
du_fill_rect(&g_fb, i, y, 4, 1, color);
|
||||
du_fill_rect(&g_fb, i, y + h - 1, 4, 1, color);
|
||||
}
|
||||
for (int i = y; i < y + h; i += 8) {
|
||||
du_fill_rect(&g_fb, x, i, 1, 4, color);
|
||||
du_fill_rect(&g_fb, x + w - 1, i, 1, 4, color);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DESHAB_DESKTOP_H */
|
||||
@@ -0,0 +1,128 @@
|
||||
/* f32dbg_test.c — f32 字库路径查找宿主复现测试(诊断 M1 zhfont -3)
|
||||
*
|
||||
* 用 deshab-dev.img 文件作 block 后端(GPT ESP @ LBA2048),
|
||||
* 直接调用 fat32_io.h 的查找逻辑,定位 simhei_16.dbf 为何找不到。
|
||||
* 用法:clang f32dbg_test.c -o f32dbg && ./f32dbg <img>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
typedef long long i64;
|
||||
|
||||
#include "../tools/fat32_io.h"
|
||||
|
||||
static FILE *g_img;
|
||||
static u64 g_esp_off;
|
||||
|
||||
static int img_block_read(u32 index, u64 lba, u32 count, void *buf) {
|
||||
(void)index;
|
||||
if (_fseeki64(g_img, (long long)(g_esp_off + lba * 512), SEEK_SET) != 0) return -1;
|
||||
if (fread(buf, 512, count, g_img) != count) return -1;
|
||||
return 0;
|
||||
}
|
||||
static int img_block_write(u32 index, u64 lba, u32 count, const void *buf) {
|
||||
(void)index; (void)lba; (void)count; (void)buf; return -1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *path = (argc > 1) ? argv[1] : "../../ISO/deshab-dev.img";
|
||||
g_img = fopen(path, "rb");
|
||||
if (!g_img) { printf("open %s failed\n", path); return 1; }
|
||||
/* GPT ESP at LBA 2048 */
|
||||
u8 hdr[512];
|
||||
fseek(g_img, 512, SEEK_SET);
|
||||
fread(hdr, 1, 512, g_img);
|
||||
if (memcmp(hdr, "EFI PART", 8) != 0) { printf("no GPT\n"); return 1; }
|
||||
g_esp_off = 2048ULL * 512;
|
||||
|
||||
f32_init(img_block_read, img_block_write);
|
||||
|
||||
int rc = f32_disk_load();
|
||||
printf("f32_disk_load rc=%d\n", rc);
|
||||
if (rc != 0) return 1;
|
||||
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
printf("spc=%u rsvd=%u fc=%u spf=%u root_clus=%u\n",
|
||||
bpb->spc, bpb->rsvd, bpb->fc, bpb->spf, bpb->root_clus);
|
||||
|
||||
/* 1. 目录定位 */
|
||||
u32 dclus = 0;
|
||||
rc = f32_find_path_dir_lfn("system/font", &dclus);
|
||||
printf("find_path_dir(system/font) rc=%d clus=%u\n", rc, dclus);
|
||||
if (rc != 0) return 1;
|
||||
|
||||
/* 2. 在 FONT 目录中找文件(对照:LFN 与短名) */
|
||||
u32 fclus = 0, fsize = 0;
|
||||
rc = f32_find_in_dir_lfn(dclus, "simhei_16.dbf", 0, &fclus, &fsize);
|
||||
printf("find(simhei_16.dbf) rc=%d clus=%u size=%u\n", rc, fclus, fsize);
|
||||
rc = f32_find_in_dir_lfn(dclus, "SIMHEI~1.DB F", 0, &fclus, &fsize);
|
||||
printf("find(SIMHEI~1.DBF) rc=%d\n", rc);
|
||||
|
||||
/* M2: 壁纸读取复现(rc=-4 路径) */
|
||||
{
|
||||
static u8 wpbuf[8u*1024u*1024u];
|
||||
u32 wpsize = 0;
|
||||
rc = f32_read_path_lfn_to("system/wallpaper.rgba", wpbuf, 2800512u, &wpsize);
|
||||
printf("read(wallpaper.rgba) rc=%d size=%u\n", rc, wpsize);
|
||||
if (rc != 0) {
|
||||
u32 c2 = 0;
|
||||
rc = f32_read_path_lfn_to("system/wallpaper.rgba", wpbuf, 4096, &c2);
|
||||
printf(" small-cap probe rc=%d size=%u\n", rc, c2);
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. 手工枚举 FONT 目录条目,dump LFN/短名/checksum */
|
||||
{
|
||||
u32 spc = bpb->spc;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u64 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 clus = dclus;
|
||||
printf("--- FONT dir entries ---\n");
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, spc, f32_cluster) != 0) {
|
||||
printf("dir cluster read failed lba=%u\n", lba);
|
||||
return 1;
|
||||
}
|
||||
cb = f32_cluster;
|
||||
}
|
||||
u32 entry_count = (spc * 512) / 32;
|
||||
for (u32 e = 0; e < entry_count; e++) {
|
||||
const u8 *entry = cb + e * 32;
|
||||
if (entry[0] == 0) { printf("[end]\n"); goto done; }
|
||||
if ((u8)entry[0] == 0xE5) continue;
|
||||
if (entry[11] == 0x0F) {
|
||||
/* LFN 片段:seq=c0 seq|0x40 首段 */
|
||||
printf("LFN seq=0x%02x name1='%.5s' name2='%.6s' name3='%.2s' cksum=0x%02x\n",
|
||||
entry[0],
|
||||
(const char*)(const void*)((u16*)entry + 1),
|
||||
(const char*)(const void*)((u16*)entry + 6),
|
||||
(const char*)(const void*)((u16*)entry + 14),
|
||||
entry[13]);
|
||||
continue;
|
||||
}
|
||||
char nm[12];
|
||||
memcpy(nm, entry, 11);
|
||||
nm[11] = 0;
|
||||
printf("SHORT '%s' attr=0x%02x clus=%u size=%u\n", nm, entry[11],
|
||||
(unsigned)(entry[26] | (entry[20] << 16)),
|
||||
(unsigned)(entry[28] | (entry[29]<<8) | (entry[30]<<16) | ((u32)entry[31]<<24)));
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
}
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/* f32io.c — fat32_io.h 唯一包含者(M0 模块化拆分)
|
||||
*
|
||||
* 全模块仅此 TU include fat32_io.h(4MB f32_data 等缓冲唯一副本),
|
||||
* 其余模块经 f32io.h 的 _w 封装访问。
|
||||
*/
|
||||
#include "desktop.h"
|
||||
|
||||
#define F32_DATA_BYTES (4u * 1024u * 1024u)
|
||||
#include "../tools/fat32_io.h"
|
||||
|
||||
void f32_io_init(desktop_block_read_fn rd, desktop_block_write_fn wr) {
|
||||
f32_init((f32_block_read_fn)rd, (f32_block_write_fn)wr);
|
||||
}
|
||||
|
||||
int f32_read_root_file_w(const char *name11, u8 **out_data, u32 *out_size) {
|
||||
return f32_read_root_file(name11, out_data, out_size);
|
||||
}
|
||||
|
||||
int f32_write_root_file_w(const char *name11, const u8 *data, u32 size) {
|
||||
return f32_write_root_file(name11, data, size);
|
||||
}
|
||||
|
||||
int f32_list_root_w(f32_list_cb_fn emit, void *user_data) {
|
||||
return f32_list_root((f32_list_cb)emit, user_data);
|
||||
}
|
||||
|
||||
int f32_list_dir_w(u32 dir_clus, f32_list_entry_fn emit, void *user_data) {
|
||||
return f32_list_dir(dir_clus, (f32_list_entry_cb)emit, user_data);
|
||||
}
|
||||
|
||||
int f32_find_path_dir_lfn_w(const char *path, u32 *out_clus) {
|
||||
return f32_find_path_dir_lfn(path, out_clus);
|
||||
}
|
||||
|
||||
int f32_read_path_lfn_w(const char *path, u8 **out_data, u32 *out_size) {
|
||||
return f32_read_path_lfn(path, out_data, out_size);
|
||||
}
|
||||
|
||||
int f32_read_path_lfn_to_w(const char *path, u8 *dst, u32 cap, u32 *out_size) {
|
||||
int rc = f32_read_path_lfn_to(path, dst, cap, out_size);
|
||||
if (rc != 0) slog_num("[f32io] read_to rc=", rc); /* M1 调试:定位失败步骤 */
|
||||
return rc;
|
||||
}
|
||||
|
||||
int f32_name_to_83_w(const char *in, char out[11]) {
|
||||
return f32_name_to_83(in, out);
|
||||
}
|
||||
|
||||
void f32_name_from_83_w(const char in[11], char out[13]) {
|
||||
f32_name_from_83(in, out);
|
||||
}
|
||||
|
||||
int f32_neq11_w(const char *a, const char *b) {
|
||||
return f32_neq11(a, b);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* f32io.h — fat32_io.h 单编译单元封装(M0)
|
||||
*
|
||||
* fat32_io.h 是 header-only static inline 实现,含 ~4.3MB 静态缓冲
|
||||
* (f32_disk 128KB + f32_cluster 4KB + f32_data 4MB)。多模块需要 FAT32
|
||||
* 访问时若各自 include 会每 TU 复制一份缓冲——f32io.c 是唯一包含者,
|
||||
* 本头导出薄封装(_w 后缀)供其他模块调用。
|
||||
*
|
||||
* 纯字符串助手(fat32_lfn_streq_ci / fat32_lfn_ends_with_ci / f32_entry)
|
||||
* 无状态,各模块直接 include fat32_lfn.h 即可,不经此封装。
|
||||
*/
|
||||
#ifndef DESHAB_F32IO_H
|
||||
#define DESHAB_F32IO_H
|
||||
|
||||
/* f32_entry / 回调签名(与 fat32_io.h 一致;f32_entry 来自 fat32_lfn.h) */
|
||||
#include "../tools/fat32_lfn.h"
|
||||
|
||||
typedef int (*f32_list_cb_fn)(const char *name, unsigned int size,
|
||||
unsigned char attr, void *user_data);
|
||||
typedef int (*f32_list_entry_fn)(const f32_entry *e, void *user_data);
|
||||
|
||||
/* 初始化(对应 f32_init,注入 block read/write) */
|
||||
void f32_io_init(desktop_block_read_fn rd, desktop_block_write_fn wr);
|
||||
|
||||
/* 根目录文件读/写/列举 */
|
||||
int f32_read_root_file_w(const char *name11, u8 **out_data, u32 *out_size);
|
||||
int f32_write_root_file_w(const char *name11, const u8 *data, u32 size);
|
||||
int f32_list_root_w(f32_list_cb_fn emit, void *user_data);
|
||||
|
||||
/* 目录列举 / 路径定位 / 长路径读取 */
|
||||
int f32_list_dir_w(u32 dir_clus, f32_list_entry_fn emit, void *user_data);
|
||||
int f32_find_path_dir_lfn_w(const char *path, u32 *out_clus);
|
||||
int f32_read_path_lfn_w(const char *path, u8 **out_data, u32 *out_size);
|
||||
|
||||
/* 流式直读到指定缓冲(绕过 f32_data 4MB 上限,zhfont 字库加载用) */
|
||||
int f32_read_path_lfn_to_w(const char *path, u8 *dst, u32 cap, u32 *out_size);
|
||||
|
||||
/* 8.3 名转换(纯函数,但定义于 fat32_io.h,故同样封装) */
|
||||
int f32_name_to_83_w(const char *in, char out[11]);
|
||||
void f32_name_from_83_w(const char in[11], char out[13]);
|
||||
int f32_neq11_w(const char *a, const char *b);
|
||||
|
||||
#endif /* DESHAB_F32IO_H */
|
||||
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""gen_textures.py — 桌面纹理资产转换(PNG/webP → 裸 RGBA 字节流)
|
||||
|
||||
输入(SYSTEM/system/deshab64/desktop/textures/):
|
||||
back.webp 用户启动背景(889×500,缩放铺满 boot 屏)
|
||||
startMenuLogo.png 开始按钮/开始菜单 logo(216×210,带 alpha)
|
||||
|
||||
输出(同目录):
|
||||
back.rgba 889×500×4
|
||||
startMenuLogo.rgba 216×210×4
|
||||
bootlogo.rgba 157×157×4(自 CODE/dsk/logo_data.c 的嵌入数据提取,
|
||||
即 DSK 静态开机 Logo,desktop boot 屏复用)
|
||||
|
||||
注意:不能用 PIL 直接 save(".rgba")——扩展名被 SgiImagePlugin 认领(512B 头)。
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
print("PIL required: pip install pillow", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
ROOT = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
TEX_DIR = os.path.join(ROOT, "SYSTEM", "system", "deshab64", "desktop", "textures")
|
||||
LOGO_C = os.path.join(ROOT, "CODE", "dsk", "logo_data.c")
|
||||
|
||||
# (源文件, 输出名, 期望宽, 期望高)
|
||||
ASSETS = [
|
||||
("back.webp", "back.rgba", 889, 500),
|
||||
("startMenuLogo.png", "startMenuLogo.rgba", 216, 210),
|
||||
]
|
||||
|
||||
|
||||
def save_raw(im, name, w, h):
|
||||
if im.size != (w, h):
|
||||
im = im.resize((w, h), Image.LANCZOS)
|
||||
data = im.convert("RGBA").tobytes()
|
||||
out = os.path.join(TEX_DIR, name)
|
||||
with open(out, "wb") as f:
|
||||
f.write(data)
|
||||
print("%s: %dx%d %d bytes" % (name, w, h, len(data)))
|
||||
|
||||
|
||||
def extract_bootlogo():
|
||||
"""从 DSK logo_data.c 提取 g_logo_rgba 字节数组 → bootlogo.rgba"""
|
||||
src = open(LOGO_C, "r", encoding="utf-8", errors="replace").read()
|
||||
m = re.search(r"g_logo_w\s*=\s*(\d+)", src)
|
||||
n = re.search(r"g_logo_h\s*=\s*(\d+)", src)
|
||||
if not m or not n:
|
||||
print("logo_data.c: no g_logo_w/h", file=sys.stderr)
|
||||
return False
|
||||
w, h = int(m.group(1)), int(n.group(1))
|
||||
body = re.search(r"g_logo_rgba\[\]\s*=\s*\{(.*?)\};", src, re.S)
|
||||
if not body:
|
||||
print("logo_data.c: no g_logo_rgba array", file=sys.stderr)
|
||||
return False
|
||||
vals = [int(v, 0) for v in re.findall(r"0x[0-9a-fA-F]+|\b\d+\b", body.group(1))]
|
||||
expect = w * h * 4
|
||||
if len(vals) < expect:
|
||||
print("logo_data.c: %d bytes < expect %d" % (len(vals), expect), file=sys.stderr)
|
||||
return False
|
||||
out = os.path.join(TEX_DIR, "bootlogo.rgba")
|
||||
with open(out, "wb") as f:
|
||||
f.write(bytes(vals[:expect]))
|
||||
print("bootlogo.rgba: %dx%d %d bytes (from logo_data.c)" % (w, h, expect))
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(TEX_DIR, exist_ok=True)
|
||||
for src_name, out_name, w, h in ASSETS:
|
||||
src = os.path.join(TEX_DIR, src_name)
|
||||
if not os.path.exists(src):
|
||||
print("skip missing:", src_name)
|
||||
continue
|
||||
save_raw(Image.open(src), out_name, w, h)
|
||||
extract_bootlogo()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""gen_wallpaper.py — 生成 Win11 风格抽象壁纸(无需外部图片素材)
|
||||
|
||||
以深海军蓝渐变为底,叠加多层低透明度旋转椭圆"花瓣"光晕
|
||||
(模拟 Win11 Bloom),四角轻微暗角。输出 wallpaper.png。
|
||||
"""
|
||||
import math
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
|
||||
W, H = 1000, 700
|
||||
|
||||
def main():
|
||||
# 底:垂直渐变 深蓝 #16264a → 暗蓝黑 #0a0e1c
|
||||
base = Image.new("RGB", (W, H))
|
||||
top = (22, 38, 74)
|
||||
bot = (9, 13, 27)
|
||||
px = base.load()
|
||||
for y in range(H):
|
||||
t = y / (H - 1)
|
||||
r = int(top[0] * (1 - t) + bot[0] * t)
|
||||
g = int(top[1] * (1 - t) + bot[1] * t)
|
||||
b = int(top[2] * (1 - t) + bot[2] * t)
|
||||
for x in range(W):
|
||||
px[x, y] = (r, g, b)
|
||||
|
||||
# Bloom 花瓣层:中心偏右上的多层旋转椭圆光晕
|
||||
glow = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||||
gd = ImageDraw.Draw(glow)
|
||||
cx, cy = W * 0.58, H * 0.44
|
||||
petals = [
|
||||
# (长半轴, 短半轴, 角度deg, 颜色, alpha)
|
||||
(300, 130, 15, (86, 130, 220), 46),
|
||||
(260, 100, -25, (110, 90, 200), 40),
|
||||
(230, 80, 50, (60, 170, 220), 36),
|
||||
(200, 70, -60, (140, 110, 230), 32),
|
||||
(170, 60, 85, (70, 200, 190), 30),
|
||||
(150, 55, -100, (170, 130, 240), 26),
|
||||
(330, 150, 30, (60, 100, 190), 22),
|
||||
(140, 50, 120, (90, 150, 240), 24),
|
||||
]
|
||||
for rx, ry, ang, col, alpha in petals:
|
||||
layer = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||||
ld = ImageDraw.Draw(layer)
|
||||
# 椭圆边界框(绕中心旋转后取包围盒,再由 rotate 转正)
|
||||
bbox = [cx - rx, cy - ry, cx + rx, cy + ry]
|
||||
ld.ellipse(bbox, fill=col + (alpha,))
|
||||
layer = layer.rotate(ang, center=(cx, cy), resample=Image.BICUBIC)
|
||||
glow = Image.alpha_composite(glow, layer)
|
||||
|
||||
# 大光晕柔化后叠加
|
||||
glow = glow.filter(ImageFilter.GaussianBlur(28))
|
||||
out = Image.alpha_composite(base.convert("RGBA"), glow)
|
||||
|
||||
# 暗角:四角压暗(径向 alpha 蒙版)
|
||||
vign = Image.new("L", (W, H), 0)
|
||||
vd = ImageDraw.Draw(vign)
|
||||
steps = 24
|
||||
for i in range(steps):
|
||||
t = i / steps
|
||||
# 由外向内画黑色环带,alpha 递减
|
||||
shrink = int((1 - t) * 260)
|
||||
alpha = int(70 * (1 - t) ** 2)
|
||||
vd.ellipse([-shrink, -shrink, W + shrink, H + shrink],
|
||||
outline=(alpha,), width=int(260 / steps) + 2)
|
||||
dark = Image.new("RGBA", (W, H), (0, 0, 0, 255))
|
||||
dark.putalpha(vign)
|
||||
out = Image.alpha_composite(out, dark)
|
||||
|
||||
out.convert("RGB").save("wallpaper.png")
|
||||
# 嵌入位图来源:PNG(人看)+ 原始 RGBA(裸机加载,无解码器)。
|
||||
# 注意:不能用 PIL 直接 save(".rgba")——该扩展被 SgiImagePlugin 认领,
|
||||
# 会写 512 字节 SGI 头。必须手写 tobytes()。
|
||||
with open("wallpaper.rgba", "wb") as f:
|
||||
f.write(out.convert("RGBA").tobytes())
|
||||
print("wallpaper.png/.rgba generated:", W, "x", H)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,306 @@
|
||||
/* input.c — PS/2 鼠标键盘、光标管理、RTC/TSC 时钟(M0 自 main.c 平移)
|
||||
*/
|
||||
#include "desktop.h"
|
||||
|
||||
/* ---- 全局状态 ---- */
|
||||
int g_mouse_x = 400, g_mouse_y = 300;
|
||||
int g_mouse_btn = 0; /* bit0=左 bit1=右 */
|
||||
int g_left_pressed = 0, g_left_released = 0, g_right_pressed = 0;
|
||||
static u8 g_mouse_buf[3];
|
||||
static int g_mouse_idx = 0;
|
||||
static int g_mouse_has_pkt = 0;
|
||||
|
||||
/* 光标背景保存 */
|
||||
static u32 g_cursor_save[CURSOR_SIZE * CURSOR_SIZE];
|
||||
int g_cursor_saved = 0;
|
||||
int g_cursor_old_x = -1, g_cursor_old_y = -1;
|
||||
|
||||
/* 键盘状态 */
|
||||
int g_shift = 0, g_ctrl = 0, g_alt = 0;
|
||||
int g_e0 = 0;
|
||||
|
||||
/* 时钟 */
|
||||
int g_rtc_boot_sec = 0; /* 开机时刻(秒级当天秒) */
|
||||
int g_last_sec = -1;
|
||||
u64 g_tsc_per_sec = 0;
|
||||
|
||||
/* ============================================================
|
||||
* 箭头光标形状(24×24,1=前景 0=透明)
|
||||
* ============================================================ */
|
||||
|
||||
static const u8 cursor_shape[24][24] = {
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
* RTC 时钟
|
||||
* ============================================================ */
|
||||
|
||||
static int bcd2bin(u8 v) { return (v >> 4) * 10 + (v & 0xF); }
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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 */
|
||||
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 秒):用于双击间隔判定 */
|
||||
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;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 光标管理
|
||||
* ============================================================ */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void cursor_draw(int mx, int my) {
|
||||
for (int r = 0; r < CURSOR_SIZE; r++) {
|
||||
for (int c = 0; c < CURSOR_SIZE; c++) {
|
||||
if (cursor_shape[r][c]) {
|
||||
du_pixel(&g_fb, mx + c, my + r, KS_ACCENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* PS/2 鼠标处理
|
||||
* ============================================================ */
|
||||
|
||||
static int ps2_wait_write(void) {
|
||||
for (int t = 0; t < 200000; t++) {
|
||||
if (!(inb(0x64) & 0x02)) return 0;
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ps2_wait_read(void) {
|
||||
for (int t = 0; t < 200000; t++) {
|
||||
if (inb(0x64) & 0x01) return 0;
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ps2_drain(void) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (!(inb(0x64) & 0x01)) break;
|
||||
inb(0x60);
|
||||
}
|
||||
}
|
||||
|
||||
void ps2_mouse_init(void) {
|
||||
ps2_drain();
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0xA8);
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0x20);
|
||||
ps2_wait_read();
|
||||
u8 cfg = inb(0x60);
|
||||
|
||||
cfg &= ~0x20;
|
||||
cfg |= 0x02;
|
||||
cfg |= 0x40;
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0x60);
|
||||
ps2_wait_write();
|
||||
outb(0x60, cfg);
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0xD4);
|
||||
ps2_wait_write();
|
||||
outb(0x60, 0xFF);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (ps2_wait_read() != 0) break;
|
||||
inb(0x60);
|
||||
}
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0xD4);
|
||||
ps2_wait_write();
|
||||
outb(0x60, 0xF4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (ps2_wait_read() != 0) break;
|
||||
inb(0x60);
|
||||
}
|
||||
|
||||
ps2_drain();
|
||||
|
||||
g_mouse_idx = 0;
|
||||
g_mouse_has_pkt = 0;
|
||||
|
||||
slog("mouse init ok");
|
||||
}
|
||||
|
||||
int ps2_mouse_poll(void) {
|
||||
u8 st = inb(0x64);
|
||||
if (!(st & 1)) return 0;
|
||||
if (!(st & 0x20)) return 0;
|
||||
u8 data = inb(0x60);
|
||||
|
||||
g_mouse_buf[g_mouse_idx++] = data;
|
||||
if (g_mouse_idx < 3) return 0;
|
||||
g_mouse_idx = 0;
|
||||
|
||||
if (!(g_mouse_buf[0] & 0x08)) return 0;
|
||||
|
||||
int dx = (int)(i8)g_mouse_buf[1];
|
||||
int dy = (int)(i8)g_mouse_buf[2];
|
||||
dy = -dy;
|
||||
|
||||
g_mouse_x += dx;
|
||||
g_mouse_y += dy;
|
||||
if (g_mouse_x < 0) g_mouse_x = 0;
|
||||
if (g_mouse_y < 0) g_mouse_y = 0;
|
||||
if (g_mouse_x >= (int)g_fb_w - CURSOR_SIZE) g_mouse_x = (int)g_fb_w - CURSOR_SIZE;
|
||||
if (g_mouse_y >= (int)g_fb_h - CURSOR_SIZE) g_mouse_y = (int)g_fb_h - CURSOR_SIZE;
|
||||
|
||||
int old_btn = g_mouse_btn;
|
||||
g_mouse_btn = g_mouse_buf[0] & 0x03;
|
||||
if ((g_mouse_btn & 1) && !(old_btn & 1)) g_left_pressed = 1;
|
||||
if (!(g_mouse_btn & 1) && (old_btn & 1)) g_left_released = 1;
|
||||
if ((g_mouse_btn & 2) && !(old_btn & 2)) g_right_pressed = 1;
|
||||
if (dx == 0 && dy == 0 && g_mouse_btn == old_btn) return 0;
|
||||
|
||||
/* VSCode Phase 2: IDE attached 时把相对位移 + 按钮状态镜像到 Linux guest。
|
||||
* dy 已在上方取反为屏幕坐标系(正=下),与 Linux REL_Y 约定一致;
|
||||
* input_forward_mouse 内部与上次状态比较,仅产生变化按钮事件 + SYN。
|
||||
* 即使宿主光标已抵边界被钳位,原始 dx/dy 仍转发,guest 光标自由移动。 */
|
||||
if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_mouse) {
|
||||
g_lxc_svc->input_forward_mouse(dx, dy, (u8)g_mouse_btn);
|
||||
}
|
||||
|
||||
g_mouse_has_pkt = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 键盘扫描码 → ASCII
|
||||
* ============================================================ */
|
||||
|
||||
char scan_to_ascii(u8 sc, int shift) {
|
||||
static const char normal[58] = {
|
||||
0, 27, '1','2','3','4','5','6','7','8','9','0','-','=', 8, '\t',
|
||||
'q','w','e','r','t','y','u','i','o','p','[',']','\n',0,
|
||||
'a','s','d','f','g','h','j','k','l',';','\'', '`',0,'\\',
|
||||
'z','x','c','v','b','n','m',',','.','/',0,'*',0,' '
|
||||
};
|
||||
static const char shifted[58] = {
|
||||
0, 27, '!','@','#','$','%','^','&','*','(',')','_','+', 8, '\t',
|
||||
'Q','W','E','R','T','Y','U','I','O','P','{','}','\n',0,
|
||||
'A','S','D','F','G','H','J','K','L',':','"','~',0,'|',
|
||||
'Z','X','C','V','B','N','M','<','>','?',0,'*',0,' '
|
||||
};
|
||||
if (sc >= 58) return 0;
|
||||
return shift ? shifted[sc] : normal[sc];
|
||||
}
|
||||
|
||||
/* VSCode Phase 2: PS/2 set-1 扫描码 → Linux keycode(linux/input-event-codes.h)。
|
||||
* base : 已去除 0x80 release 位的基础扫描码(0x01..0x39 主键区 + 少数扩展)
|
||||
* e0 : 上一字节为 0xE0 前缀时置 1,选取扩展键变体
|
||||
* 返回 0 表示该扫描码无映射(调用方应丢弃)。
|
||||
*
|
||||
* 巧合:主键区 0x01..0x39 的 Linux keycode 与扫描码数值完全一致
|
||||
* (IBM XT 布局历史遗留),故直接透传;仅 E0 扩展键需单独查表。 */
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
/* launcher.c — 统一启动器 + 外部 ELF 加载 + PE 窗口运行时(M0 自 main.c 平移)
|
||||
*/
|
||||
#include "desktop.h"
|
||||
|
||||
/* ============================================================
|
||||
* 外部 ELF 加载
|
||||
* ============================================================ */
|
||||
|
||||
#define DT_ELFCLASS64 2
|
||||
#define DT_EM_X86_64 62
|
||||
#define DT_PT_LOAD 1
|
||||
#define DT_PT_DYNAMIC 2
|
||||
#define DT_DT_RELA 7
|
||||
#define DT_DT_RELASZ 8
|
||||
#define DT_R_X86_64_RELATIVE 8
|
||||
|
||||
typedef struct { u8 ident[16]; u16 type,machine; u32 ver; u64 entry,phoff,shoff; u32 flags; u16 ehsize,phentsize,phnum,shentsize,shnum,shstrndx; } dt_elf64_ehdr;
|
||||
typedef struct { u32 type,flags; u64 offset,vaddr,paddr,filesz,memsz,align; } dt_elf64_phdr;
|
||||
typedef struct { i64 tag; u64 val; } dt_elf64_dyn;
|
||||
typedef struct { u64 offset; u64 info; i64 addend; } dt_elf64_rela;
|
||||
|
||||
static u8 g_elf_image[1048576]; /* 1 MiB:settings.elf 内存映像 ~493KB(中文字体位图 BSS),256KB 不够 */
|
||||
|
||||
static int dt_load_elf(u8 *data, u32 data_size, void **entry_out) {
|
||||
const dt_elf64_ehdr *eh = (const dt_elf64_ehdr *)data;
|
||||
if (data_size < sizeof(dt_elf64_ehdr)) return -1;
|
||||
if (eh->ident[0] != 0x7F || eh->ident[4] != DT_ELFCLASS64) return -2;
|
||||
if (eh->machine != DT_EM_X86_64) return -3;
|
||||
u64 min_vaddr = ~0ULL, max_vaddr = 0;
|
||||
u32 lc = 0;
|
||||
for (u16 i = 0; i < eh->phnum; i++) {
|
||||
const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize);
|
||||
if (ph->type != DT_PT_LOAD) continue;
|
||||
if (ph->filesz > ph->memsz) return -4;
|
||||
if (ph->vaddr < min_vaddr) min_vaddr = ph->vaddr;
|
||||
if (ph->vaddr + ph->memsz > max_vaddr) max_vaddr = ph->vaddr + ph->memsz;
|
||||
lc++;
|
||||
}
|
||||
if (!lc || min_vaddr == ~0ULL) return -5;
|
||||
u64 isize = (max_vaddr - min_vaddr + 0xFFF) & ~0xFFFULL;
|
||||
if (isize > sizeof(g_elf_image)) return -6;
|
||||
u8 *image = g_elf_image;
|
||||
for (u64 i = 0; i < isize; i++) image[i] = 0;
|
||||
for (u16 i = 0; i < eh->phnum; i++) {
|
||||
const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize);
|
||||
if (ph->type != DT_PT_LOAD) continue;
|
||||
u64 off = ph->vaddr - min_vaddr;
|
||||
for (u64 j = 0; j < ph->filesz; j++)
|
||||
image[off + j] = data[ph->offset + j];
|
||||
}
|
||||
for (u16 i = 0; i < eh->phnum; i++) {
|
||||
const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize);
|
||||
if (ph->type != DT_PT_DYNAMIC) continue;
|
||||
const dt_elf64_dyn *dyn = (const dt_elf64_dyn *)(data + ph->offset);
|
||||
u64 rela_off = 0, rela_sz = 0;
|
||||
for (u32 j = 0; j < ph->filesz / sizeof(dt_elf64_dyn); j++) {
|
||||
if (dyn[j].tag == DT_DT_RELA) rela_off = dyn[j].val;
|
||||
else if (dyn[j].tag == DT_DT_RELASZ) rela_sz = dyn[j].val;
|
||||
else if (dyn[j].tag == 0) break;
|
||||
}
|
||||
if (rela_off && rela_sz) {
|
||||
const dt_elf64_rela *r = (const dt_elf64_rela *)(data + rela_off);
|
||||
u32 rn = (u32)(rela_sz / sizeof(dt_elf64_rela));
|
||||
u64 load_bias = (u64)image - min_vaddr; /* 与 DSK loader 一致 */
|
||||
for (u32 j = 0; j < rn; j++) {
|
||||
u32 type = (u32)(r[j].info & 0xFFFFFFFF);
|
||||
if (type == DT_R_X86_64_RELATIVE) {
|
||||
/* 原 BUG: val = min_vaddr + addend,指针被写成低地址绝对值,
|
||||
* 导致 settings.elf 等模块内 .data 中的字符串指针全部失效 */
|
||||
if (r[j].offset < min_vaddr || r[j].offset >= min_vaddr + isize) continue;
|
||||
u64 target = r[j].offset - min_vaddr;
|
||||
u64 val = load_bias + (u64)r[j].addend;
|
||||
*(u64 *)(image + target) = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*entry_out = (void *)(image + (eh->entry - min_vaddr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void launch_external_elf(const char *name11) {
|
||||
u8 *data = 0; u32 size = 0;
|
||||
if (f32_read_root_file_w(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");
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 应用启动(单实例聚焦策略)
|
||||
* ============================================================ */
|
||||
|
||||
void fill_app_ctx(app_ctx *ctx, desktop_window *win) {
|
||||
int tb = win_titlebar_h();
|
||||
ctx->fb = &g_fb;
|
||||
ctx->win = win;
|
||||
ctx->client_x = win->x + BORDER_W;
|
||||
ctx->client_y = win->y + tb;
|
||||
ctx->client_w = win->w - 2 * BORDER_W;
|
||||
ctx->client_h = win->h - tb - BORDER_W;
|
||||
ctx->should_exit = 0;
|
||||
ctx->block_read = g_block_read;
|
||||
ctx->block_write = g_block_write;
|
||||
ctx->kernel_api = g_kernel_api;
|
||||
}
|
||||
|
||||
void launch_app(int app_id) {
|
||||
if (app_id < 0 || app_id >= g_app_count) return;
|
||||
app_descriptor *app = &g_apps[app_id];
|
||||
|
||||
/* VSCode Phase 5: Linux guest 应用走 exec_async 共享启动路径 */
|
||||
if (app->linux_path) {
|
||||
launch_linux_app(app_id);
|
||||
return;
|
||||
}
|
||||
|
||||
/* P5d: Windows PE 应用走 run_windowed 桌面嵌入路径 */
|
||||
if (app->pe_file) {
|
||||
launch_pe_app(app_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (app->elf_name) {
|
||||
launch_external_elf(app->elf_name);
|
||||
return;
|
||||
}
|
||||
if (!app->on_create) return;
|
||||
|
||||
/* 单实例:已有同类窗口则聚焦并迁移到当前工作区 */
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (g_windows[i].app_id == app_id && g_windows[i].visible) {
|
||||
g_windows[i].ws = g_ws_cur;
|
||||
win_focus(&g_windows[i]);
|
||||
slog("focus existing app window");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_TASKBAR_H;
|
||||
int wx = ICON_PAD + g_win_count * 30;
|
||||
int wy = top + 8 + g_win_count * 30;
|
||||
if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = ICON_PAD;
|
||||
if (wy + (int)app->default_h > bot - 20) wy = top + 8;
|
||||
|
||||
desktop_window *win = win_create(app_id, app->display_name,
|
||||
wx, wy, (int)app->default_w, (int)app->default_h);
|
||||
if (!win) return;
|
||||
|
||||
app_ctx ctx;
|
||||
fill_app_ctx(&ctx, win);
|
||||
|
||||
win->app_state = app->on_create(&ctx);
|
||||
win->dirty = 1;
|
||||
|
||||
/* 直接显示窗口(去掉打开动画以避免卡顿和闪屏) */
|
||||
|
||||
slog("app launched");
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 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 运行时状态 */
|
||||
int g_pe_running = 0;
|
||||
int g_pe_win_id = -1;
|
||||
u8 *g_pe_surf = 0;
|
||||
int g_pe_surf_w = 0, g_pe_surf_h = 0;
|
||||
static int g_pe_state_dummy = 0; /* draw_desktop_page 要求 app_state 非空 */
|
||||
|
||||
/* PE 窗口 on_draw:把 shim 渲染的 surface blit 到客户区(32bpp 行拷贝) */
|
||||
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 pe_tb = win_titlebar_h();
|
||||
int cx0 = pw->x + BORDER_W, cy0 = pw->y + pe_tb;
|
||||
int ccw = pw->w - 2 * BORDER_W, cch = pw->h - pe_tb - 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 阻塞运行 → 退出后销毁窗口 */
|
||||
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_w(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");
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
/* lxc.c — Linux guest / IDE / VSCode 启动链路 + 性能 profiling(M0 自 main.c 平移)
|
||||
*/
|
||||
#include "desktop.h"
|
||||
|
||||
/* ---- 全局状态 ---- */
|
||||
int g_ide_attached = 0;
|
||||
|
||||
/* VSCode Phase 6: guest GUI 状态机。
|
||||
* 0 = 未运行(IDE host 显示启动提示)
|
||||
* 1 = 启动中(X server/VSCode spawn 已发出,等 scanout 上线)
|
||||
* 2 = 运行中(scanout enabled,blit guest 帧) */
|
||||
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 区域) */
|
||||
int g_launch_phase = 0;
|
||||
char g_launch_status[64] = "";
|
||||
u64 g_launch_tsc = 0; /* 启动开始 TSC(测各阶段耗时) */
|
||||
int g_launch_exec_rc = 0; /* exec_async 返回码 */
|
||||
int g_launch_pgrep_rc = -1; /* pgrep 验证结果(0=找到, 1=未找到)*/
|
||||
|
||||
/* P7.4 多实例:IDE tab 动态标签——显示最近启动实例的 DISPLAY 名
|
||||
* (单 X :0 共享 scanout,多 VSCode 进程同屏;标签指示最后 attach 者)。 */
|
||||
char g_ide_tab_label[16] = "VSCODE";
|
||||
|
||||
/* P7.9: 性能 profiling(TSC 计时,每秒更新 FPS 显示)。
|
||||
* 在 IDE 页面叠加显示 FPS / blit 耗时 / dirty 跳过率。 */
|
||||
u64 g_prof_frames = 0; /* 自上次 FPS 更新以来的帧数 */
|
||||
u64 g_prof_fps_tsc = 0; /* 上次 FPS 计算的 TSC */
|
||||
int g_prof_fps = 0; /* 当前 FPS(每秒更新) */
|
||||
u64 g_prof_blit_total = 0; /* 累计 blit TSC */
|
||||
u64 g_prof_blit_count = 0; /* blit 调用次数 */
|
||||
u64 g_prof_blit_skip = 0; /* dirty=0 跳过的次数 */
|
||||
u64 g_prof_blit_full = 0; /* full repaint 次数 */
|
||||
int g_prof_last_blit_us = 0; /* 上次 blit 耗时(微秒) */
|
||||
|
||||
/* 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
|
||||
* 全量重绘一次。 */
|
||||
struct linux_compat_scanout_info g_so;
|
||||
int g_so_valid = 0; /* 1 = g_so 为本帧主循环 poll 的缓存 */
|
||||
int g_so_full_repaint = 1; /* 1 = 下次 blit 全量拷贝(初始/attach/上线) */
|
||||
|
||||
/* P8.1: slog + 数字(用于 rc/ec 日志,负数正确显示前导 -) */
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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)。 */
|
||||
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 不通)。 */
|
||||
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 等待。 */
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void desktop_launch_pending_native(void) {
|
||||
char out[32];
|
||||
u64 ec = 0;
|
||||
int i, n;
|
||||
int app_id = -1;
|
||||
const char *argv[] = {
|
||||
"sh", "-c",
|
||||
"cat /tmp/deshab-native-app 2>/dev/null; rm -f /tmp/deshab-native-app /tmp/deshab-native"
|
||||
};
|
||||
if (!g_lxc_svc || !g_lxc_svc->exec) return;
|
||||
for (i = 0; i < 32; i++) out[i] = 0;
|
||||
if (g_lxc_svc->exec("/bin/sh", 3, argv, out, 31, 0, &ec) != 0) return;
|
||||
n = 0;
|
||||
while (n < 31 && out[n] && out[n] != '\n' && out[n] != '\r' && out[n] != ' ') n++;
|
||||
out[n] = 0;
|
||||
if (!out[0]) return;
|
||||
if (fat32_lfn_streq_ci(out, "kde") || fat32_lfn_streq_ci(out, "native")) return;
|
||||
for (i = 0; i < g_app_count; i++) {
|
||||
if (g_apps[i].name && fat32_lfn_streq_ci(g_apps[i].name, out)) {
|
||||
app_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (app_id < 0) return;
|
||||
slog("native launch from KDE:");
|
||||
slog(out);
|
||||
g_page = 3;
|
||||
launch_app(app_id);
|
||||
}
|
||||
|
||||
/* VSCode Phase 5/6: 桌面图标启动 Linux 应用(LINUXAPP.CNF 注册项)。
|
||||
* 解析 args(空格分隔)后经共享路径 exec_async,并切到 IDE 页 attach。 */
|
||||
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;
|
||||
}
|
||||
|
||||
if (fat32_lfn_streq_ci(app->name, "kde")) {
|
||||
const char *kargv[2];
|
||||
struct linux_compat_scanout_info si;
|
||||
u32 *fb = (u32 *)g_fb.fb;
|
||||
slog("kde: launching fullscreen Plasma");
|
||||
if (g_lxc_svc->exec) {
|
||||
const char *rmargv[] = { "sh", "-c", "rm -f /tmp/deshab-native /tmp/deshab-native-app" };
|
||||
u64 ec = 0;
|
||||
(void)g_lxc_svc->exec("/bin/sh", 3, rmargv, 0, 0, 0, &ec);
|
||||
}
|
||||
kargv[0] = app->linux_path;
|
||||
kargv[1] = 0;
|
||||
if (g_lxc_svc->exec_async(app->linux_path, 1, kargv) != 0) {
|
||||
slog("kde: exec_async failed");
|
||||
return;
|
||||
}
|
||||
{
|
||||
int scanout_logged = 0;
|
||||
for (;;) {
|
||||
u8 st = inb(0x64);
|
||||
if (st & 1) {
|
||||
if (!(st & 0x20)) {
|
||||
u8 sc = inb(0x60);
|
||||
if (sc == 0x01) {
|
||||
slog("kde: aborted by Esc");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
(void)inb(0x60);
|
||||
}
|
||||
}
|
||||
if (g_lxc_svc->run_slice) {
|
||||
int pump;
|
||||
for (pump = 0; pump < 4; pump++)
|
||||
(void)g_lxc_svc->run_slice();
|
||||
}
|
||||
/* Do not exec test/sleep here: sync exec starves Plasma on the
|
||||
* only vCPU (native switch is Esc on the host loop). */
|
||||
if (g_lxc_svc->gpu_get_scanout_info &&
|
||||
g_lxc_svc->gpu_get_scanout_info(&si) == 0 &&
|
||||
si.enabled && si.host_vaddr && fb) {
|
||||
u32 row, copy_w;
|
||||
const u8 *src = (const u8 *)si.host_vaddr;
|
||||
u32 sw = si.width, sh = si.height, stride = si.stride;
|
||||
if (sw && sh && stride >= sw * 4) {
|
||||
if (!scanout_logged) {
|
||||
slog("KDE scanout w=");
|
||||
slog_num("KDE scanout w=", (int)sw);
|
||||
scanout_logged = 1;
|
||||
}
|
||||
for (row = 0; row < sh && row < (u32)g_fb.height; row++) {
|
||||
copy_w = sw;
|
||||
if (copy_w > (u32)g_fb.width) copy_w = (u32)g_fb.width;
|
||||
{
|
||||
u8 *dd = (u8 *)fb + (u64)row * g_fb.pitch;
|
||||
const u8 *ss = src + (u64)row * stride;
|
||||
u64 n = (u64)copy_w * 4;
|
||||
while (n--) *dd++ = *ss++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
desktop_launch_pending_native();
|
||||
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。 */
|
||||
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);
|
||||
}
|
||||
|
||||
/* attach/detach 时使 scanout 缓存失效并请求全量重绘 */
|
||||
void ide_attach_invalidate(void) {
|
||||
g_so_full_repaint = 1;
|
||||
g_so_valid = 0;
|
||||
}
|
||||
|
||||
/* 获取 scanout 状态:优先本帧缓存;缓存无效时查询并缓存。 */
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
+126
-4018
@@ -1,2036 +1,79 @@
|
||||
/* Deshab Desktop Manager — Winux-Kate 结构完整复刻
|
||||
/* main.c — Deshab Desktop Manager 组合根(M0 模块化拆分后)
|
||||
*
|
||||
* 布局结构与 D:\Code\Winux-Kate 完全一致:
|
||||
* app-shell
|
||||
* ├── topbar (34px):品牌 + 工作区切换 + 页面切换 + PG 时钟
|
||||
* page-host
|
||||
* │ ├── 1·DASHBOARD:TERM-01 / TERM-02 / EDITOR / FILES 四面板 + StatusBar
|
||||
* │ ├── 2·IDE:标签栏 + 编辑宿主
|
||||
* │ ├── 3·DESKTOP:图标网格 + 窗口 + 任务栏
|
||||
* │ └── 4+/CUSTOM:外部 .elf 全屏宿主(双击启动)
|
||||
* └── task-view overlay(工作区卡片)
|
||||
* 原 4610 行单文件已拆分为 13 个模块(见 desktop.h 头注释)。
|
||||
* 本文件保留:dsk_entry 入口、服务指针探测(lxc/pe/block)、boot 屏、
|
||||
* 主事件循环。功能与拆分前完全一致(零功能变更)。
|
||||
*
|
||||
* 新 UI 特性(Kate 之外):
|
||||
* - RTC 实时时钟(秒级)+ 开机至今 UP 计时
|
||||
* - VOL / BRIGHT 可拖滑块(BRIGHT 真实调暗全屏)
|
||||
* - 开机打字机 Boot 屏
|
||||
* - FILES 面板双击文件载入 EDITOR(FAT32 真实读写 + SAVE 回写)
|
||||
* - 窗口单实例聚焦策略
|
||||
* 原 "Winux-Kate 结构完整复刻" 布局说明见 pages.c 头注释;
|
||||
* M2-M4 将按 Win11 Fluent 桌面重构计划逐页替换。
|
||||
*/
|
||||
#include "desktop.h"
|
||||
#include "zhfont.h" /* M1: 中文位图字库(dbf 加载 + 中英混排渲染) */
|
||||
#include "wallpaper.h" /* M2: 壁纸背景层缓存 */
|
||||
#include "textures.h" /* M2: 启动背景/开机 logo/开始菜单 logo 纹理 */
|
||||
|
||||
#include "../UTSM/include/utsm/dsk.h"
|
||||
#include "../UTSM/include/utsm/linux_compat.h" /* VSCode Phase 2: input_forward_* */
|
||||
#include "../UTSM/include/utsm/pe.h" /* P5d: PE 窗口模式 run_windowed/inject_* */
|
||||
/* ---- 全局状态(渲染上下文 / 服务指针,各模块 extern) ---- */
|
||||
du_context g_fb;
|
||||
u64 g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch;
|
||||
const void *g_kernel_api;
|
||||
u8 *g_real_fb = 0; /* 真实 framebuffer 地址 */
|
||||
desktop_block_read_fn g_block_read;
|
||||
desktop_block_write_fn g_block_write;
|
||||
const dsk_boot_context *g_boot_ctx = 0;
|
||||
|
||||
/* block 设备函数类型(从 kernel_api + 0xA8 获取 block_api,read @ +0x10, write @ +0x18) */
|
||||
typedef int (*desktop_block_read_fn)(u32 index, u64 lba, u32 count, void *buffer);
|
||||
typedef int (*desktop_block_write_fn)(u32 index, u64 lba, u32 count, const void *buffer);
|
||||
|
||||
/* 先包含 ascii_bitmaps.c(定义 g_ascii),再包含 deshab_ui.h(引用 g_ascii) */
|
||||
#include "../firstInit/ascii_bitmaps.c"
|
||||
#include "../UTSM/include/utsm/deshab_ui.h"
|
||||
/* 用户态 FAT32 读写。P5d:PE exe 可达数 MB,数据缓冲同 cmd.elf 提到 4MB。 */
|
||||
#define F32_DATA_BYTES (4u * 1024u * 1024u)
|
||||
#include "../tools/fat32_io.h"
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef signed char i8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
typedef long long i64;
|
||||
|
||||
#define COM1 0x3F8
|
||||
|
||||
static __inline__ void outb(u16 port, u8 value) { __asm__ volatile("outb %0,%1"::"a"(value),"Nd"(port)); }
|
||||
static __inline__ u8 inb(u16 port) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(port)); return v; }
|
||||
|
||||
static void sputc(char c) {
|
||||
for (unsigned int i=0; i<100000; i++) { if (inb(COM1+5)&0x20) break; }
|
||||
outb(COM1, (unsigned char)c);
|
||||
}
|
||||
static void swrite(const char *s) { while(*s) { if(*s=='\n')sputc('\r'); sputc(*s++); } }
|
||||
static void slog(const char *s) { swrite("[desktop] "); swrite(s); swrite("\n"); }
|
||||
|
||||
static int kstrlen(const char *s) { int n = 0; while (s[n]) n++; return n; }
|
||||
static void kstrcpy(char *d, const char *s, int cap) {
|
||||
int i = 0;
|
||||
for (; i < cap - 1 && s[i]; i++) d[i] = s[i];
|
||||
d[i] = 0;
|
||||
}
|
||||
static void kstrcat(char *d, const char *s, int cap) {
|
||||
int n = kstrlen(d);
|
||||
if (n < cap) kstrcpy(d + n, s, cap - n);
|
||||
}
|
||||
|
||||
/* P7.9: u32 -> decimal string(返回长度) */
|
||||
static int u32dec(u32 v, char *out) {
|
||||
char tmp[10];
|
||||
int n = 0;
|
||||
if (v == 0) { out[0] = '0'; out[1] = 0; return 1; }
|
||||
while (v > 0) { tmp[n++] = '0' + (char)(v % 10); v /= 10; }
|
||||
for (int i = 0; i < n; i++) out[i] = tmp[n - 1 - i];
|
||||
out[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static u64 rdtsc(void) {
|
||||
u32 lo, hi;
|
||||
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
||||
return ((u64)hi << 32) | lo;
|
||||
}
|
||||
|
||||
/* 前向声明 */
|
||||
static void redraw_all(void);
|
||||
static int ps2_mouse_poll(void);
|
||||
static void draw_statusbar_full(void);
|
||||
static void files_refresh(void);
|
||||
|
||||
/* ============================================================
|
||||
* 常量(Winux-Kate 布局)
|
||||
* ============================================================ */
|
||||
|
||||
#define MAX_WINDOWS 8
|
||||
#define BORDER_W 1
|
||||
#define CURSOR_SIZE 24
|
||||
|
||||
#define ICON_W KATE_ICON_W /* 104 */
|
||||
#define ICON_H KATE_ICON_H /* 90 */
|
||||
#define ICON_GAP KATE_ICON_GAP /* 18 */
|
||||
#define ICON_PAD KATE_ICON_PAD /* 24 */
|
||||
|
||||
#define CLOSE_BTN_SIZE 16
|
||||
#define CLOSE_BTN_OFFSET 22
|
||||
|
||||
#define MAX_WS 6 /* 工作区上限 */
|
||||
#define MAX_CPAGES 4 /* 自定义页上限 */
|
||||
#define MAX_FILES 24 /* FILES 面板条目上限 */
|
||||
|
||||
/* 应用固定状态地址(单实例策略 → 每类应用唯一实例) */
|
||||
#define ADDR_TERM1 0x4000000ULL
|
||||
#define ADDR_TERM2 0x4100000ULL
|
||||
#define ADDR_TERMWIN 0x4200000ULL
|
||||
#define ADDR_EDDASH 0x5000000ULL
|
||||
#define ADDR_EDWIN 0x5100000ULL
|
||||
#define ADDR_CALC 0x6000000ULL
|
||||
|
||||
/* ============================================================
|
||||
* 事件系统
|
||||
* ============================================================ */
|
||||
|
||||
typedef enum {
|
||||
EV_MOUSE_MOVE = 1,
|
||||
EV_MOUSE_DOWN,
|
||||
EV_MOUSE_UP,
|
||||
EV_KEY_DOWN,
|
||||
EV_KEY_UP,
|
||||
} ev_type;
|
||||
|
||||
typedef struct {
|
||||
ev_type type;
|
||||
int mx, my;
|
||||
int button; /* 0=左, 1=右 */
|
||||
u8 scancode;
|
||||
int shift, ctrl, alt;
|
||||
} desktop_event;
|
||||
|
||||
/* ============================================================
|
||||
* 窗口
|
||||
* ============================================================ */
|
||||
|
||||
typedef struct desktop_window {
|
||||
int id;
|
||||
int x, y, w, h;
|
||||
int visible;
|
||||
int focused;
|
||||
int minimized;
|
||||
char title[48];
|
||||
int app_id;
|
||||
void *app_state;
|
||||
int closing;
|
||||
int dirty;
|
||||
int ws; /* 所属工作区 */
|
||||
} desktop_window;
|
||||
|
||||
typedef struct desktop_window desktop_win;
|
||||
|
||||
typedef struct app_ctx {
|
||||
du_context *fb;
|
||||
desktop_win *win;
|
||||
int client_x, client_y, client_w, client_h;
|
||||
int should_exit;
|
||||
desktop_block_read_fn block_read;
|
||||
desktop_block_write_fn block_write;
|
||||
const void *kernel_api;
|
||||
} app_ctx;
|
||||
|
||||
typedef void *(*app_create_fn)(app_ctx *ctx);
|
||||
typedef void (*app_event_fn)(void *state, app_ctx *ctx, desktop_event *ev);
|
||||
typedef void (*app_draw_fn)(void *state, app_ctx *ctx);
|
||||
typedef void (*app_destroy_fn)(void *state);
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *display_name;
|
||||
u32 default_w, default_h;
|
||||
app_create_fn on_create;
|
||||
app_event_fn on_event;
|
||||
app_draw_fn on_draw;
|
||||
app_destroy_fn on_destroy;
|
||||
const char *elf_name;
|
||||
/* VSCode Phase 5: Linux guest 应用(来自 LINUXAPP.CNF)。
|
||||
* linux_path 非 0 时 launch_app 走 launch_linux_app → exec_async。 */
|
||||
const char *linux_path;
|
||||
const char *linux_args;
|
||||
/* P5d: Windows PE 应用(来自 PEAPPS.CNF)。
|
||||
* pe_file 非 0 时 launch_app 走 launch_pe_app → run_windowed 桌面嵌入。 */
|
||||
const char *pe_file;
|
||||
const char *pe_args;
|
||||
} app_descriptor;
|
||||
|
||||
/* 桌面图标 */
|
||||
typedef struct {
|
||||
int app_id;
|
||||
int x, y;
|
||||
const char *label;
|
||||
int selected;
|
||||
} desktop_icon;
|
||||
|
||||
#define MAX_ICONS 16 /* P7.4: 与 MAX_APPS 对齐(多实例 VSCODE2 等占位) */
|
||||
|
||||
/* 工作区 */
|
||||
typedef struct {
|
||||
char name[16];
|
||||
int page; /* 该工作区离开时的页面 */
|
||||
} workspace;
|
||||
|
||||
/* 自定义页 */
|
||||
typedef struct {
|
||||
char name[12];
|
||||
char elf11[11];
|
||||
int id; /* 页面号 5..8 */
|
||||
} cpage;
|
||||
|
||||
/* FILES 面板条目 */
|
||||
typedef struct {
|
||||
char name11[11];
|
||||
char disp[13];
|
||||
u32 size;
|
||||
} fentry;
|
||||
|
||||
/* ============================================================
|
||||
* 全局状态
|
||||
* ============================================================ */
|
||||
|
||||
static du_context g_fb;
|
||||
static u64 g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch;
|
||||
static const void *g_kernel_api;
|
||||
|
||||
/* 双缓冲:渲染到 sprite buffer,完成后一次性 flip 到真实 framebuffer */
|
||||
#define SPRITE_BUF_ADDR 0x7000000ULL /* 112MB,在 identity-mapped 区域 */
|
||||
static u8 *g_real_fb = 0; /* 真实 framebuffer 地址 */
|
||||
static desktop_block_read_fn g_block_read;
|
||||
static desktop_block_write_fn g_block_write;
|
||||
static const dsk_boot_context *g_boot_ctx = 0;
|
||||
/* M2: Win11 Fluent 桌面开关(FUCK [desktop] fluent=,缺省 1)。
|
||||
* 1 = 壁纸背景 + Fluent 圆角窗口 + 悬浮任务栏;0 = 旧 KATE 桌面。 */
|
||||
int g_fluent = 1;
|
||||
|
||||
/* 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;
|
||||
const linux_compat_service *g_lxc_svc = 0;
|
||||
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 非空 */
|
||||
/* P5d: PE 兼容层服务指针(dsk_loader 在 reserved[4] 写入,magic 校验)。 */
|
||||
const pe_service *g_pe_svc = 0;
|
||||
|
||||
#define PE_SURFACE_ADDR 0x7800000ULL /* 120MB:sprite buf(112MB, ≤8.3MB) 之上 */
|
||||
#define PE_SURFACE_MAX (8u * 1024u * 1024u)
|
||||
#define PE_WM_CLOSE 0x0010u /* shim Win32 WM_CLOSE */
|
||||
|
||||
/* 鼠标状态 */
|
||||
static int g_mouse_x = 400, g_mouse_y = 300;
|
||||
static int g_mouse_btn = 0; /* bit0=左 bit1=右 */
|
||||
static int g_left_pressed = 0, g_left_released = 0, g_right_pressed = 0;
|
||||
static u8 g_mouse_buf[3];
|
||||
static int g_mouse_idx = 0;
|
||||
static int g_mouse_has_pkt = 0;
|
||||
|
||||
/* 光标背景保存 */
|
||||
static u32 g_cursor_save[CURSOR_SIZE * CURSOR_SIZE];
|
||||
static int g_cursor_saved = 0;
|
||||
static int g_cursor_old_x = -1, g_cursor_old_y = -1;
|
||||
|
||||
/* 键盘状态 */
|
||||
static int g_shift = 0, g_ctrl = 0, g_alt = 0;
|
||||
static int g_e0 = 0;
|
||||
|
||||
/* 窗口列表 */
|
||||
static desktop_window g_windows[MAX_WINDOWS];
|
||||
static int g_win_count = 0;
|
||||
static int g_next_win_id = 1;
|
||||
static int g_focused_win = -1;
|
||||
|
||||
/* 拖拽状态 */
|
||||
static int g_dragging = 0;
|
||||
static int g_drag_win = -1;
|
||||
static int g_drag_off_x, g_drag_off_y;
|
||||
|
||||
/* 应用表 */
|
||||
#define MAX_APPS 16
|
||||
static app_descriptor g_apps[MAX_APPS];
|
||||
static int g_app_count = 0;
|
||||
|
||||
static desktop_icon g_icons[MAX_ICONS];
|
||||
static int g_icon_count = 0;
|
||||
|
||||
/* ---- Kate 外壳状态 ---- */
|
||||
static int g_booted = 0;
|
||||
static int g_page = 1; /* 1..3 内置, 4..8 自定义 */
|
||||
static int g_taskview = 0;
|
||||
static int g_dash_focus = 0; /* 0=t1 1=t2 2=ed 3=files */
|
||||
|
||||
static workspace g_ws[MAX_WS];
|
||||
static int g_ws_count = 1;
|
||||
static int g_ws_cur = 0;
|
||||
|
||||
static cpage g_cpages[MAX_CPAGES];
|
||||
static int g_cpage_count = 0;
|
||||
|
||||
/* 右键菜单(移到工作区) */
|
||||
static int g_ctx_open = 0;
|
||||
static int g_ctx_x = 0, g_ctx_y = 0;
|
||||
static int g_ctx_win = -1;
|
||||
|
||||
/* 状态栏滑块 */
|
||||
static int g_vol = 65;
|
||||
static int g_bright = 100;
|
||||
static int g_bright_applied = 100; /* 上次应用的亮度值(避免重复处理) */
|
||||
static int g_drag_slider = 0; /* 1=vol 2=bright */
|
||||
static int g_vol_rect[4]; /* x,y,w,h 滑轨 */
|
||||
static int g_bri_rect[4];
|
||||
|
||||
static int g_quit = 0; /* EXIT 按钮 → 返回 DSK */
|
||||
|
||||
/* 时钟 */
|
||||
static int g_rtc_boot_sec = 0; /* 开机时刻(秒级当天秒) */
|
||||
static int g_last_sec = -1;
|
||||
static u64 g_tsc_per_sec = 0;
|
||||
|
||||
/* FILES 面板 */
|
||||
static fentry g_files[MAX_FILES];
|
||||
static int g_file_count = 0;
|
||||
static int g_file_sel = -1;
|
||||
static int g_files_loaded = 0;
|
||||
|
||||
/* 双击检测 */
|
||||
static u64 g_last_click_tsc = 0;
|
||||
static int g_last_click_x = -100, g_last_click_y = -100;
|
||||
|
||||
/* 面板/控件命中矩形(绘制时更新,点击时使用) */
|
||||
static int g_dash_rect[4][4]; /* 4 面板 x,y,w,h */
|
||||
static int g_save_rect[4];
|
||||
static int g_pg_rect[8][4]; /* 页面切换按钮 */
|
||||
static int g_pg_rm_rect[8][4];
|
||||
static int g_pgadd_rect[4];
|
||||
static int g_tvbtn_rect[4]; /* 任务视图按钮 */
|
||||
static int g_ws_rect[MAX_WS][4];
|
||||
static int g_wsadd_rect[4];
|
||||
static int g_tb_rect[MAX_WINDOWS][4]; /* 任务栏项 */
|
||||
static int g_tb_close_rect[MAX_WINDOWS][4];
|
||||
static int g_tb_quit_rect[4];
|
||||
static int g_tb_adopt_rect[4]; /* 收纳窗口按钮 */
|
||||
static int g_tv_card[MAX_WS + 1][4]; /* 任务视图卡片(+add) */
|
||||
static int g_tv_close[MAX_WS][4];
|
||||
static int g_ctx_rect[4];
|
||||
static int g_ctx_item[MAX_WS + 1][4];
|
||||
static int g_ctx_item_ws[MAX_WS + 1]; /* 菜单行 → 真实工作区索引,-1=新建 */
|
||||
static int g_ide_host[4];
|
||||
static int g_ide_tabx[4]; /* IDE 标签 × */
|
||||
static int g_ide_new_rect[4]; /* IDE "+ NEW IDE" 按钮 */
|
||||
static int g_cp_host[4]; /* 自定义页宿主 */
|
||||
|
||||
/* ============================================================
|
||||
* 脏矩形渲染(性能优化:只更新变化区域)
|
||||
* M2: FUCK 配置解析([desktop] fluent=)
|
||||
* 复用 dsk 的 FAT32 路径读取(f32_read_path_lfn_w),INI 最小解析。
|
||||
* ============================================================ */
|
||||
|
||||
#define MAX_DIRTY_RECTS 16
|
||||
|
||||
typedef struct {
|
||||
int x, y, w, h;
|
||||
int active;
|
||||
} dirty_rect;
|
||||
|
||||
static dirty_rect g_dirty_rects[MAX_DIRTY_RECTS];
|
||||
static int g_dirty_count = 0;
|
||||
static int g_full_redraw = 1; /* 初始全屏重绘 */
|
||||
|
||||
/* 标记脏区域 */
|
||||
__attribute__((unused))
|
||||
static void mark_dirty(int x, int y, int w, int h) {
|
||||
if (g_dirty_count >= MAX_DIRTY_RECTS) {
|
||||
/* 溢出时合并为全屏重绘 */
|
||||
g_full_redraw = 1;
|
||||
return;
|
||||
}
|
||||
g_dirty_rects[g_dirty_count].x = x;
|
||||
g_dirty_rects[g_dirty_count].y = y;
|
||||
g_dirty_rects[g_dirty_count].w = w;
|
||||
g_dirty_rects[g_dirty_count].h = h;
|
||||
g_dirty_rects[g_dirty_count].active = 1;
|
||||
g_dirty_count++;
|
||||
}
|
||||
|
||||
/* 合并所有脏矩形为一个包围盒 */
|
||||
static void merge_dirty_rects(int *out_x, int *out_y, int *out_w, int *out_h) {
|
||||
if (g_dirty_count == 0) {
|
||||
*out_x = *out_y = *out_w = *out_h = 0;
|
||||
return;
|
||||
}
|
||||
int min_x = (int)g_fb_w, min_y = (int)g_fb_h;
|
||||
int max_x = 0, max_y = 0;
|
||||
for (int i = 0; i < g_dirty_count; i++) {
|
||||
if (!g_dirty_rects[i].active) continue;
|
||||
int x1 = g_dirty_rects[i].x;
|
||||
int y1 = g_dirty_rects[i].y;
|
||||
int x2 = x1 + g_dirty_rects[i].w;
|
||||
int y2 = y1 + g_dirty_rects[i].h;
|
||||
if (x1 < min_x) min_x = x1;
|
||||
if (y1 < min_y) min_y = y1;
|
||||
if (x2 > max_x) max_x = x2;
|
||||
if (y2 > max_y) max_y = y2;
|
||||
}
|
||||
*out_x = min_x;
|
||||
*out_y = min_y;
|
||||
*out_w = max_x - min_x;
|
||||
*out_h = max_y - min_y;
|
||||
}
|
||||
|
||||
/* 清除所有脏矩形 */
|
||||
static void clear_dirty_rects(void) {
|
||||
g_dirty_count = 0;
|
||||
g_full_redraw = 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 箭头光标形状(24×24,1=前景 0=透明)
|
||||
* ============================================================ */
|
||||
|
||||
static const u8 cursor_shape[24][24] = {
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
* RTC 时钟
|
||||
* ============================================================ */
|
||||
|
||||
static int bcd2bin(u8 v) { return (v >> 4) * 10 + (v & 0xF); }
|
||||
|
||||
static void rtc_read(int *yy, int *mo, int *dd, int *hh, int *mi, int *ss) {
|
||||
outb(0x70, 0x00); *ss = bcd2bin(inb(0x71));
|
||||
outb(0x70, 0x02); *mi = bcd2bin(inb(0x71));
|
||||
outb(0x70, 0x04); *hh = bcd2bin(inb(0x71));
|
||||
outb(0x70, 0x07); *dd = bcd2bin(inb(0x71));
|
||||
outb(0x70, 0x08); *mo = bcd2bin(inb(0x71));
|
||||
outb(0x70, 0x09); *yy = bcd2bin(inb(0x71));
|
||||
}
|
||||
|
||||
static int rtc_day_sec(void) {
|
||||
int y, mo, d, h, mi, s;
|
||||
rtc_read(&y, &mo, &d, &h, &mi, &s);
|
||||
return h * 3600 + mi * 60 + s;
|
||||
}
|
||||
|
||||
/* 格式化 HH:MM:SS */
|
||||
static void fmt_hms(char *out, int sec) {
|
||||
int h = sec / 3600, m = (sec / 60) % 60, s = sec % 60;
|
||||
out[0] = '0' + h / 10; out[1] = '0' + h % 10; out[2] = ':';
|
||||
out[3] = '0' + m / 10; out[4] = '0' + m % 10; out[5] = ':';
|
||||
out[6] = '0' + s / 10; out[7] = '0' + s % 10; out[8] = 0;
|
||||
}
|
||||
|
||||
/* 校准 TSC(约 1 秒):用于双击间隔判定 */
|
||||
static void tsc_calibrate(void) {
|
||||
int s0 = rtc_day_sec();
|
||||
while (rtc_day_sec() == s0) __asm__("pause");
|
||||
u64 t0 = rdtsc();
|
||||
int s1 = rtc_day_sec();
|
||||
while (rtc_day_sec() == s1) __asm__("pause");
|
||||
g_tsc_per_sec = rdtsc() - t0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Winux-Kate 应用图标(40×40,青色几何图形)
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_circle_outline(du_context *ctx, int cx, int cy, int r, u32 color) {
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
int d2 = dx * dx + dy * dy;
|
||||
if (d2 <= r * r && d2 > (r - 1) * (r - 1)) {
|
||||
du_pixel(ctx, cx + dx, cy + dy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_icon_terminal(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2);
|
||||
du_fill_rect(ctx, cx + 8, cy + 12, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 10, cy + 14, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 12, cy + 16, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 10, cy + 18, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 8, cy + 20, 2, 2, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 16, cy + 22, 8, 2, KS_ACCENT);
|
||||
}
|
||||
|
||||
static void draw_icon_editor(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2);
|
||||
int widths[] = {24, 20, 22, 14, 20, 18};
|
||||
for (int i = 0; i < 6; i++) {
|
||||
du_fill_rect(ctx, cx + 8, cy + 8 + i * 4, widths[i], 2,
|
||||
(i == 0) ? KS_ACCENT2 : KS_ACCENT);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_icon_folder(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_BORDER, 2);
|
||||
du_fill_rect(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT_DIM);
|
||||
du_rect_outline(ctx, cx + 8, cy + 10, 12, 4, KS_ACCENT, 1);
|
||||
du_fill_rect(ctx, cx + 6, cy + 14, 28, 16, KS_BG_SECONDARY);
|
||||
du_rect_outline(ctx, cx + 6, cy + 14, 28, 16, KS_ACCENT, 1);
|
||||
du_fill_rect(ctx, cx + 6, cy + 14, 28, 1, KS_ACCENT);
|
||||
du_fill_rect(ctx, cx + 10, cy + 19, 12, 1, KS_ACCENT2);
|
||||
du_fill_rect(ctx, cx + 10, cy + 23, 16, 1, KS_TEXT_DIM);
|
||||
du_fill_rect(ctx, cx + 10, cy + 26, 10, 1, KS_TEXT_DIM);
|
||||
}
|
||||
|
||||
static void draw_icon_calc(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2);
|
||||
du_fill_rect(ctx, cx + 6, cy + 6, 28, 8, KS_BG_PRIMARY);
|
||||
du_rect_outline(ctx, cx + 6, cy + 6, 28, 8, KS_ACCENT2, 1);
|
||||
du_fill_rect(ctx, cx + 28, cy + 8, 4, 4, KS_ACCENT2);
|
||||
for (int r = 0; r < 3; r++) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
int bx = cx + 6 + c * 9;
|
||||
int by = cy + 18 + r * 7;
|
||||
du_fill_rect(ctx, bx, by, 7, 5, KS_ACCENT_DIM);
|
||||
du_rect_outline(ctx, bx, by, 7, 5, KS_ACCENT, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_icon_browser(du_context *ctx, int cx, int cy) {
|
||||
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_TERTIARY);
|
||||
du_rect_outline(ctx, cx, cy, 40, 40, KS_ACCENT, 2);
|
||||
int ccx = cx + 20, ccy = cy + 20, r = 11;
|
||||
draw_circle_outline(ctx, ccx, ccy, r, KS_ACCENT);
|
||||
du_fill_rect(ctx, ccx - r, ccy, 2 * r + 1, 1, KS_ACCENT);
|
||||
du_fill_rect(ctx, ccx, ccy - r, 1, 2 * r + 1, KS_ACCENT);
|
||||
du_pixel(ctx, ccx, ccy, KS_ACCENT2);
|
||||
}
|
||||
|
||||
static void draw_app_icon(du_context *ctx, int app_id, int cx, int cy) {
|
||||
switch (app_id) {
|
||||
case 0: draw_icon_terminal(ctx, cx, cy); break;
|
||||
case 1: draw_icon_editor(ctx, cx, cy); break;
|
||||
case 2: draw_icon_folder(ctx, cx, cy); break;
|
||||
case 3: draw_icon_calc(ctx, cx, cy); break;
|
||||
case 4: draw_icon_browser(ctx, cx, cy); break;
|
||||
default: draw_icon_terminal(ctx, cx, cy); break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 光标管理
|
||||
* ============================================================ */
|
||||
|
||||
static void cursor_save_bg(int mx, int my) {
|
||||
for (int r = 0; r < CURSOR_SIZE; r++) {
|
||||
for (int c = 0; c < CURSOR_SIZE; c++) {
|
||||
int x = mx + c, y = my + r;
|
||||
if (x >= 0 && (u64)x < g_fb_w && y >= 0 && (u64)y < g_fb_h) {
|
||||
u32 *line = (u32 *)((u8 *)g_fb.fb + (u64)y * g_fb.pitch);
|
||||
g_cursor_save[r * CURSOR_SIZE + c] = line[x];
|
||||
} else {
|
||||
g_cursor_save[r * CURSOR_SIZE + c] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_cursor_saved = 1;
|
||||
g_cursor_old_x = mx;
|
||||
g_cursor_old_y = my;
|
||||
}
|
||||
|
||||
static void cursor_restore_bg(void) {
|
||||
if (!g_cursor_saved) return;
|
||||
for (int r = 0; r < CURSOR_SIZE; r++) {
|
||||
for (int c = 0; c < CURSOR_SIZE; c++) {
|
||||
int x = g_cursor_old_x + c, y = g_cursor_old_y + r;
|
||||
if (x >= 0 && (u64)x < g_fb_w && y >= 0 && (u64)y < g_fb_h) {
|
||||
u32 *line = (u32 *)((u8 *)g_fb.fb + (u64)y * g_fb.pitch);
|
||||
line[x] = g_cursor_save[r * CURSOR_SIZE + c];
|
||||
}
|
||||
}
|
||||
}
|
||||
g_cursor_saved = 0;
|
||||
}
|
||||
|
||||
static void cursor_draw(int mx, int my) {
|
||||
for (int r = 0; r < CURSOR_SIZE; r++) {
|
||||
for (int c = 0; c < CURSOR_SIZE; c++) {
|
||||
if (cursor_shape[r][c]) {
|
||||
du_pixel(&g_fb, mx + c, my + r, KS_ACCENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 双缓冲 flip ---- */
|
||||
|
||||
/* 将 sprite buffer 完整复制到真实 framebuffer */
|
||||
static void flip_buffer(void) {
|
||||
if (!g_real_fb) return;
|
||||
u64 total = g_fb_h * g_fb_pitch / 4;
|
||||
u32 *dst = (u32 *)g_real_fb;
|
||||
u32 *src = (u32 *)SPRITE_BUF_ADDR;
|
||||
for (u64 i = 0; i < total; i++) dst[i] = src[i];
|
||||
}
|
||||
|
||||
/* 仅复制指定矩形区域(用于鼠标移动时的局部更新) */
|
||||
static void flip_rect(int x, int y, int w, int h) {
|
||||
if (!g_real_fb) return;
|
||||
for (int r = 0; r < h; r++) {
|
||||
int ry = y + r;
|
||||
if (ry < 0 || (u64)ry >= g_fb_h) continue;
|
||||
u32 *dst = (u32 *)(g_real_fb + (u64)ry * g_fb_pitch);
|
||||
u32 *src = (u32 *)((u8 *)SPRITE_BUF_ADDR + (u64)ry * g_fb_pitch);
|
||||
for (int c = 0; c < w; c++) {
|
||||
int rx = x + c;
|
||||
if (rx < 0 || (u64)rx >= g_fb_w) continue;
|
||||
dst[rx] = src[rx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 非阻塞延时:动画期间持续轮询鼠标,保持光标响应。 */
|
||||
__attribute__((unused))
|
||||
static void anim_delay_poll_mouse(u32 ms) {
|
||||
for (u32 i = 0; i < ms; i++) {
|
||||
for (volatile u32 j = 0; j < 50000; j++) {
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
if (ps2_mouse_poll()) {
|
||||
int old_x = g_cursor_old_x, old_y = g_cursor_old_y;
|
||||
cursor_restore_bg();
|
||||
flip_rect(old_x, old_y, CURSOR_SIZE, CURSOR_SIZE);
|
||||
cursor_save_bg(g_mouse_x, g_mouse_y);
|
||||
cursor_draw(g_mouse_x, g_mouse_y);
|
||||
flip_rect(g_mouse_x, g_mouse_y, CURSOR_SIZE, CURSOR_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 窗口管理
|
||||
* ============================================================ */
|
||||
|
||||
static desktop_window *win_find(int id) {
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (g_windows[i].id == id) return &g_windows[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static desktop_window *win_find_at(int mx, int my) {
|
||||
for (int i = g_win_count - 1; i >= 0; i--) {
|
||||
desktop_window *w = &g_windows[i];
|
||||
if (!w->visible || w->minimized) continue;
|
||||
if (w->ws != g_ws_cur) continue;
|
||||
if (mx >= w->x && mx < w->x + w->w && my >= w->y && my < w->y + w->h) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void win_bring_to_front(desktop_window *w) {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (g_windows[i].id == w->id) { idx = i; break; }
|
||||
}
|
||||
if (idx < 0 || idx == g_win_count - 1) return;
|
||||
desktop_window tmp = g_windows[idx];
|
||||
for (int i = idx; i < g_win_count - 1; i++) {
|
||||
g_windows[i] = g_windows[i+1];
|
||||
}
|
||||
g_windows[g_win_count - 1] = tmp;
|
||||
}
|
||||
|
||||
static void win_focus(desktop_window *w) {
|
||||
int id = w->id; /* bring_to_front 会重排数组,先保存 id 避免悬空指针 */
|
||||
win_bring_to_front(w);
|
||||
for (int i = 0; i < g_win_count; i++)
|
||||
g_windows[i].focused = (g_windows[i].id == id);
|
||||
g_focused_win = id;
|
||||
if (g_win_count > 0) g_windows[g_win_count - 1].minimized = 0;
|
||||
}
|
||||
|
||||
static desktop_window *win_create(int app_id, const char *title, int x, int y, int w, int h) {
|
||||
if (g_win_count >= MAX_WINDOWS) return 0;
|
||||
desktop_window *win = &g_windows[g_win_count++];
|
||||
int id = g_next_win_id++;
|
||||
win->id = id;
|
||||
win->x = x; win->y = y; win->w = w; win->h = h;
|
||||
win->visible = 1;
|
||||
win->focused = 1;
|
||||
win->minimized = 0;
|
||||
win->app_id = app_id;
|
||||
win->app_state = 0;
|
||||
win->closing = 0;
|
||||
win->dirty = 1;
|
||||
win->ws = g_ws_cur;
|
||||
for (int i = 0; i < 47 && title[i]; i++) win->title[i] = title[i];
|
||||
win->title[47] = 0;
|
||||
for (int i = 0; i < g_win_count - 1; i++) g_windows[i].focused = 0;
|
||||
g_focused_win = id;
|
||||
return win;
|
||||
}
|
||||
|
||||
static void win_destroy(desktop_window *w) {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (g_windows[i].id == w->id) { idx = i; break; }
|
||||
}
|
||||
if (idx < 0) return;
|
||||
|
||||
/* 直接隐藏窗口(去掉关闭动画以避免卡顿和闪屏) */
|
||||
w->visible = 0;
|
||||
|
||||
if (w->app_id >= 0 && w->app_id < g_app_count && g_apps[w->app_id].on_destroy && w->app_state) {
|
||||
g_apps[w->app_id].on_destroy(w->app_state);
|
||||
}
|
||||
for (int i = idx; i < g_win_count - 1; i++) g_windows[i] = g_windows[i+1];
|
||||
g_win_count--;
|
||||
if (g_focused_win == w->id) {
|
||||
g_focused_win = -1;
|
||||
for (int i = g_win_count - 1; i >= 0; i--) {
|
||||
if (g_windows[i].ws == g_ws_cur && g_windows[i].visible && !g_windows[i].minimized) {
|
||||
g_focused_win = g_windows[i].id;
|
||||
g_windows[i].focused = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int win_hit_titlebar(desktop_window *w, int mx, int my) {
|
||||
return mx >= w->x && mx < w->x + w->w &&
|
||||
my >= w->y && my < w->y + KATE_TITLEBAR_H;
|
||||
}
|
||||
|
||||
/* P5d: 关闭请求。运行中的 PE 窗口不能直接 win_destroy(run_windowed 阻塞中,
|
||||
* pump 持有窗口 id)——按 pe_window_host 协作契约 inject WM_CLOSE,
|
||||
* PE 自愿退出后由 launch_pe_app 收尾销毁。 */
|
||||
static void win_close_request(desktop_window *w) {
|
||||
if (g_pe_running && w->id == g_pe_win_id && g_pe_svc && g_pe_svc->inject_input) {
|
||||
g_pe_svc->inject_input(PE_WM_CLOSE, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
win_destroy(w);
|
||||
}
|
||||
|
||||
static int win_hit_close_btn(desktop_window *w, int mx, int my) {
|
||||
int bx = w->x + w->w - CLOSE_BTN_OFFSET;
|
||||
int by = w->y + (KATE_TITLEBAR_H - CLOSE_BTN_SIZE) / 2;
|
||||
return mx >= bx && mx < bx + CLOSE_BTN_SIZE &&
|
||||
my >= by && my < by + CLOSE_BTN_SIZE;
|
||||
}
|
||||
|
||||
static int ws_win_count(int ws) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < g_win_count; i++)
|
||||
if (g_windows[i].ws == ws && g_windows[i].visible) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 窗口装饰绘制(Winux-Kate 风格)
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_window_frame(desktop_window *w) {
|
||||
if (!w->visible || w->minimized) return;
|
||||
u32 border = w->focused ? KS_BORDER_FOCUS : KS_BORDER;
|
||||
|
||||
if (w->focused) {
|
||||
du_kate_glow_border(&g_fb, w->x, w->y, w->w, w->h, KS_ACCENT);
|
||||
}
|
||||
|
||||
du_fill_rounded_rect(&g_fb, w->x, w->y, w->w, w->h, KS_BG_SECONDARY, 2);
|
||||
|
||||
du_fill_rect_gradient(&g_fb, w->x + 1, w->y + 1, w->w - 2, KATE_TITLEBAR_H - 1,
|
||||
0xFF0A3050u, KS_BG_SECONDARY);
|
||||
du_divider_h(&g_fb, w->x, w->y + KATE_TITLEBAR_H, w->w, KS_BORDER);
|
||||
|
||||
du_fill_rect(&g_fb, w->x + 6, w->y + KATE_TITLEBAR_H / 2 - 2, 4, 4, KS_ACCENT2);
|
||||
|
||||
du_draw_string(&g_fb, w->title,
|
||||
w->x + 14,
|
||||
w->y + (KATE_TITLEBAR_H - (i64)DU_ASCII_LINE_H) / 2 + 1,
|
||||
KS_ACCENT, 0xFF0A3050u, DU_ASCII_STEP);
|
||||
|
||||
int bx = w->x + w->w - CLOSE_BTN_OFFSET;
|
||||
int by = w->y + (KATE_TITLEBAR_H - CLOSE_BTN_SIZE) / 2;
|
||||
du_rect_outline(&g_fb, bx, by, CLOSE_BTN_SIZE, CLOSE_BTN_SIZE, KS_DANGER, 1);
|
||||
du_draw_string(&g_fb, "X", bx + 3, by - 1,
|
||||
KS_DANGER, KS_BG_SECONDARY, DU_ASCII_STEP);
|
||||
|
||||
du_rect_outline(&g_fb, w->x, w->y, w->w, w->h, border, 2);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* PS/2 鼠标处理
|
||||
* ============================================================ */
|
||||
|
||||
static int ps2_wait_write(void) {
|
||||
for (int t = 0; t < 200000; t++) {
|
||||
if (!(inb(0x64) & 0x02)) return 0;
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ps2_wait_read(void) {
|
||||
for (int t = 0; t < 200000; t++) {
|
||||
if (inb(0x64) & 0x01) return 0;
|
||||
__asm__ volatile("pause");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ps2_drain(void) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (!(inb(0x64) & 0x01)) break;
|
||||
inb(0x60);
|
||||
}
|
||||
}
|
||||
|
||||
static void ps2_mouse_init(void) {
|
||||
ps2_drain();
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0xA8);
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0x20);
|
||||
ps2_wait_read();
|
||||
u8 cfg = inb(0x60);
|
||||
|
||||
cfg &= ~0x20;
|
||||
cfg |= 0x02;
|
||||
cfg |= 0x40;
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0x60);
|
||||
ps2_wait_write();
|
||||
outb(0x60, cfg);
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0xD4);
|
||||
ps2_wait_write();
|
||||
outb(0x60, 0xFF);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (ps2_wait_read() != 0) break;
|
||||
inb(0x60);
|
||||
}
|
||||
|
||||
ps2_wait_write();
|
||||
outb(0x64, 0xD4);
|
||||
ps2_wait_write();
|
||||
outb(0x60, 0xF4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (ps2_wait_read() != 0) break;
|
||||
inb(0x60);
|
||||
}
|
||||
|
||||
ps2_drain();
|
||||
|
||||
g_mouse_idx = 0;
|
||||
g_mouse_has_pkt = 0;
|
||||
|
||||
slog("mouse init ok");
|
||||
}
|
||||
|
||||
static int ps2_mouse_poll(void) {
|
||||
u8 st = inb(0x64);
|
||||
if (!(st & 1)) return 0;
|
||||
if (!(st & 0x20)) return 0;
|
||||
u8 data = inb(0x60);
|
||||
|
||||
g_mouse_buf[g_mouse_idx++] = data;
|
||||
if (g_mouse_idx < 3) return 0;
|
||||
g_mouse_idx = 0;
|
||||
|
||||
if (!(g_mouse_buf[0] & 0x08)) return 0;
|
||||
|
||||
int dx = (int)(i8)g_mouse_buf[1];
|
||||
int dy = (int)(i8)g_mouse_buf[2];
|
||||
dy = -dy;
|
||||
|
||||
g_mouse_x += dx;
|
||||
g_mouse_y += dy;
|
||||
if (g_mouse_x < 0) g_mouse_x = 0;
|
||||
if (g_mouse_y < 0) g_mouse_y = 0;
|
||||
if (g_mouse_x >= (int)g_fb_w - CURSOR_SIZE) g_mouse_x = (int)g_fb_w - CURSOR_SIZE;
|
||||
if (g_mouse_y >= (int)g_fb_h - CURSOR_SIZE) g_mouse_y = (int)g_fb_h - CURSOR_SIZE;
|
||||
|
||||
int old_btn = g_mouse_btn;
|
||||
g_mouse_btn = g_mouse_buf[0] & 0x03;
|
||||
if ((g_mouse_btn & 1) && !(old_btn & 1)) g_left_pressed = 1;
|
||||
if (!(g_mouse_btn & 1) && (old_btn & 1)) g_left_released = 1;
|
||||
if ((g_mouse_btn & 2) && !(old_btn & 2)) g_right_pressed = 1;
|
||||
if (dx == 0 && dy == 0 && g_mouse_btn == old_btn) return 0;
|
||||
|
||||
/* VSCode Phase 2: IDE attached 时把相对位移 + 按钮状态镜像到 Linux guest。
|
||||
* dy 已在上方取反为屏幕坐标系(正=下),与 Linux REL_Y 约定一致;
|
||||
* input_forward_mouse 内部与上次状态比较,仅产生变化按钮事件 + SYN。
|
||||
* 即使宿主光标已抵边界被钳位,原始 dx/dy 仍转发,guest 光标自由移动。 */
|
||||
if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_mouse) {
|
||||
g_lxc_svc->input_forward_mouse(dx, dy, (u8)g_mouse_btn);
|
||||
}
|
||||
|
||||
g_mouse_has_pkt = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 键盘扫描码 → ASCII
|
||||
* ============================================================ */
|
||||
|
||||
static char scan_to_ascii(u8 sc, int shift) {
|
||||
static const char normal[58] = {
|
||||
0, 27, '1','2','3','4','5','6','7','8','9','0','-','=', 8, '\t',
|
||||
'q','w','e','r','t','y','u','i','o','p','[',']','\n',0,
|
||||
'a','s','d','f','g','h','j','k','l',';','\'', '`',0,'\\',
|
||||
'z','x','c','v','b','n','m',',','.','/',0,'*',0,' '
|
||||
};
|
||||
static const char shifted[58] = {
|
||||
0, 27, '!','@','#','$','%','^','&','*','(',')','_','+', 8, '\t',
|
||||
'Q','W','E','R','T','Y','U','I','O','P','{','}','\n',0,
|
||||
'A','S','D','F','G','H','J','K','L',':','"','~',0,'|',
|
||||
'Z','X','C','V','B','N','M','<','>','?',0,'*',0,' '
|
||||
};
|
||||
if (sc >= 58) return 0;
|
||||
return shift ? shifted[sc] : normal[sc];
|
||||
}
|
||||
|
||||
/* VSCode Phase 2: PS/2 set-1 扫描码 → Linux keycode(linux/input-event-codes.h)。
|
||||
* base : 已去除 0x80 release 位的基础扫描码(0x01..0x39 主键区 + 少数扩展)
|
||||
* e0 : 上一字节为 0xE0 前缀时置 1,选取扩展键变体
|
||||
* 返回 0 表示该扫描码无映射(调用方应丢弃)。
|
||||
*
|
||||
* 巧合:主键区 0x01..0x39 的 Linux keycode 与扫描码数值完全一致
|
||||
* (IBM XT 布局历史遗留),故直接透传;仅 E0 扩展键需单独查表。 */
|
||||
static u16 scancode_to_linux_keycode(u8 base, int e0) {
|
||||
if (e0) {
|
||||
switch (base) {
|
||||
case 0x1D: return 97; /* KEY_RIGHTCTRL */
|
||||
case 0x38: return 100; /* KEY_RIGHTALT */
|
||||
case 0x48: return 103; /* KEY_UP */
|
||||
case 0x4B: return 105; /* KEY_LEFT */
|
||||
case 0x4D: return 106; /* KEY_RIGHT */
|
||||
case 0x50: return 108; /* KEY_DOWN */
|
||||
case 0x52: return 110; /* KEY_INSERT */
|
||||
case 0x53: return 111; /* KEY_DELETE */
|
||||
case 0x47: return 102; /* KEY_HOME */
|
||||
case 0x4F: return 107; /* KEY_END */
|
||||
case 0x49: return 104; /* KEY_PAGEUP */
|
||||
case 0x51: return 109; /* KEY_PAGEDOWN */
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
/* 主键区:0x01..0x39 透传,超出范围无映射 */
|
||||
if (base >= 0x01 && base <= 0x39) return (u16)base;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 终端 (Bash) — 面板/窗口共用核心
|
||||
* ============================================================ */
|
||||
|
||||
#define TERM_MAX_COLS 80
|
||||
#define TERM_MAX_ROWS 40
|
||||
#define TERM_MAX_CHARS (TERM_MAX_COLS * TERM_MAX_ROWS)
|
||||
|
||||
typedef struct {
|
||||
u8 ch[TERM_MAX_CHARS];
|
||||
u32 fg[TERM_MAX_CHARS];
|
||||
int cols, rows;
|
||||
int cur_col, cur_row;
|
||||
char input_buf[256];
|
||||
int input_len, input_cursor;
|
||||
int prompt_len;
|
||||
/* 性能优化:脏行标记(增量渲染) */
|
||||
u8 dirty_rows[TERM_MAX_ROWS]; /* 标记哪些行需要重绘 */
|
||||
int any_dirty; /* 是否有任何脏行 */
|
||||
} bash_state;
|
||||
|
||||
static const char *BASH_PROMPT = "deshab# ";
|
||||
|
||||
static void bash_reset(bash_state *s, int w, int h) {
|
||||
for (int i = 0; i < TERM_MAX_CHARS; i++) {
|
||||
s->ch[i] = ' '; s->fg[i] = KS_TEXT_PRIMARY;
|
||||
}
|
||||
s->cols = (w - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_STEP;
|
||||
s->rows = (h - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_LINE_H;
|
||||
if (s->cols > TERM_MAX_COLS) s->cols = TERM_MAX_COLS;
|
||||
if (s->rows > TERM_MAX_ROWS) s->rows = TERM_MAX_ROWS;
|
||||
if (s->cols < 10) s->cols = 10;
|
||||
if (s->rows < 3) s->rows = 3;
|
||||
s->cur_col = 0; s->cur_row = 0;
|
||||
s->input_len = 0; s->input_cursor = 0;
|
||||
s->prompt_len = kstrlen(BASH_PROMPT);
|
||||
/* 初始化脏行标记:所有行标记为脏,确保首次渲染 */
|
||||
for (int i = 0; i < TERM_MAX_ROWS; i++) s->dirty_rows[i] = 1;
|
||||
s->any_dirty = 1;
|
||||
}
|
||||
|
||||
static void bash_putc(bash_state *s, char c, u32 color) {
|
||||
if (c == '\n') {
|
||||
s->dirty_rows[s->cur_row] = 1; /* 标记当前行为脏 */
|
||||
s->any_dirty = 1;
|
||||
s->cur_col = 0; s->cur_row++;
|
||||
return;
|
||||
}
|
||||
if (s->cur_row >= s->rows) {
|
||||
/* 滚动:所有行都需要重绘 */
|
||||
for (int r = 1; r < s->rows; r++) {
|
||||
for (int c2 = 0; c2 < s->cols; c2++) {
|
||||
s->ch[(r-1)*TERM_MAX_COLS+c2] = s->ch[r*TERM_MAX_COLS+c2];
|
||||
s->fg[(r-1)*TERM_MAX_COLS+c2] = s->fg[r*TERM_MAX_COLS+c2];
|
||||
}
|
||||
}
|
||||
for (int c2 = 0; c2 < s->cols; c2++) {
|
||||
s->ch[(s->rows-1)*TERM_MAX_COLS+c2] = ' ';
|
||||
}
|
||||
s->cur_row = s->rows - 1;
|
||||
/* 标记所有行为脏 */
|
||||
for (int r = 0; r < s->rows; r++) s->dirty_rows[r] = 1;
|
||||
s->any_dirty = 1;
|
||||
}
|
||||
if (s->cur_col >= s->cols) { s->cur_col = 0; s->cur_row++; }
|
||||
int idx = s->cur_row * TERM_MAX_COLS + s->cur_col;
|
||||
s->ch[idx] = (u8)c; s->fg[idx] = color;
|
||||
s->cur_col++;
|
||||
/* 标记当前行为脏 */
|
||||
s->dirty_rows[s->cur_row] = 1;
|
||||
s->any_dirty = 1;
|
||||
}
|
||||
|
||||
static void bash_puts(bash_state *s, const char *str, u32 color) {
|
||||
while (*str) bash_putc(s, *str++, color);
|
||||
}
|
||||
|
||||
static void bash_execute(bash_state *s, const char *cmd) {
|
||||
while (*cmd == ' ') cmd++;
|
||||
bash_puts(s, BASH_PROMPT, KS_ACCENT2);
|
||||
bash_puts(s, cmd, KS_TEXT_PRIMARY);
|
||||
bash_putc(s, '\n', 0);
|
||||
|
||||
if (*cmd == 0) return;
|
||||
if (cmd[0]=='h'&&cmd[1]=='e'&&cmd[2]=='l'&&cmd[3]=='p') {
|
||||
bash_puts(s, " help clear echo version\n", KS_ACCENT2);
|
||||
bash_puts(s, " uname about reboot halt\n", KS_ACCENT2);
|
||||
bash_puts(s, " pwd whoami id\n", KS_ACCENT2);
|
||||
} else if (cmd[0]=='c'&&cmd[1]=='l'&&cmd[2]=='e'&&cmd[3]=='a'&&cmd[4]=='r') {
|
||||
for (int i = 0; i < TERM_MAX_CHARS; i++) { s->ch[i] = ' '; }
|
||||
s->cur_col = 0; s->cur_row = 0;
|
||||
} else if (cmd[0]=='v'&&cmd[1]=='e'&&cmd[2]=='r') {
|
||||
bash_puts(s, "Deshab OS v0.1.0 (Winux-Kate shell)\n", KS_ACCENT2);
|
||||
} else if (cmd[0]=='u'&&cmd[1]=='n'&&cmd[2]=='a') {
|
||||
bash_puts(s, "Deshab\n", KS_TEXT_PRIMARY);
|
||||
} else if (cmd[0]=='a'&&cmd[1]=='b'&&cmd[2]=='o'&&cmd[3]=='u'&&cmd[4]=='t') {
|
||||
bash_puts(s, "Deshab OS - SAS-R0 Kernel\n", KS_ACCENT);
|
||||
bash_puts(s, "Winux-Kate UI Structure\n", KS_ACCENT2);
|
||||
} else if (cmd[0]=='e'&&cmd[1]=='c'&&cmd[2]=='h'&&cmd[3]=='o') {
|
||||
const char *arg = cmd + 4;
|
||||
while (*arg == ' ') arg++;
|
||||
bash_puts(s, arg, KS_TEXT_PRIMARY);
|
||||
bash_putc(s, '\n', 0);
|
||||
} else if (cmd[0]=='p'&&cmd[1]=='w'&&cmd[2]=='d') {
|
||||
bash_puts(s, "/\n", KS_TEXT_PRIMARY);
|
||||
} else if (cmd[0]=='w'&&cmd[1]=='h'&&cmd[2]=='o') {
|
||||
bash_puts(s, "root\n", KS_TEXT_PRIMARY);
|
||||
} else if (cmd[0]=='i'&&cmd[1]=='d') {
|
||||
bash_puts(s, "uid=0(root) gid=0(root)\n", KS_TEXT_PRIMARY);
|
||||
} else if (cmd[0]=='r'&&cmd[1]=='e'&&cmd[2]=='b') {
|
||||
bash_puts(s, "Rebooting...\n", KS_ACCENT2);
|
||||
outb(0x64, 0xFE);
|
||||
for(;;) __asm__("hlt");
|
||||
} else if (cmd[0]=='h'&&cmd[1]=='a'&&cmd[2]=='l'&&cmd[3]=='t') {
|
||||
bash_puts(s, "Halted.\n", KS_ACCENT2);
|
||||
for(;;) __asm__("hlt");
|
||||
} else {
|
||||
bash_puts(s, "unknown: ", KS_DANGER);
|
||||
bash_puts(s, cmd, KS_DANGER);
|
||||
bash_putc(s, '\n', 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void bash_key(bash_state *s, u8 sc, int shift) {
|
||||
if (sc == 0x1C) {
|
||||
s->input_buf[s->input_len] = 0;
|
||||
bash_execute(s, s->input_buf);
|
||||
s->input_len = 0; s->input_cursor = 0;
|
||||
} else if (sc == 0x0E) {
|
||||
if (s->input_cursor > 0) {
|
||||
for (int i = s->input_cursor-1; i < s->input_len-1; i++)
|
||||
s->input_buf[i] = s->input_buf[i+1];
|
||||
s->input_len--; s->input_cursor--;
|
||||
}
|
||||
} else {
|
||||
char c = scan_to_ascii(sc, shift);
|
||||
if (c && c >= 32 && c <= 126 && s->input_len < 255) {
|
||||
for (int i = s->input_len; i > s->input_cursor; i--)
|
||||
s->input_buf[i] = s->input_buf[i-1];
|
||||
s->input_buf[s->input_cursor++] = c;
|
||||
s->input_len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 绘制终端到指定矩形区域(面板或窗口客户区)
|
||||
* 性能优化:增量渲染,只绘制脏行 */
|
||||
static void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) {
|
||||
/* 快速退出:终端内容未修改,跳过绘制 */
|
||||
if (!s->any_dirty) return;
|
||||
|
||||
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY);
|
||||
|
||||
for (int r = 0; r < s->rows; r++) {
|
||||
/* 增量渲染:只绘制脏行 */
|
||||
if (!s->dirty_rows[r]) continue;
|
||||
|
||||
for (int c = 0; c < s->cols; c++) {
|
||||
int idx = r * TERM_MAX_COLS + c;
|
||||
if (s->ch[idx] == ' ') continue;
|
||||
int px = cx + (int)DU_SPACE_SM + c * (int)DU_ASCII_STEP;
|
||||
int py = cy + (int)DU_SPACE_SM + r * (int)DU_ASCII_LINE_H;
|
||||
if (px + (int)DU_ASCII_CELL_W > cx + cw) continue;
|
||||
if (py + (int)DU_ASCII_CELL_H > cy + ch) continue;
|
||||
du_draw_char(&g_fb, s->ch[idx], px, py, s->fg[idx], KS_BG_PRIMARY);
|
||||
}
|
||||
|
||||
/* 清除脏行标记 */
|
||||
s->dirty_rows[r] = 0;
|
||||
}
|
||||
|
||||
int input_y = cy + (int)DU_SPACE_SM + s->cur_row * (int)DU_ASCII_LINE_H;
|
||||
if (input_y + (int)DU_ASCII_CELL_H <= cy + ch) {
|
||||
du_draw_string(&g_fb, BASH_PROMPT,
|
||||
cx + (int)DU_SPACE_SM, input_y,
|
||||
KS_ACCENT2, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
du_draw_string(&g_fb, s->input_buf,
|
||||
cx + (int)DU_SPACE_SM + s->prompt_len * (int)DU_ASCII_STEP,
|
||||
input_y,
|
||||
KS_TEXT_PRIMARY, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
int cursor_x = cx + (int)DU_SPACE_SM + (s->prompt_len + s->input_cursor) * (int)DU_ASCII_STEP;
|
||||
du_fill_rect(&g_fb, cursor_x, input_y + (int)DU_ASCII_CELL_H - 3,
|
||||
(int)DU_ASCII_CELL_W, 2, KS_ACCENT);
|
||||
}
|
||||
|
||||
/* 清除全局脏标记 */
|
||||
s->any_dirty = 0;
|
||||
}
|
||||
|
||||
/* ---- 窗口适配 ---- */
|
||||
__attribute__((unused))
|
||||
static void *bash_on_create(app_ctx *ctx) {
|
||||
bash_state *s = (bash_state *)ADDR_TERMWIN;
|
||||
bash_reset(s, ctx->client_w, ctx->client_h);
|
||||
bash_puts(s, "Deshab Bash v0.1\nType help for commands\n\n", KS_TEXT_PRIMARY);
|
||||
return s;
|
||||
}
|
||||
__attribute__((unused))
|
||||
static void bash_on_event(void *state, app_ctx *ctx, desktop_event *ev) {
|
||||
if (ev->type != EV_KEY_DOWN) return;
|
||||
bash_key((bash_state *)state, ev->scancode, ev->shift);
|
||||
ctx->win->dirty = 1;
|
||||
}
|
||||
__attribute__((unused))
|
||||
static void bash_on_draw(void *state, app_ctx *ctx) {
|
||||
bash_draw_to((bash_state *)state, ctx->client_x, ctx->client_y,
|
||||
ctx->client_w, ctx->client_h);
|
||||
}
|
||||
__attribute__((unused))
|
||||
static void bash_on_destroy(void *state) { (void)state; }
|
||||
|
||||
/* ============================================================
|
||||
* 文本编辑器 (Editor) — 面板/IDE/窗口共用核心
|
||||
* ============================================================ */
|
||||
|
||||
#define EDITOR_BUF_SIZE 65536
|
||||
|
||||
typedef struct {
|
||||
char text[EDITOR_BUF_SIZE];
|
||||
int text_len;
|
||||
int cursor_pos;
|
||||
int scroll_y;
|
||||
int cursor_line;
|
||||
int cursor_col;
|
||||
int modified;
|
||||
int has_file; /* 关联了磁盘文件 */
|
||||
char name11[11]; /* FAT32 8.3 名 */
|
||||
char disp[13]; /* 显示名 */
|
||||
} editor_state;
|
||||
|
||||
static void editor_recalc_cursor(editor_state *s) {
|
||||
s->cursor_line = 0; s->cursor_col = 0;
|
||||
for (int i = 0; i < s->cursor_pos && i < s->text_len; i++) {
|
||||
if (s->text[i] == '\n') { s->cursor_line++; s->cursor_col = 0; }
|
||||
else s->cursor_col++;
|
||||
}
|
||||
}
|
||||
|
||||
static int editor_line_start(editor_state *s, int line) {
|
||||
int l = 0;
|
||||
for (int i = 0; i < s->text_len; i++) {
|
||||
if (l == line) return i;
|
||||
if (s->text[i] == '\n') l++;
|
||||
}
|
||||
return s->text_len;
|
||||
}
|
||||
|
||||
static void editor_reset(editor_state *s, const char *sample) {
|
||||
s->text_len = 0; s->cursor_pos = 0;
|
||||
s->scroll_y = 0; s->modified = 0;
|
||||
s->has_file = 0;
|
||||
s->disp[0] = 0;
|
||||
while (*sample && s->text_len < EDITOR_BUF_SIZE - 1) {
|
||||
s->text[s->text_len++] = *sample++;
|
||||
}
|
||||
s->cursor_pos = s->text_len;
|
||||
editor_recalc_cursor(s);
|
||||
}
|
||||
|
||||
static void editor_key(editor_state *s, u8 sc, int shift) {
|
||||
if (sc == 0x1C) {
|
||||
if (s->text_len < EDITOR_BUF_SIZE - 1) {
|
||||
for (int i = s->text_len; i > s->cursor_pos; i--)
|
||||
s->text[i] = s->text[i-1];
|
||||
s->text[s->cursor_pos] = '\n';
|
||||
s->text_len++; s->cursor_pos++;
|
||||
s->modified = 1;
|
||||
}
|
||||
} else if (sc == 0x0E) {
|
||||
if (s->cursor_pos > 0) {
|
||||
for (int i = s->cursor_pos - 1; i < s->text_len - 1; i++)
|
||||
s->text[i] = s->text[i+1];
|
||||
s->text_len--; s->cursor_pos--;
|
||||
s->modified = 1;
|
||||
}
|
||||
} else if (sc == 0x48) {
|
||||
if (s->cursor_line > 0) {
|
||||
int prev_start = editor_line_start(s, s->cursor_line - 1);
|
||||
int col = s->cursor_col;
|
||||
s->cursor_pos = prev_start;
|
||||
for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++)
|
||||
s->cursor_pos++;
|
||||
}
|
||||
} else if (sc == 0x50) {
|
||||
int next_start = editor_line_start(s, s->cursor_line + 1);
|
||||
if (next_start < s->text_len || s->cursor_line < 999) {
|
||||
int col = s->cursor_col;
|
||||
s->cursor_pos = next_start;
|
||||
for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++)
|
||||
s->cursor_pos++;
|
||||
}
|
||||
} else if (sc == 0x4B) {
|
||||
if (s->cursor_pos > 0) s->cursor_pos--;
|
||||
} else if (sc == 0x4D) {
|
||||
if (s->cursor_pos < s->text_len) s->cursor_pos++;
|
||||
} else if (sc == 0x47) {
|
||||
s->cursor_pos = editor_line_start(s, s->cursor_line);
|
||||
} else if (sc == 0x4F) {
|
||||
s->cursor_pos = editor_line_start(s, s->cursor_line);
|
||||
while (s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n')
|
||||
s->cursor_pos++;
|
||||
} else {
|
||||
char c = scan_to_ascii(sc, shift);
|
||||
if (c && c >= 32 && c <= 126 && s->text_len < EDITOR_BUF_SIZE - 1) {
|
||||
for (int i = s->text_len; i > s->cursor_pos; i--)
|
||||
s->text[i] = s->text[i-1];
|
||||
s->text[s->cursor_pos] = c;
|
||||
s->text_len++; s->cursor_pos++;
|
||||
s->modified = 1;
|
||||
}
|
||||
}
|
||||
editor_recalc_cursor(s);
|
||||
}
|
||||
|
||||
/* 绘制编辑器到指定矩形区域 */
|
||||
static void editor_draw_to(editor_state *s, int cx, int cy, int cw, int ch) {
|
||||
int line_num_w = 5 * (int)DU_ASCII_STEP + (int)DU_SPACE_SM;
|
||||
int text_area_x = cx + line_num_w;
|
||||
int text_area_w = cw - line_num_w;
|
||||
int visible_rows = (ch - 20) / (int)DU_ASCII_LINE_H;
|
||||
int visible_cols = text_area_w / (int)DU_ASCII_STEP;
|
||||
if (visible_rows < 1) visible_rows = 1;
|
||||
if (visible_cols < 4) visible_cols = 4;
|
||||
|
||||
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY);
|
||||
|
||||
if (s->cursor_line < s->scroll_y) s->scroll_y = s->cursor_line;
|
||||
if (s->cursor_line >= s->scroll_y + visible_rows) s->scroll_y = s->cursor_line - visible_rows + 1;
|
||||
|
||||
int line = 0;
|
||||
int pos = 0;
|
||||
for (int i = 0; i < s->text_len || line <= s->cursor_line; ) {
|
||||
if (line >= s->scroll_y + visible_rows) break;
|
||||
|
||||
if (line >= s->scroll_y) {
|
||||
int screen_y = cy + (line - s->scroll_y) * (int)DU_ASCII_LINE_H;
|
||||
|
||||
char ln[8];
|
||||
ln[0] = '0' + ((line+1)/100)%10;
|
||||
ln[1] = '0' + ((line+1)/10)%10;
|
||||
ln[2] = '0' + (line+1)%10;
|
||||
ln[3] = 0;
|
||||
du_draw_string(&g_fb, ln, cx + 2, screen_y,
|
||||
KS_TEXT_DIM, KS_BG_SECONDARY, DU_ASCII_STEP);
|
||||
|
||||
du_fill_rect(&g_fb, cx + line_num_w - 2, screen_y, 1,
|
||||
(int)DU_ASCII_CELL_H, KS_BORDER_DIM);
|
||||
|
||||
int col = 0;
|
||||
while (pos < s->text_len && s->text[pos] != '\n' && col < visible_cols) {
|
||||
du_draw_char(&g_fb, s->text[pos],
|
||||
text_area_x + col * (int)DU_ASCII_STEP,
|
||||
screen_y,
|
||||
KS_TEXT_PRIMARY, KS_BG_SECONDARY);
|
||||
col++; pos++;
|
||||
}
|
||||
if (pos < s->text_len && s->text[pos] == '\n') pos++;
|
||||
} else {
|
||||
while (pos < s->text_len && s->text[pos] != '\n') pos++;
|
||||
if (pos < s->text_len) pos++;
|
||||
}
|
||||
line++;
|
||||
if (pos >= s->text_len && line > s->cursor_line) break;
|
||||
}
|
||||
|
||||
/* 光标(青色竖条) */
|
||||
if (s->cursor_line >= s->scroll_y && s->cursor_line < s->scroll_y + visible_rows) {
|
||||
int cursor_y = cy + (s->cursor_line - s->scroll_y) * (int)DU_ASCII_LINE_H;
|
||||
int cursor_x = text_area_x + s->cursor_col * (int)DU_ASCII_STEP;
|
||||
du_fill_rect(&g_fb, cursor_x, cursor_y, 2, (int)DU_ASCII_CELL_H, KS_ACCENT);
|
||||
}
|
||||
|
||||
/* 底部迷你状态栏 */
|
||||
int status_y = cy + ch - 20;
|
||||
du_fill_rect(&g_fb, cx, status_y, cw, 20, KS_BG_TERTIARY);
|
||||
du_divider_h(&g_fb, cx, status_y, cw, KS_BORDER);
|
||||
char status[64];
|
||||
int sp = 0;
|
||||
const char *p1 = "L:"; while(*p1) status[sp++] = *p1++;
|
||||
status[sp++] = '0' + (s->cursor_line+1)/10%10;
|
||||
status[sp++] = '0' + (s->cursor_line+1)%10;
|
||||
const char *p2 = " C:"; while(*p2) status[sp++] = *p2++;
|
||||
status[sp++] = '0' + (s->cursor_col+1)/10%10;
|
||||
status[sp++] = '0' + (s->cursor_col+1)%10;
|
||||
if (s->modified) { const char *m = " *"; while(*m) status[sp++] = *m++; }
|
||||
if (s->has_file) {
|
||||
const char *m = " "; while(*m) status[sp++] = *m++;
|
||||
for (int i = 0; s->disp[i] && sp < 60; i++) status[sp++] = s->disp[i];
|
||||
}
|
||||
status[sp] = 0;
|
||||
du_draw_string(&g_fb, status, cx + 4, status_y + 1,
|
||||
KS_TEXT_DIM, KS_BG_TERTIARY, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* ---- 窗口适配 ---- */
|
||||
static void *editor_on_create(app_ctx *ctx) {
|
||||
editor_state *s = (editor_state *)ADDR_EDWIN;
|
||||
editor_reset(s, "# Welcome to Deshab Editor\n#\n\nStart typing here...\n");
|
||||
for (int i = 0; i < 47; i++) ctx->win->title[i] = 0;
|
||||
const char *t = "Editor - untitled";
|
||||
for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i];
|
||||
return s;
|
||||
}
|
||||
static void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev) {
|
||||
if (ev->type != EV_KEY_DOWN) return;
|
||||
editor_key((editor_state *)state, ev->scancode, ev->shift);
|
||||
ctx->win->dirty = 1;
|
||||
}
|
||||
static void editor_on_draw(void *state, app_ctx *ctx) {
|
||||
editor_draw_to((editor_state *)state, ctx->client_x, ctx->client_y,
|
||||
ctx->client_w, ctx->client_h);
|
||||
}
|
||||
static void editor_on_destroy(void *state) { (void)state; }
|
||||
|
||||
/* ============================================================
|
||||
* 计算器 (Calculator)
|
||||
* ============================================================ */
|
||||
|
||||
typedef struct {
|
||||
char display[32];
|
||||
int display_len;
|
||||
i64 accumulator;
|
||||
i64 current;
|
||||
int op;
|
||||
int new_number;
|
||||
} calc_state;
|
||||
|
||||
static void calc_update_display(calc_state *s) {
|
||||
s->display_len = 0;
|
||||
i64 val = s->current;
|
||||
if (val < 0) { s->display[s->display_len++] = '-'; val = -val; }
|
||||
if (val == 0) { s->display[s->display_len++] = '0'; }
|
||||
else {
|
||||
char tmp[20]; int tl = 0;
|
||||
while (val > 0) { tmp[tl++] = '0' + (int)(val % 10); val /= 10; }
|
||||
for (int i = tl-1; i >= 0; i--) s->display[s->display_len++] = tmp[i];
|
||||
}
|
||||
s->display[s->display_len] = 0;
|
||||
}
|
||||
|
||||
static void *calc_on_create(app_ctx *ctx) {
|
||||
calc_state *s = (calc_state *)ADDR_CALC;
|
||||
s->accumulator = 0; s->current = 0;
|
||||
s->op = 0; s->new_number = 1;
|
||||
calc_update_display(s);
|
||||
const char *t = "Calculator";
|
||||
for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i];
|
||||
return s;
|
||||
}
|
||||
|
||||
static void calc_on_event(void *state, app_ctx *ctx, desktop_event *ev) {
|
||||
calc_state *s = (calc_state *)state;
|
||||
if (ev->type != EV_KEY_DOWN) return;
|
||||
char c = scan_to_ascii(ev->scancode, ev->shift);
|
||||
if (c >= '0' && c <= '9') {
|
||||
if (s->new_number) { s->current = 0; s->new_number = 0; }
|
||||
s->current = s->current * 10 + (c - '0');
|
||||
} else if (c == '+') { s->accumulator = s->current; s->op = 1; s->new_number = 1; }
|
||||
else if (c == '-') { s->accumulator = s->current; s->op = 2; s->new_number = 1; }
|
||||
else if (c == '*') { s->accumulator = s->current; s->op = 3; s->new_number = 1; }
|
||||
else if (c == '/') { s->accumulator = s->current; s->op = 4; s->new_number = 1; }
|
||||
else if (c == '=' || c == '\n') {
|
||||
switch (s->op) {
|
||||
case 1: s->current = s->accumulator + s->current; break;
|
||||
case 2: s->current = s->accumulator - s->current; break;
|
||||
case 3: s->current = s->accumulator * s->current; break;
|
||||
case 4: s->current = (s->current != 0) ? s->accumulator / s->current : 0; break;
|
||||
}
|
||||
s->op = 0; s->new_number = 1;
|
||||
} else if (c == 'c' || c == 'C') {
|
||||
s->current = 0; s->accumulator = 0; s->op = 0; s->new_number = 1;
|
||||
}
|
||||
calc_update_display(s);
|
||||
ctx->win->dirty = 1;
|
||||
}
|
||||
|
||||
static void calc_on_draw(void *state, app_ctx *ctx) {
|
||||
calc_state *s = (calc_state *)state;
|
||||
int cx = ctx->client_x;
|
||||
int cy = ctx->client_y;
|
||||
int cw = ctx->client_w;
|
||||
int ch = ctx->client_h;
|
||||
|
||||
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_SECONDARY);
|
||||
|
||||
int disp_h = 40;
|
||||
du_fill_rounded_rect(&g_fb, cx + 8, cy + 8, cw - 16, disp_h,
|
||||
KS_BG_PRIMARY, DU_RADIUS_MD);
|
||||
du_rect_outline(&g_fb, cx + 8, cy + 8, cw - 16, disp_h,
|
||||
KS_ACCENT, DU_RADIUS_MD);
|
||||
int text_x = cx + cw - 16 - s->display_len * (int)DU_ASCII_STEP - 8;
|
||||
du_draw_string(&g_fb, s->display, text_x, cy + 16,
|
||||
KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
|
||||
const char *btn_labels[] = {
|
||||
"C", "+/-", "%", "/",
|
||||
"7", "8", "9", "*",
|
||||
"4", "5", "6", "-",
|
||||
"1", "2", "3", "+",
|
||||
"0", ".", "=", 0
|
||||
};
|
||||
int btn_w = (cw - 16 - 12) / 4;
|
||||
int btn_h = 32;
|
||||
int btn_y = cy + 8 + disp_h + 8;
|
||||
for (int i = 0; i < 19; i++) {
|
||||
int row = i / 4, col = i % 4;
|
||||
int bx = cx + 8 + col * (btn_w + 3);
|
||||
int by = btn_y + row * (btn_h + 3);
|
||||
if (by + btn_h > cy + ch - 4) break;
|
||||
u32 bg = (i < 4) ? KS_ACCENT_DIM :
|
||||
(i % 4 == 3) ? KS_ACCENT :
|
||||
(i == 18) ? KS_ACCENT2 :
|
||||
KS_BG_TERTIARY;
|
||||
u32 fg = ((i % 4 == 3) || (i == 18)) ? KS_TEXT_INVERT : KS_TEXT_PRIMARY;
|
||||
du_fill_rounded_rect(&g_fb, bx, by, btn_w, btn_h, bg, DU_RADIUS_SM);
|
||||
du_rect_outline(&g_fb, bx, by, btn_w, btn_h, KS_BORDER, DU_RADIUS_SM);
|
||||
du_draw_string(&g_fb, btn_labels[i],
|
||||
bx + (btn_w - 1*(int)DU_ASCII_STEP)/2,
|
||||
by + (btn_h - (int)DU_ASCII_CELL_H)/2,
|
||||
fg, bg, DU_ASCII_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_on_destroy(void *state) { (void)state; }
|
||||
|
||||
/* ============================================================
|
||||
* 应用注册
|
||||
* ============================================================ */
|
||||
|
||||
/* ---- VSCode Phase 5: LINUXAPP.CNF Linux 应用注册表 ----
|
||||
* FAT32 根目录 LINUXAPP.CNF(8.3: LINUXAPPCNF),每行一个 Linux guest 应用:
|
||||
* NAME|DISPLAY|/guest/path|args(args 可含空格,可省略)
|
||||
* '#' 开头为注释。解析结果存静态池,app_descriptor.linux_path 指入。 */
|
||||
#define LINUXAPP_MAX 4
|
||||
static char g_lapp_name[LINUXAPP_MAX][12];
|
||||
static char g_lapp_disp[LINUXAPP_MAX][16];
|
||||
static char g_lapp_path[LINUXAPP_MAX][96];
|
||||
static char g_lapp_args[LINUXAPP_MAX][96];
|
||||
static int g_vscode_app_id = -1; /* 第一个名为 "vscode" 的注册项 */
|
||||
|
||||
/* 字段拷贝:src[0..n) → dst(NUL 结尾),返回拷贝长度 */
|
||||
static int lapp_field_copy(char *dst, int dst_cap, const char *src, int n) {
|
||||
if (n >= dst_cap) n = dst_cap - 1;
|
||||
for (int i = 0; i < n; i++) dst[i] = src[i];
|
||||
dst[n] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static void register_linux_apps(void) {
|
||||
static void load_fuck_config(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; /* 已注册数量 */
|
||||
if (f32_read_path_lfn_w("system/deshab64/FUCK", &data, &size) != 0) {
|
||||
slog("[cfg] FUCK not found, defaults");
|
||||
return;
|
||||
}
|
||||
/* 逐行找 [desktop] 段的 fluent= 键 */
|
||||
int in_dsk = 0;
|
||||
u32 pos = 0;
|
||||
while (pos < size && li < LINUXAPP_MAX && g_app_count < MAX_APPS) {
|
||||
/* 取一行 */
|
||||
while (pos < size) {
|
||||
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 */
|
||||
u32 len = eol - pos;
|
||||
const char *line = (const char *)data + pos;
|
||||
int f0 = -1, f1 = -1, f2 = -1; /* 三个分隔符位置 */
|
||||
for (u32 i = 0; i < line_len; i++) {
|
||||
if (line[i] == '|') {
|
||||
if (f0 < 0) f0 = (int)i;
|
||||
else if (f1 < 0) f1 = (int)i;
|
||||
else if (f2 < 0) { f2 = (int)i; break; }
|
||||
}
|
||||
}
|
||||
if (f0 > 0 && f1 > f0) {
|
||||
int path_beg = f1 + 1;
|
||||
int path_end = (f2 > path_beg) ? f2 : (int)line_len;
|
||||
int args_beg = (f2 > path_beg) ? f2 + 1 : (int)line_len;
|
||||
|
||||
lapp_field_copy(g_lapp_name[li], 12, line, f0);
|
||||
lapp_field_copy(g_lapp_disp[li], 16, line + f0 + 1, f1 - f0 - 1);
|
||||
lapp_field_copy(g_lapp_path[li], 96, line + path_beg, path_end - path_beg);
|
||||
if (args_beg < (int)line_len)
|
||||
lapp_field_copy(g_lapp_args[li], 96, line + args_beg, (int)line_len - args_beg);
|
||||
else
|
||||
g_lapp_args[li][0] = 0;
|
||||
|
||||
if (g_lapp_path[li][0] == '/') {
|
||||
g_apps[g_app_count] = (app_descriptor){
|
||||
g_lapp_name[li], g_lapp_disp[li], 1024, 768,
|
||||
0, 0, 0, 0, 0,
|
||||
g_lapp_path[li], g_lapp_args[li]
|
||||
};
|
||||
/* 记录 VSCode app id(IDE attach 共享启动路径用)。
|
||||
* P7.4 匹配收紧:精确匹配 "VSCODE"(第 7 字符须为 NUL),
|
||||
* VSCODE2 等多实例条目不占用共享启动入口。 */
|
||||
if (g_vscode_app_id < 0 &&
|
||||
g_lapp_name[li][0] == 'V' && g_lapp_name[li][1] == 'S' &&
|
||||
g_lapp_name[li][2] == 'C' && g_lapp_name[li][3] == 'O' &&
|
||||
g_lapp_name[li][4] == 'D' && g_lapp_name[li][5] == 'E' &&
|
||||
g_lapp_name[li][6] == 0)
|
||||
g_vscode_app_id = g_app_count;
|
||||
g_app_count++;
|
||||
li++;
|
||||
}
|
||||
}
|
||||
pos = next;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- P5d: PEAPPS.CNF Windows PE 应用注册表 ----
|
||||
* FAT32 根目录 PEAPPS.CNF(8.3: PEAPPS CNF),每行一个 Windows PE 应用:
|
||||
* NAME|DISPLAY|path/file.exe|args('/' 分隔长路径,args 可含空格可省略)
|
||||
* '#' 开头为注释。解析结果存静态池,app_descriptor.pe_file 指入。
|
||||
* 仅适用于 GUI PE(消息循环驱动 pump);纯控制台 PE 不会触发消息空转,
|
||||
* 窗口模式下桌面将无响应直到进程退出——控制台程序请走 CMD。 */
|
||||
#define PEAPP_MAX 4
|
||||
static char g_papp_name[PEAPP_MAX][12];
|
||||
static char g_papp_disp[PEAPP_MAX][16];
|
||||
static char g_papp_file[PEAPP_MAX][96];
|
||||
static char g_papp_args[PEAPP_MAX][96];
|
||||
|
||||
static void pe_win_on_draw(void *state, app_ctx *ctx); /* 定义于 P5d 运行时段 */
|
||||
static void launch_pe_app(int app_id);
|
||||
|
||||
static void register_pe_apps(void) {
|
||||
u8 *data = 0;
|
||||
u32 size = 0;
|
||||
char n11[11];
|
||||
if (f32_name_to_83("PEAPPS.CNF", n11) != 0) return;
|
||||
if (f32_read_root_file(n11, &data, &size) != 0) return; /* 无配置文件:跳过 */
|
||||
|
||||
int pi = 0;
|
||||
u32 pos = 0;
|
||||
while (pos < size && pi < PEAPP_MAX && g_app_count < MAX_APPS) {
|
||||
u32 eol = pos;
|
||||
while (eol < size && data[eol] != '\n' && data[eol] != '\r') eol++;
|
||||
u32 line_len = eol - pos;
|
||||
u32 next = eol;
|
||||
while (next < size && (data[next] == '\n' || data[next] == '\r')) next++;
|
||||
|
||||
if (line_len == 0 || data[pos] == '#') { pos = next; continue; }
|
||||
|
||||
/* 按 '|' 切 4 段:NAME|DISPLAY|FILE|ARGS */
|
||||
const char *line = (const char *)data + pos;
|
||||
int f0 = -1, f1 = -1, f2 = -1;
|
||||
for (u32 i = 0; i < line_len; i++) {
|
||||
if (line[i] == '|') {
|
||||
if (f0 < 0) f0 = (int)i;
|
||||
else if (f1 < 0) f1 = (int)i;
|
||||
else if (f2 < 0) { f2 = (int)i; break; }
|
||||
}
|
||||
}
|
||||
if (f0 > 0 && f1 > f0) {
|
||||
int file_beg = f1 + 1;
|
||||
int file_end = (f2 > file_beg) ? f2 : (int)line_len;
|
||||
int args_beg = (f2 > file_beg) ? f2 + 1 : (int)line_len;
|
||||
|
||||
lapp_field_copy(g_papp_name[pi], 12, line, f0);
|
||||
lapp_field_copy(g_papp_disp[pi], 16, line + f0 + 1, f1 - f0 - 1);
|
||||
lapp_field_copy(g_papp_file[pi], 96, line + file_beg, file_end - file_beg);
|
||||
if (args_beg < (int)line_len)
|
||||
lapp_field_copy(g_papp_args[pi], 96, line + args_beg, (int)line_len - args_beg);
|
||||
else
|
||||
g_papp_args[pi][0] = 0;
|
||||
|
||||
if (g_papp_file[pi][0]) {
|
||||
g_apps[g_app_count] = (app_descriptor){
|
||||
g_papp_name[pi], g_papp_disp[pi], 720, 540,
|
||||
0, 0, pe_win_on_draw, 0, 0, 0, 0,
|
||||
g_papp_file[pi], g_papp_args[pi]
|
||||
};
|
||||
g_app_count++;
|
||||
pi++;
|
||||
}
|
||||
}
|
||||
pos = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void register_apps(void) {
|
||||
/* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/
|
||||
g_apps[0] = (app_descriptor){
|
||||
"shell", "Terminal", 720, 480,
|
||||
0, 0, 0, 0, "SHELL ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* 命令行2:cmd.elf(Windows 风格命令行 + PE/EXE 执行)*/
|
||||
g_apps[1] = (app_descriptor){
|
||||
"cmd", "CMD", 720, 480,
|
||||
0, 0, 0, 0, "CMD ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
g_apps[2] = (app_descriptor){
|
||||
"editor", "Editor", 600, 450,
|
||||
editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy, 0
|
||||
};
|
||||
g_app_count++;
|
||||
g_apps[3] = (app_descriptor){
|
||||
"fileman", "Files", 500, 400,
|
||||
0, 0, 0, 0, "FILEMAN ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
g_apps[4] = (app_descriptor){
|
||||
"calc", "Calculator", 280, 400,
|
||||
calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy, 0
|
||||
};
|
||||
g_app_count++;
|
||||
g_apps[5] = (app_descriptor){
|
||||
"browser", "Browser", 700, 500,
|
||||
0, 0, 0, 0, "BROWSER ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* ProEdit — 增强版文本编辑器(语法高亮、设置、目录浏览)*/
|
||||
g_apps[6] = (app_descriptor){
|
||||
"proedit", "ProEdit", 750, 550,
|
||||
0, 0, 0, 0, "PROEDIT ELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* Settings — 系统设置(内核 FUCK 配置 + 网络 NETCONF 配置)*/
|
||||
g_apps[7] = (app_descriptor){
|
||||
"settings", "Settings", 760, 540,
|
||||
0, 0, 0, 0, "SETTINGSELF"
|
||||
};
|
||||
g_app_count++;
|
||||
/* VSCode Phase 5: Linux guest 应用(VSCode 等),来自 LINUXAPP.CNF */
|
||||
register_linux_apps();
|
||||
/* P5d: Windows PE 应用,来自 PEAPPS.CNF */
|
||||
register_pe_apps();
|
||||
}
|
||||
|
||||
/* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px,
|
||||
* 左→右排列,超出画布宽度则换行(align-content: start)
|
||||
*
|
||||
* 注意:Terminal 和 CMD 已在第一页 Dashboard(TERM-01 / TERM-02),
|
||||
* 桌面不再重复显示其图标。
|
||||
*/
|
||||
static void setup_desktop_icons(void) {
|
||||
int pad = KATE_ICON_PAD; /* 24 */
|
||||
int x = pad;
|
||||
int y = KATE_TOPBAR_H + KATE_PAGE_PAD + pad;
|
||||
int max_x = (int)g_fb_w - pad; /* canvas 右缘 */
|
||||
for (int i = 0; i < g_app_count; i++) {
|
||||
if (g_icon_count >= MAX_ICONS) break;
|
||||
/* P7.4: Linux 应用(linux_path)也要出图标——否则 VSCODE 不可见 */
|
||||
if (!g_apps[i].on_create && !g_apps[i].elf_name &&
|
||||
!g_apps[i].linux_path && !g_apps[i].pe_file) continue;
|
||||
/* 跳过 Terminal 和 CMD(已在 Dashboard 第一页) */
|
||||
if (g_apps[i].name[0] == 's' && g_apps[i].name[1] == 'h' && g_apps[i].name[2] == 'e') continue;
|
||||
if (g_apps[i].name[0] == 'c' && g_apps[i].name[1] == 'm' && g_apps[i].name[2] == 'd') continue;
|
||||
if (x > pad && x + KATE_ICON_W > max_x) {
|
||||
x = pad;
|
||||
y += KATE_ICON_H + KATE_ICON_GAP;
|
||||
}
|
||||
g_icons[g_icon_count].app_id = i;
|
||||
g_icons[g_icon_count].x = x;
|
||||
g_icons[g_icon_count].y = y;
|
||||
g_icons[g_icon_count].label = g_apps[i].display_name;
|
||||
g_icons[g_icon_count].selected = 0;
|
||||
g_icon_count++;
|
||||
x += KATE_ICON_W + KATE_ICON_GAP;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 外部 ELF 加载
|
||||
* ============================================================ */
|
||||
|
||||
#define DT_ELFCLASS64 2
|
||||
#define DT_EM_X86_64 62
|
||||
#define DT_PT_LOAD 1
|
||||
#define DT_PT_DYNAMIC 2
|
||||
#define DT_DT_RELA 7
|
||||
#define DT_DT_RELASZ 8
|
||||
#define DT_R_X86_64_RELATIVE 8
|
||||
|
||||
typedef struct { u8 ident[16]; u16 type,machine; u32 ver; u64 entry,phoff,shoff; u32 flags; u16 ehsize,phentsize,phnum,shentsize,shnum,shstrndx; } dt_elf64_ehdr;
|
||||
typedef struct { u32 type,flags; u64 offset,vaddr,paddr,filesz,memsz,align; } dt_elf64_phdr;
|
||||
typedef struct { i64 tag; u64 val; } dt_elf64_dyn;
|
||||
typedef struct { u64 offset; u64 info; i64 addend; } dt_elf64_rela;
|
||||
|
||||
static u8 g_elf_image[1048576]; /* 1 MiB:settings.elf 内存映像 ~493KB(中文字体位图 BSS),256KB 不够 */
|
||||
|
||||
static int dt_load_elf(u8 *data, u32 data_size, void **entry_out) {
|
||||
const dt_elf64_ehdr *eh = (const dt_elf64_ehdr *)data;
|
||||
if (data_size < sizeof(dt_elf64_ehdr)) return -1;
|
||||
if (eh->ident[0] != 0x7F || eh->ident[4] != DT_ELFCLASS64) return -2;
|
||||
if (eh->machine != DT_EM_X86_64) return -3;
|
||||
u64 min_vaddr = ~0ULL, max_vaddr = 0;
|
||||
u32 lc = 0;
|
||||
for (u16 i = 0; i < eh->phnum; i++) {
|
||||
const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize);
|
||||
if (ph->type != DT_PT_LOAD) continue;
|
||||
if (ph->filesz > ph->memsz) return -4;
|
||||
if (ph->vaddr < min_vaddr) min_vaddr = ph->vaddr;
|
||||
if (ph->vaddr + ph->memsz > max_vaddr) max_vaddr = ph->vaddr + ph->memsz;
|
||||
lc++;
|
||||
}
|
||||
if (!lc || min_vaddr == ~0ULL) return -5;
|
||||
u64 isize = (max_vaddr - min_vaddr + 0xFFF) & ~0xFFFULL;
|
||||
if (isize > sizeof(g_elf_image)) return -6;
|
||||
u8 *image = g_elf_image;
|
||||
for (u64 i = 0; i < isize; i++) image[i] = 0;
|
||||
for (u16 i = 0; i < eh->phnum; i++) {
|
||||
const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize);
|
||||
if (ph->type != DT_PT_LOAD) continue;
|
||||
u64 off = ph->vaddr - min_vaddr;
|
||||
for (u64 j = 0; j < ph->filesz; j++)
|
||||
image[off + j] = data[ph->offset + j];
|
||||
}
|
||||
for (u16 i = 0; i < eh->phnum; i++) {
|
||||
const dt_elf64_phdr *ph = (const dt_elf64_phdr *)(data + eh->phoff + (u64)i * eh->phentsize);
|
||||
if (ph->type != DT_PT_DYNAMIC) continue;
|
||||
const dt_elf64_dyn *dyn = (const dt_elf64_dyn *)(data + ph->offset);
|
||||
u64 rela_off = 0, rela_sz = 0;
|
||||
for (u32 j = 0; j < ph->filesz / sizeof(dt_elf64_dyn); j++) {
|
||||
if (dyn[j].tag == DT_DT_RELA) rela_off = dyn[j].val;
|
||||
else if (dyn[j].tag == DT_DT_RELASZ) rela_sz = dyn[j].val;
|
||||
else if (dyn[j].tag == 0) break;
|
||||
}
|
||||
if (rela_off && rela_sz) {
|
||||
const dt_elf64_rela *r = (const dt_elf64_rela *)(data + rela_off);
|
||||
u32 rn = (u32)(rela_sz / sizeof(dt_elf64_rela));
|
||||
u64 load_bias = (u64)image - min_vaddr; /* 与 DSK loader 一致 */
|
||||
for (u32 j = 0; j < rn; j++) {
|
||||
u32 type = (u32)(r[j].info & 0xFFFFFFFF);
|
||||
if (type == DT_R_X86_64_RELATIVE) {
|
||||
/* 原 BUG: val = min_vaddr + addend,指针被写成低地址绝对值,
|
||||
* 导致 settings.elf 等模块内 .data 中的字符串指针全部失效 */
|
||||
if (r[j].offset < min_vaddr || r[j].offset >= min_vaddr + isize) continue;
|
||||
u64 target = r[j].offset - min_vaddr;
|
||||
u64 val = load_bias + (u64)r[j].addend;
|
||||
*(u64 *)(image + target) = val;
|
||||
if (len > 0 && line[0] != '#' && line[0] != ';') {
|
||||
if (len > 1 && line[0] == '[') {
|
||||
in_dsk = (len >= 9 && line[1] == 'd' && line[2] == 's' &&
|
||||
line[3] == 'k' && line[4] == ']');
|
||||
} else if (in_dsk && len >= 7 &&
|
||||
line[0]=='f' && line[1]=='l' && line[2]=='u' &&
|
||||
line[3]=='e' && line[4]=='n' && line[5]=='t' && line[6]=='=') {
|
||||
int v = (len > 7 && line[7] == '0') ? 0 : 1;
|
||||
if (g_fluent != v) {
|
||||
g_fluent = v;
|
||||
slog(v ? "[cfg] fluent=1 (Win11 desktop)" :
|
||||
"[cfg] fluent=0 (KATE fallback)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*entry_out = (void *)(image + (eh->entry - min_vaddr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void launch_external_elf(const char *name11) {
|
||||
u8 *data = 0; u32 size = 0;
|
||||
if (f32_read_root_file(name11, &data, &size) != 0) {
|
||||
slog("elf not found");
|
||||
return;
|
||||
}
|
||||
void *entry = 0;
|
||||
if (dt_load_elf(data, size, &entry) != 0) {
|
||||
slog("elf load failed");
|
||||
return;
|
||||
}
|
||||
slog("launching external elf");
|
||||
void (*elf_entry)(const dsk_boot_context *) = (void (*)(const dsk_boot_context *))entry;
|
||||
elf_entry(g_boot_ctx);
|
||||
g_cursor_saved = 0;
|
||||
g_cursor_old_x = -1; g_cursor_old_y = -1;
|
||||
files_refresh(); /* 外部应用可能改动了磁盘 */
|
||||
redraw_all();
|
||||
slog("external elf returned");
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 应用启动(单实例聚焦策略)
|
||||
* ============================================================ */
|
||||
|
||||
static void fill_app_ctx(app_ctx *ctx, desktop_window *win) {
|
||||
ctx->fb = &g_fb;
|
||||
ctx->win = win;
|
||||
ctx->client_x = win->x + BORDER_W;
|
||||
ctx->client_y = win->y + KATE_TITLEBAR_H;
|
||||
ctx->client_w = win->w - 2 * BORDER_W;
|
||||
ctx->client_h = win->h - KATE_TITLEBAR_H - BORDER_W;
|
||||
ctx->should_exit = 0;
|
||||
ctx->block_read = g_block_read;
|
||||
ctx->block_write = g_block_write;
|
||||
ctx->kernel_api = g_kernel_api;
|
||||
}
|
||||
|
||||
static void launch_linux_app(int app_id); /* VSCode Phase 5(定义于 IDE 区) */
|
||||
|
||||
static void launch_app(int app_id) {
|
||||
if (app_id < 0 || app_id >= g_app_count) return;
|
||||
app_descriptor *app = &g_apps[app_id];
|
||||
|
||||
/* VSCode Phase 5: Linux guest 应用走 exec_async 共享启动路径 */
|
||||
if (app->linux_path) {
|
||||
launch_linux_app(app_id);
|
||||
return;
|
||||
}
|
||||
|
||||
/* P5d: Windows PE 应用走 run_windowed 桌面嵌入路径 */
|
||||
if (app->pe_file) {
|
||||
launch_pe_app(app_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (app->elf_name) {
|
||||
launch_external_elf(app->elf_name);
|
||||
return;
|
||||
}
|
||||
if (!app->on_create) return;
|
||||
|
||||
/* 单实例:已有同类窗口则聚焦并迁移到当前工作区 */
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (g_windows[i].app_id == app_id && g_windows[i].visible) {
|
||||
g_windows[i].ws = g_ws_cur;
|
||||
win_focus(&g_windows[i]);
|
||||
slog("focus existing app window");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_TASKBAR_H;
|
||||
int wx = ICON_PAD + g_win_count * 30;
|
||||
int wy = top + 8 + g_win_count * 30;
|
||||
if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = ICON_PAD;
|
||||
if (wy + (int)app->default_h > bot - 20) wy = top + 8;
|
||||
|
||||
desktop_window *win = win_create(app_id, app->display_name,
|
||||
wx, wy, (int)app->default_w, (int)app->default_h);
|
||||
if (!win) return;
|
||||
|
||||
app_ctx ctx;
|
||||
fill_app_ctx(&ctx, win);
|
||||
|
||||
win->app_state = app->on_create(&ctx);
|
||||
win->dirty = 1;
|
||||
|
||||
/* 直接显示窗口(去掉打开动画以避免卡顿和闪屏) */
|
||||
|
||||
slog("app launched");
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* FILES 面板(FAT32 真实读写)
|
||||
* ============================================================ */
|
||||
|
||||
static int files_list_cb(const char *name, u32 size, u8 attr, void *ud) {
|
||||
(void)ud;
|
||||
if (attr & 0x10) return 0; /* 跳过子目录 */
|
||||
if (g_file_count >= MAX_FILES) return 1; /* 停止 */
|
||||
|
||||
/* 只显示 user 文件:跳过 .ELF 可执行文件和系统模块 */
|
||||
int nl = kstrlen(name);
|
||||
if (nl >= 4 && name[nl-4] == '.' && name[nl-3] == 'E' &&
|
||||
name[nl-2] == 'L' && name[nl-1] == 'F') return 0;
|
||||
/* FIRSTINI* (FirstInit.elf,Name83=FIRSTINIT → 显示名 FIRSTINI.T) */
|
||||
if (nl >= 8 && name[0] == 'F' && name[1] == 'I' && name[2] == 'R' &&
|
||||
name[3] == 'S' && name[4] == 'T' && name[5] == 'I' && name[6] == 'N' &&
|
||||
name[7] == 'I') return 0;
|
||||
|
||||
fentry *f = &g_files[g_file_count];
|
||||
f->size = size;
|
||||
kstrcpy(f->disp, name, 13);
|
||||
/* disp (NAME.EXT) → name11 (NAME EXT) */
|
||||
for (int i = 0; i < 11; i++) f->name11[i] = ' ';
|
||||
int i = 0, o = 0;
|
||||
while (name[i] && name[i] != '.' && o < 8) f->name11[o++] = name[i++];
|
||||
while (name[i] && name[i] != '.') i++;
|
||||
if (name[i] == '.') {
|
||||
i++;
|
||||
o = 8;
|
||||
while (name[i] && o < 11) f->name11[o++] = name[i++];
|
||||
}
|
||||
g_file_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void files_refresh(void) {
|
||||
g_file_count = 0;
|
||||
g_file_sel = -1;
|
||||
if (!g_block_read) { g_files_loaded = 0; return; }
|
||||
if (f32_list_root(files_list_cb, 0) == 0) g_files_loaded = 1;
|
||||
else g_files_loaded = 0;
|
||||
}
|
||||
|
||||
/* 双击打开文件 → 载入 DASHBOARD EDITOR */
|
||||
static void files_open(int idx) {
|
||||
if (idx < 0 || idx >= g_file_count) return;
|
||||
editor_state *ed = (editor_state *)ADDR_EDDASH;
|
||||
fentry *f = &g_files[idx];
|
||||
|
||||
u8 *data = 0; u32 size = 0;
|
||||
if (f32_read_root_file(f->name11, &data, &size) != 0) {
|
||||
slog("file open failed");
|
||||
return;
|
||||
}
|
||||
ed->text_len = 0;
|
||||
u32 cap = EDITOR_BUF_SIZE - 1;
|
||||
if (size > cap) size = cap;
|
||||
for (u32 i = 0; i < size; i++) {
|
||||
char c = (char)data[i];
|
||||
if ((u8)c >= 32 || c == '\n' || c == '\t')
|
||||
ed->text[ed->text_len++] = c;
|
||||
}
|
||||
for (int i = 0; i < 11; i++) ed->name11[i] = f->name11[i];
|
||||
kstrcpy(ed->disp, f->disp, 13);
|
||||
ed->has_file = 1;
|
||||
ed->modified = 0;
|
||||
ed->scroll_y = 0;
|
||||
ed->cursor_pos = 0;
|
||||
editor_recalc_cursor(ed);
|
||||
g_dash_focus = 2;
|
||||
slog("file loaded into editor");
|
||||
}
|
||||
|
||||
/* EDITOR SAVE 回写磁盘 */
|
||||
static void editor_save(void) {
|
||||
editor_state *ed = (editor_state *)ADDR_EDDASH;
|
||||
if (!ed->has_file) return;
|
||||
if (f32_write_root_file(ed->name11, (const u8 *)ed->text, (u32)ed->text_len) == 0) {
|
||||
ed->modified = 0;
|
||||
slog("file saved");
|
||||
} else {
|
||||
slog("file save failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 通用小绘制
|
||||
* ============================================================ */
|
||||
|
||||
/* 矩形内居中文字 */
|
||||
static void draw_centered(const char *s, int cx, int y, u32 fg, u32 bg) {
|
||||
int l = kstrlen(s);
|
||||
du_draw_string(&g_fb, s, cx - l * (int)DU_ASCII_STEP / 2, y, fg, bg, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* 虚线矩形(ide-host / custom host) */
|
||||
static void draw_dashed_rect(int x, int y, int w, int h, u32 color) {
|
||||
for (int i = x; i < x + w; i += 8) {
|
||||
du_fill_rect(&g_fb, i, y, 4, 1, color);
|
||||
du_fill_rect(&g_fb, i, y + h - 1, 4, 1, color);
|
||||
}
|
||||
for (int i = y; i < y + h; i += 8) {
|
||||
du_fill_rect(&g_fb, x, i, 1, 4, color);
|
||||
du_fill_rect(&g_fb, x + w - 1, i, 1, 4, color);
|
||||
pos = eol;
|
||||
while (pos < size && (data[pos] == '\n' || data[pos] == '\r')) pos++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2039,18 +82,12 @@ static void draw_dashed_rect(int x, int y, int w, int h, u32 color) {
|
||||
* ============================================================ */
|
||||
|
||||
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",
|
||||
"DESHAB DESKTOP v0.1.0",
|
||||
"DEAICUP STUDIO && LINEHU STUDIO",
|
||||
"https://deaicup.com",
|
||||
"https://github.com/Linehu-Studio",
|
||||
};
|
||||
#define BOOT_LINE_COUNT 10
|
||||
#define BOOT_LINE_COUNT 4
|
||||
|
||||
static void busy_delay(u32 cycles) {
|
||||
for (volatile u32 i = 0; i < cycles; i++) __asm__("pause");
|
||||
@@ -2058,14 +95,24 @@ static void busy_delay(u32 cycles) {
|
||||
|
||||
static void boot_screen(void) {
|
||||
int W = (int)g_fb_w, H = (int)g_fb_h;
|
||||
du_fill_bg_solid(&g_fb, KS_BG_PRIMARY);
|
||||
flip_buffer();
|
||||
|
||||
/* M2 修复:保留 DSK 已绘制的启动画面(渐变背景 + 居中开机 Logo +
|
||||
* dcp/xj 角标),打字机叠加在其上下空白区——logo 不跳位、不重复、
|
||||
* 不被覆盖。DSK logo 居中 157px:占据 H/2-78 .. H/2+78。 */
|
||||
{
|
||||
u64 pitch_u32 = g_fb_pitch / 4;
|
||||
for (u64 y = 0; y < g_fb_h; y++) {
|
||||
const u32 *src = (const u32 *)((const u8 *)g_real_fb + y * g_fb_pitch);
|
||||
u32 *dst = (u32 *)SPRITE_BUF_ADDR + y * pitch_u32;
|
||||
for (u64 x = 0; x < g_fb_w; x++) dst[x] = src[x];
|
||||
}
|
||||
}
|
||||
|
||||
int lw = 46 * (int)DU_ASCII_STEP;
|
||||
int lx = (W - lw) / 2;
|
||||
int ty = H / 2 - 180;
|
||||
int ty = H / 2 - 220;
|
||||
|
||||
/* boot-title */
|
||||
/* boot-title(logo 上方空白区) */
|
||||
const char *title = "DESHAB";
|
||||
int tl = kstrlen(title);
|
||||
for (int i = 0; i < tl; i++) {
|
||||
@@ -2082,8 +129,8 @@ static void boot_screen(void) {
|
||||
flip_buffer();
|
||||
busy_delay(2500000);
|
||||
|
||||
/* boot-lines 打字机;进度条固定在全部行下方(对应 Kate .boot-bar) */
|
||||
int ly = ty + 76;
|
||||
/* boot-lines 打字机:logo 下方空白区(H/2+110 起),进度条再下方 */
|
||||
int ly = H / 2 + 110;
|
||||
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++) {
|
||||
@@ -2113,1994 +160,6 @@ static void boot_screen(void) {
|
||||
busy_delay(10000000);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 顶栏(brand + WorkspaceSwitcher + PageSwitcher + PG 时钟)
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_topbar(void) {
|
||||
int W = (int)g_fb_w;
|
||||
du_fill_rect_gradient(&g_fb, 0, 0, W, KATE_TOPBAR_H,
|
||||
0xFF0A2840u, KS_BG_PRIMARY);
|
||||
du_divider_h(&g_fb, 0, KATE_TOPBAR_H, W, KS_BORDER);
|
||||
|
||||
int x = 12;
|
||||
int ty = (KATE_TOPBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1;
|
||||
|
||||
/* brand:方块点 + DESHAB + brand-credit(对应 Kate "由 Deaicup 工作室制作") */
|
||||
du_fill_rect(&g_fb, x, KATE_TOPBAR_H / 2 - 3, 6, 6, KS_ACCENT);
|
||||
du_draw_string(&g_fb, "DESHAB", x + 12, ty, KS_ACCENT, 0, DU_ASCII_STEP + 2);
|
||||
x += 12 + 6 * ((int)DU_ASCII_STEP + 2) + 10;
|
||||
du_draw_string(&g_fb, "DEAICUP STUDIO", x, ty + 2, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
x += 14 * (int)DU_ASCII_STEP + 14;
|
||||
|
||||
/* 分隔线 + 任务视图按钮(三横线图标) */
|
||||
du_divider_v(&g_fb, x - 6, 6, KATE_TOPBAR_H - 12, KS_BORDER);
|
||||
g_tvbtn_rect[0] = x; g_tvbtn_rect[1] = 5;
|
||||
g_tvbtn_rect[2] = 28; g_tvbtn_rect[3] = 24;
|
||||
{
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + 28 &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (hover) du_fill_rect(&g_fb, x, 5, 28, 24, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, 5, 28, 24, KS_BORDER, 1);
|
||||
du_fill_rect(&g_fb, x + 6, 11, 16, 2, KS_ACCENT);
|
||||
du_fill_rect(&g_fb, x + 6, 16, 16, 2, KS_ACCENT);
|
||||
du_fill_rect(&g_fb, x + 6, 21, 16, 2, KS_ACCENT);
|
||||
}
|
||||
x += 34;
|
||||
|
||||
/* 工作区按钮 "name|n" */
|
||||
for (int i = 0; i < g_ws_count; i++) {
|
||||
char label[24];
|
||||
kstrcpy(label, g_ws[i].name, 18);
|
||||
kstrcat(label, "|", 24);
|
||||
int cnt = ws_win_count(i);
|
||||
char cn[4];
|
||||
cn[0] = '0' + (char)(cnt % 10); cn[1] = 0;
|
||||
kstrcat(label, cn, 24);
|
||||
int bw = kstrlen(label) * (int)DU_ASCII_STEP + 16;
|
||||
int active = (i == g_ws_cur);
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (active) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT2);
|
||||
du_kate_glow_border(&g_fb, x, 5, bw, 24, KS_ACCENT2);
|
||||
} else if (hover) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM);
|
||||
}
|
||||
du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT2 : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, label, x + 8, ty,
|
||||
active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
g_ws_rect[i][0] = x; g_ws_rect[i][1] = 5;
|
||||
g_ws_rect[i][2] = bw; g_ws_rect[i][3] = 24;
|
||||
x += bw + 6;
|
||||
}
|
||||
|
||||
/* ws-add "+" */
|
||||
g_wsadd_rect[0] = x; g_wsadd_rect[1] = 5;
|
||||
g_wsadd_rect[2] = 26; g_wsadd_rect[3] = 24;
|
||||
{
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + 26 &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (hover) du_fill_rect(&g_fb, x, 5, 26, 24, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, 5, 26, 24, KS_ACCENT2, 1);
|
||||
du_draw_string(&g_fb, "+", x + 7, ty - 1, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
}
|
||||
x += 32;
|
||||
|
||||
/* 分隔线 */
|
||||
du_divider_v(&g_fb, x - 4, 6, KATE_TOPBAR_H - 12, KS_BORDER);
|
||||
x += 4;
|
||||
|
||||
/* 页面切换:精简按钮文字 */
|
||||
static const char *pg_names[3] = { "DASH", "IDE", "DESK" };
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int bw = kstrlen(pg_names[i]) * (int)DU_ASCII_STEP + 24;
|
||||
int active = (g_page == i + 1);
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (active) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT);
|
||||
du_kate_glow_border(&g_fb, x, 5, bw, 24, KS_ACCENT);
|
||||
} else if (hover) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM);
|
||||
}
|
||||
du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, pg_names[i], x + 12, ty,
|
||||
active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
g_pg_rect[i][0] = x; g_pg_rect[i][1] = 5;
|
||||
g_pg_rect[i][2] = bw; g_pg_rect[i][3] = 24;
|
||||
x += bw + 8;
|
||||
}
|
||||
/* 自定义页 */
|
||||
for (int i = 0; i < g_cpage_count; i++) {
|
||||
char label[20];
|
||||
label[0] = '0' + (char)g_cpages[i].id;
|
||||
label[1] = '.'; label[2] = 0;
|
||||
kstrcat(label, g_cpages[i].name, 20);
|
||||
int bw = kstrlen(label) * (int)DU_ASCII_STEP + 28;
|
||||
int active = (g_page == g_cpages[i].id);
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (active) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT);
|
||||
} else if (hover) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM);
|
||||
}
|
||||
du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, label, x + 6, ty,
|
||||
active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_DANGER, 0, DU_ASCII_STEP);
|
||||
g_pg_rect[3 + i][0] = x; g_pg_rect[3 + i][1] = 5;
|
||||
g_pg_rect[3 + i][2] = bw; g_pg_rect[3 + i][3] = 24;
|
||||
g_pg_rm_rect[3 + i][0] = x + bw - 20; g_pg_rm_rect[3 + i][1] = 5;
|
||||
g_pg_rm_rect[3 + i][2] = 20; g_pg_rm_rect[3 + i][3] = 24;
|
||||
x += bw + 6;
|
||||
}
|
||||
/* pg-add "+" */
|
||||
if (g_cpage_count < MAX_CPAGES) {
|
||||
g_pgadd_rect[0] = x; g_pgadd_rect[1] = 5;
|
||||
g_pgadd_rect[2] = 26; g_pgadd_rect[3] = 24;
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + 26 &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (hover) du_fill_rect(&g_fb, x, 5, 26, 24, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, 5, 26, 24, KS_ACCENT2, 1);
|
||||
du_draw_string(&g_fb, "+", x + 7, ty - 1, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
} else {
|
||||
g_pgadd_rect[2] = 0;
|
||||
}
|
||||
|
||||
/* 右侧:credit-top + PG 时钟(对应 Kate "© Deaicup Studio" + "PG n") */
|
||||
char pgclk[8];
|
||||
pgclk[0] = 'P'; pgclk[1] = 'G'; pgclk[2] = ' ';
|
||||
pgclk[3] = '0' + (char)(g_page % 10); pgclk[4] = 0;
|
||||
const char *credit = "(C) DEAICUP STUDIO";
|
||||
int cl = kstrlen(credit) + 1 + kstrlen(pgclk);
|
||||
du_draw_string(&g_fb, credit, W - 12 - cl * (int)DU_ASCII_STEP, ty,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
du_draw_string(&g_fb, pgclk, W - 12 - kstrlen(pgclk) * (int)DU_ASCII_STEP, ty,
|
||||
KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 滑块(VOL / BRIGHT)
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_slider(int x, int y, int w, int val, int rect_out[4]) {
|
||||
du_fill_rect(&g_fb, x, y + 5, w, 3, KS_BG_TERTIARY);
|
||||
du_rect_outline(&g_fb, x, y + 4, w, 5, KS_BORDER, 1);
|
||||
int fw = w * val / 100;
|
||||
if (fw > 0) du_fill_rect(&g_fb, x, y + 5, fw, 3, KS_ACCENT);
|
||||
int kx = x + fw - 3;
|
||||
if (kx < x) kx = x;
|
||||
if (kx > x + w - 6) kx = x + w - 6;
|
||||
du_fill_rect(&g_fb, kx, y, 6, 13, KS_ACCENT);
|
||||
du_rect_outline(&g_fb, kx, y, 6, 13, KS_ACCENT, 1);
|
||||
rect_out[0] = x; rect_out[1] = y - 2;
|
||||
rect_out[2] = w; rect_out[3] = 17;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 状态栏(DASHBOARD 底部 32px)
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_statusbar_full(void) {
|
||||
int W = (int)g_fb_w;
|
||||
int y = (int)g_fb_h - KATE_STATUSBAR_H;
|
||||
du_fill_rect(&g_fb, 0, y, W, KATE_STATUSBAR_H, 0xFF051828u);
|
||||
du_divider_h(&g_fb, 0, y, W, KS_BORDER);
|
||||
|
||||
int x = 12;
|
||||
int ty = y + (KATE_STATUSBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1;
|
||||
|
||||
/* 时钟(日期 · 秒级时间,accent2) */
|
||||
int yy, mo, dd, hh, mi, ss;
|
||||
rtc_read(&yy, &mo, &dd, &hh, &mi, &ss);
|
||||
char clk[32];
|
||||
int cp2 = 0;
|
||||
clk[cp2++] = '2'; clk[cp2++] = '0';
|
||||
clk[cp2++] = '0' + (char)(yy / 10); clk[cp2++] = '0' + (char)(yy % 10);
|
||||
clk[cp2++] = '-';
|
||||
clk[cp2++] = '0' + (char)(mo / 10); clk[cp2++] = '0' + (char)(mo % 10);
|
||||
clk[cp2++] = '-';
|
||||
clk[cp2++] = '0' + (char)(dd / 10); clk[cp2++] = '0' + (char)(dd % 10);
|
||||
clk[cp2++] = ' '; clk[cp2++] = '.'; clk[cp2++] = ' ';
|
||||
char hms[9];
|
||||
fmt_hms(hms, hh * 3600 + mi * 60 + ss);
|
||||
for (int i = 0; hms[i]; i++) clk[cp2++] = hms[i];
|
||||
clk[cp2] = 0;
|
||||
du_draw_string(&g_fb, clk, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
x += cp2 * (int)DU_ASCII_STEP + 18;
|
||||
|
||||
/* UP 计时 */
|
||||
{
|
||||
int up = hh * 3600 + mi * 60 + ss - g_rtc_boot_sec;
|
||||
if (up < 0) up += 86400;
|
||||
char upb[16];
|
||||
kstrcpy(upb, "UP ", 16);
|
||||
char uh[9];
|
||||
fmt_hms(uh, up);
|
||||
kstrcat(upb, uh, 16);
|
||||
du_draw_string(&g_fb, upb, x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
x += kstrlen(upb) * (int)DU_ASCII_STEP + 18;
|
||||
}
|
||||
|
||||
/* VOL 滑块 */
|
||||
du_draw_string(&g_fb, "VOL", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
x += 3 * (int)DU_ASCII_STEP + 6;
|
||||
draw_slider(x, y + 9, 90, g_vol, g_vol_rect);
|
||||
x += 90 + 8;
|
||||
{
|
||||
char vb[6];
|
||||
vb[0] = '0' + (char)(g_vol / 100 % 10);
|
||||
vb[1] = '0' + (char)(g_vol / 10 % 10);
|
||||
vb[2] = '0' + (char)(g_vol % 10);
|
||||
vb[3] = '%'; vb[4] = 0;
|
||||
const char *vp = (g_vol >= 100) ? vb : (g_vol >= 10 ? vb + 1 : vb + 2);
|
||||
du_draw_string(&g_fb, vp, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
x += 4 * (int)DU_ASCII_STEP + 14;
|
||||
}
|
||||
|
||||
/* BRIGHT 滑块 */
|
||||
du_draw_string(&g_fb, "BRIGHT", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
x += 6 * (int)DU_ASCII_STEP + 6;
|
||||
draw_slider(x, y + 9, 90, g_bright, g_bri_rect);
|
||||
x += 90 + 8;
|
||||
{
|
||||
char vb[6];
|
||||
vb[0] = '0' + (char)(g_bright / 100 % 10);
|
||||
vb[1] = '0' + (char)(g_bright / 10 % 10);
|
||||
vb[2] = '0' + (char)(g_bright % 10);
|
||||
vb[3] = '%'; vb[4] = 0;
|
||||
const char *vp = (g_bright >= 100) ? vb : (g_bright >= 10 ? vb + 1 : vb + 2);
|
||||
du_draw_string(&g_fb, vp, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
x += 4 * (int)DU_ASCII_STEP + 14;
|
||||
}
|
||||
|
||||
/* BT / WIFI */
|
||||
du_draw_string(&g_fb, "BT OFF", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
x += 6 * (int)DU_ASCII_STEP + 14;
|
||||
du_draw_string(&g_fb, "WIFI --", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* 右侧 hint */
|
||||
const char *hint = "Ctrl+Tab PAGE";
|
||||
du_draw_string(&g_fb, hint, W - 12 - kstrlen(hint) * (int)DU_ASCII_STEP, ty,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 页面绘制
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_desktop_icons(void) {
|
||||
/* desktop-empty(Kate:无快捷方式提示) */
|
||||
if (g_icon_count == 0) {
|
||||
du_draw_string(&g_fb, "NO SHORTCUTS ON DESKTOP",
|
||||
KATE_ICON_PAD, KATE_TOPBAR_H + KATE_PAGE_PAD + KATE_ICON_PAD + 16,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < g_icon_count; i++) {
|
||||
desktop_icon *ic = &g_icons[i];
|
||||
du_kate_desktop_icon(&g_fb, ic->x, ic->y, ic->label, ic->selected);
|
||||
int ix = ic->x + (KATE_ICON_W - KATE_ICON_IMG) / 2;
|
||||
int iy = ic->y + 10;
|
||||
draw_app_icon(&g_fb, ic->app_id, ix, iy);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 1·DASHBOARD ---- */
|
||||
static void dash_layout(void) {
|
||||
int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_STATUSBAR_H;
|
||||
int pad = 8, gap = 8;
|
||||
int avail_w = (int)g_fb_w - 2 * pad;
|
||||
int avail_h = bot - top - 2 * pad;
|
||||
int left_w = avail_w * 35 / 100; /* 终端面板:42%→35% */
|
||||
int right_x = pad + left_w + gap;
|
||||
int right_w = (int)g_fb_w - pad - right_x;
|
||||
int row_h = (avail_h - gap) / 2;
|
||||
|
||||
g_dash_rect[0][0] = pad; g_dash_rect[0][1] = top + pad;
|
||||
g_dash_rect[0][2] = left_w; g_dash_rect[0][3] = row_h;
|
||||
g_dash_rect[1][0] = pad; g_dash_rect[1][1] = top + pad + row_h + gap;
|
||||
g_dash_rect[1][2] = left_w; g_dash_rect[1][3] = row_h;
|
||||
g_dash_rect[2][0] = right_x; g_dash_rect[2][1] = top + pad;
|
||||
g_dash_rect[2][2] = right_w; g_dash_rect[2][3] = row_h;
|
||||
g_dash_rect[3][0] = right_x; g_dash_rect[3][1] = top + pad + row_h + gap;
|
||||
g_dash_rect[3][2] = right_w; g_dash_rect[3][3] = row_h;
|
||||
}
|
||||
|
||||
static void draw_dashboard(void) {
|
||||
dash_layout();
|
||||
const char *headers[4] = { "TERM-01", "TERM-02", 0, "FILES" };
|
||||
char ed_hdr[40];
|
||||
editor_state *ed = (editor_state *)ADDR_EDDASH;
|
||||
kstrcpy(ed_hdr, "EDITOR . ", 40);
|
||||
kstrcat(ed_hdr, ed->has_file ? ed->disp : "untitled", 40);
|
||||
headers[2] = ed_hdr;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int x = g_dash_rect[i][0], y = g_dash_rect[i][1];
|
||||
int w = g_dash_rect[i][2], h = g_dash_rect[i][3];
|
||||
du_kate_panel(&g_fb, x, y, w, h, headers[i]);
|
||||
if (g_dash_focus == i) {
|
||||
du_rect_outline(&g_fb, x, y, w, h, KS_ACCENT, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* EDITOR 头 SAVE 按钮(仅关联文件后可用) */
|
||||
{
|
||||
int ex = g_dash_rect[2][0], ey = g_dash_rect[2][1], ew = g_dash_rect[2][2];
|
||||
int bw = 52, bh = 18;
|
||||
int bx = ex + ew - bw - 6, by = ey + 3;
|
||||
g_save_rect[0] = bx; g_save_rect[1] = by;
|
||||
g_save_rect[2] = bw; g_save_rect[3] = bh;
|
||||
u32 fg = ed->has_file ? KS_ACCENT : KS_TEXT_DIM;
|
||||
int hover = ed->has_file &&
|
||||
g_mouse_x >= bx && g_mouse_x < bx + bw &&
|
||||
g_mouse_y >= by && g_mouse_y < by + bh;
|
||||
if (hover) du_fill_rect(&g_fb, bx, by, bw, bh, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, bx, by, bw, bh, ed->has_file ? KS_BORDER : KS_BORDER_DIM, 1);
|
||||
du_draw_string(&g_fb, "SAVE", bx + 5, by + 1, fg, 0, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* 面板内容 */
|
||||
bash_draw_to((bash_state *)ADDR_TERM1,
|
||||
g_dash_rect[0][0] + 2, g_dash_rect[0][1] + KATE_PANEL_HEADER_H + 2,
|
||||
g_dash_rect[0][2] - 4, g_dash_rect[0][3] - KATE_PANEL_HEADER_H - 4);
|
||||
|
||||
bash_draw_to((bash_state *)ADDR_TERM2,
|
||||
g_dash_rect[1][0] + 2, g_dash_rect[1][1] + KATE_PANEL_HEADER_H + 2,
|
||||
g_dash_rect[1][2] - 4, g_dash_rect[1][3] - KATE_PANEL_HEADER_H - 4);
|
||||
|
||||
editor_draw_to(ed,
|
||||
g_dash_rect[2][0] + 2, g_dash_rect[2][1] + KATE_PANEL_HEADER_H + 2,
|
||||
g_dash_rect[2][2] - 4, g_dash_rect[2][3] - KATE_PANEL_HEADER_H - 4);
|
||||
|
||||
/* FILES 面板内容 */
|
||||
{
|
||||
int fx = g_dash_rect[3][0] + 2, fy = g_dash_rect[3][1] + KATE_PANEL_HEADER_H + 2;
|
||||
int fw = g_dash_rect[3][2] - 4, fh = g_dash_rect[3][3] - KATE_PANEL_HEADER_H - 4;
|
||||
du_fill_rect(&g_fb, fx, fy, fw, fh, KS_BG_PRIMARY);
|
||||
|
||||
char path_buf[32];
|
||||
kstrcpy(path_buf, "FAT32:/USER (", 32);
|
||||
int pl = kstrlen(path_buf);
|
||||
if (g_file_count >= 10) path_buf[pl++] = '0' + (char)(g_file_count / 10);
|
||||
path_buf[pl++] = '0' + (char)(g_file_count % 10);
|
||||
path_buf[pl++] = ')'; path_buf[pl] = 0;
|
||||
du_draw_string(&g_fb, path_buf, fx + 8, fy + 3, KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
du_divider_h(&g_fb, fx, fy + 22, fw, KS_BORDER);
|
||||
|
||||
int row_h = 22;
|
||||
int max_rows = (fh - 24) / row_h;
|
||||
for (int i = 0; i < g_file_count && i < max_rows; i++) {
|
||||
int ry = fy + 24 + i * row_h;
|
||||
if (i == g_file_sel) {
|
||||
du_fill_rect(&g_fb, fx, ry, fw, row_h, KS_ACCENT_DIM);
|
||||
}
|
||||
int hover = (g_mouse_x >= fx && g_mouse_x < fx + fw &&
|
||||
g_mouse_y >= ry && g_mouse_y < ry + row_h);
|
||||
if (hover && i != g_file_sel) {
|
||||
du_fill_rect(&g_fb, fx, ry, fw, row_h, KS_ACCENT_DIM);
|
||||
}
|
||||
u32 fg = (i == g_file_sel) ? KS_ACCENT : KS_TEXT_PRIMARY;
|
||||
du_draw_string(&g_fb, g_files[i].disp, fx + 8, ry + 2, fg, 0, DU_ASCII_STEP);
|
||||
char sz[12];
|
||||
u32 kb = g_files[i].size / 1024;
|
||||
int sp = 0;
|
||||
if (kb >= 100) { sz[sp++] = '0' + (char)(kb / 100 % 10); }
|
||||
if (kb >= 10) { sz[sp++] = '0' + (char)(kb / 10 % 10); }
|
||||
sz[sp++] = '0' + (char)(kb % 10);
|
||||
sz[sp++] = 'K'; sz[sp] = 0;
|
||||
du_draw_string(&g_fb, sz, fx + fw - sp * (int)DU_ASCII_STEP - 8, ry + 2,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
}
|
||||
if (!g_files_loaded) {
|
||||
du_draw_string(&g_fb, "NO BLOCK DEVICE", fx + 8, fy + 28,
|
||||
KS_DANGER, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
} else if (g_file_count == 0) {
|
||||
du_draw_string(&g_fb, "(empty)", fx + 8, fy + 28,
|
||||
KS_TEXT_DIM, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
draw_statusbar_full();
|
||||
}
|
||||
|
||||
/* ---- 2·IDE ---- */
|
||||
static int g_ide_attached = 0;
|
||||
static void page_switch(int p); /* 定义于工作区段,launch_linux_app 前向引用 */
|
||||
|
||||
/* VSCode Phase 6: guest GUI 状态机。
|
||||
* 0 = 未运行(IDE host 显示启动提示)
|
||||
* 1 = 启动中(X server/VSCode spawn 已发出,等 scanout 上线)
|
||||
* 2 = 运行中(scanout enabled,blit guest 帧) */
|
||||
static int g_vscode_state = 0;
|
||||
|
||||
/* P8.1: VSCode 启动插桩--分阶段状态跟踪 + 串口日志 + 屏幕显示。
|
||||
* g_launch_phase: 0=idle 1=X启动 2=X socket等待 3=app spawn 4=pgrep验证 5=等scanout 6=running 7=失败
|
||||
* g_launch_status: 屏幕显示的状态字符串(IDE host 区域) */
|
||||
static int g_launch_phase = 0;
|
||||
static char g_launch_status[64] = "";
|
||||
static u64 g_launch_tsc = 0; /* 启动开始 TSC(测各阶段耗时) */
|
||||
static int g_launch_exec_rc = 0; /* exec_async 返回码 */
|
||||
static int g_launch_pgrep_rc = -1; /* pgrep 验证结果(0=找到, 1=未找到)*/
|
||||
|
||||
/* P7.4 多实例:IDE tab 动态标签——显示最近启动实例的 DISPLAY 名
|
||||
* (单 X :0 共享 scanout,多 VSCode 进程同屏;标签指示最后 attach 者)。 */
|
||||
static char g_ide_tab_label[16] = "VSCODE";
|
||||
|
||||
/* P7.9: 性能 profiling(TSC 计时,每秒更新 FPS 显示)。
|
||||
* 在 IDE 页面叠加显示 FPS / blit 耗时 / dirty 跳过率。 */
|
||||
static u64 g_prof_frames = 0; /* 自上次 FPS 更新以来的帧数 */
|
||||
static u64 g_prof_fps_tsc = 0; /* 上次 FPS 计算的 TSC */
|
||||
static int g_prof_fps = 0; /* 当前 FPS(每秒更新) */
|
||||
static u64 g_prof_blit_total = 0; /* 累计 blit TSC */
|
||||
static u64 g_prof_blit_count = 0; /* blit 调用次数 */
|
||||
static u64 g_prof_blit_skip = 0; /* dirty=0 跳过的次数 */
|
||||
static u64 g_prof_blit_full = 0; /* full repaint 次数 */
|
||||
static int g_prof_last_blit_us = 0; /* 上次 blit 耗时(微秒) */
|
||||
|
||||
/* P7.3: attach/detach 时使 scanout 缓存失效并请求全量重绘(定义于 blit 区) */
|
||||
static void ide_attach_invalidate(void);
|
||||
|
||||
/* VSCode Phase 3: IDE attach 时在 Linux guest 中启动 X server(Xfbdev)。
|
||||
* Xfbdev 通过 virtio-gpu 2D 设备合成帧到 surface pool scanout 影子缓冲,
|
||||
* desktop 每帧 blit 到 IDE host 区域。exec 用 sh -c "... &" 后台启动,
|
||||
* shell 立即退出被 daemon 回收,Xfbdev 由 init 收养继续运行。
|
||||
* Phase 6: pgrep 守卫保证幂等(多实例/重复 attach 不重起 X)。 */
|
||||
/* P8.1: slog + 数字(用于 rc/ec 日志,负数正确显示前导 -) */
|
||||
static void slog_num(const char *prefix, int rc) {
|
||||
char buf[48];
|
||||
kstrcpy(buf, prefix, 48);
|
||||
int n = kstrlen(buf);
|
||||
if (rc < 0) { buf[n++] = '-'; rc = -rc; }
|
||||
u32dec((u32)rc, buf + n);
|
||||
slog(buf);
|
||||
}
|
||||
|
||||
static void ide_launch_guest_x(void) {
|
||||
if (!g_lxc_svc || !g_lxc_svc->exec) { slog("ide: lxc exec unavailable"); return; }
|
||||
slog("[launch] phase 1: launching Xfbdev");
|
||||
g_launch_phase = 1;
|
||||
kstrcpy(g_launch_status, "LAUNCHING X SERVER . . .", 64);
|
||||
const char *argv[] = {
|
||||
"sh", "-c",
|
||||
"pgrep -x Xfbdev >/dev/null 2>&1 || "
|
||||
"Xfbdev :0 -geometry 1024x768 -depth 32 >/dev/null 2>&1 &"
|
||||
};
|
||||
u64 exit_code = 0;
|
||||
int rc = g_lxc_svc->exec("/bin/sh", 3, argv, 0, 0, 0, &exit_code);
|
||||
slog_num("[launch] Xfbdev exec rc=", rc);
|
||||
if (rc != 0) {
|
||||
slog("[launch] WARNING: Xfbdev launch rc!=0 (may still be OK if already running)");
|
||||
}
|
||||
}
|
||||
|
||||
/* P8.1 Fix: lxc guest ping -- exec "echo OK" 快速检测 Linux guest 是否运行。
|
||||
* 返回 0=guest 可用,非 0=不可用(VMX 未启用/guest 未 park/IPC 不通)。 */
|
||||
static int lxc_guest_ping(void) {
|
||||
if (!g_lxc_svc || !g_lxc_svc->exec) return -1;
|
||||
const char *pargv[] = { "sh", "-c", "echo OK" };
|
||||
char out[8] = {0};
|
||||
u64 ec = 0;
|
||||
int rc = g_lxc_svc->exec("/bin/sh", 3, pargv, out, 7, 0, &ec);
|
||||
if (rc != 0) return rc;
|
||||
if (out[0] == 'O' && out[1] == 'K') return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* VSCode Phase 6: 共享 guest GUI 启动路径(IDE +NEW/双击 host 与桌面图标共用)。
|
||||
* 首次调用确保 X server 在线(幂等启动 + 有界等待 X socket),然后
|
||||
* exec_async 目标程序(daemon fork+setsid+detach,立即返回)。
|
||||
* 后续调用(多实例)跳过 X 准备直接 spawn。path 为 NULL 时仅确保 X 在线。
|
||||
* P8.1 Fix: 启动前 ping 检测 guest 可用性;失败时快速短路,不浪费 5s X socket 等待。 */
|
||||
static void guest_gui_launch(const char *path, int argc, const char *const *argv) {
|
||||
if (!g_lxc_svc) {
|
||||
slog("[launch] lxc service null");
|
||||
g_launch_phase = 7;
|
||||
kstrcpy(g_launch_status, "LXC SERVICE UNAVAILABLE", 64);
|
||||
return;
|
||||
}
|
||||
if (g_vscode_state == 0) {
|
||||
g_vscode_state = 1; /* 启动中 */
|
||||
g_launch_tsc = rdtsc();
|
||||
slog("[launch] === guest GUI launch sequence start ===");
|
||||
|
||||
/* P8.1 Fix: Phase 0 - ping guest 检测可用性 */
|
||||
g_launch_phase = 0;
|
||||
kstrcpy(g_launch_status, "CHECKING GUEST . . .", 64);
|
||||
int prc = lxc_guest_ping();
|
||||
slog_num("[launch] guest ping rc=", prc);
|
||||
if (prc != 0) {
|
||||
slog("[launch] FAIL: Linux guest not running (VMX/guest unavailable)");
|
||||
g_vscode_state = 0; /* 回到未启动状态 */
|
||||
g_launch_phase = 7;
|
||||
kstrcpy(g_launch_status, "GUEST NOT RUNNING - NEED KVM/VMX", 64);
|
||||
return;
|
||||
}
|
||||
slog("[launch] guest ping OK - Linux guest is running");
|
||||
|
||||
/* Phase 1: launch X server */
|
||||
ide_launch_guest_x();
|
||||
|
||||
/* Phase 2: wait for X socket (bounded ~5s) */
|
||||
g_launch_phase = 2;
|
||||
kstrcpy(g_launch_status, "WAITING FOR X SOCKET . . .", 64);
|
||||
slog("[launch] phase 2: waiting for X socket");
|
||||
if (g_lxc_svc->exec) {
|
||||
const char *wargv[] = {
|
||||
"sh", "-c",
|
||||
"i=0; while [ ! -S /tmp/.X11-unix/X0 ] && [ $i -lt 25 ]; "
|
||||
"do sleep 0.2; i=$((i+1)); done; [ -S /tmp/.X11-unix/X0 ] && echo OK || echo FAIL"
|
||||
};
|
||||
/* 用 exec + stdout 捕获验证 X socket 是否就绪 */
|
||||
char xout[16] = {0};
|
||||
u64 ec = 0;
|
||||
int rc = g_lxc_svc->exec("/bin/sh", 3, wargv, xout, 15, 0, &ec);
|
||||
slog_num("[launch] X socket wait rc=", rc);
|
||||
if (xout[0] == 'O' && xout[1] == 'K') {
|
||||
slog("[launch] X socket READY (/tmp/.X11-unix/X0 exists)");
|
||||
} else {
|
||||
slog("[launch] WARNING: X socket NOT ready after ~5s timeout");
|
||||
kstrcpy(g_launch_status, "X SOCKET TIMEOUT - RETRYING SPAWN", 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 3: exec_async spawn the app */
|
||||
if (path && g_lxc_svc->exec_async) {
|
||||
g_launch_phase = 3;
|
||||
kstrcpy(g_launch_status, "SPAWNING APPLICATION . . .", 64);
|
||||
slog("[launch] phase 3: exec_async spawn");
|
||||
slog(path);
|
||||
int rc = g_lxc_svc->exec_async(path, argc, argv);
|
||||
g_launch_exec_rc = rc;
|
||||
slog_num("[launch] exec_async rc=", rc);
|
||||
if (rc == 0) {
|
||||
slog("[launch] exec_async OK - process spawned");
|
||||
} else {
|
||||
slog("[launch] exec_async FAILED - process not spawned");
|
||||
g_vscode_state = 0; /* P8.1 Fix: 回到未启动,UI 显示错误而非永远 STARTING */
|
||||
g_launch_phase = 7;
|
||||
kstrcpy(g_launch_status, "SPAWN FAILED - CHECK SERIAL LOG", 64);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Phase 4: pgrep verify process exists (bounded wait ~1s) */
|
||||
g_launch_phase = 4;
|
||||
kstrcpy(g_launch_status, "VERIFYING PROCESS . . .", 64);
|
||||
slog("[launch] phase 4: pgrep verify");
|
||||
if (g_lxc_svc->exec) {
|
||||
/* 用 basename 做 pgrep(code -> pgrep code) */
|
||||
const char *pargv[] = {
|
||||
"sh", "-c",
|
||||
"sleep 0.5; pgrep -x code >/dev/null 2>&1 && echo FOUND || echo NOPE"
|
||||
};
|
||||
char pout[16] = {0};
|
||||
u64 pec = 0;
|
||||
int prc = g_lxc_svc->exec("/bin/sh", 3, pargv, pout, 15, 0, &pec);
|
||||
if (pout[0] == 'F') {
|
||||
g_launch_pgrep_rc = 0;
|
||||
slog("[launch] pgrep: VSCode process FOUND");
|
||||
} else if (pout[0] == 'N') {
|
||||
g_launch_pgrep_rc = 1;
|
||||
slog("[launch] pgrep: VSCode process NOT found (may still be starting)");
|
||||
} else {
|
||||
g_launch_pgrep_rc = -1;
|
||||
slog_num("[launch] pgrep: no output, exec rc=", prc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 5: wait for scanout */
|
||||
g_launch_phase = 5;
|
||||
kstrcpy(g_launch_status, "WAITING FOR SCANOUT . . .", 64);
|
||||
slog("[launch] phase 5: waiting for scanout to come online");
|
||||
} else if (path && !g_lxc_svc->exec_async) {
|
||||
slog("[launch] exec_async unavailable - cannot spawn app");
|
||||
g_launch_phase = 7;
|
||||
kstrcpy(g_launch_status, "EXEC_ASYNC UNAVAILABLE", 64);
|
||||
}
|
||||
}
|
||||
|
||||
/* VSCode Phase 5/6: 桌面图标启动 Linux 应用(LINUXAPP.CNF 注册项)。
|
||||
* 解析 args(空格分隔)后经共享路径 exec_async,并切到 IDE 页 attach。 */
|
||||
static void launch_linux_app(int app_id) {
|
||||
if (app_id < 0 || app_id >= g_app_count) return;
|
||||
app_descriptor *app = &g_apps[app_id];
|
||||
if (!app->linux_path) return;
|
||||
if (!g_lxc_svc || !g_lxc_svc->exec_async) {
|
||||
slog("linuxapp: lxc service unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
/* argv: [name, ...args](args 在本地缓冲内原地切分) */
|
||||
const char *argv[16];
|
||||
int argc = 0;
|
||||
argv[argc++] = app->name;
|
||||
char abuf[96];
|
||||
if (app->linux_args && app->linux_args[0]) {
|
||||
int n = kstrlen(app->linux_args);
|
||||
if (n > (int)sizeof(abuf) - 1) n = (int)sizeof(abuf) - 1;
|
||||
for (int i = 0; i < n; i++) abuf[i] = app->linux_args[i];
|
||||
abuf[n] = 0;
|
||||
char *p = abuf;
|
||||
while (*p && argc < 15) {
|
||||
while (*p == ' ') p++;
|
||||
if (!*p) break;
|
||||
argv[argc++] = p;
|
||||
while (*p && *p != ' ') p++;
|
||||
if (*p) *p++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
guest_gui_launch(app->linux_path, argc, argv);
|
||||
|
||||
/* P7.4: tab 标签跟随最近启动实例(DISPLAY 名,≤15 字符) */
|
||||
{
|
||||
int i = 0;
|
||||
const char *dn = app->display_name;
|
||||
while (dn && dn[i] && i + 1 < (int)sizeof(g_ide_tab_label)) {
|
||||
g_ide_tab_label[i] = dn[i];
|
||||
i++;
|
||||
}
|
||||
g_ide_tab_label[i] = 0;
|
||||
}
|
||||
|
||||
/* 切到 IDE 页并 attach(VSCode 窗口出现在 IDE host 区域) */
|
||||
g_ide_attached = 1;
|
||||
g_input_forward_enabled = 1;
|
||||
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
||||
page_switch(2);
|
||||
slog("linuxapp launched -> ide attached");
|
||||
}
|
||||
|
||||
/* VSCode Phase 6: IDE attach 共享入口——启动(或复用)VSCode。 */
|
||||
static void vscode_launch(void) {
|
||||
if (g_vscode_app_id >= 0) {
|
||||
launch_linux_app(g_vscode_app_id);
|
||||
return;
|
||||
}
|
||||
/* 未注册 VSCode(CNF 缺失):仅确保 X server 在线,保持 Phase 3 行为 */
|
||||
guest_gui_launch(0, 0, 0);
|
||||
}
|
||||
|
||||
/* VSCode Phase 3/7: 把 virtio-gpu scanout 影子缓冲 blit 到 IDE host 区域。
|
||||
* scanout 为 1024×768 XRGB8888(stride=4096),与 framebuffer 同为 32bpp。
|
||||
* Phase 7 dirty rect 优化:主循环每帧把 scanout 状态缓存进 g_so(查询会
|
||||
* 清零 UTSM 侧 dirty),blit 只拷贝累积 dirty rect(scanout 坐标系并集),
|
||||
* sprite 双缓冲持久保留未变区域;attach/首帧/状态切换经 g_so_full_repaint
|
||||
* 全量重绘一次。 */
|
||||
static struct linux_compat_scanout_info g_so;
|
||||
static int g_so_valid = 0; /* 1 = g_so 为本帧主循环 poll 的缓存 */
|
||||
static int g_so_full_repaint = 1; /* 1 = 下次 blit 全量拷贝(初始/attach/上线) */
|
||||
|
||||
/* attach/detach 时使 scanout 缓存失效并请求全量重绘 */
|
||||
static void ide_attach_invalidate(void) {
|
||||
g_so_full_repaint = 1;
|
||||
g_so_valid = 0;
|
||||
}
|
||||
|
||||
/* 获取 scanout 状态:优先本帧缓存;缓存无效时查询并缓存。 */
|
||||
static const struct linux_compat_scanout_info *ide_scanout_state(void) {
|
||||
if (g_so_valid) return &g_so;
|
||||
if (g_lxc_svc && g_lxc_svc->gpu_get_scanout_info &&
|
||||
g_lxc_svc->gpu_get_scanout_info(&g_so) == 0) {
|
||||
g_so_valid = 1;
|
||||
return &g_so;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void blit_scanout_to_ide(int hx, int hy, int hw, int hh) {
|
||||
const struct linux_compat_scanout_info *si = ide_scanout_state();
|
||||
if (!si || !si->enabled || !si->host_vaddr) {
|
||||
/* scanout 未就绪:X server 尚未 SET_SCANOUT,显示等待提示 */
|
||||
du_fill_rect(&g_fb, hx, hy, hw, hh, 0xFF051828u);
|
||||
draw_centered("WAITING FOR GUEST X SERVER . . .",
|
||||
hx + hw / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int sw = (int)si->width, sh = (int)si->height;
|
||||
int sstride = (int)si->stride; /* bytes/row */
|
||||
int dstride = (int)g_fb_pitch; /* bytes/row */
|
||||
u8 *src = (u8 *)si->host_vaddr;
|
||||
u8 *dst_base = (u8 *)g_fb.fb;
|
||||
|
||||
/* 拷贝区域:默认仅累积 dirty rect;full repaint 时整帧 */
|
||||
int full = g_so_full_repaint;
|
||||
int cx = 0, cy = 0, cw = sw, ch = sh;
|
||||
if (!full) {
|
||||
if (!si->dirty || si->dirty_w == 0 || si->dirty_h == 0) {
|
||||
g_prof_blit_skip++; /* P7.9: dirty=0 零拷贝跳过 */
|
||||
return;
|
||||
}
|
||||
cx = (int)si->dirty_x; cy = (int)si->dirty_y;
|
||||
cw = (int)si->dirty_w; ch = (int)si->dirty_h;
|
||||
}
|
||||
g_so_full_repaint = 0;
|
||||
|
||||
/* P7.9: TSC 计时 blit */
|
||||
u64 _tsc0 = rdtsc();
|
||||
if (full) g_prof_blit_full++;
|
||||
|
||||
/* clamp 到 host 区域与 scanout 边界 */
|
||||
if (cx + cw > hw) cw = hw - cx;
|
||||
if (cy + ch > hh) ch = hh - cy;
|
||||
if (cw > sw - cx) cw = sw - cx;
|
||||
if (ch > sh - cy) ch = sh - cy;
|
||||
if (cw <= 0 || ch <= 0) return;
|
||||
|
||||
for (int row = 0; row < ch; row++) {
|
||||
u32 *s = (u32 *)(src + (u64)(cy + row) * sstride + (u64)cx * 4);
|
||||
u32 *d = (u32 *)(dst_base + (u64)(hy + cy + row) * dstride
|
||||
+ (u64)(hx + cx) * 4);
|
||||
for (int col = 0; col < cw; col++) d[col] = s[col];
|
||||
}
|
||||
|
||||
/* 全量重绘时,scanout 未覆盖的 host 剩余区域填深色背景 */
|
||||
if (full) {
|
||||
int cov_w = (hw < sw) ? hw : sw;
|
||||
int cov_h = (hh < sh) ? hh : sh;
|
||||
if (cov_w < hw)
|
||||
du_fill_rect(&g_fb, hx + cov_w, hy, hw - cov_w, cov_h, 0xFF051828u);
|
||||
if (cov_h < hh)
|
||||
du_fill_rect(&g_fb, hx, hy + cov_h, hw, hh - cov_h, 0xFF051828u);
|
||||
}
|
||||
|
||||
/* P7.6: 叠加 guest 硬件光标(ARGB alpha-blend 到 framebuffer)。
|
||||
* 光标位图 row stride = 64(virtio-gpu 规范 cursor max 64x64)。
|
||||
* 坐标从 scanout 坐标系映射到 framebuffer:(hx + cursor_x - hot_x, ...) */
|
||||
if (si->cursor_visible && si->cursor_bitmap) {
|
||||
int cw = (int)si->cursor_w, ch = (int)si->cursor_h;
|
||||
if (cw > 0 && ch > 0) {
|
||||
int fx = hx + (int)si->cursor_x - (int)si->cursor_hot_x;
|
||||
int fy = hy + (int)si->cursor_y - (int)si->cursor_hot_y;
|
||||
u32 *cbmp = (u32 *)si->cursor_bitmap;
|
||||
for (int row = 0; row < ch; row++) {
|
||||
int dy = fy + row;
|
||||
if (dy < 0 || (u64)dy >= g_fb_h) continue;
|
||||
u32 *d = (u32 *)((u8 *)g_fb.fb + (u64)dy * (u64)g_fb_pitch);
|
||||
const u32 *s = &cbmp[row * 64]; /* row stride = VGPU_CURSOR_MAX */
|
||||
for (int col = 0; col < cw; col++) {
|
||||
int dx = fx + col;
|
||||
if (dx < 0 || (u64)dx >= g_fb_w) continue;
|
||||
u32 src = s[col];
|
||||
u32 a = (src >> 24) & 0xFF;
|
||||
if (a == 0) continue;
|
||||
if (a == 255) { d[dx] = src; continue; }
|
||||
u32 dst = d[dx];
|
||||
u32 rr = (((src >> 16) & 0xFF) * a + ((dst >> 16) & 0xFF) * (255 - a)) / 255;
|
||||
u32 gg = (((src >> 8) & 0xFF) * a + ((dst >> 8) & 0xFF) * (255 - a)) / 255;
|
||||
u32 bb = ( (src & 0xFF) * a + ( dst & 0xFF) * (255 - a)) / 255;
|
||||
d[dx] = 0xFF000000u | (rr << 16) | (gg << 8) | bb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* P7.9: 记录 blit 耗时 */
|
||||
g_prof_blit_total += rdtsc() - _tsc0;
|
||||
g_prof_blit_count++;
|
||||
if (g_tsc_per_sec)
|
||||
g_prof_last_blit_us = (int)((rdtsc() - _tsc0) * 1000000 / g_tsc_per_sec);
|
||||
}
|
||||
|
||||
static void draw_ide(void) {
|
||||
int W = (int)g_fb_w;
|
||||
int top = KATE_TOPBAR_H;
|
||||
|
||||
/* ide-tabs(36px):dot + 实例标签 + "+ NEW IDE" + ide-hint(Kate 结构) */
|
||||
du_fill_rect(&g_fb, 0, top, W, 36, 0xFF051828u);
|
||||
du_divider_h(&g_fb, 0, top + 36, W, KS_BORDER);
|
||||
int x = 12;
|
||||
int ty = top + (36 - (int)DU_ASCII_LINE_H) / 2 + 1;
|
||||
|
||||
/* .dot(8×8 accent2 发光点) */
|
||||
du_fill_rect(&g_fb, x, top + 14, 8, 8, KS_ACCENT2);
|
||||
x += 16;
|
||||
|
||||
/* 实例标签(Kate .ide-tab):P7.4 动态标签 = 最近启动实例 DISPLAY 名 */
|
||||
{
|
||||
const char *label = g_ide_tab_label;
|
||||
int bw = kstrlen(label) * (int)DU_ASCII_STEP + 32;
|
||||
if (g_ide_attached) {
|
||||
du_fill_rect(&g_fb, x, top + 5, bw, 26, KS_ACCENT);
|
||||
du_kate_glow_border(&g_fb, x, top + 5, bw, 26, KS_ACCENT);
|
||||
}
|
||||
du_rect_outline(&g_fb, x, top + 5, bw, 26,
|
||||
g_ide_attached ? KS_ACCENT : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, label, x + 8, ty,
|
||||
g_ide_attached ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_DANGER, 0, DU_ASCII_STEP);
|
||||
g_ide_tabx[0] = x + bw - 20; g_ide_tabx[1] = top + 5;
|
||||
g_ide_tabx[2] = 20; g_ide_tabx[3] = 26;
|
||||
x += bw + 8;
|
||||
}
|
||||
|
||||
/* "+ NEW IDE" 按钮(Kate .btn,透明底 + 边框 + accent 字) */
|
||||
{
|
||||
const char *nb = "+ NEW IDE";
|
||||
int bw = kstrlen(nb) * (int)DU_ASCII_STEP + 20;
|
||||
g_ide_new_rect[0] = x; g_ide_new_rect[1] = top + 7;
|
||||
g_ide_new_rect[2] = bw; g_ide_new_rect[3] = 22;
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= top + 7 && g_mouse_y < top + 29;
|
||||
if (hover) du_fill_rect(&g_fb, x, top + 7, bw, 22, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, top + 7, bw, 22, KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, nb, x + 10, ty, KS_ACCENT, 0, DU_ASCII_STEP);
|
||||
x += bw + 8;
|
||||
}
|
||||
|
||||
/* ide-hint */
|
||||
du_draw_string(&g_fb, "CTRL+SHIFT+TAB CTX . x DETACH . DBL-CLICK HOST ATTACH",
|
||||
x + 8, ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* page-credit(对应 Kate "由 Deaicup 工作室制作") */
|
||||
du_draw_string(&g_fb, "DEAICUP STUDIO",
|
||||
W - 12 - 14 * (int)DU_ASCII_STEP, ty + 1,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* ide-host(虚线,margin 8px) */
|
||||
int hx = 8, hy = top + 36 + 8;
|
||||
int hw = W - 16, hh = (int)g_fb_h - hy - 8;
|
||||
g_ide_host[0] = hx; g_ide_host[1] = hy;
|
||||
g_ide_host[2] = hw; g_ide_host[3] = hh;
|
||||
draw_dashed_rect(hx, hy, hw, hh, KS_BORDER);
|
||||
|
||||
if (g_ide_attached) {
|
||||
if (g_input_forward_enabled) {
|
||||
/* VSCode Phase 6 状态机:启动中探测 scanout 上线 → 运行中 */
|
||||
if (g_vscode_state == 1) {
|
||||
const struct linux_compat_scanout_info *si = ide_scanout_state();
|
||||
if (si && si->enabled) {
|
||||
g_vscode_state = 2;
|
||||
g_launch_phase = 6;
|
||||
g_so_full_repaint = 1; /* 上线首帧全量重绘 */
|
||||
/* P8.1: scanout 上线日志 + 耗时 */
|
||||
slog("[launch] phase 6: SCANOUT ONLINE");
|
||||
if (g_tsc_per_sec && g_launch_tsc) {
|
||||
u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec;
|
||||
slog_num("[launch] total launch time ms=", (int)ms);
|
||||
}
|
||||
{
|
||||
char sbuf[48];
|
||||
kstrcpy(sbuf, "scanout: ", 48);
|
||||
u32dec(si->width, sbuf + kstrlen(sbuf));
|
||||
kstrcat(sbuf, "x", 48);
|
||||
u32dec(si->height, sbuf + kstrlen(sbuf));
|
||||
slog(sbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (g_vscode_state == 2) {
|
||||
/* 运行中:blit guest 合成帧 */
|
||||
blit_scanout_to_ide(hx + 2, hy + 2, hw - 4, hh - 4);
|
||||
} else {
|
||||
/* P8.1: 启动中--显示分阶段状态 + 详细信息 */
|
||||
du_fill_rect(&g_fb, hx + 2, hy + 2, hw - 4, hh - 4, 0xFF051828u);
|
||||
draw_centered(g_launch_status[0] ? g_launch_status :
|
||||
"STARTING VSCODE . . .",
|
||||
W / 2, hy + hh / 2 - 20, KS_TEXT_DIM, 0);
|
||||
/* 详细信息行(phase/exec_rc/pgrep/耗时) */
|
||||
{
|
||||
char dbuf[80];
|
||||
int dp = 0;
|
||||
kstrcpy(dbuf + dp, "PHASE:", 80); dp = kstrlen(dbuf);
|
||||
dp += u32dec((u32)g_launch_phase, dbuf + dp);
|
||||
if (g_launch_exec_rc != 0) {
|
||||
kstrcpy(dbuf + dp, " RC:", 80); dp = kstrlen(dbuf);
|
||||
dp += u32dec((u32)(g_launch_exec_rc < 0 ?
|
||||
(u32)(-g_launch_exec_rc) : (u32)g_launch_exec_rc),
|
||||
dbuf + dp);
|
||||
}
|
||||
if (g_launch_pgrep_rc >= 0) {
|
||||
kstrcpy(dbuf + dp, " PGREP:", 80); dp = kstrlen(dbuf);
|
||||
kstrcpy(dbuf + dp, g_launch_pgrep_rc == 0 ? "FOUND" : "MISSING",
|
||||
80);
|
||||
dp = kstrlen(dbuf);
|
||||
}
|
||||
if (g_tsc_per_sec && g_launch_tsc) {
|
||||
u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec;
|
||||
kstrcpy(dbuf + dp, " T:", 80); dp = kstrlen(dbuf);
|
||||
dp += u32dec((u32)ms, dbuf + dp);
|
||||
kstrcpy(dbuf + dp, "ms", 80);
|
||||
}
|
||||
draw_centered(dbuf, W / 2, hy + hh / 2 + 4, KS_TEXT_DIM, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
editor_draw_to((editor_state *)ADDR_EDDASH, hx + 2, hy + 2, hw - 4, hh - 4);
|
||||
}
|
||||
} else {
|
||||
/* embed-empty(Kate:未启动 VSCode 提示) */
|
||||
draw_centered("VSCODE NOT RUNNING . DOUBLE-CLICK OR + NEW IDE TO START",
|
||||
W / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0);
|
||||
}
|
||||
|
||||
/* P7.9: 性能 profiling 叠加(IDE 页面左下角) */
|
||||
if (g_ide_attached && g_input_forward_enabled) {
|
||||
char buf[80];
|
||||
int p = 0;
|
||||
kstrcpy(buf + p, "FPS:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_fps, buf + p);
|
||||
kstrcpy(buf + p, " BLT:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_last_blit_us, buf + p);
|
||||
kstrcpy(buf + p, "us SKP:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_blit_skip, buf + p);
|
||||
kstrcpy(buf + p, " FUL:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_blit_full, buf + p);
|
||||
du_draw_string(&g_fb, buf, hx + 6, hy + hh - 16,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 3·DESKTOP ---- */
|
||||
static void draw_desktop_page(void) {
|
||||
/* 图标网格 */
|
||||
draw_desktop_icons();
|
||||
|
||||
/* 窗口(当前工作区,从底到顶) */
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
desktop_window *w = &g_windows[i];
|
||||
if (w->minimized || !w->visible) continue;
|
||||
if (w->ws != g_ws_cur) continue;
|
||||
|
||||
draw_window_frame(w);
|
||||
|
||||
if (w->app_id >= 0 && w->app_id < g_app_count &&
|
||||
g_apps[w->app_id].on_draw && w->app_state) {
|
||||
app_ctx ctx;
|
||||
fill_app_ctx(&ctx, w);
|
||||
g_apps[w->app_id].on_draw(w->app_state, &ctx);
|
||||
}
|
||||
|
||||
du_kate_scanlines(&g_fb, w->x + 1, w->y + KATE_TITLEBAR_H,
|
||||
w->w - 2, w->h - KATE_TITLEBAR_H - 1);
|
||||
}
|
||||
|
||||
/* 任务栏(40px) */
|
||||
int W = (int)g_fb_w;
|
||||
int tb_y = (int)g_fb_h - KATE_TASKBAR_H;
|
||||
du_fill_rect_gradient(&g_fb, 0, tb_y, W, KATE_TASKBAR_H,
|
||||
0xFF0A2840u, KS_BG_PRIMARY);
|
||||
du_divider_h(&g_fb, 0, tb_y, W, KS_BORDER);
|
||||
|
||||
int x = 8;
|
||||
int ty = tb_y + (KATE_TASKBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1;
|
||||
|
||||
/* tb-item per window(当前工作区) */
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
desktop_window *w = &g_windows[i];
|
||||
if (!w->visible || w->ws != g_ws_cur) {
|
||||
g_tb_rect[i][2] = 0; g_tb_close_rect[i][2] = 0;
|
||||
continue;
|
||||
}
|
||||
char t[16];
|
||||
int tl = 0;
|
||||
while (w->title[tl] && tl < 11) { t[tl] = w->title[tl]; tl++; }
|
||||
if (w->title[tl]) { t[tl-1] = '.'; t[tl] = '.'; tl++; }
|
||||
t[tl] = 0;
|
||||
int bw = tl * (int)DU_ASCII_STEP + 34;
|
||||
if (bw > 150) bw = 150;
|
||||
int active = w->focused;
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= tb_y + 6 && g_mouse_y < tb_y + 34;
|
||||
if (active) du_fill_rect(&g_fb, x, tb_y + 6, bw, 28, KS_ACCENT_DIM);
|
||||
else if (hover) du_fill_rect(&g_fb, x, tb_y + 6, bw, 28, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, tb_y + 6, bw, 28,
|
||||
active ? KS_ACCENT : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, t, x + 6, ty,
|
||||
active ? KS_ACCENT : KS_TEXT_PRIMARY, 0, DU_ASCII_STEP);
|
||||
du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
g_tb_rect[i][0] = x; g_tb_rect[i][1] = tb_y + 6;
|
||||
g_tb_rect[i][2] = bw; g_tb_rect[i][3] = 28;
|
||||
g_tb_close_rect[i][0] = x + bw - 20; g_tb_close_rect[i][1] = tb_y + 6;
|
||||
g_tb_close_rect[i][2] = 20; g_tb_close_rect[i][3] = 28;
|
||||
x += bw + 6;
|
||||
if (x > W - 260) break;
|
||||
}
|
||||
|
||||
/* Winux-Kate 任务栏右组(margin-left:auto):
|
||||
* [+ ADOPT 虚框] [tb-credit 署名] [EXIT 危险色] */
|
||||
const char *credit = "DEAICUP STUDIO";
|
||||
const char *quit = "EXIT";
|
||||
const char *adopt = "+ ADOPT";
|
||||
|
||||
int qw = 60;
|
||||
int qx = W - qw - 8;
|
||||
int credit_w = kstrlen(credit) * (int)DU_ASCII_STEP;
|
||||
int cx = qx - 8 - credit_w;
|
||||
int aw = kstrlen(adopt) * (int)DU_ASCII_STEP + 16;
|
||||
int ax = cx - 8 - aw;
|
||||
|
||||
/* tb-adopt:虚线 accent 边框 */
|
||||
g_tb_adopt_rect[0] = ax; g_tb_adopt_rect[1] = tb_y + 6;
|
||||
g_tb_adopt_rect[2] = aw; g_tb_adopt_rect[3] = 28;
|
||||
{
|
||||
int hover = g_mouse_x >= ax && g_mouse_x < ax + aw &&
|
||||
g_mouse_y >= tb_y + 6 && g_mouse_y < tb_y + 34;
|
||||
if (hover) du_fill_rect(&g_fb, ax, tb_y + 6, aw, 28, KS_ACCENT_DIM);
|
||||
draw_dashed_rect(ax, tb_y + 6, aw, 28, KS_ACCENT);
|
||||
du_draw_string(&g_fb, adopt, ax + 8, ty, KS_ACCENT, 0, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* tb-credit */
|
||||
du_draw_string(&g_fb, credit, cx, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* tb-quit */
|
||||
g_tb_quit_rect[0] = qx; g_tb_quit_rect[1] = tb_y + 6;
|
||||
g_tb_quit_rect[2] = qw; g_tb_quit_rect[3] = 28;
|
||||
{
|
||||
int hover = g_mouse_x >= qx && g_mouse_x < qx + qw &&
|
||||
g_mouse_y >= tb_y + 6 && g_mouse_y < tb_y + 34;
|
||||
if (hover) du_fill_rect(&g_fb, qx, tb_y + 6, qw, 28, 0x30FF4D6Du);
|
||||
du_rect_outline(&g_fb, qx, tb_y + 6, qw, 28, KS_DANGER, 1);
|
||||
du_draw_string(&g_fb, quit, qx + (qw - 4 * (int)DU_ASCII_STEP) / 2, ty,
|
||||
KS_DANGER, 0, DU_ASCII_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 5+/CUSTOM ---- */
|
||||
static int g_cp_new_rect[4]; /* 自定义页 "+ NEW INSTANCE" 按钮 */
|
||||
|
||||
static void draw_custom_page(int cp_idx) {
|
||||
int W = (int)g_fb_w;
|
||||
int top = KATE_TOPBAR_H;
|
||||
cpage *cp = &g_cpages[cp_idx];
|
||||
|
||||
/* ide-tabs 栏(36px):dot + 页名 + "+ NEW INSTANCE" + ide-hint(Kate 结构) */
|
||||
du_fill_rect(&g_fb, 0, top, W, 36, 0xFF051828u);
|
||||
du_divider_h(&g_fb, 0, top + 36, W, KS_BORDER);
|
||||
int ty = top + (36 - (int)DU_ASCII_LINE_H) / 2 + 1;
|
||||
|
||||
/* .dot + 页名 */
|
||||
du_fill_rect(&g_fb, 12, top + 14, 8, 8, KS_ACCENT2);
|
||||
du_draw_string(&g_fb, cp->name, 28, ty, KS_ACCENT, 0, DU_ASCII_STEP);
|
||||
int x = 28 + kstrlen(cp->name) * (int)DU_ASCII_STEP + 12;
|
||||
|
||||
/* "+ NEW INSTANCE" 按钮(对应 Kate custom launch_custom_new) */
|
||||
{
|
||||
const char *nb = "+ NEW INSTANCE";
|
||||
int bw = kstrlen(nb) * (int)DU_ASCII_STEP + 20;
|
||||
g_cp_new_rect[0] = x; g_cp_new_rect[1] = top + 7;
|
||||
g_cp_new_rect[2] = bw; g_cp_new_rect[3] = 22;
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= top + 7 && g_mouse_y < top + 29;
|
||||
if (hover) du_fill_rect(&g_fb, x, top + 7, bw, 22, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, top + 7, bw, 22, KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, nb, x + 10, ty, KS_ACCENT, 0, DU_ASCII_STEP);
|
||||
x += bw + 8;
|
||||
}
|
||||
|
||||
/* ide-hint */
|
||||
du_draw_string(&g_fb, "DBL-CLICK HOST LAUNCH . EXTERNAL ELF FULLSCREEN",
|
||||
x + 8, ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* page-credit */
|
||||
du_draw_string(&g_fb, "DEAICUP STUDIO",
|
||||
W - 12 - 14 * (int)DU_ASCII_STEP, ty + 1,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* ide-host(虚线,margin 8px) */
|
||||
int hx = 8, hy = top + 36 + 8;
|
||||
int hw = W - 16, hh = (int)g_fb_h - hy - 8;
|
||||
g_cp_host[0] = hx; g_cp_host[1] = hy;
|
||||
g_cp_host[2] = hw; g_cp_host[3] = hh;
|
||||
draw_dashed_rect(hx, hy, hw, hh, KS_BORDER);
|
||||
|
||||
/* embed-empty(Kate:正在启动提示) */
|
||||
char msg[40];
|
||||
kstrcpy(msg, "DOUBLE-CLICK TO LAUNCH ", 40);
|
||||
kstrcat(msg, cp->name, 40);
|
||||
draw_centered(msg, hx + hw / 2, hy + hh / 2 - 12, KS_TEXT_DIM, 0);
|
||||
|
||||
char sub[40];
|
||||
kstrcpy(sub, "ELF: ", 40);
|
||||
{
|
||||
char disp[13];
|
||||
f32_name_from_83(cp->elf11, disp);
|
||||
kstrcat(sub, disp, 40);
|
||||
}
|
||||
draw_centered(sub, hx + hw / 2, hy + hh / 2 + 12, KS_ACCENT, 0);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 任务视图 overlay
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_taskview(void) {
|
||||
int W = (int)g_fb_w;
|
||||
|
||||
/* 背景暗化(全屏 blend,模态 overlay 下文字可读性优先) */
|
||||
for (u64 ry = 0; ry < g_fb_h; ry++) {
|
||||
u32 *line = (u32 *)((u8 *)g_fb.fb + ry * g_fb_pitch);
|
||||
for (u64 rx = 0; rx < g_fb_w; rx++) {
|
||||
line[rx] = du_blend(line[rx], KS_BG_PRIMARY, 220);
|
||||
}
|
||||
}
|
||||
|
||||
/* tv-header */
|
||||
du_draw_string(&g_fb, "TASK VIEW", 60, 44, KS_ACCENT, 0, DU_ASCII_STEP + 4);
|
||||
du_draw_string(&g_fb, "CLICK TO SWITCH . ESC CLOSE . x CLOSE WS",
|
||||
60 + 9 * ((int)DU_ASCII_STEP + 4) + 20, 48,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
du_divider_h(&g_fb, 60, 76, W - 120, KS_BORDER);
|
||||
|
||||
/* tv-grid 卡片 */
|
||||
int cw = 260, ch = 170, gap = 20;
|
||||
int cols = (W - 120 + gap) / (cw + gap);
|
||||
if (cols < 1) cols = 1;
|
||||
for (int i = 0; i <= g_ws_count; i++) {
|
||||
int row = i / cols, col = i % cols;
|
||||
int x = 60 + col * (cw + gap);
|
||||
int y = 96 + row * (ch + gap);
|
||||
g_tv_card[i][0] = x; g_tv_card[i][1] = y;
|
||||
g_tv_card[i][2] = cw; g_tv_card[i][3] = ch;
|
||||
|
||||
if (i == g_ws_count) {
|
||||
/* 新建工作区卡片(虚线 +) */
|
||||
draw_dashed_rect(x, y, cw, ch, KS_ACCENT2);
|
||||
du_draw_string(&g_fb, "+", x + cw / 2 - 6, y + ch / 2 - 24,
|
||||
KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
draw_centered("NEW WORKSPACE", x + cw / 2, y + ch / 2 + 8,
|
||||
KS_ACCENT2, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
int active = (i == g_ws_cur);
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + cw &&
|
||||
g_mouse_y >= y && g_mouse_y < y + ch;
|
||||
du_fill_rect(&g_fb, x, y, cw, ch, KS_BG_SECONDARY);
|
||||
du_rect_outline(&g_fb, x, y, cw, ch,
|
||||
active ? KS_ACCENT2 : (hover ? KS_ACCENT : KS_BORDER), 1);
|
||||
if (active) du_kate_glow_border(&g_fb, x, y, cw, ch, KS_ACCENT2);
|
||||
|
||||
/* tv-card-top */
|
||||
du_draw_string(&g_fb, g_ws[i].name, x + 12, y + 10, KS_ACCENT, 0, DU_ASCII_STEP);
|
||||
du_divider_h(&g_fb, x, y + 34, cw, KS_BORDER);
|
||||
/* × close */
|
||||
g_tv_close[i][0] = x + cw - 26; g_tv_close[i][1] = y + 4;
|
||||
g_tv_close[i][2] = 22; g_tv_close[i][3] = 26;
|
||||
du_draw_string(&g_fb, "x", x + cw - 20, y + 8, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* tv-stat */
|
||||
char nbuf[4];
|
||||
int wc = ws_win_count(i);
|
||||
nbuf[0] = '0' + (char)(wc % 10); nbuf[1] = 0;
|
||||
draw_centered(nbuf, x + cw / 4, y + 70, KS_ACCENT2, 0);
|
||||
draw_centered("WINS", x + cw / 4, y + 96, KS_TEXT_DIM, 0);
|
||||
char pbuf[6];
|
||||
kstrcpy(pbuf, "PG", 6);
|
||||
pbuf[2] = '0' + (char)(g_ws[i].page % 10); pbuf[3] = 0;
|
||||
draw_centered(pbuf, x + cw * 3 / 4, y + 70, KS_ACCENT2, 0);
|
||||
draw_centered("PAGE", x + cw * 3 / 4, y + 96, KS_TEXT_DIM, 0);
|
||||
|
||||
/* badge CURRENT */
|
||||
if (active) {
|
||||
int bx = x + cw - 84, by = y + 6;
|
||||
du_fill_rect(&g_fb, bx, by, 56, 14, KS_ACCENT2);
|
||||
du_draw_string(&g_fb, "CURRENT", bx + 2, by - 1,
|
||||
KS_TEXT_INVERT, 0, DU_ASCII_STEP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 右键菜单(移到工作区)
|
||||
* ============================================================ */
|
||||
|
||||
/* 构建"移到工作区"列表(排除当前工作区,与 Winux-Kate 一致),返回行数 */
|
||||
static int ctx_list_build(int *map) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < g_ws_count; i++) {
|
||||
if (i == g_ws_cur) continue;
|
||||
map[n++] = i;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static void draw_ctx_menu(void) {
|
||||
if (!g_ctx_open) return;
|
||||
int lmap[MAX_WS];
|
||||
int ln = ctx_list_build(lmap);
|
||||
int rows = (ln ? ln : 1) + 1; /* 列表行(或空态行) + 新建行 */
|
||||
int mw = 200;
|
||||
int mh = 24 + rows * 22 + 8;
|
||||
int x = g_ctx_x, y = g_ctx_y;
|
||||
if (x + mw > (int)g_fb_w) x = (int)g_fb_w - mw - 4;
|
||||
if (y + mh > (int)g_fb_h) y = (int)g_fb_h - mh - 4;
|
||||
g_ctx_rect[0] = x; g_ctx_rect[1] = y;
|
||||
g_ctx_rect[2] = mw; g_ctx_rect[3] = mh;
|
||||
|
||||
du_fill_rect(&g_fb, x, y, mw, mh, 0xFF050A18u);
|
||||
du_rect_outline(&g_fb, x, y, mw, mh, KS_ACCENT, 1);
|
||||
du_kate_glow_border(&g_fb, x, y, mw, mh, KS_ACCENT);
|
||||
|
||||
du_draw_string(&g_fb, "MOVE TO WORKSPACE", x + 12, y + 5,
|
||||
KS_ACCENT, 0, DU_ASCII_STEP);
|
||||
du_divider_h(&g_fb, x, y + 22, mw, KS_BORDER);
|
||||
|
||||
int iy = y + 26;
|
||||
int row = 0;
|
||||
if (ln == 0) {
|
||||
/* 空态(不可点击) */
|
||||
du_draw_string(&g_fb, "NO OTHER WORKSPACE", x + 12, iy + 2,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
g_ctx_item[0][2] = 0; /* 禁用命中 */
|
||||
g_ctx_item_ws[0] = -2;
|
||||
iy += 22;
|
||||
row = 1;
|
||||
}
|
||||
for (int r = 0; r < ln; r++) {
|
||||
int wi = lmap[r];
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + mw &&
|
||||
g_mouse_y >= iy && g_mouse_y < iy + 22;
|
||||
if (hover) du_fill_rect(&g_fb, x + 1, iy, mw - 2, 22, KS_ACCENT_DIM);
|
||||
du_draw_string(&g_fb, g_ws[wi].name, x + 12, iy + 2,
|
||||
hover ? KS_ACCENT : KS_TEXT_PRIMARY, 0, DU_ASCII_STEP);
|
||||
char cn[4];
|
||||
cn[0] = '0' + (char)(ws_win_count(wi) % 10); cn[1] = 0;
|
||||
du_draw_string(&g_fb, cn, x + mw - 24, iy + 2, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
g_ctx_item[row][0] = x; g_ctx_item[row][1] = iy;
|
||||
g_ctx_item[row][2] = mw; g_ctx_item[row][3] = 22;
|
||||
g_ctx_item_ws[row] = wi;
|
||||
iy += 22;
|
||||
row++;
|
||||
}
|
||||
/* + NEW WORKSPACE */
|
||||
du_divider_h(&g_fb, x, iy, mw, KS_BORDER);
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + mw &&
|
||||
g_mouse_y >= iy && g_mouse_y < iy + 22;
|
||||
if (hover) du_fill_rect(&g_fb, x + 1, iy, mw - 2, 22, KS_ACCENT_DIM);
|
||||
du_draw_string(&g_fb, "+ NEW WORKSPACE", x + 12, iy + 2,
|
||||
KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
g_ctx_item[row][0] = x; g_ctx_item[row][1] = iy;
|
||||
g_ctx_item[row][2] = mw; g_ctx_item[row][3] = 22;
|
||||
g_ctx_item_ws[row] = -1;
|
||||
/* 清除残余行命中 */
|
||||
for (int r = row + 1; r <= MAX_WS; r++) g_ctx_item[r][2] = 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 全屏重绘
|
||||
* ============================================================ */
|
||||
|
||||
/* 全局亮度(BRIGHT 滑块真实调暗)
|
||||
* 性能优化:仅在亮度变化时处理,避免每帧全屏遍历 */
|
||||
static void apply_brightness(void) {
|
||||
/* 快速退出:亮度100%无需调整 */
|
||||
if (g_bright >= 100) {
|
||||
g_bright_applied = 100;
|
||||
return;
|
||||
}
|
||||
|
||||
/* 快速退出:亮度未变化,跳过处理 */
|
||||
if (g_bright == g_bright_applied) return;
|
||||
|
||||
/* 记录当前应用的亮度值 */
|
||||
g_bright_applied = g_bright;
|
||||
|
||||
u32 f = (u32)g_bright;
|
||||
for (u64 y = 0; y < g_fb_h; y++) {
|
||||
u32 *line = (u32 *)((u8 *)g_fb.fb + y * g_fb_pitch);
|
||||
for (u64 x = 0; x < g_fb_w; x++) {
|
||||
u32 c = line[x];
|
||||
u32 b = ((c >> 16) & 0xFF) * f / 100;
|
||||
u32 g = ((c >> 8) & 0xFF) * f / 100;
|
||||
u32 r = (c & 0xFF) * f / 100;
|
||||
line[x] = 0xFF000000u | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void redraw_all(void) {
|
||||
/* 深空黑渐变背景 */
|
||||
du_fill_bg_gradient(&g_fb, KS_BG_PRIMARY, KS_BG_TERTIARY);
|
||||
|
||||
/* 顶栏 */
|
||||
draw_topbar();
|
||||
|
||||
/* 页面 */
|
||||
if (g_page == 1) draw_dashboard();
|
||||
else if (g_page == 2) draw_ide();
|
||||
else if (g_page == 3) draw_desktop_page();
|
||||
else {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < g_cpage_count; i++)
|
||||
if (g_cpages[i].id == g_page) { idx = i; break; }
|
||||
if (idx >= 0) draw_custom_page(idx);
|
||||
else g_page = 1;
|
||||
}
|
||||
|
||||
/* 任务视图 overlay */
|
||||
if (g_taskview) draw_taskview();
|
||||
|
||||
/* 右键菜单(最顶) */
|
||||
if (g_ctx_open) draw_ctx_menu();
|
||||
|
||||
/* 亮度调整 */
|
||||
apply_brightness();
|
||||
|
||||
/* 鼠标光标 */
|
||||
cursor_save_bg(g_mouse_x, g_mouse_y);
|
||||
cursor_draw(g_mouse_x, g_mouse_y);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 工作区 / 页面管理
|
||||
* ============================================================ */
|
||||
|
||||
static void ws_switch(int id) {
|
||||
if (id < 0 || id >= g_ws_count || id == g_ws_cur) { g_taskview = 0; return; }
|
||||
g_ws[g_ws_cur].page = g_page; /* 记住离开时的页面 */
|
||||
g_ws_cur = id;
|
||||
g_page = g_ws[id].page;
|
||||
g_taskview = 0;
|
||||
}
|
||||
|
||||
static void ws_create(void) {
|
||||
if (g_ws_count >= MAX_WS) return;
|
||||
char *nm = g_ws[g_ws_count].name;
|
||||
nm[0] = 'W'; nm[1] = 'S'; nm[2] = '-';
|
||||
nm[3] = '0' + (char)(g_ws_count + 1); nm[4] = 0;
|
||||
g_ws[g_ws_count].page = 1;
|
||||
g_ws_count++;
|
||||
}
|
||||
|
||||
/* 关闭工作区:窗口释放到关闭后的当前工作区(Kate release 语义) */
|
||||
static void ws_close(int id) {
|
||||
if (g_ws_count <= 1 || id < 0 || id >= g_ws_count) return;
|
||||
int cur_after = g_ws_cur;
|
||||
if (g_ws_cur == id) cur_after = (id > 0) ? id - 1 : 0;
|
||||
else if (g_ws_cur > id) cur_after = g_ws_cur - 1;
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (g_windows[i].ws == id) g_windows[i].ws = cur_after;
|
||||
else if (g_windows[i].ws > id) g_windows[i].ws--;
|
||||
}
|
||||
for (int i = id; i < g_ws_count - 1; i++) g_ws[i] = g_ws[i + 1];
|
||||
g_ws_count--;
|
||||
g_ws_cur = cur_after;
|
||||
g_taskview = 0;
|
||||
}
|
||||
|
||||
static void page_switch(int p) {
|
||||
if (p < 1 || p > 3 + g_cpage_count) return;
|
||||
g_page = p;
|
||||
}
|
||||
|
||||
/* 自定义页:为尚未挂载的外部 .elf 应用新建页面 */
|
||||
static void cpage_add(void) {
|
||||
if (g_cpage_count >= MAX_CPAGES) return;
|
||||
for (int a = 0; a < g_app_count; a++) {
|
||||
if (!g_apps[a].elf_name) continue;
|
||||
int dup = 0;
|
||||
for (int i = 0; i < g_cpage_count; i++)
|
||||
if (f32_neq11(g_cpages[i].elf11, g_apps[a].elf_name)) { dup = 1; break; }
|
||||
if (dup) continue;
|
||||
cpage *cp = &g_cpages[g_cpage_count];
|
||||
int n = kstrlen(g_apps[a].display_name);
|
||||
if (n > 11) n = 11;
|
||||
for (int i = 0; i < n; i++) cp->name[i] = g_apps[a].display_name[i];
|
||||
cp->name[n] = 0;
|
||||
for (int i = 0; i < 11; i++) cp->elf11[i] = g_apps[a].elf_name[i];
|
||||
cp->id = 4 + g_cpage_count;
|
||||
g_cpage_count++;
|
||||
g_page = cp->id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void cpage_remove(int idx) {
|
||||
if (idx < 0 || idx >= g_cpage_count) return;
|
||||
int removed_page = g_cpages[idx].id;
|
||||
for (int i = idx; i < g_cpage_count - 1; i++) g_cpages[i] = g_cpages[i + 1];
|
||||
g_cpage_count--;
|
||||
for (int i = 0; i < g_cpage_count; i++) g_cpages[i].id = 4 + i;
|
||||
if (g_page == removed_page) g_page = 1;
|
||||
else if (g_page > removed_page) g_page--;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 事件处理
|
||||
* ============================================================ */
|
||||
|
||||
static int rect_hit(const int r[4], int mx, int my) {
|
||||
return r[2] > 0 && mx >= r[0] && mx < r[0] + r[2] &&
|
||||
my >= r[1] && my < r[1] + r[3];
|
||||
}
|
||||
|
||||
static void slider_set(const int rect[4], int *val, int mx) {
|
||||
int v = (mx - rect[0]) * 100 / rect[2];
|
||||
if (v < 0) v = 0;
|
||||
if (v > 100) v = 100;
|
||||
*val = v;
|
||||
}
|
||||
|
||||
/* 双击判定(TSC 半秒 + 8px 容差),不更新状态 */
|
||||
static int dbl_check(int mx, int my) {
|
||||
if (!g_tsc_per_sec) return 0;
|
||||
u64 now = rdtsc();
|
||||
int dx = mx - g_last_click_x, dy = my - g_last_click_y;
|
||||
return (now - g_last_click_tsc) < g_tsc_per_sec / 2 &&
|
||||
dx > -8 && dx < 8 && dy > -8 && dy < 8;
|
||||
}
|
||||
|
||||
/* ---- 1·DASHBOARD 点击 ---- */
|
||||
static int dash_click(int mx, int my, int dbl) {
|
||||
editor_state *ed = (editor_state *)ADDR_EDDASH;
|
||||
|
||||
/* SAVE 按钮 */
|
||||
if (ed->has_file && rect_hit(g_save_rect, mx, my)) { editor_save(); return 1; }
|
||||
|
||||
/* 面板聚焦 */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!rect_hit(g_dash_rect[i], mx, my)) continue;
|
||||
g_dash_focus = i;
|
||||
if (i == 3) {
|
||||
/* FILES 行命中 / 双击打开 */
|
||||
int fx = g_dash_rect[3][0] + 2;
|
||||
int fy = g_dash_rect[3][1] + KATE_PANEL_HEADER_H + 2;
|
||||
int fw = g_dash_rect[3][2] - 4;
|
||||
int fh = g_dash_rect[3][3] - KATE_PANEL_HEADER_H - 4;
|
||||
if (mx >= fx && mx < fx + fw && my >= fy + 24) {
|
||||
int max_rows = (fh - 24) / 22;
|
||||
int row = (my - fy - 24) / 22;
|
||||
if (row >= 0 && row < max_rows && row < g_file_count) {
|
||||
if (dbl && row == g_file_sel) files_open(row);
|
||||
else g_file_sel = row;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 状态栏滑块 */
|
||||
if (rect_hit(g_vol_rect, mx, my)) {
|
||||
g_drag_slider = 1;
|
||||
slider_set(g_vol_rect, &g_vol, mx);
|
||||
return 1;
|
||||
}
|
||||
if (rect_hit(g_bri_rect, mx, my)) {
|
||||
g_drag_slider = 2;
|
||||
slider_set(g_bri_rect, &g_bright, mx);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 2·IDE 点击 ---- */
|
||||
static int ide_click(int mx, int my, int dbl) {
|
||||
if (g_ide_attached && rect_hit(g_ide_tabx, mx, my)) {
|
||||
g_ide_attached = 0;
|
||||
/* VSCode Phase 2: detach 时停止向 guest 转发输入 */
|
||||
g_input_forward_enabled = 0;
|
||||
ide_attach_invalidate(); /* P7.3: detach 后下次 attach 全量重绘 */
|
||||
slog("ide detached, input forwarding off");
|
||||
return 1;
|
||||
}
|
||||
if (!g_ide_attached && rect_hit(g_ide_new_rect, mx, my)) {
|
||||
g_ide_attached = 1;
|
||||
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
||||
/* attach 时仅当 Linux guest 服务可用才开启转发;否则降级为本地 editor */
|
||||
g_input_forward_enabled = (g_lxc_svc != 0);
|
||||
slog(g_input_forward_enabled ? "ide attached, forwarding to guest"
|
||||
: "ide attached, lxc unavailable (local editor)");
|
||||
/* VSCode Phase 6: 共享启动路径(X server + VSCode) */
|
||||
if (g_input_forward_enabled) vscode_launch();
|
||||
return 1;
|
||||
}
|
||||
if (!g_ide_attached && dbl && rect_hit(g_ide_host, mx, my)) {
|
||||
g_ide_attached = 1;
|
||||
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
||||
g_input_forward_enabled = (g_lxc_svc != 0);
|
||||
slog(g_input_forward_enabled ? "ide attached (dbl), forwarding to guest"
|
||||
: "ide attached (dbl), lxc unavailable (local editor)");
|
||||
/* VSCode Phase 6: 共享启动路径(X server + VSCode) */
|
||||
if (g_input_forward_enabled) vscode_launch();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 4·DESKTOP 点击 ---- */
|
||||
static int desktop_click(int mx, int my, int dbl) {
|
||||
int tb_y = (int)g_fb_h - KATE_TASKBAR_H;
|
||||
|
||||
/* 任务栏 */
|
||||
if (my >= tb_y) {
|
||||
if (rect_hit(g_tb_quit_rect, mx, my)) { g_quit = 1; return 1; }
|
||||
if (rect_hit(g_tb_adopt_rect, mx, my)) {
|
||||
/* 收纳窗口:全部工作区窗口收归当前工作区并还原最小化 */
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
g_windows[i].ws = g_ws_cur;
|
||||
g_windows[i].minimized = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (!g_tb_rect[i][2] || !rect_hit(g_tb_rect[i], mx, my)) continue;
|
||||
if (rect_hit(g_tb_close_rect[i], mx, my)) {
|
||||
win_close_request(&g_windows[i]);
|
||||
} else {
|
||||
win_focus(&g_windows[i]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 窗口 */
|
||||
desktop_window *w = win_find_at(mx, my);
|
||||
if (w) {
|
||||
int wid = w->id;
|
||||
win_focus(w);
|
||||
w = win_find(wid); /* focus 重排后重新定位 */
|
||||
if (!w) return 1;
|
||||
if (win_hit_close_btn(w, mx, my)) { win_close_request(w); return 1; }
|
||||
if (win_hit_titlebar(w, mx, my)) {
|
||||
g_dragging = 1;
|
||||
g_drag_win = w->id;
|
||||
g_drag_off_x = mx - w->x;
|
||||
g_drag_off_y = my - w->y;
|
||||
return 1;
|
||||
}
|
||||
/* 客户区事件 → 应用 */
|
||||
if (w->app_id >= 0 && w->app_id < g_app_count &&
|
||||
g_apps[w->app_id].on_event && w->app_state) {
|
||||
desktop_event dev;
|
||||
dev.type = EV_MOUSE_DOWN;
|
||||
dev.mx = mx; dev.my = my;
|
||||
dev.button = 0; dev.scancode = 0;
|
||||
dev.shift = g_shift; dev.ctrl = g_ctrl; dev.alt = g_alt;
|
||||
app_ctx actx;
|
||||
fill_app_ctx(&actx, w);
|
||||
g_apps[w->app_id].on_event(w->app_state, &actx, &dev);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 图标(单击选中 / 双击启动) */
|
||||
for (int i = 0; i < g_icon_count; i++) {
|
||||
int ix = g_icons[i].x, iy = g_icons[i].y;
|
||||
if (mx >= ix && mx < ix + ICON_W && my >= iy && my < iy + ICON_H) {
|
||||
if (dbl && g_icons[i].selected) {
|
||||
launch_app(g_icons[i].app_id);
|
||||
} else {
|
||||
for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0;
|
||||
g_icons[i].selected = 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 空白:取消选择 */
|
||||
for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- 左键按下 ---- */
|
||||
static int on_left_press(void) {
|
||||
int mx = g_mouse_x, my = g_mouse_y;
|
||||
int dbl = dbl_check(mx, my);
|
||||
g_last_click_tsc = rdtsc();
|
||||
g_last_click_x = mx; g_last_click_y = my;
|
||||
|
||||
/* 任务视图 overlay(模态) */
|
||||
if (g_taskview) {
|
||||
for (int i = 0; i <= g_ws_count; i++) {
|
||||
if (!rect_hit(g_tv_card[i], mx, my)) continue;
|
||||
if (i == g_ws_count) ws_create();
|
||||
else if (rect_hit(g_tv_close[i], mx, my)) ws_close(i);
|
||||
else ws_switch(i);
|
||||
return 1;
|
||||
}
|
||||
g_taskview = 0; /* 背景点击关闭 */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 右键菜单(模态) */
|
||||
if (g_ctx_open) {
|
||||
if (rect_hit(g_ctx_rect, mx, my)) {
|
||||
for (int i = 0; i <= MAX_WS; i++) {
|
||||
if (!rect_hit(g_ctx_item[i], mx, my)) continue;
|
||||
int wi = g_ctx_item_ws[i];
|
||||
if (wi == -1) {
|
||||
/* + NEW WORKSPACE:新建并迁移 */
|
||||
ws_create();
|
||||
desktop_window *w = win_find(g_ctx_win);
|
||||
if (w && g_ws_count > 0) w->ws = g_ws_count - 1;
|
||||
} else if (wi >= 0) {
|
||||
desktop_window *w = win_find(g_ctx_win);
|
||||
if (w) w->ws = wi;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_ctx_open = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 顶栏 */
|
||||
if (my < KATE_TOPBAR_H) {
|
||||
if (rect_hit(g_tvbtn_rect, mx, my)) { g_taskview = 1; return 1; }
|
||||
for (int i = 0; i < g_ws_count; i++)
|
||||
if (rect_hit(g_ws_rect[i], mx, my)) { ws_switch(i); return 1; }
|
||||
if (rect_hit(g_wsadd_rect, mx, my)) { ws_create(); return 1; }
|
||||
for (int i = 0; i < 3 + g_cpage_count; i++) {
|
||||
if (i >= 3 && rect_hit(g_pg_rm_rect[i], mx, my)) {
|
||||
cpage_remove(i - 3);
|
||||
return 1;
|
||||
}
|
||||
if (rect_hit(g_pg_rect[i], mx, my)) { page_switch(i + 1); return 1; }
|
||||
}
|
||||
if (rect_hit(g_pgadd_rect, mx, my)) { cpage_add(); return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 页面内容 */
|
||||
if (g_page == 1) return dash_click(mx, my, dbl);
|
||||
if (g_page == 2) return ide_click(mx, my, dbl);
|
||||
if (g_page == 3) return desktop_click(mx, my, dbl);
|
||||
if (g_page >= 4) {
|
||||
int launch = 0;
|
||||
if (rect_hit(g_cp_new_rect, mx, my)) launch = 1;
|
||||
if (dbl && rect_hit(g_cp_host, mx, my)) launch = 1;
|
||||
if (launch) {
|
||||
for (int i = 0; i < g_cpage_count; i++) {
|
||||
if (g_cpages[i].id != g_page) continue;
|
||||
launch_external_elf(g_cpages[i].elf11);
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 右键按下:窗口/任务栏项 → 移到工作区菜单 ---- */
|
||||
static int on_right_press(void) {
|
||||
int mx = g_mouse_x, my = g_mouse_y;
|
||||
|
||||
if (g_taskview) { g_taskview = 0; return 1; }
|
||||
if (g_ctx_open) { g_ctx_open = 0; return 1; }
|
||||
if (g_page != 3) return 0;
|
||||
|
||||
int tb_y = (int)g_fb_h - KATE_TASKBAR_H;
|
||||
if (my >= tb_y) {
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (!g_tb_rect[i][2] || !rect_hit(g_tb_rect[i], mx, my)) continue;
|
||||
int lmap[MAX_WS];
|
||||
int ln = ctx_list_build(lmap);
|
||||
int mh = 24 + ((ln ? ln : 1) + 1) * 22 + 8;
|
||||
g_ctx_win = g_windows[i].id;
|
||||
g_ctx_x = mx;
|
||||
g_ctx_y = my - mh; /* 菜单向上展开 */
|
||||
if (g_ctx_y < KATE_TOPBAR_H) g_ctx_y = my;
|
||||
g_ctx_open = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
desktop_window *w = win_find_at(mx, my);
|
||||
if (w) {
|
||||
g_ctx_win = w->id;
|
||||
g_ctx_x = mx; g_ctx_y = my;
|
||||
g_ctx_open = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 键盘 ---- */
|
||||
static int handle_key(u8 sc) {
|
||||
/* VSCode Phase 2: IDE attached 且在 IDE 页时,把按键序列转发到 Linux guest。
|
||||
* 0xE0 前缀字节本身不转发(无对应 keycode),仅靠 g_e0 标志让下一字节选取扩展键变体。
|
||||
* 转发在修饰键跟踪与页面路由之前完成,保证 VSCode 收到完整 press/release
|
||||
* 序列(含 Shift/Ctrl/Alt/方向键)。宿主修饰键跟踪继续运行,Ctrl+Tab 切页、
|
||||
* Ctrl+S 等热键仍可在宿主侧生效,便于用户随时切离 IDE 页。 */
|
||||
if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_keyboard &&
|
||||
sc != 0xE0 && g_page == 2) {
|
||||
u8 base = (u8)(sc & 0x7F);
|
||||
u16 code = scancode_to_linux_keycode(base, g_e0);
|
||||
if (code) {
|
||||
u32 value = (sc & 0x80) ? 0 : 1; /* 0x80 位 = release */
|
||||
g_lxc_svc->input_forward_keyboard(code, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (sc == 0xE0) { g_e0 = 1; return 0; }
|
||||
if (sc == 0x2A || sc == 0x36) { g_shift = 1; return 0; }
|
||||
if (sc == 0xAA || sc == 0xB6) { g_shift = 0; return 0; }
|
||||
if (sc == 0x1D) { g_ctrl = 1; g_e0 = 0; return 0; }
|
||||
if (sc == 0x9D) { g_ctrl = 0; return 0; }
|
||||
if (sc == 0x38) { g_alt = 1; g_e0 = 0; return 0; }
|
||||
if (sc == 0xB8) { g_alt = 0; return 0; }
|
||||
if (sc & 0x80) { g_e0 = 0; return 0; } /* 其他释放码忽略 */
|
||||
|
||||
/* Esc:关闭 overlay */
|
||||
if (sc == 0x01) {
|
||||
if (g_taskview) { g_taskview = 0; return 1; }
|
||||
if (g_ctx_open) { g_ctx_open = 0; return 1; }
|
||||
return 0;
|
||||
}
|
||||
if (g_taskview || g_ctx_open) return 0; /* 模态期间吞掉按键 */
|
||||
|
||||
/* Ctrl+Tab 切页 / Ctrl+Shift+Tab 上下文菜单(DESKTOP 页) */
|
||||
if (g_ctrl && sc == 0x0F) {
|
||||
if (g_shift) {
|
||||
/* Ctrl+Shift+Tab:对当前聚焦窗口打开"移到工作区"菜单 */
|
||||
if (g_page == 3 && win_find(g_focused_win)) {
|
||||
g_ctx_win = g_focused_win;
|
||||
g_ctx_x = g_mouse_x; g_ctx_y = g_mouse_y;
|
||||
g_ctx_open = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int maxp = 3 + g_cpage_count;
|
||||
int p = g_page + 1;
|
||||
if (p > maxp) p = 1;
|
||||
page_switch(p);
|
||||
return 1;
|
||||
}
|
||||
if (g_ctrl && sc == 0x1F) { editor_save(); return 1; }
|
||||
|
||||
/* 页面路由 */
|
||||
if (g_page == 1) {
|
||||
if (g_dash_focus <= 1) {
|
||||
bash_key((bash_state *)(g_dash_focus == 0 ? ADDR_TERM1 : ADDR_TERM2),
|
||||
sc, g_shift);
|
||||
return 1;
|
||||
}
|
||||
if (g_dash_focus == 2) {
|
||||
editor_key((editor_state *)ADDR_EDDASH, sc, g_shift);
|
||||
return 1;
|
||||
}
|
||||
/* FILES 面板导航 */
|
||||
if (sc == 0x48) { if (g_file_sel > 0) g_file_sel--; return 1; }
|
||||
if (sc == 0x50) { if (g_file_sel < g_file_count - 1) g_file_sel++; return 1; }
|
||||
if (sc == 0x1C) { files_open(g_file_sel); return 1; }
|
||||
return 0;
|
||||
}
|
||||
if (g_page == 2) {
|
||||
if (g_ide_attached) {
|
||||
if (g_input_forward_enabled) {
|
||||
/* VSCode Phase 2: 键盘已转发到 guest 中的 VSCode,
|
||||
* 本地 editor 不再重复处理,避免双输入。 */
|
||||
return 1;
|
||||
}
|
||||
editor_key((editor_state *)ADDR_EDDASH, sc, g_shift);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (g_page == 3) {
|
||||
desktop_window *fw = win_find(g_focused_win);
|
||||
if (fw && fw->ws == g_ws_cur && fw->app_id >= 0 && fw->app_id < g_app_count &&
|
||||
g_apps[fw->app_id].on_event && fw->app_state) {
|
||||
desktop_event dev;
|
||||
dev.type = EV_KEY_DOWN;
|
||||
dev.mx = g_mouse_x; dev.my = g_mouse_y;
|
||||
dev.button = 0;
|
||||
dev.scancode = sc;
|
||||
dev.shift = g_shift; dev.ctrl = g_ctrl; dev.alt = g_alt;
|
||||
app_ctx actx;
|
||||
fill_app_ctx(&actx, fw);
|
||||
g_apps[fw->app_id].on_event(fw->app_state, &actx, &dev);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* P5d: PE 窗口运行时(run_windowed 桌面嵌入)
|
||||
*
|
||||
* 协作模型(契约见 utsm/pe.h pe_window_host):
|
||||
* launch_pe_app 创建受管窗口并把客户区大小的内存 surface 交给 shim,
|
||||
* 随后阻塞在 pe_service.run_windowed。PE 消息空转(GetMessage/
|
||||
* PeekMessage 无消息)时 shim 回调 pe_pump_cb,由它驱动 desktop 一帧:
|
||||
* 采集 PS/2 输入(PE 窗口聚焦时经 inject_* 注入,否则走桌面正常路径)、
|
||||
* redraw_all + flip。关闭 = inject WM_CLOSE(win_close_request),
|
||||
* PE 自愿退出后 run_windowed 返回,launch_pe_app 收尾销毁窗口。
|
||||
*
|
||||
* 注意:pump 运行在 PE 的 1MB 大栈上,desktop 帧代码栈用量远小于此;
|
||||
* pump 内禁止再次调用 run/run_windowed(launch_pe_app 以 g_pe_running 拒绝)。
|
||||
* ============================================================ */
|
||||
|
||||
/* PE 窗口 on_draw:把 shim 渲染的 surface blit 到客户区(32bpp 行拷贝) */
|
||||
static void pe_win_on_draw(void *state, app_ctx *ctx) {
|
||||
(void)state;
|
||||
if (!g_pe_surf) return;
|
||||
int w = (g_pe_surf_w < ctx->client_w) ? g_pe_surf_w : ctx->client_w;
|
||||
int h = (g_pe_surf_h < ctx->client_h) ? g_pe_surf_h : ctx->client_h;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
u8 *dst0 = (u8 *)g_fb.fb + (u64)ctx->client_y * g_fb_pitch
|
||||
+ (u64)ctx->client_x * 4;
|
||||
u32 sp = (u32)g_pe_surf_w * 4;
|
||||
for (int r = 0; r < h; r++) {
|
||||
u32 *d = (u32 *)(dst0 + (u64)r * g_fb_pitch);
|
||||
const u32 *s = (const u32 *)(g_pe_surf + (u64)r * sp);
|
||||
for (int c = 0; c < w; c++) d[c] = s[c] | 0xFF000000u;
|
||||
}
|
||||
}
|
||||
|
||||
/* PE 消息空转让出点:驱动 desktop 一帧。返回 0 继续,非 0 请求 PE 退出。 */
|
||||
static int pe_pump_cb(void *ud) {
|
||||
int win_id = (int)(u64)ud;
|
||||
int moved = 0;
|
||||
while (ps2_mouse_poll()) moved = 1;
|
||||
|
||||
desktop_window *pw = win_find(win_id);
|
||||
if (!pw) return 1; /* 窗口已销毁:请求 PE 退出 */
|
||||
|
||||
int pe_focused = (g_page == 3 && pw->focused && pw->ws == g_ws_cur &&
|
||||
!g_taskview && !g_ctx_open);
|
||||
|
||||
/* 鼠标注入:PE 窗口聚焦且光标在客户区内 → surface 局部坐标 + 按钮位 */
|
||||
int cx0 = pw->x + BORDER_W, cy0 = pw->y + KATE_TITLEBAR_H;
|
||||
int ccw = pw->w - 2 * BORDER_W, cch = pw->h - KATE_TITLEBAR_H - BORDER_W;
|
||||
if (pe_focused && g_pe_svc->inject_pointer &&
|
||||
g_mouse_x >= cx0 && g_mouse_x < cx0 + ccw &&
|
||||
g_mouse_y >= cy0 && g_mouse_y < cy0 + cch) {
|
||||
g_pe_svc->inject_pointer(g_mouse_x - cx0, g_mouse_y - cy0,
|
||||
(u32)(g_mouse_btn & 3));
|
||||
}
|
||||
|
||||
/* 点击/释放走桌面正常路径(聚焦、标题栏拖拽、关闭按钮→win_close_request、
|
||||
* 其他窗口/图标)。双击其他 PE 图标会被 launch_pe_app 的 g_pe_running 拒绝。 */
|
||||
if (g_left_pressed) { g_left_pressed = 0; on_left_press(); }
|
||||
if (g_right_pressed) { g_right_pressed = 0; on_right_press(); }
|
||||
if (g_left_released) {
|
||||
g_left_released = 0;
|
||||
if (g_dragging || g_drag_slider) {
|
||||
g_dragging = 0; g_drag_win = -1; g_drag_slider = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pw = win_find(win_id); /* 点击可能重排窗口数组 */
|
||||
if (!pw) return 1;
|
||||
pe_focused = (g_page == 3 && pw->focused && pw->ws == g_ws_cur &&
|
||||
!g_taskview && !g_ctx_open);
|
||||
|
||||
/* 窗口/滑块拖拽(与主循环相同的钳位) */
|
||||
if (moved && g_dragging && g_drag_win == win_id) {
|
||||
pw->x = g_mouse_x - g_drag_off_x;
|
||||
pw->y = g_mouse_y - g_drag_off_y;
|
||||
if (pw->y < KATE_TOPBAR_H) pw->y = KATE_TOPBAR_H;
|
||||
if (pw->x < 0 - pw->w + 40) pw->x = 0 - pw->w + 40;
|
||||
if (pw->x > (int)g_fb_w - 40) pw->x = (int)g_fb_w - 40;
|
||||
if (pw->y > (int)g_fb_h - KATE_TASKBAR_H - 20)
|
||||
pw->y = (int)g_fb_h - KATE_TASKBAR_H - 20;
|
||||
}
|
||||
if (moved && g_drag_slider == 1) slider_set(g_vol_rect, &g_vol, g_mouse_x);
|
||||
if (moved && g_drag_slider == 2) slider_set(g_bri_rect, &g_bright, g_mouse_x);
|
||||
|
||||
/* 键盘:PE 聚焦时注入(shim 内部合成 KEYUP/CHAR/修饰键),
|
||||
* Ctrl+Tab 保留给宿主切页;否则走桌面正常路径。 */
|
||||
for (;;) {
|
||||
u8 st = inb(0x64);
|
||||
if (!(st & 1) || (st & 0x20)) break;
|
||||
u8 sc = inb(0x60);
|
||||
if (pe_focused && g_pe_svc->inject_scancode) {
|
||||
if (sc == 0xE0) { g_e0 = 1; continue; }
|
||||
/* 宿主侧同步跟踪修饰键(Ctrl+Tab 切页判定用) */
|
||||
if (sc == 0x2A || sc == 0x36) g_shift = 1;
|
||||
if (sc == 0xAA || sc == 0xB6) g_shift = 0;
|
||||
if (sc == 0x1D) g_ctrl = 1;
|
||||
if (sc == 0x9D) g_ctrl = 0;
|
||||
if (g_ctrl && sc == 0x0F) {
|
||||
/* 切页前补修饰键释放,防止 PE 侧卡键 */
|
||||
g_pe_svc->inject_scancode(0x9D, 0);
|
||||
g_pe_svc->inject_scancode(0xAA, 0);
|
||||
g_pe_svc->inject_scancode(0xB8, 0);
|
||||
g_e0 = 0;
|
||||
handle_key(sc);
|
||||
continue;
|
||||
}
|
||||
g_pe_svc->inject_scancode(sc, g_e0);
|
||||
g_e0 = 0;
|
||||
} else {
|
||||
handle_key(sc);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_quit) return 1; /* 任务栏 QUIT:请求 PE 退出 */
|
||||
|
||||
/* 驱动一帧(redraw_all 内含 PE surface blit 与光标绘制) */
|
||||
redraw_all();
|
||||
flip_buffer();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 启动 PE 应用:读 exe → 建窗口 → run_windowed 阻塞运行 → 退出后销毁窗口 */
|
||||
static void launch_pe_app(int app_id) {
|
||||
app_descriptor *app = &g_apps[app_id];
|
||||
if (!g_pe_svc || !g_pe_svc->run_windowed) {
|
||||
slog("pe: service unavailable");
|
||||
return;
|
||||
}
|
||||
if (g_pe_running) {
|
||||
slog("pe: another PE window is running");
|
||||
return;
|
||||
}
|
||||
|
||||
u8 *data = 0; u32 size = 0;
|
||||
if (f32_read_path_lfn(app->pe_file, &data, &size) != 0) {
|
||||
slog("pe: exe not found");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 窗口布局:与 launch_app 相同的级联策略 */
|
||||
int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_TASKBAR_H;
|
||||
int wx = ICON_PAD + g_win_count * 30;
|
||||
int wy = top + 8 + g_win_count * 30;
|
||||
if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = ICON_PAD;
|
||||
if (wy + (int)app->default_h > bot - 20) wy = top + 8;
|
||||
|
||||
desktop_window *win = win_create(app_id, app->display_name,
|
||||
wx, wy, (int)app->default_w, (int)app->default_h);
|
||||
if (!win) return;
|
||||
int win_id = win->id;
|
||||
|
||||
int cw = (int)app->default_w - 2 * BORDER_W;
|
||||
int ch = (int)app->default_h - KATE_TITLEBAR_H - BORDER_W;
|
||||
if (cw <= 0 || ch <= 0 || (u64)cw * (u64)ch * 4 > PE_SURFACE_MAX) {
|
||||
win_destroy(win);
|
||||
slog("pe: client area too large for surface");
|
||||
return;
|
||||
}
|
||||
g_pe_surf = (u8 *)PE_SURFACE_ADDR;
|
||||
g_pe_surf_w = cw; g_pe_surf_h = ch;
|
||||
/* 初始化为深色底:PE 窗口未覆盖区域不残留脏数据 */
|
||||
{
|
||||
u32 *p = (u32 *)g_pe_surf;
|
||||
u64 n = (u64)cw * (u64)ch;
|
||||
for (u64 i = 0; i < n; i++) p[i] = 0xFF101418u;
|
||||
}
|
||||
|
||||
win->app_state = &g_pe_state_dummy; /* draw_desktop_page 要求非空才调 on_draw */
|
||||
g_pe_win_id = win_id;
|
||||
g_pe_running = 1;
|
||||
slog("pe: run_windowed");
|
||||
|
||||
/* cmdline:"file.exe args"(GetCommandLineA 返回值) */
|
||||
static char cmdline[160];
|
||||
kstrcpy(cmdline, app->pe_file, (int)sizeof(cmdline));
|
||||
if (app->pe_args && app->pe_args[0]) {
|
||||
kstrcat(cmdline, " ", (int)sizeof(cmdline));
|
||||
kstrcat(cmdline, app->pe_args, (int)sizeof(cmdline));
|
||||
}
|
||||
|
||||
pe_window_host host;
|
||||
host.pump = pe_pump_cb;
|
||||
host.ud = (void *)(u64)win_id; /* 传 id:窗口数组重排不悬空 */
|
||||
host.surface = g_pe_surf;
|
||||
host.width = (u32)cw;
|
||||
host.height = (u32)ch;
|
||||
host.pitch = (u32)cw * 4;
|
||||
|
||||
u64 exit_code = 0;
|
||||
g_pe_svc->run_windowed(data, size, cmdline, &exit_code, &host);
|
||||
|
||||
g_pe_running = 0;
|
||||
g_pe_win_id = -1;
|
||||
g_pe_surf = 0;
|
||||
|
||||
/* PE 已退出:销毁宿主窗口并全屏重绘 */
|
||||
desktop_window *w2 = win_find(win_id);
|
||||
if (w2) win_destroy(w2);
|
||||
files_refresh(); /* PE 可能改动了磁盘 */
|
||||
g_full_redraw = 1;
|
||||
redraw_all();
|
||||
flip_buffer();
|
||||
slog("pe: exited");
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 主入口
|
||||
* ============================================================ */
|
||||
@@ -4153,7 +212,38 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
/* 双缓冲:保存真实 fb,切换到 sprite buffer 渲染 */
|
||||
g_real_fb = (u8 *)g_fb_addr;
|
||||
g_fb.fb = (du_u32 *)SPRITE_BUF_ADDR;
|
||||
f32_init(g_block_read, g_block_write);
|
||||
f32_io_init(g_block_read, g_block_write);
|
||||
|
||||
/* M1: 中文位图字库加载(system/font/simhei_16.dbf → 0x8000000)。
|
||||
* 失败阻塞启动。 */
|
||||
{
|
||||
int zrc = zhfont_init(f32_read_path_lfn_to_w);
|
||||
if (zrc == 0) {
|
||||
slog("[zhfont] dbf loaded (16px ok)");
|
||||
} else {
|
||||
slog_num("[zhfont] dbf load failed rc=", zrc);
|
||||
slog("[zhfont] CJK disabled (ASCII fallback)");
|
||||
for (;;) __asm__("hlt");
|
||||
}
|
||||
}
|
||||
|
||||
/* M2: FUCK 配置([desktop] fluent=)→ 壁纸初始化(Fluent 路径需要) */
|
||||
load_fuck_config();
|
||||
if (g_fluent) {
|
||||
int wrc = wallpaper_init(f32_read_path_lfn_to_w, (int)g_fb_w, (int)g_fb_h);
|
||||
if (wrc == 0) {
|
||||
slog("[wallpaper] loaded + scaled");
|
||||
} else {
|
||||
slog_num("[wallpaper] load failed rc=", wrc);
|
||||
slog("[wallpaper] gradient fallback");
|
||||
}
|
||||
/* M2: 开始菜单 logo(textures/startMenuLogo.rgba → 0x8700000) */
|
||||
if (taskbar_init(f32_read_path_lfn_to_w) == 0)
|
||||
slog("[taskbar] startMenu logo loaded");
|
||||
else
|
||||
slog("[taskbar] startMenu logo missing (glyph fallback)");
|
||||
g_page = 3; /* Fluent 默认进 DESKTOP 页 */
|
||||
}
|
||||
|
||||
tsc_calibrate();
|
||||
g_rtc_boot_sec = rtc_day_sec();
|
||||
@@ -4194,7 +284,9 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
g_booted = 1;
|
||||
|
||||
redraw_all();
|
||||
flip_buffer(); /* M2 修复:boot 后立即上屏,打字机残留清屏 */
|
||||
slog("desktop ready");
|
||||
desktop_launch_pending_native();
|
||||
|
||||
/* 主事件循环 */
|
||||
/* P8.1: dev 自动 VSCode 启动测试--无需 GUI 交互即可验证 launch 链路 */
|
||||
@@ -4216,12 +308,12 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
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");
|
||||
slog("[autotest] guest OK, waiting 90s before vscode_launch (KDE serial 2 first)");
|
||||
}
|
||||
}
|
||||
if (g_autotest_state == 1 && g_tsc_per_sec) {
|
||||
u64 elapsed = rdtsc() - g_autotest_tsc;
|
||||
if (elapsed >= (u64)g_tsc_per_sec * 3) {
|
||||
if (elapsed >= (u64)g_tsc_per_sec * 90) {
|
||||
slog("[autotest] triggering vscode_launch");
|
||||
g_autotest_state = 2;
|
||||
vscode_launch();
|
||||
@@ -4247,6 +339,22 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
/* 鼠标:排空所有数据包 */
|
||||
while (ps2_mouse_poll()) moved = 1;
|
||||
|
||||
if (inb(COM1 + 5) & 1) {
|
||||
unsigned char ch = inb(COM1);
|
||||
if (ch == '2') {
|
||||
int ki;
|
||||
slog("serial 2: launch KDE");
|
||||
for (ki = 0; ki < g_app_count; ki++) {
|
||||
if (g_apps[ki].name &&
|
||||
fat32_lfn_streq_ci(g_apps[ki].name, "kde")) {
|
||||
launch_app(ki);
|
||||
need_redraw = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (g_left_pressed) {
|
||||
g_left_pressed = 0;
|
||||
if (on_left_press()) need_redraw = 1;
|
||||
@@ -4336,15 +444,15 @@ void dsk_entry(const dsk_boot_context *ctx) {
|
||||
/* 合并脏矩形 */
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,886 @@
|
||||
/* pages.c — KATE 风格页面绘制(M0 自 main.c 平移)
|
||||
*
|
||||
* 包含:topbar / DASHBOARD / StatusBar / IDE / DESKTOP(含旧任务栏) /
|
||||
* CUSTOM / 任务视图 / 右键菜单。M2-M4 将按 Fluent 计划逐页替换:
|
||||
* topbar→删除、DASHBOARD→小组件、DESKTOP 任务栏→悬浮任务栏。
|
||||
*/
|
||||
#include "desktop.h"
|
||||
#include "zhfont.h" /* M2: 桌面图标中文标签 */
|
||||
|
||||
/* ============================================================
|
||||
* 顶栏(brand + WorkspaceSwitcher + PageSwitcher + PG 时钟)
|
||||
* ============================================================ */
|
||||
|
||||
void draw_topbar(void) {
|
||||
int W = (int)g_fb_w;
|
||||
du_fill_rect_gradient(&g_fb, 0, 0, W, KATE_TOPBAR_H,
|
||||
0xFF0A2840u, KS_BG_PRIMARY);
|
||||
du_divider_h(&g_fb, 0, KATE_TOPBAR_H, W, KS_BORDER);
|
||||
|
||||
int x = 12;
|
||||
int ty = (KATE_TOPBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1;
|
||||
|
||||
/* brand:方块点 + DESHAB + brand-credit(对应 Kate "由 Deaicup 工作室制作") */
|
||||
du_fill_rect(&g_fb, x, KATE_TOPBAR_H / 2 - 3, 6, 6, KS_ACCENT);
|
||||
du_draw_string(&g_fb, "DESHAB", x + 12, ty, KS_ACCENT, 0, DU_ASCII_STEP + 2);
|
||||
x += 12 + 6 * ((int)DU_ASCII_STEP + 2) + 10;
|
||||
du_draw_string(&g_fb, "DEAICUP STUDIO", x, ty + 2, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
x += 14 * (int)DU_ASCII_STEP + 14;
|
||||
|
||||
/* 分隔线 + 任务视图按钮(三横线图标) */
|
||||
du_divider_v(&g_fb, x - 6, 6, KATE_TOPBAR_H - 12, KS_BORDER);
|
||||
g_tvbtn_rect[0] = x; g_tvbtn_rect[1] = 5;
|
||||
g_tvbtn_rect[2] = 28; g_tvbtn_rect[3] = 24;
|
||||
{
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + 28 &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (hover) du_fill_rect(&g_fb, x, 5, 28, 24, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, 5, 28, 24, KS_BORDER, 1);
|
||||
du_fill_rect(&g_fb, x + 6, 11, 16, 2, KS_ACCENT);
|
||||
du_fill_rect(&g_fb, x + 6, 16, 16, 2, KS_ACCENT);
|
||||
du_fill_rect(&g_fb, x + 6, 21, 16, 2, KS_ACCENT);
|
||||
}
|
||||
x += 34;
|
||||
|
||||
/* 工作区按钮 "name|n" */
|
||||
for (int i = 0; i < g_ws_count; i++) {
|
||||
char label[24];
|
||||
kstrcpy(label, g_ws[i].name, 18);
|
||||
kstrcat(label, "|", 24);
|
||||
int cnt = ws_win_count(i);
|
||||
char cn[4];
|
||||
cn[0] = '0' + (char)(cnt % 10); cn[1] = 0;
|
||||
kstrcat(label, cn, 24);
|
||||
int bw = kstrlen(label) * (int)DU_ASCII_STEP + 16;
|
||||
int active = (i == g_ws_cur);
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (active) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT2);
|
||||
du_kate_glow_border(&g_fb, x, 5, bw, 24, KS_ACCENT2);
|
||||
} else if (hover) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM);
|
||||
}
|
||||
du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT2 : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, label, x + 8, ty,
|
||||
active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
g_ws_rect[i][0] = x; g_ws_rect[i][1] = 5;
|
||||
g_ws_rect[i][2] = bw; g_ws_rect[i][3] = 24;
|
||||
x += bw + 6;
|
||||
}
|
||||
|
||||
/* ws-add "+" */
|
||||
g_wsadd_rect[0] = x; g_wsadd_rect[1] = 5;
|
||||
g_wsadd_rect[2] = 26; g_wsadd_rect[3] = 24;
|
||||
{
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + 26 &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (hover) du_fill_rect(&g_fb, x, 5, 26, 24, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, 5, 26, 24, KS_ACCENT2, 1);
|
||||
du_draw_string(&g_fb, "+", x + 7, ty - 1, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
}
|
||||
x += 32;
|
||||
|
||||
/* 分隔线 */
|
||||
du_divider_v(&g_fb, x - 4, 6, KATE_TOPBAR_H - 12, KS_BORDER);
|
||||
x += 4;
|
||||
|
||||
/* 页面切换:精简按钮文字 */
|
||||
static const char *pg_names[3] = { "DASH", "IDE", "DESK" };
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int bw = kstrlen(pg_names[i]) * (int)DU_ASCII_STEP + 24;
|
||||
int active = (g_page == i + 1);
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (active) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT);
|
||||
du_kate_glow_border(&g_fb, x, 5, bw, 24, KS_ACCENT);
|
||||
} else if (hover) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM);
|
||||
}
|
||||
du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, pg_names[i], x + 12, ty,
|
||||
active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
g_pg_rect[i][0] = x; g_pg_rect[i][1] = 5;
|
||||
g_pg_rect[i][2] = bw; g_pg_rect[i][3] = 24;
|
||||
x += bw + 8;
|
||||
}
|
||||
/* 自定义页 */
|
||||
for (int i = 0; i < g_cpage_count; i++) {
|
||||
char label[20];
|
||||
label[0] = '0' + (char)g_cpages[i].id;
|
||||
label[1] = '.'; label[2] = 0;
|
||||
kstrcat(label, g_cpages[i].name, 20);
|
||||
int bw = kstrlen(label) * (int)DU_ASCII_STEP + 28;
|
||||
int active = (g_page == g_cpages[i].id);
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (active) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT);
|
||||
} else if (hover) {
|
||||
du_fill_rect(&g_fb, x, 5, bw, 24, KS_ACCENT_DIM);
|
||||
}
|
||||
du_rect_outline(&g_fb, x, 5, bw, 24, active ? KS_ACCENT : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, label, x + 6, ty,
|
||||
active ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_DANGER, 0, DU_ASCII_STEP);
|
||||
g_pg_rect[3 + i][0] = x; g_pg_rect[3 + i][1] = 5;
|
||||
g_pg_rect[3 + i][2] = bw; g_pg_rect[3 + i][3] = 24;
|
||||
g_pg_rm_rect[3 + i][0] = x + bw - 20; g_pg_rm_rect[3 + i][1] = 5;
|
||||
g_pg_rm_rect[3 + i][2] = 20; g_pg_rm_rect[3 + i][3] = 24;
|
||||
x += bw + 6;
|
||||
}
|
||||
/* pg-add "+" */
|
||||
if (g_cpage_count < MAX_CPAGES) {
|
||||
g_pgadd_rect[0] = x; g_pgadd_rect[1] = 5;
|
||||
g_pgadd_rect[2] = 26; g_pgadd_rect[3] = 24;
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + 26 &&
|
||||
g_mouse_y >= 5 && g_mouse_y < 29;
|
||||
if (hover) du_fill_rect(&g_fb, x, 5, 26, 24, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, 5, 26, 24, KS_ACCENT2, 1);
|
||||
du_draw_string(&g_fb, "+", x + 7, ty - 1, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
} else {
|
||||
g_pgadd_rect[2] = 0;
|
||||
}
|
||||
|
||||
/* 右侧:credit-top + PG 时钟(对应 Kate "© Deaicup Studio" + "PG n") */
|
||||
char pgclk[8];
|
||||
pgclk[0] = 'P'; pgclk[1] = 'G'; pgclk[2] = ' ';
|
||||
pgclk[3] = '0' + (char)(g_page % 10); pgclk[4] = 0;
|
||||
const char *credit = "(C) DEAICUP STUDIO";
|
||||
int cl = kstrlen(credit) + 1 + kstrlen(pgclk);
|
||||
du_draw_string(&g_fb, credit, W - 12 - cl * (int)DU_ASCII_STEP, ty,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
du_draw_string(&g_fb, pgclk, W - 12 - kstrlen(pgclk) * (int)DU_ASCII_STEP, ty,
|
||||
KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 滑块(VOL / BRIGHT)
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_slider(int x, int y, int w, int val, int rect_out[4]) {
|
||||
du_fill_rect(&g_fb, x, y + 5, w, 3, KS_BG_TERTIARY);
|
||||
du_rect_outline(&g_fb, x, y + 4, w, 5, KS_BORDER, 1);
|
||||
int fw = w * val / 100;
|
||||
if (fw > 0) du_fill_rect(&g_fb, x, y + 5, fw, 3, KS_ACCENT);
|
||||
int kx = x + fw - 3;
|
||||
if (kx < x) kx = x;
|
||||
if (kx > x + w - 6) kx = x + w - 6;
|
||||
du_fill_rect(&g_fb, kx, y, 6, 13, KS_ACCENT);
|
||||
du_rect_outline(&g_fb, kx, y, 6, 13, KS_ACCENT, 1);
|
||||
rect_out[0] = x; rect_out[1] = y - 2;
|
||||
rect_out[2] = w; rect_out[3] = 17;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 状态栏(DASHBOARD 底部 32px)
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_statusbar_full(void) {
|
||||
int W = (int)g_fb_w;
|
||||
int y = (int)g_fb_h - KATE_STATUSBAR_H;
|
||||
du_fill_rect(&g_fb, 0, y, W, KATE_STATUSBAR_H, 0xFF051828u);
|
||||
du_divider_h(&g_fb, 0, y, W, KS_BORDER);
|
||||
|
||||
int x = 12;
|
||||
int ty = y + (KATE_STATUSBAR_H - (int)DU_ASCII_LINE_H) / 2 + 1;
|
||||
|
||||
/* 时钟(日期 · 秒级时间,accent2) */
|
||||
int yy, mo, dd, hh, mi, ss;
|
||||
rtc_read(&yy, &mo, &dd, &hh, &mi, &ss);
|
||||
char clk[32];
|
||||
int cp2 = 0;
|
||||
clk[cp2++] = '2'; clk[cp2++] = '0';
|
||||
clk[cp2++] = '0' + (char)(yy / 10); clk[cp2++] = '0' + (char)(yy % 10);
|
||||
clk[cp2++] = '-';
|
||||
clk[cp2++] = '0' + (char)(mo / 10); clk[cp2++] = '0' + (char)(mo % 10);
|
||||
clk[cp2++] = '-';
|
||||
clk[cp2++] = '0' + (char)(dd / 10); clk[cp2++] = '0' + (char)(dd % 10);
|
||||
clk[cp2++] = ' '; clk[cp2++] = '.'; clk[cp2++] = ' ';
|
||||
char hms[9];
|
||||
fmt_hms(hms, hh * 3600 + mi * 60 + ss);
|
||||
for (int i = 0; hms[i]; i++) clk[cp2++] = hms[i];
|
||||
clk[cp2] = 0;
|
||||
du_draw_string(&g_fb, clk, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
x += cp2 * (int)DU_ASCII_STEP + 18;
|
||||
|
||||
/* UP 计时 */
|
||||
{
|
||||
int up = hh * 3600 + mi * 60 + ss - g_rtc_boot_sec;
|
||||
if (up < 0) up += 86400;
|
||||
char upb[16];
|
||||
kstrcpy(upb, "UP ", 16);
|
||||
char uh[9];
|
||||
fmt_hms(uh, up);
|
||||
kstrcat(upb, uh, 16);
|
||||
du_draw_string(&g_fb, upb, x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
x += kstrlen(upb) * (int)DU_ASCII_STEP + 18;
|
||||
}
|
||||
|
||||
/* VOL 滑块 */
|
||||
du_draw_string(&g_fb, "VOL", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
x += 3 * (int)DU_ASCII_STEP + 6;
|
||||
draw_slider(x, y + 9, 90, g_vol, g_vol_rect);
|
||||
x += 90 + 8;
|
||||
{
|
||||
char vb[6];
|
||||
vb[0] = '0' + (char)(g_vol / 100 % 10);
|
||||
vb[1] = '0' + (char)(g_vol / 10 % 10);
|
||||
vb[2] = '0' + (char)(g_vol % 10);
|
||||
vb[3] = '%'; vb[4] = 0;
|
||||
const char *vp = (g_vol >= 100) ? vb : (g_vol >= 10 ? vb + 1 : vb + 2);
|
||||
du_draw_string(&g_fb, vp, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
x += 4 * (int)DU_ASCII_STEP + 14;
|
||||
}
|
||||
|
||||
/* BRIGHT 滑块 */
|
||||
du_draw_string(&g_fb, "BRIGHT", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
x += 6 * (int)DU_ASCII_STEP + 6;
|
||||
draw_slider(x, y + 9, 90, g_bright, g_bri_rect);
|
||||
x += 90 + 8;
|
||||
{
|
||||
char vb[6];
|
||||
vb[0] = '0' + (char)(g_bright / 100 % 10);
|
||||
vb[1] = '0' + (char)(g_bright / 10 % 10);
|
||||
vb[2] = '0' + (char)(g_bright % 10);
|
||||
vb[3] = '%'; vb[4] = 0;
|
||||
const char *vp = (g_bright >= 100) ? vb : (g_bright >= 10 ? vb + 1 : vb + 2);
|
||||
du_draw_string(&g_fb, vp, x, ty, KS_ACCENT2, 0, DU_ASCII_STEP);
|
||||
x += 4 * (int)DU_ASCII_STEP + 14;
|
||||
}
|
||||
|
||||
/* BT / WIFI */
|
||||
du_draw_string(&g_fb, "BT OFF", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
x += 6 * (int)DU_ASCII_STEP + 14;
|
||||
du_draw_string(&g_fb, "WIFI --", x, ty, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* 右侧 hint */
|
||||
const char *hint = "Ctrl+Tab PAGE";
|
||||
du_draw_string(&g_fb, hint, W - 12 - kstrlen(hint) * (int)DU_ASCII_STEP, ty,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 页面绘制
|
||||
* ============================================================ */
|
||||
|
||||
static void draw_desktop_icons(void) {
|
||||
/* desktop-empty(Kate:无快捷方式提示) */
|
||||
if (g_icon_count == 0) {
|
||||
zh_draw_string(&g_fb, "桌面无快捷方式",
|
||||
KATE_ICON_PAD, KATE_TOPBAR_H + KATE_PAGE_PAD + KATE_ICON_PAD + 16,
|
||||
KS_TEXT_DIM, 0);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < g_icon_count; i++) {
|
||||
desktop_icon *ic = &g_icons[i];
|
||||
if (g_fluent) {
|
||||
/* M2 Fluent:图标 + 中文标签(zh 字库,缺失自动回退 ASCII) */
|
||||
du_kate_desktop_icon(&g_fb, ic->x, ic->y, "", ic->selected);
|
||||
int ix = ic->x + (KATE_ICON_W - KATE_ICON_IMG) / 2;
|
||||
int iy = ic->y + 10;
|
||||
draw_app_icon(&g_fb, ic->app_id, ix, iy);
|
||||
int lw = zh_string_width(ic->label, 0);
|
||||
zh_draw_string(&g_fb, ic->label,
|
||||
ic->x + (KATE_ICON_W - lw) / 2,
|
||||
ic->y + KATE_ICON_IMG + 16,
|
||||
ic->selected ? 0xFFFFFFFFu : 0xFFE8E8E8u, 0);
|
||||
} else {
|
||||
du_kate_desktop_icon(&g_fb, ic->x, ic->y, ic->label, ic->selected);
|
||||
int ix = ic->x + (KATE_ICON_W - KATE_ICON_IMG) / 2;
|
||||
int iy = ic->y + 10;
|
||||
draw_app_icon(&g_fb, ic->app_id, ix, iy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 1·DASHBOARD ---- */
|
||||
static void dash_layout(void) {
|
||||
int top = KATE_TOPBAR_H, bot = (int)g_fb_h - KATE_STATUSBAR_H;
|
||||
int pad = 8, gap = 8;
|
||||
int avail_w = (int)g_fb_w - 2 * pad;
|
||||
int avail_h = bot - top - 2 * pad;
|
||||
int left_w = avail_w * 35 / 100; /* 终端面板:42%→35% */
|
||||
int right_x = pad + left_w + gap;
|
||||
int right_w = (int)g_fb_w - pad - right_x;
|
||||
int row_h = (avail_h - gap) / 2;
|
||||
|
||||
g_dash_rect[0][0] = pad; g_dash_rect[0][1] = top + pad;
|
||||
g_dash_rect[0][2] = left_w; g_dash_rect[0][3] = row_h;
|
||||
g_dash_rect[1][0] = pad; g_dash_rect[1][1] = top + pad + row_h + gap;
|
||||
g_dash_rect[1][2] = left_w; g_dash_rect[1][3] = row_h;
|
||||
g_dash_rect[2][0] = right_x; g_dash_rect[2][1] = top + pad;
|
||||
g_dash_rect[2][2] = right_w; g_dash_rect[2][3] = row_h;
|
||||
g_dash_rect[3][0] = right_x; g_dash_rect[3][1] = top + pad + row_h + gap;
|
||||
g_dash_rect[3][2] = right_w; g_dash_rect[3][3] = row_h;
|
||||
}
|
||||
|
||||
void draw_dashboard(void) {
|
||||
dash_layout();
|
||||
const char *headers[4] = { "TERM-01", "TERM-02", 0, "FILES" };
|
||||
char ed_hdr[40];
|
||||
editor_state *ed = (editor_state *)ADDR_EDDASH;
|
||||
kstrcpy(ed_hdr, "EDITOR . ", 40);
|
||||
kstrcat(ed_hdr, ed->has_file ? ed->disp : "untitled", 40);
|
||||
headers[2] = ed_hdr;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int x = g_dash_rect[i][0], y = g_dash_rect[i][1];
|
||||
int w = g_dash_rect[i][2], h = g_dash_rect[i][3];
|
||||
du_kate_panel(&g_fb, x, y, w, h, headers[i]);
|
||||
if (g_dash_focus == i) {
|
||||
du_rect_outline(&g_fb, x, y, w, h, KS_ACCENT, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* EDITOR 头 SAVE 按钮(仅关联文件后可用) */
|
||||
{
|
||||
int ex = g_dash_rect[2][0], ey = g_dash_rect[2][1], ew = g_dash_rect[2][2];
|
||||
int bw = 52, bh = 18;
|
||||
int bx = ex + ew - bw - 6, by = ey + 3;
|
||||
g_save_rect[0] = bx; g_save_rect[1] = by;
|
||||
g_save_rect[2] = bw; g_save_rect[3] = bh;
|
||||
u32 fg = ed->has_file ? KS_ACCENT : KS_TEXT_DIM;
|
||||
int hover = ed->has_file &&
|
||||
g_mouse_x >= bx && g_mouse_x < bx + bw &&
|
||||
g_mouse_y >= by && g_mouse_y < by + bh;
|
||||
if (hover) du_fill_rect(&g_fb, bx, by, bw, bh, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, bx, by, bw, bh, ed->has_file ? KS_BORDER : KS_BORDER_DIM, 1);
|
||||
du_draw_string(&g_fb, "SAVE", bx + 5, by + 1, fg, 0, DU_ASCII_STEP);
|
||||
}
|
||||
|
||||
/* 面板内容 */
|
||||
bash_draw_to((bash_state *)ADDR_TERM1,
|
||||
g_dash_rect[0][0] + 2, g_dash_rect[0][1] + KATE_PANEL_HEADER_H + 2,
|
||||
g_dash_rect[0][2] - 4, g_dash_rect[0][3] - KATE_PANEL_HEADER_H - 4);
|
||||
|
||||
bash_draw_to((bash_state *)ADDR_TERM2,
|
||||
g_dash_rect[1][0] + 2, g_dash_rect[1][1] + KATE_PANEL_HEADER_H + 2,
|
||||
g_dash_rect[1][2] - 4, g_dash_rect[1][3] - KATE_PANEL_HEADER_H - 4);
|
||||
|
||||
editor_draw_to(ed,
|
||||
g_dash_rect[2][0] + 2, g_dash_rect[2][1] + KATE_PANEL_HEADER_H + 2,
|
||||
g_dash_rect[2][2] - 4, g_dash_rect[2][3] - KATE_PANEL_HEADER_H - 4);
|
||||
|
||||
/* FILES 面板内容 */
|
||||
{
|
||||
int fx = g_dash_rect[3][0] + 2, fy = g_dash_rect[3][1] + KATE_PANEL_HEADER_H + 2;
|
||||
int fw = g_dash_rect[3][2] - 4, fh = g_dash_rect[3][3] - KATE_PANEL_HEADER_H - 4;
|
||||
du_fill_rect(&g_fb, fx, fy, fw, fh, KS_BG_PRIMARY);
|
||||
|
||||
char path_buf[32];
|
||||
kstrcpy(path_buf, "FAT32:/USER (", 32);
|
||||
int pl = kstrlen(path_buf);
|
||||
if (g_file_count >= 10) path_buf[pl++] = '0' + (char)(g_file_count / 10);
|
||||
path_buf[pl++] = '0' + (char)(g_file_count % 10);
|
||||
path_buf[pl++] = ')'; path_buf[pl] = 0;
|
||||
du_draw_string(&g_fb, path_buf, fx + 8, fy + 3, KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
du_divider_h(&g_fb, fx, fy + 22, fw, KS_BORDER);
|
||||
|
||||
int row_h = 22;
|
||||
int max_rows = (fh - 24) / row_h;
|
||||
for (int i = 0; i < g_file_count && i < max_rows; i++) {
|
||||
int ry = fy + 24 + i * row_h;
|
||||
if (i == g_file_sel) {
|
||||
du_fill_rect(&g_fb, fx, ry, fw, row_h, KS_ACCENT_DIM);
|
||||
}
|
||||
int hover = (g_mouse_x >= fx && g_mouse_x < fx + fw &&
|
||||
g_mouse_y >= ry && g_mouse_y < ry + row_h);
|
||||
if (hover && i != g_file_sel) {
|
||||
du_fill_rect(&g_fb, fx, ry, fw, row_h, KS_ACCENT_DIM);
|
||||
}
|
||||
u32 fg = (i == g_file_sel) ? KS_ACCENT : KS_TEXT_PRIMARY;
|
||||
du_draw_string(&g_fb, g_files[i].disp, fx + 8, ry + 2, fg, 0, DU_ASCII_STEP);
|
||||
char sz[12];
|
||||
u32 kb = g_files[i].size / 1024;
|
||||
int sp = 0;
|
||||
if (kb >= 100) { sz[sp++] = '0' + (char)(kb / 100 % 10); }
|
||||
if (kb >= 10) { sz[sp++] = '0' + (char)(kb / 10 % 10); }
|
||||
sz[sp++] = '0' + (char)(kb % 10);
|
||||
sz[sp++] = 'K'; sz[sp] = 0;
|
||||
du_draw_string(&g_fb, sz, fx + fw - sp * (int)DU_ASCII_STEP - 8, ry + 2,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
}
|
||||
if (!g_files_loaded) {
|
||||
du_draw_string(&g_fb, "NO BLOCK DEVICE", fx + 8, fy + 28,
|
||||
KS_DANGER, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
} else if (g_file_count == 0) {
|
||||
du_draw_string(&g_fb, "(empty)", fx + 8, fy + 28,
|
||||
KS_TEXT_DIM, KS_BG_PRIMARY, DU_ASCII_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
draw_statusbar_full();
|
||||
}
|
||||
|
||||
/* ---- 2·IDE ---- */
|
||||
void draw_ide(void) {
|
||||
int W = (int)g_fb_w;
|
||||
int top = KATE_TOPBAR_H;
|
||||
|
||||
/* ide-tabs(36px):dot + 实例标签 + "+ NEW IDE" + ide-hint(Kate 结构) */
|
||||
du_fill_rect(&g_fb, 0, top, W, 36, 0xFF051828u);
|
||||
du_divider_h(&g_fb, 0, top + 36, W, KS_BORDER);
|
||||
int x = 12;
|
||||
int ty = top + (36 - (int)DU_ASCII_LINE_H) / 2 + 1;
|
||||
|
||||
/* .dot(8×8 accent2 发光点) */
|
||||
du_fill_rect(&g_fb, x, top + 14, 8, 8, KS_ACCENT2);
|
||||
x += 16;
|
||||
|
||||
/* 实例标签(Kate .ide-tab):P7.4 动态标签 = 最近启动实例 DISPLAY 名 */
|
||||
{
|
||||
const char *label = g_ide_tab_label;
|
||||
int bw = kstrlen(label) * (int)DU_ASCII_STEP + 32;
|
||||
if (g_ide_attached) {
|
||||
du_fill_rect(&g_fb, x, top + 5, bw, 26, KS_ACCENT);
|
||||
du_kate_glow_border(&g_fb, x, top + 5, bw, 26, KS_ACCENT);
|
||||
}
|
||||
du_rect_outline(&g_fb, x, top + 5, bw, 26,
|
||||
g_ide_attached ? KS_ACCENT : KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, label, x + 8, ty,
|
||||
g_ide_attached ? KS_TEXT_INVERT : KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
du_draw_string(&g_fb, "x", x + bw - 14, ty, KS_DANGER, 0, DU_ASCII_STEP);
|
||||
g_ide_tabx[0] = x + bw - 20; g_ide_tabx[1] = top + 5;
|
||||
g_ide_tabx[2] = 20; g_ide_tabx[3] = 26;
|
||||
x += bw + 8;
|
||||
}
|
||||
|
||||
/* "+ NEW IDE" 按钮(Kate .btn,透明底 + 边框 + accent 字) */
|
||||
{
|
||||
const char *nb = "+ NEW IDE";
|
||||
int bw = kstrlen(nb) * (int)DU_ASCII_STEP + 20;
|
||||
g_ide_new_rect[0] = x; g_ide_new_rect[1] = top + 7;
|
||||
g_ide_new_rect[2] = bw; g_ide_new_rect[3] = 22;
|
||||
int hover = g_mouse_x >= x && g_mouse_x < x + bw &&
|
||||
g_mouse_y >= top + 7 && g_mouse_y < top + 29;
|
||||
if (hover) du_fill_rect(&g_fb, x, top + 7, bw, 22, KS_ACCENT_DIM);
|
||||
du_rect_outline(&g_fb, x, top + 7, bw, 22, KS_BORDER, 1);
|
||||
du_draw_string(&g_fb, nb, x + 10, ty, KS_ACCENT, 0, DU_ASCII_STEP);
|
||||
x += bw + 8;
|
||||
}
|
||||
|
||||
/* ide-hint */
|
||||
du_draw_string(&g_fb, "CTRL+SHIFT+TAB CTX . x DETACH . DBL-CLICK HOST ATTACH",
|
||||
x + 8, ty + 1, KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* page-credit(对应 Kate "由 Deaicup 工作室制作") */
|
||||
du_draw_string(&g_fb, "DEAICUP STUDIO",
|
||||
W - 12 - 14 * (int)DU_ASCII_STEP, ty + 1,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
|
||||
/* ide-host(虚线,margin 8px) */
|
||||
int hx = 8, hy = top + 36 + 8;
|
||||
int hw = W - 16, hh = (int)g_fb_h - hy - 8;
|
||||
g_ide_host[0] = hx; g_ide_host[1] = hy;
|
||||
g_ide_host[2] = hw; g_ide_host[3] = hh;
|
||||
draw_dashed_rect(hx, hy, hw, hh, KS_BORDER);
|
||||
|
||||
if (g_ide_attached) {
|
||||
if (g_input_forward_enabled) {
|
||||
/* VSCode Phase 6 状态机:启动中探测 scanout 上线 → 运行中 */
|
||||
if (g_vscode_state == 1) {
|
||||
const struct linux_compat_scanout_info *si = ide_scanout_state();
|
||||
if (si && si->enabled) {
|
||||
g_vscode_state = 2;
|
||||
g_launch_phase = 6;
|
||||
g_so_full_repaint = 1; /* 上线首帧全量重绘 */
|
||||
/* P8.1: scanout 上线日志 + 耗时 */
|
||||
slog("[launch] phase 6: SCANOUT ONLINE");
|
||||
if (g_tsc_per_sec && g_launch_tsc) {
|
||||
u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec;
|
||||
slog_num("[launch] total launch time ms=", (int)ms);
|
||||
}
|
||||
{
|
||||
char sbuf[48];
|
||||
kstrcpy(sbuf, "scanout: ", 48);
|
||||
u32dec(si->width, sbuf + kstrlen(sbuf));
|
||||
kstrcat(sbuf, "x", 48);
|
||||
u32dec(si->height, sbuf + kstrlen(sbuf));
|
||||
slog(sbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (g_vscode_state == 2) {
|
||||
/* 运行中:blit guest 合成帧 */
|
||||
blit_scanout_to_ide(hx + 2, hy + 2, hw - 4, hh - 4);
|
||||
} else {
|
||||
/* P8.1: 启动中--显示分阶段状态 + 详细信息 */
|
||||
du_fill_rect(&g_fb, hx + 2, hy + 2, hw - 4, hh - 4, 0xFF051828u);
|
||||
draw_centered(g_launch_status[0] ? g_launch_status :
|
||||
"STARTING VSCODE . . .",
|
||||
W / 2, hy + hh / 2 - 20, KS_TEXT_DIM, 0);
|
||||
/* 详细信息行(phase/exec_rc/pgrep/耗时) */
|
||||
{
|
||||
char dbuf[80];
|
||||
int dp = 0;
|
||||
kstrcpy(dbuf + dp, "PHASE:", 80); dp = kstrlen(dbuf);
|
||||
dp += u32dec((u32)g_launch_phase, dbuf + dp);
|
||||
if (g_launch_exec_rc != 0) {
|
||||
kstrcpy(dbuf + dp, " RC:", 80); dp = kstrlen(dbuf);
|
||||
dp += u32dec((u32)(g_launch_exec_rc < 0 ?
|
||||
(u32)(-g_launch_exec_rc) : (u32)g_launch_exec_rc),
|
||||
dbuf + dp);
|
||||
}
|
||||
if (g_launch_pgrep_rc >= 0) {
|
||||
kstrcpy(dbuf + dp, " PGREP:", 80); dp = kstrlen(dbuf);
|
||||
kstrcpy(dbuf + dp, g_launch_pgrep_rc == 0 ? "FOUND" : "MISSING",
|
||||
80);
|
||||
dp = kstrlen(dbuf);
|
||||
}
|
||||
if (g_tsc_per_sec && g_launch_tsc) {
|
||||
u64 ms = (rdtsc() - g_launch_tsc) * 1000 / g_tsc_per_sec;
|
||||
kstrcpy(dbuf + dp, " T:", 80); dp = kstrlen(dbuf);
|
||||
dp += u32dec((u32)ms, dbuf + dp);
|
||||
kstrcpy(dbuf + dp, "ms", 80);
|
||||
}
|
||||
draw_centered(dbuf, W / 2, hy + hh / 2 + 4, KS_TEXT_DIM, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
editor_draw_to((editor_state *)ADDR_EDDASH, hx + 2, hy + 2, hw - 4, hh - 4);
|
||||
}
|
||||
} else {
|
||||
/* embed-empty(Kate:未启动 VSCode 提示) */
|
||||
draw_centered("VSCODE NOT RUNNING . DOUBLE-CLICK OR + NEW IDE TO START",
|
||||
W / 2, hy + hh / 2 - 8, KS_TEXT_DIM, 0);
|
||||
}
|
||||
|
||||
/* P7.9: 性能 profiling 叠加(IDE 页面左下角) */
|
||||
if (g_ide_attached && g_input_forward_enabled) {
|
||||
char buf[80];
|
||||
int p = 0;
|
||||
kstrcpy(buf + p, "FPS:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_fps, buf + p);
|
||||
kstrcpy(buf + p, " BLT:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_last_blit_us, buf + p);
|
||||
kstrcpy(buf + p, "us SKP:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_blit_skip, buf + p);
|
||||
kstrcpy(buf + p, " FUL:", 80 - p); p = kstrlen(buf);
|
||||
p += u32dec((u32)g_prof_blit_full, buf + p);
|
||||
du_draw_string(&g_fb, buf, hx + 6, hy + hh - 16,
|
||||
KS_TEXT_DIM, 0, DU_ASCII_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 3·DESKTOP ---- */
|
||||
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);
|
||||
}
|
||||
|
||||
if (!g_fluent) { /* KATE 扫描线特效;Fluent 窗口干净背景 */
|
||||
du_kate_scanlines(&g_fb, w->x + 1, w->y + KATE_TITLEBAR_H,
|
||||
w->w - 2, w->h - KATE_TITLEBAR_H - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* M2 Fluent:悬浮任务栏由 compositor(redraw_all_fluent)统一叠加,
|
||||
* 此处跳过旧 40px 任务栏。 */
|
||||
if (g_fluent) return;
|
||||
|
||||
/* 任务栏(40px,KATE 回退路径) */
|
||||
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 ---- */
|
||||
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_w(cp->elf11, disp);
|
||||
kstrcat(sub, disp, 40);
|
||||
}
|
||||
draw_centered(sub, hx + hw / 2, hy + hh / 2 + 12, KS_ACCENT, 0);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 任务视图 overlay
|
||||
* ============================================================ */
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 右键菜单(移到工作区)
|
||||
* ============================================================ */
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
/* shell.c — 工作区/页面状态机 + 任务视图/右键菜单 + 事件路由(M0 自 main.c 平移)
|
||||
*/
|
||||
#include "desktop.h"
|
||||
|
||||
/* ---- 全局状态 ---- */
|
||||
int g_page = 1; /* 1..3 内置, 4..8 自定义 */
|
||||
int g_taskview = 0;
|
||||
int g_dash_focus = 0; /* 0=t1 1=t2 2=ed 3=files */
|
||||
|
||||
workspace g_ws[MAX_WS];
|
||||
int g_ws_count = 1;
|
||||
int g_ws_cur = 0;
|
||||
|
||||
cpage g_cpages[MAX_CPAGES];
|
||||
int g_cpage_count = 0;
|
||||
|
||||
/* 右键菜单(移到工作区) */
|
||||
int g_ctx_open = 0;
|
||||
int g_ctx_x = 0, g_ctx_y = 0;
|
||||
int g_ctx_win = -1;
|
||||
|
||||
/* 状态栏滑块 */
|
||||
int g_vol = 65;
|
||||
int g_bright = 100;
|
||||
int g_bright_applied = 100; /* 上次应用的亮度值(避免重复处理) */
|
||||
int g_drag_slider = 0; /* 1=vol 2=bright */
|
||||
int g_vol_rect[4]; /* x,y,w,h 滑轨 */
|
||||
int g_bri_rect[4];
|
||||
|
||||
int g_quit = 0; /* EXIT 按钮 → 返回 DSK */
|
||||
|
||||
/* 双击检测 */
|
||||
u64 g_last_click_tsc = 0;
|
||||
int g_last_click_x = -100, g_last_click_y = -100;
|
||||
|
||||
/* 面板/控件命中矩形(绘制时更新,点击时使用) */
|
||||
int g_dash_rect[4][4]; /* 4 面板 x,y,w,h */
|
||||
int g_save_rect[4];
|
||||
int g_pg_rect[8][4]; /* 页面切换按钮 */
|
||||
int g_pg_rm_rect[8][4];
|
||||
int g_pgadd_rect[4];
|
||||
int g_tvbtn_rect[4]; /* 任务视图按钮 */
|
||||
int g_ws_rect[MAX_WS][4];
|
||||
int g_wsadd_rect[4];
|
||||
int g_tb_rect[MAX_WINDOWS][4]; /* 任务栏项 */
|
||||
int g_tb_close_rect[MAX_WINDOWS][4];
|
||||
int g_tb_quit_rect[4];
|
||||
int g_tb_adopt_rect[4]; /* 收纳窗口按钮 */
|
||||
int g_tv_card[MAX_WS + 1][4]; /* 任务视图卡片(+add) */
|
||||
int g_tv_close[MAX_WS][4];
|
||||
int g_ctx_rect[4];
|
||||
int g_ctx_item[MAX_WS + 1][4];
|
||||
int g_ctx_item_ws[MAX_WS + 1]; /* 菜单行 → 真实工作区索引,-1=新建 */
|
||||
int g_ide_host[4];
|
||||
int g_ide_tabx[4]; /* IDE 标签 × */
|
||||
int g_ide_new_rect[4]; /* IDE "+ NEW IDE" 按钮 */
|
||||
int g_cp_host[4]; /* 自定义页宿主 */
|
||||
int g_cp_new_rect[4]; /* 自定义页 "+ NEW INSTANCE" 按钮 */
|
||||
|
||||
/* ============================================================
|
||||
* 工作区 / 页面管理
|
||||
* ============================================================ */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void page_switch(int p) {
|
||||
if (p < 1 || p > 3 + g_cpage_count) return;
|
||||
g_page = p;
|
||||
}
|
||||
|
||||
/* 自定义页:为尚未挂载的外部 .elf 应用新建页面 */
|
||||
static void cpage_add(void) {
|
||||
if (g_cpage_count >= MAX_CPAGES) return;
|
||||
for (int a = 0; a < g_app_count; a++) {
|
||||
if (!g_apps[a].elf_name) continue;
|
||||
int dup = 0;
|
||||
for (int i = 0; i < g_cpage_count; i++)
|
||||
if (f32_neq11_w(g_cpages[i].elf11, g_apps[a].elf_name)) { dup = 1; break; }
|
||||
if (dup) continue;
|
||||
cpage *cp = &g_cpages[g_cpage_count];
|
||||
int n = kstrlen(g_apps[a].display_name);
|
||||
if (n > 11) n = 11;
|
||||
for (int i = 0; i < n; i++) cp->name[i] = g_apps[a].display_name[i];
|
||||
cp->name[n] = 0;
|
||||
for (int i = 0; i < 11; i++) cp->elf11[i] = g_apps[a].elf_name[i];
|
||||
cp->id = 4 + g_cpage_count;
|
||||
g_cpage_count++;
|
||||
g_page = cp->id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void cpage_remove(int idx) {
|
||||
if (idx < 0 || idx >= g_cpage_count) return;
|
||||
int removed_page = g_cpages[idx].id;
|
||||
for (int i = idx; i < g_cpage_count - 1; i++) g_cpages[i] = g_cpages[i + 1];
|
||||
g_cpage_count--;
|
||||
for (int i = 0; i < g_cpage_count; i++) g_cpages[i].id = 4 + i;
|
||||
if (g_page == removed_page) g_page = 1;
|
||||
else if (g_page > removed_page) g_page--;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 事件处理
|
||||
* ============================================================ */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 构建"移到工作区"列表(排除当前工作区,与 Winux-Kate 一致),返回行数 */
|
||||
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;
|
||||
}
|
||||
|
||||
/* ---- 1·DASHBOARD 点击 ---- */
|
||||
static int dash_click(int mx, int my, int dbl) {
|
||||
editor_state *ed = (editor_state *)ADDR_EDDASH;
|
||||
|
||||
/* SAVE 按钮 */
|
||||
if (ed->has_file && rect_hit(g_save_rect, mx, my)) { editor_save(); return 1; }
|
||||
|
||||
/* 面板聚焦 */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!rect_hit(g_dash_rect[i], mx, my)) continue;
|
||||
g_dash_focus = i;
|
||||
if (i == 3) {
|
||||
/* FILES 行命中 / 双击打开 */
|
||||
int fx = g_dash_rect[3][0] + 2;
|
||||
int fy = g_dash_rect[3][1] + KATE_PANEL_HEADER_H + 2;
|
||||
int fw = g_dash_rect[3][2] - 4;
|
||||
int fh = g_dash_rect[3][3] - KATE_PANEL_HEADER_H - 4;
|
||||
if (mx >= fx && mx < fx + fw && my >= fy + 24) {
|
||||
int max_rows = (fh - 24) / 22;
|
||||
int row = (my - fy - 24) / 22;
|
||||
if (row >= 0 && row < max_rows && row < g_file_count) {
|
||||
if (dbl && row == g_file_sel) files_open(row);
|
||||
else g_file_sel = row;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 状态栏滑块 */
|
||||
if (rect_hit(g_vol_rect, mx, my)) {
|
||||
g_drag_slider = 1;
|
||||
slider_set(g_vol_rect, &g_vol, mx);
|
||||
return 1;
|
||||
}
|
||||
if (rect_hit(g_bri_rect, mx, my)) {
|
||||
g_drag_slider = 2;
|
||||
slider_set(g_bri_rect, &g_bright, mx);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 2·IDE 点击 ---- */
|
||||
static int ide_click(int mx, int my, int dbl) {
|
||||
if (g_ide_attached && rect_hit(g_ide_tabx, mx, my)) {
|
||||
g_ide_attached = 0;
|
||||
/* VSCode Phase 2: detach 时停止向 guest 转发输入 */
|
||||
g_input_forward_enabled = 0;
|
||||
ide_attach_invalidate(); /* P7.3: detach 后下次 attach 全量重绘 */
|
||||
slog("ide detached, input forwarding off");
|
||||
return 1;
|
||||
}
|
||||
if (!g_ide_attached && rect_hit(g_ide_new_rect, mx, my)) {
|
||||
g_ide_attached = 1;
|
||||
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
||||
/* attach 时仅当 Linux guest 服务可用才开启转发;否则降级为本地 editor */
|
||||
g_input_forward_enabled = (g_lxc_svc != 0);
|
||||
slog(g_input_forward_enabled ? "ide attached, forwarding to guest"
|
||||
: "ide attached, lxc unavailable (local editor)");
|
||||
/* VSCode Phase 6: 共享启动路径(X server + VSCode) */
|
||||
if (g_input_forward_enabled) vscode_launch();
|
||||
return 1;
|
||||
}
|
||||
if (!g_ide_attached && dbl && rect_hit(g_ide_host, mx, my)) {
|
||||
g_ide_attached = 1;
|
||||
ide_attach_invalidate(); /* P7.3: attach 首帧全量重绘 */
|
||||
g_input_forward_enabled = (g_lxc_svc != 0);
|
||||
slog(g_input_forward_enabled ? "ide attached (dbl), forwarding to guest"
|
||||
: "ide attached (dbl), lxc unavailable (local editor)");
|
||||
/* VSCode Phase 6: 共享启动路径(X server + VSCode) */
|
||||
if (g_input_forward_enabled) vscode_launch();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 4·DESKTOP 点击 ---- */
|
||||
static int desktop_click(int mx, int my, int dbl) {
|
||||
/* M2 Fluent:悬浮任务栏 + 托盘优先路由 */
|
||||
if (g_fluent && taskbar_click(mx, my)) return 1;
|
||||
|
||||
int tb_y = (int)g_fb_h - (g_fluent ? 56 : KATE_TASKBAR_H);
|
||||
|
||||
/* 任务栏(仅 KATE 回退路径;Fluent 已由 taskbar_click 消费) */
|
||||
if (!g_fluent && my >= tb_y) {
|
||||
if (rect_hit(g_tb_quit_rect, mx, my)) { g_quit = 1; return 1; }
|
||||
if (rect_hit(g_tb_adopt_rect, mx, my)) {
|
||||
/* 收纳窗口:全部工作区窗口收归当前工作区并还原最小化 */
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
g_windows[i].ws = g_ws_cur;
|
||||
g_windows[i].minimized = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (!g_tb_rect[i][2] || !rect_hit(g_tb_rect[i], mx, my)) continue;
|
||||
if (rect_hit(g_tb_close_rect[i], mx, my)) {
|
||||
win_close_request(&g_windows[i]);
|
||||
} else {
|
||||
win_focus(&g_windows[i]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 窗口 */
|
||||
desktop_window *w = win_find_at(mx, my);
|
||||
if (w) {
|
||||
int wid = w->id;
|
||||
win_focus(w);
|
||||
w = win_find(wid); /* focus 重排后重新定位 */
|
||||
if (!w) return 1;
|
||||
/* M2 Fluent:右上三钮(关闭/最大化/最小化) */
|
||||
if (g_fluent) {
|
||||
if (win_hit_close_btn(w, mx, my)) { win_close_request(w); return 1; }
|
||||
if (win_hit_max_btn(w, mx, my)) {
|
||||
/* 最大化/还原:铺满桌面区(顶部 0 到任务栏上方) */
|
||||
if (w->w >= (int)g_fb_w - 8 && w->h >= (int)g_fb_h - 64) {
|
||||
w->x = 60; w->y = 40; w->w = 720; w->h = 500; /* 还原 */
|
||||
} else {
|
||||
w->x = 0; w->y = 0;
|
||||
w->w = (int)g_fb_w;
|
||||
w->h = (int)g_fb_h - 56; /* 任务栏悬浮区预留 */
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (win_hit_min_btn(w, mx, my)) {
|
||||
w->minimized = 1;
|
||||
w->focused = 0;
|
||||
g_focused_win = -1;
|
||||
return 1;
|
||||
}
|
||||
} else if (win_hit_close_btn(w, mx, my)) { win_close_request(w); return 1; }
|
||||
if (win_hit_titlebar(w, mx, my)) {
|
||||
g_dragging = 1;
|
||||
g_drag_win = w->id;
|
||||
g_drag_off_x = mx - w->x;
|
||||
g_drag_off_y = my - w->y;
|
||||
return 1;
|
||||
}
|
||||
/* 客户区事件 → 应用 */
|
||||
if (w->app_id >= 0 && w->app_id < g_app_count &&
|
||||
g_apps[w->app_id].on_event && w->app_state) {
|
||||
desktop_event dev;
|
||||
dev.type = EV_MOUSE_DOWN;
|
||||
dev.mx = mx; dev.my = my;
|
||||
dev.button = 0; dev.scancode = 0;
|
||||
dev.shift = g_shift; dev.ctrl = g_ctrl; dev.alt = g_alt;
|
||||
app_ctx actx;
|
||||
fill_app_ctx(&actx, w);
|
||||
g_apps[w->app_id].on_event(w->app_state, &actx, &dev);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 图标(单击选中 / 双击启动) */
|
||||
for (int i = 0; i < g_icon_count; i++) {
|
||||
int ix = g_icons[i].x, iy = g_icons[i].y;
|
||||
if (mx >= ix && mx < ix + ICON_W && my >= iy && my < iy + ICON_H) {
|
||||
if (dbl && g_icons[i].selected) {
|
||||
launch_app(g_icons[i].app_id);
|
||||
} else {
|
||||
for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0;
|
||||
g_icons[i].selected = 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 空白:取消选择 */
|
||||
for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- 左键按下 ---- */
|
||||
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;
|
||||
}
|
||||
|
||||
/* 顶栏(仅 KATE 回退路径;Fluent 无 topbar,点击直达页面内容) */
|
||||
if (!g_fluent && my < KATE_TOPBAR_H) {
|
||||
if (rect_hit(g_tvbtn_rect, mx, my)) { g_taskview = 1; return 1; }
|
||||
for (int i = 0; i < g_ws_count; i++)
|
||||
if (rect_hit(g_ws_rect[i], mx, my)) { ws_switch(i); return 1; }
|
||||
if (rect_hit(g_wsadd_rect, mx, my)) { ws_create(); return 1; }
|
||||
for (int i = 0; i < 3 + g_cpage_count; i++) {
|
||||
if (i >= 3 && rect_hit(g_pg_rm_rect[i], mx, my)) {
|
||||
cpage_remove(i - 3);
|
||||
return 1;
|
||||
}
|
||||
if (rect_hit(g_pg_rect[i], mx, my)) { page_switch(i + 1); return 1; }
|
||||
}
|
||||
if (rect_hit(g_pgadd_rect, mx, my)) { cpage_add(); return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 页面内容 */
|
||||
if (g_page == 1) return dash_click(mx, my, dbl);
|
||||
if (g_page == 2) return ide_click(mx, my, dbl);
|
||||
if (g_page == 3) return desktop_click(mx, my, dbl);
|
||||
if (g_page >= 4) {
|
||||
int launch = 0;
|
||||
if (rect_hit(g_cp_new_rect, mx, my)) launch = 1;
|
||||
if (dbl && rect_hit(g_cp_host, mx, my)) launch = 1;
|
||||
if (launch) {
|
||||
for (int i = 0; i < g_cpage_count; i++) {
|
||||
if (g_cpages[i].id != g_page) continue;
|
||||
launch_external_elf(g_cpages[i].elf11);
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 右键按下:窗口/任务栏项 → 移到工作区菜单 ---- */
|
||||
int on_right_press(void) {
|
||||
int mx = g_mouse_x, my = g_mouse_y;
|
||||
|
||||
if (g_taskview) { g_taskview = 0; return 1; }
|
||||
if (g_ctx_open) { g_ctx_open = 0; return 1; }
|
||||
if (g_page != 3) return 0;
|
||||
|
||||
int tb_y = (int)g_fb_h - KATE_TASKBAR_H;
|
||||
if (my >= tb_y) {
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (!g_tb_rect[i][2] || !rect_hit(g_tb_rect[i], mx, my)) continue;
|
||||
int lmap[MAX_WS];
|
||||
int ln = ctx_list_build(lmap);
|
||||
int mh = 24 + ((ln ? ln : 1) + 1) * 22 + 8;
|
||||
g_ctx_win = g_windows[i].id;
|
||||
g_ctx_x = mx;
|
||||
g_ctx_y = my - mh; /* 菜单向上展开 */
|
||||
if (g_ctx_y < KATE_TOPBAR_H) g_ctx_y = my;
|
||||
g_ctx_open = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
desktop_window *w = win_find_at(mx, my);
|
||||
if (w) {
|
||||
g_ctx_win = w->id;
|
||||
g_ctx_x = mx; g_ctx_y = my;
|
||||
g_ctx_open = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 键盘 ---- */
|
||||
int handle_key(u8 sc) {
|
||||
/* VSCode Phase 2: IDE attached 且在 IDE 页时,把按键序列转发到 Linux guest。
|
||||
* 0xE0 前缀字节本身不转发(无对应 keycode),仅靠 g_e0 标志让下一字节选取扩展键变体。
|
||||
* 转发在修饰键跟踪与页面路由之前完成,保证 VSCode 收到完整 press/release
|
||||
* 序列(含 Shift/Ctrl/Alt/方向键)。宿主修饰键跟踪继续运行,Ctrl+Tab 切页、
|
||||
* Ctrl+S 等热键仍可在宿主侧生效,便于用户随时切离 IDE 页。 */
|
||||
if (g_input_forward_enabled && g_lxc_svc && g_lxc_svc->input_forward_keyboard &&
|
||||
sc != 0xE0 && g_page == 2) {
|
||||
u8 base = (u8)(sc & 0x7F);
|
||||
u16 code = scancode_to_linux_keycode(base, g_e0);
|
||||
if (code) {
|
||||
u32 value = (sc & 0x80) ? 0 : 1; /* 0x80 位 = release */
|
||||
g_lxc_svc->input_forward_keyboard(code, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (sc == 0xE0) { g_e0 = 1; return 0; }
|
||||
if (sc == 0x2A || sc == 0x36) { g_shift = 1; return 0; }
|
||||
if (sc == 0xAA || sc == 0xB6) { g_shift = 0; return 0; }
|
||||
if (sc == 0x1D) { g_ctrl = 1; g_e0 = 0; return 0; }
|
||||
if (sc == 0x9D) { g_ctrl = 0; return 0; }
|
||||
if (sc == 0x38) { g_alt = 1; g_e0 = 0; return 0; }
|
||||
if (sc == 0xB8) { g_alt = 0; return 0; }
|
||||
if (sc & 0x80) { g_e0 = 0; return 0; } /* 其他释放码忽略 */
|
||||
|
||||
/* Esc:关闭 overlay */
|
||||
if (sc == 0x01) {
|
||||
if (g_taskview) { g_taskview = 0; return 1; }
|
||||
if (g_ctx_open) { g_ctx_open = 0; return 1; }
|
||||
return 0;
|
||||
}
|
||||
if (g_taskview || g_ctx_open) return 0; /* 模态期间吞掉按键 */
|
||||
|
||||
/* Ctrl+Tab 切页 / Ctrl+Shift+Tab 上下文菜单(DESKTOP 页) */
|
||||
if (g_ctrl && sc == 0x0F) {
|
||||
if (g_shift) {
|
||||
/* Ctrl+Shift+Tab:对当前聚焦窗口打开"移到工作区"菜单 */
|
||||
if (g_page == 3 && win_find(g_focused_win)) {
|
||||
g_ctx_win = g_focused_win;
|
||||
g_ctx_x = g_mouse_x; g_ctx_y = g_mouse_y;
|
||||
g_ctx_open = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int maxp = 3 + g_cpage_count;
|
||||
int p = g_page + 1;
|
||||
if (p > maxp) p = 1;
|
||||
page_switch(p);
|
||||
return 1;
|
||||
}
|
||||
if (g_ctrl && sc == 0x1F) { editor_save(); return 1; }
|
||||
|
||||
/* 页面路由 */
|
||||
if (g_page == 1) {
|
||||
if (g_dash_focus <= 1) {
|
||||
bash_key((bash_state *)(g_dash_focus == 0 ? ADDR_TERM1 : ADDR_TERM2),
|
||||
sc, g_shift);
|
||||
return 1;
|
||||
}
|
||||
if (g_dash_focus == 2) {
|
||||
editor_key((editor_state *)ADDR_EDDASH, sc, g_shift);
|
||||
return 1;
|
||||
}
|
||||
/* FILES 面板导航 */
|
||||
if (sc == 0x48) { if (g_file_sel > 0) g_file_sel--; return 1; }
|
||||
if (sc == 0x50) { if (g_file_sel < g_file_count - 1) g_file_sel++; return 1; }
|
||||
if (sc == 0x1C) { files_open(g_file_sel); return 1; }
|
||||
return 0;
|
||||
}
|
||||
if (g_page == 2) {
|
||||
if (g_ide_attached) {
|
||||
if (g_input_forward_enabled) {
|
||||
/* VSCode Phase 2: 键盘已转发到 guest 中的 VSCode,
|
||||
* 本地 editor 不再重复处理,避免双输入。 */
|
||||
return 1;
|
||||
}
|
||||
editor_key((editor_state *)ADDR_EDDASH, sc, g_shift);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (g_page == 3) {
|
||||
desktop_window *fw = win_find(g_focused_win);
|
||||
if (fw && fw->ws == g_ws_cur && fw->app_id >= 0 && fw->app_id < g_app_count &&
|
||||
g_apps[fw->app_id].on_event && fw->app_state) {
|
||||
desktop_event dev;
|
||||
dev.type = EV_KEY_DOWN;
|
||||
dev.mx = g_mouse_x; dev.my = g_mouse_y;
|
||||
dev.button = 0;
|
||||
dev.scancode = sc;
|
||||
dev.shift = g_shift; dev.ctrl = g_ctrl; dev.alt = g_alt;
|
||||
app_ctx actx;
|
||||
fill_app_ctx(&actx, fw);
|
||||
g_apps[fw->app_id].on_event(fw->app_state, &actx, &dev);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/* taskbar.c — Win11 Fluent 居中悬浮任务栏 + 系统托盘(M2)
|
||||
*
|
||||
* 布局(深色主题 g_wf_dark):
|
||||
* 主任务栏:48px 高,距底 8px,水平居中悬浮,acrylic 圆角 12
|
||||
* [开始 40×40] [任务视图 40×40] [运行窗口按钮 44×44 ...]
|
||||
* 运行中 = 底部 3×16 accent 指示条;聚焦 = 指示条加宽 6px + 微亮蒙层
|
||||
* 托盘组(右下角独立悬浮,同视觉):
|
||||
* [网络 40] [音量 40] [电源 40] [两行时钟:HH:MM / M-D]
|
||||
*
|
||||
* 点击路由 taskbar_click() 由 shell.c 在 Fluent 分支调用。
|
||||
* 开始菜单 M3 接入;电源 M2 直接退出(M7 扩展菜单)。
|
||||
*/
|
||||
#include "desktop.h"
|
||||
#include "ascii_font.h" /* 顺序敏感:先于 fluent_ui.h */
|
||||
#include "../UTSM/include/utsm/fluent_ui.h"
|
||||
#include "zhfont.h"
|
||||
#include "textures.h"
|
||||
|
||||
/* ---- 命中矩形(绘制时更新,点击时使用) ---- */
|
||||
int g_tb2_start_rect[4];
|
||||
int g_tb2_tv_rect[4];
|
||||
int g_tb2_app_rect[MAX_WINDOWS][4];
|
||||
int g_tb2_tray_rect[4]; /* 托盘组整体 */
|
||||
int g_tb2_net_rect[4];
|
||||
int g_tb2_vol_rect[4];
|
||||
int g_tb2_pwr_rect[4];
|
||||
int g_tb2_clk_rect[4];
|
||||
|
||||
#define TB_H 48
|
||||
#define TB_BOTTOM_GAP 8
|
||||
#define TB_BTN 40 /* 开始/任务视图/托盘钮边长 */
|
||||
#define TB_APPBTN 44 /* 应用钮边长 */
|
||||
#define TB_PAD 8 /* 条内边距 */
|
||||
|
||||
static const wf_theme *tb_theme(void) { return &g_wf_dark; }
|
||||
|
||||
/* ---- 开始菜单 logo(textures/startMenuLogo.rgba,加载失败回退字形) ---- */
|
||||
static int g_smlogo_ok = 0;
|
||||
|
||||
typedef int (*tb_read_fn)(const char *path, unsigned char *dst,
|
||||
unsigned int cap, unsigned int *out_size);
|
||||
|
||||
int taskbar_init(tb_read_fn read_to) {
|
||||
u32 sz = 0;
|
||||
if (!read_to) return -1;
|
||||
if (read_to("system/deshab64/desktop/textures/startMenuLogo.rgba",
|
||||
(u8 *)SMLOGO_ADDR, SMLOGO_W * SMLOGO_H * 4u, &sz) != 0)
|
||||
return -2;
|
||||
if (sz < SMLOGO_W * SMLOGO_H * 4u) return -3;
|
||||
g_smlogo_ok = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* app_id → Fluent 图标 */
|
||||
static wf_icon_id tb_app_icon(int app_id) {
|
||||
if (app_id < 0 || app_id >= g_app_count) return WF_ICON_TERMINAL;
|
||||
const char *n = g_apps[app_id].name;
|
||||
if (!n) return WF_ICON_FOLDER;
|
||||
if (n[0]=='s' && n[1]=='h') return WF_ICON_TERMINAL; /* shell */
|
||||
if (n[0]=='c' && n[1]=='m' && n[2]=='d') return WF_ICON_TERMINAL;
|
||||
if (n[0]=='e' && n[1]=='d') return WF_ICON_EDITOR; /* editor */
|
||||
if (n[0]=='p' && n[1]=='r') return WF_ICON_EDITOR; /* proedit */
|
||||
if (n[0]=='f' && n[1]=='i') return WF_ICON_FOLDER; /* fileman */
|
||||
if (n[0]=='c' && n[1]=='a') return WF_ICON_CALC; /* calc */
|
||||
if (n[0]=='b' && n[1]=='r') return WF_ICON_BROWSER; /* browser */
|
||||
if (n[0]=='s' && n[1]=='e') return WF_ICON_SETTINGS; /* settings */
|
||||
if (n[0]=='v' || n[0]=='k') return WF_ICON_BROWSER; /* vscode/kde */
|
||||
return WF_ICON_FOLDER;
|
||||
}
|
||||
|
||||
static wf_btn_state tb_hover_state(const int r[4]) {
|
||||
if (g_mouse_x >= r[0] && g_mouse_x < r[0] + r[2] &&
|
||||
g_mouse_y >= r[1] && g_mouse_y < r[1] + r[3])
|
||||
return WF_BTN_HOVER;
|
||||
return WF_BTN_NORMAL;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 绘制
|
||||
* ============================================================ */
|
||||
|
||||
void taskbar_draw(void) {
|
||||
const wf_theme *t = tb_theme();
|
||||
int W = (int)g_fb_w, H = (int)g_fb_h;
|
||||
int by = H - TB_H - TB_BOTTOM_GAP;
|
||||
|
||||
/* ---- 主任务栏内容宽度 ---- */
|
||||
int nbtn = 0;
|
||||
for (int i = 0; i < g_win_count; i++)
|
||||
if (g_windows[i].visible && g_windows[i].ws == g_ws_cur) nbtn++;
|
||||
int content_w = TB_PAD + TB_BTN + 4 + TB_BTN + 6 + nbtn * (TB_APPBTN + 4) + TB_PAD - 4;
|
||||
int bx = (W - content_w) / 2;
|
||||
|
||||
/* ---- 托盘组宽度 ---- */
|
||||
const char *time_s = "00:00";
|
||||
const char *date_s = "0/0";
|
||||
char tbuf[8], dbuf[10];
|
||||
{
|
||||
int yy, mo, dd, hh, mi, ss;
|
||||
rtc_read(&yy, &mo, &dd, &hh, &mi, &ss);
|
||||
tbuf[0] = '0' + hh / 10; tbuf[1] = '0' + hh % 10; tbuf[2] = ':';
|
||||
tbuf[3] = '0' + mi / 10; tbuf[4] = '0' + mi % 10; tbuf[5] = 0;
|
||||
time_s = tbuf;
|
||||
int dl = 0;
|
||||
if (mo >= 10) dbuf[dl++] = '0' + mo / 10;
|
||||
dbuf[dl++] = '0' + mo % 10; dbuf[dl++] = '/';
|
||||
if (dd >= 10) dbuf[dl++] = '0' + dd / 10;
|
||||
dbuf[dl++] = '0' + dd % 10; dbuf[dl] = 0;
|
||||
date_s = dbuf;
|
||||
}
|
||||
int clk_w = zh_string_width(time_s, 0) + 8;
|
||||
int tray_w = TB_PAD + TB_BTN + 2 + TB_BTN + 2 + TB_BTN + 6 + clk_w + TB_PAD;
|
||||
int tx = W - tray_w - 8;
|
||||
|
||||
/* ---- 主任务栏面板 ---- */
|
||||
du_wf_acrylic(&g_fb, bx, by, content_w, TB_H, 216, t->radius_lg, t);
|
||||
int x = bx + TB_PAD;
|
||||
|
||||
/* 开始按钮(logo:用户 textures/startMenuLogo,缺失回退 "D" 字形) */
|
||||
g_tb2_start_rect[0] = x; g_tb2_start_rect[1] = by + (TB_H - TB_BTN) / 2;
|
||||
g_tb2_start_rect[2] = TB_BTN; g_tb2_start_rect[3] = TB_BTN;
|
||||
du_wf_icon_button(&g_fb, x, by + (TB_H - TB_BTN) / 2, TB_BTN, t,
|
||||
tb_hover_state(g_tb2_start_rect));
|
||||
if (g_smlogo_ok) {
|
||||
tex_draw_scaled(&g_fb, (const u8 *)SMLOGO_ADDR, SMLOGO_W, SMLOGO_H,
|
||||
x + 6, by + (TB_H - TB_BTN) / 2 + 6, 28, 28, 256);
|
||||
} else {
|
||||
du_draw_string(&g_fb, "D", x + TB_BTN / 2 - 4,
|
||||
by + (TB_H - TB_BTN) / 2 + TB_BTN / 2 - 8,
|
||||
t->accent, 0, DU_ASCII_STEP * 2);
|
||||
}
|
||||
x += TB_BTN + 4;
|
||||
|
||||
/* 任务视图按钮 */
|
||||
g_tb2_tv_rect[0] = x; g_tb2_tv_rect[1] = by + (TB_H - TB_BTN) / 2;
|
||||
g_tb2_tv_rect[2] = TB_BTN; g_tb2_tv_rect[3] = TB_BTN;
|
||||
du_wf_icon_button(&g_fb, x, by + (TB_H - TB_BTN) / 2, TB_BTN, t,
|
||||
tb_hover_state(g_tb2_tv_rect));
|
||||
du_wf_icon(&g_fb, WF_ICON_TASKVIEW, x + (TB_BTN - 20) / 2,
|
||||
by + (TB_H - TB_BTN) / 2 + (TB_BTN - 20) / 2, 20, t->text_primary);
|
||||
x += TB_BTN + 6;
|
||||
|
||||
/* 运行窗口按钮(当前工作区,数组序 = z 序) */
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
desktop_window *w = &g_windows[i];
|
||||
if (!w->visible || w->ws != g_ws_cur) {
|
||||
g_tb2_app_rect[i][2] = 0;
|
||||
continue;
|
||||
}
|
||||
int byy = by + (TB_H - TB_APPBTN) / 2;
|
||||
g_tb2_app_rect[i][0] = x; g_tb2_app_rect[i][1] = byy;
|
||||
g_tb2_app_rect[i][2] = TB_APPBTN; g_tb2_app_rect[i][3] = TB_APPBTN;
|
||||
wf_btn_state st = tb_hover_state(g_tb2_app_rect[i]);
|
||||
if (w->focused) du_wf_overlay(&g_fb, x, byy, TB_APPBTN, TB_APPBTN, 0x22FFFFFFu);
|
||||
else if (st == WF_BTN_HOVER) du_wf_overlay(&g_fb, x, byy, TB_APPBTN, TB_APPBTN, 0x22FFFFFFu);
|
||||
du_wf_icon(&g_fb, tb_app_icon(w->app_id), x + (TB_APPBTN - 22) / 2,
|
||||
byy + (TB_APPBTN - 22) / 2, 22, t->text_primary);
|
||||
/* 运行指示条:底部 accent,聚焦加宽 */
|
||||
int ind_w = w->focused ? 18 : 12;
|
||||
du_fill_rounded_rect(&g_fb, x + (TB_APPBTN - ind_w) / 2,
|
||||
byy + TB_APPBTN - 6, ind_w, 3, t->accent, 1);
|
||||
x += TB_APPBTN + 4;
|
||||
}
|
||||
|
||||
/* ---- 托盘组面板 ---- */
|
||||
du_wf_acrylic(&g_fb, tx, by, tray_w, TB_H, 216, t->radius_lg, t);
|
||||
x = tx + TB_PAD;
|
||||
int iy = by + (TB_H - TB_BTN) / 2;
|
||||
|
||||
g_tb2_net_rect[0] = x; g_tb2_net_rect[1] = iy;
|
||||
g_tb2_net_rect[2] = TB_BTN; g_tb2_net_rect[3] = TB_BTN;
|
||||
du_wf_icon_button(&g_fb, x, iy, TB_BTN, t, tb_hover_state(g_tb2_net_rect));
|
||||
du_wf_icon(&g_fb, WF_ICON_NETWORK, x + (TB_BTN - 20) / 2, iy + (TB_BTN - 20) / 2, 20, t->text_primary);
|
||||
x += TB_BTN + 2;
|
||||
|
||||
g_tb2_vol_rect[0] = x; g_tb2_vol_rect[1] = iy;
|
||||
g_tb2_vol_rect[2] = TB_BTN; g_tb2_vol_rect[3] = TB_BTN;
|
||||
du_wf_icon_button(&g_fb, x, iy, TB_BTN, t, tb_hover_state(g_tb2_vol_rect));
|
||||
du_wf_icon(&g_fb, WF_ICON_VOLUME, x + (TB_BTN - 20) / 2, iy + (TB_BTN - 20) / 2, 20, t->text_primary);
|
||||
x += TB_BTN + 2;
|
||||
|
||||
g_tb2_pwr_rect[0] = x; g_tb2_pwr_rect[1] = iy;
|
||||
g_tb2_pwr_rect[2] = TB_BTN; g_tb2_pwr_rect[3] = TB_BTN;
|
||||
du_wf_icon_button(&g_fb, x, iy, TB_BTN, t, tb_hover_state(g_tb2_pwr_rect));
|
||||
du_wf_icon(&g_fb, WF_ICON_POWER, x + (TB_BTN - 20) / 2, iy + (TB_BTN - 20) / 2, 20, t->text_primary);
|
||||
x += TB_BTN + 6;
|
||||
|
||||
/* 两行时钟(16px 中文位图,行高 18) */
|
||||
g_tb2_clk_rect[0] = x; g_tb2_clk_rect[1] = by;
|
||||
g_tb2_clk_rect[2] = clk_w; g_tb2_clk_rect[3] = TB_H;
|
||||
{
|
||||
int ty1 = by + (TB_H - 36) / 2;
|
||||
zh_draw_string(&g_fb, time_s, x, ty1, t->text_primary, 0);
|
||||
zh_draw_string(&g_fb, date_s, x, ty1 + 18, t->text_dim, 0);
|
||||
}
|
||||
g_tb2_tray_rect[0] = tx; g_tb2_tray_rect[1] = by;
|
||||
g_tb2_tray_rect[2] = tray_w; g_tb2_tray_rect[3] = TB_H;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 点击路由(shell.c Fluent 分支调用;返回 1 = 已消费)
|
||||
* ============================================================ */
|
||||
|
||||
int taskbar_click(int mx, int my) {
|
||||
/* 主任务栏 */
|
||||
if (rect_hit(g_tb2_start_rect, mx, my)) {
|
||||
slog("[taskbar] start button (start menu: M3)");
|
||||
return 1;
|
||||
}
|
||||
if (rect_hit(g_tb2_tv_rect, mx, my)) {
|
||||
g_taskview = 1;
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (!g_tb2_app_rect[i][2] || !rect_hit(g_tb2_app_rect[i], mx, my))
|
||||
continue;
|
||||
/* 聚焦窗口;已聚焦则最小化(Win11 行为) */
|
||||
if (g_windows[i].focused && !g_windows[i].minimized) {
|
||||
g_windows[i].minimized = 1;
|
||||
g_focused_win = -1;
|
||||
g_windows[i].focused = 0;
|
||||
} else {
|
||||
g_windows[i].minimized = 0;
|
||||
win_focus(&g_windows[i]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/* 托盘 */
|
||||
if (rect_hit(g_tb2_pwr_rect, mx, my)) {
|
||||
g_quit = 1; /* M7:电源菜单(睡眠/重启/关机) */
|
||||
return 1;
|
||||
}
|
||||
if (rect_hit(g_tb2_net_rect, mx, my)) {
|
||||
slog("[taskbar] network (quick settings: M6)");
|
||||
return 1;
|
||||
}
|
||||
if (rect_hit(g_tb2_vol_rect, mx, my)) {
|
||||
slog("[taskbar] volume (quick settings: M6)");
|
||||
return 1;
|
||||
}
|
||||
if (rect_hit(g_tb2_clk_rect, mx, my)) {
|
||||
slog("[taskbar] clock (notification center: M6)");
|
||||
return 1;
|
||||
}
|
||||
/* 命中面板空白处:吞掉点击(不落到桌面) */
|
||||
if (rect_hit(g_tb2_tray_rect, mx, my)) return 1;
|
||||
{
|
||||
int W = (int)g_fb_w, H = (int)g_fb_h;
|
||||
int by = H - TB_H - TB_BOTTOM_GAP;
|
||||
if (my >= by && my < by + TB_H) {
|
||||
int nbtn = 0;
|
||||
for (int i = 0; i < g_win_count; i++)
|
||||
if (g_windows[i].visible && g_windows[i].ws == g_ws_cur) nbtn++;
|
||||
int content_w = TB_PAD + TB_BTN + 4 + TB_BTN + 6 + nbtn * (TB_APPBTN + 4) + TB_PAD - 4;
|
||||
int bx = (W - content_w) / 2;
|
||||
if (mx >= bx && mx < bx + content_w) return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/* textures.c — 裸 RGBA 纹理绘制实现(M2)
|
||||
*
|
||||
* 数组字节序 R,G,B,A;framebuffer u32 = 0xAARRGGBB。
|
||||
* 双线性插值(像素中心对齐,8.8 定点)+ src-over alpha。
|
||||
*/
|
||||
#include "ascii_font.h" /* 顺序敏感:必须先于 textures.h(deshab_ui.h 需 g_ascii) */
|
||||
#include "textures.h"
|
||||
|
||||
void tex_draw_scaled(du_context *ctx, const tex_u8 *rgba, int sw, int sh,
|
||||
int x, int y, int w, int h, int dim) {
|
||||
if (!ctx || !rgba || sw <= 0 || sh <= 0 || w <= 0 || h <= 0) return;
|
||||
if (dim < 0) dim = 0;
|
||||
if (dim > 256) dim = 256;
|
||||
|
||||
int fw = (int)ctx->width, fh = (int)ctx->height;
|
||||
tex_u32 *fb = ctx->fb;
|
||||
tex_i64 pitch = (tex_i64)ctx->pitch / 4; /* u32 单位 */
|
||||
|
||||
for (int dy = 0; dy < h; dy++) {
|
||||
int py = y + dy;
|
||||
if (py < 0 || py >= fh) continue;
|
||||
tex_i64 fy64 = ((tex_i64)dy * sh << 8) / h;
|
||||
int sy0 = (int)(fy64 >> 8);
|
||||
int fy = (int)(fy64 & 0xFF);
|
||||
int sy1 = (sy0 + 1 < sh) ? sy0 + 1 : sy0;
|
||||
if (sy0 >= sh) sy0 = sy1 = sh - 1;
|
||||
|
||||
tex_u32 *dline = fb + (tex_i64)py * pitch;
|
||||
for (int dx = 0; dx < w; dx++) {
|
||||
int px = x + dx;
|
||||
if (px < 0 || px >= fw) continue;
|
||||
tex_i64 fx64 = ((tex_i64)dx * sw << 8) / w;
|
||||
int sx0 = (int)(fx64 >> 8);
|
||||
int fx = (int)(fx64 & 0xFF);
|
||||
int sx1 = (sx0 + 1 < sw) ? sx0 + 1 : sx0;
|
||||
if (sx0 >= sw) sx0 = sx1 = sw - 1;
|
||||
|
||||
const tex_u8 *p00 = rgba + ((tex_i64)sy0 * sw + sx0) * 4;
|
||||
const tex_u8 *p10 = rgba + ((tex_i64)sy0 * sw + sx1) * 4;
|
||||
const tex_u8 *p01 = rgba + ((tex_i64)sy1 * sw + sx0) * 4;
|
||||
const tex_u8 *p11 = rgba + ((tex_i64)sy1 * sw + sx1) * 4;
|
||||
|
||||
tex_u32 a, r, g, b;
|
||||
{
|
||||
tex_u32 rtop = ((tex_u32)p00[0] * (256 - fx) + (tex_u32)p10[0] * fx) >> 8;
|
||||
tex_u32 rbot = ((tex_u32)p01[0] * (256 - fx) + (tex_u32)p11[0] * fx) >> 8;
|
||||
tex_u32 gtop = ((tex_u32)p00[1] * (256 - fx) + (tex_u32)p10[1] * fx) >> 8;
|
||||
tex_u32 gbot = ((tex_u32)p01[1] * (256 - fx) + (tex_u32)p11[1] * fx) >> 8;
|
||||
tex_u32 btop = ((tex_u32)p00[2] * (256 - fx) + (tex_u32)p10[2] * fx) >> 8;
|
||||
tex_u32 bbot = ((tex_u32)p01[2] * (256 - fx) + (tex_u32)p11[2] * fx) >> 8;
|
||||
tex_u32 atop = ((tex_u32)p00[3] * (256 - fx) + (tex_u32)p10[3] * fx) >> 8;
|
||||
tex_u32 abot = ((tex_u32)p01[3] * (256 - fx) + (tex_u32)p11[3] * fx) >> 8;
|
||||
r = (rtop * (256 - fy) + rbot * fy) >> 8;
|
||||
g = (gtop * (256 - fy) + gbot * fy) >> 8;
|
||||
b = (btop * (256 - fy) + bbot * fy) >> 8;
|
||||
a = (atop * (256 - fy) + abot * fy) >> 8;
|
||||
}
|
||||
if (a == 0) continue;
|
||||
|
||||
/* 亮度衰减(boot 背景) */
|
||||
r = r * dim >> 8; g = g * dim >> 8; b = b * dim >> 8;
|
||||
|
||||
tex_u32 dst = dline[px];
|
||||
if (a >= 255) {
|
||||
dline[px] = 0xFF000000u | (r << 16) | (g << 8) | b;
|
||||
} else {
|
||||
tex_u32 dr = (dst >> 16) & 0xFF, dg = (dst >> 8) & 0xFF, db = dst & 0xFF;
|
||||
tex_u32 na = 255 - a;
|
||||
dline[px] = 0xFF000000u |
|
||||
(((r * a + dr * na) >> 8) << 16) |
|
||||
(((g * a + dg * na) >> 8) << 8) |
|
||||
((b * a + db * na) >> 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* textures.h — 裸 RGBA 纹理绘制(M2)
|
||||
*
|
||||
* 纹理由 gen_textures.py 生成(.rgba = 无头 RGBA 字节流,R,G,B,A),
|
||||
* 运行时经 FAT32 流式读入固定地址,tex_draw_scaled 双线性缩放 + alpha 混合。
|
||||
*/
|
||||
#ifndef DESHAB_TEXTURES_H
|
||||
#define DESHAB_TEXTURES_H
|
||||
|
||||
#include "../UTSM/include/utsm/deshab_ui.h"
|
||||
|
||||
typedef unsigned char tex_u8;
|
||||
typedef unsigned int tex_u32;
|
||||
typedef unsigned long long tex_u64;
|
||||
typedef long long tex_i64;
|
||||
|
||||
/* 开始菜单 logo 缓存区(zhfont24 之后、WAPP 之前,1MB) */
|
||||
#define SMLOGO_ADDR 0x8700000ULL
|
||||
#define SMLOGO_MAX (1024u * 1024u)
|
||||
#define SMLOGO_W 216
|
||||
#define SMLOGO_H 210
|
||||
|
||||
/* 启动背景(boot 屏专用,读入 PE surface 暂存区,启动期独占) */
|
||||
#define BOOTBG_W 889
|
||||
#define BOOTBG_H 500
|
||||
#define BOOTLOGO_W 157
|
||||
#define BOOTLOGO_H 157
|
||||
|
||||
/* 双线性缩放绘制 RGBA 纹理(src-over alpha 混合)。
|
||||
* dim: 0..256 亮度衰减(256 = 原亮度;boot 背景用 ~150 压暗保证文字对比)。 */
|
||||
void tex_draw_scaled(du_context *ctx, const tex_u8 *rgba, int sw, int sh,
|
||||
int x, int y, int w, int h, int dim);
|
||||
|
||||
#endif /* DESHAB_TEXTURES_H */
|
||||
@@ -0,0 +1,80 @@
|
||||
/* wallpaper.c — 壁纸背景层缓存实现(M2:磁盘加载版)
|
||||
*
|
||||
* 流程:FAT32 流式读 system/wallpaper.rgba(1000×700 RGBA 字节流)
|
||||
* → WALLPAPER_STAGING_ADDR 暂存 → 双线性缩放(对齐像素中心,8.8 定点)
|
||||
* → WALLPAPER_ADDR 背景层缓存。
|
||||
* 数组字节序 R,G,B,A;framebuffer u32 = 0xAARRGGBB(R 在 bit16)。
|
||||
*/
|
||||
#include "wallpaper.h"
|
||||
|
||||
/* 嵌入资产尺寸(与 gen_wallpaper.py 的 W×H 一致;运行时按文件大小校验) */
|
||||
#define WP_SRC_W 1000
|
||||
#define WP_SRC_H 700
|
||||
|
||||
static wp_u32 *g_wp_layer = 0;
|
||||
static int g_wp_w = 0, g_wp_h = 0;
|
||||
static wp_u32 g_wp_src_w = WP_SRC_W, g_wp_src_h = WP_SRC_H;
|
||||
|
||||
int wallpaper_init(wallpaper_read_fn read_to, int fb_w, int fb_h) {
|
||||
if (!read_to || fb_w <= 0 || fb_h <= 0) return -1;
|
||||
if ((wp_u64)fb_w * (wp_u64)fb_h * 4u > WALLPAPER_MAX) return -2;
|
||||
|
||||
/* 1. 流式读原始 RGBA 到暂存区(PE surface 区,启动期独占) */
|
||||
wp_u32 src_total = g_wp_src_w * g_wp_src_h * 4u;
|
||||
if (src_total > WALLPAPER_STAGING_MAX) return -3;
|
||||
wp_u32 got = 0;
|
||||
if (read_to("system/wallpaper.rgba",
|
||||
(wp_u8 *)WALLPAPER_STAGING_ADDR, src_total, &got) != 0)
|
||||
return -4;
|
||||
if (got < src_total) {
|
||||
/* 文件比预期小:按实际行数截断(防御,正常部署不会发生) */
|
||||
if (got < g_wp_src_w * 4u) return -5;
|
||||
g_wp_src_h = got / (g_wp_src_w * 4u);
|
||||
}
|
||||
const wp_u8 *src = (const wp_u8 *)WALLPAPER_STAGING_ADDR;
|
||||
|
||||
/* 2. 双线性缩放到背景层缓存 */
|
||||
g_wp_layer = (wp_u32 *)WALLPAPER_ADDR;
|
||||
g_wp_w = fb_w;
|
||||
g_wp_h = fb_h;
|
||||
int sw = (int)g_wp_src_w, sh = (int)g_wp_src_h;
|
||||
for (int y = 0; y < fb_h; y++) {
|
||||
wp_i64 fy64 = ((wp_i64)y * sh << 8) / fb_h; /* 8.8 定点 */
|
||||
int sy0 = (int)(fy64 >> 8);
|
||||
int fy = (int)(fy64 & 0xFF);
|
||||
int sy1 = (sy0 + 1 < sh) ? sy0 + 1 : sy0;
|
||||
if (sy0 >= sh) sy0 = sy1 = sh - 1;
|
||||
for (int x = 0; x < fb_w; x++) {
|
||||
wp_i64 fx64 = ((wp_i64)x * sw << 8) / fb_w;
|
||||
int sx0 = (int)(fx64 >> 8);
|
||||
int fx = (int)(fx64 & 0xFF);
|
||||
int sx1 = (sx0 + 1 < sw) ? sx0 + 1 : sx0;
|
||||
if (sx0 >= sw) sx0 = sx1 = sw - 1;
|
||||
|
||||
const wp_u8 *p00 = src + ((wp_i64)sy0 * sw + sx0) * 4;
|
||||
const wp_u8 *p10 = src + ((wp_i64)sy0 * sw + sx1) * 4;
|
||||
const wp_u8 *p01 = src + ((wp_i64)sy1 * sw + sx0) * 4;
|
||||
const wp_u8 *p11 = src + ((wp_i64)sy1 * sw + sx1) * 4;
|
||||
|
||||
wp_u32 r, g, b;
|
||||
{
|
||||
wp_u32 rtop = ((wp_u32)p00[0] * (256 - fx) + (wp_u32)p10[0] * fx) >> 8;
|
||||
wp_u32 rbot = ((wp_u32)p01[0] * (256 - fx) + (wp_u32)p11[0] * fx) >> 8;
|
||||
wp_u32 gtop = ((wp_u32)p00[1] * (256 - fx) + (wp_u32)p10[1] * fx) >> 8;
|
||||
wp_u32 gbot = ((wp_u32)p01[1] * (256 - fx) + (wp_u32)p11[1] * fx) >> 8;
|
||||
wp_u32 btop = ((wp_u32)p00[2] * (256 - fx) + (wp_u32)p10[2] * fx) >> 8;
|
||||
wp_u32 bbot = ((wp_u32)p01[2] * (256 - fx) + (wp_u32)p11[2] * fx) >> 8;
|
||||
r = (rtop * (256 - fy) + rbot * fy) >> 8;
|
||||
g = (gtop * (256 - fy) + gbot * fy) >> 8;
|
||||
b = (btop * (256 - fy) + bbot * fy) >> 8;
|
||||
}
|
||||
g_wp_layer[(wp_i64)y * fb_w + x] =
|
||||
0xFF000000u | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
wp_u32 *wallpaper_layer(void) {
|
||||
return g_wp_layer;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* wallpaper.h — 桌面壁纸(磁盘加载 + 一次性缩放进背景层缓存)
|
||||
*
|
||||
* 壁纸资产由 gen_wallpaper.py 生成:
|
||||
* wallpaper.png — 人看预览
|
||||
* wallpaper.rgba — 1000×700 原始 RGBA 字节流,部署于 FAT32
|
||||
* system/wallpaper.rgba(避免 2.8MB 嵌入 ELF 撑大
|
||||
* DSK ibuf 占用,且换壁纸无需重编 desktop.elf)
|
||||
*
|
||||
* 加载路径(wallpaper_init):
|
||||
* FAT32 流式读 wallpaper.rgba → PE surface 暂存区(0x7800000,8MB,
|
||||
* 启动期独占,PE 应用启动时自行覆盖初始化)
|
||||
* → 双线性缩放到背景层缓存(WALLPAPER_ADDR 0x6200000,≤7.5MB)
|
||||
*
|
||||
* 运行期 compositor/acrylic 直接读背景层缓存,零缩放开销。
|
||||
* 加载失败返回负值,调用方回退渐变背景,不阻塞启动。
|
||||
*/
|
||||
#ifndef DESHAB_WALLPAPER_H
|
||||
#define DESHAB_WALLPAPER_H
|
||||
|
||||
typedef unsigned char wp_u8;
|
||||
typedef unsigned int wp_u32;
|
||||
typedef unsigned long long wp_u64;
|
||||
typedef long long wp_i64;
|
||||
|
||||
#define WALLPAPER_ADDR 0x6200000ULL
|
||||
#define WALLPAPER_MAX (7u * 1024u * 1024u + 512u * 1024u) /* 7.5MB */
|
||||
|
||||
/* 壁纸原始数据暂存区(= PE surface 区,启动期独占) */
|
||||
#define WALLPAPER_STAGING_ADDR 0x7800000ULL
|
||||
#define WALLPAPER_STAGING_MAX (8u * 1024u * 1024u)
|
||||
|
||||
/* FAT32 流式读取函数原型(f32io.h 的 f32_read_path_lfn_to_w) */
|
||||
typedef int (*wallpaper_read_fn)(const char *path, unsigned char *dst,
|
||||
unsigned int cap, unsigned int *out_size);
|
||||
|
||||
/* 启动时调用一次:加载 + 缩放到背景层缓存(fb 尺寸)。
|
||||
* 返回 0 成功,负值失败(调用方回退渐变)。 */
|
||||
int wallpaper_init(wallpaper_read_fn read_to, int fb_w, int fb_h);
|
||||
|
||||
/* 背景层缓存指针(成功后有效;失败返回 0) */
|
||||
wp_u32 *wallpaper_layer(void);
|
||||
|
||||
#endif /* DESHAB_WALLPAPER_H */
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,270 @@
|
||||
/* wm.c — 窗口管理(win_*)+ 窗口装饰绘制(M0 自 main.c 平移)
|
||||
*/
|
||||
#include "desktop.h"
|
||||
|
||||
/* ---- 全局状态 ---- */
|
||||
desktop_window g_windows[MAX_WINDOWS];
|
||||
int g_win_count = 0;
|
||||
static int g_next_win_id = 1;
|
||||
int g_focused_win = -1;
|
||||
|
||||
/* 拖拽状态 */
|
||||
int g_dragging = 0;
|
||||
int g_drag_win = -1;
|
||||
int g_drag_off_x, g_drag_off_y;
|
||||
|
||||
/* ============================================================
|
||||
* 窗口管理
|
||||
* ============================================================ */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
desktop_window *win_find_at(int mx, int my) {
|
||||
for (int i = g_win_count - 1; i >= 0; i--) {
|
||||
desktop_window *w = &g_windows[i];
|
||||
if (!w->visible || w->minimized) continue;
|
||||
if (w->ws != g_ws_cur) continue;
|
||||
if (mx >= w->x && mx < w->x + w->w && my >= w->y && my < w->y + w->h) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void win_bring_to_front(desktop_window *w) {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (g_windows[i].id == w->id) { idx = i; break; }
|
||||
}
|
||||
if (idx < 0 || idx == g_win_count - 1) return;
|
||||
desktop_window tmp = g_windows[idx];
|
||||
for (int i = idx; i < g_win_count - 1; i++) {
|
||||
g_windows[i] = g_windows[i+1];
|
||||
}
|
||||
g_windows[g_win_count - 1] = tmp;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
desktop_window *win_create(int app_id, const char *title, int x, int y, int w, int h) {
|
||||
if (g_win_count >= MAX_WINDOWS) return 0;
|
||||
desktop_window *win = &g_windows[g_win_count++];
|
||||
int id = g_next_win_id++;
|
||||
win->id = id;
|
||||
win->x = x; win->y = y; win->w = w; win->h = h;
|
||||
win->visible = 1;
|
||||
win->focused = 1;
|
||||
win->minimized = 0;
|
||||
win->app_id = app_id;
|
||||
win->app_state = 0;
|
||||
win->closing = 0;
|
||||
win->dirty = 1;
|
||||
win->ws = g_ws_cur;
|
||||
for (int i = 0; i < 47 && title[i]; i++) win->title[i] = title[i];
|
||||
win->title[47] = 0;
|
||||
for (int i = 0; i < g_win_count - 1; i++) g_windows[i].focused = 0;
|
||||
g_focused_win = id;
|
||||
return win;
|
||||
}
|
||||
|
||||
void win_destroy(desktop_window *w) {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < g_win_count; i++) {
|
||||
if (g_windows[i].id == w->id) { idx = i; break; }
|
||||
}
|
||||
if (idx < 0) return;
|
||||
|
||||
/* 直接隐藏窗口(去掉关闭动画以避免卡顿和闪屏) */
|
||||
w->visible = 0;
|
||||
|
||||
if (w->app_id >= 0 && w->app_id < g_app_count && g_apps[w->app_id].on_destroy && w->app_state) {
|
||||
g_apps[w->app_id].on_destroy(w->app_state);
|
||||
}
|
||||
for (int i = idx; i < g_win_count - 1; i++) g_windows[i] = g_windows[i+1];
|
||||
g_win_count--;
|
||||
if (g_focused_win == w->id) {
|
||||
g_focused_win = -1;
|
||||
for (int i = g_win_count - 1; i >= 0; i--) {
|
||||
if (g_windows[i].ws == g_ws_cur && g_windows[i].visible && !g_windows[i].minimized) {
|
||||
g_focused_win = g_windows[i].id;
|
||||
g_windows[i].focused = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 窗口标题栏高度(M2:Fluent 36px / KATE 28px) */
|
||||
int win_titlebar_h(void) {
|
||||
return g_fluent ? 36 : KATE_TITLEBAR_H;
|
||||
}
|
||||
|
||||
/* P5d: 关闭请求。运行中的 PE 窗口不能直接 win_destroy(run_windowed 阻塞中,
|
||||
* pump 持有窗口 id)——按 pe_window_host 协作契约 inject WM_CLOSE,
|
||||
* PE 自愿退出后由 launch_pe_app 收尾销毁。 */
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 窗口装饰绘制
|
||||
* ============================================================ */
|
||||
|
||||
/* M2:Fluent 窗口框架 —— 圆角 8、36px 标题栏、右上三钮、投影。
|
||||
* 窗口按钮命中区(WM_FLUENT_TB=36px 标题栏,三钮各 46×36)。 */
|
||||
#define WM_FLUENT_TB 36
|
||||
#define WM_BTN_W 46
|
||||
|
||||
static int wm_fluent_hit_btn(desktop_window *w, int mx, int my, int btn) {
|
||||
int bx = w->x + w->w - WM_BTN_W * btn;
|
||||
return mx >= bx && mx < bx + WM_BTN_W &&
|
||||
my >= w->y && my < w->y + WM_FLUENT_TB;
|
||||
}
|
||||
|
||||
/* 静态状态:正在操作的三钮(0=无 1=min 2=max 3=close) */
|
||||
static int g_wm_btn_hover = 0;
|
||||
|
||||
int win_hit_close_btn(desktop_window *w, int mx, int my) {
|
||||
if (g_fluent) return wm_fluent_hit_btn(w, mx, my, 3);
|
||||
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;
|
||||
}
|
||||
|
||||
int win_hit_min_btn(desktop_window *w, int mx, int my) {
|
||||
return g_fluent ? wm_fluent_hit_btn(w, mx, my, 1) : 0;
|
||||
}
|
||||
|
||||
int win_hit_max_btn(desktop_window *w, int mx, int my) {
|
||||
return g_fluent ? wm_fluent_hit_btn(w, mx, my, 2) : 0;
|
||||
}
|
||||
|
||||
static void draw_window_frame_fluent(desktop_window *w) {
|
||||
/* 投影 + 亚克力底(窗口主体用不透明深色,性能优先) */
|
||||
du_rect_outline(&g_fb, w->x - 2, w->y - 2, w->w + 4, w->h + 4,
|
||||
du_blend(du_pixel_read(&g_fb, w->x - 2, w->y - 2), 0xFF000000u, 70), 10);
|
||||
du_fill_rounded_rect(&g_fb, w->x, w->y, w->w, w->h, 0xFF2B2B2Bu, 8);
|
||||
du_rect_outline(&g_fb, w->x, w->y, w->w, w->h,
|
||||
w->focused ? 0xFF4A4A4Au : 0xFF3F3F3Fu, 8);
|
||||
|
||||
/* 标题栏(36px,无强调色,仅底部分隔线) */
|
||||
du_fill_rect(&g_fb, w->x + 1, w->y + 1, w->w - 2, WM_FLUENT_TB - 1, 0xFF2B2B2Bu);
|
||||
du_divider_h(&g_fb, w->x + 1, w->y + WM_FLUENT_TB, w->w - 2, 0xFF3A3A3Au);
|
||||
du_draw_string(&g_fb, w->title,
|
||||
w->x + 14,
|
||||
w->y + (WM_FLUENT_TB - (i64)DU_ASCII_LINE_H) / 2,
|
||||
w->focused ? 0xFFFFFFFFu : 0xFFC8C8C8u, 0xFF2B2B2Bu, DU_ASCII_STEP);
|
||||
|
||||
/* 右上三钮:最小化 / 最大化 / 关闭(hover:前两钮浅灰,关闭钮红) */
|
||||
int by = w->y;
|
||||
int iy = by + WM_FLUENT_TB / 2;
|
||||
/* min */
|
||||
{
|
||||
int bx = w->x + w->w - WM_BTN_W * 3;
|
||||
if (g_wm_btn_hover == 1)
|
||||
du_fill_rect(&g_fb, bx, by, WM_BTN_W, WM_FLUENT_TB, 0xFF3A3A3Au);
|
||||
du_fill_rect(&g_fb, bx + 16, iy, 14, 2, 0xFFFFFFFFu);
|
||||
}
|
||||
/* max */
|
||||
{
|
||||
int bx = w->x + w->w - WM_BTN_W * 2;
|
||||
if (g_wm_btn_hover == 2)
|
||||
du_fill_rect(&g_fb, bx, by, WM_BTN_W, WM_FLUENT_TB, 0xFF3A3A3Au);
|
||||
du_rect_outline(&g_fb, bx + 17, iy - 7, 13, 13, 0xFFFFFFFFu, 1);
|
||||
}
|
||||
/* close:hover 红 #E81123 */
|
||||
{
|
||||
int bx = w->x + w->w - WM_BTN_W;
|
||||
if (g_wm_btn_hover == 3)
|
||||
du_fill_rect(&g_fb, bx, by, WM_BTN_W, WM_FLUENT_TB, 0xFFC42B1Cu);
|
||||
/* × 用两条 2px 对角线 */
|
||||
for (int i = 0; i < 11; i++) {
|
||||
du_pixel(&g_fb, bx + 18 + i, iy - 5 + i, 0xFFFFFFFFu);
|
||||
du_pixel(&g_fb, bx + 19 + i, iy - 5 + i, 0xFFFFFFFFu);
|
||||
du_pixel(&g_fb, bx + 18 + i, iy + 5 - i, 0xFFFFFFFFu);
|
||||
du_pixel(&g_fb, bx + 19 + i, iy + 5 - i, 0xFFFFFFFFu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int win_hit_titlebar(desktop_window *w, int mx, int my) {
|
||||
int tb = g_fluent ? WM_FLUENT_TB : KATE_TITLEBAR_H;
|
||||
if (g_fluent && (win_hit_close_btn(w, mx, my) || win_hit_max_btn(w, mx, my) ||
|
||||
win_hit_min_btn(w, mx, my)))
|
||||
return 0; /* 三钮区域不算标题栏拖拽 */
|
||||
return mx >= w->x && mx < w->x + w->w &&
|
||||
my >= w->y && my < w->y + tb;
|
||||
}
|
||||
|
||||
/* 窗口按钮 hover 状态跟踪(绘制前调用;btn 0=无) */
|
||||
void wm_set_btn_hover(int btn) { g_wm_btn_hover = btn; }
|
||||
|
||||
/* 当前鼠标悬停的窗口按钮(0=无 1=min 2=max 3=close),-1=不在窗口上 */
|
||||
int wm_detect_btn_hover(desktop_window *w, int mx, int my) {
|
||||
if (!w) return 0;
|
||||
if (win_hit_close_btn(w, mx, my)) return 3;
|
||||
if (win_hit_max_btn(w, mx, my)) return 2;
|
||||
if (win_hit_min_btn(w, mx, my)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void draw_window_frame(desktop_window *w) {
|
||||
if (!w->visible || w->minimized) return;
|
||||
|
||||
if (g_fluent) {
|
||||
draw_window_frame_fluent(w);
|
||||
return;
|
||||
}
|
||||
|
||||
/* ---- Winux-Kate 风格(fluent=0 回退路径) ---- */
|
||||
u32 border = w->focused ? KS_BORDER_FOCUS : KS_BORDER;
|
||||
|
||||
if (w->focused) {
|
||||
du_kate_glow_border(&g_fb, w->x, w->y, w->w, w->h, KS_ACCENT);
|
||||
}
|
||||
|
||||
du_fill_rounded_rect(&g_fb, w->x, w->y, w->w, w->h, KS_BG_SECONDARY, 2);
|
||||
|
||||
du_fill_rect_gradient(&g_fb, w->x + 1, w->y + 1, w->w - 2, KATE_TITLEBAR_H - 1,
|
||||
0xFF0A3050u, KS_BG_SECONDARY);
|
||||
du_divider_h(&g_fb, w->x, w->y + KATE_TITLEBAR_H, w->w, KS_BORDER);
|
||||
|
||||
du_fill_rect(&g_fb, w->x + 6, w->y + KATE_TITLEBAR_H / 2 - 2, 4, 4, KS_ACCENT2);
|
||||
|
||||
du_draw_string(&g_fb, w->title,
|
||||
w->x + 14,
|
||||
w->y + (KATE_TITLEBAR_H - (i64)DU_ASCII_LINE_H) / 2 + 1,
|
||||
KS_ACCENT, 0xFF0A3050u, DU_ASCII_STEP);
|
||||
|
||||
int bx = w->x + w->w - CLOSE_BTN_OFFSET;
|
||||
int by = w->y + (KATE_TITLEBAR_H - CLOSE_BTN_SIZE) / 2;
|
||||
du_rect_outline(&g_fb, bx, by, CLOSE_BTN_SIZE, CLOSE_BTN_SIZE, KS_DANGER, 1);
|
||||
du_draw_string(&g_fb, "X", bx + 3, by - 1,
|
||||
KS_DANGER, KS_BG_SECONDARY, DU_ASCII_STEP);
|
||||
|
||||
du_rect_outline(&g_fb, w->x, w->y, w->w, w->h, border, 2);
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/* zhfont.c — 中文位图字库加载与渲染实现
|
||||
*
|
||||
* .dbf 格式(见 ../font/dbf.h):
|
||||
* header 14 字节 + count*12 字节索引(按 Unicode 码点升序)+ 字形数据。
|
||||
* 索引项 data_offset 相对文件头。
|
||||
*
|
||||
* 渲染:8bpp 灰度字形逐像素 du_blend(与 ASCII 渲染同路径,无字体缓存,
|
||||
* 每屏 <500 字在性能预算内,见桌面重构计划第七节)。
|
||||
*/
|
||||
/* 顺序敏感:ascii_font.h 声明 g_ascii(deshab_ui.h 的 du_draw_char 引用) */
|
||||
#include "ascii_font.h"
|
||||
#include "zhfont.h"
|
||||
#include "../font/dbf.h"
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
typedef long long i64;
|
||||
|
||||
/* ---- 字库状态 ---- */
|
||||
static zhfont_read_fn g_zh_read = 0;
|
||||
static const u8 *g_zh16 = 0;
|
||||
static const u8 *g_zh24 = 0;
|
||||
|
||||
/* ---- dbf 访问 ---- */
|
||||
|
||||
static const u8 *zh_dbf_get(const u8 *font, u32 cp, int *out_w, int *out_h) {
|
||||
const dbf_header *hdr = (const dbf_header *)font;
|
||||
const dbf_index_entry *idx = (const dbf_index_entry *)(font + 14);
|
||||
if (cp > 0xFFFF) return 0;
|
||||
int i = dbf_lookup((dbf_index_entry *)idx, hdr->count, (u16)cp);
|
||||
if (i < 0) return 0;
|
||||
*out_w = idx[i].width;
|
||||
*out_h = hdr->height;
|
||||
return font + idx[i].offset;
|
||||
}
|
||||
|
||||
/* 缺字占位:1px 空框 */
|
||||
static int zh_draw_missing(du_context *ctx, i64 x, i64 y, int w, int h, u32 fg) {
|
||||
du_rect_outline(ctx, x, y, w, h, fg, 1);
|
||||
return w;
|
||||
}
|
||||
|
||||
/* ---- 公开接口 ---- */
|
||||
|
||||
/* 头部静态校验(magic / bpp / 尺寸字段),不依赖文件大小 */
|
||||
static int zh_dbf_valid(const u8 *dst) {
|
||||
const dbf_header *hdr = (const dbf_header *)dst;
|
||||
if (!dst) return -1;
|
||||
if (hdr->magic[0] != 'D' || hdr->magic[1] != 'B' ||
|
||||
hdr->magic[2] != 'F' || hdr->magic[3] != 0x10) return -2;
|
||||
if (hdr->bpp != 8) return -3;
|
||||
if (hdr->count == 0 || hdr->height == 0 || hdr->width == 0) return -4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int zh_load_dbf(const char *path, u8 *dst, u32 cap) {
|
||||
u32 sz = 0;
|
||||
if (!g_zh_read) return -1;
|
||||
if (g_zh_read(path, dst, cap, &sz) != 0) return -2;
|
||||
if (sz < 14) return -3;
|
||||
int rc = zh_dbf_valid(dst);
|
||||
if (rc != 0) return rc - 10;
|
||||
/* 越界防护:索引区与最大数据项必须在文件内 */
|
||||
{
|
||||
const dbf_header *hdr = (const dbf_header *)dst;
|
||||
const dbf_index_entry *last =
|
||||
(const dbf_index_entry *)(dst + 14 + (u64)(hdr->count - 1) * 12);
|
||||
if (14 + (u64)hdr->count * 12 > sz) return -7;
|
||||
if ((u64)last->offset + last->size > sz) return -8;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zhfont_init(zhfont_read_fn read_to) {
|
||||
int rc;
|
||||
g_zh_read = read_to;
|
||||
rc = zh_load_dbf("system/font/simhei_16.dbf",
|
||||
(u8 *)ZHFONT16_ADDR, ZHFONT16_MAX);
|
||||
if (rc != 0) {
|
||||
g_zh16 = 0;
|
||||
return rc; /* -1=无读取器 -2=FAT32读失败 -3=过小 -7/-8=越界 -11..-13=头校验 */
|
||||
}
|
||||
g_zh16 = (const u8 *)ZHFONT16_ADDR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zhfont_load24(void) {
|
||||
if (g_zh24) return 0;
|
||||
if (zh_load_dbf("system/font/simhei_24.dbf",
|
||||
(u8 *)ZHFONT24_ADDR, ZHFONT24_MAX) != 0) {
|
||||
return -1;
|
||||
}
|
||||
g_zh24 = (const u8 *)ZHFONT24_ADDR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zhfont_ok(void) { return g_zh16 != 0; }
|
||||
int zhfont_ok24(void) { return g_zh24 != 0; }
|
||||
|
||||
void zhfont_attach16(const void *dbf_data) {
|
||||
/* 校验 magic 后挂接(数据由调用方保证生命周期) */
|
||||
if (dbf_data && zh_dbf_valid((const u8 *)dbf_data) == 0) g_zh16 = (const u8 *)dbf_data;
|
||||
else g_zh16 = 0;
|
||||
}
|
||||
|
||||
void zhfont_attach24(const void *dbf_data) {
|
||||
if (dbf_data && zh_dbf_valid((const u8 *)dbf_data) == 0) g_zh24 = (const u8 *)dbf_data;
|
||||
else g_zh24 = 0;
|
||||
}
|
||||
|
||||
unsigned int zh_utf8_decode(const char **s) {
|
||||
const u8 *p = (const u8 *)*s;
|
||||
u8 c = *p++;
|
||||
unsigned int cp;
|
||||
int extra;
|
||||
if (c < 0x80) { *s = (const char *)p; return c; }
|
||||
else if ((c & 0xE0) == 0xC0) { cp = c & 0x1F; extra = 1; }
|
||||
else if ((c & 0xF0) == 0xE0) { cp = c & 0x0F; extra = 2; }
|
||||
else if ((c & 0xF8) == 0xF0) { cp = c & 0x07; extra = 3; }
|
||||
else { (*s)++; return 0xFFFFFFFFu; } /* 无效首字节 */
|
||||
for (int i = 0; i < extra; i++) {
|
||||
u8 cc = *p;
|
||||
if ((cc & 0xC0) != 0x80) { (*s)++; return 0xFFFFFFFFu; }
|
||||
cp = (cp << 6) | (cc & 0x3F);
|
||||
p++;
|
||||
}
|
||||
*s = (const char *)p;
|
||||
return cp;
|
||||
}
|
||||
|
||||
int zh_draw_char(du_context *ctx, unsigned int codepoint,
|
||||
i64 x, i64 y, unsigned int fg) {
|
||||
if (!g_zh16) {
|
||||
/* 字库未加载:ASCII 走 Consolas,其余画空框 */
|
||||
if (codepoint >= 0x20 && codepoint < 0x7F) {
|
||||
du_draw_char(ctx, (char)codepoint, x, y, fg, 0);
|
||||
return (int)DU_ASCII_STEP;
|
||||
}
|
||||
return 16;
|
||||
}
|
||||
if (codepoint < 0x20) return 4; /* 控制字符:窄空隙 */
|
||||
int w, h;
|
||||
const u8 *g = zh_dbf_get(g_zh16, codepoint, &w, &h);
|
||||
if (!g) return zh_draw_missing(ctx, x, y, 16, 16, fg);
|
||||
for (int r = 0; r < h; r++) {
|
||||
for (int c = 0; c < w; c++) {
|
||||
u8 a = g[r * w + c];
|
||||
if (!a) continue;
|
||||
du_u32 bg = du_pixel_read(ctx, x + c, y + r);
|
||||
du_pixel(ctx, x + c, y + r,
|
||||
(a == 255) ? fg : du_blend(bg, fg, a));
|
||||
}
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
int zh_string_width(const char *utf8, int size24) {
|
||||
int total = 0;
|
||||
const u8 *font = size24 ? g_zh24 : g_zh16;
|
||||
if (!utf8) return 0;
|
||||
while (*utf8) {
|
||||
unsigned int cp = zh_utf8_decode(&utf8);
|
||||
if (cp == 0xFFFFFFFFu) { total += 4; continue; }
|
||||
if (cp < 0x20) { total += 4; continue; }
|
||||
int w, h;
|
||||
const u8 *g = font ? zh_dbf_get(font, cp, &w, &h) : 0;
|
||||
if (!g) w = size24 ? 24 : 16;
|
||||
total += w + 1; /* 1px 字间距 */
|
||||
}
|
||||
return total > 0 ? total - 1 : 0;
|
||||
}
|
||||
|
||||
int zh_line_height(int size24) { return size24 ? 26 : 18; }
|
||||
|
||||
int zh_draw_string(du_context *ctx, const char *utf8,
|
||||
i64 x, i64 y, unsigned int fg, int size24) {
|
||||
const u8 *font = size24 ? g_zh24 : g_zh16;
|
||||
i64 cx = x;
|
||||
if (!utf8) return 0;
|
||||
while (*utf8) {
|
||||
unsigned int cp = zh_utf8_decode(&utf8);
|
||||
if (cp == 0xFFFFFFFFu) { cx += 4; continue; }
|
||||
if (cp < 0x20) { cx += 4; continue; }
|
||||
if (cp == 0x20) { cx += (size24 ? 18 : 12); continue; }
|
||||
int w, h;
|
||||
const u8 *g = font ? zh_dbf_get(font, cp, &w, &h) : 0;
|
||||
if (!g) {
|
||||
cx += zh_draw_missing(ctx, cx, y, size24 ? 24 : 16,
|
||||
size24 ? 24 : 16, fg) + 1;
|
||||
continue;
|
||||
}
|
||||
for (int r = 0; r < h; r++) {
|
||||
for (int c = 0; c < w; c++) {
|
||||
u8 a = g[r * w + c];
|
||||
if (!a) continue;
|
||||
du_u32 bg = du_pixel_read(ctx, cx + c, y + r);
|
||||
du_pixel(ctx, cx + c, y + r,
|
||||
(a == 255) ? fg : du_blend(bg, fg, a));
|
||||
}
|
||||
}
|
||||
cx += w + 1;
|
||||
}
|
||||
return (int)(cx - x);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* zhfont.h — Deshab 中文位图字库(.dbf)加载与渲染
|
||||
*
|
||||
* 字库文件由 CODE/font/mkfont.py 生成(Unicode BMP 码点索引、升序、
|
||||
* data_offset 相对文件头,见 CODE/font/dbf.h),部署于 FAT32
|
||||
* system/font/simhei_16.dbf(正文/菜单)与 simhei_24.dbf(标题/时钟)。
|
||||
*
|
||||
* 内存布局(identity-mapped 固定地址,无 malloc):
|
||||
* 0x8000000 16px .dbf(≤1.8MB,zhfont_init 启动时加载)
|
||||
* 0x8300000 24px .dbf(≤3.9MB,zhfont_load24 懒加载)
|
||||
*
|
||||
* 字库缺失时 zh_draw_* 自动回退(缺字画空框),绝不阻塞启动。
|
||||
*/
|
||||
#ifndef DESHAB_ZHFONT_H
|
||||
#define DESHAB_ZHFONT_H
|
||||
|
||||
#include <utsm/deshab_ui.h>
|
||||
|
||||
/* 固定地址与容量 */
|
||||
#define ZHFONT16_ADDR 0x8000000ULL
|
||||
#define ZHFONT16_MAX (2u * 1024u * 1024u)
|
||||
#define ZHFONT24_ADDR 0x8300000ULL
|
||||
#define ZHFONT24_MAX (4u * 1024u * 1024u)
|
||||
|
||||
/* FAT32 流式读取函数原型(由持有 fat32_io.h 的编译单元注入,
|
||||
* 即 f32_read_path_lfn_to)。 */
|
||||
typedef int (*zhfont_read_fn)(const char *path, unsigned char *dst,
|
||||
unsigned int cap, unsigned int *out_size);
|
||||
|
||||
/* 注入读取器并加载 16px 字库。返回 0 成功;负值失败(中文渲染回退 ASCII)。 */
|
||||
int zhfont_init(zhfont_read_fn read_to);
|
||||
|
||||
/* 懒加载 24px 字库(开始菜单/小组件首次打开时调用)。返回 0 成功。 */
|
||||
int zhfont_load24(void);
|
||||
|
||||
int zhfont_ok(void); /* 16px 字库可用 */
|
||||
int zhfont_ok24(void); /* 24px 字库可用 */
|
||||
|
||||
/* 直接附加已加载的字库镜像(host SDL 宿主用 malloc 缓冲替代固定地址;
|
||||
* 裸机构建走 zhfont_init 固定地址路径,不使用本接口)。 */
|
||||
void zhfont_attach16(const void *dbf_data);
|
||||
void zhfont_attach24(const void *dbf_data);
|
||||
|
||||
/* UTF-8 解码:推进 *s,返回码点(BMP)。无效序列返回 0xFFFFFFFF 并跳 1 字节。 */
|
||||
unsigned int zh_utf8_decode(const char **s);
|
||||
|
||||
/* 渲染单个 Unicode 码点(16px 字库)。返回推进像素数;未加载/缺字返回宽度 16。 */
|
||||
int zh_draw_char(du_context *ctx, unsigned int codepoint,
|
||||
long long x, long long y, unsigned int fg);
|
||||
|
||||
/* UTF-8 字符串渲染(纯 dbf 字体:ASCII 12px 半宽 + CJK 全宽,风格统一)。
|
||||
* size24 : 0 = 16px 字库(正文/菜单),1 = 24px 字库(标题/时钟)
|
||||
* 返回总像素宽度。 */
|
||||
int zh_draw_string(du_context *ctx, const char *utf8,
|
||||
long long x, long long y, unsigned int fg, int size24);
|
||||
|
||||
/* UTF-8 字符串像素宽度(不绘制)。 */
|
||||
int zh_string_width(const char *utf8, int size24);
|
||||
|
||||
/* 行高:16px 模式 18,24px 模式 26。 */
|
||||
int zh_line_height(int size24);
|
||||
|
||||
#endif /* DESHAB_ZHFONT_H */
|
||||
@@ -0,0 +1,124 @@
|
||||
/* zhfont_test.c — zhfont 渲染冒烟测试(Windows/Linux 本机编译运行,非裸机)
|
||||
*
|
||||
* 验证:
|
||||
* 1. .dbf 附加 + UTF-8 解码 + zh_string_width 一致性
|
||||
* 2. zh_draw_char 单字渲染与 dbf 字形逐像素一致(fg=白 bg=黑 时
|
||||
* 每通道值 == 字形灰度字节)
|
||||
* 3. zh_draw_string 混排推进正确、缺字回退空框
|
||||
*
|
||||
* 用法:clang zhfont_test.c zhfont.c ascii_font.c -I../UTSM/include -I. -o zhfont_test
|
||||
* ./zhfont_test ../font/simhei_16.dbf
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ascii_font.h"
|
||||
#include "zhfont.h"
|
||||
#include "../font/dbf.h"
|
||||
|
||||
#define FB_W 480
|
||||
#define FB_H 120
|
||||
static unsigned int fb[FB_W * FB_H];
|
||||
|
||||
static int fail = 0;
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (!(cond)) { printf("FAIL: %s\n", msg); fail = 1; } \
|
||||
} while (0)
|
||||
|
||||
/* 与 zhfont 相同的 dbf 查找(测试侧独立实现) */
|
||||
static const unsigned char *ref_glyph(const unsigned char *font, unsigned int cp,
|
||||
int *w, int *h) {
|
||||
const dbf_header *hdr = (const dbf_header *)font;
|
||||
const dbf_index_entry *idx = (const dbf_index_entry *)(font + 14);
|
||||
int lo = 0, hi = (int)hdr->count - 1;
|
||||
while (lo <= hi) {
|
||||
int m = (lo + hi) / 2;
|
||||
if ((unsigned)idx[m].codepoint == cp) {
|
||||
*w = idx[m].width; *h = hdr->height;
|
||||
return font + idx[m].offset;
|
||||
}
|
||||
if ((unsigned)idx[m].codepoint < cp) lo = m + 1; else hi = m - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *path = (argc > 1) ? argv[1] : "../font/simhei_16.dbf";
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) { printf("cannot open %s\n", path); return 1; }
|
||||
fseek(f, 0, SEEK_END);
|
||||
long sz = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
unsigned char *buf = malloc(sz);
|
||||
if (fread(buf, 1, sz, f) != (size_t)sz) { printf("read fail\n"); return 1; }
|
||||
fclose(f);
|
||||
|
||||
zhfont_attach16(buf);
|
||||
CHECK(zhfont_ok(), "attach16 failed");
|
||||
|
||||
du_context ctx;
|
||||
du_context_init(&ctx, (unsigned long long)(size_t)fb, FB_W, FB_H, FB_W * 4);
|
||||
du_fill_rect(&ctx, 0, 0, FB_W, FB_H, 0xFF000000u);
|
||||
|
||||
/* --- 1. 单字渲染逐像素比对:中(0x4E2D) 文(0x6587) --- */
|
||||
unsigned int cps[2] = { 0x4E2D, 0x6587 };
|
||||
for (int k = 0; k < 2; k++) {
|
||||
int w, h;
|
||||
const unsigned char *g = ref_glyph(buf, cps[k], &w, &h);
|
||||
CHECK(g != 0, "ref_glyph missing");
|
||||
memset(fb, 0, sizeof(fb));
|
||||
du_fill_rect(&ctx, 0, 0, FB_W, FB_H, 0xFF000000u);
|
||||
int adv = zh_draw_char(&ctx, cps[k], 10, 5, 0xFFFFFFFFu);
|
||||
CHECK(adv == w, "advance mismatch");
|
||||
int diff = 0;
|
||||
for (int r = 0; r < h; r++)
|
||||
for (int c = 0; c < w; c++) {
|
||||
unsigned int px = fb[(5 + r) * FB_W + (10 + c)];
|
||||
unsigned int chan = px & 0xFF; /* bg 黑 fg 白 → 通道值 == du_blend 量化后的 alpha */
|
||||
unsigned int a = g[r * w + c];
|
||||
unsigned int expect = (a == 255) ? 255 : ((a * 255u) >> 8);
|
||||
if (chan != expect) diff++;
|
||||
}
|
||||
CHECK(diff == 0, "pixel mismatch vs dbf glyph");
|
||||
printf("glyph U+%04X: %dx%d advance=%d pixel-diff=%d\n",
|
||||
cps[k], w, h, adv, diff);
|
||||
}
|
||||
|
||||
/* --- 2. UTF-8 解码 --- */
|
||||
{
|
||||
const char *s = "\xe4\xb8\xad"; /* 中 */
|
||||
CHECK(zh_utf8_decode(&s) == 0x4E2D, "utf8 decode 中");
|
||||
CHECK(*s == 0, "utf8 advanced past end");
|
||||
const char *bad = "\xff";
|
||||
CHECK(zh_utf8_decode(&bad) == 0xFFFFFFFFu, "utf8 invalid byte");
|
||||
}
|
||||
|
||||
/* --- 3. 字符串宽度一致性 --- */
|
||||
{
|
||||
const char *s = "Deshab \xe6\xa1\x8c\xe9\x9d\xa2\xe7\xb3\xbb\xe7\xbb\x9f";
|
||||
int w1 = zh_string_width(s, 0);
|
||||
memset(fb, 0, sizeof(fb));
|
||||
du_fill_rect(&ctx, 0, 0, FB_W, FB_H, 0xFF000000u);
|
||||
int w2 = zh_draw_string(&ctx, s, 2, 2, 0xFFFFFFFFu, 0);
|
||||
printf("width: zh_string_width=%d zh_draw_string=%d\n", w1, w2);
|
||||
CHECK(w1 == w2, "string width mismatch");
|
||||
CHECK(w1 > 0, "zero width");
|
||||
}
|
||||
|
||||
/* --- 4. 缺字回退:U+0391 希腊字母(GB2312 无,dbf 应缺字画框) --- */
|
||||
{
|
||||
memset(fb, 0, sizeof(fb));
|
||||
du_fill_rect(&ctx, 0, 0, FB_W, FB_H, 0xFF000000u);
|
||||
zh_draw_char(&ctx, 0x0391, 4, 4, 0xFFFFFFFFu);
|
||||
/* 空框:边框至少有非零像素 */
|
||||
int ink = 0;
|
||||
for (int i = 0; i < FB_W * FB_H; i++) if (fb[i] & 0xFF) ink++;
|
||||
CHECK(ink > 8, "missing-glyph fallback box not drawn");
|
||||
printf("missing-glyph fallback: ink=%d px\n", ink);
|
||||
}
|
||||
|
||||
if (fail) { printf("RESULT: FAIL\n"); return 1; }
|
||||
printf("RESULT: PASS\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user