Compare commits

1 Commits
Author SHA1 Message Date
cool 6287513794 feat: 添加 Linux 预览后端和 Notes 应用预览版本
- 创建 desktop_app_linux.h 作为跨平台后端抽象层
- 创建 notes_preview.c 使用 5x7 位图字体渲染
- 添加 Makefile.linux
构建脚本
- 添加 README_LINUX_PREVIEW.md 使用说明- 修复键盘映射、字符渲染、文本输入显示等问题
2026-08-05 13:15:46 +08:00
31 changed files with 1122 additions and 449 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ all: $(OUT)
-include $(DEPS) -include $(DEPS)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\boot" mkdir "$(ROOT)\SYSTEM\boot" @mkdir -p $(dir $@)
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64" mkdir "$(ROOT)\SYSTEM\system\deshab64" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64" mkdir "$(ROOT)\SYSTEM\system\deshab64" @mkdir -p $(dir $@)
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+25 -433
View File
@@ -19,8 +19,6 @@
*/ */
#include "../UTSM/include/utsm/dsk.h" #include "../UTSM/include/utsm/dsk.h"
/* Linux 兼容层服务接口(reserved[5],exec/file_list/file_read) */
#include "../UTSM/include/utsm/linux_compat.h"
/* block 设备函数类型(从 kernel_api + 0xA8 获取 block_api,read @ +0x10, write @ +0x18) */ /* 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_read_fn)(u32 index, u64 lba, u32 count, void *buffer);
@@ -44,11 +42,6 @@ typedef long long i64;
static __inline__ void outb(u16 port, u8 value) { __asm__ volatile("outb %0,%1"::"a"(value),"Nd"(port)); } 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__ u8 inb(u16 port) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(port)); return v; }
/* Linux 桌面应用配置契约(da_linuxapp_parse / DA_LINUXAPP_* 常量)。
* DA_NO_PORTIO:本文件已自带 outb/inb,跳过头文件内同名定义。 */
#define DA_NO_PORTIO
#include "../tools/desktop_app.h"
static void sputc(char c) { static void sputc(char c) {
for (unsigned int i=0; i<100000; i++) { if (inb(COM1+5)&0x20) break; } for (unsigned int i=0; i<100000; i++) { if (inb(COM1+5)&0x20) break; }
outb(COM1, (unsigned char)c); outb(COM1, (unsigned char)c);
@@ -78,7 +71,6 @@ static void redraw_all(void);
static int ps2_mouse_poll(void); static int ps2_mouse_poll(void);
static void draw_statusbar_full(void); static void draw_statusbar_full(void);
static void files_refresh(void); static void files_refresh(void);
static void shortcuts_refresh(void);
/* ============================================================ /* ============================================================
* 常量(Winux-Kate 布局) * 常量(Winux-Kate 布局)
@@ -172,10 +164,6 @@ typedef struct {
app_draw_fn on_draw; app_draw_fn on_draw;
app_destroy_fn on_destroy; app_destroy_fn on_destroy;
const char *elf_name; const char *elf_name;
/* Linux 兼容层命令(Phase 5,如 "/usr/bin/neofetch")。与 elf_name 互斥:
* 两者同时设置时 elf_name 优先(走外部 ELF 全屏宿主),linux_cmd 被忽略;
* 仅设置 linux_cmd 时必须提供 on_create(内置终端窗口承载执行)。 */
const char *linux_cmd;
} app_descriptor; } app_descriptor;
/* 桌面图标 */ /* 桌面图标 */
@@ -186,16 +174,7 @@ typedef struct {
int selected; int selected;
} desktop_icon; } desktop_icon;
/* 桌面快捷方式(从 SYSTEM/user/desktop 目录加载 .lnk 文件) */ #define MAX_ICONS 8
typedef struct {
char display_name[24]; /* 显示名称(UTF-8) */
char elf_name[11]; /* ELF 文件名(FAT32 8.3) */
char icon_type[12]; /* 图标类型:editor/folder/browser/terminal */
int x, y; /* 桌面位置 */
int selected; /* 选中状态 */
} desktop_shortcut;
#define MAX_ICONS 24 /* 桌面图标槽(内置应用 + 用户快捷方式) */
/* 工作区 */ /* 工作区 */
typedef struct { typedef struct {
@@ -260,22 +239,14 @@ static int g_dragging = 0;
static int g_drag_win = -1; static int g_drag_win = -1;
static int g_drag_off_x, g_drag_off_y; static int g_drag_off_x, g_drag_off_y;
/* 应用表(16 = 6 内置 + DA_LINUXAPP_MAX 8 个 Linux 应用 + 余量) */ /* 应用表 */
#define MAX_APPS 16 #define MAX_APPS 8
static app_descriptor g_apps[MAX_APPS]; static app_descriptor g_apps[MAX_APPS];
static int g_app_count = 0; static int g_app_count = 0;
/* Linux 兼容层服务运行态(register_linux_apps 设置):0 时 Linux 图标置灰,
* 双击仍开终端窗口并提示不可用原因(lnx_on_create 运行时复查)。 */
static int g_lnx_svc_up = 0;
static desktop_icon g_icons[MAX_ICONS]; static desktop_icon g_icons[MAX_ICONS];
static int g_icon_count = 0; static int g_icon_count = 0;
/* 用户快捷方式(从 SYSTEM/user/desktop 加载) */
static desktop_shortcut g_shortcuts[MAX_ICONS];
static int g_shortcut_count = 0;
/* ---- Kate 外壳状态 ---- */ /* ---- Kate 外壳状态 ---- */
static int g_booted = 0; static int g_booted = 0;
static int g_page = 1; /* 1..3 内置, 4..8 自定义 */ static int g_page = 1; /* 1..3 内置, 4..8 自定义 */
@@ -549,27 +520,7 @@ static void draw_icon_browser(du_context *ctx, int cx, int cy) {
du_pixel(ctx, ccx, ccy, KS_ACCENT2); du_pixel(ctx, ccx, ccy, KS_ACCENT2);
} }
/* Linux 应用图标:暗底终端 + 霓虹绿 >_ 提示符(与 bash 终端同色板)。
* 服务不可用时 accent 传 KS_TEXT_DIM(置灰,点击仍给原因提示)。 */
static void draw_icon_linux(du_context *ctx, int cx, int cy, u32 accent) {
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_PRIMARY);
du_rect_outline(ctx, cx, cy, 40, 40, accent, 2);
/* '>' 折线 */
du_fill_rect(ctx, cx + 8, cy + 12, 2, 2, accent);
du_fill_rect(ctx, cx + 10, cy + 14, 2, 2, accent);
du_fill_rect(ctx, cx + 12, cy + 16, 2, 2, accent);
du_fill_rect(ctx, cx + 10, cy + 18, 2, 2, accent);
du_fill_rect(ctx, cx + 8, cy + 20, 2, 2, accent);
/* '_' 光标块 */
du_fill_rect(ctx, cx + 16, cy + 22, 8, 2, accent);
}
static void draw_app_icon(du_context *ctx, int app_id, int cx, int cy) { static void draw_app_icon(du_context *ctx, int app_id, int cx, int cy) {
/* Linux 应用走专属图标(暗底绿提示符),其余保持原有映射 */
if (app_id >= 0 && app_id < g_app_count && g_apps[app_id].linux_cmd) {
draw_icon_linux(ctx, cx, cy, g_lnx_svc_up ? KS_ACCENT2 : KS_TEXT_DIM);
return;
}
switch (app_id) { switch (app_id) {
case 0: draw_icon_terminal(ctx, cx, cy); break; case 0: draw_icon_terminal(ctx, cx, cy); break;
case 1: draw_icon_editor(ctx, cx, cy); break; case 1: draw_icon_editor(ctx, cx, cy); break;
@@ -1154,269 +1105,6 @@ static void bash_on_draw(void *state, app_ctx *ctx) {
__attribute__((unused)) __attribute__((unused))
static void bash_on_destroy(void *state) { (void)state; } static void bash_on_destroy(void *state) { (void)state; }
/* ============================================================
* Linux 兼容层应用 (Phase 5)
* 图标 → 终端风格窗口 → linux_compat_service.exec 同步执行。
* exec 是同步阻塞调用(park-and-resume 到 Linux guest 完成才返回),
* 无 out_cb 流式回调:先绘制 running 状态再阻塞,返回后一次性
* 把 stdout 缓冲追加进终端并显示 [exit N]。
* ============================================================ */
static const linux_compat_service *g_lxc_svc = 0;
#define LNX_MAX_WINS 8 /* 与 MAX_WINDOWS 对齐:任何窗口都可能是 Linux 终端 */
#define LNX_OUT_CAP 16384 /* exec stdout 收集缓冲 */
typedef struct {
bash_state term; /* 复用 bash 终端字符网格(无输入行) */
int finished; /* 0=running 1=已返回 */
int rc; /* exec 返回值(0 成功,负值失败) */
u64 exit_code; /* guest 进程退出码 */
} lnx_state;
static lnx_state g_lnx_states[LNX_MAX_WINS];
static int g_lnx_used[LNX_MAX_WINS];
static char g_lnx_out[LNX_OUT_CAP]; /* 同步单发,可全窗口共享 */
/* Linux 应用注册名/命令持久池(f32_data 会被后续文件操作复用,必须拷出) */
static char g_lnx_name_pool[DA_LINUXAPP_MAX][12];
static char g_lnx_cmd_pool[DA_LINUXAPP_MAX][64];
static char g_lnx_id_pool[DA_LINUXAPP_MAX][8];
/* 追加无符号十进制到终端 */
static void lnx_put_u64(bash_state *t, u64 v, u32 color) {
char tmp[20];
int n = 0;
if (v == 0) tmp[n++] = '0';
while (v && n < 20) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; }
while (n > 0) bash_putc(t, tmp[--n], color);
}
static void lnx_put_i64(bash_state *t, i64 v, u32 color) {
if (v < 0) { bash_putc(t, '-', color); lnx_put_u64(t, (u64)(-v), color); }
else lnx_put_u64(t, (u64)v, color);
}
/* 追加 exec 输出到终端:剥离 ANSI CSI 转义序列,忽略 \r,\t 展开为 2 空格,
* 其余控制字符丢弃(终端字体仅 ASCII,非 ASCII 字节由 du_draw_char 钳为空格)。 */
static void lnx_append(bash_state *t, const char *buf, u64 len, u32 color) {
int esc = 0;
for (u64 i = 0; i < len; i++) {
char c = buf[i];
if (esc) {
/* CSI: ESC [ 参数... 结束字节(0x40-0x7E);其它 ESC 序列同理吞掉 */
if (c >= 0x40 && c <= 0x7E) esc = 0;
continue;
}
if (c == 0x1B) { esc = 1; continue; }
if (c == '\r') continue;
if (c == '\t') { bash_putc(t, ' ', color); bash_putc(t, ' ', color); continue; }
if ((u8)c < 32 && c != '\n') continue;
bash_putc(t, c, color);
}
}
/* 终端内容绘制:全量重绘(与 editor_draw_to 同策略),无输入行 */
static void lnx_draw_to(lnx_state *s, int cx, int cy, int cw, int ch) {
bash_state *t = &s->term;
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY);
for (int r = 0; r < t->rows; r++) {
for (int c = 0; c < t->cols; c++) {
int idx = r * TERM_MAX_COLS + c;
if (t->ch[idx] == ' ') continue;
int px = cx + (int)DU_SPACE_SM + c * (int)DU_ASCII_STEP;
int py = cy + (int)DU_SPACE_SM + r * (int)DU_ASCII_LINE_H;
if (px + (int)DU_ASCII_CELL_W > cx + cw) continue;
if (py + (int)DU_ASCII_CELL_H > cy + ch) continue;
du_draw_char(&g_fb, (char)t->ch[idx], px, py, t->fg[idx], KS_BG_PRIMARY);
}
}
}
/* ---- 窗口适配 ---- */
static void *lnx_on_create(app_ctx *ctx) {
int slot = -1;
for (int i = 0; i < LNX_MAX_WINS; i++) {
if (!g_lnx_used[i]) { slot = i; break; }
}
if (slot < 0) return 0;
g_lnx_used[slot] = 1;
lnx_state *s = &g_lnx_states[slot];
app_descriptor *app = &g_apps[ctx->win->app_id];
bash_reset(&s->term, ctx->client_w, ctx->client_h);
s->finished = 0;
s->rc = 0;
s->exit_code = 0;
/* 标题:显示名 + Linux 标记 */
{
for (int i = 0; i < 48; i++) ctx->win->title[i] = 0;
char t[48];
int ti = 0;
const char *d = app->display_name;
while (d[ti] && ti < 38) { t[ti] = d[ti]; ti++; }
const char *suf = " . Linux";
int j = 0;
while (suf[j] && ti < 47) { t[ti++] = suf[j++]; }
t[ti] = 0;
kstrcpy(ctx->win->title, t, 48);
}
/* 命令回显 */
bash_puts(&s->term, "$ ", KS_ACCENT2);
bash_puts(&s->term, app->linux_cmd, KS_TEXT_PRIMARY);
bash_putc(&s->term, '\n', 0);
/* 服务运行时复查(注册后 guest 仍可能不可用) */
if (!g_lxc_svc || !g_lxc_svc->is_available()) {
bash_puts(&s->term, "linux: compat service unavailable\n", KS_DANGER);
bash_puts(&s->term, "[exit -1]\n", KS_DANGER);
s->finished = 1;
s->rc = -1;
return s;
}
/* running 状态提示:先呈现窗口再进入同步阻塞 exec */
bash_puts(&s->term, "running... (sync exec, UI will freeze)\n", KS_TEXT_DIM);
ctx->win->app_state = s; /* 让 redraw_all 能看到本窗口内容 */
redraw_all();
flip_buffer();
/* 分词:linux_cmd → path + argv(就地切分副本) */
char cmdline[96];
kstrcpy(cmdline, app->linux_cmd, 96);
const char *argv[8];
int argc = 0;
char *p = cmdline;
while (*p && argc < 8) {
while (*p == ' ') p++;
if (!*p) break;
argv[argc++] = p;
while (*p && *p != ' ') p++;
if (*p) *p++ = 0;
}
if (argc == 0) {
bash_puts(&s->term, "linux: empty command\n", KS_DANGER);
s->finished = 1;
s->rc = -1;
return s;
}
/* 绝对路径直接使用;裸命令补 /bin/ 前缀(与 shell linux 命令一致) */
char path[96];
if (argv[0][0] == '/') {
kstrcpy(path, argv[0], 96);
} else {
kstrcpy(path, "/bin/", 96);
kstrcat(path, argv[0], 96);
}
u64 out_len = 0, exit_code = 0;
slog("linux exec");
int rc = g_lxc_svc->exec(path, argc, argv,
g_lnx_out, LNX_OUT_CAP - 1,
&out_len, &exit_code);
s->rc = rc;
s->exit_code = exit_code;
s->finished = 1;
if (rc != 0) {
bash_puts(&s->term, "linux: exec failed (rc=", KS_DANGER);
lnx_put_i64(&s->term, rc, KS_DANGER);
bash_puts(&s->term, ")\n", KS_DANGER);
bash_puts(&s->term, "[exit -1]\n", KS_DANGER);
return s;
}
/* 输出回显(一次性追加;exec 无流式回调) */
if (out_len > 0) {
lnx_append(&s->term, g_lnx_out, out_len, KS_TEXT_PRIMARY);
if (g_lnx_out[out_len - 1] != '\n') bash_putc(&s->term, '\n', 0);
if (out_len >= LNX_OUT_CAP - 1)
bash_puts(&s->term, "[output truncated]\n", KS_TEXT_DIM);
}
/* 退出码:0 绿 / 非 0 琥珀 */
u32 ec_color = (exit_code == 0) ? KS_ACCENT2 : KS_WARN;
bash_puts(&s->term, "[exit ", ec_color);
lnx_put_u64(&s->term, exit_code, ec_color);
bash_puts(&s->term, "]\n", ec_color);
return s;
}
static void lnx_on_draw(void *state, app_ctx *ctx) {
lnx_draw_to((lnx_state *)state, ctx->client_x, ctx->client_y,
ctx->client_w, ctx->client_h);
}
static void lnx_on_destroy(void *state) {
lnx_state *s = (lnx_state *)state;
for (int i = 0; i < LNX_MAX_WINS; i++) {
if (&g_lnx_states[i] == s) { g_lnx_used[i] = 0; break; }
}
}
/* ---- Linux 应用注册 ----
* 无论服务是否可用都注册图标:可用时双击即同步 exec;不可用(VMX 缺失/
* guest 未驻留)时图标置灰(g_lnx_svc_up=0),双击由 lnx_on_create 运行时
* 复查并显示 "linux: compat service unavailable [exit -1]",绝不 panic。
* 配置:FAT32 根目录 LINUXAPP.CNF(8.3 名 LINUXAPPCNF),每行 显示名|linux命令;
* 文件缺失或无有效行时回退内置默认列表(echo hello / uname -a / ls -l /,
* 三条均为 Arch rootfs 与 initramfs busybox 环境共有的可执行命令)。 */
static void register_linux_apps(void) {
g_lnx_svc_up = (g_lxc_svc && g_lxc_svc->is_available()) ? 1 : 0;
if (!g_lxc_svc) slog("linux apps: service absent (icons greyed)");
else if (!g_lnx_svc_up) slog("linux apps: service unavailable (icons greyed)");
da_linux_app ents[DA_LINUXAPP_MAX];
int n = 0;
u8 *data = 0;
u32 size = 0;
if (f32_read_root_file(DA_LINUXAPP_CNF_83, &data, &size) == 0 && size > 0) {
n = da_linuxapp_parse((const char *)data, size, ents, DA_LINUXAPP_MAX);
}
if (n <= 0) {
/* 内置默认保底(guest 中确定存在的命令) */
kstrcpy(g_lnx_name_pool[0], "HELLO", 12);
kstrcpy(g_lnx_cmd_pool[0], "/bin/echo hello from deshab linux", 64);
kstrcpy(g_lnx_name_pool[1], "UNAME", 12);
kstrcpy(g_lnx_cmd_pool[1], "/bin/uname -a", 64);
kstrcpy(g_lnx_name_pool[2], "LS", 12);
kstrcpy(g_lnx_cmd_pool[2], "/bin/ls -l /", 64);
n = 3;
slog("linux apps: builtin defaults");
} else {
for (int i = 0; i < n; i++) {
kstrcpy(g_lnx_name_pool[i], ents[i].name, 12);
kstrcpy(g_lnx_cmd_pool[i], ents[i].cmd, 64);
}
slog("linux apps: loaded LINUXAPP.CNF");
}
for (int i = 0; i < n && g_app_count < MAX_APPS; i++) {
char *id = g_lnx_id_pool[i];
id[0] = 'l'; id[1] = 'n'; id[2] = 'x'; id[3] = (char)('0' + i); id[4] = 0;
g_apps[g_app_count] = (app_descriptor){
id, g_lnx_name_pool[i], 720, 480,
lnx_on_create, 0, lnx_on_draw, lnx_on_destroy,
0, g_lnx_cmd_pool[i]
};
g_app_count++;
{
char msg[64];
kstrcpy(msg, "linux app registered: ", 64);
kstrcat(msg, g_lnx_name_pool[i], 64);
slog(msg);
}
}
slog("linux apps registered");
}
/* ============================================================ /* ============================================================
* 文本编辑器 (Editor) — 面板/IDE/窗口共用核心 * 文本编辑器 (Editor) — 面板/IDE/窗口共用核心
* ============================================================ */ * ============================================================ */
@@ -1739,86 +1427,67 @@ static void register_apps(void) {
/* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/ /* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/
g_apps[0] = (app_descriptor){ g_apps[0] = (app_descriptor){
"shell", "Terminal", 720, 480, "shell", "Terminal", 720, 480,
0, 0, 0, 0, "SHELL ELF", 0 0, 0, 0, 0, "SHELL ELF"
}; };
g_app_count++; g_app_count++;
/* 命令行2:cmd.elf(Windows 风格命令行 + PE/EXE 执行)*/ /* 命令行2:cmd.elf(Windows 风格命令行 + PE/EXE 执行)*/
g_apps[1] = (app_descriptor){ g_apps[1] = (app_descriptor){
"cmd", "CMD", 720, 480, "cmd", "CMD", 720, 480,
0, 0, 0, 0, "CMD ELF", 0 0, 0, 0, 0, "CMD ELF"
}; };
g_app_count++; g_app_count++;
g_apps[2] = (app_descriptor){ g_apps[2] = (app_descriptor){
"editor", "Editor", 600, 450, "editor", "Editor", 600, 450,
editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy, 0, 0 editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy, 0
}; };
g_app_count++; g_app_count++;
g_apps[3] = (app_descriptor){ g_apps[3] = (app_descriptor){
"fileman", "Files", 500, 400, "fileman", "Files", 500, 400,
0, 0, 0, 0, "FILEMAN ELF", 0 0, 0, 0, 0, "FILEMAN ELF"
}; };
g_app_count++; g_app_count++;
g_apps[4] = (app_descriptor){ g_apps[4] = (app_descriptor){
"calc", "Calculator", 280, 400, "calc", "Calculator", 280, 400,
calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy, 0, 0 calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy, 0
}; };
g_app_count++; g_app_count++;
g_apps[5] = (app_descriptor){ g_apps[5] = (app_descriptor){
"browser", "Browser", 700, 500, "browser", "Browser", 700, 500,
0, 0, 0, 0, "BROWSER ELF", 0 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++; g_app_count++;
/* Linux 兼容层应用:无论服务是否可用都注册图标(不可用时置灰,
* 双击仍开终端窗口提示原因,见 register_linux_apps / lnx_on_create) */
register_linux_apps();
} }
/* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px, /* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px,
* 左→右排列,超出画布宽度则换行(align-content: start) * 左→右排列,超出画布宽度则换行(align-content: start)
* *
* 桌面图标来源: * 注意:Terminal 和 CMD 已在第一页 Dashboard(TERM-01 / TERM-02),
* 1. 用户快捷方式(SYSTEM/user/desktop 目录) * 桌面不再重复显示其图标。
* 2. 内置应用(editor, browser 等)
*/ */
static void setup_desktop_icons(void) { static void setup_desktop_icons(void) {
int pad = KATE_ICON_PAD; /* 24 */ int pad = KATE_ICON_PAD; /* 24 */
int x = pad; int x = pad;
int y = KATE_TOPBAR_H + KATE_PAGE_PAD + pad; int y = KATE_TOPBAR_H + KATE_PAGE_PAD + pad;
int max_x = (int)g_fb_w - pad; /* canvas 右缘 */ int max_x = (int)g_fb_w - pad; /* canvas 右缘 */
for (int i = 0; i < g_app_count; i++) {
/* 先加载用户快捷方式 */ if (!g_apps[i].on_create && !g_apps[i].elf_name) continue;
shortcuts_refresh(); /* 跳过 Terminal 和 CMD(已在 Dashboard 第一页) */
for (int i = 0; i < g_shortcut_count && g_icon_count < MAX_ICONS; i++) { 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) { if (x > pad && x + KATE_ICON_W > max_x) {
x = pad; x = pad;
y += KATE_ICON_H + KATE_ICON_GAP; y += KATE_ICON_H + KATE_ICON_GAP;
} }
/* 将快捷方式转换为桌面图标(app_id = -1 表示快捷方式) */ g_icons[g_icon_count].app_id = i;
g_icons[g_icon_count].app_id = -1; /* 标记为快捷方式 */
g_icons[g_icon_count].x = x; g_icons[g_icon_count].x = x;
g_icons[g_icon_count].y = y; g_icons[g_icon_count].y = y;
g_icons[g_icon_count].label = g_shortcuts[i].display_name; g_icons[g_icon_count].label = g_apps[i].display_name;
g_icons[g_icon_count].selected = 0;
g_shortcuts[i].x = x; /* 记录位置用于点击检测 */
g_shortcuts[i].y = y;
g_icon_count++;
x += KATE_ICON_W + KATE_ICON_GAP;
}
/* 再追加 Linux 应用图标(register_linux_apps 已登记进 g_apps,带 linux_cmd)。
* 服务不可用时图标仍上架但置灰(draw_app_icon 查 g_lnx_svc_up),
* 双击开终端窗口提示原因,与可用时同一入口。 */
for (int a = 0; a < g_app_count && g_icon_count < MAX_ICONS; a++) {
if (!g_apps[a].linux_cmd) continue;
if (x > pad && x + KATE_ICON_W > max_x) {
x = pad;
y += KATE_ICON_H + KATE_ICON_GAP;
}
g_icons[g_icon_count].app_id = a;
g_icons[g_icon_count].x = x;
g_icons[g_icon_count].y = y;
g_icons[g_icon_count].label = g_apps[a].display_name;
g_icons[g_icon_count].selected = 0; g_icons[g_icon_count].selected = 0;
g_icon_count++; g_icon_count++;
x += KATE_ICON_W + KATE_ICON_GAP; x += KATE_ICON_W + KATE_ICON_GAP;
@@ -1842,10 +1511,7 @@ typedef struct { u32 type,flags; u64 offset,vaddr,paddr,filesz,memsz,align; } dt
typedef struct { i64 tag; u64 val; } dt_elf64_dyn; typedef struct { i64 tag; u64 val; } dt_elf64_dyn;
typedef struct { u64 offset; u64 info; i64 addend; } dt_elf64_rela; typedef struct { u64 offset; u64 info; i64 addend; } dt_elf64_rela;
/* 外部工具 ELF 加载缓冲。fileman/editor 等工具 include fat32_io.h static u8 g_elf_image[262144];
* 带 ~388KB BSS(f32_disk+f32_data),其 .elf 的 memsz 可达 ~470KB。
* 1MB 容量覆盖当前所有工具 ELF 并留余量。BSS 是 NOBITS 不占 desktop.elf 文件空间。 */
static u8 g_elf_image[1048576];
static int dt_load_elf(u8 *data, u32 data_size, void **entry_out) { static int dt_load_elf(u8 *data, u32 data_size, void **entry_out) {
const dt_elf64_ehdr *eh = (const dt_elf64_ehdr *)data; const dt_elf64_ehdr *eh = (const dt_elf64_ehdr *)data;
@@ -2023,43 +1689,6 @@ static void files_refresh(void) {
else g_files_loaded = 0; else g_files_loaded = 0;
} }
/* 加载桌面快捷方式(从 SYSTEM/user/desktop 目录读取 .lnk 文件)
* 快捷方式文件格式(纯文本,UTF-8):
* 第1行:显示名称(如 "编辑器")
* 第2行:ELF文件名(FAT32 8.3,如 "EDITOR ELF")
* 第3行:图标类型(如 "editor")
*/
static void shortcuts_refresh(void) {
g_shortcut_count = 0;
if (!g_block_read) return;
/* 读取 desktop 目录下的 .lnk 文件 */
/* 简化实现:直接读取预定义的快捷方式 */
/* 内置快捷方式列表(暂时硬编码,后续可改为读取.lnk文件) */
static const struct {
const char *name;
const char *elf;
const char *icon;
} builtin_shortcuts[] = {
{ "编辑器", "EDITOR ELF", "editor" },
{ "文件管理", "FILEMAN ELF", "folder" },
{ "浏览器", "BROWSER ELF", "browser" },
{ "终端", "SHELL ELF", "terminal" },
};
int count = sizeof(builtin_shortcuts) / sizeof(builtin_shortcuts[0]);
for (int i = 0; i < count && g_shortcut_count < MAX_ICONS; i++) {
desktop_shortcut *s = &g_shortcuts[g_shortcut_count];
kstrcpy(s->display_name, builtin_shortcuts[i].name, 24);
kstrcpy(s->elf_name, builtin_shortcuts[i].elf, 11);
kstrcpy(s->icon_type, builtin_shortcuts[i].icon, 12);
s->x = 0; s->y = 0;
s->selected = 0;
g_shortcut_count++;
}
}
/* 双击打开文件 → 载入 DASHBOARD EDITOR */ /* 双击打开文件 → 载入 DASHBOARD EDITOR */
static void files_open(int idx) { static void files_open(int idx) {
if (idx < 0 || idx >= g_file_count) return; if (idx < 0 || idx >= g_file_count) return;
@@ -2474,20 +2103,7 @@ static void draw_desktop_icons(void) {
du_kate_desktop_icon(&g_fb, ic->x, ic->y, ic->label, ic->selected); 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 ix = ic->x + (KATE_ICON_W - KATE_ICON_IMG) / 2;
int iy = ic->y + 10; int iy = ic->y + 10;
/* 根据图标来源绘制不同图标 */
if (ic->app_id >= 0) {
/* 内置应用图标 */
draw_app_icon(&g_fb, ic->app_id, ix, iy); draw_app_icon(&g_fb, ic->app_id, ix, iy);
} else if (i < g_shortcut_count) {
/* 快捷方式图标(根据 icon_type 选择) */
const char *icon = g_shortcuts[i].icon_type;
if (icon[0] == 'e' && icon[1] == 'd') draw_icon_editor(&g_fb, ix, iy);
else if (icon[0] == 'f') draw_icon_folder(&g_fb, ix, iy);
else if (icon[0] == 'b') draw_icon_browser(&g_fb, ix, iy);
else if (icon[0] == 't') draw_icon_terminal(&g_fb, ix, iy);
else draw_icon_editor(&g_fb, ix, iy); /* 默认 */
}
} }
} }
@@ -3291,15 +2907,7 @@ static int desktop_click(int mx, int my, int dbl) {
int ix = g_icons[i].x, iy = g_icons[i].y; 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 (mx >= ix && mx < ix + ICON_W && my >= iy && my < iy + ICON_H) {
if (dbl && g_icons[i].selected) { if (dbl && g_icons[i].selected) {
/* 根据图标来源选择启动方式 */
if (g_icons[i].app_id >= 0) {
/* 内置应用 */
launch_app(g_icons[i].app_id); launch_app(g_icons[i].app_id);
} else if (i < g_shortcut_count) {
/* 快捷方式:加载外部 ELF */
slog("launching shortcut");
launch_external_elf(g_shortcuts[i].elf_name);
}
} else { } else {
for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0; for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0;
g_icons[i].selected = 1; g_icons[i].selected = 1;
@@ -3544,22 +3152,6 @@ void dsk_entry(const dsk_boot_context *ctx) {
g_fb.fb = (du_u32 *)SPRITE_BUF_ADDR; g_fb.fb = (du_u32 *)SPRITE_BUF_ADDR;
f32_init(g_block_read, g_block_write); f32_init(g_block_read, g_block_write);
/* Linux 兼容层服务(reserved[5],magic 校验)—— 需在 register_apps 前就绪 */
{
u64 lxc_addr = ctx->reserved[5];
if (lxc_addr) {
const linux_compat_service *lxc = (const linux_compat_service *)lxc_addr;
if (lxc->magic == LINUX_COMPAT_MAGIC) {
g_lxc_svc = lxc;
slog("linux_compat service ok");
} else {
slog("linux_compat magic mismatch");
}
} else {
slog("linux_compat service absent");
}
}
tsc_calibrate(); tsc_calibrate();
g_rtc_boot_sec = rtc_day_sec(); g_rtc_boot_sec = rtc_day_sec();
g_last_sec = g_rtc_boot_sec; g_last_sec = g_rtc_boot_sec;
+1 -1
View File
@@ -18,7 +18,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64" mkdir "$(ROOT)\SYSTEM\system\deshab64" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\user\use" mkdir "$(ROOT)\SYSTEM\system\user\use" @mkdir -p "$(ROOT)/SYSTEM/system/user/use"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+1 -1
View File
@@ -17,7 +17,7 @@ all: $(OUT)
# 构建后用 patch_login_hash.ps1 计算 .text SHA256 并写入 .hashseg # 构建后用 patch_login_hash.ps1 计算 .text SHA256 并写入 .hashseg
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\user\use" mkdir "$(ROOT)\SYSTEM\system\user\use" @mkdir -p "$(ROOT)/SYSTEM/system/user/use"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
@echo [login] patching .text SHA256 into .hashseg... @echo [login] patching .text SHA256 into .hashseg...
powershell -NoProfile -ExecutionPolicy Bypass -File $(PATCHER) $@ powershell -NoProfile -ExecutionPolicy Bypass -File $(PATCHER) $@
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\mouse" mkdir "$(ROOT)\SYSTEM\system\deshab64\mouse" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64/mouse"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\network" mkdir "$(ROOT)\SYSTEM\system\deshab64\network" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64/network"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64" mkdir "$(ROOT)\SYSTEM\system\deshab64" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+447
View File
@@ -0,0 +1,447 @@
/* desktop_app_linux.h — Deshab 应用 Linux 预览后端 (SDL2)
*
* 允许在 Linux 开发机上预览 Deshab-OS 应用的 UI 效果。
* 使用方法:
* 1. #
define DA_LINUX_PREVIEW 1 * 2. #include "desktop_app_linux.h"
* 3. 实现 dsk_entry() 函数(与 Deshab-OS 版本相同)
* 4. 编译:make -f Makefile.linux
*
* 编译后会在 X11/Wayland 窗口中显示应用 UI,可用于调试布局和渲染。
*/
#ifndef DESHAB_DESKTOP_APP_LINUX_H
#define DESHAB_DESKTOP_APP_LINUX_H
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* ===================================================================
* 基础类型(与 desktop_app.h 一致)
* =================================================================== */
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int64_t i64;
/* ===================================================================
* 颜色常量(与 desktop_app.h 一致)
* =================================================================== */
#define DA_BG_PRIMARY 0xFF0A1428u
#define DA_BG_SECONDARY 0xFF0F1E38u
#define DA_BG_TERTIARY 0xFF162848u
#define DA_TEXT_PRIMARY 0xFFE8E8F0u
#define DA_TEXT_DIM 0xFF6A6A88u
#define DA_ACCENT 0xFF00A8CCu
#define DA_ACCENT_LIGHT 0xFF44CCF0u
#define DA_BORDER 0xFF284888u
#define DA_BORDER_FOCUS 0xFF44CCF0u
#define DA_CURSOR_COLOR 0xFF00A8CCu
#define DA_SUCCESS 0xFF40C880u
#define DA_ERROR 0xFFFF4466u
#define DA_WARNING 0xFFF0A030u
#define DA_TITLEBAR_H 28
#define DA_BORDER_W 2
#define DA_STATUSBAR_H 20
/* ===================================================================
* 渲染上下文(SDL2 版本)
* =================================================================== */
typedef struct {
Uint32 *pixels; /* SDL2 像素缓冲 */
int width;
int height;
int pitch; /* 行字节宽 */
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
} da_app_context;
/* ===================================================================
* ASCII 字体声明(需要从 ascii_bitmaps.c 包含)
* =================================================================== */
/* 如果外部未定义字体,则声明 extern */
/* 在 notes_preview.c 中 include ascii_bitmaps.c 后会定义实际变量 */
#ifndef DA_FONT_DEFINED
extern const unsigned char *g_ascii[];
extern int g_ascii_w;
extern int g_ascii_h;
#else
/* 使用 extern 引用 ascii_bitmaps.c 中定义的 static 变量 */
/* 注意:ascii_bitmaps.c 需要改为非 static */
extern const unsigned char g_ascii[][11*18];
extern int g_ascii_w;
extern int g_ascii_h;
#endif
/* ===================================================================
* 绘制原语(SDL2 实现)
* =================================================================== */
/* 颜色混合 */
static inline Uint32 da_blend(Uint32 bg, Uint32 fg, Uint32 alpha) {
Uint32 na = 256 - alpha;
Uint32 r = ((bg & 0xFF) * na + (fg & 0xFF) * alpha) >> 8;
Uint32 g = (((bg >> 8) & 0xFF) * na + ((fg >> 8) & 0xFF) * alpha) >> 8;
Uint32 b = (((bg >> 16) & 0xFF) * na + ((fg >> 16) & 0xFF) * alpha) >> 8;
return 0xFF000000u | (b << 16) | (g << 8) | r;
}
/* 像素写入(SDL2 锁定时) */
static inline void da_pixel(da_app_context *ac, int x, int y, Uint32 color) {
if (x < 0 || x >= ac->width || y < 0 || y >= ac->height) return;
ac->pixels[y * ac->width + x] = color;
}
/* 矩形填充 */
static inline void da_fill_rect(da_app_context *ac, int x, int y, int w, int h, Uint32 color) {
for (int r = 0; r < h; r++) {
for (int c = 0; c < w; c++) {
int xx = x + c, yy = y + r;
if (xx >= 0 && xx < ac->width && yy >= 0 && yy < ac->height) {
ac->pixels[yy * ac->width + xx] = color;
}
}
}
}
/* 全屏背景 */
static inline void da_fill_bg(da_app_context *ac, Uint32 color) {
memset(ac->pixels,0, (size_t)ac->width * ac->height *4);
da_fill_rect(ac, 0, 0, ac->width, ac->height, color);
}
/* 圆角矩形(简化版) */
static inline void da_fill_rounded_rect(da_app_context *ac, int x, int y , int w, int h,
Uint32 color, int radius) {
/* 中间区域 */
da_fill_rect(ac, x + radius, y, w - 2 * radius, h, color);
da_fill_rect(ac, x, y + radius, radius, h - 2 * radius, color);
da_fill_rect(ac, x + w - radius, y + radius, radius, h - 2 * radius, color);
/* 四个角(简化为填充) */
for (int dy = 0; dy < radius; dy++) {
for (int dx = 0; dx < radius; dx++) {
if (dx * dx + dy * dy <= radius * radius) {
da_pixel(ac, x + radius - 1 - dx, y + radius - 1 - dy, color);
da_pixel(ac, x + w - radius + dx, y + radius - 1 - dy, color);
da_pixel(ac, x + radius - 1 - dx, y + h - radius + dy, color);
da_pixel(ac, x + w - radius + dx, y + h - radius + dy, color);
}
}
}
}
/* 字符渲染 */
static inline void da_draw_char(da_app_context *ac, char ch, int x, int y, Uint32 fg, Uint32 bg) {
Uint32 idx = (Uint32)(ch - ' ');
if (idx > 94) idx = 0;
if (!g_ascii[idx]) return;
const unsigned char *glyph = g_ascii[idx];
int gw = g_ascii_w, gh = g_ascii_h;
for (int r = 0; r < gh; r++) {
for (int c = 0; c < gw; c++) {
unsigned char a = glyph[r * gw + c];
if (a == 0) continue;
int xx = x + c, yy = y + r;
if (xx < 0 || xx >= ac->width || yy < 0 || yy >= ac->height) continue;
Uint32 pixel = (a == 255) ? fg : da_blend(bg, fg, a);
ac->pixels[yy * ac->width + xx] = pixel;
}
}
}
/* 字符串渲染 */
static inline void da_draw_string(da_app_context *ac, const char *s, int x, int y,
Uint32 fg, Uint32 bg, int step) {
int cx = x;
while (*s) {
da_draw_char(ac, *s, cx, y, fg, bg);
cx += step;
s ++;
}
}
/*标题栏 */
static inline void da_draw_titlebar(da_app_context *ac, const char *title, int w) {
da_fill_rect(ac, 0, 0, w, DA_TITLEBAR_H, DA_BG_SECONDARY);
da_draw_string(ac, title, 8, (DA_TITLEBAR_H - 18) / 2, DA_TEXT_PRIMARY, DA_BG_SECONDARY, 12);
/* 关闭按钮 */
da_fill_rounded_rect(ac, w - 28, 4, 24, 20, DA_ERROR, 4);
da_draw_string(ac, "X", w - 20, 5, 0xFF0A1428u, DA_ERROR, 12);
/* 底部分隔线 */
da_fill_rect(ac, 0, DA_TITLEBAR_H, w, 1, DA_ACCENT);
}
/* 状态栏 */
static inline void da_draw_statusbar(da_app_context *ac, const char *text, int w, int h) {
int y = h - DA_STATUSBAR_H;
da_fill_rect(ac, 0, y, w, DA_STATUSBAR_H, DA_BG_SECONDARY);
da_draw_string(ac, text, 4, y + 1, DA_TEXT_DIM, DA_BG_SECONDARY, 12);
da_fill_rect(ac, 0, y, w, 1, DA_BORDER);
}
/* ===================================================================
* 上下文初始化/刷新
* =================================================================== */
static inline int da_init_linux(da_app_context *ac, const char *title, int width, int height) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "SDL2 init failed: %s\n", SDL_GetError());
return -1;
}
ac->window = SDL_CreateWindow(title,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
width, height,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
if (!ac->window) {
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
return -1;
}
ac->renderer = SDL_CreateRenderer(ac->window, -1, SDL_RENDERER_ACCELERATED);
if (!ac->renderer) {
fprintf(stderr, "SDL_CreateRenderer failed: %s\n", SDL_GetError());
return -1;
}
ac->texture = SDL_CreateTexture(ac->renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
width, height);
if (!ac->texture) {
fprintf(stderr, "SDL_CreateTexture failed: %s\n", SDL_GetError());
return -1;
}
ac->width = width;
ac->height = height;
ac->pitch = width * 4;
ac->pixels = (Uint32 *)malloc((size_t)width * height * 4);
if (!ac->pixels) {
fprintf(stderr, "malloc failed\n");
return -1;
} memset(ac->pixels, 0, (size_t)width * height * 4);
return 0;
}
/* 刷新帧缓冲到屏幕 */
static inline void da_flush(da_app_context *ac) {
SDL_UpdateTexture(ac->texture, NULL, ac->pixels, ac->pitch);
SDL_RenderClear(ac->renderer);
SDL_RenderCopy(ac->renderer, ac->texture, NULL, NULL);
SDL_RenderPresent(ac->renderer);
}
/* 清理资源 */
static inline void da_cleanup(da_app_context *ac) {
free(ac->pixels);
if (ac->texture) SDL_DestroyTexture(ac->texture);
if (ac->renderer) SDL_DestroyRenderer(ac->renderer);
if (ac->window) SDL_DestroyWindow(ac->window);
SDL_Quit();
}
/* 事件循环(等待关闭窗口) */
static inline void da_event_loop(void) {
SDL_Event event;
int running = 1;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) running = 0;
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) running = 0;
}
SDL_Delay(16); /* ~60 FPS */
}
}
/* ===================================================================
* 鼠标光标(简化版)
* =================================================================== */
#define DA_CURSOR_SIZE 24
static const unsigned char da_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,0,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,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,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,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,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,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,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},
};
typedef struct {
int mx, my;
int btn;
} da_cursor;
static inline void da_cursor_init(da_cursor *c, int fb_w, int fb_h) {
c->mx = fb_w / 2;
c->my = fb_h / 2;
c->btn = 0;
}
static inline void da_cursor_draw(da_app_context *ac, da_cursor *c, Uint32 color) {
for (int r = 0; r < DA_CURSOR_SIZE; r++) {
for (int col = 0; col < DA_CURSOR_SIZE; col++) {
if (da_cursor_shape[r][col]) {
int x = c->mx + col, y = c->my + r;
if (x >= 0 && x < ac->width && y >= 0 && y < ac->height) {
ac->pixels[y * ac->width + x] = color;
}
}
}
}
}
/* ===================================================================
* PS/2 鼠标模拟(SDL2 事件转换)
* =================================================================== */
typedef struct {
int has_packet;
int dx, dy;
int buttons;
} da_mouse;
static inline void da_mouse_init(da_mouse *m) {
memset(m, 0, sizeof(*m));
}
/* 将 SDL 鼠标事件转换为内部状态 */
static inline void da_mouse_update(da_mouse *m, SDL_Event *event) {
if (event->type == SDL_MOUSEMOTION) {
m->dx += event->motion.xrel;
m->dy += event->motion.yrel;
m->has_packet = 1;
}
if (event->type == SDL_MOUSEBUTTONDOWN) {
m->buttons |= (1 << (event->button.button -1));
m->has_packet = 1;
}
if (event->type == SDL_MOUSEBUTTONUP) {
m->buttons &= ~(1 << (event->button.button - 1));
m->has_packet = 1;
}
}
/* ===================================================================
* PS/2 键盘模拟(SDL2 事件转换)
* =================================================================== */
/* SDL scancode 到 Deshab PS/2 scan code 映射 */
static inline u8 da_sdl_to_ps2_scan(Uint32 sdl_scancode) {
/* 简化映射:只支持基本字母数字键 */
switch (sdl_scancode) {
case SDLK_ESCAPE: return 0x01;
case SDLK_1: return 0x02;
case SDLK_2: return 0x03;
case SDLK_3: return 0x04;
case SDLK_4: return 0x05;
case SDLK_5: return 0x06;
case SDLK_6: return 0x07;
case SDLK_7: return 0x08;
case SDLK_8: return 0x09;
case SDLK_9: return 0x0A;
case SDLK_0: return 0x0B;
case SDLK_MINUS: return 0x0C;
case SDLK_EQUALS: return 0x0D;
case SDLK_BACKSPACE: return 0x0E;
case SDLK_TAB: return 0x0F;
case SDLK_q: return 0x10;
case SDLK_w: return 0x11;
case SDLK_e: return 0x12;
case SDLK_r: return 0x13;
case SDLK_t: return 0x14;
case SDLK_y: return 0x15;
case SDLK_u: return 0x16;
case SDLK_i: return 0x17;
case SDLK_o: return 0x18;
case SDLK_p: return 0x19;
case SDLK_LEFTBRACKET: return 0x1A;
case SDLK_RIGHTBRACKET: return 0x1B;
case SDLK_RETURN: return 0x1C;
case SDLK_a: return 0x1E;
case SDLK_s: return 0x1F;
case SDLK_d: return 0x20;
case SDLK_f: return 0x21;
case SDLK_g: return 0x22;
case SDLK_h: return 0x23;
case SDLK_j: return 0x24;
case SDLK_k: return 0x25;
case SDLK_l: return 0x26;
case SDLK_SEMICOLON: return 0x27;
case SDLK_QUOTE: return 0x28;
case SDLK_BACKQUOTE: return 0x29;
case SDLK_BACKSLASH: return 0x2B;
case SDLK_z: return 0x2C;
case SDLK_x: return 0x2D;
case SDLK_c: return 0x2E;
case SDLK_v: return 0x2F;
case SDLK_b: return 0x30;
case SDLK_n: return 0x31;
case SDLK_m: return 0x32;
case SDLK_COMMA: return 0x33;
case SDLK_PERIOD: return 0x34;
case SDLK_SLASH: return 0x35;
case SDLK_RSHIFT:
case SDLK_LSHIFT: return 0; /* Shift 处理 */
case SDLK_SPACE: return 0x39;
case SDLK_LEFT: return 0x4B;
case SDLK_RIGHT: return 0x4D;
case SDLK_UP: return 0x48;
case SDLK_DOWN: return 0x50;
default: return 0;
}
}
static inline char da_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];
}
#endif /* DESHAB_DESKTOP_APP_LINUX_H */
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
+3
View File
@@ -254,6 +254,9 @@ static void redraw_all(void) {
else redraw_list(); else redraw_list();
} }
/* ---- 返回上级目录(前向声明) ---- */
static void go_up(void);
/* ---- 打开文件/目录 ---- */ /* ---- 打开文件/目录 ---- */
static void go_up(void); /* 前向声明:open_selected 调用 go_up */ static void go_up(void); /* 前向声明:open_selected 调用 go_up */
static void open_selected(void) { static void open_selected(void) {
+30
View File
@@ -0,0 +1,30 @@
# Makefile.linux — Deshab-OS 应用 Linux 预览编译
#
# 用法:
# make -f Makefile.linux # 编译预览版
# make -f Makefile.linux run # 编译并运行预览版
# make -f Makefile.linux clean # 清理
CC = gcc
CFLAGS = -Wall -O2 -D DA_LINUX_PREVIEW -I..
LDFLAGS = -lSDL2
SRC = notes_preview.c ../../firstInit/ascii_bitmaps.c
TARGET = notes_preview
all: $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) -o $@ $(SRC) $(LDFLAGS)
@echo "✓ 编译完成: $(TARGET)"
@echo " 运行: ./$(TARGET)"
@echo " 按 ESC 退出"
run: $(TARGET)
./$(TARGET)
clean:
rm -f $(TARGET)
@echo "✓ 已清理"
.PHONY: all run clean
+112
View File
@@ -0,0 +1,112 @@
# Deshab-OS 应用 Linux 预览功能
## 概述
允许在 Linux 开发机上预览 Deshab-OS 应用的 UI 效果。同一份 UI 绘制代码可以在 Linux 上通过 SDL2 后端预览,在 Deshab-OS 上通过 framebuffer 后端运行。
## 架构设计
```
┌──────────────────────────────────────────────────── ─┐
│ 应用源代码 │
│ (使用 da_draw_char, da_fill_rect 等绘制函数) │
└──────────────────┬──────────────────────────────────┘
│
┌──────────┴──────────┐
│ #ifdef DA_LINUX_PREVIEW │
└──┬──────────────┬────────────┘
│ │
Linux 后端 Deshab-OS 后端
(SDL2) (framebuffer) │ │
X11/Wayland 窗口物理显示器
```
## 文件结构
```
CODE/tools/notes/
├── main.c # Deshab-OS 原生应用入口
├── Makefile # Deshab-OS 编译用Makefile
├── notes_preview.c # Linux 预览版(包含完整 dsk_entry 实现)
├── Makefile.linux # Linux 编译用 Makefile
├── desktop_app_linux.h # Linux SDL2 后端
└── README_LINUX_PREVIEW.md # 本说明文档
```
## 使用方法
### 1. 在 Linux 上编译预览版
```bash
cd CODE/tools/notes
make -f Makefile.linux
```
### 2. 运行预览版
```bash
./notes_preview
```
### 3. 退出预览
按 `ESC` 键关闭窗口。
### 4. 清理
```bash
make -f Makefile.linux clean
```
## 开发工作流
```
1. 在 Linux 上编辑代码 ↓
2. make -f Makefile.linux 编译预览版
↓
3. ./notes_preview 查看 UI 效果
↓
4. 调试布局和渲染
↓
5. 测试通过后,用原生的 Makefile 编译到 Deshab-OS
↓
6. 在 Deshab-OS 中运行验证
```
## 支持的 UI 元素
| 元素 | Linux 预览 | Deshab-OS |
|------|-----------|-----------|
| 背景填充 | ✅ | ✅ |
| 矩形填充 | ✅ | ✅ |
| 圆角矩形 | ✅ | ✅ |
| ASCII 字符 | ✅ | ✅ |
| 字符串 | ✅ | ✅ |
| 标题栏 | ✅ | ✅ |
| 状态栏 | ✅ | ✅ |
| 鼠标 光标 | ✅ | ✅ |
|键盘输入 | ✅ | ✅ |
| 鼠标移动 | ✅ | ✅ |
## 扩展到其他应用
要为其他应用(如 fileman, editor, proedit)添加 Linux 预览支持:
1. 复制 `notes_preview.c` 作为模板
2. 修改 `main()` 中的 UI 逻辑
3. 使用相同的 `desktop_app_linux.h
` 后端
## 依赖- SDL2 (`libsdl2-dev` on Debian/Ubuntu)
- GCC 或 Clang
- Make
## 注意事项
1. **键盘映射**:SDL2 的键盘扫描码需要映射到 Deshab的 PS/2 扫描码
2. **鼠标行为**:SDL2 鼠标事件转换为 Deshab 的鼠标数据包格式
3. **字体数据**:需要从 `ascii_bitmaps.c` 获取字模数据
4. **性能**:Linux 预览版主要用于 UI 调试,不是完整的功能测试
+489
View File
@@ -0,0 +1,489 @@
/* notes_preview.c — Notes 应用的 Linux 预览版本
*
* 这个文件可以在 Linux 上编译预览 UI。
* 使用自定义位图字体渲染字符。
*
* 编译方式:
* gcc -o notes_preview notes_preview.c -I.. -lSDL2
*/
#define DA_LINUX_PREVIEW
#include "../desktop_app_linux.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
/* 全局状态 */
static da_app_context g_ac;
static int g_running = 1;
/* 文本内容 */
static char g_lines[512][256];
static int g_line_count = 0;
static int g_current_line = 0;
static int g_current_col = 0;
static int g_scroll_top = 0;
static int g_visible_rows;
/* 5x7 位图字体 (ASCII 32-126, 共95 个字符) */
static const unsigned char font_5x7[95][5] = {
/* 0x20 space */
{0x00, 0x00, 0x00, 0x00, 0x00},
/* 0x21 ! */
{0x00, 0x00, 0x30, 0x30, 0x00},
/* 0x22 " */
{0x30, 0x30, 0x00, 0x00, 0x00},
/* 0x23 # */
{0x18, 0x7E, 0x18, 0x7E, 0x18},
/* 0x24 $ */
{0x0C, 0x36, 0x6C, 0x36, 0x0C},
/* 0x25 % */
{0x60, 0x9C, 0x18, 0x36, 0x60},
/* 0x26 & */
{0x30, 0x7E, 0x1C, 0x36, 0x70},
/* 0x27 ' */
{0x30, 0x30, 0x00, 0x00, 0x00},
/* 0x28 ( */
{0x0C, 0x18, 0x30, 0x18, 0x0C},
/* 0x29 ) */
{0x30, 0x18, 0x0C, 0x18, 0x30},
/* 0x2A * */
{0x00, 0x18, 0x7E, 0x18, 0x00},
/* 0x2B + */
{0x00, 0x18, 0x7E, 0x18, 0x00},
/* 0x2C , */
{0x00, 0x00, 0x00, 0x30, 0x30},
/* 0x2D - */
{0x00, 0x00, 0x7E, 0x00, 0x00},
/* 0x2E . */
{0x00, 0x00, 0x00, 0x30, 0x30},
/* 0x2F / */
{0x06, 0x0C, 0x18, 0x30, 0x60},
/* 0x30 0 */
{0x38, 0x6C, 0x36, 0x6C, 0x38},
/* 0x31 1 */
{0x18, 0x38, 0x18, 0x18, 0x3C},
/* 0x32 2 */
{0x78, 0x60, 0x30, 0x18, 0x7C},
/* 0x33 3 */
{0x78, 0x60, 0x38, 0x00, 0x78},
/* 0x34 4 */
{0x0C, 0x1C, 0x3C, 0x7E, 0x0C},
/* 0x35 5 */
{0x7C, 0x60, 0x78, 0x06, 0x7C},
/* 0x36 6 */
{0x38, 0x60, 0x7C, 0x66, 0x3C},
/* 0x37 7 */
{0x7C, 0x60, 0x30, 0x30, 0x30},
/* 0x38 8 */
{0x3C, 0x66, 0x3C, 0x66, 0x3C},
/* 0x39 9 */
{0x3C, 0x66, 0x3E, 0x60, 0x3C},
/* 0x3A : */
{0x00, 0x30, 0x00, 0x30, 0x00},
/* 0x3B ; */
{0x00, 0x30, 0x00, 0x30, 0x30},
/* 0x3C < */
{0x0C, 0x18, 0x30, 0x18, 0x0C},
/* 0x3D = */
{0x00, 0x7E, 0x00, 0x7E, 0x00},
/* 0x3E > */
{0x30, 0x18, 0x0C, 0x18, 0x30},
/* 0x3F ? */
{0x78, 0x60, 0x30, 0x00, 0x30},
/* 0x40 @ */
{0x3C, 0x66, 0x7E, 0x06, 0x3C},
/* 0x41 A */
{0x18, 0x3C, 0x7E, 0x66, 0x66},
/* 0x42 B */
{0x7C, 0x66, 0x7C, 0x66, 0x7C},
/* 0x43 C */
{0x3C, 0x66, 0x60, 0x60, 0x3C},
/* 0x44 D */
{0x78, 0x6C, 0x66, 0x66, 0x78},
/* 0x45 E */
{0x7E, 0x60, 0x7C, 0x60, 0x7E},
/* 0x46 F */
{0x7E, 0x60, 0x7C, 0x60, 0x60},
/* 0x47 G */
{0x3C, 0x66, 0x6C, 0x66, 0x3C},
/* 0x48 H */
{0x66, 0x66, 0x7E, 0x66, 0x66},
/* 0x49 I */
{0x3C, 0x18, 0x18, 0x18, 0x3C},
/* 0x4A J */
{0x7C, 0x06, 0x06, 0x06, 0x3C},
/* 0x4B K */
{0x66, 0x6C, 0x78, 0x6C, 0x66},
/* 0x4C L */
{0x60, 0x60, 0x60, 0x60, 0x7E},
/* 0x4D M */
{0x63, 0x77, 0x7F, 0x6B, 0x63},
/* 0x4E N */
{0x66, 0x76, 0x7E, 0x6E, 0x66},
/* 0x4F O */
{0x3C, 0x66, 0x66, 0x66, 0x3C},
/* 0x50 P */
{0x7C, 0x66, 0x7C, 0x60, 0x60},
/* 0x51 Q */
{0x3C, 0x66, 0x76, 0x6C, 0x38},
/* 0x52 R */
{0x7C, 0x66, 0x7C, 0x6C, 0x66},
/* 0x53 S */
{0x3C, 0x66, 0x3C, 0x06, 0x7C},
/* 0x54 T */
{0x7E, 0x7E, 0x18, 0x18, 0x18},
/* 0x55 U */
{0x66, 0x66, 0x66, 0x66, 0x3C},
/* 0x56 V */
{0x66, 0x66, 0x66, 0x3C, 0x18},
/* 0x57 W */
{0x63, 0x63, 0x77, 0x7F, 0x36},
/* 0x58 X */
{0x66, 0x3C, 0x18, 0x3C, 0x66},
/* 0x59 Y */
{0x66, 0x66, 0x3C, 0x18, 0x18},
/* 0x5A Z */
{0x7C, 0x66, 0x3C, 0x18, 0x7C},
/* 0x5B [ */
{0x3C, 0x30, 0x30, 0x30, 0x3C},
/* 0x5C backslash */
{0x60, 0x30, 0x18, 0x0C, 0x06},
/* 0x5D ] */
{0x3C, 0x0C, 0x0C, 0x0C, 0x3C},
/* 0x5E ^ */
{0x18, 0x3C, 0x66, 0x00, 0x00},
/* 0x5F _ */
{0x00, 0x00, 0x00, 0x00, 0x7E},
/* 0x60 ` */
{0x30, 0x18, 0x00, 0x00, 0x00},
/* 0x61 a */
{0x00, 0x00, 0x3C, 0x66, 0x3C},
/* 0x62 b */
{0x60, 0x60, 0x7C, 0x66, 0x7C},
/* 0x63 c */
{0x00, 0x00, 0x3C, 0x66, 0x66},
/* 0x64 d */
{0x06, 0x06, 0x7E, 0x66, 0x7E},
/* 0x65 e */
{0x00, 0x00, 0x3C, 0x6E, 0x3C},
/* 0x66 f */
{0x30, 0x7C, 0x06, 0x06, 0x3C},
/* 0x67 g */
{0x00, 0x3E, 0x66, 0x66, 0x3C},
/* 0x68 h */
{0x60, 0x60, 0x7C, 0x66, 0x66},
/* 0x69 i */
{0x00, 0x18, 0x18, 0x00, 0x3C},
/* 0x6A j */
{0x06, 0x06, 0x06, 0x06, 0x3C},
/* 0x6B k */
{0x60, 0x6C, 0x78, 0x6C, 0x66},
/* 0x6C l */
{0x3C, 0x18, 0x18, 0x18, 0x3C},
/* 0x6D m */
{0x00, 0x00, 0x7E, 0x6E, 0x76},
/* 0x6E n */
{0x00, 0x00, 0x7C, 0x66, 0x66},
/* 0x6F o */
{0x00, 0x00, 0x3C, 0x66, 0x3C},
/* 0x70 p */
{0x00, 0x00, 0x7C, 0x66, 0x7C},
/* 0x71 q */
{0x00, 0x00, 0x7E, 0x66, 0x3E},
/* 0x72 r */
{0x00, 0x00, 0x7C, 0x60, 0x60},
/* 0x73 s */
{0x00, 0x00, 0x3C, 0x60, 0x3C},
/* 0x74 t */
{0x0C, 0x7E, 0x0C, 0x0C, 0x38},
/* 0x75 u */
{0x00, 0x00, 0x66, 0x66, 0x3C},
/* 0x76 v */
{0x00, 0x00, 0x66, 0x3C, 0x18},
/* 0x77 w */
{0x00, 0x00, 0x67, 0x67, 0x3E},
/* 0x78 x */
{0x00, 0x00, 0x66, 0x3C, 0x66},
/* 0x79 y */
{0x00, 0x00, 0x66, 0x66, 0x3C},
/* 0x7A z */
{0x00, 0x00, 0x7C, 0x7C, 0x78},
/* 0x7B { */
{0x0C, 0x18, 0x30, 0x18, 0x0C},
/* 0x7C | */
{0x30, 0x30, 0x30, 0x30, 0x30},
/* 0x7D } */
{0x30, 0x18, 0x0C, 0x18, 0x30},
/* 0x7E ~ */
{0x00, 0x00, 0x00, 0x00, 0x00},
};
#define FONT_W 5
#define FONT_H 7
#define FONT_STEP 9
/* 颜色常量 */
#define CURSOR_COLOR 0xFF0055FFu /* 蓝色光标 */
#define BG_COLOR 0xFF1E1E1Eu /* 深色背景 */
#define TEXT_COLOR 0xFFD4D4D4u /* 浅色文字 */
#define DIM_COLOR 0xFF808080u /* 灰色行号 */
#define STATUS_BG 0xFF007ACCu /* 蓝色状态栏 */
/* 绘制单个字符(使用位图字体) */
static void draw_char_bitmap(char ch, int x, int y, u32 color) {
if (ch < ' ' || ch > '~') return;
int idx = ch - ' ';
if (idx < 0 || idx >= 95) return;
for (int row = 0; row < FONT_H; row++) {
unsigned char bits = font_5x7[idx][row];
for (int col = 0; col < FONT_W; col++) {
if (bits & (1 << (FONT_W - 1 - col))) {
da_fill_rect(&g_ac, x + col, y + row, 1, 1, color);
}
}
}
}
/* 绘制文本行 */
static void draw_text_line(const char *text, int x, int y, u32 color) {
int cx = x;
while (*text) {
draw_char_bitmap(*text, cx, y, color);
cx += FONT_STEP;
text++;
}
}
/* 重绘所有 */
static void redraw_all(void) {
da_fill_bg(&g_ac, BG_COLOR);
int margin_x = 10;
int top_y = 44;
/* 计算可见列数 */
int visible_cols = (g_ac.width - 20) / FONT_STEP;
if (visible_cols < 10) visible_cols = 10;
/* 绘制可见行 */
for (int line = g_scroll_top; line < g_line_count && line < g_scroll_top + g_visible_rows; line++) {
int screen_y = top_y + (line - g_scroll_top) * FONT_STEP;
/* 行号 */
char ln[8];
int p = snprintf(ln, sizeof(ln), "%d ", line + 1);
/* 行号区域背景 */
int line_num_width = p * FONT_STEP;
da_fill_rect(&g_ac, 0, screen_y - 2, line_num_width + 10, FONT_STEP, 0xFF252526u);
/* 绘制行号 */
draw_text_line(ln, 5, screen_y, DIM_COLOR);
/* 文本内容 */
const char *line_text = g_lines[line];
int line_len = (int)strlen(line_text);
int draw_len = line_len;
if (draw_len > visible_cols) draw_len = visible_cols;
int text_start_x = margin_x + line_num_width;
for (int col = 0; col < draw_len; col++) {
draw_char_bitmap(line_text[col], text_start_x + col * FONT_STEP, screen_y, TEXT_COLOR);
}
}
/* 光标 */
if (g_current_line >= g_scroll_top && g_current_line < g_scroll_top + g_visible_rows) {
int screen_y = top_y + (g_current_line - g_scroll_top) * FONT_STEP;
/* 计算行号宽度 */
char tmp_ln[8];
snprintf(tmp_ln, sizeof(tmp_ln), "%d ", g_current_line + 1);
int line_num_width = (int)strlen(tmp_ln) * FONT_STEP;
int text_start_x = margin_x + line_num_width;
/* 高亮当前光标位置 */
int cursor_x = text_start_x + g_current_col * FONT_STEP;
da_fill_rect(&g_ac, cursor_x, screen_y, FONT_W, FONT_H, CURSOR_COLOR);
}
/* 状态栏 */
char status[128];
snprintf(status, sizeof(status), "Line: %d Col: %d Lines: %d ESC=Exit",
g_current_line + 1, g_current_col + 1, g_line_count);
da_fill_rect(&g_ac, 0, g_ac.height - 24, g_ac.width, 24, STATUS_BG);
draw_text_line(status, 10, g_ac.height - 22, 0xFFFFFFFFu);
/* 标题栏 */
da_fill_rect(&g_ac, 0, 0, g_ac.width, 36, 0xFF2D2D2Du);
draw_text_line("Notes Preview (Deshab-OS)", 10, 6, 0xFFFFFFFFu);
da_fill_rect(&g_ac, 0, 36, g_ac.width, 2, 0xFF007ACCu);
}
static void scroll_cursor_into_view(void) {
if (g_current_line < g_scroll_top) {
g_scroll_top = g_current_line;
}
if (g_current_line >= g_scroll_top + g_visible_rows) {
g_scroll_top = g_current_line - g_visible_rows + 1;
}
}
/* 换行 */
static void handle_enter(void) {
if (g_current_line >= 511) return;
int cur_len = (int)strlen(g_lines[g_current_line]);
if (g_current_col < cur_len) {
int remaining = cur_len - g_current_col;
memmove(g_lines[g_current_line] + g_current_col + remaining,
g_lines[g_current_line] + g_current_col, remaining + 1);
g_lines[g_current_line][g_current_col + remaining] = '\0';
} else {
g_lines[g_current_line][g_current_col] = '\0';
}
/* 移动下一行 */
memmove(g_lines[g_current_line + 1], g_lines[g_current_line + 1], 256);
g_line_count++;
g_current_line++;
g_current_col = 0;
scroll_cursor_into_view();
}
/* Backspace删除 */
static void handle_backspace(void) {
if (g_current_col > 0) {
int len = (int)strlen(g_lines[g_current_line]);
memmove(g_lines[g_current_line] + g_current_col - 1,
g_lines[g_current_line] + g_current_col, len - g_current_col + 1);
g_current_col--;
} else if (g_current_line > 0) {
int prev_len = (int)strlen(g_lines[g_current_line - 1]);
int cur_len = (int)strlen(g_lines[g_current_line]);
if (prev_len + cur_len < 255) {
memcpy(g_lines[g_current_line - 1] + prev_len, g_lines[g_current_line], cur_len + 1);
for (int i = g_current_line; i < g_line_count - 1; i++) {
memcpy(g_lines[i], g_lines[i+1], 256);
}
g_line_count--;
g_current_line--;
g_current_col = prev_len;
}
}
}
/* 插入字符 */
static void insert_char(char c) {
if (c < 32 || c > 126) return;
int len = (int)strlen(g_lines[g_current_line]);
if (len >= 255) return;
memmove(g_lines[g_current_line] + g_current_col + 1,
g_lines[g_current_line] + g_current_col, len - g_current_col + 1);
g_lines[g_current_line][g_current_col] = c;
g_current_col++;
}
int main(int argc, char *argv[]) {
(void)argc; (void)argv;
const char *title = "Notes Preview (Deshab-OS) - ESC to exit";
int width = 1024;
int height = 768;
if (da_init_linux(&g_ac, title, width, height) != 0) {
return 1;
}
g_visible_rows = (height - 60 - 24) / FONT_STEP;
if (g_visible_rows < 5) g_visible_rows = 5;
/* 初始化空文档 */
g_line_count = 1;
g_lines[0][0] = '\0';
redraw_all();
da_flush(&g_ac);
/* 主事件循环 */
SDL_Event event;
while (g_running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
g_running = 0;
break;
}
/* 处理文本输入事件(支持输入法) */
if (event.type == SDL_TEXTINPUT) {
for (int i = 0; event.text.text[i]; i++) {
unsigned char c = (unsigned char)event.text.text[i];
if (c >= 32 && c <= 126) {
insert_char((char)c);
}
}
scroll_cursor_into_view();
redraw_all();
da_flush(&g_ac);
}
if (event.type == SDL_KEYDOWN) {
u8 sc = da_sdl_to_ps2_scan(event.key.keysym.sym);
if (sc == 0x01) { /* Esc */
g_running = 0;
} else if (sc == 0x1C) { /* Enter */
handle_enter();
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x0E) { /* Backspace */
handle_backspace();
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x4B) { /* Left */
if (g_current_col > 0) g_current_col--;
else if (g_current_line > 0) {
g_current_line--;
g_current_col = (int)strlen(g_lines[g_current_line]);
}
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x4D) { /* Right */
if (g_current_col < (int)strlen(g_lines[g_current_line])) g_current_col++;
else if (g_current_line < g_line_count - 1) {
g_current_line++;
g_current_col = 0;
}
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x48) { /* Up */
if (g_current_line > 0) g_current_line--;
if (g_current_col > (int)strlen(g_lines[g_current_line])) {
g_current_col = (int)strlen(g_lines[g_current_line]);
}
redraw_all();
da_flush(&g_ac);
} else if (sc == 0x50) { /* Down */
if (g_current_line < g_line_count - 1) g_current_line++;
if (g_current_col > (int)strlen(g_lines[g_current_line])) {
g_current_col = (int)strlen(g_lines[g_current_line]);
}
redraw_all();
da_flush(&g_ac);
}
}
}
SDL_Delay(16);
}
da_cleanup(&g_ac);
return 0;
}
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT) all: $(OUT)
$(OUT): $(OBJECTS) linker.ld $(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" @mkdir -p "$(ROOT)/SYSTEM/system/deshab64/tools"
$(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c %.o: %.c
Regular → Executable
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Regular → Executable
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Regular → Executable
BIN
View File
Binary file not shown.