Compare commits
3
Commits
86df253a32
..
allow
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8717ea8f20 | ||
|
|
4a62932db2 | ||
|
|
6287513794 |
+1
-1
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
+27
-435
@@ -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); /* 默认 */
|
||||
}
|
||||
draw_app_icon(&g_fb, ic->app_id, 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);
|
||||
}
|
||||
launch_app(g_icons[i].app_id);
|
||||
} else {
|
||||
for (int j = 0; j < g_icon_count; j++) g_icons[j].selected = 0;
|
||||
g_icons[i].selected = 1;
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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 调试,不是完整的功能测试
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
Regular → Executable
BIN
Binary file not shown.
+12
-12
@@ -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
@@ -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 |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user