Compare commits

3 Commits
Author SHA1 Message Date
xyou 8717ea8f20 docs: 多智能体推进收尾交接文档 + Bug记录/策划文档状态修正
新增 docs/上下文/260805/交接文档-多智能体推进收尾.md:W2-W6 全程成果、W6 丢失项、Bug 状态核对、下一步优先级、环境坑点

Bug记录.txt: BUG-005/006/007 状态 OPEN->FIXED(代码已修于 a26866e,文档过时未同步)

系统开发策划.md: M2 进度更新(vector allocator/LAPIC EOI 标完成,MSI-X 标 NVMe完成/virtio_net待做,B7 阻塞点更新)
2026-08-05 14:08:11 +08:00
xyou 4a62932db2 FUCK3 2026-08-05 13:58:13 +08:00
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
51 changed files with 2407 additions and 467 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ all: $(OUT)
-include $(DEPS)
$(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\boot" mkdir "$(ROOT)\SYSTEM\boot"
@mkdir -p $(dir $@)
$(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c
+275
View File
@@ -0,0 +1,275 @@
/* pe_dll_manager.c — PE 真实 DLL 加载管理器实现。
*
* 加载流程:
* 1. pe_dll_resolve(dll, func) 被 resolve_imports 调用(shim miss 后)
* 2. 若 DLL 未缓存,pe_dll_load 读取 DLL 文件 → 复制到 arena → pe_load_image
* 3. pe_load_image 内部 resolve_imports 递归解析该 DLL 的依赖
* 4. DLL 加载完成后,解析导出表按名称查找函数地址
*
* 内存安全:fat32_read_path 返回 static 缓冲(g_dsk_fat32_filedata),
* 递归加载会覆盖该缓冲。因此 pe_dll_load 先将文件数据复制到 arena,
* 再调 pe_load_image,确保递归不破坏外层 pe_data。
*/
#include "pe_dll_manager.h"
#include "pe_loader.h"
#include "pe_shim.h"
#include <utsm/arena.h>
#include <utsm/log.h>
#include <utsm/types.h>
#include <utsm/pe.h>
/* ---- PE 导出目录结构(packed,与 pe_loader.h 风格一致) ---- */
typedef struct __attribute__((packed)) {
u32 characteristics;
u32 time_date_stamp;
u16 major_version;
u16 minor_version;
u32 name_rva;
u32 ordinal_base;
u32 number_of_functions;
u32 number_of_names;
u32 address_of_functions; /* EAT RVA */
u32 address_of_names; /* ENPT RVA */
u32 address_of_name_ordinals; /* EOT RVA */
} image_export_directory;
/* ---- DLL 缓存 ---- */
#define PE_DLL_CACHE_MAX 32
#define PE_DLL_NAME_MAX 64
enum {
DLL_STATE_NONE = 0,
DLL_STATE_LOADING = 1,
DLL_STATE_LOADED = 2,
};
typedef struct {
char name[PE_DLL_NAME_MAX]; /* 小写 DLL 名 */
u64 image_base; /* pe_load_image 返回基址 */
u32 image_size; /* SizeOfImage */
u32 export_dir_rva; /* 导出表 RVA(0=无导出) */
u32 export_dir_size;
int state; /* DLL_STATE_* */
} pe_dll_entry;
static pe_dll_entry g_dll_cache[PE_DLL_CACHE_MAX];
static pe_dll_reader_fn g_dll_reader = 0;
int g_pe_dll_manager_ready = 0;
/* ---- 辅助函数 ---- */
/* ASCII 大小写不敏感字符串比较(freestanding,无 strcmp) */
static int dll_streq_ci(const char *a, const char *b) {
if (!a || !b) return 0;
while (*a && *b) {
char ca = *a, cb = *b;
if (ca >= 'a' && ca <= 'z') ca = (char)(ca - 32);
if (cb >= 'a' && cb <= 'z') cb = (char)(cb - 32);
if (ca != cb) return 0;
a++; b++;
}
return *a == *b;
}
/* 将 DLL 名转为小写到 dst(最多 n-1 字符 + NUL) */
static void dll_name_lower(const char *src, char *dst, int n) {
int i = 0;
while (src[i] && i < n - 1) {
char c = src[i];
if (c >= 'A' && c <= 'Z') c = (char)(c + 32);
dst[i] = c;
i++;
}
dst[i] = 0;
}
/* 在缓存中查找 DLL 名(大小写不敏感)。返回索引,-1 未找到。 */
static int dll_cache_find(const char *dll_name) {
char lower[PE_DLL_NAME_MAX];
dll_name_lower(dll_name, lower, sizeof(lower));
for (int i = 0; i < PE_DLL_CACHE_MAX; i++) {
if (g_dll_cache[i].state == DLL_STATE_NONE) continue;
if (dll_streq_ci(g_dll_cache[i].name, lower)) return i;
}
return -1;
}
/* 在缓存中分配一个空闲槽。返回索引,-1 缓存满。 */
static int dll_cache_alloc(void) {
for (int i = 0; i < PE_DLL_CACHE_MAX; i++) {
if (g_dll_cache[i].state == DLL_STATE_NONE) return i;
}
return -1;
}
/* 从已加载的 PE 镜像中提取导出表 RVA 和 size。
* image 指向 pe_load_image 分配的镜像基址(headers 已映射)。 */
static void extract_export_dir(u64 image_base, u32 *out_rva, u32 *out_size) {
*out_rva = 0;
*out_size = 0;
const u8 *img = (const u8 *)image_base;
/* DOS header */
const image_dos_header *dos = (const image_dos_header *)img;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return;
/* NT headers */
const u8 *nt = img + dos->e_lfanew;
if (*(const u32 *)nt != IMAGE_NT_SIGNATURE) return;
const image_file_header *fh = (const image_file_header *)(nt + 4);
const void *opt = (const void *)(fh + 1);
u16 magic = *(const u16 *)opt;
const image_data_directory *data_dir;
if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
const image_optional_header64 *oh = (const image_optional_header64 *)opt;
data_dir = oh->data_directory;
} else {
const image_optional_header32 *oh = (const image_optional_header32 *)opt;
data_dir = oh->data_directory;
}
*out_rva = data_dir[IMAGE_DIRECTORY_ENTRY_EXPORT].virtual_address;
*out_size = data_dir[IMAGE_DIRECTORY_ENTRY_EXPORT].size;
}
/* 在已加载 DLL 的导出表中按名称查找函数。
* 返回绝对地址(image_base + func_rva),0=未找到/转发导出。 */
static u64 resolve_export_by_name(u64 image_base, u32 export_dir_rva,
const char *func_name) {
if (export_dir_rva == 0) return 0;
const u8 *img = (const u8 *)image_base;
const image_export_directory *exp =
(const image_export_directory *)(img + export_dir_rva);
if (exp->number_of_names == 0) return 0;
const u32 *names = (const u32 *)(img + exp->address_of_names);
const u16 *ordinals = (const u16 *)(img + exp->address_of_name_ordinals);
const u32 *functions = (const u32 *)(img + exp->address_of_functions);
for (u32 i = 0; i < exp->number_of_names; i++) {
const char *name = (const char *)(img + names[i]);
if (dll_streq_ci(name, func_name)) {
u16 eat_index = ordinals[i];
if (eat_index >= exp->number_of_functions) return 0;
u32 func_rva = functions[eat_index];
/* 转发导出:func_rva 落在导出表范围内 → 是转发字符串而非代码地址 */
if (func_rva >= export_dir_rva &&
func_rva < export_dir_rva + exp->number_of_functions * 4) {
/* 粗略范围检查(更精确应使用 export_dir_size,但此处足够) */
log_info("[PE] forwarded export (not supported)");
return 0;
}
return image_base + func_rva;
}
}
return 0;
}
/* ---- 公共接口 ---- */
void pe_dll_manager_init(pe_dll_reader_fn reader) {
g_dll_reader = reader;
for (int i = 0; i < PE_DLL_CACHE_MAX; i++) {
g_dll_cache[i].state = DLL_STATE_NONE;
}
g_pe_dll_manager_ready = 1;
log_info("[PE] DLL manager initialized (SYSTEM/lib)");
}
int pe_dll_load(const char *dll_name, u64 *out_base) {
if (!g_pe_dll_manager_ready || !g_dll_reader || !dll_name) {
if (out_base) *out_base = 0;
return -1;
}
/* 查缓存 */
int idx = dll_cache_find(dll_name);
if (idx >= 0) {
pe_dll_entry *e = &g_dll_cache[idx];
if (e->state == DLL_STATE_LOADED) {
if (out_base) *out_base = e->image_base;
return 0;
}
if (e->state == DLL_STATE_LOADING) {
/* 循环依赖:返回 -1,调用方填 unimpl stub */
if (out_base) *out_base = 0;
return -1;
}
}
/* 分配缓存槽 */
idx = dll_cache_alloc();
if (idx < 0) {
log_warn("[PE] DLL cache full, cannot load");
if (out_base) *out_base = 0;
return -2;
}
pe_dll_entry *entry = &g_dll_cache[idx];
dll_name_lower(dll_name, entry->name, PE_DLL_NAME_MAX);
entry->state = DLL_STATE_LOADING;
entry->image_base = 0;
entry->export_dir_rva = 0;
entry->export_dir_size = 0;
/* 读取 DLL 文件 */
u8 *file_data = 0;
u32 file_size = 0;
if (g_dll_reader(dll_name, &file_data, &file_size) != 0 || !file_data || file_size == 0) {
entry->state = DLL_STATE_NONE; /* 回收槽 */
if (out_base) *out_base = 0;
return -3;
}
/* 复制文件数据到 arena(防止递归加载覆盖 fat32_read_path 的 static 缓冲) */
u8 *file_copy = (u8 *)kmem_alloc_aligned(file_size, 16);
if (!file_copy) {
entry->state = DLL_STATE_NONE;
if (out_base) *out_base = 0;
return -4;
}
for (u32 i = 0; i < file_size; i++) file_copy[i] = file_data[i];
/* 加载 PE 镜像(内部会递归 resolve_imports → 可能再次 pe_dll_load) */
pe_image_info info;
if (pe_load_image(file_copy, file_size, &info) != 0) {
log_warn("[PE] DLL load failed: pe_load_image error");
log_info(dll_name);
entry->state = DLL_STATE_NONE;
if (out_base) *out_base = 0;
return -5;
}
/* 提取导出表信息 */
extract_export_dir(info.image_base, &entry->export_dir_rva,
&entry->export_dir_size);
entry->image_base = info.image_base;
entry->image_size = (u32)info.image_size;
entry->state = DLL_STATE_LOADED;
log_info("[PE] loaded DLL:");
log_info(dll_name);
log_hex64("[PE] DLL base=", info.image_base);
if (out_base) *out_base = info.image_base;
return 0;
}
u64 pe_dll_resolve(const char *dll_name, const char *func_name) {
if (!g_pe_dll_manager_ready || !dll_name || !func_name) return 0;
/* 确保 DLL 已加载 */
int idx = dll_cache_find(dll_name);
if (idx < 0 || g_dll_cache[idx].state != DLL_STATE_LOADED) {
u64 base = 0;
if (pe_dll_load(dll_name, &base) != 0) return 0;
idx = dll_cache_find(dll_name);
if (idx < 0 || g_dll_cache[idx].state != DLL_STATE_LOADED) return 0;
}
pe_dll_entry *e = &g_dll_cache[idx];
return resolve_export_by_name(e->image_base, e->export_dir_rva, func_name);
}
+10
View File
@@ -0,0 +1,10 @@
pe/pe_dll_manager.o: pe\pe_dll_manager.c pe\pe_dll_manager.h \
include\utsm\types.h pe\pe_loader.h include\utsm\pe.h pe\pe_shim.h \
include\utsm\arena.h include\utsm\log.h
pe\pe_dll_manager.h:
include\utsm\types.h:
pe\pe_loader.h:
include\utsm\pe.h:
pe\pe_shim.h:
include\utsm\arena.h:
include\utsm\log.h:
+51
View File
@@ -0,0 +1,51 @@
#ifndef PE_DLL_MANAGER_H
#define PE_DLL_MANAGER_H
/* pe_dll_manager.h — PE 真实 DLL 加载管理器(SYSTEM/lib 兼容层)。
*
* PE 兼容层默认使用内置 shim 表解析 imports。shim 未命中的函数,
* 由 DLL 管理器从 SYSTEM/lib/ 加载真实 DLL 并解析导出表。
*
* 架构决策:
* - 仅 PE32+(64 位原生)启用真实 DLL 加载
* - PE32(32 位解释器)不加载真实 DLL(x86emu32 不能调 64 位代码)
* - 不修改 pe_service 结构体 ABI(通过全局注册模式)
* - 递归依赖:pe_dll_load → pe_load_image → resolve_imports → pe_dll_resolve → pe_dll_load
* - 循环检测:DLL_LOADING 状态遇到则跳过(填 unimpl stub,安全降级)
* - 内存:每个 DLL 用 kmem_alloc_aligned 分配 SizeOfImage + 文件副本(arena 不释放)
* - 缓存上限 32 个 DLL
*/
#include <utsm/types.h>
/* DLL 文件读取回调:从 SYSTEM/lib/ 读取 DLL 到内存。
* name : DLL 名(如 "foo.dll",原样来自 PE import 表)
* out_data : 返回内存数据指针(调用方不负责释放)
* out_size : 返回字节大小
* 返回 0 成功,负值失败。 */
typedef int (*pe_dll_reader_fn)(const char *name, u8 **out_data, u32 *out_size);
/* 全局就绪标志(1=DLL 管理器已初始化,可解析真实 DLL)。
* pe_loader.c resolve_imports 检查此标志决定是否尝试真实 DLL。 */
extern int g_pe_dll_manager_ready;
/* 初始化 DLL 管理器(注册文件读取回调)。
* 在 DKM 驱动加载完成、block provider 注册后调用。 */
void pe_dll_manager_init(pe_dll_reader_fn reader);
/* 加载 DLL(含递归解析其自身 import)。
* 已加载返回缓存基址。循环依赖检测:LOADING 状态遇到则返回 -1。
* dll_name : DLL 名(如 "foo.dll",大小写不敏感)
* out_base : 输出加载基址
* 返回 0 成功,负值失败。 */
int pe_dll_load(const char *dll_name, u64 *out_base);
/* 按名称解析导出函数。
* 若 DLL 未加载,自动调用 pe_dll_load。
* 管理器未就绪时返回 0。
* dll_name : DLL 名(大小写不敏感)
* func_name : 函数名(大小写不敏感,PE 导出表规范)
* 返回绝对地址,0=未找到。 */
u64 pe_dll_resolve(const char *dll_name, const char *func_name);
#endif /* PE_DLL_MANAGER_H */
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(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)
%.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(OUT): $(OBJECTS) linker.ld
@if not exist "$(ROOT)\SYSTEM\system\deshab64" mkdir "$(ROOT)\SYSTEM\system\deshab64"
@mkdir -p $(dir $@)
$(LD) $(LDFLAGS) -o $@ $(OBJECTS)
%.o: %.c
+44
View File
@@ -0,0 +1,44 @@
# Makefile.linux — Deshab 桌面 Linux SDL2 宿主构建
#
# 用法(在 Linux 下,CODE/desktop/ 目录):
# make -f Makefile.linux # 编译 desktop_host
# make -f Makefile.linux run # 编译并运行(默认读 ../../ISO/deshab.img)
# make -f Makefile.linux clean
#
# 依赖:gcc/clang、libsdl2-dev(apt install libsdl2-dev)
# 产物:desktop_host(普通 Linux 可执行文件)
#
# 与裸机 MAKEFILE 完全独立:不带 -ffreestanding/-mno-sse/-target,
# 不使用 linker.ld;通过 -DDESHAB_HOST 启用 host_main.c + main.c 中的宿主分支。
CC ?= gcc
SDL_CFLAGS := $(shell sdl2-config --cflags 2>/dev/null)
SDL_LIBS := $(shell sdl2-config --libs 2>/dev/null)
ROOT := ../..
IMG := $(ROOT)/ISO/deshab.img
CFLAGS := -DDESHAB_HOST -O2 -Wall -Wextra -g \
-I. -I../UTSM/include -I../tools -I../firstInit \
$(SDL_CFLAGS)
LDFLAGS := $(SDL_LIBS) -lm
TARGET := desktop_host
SRCS := main.c host_main.c
OBJS := $(SRCS:.c=.o)
.PHONY: all run clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS)
%.o: %.c host_main.h
$(CC) $(CFLAGS) -c $< -o $@
run: $(TARGET)
./$(TARGET) --img $(IMG)
clean:
rm -f $(OBJS) $(TARGET)
+376
View File
@@ -0,0 +1,376 @@
/* host_main.c — Deshab 桌面 Linux SDL2 宿主
*
* 仅在 -DDESHAB_HOST 编译时链接。提供:
* - SDL2 窗口 + streaming texture(模拟 framebuffer,g_host_backbuf)
* - 鼠标/键盘事件队列(模拟 PS/2,供 host_mouse_poll/host_kb_poll 消费)
* - deshab.img GPT ESP 文件 block 后端(模拟 kernel_api block provider)
* - 单调时钟 / 当天秒数(替代 rdtsc / RTC CMOS)
*
* main() 构造 dsk_boot_context 后调用 dsk_entry(),进入桌面主循环。
* 主循环每帧末尾调用 host_pump_and_present() 上传 back buffer 并 pump 事件。
*/
#include "host_main.h"
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <utsm/dsk.h>
/* ---- 全局缓冲(供 desktop/main.c 经 extern 使用) ---- */
u8 *g_host_sprite = NULL;
u8 *g_host_backbuf = NULL;
/* ---- SDL 状态 ---- */
static SDL_Window *g_win = NULL;
static SDL_Renderer *g_rend = NULL;
static SDL_Texture *g_tex = NULL;
static int g_fb_w = 1280, g_fb_h = 720;
/* ---- 鼠标状态(SDL 事件累积,host_mouse_poll 消费) ---- */
static struct {
int x, y; /* 绝对坐标 */
int btn; /* bit0=左 bit1=右 */
int prev_btn; /* 上次 poll 时的按键状态 */
int changed; /* 自上次 poll 以来有变化 */
} g_mse = { 400, 300, 0, 0, 0 };
/* ---- 键盘扫描码队列(SDL 键事件 → set-1 字节序列) ---- */
#define KB_QSZ 64
static u8 g_kb_q[KB_QSZ];
static int g_kb_head = 0, g_kb_tail = 0;
static void kb_push(u8 b) {
int nxt = (g_kb_tail + 1) % KB_QSZ;
if (nxt == g_kb_head) return; /* 满,丢弃 */
g_kb_q[g_kb_tail] = b;
g_kb_tail = nxt;
}
/* ---- 日志 ---- */
void host_log(const char *s) { fputs(s, stderr); fflush(stderr); }
void host_log_char(char c) { fputc(c, stderr); fflush(stderr); }
/* ---- 时间 ---- */
u64 host_now_us(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (u64)ts.tv_sec * 1000000ULL + (u64)ts.tv_nsec / 1000ULL;
}
int host_rtc_seconds(void) {
time_t t = time(NULL);
struct tm *lt = localtime(&t);
return lt->tm_hour * 3600 + lt->tm_min * 60 + lt->tm_sec;
}
/* ---- 鼠标 ---- */
int host_mouse_poll(int *px, int *py, int *pbtn,
int *p_left_pressed, int *p_left_released,
int *p_right_pressed, int max_x, int max_y) {
if (!g_mse.changed && g_mse.btn == g_mse.prev_btn) return 0;
g_mse.changed = 0;
int btn = g_mse.btn;
int prev = g_mse.prev_btn;
g_mse.prev_btn = btn;
int x = g_mse.x, y = g_mse.y;
if (x < 0) x = 0; if (y < 0) y = 0;
if (x > max_x) x = max_x; if (y > max_y) y = max_y;
*px = x; *py = y; *pbtn = btn;
*p_left_pressed = (btn & 1) && !(prev & 1);
*p_left_released = !(btn & 1) && (prev & 1);
*p_right_pressed = (btn & 2) && !(prev & 2);
return 1;
}
/* ---- 键盘 ---- */
int host_kb_poll(u8 *out_sc) {
if (g_kb_head == g_kb_tail) return 0;
*out_sc = g_kb_q[g_kb_head];
g_kb_head = (g_kb_head + 1) % KB_QSZ;
return 1;
}
/* SDL_Scancode → PS/2 set-1 make 码(单字节;E0 前缀键返回 0xFF 表示需 E0) */
static u8 sdl_to_set1(SDL_Scancode sc, int *e0) {
*e0 = 0;
switch (sc) {
case SDL_SCANCODE_A: return 0x1E; case SDL_SCANCODE_B: return 0x30;
case SDL_SCANCODE_C: return 0x2E; case SDL_SCANCODE_D: return 0x20;
case SDL_SCANCODE_E: return 0x12; case SDL_SCANCODE_F: return 0x21;
case SDL_SCANCODE_G: return 0x22; case SDL_SCANCODE_H: return 0x23;
case SDL_SCANCODE_I: return 0x17; case SDL_SCANCODE_J: return 0x24;
case SDL_SCANCODE_K: return 0x25; case SDL_SCANCODE_L: return 0x26;
case SDL_SCANCODE_M: return 0x32; case SDL_SCANCODE_N: return 0x31;
case SDL_SCANCODE_O: return 0x18; case SDL_SCANCODE_P: return 0x19;
case SDL_SCANCODE_Q: return 0x10; case SDL_SCANCODE_R: return 0x13;
case SDL_SCANCODE_S: return 0x1F; case SDL_SCANCODE_T: return 0x14;
case SDL_SCANCODE_U: return 0x16; case SDL_SCANCODE_V: return 0x2F;
case SDL_SCANCODE_W: return 0x11; case SDL_SCANCODE_X: return 0x2D;
case SDL_SCANCODE_Y: return 0x15; case SDL_SCANCODE_Z: return 0x2C;
case SDL_SCANCODE_1: return 0x02; case SDL_SCANCODE_2: return 0x03;
case SDL_SCANCODE_3: return 0x04; case SDL_SCANCODE_4: return 0x05;
case SDL_SCANCODE_5: return 0x06; case SDL_SCANCODE_6: return 0x07;
case SDL_SCANCODE_7: return 0x08; case SDL_SCANCODE_8: return 0x09;
case SDL_SCANCODE_9: return 0x0A; case SDL_SCANCODE_0: return 0x0B;
case SDL_SCANCODE_RETURN: return 0x1C;
case SDL_SCANCODE_BACKSPACE: return 0x0E;
case SDL_SCANCODE_TAB: return 0x0F;
case SDL_SCANCODE_SPACE: return 0x39;
case SDL_SCANCODE_ESCAPE: return 0x01;
case SDL_SCANCODE_LSHIFT: return 0x2A; case SDL_SCANCODE_RSHIFT: return 0x36;
case SDL_SCANCODE_LCTRL: return 0x1D; case SDL_SCANCODE_RCTRL: return 0x1D;
case SDL_SCANCODE_LALT: return 0x38; case SDL_SCANCODE_RALT: return 0x38;
case SDL_SCANCODE_CAPSLOCK: return 0x3A;
case SDL_SCANCODE_MINUS: return 0x0C;
case SDL_SCANCODE_EQUALS: return 0x0D;
case SDL_SCANCODE_LEFTBRACKET: return 0x1A;
case SDL_SCANCODE_RIGHTBRACKET: return 0x1B;
case SDL_SCANCODE_SEMICOLON: return 0x27;
case SDL_SCANCODE_APOSTROPHE: return 0x28;
case SDL_SCANCODE_GRAVE: return 0x29;
case SDL_SCANCODE_BACKSLASH: return 0x2B;
case SDL_SCANCODE_COMMA: return 0x33;
case SDL_SCANCODE_PERIOD: return 0x34;
case SDL_SCANCODE_SLASH: return 0x35;
case SDL_SCANCODE_LEFT: *e0 = 1; return 0x4B;
case SDL_SCANCODE_RIGHT: *e0 = 1; return 0x4D;
case SDL_SCANCODE_UP: *e0 = 1; return 0x48;
case SDL_SCANCODE_DOWN: *e0 = 1; return 0x50;
default: return 0;
}
}
static void handle_key_event(SDL_Scancode sc, int down) {
int e0 = 0;
u8 code = sdl_to_set1(sc, &e0);
if (!code) return;
if (e0) kb_push(0xE0);
kb_push(down ? code : (u8)(code | 0x80));
}
/* ---- 帧末 pump + present ---- */
int host_pump_and_present(void) {
int quit = 0;
/* 上传 back buffer 到 SDL texture 并 present */
if (g_tex && g_host_backbuf) {
SDL_UpdateTexture(g_tex, NULL, g_host_backbuf, g_fb_w * 4);
SDL_RenderClear(g_rend);
SDL_RenderCopy(g_rend, g_tex, NULL, NULL);
SDL_RenderPresent(g_rend);
}
/* pump 事件 */
SDL_Event ev;
while (SDL_PollEvent(&ev)) {
switch (ev.type) {
case SDL_QUIT:
quit = 1;
break;
case SDL_MOUSEMOTION:
g_mse.x = ev.motion.x;
g_mse.y = ev.motion.y;
g_mse.changed = 1;
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP: {
int down = (ev.type == SDL_MOUSEBUTTONDOWN);
if (ev.button.button == SDL_BUTTON_LEFT) {
if (down) g_mse.btn |= 1; else g_mse.btn &= ~1;
g_mse.changed = 1;
} else if (ev.button.button == SDL_BUTTON_RIGHT) {
if (down) g_mse.btn |= 2; else g_mse.btn &= ~2;
g_mse.changed = 1;
}
break;
}
case SDL_KEYDOWN:
case SDL_KEYUP: {
/* 忽略重复事件(SDL_KEYDOWN 带 repeat>0) */
if (ev.key.repeat) break;
handle_key_event(ev.key.keysym.scancode, ev.type == SDL_KEYDOWN);
break;
}
}
}
return quit;
}
/* ---- block 后端:deshab.img GPT ESP 文件 ---- */
static int g_img_fd = -1;
static u64 g_esp_byte_off = 0; /* ESP 分区起始字节偏移 */
/* 读取 img LBA1 GPT header,定位 ESP 分区起始 LBA。失败回退 2048。 */
static u64 find_esp_offset(int fd) {
u8 hdr[512];
if (pread(fd, hdr, 512, 512) != 512) return 2048 * 512;
/* GPT signature "EFI PART" */
if (memcmp(hdr, "EFI PART", 8) != 0) return 2048 * 512;
u64 entries_lba = 0;
u32 entry_count = 0, entry_size = 0;
memcpy(&entries_lba, hdr + 72, 8);
memcpy(&entry_count, hdr + 80, 4);
memcpy(&entry_size, hdr + 84, 4);
if (entry_size < 56 || entry_count == 0 || entry_count > 256) return 2048 * 512;
/* ESP 类型 GUID(混合字节序):c12a7328-f81f-11d2-ba4b-00a0c93ec93b */
static const u8 esp_guid[16] = {
0x28,0x73,0x2a,0xc1, 0x1f,0xf8, 0xd2,0x11,
0xba,0x4b, 0x00,0xa0,0xc9,0x3e,0xc9,0x3b
};
u8 *entries = (u8 *)malloc((u64)entry_count * entry_size);
if (!entries) return 2048 * 512;
u64 entries_byte = entries_lba * 512;
ssize_t got = pread(fd, entries, (u64)entry_count * entry_size, entries_byte);
if (got <= 0) { free(entries); return 2048 * 512; }
u64 esp_lba = 0;
int found = 0;
for (u32 i = 0; i < entry_count; i++) {
u8 *e = entries + (u64)i * entry_size;
if (memcmp(e, esp_guid, 16) == 0) {
memcpy(&esp_lba, e + 32, 8);
found = 1;
break;
}
}
free(entries);
return found ? (esp_lba * 512) : (2048 * 512);
}
/* block_read/write:desktop 期望 int (u32 index, u64 lba, u32 count, void* buf)
* index 固定 0;扇区 512 字节;偏移 = esp_byte_off + lba*512。 */
static int host_block_read(u32 index, u64 lba, u32 count, void *buf) {
(void)index;
if (g_img_fd < 0) return -1;
u64 off = g_esp_byte_off + lba * 512ULL;
u64 total = (u64)count * 512ULL;
u8 *p = (u8 *)buf;
while (total > 0) {
ssize_t n = pread(g_img_fd, p, total, off);
if (n <= 0) return -1;
p += n; off += (u64)n; total -= (u64)n;
}
return 0;
}
static int host_block_write(u32 index, u64 lba, u32 count, const void *buf) {
(void)index;
if (g_img_fd < 0) return -1;
u64 off = g_esp_byte_off + lba * 512ULL;
u64 total = (u64)count * 512ULL;
const u8 *p = (const u8 *)buf;
while (total > 0) {
ssize_t n = pwrite(g_img_fd, p, total, off);
if (n <= 0) return -1;
p += n; off += (u64)n; total -= (u64)n;
}
return 0;
}
/* ---- 伪造 kernel_api:让 dsk_entry 现有 block 获取路径零改动 ----
* desktop/main.c: u64 blk = *(u64*)(ctx->dkm_kernel_api + 0xA8);
* g_block_read = *(u64*)(blk + 16);
* g_block_write = *(u64*)(blk + 24);
* 故 host_kernel_api 偏移 0xA8 处填 &host_block_api;
* host_block_api[2] = read, [3] = write(偏移 16/24)。 */
static u64 host_block_api[8];
static u8 host_kernel_api[0xB0 + 8];
static void build_fake_kernel_api(void) {
memset(host_kernel_api, 0, sizeof(host_kernel_api));
memset(host_block_api, 0, sizeof(host_block_api));
host_block_api[2] = (u64)(uintptr_t)host_block_read;
host_block_api[3] = (u64)(uintptr_t)host_block_write;
*(u64 *)(host_kernel_api + 0xA8) = (u64)(uintptr_t)host_block_api;
}
/* ---- main ---- */
int main(int argc, char **argv) {
const char *img_path = "../../ISO/deshab.img";
int width = 1280, height = 720;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--img") == 0 && i + 1 < argc) { img_path = argv[++i]; continue; }
if (strcmp(argv[i], "--width") == 0 && i + 1 < argc) { width = atoi(argv[++i]); continue; }
if (strcmp(argv[i], "--height") == 0 && i + 1 < argc) { height = atoi(argv[++i]); continue; }
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
fprintf(stderr, "usage: desktop_host [--img PATH] [--width W] [--height H]\n");
return 0;
}
}
g_fb_w = width; g_fb_h = height;
/* 打开镜像 */
g_img_fd = open(img_path, O_RDWR);
if (g_img_fd < 0) {
/* 只读回退 */
g_img_fd = open(img_path, O_RDONLY);
if (g_img_fd < 0) {
fprintf(stderr, "[host] cannot open %s: ", img_path);
perror("");
return 1;
}
fprintf(stderr, "[host] warning: %s opened read-only, FAT32 writes will fail\n", img_path);
}
g_esp_byte_off = find_esp_offset(g_img_fd);
fprintf(stderr, "[host] img=%s esp_off=%llu width=%d height=%d\n",
img_path, (unsigned long long)g_esp_byte_off, width, height);
/* SDL */
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
fprintf(stderr, "[host] SDL_Init: %s\n", SDL_GetError());
return 1;
}
g_win = SDL_CreateWindow("Deshab Desktop (Linux host)",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
width, height, SDL_WINDOW_SHOWN);
if (!g_win) { fprintf(stderr, "[host] SDL_CreateWindow: %s\n", SDL_GetError()); return 1; }
g_rend = SDL_CreateRenderer(g_win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!g_rend) g_rend = SDL_CreateRenderer(g_win, -1, 0);
if (!g_rend) { fprintf(stderr, "[host] SDL_CreateRenderer: %s\n", SDL_GetError()); return 1; }
g_tex = SDL_CreateTexture(g_rend, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, width, height);
if (!g_tex) { fprintf(stderr, "[host] SDL_CreateTexture: %s\n", SDL_GetError()); return 1; }
/* back buffer + sprite buffer */
size_t fbsz = (size_t)width * height * 4;
g_host_backbuf = (u8 *)malloc(fbsz);
g_host_sprite = (u8 *)malloc(fbsz);
if (!g_host_backbuf || !g_host_sprite) {
fprintf(stderr, "[host] alloc buffer failed\n");
return 1;
}
memset(g_host_backbuf, 0, fbsz);
memset(g_host_sprite, 0, fbsz);
build_fake_kernel_api();
/* 构造 dsk_boot_context */
dsk_boot_context ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.magic = DSK_BOOT_MAGIC;
ctx.abi_version = DSK_BOOT_ABI_VERSION;
ctx.size = sizeof(ctx);
ctx.framebuffer_address = (u64)(uintptr_t)g_host_backbuf;
ctx.framebuffer_width = (u64)width;
ctx.framebuffer_height = (u64)height;
ctx.framebuffer_pitch = (u64)width * 4;
ctx.framebuffer_bpp = 32;
ctx.dkm_kernel_api = (u64)(uintptr_t)host_kernel_api;
/* reserved[5]=0:无 linux_compat 服务,桌面侧 g_lxc_svc 保持 NULL */
fprintf(stderr, "[host] entering dsk_entry\n");
dsk_entry(&ctx);
fprintf(stderr, "[host] dsk_entry returned, exiting\n");
SDL_DestroyTexture(g_tex);
SDL_DestroyRenderer(g_rend);
SDL_DestroyWindow(g_win);
SDL_Quit();
if (g_img_fd >= 0) close(g_img_fd);
free(g_host_backbuf);
free(g_host_sprite);
return 0;
}
+59
View File
@@ -0,0 +1,59 @@
/* host_main.h — Deshab 桌面 Linux 宿主接口
*
* 仅在 -DDESHAB_HOST 编译时使用。提供 SDL2 窗口(模拟 framebuffer)、
* 鼠标/键盘事件(模拟 PS/2)、deshab.img 文件 block 后端、单调时钟
* (替代 rDTSC)与当天秒数(替代 RTC CMOS)。
*
* desktop/main.c 通过 #ifdef DESHAB_HOST 调用本接口;裸机构建不包含本头。
*/
#ifndef DESHAB_HOST_MAIN_H
#define DESHAB_HOST_MAIN_H
#include <utsm/types.h>
/* dsk_entry 由 desktop/main.c 提供,host_main.c 构造好 ctx 后调用。 */
struct dsk_boot_context;
void dsk_entry(const struct dsk_boot_context *ctx);
/* ---- 日志(替代 COM1 串口 sputc/swrite/slog) ---- */
void host_log(const char *s); /* 输出字符串到 stderr */
void host_log_char(char c); /* 输出单字符到 stderr */
/* ---- 时间 ---- */
/* 单调时钟微秒,替代 rdtsc。供 tsc_calibrate/g_last_click_tsc 使用。
* g_tsc_per_sec 应设为 1000000。 */
u64 host_now_us(void);
/* 当天秒数(0..86399),替代 rtc_day_sec。 */
int host_rtc_seconds(void);
/* ---- 鼠标(替代 ps2_mouse_poll) ----
* 从 SDL 事件累积的鼠标状态生成一次快照。
* px/py : 输出当前绝对坐标(已钳位到 [0,max])
* pbtn : 输出按键位(bit0=左 bit1=右)
* p_left_pressed / p_left_released / p_right_pressed : 边沿事件(1 表示本次发生)
* max_x/max_y : 坐标上界(g_fb_w - CURSOR_SIZE 等)
* 返回 1 表示有移动或按键变化,0 表示无事件。 */
int host_mouse_poll(int *px, int *py, int *pbtn,
int *p_left_pressed, int *p_left_released,
int *p_right_pressed, int max_x, int max_y);
/* ---- 键盘(替代 inb(0x64)/inb(0x60) 扫描码轮询) ----
* 取一个 PS/2 set-1 扫描码(make 码;break 码为 |0x80)。
* 返回 1 表示取到,0 表示队列空。 */
int host_kb_poll(u8 *out_sc);
/* ---- 帧末:pump SDL 事件 + present back buffer ----
* 在主循环末尾调用(替代 __asm__("pause"))。
* 1. 把 back buffer(g_host_backbuf)上传到 SDL texture 并 RenderPresent
* 2. SDL_PollEvent 排空:鼠标/键盘事件入内部状态,SDL_QUIT 返回非 0
* 返回非 0 表示收到退出请求(窗口关闭),调用方应置 g_quit。 */
int host_pump_and_present(void);
/* ---- host 分配的缓冲(替代裸机硬编码物理地址) ----
* g_host_sprite : sprite 渲染缓冲(替代 SPRITE_BUF_ADDR 0x7000000)
* g_host_backbuf: 真实 framebuffer 对应的 back buffer(替代 g_real_fb 指向的显存)
* 大小均为 width*height*4 字节,由 host_main.c 在初始化时分配。 */
extern u8 *g_host_sprite;
extern u8 *g_host_backbuf;
#endif /* DESHAB_HOST_MAIN_H */
+25 -433
View File
@@ -19,8 +19,6 @@
*/
#include "../UTSM/include/utsm/dsk.h"
/* Linux 兼容层服务接口(reserved[5],exec/file_list/file_read) */
#include "../UTSM/include/utsm/linux_compat.h"
/* block 设备函数类型(从 kernel_api + 0xA8 获取 block_api,read @ +0x10, write @ +0x18) */
typedef int (*desktop_block_read_fn)(u32 index, u64 lba, u32 count, void *buffer);
@@ -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__ u8 inb(u16 port) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(port)); return v; }
/* Linux 桌面应用配置契约(da_linuxapp_parse / DA_LINUXAPP_* 常量)。
* DA_NO_PORTIO:本文件已自带 outb/inb,跳过头文件内同名定义。 */
#define DA_NO_PORTIO
#include "../tools/desktop_app.h"
static void sputc(char c) {
for (unsigned int i=0; i<100000; i++) { if (inb(COM1+5)&0x20) break; }
outb(COM1, (unsigned char)c);
@@ -78,7 +71,6 @@ static void redraw_all(void);
static int ps2_mouse_poll(void);
static void draw_statusbar_full(void);
static void files_refresh(void);
static void shortcuts_refresh(void);
/* ============================================================
* 常量(Winux-Kate 布局)
@@ -172,10 +164,6 @@ typedef struct {
app_draw_fn on_draw;
app_destroy_fn on_destroy;
const char *elf_name;
/* Linux 兼容层命令(Phase 5,如 "/usr/bin/neofetch")。与 elf_name 互斥:
* 两者同时设置时 elf_name 优先(走外部 ELF 全屏宿主),linux_cmd 被忽略;
* 仅设置 linux_cmd 时必须提供 on_create(内置终端窗口承载执行)。 */
const char *linux_cmd;
} app_descriptor;
/* 桌面图标 */
@@ -186,16 +174,7 @@ typedef struct {
int selected;
} desktop_icon;
/* 桌面快捷方式(从 SYSTEM/user/desktop 目录加载 .lnk 文件) */
typedef struct {
char display_name[24]; /* 显示名称(UTF-8) */
char elf_name[11]; /* ELF 文件名(FAT32 8.3) */
char icon_type[12]; /* 图标类型:editor/folder/browser/terminal */
int x, y; /* 桌面位置 */
int selected; /* 选中状态 */
} desktop_shortcut;
#define MAX_ICONS 24 /* 桌面图标槽(内置应用 + 用户快捷方式) */
#define MAX_ICONS 8
/* 工作区 */
typedef struct {
@@ -260,22 +239,14 @@ static int g_dragging = 0;
static int g_drag_win = -1;
static int g_drag_off_x, g_drag_off_y;
/* 应用表(16 = 6 内置 + DA_LINUXAPP_MAX 8 个 Linux 应用 + 余量) */
#define MAX_APPS 16
/* 应用表 */
#define MAX_APPS 8
static app_descriptor g_apps[MAX_APPS];
static int g_app_count = 0;
/* Linux 兼容层服务运行态(register_linux_apps 设置):0 时 Linux 图标置灰,
* 双击仍开终端窗口并提示不可用原因(lnx_on_create 运行时复查)。 */
static int g_lnx_svc_up = 0;
static desktop_icon g_icons[MAX_ICONS];
static int g_icon_count = 0;
/* 用户快捷方式(从 SYSTEM/user/desktop 加载) */
static desktop_shortcut g_shortcuts[MAX_ICONS];
static int g_shortcut_count = 0;
/* ---- Kate 外壳状态 ---- */
static int g_booted = 0;
static int g_page = 1; /* 1..3 内置, 4..8 自定义 */
@@ -549,27 +520,7 @@ static void draw_icon_browser(du_context *ctx, int cx, int cy) {
du_pixel(ctx, ccx, ccy, KS_ACCENT2);
}
/* Linux 应用图标:暗底终端 + 霓虹绿 >_ 提示符(与 bash 终端同色板)。
* 服务不可用时 accent 传 KS_TEXT_DIM(置灰,点击仍给原因提示)。 */
static void draw_icon_linux(du_context *ctx, int cx, int cy, u32 accent) {
du_fill_rect(ctx, cx, cy, 40, 40, KS_BG_PRIMARY);
du_rect_outline(ctx, cx, cy, 40, 40, accent, 2);
/* '>' 折线 */
du_fill_rect(ctx, cx + 8, cy + 12, 2, 2, accent);
du_fill_rect(ctx, cx + 10, cy + 14, 2, 2, accent);
du_fill_rect(ctx, cx + 12, cy + 16, 2, 2, accent);
du_fill_rect(ctx, cx + 10, cy + 18, 2, 2, accent);
du_fill_rect(ctx, cx + 8, cy + 20, 2, 2, accent);
/* '_' 光标块 */
du_fill_rect(ctx, cx + 16, cy + 22, 8, 2, accent);
}
static void draw_app_icon(du_context *ctx, int app_id, int cx, int cy) {
/* Linux 应用走专属图标(暗底绿提示符),其余保持原有映射 */
if (app_id >= 0 && app_id < g_app_count && g_apps[app_id].linux_cmd) {
draw_icon_linux(ctx, cx, cy, g_lnx_svc_up ? KS_ACCENT2 : KS_TEXT_DIM);
return;
}
switch (app_id) {
case 0: draw_icon_terminal(ctx, cx, cy); break;
case 1: draw_icon_editor(ctx, cx, cy); break;
@@ -1154,269 +1105,6 @@ static void bash_on_draw(void *state, app_ctx *ctx) {
__attribute__((unused))
static void bash_on_destroy(void *state) { (void)state; }
/* ============================================================
* Linux 兼容层应用 (Phase 5)
* 图标 → 终端风格窗口 → linux_compat_service.exec 同步执行。
* exec 是同步阻塞调用(park-and-resume 到 Linux guest 完成才返回),
* 无 out_cb 流式回调:先绘制 running 状态再阻塞,返回后一次性
* 把 stdout 缓冲追加进终端并显示 [exit N]。
* ============================================================ */
static const linux_compat_service *g_lxc_svc = 0;
#define LNX_MAX_WINS 8 /* 与 MAX_WINDOWS 对齐:任何窗口都可能是 Linux 终端 */
#define LNX_OUT_CAP 16384 /* exec stdout 收集缓冲 */
typedef struct {
bash_state term; /* 复用 bash 终端字符网格(无输入行) */
int finished; /* 0=running 1=已返回 */
int rc; /* exec 返回值(0 成功,负值失败) */
u64 exit_code; /* guest 进程退出码 */
} lnx_state;
static lnx_state g_lnx_states[LNX_MAX_WINS];
static int g_lnx_used[LNX_MAX_WINS];
static char g_lnx_out[LNX_OUT_CAP]; /* 同步单发,可全窗口共享 */
/* Linux 应用注册名/命令持久池(f32_data 会被后续文件操作复用,必须拷出) */
static char g_lnx_name_pool[DA_LINUXAPP_MAX][12];
static char g_lnx_cmd_pool[DA_LINUXAPP_MAX][64];
static char g_lnx_id_pool[DA_LINUXAPP_MAX][8];
/* 追加无符号十进制到终端 */
static void lnx_put_u64(bash_state *t, u64 v, u32 color) {
char tmp[20];
int n = 0;
if (v == 0) tmp[n++] = '0';
while (v && n < 20) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; }
while (n > 0) bash_putc(t, tmp[--n], color);
}
static void lnx_put_i64(bash_state *t, i64 v, u32 color) {
if (v < 0) { bash_putc(t, '-', color); lnx_put_u64(t, (u64)(-v), color); }
else lnx_put_u64(t, (u64)v, color);
}
/* 追加 exec 输出到终端:剥离 ANSI CSI 转义序列,忽略 \r,\t 展开为 2 空格,
* 其余控制字符丢弃(终端字体仅 ASCII,非 ASCII 字节由 du_draw_char 钳为空格)。 */
static void lnx_append(bash_state *t, const char *buf, u64 len, u32 color) {
int esc = 0;
for (u64 i = 0; i < len; i++) {
char c = buf[i];
if (esc) {
/* CSI: ESC [ 参数... 结束字节(0x40-0x7E);其它 ESC 序列同理吞掉 */
if (c >= 0x40 && c <= 0x7E) esc = 0;
continue;
}
if (c == 0x1B) { esc = 1; continue; }
if (c == '\r') continue;
if (c == '\t') { bash_putc(t, ' ', color); bash_putc(t, ' ', color); continue; }
if ((u8)c < 32 && c != '\n') continue;
bash_putc(t, c, color);
}
}
/* 终端内容绘制:全量重绘(与 editor_draw_to 同策略),无输入行 */
static void lnx_draw_to(lnx_state *s, int cx, int cy, int cw, int ch) {
bash_state *t = &s->term;
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY);
for (int r = 0; r < t->rows; r++) {
for (int c = 0; c < t->cols; c++) {
int idx = r * TERM_MAX_COLS + c;
if (t->ch[idx] == ' ') continue;
int px = cx + (int)DU_SPACE_SM + c * (int)DU_ASCII_STEP;
int py = cy + (int)DU_SPACE_SM + r * (int)DU_ASCII_LINE_H;
if (px + (int)DU_ASCII_CELL_W > cx + cw) continue;
if (py + (int)DU_ASCII_CELL_H > cy + ch) continue;
du_draw_char(&g_fb, (char)t->ch[idx], px, py, t->fg[idx], KS_BG_PRIMARY);
}
}
}
/* ---- 窗口适配 ---- */
static void *lnx_on_create(app_ctx *ctx) {
int slot = -1;
for (int i = 0; i < LNX_MAX_WINS; i++) {
if (!g_lnx_used[i]) { slot = i; break; }
}
if (slot < 0) return 0;
g_lnx_used[slot] = 1;
lnx_state *s = &g_lnx_states[slot];
app_descriptor *app = &g_apps[ctx->win->app_id];
bash_reset(&s->term, ctx->client_w, ctx->client_h);
s->finished = 0;
s->rc = 0;
s->exit_code = 0;
/* 标题:显示名 + Linux 标记 */
{
for (int i = 0; i < 48; i++) ctx->win->title[i] = 0;
char t[48];
int ti = 0;
const char *d = app->display_name;
while (d[ti] && ti < 38) { t[ti] = d[ti]; ti++; }
const char *suf = " . Linux";
int j = 0;
while (suf[j] && ti < 47) { t[ti++] = suf[j++]; }
t[ti] = 0;
kstrcpy(ctx->win->title, t, 48);
}
/* 命令回显 */
bash_puts(&s->term, "$ ", KS_ACCENT2);
bash_puts(&s->term, app->linux_cmd, KS_TEXT_PRIMARY);
bash_putc(&s->term, '\n', 0);
/* 服务运行时复查(注册后 guest 仍可能不可用) */
if (!g_lxc_svc || !g_lxc_svc->is_available()) {
bash_puts(&s->term, "linux: compat service unavailable\n", KS_DANGER);
bash_puts(&s->term, "[exit -1]\n", KS_DANGER);
s->finished = 1;
s->rc = -1;
return s;
}
/* running 状态提示:先呈现窗口再进入同步阻塞 exec */
bash_puts(&s->term, "running... (sync exec, UI will freeze)\n", KS_TEXT_DIM);
ctx->win->app_state = s; /* 让 redraw_all 能看到本窗口内容 */
redraw_all();
flip_buffer();
/* 分词:linux_cmd → path + argv(就地切分副本) */
char cmdline[96];
kstrcpy(cmdline, app->linux_cmd, 96);
const char *argv[8];
int argc = 0;
char *p = cmdline;
while (*p && argc < 8) {
while (*p == ' ') p++;
if (!*p) break;
argv[argc++] = p;
while (*p && *p != ' ') p++;
if (*p) *p++ = 0;
}
if (argc == 0) {
bash_puts(&s->term, "linux: empty command\n", KS_DANGER);
s->finished = 1;
s->rc = -1;
return s;
}
/* 绝对路径直接使用;裸命令补 /bin/ 前缀(与 shell linux 命令一致) */
char path[96];
if (argv[0][0] == '/') {
kstrcpy(path, argv[0], 96);
} else {
kstrcpy(path, "/bin/", 96);
kstrcat(path, argv[0], 96);
}
u64 out_len = 0, exit_code = 0;
slog("linux exec");
int rc = g_lxc_svc->exec(path, argc, argv,
g_lnx_out, LNX_OUT_CAP - 1,
&out_len, &exit_code);
s->rc = rc;
s->exit_code = exit_code;
s->finished = 1;
if (rc != 0) {
bash_puts(&s->term, "linux: exec failed (rc=", KS_DANGER);
lnx_put_i64(&s->term, rc, KS_DANGER);
bash_puts(&s->term, ")\n", KS_DANGER);
bash_puts(&s->term, "[exit -1]\n", KS_DANGER);
return s;
}
/* 输出回显(一次性追加;exec 无流式回调) */
if (out_len > 0) {
lnx_append(&s->term, g_lnx_out, out_len, KS_TEXT_PRIMARY);
if (g_lnx_out[out_len - 1] != '\n') bash_putc(&s->term, '\n', 0);
if (out_len >= LNX_OUT_CAP - 1)
bash_puts(&s->term, "[output truncated]\n", KS_TEXT_DIM);
}
/* 退出码:0 绿 / 非 0 琥珀 */
u32 ec_color = (exit_code == 0) ? KS_ACCENT2 : KS_WARN;
bash_puts(&s->term, "[exit ", ec_color);
lnx_put_u64(&s->term, exit_code, ec_color);
bash_puts(&s->term, "]\n", ec_color);
return s;
}
static void lnx_on_draw(void *state, app_ctx *ctx) {
lnx_draw_to((lnx_state *)state, ctx->client_x, ctx->client_y,
ctx->client_w, ctx->client_h);
}
static void lnx_on_destroy(void *state) {
lnx_state *s = (lnx_state *)state;
for (int i = 0; i < LNX_MAX_WINS; i++) {
if (&g_lnx_states[i] == s) { g_lnx_used[i] = 0; break; }
}
}
/* ---- Linux 应用注册 ----
* 无论服务是否可用都注册图标:可用时双击即同步 exec;不可用(VMX 缺失/
* guest 未驻留)时图标置灰(g_lnx_svc_up=0),双击由 lnx_on_create 运行时
* 复查并显示 "linux: compat service unavailable [exit -1]",绝不 panic。
* 配置:FAT32 根目录 LINUXAPP.CNF(8.3 名 LINUXAPPCNF),每行 显示名|linux命令;
* 文件缺失或无有效行时回退内置默认列表(echo hello / uname -a / ls -l /,
* 三条均为 Arch rootfs 与 initramfs busybox 环境共有的可执行命令)。 */
static void register_linux_apps(void) {
g_lnx_svc_up = (g_lxc_svc && g_lxc_svc->is_available()) ? 1 : 0;
if (!g_lxc_svc) slog("linux apps: service absent (icons greyed)");
else if (!g_lnx_svc_up) slog("linux apps: service unavailable (icons greyed)");
da_linux_app ents[DA_LINUXAPP_MAX];
int n = 0;
u8 *data = 0;
u32 size = 0;
if (f32_read_root_file(DA_LINUXAPP_CNF_83, &data, &size) == 0 && size > 0) {
n = da_linuxapp_parse((const char *)data, size, ents, DA_LINUXAPP_MAX);
}
if (n <= 0) {
/* 内置默认保底(guest 中确定存在的命令) */
kstrcpy(g_lnx_name_pool[0], "HELLO", 12);
kstrcpy(g_lnx_cmd_pool[0], "/bin/echo hello from deshab linux", 64);
kstrcpy(g_lnx_name_pool[1], "UNAME", 12);
kstrcpy(g_lnx_cmd_pool[1], "/bin/uname -a", 64);
kstrcpy(g_lnx_name_pool[2], "LS", 12);
kstrcpy(g_lnx_cmd_pool[2], "/bin/ls -l /", 64);
n = 3;
slog("linux apps: builtin defaults");
} else {
for (int i = 0; i < n; i++) {
kstrcpy(g_lnx_name_pool[i], ents[i].name, 12);
kstrcpy(g_lnx_cmd_pool[i], ents[i].cmd, 64);
}
slog("linux apps: loaded LINUXAPP.CNF");
}
for (int i = 0; i < n && g_app_count < MAX_APPS; i++) {
char *id = g_lnx_id_pool[i];
id[0] = 'l'; id[1] = 'n'; id[2] = 'x'; id[3] = (char)('0' + i); id[4] = 0;
g_apps[g_app_count] = (app_descriptor){
id, g_lnx_name_pool[i], 720, 480,
lnx_on_create, 0, lnx_on_draw, lnx_on_destroy,
0, g_lnx_cmd_pool[i]
};
g_app_count++;
{
char msg[64];
kstrcpy(msg, "linux app registered: ", 64);
kstrcat(msg, g_lnx_name_pool[i], 64);
slog(msg);
}
}
slog("linux apps registered");
}
/* ============================================================
* 文本编辑器 (Editor) — 面板/IDE/窗口共用核心
* ============================================================ */
@@ -1739,86 +1427,67 @@ static void register_apps(void) {
/* 命令行1:shell.elf(原生命令行 + 可运行 Linux 程序)*/
g_apps[0] = (app_descriptor){
"shell", "Terminal", 720, 480,
0, 0, 0, 0, "SHELL ELF", 0
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", 0
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, 0
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", 0
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, 0
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", 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++;
/* Linux 兼容层应用:无论服务是否可用都注册图标(不可用时置灰,
* 双击仍开终端窗口提示原因,见 register_linux_apps / lnx_on_create) */
register_linux_apps();
}
/* Winux-Kate .desktop-grid:canvas padding 24px,列宽 104px,gap 18px,
* 左→右排列,超出画布宽度则换行(align-content: start)
*
* 桌面图标来源:
* 1. 用户快捷方式(SYSTEM/user/desktop 目录)
* 2. 内置应用(editor, browser 等)
* 注意: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 右缘 */
/* 先加载用户快捷方式 */
shortcuts_refresh();
for (int i = 0; i < g_shortcut_count && g_icon_count < MAX_ICONS; i++) {
for (int i = 0; i < g_app_count; i++) {
if (!g_apps[i].on_create && !g_apps[i].elf_name) 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;
}
/* 将快捷方式转换为桌面图标(app_id = -1 表示快捷方式) */
g_icons[g_icon_count].app_id = -1; /* 标记为快捷方式 */
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_shortcuts[i].display_name;
g_icons[g_icon_count].selected = 0;
g_shortcuts[i].x = x; /* 记录位置用于点击检测 */
g_shortcuts[i].y = y;
g_icon_count++;
x += KATE_ICON_W + KATE_ICON_GAP;
}
/* 再追加 Linux 应用图标(register_linux_apps 已登记进 g_apps,带 linux_cmd)。
* 服务不可用时图标仍上架但置灰(draw_app_icon 查 g_lnx_svc_up),
* 双击开终端窗口提示原因,与可用时同一入口。 */
for (int a = 0; a < g_app_count && g_icon_count < MAX_ICONS; a++) {
if (!g_apps[a].linux_cmd) continue;
if (x > pad && x + KATE_ICON_W > max_x) {
x = pad;
y += KATE_ICON_H + KATE_ICON_GAP;
}
g_icons[g_icon_count].app_id = a;
g_icons[g_icon_count].x = x;
g_icons[g_icon_count].y = y;
g_icons[g_icon_count].label = g_apps[a].display_name;
g_icons[g_icon_count].label = g_apps[i].display_name;
g_icons[g_icon_count].selected = 0;
g_icon_count++;
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 { u64 offset; u64 info; i64 addend; } dt_elf64_rela;
/* 外部工具 ELF 加载缓冲。fileman/editor 等工具 include fat32_io.h
* 带 ~388KB BSS(f32_disk+f32_data),其 .elf 的 memsz 可达 ~470KB。
* 1MB 容量覆盖当前所有工具 ELF 并留余量。BSS 是 NOBITS 不占 desktop.elf 文件空间。 */
static u8 g_elf_image[1048576];
static u8 g_elf_image[262144];
static int dt_load_elf(u8 *data, u32 data_size, void **entry_out) {
const dt_elf64_ehdr *eh = (const dt_elf64_ehdr *)data;
@@ -2023,43 +1689,6 @@ static void files_refresh(void) {
else g_files_loaded = 0;
}
/* 加载桌面快捷方式(从 SYSTEM/user/desktop 目录读取 .lnk 文件)
* 快捷方式文件格式(纯文本,UTF-8):
* 第1行:显示名称(如 "编辑器")
* 第2行:ELF文件名(FAT32 8.3,如 "EDITOR ELF")
* 第3行:图标类型(如 "editor")
*/
static void shortcuts_refresh(void) {
g_shortcut_count = 0;
if (!g_block_read) return;
/* 读取 desktop 目录下的 .lnk 文件 */
/* 简化实现:直接读取预定义的快捷方式 */
/* 内置快捷方式列表(暂时硬编码,后续可改为读取.lnk文件) */
static const struct {
const char *name;
const char *elf;
const char *icon;
} builtin_shortcuts[] = {
{ "编辑器", "EDITOR ELF", "editor" },
{ "文件管理", "FILEMAN ELF", "folder" },
{ "浏览器", "BROWSER ELF", "browser" },
{ "终端", "SHELL ELF", "terminal" },
};
int count = sizeof(builtin_shortcuts) / sizeof(builtin_shortcuts[0]);
for (int i = 0; i < count && g_shortcut_count < MAX_ICONS; i++) {
desktop_shortcut *s = &g_shortcuts[g_shortcut_count];
kstrcpy(s->display_name, builtin_shortcuts[i].name, 24);
kstrcpy(s->elf_name, builtin_shortcuts[i].elf, 11);
kstrcpy(s->icon_type, builtin_shortcuts[i].icon, 12);
s->x = 0; s->y = 0;
s->selected = 0;
g_shortcut_count++;
}
}
/* 双击打开文件 → 载入 DASHBOARD EDITOR */
static void files_open(int idx) {
if (idx < 0 || idx >= g_file_count) return;
@@ -2474,20 +2103,7 @@ static void draw_desktop_icons(void) {
du_kate_desktop_icon(&g_fb, ic->x, ic->y, ic->label, ic->selected);
int ix = ic->x + (KATE_ICON_W - KATE_ICON_IMG) / 2;
int iy = ic->y + 10;
/* 根据图标来源绘制不同图标 */
if (ic->app_id >= 0) {
/* 内置应用图标 */
draw_app_icon(&g_fb, ic->app_id, ix, iy);
} else if (i < g_shortcut_count) {
/* 快捷方式图标(根据 icon_type 选择) */
const char *icon = g_shortcuts[i].icon_type;
if (icon[0] == 'e' && icon[1] == 'd') draw_icon_editor(&g_fb, ix, iy);
else if (icon[0] == 'f') draw_icon_folder(&g_fb, ix, iy);
else if (icon[0] == 'b') draw_icon_browser(&g_fb, ix, iy);
else if (icon[0] == 't') draw_icon_terminal(&g_fb, ix, iy);
else draw_icon_editor(&g_fb, ix, iy); /* 默认 */
}
}
}
@@ -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;
if (mx >= ix && mx < ix + ICON_W && my >= iy && my < iy + ICON_H) {
if (dbl && g_icons[i].selected) {
/* 根据图标来源选择启动方式 */
if (g_icons[i].app_id >= 0) {
/* 内置应用 */
launch_app(g_icons[i].app_id);
} else if (i < g_shortcut_count) {
/* 快捷方式:加载外部 ELF */
slog("launching shortcut");
launch_external_elf(g_shortcuts[i].elf_name);
}
} else {
for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0;
g_icons[i].selected = 1;
@@ -3544,22 +3152,6 @@ void dsk_entry(const dsk_boot_context *ctx) {
g_fb.fb = (du_u32 *)SPRITE_BUF_ADDR;
f32_init(g_block_read, g_block_write);
/* Linux 兼容层服务(reserved[5],magic 校验)—— 需在 register_apps 前就绪 */
{
u64 lxc_addr = ctx->reserved[5];
if (lxc_addr) {
const linux_compat_service *lxc = (const linux_compat_service *)lxc_addr;
if (lxc->magic == LINUX_COMPAT_MAGIC) {
g_lxc_svc = lxc;
slog("linux_compat service ok");
} else {
slog("linux_compat magic mismatch");
}
} else {
slog("linux_compat service absent");
}
}
tsc_calibrate();
g_rtc_boot_sec = rtc_day_sec();
g_last_sec = g_rtc_boot_sec;
+1 -1
View File
@@ -18,7 +18,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(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)
%.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(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)
%.o: %.c
+1 -1
View File
@@ -17,7 +17,7 @@ all: $(OUT)
# 构建后用 patch_login_hash.ps1 计算 .text SHA256 并写入 .hashseg
$(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)
@echo [login] patching .text SHA256 into .hashseg...
powershell -NoProfile -ExecutionPolicy Bypass -File $(PATCHER) $@
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(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)
%.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(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)
%.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(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)
%.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(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)
%.o: %.c
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(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)
%.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)
$(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)
%.o: %.c
+231
View File
@@ -0,0 +1,231 @@
/* fat32_lfn.h — Deshab FAT32 VFAT 长文件名(LFN)解析共享库(static inline,无库)
*
* 前置条件:包含者已定义 u8/u16/u32 基础类型(与 fat32_io.h 一致)。
*
* FAT32 LFN 原理:
* 目录项中 attr==0x0F 的是 LFN 片段项。每项含 13 个 UTF-16 LE 字符
* (name1[5] + name2[6] + name3[2]),按逆序存储——序号最高(带 0x40
* 标志)的项最先出现,序号递减,最后紧跟一个 8.3 短目录项作为"主项"。
* 短名项的 checksum(由 name11 计算)用于校验整条 LFN 链完整性。
*
* 用法(在目录遍历循环中):
* fat32_lfn_buf lfn;
* fat32_lfn_init(&lfn);
* // 外层簇遍历循环——lfn 必须跨簇存活(LFN 链可能跨簇边界)
* while (遍历目录簇) {
* for (每个 32 字节目录项 e) {
* if (name[0]==0) 目录结束;
* if (name[0]==0xE5) { fat32_lfn_init(&lfn); continue; }
* int is_short = fat32_lfn_process(&lfn, entry_ptr);
* if (is_short) {
* // lfn.valid==1 时有完整 LFN,可 fat32_lfn_to_ascii 取名
* // 用 fat32_lfn_match 匹配长名或 8.3 短名
* }
* }
* }
*
* 注意:fat32_lfn_buf 必须在簇遍历循环**外**声明,跨簇切换时不清空,
* 以支持跨簇边界的 LFN 链。
*/
#ifndef DESHAB_FAT32_LFN_H
#define DESHAB_FAT32_LFN_H
/* LFN 最大字符数(FAT32 规范最多 20 个 LFN 项 = 260 字符) */
#define FAT32_LFN_MAX 260
/* LFN 收集缓冲(栈/局部变量分配) */
typedef struct {
u16 chars[FAT32_LFN_MAX]; /* UTF-16 LE 累积字符(正序) */
int count; /* 有效字符数(不含 0x0000 终止符/0xFFFF 填充) */
u8 checksum; /* LFN 链 checksum(来自 0x40 标志项) */
int valid; /* 1=LFN 链完整且 checksum 与短名项匹配 */
} fat32_lfn_buf;
/* 初始化/清空 LFN 缓冲 */
static inline void fat32_lfn_init(fat32_lfn_buf *buf) {
buf->count = 0;
buf->valid = 0;
buf->checksum = 0;
for (int i = 0; i < FAT32_LFN_MAX; i++) buf->chars[i] = 0;
}
/* 计算 8.3 短名(11 字节)的 LFN checksum。
* 标准 FAT32 算法:cksum = ((cksum & 1) << 7) + (cksum >> 1) + name[i] */
static inline u8 fat32_lfn_checksum(const u8 name11[11]) {
u8 cksum = 0;
for (int i = 0; i < 11; i++) {
cksum = (u8)(((cksum & 1) << 7) + (cksum >> 1) + name11[i]);
}
return cksum;
}
/* 处理一个 32 字节目录项。自动判断 LFN 项 / 短名项 / 已删除项。
*
* 调用方对目录遍历中的每个条目(跳过 name[0]==0 结束符后)调用此函数。
* - LFN 项(attr==0x0F):收集 13 个 UTF-16 字符到 buf,返回 0
* - 已删除项(name[0]==0xE5):清空 buf,返回 0
* - 短名项:校验 checksum,设 buf->valid,返回 1
*
* 返回 1=刚处理的是短名项(此时可检查 buf->valid 判断是否有有效 LFN),
* 0=LFN 项或已删除项。
*
* 注意:遇到带 0x40 标志的 LFN 项(链开始)时自动重置 buf。
* 遇到已删除项时自动清空 buf(防止残留 LFN 污染下一个文件)。 */
static inline int fat32_lfn_process(fat32_lfn_buf *buf, const u8 *entry) {
u8 attr = entry[11];
/* 已删除项:清空 LFN 缓冲 */
if (entry[0] == 0xE5) {
fat32_lfn_init(buf);
return 0;
}
/* LFN 项(attr == 0x0F) */
if (attr == 0x0F) {
u8 seq = entry[0];
u8 ord = (u8)(seq & 0x3F); /* 序号 1-20 */
if (ord == 0 || ord > 20) {
fat32_lfn_init(buf);
return 0;
}
/* 带 0x40 标志 = LFN 链开始(最高序号项),重置缓冲 */
if (seq & 0x40) {
int prev_cksum = buf->checksum;
fat32_lfn_init(buf);
buf->checksum = entry[13];
(void)prev_cksum;
}
/* 提取 13 个 UTF-16 LE 字符,放到 (ord-1)*13 位置(正序) */
int base = (ord - 1) * 13;
if (base + 13 > FAT32_LFN_MAX) {
fat32_lfn_init(buf);
return 0;
}
/* name1: entry[1..10] = 5 个 UTF-16 LE 字符 */
for (int i = 0; i < 5; i++) {
u16 c = (u16)entry[1 + i * 2] | ((u16)entry[2 + i * 2] << 8);
buf->chars[base + i] = c;
}
/* name2: entry[14..25] = 6 个 UTF-16 LE 字符 */
for (int i = 0; i < 6; i++) {
u16 c = (u16)entry[14 + i * 2] | ((u16)entry[15 + i * 2] << 8);
buf->chars[base + 5 + i] = c;
}
/* name3: entry[28..31] = 2 个 UTF-16 LE 字符 */
for (int i = 0; i < 2; i++) {
u16 c = (u16)entry[28 + i * 2] | ((u16)entry[29 + i * 2] << 8);
buf->chars[base + 11 + i] = c;
}
return 0; /* LFN 项,还不是短名项 */
}
/* 短名项:校验 checksum */
u8 cksum = fat32_lfn_checksum(entry);
if (buf->checksum != 0 && buf->checksum == cksum) {
/* LFN 链有效——计算实际字符数(截断于 0x0000 终止符或 0xFFFF 填充符) */
int n = 0;
for (int i = 0; i < FAT32_LFN_MAX; i++) {
if (buf->chars[i] == 0x0000 || buf->chars[i] == 0xFFFF) break;
n++;
}
buf->count = n;
buf->valid = 1;
} else {
/* checksum 不匹配或无 LFN 链:清空,回退 8.3 */
fat32_lfn_init(buf);
}
return 1; /* 短名项处理完毕 */
}
/* 从 LFN 缓冲提取 ASCII 名(UTF-16→ASCII 缩窄,仅 BMP U+0000..U+007F)。
* out_cap 为 out 缓冲容量(含 NUL)。
* 返回字符串长度(不含 NUL),-1=无有效 LFN 或含非 ASCII 字符。 */
static inline int fat32_lfn_to_ascii(const fat32_lfn_buf *buf, char *out, int out_cap) {
if (!buf->valid || buf->count == 0 || out_cap <= 0) return -1;
int n = buf->count;
if (n >= out_cap) n = out_cap - 1;
for (int i = 0; i < n; i++) {
u16 c = buf->chars[i];
if (c > 0x7F) return -1; /* 含非 ASCII 字符 */
out[i] = (char)(u8)c;
}
out[n] = 0;
return n;
}
/* ASCII 大小写不敏感字符串比较 */
static inline int fat32_lfn_streq_ci(const char *a, const char *b) {
if (!a || !b) return 0;
while (*a && *b) {
char ca = *a, cb = *b;
if (ca >= 'a' && ca <= 'z') ca = (char)(ca - 32);
if (cb >= 'a' && cb <= 'z') cb = (char)(cb - 32);
if (ca != cb) return 0;
a++; b++;
}
return *a == *b;
}
/* 判断 ASCII 字符串是否以指定后缀结尾(大小写不敏感)。
* 例如 fat32_lfn_ends_with_ci("libtest.so", ".so") → 1 */
static inline int fat32_lfn_ends_with_ci(const char *s, const char *suffix) {
if (!s || !suffix) return 0;
int sl = 0, xl = 0;
while (s[sl]) sl++;
while (suffix[xl]) xl++;
if (xl > sl || xl == 0) return 0;
for (int i = 0; i < xl; i++) {
char a = s[sl - xl + i];
char b = suffix[i];
if (a >= 'a' && a <= 'z') a = (char)(a - 32);
if (b >= 'a' && b <= 'z') b = (char)(b - 32);
if (a != b) return 0;
}
return 1;
}
/* 匹配目录项:优先匹配 LFN 长名(大小写不敏感),回退 8.3 短名。
* buf : LFN 收集缓冲(短名项处理后)
* name11 : 短名项的 11 字节 8.3 名
* target_long: 目标长名(ASCII,可为 NULL=不匹配长名)
* target83 : 目标 8.3 名(11 字节,可为 NULL=不匹配 8.3)
* 返回 1=匹配,0=不匹配。
* FAT32 规范:文件名查找大小写不敏感。 */
static inline int fat32_lfn_match(const fat32_lfn_buf *buf, const u8 name11[11],
const char *target_long, const char *target83) {
/* 1. 优先匹配 LFN 长名 */
if (buf->valid && buf->count > 0 && target_long) {
char ascii[FAT32_LFN_MAX];
int n = fat32_lfn_to_ascii(buf, ascii, sizeof(ascii));
if (n >= 0 && fat32_lfn_streq_ci(ascii, target_long)) return 1;
}
/* 2. 回退 8.3 短名匹配(精确比较 11 字节) */
if (target83 && name11) {
for (int i = 0; i < 11; i++) {
if (name11[i] != (u8)target83[i]) return 0;
}
return 1;
}
return 0;
}
/* 从 8.3 短名(11 字节)生成可显示 ASCII 字符串(如 "README.TXT")。
* 与 fat32_io.h 的 f32_name_from_83 功能相同,独立提供以避免循环依赖。 */
static inline void fat32_lfn_short_to_str(const u8 name11[11], char out[13]) {
int p = 0;
for (int j = 0; j < 8; j++) {
if (name11[j] == ' ') break;
out[p++] = (char)name11[j];
}
if (name11[8] != ' ') {
out[p++] = '.';
for (int j = 8; j < 11; j++) {
if (name11[j] == ' ') break;
out[p++] = (char)name11[j];
}
}
out[p] = 0;
}
#endif /* DESHAB_FAT32_LFN_H */
+1 -1
View File
@@ -15,7 +15,7 @@ OBJECTS := $(C_SOURCES:.c=.o)
all: $(OUT)
$(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)
%.o: %.c
+3
View File
@@ -254,6 +254,9 @@ static void redraw_all(void) {
else redraw_list();
}
/* ---- 返回上级目录(前向声明) ---- */
static void go_up(void);
/* ---- 打开文件/目录 ---- */
static void go_up(void); /* 前向声明:open_selected 调用 go_up */
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)
$(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)
%.o: %.c
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
# run_desktop_host.sh — 在 Linux 下直接启动 Deshab 桌面进行测试
#
# 用法:
# ./ISO/run_desktop_host.sh # 编译并运行,默认 ISO/deshab.img
# ./ISO/run_desktop_host.sh /path/to.img # 指定镜像
#
# 依赖:libsdl2-dev(Debian/Ubuntu: sudo apt install libsdl2-dev)
set -e
# 定位项目根(本脚本位于 ISO/ 下)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
IMG="${1:-$ROOT/ISO/deshab.img}"
# 检查 SDL2
if ! command -v sdl2-config >/dev/null 2>&1; then
echo "[run] 错误:未找到 sdl2-config,请先安装 libsdl2-dev" >&2
echo " Debian/Ubuntu: sudo apt install libsdl2-dev" >&2
echo " Fedora: sudo dnf install SDL2-devel" >&2
exit 1
fi
# 检查镜像
if [ ! -f "$IMG" ]; then
echo "[run] 错误:镜像不存在: $IMG" >&2
echo " 请先在 Windows 下运行 build.bat 生成 ISO/deshab.img" >&2
exit 1
fi
# 编译
echo "[run] 编译 desktop_host($(basename "$IMG"))..."
make -C "$ROOT/CODE/desktop" -f Makefile.linux
# 运行
echo "[run] 启动桌面..."
exec "$ROOT/CODE/desktop/desktop_host" --img "$IMG"
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.
+12 -12
View File
@@ -62,10 +62,10 @@
2. x86emu32.c try_iat_call() 拦截 CALL IAT(合成地址 0x0001xxxx)后 fn32 为空 → 走 fn64 回退 `f(a0,a1,a2,a3)`,a0..a3 是从模拟栈读出的**原始 32 位 guest 值**:hello32 的 `push 0x401017`(msg guest VA = image_base 0x400000 + 0x1017)→ a0=0x401017。
3. shim_printf(pe_shim.c L219)`const char *f = (const char *)fmt;` 把 guest VA 直接当 host 指针解引用,**无 guest→host 转换**(解释器映像驻留在 emu->mem 大缓冲区,正确地址应为 emu->mem + 0x401017)→ host 低地址 0x401017 未映射 → #PF → BUG-20260801-005 放大为三重故障。
拦截前链路均正确:0x68 push imm32、FF /2 `call [disp32]` ModRM 解码、read_rm32 读 IAT 合成地址、try_iat_call 参数读取——解释器覆盖率不记倒退。
- 修复方案(系统侧,本轮测试任务约束不改系统代码,仅登记): ①为每个含指针参数的 shim 实现 fn32(接收 esp_ptr=&emu->mem[esp],guest VA 经 emu->mem 基址转换为 host 指针);②或 fn64 回退前设置全局 g_emu_mem_base,shim 内对 <0x10000000 的指针统一加基址转换;③kernel32 WriteFile/ReadFile 等指针参数函数同理。
- 状态: [OPEN]
- 修复提交: 待提交
- 验证: 修复后 x86emu32-coverage.ps1 应见 "Hello from PE32!" → "[cmd] PE run end" → "[DSK] cmd.elf returned";pe-compat.ps1 会话 B 同步验证(两处均已留自动升级断言:日志出现即升 PASS)。
- 修复方案: 引入 g_emu_mem_base 全局基址 + pe_shim_set_emu_base() 设置 + gp() 转换函数(va < 0x10000000 且 g_emu_mem_base 非零时返回 g_emu_mem_base+va);x86emu32 解释执行 PE32 前设基址,shim 内指针参数经 gp() 转换;原生 PE32+ 路径 g_emu_mem_base 恒为 0,gp() 恒等映射零影响。
- 状态: [FIXED]
- 修复提交: a26866e(CODE/UTSM/pe/pe_shim.c g_emu_mem_base + pe_shim_set_emu_base + gp())
- 验证: x86emu32-coverage.ps1 PASS——HELLO32.EXE 解释执行输出 "Hello from PE32!";pe-compat.ps1 会话 B 同步验证通过。
### BUG-20260801-006 [P1] [UTSM/pe] pe_shim setjmp/longjmp 跨已返回函数 UB:longjmp 恢复脏栈寄存器 → 野指针 call → 三重故障
- 现象: `pe HELLO64.EXE` 原生执行成功打印 "Hello from PE32+!"(BUG-004 修复后),随后在 ExitProcess 阶段 guest 三重故障,QEMU(-no-reboot)静默退出;后续 AUTOEXEC 命令(HELLO32/NONEXIST/exit)全部不再执行。TCG 与 WHPX 表现一致。
@@ -76,10 +76,10 @@
反汇编定案(utsm.elf):fault RIP `pe_service_run+0x226` = `call *%r15`(pe_call_on_stack 内联汇编调 PE 入口那条);但 R15=0xffffffff8055b260 并非 PE 入口 0xffffffff80d22000、RSP=0xffffffff80003030 并非 PE 栈顶 0xffffffff80e23000——**这是 longjmp 用脏栈"恢复"出来的假现场**,不是正常调用路径。
pe_shim_setup_exit(0xffffffff80014d60)反汇编:clang 把 __builtin_setjmp 实现为 `g_exit_jmp={RBP,RSP,resume=0x80014d94}`,恢复后从 0x80014d94 起执行函数尾声 `pop rbx;pop r12;pop r13;pop r14;pop r15;pop rbp;ret`——**从恢复的栈上弹 7 个槽**。
- 根因分析: pe_shim_setup_exit() 内调 __builtin_setjmp 后**即返回**(C 标准:含 setjmp 的函数已返回时再 longjmp 为未定义行为)。setjmp→longjmp 之间 pe_service_run 又调用 log_hex64×2("[PE] entry="/"[PE] stack_top="),其栈帧**精确复用** pe_shim_setup_exit 尾声要弹的那片栈区 → longjmp 尾声 pop 出的 rbx/r12-r15/rbp 全是 log_hex64 帧残片(R12/R13 即十六进制字符串残片),最后 `ret` 弹到 [X-8] 槽位的残留返回地址 0x8001478c(log_hex64 调用点返回地址)→ 落到 pe_call_on_stack 内联汇编正中间(`mov %rsp,g_pe_saved_rsp; mov %r14,%rsp; call *%r15`)→ 以脏 R14 为栈、脏 R15 为入口野调用 → 写返回地址到只读页 0xffffffff80003028 → #PF(保护违例) → BUG-005 放大为三重故障。PE32 解释器路径同一 setjmp/longjmp 机制,同样暴露在 UB 下(只是中间无 log 调用、脏数据不同,崩溃形态可能不同)。
- 修复方案(系统侧,本轮测试任务约束不改系统代码,仅登记): ①把 setjmp 下沉到 pe_service_run 本体内(含 PE 调用全程存活的作用域),不要用"setjmp 包装函数";②或弃用 __builtin_setjmp/longjmp,改用显式上下文保存结构(保存 callee-saved + RSP/RIP 到 pe_service_run 帧内,ExitProcess 经受控 trampoline 返航);③PE32 路径同理。
- 状态: [OPEN]
- 修复提交: 待提交
- 验证: 修复后 pe-compat.ps1 应见 "[PE] ExitProcess code=" → "[cmd] PE run end" → "[DSK] cmd.elf returned" 完整链路;x86emu32-coverage.ps1 动态链路同步验证。
- 修复方案: 把 setjmp 下沉到 pe_service_run 本体内执行(含 PE 调用全程存活的作用域);g_exit_jmp[24] 缓冲区,pe_shim_setup_exit 只提供缓冲区并重置退出状态,__builtin_setjmp 由 pe_service_run 本体直接调用,确保 setjmp 帧不再跨已返回函数。
- 状态: [FIXED]
- 修复提交: a26866e(CODE/UTSM/pe/pe_shim.c g_exit_jmp 重构 + pe_shim_setup_exit 语义调整)
- 验证: pe-compat.ps1 4 会话全绿——HELLO64.EXE 打印 "Hello from PE32+!" 后 `[PE] ExitProcess code=` → `[cmd] PE run end` 干净返回;Deaicup GUI 长时间运行后 Esc→WM_CLOSE→ExitProcess(0) 退出链回归确认。
### BUG-20260801-004 [P1] [tools/pe-samples] gen_pe.py PE32+ 二进制双重构造缺陷 → 原生执行三重故障
- 现象: `pe HELLO64.EXE` 原生执行时 guest 直接三重故障,QEMU(-no-reboot)进程退出;串口最后一行为 `[PE] stack_top=...`,无 "Hello from PE32+!"。WHPX 下表现为 "Unexpected VP exit code 4",TCG 下同样退出(此前误判为 WHPX 平台限制)。
@@ -99,10 +99,10 @@
- 复现步骤: 运行含缺陷的 HELLO64.EXE(BUG-004,call 到未映射地址 0x300)→ QEMU 直接退出;或任何 DSK 阶段空指针解引用。
- 串口/日志证据: QEMU `-d int` 追踪(`.build_tmp/pe-dbg-qemu.log`):`check_exception old: 0xffffffff new 0xe`(#PF at RIP=0x300, CR2=0x300, RSP=0xffffffff80e22ff0 栈有效)→ `old: 0xe new: 0xd`(**投递 #PF 时即 #GP**)→ `old: 0x8 new 0xd`(投递 DF 时再 #GP)→ Triple fault。现场:IDT=ffffffff8002b000 lim=0xfff,GDT=ffff80007fe3d000 lim=0x47,CS=0028/SS=0030,TR=0038 且 TSS base=0。
- 根因分析: BUG-20260729-013 的 `idt_halt_all()` 把 256 个 IDT 条目换成 halt stub,意图 cli;hlt 安全停机;但实测异常**投递本身**就 #GP——halt 门描述符的选择子/IST 与 DSK 阶段 GDT/TSS(TSS base=0)不匹配,异常帧无法压栈。净效果:DSK 阶段是无异常容错的裸奔环境,任何 guest bug 都表现为"QEMU 静默退出",测试侧只能开 `-d int` 才能定位。
- 修复方案(系统侧,本轮测试任务约束不改系统代码,仅登记): ① halt IDT 门用 DSK 阶段 GDT 内有效选择子重建、IST=0;② 或 DSK 入口自建最小 IDT(#PF/#GP/#DF 打印向量+CR2+RIP 后 cli;hlt);③ 恢复 TSS 有效基址。
- 状态: [OPEN]
- 修复提交: 待提交
- 验证: 修复后重跑含缺陷 PE:串口应输出异常诊断而非 QEMU 静默退出。
- 修复方案: DSK 交接时装配最小诊断 IDT(idt_install_dsk_diag):#PF/#GP/#DF 等 0-31 号异常走 idt_handler 打印向量+err+RIP+CR2+RAX 后 cli;hlt,门用 DSK 阶段有效 CS 选择子、IST=0。
- 状态: [FIXED]
- 修复提交: a26866e(CODE/UTSM/arch/x86_64/idt.c idt_install_dsk_diag)
- 验证: pe-compat.ps1 fault64 会话 PASS——故意 #PF(mov [0x4000000000],1)串口打出 `[IDT] vector=0xe err=0x2 rip=... cr2=0x4000000000` 完整现场后安全停机,修复前静默三重故障。
### BUG-20260801-003 [P1] [tools/desktop_app.h] da_mouse_poll 吞键盘扫描码导致应用键盘全灭
- 现象: fileman/browser/editor(所有 #include desktop_app.h 的全屏应用)内键盘输入完全无效:Esc 无法退出、方向键/回车无响应;应用只能由宿主动作(关机/会话终止)结束。
@@ -0,0 +1,183 @@
# 交接文档 — 多智能体推进收尾(2026-08-05)
> **接手者须知**:本文档记录截至 2026-08-05 的工作全貌。此前通过多轮多智能体并发推进 W2→W6,主体成果已合并提交(commit `a26866e` + `019585f`),但**第六波(W6)三线成果全部丢失未入库**,需重做。请先读"未完成工作"与"Bug 状态核对"两节。
---
## 一、项目方向与当前里程碑
**Deshab OS**:SAS-R0 单地址空间 Ring0 内核,UTSM 内置 PE 兼容层 + Linux 兼容层(VMX guest)。
**当前里程碑**:M2 — 存储升级 + 中断后端(`docs/系统开发策划.md` §4.4)。
**本轮大方向**:通过多智能体并发推进,把 M2 的阻塞点逐个清掉——NVMe 写路径、virtio_net 数据路径、B7 IRQ 后端(APIC/IOAPIC/MSI-X)、PE 兼容层缺陷、用户态输入缺陷、QEMU 测试套件脚本化。
---
## 二、已落地成果(commit `a26866e` + `019585f`,已核实代码在库)
### 1. B7 IRQ 后端(APIC/IOAPIC)— stage1+2 完成
- **DKM `apic.drv`**(`CODE/DKM/apic/apic.c`,+1025 行):LAPIC 接管、MADT/ISO 解析、IOAPIC IOREDTBL 编程(82093AA 位布局已修正 bit13/15/16)、FUCK 开关 `apic_route=0/1`(0=PIC 零回归,1=IOAPIC→LAPIC 接管,失败完整降级)、`apic_route_legacy` 放开 IRQ1/12/11、`apic_diag` HPET 对拍。
- **UTSM LAPIC EOI 钩子**(`CODE/UTSM/arch/x86_64/idt.c` + `dkm.h` 尾部追加 `register_apic_eoi`,ABI 兼容):idt_handler 分发后调钩子写 LAPIC EOI,0xFF spurious 特判,PIC EOI 保留无害。DSK diag IDT(`idt_install_dsk_diag`)让 0-31 号异常打印完整现场后安全停机。
- **tick 链路验证**:route=1 下 500ms 窗口 100 ticks(PIT mode3 双沿触发,定论为 200Hz,BUG-20260803-009 CLOSED,非缺陷)。
- **生产配置**:FUCK `apic_route=1` 已启用,boot-regression B1-B5 全 PASS(含 FirstInit 向导真实按键走 IOAPIC→LAPIC→IDT 链路)。
### 2. NVMe — 写路径 + MSI/MSI-X 中断驱动完成
- 写路径 + PRP list 多页 + 读回校验自检(`CODE/DKM/nvme/nvme.c`)。
- **MSI/MSI-X 中断驱动化**(commit `019585f`):vector allocator(bitmap+ctzll,0x40-0xDF 池,O(1))、MSI-X 表项编程、完成槽+irqsave 自适应轮询混合模型、**幽灵 CQE 三层 CID 校验防御**(BUG-20260803-010 FIXED,QEMU 在 unmask 边界补投 CID=0 幽灵 CQE)、handler 自写 LAPIC EOI。FUCK `nvme_msi=1` 默认开,0 强制轮询。
- 三配置回归全绿(MSI-X / route=0 PIC / nvme_msi=0 纯轮询),driver-matrix.ps1 PASS。
### 3. virtio_net — virtqueue RX/TX 完成(纯轮询,无 MSI-X)
- legacy transitional 探测、feature negotiation、RXQ/TXQ 各 256 描述符、netdev 注册 index=1、boot 时 DHCP 自测(slirp 真实 OFFER)。
- **数据路径纯轮询**:rx_poll 轮询 used ring,TX 后同步等待。IRQ handler 已注册但未用于数据路径。
- **MSI-X 中断驱动化未完成**(W6-B 丢失,见第三节)。
### 4. PE 兼容层 — 004/005/006/007 全部修复
- **BUG-004**(gen_pe.py PE32+ RVA 偏置):call disp32 偏移 + import RVA 全部加 code_rva 偏置,重新生成 hello64/hello32/cmd.exe。
- **BUG-005**(DSK 阶段异常无法投递):`idt_install_dsk_diag` 装配最小 IDT,#PF/#GP/#DF 打印向量+CR2+RIP+CS 后 cli;hlt。
- **BUG-006**(pe_shim longjmp 跨已返回函数 UB):setjmp 下沉到 `pe_service_run` 本体,`g_exit_jmp[24]` 缓冲区,`pe_shim_setup_exit` 只提供缓冲区(setjmp 帧在 PE 执行全程存活)。
- **BUG-007**(shim fn32 guest 指针当 host 指针):`g_emu_mem_base` + `pe_shim_set_emu_base()` + `gp()` 转换(va<0x10000000 加基址),原生路径恒等映射零影响。
- **Deaicup GUI** 端到端验证通过(首帧渲染 + PS/2 键盘 + AUX 鼠标 + 软件光标 + Esc 退出链),raster.rs 重心坐标符号 bug 顺带修复。
### 5. 用户态输入/加载 — 001/002/003/008 全部修复
- **BUG-001**(shell 吞 y/i/0):Ctrl 修饰键跟踪(0x1D/0x9D),快捷键仅 Ctrl 按下时生效。
- **BUG-002**(g_run_image 256KB):扩至 1MB,editor.elf(496KB 内存镜像)可加载。
- **BUG-003**(da_mouse_poll 吞键盘扫描码):先判 AUX 位(st&0x20)再读 0x60。
- **BUG-008**(da_mouse_init 配置字污染):序列首尾 da_ps2_drain 冲刷残留,配置字补 0x40,复位读完 FA AA 00。
### 6. QEMU 测试套件 — 9 用例全自动化
`tests/qemu/`(commit `a26866e` 已入库,已核实存在):
- `lib/QemuTest.ps1`:公共框架(启动 QEMU/计时杀/串口断言/monitor TCP sendkey/screendump/HTTP 服务器/并发隔离端口分配)。
- 9 用例:`boot-regression` / `driver-matrix` / `linux-compat` / `pe-compat` / `deaicup-e2e` / `userapps-matrix` / `network-e2e` / `performance-baseline` / `x86emu32-coverage`。
- `Run-All.ps1`:汇总表 + 退出码。
- W5-A 基线实跑 8/9 PASS(唯一 FAIL 是 cmd AUTOEXEC 缓冲别名,见 BUG-011)。
- `docs/RE/test-cases/` 8 份用例文档均补"自动化脚本"小节。
---
## 三、未完成工作(W6 三波成果全部丢失,需重做)
> 第六波并发三线(W6-A/B/C)的 subagent 报告完成,但**改动未入库**(工作区干净,代码核实无对应实现)。下个 agent 需重做。
### W6-A:cmd AUTOEXEC 缓冲别名修复(BUG-011,P1)
- **根因**(已定位定案):`f32_read_root_file`(`CODE/tools/fat32_io.h`)把文件读入**共享静态缓冲** `f32_data` 并返回指针;`run_autoexec`(`CODE/cmd/main.c` L1232)跨 `execute_command` 持有该指针逐行执行;首行 `pe HELLO32.EXE` 加载 PE 文件覆盖缓冲中剩余 AUTOEXEC 脚本 → `exit` 行丢失 → cmd 落入交互主循环永远轮询键盘。
- **影响**:任何"读文件的 AUTOEXEC 命令 + 后续行"组合。历史上从无"真实 PE 执行后 `[DSK] cmd.elf returned`"。
- **修法**(W6-A 已设计未入库):`run_autoexec` 先把脚本整体拷入私有缓冲 `g_autoexec_snapshot[4096]` 再逐行执行。`CODE/cmd/main.c` 当前 L1232 仍是原样,**需重做**。
- **验证**:修复后 x86emu32-coverage.ps1 末锚点 `[DSK] cmd.elf returned` 转绿(该用例硬断言已就位,W6-A 报告复跑 PASS 但成果丢失)。
- **登记**:Bug记录.txt **无 011 条目**(W6-A 报告登记了但未入库),需补登。
### W6-B:virtio_net MSI-X 中断驱动 RX/TX
- **现状**:virtio_net.c 有 virtqueue RX/TX(纯轮询),**无 MSI-X 代码**(Select-String 'msix|MSI-X' 无匹配)。
- **W6-B 设计**(未入库):legacy transitional + MSI-X 组合,三向量(cfg/RXQ/TXQ)编程,SPSC pending 环 IRQ 上下文收包,三级降级(MSI-X→INTx→轮询),FUCK `vnet_msi=1` 开关。参考 NVMe 的 `nvme_msix_program` 模式。
- **需重做**。QEMU 拓扑:e1000@net0 + virtio-net-pci@net1。
### W6-C:用户态工具串口锚点增补 + 策划文档收尾
- **现状**:`CODE/tools/editor.c` / `curl.c` / `ping.c` / `browser.c` **无** `[editor] saved` / `[curl] http status` / `[ping] reply` / `[browser] loaded` 锚点(Select-String 无匹配)。
- **W6-C 设计**(未入库):为各工具补 1-3 个关键事件串口锚点,升级 userapps-matrix.ps1 / network-e2e.ps1 的 MANUAL 项为自动断言。
- **需重做**。
---
## 四、Bug 状态核对表(重要 — 文档与代码不符)
> `docs/RE/Bug记录.txt` 中以下条目**代码已修复并提交(a26866e)但状态仍标 [OPEN]**,本次交接已修正为 [FIXED]。
| Bug | 登记状态 | 代码实际 | 本次处理 |
|-----|---------|---------|---------|
| BUG-20260801-005 DSK 异常无法投递 | [OPEN] | ✅ idt_install_dsk_diag 已落地 | 改 [FIXED] |
| BUG-20260801-006 pe_shim longjmp UB | [OPEN] | ✅ g_exit_jmp 重构已落地 | 改 [FIXED] |
| BUG-20260801-007 shim fn32 guest 指针 | [OPEN] | ✅ g_emu_mem_base+gp() 已落地 | 改 [FIXED] |
| BUG-20260803-011 cmd AUTOEXEC 别名 | **未登记** | ❌ 代码未入库 | 需补登 + 重做修复 |
其余已 FIXED 条目(001/002/003/004/008/010)状态正确。009 CLOSED(定论非缺陷)。
---
## 五、新出现的并行工作(8/5 下午,commit `6287513` + `4a62932`)
> 这两批提交不是本轮多智能体推进的产物,是另一条线的工作,接手时注意区分:
- **Linux 预览后端**(`6287513`):`CODE/tools/desktop_app_linux.h`(跨平台后端抽象)、`CODE/desktop/host_main.c`/`host_main.h`(桌面宿主 Linux 预览)、`ISO/run_desktop_host.sh`、Notes 应用预览(`CODE/tools/notes/`,5x7 位图字体)。
- **PE DLL 管理器**(`4a62932`):`CODE/UTSM/pe/pe_dll_manager.c/h`(新模块,PE DLL 加载管理)。
- **FAT32 长文件名**:`CODE/tools/fat32_lfn.h`。
- **上下文目录重命名**:`docs/上下文/250729`→`260729`、`25072901`→`26072901`(年份修正 25→26)。
接手者若推进这些方向,先读对应源码理解现状。
---
## 六、下一步优先级建议
| 优先级 | 任务 | 理由 |
|--------|------|------|
| **P1** | 重做 W6-A:cmd AUTOEXEC 缓冲别名(BUG-011) | 阻断 x86emu32-coverage 用例,影响所有 AUTOEXEC 多行场景;修法简单(私有快照缓冲) |
| **P1** | 补登 BUG-011 条目 + 核对 Bug记录其余状态 | 文档与代码一致性 |
| **P2** | 重做 W6-B:virtio_net MSI-X | NVMe MSI-X 已完成可参照;virtio_net 当前纯轮询,中断化收益明确 |
| **P2** | M2 剩余:e1000 中断驱动收发(替代 polling) | 策划 L134 仍空;e1000 走 legacy IRQ11 已通,MSI 切换成本低 |
| **P3** | 重做 W6-C:tools 串口锚点 + 测试断言升级 | 把 MANUAL 项回收为自动化 |
| **P3** | M2 收尾:替换 arena 为真实页分配(L129)、DKM 工程化(ABI 头统一/零 warning,L136) | 策划仍空 |
| **P3** | 真机冒烟:APIC/MSI-X 在真机验证(QEMU 无多 IOAPIC/非常规 GSI) | W4-B/W5-B 均提遗留 |
| **P4** | M3 兼容层验证推进(VMX 真机/KVM) | B4 阻塞,需嵌套虚拟化环境 |
---
## 七、环境与协作约定(坑点合集,务必读完)
### 构建环境
- Windows + **PowerShell 5.1**(不能用 `&&`/`||`/三元)。
- `make.exe` 在 `C:\mingw64\bin`,clang/ld.lld 在 LLVM bin。
- 全量构建:根目录 `build.ps1`。UTSM 编译:`CODE/UTSM/MAKEFILE`(-MMD -MP,零警告)。
- **工具 MAKEFILE 普遍缺头文件依赖**:改 `desktop_app.h` 等公共头不会触发工具重编,需手动删对应 `.o`。
- **仓库内 .ps1 一律纯 ASCII**:PS5.1 把无 BOM UTF-8 按 GBK 解码,CJK 注释第三字节(0x80-0xBF)被当 GBK lead byte 会吞掉紧跟换行符,把后续代码并进注释。编辑已有含 BOM 文件保留 BOM。
### QEMU
- `ISO/run_qemu.bat`(WHPX)。WHPX 在 PE 执行处会 VP exit 4,pe-compat/deaicup-e2e 已有 TCG 自动兜底。
- **`-snapshot` 必加**(镜像只读,防 deshab.img 锁冲突,多代理并发必备)。
- monitor TCP 端口分配:deaicup-e2e 固定 45454,测试套件 45501-45509,其他按需避让。
- **Start-Process 下 `-serial file:` 不生效**(已知坑):用临时 cmd 批处理 + `-serial stdio` 重定向到日志文件,计时按 PID 杀。
- **monitor screendump 路径必须正斜杠**:反斜杠 `\C` 转义静默失败。
- `-d reset` 非法(本版叫 `cpu_reset`)。
- 并发时**只杀自己启动的 QEMU(记 PID),绝不全局 `Stop-Process qemu`**。
### 多智能体并发协议(若继续用多 agent)
- **构建互斥**:`build.ps1` 前检查 `.build_tmp\BUILD.lock`:存在且 30 分钟内则 `Start-Sleep 15-30s` 重试,不存在才创建(写名字+PID+时间),构建+打包完成**立即删除**。
- 地盘划分:UTSM/DKM/desktop/cmd/tools/tests 分属不同 agent,不交叉改动。
- **subagent 改动落地风险**:本轮 W6 三线 subagent 报告完成但改动未入库——**接手者用 subagent 后务必核实改动是否落盘主仓库工作区**(`git status` + Grep 代码),不能只信报告。建议关键修复由主会话直接 Edit/Write 落盘。
### kernel_api ABI
- 结构体 **ABI 冻结**:成员顺序/签名不可改,**只允许尾部追加**。本轮已追加 `register_apic_eoi`、`irq_vector_alloc/free`(dkm.h 尾部)。
---
## 八、关键文件索引
| 模块 | 关键文件 |
|------|---------|
| APIC/IOAPIC | `CODE/DKM/apic/apic.c`、`CODE/UTSM/arch/x86_64/idt.c`(EOI 钩子+DSK diag+vector allocator)、`CODE/UTSM/include/utsm/dkm.h`(kernel_api 尾部追加) |
| NVMe | `CODE/DKM/nvme/nvme.c`(写+MSI-X+CID 防御)、`SYSTEM/system/deshab64/FUCK`(nvme_msi 开关) |
| virtio_net | `CODE/DKM/virtio_net/virtio_net.c`(virtqueue 轮询,**无 MSI-X**) |
| PE 兼容层 | `CODE/UTSM/pe/pe_shim.c`(g_exit_jmp/g_emu_mem_base)、`pe_service.c`、`pe_loader.c`、`CODE/UTSM/pe/pe_dll_manager.c`(新)、`CODE/tools/pe-samples/gen_pe.py` |
| 用户态 | `CODE/shell/main.c`(Ctrl 跟踪+g_run_image 1MB)、`CODE/cmd/main.c`(**run_autoexec 待修**)、`CODE/tools/desktop_app.h`(da_mouse_init/poll)、`CODE/tools/editor.c`/`curl.c`/`ping.c`/`browser.c`(**锚点待补**) |
| 测试套件 | `tests/qemu/Run-All.ps1`、`tests/qemu/lib/QemuTest.ps1`、9 用例脚本 |
| 配置 | `SYSTEM/system/deshab64/FUCK`(apic_route/nvme_msi/vnet_msi/dev_mode 等) |
| 文档 | `docs/系统开发策划.md`(M2 进度)、`docs/RE/Bug记录.txt`、`docs/RE/test-cases/`、`docs/RE/桌面设计.md`、`LESSONS_LEARNED.md`、`CLAUDE.md` |
---
## 九、本轮提交时间线
| commit | 时间 | 内容 |
|--------|------|------|
| `a26866e` | 8/4 18:08 | **大合并**:B7 APIC stage1+2、NVMe 写+MSI-X、virtio_net virtqueue、PE 004-007、用户态 001-003/008、9 用例测试套件、文档 |
| `019585f` | 8/4 18:36 | B7 阶段3:NVMe MSI-X 增强 + vector allocator + 幽灵 CQE 防御(BUG-010) |
| `6287513` | 8/5 13:15 | Linux 预览后端 + Notes 应用(并行工作,非本轮) |
| `4a62932` | 8/5 13:58 | PE DLL 管理器 + desktop host + fat32_lfn + 上下文目录重命名(并行工作) |
+6 -6
View File
@@ -78,7 +78,7 @@ docs/上下文/ 开发对话记录(待提炼为正式设计文
| **B4** | QEMU TCG不支持VMX | P0 | 真机VT-x/KVM/云VM验证;WHPX检测已落地 |
| B5 | ~~fileman QEMU exit~~ ✅已解决 | — | linker.ld BSS NOBITS分离+g_elf_image扩至1MB |
| **B6** | FirstInit按键崩溃 | P1 | fb_text边界检查+编译优化降级调试中 |
| **B7** | APIC/IOAPIC stage1 已落地(LAPIC接管/MADT/IOREDTBL/apic_route开关/tick链路验证) | P2 | UTSM LAPIC EOI、IDT 256+vector allocator、MSI-X 进行中(Phase 2) |
| **B7** | APIC/IOAPIC stage1+2 已落地 + NVMe MSI-X 完成 | P2 | UTSM LAPIC EOI 钩子已落地;vector allocator 已落地;NVMe MSI-X 已完成;剩余:virtio_net/e1000 MSI-X、真机冒烟 |
### 3.2 风险登记
@@ -128,11 +128,11 @@ M5 软件生态 (依赖M3) Pacman→AUR→desktop集成
| 高位PCI MMIO映射(B2) | NVMe BAR0可读register | ✅(NVMe 数据路径已验证) |
| 替换arena为真实页分配 | 移除16MB arena上限 | |
| NVMe admin queue/identify+block provider | 注册nvme0 | ✅ 写路径+PRP list+读回校验完成 |
| IDT扩展256 vectors+vector allocator | 动态分配中断向量 | |
| APIC EOI→IOAPIC redirection→LAPIC timer | 替代PIC | 🔶 stage1 完成(LAPIC接管/MADT/IOREDTBL/apic_route开关/tick链路验证),UTSM LAPIC EOI 进行中 |
| MSI/MSI-X | NVMe/e1000使用MSI-X | |
| e1000中断驱动收发 | 替代polling | |
| virtio-net virtqueue RX/TX | 收发包成功 | ✅ virtqueue RX/TX+netdev注册+DHCP自测完成 |
| IDT扩展256 vectors+vector allocator | 动态分配中断向量 | ✅ bitmap+ctzll,0x40-0xDF池,O(1)(commit 019585f) |
| APIC EOI→IOAPIC redirection→LAPIC timer | 替代PIC | ✅ stage1+2 完成(LAPIC接管/MADT/IOREDTBL/apic_route开关/tick链路验证/LAPIC EOI钩子/legacy IRQ1/12/11持续投递验证) |
| MSI/MSI-X | NVMe/e1000使用MSI-X | 🔶 NVMe MSI-X 完成(nvme_msi开关+幽灵CQE防御);virtio_net MSI-X 未完成(W6-B丢失);e1000 仍 legacy IRQ11 |
| e1000中断驱动收发 | 替代polling | ⬜ 仍 polling(走 legacy IRQ11 已通,MSI 切换待做) |
| virtio-net virtqueue RX/TX | 收发包成功 | ✅ virtqueue RX/TX+netdev注册+DHCP自测完成(纯轮询,MSI-X 待做) |
| DKM工程化收尾 | 统一ABI头、零warning | |
| QEMU测试套件脚本化 | Run-All 全用例自动化 | ✅ 9 用例(8+deaicup-e2e)全自动化 PASS |