From 48dcb5568e99caa7714107fe5c2e15f0bf2ee4e1 Mon Sep 17 00:00:00 2001 From: xyou Date: Fri, 3 Jul 2026 21:13:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9B=BE=E5=BD=A2=E6=A1=8C=E9=9D=A2=20?= =?UTF-8?q?+=20=E7=8B=AC=E7=AB=8B=E5=B7=A5=E5=85=B7=E5=BA=94=E7=94=A8=20+?= =?UTF-8?q?=20Sealed=20Arc=20UI=20=E8=AE=BE=E8=AE=A1=E7=B3=BB=E7=BB=9F=20|?= =?UTF-8?q?=20desktop.elf=E7=AA=97=E5=8F=A3=E7=AE=A1=E7=90=86=E5=99=A8/6?= =?UTF-8?q?=E4=B8=AA=E5=B7=A5=E5=85=B7=E5=BA=94=E7=94=A8(bash/editor/filem?= =?UTF-8?q?an/browser/curl/ping)/desktop=5Fapp.h=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E5=A4=B4=E6=96=87=E4=BB=B6/Sealed=20Arc=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F/DRR=E5=AE=8C=E6=95=B4=E5=AE=9E=E7=8E=B0/Proc?= =?UTF-8?q?ess=E5=A2=9E=E5=BC=BA/DSK=E5=90=AF=E5=8A=A8=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=8A=A0=E8=BD=BDdesktop.elf/Shell=E9=87=8D=E6=9E=84/=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + CODE/UTSM/core/process.c | 119 +- CODE/UTSM/drr/drr_stub.c | 309 ++++- CODE/UTSM/include/utsm/deshab_ui.h | 708 +++++++++++ CODE/UTSM/include/utsm/drr.h | 118 +- CODE/UTSM/include/utsm/process.h | 67 +- CODE/UTSM/kernel/main.c | 6 +- CODE/desktop/MAKEFILE | 25 + CODE/desktop/linker.ld | 16 + CODE/desktop/main.c | 1438 ++++++++++++++++++++++ CODE/dsk/main.c | 40 +- CODE/shell/main.c | 289 ++--- CODE/tools/bash/MAKEFILE | 25 + CODE/tools/bash/linker.ld | 16 + CODE/tools/bash/main.c | 456 +++++++ CODE/tools/browser/MAKEFILE | 25 + CODE/tools/browser/linker.ld | 16 + CODE/tools/browser/main.c | 128 ++ CODE/tools/curl/MAKEFILE | 25 + CODE/tools/curl/linker.ld | 16 + CODE/tools/curl/main.c | 324 +++++ CODE/tools/desktop_app.h | 374 ++++++ CODE/tools/editor/MAKEFILE | 25 + CODE/tools/editor/linker.ld | 16 + CODE/tools/editor/main.c | 238 ++++ CODE/tools/fileman/MAKEFILE | 25 + CODE/tools/fileman/linker.ld | 16 + CODE/tools/fileman/main.c | 230 ++++ CODE/tools/ping/MAKEFILE | 25 + CODE/tools/ping/linker.ld | 16 + CODE/tools/ping/main.c | 339 +++++ RE/docs/desktop_architecture.md | 461 +++++++ RE/docs/tool_apps_design.md | 528 ++++++++ SYSTEM/boot/utsm.elf | Bin 17265952 -> 17867232 bytes SYSTEM/system/deshab64/deshab.elf | Bin 743840 -> 743840 bytes SYSTEM/system/deshab64/desktop.elf | Bin 0 -> 69016 bytes SYSTEM/system/deshab64/shell.elf | Bin 180160 -> 196632 bytes SYSTEM/system/deshab64/tools/bash.elf | Bin 0 -> 117152 bytes SYSTEM/system/deshab64/tools/browser.elf | Bin 0 -> 41136 bytes SYSTEM/system/deshab64/tools/curl.elf | Bin 0 -> 125152 bytes SYSTEM/system/deshab64/tools/editor.elf | Bin 0 -> 104552 bytes SYSTEM/system/deshab64/tools/fileman.elf | Bin 0 -> 47384 bytes SYSTEM/system/deshab64/tools/ping.elf | Bin 0 -> 121056 bytes build.ps1 | 58 +- 44 files changed, 6302 insertions(+), 220 deletions(-) create mode 100644 CODE/UTSM/include/utsm/deshab_ui.h create mode 100644 CODE/desktop/MAKEFILE create mode 100644 CODE/desktop/linker.ld create mode 100644 CODE/desktop/main.c create mode 100644 CODE/tools/bash/MAKEFILE create mode 100644 CODE/tools/bash/linker.ld create mode 100644 CODE/tools/bash/main.c create mode 100644 CODE/tools/browser/MAKEFILE create mode 100644 CODE/tools/browser/linker.ld create mode 100644 CODE/tools/browser/main.c create mode 100644 CODE/tools/curl/MAKEFILE create mode 100644 CODE/tools/curl/linker.ld create mode 100644 CODE/tools/curl/main.c create mode 100644 CODE/tools/desktop_app.h create mode 100644 CODE/tools/editor/MAKEFILE create mode 100644 CODE/tools/editor/linker.ld create mode 100644 CODE/tools/editor/main.c create mode 100644 CODE/tools/fileman/MAKEFILE create mode 100644 CODE/tools/fileman/linker.ld create mode 100644 CODE/tools/fileman/main.c create mode 100644 CODE/tools/ping/MAKEFILE create mode 100644 CODE/tools/ping/linker.ld create mode 100644 CODE/tools/ping/main.c create mode 100644 RE/docs/desktop_architecture.md create mode 100644 RE/docs/tool_apps_design.md create mode 100644 SYSTEM/system/deshab64/desktop.elf create mode 100644 SYSTEM/system/deshab64/tools/bash.elf create mode 100644 SYSTEM/system/deshab64/tools/browser.elf create mode 100644 SYSTEM/system/deshab64/tools/curl.elf create mode 100644 SYSTEM/system/deshab64/tools/editor.elf create mode 100644 SYSTEM/system/deshab64/tools/fileman.elf create mode 100644 SYSTEM/system/deshab64/tools/ping.elf diff --git a/.gitignore b/.gitignore index c48c072..5fe8947 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ ISO/deshab.img +*.o +.build_tmp/ +.trae/ +docs/workbuddy-planning/ +sata_fat32_dsk.img diff --git a/CODE/UTSM/core/process.c b/CODE/UTSM/core/process.c index 941ddd3..d4aa0a2 100644 --- a/CODE/UTSM/core/process.c +++ b/CODE/UTSM/core/process.c @@ -1,11 +1,126 @@ +/* process.c — UTSM 进程上下文管理 + * + * 进程表分配/释放、capability table 操作、crypto context 管理。 + */ + #include #include +#include +#include + +/* ---- 进程表 ---- */ +static utsm_process_context g_process_table[UTSM_MAX_PROCESSES]; +static u32 g_process_count; + +void utsm_process_init(void) { + log_info("[UTSM] process table init"); + for (u32 i = 0; i < UTSM_MAX_PROCESSES; i++) { + g_process_table[i].state = UTSM_PROC_UNUSED; + g_process_table[i].process_slot = i; + g_process_table[i].cap_table.count = 0; + g_process_table[i].cap_table.generation = 0; + } + g_process_count = 0; +} void utsm_process_create_test(utsm_process_context *process, u64 seed) { process->process_uuid = uuid_make_test(seed); process->crypto_epoch = 1; process->recovery_generation = drr_recovery_generation(); - process->capability_table_slot = 0; process->capability_count = 0; - process->flags = 0; + process->flags = UTSM_PROC_F_KERNEL; + process->state = UTSM_PROC_ACTIVE; + process->hot_segment_slot = 0; + process->drr_recovery_gen = drr_recovery_generation(); + + /* 初始化 crypto context */ + process->crypto_ctx.crypto_epoch = 1; + process->crypto_ctx.key_derivation_seed = seed ^ 0x9e3779b97f4a7c15ULL; + process->crypto_ctx.pckc_hot_hint = 0; + + /* 初始化 capability table */ + process->cap_table.count = 0; + process->cap_table.generation = 1; +} + +utsm_process_context *utsm_process_alloc(void) { + for (u32 i = 0; i < UTSM_MAX_PROCESSES; i++) { + if (g_process_table[i].state == UTSM_PROC_UNUSED) { + utsm_process_context *p = &g_process_table[i]; + p->process_slot = i; + p->state = UTSM_PROC_ACTIVE; + p->crypto_epoch = 1; + p->recovery_generation = drr_recovery_generation(); + p->capability_count = 0; + p->flags = 0; + p->hot_segment_slot = 0; + p->drr_recovery_gen = drr_recovery_generation(); + p->cap_table.count = 0; + p->cap_table.generation = 1; + p->crypto_ctx.crypto_epoch = 1; + p->crypto_ctx.key_derivation_seed = 0; + p->crypto_ctx.pckc_hot_hint = 0; + g_process_count++; + return p; + } + } + return NULL; +} + +void utsm_process_free(utsm_process_context *process) { + if (!process) return; + process->state = UTSM_PROC_UNUSED; + process->cap_table.count = 0; + process->capability_count = 0; + if (g_process_count > 0) g_process_count--; +} + +utsm_process_context *utsm_process_get(u32 slot) { + if (slot >= UTSM_MAX_PROCESSES) return NULL; + return &g_process_table[slot]; +} + +/* ---- Capability 操作 ---- */ +int utsm_process_grant_cap(utsm_process_context *process, utsm_capability cap) { + if (!process || process->cap_table.count >= UTSM_MAX_CAPABILITIES) { + return -1; + } + u32 idx = process->cap_table.count; + process->cap_table.caps[idx] = cap; + process->cap_table.count++; + process->capability_count = process->cap_table.count; + process->cap_table.generation++; + return 0; +} + +int utsm_process_revoke_cap(utsm_process_context *process, u32 cap_index) { + if (!process || cap_index >= process->cap_table.count) { + return -1; + } + /* 将最后一个 capability 移到被删除的位置 */ + u32 last = process->cap_table.count - 1; + if (cap_index != last) { + process->cap_table.caps[cap_index] = process->cap_table.caps[last]; + } + process->cap_table.count--; + process->capability_count = process->cap_table.count; + process->cap_table.generation++; + return 0; +} + +utsm_capability *utsm_process_get_cap(utsm_process_context *process, u32 cap_index) { + if (!process || cap_index >= process->cap_table.count) { + return NULL; + } + return &process->cap_table.caps[cap_index]; +} + +/* ---- Crypto context 操作 ---- */ +void utsm_process_advance_epoch(utsm_process_context *process) { + if (!process) return; + process->crypto_epoch++; + process->crypto_ctx.crypto_epoch = process->crypto_epoch; + /* 清空 PCKC 热提示(epoch 变化后旧缓存无效) */ + process->crypto_ctx.pckc_hot_hint = 0; + process->hot_segment_slot = 0; } diff --git a/CODE/UTSM/drr/drr_stub.c b/CODE/UTSM/drr/drr_stub.c index f1860d1..60b721b 100644 --- a/CODE/UTSM/drr/drr_stub.c +++ b/CODE/UTSM/drr/drr_stub.c @@ -1,37 +1,316 @@ +/* drr_core.c — DRR Dedicated Recovery Root 核心实现 + * + * 替换原 drr_stub.c,提供完整的: + * - Emergency Pool(独立于普通堆分配器) + * - Recovery Log(write intent/commit 追踪) + * - Checkpoint A/B 双槽(CRC 校验、原子 active slot 切换) + * - Root key 管理 + * - Heartbeat + */ + #include #include -static const u64 g_root_key[4] = { - 0x4452525554534d31ULL, - 0xfeedfacecafebeefULL, - 0x0123456789abcdefULL, - 0xfedcba9876543210ULL +/* ---- 内部内存操作(不依赖 libc) ---- */ +static void drr_memzero(void *dst, u64 n) { + u8 *d = (u8 *)dst; + while (n--) *d++ = 0; +} + +static void drr_memcpy(void *dst, const void *src, u64 n) { + u8 *d = (u8 *)dst; + const u8 *s = (const u8 *)src; + while (n--) *d++ = *s++; +} + +/* ---- CRC64-ECMA ---- */ +static const u64 crc64_table[256] = { + 0x0000000000000000ULL, 0x42F0E1EBA5EA001AULL, 0x85E1C3D753D46D34ULL, 0xC711223CF63E6D2EULL, + 0x493366450E42ECDFULL, 0x0BC387AEABA8ECC5ULL, 0xCCD2A5925D9681EBULL, 0x8E224479F87C81F1ULL, + 0x9266CC8A1C85D9BEULL, 0xD0962D61B96FD9A4ULL, 0x17870F5D4F51B48AULL, 0x5577EEB6EABBB490ULL, + 0xDB55AACF12C73561ULL, 0x99A54B24B72D357BULL, 0x5EB4691841135855ULL, 0x1C4488F3E4F9584FULL, + 0x2644C6D0383BF54AULL, 0x64B4273B9DD1F550ULL, 0xA3A505076BEF987EULL, 0xE155E4ECCE059864ULL, + 0x6F77A09536791995ULL, 0x2D87417E9393198FULL, 0xEA96634265AD74A1ULL, 0xA86682A9C04774BBULL, + 0xB4220A5A24BE2CF4ULL, 0xF6D2EBB181542CEEULL, 0x31C3C98D776A41C0ULL, 0x73332866D28041DAULL, + 0xFD116C1F2AFCC02BULL, 0xBFE18DF48F16C031ULL, 0x78F0AFC87928AD1FULL, 0x3A004E23DCC2AD05ULL, + 0x4C898D607077A834ULL, 0x0E796C8BD19DA82EULL, 0xC9684EB723A3C500ULL, 0x8B98AF5C8649C51AULL, + 0x05BAEB257E3544EBULL, 0x474A0ACEDBDF44F1ULL, 0x805B28F22DE129DFULL, 0xC2ABC919880B29C5ULL, + 0xDEEF41EA6CF2718AULL, 0x9C1FA001C9187190ULL, 0x5B0E823D3F261CBEULL, 0x19FE63D69ACC1CA4ULL, + 0x97DC27AF62B09D55ULL, 0xD52CC644C75A9D4FULL, 0x123DE4783164F061ULL, 0x50CD0593948EF07BULL, + 0x6ACD4BB0484C5D7EULL, 0x283DAA5BECA65D64ULL, 0xEF2C88671B98304AULL, 0xADDC698CBE723050ULL, + 0x23FE2DF5460EB1A1ULL, 0x610ECC1EE3E4B1BBULL, 0xA61FEE2215DADC95ULL, 0xE4EF0FC9B030DC8FULL, + 0xF8AB873A54C984C0ULL, 0xBA5B66D1F12384DAULL, 0x7D4A44ED071DE9F4ULL, 0x3FBAA506A2F7E9EEULL, + 0xB198E17F5A8B681FULL, 0xF3680094FF616805ULL, 0x347922A8095F052BULL, 0x7689C343ACB50531ULL, + 0x98131AE0E0ED2F68ULL, 0xDAE3FB0B45072F72ULL, 0x1DF2D937B339425CULL, 0x5F0238DC16D34246ULL, + 0xD1207CA5EEAFC3B7ULL, 0x93D09D4E4B45C3ADULL, 0x54C1B172BD7BAE83ULL, 0x163150991891AE99ULL, + 0x0A75D66AFC68F6D6ULL, 0x488537815982F6CCULL, 0x8F9415BDAFBC9BE2ULL, 0xCD64F4560A569BF8ULL, + 0x4346B02FF22A1A09ULL, 0x01B651C457C01A13ULL, 0xC6A773F8A1FE773DULL, 0x8457921304147727ULL, + 0xBE57DC30D8D6DA22ULL, 0xFCA73DDB7D3CDA38ULL, 0x3BB61FE78B02B716ULL, 0x7946FE0C2EE8B70CULL, + 0xF764BA75D69436FDULL, 0xB5945B9E737E36E7ULL, 0x728579A285405BC9ULL, 0x3075984920AA5BD3ULL, + 0x2C3110BAC453039CULL, 0x6EC1F15161B90386ULL, 0xA9D0D36D97876EA8ULL, 0xEB203286326D6EB2ULL, + 0x650276FFCA11EF43ULL, 0x27F297146FFBEF59ULL, 0xE0E3B52899C58277ULL, 0xA21354C33C2F826DULL, + 0xD49A9780909A875CULL, 0x966A766B35708746ULL, 0x517B5457C34EEA68ULL, 0x138BB5BC66A4EA72ULL, + 0x9DA9F1C59ED86B83ULL, 0xDF59102E3B326B99ULL, 0x18483212CD0C06B7ULL, 0x5AB8D3F968E606ADULL, + 0x46FC5B0A8C1F5EE2ULL, 0x040CBA4129F55EF8ULL, 0xC31D98DDDFCB33D6ULL, 0x81ED79367A2133CCULL, + 0x0FCF3D4F825DB23DULL, 0x4D3FDCA427B7B227ULL, 0x8A2EFE98D189DF09ULL, 0xC8DE1F737463DF13ULL, + 0xF2DE5150A8A17216ULL, 0xB02EB0BB0D4B720CULL, 0x773F9287FB751F22ULL, 0x35CF736C5E9F1F38ULL, + 0xBBED3715A6E39EC9ULL, 0xF91DD6FE03099ED3ULL, 0x3E0CF4C2F537F3FDULL, 0x7CFC152950DDF3E7ULL, + 0x60B89DDAB424ABA8ULL, 0x22487C3111CEABB2ULL, 0xE5595E0DE7F0C69CULL, 0xA7A9BFE6421AC686ULL, + 0x298BFB9FBA664777ULL, 0x6B7B1A741F8C476DULL, 0xAC6A3848E9B22A43ULL, 0xEE9AD9A34C582A59ULL, }; -void drr_stub_init(void) { +u64 drr_crc64(const void *data, u64 len) { + const u8 *p = (const u8 *)data; + u64 crc = 0xFFFFFFFFFFFFFFFFULL; + for (u64 i = 0; i < len; i++) { + crc = crc64_table[(crc ^ p[i]) & 0xFF] ^ (crc >> 8); + } + return crc ^ 0xFFFFFFFFFFFFFFFFULL; +} + +/* ---- 全局 DRR 状态 ---- */ +static drr_state g_drr; + +/* ---- Root key(最终应从安全启动链获取) ---- */ +static const u64 g_root_key[4] = { + 0x4452525554534D31ULL, /* "DRRUTSM1" */ + 0xFEEDFACECAFEF00DULL, + 0x0123456789ABCDEFULL, + 0xFEDCBA9876543210ULL +}; + +/* ---- Emergency Pool 内部分配 ---- */ +static void *pool_alloc_internal(u64 size, u64 alignment) { + u64 current = (u64)&g_drr.pool[0] + g_drr.pool_offset; + u64 aligned = utsm_align_up_u64(current, alignment); + u64 new_offset = (aligned - (u64)&g_drr.pool[0]) + size; + if (new_offset > g_drr.pool_limit) { + return NULL; + } + g_drr.pool_offset = new_offset; + return (void *)aligned; +} + +void *drr_emergency_alloc(u64 size, u64 alignment) { + if (!g_drr.initialized) return NULL; + return pool_alloc_internal(size, alignment); +} + +void drr_emergency_reset(void) { + if (!g_drr.initialized) return; + /* 保留已分配的子缓冲区,仅重置池偏移到子缓冲区之后 */ + /* 注意:此函数仅在 recovery 路径紧急情况下使用 */ + g_drr.pool_offset = 0; + /* 重新分配子缓冲区 */ + drr_init(); +} + +/* ---- 初始化 ---- */ +void drr_init(void) { + log_info("[DRR] init begin"); + drr_memzero(&g_drr, sizeof(g_drr)); + + g_drr.pool_offset = 0; + g_drr.pool_limit = DRR_EMERGENCY_POOL_SIZE; + g_drr.root_key = g_root_key; + g_drr.recovery_generation = 1; + g_drr.heartbeat_interval = 1000; + g_drr.active_slot = 0; /* A */ + + /* 分配 Emergency Pool 子缓冲区 */ + g_drr.stack_base = (u8 *)pool_alloc_internal(DRR_EMERGENCY_STACK_SIZE, 64); + g_drr.stack_size = DRR_EMERGENCY_STACK_SIZE; + g_drr.stack_ptr = DRR_EMERGENCY_STACK_SIZE; /* 栈从顶部向下增长 */ + + g_drr.log_base = (drr_log_entry *)pool_alloc_internal(DRR_RECOVERY_LOG_SIZE, 64); + g_drr.log_capacity = DRR_RECOVERY_LOG_SIZE / sizeof(drr_log_entry); + g_drr.log_count = 0; + + g_drr.ckpt_meta_buf = (drr_checkpoint_meta *)pool_alloc_internal(DRR_CKPT_META_BUF_SIZE, 64); + g_drr.crash_buf = (u8 *)pool_alloc_internal(DRR_CRASH_BUF_SIZE, 64); + g_drr.crash_buf_size = DRR_CRASH_BUF_SIZE; + g_drr.meta_slab = pool_alloc_internal(DRR_META_SLAB_SIZE, 64); + g_drr.meta_slab_offset = 0; + + /* 初始化 A/B slot */ + drr_memzero(&g_drr.ckpt_a, sizeof(drr_checkpoint_meta)); + drr_memzero(&g_drr.ckpt_b, sizeof(drr_checkpoint_meta)); + g_drr.ckpt_a.magic = DRR_CKPT_MAGIC; + g_drr.ckpt_a.version = DRR_CKPT_VERSION; + g_drr.ckpt_a.active = 1; + g_drr.ckpt_b.magic = DRR_CKPT_MAGIC; + g_drr.ckpt_b.version = DRR_CKPT_VERSION; + + g_drr.initialized = 1; + log_info("[DRR] init ok"); } const u64 *drr_get_root_key(void) { - return g_root_key; + return g_drr.root_key; } u64 drr_recovery_generation(void) { - return 1; + return g_drr.recovery_generation; } +/* ---- Fault 报告 ---- */ void drr_report_fault(const char *reason) { - log_error("[DRR] fault reported"); + if (!g_drr.initialized) return; + log_error("[DRR] FAULT: "); log_error(reason); + /* 记录到 crash buffer */ + if (g_drr.crash_buf && g_drr.crash_buf_size > 0) { + /* 简单记录:写入 reason 的前 N 字节 */ + u64 i = 0; + const char *s = reason; + while (*s && i < g_drr.crash_buf_size - 1) { + g_drr.crash_buf[i++] = (u8)*s++; + } + g_drr.crash_buf[i] = 0; + } + /* 增加 recovery generation */ + g_drr.recovery_generation++; } +/* ---- Recovery Log ---- */ void drr_log_write_intent(u32 segment_slot, u64 offset, u64 len) { - UTSM_UNUSED(segment_slot); - UTSM_UNUSED(offset); - UTSM_UNUSED(len); + if (!g_drr.initialized || !g_drr.log_base) return; + if (g_drr.log_count >= g_drr.log_capacity) { + /* 日志满,最简单的策略:丢弃最早的条目 */ + for (u32 i = 1; i < g_drr.log_count; i++) { + g_drr.log_base[i - 1] = g_drr.log_base[i]; + } + g_drr.log_count--; + } + drr_log_entry *e = &g_drr.log_base[g_drr.log_count++]; + e->segment_slot = segment_slot; + e->flags = DRR_LOG_FLAG_INTENT; + e->offset = offset; + e->length = len; + e->timestamp = g_drr.heartbeat_counter; } void drr_log_write_commit(u32 segment_slot, u64 offset, u64 len) { - UTSM_UNUSED(segment_slot); - UTSM_UNUSED(offset); - UTSM_UNUSED(len); + if (!g_drr.initialized || !g_drr.log_base) return; + if (g_drr.log_count >= g_drr.log_capacity) { + for (u32 i = 1; i < g_drr.log_count; i++) { + g_drr.log_base[i - 1] = g_drr.log_base[i]; + } + g_drr.log_count--; + } + drr_log_entry *e = &g_drr.log_base[g_drr.log_count++]; + e->segment_slot = segment_slot; + e->flags = DRR_LOG_FLAG_COMMIT; + e->offset = offset; + e->length = len; + e->timestamp = g_drr.heartbeat_counter; +} + +/* ---- Checkpoint ---- */ +int drr_checkpoint_begin(drr_checkpoint_type type) { + if (!g_drr.initialized) return -1; + + /* 选择 inactive slot */ + drr_checkpoint_meta *target; + if (g_drr.active_slot == 0) { + target = &g_drr.ckpt_b; + } else { + target = &g_drr.ckpt_a; + } + + target->magic = DRR_CKPT_MAGIC; + target->version = DRR_CKPT_VERSION; + target->active = 0; /* 写入时为 inactive */ + target->type = type; + target->global_epoch = g_drr.recovery_generation; + target->dirty_page_count = 0; + target->mac_root = 0; + target->segment_count = 0; + target->crc = 0; + + return 0; +} + +int drr_checkpoint_finish(void) { + if (!g_drr.initialized) return -1; + + /* 写入 inactive slot 的 CRC */ + drr_checkpoint_meta *target; + if (g_drr.active_slot == 0) { + target = &g_drr.ckpt_b; + } else { + target = &g_drr.ckpt_a; + } + + /* 计算 CRC(不含 crc 字段本身) */ + target->crc = drr_crc64(target, + sizeof(drr_checkpoint_meta) - sizeof(u64)); + + /* 原子切换 active slot */ + target->active = 1; + /* 旧 slot 标记 inactive */ + if (g_drr.active_slot == 0) { + g_drr.ckpt_a.active = 0; + g_drr.active_slot = 1; + } else { + g_drr.ckpt_b.active = 0; + g_drr.active_slot = 0; + } + + /* 增加 recovery generation */ + g_drr.recovery_generation++; + + /* 清空 recovery log */ + g_drr.log_count = 0; + + return 0; +} + +int drr_checkpoint_recover(drr_checkpoint_meta *out_meta) { + if (!g_drr.initialized || !out_meta) return -1; + + /* 尝试 active slot */ + drr_checkpoint_meta *active = (g_drr.active_slot == 0) ? + &g_drr.ckpt_a : &g_drr.ckpt_b; + drr_checkpoint_meta *fallback = (g_drr.active_slot == 0) ? + &g_drr.ckpt_b : &g_drr.ckpt_a; + + /* 验证 active slot */ + if (active->magic == DRR_CKPT_MAGIC && active->active == 1) { + u64 expected_crc = active->crc; + active->crc = 0; + u64 actual_crc = drr_crc64(active, + sizeof(drr_checkpoint_meta) - sizeof(u64)); + active->crc = expected_crc; + if (actual_crc == expected_crc) { + drr_memcpy(out_meta, active, sizeof(drr_checkpoint_meta)); + return 0; + } + } + + /* active 无效,尝试 fallback */ + if (fallback->magic == DRR_CKPT_MAGIC && fallback->active == 1) { + u64 expected_crc = fallback->crc; + fallback->crc = 0; + u64 actual_crc = drr_crc64(fallback, + sizeof(drr_checkpoint_meta) - sizeof(u64)); + fallback->crc = expected_crc; + if (actual_crc == expected_crc) { + drr_memcpy(out_meta, fallback, sizeof(drr_checkpoint_meta)); + return 0; + } + } + + /* 双槽都无效 */ + return -2; +} + +/* ---- Heartbeat ---- */ +void drr_heartbeat_tick(void) { + if (!g_drr.initialized) return; + g_drr.heartbeat_counter++; +} + +u64 drr_heartbeat_get(void) { + return g_drr.heartbeat_counter; } diff --git a/CODE/UTSM/include/utsm/deshab_ui.h b/CODE/UTSM/include/utsm/deshab_ui.h new file mode 100644 index 0000000..ba60b97 --- /dev/null +++ b/CODE/UTSM/include/utsm/deshab_ui.h @@ -0,0 +1,708 @@ +/* deshab_ui.h — Deshab 视觉风格系统 + * + * "Sealed Arc" 设计语言:以 UTSM 封缄内存为灵感的统一视觉体系。 + * + * 核心设计隐喻: + * Arc(弧) — 封缄/解封的视觉表达,圆角、弧形边框、弧线动画 + * Seal(封) — 密封容器的安全感,卡片容器、发光边缘、透明覆盖 + * Flow(流) — 数据流的连续性,渐变背景、平滑过渡、流式布局 + * + * 设计令牌分层: + * L0 — 原始色值(Deshab Palette) + * L1 — 语义色值(Semantic Colors,引用 L0) + * L2 — 组件令牌(Component Tokens,引用 L1) + * + * 所有 UI 组件必须通过本头文件获取视觉参数,禁止硬编码颜色/尺寸。 + */ + +#ifndef DESHAB_UI_H +#define DESHAB_UI_H + +/* =================================================================== + * 基础类型(自包含,不依赖 utsm/types.h 以便 DSK/Shell 独立使用) + * =================================================================== */ +typedef unsigned char du_u8; +typedef unsigned short du_u16; +typedef unsigned int du_u32; +typedef unsigned long long du_u64; +typedef long long du_i64; + +/* =================================================================== + * L0 — Deshab 原始色板 (Deshab Palette) + * + * 色彩命名规则:{色调}_{明度} + * 格式:0xAARRGGBB(与 framebuffer 一致,小端 x86 上 BB 置低位) + * =================================================================== */ + +/* --- 主色调:深海蓝 (Abyss Blue) --- */ +#define DP_ABYSS_900 0xFF0A1428u /* 最深:终端背景、底色 */ +#define DP_ABYSS_800 0xFF0F1E38u /* 深层:暗卡片背景 */ +#define DP_ABYSS_700 0xFF162848u /* 暗面:悬停态背景 */ +#define DP_ABYSS_600 0xFF1E3868u /* 中暗:输入框背景 */ +#define DP_ABYSS_500 0xFF284888u /* 中间:次级强调 */ +#define DP_ABYSS_400 0xFF3868B0u /* 中亮:默认强调 */ +#define DP_ABYSS_300 0xFF5088D0u /* 亮面:链接色 */ +#define DP_ABYSS_200 0xFF78A8E8u /* 高亮:焦点指示 */ +#define DP_ABYSS_100 0xFFA8CCF4u /* 最亮:浅色辅助 */ + +/* --- 强调色:封缄青 (Seal Cyan) — 主要操作/交互 --- */ +#define DP_SEAL_900 0xFF004858u +#define DP_SEAL_700 0xFF007898u +#define DP_SEAL_500 0xFF00A8CCu /* 标准强调:按钮、选中态 */ +#define DP_SEAL_300 0xFF44CCF0u /* 高亮强调:焦点环 */ +#define DP_SEAL_100 0xFF99E8FFu /* 亮强调:发光边缘 */ + +/* --- 辅助色:弧光紫 (Arc Violet) — 次级/装饰 --- */ +#define DP_ARC_900 0xFF2A1040u +#define DP_ARC_700 0xFF5A2080u +#define DP_ARC_500 0xFF8844CCu /* 装饰强调:渐变终点、图标 */ +#define DP_ARC_300 0xFFB888E8u +#define DP_ARC_100 0xFFE0C8F8u + +/* --- 中性色 (Neutral) --- */ +#define DP_NEUTRAL_0 0xFF000000u /* 纯黑 */ +#define DP_NEUTRAL_50 0xFF181820u /* 接近黑 */ +#define DP_NEUTRAL_100 0xFF2A2A38u /* 暗灰 */ +#define DP_NEUTRAL_200 0xFF484860u /* 中暗灰 */ +#define DP_NEUTRAL_300 0xFF6A6A88u /* 中灰 */ +#define DP_NEUTRAL_400 0xFF9090A8u /* 中亮灰 */ +#define DP_NEUTRAL_500 0xFFB0B0C0u /* 标准灰 */ +#define DP_NEUTRAL_600 0xFFD0D0DCu /* 亮灰 */ +#define DP_NEUTRAL_700 0xFFE8E8F0u /* 浅灰 */ +#define DP_NEUTRAL_800 0xFFF0F0F8u /* 接近白 */ +#define DP_NEUTRAL_900 0xFFFFFFFFu /* 纯白 */ + +/* --- 语义辅助色 --- */ +#define DP_SUCCESS 0xFF40C880u /* 成功:绿色 */ +#define DP_WARNING 0xFFF0A030u /* 警告:琥珀色 */ +#define DP_ERROR 0xFFFF4466u /* 错误:红色 */ +#define DP_INFO 0xFF4499FFu /* 信息:蓝色 */ + +/* --- 渐变色 — DSK 加载器 / FirstInit 背景专用 --- */ +#define DP_GRAD_TOP 0xFFC8E0F0u /* 淡蓝顶 */ +#define DP_GRAD_MID 0xFF8888B0u /* 紫灰中 */ +#define DP_GRAD_BOT 0xFF49306Fu /* 深紫底 */ + +/* --- 欢迎页专用 --- */ +#define DP_WELCOME_FG 0xFFE8F2FCu /* 近白浅蓝(欢迎文字) */ + +/* =================================================================== + * L1 — 语义色值 (Semantic Colors) + * + * 暗色主题(Dark Theme)— Shell / 终端 + * 亮色主题(Light Theme)— FirstInit / DSK + * =================================================================== */ + +/* --- 暗色主题 --- */ +#define DS_DARK_BG_PRIMARY DP_ABYSS_900 /* 主背景 */ +#define DS_DARK_BG_SECONDARY DP_ABYSS_800 /* 卡片/面板背景 */ +#define DS_DARK_BG_TERTIARY DP_ABYSS_700 /* 悬停/输入框 */ +#define DS_DARK_BG_INSET DP_ABYSS_600 /* 凹陷区域 */ + +#define DS_DARK_TEXT_PRIMARY DP_NEUTRAL_700 /* 主文字 */ +#define DS_DARK_TEXT_SECONDARY DP_NEUTRAL_500 /* 次级文字 */ +#define DS_DARK_TEXT_DIM DP_NEUTRAL_300 /* 注释/禁用 */ +#define DS_DARK_TEXT_INVERT DP_ABYSS_900 /* 反色文字(亮背景上) */ + +#define DS_DARK_ACCENT DP_SEAL_500 /* 主强调 */ +#define DS_DARK_ACCENT_LIGHT DP_SEAL_300 /* 高亮强调 */ +#define DS_DARK_ACCENT_GLOW DP_SEAL_100 /* 发光 */ +#define DS_DARK_DECORATIVE DP_ARC_500 /* 装饰色 */ +#define DS_DARK_DECORATIVE_L DP_ARC_300 /* 浅装饰 */ + +#define DS_DARK_BORDER DP_ABYSS_500 /* 边框 */ +#define DS_DARK_BORDER_FOCUS DP_SEAL_300 /* 焦点边框 */ +#define DS_DARK_DIVIDER DP_ABYSS_600 /* 分隔线 */ + +#define DS_DARK_CURSOR DP_SEAL_500 /* 光标 */ +#define DS_DARK_PROMPT DP_SEAL_300 /* 提示符 */ +#define DS_DARK_SELECTION_BG DP_SEAL_900 /* 选中背景 */ + +/* --- 亮色主题 --- */ +#define DS_LIGHT_BG_PRIMARY DP_NEUTRAL_900 /* 主背景(白) */ +#define DS_LIGHT_BG_SECONDARY DP_NEUTRAL_700 /* 卡片背景 */ +#define DS_LIGHT_BG_TERTIARY DP_NEUTRAL_600 /* 悬停 */ +#define DS_LIGHT_BG_INSET DP_NEUTRAL_500 /* 输入框 */ + +#define DS_LIGHT_TEXT_PRIMARY DP_ABYSS_900 /* 主文字 */ +#define DS_LIGHT_TEXT_SECONDARY DP_ABYSS_500 /* 次级 */ +#define DS_LIGHT_TEXT_DIM DP_NEUTRAL_300 /* 注释 */ +#define DS_LIGHT_TEXT_INVERT DP_NEUTRAL_900 /* 反色 */ + +#define DS_LIGHT_ACCENT DP_SEAL_700 /* 主强调(亮色上更深) */ +#define DS_LIGHT_ACCENT_LIGHT DP_SEAL_500 +#define DS_LIGHT_DECORATIVE DP_ARC_700 + +#define DS_LIGHT_BORDER DP_NEUTRAL_400 +#define DS_LIGHT_BORDER_FOCUS DP_SEAL_500 + +/* =================================================================== + * 排版规范 (Typography) + * =================================================================== */ + +/* 字体尺寸族 */ +#define DU_FONT_SIZE_XS 10u /* 辅助标注 */ +#define DU_FONT_SIZE_SM 14u /* 次要说明 */ +#define DU_FONT_SIZE_MD 18u /* 正文(Consolas 18px) */ +#define DU_FONT_SIZE_LG 24u /* 小标题 */ +#define DU_FONT_SIZE_XL 32u /* 标题 */ +#define DU_FONT_SIZE_2XL 48u /* 大标题 / 欢迎文字 */ + +/* ASCII 位图字体参数(与 ascii_bitmaps.c 对应) */ +#define DU_ASCII_CELL_W 11u +#define DU_ASCII_CELL_H 18u +#define DU_ASCII_STEP 12u /* 字符间距(含 1px gap) */ +#define DU_ASCII_LINE_H 20u /* 行高(含 2px 行间距) */ + +/* =================================================================== + * 间距体系 (Spacing) + * =================================================================== */ + +#define DU_SPACE_XXS 2u +#define DU_SPACE_XS 4u +#define DU_SPACE_SM 8u +#define DU_SPACE_MD 16u +#define DU_SPACE_LG 24u +#define DU_SPACE_XL 32u +#define DU_SPACE_2XL 48u +#define DU_SPACE_3XL 64u + +/* =================================================================== + * 圆角体系 (Border Radius) — "Sealed Arc" 签名元素 + * =================================================================== */ + +#define DU_RADIUS_NONE 0u +#define DU_RADIUS_SM 4u /* 小按钮、标签 */ +#define DU_RADIUS_MD 8u /* 卡片、输入框 */ +#define DU_RADIUS_LG 12u /* 大卡片、面板 */ +#define DU_RADIUS_XL 16u /* 模态框 */ +#define DU_RADIUS_FULL 9999u /* 完全圆角(药丸形) */ + +/* =================================================================== + * 边框体系 (Border) + * =================================================================== */ + +#define DU_BORDER_THIN 1u +#define DU_BORDER_STD 2u +#define DU_BORDER_THICK 3u + +/* =================================================================== + * 动画参数 (Animation) + * =================================================================== */ + +/* 帧延迟(busy-wait 循环次数 ≈ 毫秒级,需根据 CPU 速度校准) */ +#define DU_ANIM_DELAY_SLOW 200000u /* 慢速过渡 */ +#define DU_ANIM_DELAY_NORMAL 100000u /* 标准动画 */ +#define DU_ANIM_DELAY_FAST 40000u /* 快速反馈 */ + +/* 弧线动画参数(DSK spinner 专用) */ +#define DU_SPINNER_RADIUS 48u +#define DU_SPINNER_THICKNESS 4u +#define DU_SPINNER_ARC_GAP 82u /* 每弧宽度(度) */ +#define DU_SPINNER_ARC_COUNT 4u +#define DU_SPINNER_SPEED 2u /* 每帧旋转度数 */ +#define DU_SPINNER_SPRITE 128u /* 精灵缓冲边长 */ + +/* 渐变动画步进 */ +#define DU_FADE_STEPS 32u /* 淡入/淡出步数 */ +#define DU_FADE_STEP_DELAY 8000u /* 每步延迟 */ + +/* =================================================================== + * 组件尺寸 (Component Sizes) + * =================================================================== */ + +#define DU_BUTTON_HEIGHT 36u +#define DU_INPUT_HEIGHT 32u +#define DU_CARD_PADDING_X 24u +#define DU_CARD_PADDING_Y 16u +#define DU_CARD_MIN_WIDTH 200u +#define DU_CARD_MAX_WIDTH 480u +#define DU_CURSOR_SIZE 24u /* 鼠标光标边长 */ +#define DU_NOTIFICATION_H 40u + +/* =================================================================== + * 渲染上下文 (Render Context) + * + * 所有绘制函数通过此上下文访问 framebuffer,避免全局状态。 + * =================================================================== */ + +typedef struct { + du_u32 *fb; /* framebuffer 基址 */ + du_u64 width; /* 像素宽 */ + du_u64 height; /* 像素高 */ + du_u64 pitch; /* 字节行宽 */ +} du_context; + +/* 初始化渲染上下文 */ +static inline void du_context_init(du_context *ctx, du_u64 fb_addr, + du_u64 w, du_u64 h, du_u64 pitch) { + ctx->fb = (du_u32 *)(du_u64)fb_addr; + ctx->width = w; + ctx->height = h; + ctx->pitch = pitch; +} + +/* =================================================================== + * L2 — 绘制原语 (Rendering Primitives) + * =================================================================== */ + +/* 颜色混合:alpha 范围 0–255 */ +static inline du_u32 du_blend(du_u32 bg, du_u32 fg, du_u32 alpha) { + du_u32 na = 256 - alpha; + du_u32 r = ((bg & 0xFF) * na + (fg & 0xFF) * alpha) >> 8; + du_u32 g = (((bg >> 8) & 0xFF) * na + ((fg >> 8) & 0xFF) * alpha) >> 8; + du_u32 b = (((bg >> 16) & 0xFF) * na + ((fg >> 16) & 0xFF) * alpha) >> 8; + return 0xFF000000u | (b << 16) | (g << 8) | r; +} + +/* 线性插值两色 */ +static inline du_u32 du_lerp_color(du_u32 c1, du_u32 c2, du_u32 t) { + return du_blend(c1, c2, t); +} + +/* 渐变背景:顶色 → 底色,按 Y 坐标插值 */ +static inline du_u32 du_gradient_v(du_u32 top_color, du_u32 bot_color, + du_i64 y, du_i64 total_h) { + if (total_h <= 1) return top_color; + if (y < 0) y = 0; + if (y >= total_h) y = total_h - 1; + du_u32 alpha = (du_u32)(((du_u64)y * 255ULL) / ((du_u64)total_h - 1)); + return du_blend(top_color, bot_color, alpha); +} + +/* 单像素写(带边界检查) */ +static inline void du_pixel(du_context *ctx, du_i64 x, du_i64 y, du_u32 color) { + if (x < 0 || (du_u64)x >= ctx->width || y < 0 || (du_u64)y >= ctx->height) return; + ((du_u32 *)((du_u8 *)ctx->fb + (du_u64)y * ctx->pitch))[(du_u64)x] = color; +} + +/* 读取像素(带边界检查) */ +static inline du_u32 du_pixel_read(du_context *ctx, du_i64 x, du_i64 y) { + if (x < 0 || (du_u64)x >= ctx->width || y < 0 || (du_u64)y >= ctx->height) return 0; + return ((du_u32 *)((du_u8 *)ctx->fb + (du_u64)y * ctx->pitch))[(du_u64)x]; +} + +/* 矩形填充 */ +static inline void du_fill_rect(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, du_i64 h, du_u32 color) { + for (du_i64 r = 0; r < h; r++) { + du_i64 yy = y + r; + if (yy < 0 || (du_u64)yy >= ctx->height) continue; + du_u32 *line = (du_u32 *)((du_u8 *)ctx->fb + (du_u64)yy * ctx->pitch); + for (du_i64 c = 0; c < w; c++) { + du_i64 xx = x + c; + if (xx < 0 || (du_u64)xx >= ctx->width) continue; + line[(du_u64)xx] = color; + } + } +} + +/* 渐变矩形填充(垂直渐变) */ +static inline void du_fill_rect_gradient(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, du_i64 h, + du_u32 top_color, du_u32 bot_color) { + for (du_i64 r = 0; r < h; r++) { + du_i64 yy = y + r; + if (yy < 0 || (du_u64)yy >= ctx->height) continue; + du_u32 color = du_gradient_v(top_color, bot_color, r, h); + du_u32 *line = (du_u32 *)((du_u8 *)ctx->fb + (du_u64)yy * ctx->pitch); + for (du_i64 c = 0; c < w; c++) { + du_i64 xx = x + c; + if (xx < 0 || (du_u64)xx >= ctx->width) continue; + line[(du_u64)xx] = color; + } + } +} + +/* 全屏渐变背景 */ +static inline void du_fill_bg_gradient(du_context *ctx, + du_u32 top_color, du_u32 bot_color) { + du_fill_rect_gradient(ctx, 0, 0, (du_i64)ctx->width, (du_i64)ctx->height, + top_color, bot_color); +} + +/* 全屏纯色背景 */ +static inline void du_fill_bg_solid(du_context *ctx, du_u32 color) { + du_fill_rect(ctx, 0, 0, (du_i64)ctx->width, (du_i64)ctx->height, color); +} + +/* =================================================================== + * 圆角矩形 (Rounded Rectangle) — "Sealed Arc" 核心形状 + * =================================================================== */ + +/* 绘制圆角矩形边框(单像素宽) */ +static inline void du_rect_outline(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, du_i64 h, + du_u32 color, du_u32 radius) { + /* 四个角的圆弧 + 四条直线 */ + du_i64 r = (du_i64)radius; + if (r > w / 2) r = w / 2; + if (r > h / 2) r = h / 2; + + /* 顶线 */ + for (du_i64 c = r; c < w - r; c++) { + du_pixel(ctx, x + c, y, color); + } + /* 底线 */ + for (du_i64 c = r; c < w - r; c++) { + du_pixel(ctx, x + c, y + h - 1, color); + } + /* 左线 */ + for (du_i64 rr = r; rr < h - r; rr++) { + du_pixel(ctx, x, y + rr, color); + } + /* 右线 */ + for (du_i64 rr = r; rr < h - r; rr++) { + du_pixel(ctx, x + w - 1, y + rr, color); + } + /* 四个圆角 */ + for (du_i64 dy = 0; dy <= r; dy++) { + for (du_i64 dx = 0; dx <= r; dx++) { + du_i64 d2 = dx * dx + dy * dy; + if (d2 <= r * r && d2 > (r - 1) * (r - 1)) { + /* 左上 */ + du_pixel(ctx, x + r - dx, y + r - dy, color); + /* 右上 */ + du_pixel(ctx, x + w - 1 - r + dx, y + r - dy, color); + /* 左下 */ + du_pixel(ctx, x + r - dx, y + h - 1 - r + dy, color); + /* 右下 */ + du_pixel(ctx, x + w - 1 - r + dx, y + h - 1 - r + dy, color); + } + } + } +} + +/* 填充圆角矩形 */ +static inline void du_fill_rounded_rect(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, du_i64 h, + du_u32 color, du_u32 radius) { + du_i64 r = (du_i64)radius; + if (r > w / 2) r = w / 2; + if (r > h / 2) r = h / 2; + + /* 中间区域(不含角) */ + du_fill_rect(ctx, x + r, y, w - 2 * r, h, color); + /* 左侧矩形 */ + du_fill_rect(ctx, x, y + r, r, h - 2 * r, color); + /* 右侧矩形 */ + du_fill_rect(ctx, x + w - r, y + r, r, h - 2 * r, color); + + /* 四个圆角填充 */ + for (du_i64 dy = 0; dy < r; dy++) { + for (du_i64 dx = 0; dx < r; dx++) { + if (dx * dx + dy * dy <= r * r) { + /* 左上 */ + du_pixel(ctx, x + r - 1 - dx, y + r - 1 - dy, color); + /* 右上 */ + du_pixel(ctx, x + w - r + dx, y + r - 1 - dy, color); + /* 左下 */ + du_pixel(ctx, x + r - 1 - dx, y + h - r + dy, color); + /* 右下 */ + du_pixel(ctx, x + w - r + dx, y + h - r + dy, color); + } + } + } +} + +/* 带发光效果的圆角矩形边框(模拟 UTSM 封缄发光) */ +static inline void du_rect_glow(du_context *ctx, du_i64 x, du_i64 y, + du_i64 w, du_i64 h, + du_u32 glow_color, du_u32 radius, + du_i64 glow_width) { + /* 从外到内逐层降低透明度 */ + for (du_i64 i = glow_width; i >= 1; i--) { + du_u32 alpha = (du_u32)(40 + (glow_width - i) * 50 / glow_width); + du_u32 color = du_blend(DP_ABYSS_900, glow_color, alpha); + du_rect_outline(ctx, x - i, y - i, w + 2 * i, h + 2 * i, color, radius + (du_u32)i); + } + /* 核心边框 */ + du_rect_outline(ctx, x, y, w, h, glow_color, radius); +} + +/* =================================================================== + * 分隔线 (Divider) + * =================================================================== */ + +static inline void du_divider_h(du_context *ctx, du_i64 x, du_i64 y, + du_i64 width, du_u32 color) { + du_fill_rect(ctx, x, y, width, 1, color); +} + +static inline void du_divider_v(du_context *ctx, du_i64 x, du_i64 y, + du_i64 height, du_u32 color) { + du_fill_rect(ctx, x, y, 1, height, color); +} + +/* =================================================================== + * ASCII 字符渲染(需要外部提供 g_ascii 位图数组) + * + * 使用方法:包含 ascii_bitmaps.c 后调用 du_draw_char / du_draw_string + * =================================================================== */ + +/* ASCII 位图数组 — 由 ascii_bitmaps.c 定义(static),在包含该 .c 的编译单元内可见 */ + +/* 渲染单个 ASCII 字符(灰度位图 + 前景色 alpha 混合) */ +static inline void du_draw_char(du_context *ctx, char ch, + du_i64 x, du_i64 y, + du_u32 fg, du_u32 bg) { + du_u32 idx = (du_u32)(ch - ' '); + if (idx > 94) idx = 0; + const du_u8 *glyph = g_ascii[idx]; + du_i64 gw = g_ascii_w; + du_i64 gh = g_ascii_h; + for (du_i64 r = 0; r < gh; r++) { + du_i64 yy = y + r; + if (yy < 0 || (du_u64)yy >= ctx->height) continue; + du_u32 *line = (du_u32 *)((du_u8 *)ctx->fb + (du_u64)yy * ctx->pitch); + for (du_i64 c = 0; c < gw; c++) { + du_u8 a = glyph[r * gw + c]; + if (a == 0) continue; + du_i64 xx = x + c; + if (xx < 0 || (du_u64)xx >= ctx->width) continue; + line[(du_u64)xx] = (a == 255) ? fg : du_blend(bg, fg, a); + } + } +} + +/* 渲染字符串(不换行,无自动折行) */ +static inline void du_draw_string(du_context *ctx, const char *s, + du_i64 x, du_i64 y, + du_u32 fg, du_u32 bg, + du_u32 step) { + du_i64 cx = x; + while (*s) { + du_draw_char(ctx, *s, cx, y, fg, bg); + cx += (du_i64)step; + s++; + } +} + +/* =================================================================== + * 组件令牌 — 按钮 (Button) + * =================================================================== */ + +typedef struct { + du_u32 bg_normal; /* 正常背景 */ + du_u32 bg_hover; /* 悬停背景 */ + du_u32 bg_active; /* 按下背景 */ + du_u32 fg; /* 文字色 */ + du_u32 border; /* 边框色 */ + du_u32 border_focus; /* 焦点边框色 */ + du_u32 radius; /* 圆角半径 */ + du_u32 height; /* 高度 */ + du_u32 padding_x; /* 水平内边距 */ +} du_button_style; + +/* 暗色主题按钮样式 */ +#define DU_BTN_DARK_PRIMARY { \ + DS_DARK_ACCENT, DS_DARK_ACCENT_LIGHT, DP_SEAL_700, \ + DP_NEUTRAL_900, DP_SEAL_300, DP_SEAL_100, \ + DU_RADIUS_MD, DU_BUTTON_HEIGHT, DU_SPACE_LG \ +} + +#define DU_BTN_DARK_SECONDARY { \ + DS_DARK_BG_TERTIARY, DS_DARK_BG_INSET, DS_DARK_BG_INSET, \ + DS_DARK_TEXT_PRIMARY, DS_DARK_BORDER, DS_DARK_BORDER_FOCUS, \ + DU_RADIUS_MD, DU_BUTTON_HEIGHT, DU_SPACE_LG \ +} + +/* =================================================================== + * 组件令牌 — 卡片 (Card) + * =================================================================== */ + +typedef struct { + du_u32 bg; /* 卡片背景 */ + du_u32 border; /* 边框色 */ + du_u32 border_focus; /* 焦点边框色 */ + du_u32 shadow_color; /* 阴影色 */ + du_u32 radius; /* 圆角半径 */ + du_u32 padding_x; /* 水平内边距 */ + du_u32 padding_y; /* 垂直内边距 */ + du_u32 border_width; /* 边框宽度 */ +} du_card_style; + +#define DU_CARD_DARK { \ + DS_DARK_BG_SECONDARY, DS_DARK_BORDER, DS_DARK_BORDER_FOCUS, \ + DP_ABYSS_900, DU_RADIUS_LG, DU_CARD_PADDING_X, DU_CARD_PADDING_Y, DU_BORDER_STD \ +} + +#define DU_CARD_LIGHT { \ + DS_LIGHT_BG_SECONDARY, DS_LIGHT_BORDER, DS_LIGHT_BORDER_FOCUS, \ + DP_NEUTRAL_300, DU_RADIUS_LG, DU_CARD_PADDING_X, DU_CARD_PADDING_Y, DU_BORDER_STD \ +} + +/* =================================================================== + * 组件令牌 — 输入框 (Input) + * =================================================================== */ + +typedef struct { + du_u32 bg; + du_u32 bg_focus; + du_u32 fg; + du_u32 placeholder; + du_u32 border; + du_u32 border_focus; + du_u32 cursor; + du_u32 radius; + du_u32 height; +} du_input_style; + +#define DU_INPUT_DARK { \ + DS_DARK_BG_INSET, DS_DARK_BG_TERTIARY, DS_DARK_TEXT_PRIMARY, \ + DS_DARK_TEXT_DIM, DS_DARK_BORDER, DS_DARK_BORDER_FOCUS, \ + DS_DARK_CURSOR, DU_RADIUS_MD, DU_INPUT_HEIGHT \ +} + +/* =================================================================== + * 组件令牌 — 通知栏 (Notification) + * =================================================================== */ + +typedef enum { + DU_NOTIFY_INFO, + DU_NOTIFY_SUCCESS, + DU_NOTIFY_WARNING, + DU_NOTIFY_ERROR +} du_notify_type; + +static inline du_u32 du_notify_color(du_notify_type type) { + switch (type) { + case DU_NOTIFY_SUCCESS: return DP_SUCCESS; + case DU_NOTIFY_WARNING: return DP_WARNING; + case DU_NOTIFY_ERROR: return DP_ERROR; + default: return DP_INFO; + } +} + +/* =================================================================== + * 组件绘制函数 (Component Drawing) + * =================================================================== */ + +/* 绘制按钮 */ +static inline void du_draw_button(du_context *ctx, + du_i64 x, du_i64 y, du_i64 w, + const du_button_style *style, + const char *label, int focused) { + du_u32 bg = style->bg_normal; + du_u32 border = style->border; + if (focused) { + border = style->border_focus; + } + /* 填充圆角矩形背景 */ + du_fill_rounded_rect(ctx, x, y, w, (du_i64)style->height, bg, style->radius); + /* 边框 */ + du_rect_outline(ctx, x, y, w, (du_i64)style->height, border, style->radius); + /* 文字居中 */ + du_i64 label_len = 0; + while (label[label_len]) label_len++; + du_i64 text_w = label_len * (du_i64)DU_ASCII_STEP; + du_i64 text_x = x + (w - text_w) / 2; + du_i64 text_y = y + ((du_i64)style->height - (du_i64)DU_ASCII_CELL_H) / 2; + du_draw_string(ctx, label, text_x, text_y, style->fg, bg, DU_ASCII_STEP); +} + +/* 绘制卡片 */ +static inline void du_draw_card(du_context *ctx, + du_i64 x, du_i64 y, du_i64 w, du_i64 h, + const du_card_style *style, int focused) { + du_u32 border = style->border; + if (focused) { + border = style->border_focus; + /* 焦点发光效果 */ + du_rect_glow(ctx, x - 2, y - 2, w + 4, h + 4, + border, style->radius + 2, 3); + } + du_fill_rounded_rect(ctx, x, y, w, h, style->bg, style->radius); + du_rect_outline(ctx, x, y, w, h, border, style->radius); +} + +/* 绘制输入框 */ +static inline void du_draw_input(du_context *ctx, + du_i64 x, du_i64 y, du_i64 w, + const du_input_style *style, + const char *text, int cursor_pos, + int focused) { + du_u32 bg = focused ? style->bg_focus : style->bg; + du_u32 border = focused ? style->border_focus : style->border; + du_fill_rounded_rect(ctx, x, y, w, (du_i64)style->height, bg, style->radius); + du_rect_outline(ctx, x, y, w, (du_i64)style->height, border, style->radius); + + /* 文字 */ + du_i64 text_x = x + (du_i64)DU_SPACE_SM; + du_i64 text_y = y + ((du_i64)style->height - (du_i64)DU_ASCII_CELL_H) / 2; + du_u32 fg = text[0] ? style->fg : style->placeholder; + du_draw_string(ctx, text, text_x, text_y, fg, bg, DU_ASCII_STEP); + + /* 光标 */ + if (focused && cursor_pos >= 0) { + du_i64 cx = text_x + (du_i64)cursor_pos * (du_i64)DU_ASCII_STEP; + du_fill_rect(ctx, cx, text_y, 2, (du_i64)DU_ASCII_CELL_H, style->cursor); + } +} + +/* 绘制通知栏 */ +static inline void du_draw_notification(du_context *ctx, + du_i64 x, du_i64 y, du_i64 w, + du_notify_type type, + const char *message) { + du_u32 accent = du_notify_color(type); + /* 半透明背景 */ + du_fill_rounded_rect(ctx, x, y, w, (du_i64)DU_NOTIFICATION_H, + DP_ABYSS_800, DU_RADIUS_MD); + /* 左侧色条 */ + du_fill_rect(ctx, x, y + 4, 4, (du_i64)DU_NOTIFICATION_H - 8, accent); + /* 文字 */ + du_i64 text_y = y + ((du_i64)DU_NOTIFICATION_H - (du_i64)DU_ASCII_CELL_H) / 2; + du_draw_string(ctx, message, x + (du_i64)DU_SPACE_MD, text_y, + DS_DARK_TEXT_PRIMARY, DP_ABYSS_800, DU_ASCII_STEP); +} + +/* 绘制进度条 */ +static inline void du_draw_progress(du_context *ctx, + du_i64 x, du_i64 y, du_i64 w, du_i64 h, + du_u32 progress_0_255) { + /* 背景 */ + du_fill_rounded_rect(ctx, x, y, w, h, DP_ABYSS_700, DU_RADIUS_FULL); + /* 填充 */ + du_i64 fill_w = (w * (du_i64)progress_0_255) / 255; + if (fill_w > 0) { + du_fill_rounded_rect(ctx, x, y, fill_w, h, DP_SEAL_500, DU_RADIUS_FULL); + } +} + +/* =================================================================== + * Spinner(加载弧) — DSK 启动加载动画 + * + * 使用 "Sealed Arc" 四弧旋转设计,弧色取自色板。 + * =================================================================== */ + +/* 默认弧色(从亮到暗的青蓝色) */ +#define DU_SPINNER_ARCS { \ + DP_SEAL_100, DP_SEAL_300, DP_SEAL_500, DP_SEAL_700 \ +} + +/* =================================================================== + * 主题切换辅助 (Theme Switch) + * =================================================================== */ + +typedef enum { + DU_THEME_DARK, + DU_THEME_LIGHT +} du_theme; + +/* 根据主题获取对应的背景色 */ +static inline du_u32 du_theme_bg(du_theme t) { + return (t == DU_THEME_DARK) ? DS_DARK_BG_PRIMARY : DS_LIGHT_BG_PRIMARY; +} + +/* 根据主题获取对应的文字色 */ +static inline du_u32 du_theme_text(du_theme t) { + return (t == DU_THEME_DARK) ? DS_DARK_TEXT_PRIMARY : DS_LIGHT_TEXT_PRIMARY; +} + +/* 根据主题获取对应的强调色 */ +static inline du_u32 du_theme_accent(du_theme t) { + return (t == DU_THEME_DARK) ? DS_DARK_ACCENT : DS_LIGHT_ACCENT; +} + +#endif /* DESHAB_UI_H */ diff --git a/CODE/UTSM/include/utsm/drr.h b/CODE/UTSM/include/utsm/drr.h index 9bff1f3..808b692 100644 --- a/CODE/UTSM/include/utsm/drr.h +++ b/CODE/UTSM/include/utsm/drr.h @@ -2,12 +2,126 @@ #define UTSM_DRR_H #include +#include -void drr_stub_init(void); +/* =================================================================== + * DRR — Dedicated Recovery Root + * + * 专用恢复根,独立于普通调度器和堆分配器。 + * 负责:root key、recovery log、checkpoint A/B、Emergency Pool。 + * + * 约束: + * - DRR recovery 路径禁止依赖普通堆分配器 + * - 普通 OOM 不影响 DRR recovery + * - Emergency Pool 耗尽时进入 system rollback + * =================================================================== */ + +/* ---- Checkpoint 类型 ---- */ +typedef enum { + DRR_CKPT_LIGHT = 0, /* 控制面快照 */ + DRR_CKPT_DIRTY_PAGE, /* dirty ciphertext page 快照 */ + DRR_CKPT_BOOT /* 启动状态 + A/B slot */ +} drr_checkpoint_type; + +/* ---- Recovery Log 条目 ---- */ +typedef struct { + u32 segment_slot; + u32 flags; /* 0=intent, 1=commit */ + u64 offset; + u64 length; + u64 timestamp; /* writer_seq snapshot */ +} drr_log_entry; + +#define DRR_LOG_FLAG_INTENT 0u +#define DRR_LOG_FLAG_COMMIT 1u + +/* ---- Checkpoint Metadata Slot ---- */ +typedef struct { + u64 magic; /* DRR_CKPT_MAGIC */ + u32 version; + u32 active; /* 0=inactive, 1=active */ + drr_checkpoint_type type; + u64 global_epoch; + u64 dirty_page_count; + u64 mac_root; /* MAC root hash of all sealed pages */ + u64 segment_count; + u64 crc; /* CRC64 of the above */ +} drr_checkpoint_meta; + +#define DRR_CKPT_MAGIC 0x445252434B505421ULL /* "DRRCKPT!" */ +#define DRR_CKPT_VERSION 1u + +/* ---- Emergency Pool ---- */ +#define DRR_EMERGENCY_POOL_SIZE (64ULL * 1024ULL) /* 64KB 独立池 */ +#define DRR_EMERGENCY_STACK_SIZE 4096ULL +#define DRR_RECOVERY_LOG_SIZE 4096ULL +#define DRR_CKPT_META_BUF_SIZE 4096ULL +#define DRR_CRASH_BUF_SIZE 2048ULL +#define DRR_META_SLAB_SIZE 2048ULL +#define DRR_EMERGENCY_PAGES 4 + +typedef struct { + u8 pool[DRR_EMERGENCY_POOL_SIZE] __attribute__((aligned(64))); + u64 pool_offset; + u64 pool_limit; + + /* 子缓冲区指针(pool 内偏移分配) */ + u8 *stack_base; + u64 stack_size; + u64 stack_ptr; + + drr_log_entry *log_base; + u32 log_count; + u32 log_capacity; + + drr_checkpoint_meta *ckpt_meta_buf; + u8 *crash_buf; + u64 crash_buf_size; + void *meta_slab; + u64 meta_slab_offset; + + /* A/B checkpoint slots */ + drr_checkpoint_meta ckpt_a; + drr_checkpoint_meta ckpt_b; + u32 active_slot; /* 0=A, 1=B */ + + /* Root key */ + const u64 *root_key; + + /* Recovery generation */ + u64 recovery_generation; + + /* Heartbeat */ + u64 heartbeat_counter; + u64 heartbeat_interval; + + /* 初始化标记 */ + int initialized; +} drr_state; + +/* ---- 公共接口 ---- */ + +void drr_init(void); const u64 *drr_get_root_key(void); u64 drr_recovery_generation(void); void drr_report_fault(const char *reason); void drr_log_write_intent(u32 segment_slot, u64 offset, u64 len); void drr_log_write_commit(u32 segment_slot, u64 offset, u64 len); -#endif +/* Checkpoint 操作 */ +int drr_checkpoint_begin(drr_checkpoint_type type); +int drr_checkpoint_finish(void); +int drr_checkpoint_recover(drr_checkpoint_meta *out_meta); + +/* Emergency Pool 分配 */ +void *drr_emergency_alloc(u64 size, u64 alignment); +void drr_emergency_reset(void); + +/* Heartbeat */ +void drr_heartbeat_tick(void); +u64 drr_heartbeat_get(void); + +/* CRC64 */ +u64 drr_crc64(const void *data, u64 len); + +#endif /* UTSM_DRR_H */ diff --git a/CODE/UTSM/include/utsm/process.h b/CODE/UTSM/include/utsm/process.h index 2b1243d..5b01267 100644 --- a/CODE/UTSM/include/utsm/process.h +++ b/CODE/UTSM/include/utsm/process.h @@ -3,16 +3,77 @@ #include #include +#include +#include +/* ---- 进程标志 ---- */ +#define UTSM_PROC_F_BOOTSTRAP (1U << 0) /* 内核 bootstrap 进程 */ +#define UTSM_PROC_F_KERNEL (1U << 1) /* 内核态进程 */ +#define UTSM_PROC_F_ENCRYPTED (1U << 2) /* 加密堆栈/堆 */ + +/* ---- 进程状态 ---- */ +typedef enum { + UTSM_PROC_UNUSED = 0, + UTSM_PROC_ACTIVE, + UTSM_PROC_BLOCKED, + UTSM_PROC_TERMINATED +} utsm_process_state; + +/* ---- Capability Table(每进程) ---- */ +typedef struct { + utsm_capability caps[UTSM_MAX_CAPABILITIES]; + u32 count; + u32 generation; /* 表版本号 */ +} utsm_cap_table; + +/* ---- Crypto Context(每进程) ---- */ +typedef struct { + u64 crypto_epoch; /* 当前加密纪元 */ + u64 key_derivation_seed; /* 密钥派生种子 */ + u32 pckc_hot_hint; /* PCKC 热段提示 */ + u32 reserved; +} utsm_crypto_context; + +/* ---- 进程上下文 ---- */ typedef struct utsm_process_context { uuid128_t process_uuid; u64 crypto_epoch; u64 recovery_generation; - u32 capability_table_slot; + u32 process_slot; /* 进程表索引 */ u32 capability_count; u32 flags; + utsm_process_state state; + + /* Capability Table */ + utsm_cap_table cap_table; + + /* Crypto Context */ + utsm_crypto_context crypto_ctx; + + /* PCKC 热段提示 */ + u32 hot_segment_slot; + u32 reserved; + + /* DRR recovery generation */ + u64 drr_recovery_gen; } utsm_process_context; -void utsm_process_create_test(utsm_process_context *process, u64 seed); +/* ---- 进程表 ---- */ +#define UTSM_MAX_PROCESSES 64 -#endif +/* ---- 公共接口 ---- */ +void utsm_process_init(void); +void utsm_process_create_test(utsm_process_context *process, u64 seed); +utsm_process_context *utsm_process_alloc(void); +void utsm_process_free(utsm_process_context *process); +utsm_process_context *utsm_process_get(u32 slot); + +/* Capability 操作 */ +int utsm_process_grant_cap(utsm_process_context *process, utsm_capability cap); +int utsm_process_revoke_cap(utsm_process_context *process, u32 cap_index); +utsm_capability *utsm_process_get_cap(utsm_process_context *process, u32 cap_index); + +/* Crypto context 操作 */ +void utsm_process_advance_epoch(utsm_process_context *process); + +#endif /* UTSM_PROCESS_H */ diff --git a/CODE/UTSM/kernel/main.c b/CODE/UTSM/kernel/main.c index ed1d63a..53fb922 100644 --- a/CODE/UTSM/kernel/main.c +++ b/CODE/UTSM/kernel/main.c @@ -28,9 +28,9 @@ void kernel_main(void) { net_init(); log_info("[UTSM] net registry init ok"); - log_info("[UTSM] drr stub init begin"); - drr_stub_init(); - log_info("[UTSM] drr stub init ok"); + log_info("[UTSM] drr init begin"); + drr_init(); + log_info("[UTSM] drr init ok"); log_info("[UTSM] core init begin"); utsm_init(); diff --git a/CODE/desktop/MAKEFILE b/CODE/desktop/MAKEFILE new file mode 100644 index 0000000..3d6f2f9 --- /dev/null +++ b/CODE/desktop/MAKEFILE @@ -0,0 +1,25 @@ +CC := clang +LD := ld.lld + +ROOT := ../.. +OUT := $(ROOT)/SYSTEM/system/deshab64/desktop.elf + +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I../UTSM/include -O2 +LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld + +C_SOURCES := main.c +OBJECTS := $(C_SOURCES:.c=.o) + +.PHONY: all clean + +all: $(OUT) + +$(OUT): $(OBJECTS) linker.ld + @if not exist "$(ROOT)\SYSTEM\system\deshab64" mkdir "$(ROOT)\SYSTEM\system\deshab64" + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }" diff --git a/CODE/desktop/linker.ld b/CODE/desktop/linker.ld new file mode 100644 index 0000000..94dddf7 --- /dev/null +++ b/CODE/desktop/linker.ld @@ -0,0 +1,16 @@ +ENTRY(dsk_entry) + +PHDRS +{ + text PT_LOAD FLAGS(7); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(7); +} + +SECTIONS +{ + . = 0x1000; + .text : ALIGN(4K) { *(.text*) } :text + .rodata : ALIGN(4K) { *(.rodata*) } :rodata + .data : ALIGN(4K) { *(.data*) *(COMMON) *(.bss*) } :data +} diff --git a/CODE/desktop/main.c b/CODE/desktop/main.c new file mode 100644 index 0000000..d938fd2 --- /dev/null +++ b/CODE/desktop/main.c @@ -0,0 +1,1438 @@ +/* Deshab Desktop Manager — 图形桌面环境 + * + * 功能:窗口管理、任务栏、桌面图标、事件路由、应用加载 + * 风格:Sealed Arc 视觉设计语言 + * 输入:PS/2 键盘 + PS/2 鼠标 + */ + +#include "../UTSM/include/utsm/dsk.h" + +/* block_read 函数类型(从 kernel_api + 0xA8 获取) */ +typedef int (*desktop_block_read_fn)(void *ctx, unsigned long long lba, unsigned int count, void *buffer); + +/* 先包含 ascii_bitmaps.c(定义 g_ascii),再包含 deshab_ui.h(引用 g_ascii) */ +#include "../firstInit/ascii_bitmaps.c" +#include "../UTSM/include/utsm/deshab_ui.h" + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; +typedef long long i64; + +#define COM1 0x3F8 + +static __inline__ void outb(u16 port, u8 value) { __asm__ volatile("outb %0,%1"::"a"(value),"Nd"(port)); } +static __inline__ u8 inb(u16 port) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(port)); return v; } + +static void sputc(char c) { + for (unsigned int i=0; i<100000; i++) { if (inb(COM1+5)&0x20) break; } + outb(COM1, (unsigned char)c); +} +static void swrite(const char *s) { while(*s) { if(*s=='\n')sputc('\r'); sputc(*s++); } } +static void slog(const char *s) { swrite("[desktop] "); swrite(s); swrite("\n"); } + +/* ============================================================ + * 常量 + * ============================================================ */ + +#define MAX_WINDOWS 8 +#define TASKBAR_H 40 +#define TITLEBAR_H 28 +#define BORDER_W 2 +#define DESKTOP_ICON_W 80 +#define DESKTOP_ICON_H 72 +#define ICON_GUTTER_X 16 +#define ICON_GUTTER_Y 16 +#define CURSOR_SIZE 24 + +/* ============================================================ + * 事件系统 + * ============================================================ */ + +typedef enum { + EV_MOUSE_MOVE = 1, + EV_MOUSE_DOWN, + EV_MOUSE_UP, + EV_KEY_DOWN, + EV_KEY_UP, +} ev_type; + +typedef struct { + ev_type type; + int mx, my; /* 鼠标全局坐标 */ + int button; /* 0=左, 1=右 */ + u8 scancode; + int shift, ctrl, alt; +} desktop_event; + +/* ============================================================ + * 窗口 + * ============================================================ */ + +typedef struct desktop_window { + int id; + int x, y, w, h; + int visible; + int focused; + int minimized; + char title[48]; + int app_id; /* 关联应用 ID */ + void *app_state; /* 应用私有状态 */ + int closing; /* 关闭中标记 */ + int dirty; /* 需要重绘 */ +} desktop_window; + +/* ============================================================ + * 应用描述符 + * ============================================================ */ + +/* 前向声明 */ +struct desktop_window; +typedef struct desktop_window desktop_win; + +typedef struct app_ctx { + du_context *fb; + desktop_win *win; + int client_x, client_y, client_w, client_h; + int should_exit; + desktop_block_read_fn block_read; + const void *kernel_api; +} app_ctx; + +typedef void *(*app_create_fn)(app_ctx *ctx); +typedef void (*app_event_fn)(void *state, app_ctx *ctx, desktop_event *ev); +typedef void (*app_draw_fn)(void *state, app_ctx *ctx); +typedef void (*app_destroy_fn)(void *state); + +typedef struct { + const char *name; + const char *display_name; + u32 default_w, default_h; + app_create_fn on_create; + app_event_fn on_event; + app_draw_fn on_draw; + app_destroy_fn on_destroy; +} app_descriptor; + +/* ============================================================ + * 全局状态 + * ============================================================ */ + +static du_context g_fb; +static u64 g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch; +static const void *g_kernel_api; +static desktop_block_read_fn g_block_read; + +/* 鼠标状态 */ +static int g_mouse_x = 400, g_mouse_y = 300; +static int g_mouse_btn = 0; /* bit0=左键 */ +static u8 g_mouse_buf[3]; +static int g_mouse_idx = 0; +static int g_mouse_has_pkt = 0; + +/* 光标背景保存 */ +static u32 g_cursor_save[CURSOR_SIZE * CURSOR_SIZE]; +static int g_cursor_saved = 0; +static int g_cursor_old_x = -1, g_cursor_old_y = -1; + +/* 键盘状态 */ +static int g_shift = 0, g_ctrl = 0, g_alt = 0; +static int g_e0 = 0; + +/* 窗口列表 */ +static desktop_window g_windows[MAX_WINDOWS]; +static int g_win_count = 0; +static int g_next_win_id = 1; +static int g_focused_win = -1; + +/* 拖拽状态 */ +static int g_dragging = 0; +static int g_drag_win = -1; +static int g_drag_off_x, g_drag_off_y; + +/* 应用表 */ +#define MAX_APPS 8 +static app_descriptor g_apps[MAX_APPS]; +static int g_app_count = 0; + +/* 桌面图标 */ +typedef struct { + int app_id; + int x, y; /* 图标位置 */ + const char *label; +} desktop_icon; + +#define MAX_ICONS 8 +static desktop_icon g_icons[MAX_ICONS]; +static int g_icon_count = 0; + +/* ============================================================ + * 箭头光标形状(24×24,1=前景 0=透明) + * ============================================================ */ + +static const u8 cursor_shape[24][24] = { + {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {1,1,0,0,0,1,1,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}, +}; + +/* ============================================================ + * 应用图标形状(32×32 像素数据,用程序化方式生成) + * ============================================================ */ + +/* 绘制终端图标(32×32)到指定位置 */ +static void draw_icon_terminal(du_context *ctx, int cx, int cy) { + u32 bg = DP_ABYSS_700; + u32 fg = DP_SEAL_300; + u32 border = DP_SEAL_500; + /* 圆角矩形背景 */ + du_fill_rounded_rect(ctx, cx, cy, 32, 32, bg, 6); + du_rect_outline(ctx, cx, cy, 32, 32, border, 6); + /* >_ 符号 */ + du_fill_rect(ctx, cx+5, cy+10, 8, 2, fg); + du_fill_rect(ctx, cx+5, cy+10, 2, 6, fg); + du_fill_rect(ctx, cx+13, cy+12, 2, 2, fg); + du_fill_rect(ctx, cx+17, cy+16, 6, 2, fg); +} + +/* 绘制编辑器图标(32×32) */ +static void draw_icon_editor(du_context *ctx, int cx, int cy) { + u32 bg = DP_ABYSS_700; + u32 fg = DP_SEAL_500; + u32 border = DP_SEAL_300; + du_fill_rounded_rect(ctx, cx, cy, 32, 32, bg, 6); + du_rect_outline(ctx, cx, cy, 32, 32, border, 6); + /* 文本行 */ + for (int i = 0; i < 5; i++) { + int lw = (i == 0) ? 20 : (i == 3 ? 12 : 16); + du_fill_rect(ctx, cx+6, cy+6+i*5, lw, 2, fg); + } +} + +/* 绘制文件夹图标(32×32) */ +static void draw_icon_folder(du_context *ctx, int cx, int cy) { + u32 bg = DP_ARC_500; + u32 fg = DP_ARC_300; + u32 border = DP_ARC_700; + du_fill_rounded_rect(ctx, cx, cy, 32, 32, bg, 6); + du_rect_outline(ctx, cx, cy, 32, 32, border, 6); + /* 文件夹标签 */ + du_fill_rect(ctx, cx+4, cy+6, 12, 3, fg); + /* 文件夹主体 */ + du_fill_rect(ctx, cx+4, cy+12, 24, 2, border); + du_fill_rect(ctx, cx+4, cy+14, 24, 12, fg); +} + +/* 绘制计算器图标(32×32) */ +static void draw_icon_calc(du_context *ctx, int cx, int cy) { + u32 bg = DP_ABYSS_700; + u32 fg = DP_WARNING; + u32 border = DP_WARNING; + du_fill_rounded_rect(ctx, cx, cy, 32, 32, bg, 6); + du_rect_outline(ctx, cx, cy, 32, 32, border, 6); + /* 屏幕 */ + du_fill_rect(ctx, cx+4, cy+4, 24, 6, DP_ABYSS_900); + du_fill_rect(ctx, cx+22, cy+5, 4, 4, fg); + /* 按键网格 */ + for (int r = 0; r < 3; r++) { + for (int c = 0; c < 3; c++) { + du_fill_rect(ctx, cx+4+c*8, cy+14+r*6, 6, 4, DP_NEUTRAL_400); + } + } +} + +/* ============================================================ + * 光标管理 + * ============================================================ */ + +static void cursor_save_bg(int mx, int my) { + for (int r = 0; r < CURSOR_SIZE; r++) { + for (int c = 0; c < CURSOR_SIZE; c++) { + int x = mx + c, y = my + r; + if (x >= 0 && (u64)x < g_fb_w && y >= 0 && (u64)y < g_fb_h) { + u32 *line = (u32 *)((u8 *)g_fb.fb + (u64)y * g_fb.pitch); + g_cursor_save[r * CURSOR_SIZE + c] = line[x]; + } else { + g_cursor_save[r * CURSOR_SIZE + c] = 0; + } + } + } + g_cursor_saved = 1; + g_cursor_old_x = mx; + g_cursor_old_y = my; +} + +static void cursor_restore_bg(void) { + if (!g_cursor_saved) return; + for (int r = 0; r < CURSOR_SIZE; r++) { + for (int c = 0; c < CURSOR_SIZE; c++) { + int x = g_cursor_old_x + c, y = g_cursor_old_y + r; + if (x >= 0 && (u64)x < g_fb_w && y >= 0 && (u64)y < g_fb_h) { + u32 *line = (u32 *)((u8 *)g_fb.fb + (u64)y * g_fb.pitch); + line[x] = g_cursor_save[r * CURSOR_SIZE + c]; + } + } + } + g_cursor_saved = 0; +} + +static void cursor_draw(int mx, int my) { + for (int r = 0; r < CURSOR_SIZE; r++) { + for (int c = 0; c < CURSOR_SIZE; c++) { + if (cursor_shape[r][c]) { + du_pixel(&g_fb, mx + c, my + r, DS_DARK_TEXT_PRIMARY); + } + } + } +} + +/* ============================================================ + * 窗口管理 + * ============================================================ */ + +static desktop_window *win_find(int id) { + for (int i = 0; i < g_win_count; i++) { + if (g_windows[i].id == id) return &g_windows[i]; + } + return 0; +} + +static desktop_window *win_find_at(int mx, int my) { + /* 从后往前搜索(后面的窗口在上面) */ + for (int i = g_win_count - 1; i >= 0; i--) { + desktop_window *w = &g_windows[i]; + if (!w->visible || w->minimized) continue; + if (mx >= w->x && mx < w->x + w->w && my >= w->y && my < w->y + w->h) { + return w; + } + } + return 0; +} + +static void win_bring_to_front(desktop_window *w) { + int idx = -1; + for (int i = 0; i < g_win_count; i++) { + if (g_windows[i].id == w->id) { idx = i; break; } + } + if (idx < 0 || idx == g_win_count - 1) return; + desktop_window tmp = g_windows[idx]; + for (int i = idx; i < g_win_count - 1; i++) { + g_windows[i] = g_windows[i+1]; + } + g_windows[g_win_count - 1] = tmp; +} + +static desktop_window *win_create(int app_id, const char *title, int x, int y, int w, int h) { + if (g_win_count >= MAX_WINDOWS) return 0; + desktop_window *win = &g_windows[g_win_count++]; + int id = g_next_win_id++; + win->id = id; + win->x = x; win->y = y; win->w = w; win->h = h; + win->visible = 1; + win->focused = 1; + win->minimized = 0; + win->app_id = app_id; + win->app_state = 0; + win->closing = 0; + win->dirty = 1; + /* 拷贝标题 */ + for (int i = 0; i < 47 && title[i]; i++) win->title[i] = title[i]; + win->title[47] = 0; + /* 取消其他窗口焦点 */ + for (int i = 0; i < g_win_count - 1; i++) g_windows[i].focused = 0; + g_focused_win = id; + return win; +} + +static void win_destroy(desktop_window *w) { + int idx = -1; + for (int i = 0; i < g_win_count; i++) { + if (g_windows[i].id == w->id) { idx = i; break; } + } + if (idx < 0) return; + /* 调用应用的 on_destroy */ + if (w->app_id >= 0 && w->app_id < g_app_count && g_apps[w->app_id].on_destroy && w->app_state) { + g_apps[w->app_id].on_destroy(w->app_state); + } + for (int i = idx; i < g_win_count - 1; i++) g_windows[i] = g_windows[i+1]; + g_win_count--; + if (g_focused_win == w->id) { + g_focused_win = g_win_count > 0 ? g_windows[g_win_count-1].id : -1; + if (g_focused_win >= 0) win_find(g_focused_win)->focused = 1; + } +} + +static int win_hit_titlebar(desktop_window *w, int mx, int my) { + return mx >= w->x && mx < w->x + w->w && + my >= w->y && my < w->y + TITLEBAR_H; +} + +static int win_hit_close_btn(desktop_window *w, int mx, int my) { + int bx = w->x + w->w - 28; + int by = w->y + 4; + return mx >= bx && mx < bx + 24 && my >= by && my < by + 20; +} + +/* ============================================================ + * 窗口装饰绘制 + * ============================================================ */ + +static void draw_window_frame(desktop_window *w) { + if (!w->visible || w->minimized) return; + u32 titlebar_bg = w->focused ? DP_ABYSS_600 : DP_ABYSS_800; + u32 border_col = w->focused ? DS_DARK_BORDER_FOCUS : DS_DARK_BORDER; + + /* 发光效果(焦点窗口) */ + if (w->focused) { + du_rect_glow(&g_fb, w->x-2, w->y-2, w->w+4, w->h+4, + DS_DARK_BORDER_FOCUS, 10, 3); + } + + /* 窗口主体背景 */ + du_fill_rounded_rect(&g_fb, w->x, w->y, w->w, w->h, + DS_DARK_BG_SECONDARY, DU_RADIUS_LG); + + /* 标题栏 */ + du_fill_rect(&g_fb, w->x + BORDER_W, w->y + BORDER_W, + w->w - 2*BORDER_W, TITLEBAR_H - BORDER_W, titlebar_bg); + + /* 标题文字 */ + du_draw_string(&g_fb, w->title, + w->x + (i64)DU_SPACE_SM, + w->y + (TITLEBAR_H - (i64)DU_ASCII_CELL_H) / 2, + DS_DARK_TEXT_PRIMARY, titlebar_bg, DU_ASCII_STEP); + + /* 关闭按钮 */ + int bx = w->x + w->w - 28; + int by = w->y + 4; + du_fill_rounded_rect(&g_fb, bx, by, 24, 20, DP_ERROR, DU_RADIUS_SM); + du_draw_string(&g_fb, "X", bx + 8, by + 1, DP_NEUTRAL_900, DP_ERROR, DU_ASCII_STEP); + + /* 边框 */ + du_rect_outline(&g_fb, w->x, w->y, w->w, w->h, border_col, DU_RADIUS_LG); + + /* 标题栏底线 */ + du_fill_rect(&g_fb, w->x + BORDER_W, w->y + TITLEBAR_H, + w->w - 2*BORDER_W, 1, border_col); +} + +/* ============================================================ + * 任务栏绘制 + * ============================================================ */ + +static void draw_taskbar(void) { + i64 tb_y = (i64)g_fb_h - TASKBAR_H; + du_fill_rect(&g_fb, 0, tb_y, (i64)g_fb_w, TASKBAR_H, DP_ABYSS_800); + /* 顶部强调线 */ + du_fill_rect(&g_fb, 0, tb_y, (i64)g_fb_w, 2, DS_DARK_ACCENT); + + /* Start 按钮 */ + du_fill_rounded_rect(&g_fb, (i64)DU_SPACE_SM, tb_y + 4, 48, 32, + DP_ABYSS_600, DU_RADIUS_SM); + du_draw_string(&g_fb, "D", + (i64)DU_SPACE_SM + 18, tb_y + 8, + DS_DARK_ACCENT_LIGHT, DP_ABYSS_600, DU_ASCII_STEP); + + /* 快速启动图标 */ + int qx = 64; + for (int i = 0; i < g_app_count && i < 4; i++) { + int icon_x = qx + i * 40; + switch (i) { + case 0: draw_icon_terminal(&g_fb, icon_x + 4, (int)tb_y + 4); break; + case 1: draw_icon_editor(&g_fb, icon_x + 4, (int)tb_y + 4); break; + case 2: draw_icon_folder(&g_fb, icon_x + 4, (int)tb_y + 4); break; + case 3: draw_icon_calc(&g_fb, icon_x + 4, (int)tb_y + 4); break; + } + } + + /* 窗口按钮 */ + int wx = qx + 4 * 40 + (int)DU_SPACE_LG; + for (int i = 0; i < g_win_count; i++) { + desktop_window *w = &g_windows[i]; + if (w->minimized) continue; + u32 btn_bg = w->focused ? DS_DARK_ACCENT : DP_ABYSS_600; + du_fill_rounded_rect(&g_fb, wx, tb_y + 4, 100, 32, btn_bg, DU_RADIUS_SM); + du_draw_string(&g_fb, w->title, wx + 4, tb_y + 8, + DS_DARK_TEXT_PRIMARY, btn_bg, DU_ASCII_STEP); + wx += 104; + if (wx > (int)g_fb_w - 200) break; + } + + /* 时钟 */ + u8 rtc_h = 0, rtc_m = 0; + outb(0x70, 4); rtc_h = inb(0x71); + outb(0x70, 2); rtc_m = inb(0x71); + /* BCD → 二进制 */ + rtc_h = (u8)((rtc_h >> 4) * 10 + (rtc_h & 0xF)); + rtc_m = (u8)((rtc_m >> 4) * 10 + (rtc_m & 0xF)); + char clock_str[8]; + clock_str[0] = '0' + rtc_h / 10; + clock_str[1] = '0' + rtc_h % 10; + clock_str[2] = ':'; + clock_str[3] = '0' + rtc_m / 10; + clock_str[4] = '0' + rtc_m % 10; + clock_str[5] = 0; + du_draw_string(&g_fb, clock_str, + (i64)g_fb_w - 5 * (i64)DU_ASCII_STEP - (i64)DU_SPACE_MD, + tb_y + (TASKBAR_H - (i64)DU_ASCII_CELL_H) / 2, + DS_DARK_TEXT_SECONDARY, DP_ABYSS_800, DU_ASCII_STEP); +} + +/* ============================================================ + * 桌面图标绘制 + * ============================================================ */ + +static void draw_desktop_icons(void) { + for (int i = 0; i < g_icon_count; i++) { + desktop_icon *ic = &g_icons[i]; + int cx = ic->x; + int cy = ic->y; + /* 根据应用 ID 画不同图标 */ + switch (ic->app_id) { + case 0: draw_icon_terminal(&g_fb, cx, cy); break; + case 1: draw_icon_editor(&g_fb, cx, cy); break; + case 2: draw_icon_folder(&g_fb, cx, cy); break; + case 3: draw_icon_calc(&g_fb, cx, cy); break; + default: draw_icon_terminal(&g_fb, cx, cy); break; + } + /* 标签文字 */ + int label_len = 0; + while (ic->label[label_len]) label_len++; + int label_x = cx + (32 - label_len * (int)DU_ASCII_STEP) / 2; + if (label_x < cx) label_x = cx; + du_draw_string(&g_fb, ic->label, label_x, cy + 36, + DS_DARK_TEXT_PRIMARY, DS_DARK_BG_PRIMARY, DU_ASCII_STEP); + } +} + +/* ============================================================ + * PS/2 鼠标处理 + * ============================================================ */ + +static void ps2_mouse_init(void) { + /* 启用 AUX 设备 */ + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0xA8); + /* 启用 AUX IRQ */ + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0x20); + for (int t = 0; t < 100000; t++) { if (inb(0x64) & 1) break; } + u8 cfg = inb(0x60); + cfg |= 0x02; /* enable AUX IRQ12 */ + cfg &= ~0x20; /* enable AUX clock */ + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0x60); + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x60, cfg); + /* 复位鼠标 */ + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0xD4); + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x60, 0xFF); + for (int t = 0; t < 100000; t++) { if (inb(0x64) & 1) break; } + inb(0x60); /* ACK */ + /* 启用 streaming */ + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0xD4); + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x60, 0xF4); + for (int t = 0; t < 100000; t++) { if (inb(0x64) & 1) break; } + inb(0x60); /* ACK */ + slog("mouse init ok"); +} + +static int ps2_mouse_poll(void) { + u8 st = inb(0x64); + if (!(st & 1)) return 0; + u8 data = inb(0x60); + if (!(st & 0x20)) return 0; /* 不是鼠标数据 */ + + g_mouse_buf[g_mouse_idx++] = data; + if (g_mouse_idx < 3) return 0; + g_mouse_idx = 0; + + /* 检查 bit3 同步 */ + if (!(g_mouse_buf[0] & 0x08)) return 0; + + int dx = (int)(i8)g_mouse_buf[1]; + int dy = (int)(i8)g_mouse_buf[2]; + if (g_mouse_buf[0] & 0x10) dx = -dx; /* X sign */ + /* 注意:Y 轴方向(鼠标 Y 向上为正,屏幕 Y 向下为正) */ + if (!(g_mouse_buf[0] & 0x20)) dy = -dy; /* Y sign */ + else dy = -dy; + dy = -dy; /* 鼠标 Y 翻转 */ + + g_mouse_x += dx; + g_mouse_y += dy; + if (g_mouse_x < 0) g_mouse_x = 0; + if (g_mouse_y < 0) g_mouse_y = 0; + if (g_mouse_x >= (int)g_fb_w - CURSOR_SIZE) g_mouse_x = (int)g_fb_w - CURSOR_SIZE; + if (g_mouse_y >= (int)g_fb_h - CURSOR_SIZE) g_mouse_y = (int)g_fb_h - CURSOR_SIZE; + + g_mouse_btn = (g_mouse_buf[0] & 0x01) ? 1 : 0; + g_mouse_has_pkt = 1; + return 1; +} + +/* ============================================================ + * 键盘扫描码 → ASCII + * ============================================================ */ + +static char scan_to_ascii(u8 sc, int shift) { + static const char normal[58] = { + 0, 27, '1','2','3','4','5','6','7','8','9','0','-','=', 8, '\t', + 'q','w','e','r','t','y','u','i','o','p','[',']','\n',0, + 'a','s','d','f','g','h','j','k','l',';','\'', '`',0,'\\', + 'z','x','c','v','b','n','m',',','.','/',0,'*',0,' ' + }; + static const char shifted[58] = { + 0, 27, '!','@','#','$','%','^','&','*','(',')','_','+', 8, '\t', + 'Q','W','E','R','T','Y','U','I','O','P','{','}','\n',0, + 'A','S','D','F','G','H','J','K','L',':','"','~',0,'|', + 'Z','X','C','V','B','N','M','<','>','?',0,'*',0,' ' + }; + if (sc >= 58) return 0; + return shift ? shifted[sc] : normal[sc]; +} + +/* ============================================================ + * 内置应用:终端 (Bash) + * ============================================================ */ + +#define TERM_MAX_COLS 80 +#define TERM_MAX_ROWS 40 +#define TERM_MAX_CHARS (TERM_MAX_COLS * TERM_MAX_ROWS) + +typedef struct { + u8 ch[TERM_MAX_CHARS]; + u32 fg[TERM_MAX_CHARS]; + int cols, rows; + int cur_col, cur_row; + char input_buf[256]; + int input_len, input_cursor; + int prompt_len; +} bash_state; + +static const char *BASH_PROMPT = "deshab# "; + +static void *bash_on_create(app_ctx *ctx) { + bash_state *s = (bash_state *)0x4000000; /* 固定地址分配,避免堆依赖 */ + for (int i = 0; i < TERM_MAX_CHARS; i++) { + s->ch[i] = ' '; s->fg[i] = DS_DARK_TEXT_PRIMARY; + } + s->cols = (ctx->client_w - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_STEP; + s->rows = (ctx->client_h - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_LINE_H; + if (s->cols > TERM_MAX_COLS) s->cols = TERM_MAX_COLS; + if (s->rows > TERM_MAX_ROWS) s->rows = TERM_MAX_ROWS; + s->cur_col = 0; s->cur_row = 0; + s->input_len = 0; s->input_cursor = 0; + s->prompt_len = 0; + while (BASH_PROMPT[s->prompt_len]) s->prompt_len++; + + /* 打印欢迎 */ + const char *welcome = "Deshab Bash v0.1\n输入 help 查看命令\n\n"; + while (*welcome) { + if (*welcome == '\n') { + s->cur_col = 0; s->cur_row++; + } else { + if (s->cur_row < s->rows && s->cur_col < s->cols) { + int idx = s->cur_row * TERM_MAX_COLS + s->cur_col; + s->ch[idx] = (u8)*welcome; + s->fg[idx] = DS_DARK_TEXT_PRIMARY; + } + s->cur_col++; + } + welcome++; + } + return s; +} + +static void bash_putc(bash_state *s, char c, u32 color) { + if (c == '\n') { s->cur_col = 0; s->cur_row++; return; } + if (s->cur_row >= s->rows) { + /* 滚动 */ + for (int r = 1; r < s->rows; r++) { + for (int c2 = 0; c2 < s->cols; c2++) { + s->ch[(r-1)*TERM_MAX_COLS+c2] = s->ch[r*TERM_MAX_COLS+c2]; + s->fg[(r-1)*TERM_MAX_COLS+c2] = s->fg[r*TERM_MAX_COLS+c2]; + } + } + for (int c2 = 0; c2 < s->cols; c2++) { + s->ch[(s->rows-1)*TERM_MAX_COLS+c2] = ' '; + } + s->cur_row = s->rows - 1; + } + if (s->cur_col >= s->cols) { s->cur_col = 0; s->cur_row++; } + int idx = s->cur_row * TERM_MAX_COLS + s->cur_col; + s->ch[idx] = (u8)c; s->fg[idx] = color; + s->cur_col++; +} + +static void bash_puts(bash_state *s, const char *str, u32 color) { + while (*str) bash_putc(s, *str++, color); +} + +static void bash_execute(bash_state *s, const char *cmd) { + while (*cmd == ' ') cmd++; + bash_puts(s, BASH_PROMPT, DS_DARK_PROMPT); + bash_puts(s, cmd, DS_DARK_TEXT_PRIMARY); + bash_putc(s, '\n', 0); + + if (*cmd == 0) return; + if (cmd[0]=='h'&&cmd[1]=='e'&&cmd[2]=='l'&&cmd[3]=='p') { + bash_puts(s, " help clear echo version\n", DP_SUCCESS); + bash_puts(s, " uname date about reboot\n", DP_SUCCESS); + bash_puts(s, " halt pwd whoami id\n", DP_SUCCESS); + } else if (cmd[0]=='c'&&cmd[1]=='l'&&cmd[2]=='e'&&cmd[3]=='a'&&cmd[4]=='r') { + for (int i = 0; i < TERM_MAX_CHARS; i++) { s->ch[i] = ' '; } + s->cur_col = 0; s->cur_row = 0; + } else if (cmd[0]=='v'&&cmd[1]=='e'&&cmd[2]=='r') { + bash_puts(s, "Deshab OS v0.1.0\n", DP_SUCCESS); + } else if (cmd[0]=='u'&&cmd[1]=='n'&&cmd[2]=='a') { + bash_puts(s, "Deshab\n", DS_DARK_TEXT_PRIMARY); + } else if (cmd[0]=='a'&&cmd[1]=='b'&&cmd[2]=='o'&&cmd[3]=='u'&&cmd[4]=='t') { + bash_puts(s, "Deshab OS - SAS-R0 Kernel\n", DP_SEAL_300); + bash_puts(s, "Sealed Arc UI Design\n", DP_ARC_300); + } else if (cmd[0]=='e'&&cmd[1]=='c'&&cmd[2]=='h'&&cmd[3]=='o') { + const char *arg = cmd + 4; + while (*arg == ' ') arg++; + bash_puts(s, arg, DS_DARK_TEXT_PRIMARY); + bash_putc(s, '\n', 0); + } else if (cmd[0]=='p'&&cmd[1]=='w'&&cmd[2]=='d') { + bash_puts(s, "/\n", DS_DARK_TEXT_PRIMARY); + } else if (cmd[0]=='w'&&cmd[1]=='h'&&cmd[2]=='o') { + bash_puts(s, "root\n", DS_DARK_TEXT_PRIMARY); + } else if (cmd[0]=='i'&&cmd[1]=='d') { + bash_puts(s, "uid=0(root) gid=0(root)\n", DS_DARK_TEXT_PRIMARY); + } else if (cmd[0]=='r'&&cmd[1]=='e'&&cmd[2]=='b') { + bash_puts(s, "Rebooting...\n", DS_DARK_PROMPT); + outb(0x64, 0xFE); + for(;;) __asm__("hlt"); + } else if (cmd[0]=='h'&&cmd[1]=='a'&&cmd[2]=='l'&&cmd[3]=='t') { + bash_puts(s, "Halted.\n", DS_DARK_PROMPT); + for(;;) __asm__("hlt"); + } else { + bash_puts(s, "unknown: ", DP_ERROR); + bash_puts(s, cmd, DP_ERROR); + bash_putc(s, '\n', 0); + } +} + +static void bash_on_event(void *state, app_ctx *ctx, desktop_event *ev) { + bash_state *s = (bash_state *)state; + if (ev->type != EV_KEY_DOWN) return; + u8 sc = ev->scancode; + if (sc == 0x1C) { /* Enter */ + s->input_buf[s->input_len] = 0; + bash_execute(s, s->input_buf); + s->input_len = 0; s->input_cursor = 0; + } else if (sc == 0x0E) { /* Backspace */ + if (s->input_cursor > 0) { + for (int i = s->input_cursor-1; i < s->input_len-1; i++) + s->input_buf[i] = s->input_buf[i+1]; + s->input_len--; s->input_cursor--; + } + } else { + char c = scan_to_ascii(sc, ev->shift); + if (c && c >= 32 && c <= 126 && s->input_len < 255) { + for (int i = s->input_len; i > s->input_cursor; i--) + s->input_buf[i] = s->input_buf[i-1]; + s->input_buf[s->input_cursor++] = c; + s->input_len++; + } + } + ctx->win->dirty = 1; +} + +static void bash_on_draw(void *state, app_ctx *ctx) { + bash_state *s = (bash_state *)state; + int cx = ctx->client_x; + int cy = ctx->client_y; + int cw = ctx->client_w; + int ch = ctx->client_h; + + /* 背景 */ + du_fill_rect(&g_fb, cx, cy, cw, ch, DS_DARK_BG_PRIMARY); + + /* 渲染所有字符 */ + for (int r = 0; r < s->rows; r++) { + for (int c = 0; c < s->cols; c++) { + int idx = r * TERM_MAX_COLS + c; + if (s->ch[idx] == ' ') continue; + du_draw_char(&g_fb, s->ch[idx], + cx + (int)DU_SPACE_SM + c * (int)DU_ASCII_STEP, + cy + (int)DU_SPACE_SM + r * (int)DU_ASCII_LINE_H, + s->fg[idx], DS_DARK_BG_PRIMARY); + } + } + + /* 绘制当前输入行 */ + int input_y = cy + (int)DU_SPACE_SM + s->cur_row * (int)DU_ASCII_LINE_H; + /* 提示符 */ + du_draw_string(&g_fb, BASH_PROMPT, + cx + (int)DU_SPACE_SM, input_y, + DS_DARK_PROMPT, DS_DARK_BG_PRIMARY, DU_ASCII_STEP); + /* 输入内容 */ + du_draw_string(&g_fb, s->input_buf, + cx + (int)DU_SPACE_SM + s->prompt_len * (int)DU_ASCII_STEP, + input_y, + DS_DARK_TEXT_PRIMARY, DS_DARK_BG_PRIMARY, DU_ASCII_STEP); + + /* 光标 */ + int cursor_x = cx + (int)DU_SPACE_SM + (s->prompt_len + s->input_cursor) * (int)DU_ASCII_STEP; + du_fill_rect(&g_fb, cursor_x, input_y + (int)DU_ASCII_CELL_H - 3, + (int)DU_ASCII_CELL_W, 2, DS_DARK_CURSOR); +} + +static void bash_on_destroy(void *state) { + (void)state; +} + +/* ============================================================ + * 内置应用:文本编辑器 (Editor) + * ============================================================ */ + +#define EDITOR_BUF_SIZE 65536 + +typedef struct { + char text[EDITOR_BUF_SIZE]; + int text_len; + int cursor_pos; + int scroll_y; /* 行偏移 */ + int cursor_line; + int cursor_col; + int modified; + char filename[64]; +} editor_state; + +static void editor_recalc_cursor(editor_state *s) { + s->cursor_line = 0; s->cursor_col = 0; + for (int i = 0; i < s->cursor_pos && i < s->text_len; i++) { + if (s->text[i] == '\n') { s->cursor_line++; s->cursor_col = 0; } + else s->cursor_col++; + } +} + +static int editor_line_start(editor_state *s, int line) { + int l = 0; + for (int i = 0; i < s->text_len; i++) { + if (l == line) return i; + if (s->text[i] == '\n') l++; + } + return s->text_len; +} + +static void *editor_on_create(app_ctx *ctx) { + editor_state *s = (editor_state *)0x5000000; /* 固定地址 */ + s->text_len = 0; s->cursor_pos = 0; + s->scroll_y = 0; s->modified = 0; + s->filename[0] = 0; + + /* 默认内容 */ + const char *sample = "# Welcome to Deshab Editor\n#\n\nStart typing here...\n"; + while (*sample && s->text_len < EDITOR_BUF_SIZE - 1) { + s->text[s->text_len++] = *sample++; + } + s->cursor_pos = s->text_len; + editor_recalc_cursor(s); + + /* 设置窗口标题 */ + du_draw_string(&g_fb, "", 0, 0, 0, 0, 0); /* 占位 */ + for (int i = 0; i < 63; i++) ctx->win->title[i] = 0; + const char *t = "Editor - untitled"; + for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i]; + + (void)ctx; + return s; +} + +static void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev) { + editor_state *s = (editor_state *)state; + if (ev->type != EV_KEY_DOWN) return; + u8 sc = ev->scancode; + int e0 = 0; /* TODO: 扩展键处理 */ + + if (sc == 0x1C) { /* Enter */ + if (s->text_len < EDITOR_BUF_SIZE - 1) { + for (int i = s->text_len; i > s->cursor_pos; i--) + s->text[i] = s->text[i-1]; + s->text[s->cursor_pos] = '\n'; + s->text_len++; s->cursor_pos++; + s->modified = 1; + } + } else if (sc == 0x0E) { /* Backspace */ + if (s->cursor_pos > 0) { + for (int i = s->cursor_pos - 1; i < s->text_len - 1; i++) + s->text[i] = s->text[i+1]; + s->text_len--; s->cursor_pos--; + s->modified = 1; + } + } else if (sc == 0x48) { /* Up */ + if (s->cursor_line > 0) { + int prev_start = editor_line_start(s, s->cursor_line - 1); + int col = s->cursor_col; + s->cursor_pos = prev_start; + for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++) + s->cursor_pos++; + } + } else if (sc == 0x50) { /* Down */ + if (s->cursor_line < 999) { + int next_start = editor_line_start(s, s->cursor_line + 1); + int col = s->cursor_col; + s->cursor_pos = next_start; + for (int i = 0; i < col && s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n'; i++) + s->cursor_pos++; + } + } else if (sc == 0x4B) { /* Left */ + if (s->cursor_pos > 0) s->cursor_pos--; + } else if (sc == 0x4D) { /* Right */ + if (s->cursor_pos < s->text_len) s->cursor_pos++; + } else if (sc == 0x47) { /* Home */ + s->cursor_pos = editor_line_start(s, s->cursor_line); + } else if (sc == 0x4F) { /* End */ + s->cursor_pos = editor_line_start(s, s->cursor_line); + while (s->cursor_pos < s->text_len && s->text[s->cursor_pos] != '\n') + s->cursor_pos++; + } else { + char c = scan_to_ascii(sc, ev->shift); + if (c && c >= 32 && c <= 126 && s->text_len < EDITOR_BUF_SIZE - 1) { + for (int i = s->text_len; i > s->cursor_pos; i--) + s->text[i] = s->text[i-1]; + s->text[s->cursor_pos] = c; + s->text_len++; s->cursor_pos++; + s->modified = 1; + } + } + editor_recalc_cursor(s); + ctx->win->dirty = 1; + (void)e0; +} + +static void editor_on_draw(void *state, app_ctx *ctx) { + editor_state *s = (editor_state *)state; + int cx = ctx->client_x; + int cy = ctx->client_y; + int cw = ctx->client_w; + int ch = ctx->client_h; + + int line_num_w = 5 * (int)DU_ASCII_STEP + (int)DU_SPACE_SM; + int text_area_x = cx + line_num_w; + int text_area_w = cw - line_num_w; + int visible_rows = ch / (int)DU_ASCII_LINE_H; + int visible_cols = text_area_w / (int)DU_ASCII_STEP; + + /* 背景 */ + du_fill_rect(&g_fb, cx, cy, cw, ch, DS_DARK_BG_SECONDARY); + + /* 自动滚动 */ + if (s->cursor_line < s->scroll_y) s->scroll_y = s->cursor_line; + if (s->cursor_line >= s->scroll_y + visible_rows) s->scroll_y = s->cursor_line - visible_rows + 1; + + /* 渲染可见行 */ + int line = 0; + int pos = 0; + for (int i = 0; i < s->text_len || line <= s->cursor_line; ) { + if (line >= s->scroll_y + visible_rows) break; + + if (line >= s->scroll_y) { + int screen_y = cy + (line - s->scroll_y) * (int)DU_ASCII_LINE_H; + + /* 行号 */ + char ln[8]; + ln[0] = '0' + ((line+1)/100)%10; + ln[1] = '0' + ((line+1)/10)%10; + ln[2] = '0' + (line+1)%10; + ln[3] = 0; + du_draw_string(&g_fb, ln, cx + 2, screen_y, + DS_DARK_TEXT_DIM, DS_DARK_BG_SECONDARY, DU_ASCII_STEP); + + /* 行号分隔线 */ + du_fill_rect(&g_fb, cx + line_num_w - 2, screen_y, 1, + (int)DU_ASCII_CELL_H, DS_DARK_DIVIDER); + + /* 行文本 */ + int col = 0; + while (pos < s->text_len && s->text[pos] != '\n' && col < visible_cols) { + du_draw_char(&g_fb, s->text[pos], + text_area_x + col * (int)DU_ASCII_STEP, + screen_y, + DS_DARK_TEXT_PRIMARY, DS_DARK_BG_SECONDARY); + col++; pos++; + } + if (pos < s->text_len && s->text[pos] == '\n') pos++; + } else { + while (pos < s->text_len && s->text[pos] != '\n') pos++; + if (pos < s->text_len) pos++; + } + line++; + if (pos >= s->text_len && line > s->cursor_line) break; + } + + /* 光标 */ + if (s->cursor_line >= s->scroll_y && s->cursor_line < s->scroll_y + visible_rows) { + int cursor_y = cy + (s->cursor_line - s->scroll_y) * (int)DU_ASCII_LINE_H; + int cursor_x = text_area_x + s->cursor_col * (int)DU_ASCII_STEP; + du_fill_rect(&g_fb, cursor_x, cursor_y, 2, (int)DU_ASCII_CELL_H, DS_DARK_CURSOR); + } + + /* 状态栏 */ + int status_y = cy + ch - 20; + du_fill_rect(&g_fb, cx, status_y, cw, 20, DP_ABYSS_800); + char status[64]; + int sp = 0; + /* 行:列 */ + const char *p1 = "L:"; while(*p1) status[sp++] = *p1++; + status[sp++] = '0' + (s->cursor_line+1)/10%10; + status[sp++] = '0' + (s->cursor_line+1)%10; + const char *p2 = " C:"; while(*p2) status[sp++] = *p2++; + status[sp++] = '0' + (s->cursor_col+1)/10%10; + status[sp++] = '0' + (s->cursor_col+1)%10; + if (s->modified) { const char *m = " *"; while(*m) status[sp++] = *m++; } + status[sp] = 0; + du_draw_string(&g_fb, status, cx + 4, status_y + 1, + DS_DARK_TEXT_DIM, DP_ABYSS_800, DU_ASCII_STEP); +} + +static void editor_on_destroy(void *state) { (void)state; } + +/* ============================================================ + * 内置应用:计算器 (Calculator) + * ============================================================ */ + +typedef struct { + char display[32]; + int display_len; + i64 accumulator; + i64 current; + int op; /* 0=none, 1=+, 2=-, 3=*, 4=/ */ + int new_number; +} calc_state; + +static void calc_update_display(calc_state *s) { + s->display_len = 0; + i64 val = s->current; + if (val < 0) { s->display[s->display_len++] = '-'; val = -val; } + if (val == 0) { s->display[s->display_len++] = '0'; } + else { + char tmp[20]; int tl = 0; + while (val > 0) { tmp[tl++] = '0' + (int)(val % 10); val /= 10; } + for (int i = tl-1; i >= 0; i--) s->display[s->display_len++] = tmp[i]; + } + s->display[s->display_len] = 0; +} + +static void *calc_on_create(app_ctx *ctx) { + calc_state *s = (calc_state *)0x6000000; + s->accumulator = 0; s->current = 0; + s->op = 0; s->new_number = 1; + calc_update_display(s); + const char *t = "Calculator"; + for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i]; + return s; +} + +static void calc_on_event(void *state, app_ctx *ctx, desktop_event *ev) { + calc_state *s = (calc_state *)state; + if (ev->type != EV_KEY_DOWN) return; + char c = scan_to_ascii(ev->scancode, ev->shift); + if (c >= '0' && c <= '9') { + if (s->new_number) { s->current = 0; s->new_number = 0; } + s->current = s->current * 10 + (c - '0'); + } else if (c == '+') { s->accumulator = s->current; s->op = 1; s->new_number = 1; } + else if (c == '-') { s->accumulator = s->current; s->op = 2; s->new_number = 1; } + else if (c == '*') { s->accumulator = s->current; s->op = 3; s->new_number = 1; } + else if (c == '/') { s->accumulator = s->current; s->op = 4; s->new_number = 1; } + else if (c == '=' || c == '\n') { + switch (s->op) { + case 1: s->current = s->accumulator + s->current; break; + case 2: s->current = s->accumulator - s->current; break; + case 3: s->current = s->accumulator * s->current; break; + case 4: s->current = (s->current != 0) ? s->accumulator / s->current : 0; break; + } + s->op = 0; s->new_number = 1; + } else if (c == 'c' || c == 'C') { + s->current = 0; s->accumulator = 0; s->op = 0; s->new_number = 1; + } + calc_update_display(s); + ctx->win->dirty = 1; +} + +static void calc_on_draw(void *state, app_ctx *ctx) { + calc_state *s = (calc_state *)state; + int cx = ctx->client_x; + int cy = ctx->client_y; + int cw = ctx->client_w; + int ch = ctx->client_h; + + du_fill_rect(&g_fb, cx, cy, cw, ch, DS_DARK_BG_SECONDARY); + + /* 显示屏 */ + int disp_h = 40; + du_fill_rounded_rect(&g_fb, cx + 8, cy + 8, cw - 16, disp_h, + DP_ABYSS_900, DU_RADIUS_MD); + du_rect_outline(&g_fb, cx + 8, cy + 8, cw - 16, disp_h, + DS_DARK_BORDER, DU_RADIUS_MD); + /* 数字右对齐 */ + int text_x = cx + cw - 16 - s->display_len * (int)DU_ASCII_STEP - 8; + du_draw_string(&g_fb, s->display, text_x, cy + 16, + DS_DARK_TEXT_PRIMARY, DP_ABYSS_900, DU_ASCII_STEP); + + /* 按钮网格 4×4 */ + const char *btn_labels[] = { + "C", "+/-", "%", "/", + "7", "8", "9", "*", + "4", "5", "6", "-", + "1", "2", "3", "+", + "0", ".", "=", 0 + }; + int btn_w = (cw - 16 - 12) / 4; + int btn_h = 32; + int btn_y = cy + 8 + disp_h + 8; + for (int i = 0; i < 19; i++) { + int row = i / 4, col = i % 4; + int bx = cx + 8 + col * (btn_w + 3); + int by = btn_y + row * (btn_h + 3); + if (by + btn_h > cy + ch - 4) break; + u32 bg = (i < 4) ? DP_ABYSS_600 : + (i % 4 == 3) ? DS_DARK_ACCENT : + (i == 18) ? DP_SUCCESS : + DP_ABYSS_700; + du_fill_rounded_rect(&g_fb, bx, by, btn_w, btn_h, bg, DU_RADIUS_SM); + du_draw_string(&g_fb, btn_labels[i], + bx + (btn_w - 1*(int)DU_ASCII_STEP)/2, + by + (btn_h - (int)DU_ASCII_CELL_H)/2, + DS_DARK_TEXT_PRIMARY, bg, DU_ASCII_STEP); + } +} + +static void calc_on_destroy(void *state) { (void)state; } + +/* ============================================================ + * 应用注册 + * ============================================================ */ + +static void register_apps(void) { + /* 0: Terminal */ + g_apps[0] = (app_descriptor){ + "bash", "Terminal", 640, 440, + bash_on_create, bash_on_event, bash_on_draw, bash_on_destroy + }; + g_app_count++; + + /* 1: Editor */ + g_apps[1] = (app_descriptor){ + "editor", "Editor", 600, 450, + editor_on_create, editor_on_event, editor_on_draw, editor_on_destroy + }; + g_app_count++; + + /* 2: File Manager */ + g_apps[2] = (app_descriptor){ + "fileman", "Files", 500, 400, + 0, 0, 0, 0 /* placeholder */ + }; + g_app_count++; + + /* 3: Calculator */ + g_apps[3] = (app_descriptor){ + "calc", "Calculator", 280, 400, + calc_on_create, calc_on_event, calc_on_draw, calc_on_destroy + }; + g_app_count++; +} + +static void setup_desktop_icons(void) { + int start_x = (int)g_fb_w - DESKTOP_ICON_W - ICON_GUTTER_X; + int y = ICON_GUTTER_Y; + for (int i = 0; i < g_app_count; i++) { + if (!g_apps[i].on_create) continue; + g_icons[g_icon_count].app_id = i; + g_icons[g_icon_count].x = start_x; + g_icons[g_icon_count].y = y; + g_icons[g_icon_count].label = g_apps[i].display_name; + g_icon_count++; + y += DESKTOP_ICON_H; + } +} + +/* ============================================================ + * 应用启动 + * ============================================================ */ + +static void launch_app(int app_id) { + if (app_id < 0 || app_id >= g_app_count) return; + app_descriptor *app = &g_apps[app_id]; + if (!app->on_create) return; + + /* 计算窗口位置(居中偏移) */ + int wx = 80 + g_win_count * 30; + int wy = 40 + g_win_count * 30; + if (wx + (int)app->default_w > (int)g_fb_w - 20) wx = 80; + if (wy + (int)app->default_h > (int)g_fb_h - TASKBAR_H - 20) wy = 40; + + desktop_window *win = win_create(app_id, app->display_name, + wx, wy, (int)app->default_w, (int)app->default_h); + if (!win) return; + + /* 构建应用上下文 */ + app_ctx ctx; + ctx.fb = &g_fb; + ctx.win = win; + ctx.client_x = win->x + BORDER_W; + ctx.client_y = win->y + TITLEBAR_H; + ctx.client_w = win->w - 2 * BORDER_W; + ctx.client_h = win->h - TITLEBAR_H - BORDER_W; + ctx.should_exit = 0; + ctx.block_read = g_block_read; + ctx.kernel_api = g_kernel_api; + + win->app_state = app->on_create(&ctx); + win->dirty = 1; + slog("app launched"); +} + +/* ============================================================ + * 全屏重绘 + * ============================================================ */ + +static void redraw_all(void) { + /* 渐变背景 */ + du_fill_bg_gradient(&g_fb, DP_ABYSS_900, DP_ABYSS_800); + + /* 桌面图标 */ + draw_desktop_icons(); + + /* 窗口(从底到顶) */ + for (int i = 0; i < g_win_count; i++) { + desktop_window *w = &g_windows[i]; + if (w->minimized || !w->visible) continue; + + /* 窗口客户区背景 */ + int cx = w->x + BORDER_W; + int cy = w->y + TITLEBAR_H; + int cw = w->w - 2 * BORDER_W; + int ch = w->h - TITLEBAR_H - BORDER_W; + + /* 调用应用绘制 */ + if (w->app_id >= 0 && w->app_id < g_app_count && g_apps[w->app_id].on_draw && w->app_state) { + app_ctx ctx; + ctx.fb = &g_fb; + ctx.win = w; + ctx.client_x = cx; ctx.client_y = cy; + ctx.client_w = cw; ctx.client_h = ch; + ctx.should_exit = 0; + ctx.block_read = g_block_read; + ctx.kernel_api = g_kernel_api; + g_apps[w->app_id].on_draw(w->app_state, &ctx); + } + + /* 窗口装饰(在应用内容之上) */ + draw_window_frame(w); + } + + /* 任务栏 */ + draw_taskbar(); + + /* 鼠标光标 */ + cursor_save_bg(g_mouse_x, g_mouse_y); + cursor_draw(g_mouse_x, g_mouse_y); +} + +/* ============================================================ + * 主循环 + * ============================================================ */ + +__attribute__((visibility("default"))) +void dsk_entry(const dsk_boot_context *ctx) { + __asm__ volatile("cli"); + slog("boot"); + + if (!ctx || ctx->magic != 0x44534B31424F4F54ULL) { + slog("bad context"); + for(;;) __asm__("hlt"); + } + + g_fb_addr = ctx->framebuffer_address; + g_fb_w = ctx->framebuffer_width; + g_fb_h = ctx->framebuffer_height; + g_fb_pitch = ctx->framebuffer_pitch; + g_kernel_api = (const void *)ctx->dkm_kernel_api; + g_block_read = (desktop_block_read_fn)(*(u64 *)(ctx->dkm_kernel_api + 0xA8)); + + du_context_init(&g_fb, g_fb_addr, g_fb_w, g_fb_h, g_fb_pitch); + + /* 初始化 PS/2 鼠标 */ + ps2_mouse_init(); + + /* 注册应用 */ + register_apps(); + setup_desktop_icons(); + + /* 初始绘制 */ + redraw_all(); + slog("desktop ready"); + + /* 主事件循环 */ + for (;;) { + int need_redraw = 0; + g_mouse_has_pkt = 0; + + /* 轮询 PS/2 鼠标 */ + if (ps2_mouse_poll()) { + need_redraw = 1; + + /* 拖拽处理 */ + if (g_dragging && g_drag_win >= 0) { + desktop_window *w = win_find(g_drag_win); + if (w) { + w->x = g_mouse_x - g_drag_off_x; + w->y = g_mouse_y - g_drag_off_y; + } + } + + /* 鼠标点击事件 */ + if (g_mouse_btn) { + /* 检查任务栏点击 */ + int tb_y = (int)g_fb_h - TASKBAR_H; + if (g_mouse_y >= tb_y) { + /* 快速启动图标 */ + int qx = 64; + for (int i = 0; i < g_app_count && i < 4; i++) { + int icon_x = qx + i * 40; + if (g_mouse_x >= icon_x && g_mouse_x < icon_x + 40) { + launch_app(i); + need_redraw = 1; + break; + } + } + } else { + /* 窗口命中测试 */ + desktop_window *hit = win_find_at(g_mouse_x, g_mouse_y); + if (hit) { + win_bring_to_front(hit); + /* 取消其他焦点 */ + for (int i = 0; i < g_win_count; i++) + g_windows[i].focused = (g_windows[i].id == hit->id); + g_focused_win = hit->id; + + /* 检查关闭按钮 */ + if (win_hit_close_btn(hit, g_mouse_x, g_mouse_y)) { + win_destroy(hit); + need_redraw = 1; + } + /* 检查标题栏拖拽 */ + else if (win_hit_titlebar(hit, g_mouse_x, g_mouse_y)) { + g_dragging = 1; + g_drag_win = hit->id; + g_drag_off_x = g_mouse_x - hit->x; + g_drag_off_y = g_mouse_y - hit->y; + } + /* 传递鼠标事件给应用 */ + else if (hit->app_id >= 0 && g_apps[hit->app_id].on_event && hit->app_state) { + desktop_event dev; + dev.type = EV_MOUSE_DOWN; + dev.mx = g_mouse_x; dev.my = g_mouse_y; + dev.button = 0; + app_ctx actx; + actx.fb = &g_fb; actx.win = hit; + actx.client_x = hit->x + BORDER_W; + actx.client_y = hit->y + TITLEBAR_H; + actx.client_w = hit->w - 2*BORDER_W; + actx.client_h = hit->h - TITLEBAR_H - BORDER_W; + actx.should_exit = 0; + actx.block_read = g_block_read; + actx.kernel_api = g_kernel_api; + g_apps[hit->app_id].on_event(hit->app_state, &actx, &dev); + } + } else { + /* 桌面图标点击 */ + for (int i = 0; i < g_icon_count; i++) { + int ix = g_icons[i].x, iy = g_icons[i].y; + if (g_mouse_x >= ix && g_mouse_x < ix + 32 && + g_mouse_y >= iy && g_mouse_y < iy + 32) { + launch_app(g_icons[i].app_id); + need_redraw = 1; + break; + } + } + } + } + } else { + if (g_dragging) { + g_dragging = 0; + g_drag_win = -1; + } + } + } + + /* 轮询 PS/2 键盘 */ + { + u8 st = inb(0x64); + if ((st & 1) && !(st & 0x20)) { + u8 data = inb(0x60); + u8 sc = data; + if (sc == 0xE0) { g_e0 = 1; goto next; } + if (sc == 0x2A || sc == 0x36) { g_shift = 1; goto next; } + if (sc == 0xAA || sc == 0xB6) { g_shift = 0; goto next; } + if (sc & 0x80) { g_e0 = 0; goto next; } + + /* 发送键盘事件到焦点窗口 */ + if (g_focused_win >= 0) { + desktop_window *fw = win_find(g_focused_win); + if (fw && fw->app_id >= 0 && g_apps[fw->app_id].on_event && fw->app_state) { + desktop_event dev; + dev.type = EV_KEY_DOWN; + dev.mx = g_mouse_x; dev.my = g_mouse_y; + dev.button = 0; + dev.scancode = sc; + dev.shift = g_shift; + dev.ctrl = g_ctrl; + dev.alt = g_alt; + app_ctx actx; + actx.fb = &g_fb; actx.win = fw; + actx.client_x = fw->x + BORDER_W; + actx.client_y = fw->y + TITLEBAR_H; + actx.client_w = fw->w - 2*BORDER_W; + actx.client_h = fw->h - TITLEBAR_H - BORDER_W; + actx.should_exit = 0; + actx.block_read = g_block_read; + actx.kernel_api = g_kernel_api; + g_apps[fw->app_id].on_event(fw->app_state, &actx, &dev); + need_redraw = 1; + } + } + g_e0 = 0; + } + } +next: + + if (need_redraw) { + cursor_restore_bg(); + redraw_all(); + } + + __asm__("pause"); + } +} diff --git a/CODE/dsk/main.c b/CODE/dsk/main.c index 22d94c7..9eac4bb 100644 --- a/CODE/dsk/main.c +++ b/CODE/dsk/main.c @@ -373,31 +373,31 @@ static void dsk_spinner_and_check(u32 *fb_arg, i64 cx, i64 cy, i64 r, i64 thk, if(fe_entry&&frame>150){logl("[DSK] jumping to FirstInit"); /* was 600 */ void(*entry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))fe_entry; entry(ctx); - logl("[DSK] FirstInit returned, loading shell"); - /* FirstInit 返回后加载 shell */ - char sh[12]; /* SHELL ELF */ - sh[0]='S';sh[1]='H';sh[2]='E';sh[3]='L';sh[4]='L';sh[5]=' ';sh[6]=' ';sh[7]=' ';sh[8]='E';sh[9]='L';sh[10]='F'; - u8 *sd=0; u32 ss=0; void *se=0; - if(fat32_read_root_file(sh,&sd,&ss)==0){ - if(dsk_load_elf(sd,ss,&se)==0){ - logl("[DSK] jumping to shell"); - void(*sentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))se; - sentry(ctx); + logl("[DSK] FirstInit returned, loading desktop"); + /* FirstInit 返回后加载 desktop */ + char dn[12]; /* DESKTOP ELF */ + dn[0]='D';dn[1]='E';dn[2]='S';dn[3]='K';dn[4]='T';dn[5]='O';dn[6]='P';dn[7]=' ';dn[8]='E';dn[9]='L';dn[10]='F'; + u8 *dd=0; u32 ds=0; void *de=0; + if(fat32_read_root_file(dn,&dd,&ds)==0){ + if(dsk_load_elf(dd,ds,&de)==0){ + logl("[DSK] jumping to desktop"); + void(*dentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))de; + dentry(ctx); } } for(;;)__asm__("hlt"); } - /* 非首次启动:直接加载 shell */ + /* 非首次启动:直接加载 desktop */ if(checked&&!fe_entry&&frame>150){ - logl("[DSK] loading shell directly (non-first boot)"); - char sh[12]; /* SHELL ELF */ - sh[0]='S';sh[1]='H';sh[2]='E';sh[3]='L';sh[4]='L';sh[5]=' ';sh[6]=' ';sh[7]=' ';sh[8]='E';sh[9]='L';sh[10]='F'; - u8 *sd=0; u32 ss=0; void *se=0; - if(fat32_read_root_file(sh,&sd,&ss)==0){ - if(dsk_load_elf(sd,ss,&se)==0){ - logl("[DSK] jumping to shell"); - void(*sentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))se; - sentry(ctx); + logl("[DSK] loading desktop directly (non-first boot)"); + char dn[12]; /* DESKTOP ELF */ + dn[0]='D';dn[1]='E';dn[2]='S';dn[3]='K';dn[4]='T';dn[5]='O';dn[6]='P';dn[7]=' ';dn[8]='E';dn[9]='L';dn[10]='F'; + u8 *dd=0; u32 ds=0; void *de=0; + if(fat32_read_root_file(dn,&dd,&ds)==0){ + if(dsk_load_elf(dd,ds,&de)==0){ + logl("[DSK] jumping to desktop"); + void(*dentry)(const dsk_boot_context*)=(void(*)(const dsk_boot_context*))de; + dentry(ctx); } } for(;;)__asm__("hlt"); diff --git a/CODE/shell/main.c b/CODE/shell/main.c index 9c8c4c8..ed8a9da 100644 --- a/CODE/shell/main.c +++ b/CODE/shell/main.c @@ -1,11 +1,10 @@ /* Deshab Shell — 命令行交互界面 * 接收 PS/2 键盘输入,执行内置命令。 - * 显示在 framebuffer 上,使用 Consolas 18px 等宽字体。 + * 使用 Deshab "Sealed Arc" 视觉风格系统。 */ #include "../UTSM/include/utsm/dsk.h" -typedef signed char i8; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; @@ -24,28 +23,37 @@ static void sputc(char c) { static void swrite(const char *s) { while(*s) { if(*s=='\n')sputc('\r'); sputc(*s++); } } static void logl(const char *s) { swrite(s); swrite("\n"); } -/* ASCII 字体 — Consolas 18px 灰度位图(与 FirstInit 共享) */ +/* ASCII 字体 — 必须在 deshab_ui.h 之前包含,因为 du_draw_char 引用 g_ascii */ #include "../firstInit/ascii_bitmaps.c" +#include "../UTSM/include/utsm/deshab_ui.h" -#define ASCII_W 11 -#define ASCII_H 18 -#define ASCII_STEP 11 +/* ---- 风格令牌(引用 deshab_ui.h 语义色) ---- */ +#define BG_COLOR DS_DARK_BG_PRIMARY +#define TEXT_FG DS_DARK_TEXT_PRIMARY +#define PROMPT_FG DS_DARK_PROMPT +#define ERROR_FG DP_ERROR +#define OK_FG DP_SUCCESS +#define DIM_FG DS_DARK_TEXT_DIM +#define ACCENT_FG DS_DARK_ACCENT +#define DECORATIVE_FG DS_DARK_DECORATIVE + +/* 标题栏参数 */ +#define TITLEBAR_H 28 +#define TITLEBAR_BG DP_ABYSS_800 +#define TITLEBAR_FG DS_DARK_TEXT_SECONDARY +#define TITLEBAR_ACCENT DS_DARK_ACCENT + +/* 状态栏参数 */ +#define STATUSBAR_H 22 +#define STATUSBAR_BG DP_ABYSS_800 /* 终端尺寸 */ #define TERM_COLS 100 #define TERM_ROWS 35 #define TERM_MAX_CHARS (TERM_COLS * TERM_ROWS) -/* 颜色 — 深色主题,类似经典终端 */ -#define BG_COLOR 0xFF0A1428u /* 深蓝黑 */ -#define TEXT_FG 0xFFE0E8F0u /* 浅灰白 */ -#define PROMPT_FG 0xFF66CCFFu /* 青色提示符 */ -#define ERROR_FG 0xFFFF6680u /* 红色错误 */ -#define OK_FG 0xFF66FF99u /* 绿色成功 */ -#define DIM_FG 0xFF7080A0u /* 灰色注释 */ - +static du_context g_ctx; static u64 fb_a, fb_w, fb_h, fb_p; -static u32 *g_fb; /* 终端缓冲区 — 字符 + 颜色 */ static u8 term_ch[TERM_MAX_CHARS]; @@ -56,61 +64,18 @@ static int cur_col = 0; static int cur_row = 0; static int cur_visible = 1; -/* 颜色混合 */ -static u32 blend(u32 c1, u32 c2, u32 a) { - u32 na=256-a; - u32 r=((c1&0xFF)*na+(c2&0xFF)*a)>>8; - u32 g=(((c1>>8)&0xFF)*na+((c2>>8)&0xFF)*a)>>8; - u32 b=(((c1>>16)&0xFF)*na+((c2>>16)&0xFF)*a)>>8; - return 0xFF000000|(b<<16)|(g<<8)|r; -} - -/* ---- 字符渲染(与 FirstInit 类似,但用纯色背景) ---- */ -static void fb_char(u32 *fb, u32 ch, i64 x, i64 y, u32 fg, u32 bg) { - if (ch < ' ' || ch > '~') ch = ' '; - u32 idx = (u32)(ch - ' '); - const u8 *g = g_ascii[idx]; - for (i64 r=0; r= fb_h) continue; - u32 *line = (u32 *)((u8 *)fb + (u64)yy * fb_p); - for (i64 c=0; c= fb_w) continue; - line[(u64)xx] = (a == 255) ? fg : blend(bg, fg, a); - } - } -} - -static void fill_rect(u32 *fb, i64 x, i64 y, i64 w, i64 h, u32 color) { - for (i64 r=0; r= fb_h) continue; - u32 *line = (u32 *)((u8 *)fb + (u64)yy * fb_p); - for (i64 c=0; c= fb_w) continue; - line[(u64)xx] = color; - } - } -} - -/* ---- 终端逻辑 ---- */ -static int term_start_x, term_start_y; /* 像素坐标 */ +/* ---- 终端像素坐标 ---- */ +static int term_start_x, term_start_y; static void term_init_pos(void) { - /* 居中布局,每字符 11px 宽,行高 20px */ - int margin_x = 16; - int margin_y = 16; + int margin_x = (int)DU_SPACE_MD; term_start_x = margin_x; - term_start_y = margin_y; - /* 自适应行数/列数到 framebuffer */ + /* 标题栏下方开始 */ + term_start_y = TITLEBAR_H + (int)DU_SPACE_SM; int avail_w = (int)fb_w - margin_x * 2; - int avail_h = (int)fb_h - margin_y * 2; - term_w = avail_w / ASCII_STEP; - term_h = avail_h / (ASCII_H + 2); + int avail_h = (int)fb_h - term_start_y - STATUSBAR_H - (int)DU_SPACE_SM; + term_w = avail_w / (int)DU_ASCII_STEP; + term_h = avail_h / (int)DU_ASCII_LINE_H; if (term_w < 10) term_w = 10; if (term_h < 5) term_h = 5; if (term_w > TERM_COLS) term_w = TERM_COLS; @@ -127,7 +92,6 @@ static void term_clear(void) { } static void term_scroll(void) { - /* 向上滚动一行 */ for (int r = 1; r < term_h; r++) { for (int c = 0; c < term_w; c++) { int src = r * TERM_COLS + c; @@ -136,7 +100,6 @@ static void term_scroll(void) { term_fg[dst] = term_fg[src]; } } - /* 清空最后一行 */ for (int c = 0; c < term_w; c++) { term_ch[(term_h-1) * TERM_COLS + c] = ' '; term_fg[(term_h-1) * TERM_COLS + c] = TEXT_FG; @@ -168,57 +131,96 @@ static void term_putc_color(char c, u32 color) { } static void term_putc(char c) { term_putc_color(c, TEXT_FG); } +static void term_puts_color(const char *s, u32 color) { while (*s) term_putc_color(*s++, color); } +static void term_puts(const char *s) { term_puts_color(s, TEXT_FG); } -static void term_puts_color(const char *s, u32 color) { - while (*s) term_putc_color(*s++, color); +/* ---- 标题栏渲染 ---- */ +static void draw_titlebar(void) { + du_fill_rect(&g_ctx, 0, 0, (i64)fb_w, TITLEBAR_H, TITLEBAR_BG); + /* 底部强调线 */ + du_fill_rect(&g_ctx, 0, TITLEBAR_H - 2, (i64)fb_w, 2, TITLEBAR_ACCENT); + /* 左侧标题文字 */ + du_draw_string(&g_ctx, "Deshab Shell", + (i64)DU_SPACE_MD, (TITLEBAR_H - (i64)DU_ASCII_CELL_H) / 2, + DS_DARK_TEXT_PRIMARY, TITLEBAR_BG, DU_ASCII_STEP); + /* 右侧版本号 */ + du_draw_string(&g_ctx, "v0.1.0", + (i64)fb_w - 6 * (i64)DU_ASCII_STEP - (i64)DU_SPACE_MD, + (TITLEBAR_H - (i64)DU_ASCII_CELL_H) / 2, + DS_DARK_TEXT_DIM, TITLEBAR_BG, DU_ASCII_STEP); } -static void term_puts(const char *s) { term_puts_color(s, TEXT_FG); } +/* ---- 状态栏渲染 ---- */ +static void draw_statusbar(void) { + i64 sy = (i64)fb_h - STATUSBAR_H; + du_fill_rect(&g_ctx, 0, sy, (i64)fb_w, STATUSBAR_H, STATUSBAR_BG); + /* 顶部强调线 */ + du_fill_rect(&g_ctx, 0, sy, (i64)fb_w, 1, DS_DARK_DIVIDER); + /* 左侧:当前路径 */ + du_draw_string(&g_ctx, "/", + (i64)DU_SPACE_MD, sy + (STATUSBAR_H - (i64)DU_ASCII_CELL_H) / 2, + DS_DARK_TEXT_DIM, STATUSBAR_BG, DU_ASCII_STEP); + /* 右侧:root@deshab */ + const char *user = "root@deshab"; + int ulen = 0; + while (user[ulen]) ulen++; + du_draw_string(&g_ctx, user, + (i64)fb_w - ulen * (i64)DU_ASCII_STEP - (i64)DU_SPACE_MD, + sy + (STATUSBAR_H - (i64)DU_ASCII_CELL_H) / 2, + DS_DARK_ACCENT, STATUSBAR_BG, DU_ASCII_STEP); +} static void term_redraw_all(void) { /* 清屏背景 */ - fill_rect(g_fb, 0, 0, (i64)fb_w, (i64)fb_h, BG_COLOR); + du_fill_bg_solid(&g_ctx, BG_COLOR); + /* 标题栏 */ + draw_titlebar(); /* 渲染所有字符 */ for (int r = 0; r < term_h; r++) { for (int c = 0; c < term_w; c++) { int idx = r * TERM_COLS + c; u8 ch = term_ch[idx]; if (ch == ' ') continue; - i64 x = term_start_x + (i64)c * ASCII_STEP; - i64 y = term_start_y + (i64)r * (ASCII_H + 2); - fb_char(g_fb, ch, x, y, term_fg[idx], BG_COLOR); + i64 x = term_start_x + (i64)c * (i64)DU_ASCII_STEP; + i64 y = term_start_y + (i64)r * (i64)DU_ASCII_LINE_H; + du_draw_char(&g_ctx, ch, x, y, term_fg[idx], BG_COLOR); } } - /* 渲染光标 */ + /* 光标 */ if (cur_visible && cur_col < term_w && cur_row < term_h) { - i64 x = term_start_x + (i64)cur_col * ASCII_STEP; - i64 y = term_start_y + (i64)cur_row * (ASCII_H + 2); - fill_rect(g_fb, x, y + ASCII_H - 3, ASCII_W, 2, TEXT_FG); + i64 x = term_start_x + (i64)cur_col * (i64)DU_ASCII_STEP; + i64 y = term_start_y + (i64)cur_row * (i64)DU_ASCII_LINE_H; + du_fill_rect(&g_ctx, x, y + (i64)DU_ASCII_CELL_H - 3, + (i64)DU_ASCII_CELL_W, 2, DS_DARK_CURSOR); } + /* 状态栏 */ + draw_statusbar(); } static void term_redraw_cursor(void) { if (cur_visible && cur_col < term_w && cur_row < term_h) { - i64 x = term_start_x + (i64)cur_col * ASCII_STEP; - i64 y = term_start_y + (i64)cur_row * (ASCII_H + 2); - fill_rect(g_fb, x, y + ASCII_H - 3, ASCII_W, 2, TEXT_FG); + i64 x = term_start_x + (i64)cur_col * (i64)DU_ASCII_STEP; + i64 y = term_start_y + (i64)cur_row * (i64)DU_ASCII_LINE_H; + du_fill_rect(&g_ctx, x, y + (i64)DU_ASCII_CELL_H - 3, + (i64)DU_ASCII_CELL_W, 2, DS_DARK_CURSOR); } } static void term_clear_cursor(void) { if (cur_col < term_w && cur_row < term_h) { - i64 x = term_start_x + (i64)cur_col * ASCII_STEP; - i64 y = term_start_y + (i64)cur_row * (ASCII_H + 2); - fill_rect(g_fb, x, y + ASCII_H - 3, ASCII_W, 2, BG_COLOR); + i64 x = term_start_x + (i64)cur_col * (i64)DU_ASCII_STEP; + i64 y = term_start_y + (i64)cur_row * (i64)DU_ASCII_LINE_H; + du_fill_rect(&g_ctx, x, y + (i64)DU_ASCII_CELL_H - 3, + (i64)DU_ASCII_CELL_W, 2, BG_COLOR); } } /* ---- 输入缓冲 ---- */ static char input_buf[256]; static int input_len = 0; -static int input_cursor = 0; /* 在 input_buf 中的位置(支持左右移动) */ +static int input_cursor = 0; -/* ---- 键盘扫描码 → ASCII(Set 1,与 FirstInit 共享逻辑) ---- */ +/* ---- 键盘扫描码 → ASCII(Set 1) ---- */ static char scan_to_ascii(u8 sc, int shift) { static const char normal[58] = { 0, 27, '1','2','3','4','5','6','7','8','9','0','-','=', 8, '\t', @@ -264,6 +266,7 @@ static void cmd_version(void) { term_puts(" Architecture: x86_64\n"); term_puts(" ABI: DKM (Deshab Kernel Module) v1\n"); term_puts(" Bootloader: Limine\n"); + term_puts_color(" Style: Sealed Arc (Deshab UI)\n", DIM_FG); } static void cmd_uname(void) { @@ -274,20 +277,20 @@ static void cmd_about(void) { term_puts_color("=== Deshab OS ===\n", PROMPT_FG); term_puts("单地址空间 Ring0 内核实验系统\n"); term_puts("架构: UTSM + DSK + DKM 驱动模型\n"); + term_puts_color("视觉: Sealed Arc 设计语言\n", DECORATIVE_FG); term_puts_color("Built with Clang + lld\n", DIM_FG); - term_puts_color("© 2026 Deshab Project\n", DIM_FG); + term_puts_color("(c) 2026 Deshab Project\n", DIM_FG); } static void cmd_clear(void) { term_clear(); + term_redraw_all(); } static void cmd_reboot(void) { term_puts_color("Rebooting...\n", PROMPT_FG); term_redraw_all(); - /* ACPI 重启:写 0x64 = 0xFE 触发键盘控制器复位 */ outb(0x64, 0xFE); - /* 备用:三重故障 */ __asm__ volatile("int $0x03"); for(;;) __asm__("hlt"); } @@ -303,22 +306,18 @@ static void cmd_echo(const char *args) { term_putc('\n'); } -/* 前向声明 — CMOS RTC 读取 */ static u8 read_rtc_reg(u8 reg); static u8 bcd_to_bin_impl(u8 bcd); static void cmd_date(void) { - /* 通过 CMOS RTC 读取日期 */ u8 year = bcd_to_bin_impl(read_rtc_reg(9)); u8 month = bcd_to_bin_impl(read_rtc_reg(8)); u8 day = bcd_to_bin_impl(read_rtc_reg(7)); u8 hour = bcd_to_bin_impl(read_rtc_reg(4)); u8 minute = bcd_to_bin_impl(read_rtc_reg(2)); u8 second = bcd_to_bin_impl(read_rtc_reg(0)); - /* 简单打印 */ char buf[32]; int p = 0; - /* YYYY-MM-DD HH:MM:SS */ buf[p++] = '2'; buf[p++] = '0'; buf[p++] = '0' + year / 10; buf[p++] = '0' + year % 10; buf[p++] = '-'; @@ -349,21 +348,16 @@ static void cmd_not_impl(const char *cmd) { } static void execute_command(const char *cmd) { - /* 跳过前导空格 */ while (*cmd == ' ') cmd++; if (*cmd == 0) return; - /* 找命令名(第一个单词) */ const char *args = cmd; while (*args && *args != ' ') args++; int cmd_name_len = (int)(args - cmd); - /* 复制命令名到本地缓冲 */ char name[32]; if (cmd_name_len >= 32) cmd_name_len = 31; for (int i = 0; i < cmd_name_len; i++) name[i] = cmd[i]; name[cmd_name_len] = 0; - /* 跳过参数前的空格 */ while (*args == ' ') args++; - /* 分派 */ if (str_eq(name, "help") || str_eq(name, "?")) cmd_help(); else if (str_eq(name, "version") || str_eq(name, "ver")) cmd_version(); else if (str_eq(name, "uname")) cmd_uname(); @@ -402,30 +396,26 @@ static void draw_prompt(void) { /* ---- 输入行编辑 ---- */ static void redraw_input_line(void) { - /* 清除当前行从提示符开始的部分并重绘 */ int prompt_len = 0; while (PROMPT[prompt_len]) prompt_len++; - /* 清除整行 */ - i64 y = term_start_y + (i64)cur_row * (ASCII_H + 2); - fill_rect(g_fb, term_start_x, y, (i64)term_w * ASCII_STEP, ASCII_H, BG_COLOR); - /* 重绘提示符 */ + i64 y = term_start_y + (i64)cur_row * (i64)DU_ASCII_LINE_H; + du_fill_rect(&g_ctx, term_start_x, y, + (i64)term_w * (i64)DU_ASCII_STEP, + (i64)DU_ASCII_CELL_H, BG_COLOR); for (int i = 0; i < prompt_len; i++) { - i64 x = term_start_x + (i64)i * ASCII_STEP; - fb_char(g_fb, PROMPT[i], x, y, PROMPT_FG, BG_COLOR); + i64 x = term_start_x + (i64)i * (i64)DU_ASCII_STEP; + du_draw_char(&g_ctx, PROMPT[i], x, y, PROMPT_FG, BG_COLOR); } - /* 重绘输入缓冲 */ for (int i = 0; i < input_len; i++) { - i64 x = term_start_x + (i64)(prompt_len + i) * ASCII_STEP; - fb_char(g_fb, input_buf[i], x, y, TEXT_FG, BG_COLOR); + i64 x = term_start_x + (i64)(prompt_len + i) * (i64)DU_ASCII_STEP; + du_draw_char(&g_ctx, input_buf[i], x, y, TEXT_FG, BG_COLOR); } - /* 同步 cur_col 到输入光标 */ cur_col = prompt_len + input_cursor; term_redraw_cursor(); } static void insert_char(char c) { if (input_len >= 255) return; - /* 在 input_cursor 处插入 */ for (int i = input_len; i > input_cursor; i--) { input_buf[i] = input_buf[i-1]; } @@ -437,7 +427,6 @@ static void insert_char(char c) { static void delete_char_back(void) { if (input_cursor == 0) return; - /* 删除 input_cursor-1 处的字符 */ for (int i = input_cursor - 1; i < input_len - 1; i++) { input_buf[i] = input_buf[i+1]; } @@ -523,14 +512,16 @@ void dsk_entry(const dsk_boot_context *ctx) { fb_w = ctx->framebuffer_width; fb_h = ctx->framebuffer_height; fb_p = ctx->framebuffer_pitch; - g_fb = (u32 *)(u64)fb_a; + + /* 初始化风格系统渲染上下文 */ + du_context_init(&g_ctx, fb_a, fb_w, fb_h, fb_p); term_init_pos(); term_clear(); term_redraw_all(); - /* 启动 banner */ - term_puts_color("=== Deshab OS v0.1.0 ===\n", PROMPT_FG); + /* 启动 banner — 使用风格令牌 */ + term_puts_color("=== Deshab OS v0.1.0 ===\n", ACCENT_FG); term_puts_color("欢迎使用 Deshab Shell\n", OK_FG); term_puts_color("输入 'help' 查看可用命令\n", DIM_FG); term_putc('\n'); @@ -548,47 +539,40 @@ void dsk_entry(const dsk_boot_context *ctx) { cur_col = prompt_len; term_redraw_cursor(); - /* 等待命令输入 */ int cmd_done = 0; while (!cmd_done) { u8 st = inb(0x64); if (!(st & 1)) { __asm__("pause"); continue; } u8 data = inb(0x60); - /* 忽略鼠标数据(AUX 位) */ if (st & 0x20) continue; u8 sc = data; if (sc == 0xE0) { e0 = 1; continue; } if (sc == 0x2A || sc == 0x36) { shift = 1; continue; } if (sc == 0xAA || sc == 0xB6) { shift = 0; continue; } if (sc & 0x80) { - /* 松开事件 — 仅处理扩展键 */ - if (e0 && sc == 0x9C) { e0 = 0; } /* Enter 释放 */ - else if (e0 && sc == 0xCB) { e0 = 0; } /* Left 释放 */ - else if (e0 && sc == 0xCD) { e0 = 0; } /* Right 释放 */ - else if (e0 && sc == 0xC8) { e0 = 0; } /* Up 释放 */ - else if (e0 && sc == 0xD0) { e0 = 0; } /* Down 释放 */ - else if (e0 && sc == 0xD3) { e0 = 0; } /* Delete 释放 */ - else if (e0 && sc == 0xC7) { e0 = 0; } /* Home 释放 */ - else if (e0 && sc == 0xCF) { e0 = 0; } /* End 释放 */ + if (e0 && sc == 0x9C) { e0 = 0; } + else if (e0 && sc == 0xCB) { e0 = 0; } + else if (e0 && sc == 0xCD) { e0 = 0; } + else if (e0 && sc == 0xC8) { e0 = 0; } + else if (e0 && sc == 0xD0) { e0 = 0; } + else if (e0 && sc == 0xD3) { e0 = 0; } + else if (e0 && sc == 0xC7) { e0 = 0; } + else if (e0 && sc == 0xCF) { e0 = 0; } e0 = 0; continue; } - /* 按下事件 */ if (e0) { - /* 扩展键 */ - if (sc == 0x4B) cursor_left(); /* Left */ - else if (sc == 0x4D) cursor_right(); /* Right */ - else if (sc == 0x47) cursor_home(); /* Home */ - else if (sc == 0x4F) cursor_end(); /* End */ - else if (sc == 0x53) delete_char_fwd(); /* Delete */ - else if (sc == 0x48) { /* Up — 命令历史暂未实现 */ } - else if (sc == 0x50) { /* Down — 命令历史暂未实现 */ } + if (sc == 0x4B) cursor_left(); + else if (sc == 0x4D) cursor_right(); + else if (sc == 0x47) cursor_home(); + else if (sc == 0x4F) cursor_end(); + else if (sc == 0x53) delete_char_fwd(); + else if (sc == 0x48) { } + else if (sc == 0x50) { } e0 = 0; continue; } - /* 控制键 */ if (sc == 0x1C) { - /* Enter — 执行命令 */ term_clear_cursor(); input_buf[input_len] = 0; term_putc('\n'); @@ -596,32 +580,11 @@ void dsk_entry(const dsk_boot_context *ctx) { cmd_done = 1; continue; } - if (sc == 0x0E) { - /* Backspace */ - delete_char_back(); - continue; - } - if (sc == 0x01) { - /* Esc — 清行 */ - kill_line(); - continue; - } - /* Ctrl+U (kill line) */ - if (sc == 0x15 && !shift) { - kill_line(); - continue; - } - if (sc == 0x17 && !shift) { - /* Ctrl+W — 删除到行首 */ - while (input_cursor > 0) delete_char_back(); - continue; - } - if (sc == 0x0B && !shift) { - /* Ctrl+K — 删除到行尾 */ - kill_to_end(); - continue; - } - /* 普通字符 */ + if (sc == 0x0E) { delete_char_back(); continue; } + if (sc == 0x01) { kill_line(); continue; } + if (sc == 0x15 && !shift) { kill_line(); continue; } + if (sc == 0x17 && !shift) { while (input_cursor > 0) delete_char_back(); continue; } + if (sc == 0x0B && !shift) { kill_to_end(); continue; } char c = scan_to_ascii(sc, shift); if (c && c >= 32 && c <= 126) { insert_char(c); diff --git a/CODE/tools/bash/MAKEFILE b/CODE/tools/bash/MAKEFILE new file mode 100644 index 0000000..1a9b8a2 --- /dev/null +++ b/CODE/tools/bash/MAKEFILE @@ -0,0 +1,25 @@ +CC := clang +LD := ld.lld + +ROOT := ../../.. +OUT := $(ROOT)/SYSTEM/system/deshab64/tools/bash.elf + +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I.. -I../../UTSM/include -O2 +LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld + +C_SOURCES := main.c +OBJECTS := $(C_SOURCES:.c=.o) + +.PHONY: all clean + +all: $(OUT) + +$(OUT): $(OBJECTS) linker.ld + @if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }" diff --git a/CODE/tools/bash/linker.ld b/CODE/tools/bash/linker.ld new file mode 100644 index 0000000..94dddf7 --- /dev/null +++ b/CODE/tools/bash/linker.ld @@ -0,0 +1,16 @@ +ENTRY(dsk_entry) + +PHDRS +{ + text PT_LOAD FLAGS(7); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(7); +} + +SECTIONS +{ + . = 0x1000; + .text : ALIGN(4K) { *(.text*) } :text + .rodata : ALIGN(4K) { *(.rodata*) } :rodata + .data : ALIGN(4K) { *(.data*) *(COMMON) *(.bss*) } :data +} diff --git a/CODE/tools/bash/main.c b/CODE/tools/bash/main.c new file mode 100644 index 0000000..790ee6c --- /dev/null +++ b/CODE/tools/bash/main.c @@ -0,0 +1,456 @@ +/* Deshab Bash — 全屏终端应用 + * PS/2 键盘输入,命令行编辑,内置命令。 + * Esc 键退出返回 DSK。 + */ + +#include "../../firstInit/ascii_bitmaps.c" +#include "../desktop_app.h" + +/* ---- 终端参数 ---- */ +#define TERM_COLS 120 +#define TERM_ROWS 40 +#define TERM_MAX_CHARS (TERM_COLS * TERM_ROWS) +#define CHAR_STEP 12 +#define CHAR_H 18 +#define PROMPT_STR "deshab# " + +static da_app_context g_ac; + +/* 终端缓冲区 */ +static u8 term_ch[TERM_MAX_CHARS]; +static u32 term_fg[TERM_MAX_CHARS]; +static int term_w = TERM_COLS; +static int term_h = TERM_ROWS; +static int cur_col = 0; +static int cur_row = 0; + +/* 输入缓冲 */ +static char input_buf[256]; +static int input_len = 0; +static int input_cursor = 0; + +/* 命令历史 */ +#define HIST_SIZE 16 +static char history[HIST_SIZE][256]; +static int hist_count = 0; +static int hist_idx = 0; + +/* 坐标 */ +static int term_x, term_y; +static int content_h; + +static void term_init_layout(void) { + term_x = 8; + term_y = DA_TITLEBAR_H + 4; + int avail_w = (int)g_ac.fb_w - 16; + int avail_h = (int)g_ac.fb_h - term_y - DA_STATUSBAR_H - 8; + term_w = avail_w / CHAR_STEP; + term_h = avail_h / CHAR_H; + if (term_w < 10) term_w = 10; + if (term_h < 5) term_h = 5; + if (term_w > TERM_COLS) term_w = TERM_COLS; + if (term_h > TERM_ROWS) term_h = TERM_ROWS; + content_h = term_h * CHAR_H; +} + +static void term_clear(void) { + for (int i = 0; i < TERM_MAX_CHARS; i++) { + term_ch[i] = ' '; + term_fg[i] = DA_TEXT_PRIMARY; + } + cur_col = 0; cur_row = 0; +} + +static void term_scroll(void) { + for (int r = 1; r < term_h; r++) { + for (int c = 0; c < term_w; c++) { + term_ch[(r-1)*TERM_COLS+c] = term_ch[r*TERM_COLS+c]; + term_fg[(r-1)*TERM_COLS+c] = term_fg[r*TERM_COLS+c]; + } + } + for (int c = 0; c < term_w; c++) { + term_ch[(term_h-1)*TERM_COLS+c] = ' '; + term_fg[(term_h-1)*TERM_COLS+c] = DA_TEXT_PRIMARY; + } + cur_row = term_h - 1; cur_col = 0; +} + +static void term_newline(void) { + cur_col = 0; cur_row++; + if (cur_row >= term_h) term_scroll(); +} + +static void term_putc_color(char c, u32 color) { + if (c == '\n') { term_newline(); return; } + if (c == '\r') { cur_col = 0; return; } + if (c == '\t') { int sp = 4 - (cur_col % 4); for (int i = 0; i < sp; i++) term_putc_color(' ', color); return; } + if (c < ' ' || c > '~') c = '?'; + if (cur_col >= term_w) term_newline(); + int idx = cur_row * TERM_COLS + cur_col; + term_ch[idx] = (u8)c; term_fg[idx] = color; + cur_col++; +} + +static void term_putc(char c) { term_putc_color(c, DA_TEXT_PRIMARY); } +static void term_puts_color(const char *s, u32 color) { while (*s) term_putc_color(*s++, color); } +static void term_puts(const char *s) { term_puts_color(s, DA_TEXT_PRIMARY); } + +/* ---- 绘制 ---- */ +static void redraw_all(void) { + da_fill_bg(&g_ac, DA_BG_PRIMARY); + da_draw_titlebar(&g_ac, "Deshab Bash", (i64)g_ac.fb_w); + /* 终端字符 */ + for (int r = 0; r < term_h; r++) { + for (int c = 0; c < term_w; c++) { + int idx = r * TERM_COLS + c; + if (term_ch[idx] == ' ') continue; + da_draw_char(&g_ac, term_ch[idx], + term_x + c * CHAR_STEP, term_y + r * CHAR_H, + term_fg[idx], DA_BG_PRIMARY); + } + } + /* 光标 */ + if (cur_col < term_w && cur_row < term_h) { + da_fill_rect(&g_ac, term_x + cur_col * CHAR_STEP, + term_y + cur_row * CHAR_H + CHAR_H - 3, 11, 2, DA_ACCENT); + } + /* 状态栏 */ + char status[64]; + int p = 0; + const char *s1 = "/ root@deshab "; + while (*s1) status[p++] = *s1++; + u8 hh, mm; da_rtc_time(&hh, &mm); + status[p++] = '0' + hh/10; status[p++] = '0' + hh%10; + status[p++] = ':'; status[p++] = '0' + mm/10; status[p++] = '0' + mm%10; + status[p] = 0; + da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h); +} + +/* ---- 内置命令 ---- */ +static int str_eq(const char *a, const char *b) { + while (*a && *b) { if (*a != *b) return 0; a++; b++; } + return *a == *b; +} + +static void cmd_help(void) { + term_puts_color("Deshab Bash - 内置命令:\n", DA_PROMPT_COLOR); + term_puts(" help 显示此帮助\n"); + term_puts(" clear 清屏\n"); + term_puts(" echo 显示文本\n"); + term_puts(" version 显示系统版本\n"); + term_puts(" ver 同 version\n"); + term_puts(" uname 显示系统名称\n"); + term_puts(" date 显示当前日期时间\n"); + term_puts(" about 关于 Deshab\n"); + term_puts(" reboot 重启系统\n"); + term_puts(" halt 关机\n"); + term_puts(" pwd 显示当前目录\n"); + term_puts(" whoami 显示当前用户\n"); + term_puts(" id 显示用户ID\n"); + term_puts(" ls 列出根目录文件\n"); + term_puts(" cat 显示文件内容\n"); +} + +static void cmd_version(void) { + term_puts_color("Deshab OS v0.1.0\n", DA_SUCCESS); + term_puts(" Kernel: DSK (Deshab System Kernel)\n"); + term_puts(" Architecture: x86_64\n"); + term_puts(" ABI: DKM v1\n"); + term_puts(" Bootloader: Limine\n"); +} + +static void cmd_uname(void) { term_puts("Deshab\n"); } + +static void cmd_about(void) { + term_puts_color("=== Deshab OS ===\n", DA_PROMPT_COLOR); + term_puts("单地址空间 Ring0 内核实验系统\n"); + term_puts("架构: UTSM + DSK + DKM\n"); + term_puts_color("Built with Clang + lld\n", DA_TEXT_DIM); +} + +static void cmd_clear(void) { term_clear(); } + +static void cmd_reboot(void) { + term_puts_color("Rebooting...\n", DA_PROMPT_COLOR); + redraw_all(); + outb(0x64, 0xFE); + for(;;) __asm__("hlt"); +} + +static void cmd_halt(void) { + term_puts_color("System halted.\n", DA_PROMPT_COLOR); + redraw_all(); + for(;;) __asm__("hlt"); +} + +static void cmd_echo(const char *args) { + if (args && *args) term_puts_color(args, DA_TEXT_PRIMARY); + term_putc('\n'); +} + +static void cmd_date(void) { + u8 year, month, day, hour, minute, second; + outb(0x70, 9); year = inb(0x71); + outb(0x70, 8); month = inb(0x71); + outb(0x70, 7); day = inb(0x71); + outb(0x70, 4); hour = inb(0x71); + outb(0x70, 2); minute = inb(0x71); + outb(0x70, 0); second = inb(0x71); + year = (u8)((year>>4)*10+(year&0xF)); + month = (u8)((month>>4)*10+(month&0xF)); + day = (u8)((day>>4)*10+(day&0xF)); + hour = (u8)((hour>>4)*10+(hour&0xF)); + minute = (u8)((minute>>4)*10+(minute&0xF)); + second = (u8)((second>>4)*10+(second&0xF)); + char buf[32]; int p = 0; + buf[p++]='2'; buf[p++]='0'; buf[p++]='0'+year/10; buf[p++]='0'+year%10; buf[p++]='-'; + buf[p++]='0'+month/10; buf[p++]='0'+month%10; buf[p++]='-'; + buf[p++]='0'+day/10; buf[p++]='0'+day%10; buf[p++]=' '; + buf[p++]='0'+hour/10; buf[p++]='0'+hour%10; buf[p++]=':'; + buf[p++]='0'+minute/10; buf[p++]='0'+minute%10; buf[p++]=':'; + buf[p++]='0'+second/10; buf[p++]='0'+second%10; buf[p]=0; + term_puts(buf); term_putc('\n'); +} + +static void cmd_ls(void) { + if (!g_ac.block_read) { + term_puts_color("ls: 无块设备可用\n", DA_ERROR); + return; + } + /* 读取 FAT32 根目录 — 简化版:读取 BPB 获取根目录区 */ + u8 bpb[512]; + if (g_ac.block_read(0, 0, 1, bpb) != 0) { + term_puts_color("ls: 读取 BPB 失败\n", DA_ERROR); + return; + } + /* 检查 FAT32 签名 */ + if (bpb[510] != 0x55 || bpb[511] != 0xAA) { + term_puts_color("ls: 无有效 FAT32 分区\n", DA_ERROR); + return; + } + u32 root_cluster = *(u32 *)(bpb + 44); + u16 bytes_per_sector = *(u16 *)(bpb + 11); + u8 sectors_per_cluster = bpb[13]; + u32 fat_start = *(u16 *)(bpb + 14); + u32 data_start = fat_start + (*(u16 *)(bpb + 22)) * 2; + (void)bytes_per_sector; + (void)sectors_per_cluster; + (void)data_start; + + /* 读取根目录簇 — 简化:只读取第一个簇 */ + u8 dir_buf[8192]; + u32 cluster = root_cluster; + u32 lba = data_start + (cluster - 2) * sectors_per_cluster; + if (g_ac.block_read(0, lba, sectors_per_cluster, dir_buf) != 0) { + term_puts_color("ls: 读取根目录失败\n", DA_ERROR); + return; + } + /* 解析目录项 */ + int found = 0; + for (int i = 0; i < (int)(sectors_per_cluster * 512); i += 32) { + u8 first = dir_buf[i]; + if (first == 0x00) break; + if (first == 0xE5) continue; + if (dir_buf[i+11] & 0x08) continue; /* 卷标 */ + if (dir_buf[i+11] & 0x10) { + /* 目录 */ + term_puts_color("[DIR] ", DA_ACCENT); + } else { + term_puts_color(" ", DA_TEXT_PRIMARY); + } + /* 文件名(8.3) */ + for (int j = 0; j < 8; j++) { + if (dir_buf[i+j] == ' ') break; + term_putc((char)dir_buf[i+j]); + } + if (dir_buf[i+8] != ' ') { + term_putc('.'); + for (int j = 8; j < 11; j++) { + if (dir_buf[i+j] == ' ') break; + term_putc((char)dir_buf[i+j]); + } + } + term_putc('\n'); + found++; + } + if (found == 0) term_puts_color("(empty)\n", DA_TEXT_DIM); +} + +static void cmd_cat(const char *args) { + if (!args || !*args) { + term_puts_color("cat: 需要文件名参数\n", DA_ERROR); + return; + } + if (!g_ac.block_read) { + term_puts_color("cat: 无块设备可用\n", DA_ERROR); + return; + } + term_puts_color("cat: 文件读取暂未完整实现\n", DA_WARNING); +} + +static void execute_command(const char *cmd) { + while (*cmd == ' ') cmd++; + if (*cmd == 0) return; + const char *args = cmd; + while (*args && *args != ' ') args++; + char name[32]; + int nlen = (int)(args - cmd); + if (nlen >= 32) nlen = 31; + for (int i = 0; i < nlen; i++) name[i] = cmd[i]; + name[nlen] = 0; + while (*args == ' ') args++; + + if (str_eq(name,"help")||str_eq(name,"?")) cmd_help(); + else if (str_eq(name,"version")||str_eq(name,"ver")) cmd_version(); + else if (str_eq(name,"uname")) cmd_uname(); + else if (str_eq(name,"about")) cmd_about(); + else if (str_eq(name,"clear")||str_eq(name,"cls")) cmd_clear(); + else if (str_eq(name,"reboot")) cmd_reboot(); + else if (str_eq(name,"halt")||str_eq(name,"shutdown")) cmd_halt(); + else if (str_eq(name,"echo")) cmd_echo(args); + else if (str_eq(name,"date")||str_eq(name,"time")) cmd_date(); + else if (str_eq(name,"ls")||str_eq(name,"dir")) cmd_ls(); + else if (str_eq(name,"cat")) cmd_cat(args); + else if (str_eq(name,"pwd")) { term_puts("/\n"); } + else if (str_eq(name,"whoami")) { term_puts("root\n"); } + else if (str_eq(name,"id")) { term_puts("uid=0(root) gid=0(root)\n"); } + else { + term_puts_color("未知命令: ", DA_ERROR); + term_puts_color(name, DA_ERROR); + term_putc('\n'); + term_puts_color("输入 'help' 查看可用命令\n", DA_TEXT_DIM); + } +} + +static void add_history(const char *cmd) { + if (hist_count < HIST_SIZE) { + int i; + for (i = 0; cmd[i] && i < 255; i++) history[hist_count][i] = cmd[i]; + history[hist_count][i] = 0; + hist_count++; + } + hist_idx = hist_count; +} + +/* ---- 输入行重绘 ---- */ +static void redraw_input_line(void) { + int plen = 0; + while (PROMPT_STR[plen]) plen++; + i64 y = term_y + (i64)cur_row * CHAR_H; + da_fill_rect(&g_ac, term_x, y, (i64)term_w * CHAR_STEP, CHAR_H, DA_BG_PRIMARY); + for (int i = 0; i < plen; i++) + da_draw_char(&g_ac, PROMPT_STR[i], term_x + i * CHAR_STEP, y, DA_PROMPT_COLOR, DA_BG_PRIMARY); + for (int i = 0; i < input_len; i++) + da_draw_char(&g_ac, input_buf[i], term_x + (plen + i) * CHAR_STEP, y, DA_TEXT_PRIMARY, DA_BG_PRIMARY); + cur_col = plen + input_cursor; + /* 光标 */ + da_fill_rect(&g_ac, term_x + cur_col * CHAR_STEP, y + CHAR_H - 3, 11, 2, DA_ACCENT); +} + +/* ---- 主入口 ---- */ +__attribute__((visibility("default"))) +void dsk_entry(const da_boot_context *ctx) { + __asm__ volatile("cli"); + da_slog("bash", "boot"); + + if (!ctx || ctx->magic != DA_BOOT_MAGIC) { + da_slog("bash", "bad context"); + for(;;) __asm__("hlt"); + } + + da_init(&g_ac, ctx); + term_init_layout(); + term_clear(); + + /* 启动 banner */ + term_puts_color("=== Deshab Bash v0.1 ===\n", DA_ACCENT); + term_puts_color("欢迎使用 Deshab 终端\n", DA_SUCCESS); + term_puts_color("输入 'help' 查看可用命令 | Esc 退出\n", DA_TEXT_DIM); + term_putc('\n'); + + redraw_all(); + + int shift = 0, e0 = 0; + for (;;) { + /* 绘制提示符 */ + term_puts_color(PROMPT_STR, DA_PROMPT_COLOR); + input_len = 0; input_cursor = 0; input_buf[0] = 0; + int plen = 0; while (PROMPT_STR[plen]) plen++; + cur_col = plen; + redraw_all(); + + int cmd_done = 0; + while (!cmd_done) { + u8 st = inb(0x64); + if (!(st & 1)) { __asm__("pause"); continue; } + u8 data = inb(0x60); + if (st & 0x20) continue; + u8 sc = data; + if (sc == 0xE0) { e0 = 1; continue; } + if (sc == 0x2A || sc == 0x36) { shift = 1; continue; } + if (sc == 0xAA || sc == 0xB6) { shift = 0; continue; } + if (sc & 0x80) { e0 = 0; continue; } + + /* Esc 退出 */ + if (sc == 0x01 && !e0) { + da_slog("bash", "exit"); + return; + } + + if (e0) { + if (sc == 0x4B) { if (input_cursor > 0) input_cursor--; } + else if (sc == 0x4D) { if (input_cursor < input_len) input_cursor++; } + else if (sc == 0x47) { input_cursor = 0; } + else if (sc == 0x4F) { input_cursor = input_len; } + else if (sc == 0x48) { /* 上箭头 - 历史 */ + if (hist_idx > 0) { + hist_idx--; + int i; for (i = 0; history[hist_idx][i] && i < 255; i++) input_buf[i] = history[hist_idx][i]; + input_len = i; input_cursor = i; input_buf[i] = 0; + } + } + else if (sc == 0x50) { /* 下箭头 - 历史 */ + if (hist_idx < hist_count - 1) { + hist_idx++; + int i; for (i = 0; history[hist_idx][i] && i < 255; i++) input_buf[i] = history[hist_idx][i]; + input_len = i; input_cursor = i; input_buf[i] = 0; + } else if (hist_idx < hist_count) { + hist_idx = hist_count; + input_len = 0; input_cursor = 0; input_buf[0] = 0; + } + } + e0 = 0; + redraw_input_line(); + continue; + } + + if (sc == 0x1C) { /* Enter */ + input_buf[input_len] = 0; + term_putc('\n'); + if (input_len > 0) add_history(input_buf); + execute_command(input_buf); + cmd_done = 1; + continue; + } + if (sc == 0x0E) { /* Backspace */ + if (input_cursor > 0) { + for (int i = input_cursor - 1; i < input_len - 1; i++) + input_buf[i] = input_buf[i+1]; + input_len--; input_cursor--; + input_buf[input_len] = 0; + } + redraw_input_line(); + continue; + } + char c = da_scan_to_ascii(sc, shift); + if (c && c >= 32 && c <= 126 && input_len < 255) { + for (int i = input_len; i > input_cursor; i--) + input_buf[i] = input_buf[i-1]; + input_buf[input_cursor++] = c; + input_len++; + redraw_input_line(); + } + } + redraw_all(); + } +} diff --git a/CODE/tools/browser/MAKEFILE b/CODE/tools/browser/MAKEFILE new file mode 100644 index 0000000..7d04ada --- /dev/null +++ b/CODE/tools/browser/MAKEFILE @@ -0,0 +1,25 @@ +CC := clang +LD := ld.lld + +ROOT := ../../.. +OUT := $(ROOT)/SYSTEM/system/deshab64/tools/browser.elf + +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I.. -I../../UTSM/include -O2 +LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld + +C_SOURCES := main.c +OBJECTS := $(C_SOURCES:.c=.o) + +.PHONY: all clean + +all: $(OUT) + +$(OUT): $(OBJECTS) linker.ld + @if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }" diff --git a/CODE/tools/browser/linker.ld b/CODE/tools/browser/linker.ld new file mode 100644 index 0000000..94dddf7 --- /dev/null +++ b/CODE/tools/browser/linker.ld @@ -0,0 +1,16 @@ +ENTRY(dsk_entry) + +PHDRS +{ + text PT_LOAD FLAGS(7); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(7); +} + +SECTIONS +{ + . = 0x1000; + .text : ALIGN(4K) { *(.text*) } :text + .rodata : ALIGN(4K) { *(.rodata*) } :rodata + .data : ALIGN(4K) { *(.data*) *(COMMON) *(.bss*) } :data +} diff --git a/CODE/tools/browser/main.c b/CODE/tools/browser/main.c new file mode 100644 index 0000000..26844ad --- /dev/null +++ b/CODE/tools/browser/main.c @@ -0,0 +1,128 @@ +/* Deshab Browser — 全屏浏览器 (Stub) + * 地址栏 + 导航按钮 + 内容渲染区域。 + * 当前显示 stub 提示,Esc 退出。 + */ + +#include "../../firstInit/ascii_bitmaps.c" +#include "../desktop_app.h" + +#define CHAR_STEP 12 +#define CHAR_H 18 + +static da_app_context g_ac; +static da_cursor g_cursor; +static da_mouse g_mouse; + +static void redraw_all(void) { + da_fill_bg(&g_ac, DA_BG_PRIMARY); + + /* 标题栏 */ + da_draw_titlebar(&g_ac, "Browser", (i64)g_ac.fb_w); + + /* 地址栏 */ + int bar_y = DA_TITLEBAR_H + 6; + int bar_h = 28; + int bar_x = 12; + int bar_w = (int)g_ac.fb_w - 24; + da_fill_rounded_rect(&g_ac, bar_x, bar_y, bar_w, bar_h, DA_BG_TERTIARY, 6); + da_rect_outline(&g_ac, bar_x, bar_y, bar_w, bar_h, DA_BORDER, 6); + + /* 导航按钮 */ + int btn_w = 32; + da_fill_rounded_rect(&g_ac, bar_x + 4, bar_y + 4, btn_w, bar_h - 8, DA_BG_SECONDARY, 4); + da_draw_string(&g_ac, "<", bar_x + 14, bar_y + 5, DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP); + da_fill_rounded_rect(&g_ac, bar_x + 4 + btn_w + 4, bar_y + 4, btn_w, bar_h - 8, DA_BG_SECONDARY, 4); + da_draw_string(&g_ac, ">", bar_x + 4 + btn_w + 4 + 12, bar_y + 5, DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP); + da_fill_rounded_rect(&g_ac, bar_x + 4 + (btn_w + 4) * 2, bar_y + 4, btn_w, bar_h - 8, DA_BG_SECONDARY, 4); + da_draw_string(&g_ac, "R", bar_x + 4 + (btn_w + 4) * 2 + 12, bar_y + 5, DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP); + + /* URL 文本 */ + int url_x = bar_x + (btn_w + 4) * 3 + 8; + da_draw_string(&g_ac, "deshab://home", url_x, bar_y + 5, DA_TEXT_DIM, DA_BG_TERTIARY, CHAR_STEP); + + /* 内容区域 */ + int content_y = bar_y + bar_h + 12; + int content_x = 16; + int content_w = (int)g_ac.fb_w - 32; + + /* Stub 页面 */ + da_fill_rounded_rect(&g_ac, content_x, content_y, content_w, 200, DA_BG_SECONDARY, 8); + da_rect_outline(&g_ac, content_x, content_y, content_w, 200, DA_BORDER, 8); + + /* 居中图标 */ + int icon_cx = content_x + content_w / 2 - 32; + int icon_cy = content_y + 24; + da_fill_rounded_rect(&g_ac, icon_cx, icon_cy, 64, 48, DA_BG_TERTIARY, 8); + /* 地球图标简化:圆圈 */ + int cx = icon_cx + 32, cy = icon_cy + 24, r = 16; + for (int dy = -r; dy <= r; dy++) + for (int dx = -r; dx <= r; dx++) + if (dx*dx+dy*dy <= r*r && dx*dx+dy*dy > (r-1)*(r-1)) + da_pixel(&g_ac, cx+dx, cy+dy, DA_ACCENT); + /* 横线 */ + for (int dx = -r; dx <= r; dx++) + if (dx*dx + 0 <= r*r) + da_pixel(&g_ac, cx+dx, cy, DA_ACCENT); + /* 竖线 */ + for (int dy = -r; dy <= r; dy++) + if (0 + dy*dy <= r*r) + da_pixel(&g_ac, cx, cy+dy, DA_ACCENT); + + /* 提示文字 */ + da_draw_string(&g_ac, "Deshab Browser", + content_x + content_w / 2 - 7 * CHAR_STEP, icon_cy + 60, + DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP); + da_draw_string(&g_ac, "HTTP + HTML rendering required", + content_x + content_w / 2 - 15 * CHAR_STEP, icon_cy + 82, + DA_TEXT_DIM, DA_BG_SECONDARY, CHAR_STEP); + da_draw_string(&g_ac, "Press Esc to exit", + content_x + content_w / 2 - 9 * CHAR_STEP, icon_cy + 104, + DA_TEXT_DIM, DA_BG_SECONDARY, CHAR_STEP); + + /* 状态栏 */ + da_draw_statusbar(&g_ac, "Browser | Stub - No network stack", (i64)g_ac.fb_w, (i64)g_ac.fb_h); + + /* 鼠标光标 */ + da_cursor_save(&g_ac, &g_cursor); + da_cursor_draw(&g_ac, &g_cursor, DA_CURSOR_COLOR); +} + +__attribute__((visibility("default"))) +void dsk_entry(const da_boot_context *ctx) { + __asm__ volatile("cli"); + da_slog("browser", "boot"); + + if (!ctx || ctx->magic != DA_BOOT_MAGIC) { + da_slog("browser", "bad context"); + for(;;) __asm__("hlt"); + } + + da_init(&g_ac, ctx); + da_cursor_init(&g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h); + da_mouse_init(); + redraw_all(); + + for (;;) { + int need_redraw = 0; + + /* 鼠标轮询 */ + if (da_mouse_poll(&g_mouse, &g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h)) + need_redraw = 1; + + /* 键盘轮询 */ + u8 st = inb(0x64); + if ((st & 1) && !(st & 0x20)) { + u8 data = inb(0x60); + if (!(data & 0x80) && data == 0x01) { + da_slog("browser", "exit"); + return; + } + } + + if (need_redraw) { + da_cursor_restore(&g_ac, &g_cursor); + redraw_all(); + } + __asm__("pause"); + } +} diff --git a/CODE/tools/curl/MAKEFILE b/CODE/tools/curl/MAKEFILE new file mode 100644 index 0000000..d0bef11 --- /dev/null +++ b/CODE/tools/curl/MAKEFILE @@ -0,0 +1,25 @@ +CC := clang +LD := ld.lld + +ROOT := ../../.. +OUT := $(ROOT)/SYSTEM/system/deshab64/tools/curl.elf + +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I.. -I../../UTSM/include -O2 +LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld + +C_SOURCES := main.c +OBJECTS := $(C_SOURCES:.c=.o) + +.PHONY: all clean + +all: $(OUT) + +$(OUT): $(OBJECTS) linker.ld + @if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }" diff --git a/CODE/tools/curl/linker.ld b/CODE/tools/curl/linker.ld new file mode 100644 index 0000000..94dddf7 --- /dev/null +++ b/CODE/tools/curl/linker.ld @@ -0,0 +1,16 @@ +ENTRY(dsk_entry) + +PHDRS +{ + text PT_LOAD FLAGS(7); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(7); +} + +SECTIONS +{ + . = 0x1000; + .text : ALIGN(4K) { *(.text*) } :text + .rodata : ALIGN(4K) { *(.rodata*) } :rodata + .data : ALIGN(4K) { *(.data*) *(COMMON) *(.bss*) } :data +} diff --git a/CODE/tools/curl/main.c b/CODE/tools/curl/main.c new file mode 100644 index 0000000..b06724c --- /dev/null +++ b/CODE/tools/curl/main.c @@ -0,0 +1,324 @@ +/* Deshab curl — 终端风格 HTTP 客户端 stub + * 命令行输入 URL,显示 HTTP GET 请求结果。 + * 当前为 stub,提示需要 TCP/IP 栈支持。 + * Esc 键退出返回 DSK。 + */ + +#include "../../firstInit/ascii_bitmaps.c" +#include "../desktop_app.h" + +/* ---- 终端参数 ---- */ +#define TERM_COLS 120 +#define TERM_ROWS 40 +#define TERM_MAX_CHARS (TERM_COLS * TERM_ROWS) +#define CHAR_STEP 12 +#define CHAR_H 18 +#define PROMPT_STR "curl> " + +static da_app_context g_ac; + +/* 终端缓冲区 */ +static u8 term_ch[TERM_MAX_CHARS]; +static u32 term_fg[TERM_MAX_CHARS]; +static int term_w = TERM_COLS; +static int term_h = TERM_ROWS; +static int cur_col = 0; +static int cur_row = 0; + +/* 输入缓冲 */ +static char input_buf[256]; +static int input_len = 0; +static int input_cursor = 0; + +/* 命令历史 */ +#define HIST_SIZE 16 +static char history[HIST_SIZE][256]; +static int hist_count = 0; +static int hist_idx = 0; + +/* 坐标 */ +static int term_x, term_y; + +static void term_init_layout(void) { + term_x = 8; + term_y = DA_TITLEBAR_H + 4; + int avail_w = (int)g_ac.fb_w - 16; + int avail_h = (int)g_ac.fb_h - term_y - DA_STATUSBAR_H - 8; + term_w = avail_w / CHAR_STEP; + term_h = avail_h / CHAR_H; + if (term_w < 10) term_w = 10; + if (term_h < 5) term_h = 5; + if (term_w > TERM_COLS) term_w = TERM_COLS; + if (term_h > TERM_ROWS) term_h = TERM_ROWS; +} + +static void term_clear(void) { + for (int i = 0; i < TERM_MAX_CHARS; i++) { + term_ch[i] = ' '; + term_fg[i] = DA_TEXT_PRIMARY; + } + cur_col = 0; cur_row = 0; +} + +static void term_scroll(void) { + for (int r = 1; r < term_h; r++) { + for (int c = 0; c < term_w; c++) { + term_ch[(r-1)*TERM_COLS+c] = term_ch[r*TERM_COLS+c]; + term_fg[(r-1)*TERM_COLS+c] = term_fg[r*TERM_COLS+c]; + } + } + for (int c = 0; c < term_w; c++) { + term_ch[(term_h-1)*TERM_COLS+c] = ' '; + term_fg[(term_h-1)*TERM_COLS+c] = DA_TEXT_PRIMARY; + } + cur_row = term_h - 1; cur_col = 0; +} + +static void term_newline(void) { + cur_col = 0; cur_row++; + if (cur_row >= term_h) term_scroll(); +} + +static void term_putc_color(char c, u32 color) { + if (c == '\n') { term_newline(); return; } + if (c == '\r') { cur_col = 0; return; } + if (c == '\t') { int sp = 4 - (cur_col % 4); for (int i = 0; i < sp; i++) term_putc_color(' ', color); return; } + if (c < ' ' || c > '~') c = '?'; + if (cur_col >= term_w) term_newline(); + int idx = cur_row * TERM_COLS + cur_col; + term_ch[idx] = (u8)c; term_fg[idx] = color; + cur_col++; +} + +static void term_putc(char c) { term_putc_color(c, DA_TEXT_PRIMARY); } +static void term_puts_color(const char *s, u32 color) { while (*s) term_putc_color(*s++, color); } +static void term_puts(const char *s) { term_puts_color(s, DA_TEXT_PRIMARY); } + +/* ---- 绘制 ---- */ +static void redraw_all(void) { + da_fill_bg(&g_ac, DA_BG_PRIMARY); + da_draw_titlebar(&g_ac, "curl", (i64)g_ac.fb_w); + /* 终端字符 */ + for (int r = 0; r < term_h; r++) { + for (int c = 0; c < term_w; c++) { + int idx = r * TERM_COLS + c; + if (term_ch[idx] == ' ') continue; + da_draw_char(&g_ac, term_ch[idx], + term_x + c * CHAR_STEP, term_y + r * CHAR_H, + term_fg[idx], DA_BG_PRIMARY); + } + } + /* 光标 */ + if (cur_col < term_w && cur_row < term_h) { + da_fill_rect(&g_ac, term_x + cur_col * CHAR_STEP, + term_y + cur_row * CHAR_H + CHAR_H - 3, 11, 2, DA_ACCENT); + } + /* 状态栏 */ + char status[64]; + int p = 0; + const char *s1 = "HTTP client | Esc 退出 "; + while (*s1) status[p++] = *s1++; + u8 hh, mm; da_rtc_time(&hh, &mm); + status[p++] = '0' + hh/10; status[p++] = '0' + hh%10; + status[p++] = ':'; status[p++] = '0' + mm/10; status[p++] = '0' + mm%10; + status[p] = 0; + da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h); +} + +/* ---- URL 请求模拟 ---- */ +static void do_http_get(const char *url) { + term_puts_color("GET ", DA_PROMPT_COLOR); + term_puts_color(url, DA_ACCENT_LIGHT); + term_putc('\n'); + term_puts_color(" -> 解析主机...\n", DA_TEXT_DIM); + term_puts_color(" -> 建立 TCP 连接...\n", DA_TEXT_DIM); + + /* stub: 检查 kernel_api 是否有 net 接口 */ + if (!g_ac.kernel_api) { + term_puts_color(" [错误] 无内核 API\n", DA_ERROR); + return; + } + /* 尝试读取 net API — 当前为 stub */ + u64 net_api = *(u64 *)((u64)g_ac.kernel_api + 0xC0); + if (!net_api) { + term_puts_color(" [错误] 需要 TCP/IP 栈支持\n", DA_ERROR); + term_puts_color(" 当前内核未实现网络协议栈。\n", DA_WARNING); + term_puts_color(" 请等待后续版本更新。\n", DA_TEXT_DIM); + return; + } + /* 如果有 net API,此处应实现真正的 HTTP GET */ + term_puts_color(" [stub] 网络功能尚未实现\n", DA_WARNING); +} + +/* ---- 内置命令 ---- */ +static int str_eq(const char *a, const char *b) { + while (*a && *b) { if (*a != *b) return 0; a++; b++; } + return *a == *b; +} + +static void cmd_help(void) { + term_puts_color("curl — 命令行 HTTP 客户端\n", DA_PROMPT_COLOR); + term_puts(" 发送 HTTP GET 请求\n"); + term_puts(" help 显示此帮助\n"); + term_puts(" clear 清屏\n"); + term_puts(" version 显示版本\n"); + term_puts(" Esc 退出\n"); +} + +static void cmd_clear(void) { term_clear(); } + +static void cmd_version(void) { + term_puts_color("Deshab curl v0.1 (stub)\n", DA_SUCCESS); + term_puts_color(" HTTP 客户端功能需要 TCP/IP 栈支持\n", DA_TEXT_DIM); +} + +static void execute_command(const char *cmd) { + while (*cmd == ' ') cmd++; + if (*cmd == 0) return; + + if (str_eq(cmd, "help") || str_eq(cmd, "?")) { cmd_help(); return; } + if (str_eq(cmd, "clear") || str_eq(cmd, "cls")) { cmd_clear(); return; } + if (str_eq(cmd, "version") || str_eq(cmd, "ver")) { cmd_version(); return; } + + /* 非内置命令视为 URL */ + if (cmd[0] == 'h' && cmd[1] == 't' && cmd[2] == 't' && cmd[3] == 'p') { + do_http_get(cmd); + } else { + term_puts_color("无效 URL: ", DA_ERROR); + term_puts_color(cmd, DA_ERROR); + term_putc('\n'); + term_puts_color("请输入完整 URL (http://...)\n", DA_TEXT_DIM); + } +} + +static void add_history(const char *cmd) { + if (hist_count < HIST_SIZE) { + int i; + for (i = 0; cmd[i] && i < 255; i++) history[hist_count][i] = cmd[i]; + history[hist_count][i] = 0; + hist_count++; + } + hist_idx = hist_count; +} + +/* ---- 输入行重绘 ---- */ +static void redraw_input_line(void) { + int plen = 0; + while (PROMPT_STR[plen]) plen++; + i64 y = term_y + (i64)cur_row * CHAR_H; + da_fill_rect(&g_ac, term_x, y, (i64)term_w * CHAR_STEP, CHAR_H, DA_BG_PRIMARY); + for (int i = 0; i < plen; i++) + da_draw_char(&g_ac, PROMPT_STR[i], term_x + i * CHAR_STEP, y, DA_PROMPT_COLOR, DA_BG_PRIMARY); + for (int i = 0; i < input_len; i++) + da_draw_char(&g_ac, input_buf[i], term_x + (plen + i) * CHAR_STEP, y, DA_TEXT_PRIMARY, DA_BG_PRIMARY); + cur_col = plen + input_cursor; + da_fill_rect(&g_ac, term_x + cur_col * CHAR_STEP, y + CHAR_H - 3, 11, 2, DA_ACCENT); +} + +/* ---- 主入口 ---- */ +__attribute__((visibility("default"))) +void dsk_entry(const da_boot_context *ctx) { + __asm__ volatile("cli"); + da_slog("curl", "boot"); + + if (!ctx || ctx->magic != DA_BOOT_MAGIC) { + da_slog("curl", "bad context"); + for(;;) __asm__("hlt"); + } + + da_init(&g_ac, ctx); + term_init_layout(); + term_clear(); + + /* 启动 banner */ + term_puts_color("=== Deshab curl v0.1 (stub) ===\n", DA_ACCENT); + term_puts_color("HTTP 命令行客户端\n", DA_SUCCESS); + term_puts_color("输入 URL 发送 GET 请求 | 输入 'help' 查看帮助 | Esc 退出\n", DA_TEXT_DIM); + term_puts_color("注意: 当前需要 TCP/IP 栈支持\n", DA_WARNING); + term_putc('\n'); + + redraw_all(); + + int shift = 0, e0 = 0; + for (;;) { + term_puts_color(PROMPT_STR, DA_PROMPT_COLOR); + input_len = 0; input_cursor = 0; input_buf[0] = 0; + int plen = 0; while (PROMPT_STR[plen]) plen++; + cur_col = plen; + redraw_all(); + + int cmd_done = 0; + while (!cmd_done) { + u8 st = inb(0x64); + if (!(st & 1)) { __asm__("pause"); continue; } + u8 data = inb(0x60); + if (st & 0x20) continue; + u8 sc = data; + if (sc == 0xE0) { e0 = 1; continue; } + if (sc == 0x2A || sc == 0x36) { shift = 1; continue; } + if (sc == 0xAA || sc == 0xB6) { shift = 0; continue; } + if (sc & 0x80) { e0 = 0; continue; } + + /* Esc 退出 */ + if (sc == 0x01 && !e0) { + da_slog("curl", "exit"); + return; + } + + if (e0) { + if (sc == 0x4B) { if (input_cursor > 0) input_cursor--; } + else if (sc == 0x4D) { if (input_cursor < input_len) input_cursor++; } + else if (sc == 0x47) { input_cursor = 0; } + else if (sc == 0x4F) { input_cursor = input_len; } + else if (sc == 0x48) { + if (hist_idx > 0) { + hist_idx--; + int i; for (i = 0; history[hist_idx][i] && i < 255; i++) input_buf[i] = history[hist_idx][i]; + input_len = i; input_cursor = i; input_buf[i] = 0; + } + } + else if (sc == 0x50) { + if (hist_idx < hist_count - 1) { + hist_idx++; + int i; for (i = 0; history[hist_idx][i] && i < 255; i++) input_buf[i] = history[hist_idx][i]; + input_len = i; input_cursor = i; input_buf[i] = 0; + } else if (hist_idx < hist_count) { + hist_idx = hist_count; + input_len = 0; input_cursor = 0; input_buf[0] = 0; + } + } + e0 = 0; + redraw_input_line(); + continue; + } + + if (sc == 0x1C) { /* Enter */ + input_buf[input_len] = 0; + term_putc('\n'); + if (input_len > 0) add_history(input_buf); + execute_command(input_buf); + cmd_done = 1; + continue; + } + if (sc == 0x0E) { /* Backspace */ + if (input_cursor > 0) { + for (int i = input_cursor - 1; i < input_len - 1; i++) + input_buf[i] = input_buf[i+1]; + input_len--; input_cursor--; + input_buf[input_len] = 0; + } + redraw_input_line(); + continue; + } + char c = da_scan_to_ascii(sc, shift); + if (c && c >= 32 && c <= 126 && input_len < 255) { + for (int i = input_len; i > input_cursor; i--) + input_buf[i] = input_buf[i-1]; + input_buf[input_cursor++] = c; + input_len++; + redraw_input_line(); + } + } + redraw_all(); + } +} diff --git a/CODE/tools/desktop_app.h b/CODE/tools/desktop_app.h new file mode 100644 index 0000000..82cada8 --- /dev/null +++ b/CODE/tools/desktop_app.h @@ -0,0 +1,374 @@ +/* desktop_app.h — Deshab 独立工具应用公共头文件 + * + * 独立工具应用运行在 DSK 加载的 .elf 中,全屏独占 framebuffer。 + * 每个 .elf 实现 dsk_entry(dsk_boot_context *ctx) 入口点。 + * 通过 kernel_api 访问块设备、网络等内核服务。 + * PS/2 键盘/鼠标通过端口 I/O 直接轮询。 + */ + +#ifndef DESHAB_DESKTOP_APP_H +#define DESHAB_DESKTOP_APP_H + +/* 基础类型 */ +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; +typedef long long i64; + +/* 端口 I/O */ +static inline void outb(u16 port, u8 val) { __asm__ volatile("outb %0,%1"::"a"(val),"Nd"(port)); } +static inline u8 inb(u16 port) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(port)); return v; } + +/* COM1 串口日志 */ +#define DA_COM1 0x3F8 +static inline void da_sputc(char c) { for(unsigned i=0;i<100000;i++){if(inb(DA_COM1+5)&0x20)break;} outb(DA_COM1,(u8)c); } +static inline void da_swrite(const char *s) { while(*s){if(*s=='\n')da_sputc('\r');da_sputc(*s++);} } +static inline void da_slog(const char *tag, const char *msg) { da_swrite("["); da_swrite(tag); da_swrite("] "); da_swrite(msg); da_swrite("\n"); } + +/* dsk_boot_context 定义(与 dsk.h 一致,自包含避免依赖) */ +#define DA_BOOT_MAGIC 0x44534B31424F4F54ULL +typedef struct { + u64 magic; u32 abi_version; u32 size; u64 flags; + u64 hhdm_offset; u64 rsdp_address; + u64 framebuffer_address; u64 framebuffer_width; u64 framebuffer_height; + u64 framebuffer_pitch; u32 framebuffer_bpp; u32 reserved_fb; + u64 boot_modules_response; + u64 dkm_kernel_api; u64 dkm_driver_table; u64 dkm_driver_count; + u64 utsm_state; u64 drr_state; + u64 memory_map; u64 memory_map_count; u64 memory_map_entry_size; + u64 kernel_stack_top; u64 reserved[8]; +} da_boot_context; + +/* block_read 函数类型 */ +typedef int (*da_block_read_fn)(void *ctx, u64 lba, u32 count, void *buffer); + +/* 应用上下文 — 从 dsk_boot_context 提取的常用数据 */ +typedef struct { + u32 *fb; /* framebuffer 基址 */ + u64 fb_w; /* 宽度 */ + u64 fb_h; /* 高度 */ + u64 fb_pitch; /* 行宽(字节) */ + da_block_read_fn block_read; + const void *kernel_api; +} da_app_context; + +/* 从 dsk_boot_context 初始化 da_app_context */ +static inline void da_init(da_app_context *ac, const da_boot_context *ctx) { + ac->fb = (u32 *)ctx->framebuffer_address; + ac->fb_w = ctx->framebuffer_width; + ac->fb_h = ctx->framebuffer_height; + ac->fb_pitch = ctx->framebuffer_pitch; + ac->kernel_api = (const void *)ctx->dkm_kernel_api; + /* block_read 从 kernel_api + 0xA8 获取 */ + u64 api = ctx->dkm_kernel_api; + ac->block_read = (da_block_read_fn)*(u64 *)(api + 0xA8); + if (ac->block_read) + ac->block_read = (da_block_read_fn)*(u64 *)((u64)ac->block_read + 16); +} + +/* ========== 帧缓冲绘制 ========== */ + +static inline void da_pixel(da_app_context *ac, i64 x, i64 y, u32 color) { + if (x < 0 || (u64)x >= ac->fb_w || y < 0 || (u64)y >= ac->fb_h) return; + u32 *line = (u32 *)((u8 *)ac->fb + (u64)y * ac->fb_pitch); + line[x] = color; +} + +static inline u32 da_pixel_read(da_app_context *ac, i64 x, i64 y) { + if (x < 0 || (u64)x >= ac->fb_w || y < 0 || (u64)y >= ac->fb_h) return 0; + u32 *line = (u32 *)((u8 *)ac->fb + (u64)y * ac->fb_pitch); + return line[x]; +} + +static inline u32 da_blend(u32 bg, u32 fg, u32 alpha) { + u32 na = 256 - alpha; + u32 r = ((bg & 0xFF) * na + (fg & 0xFF) * alpha) >> 8; + u32 g = (((bg >> 8) & 0xFF) * na + ((fg >> 8) & 0xFF) * alpha) >> 8; + u32 b = (((bg >> 16) & 0xFF) * na + ((fg >> 16) & 0xFF) * alpha) >> 8; + return 0xFF000000u | (b << 16) | (g << 8) | r; +} + +static inline void da_fill_rect(da_app_context *ac, i64 x, i64 y, i64 w, i64 h, u32 color) { + for (i64 r = 0; r < h; r++) { + i64 yy = y + r; + if (yy < 0 || (u64)yy >= ac->fb_h) continue; + u32 *line = (u32 *)((u8 *)ac->fb + (u64)yy * ac->fb_pitch); + for (i64 c = 0; c < w; c++) { + i64 xx = x + c; + if (xx < 0 || (u64)xx >= ac->fb_w) continue; + line[xx] = color; + } + } +} + +static inline void da_fill_bg(da_app_context *ac, u32 color) { + da_fill_rect(ac, 0, 0, (i64)ac->fb_w, (i64)ac->fb_h, color); +} + +/* 圆角矩形 */ +static inline void da_fill_rounded_rect(da_app_context *ac, i64 x, i64 y, i64 w, i64 h, u32 color, i64 radius) { + i64 r = radius; + if (r > w/2) r = w/2; + if (r > h/2) r = h/2; + da_fill_rect(ac, x+r, y, w-2*r, h, color); + da_fill_rect(ac, x, y+r, r, h-2*r, color); + da_fill_rect(ac, x+w-r, y+r, r, h-2*r, color); + for (i64 dy = 0; dy < r; dy++) + for (i64 dx = 0; dx < r; dx++) + if (dx*dx+dy*dy <= r*r) { + da_pixel(ac, x+r-1-dx, y+r-1-dy, color); + da_pixel(ac, x+w-r+dx, y+r-1-dy, color); + da_pixel(ac, x+r-1-dx, y+h-r+dy, color); + da_pixel(ac, x+w-r+dx, y+h-r+dy, color); + } +} + +static inline void da_rect_outline(da_app_context *ac, i64 x, i64 y, i64 w, i64 h, u32 color, i64 radius) { + i64 r = radius; + if (r > w/2) r = w/2; + if (r > h/2) r = h/2; + for (i64 c = r; c < w-r; c++) { da_pixel(ac, x+c, y, color); da_pixel(ac, x+c, y+h-1, color); } + for (i64 rr = r; rr < h-r; rr++) { da_pixel(ac, x, y+rr, color); da_pixel(ac, x+w-1, y+rr, color); } + for (i64 dy = 0; dy <= r; dy++) + for (i64 dx = 0; dx <= r; dx++) { + i64 d2 = dx*dx+dy*dy; + if (d2 <= r*r && d2 > (r-1)*(r-1)) { + da_pixel(ac, x+r-dx, y+r-dy, color); + da_pixel(ac, x+w-1-r+dx, y+r-dy, color); + da_pixel(ac, x+r-dx, y+h-1-r+dy, color); + da_pixel(ac, x+w-1-r+dx, y+h-1-r+dy, color); + } + } +} + +/* ========== ASCII 字体渲染 ========== + * + * 使用方法:先 #include ascii_bitmaps.c(定义 static g_ascii/g_ascii_w/g_ascii_h), + * 再 #include desktop_app.h。da_draw_char/da_draw_string 直接引用 g_ascii。 + * 与 deshab_ui.h 使用相同的包含顺序约定。 + */ + +static inline void da_draw_char(da_app_context *ac, char ch, i64 x, i64 y, u32 fg, u32 bg) { + u32 idx = (u32)(ch - ' '); + if (idx > 94) idx = 0; + const u8 *glyph = g_ascii[idx]; + i64 gw = g_ascii_w, gh = g_ascii_h; + for (i64 r = 0; r < gh; r++) { + i64 yy = y + r; + if (yy < 0 || (u64)yy >= ac->fb_h) continue; + u32 *line = (u32 *)((u8 *)ac->fb + (u64)yy * ac->fb_pitch); + for (i64 c = 0; c < gw; c++) { + u8 a = glyph[r * gw + c]; + if (a == 0) continue; + i64 xx = x + c; + if (xx < 0 || (u64)xx >= ac->fb_w) continue; + line[xx] = (a == 255) ? fg : da_blend(bg, fg, a); + } + } +} + +static inline void da_draw_string(da_app_context *ac, const char *s, i64 x, i64 y, u32 fg, u32 bg, i64 step) { + i64 cx = x; + while (*s) { da_draw_char(ac, *s, cx, y, fg, bg); cx += step; s++; } +} + +/* ========== 鼠标光标 ========== */ +#define DA_CURSOR_SIZE 24 + +static const u8 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 { + u32 save[DA_CURSOR_SIZE * DA_CURSOR_SIZE]; + int saved; + int old_x, old_y; + int mx, my; + int btn; +} da_cursor; + +static inline void da_cursor_init(da_cursor *c, i64 fb_w, i64 fb_h) { + for (int i = 0; i < DA_CURSOR_SIZE*DA_CURSOR_SIZE; i++) c->save[i] = 0; + c->saved = 0; c->old_x = -1; c->old_y = -1; + c->mx = (int)(fb_w / 2); c->my = (int)(fb_h / 2); c->btn = 0; +} + +static inline void da_cursor_save(da_app_context *ac, da_cursor *c) { + for (int r = 0; r < DA_CURSOR_SIZE; r++) + for (int col = 0; col < DA_CURSOR_SIZE; col++) { + int x = c->mx + col, y = c->my + r; + c->save[r * DA_CURSOR_SIZE + col] = da_pixel_read(ac, x, y); + } + c->saved = 1; c->old_x = c->mx; c->old_y = c->my; +} + +static inline void da_cursor_restore(da_app_context *ac, da_cursor *c) { + if (!c->saved) return; + for (int r = 0; r < DA_CURSOR_SIZE; r++) + for (int col = 0; col < DA_CURSOR_SIZE; col++) { + int x = c->old_x + col, y = c->old_y + r; + da_pixel(ac, x, y, c->save[r * DA_CURSOR_SIZE + col]); + } + c->saved = 0; +} + +static inline void da_cursor_draw(da_app_context *ac, da_cursor *c, u32 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]) + da_pixel(ac, c->mx + col, c->my + r, color); +} + +/* ========== PS/2 鼠标 ========== */ +typedef struct { + u8 buf[3]; + int idx; + int has_pkt; +} da_mouse; + +static inline void da_mouse_init(void) { + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0xA8); + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0x20); + for (int t = 0; t < 100000; t++) { if (inb(0x64) & 1) break; } + u8 cfg = inb(0x60); + cfg |= 0x02; cfg &= ~0x20; + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0x60); + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x60, cfg); + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0xD4); + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x60, 0xFF); + for (int t = 0; t < 100000; t++) { if (inb(0x64) & 1) break; } + inb(0x60); + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x64, 0xD4); + for (int t = 0; t < 100000; t++) { if (!(inb(0x64) & 2)) break; } + outb(0x60, 0xF4); + for (int t = 0; t < 100000; t++) { if (inb(0x64) & 1) break; } + inb(0x60); +} + +static inline int da_mouse_poll(da_mouse *m, da_cursor *c, i64 fb_w, i64 fb_h) { + u8 st = inb(0x64); + if (!(st & 1)) return 0; + u8 data = inb(0x60); + if (!(st & 0x20)) return 0; + m->buf[m->idx++] = data; + if (m->idx < 3) return 0; + m->idx = 0; + if (!(m->buf[0] & 0x08)) return 0; + int dx = (int)(signed char)m->buf[1]; + int dy = (int)(signed char)m->buf[2]; + if (m->buf[0] & 0x10) dx = -dx; + if (!(m->buf[0] & 0x20)) dy = -dy; else dy = -dy; + dy = -dy; + c->mx += dx; c->my += dy; + if (c->mx < 0) c->mx = 0; + if (c->my < 0) c->my = 0; + if (c->mx >= (int)fb_w - DA_CURSOR_SIZE) c->mx = (int)fb_w - DA_CURSOR_SIZE; + if (c->my >= (int)fb_h - DA_CURSOR_SIZE) c->my = (int)fb_h - DA_CURSOR_SIZE; + c->btn = (m->buf[0] & 0x01) ? 1 : 0; + m->has_pkt = 1; + return 1; +} + +/* ========== 键盘 ========== */ +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]; +} + +/* ========== 窗口概念 ========== */ +/* 独立工具全屏运行时的窗口装饰:标题栏 + 关闭按钮 */ + +#define DA_TITLEBAR_H 28 +#define DA_BORDER_W 2 +#define DA_STATUSBAR_H 20 + +/* 色板引用(与 deshab_ui.h 一致) */ +#define DA_BG_PRIMARY 0xFF0A1428u /* DP_ABYSS_900 */ +#define DA_BG_SECONDARY 0xFF0F1E38u /* DP_ABYSS_800 */ +#define DA_BG_TERTIARY 0xFF162848u /* DP_ABYSS_700 */ +#define DA_TEXT_PRIMARY 0xFFE8E8F0u /* DP_NEUTRAL_700 */ +#define DA_TEXT_DIM 0xFF6A6A88u /* DP_NEUTRAL_300 */ +#define DA_ACCENT 0xFF00A8CCu /* DP_SEAL_500 */ +#define DA_ACCENT_LIGHT 0xFF44CCF0u /* DP_SEAL_300 */ +#define DA_BORDER 0xFF284888u /* DP_ABYSS_500 */ +#define DA_BORDER_FOCUS 0xFF44CCF0u /* DP_SEAL_300 */ +#define DA_CURSOR_COLOR 0xFF00A8CCu /* DP_SEAL_500 */ +#define DA_PROMPT_COLOR 0xFF44CCF0u /* DP_SEAL_300 */ +#define DA_SUCCESS 0xFF40C880u /* DP_SUCCESS */ +#define DA_ERROR 0xFFFF4466u /* DP_ERROR */ +#define DA_WARNING 0xFFF0A030u /* DP_WARNING */ + +static inline void da_draw_titlebar(da_app_context *ac, const char *title, i64 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, i64 w, i64 h) { + i64 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); +} + +/* ========== RTC 时钟 ========== */ +static inline void da_rtc_time(u8 *h, u8 *m) { + outb(0x70, 4); *h = inb(0x71); + outb(0x70, 2); *m = inb(0x71); + *h = (u8)((*h >> 4) * 10 + (*h & 0xF)); + *m = (u8)((*m >> 4) * 10 + (*m & 0xF)); +} + +/* ========== memset/memcpy ========== */ +static inline void *da_memset(void *d, int c, u64 n) { u8 *p=(u8*)d; while(n--)*p++=(u8)c; return d; } +static inline void *da_memcpy(void *d, const void *s, u64 n) { u8 *dd=(u8*)d; const u8 *ss=(const u8*)s; while(n--)*dd++=*ss++; return d; } + +#endif /* DESHAB_DESKTOP_APP_H */ diff --git a/CODE/tools/editor/MAKEFILE b/CODE/tools/editor/MAKEFILE new file mode 100644 index 0000000..da9a6d9 --- /dev/null +++ b/CODE/tools/editor/MAKEFILE @@ -0,0 +1,25 @@ +CC := clang +LD := ld.lld + +ROOT := ../../.. +OUT := $(ROOT)/SYSTEM/system/deshab64/tools/editor.elf + +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I.. -I../../UTSM/include -O2 +LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld + +C_SOURCES := main.c +OBJECTS := $(C_SOURCES:.c=.o) + +.PHONY: all clean + +all: $(OUT) + +$(OUT): $(OBJECTS) linker.ld + @if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }" diff --git a/CODE/tools/editor/linker.ld b/CODE/tools/editor/linker.ld new file mode 100644 index 0000000..94dddf7 --- /dev/null +++ b/CODE/tools/editor/linker.ld @@ -0,0 +1,16 @@ +ENTRY(dsk_entry) + +PHDRS +{ + text PT_LOAD FLAGS(7); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(7); +} + +SECTIONS +{ + . = 0x1000; + .text : ALIGN(4K) { *(.text*) } :text + .rodata : ALIGN(4K) { *(.rodata*) } :rodata + .data : ALIGN(4K) { *(.data*) *(COMMON) *(.bss*) } :data +} diff --git a/CODE/tools/editor/main.c b/CODE/tools/editor/main.c new file mode 100644 index 0000000..fdb863a --- /dev/null +++ b/CODE/tools/editor/main.c @@ -0,0 +1,238 @@ +/* Deshab Editor — 全屏文本编辑器 + * PS/2 键盘输入,行号 + 光标 + 垂直滚动。 + * Ctrl+O 打开文件,Ctrl+S 保存(暂不支持),Esc 退出。 + */ + +#include "../../firstInit/ascii_bitmaps.c" +#include "../desktop_app.h" + +#define CHAR_STEP 12 +#define CHAR_H 18 +#define EDITOR_BUF_SIZE 65536 + +static da_app_context g_ac; + +static char text_buf[EDITOR_BUF_SIZE]; +static int text_len = 0; +static int cursor_pos = 0; +static int scroll_y = 0; +static int cursor_line = 0; +static int cursor_col = 0; +static int modified = 0; +static char filename[64] = "untitled"; +static int ctrl_down = 0; + +static int line_start(int line) { + int l = 0; + for (int i = 0; i < text_len; i++) { + if (l == line) return i; + if (text_buf[i] == '\n') l++; + } + return text_len; +} + +static void recalc_cursor(void) { + cursor_line = 0; cursor_col = 0; + for (int i = 0; i < cursor_pos && i < text_len; i++) { + if (text_buf[i] == '\n') { cursor_line++; cursor_col = 0; } + else cursor_col++; + } +} + +static void init_default_text(void) { + const char *sample = "# Deshab Editor\n#\n\nStart typing here...\n\nCtrl+O: Open Ctrl+S: Save Esc: Exit\n"; + while (*sample && text_len < EDITOR_BUF_SIZE - 1) + text_buf[text_len++] = *sample++; + cursor_pos = text_len; + recalc_cursor(); +} + +static void redraw_all(void) { + da_fill_bg(&g_ac, DA_BG_SECONDARY); + char title[80]; + int p = 0; + const char *t1 = "Editor - "; + while (*t1) title[p++] = *t1++; + for (int i = 0; filename[i] && p < 70; i++) title[p++] = filename[i]; + if (modified) { title[p++] = ' '; title[p++] = '*'; } + title[p] = 0; + da_draw_titlebar(&g_ac, title, (i64)g_ac.fb_w); + + int margin_x = 8; + int top_y = DA_TITLEBAR_H + 4; + int line_num_w = 5 * CHAR_STEP + 8; + int text_x = margin_x + line_num_w; + int visible_rows = ((int)g_ac.fb_h - top_y - DA_STATUSBAR_H - 8) / CHAR_H; + int visible_cols = ((int)g_ac.fb_w - text_x - 8) / CHAR_STEP; + + /* 自动滚动 */ + if (cursor_line < scroll_y) scroll_y = cursor_line; + if (cursor_line >= scroll_y + visible_rows) scroll_y = cursor_line - visible_rows + 1; + + /* 渲染可见行 */ + int line = 0, pos = 0; + for (; pos <= text_len && line < scroll_y + visible_rows; ) { + if (line >= scroll_y) { + int screen_y = top_y + (line - scroll_y) * CHAR_H; + /* 行号 */ + char ln[8]; + ln[0] = '0' + ((line+1)/100)%10; + ln[1] = '0' + ((line+1)/10)%10; + ln[2] = '0' + (line+1)%10; + ln[3] = 0; + da_draw_string(&g_ac, ln, margin_x, screen_y, DA_TEXT_DIM, DA_BG_SECONDARY, CHAR_STEP); + /* 分隔线 */ + da_fill_rect(&g_ac, margin_x + line_num_w - 2, screen_y, 1, CHAR_H, DA_BORDER); + /* 文本 */ + int col = 0; + while (pos < text_len && text_buf[pos] != '\n' && col < visible_cols) { + da_draw_char(&g_ac, text_buf[pos], + text_x + col * CHAR_STEP, screen_y, + DA_TEXT_PRIMARY, DA_BG_SECONDARY); + col++; pos++; + } + } else { + while (pos < text_len && text_buf[pos] != '\n') pos++; + } + if (pos < text_len && text_buf[pos] == '\n') pos++; + line++; + if (pos >= text_len) { + /* 空行也画光标 */ + if (line > cursor_line) break; + } + } + + /* 光标 */ + if (cursor_line >= scroll_y && cursor_line < scroll_y + visible_rows) { + int cy = top_y + (cursor_line - scroll_y) * CHAR_H; + int cx = text_x + cursor_col * CHAR_STEP; + da_fill_rect(&g_ac, cx, cy, 2, CHAR_H, DA_ACCENT_LIGHT); + } + + /* 状态栏 */ + char status[64]; p = 0; + const char *s1 = "L:"; while (*s1) status[p++] = *s1++; + status[p++] = '0' + (cursor_line+1)/10%10; status[p++] = '0' + (cursor_line+1)%10; + const char *s2 = " C:"; while (*s2) status[p++] = *s2++; + status[p++] = '0' + (cursor_col+1)/10%10; status[p++] = '0' + (cursor_col+1)%10; + if (modified) { const char *m = " *"; while (*m) status[p++] = *m++; } + status[p] = 0; + da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h); +} + +__attribute__((visibility("default"))) +void dsk_entry(const da_boot_context *ctx) { + __asm__ volatile("cli"); + da_slog("editor", "boot"); + + if (!ctx || ctx->magic != DA_BOOT_MAGIC) { + da_slog("editor", "bad context"); + for(;;) __asm__("hlt"); + } + + da_init(&g_ac, ctx); + init_default_text(); + redraw_all(); + + int shift = 0, e0 = 0; + for (;;) { + u8 st = inb(0x64); + if (!(st & 1)) { __asm__("pause"); continue; } + u8 data = inb(0x60); + if (st & 0x20) continue; + u8 sc = data; + if (sc == 0xE0) { e0 = 1; continue; } + if (sc == 0x2A || sc == 0x36) { shift = 1; continue; } + if (sc == 0xAA || sc == 0xB6) { shift = 0; continue; } + if (sc == 0x1D) { ctrl_down = 1; continue; } + if (sc == 0x9D) { ctrl_down = 0; continue; } + if (sc & 0x80) { e0 = 0; continue; } + + /* Esc 退出 */ + if (sc == 0x01 && !e0) { da_slog("editor", "exit"); return; } + + if (e0) { + if (sc == 0x48) { /* Up */ + if (cursor_line > 0) { + int prev = line_start(cursor_line - 1); + cursor_pos = prev; + for (int i = 0; i < cursor_col && cursor_pos < text_len && text_buf[cursor_pos] != '\n'; i++) + cursor_pos++; + } + } else if (sc == 0x50) { /* Down */ + int next = line_start(cursor_line + 1); + cursor_pos = next; + for (int i = 0; i < cursor_col && cursor_pos < text_len && text_buf[cursor_pos] != '\n'; i++) + cursor_pos++; + } else if (sc == 0x4B) { /* Left */ + if (cursor_pos > 0) cursor_pos--; + } else if (sc == 0x4D) { /* Right */ + if (cursor_pos < text_len) cursor_pos++; + } else if (sc == 0x47) { /* Home */ + cursor_pos = line_start(cursor_line); + } else if (sc == 0x4F) { /* End */ + cursor_pos = line_start(cursor_line); + while (cursor_pos < text_len && text_buf[cursor_pos] != '\n') cursor_pos++; + } else if (sc == 0x49) { /* PageUp */ + for (int i = 0; i < 10 && cursor_line > 0; i++) { + cursor_pos = line_start(cursor_line - 1); + recalc_cursor(); + } + } else if (sc == 0x51) { /* PageDown */ + for (int i = 0; i < 10; i++) { + int next = line_start(cursor_line + 1); + if (next >= text_len) break; + cursor_pos = next; + recalc_cursor(); + } + } + e0 = 0; + recalc_cursor(); + redraw_all(); + continue; + } + + if (sc == 0x1C) { /* Enter */ + if (text_len < EDITOR_BUF_SIZE - 1) { + for (int i = text_len; i > cursor_pos; i--) text_buf[i] = text_buf[i-1]; + text_buf[cursor_pos] = '\n'; + text_len++; cursor_pos++; + modified = 1; + } + } else if (sc == 0x0E) { /* Backspace */ + if (cursor_pos > 0) { + for (int i = cursor_pos - 1; i < text_len - 1; i++) text_buf[i] = text_buf[i+1]; + text_len--; cursor_pos--; + modified = 1; + } + } else if (sc == 0x53 && e0) { /* Delete */ + if (cursor_pos < text_len) { + for (int i = cursor_pos; i < text_len - 1; i++) text_buf[i] = text_buf[i+1]; + text_len--; + modified = 1; + } + } else { + /* Ctrl+O: 打开文件(提示暂不支持) */ + if (ctrl_down && (sc == 0x18)) { + /* Ctrl+O - stub */ + da_slog("editor", "Ctrl+O - open not yet"); + continue; + } + /* Ctrl+S: 保存(提示暂不支持) */ + if (ctrl_down && (sc == 0x1F)) { + da_slog("editor", "Ctrl+S - save not yet"); + modified = 0; + continue; + } + char c = da_scan_to_ascii(sc, shift); + if (c && c >= 32 && c <= 126 && text_len < EDITOR_BUF_SIZE - 1) { + for (int i = text_len; i > cursor_pos; i--) text_buf[i] = text_buf[i-1]; + text_buf[cursor_pos] = c; + text_len++; cursor_pos++; + modified = 1; + } + } + recalc_cursor(); + redraw_all(); + } +} diff --git a/CODE/tools/fileman/MAKEFILE b/CODE/tools/fileman/MAKEFILE new file mode 100644 index 0000000..bb408c8 --- /dev/null +++ b/CODE/tools/fileman/MAKEFILE @@ -0,0 +1,25 @@ +CC := clang +LD := ld.lld + +ROOT := ../../.. +OUT := $(ROOT)/SYSTEM/system/deshab64/tools/fileman.elf + +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I.. -I../../UTSM/include -O2 +LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld + +C_SOURCES := main.c +OBJECTS := $(C_SOURCES:.c=.o) + +.PHONY: all clean + +all: $(OUT) + +$(OUT): $(OBJECTS) linker.ld + @if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }" diff --git a/CODE/tools/fileman/linker.ld b/CODE/tools/fileman/linker.ld new file mode 100644 index 0000000..94dddf7 --- /dev/null +++ b/CODE/tools/fileman/linker.ld @@ -0,0 +1,16 @@ +ENTRY(dsk_entry) + +PHDRS +{ + text PT_LOAD FLAGS(7); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(7); +} + +SECTIONS +{ + . = 0x1000; + .text : ALIGN(4K) { *(.text*) } :text + .rodata : ALIGN(4K) { *(.rodata*) } :rodata + .data : ALIGN(4K) { *(.data*) *(COMMON) *(.bss*) } :data +} diff --git a/CODE/tools/fileman/main.c b/CODE/tools/fileman/main.c new file mode 100644 index 0000000..41b69be --- /dev/null +++ b/CODE/tools/fileman/main.c @@ -0,0 +1,230 @@ +/* Deshab File Manager — 全屏文件管理器 + * 文件列表视图,鼠标点击选择,键盘导航。 + * FAT32 根目录遍历,Esc 退出。 + */ + +#include "../../firstInit/ascii_bitmaps.c" +#include "../desktop_app.h" + +#define CHAR_STEP 12 +#define CHAR_H 18 +#define MAX_FILES 256 + +static da_app_context g_ac; +static da_cursor g_cursor; +static da_mouse g_mouse; + +typedef struct { + char name[13]; /* 8.3 */ + int is_dir; + u32 size; +} file_entry; + +static file_entry files[MAX_FILES]; +static int file_count = 0; +static int selected = 0; +static int scroll_off = 0; + +static void load_root_dir(void) { + file_count = 0; + if (!g_ac.block_read) return; + + u8 bpb[512]; + if (g_ac.block_read(0, 0, 1, bpb) != 0) return; + if (bpb[510] != 0x55 || bpb[511] != 0xAA) return; + + u32 root_cluster = *(u32 *)(bpb + 44); + u8 sectors_per_cluster = bpb[13]; + u32 fat_start = *(u16 *)(bpb + 14); + u32 data_start = fat_start + (*(u16 *)(bpb + 22)) * 2; + + u8 dir_buf[8192]; + u32 lba = data_start + (root_cluster - 2) * sectors_per_cluster; + if (g_ac.block_read(0, lba, sectors_per_cluster, dir_buf) != 0) return; + + for (int i = 0; i < (int)(sectors_per_cluster * 512) && file_count < MAX_FILES; i += 32) { + u8 first = dir_buf[i]; + if (first == 0x00) break; + if (first == 0xE5) continue; + if (dir_buf[i+11] & 0x08) continue; + if (dir_buf[i+11] & 0x10) continue; /* 暂跳过目录 */ + + file_entry *f = &files[file_count]; + f->is_dir = (dir_buf[i+11] & 0x10) ? 1 : 0; + f->size = *(u32 *)(dir_buf + i + 28); + int p = 0; + for (int j = 0; j < 8 && dir_buf[i+j] != ' '; j++) + f->name[p++] = (char)dir_buf[i+j]; + if (dir_buf[i+8] != ' ') { + f->name[p++] = '.'; + for (int j = 8; j < 11 && dir_buf[i+j] != ' '; j++) + f->name[p++] = (char)dir_buf[i+j]; + } + f->name[p] = 0; + file_count++; + } +} + +static void redraw_all(void) { + da_fill_bg(&g_ac, DA_BG_PRIMARY); + da_draw_titlebar(&g_ac, "Files", (i64)g_ac.fb_w); + + int margin = 12; + int top_y = DA_TITLEBAR_H + 4; + int row_h = CHAR_H + 6; + int visible_rows = ((int)g_ac.fb_h - top_y - DA_STATUSBAR_H - 8) / row_h; + int col_name_x = margin + 32; + int col_size_x = (int)g_ac.fb_w - 120; + int col_type_x = (int)g_ac.fb_w - 60; + + /* 列标题 */ + da_draw_string(&g_ac, "Name", col_name_x, top_y, DA_TEXT_DIM, DA_BG_PRIMARY, CHAR_STEP); + da_draw_string(&g_ac, "Size", col_size_x, top_y, DA_TEXT_DIM, DA_BG_PRIMARY, CHAR_STEP); + da_draw_string(&g_ac, "Type", col_type_x, top_y, DA_TEXT_DIM, DA_BG_PRIMARY, CHAR_STEP); + da_fill_rect(&g_ac, margin, top_y + CHAR_H, (i64)g_ac.fb_w - margin * 2, 1, DA_BORDER); + + /* 文件列表 */ + if (scroll_off < 0) scroll_off = 0; + if (scroll_off > file_count - visible_rows && file_count > visible_rows) scroll_off = file_count - visible_rows; + if (scroll_off < 0) scroll_off = 0; + + for (int i = 0; i < visible_rows; i++) { + int idx = scroll_off + i; + if (idx >= file_count) break; + int y = top_y + CHAR_H + 6 + i * row_h; + + /* 选中高亮 */ + if (idx == selected) { + da_fill_rect(&g_ac, margin, y - 2, (i64)g_ac.fb_w - margin * 2, row_h, DA_BG_TERTIARY); + da_rect_outline(&g_ac, margin, y - 2, (i64)g_ac.fb_w - margin * 2, row_h, DA_ACCENT, 4); + } + + /* 图标 */ + u32 icon_color = files[idx].is_dir ? DA_WARNING : DA_ACCENT; + da_fill_rounded_rect(&g_ac, margin + 4, y, 24, 16, icon_color, 3); + + /* 文件名 */ + u32 name_color = (idx == selected) ? DA_ACCENT_LIGHT : DA_TEXT_PRIMARY; + da_draw_string(&g_ac, files[idx].name, col_name_x, y, name_color, + (idx == selected) ? DA_BG_TERTIARY : DA_BG_PRIMARY, CHAR_STEP); + + /* 大小 */ + if (!files[idx].is_dir) { + char sz[16]; int p = 0; + u32 s = files[idx].size; + if (s == 0) { sz[p++] = '0'; } + else { char tmp[12]; int tl = 0; while(s>0){tmp[tl++]='0'+(s%10);s/=10;} for(int j=tl-1;j>=0;j--)sz[p++]=tmp[j]; } + sz[p++] = ' '; sz[p++] = 'B'; sz[p] = 0; + da_draw_string(&g_ac, sz, col_size_x, y, DA_TEXT_DIM, + (idx == selected) ? DA_BG_TERTIARY : DA_BG_PRIMARY, CHAR_STEP); + } else { + da_draw_string(&g_ac, "--", col_size_x, y, DA_TEXT_DIM, + (idx == selected) ? DA_BG_TERTIARY : DA_BG_PRIMARY, CHAR_STEP); + } + + /* 类型 */ + const char *type = files[idx].is_dir ? "DIR" : "FILE"; + da_draw_string(&g_ac, type, col_type_x, y, DA_TEXT_DIM, + (idx == selected) ? DA_BG_TERTIARY : DA_BG_PRIMARY, CHAR_STEP); + } + + if (file_count == 0) { + da_draw_string(&g_ac, "No files found (empty or no block device)", + margin + 20, top_y + 60, DA_TEXT_DIM, DA_BG_PRIMARY, CHAR_STEP); + } + + /* 状态栏 */ + char status[64]; int p = 0; + const char *p1 = "/ "; while (*p1) status[p++] = *p1++; + status[p++] = '0' + file_count / 10; status[p++] = '0' + file_count % 10; + const char *p2 = " items"; while (*p2) status[p++] = *p2++; + status[p] = 0; + da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h); + + /* 鼠标光标 */ + da_cursor_save(&g_ac, &g_cursor); + da_cursor_draw(&g_ac, &g_cursor, DA_CURSOR_COLOR); +} + +__attribute__((visibility("default"))) +void dsk_entry(const da_boot_context *ctx) { + __asm__ volatile("cli"); + da_slog("fileman", "boot"); + + if (!ctx || ctx->magic != DA_BOOT_MAGIC) { + da_slog("fileman", "bad context"); + for(;;) __asm__("hlt"); + } + + da_init(&g_ac, ctx); + da_cursor_init(&g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h); + da_mouse_init(); + + load_root_dir(); + redraw_all(); + + int e0 = 0, shift = 0; (void)shift; + for (;;) { + int need_redraw = 0; + + /* 鼠标轮询 */ + if (da_mouse_poll(&g_mouse, &g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h)) { + need_redraw = 1; + if (g_cursor.btn) { + /* 点击选择文件 */ + int margin = 12; + int top_y = DA_TITLEBAR_H + 4; + int row_h = CHAR_H + 6; + int click_y = g_cursor.my - top_y - CHAR_H - 6; + if (click_y >= 0) { + int idx = click_y / row_h + scroll_off; + if (idx >= 0 && idx < file_count) { + selected = idx; + need_redraw = 1; + } + } + } + } + + /* 键盘轮询 */ + u8 st = inb(0x64); + if ((st & 1) && !(st & 0x20)) { + u8 data = inb(0x60); + u8 sc = data; + if (sc == 0xE0) { e0 = 1; goto skip; } + if (sc == 0x2A || sc == 0x36) { shift = 1; goto skip; } + if (sc == 0xAA || sc == 0xB6) { shift = 0; goto skip; } + if (sc & 0x80) { e0 = 0; goto skip; } + + if (sc == 0x01) { da_slog("fileman", "exit"); return; } + + if (e0) { + if (sc == 0x48) { /* Up */ + if (selected > 0) selected--; + need_redraw = 1; + } else if (sc == 0x50) { /* Down */ + if (selected < file_count - 1) selected++; + need_redraw = 1; + } + e0 = 0; + goto skip; + } + + if (sc == 0x1C) { /* Enter - 打开 */ + if (selected >= 0 && selected < file_count) { + da_slog("fileman", "open file (stub)"); + } + need_redraw = 1; + } else if (sc == 0x0E) { /* Backspace - 返回上级 */ + da_slog("fileman", "back (root only)"); + need_redraw = 1; + } + } + skip: + if (need_redraw) { + da_cursor_restore(&g_ac, &g_cursor); + redraw_all(); + } + __asm__("pause"); + } +} diff --git a/CODE/tools/ping/MAKEFILE b/CODE/tools/ping/MAKEFILE new file mode 100644 index 0000000..2eef1fd --- /dev/null +++ b/CODE/tools/ping/MAKEFILE @@ -0,0 +1,25 @@ +CC := clang +LD := ld.lld + +ROOT := ../../.. +OUT := $(ROOT)/SYSTEM/system/deshab64/tools/ping.elf + +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 -mno-mmx -msoft-float -m64 -Wall -Wextra -I.. -I../../UTSM/include -O2 +LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld + +C_SOURCES := main.c +OBJECTS := $(C_SOURCES:.c=.o) + +.PHONY: all clean + +all: $(OUT) + +$(OUT): $(OBJECTS) linker.ld + @if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + powershell -NoProfile -Command "Get-ChildItem -Path . -Recurse -Filter *.o | Remove-Item -Force; if (Test-Path '$(OUT)') { Remove-Item '$(OUT)' -Force }" diff --git a/CODE/tools/ping/linker.ld b/CODE/tools/ping/linker.ld new file mode 100644 index 0000000..94dddf7 --- /dev/null +++ b/CODE/tools/ping/linker.ld @@ -0,0 +1,16 @@ +ENTRY(dsk_entry) + +PHDRS +{ + text PT_LOAD FLAGS(7); + rodata PT_LOAD FLAGS(4); + data PT_LOAD FLAGS(7); +} + +SECTIONS +{ + . = 0x1000; + .text : ALIGN(4K) { *(.text*) } :text + .rodata : ALIGN(4K) { *(.rodata*) } :rodata + .data : ALIGN(4K) { *(.data*) *(COMMON) *(.bss*) } :data +} diff --git a/CODE/tools/ping/main.c b/CODE/tools/ping/main.c new file mode 100644 index 0000000..f7fc219 --- /dev/null +++ b/CODE/tools/ping/main.c @@ -0,0 +1,339 @@ +/* Deshab ping — 终端风格 ICMP Echo 工具 stub + * 命令行输入 IP 地址,发送 ICMP Echo Request。 + * 当前为 stub,提示需要 TCP/IP 栈支持。 + * Esc 键退出返回 DSK。 + */ + +#include "../../firstInit/ascii_bitmaps.c" +#include "../desktop_app.h" + +/* ---- 终端参数 ---- */ +#define TERM_COLS 120 +#define TERM_ROWS 40 +#define TERM_MAX_CHARS (TERM_COLS * TERM_ROWS) +#define CHAR_STEP 12 +#define CHAR_H 18 +#define PROMPT_STR "ping> " + +static da_app_context g_ac; + +/* 终端缓冲区 */ +static u8 term_ch[TERM_MAX_CHARS]; +static u32 term_fg[TERM_MAX_CHARS]; +static int term_w = TERM_COLS; +static int term_h = TERM_ROWS; +static int cur_col = 0; +static int cur_row = 0; + +/* 输入缓冲 */ +static char input_buf[256]; +static int input_len = 0; +static int input_cursor = 0; + +/* 命令历史 */ +#define HIST_SIZE 16 +static char history[HIST_SIZE][256]; +static int hist_count = 0; +static int hist_idx = 0; + +/* 坐标 */ +static int term_x, term_y; + +static void term_init_layout(void) { + term_x = 8; + term_y = DA_TITLEBAR_H + 4; + int avail_w = (int)g_ac.fb_w - 16; + int avail_h = (int)g_ac.fb_h - term_y - DA_STATUSBAR_H - 8; + term_w = avail_w / CHAR_STEP; + term_h = avail_h / CHAR_H; + if (term_w < 10) term_w = 10; + if (term_h < 5) term_h = 5; + if (term_w > TERM_COLS) term_w = TERM_COLS; + if (term_h > TERM_ROWS) term_h = TERM_ROWS; +} + +static void term_clear(void) { + for (int i = 0; i < TERM_MAX_CHARS; i++) { + term_ch[i] = ' '; + term_fg[i] = DA_TEXT_PRIMARY; + } + cur_col = 0; cur_row = 0; +} + +static void term_scroll(void) { + for (int r = 1; r < term_h; r++) { + for (int c = 0; c < term_w; c++) { + term_ch[(r-1)*TERM_COLS+c] = term_ch[r*TERM_COLS+c]; + term_fg[(r-1)*TERM_COLS+c] = term_fg[r*TERM_COLS+c]; + } + } + for (int c = 0; c < term_w; c++) { + term_ch[(term_h-1)*TERM_COLS+c] = ' '; + term_fg[(term_h-1)*TERM_COLS+c] = DA_TEXT_PRIMARY; + } + cur_row = term_h - 1; cur_col = 0; +} + +static void term_newline(void) { + cur_col = 0; cur_row++; + if (cur_row >= term_h) term_scroll(); +} + +static void term_putc_color(char c, u32 color) { + if (c == '\n') { term_newline(); return; } + if (c == '\r') { cur_col = 0; return; } + if (c == '\t') { int sp = 4 - (cur_col % 4); for (int i = 0; i < sp; i++) term_putc_color(' ', color); return; } + if (c < ' ' || c > '~') c = '?'; + if (cur_col >= term_w) term_newline(); + int idx = cur_row * TERM_COLS + cur_col; + term_ch[idx] = (u8)c; term_fg[idx] = color; + cur_col++; +} + +static void term_putc(char c) { term_putc_color(c, DA_TEXT_PRIMARY); } +static void term_puts_color(const char *s, u32 color) { while (*s) term_putc_color(*s++, color); } +static void term_puts(const char *s) { term_puts_color(s, DA_TEXT_PRIMARY); } + +/* ---- 绘制 ---- */ +static void redraw_all(void) { + da_fill_bg(&g_ac, DA_BG_PRIMARY); + da_draw_titlebar(&g_ac, "Ping", (i64)g_ac.fb_w); + /* 终端字符 */ + for (int r = 0; r < term_h; r++) { + for (int c = 0; c < term_w; c++) { + int idx = r * TERM_COLS + c; + if (term_ch[idx] == ' ') continue; + da_draw_char(&g_ac, term_ch[idx], + term_x + c * CHAR_STEP, term_y + r * CHAR_H, + term_fg[idx], DA_BG_PRIMARY); + } + } + /* 光标 */ + if (cur_col < term_w && cur_row < term_h) { + da_fill_rect(&g_ac, term_x + cur_col * CHAR_STEP, + term_y + cur_row * CHAR_H + CHAR_H - 3, 11, 2, DA_ACCENT); + } + /* 状态栏 */ + char status[64]; + int p = 0; + const char *s1 = "ICMP Echo | Esc 退出 "; + while (*s1) status[p++] = *s1++; + u8 hh, mm; da_rtc_time(&hh, &mm); + status[p++] = '0' + hh/10; status[p++] = '0' + hh%10; + status[p++] = ':'; status[p++] = '0' + mm/10; status[p++] = '0' + mm%10; + status[p] = 0; + da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h); +} + +/* ---- IP 格式校验 ---- */ +static int is_valid_ip(const char *s) { + int dots = 0, digits = 0; + while (*s) { + if (*s == '.') { dots++; digits = 0; } + else if (*s >= '0' && *s <= '9') { digits++; if (digits > 3) return 0; } + else return 0; + s++; + } + return dots == 3; +} + +/* ---- Ping 模拟 ---- */ +static void do_ping(const char *ip) { + term_puts_color("PING ", DA_PROMPT_COLOR); + term_puts_color(ip, DA_ACCENT_LIGHT); + term_puts_color(" 56(84) bytes of data.\n", DA_TEXT_DIM); + + /* stub: 检查 kernel_api 是否有 net 接口 */ + if (!g_ac.kernel_api) { + term_puts_color(" [错误] 无内核 API\n", DA_ERROR); + return; + } + u64 net_api = *(u64 *)((u64)g_ac.kernel_api + 0xC0); + if (!net_api) { + term_puts_color(" [错误] 需要 TCP/IP 栈支持\n", DA_ERROR); + term_puts_color(" 当前内核未实现 ICMP 协议栈。\n", DA_WARNING); + term_puts_color(" 请等待后续版本更新。\n", DA_TEXT_DIM); + term_putc('\n'); + term_puts_color("--- ", DA_TEXT_DIM); + term_puts_color(ip, DA_TEXT_DIM); + term_puts_color(" ping statistics ---\n", DA_TEXT_DIM); + term_puts_color(" 0 packets transmitted, 0 received, 100% packet loss\n", DA_ERROR); + return; + } + + /* 如果有 net API,此处应实现真正的 ICMP Echo */ + term_puts_color(" [stub] 网络功能尚未实现\n", DA_WARNING); +} + +/* ---- 内置命令 ---- */ +static int str_eq(const char *a, const char *b) { + while (*a && *b) { if (*a != *b) return 0; a++; b++; } + return *a == *b; +} + +static void cmd_help(void) { + term_puts_color("ping — ICMP Echo 请求工具\n", DA_PROMPT_COLOR); + term_puts(" 发送 ICMP Echo Request\n"); + term_puts(" help 显示此帮助\n"); + term_puts(" clear 清屏\n"); + term_puts(" version 显示版本\n"); + term_puts(" Esc 退出\n"); +} + +static void cmd_clear(void) { term_clear(); } + +static void cmd_version(void) { + term_puts_color("Deshab ping v0.1 (stub)\n", DA_SUCCESS); + term_puts_color(" ICMP Echo 功能需要 TCP/IP 栈支持\n", DA_TEXT_DIM); +} + +static void execute_command(const char *cmd) { + while (*cmd == ' ') cmd++; + if (*cmd == 0) return; + + if (str_eq(cmd, "help") || str_eq(cmd, "?")) { cmd_help(); return; } + if (str_eq(cmd, "clear") || str_eq(cmd, "cls")) { cmd_clear(); return; } + if (str_eq(cmd, "version") || str_eq(cmd, "ver")) { cmd_version(); return; } + + /* 非内置命令视为 IP 地址 */ + if (is_valid_ip(cmd)) { + do_ping(cmd); + } else { + term_puts_color("无效 IP 地址: ", DA_ERROR); + term_puts_color(cmd, DA_ERROR); + term_putc('\n'); + term_puts_color("请输入有效的 IPv4 地址 (如 192.168.1.1)\n", DA_TEXT_DIM); + } +} + +static void add_history(const char *cmd) { + if (hist_count < HIST_SIZE) { + int i; + for (i = 0; cmd[i] && i < 255; i++) history[hist_count][i] = cmd[i]; + history[hist_count][i] = 0; + hist_count++; + } + hist_idx = hist_count; +} + +/* ---- 输入行重绘 ---- */ +static void redraw_input_line(void) { + int plen = 0; + while (PROMPT_STR[plen]) plen++; + i64 y = term_y + (i64)cur_row * CHAR_H; + da_fill_rect(&g_ac, term_x, y, (i64)term_w * CHAR_STEP, CHAR_H, DA_BG_PRIMARY); + for (int i = 0; i < plen; i++) + da_draw_char(&g_ac, PROMPT_STR[i], term_x + i * CHAR_STEP, y, DA_PROMPT_COLOR, DA_BG_PRIMARY); + for (int i = 0; i < input_len; i++) + da_draw_char(&g_ac, input_buf[i], term_x + (plen + i) * CHAR_STEP, y, DA_TEXT_PRIMARY, DA_BG_PRIMARY); + cur_col = plen + input_cursor; + da_fill_rect(&g_ac, term_x + cur_col * CHAR_STEP, y + CHAR_H - 3, 11, 2, DA_ACCENT); +} + +/* ---- 主入口 ---- */ +__attribute__((visibility("default"))) +void dsk_entry(const da_boot_context *ctx) { + __asm__ volatile("cli"); + da_slog("ping", "boot"); + + if (!ctx || ctx->magic != DA_BOOT_MAGIC) { + da_slog("ping", "bad context"); + for(;;) __asm__("hlt"); + } + + da_init(&g_ac, ctx); + term_init_layout(); + term_clear(); + + /* 启动 banner */ + term_puts_color("=== Deshab ping v0.1 (stub) ===\n", DA_ACCENT); + term_puts_color("ICMP Echo 请求工具\n", DA_SUCCESS); + term_puts_color("输入 IP 地址发送 ping | 输入 'help' 查看帮助 | Esc 退出\n", DA_TEXT_DIM); + term_puts_color("注意: 当前需要 TCP/IP 栈支持\n", DA_WARNING); + term_putc('\n'); + + redraw_all(); + + int shift = 0, e0 = 0; + for (;;) { + term_puts_color(PROMPT_STR, DA_PROMPT_COLOR); + input_len = 0; input_cursor = 0; input_buf[0] = 0; + int plen = 0; while (PROMPT_STR[plen]) plen++; + cur_col = plen; + redraw_all(); + + int cmd_done = 0; + while (!cmd_done) { + u8 st = inb(0x64); + if (!(st & 1)) { __asm__("pause"); continue; } + u8 data = inb(0x60); + if (st & 0x20) continue; + u8 sc = data; + if (sc == 0xE0) { e0 = 1; continue; } + if (sc == 0x2A || sc == 0x36) { shift = 1; continue; } + if (sc == 0xAA || sc == 0xB6) { shift = 0; continue; } + if (sc & 0x80) { e0 = 0; continue; } + + /* Esc 退出 */ + if (sc == 0x01 && !e0) { + da_slog("ping", "exit"); + return; + } + + if (e0) { + if (sc == 0x4B) { if (input_cursor > 0) input_cursor--; } + else if (sc == 0x4D) { if (input_cursor < input_len) input_cursor++; } + else if (sc == 0x47) { input_cursor = 0; } + else if (sc == 0x4F) { input_cursor = input_len; } + else if (sc == 0x48) { + if (hist_idx > 0) { + hist_idx--; + int i; for (i = 0; history[hist_idx][i] && i < 255; i++) input_buf[i] = history[hist_idx][i]; + input_len = i; input_cursor = i; input_buf[i] = 0; + } + } + else if (sc == 0x50) { + if (hist_idx < hist_count - 1) { + hist_idx++; + int i; for (i = 0; history[hist_idx][i] && i < 255; i++) input_buf[i] = history[hist_idx][i]; + input_len = i; input_cursor = i; input_buf[i] = 0; + } else if (hist_idx < hist_count) { + hist_idx = hist_count; + input_len = 0; input_cursor = 0; input_buf[0] = 0; + } + } + e0 = 0; + redraw_input_line(); + continue; + } + + if (sc == 0x1C) { /* Enter */ + input_buf[input_len] = 0; + term_putc('\n'); + if (input_len > 0) add_history(input_buf); + execute_command(input_buf); + cmd_done = 1; + continue; + } + if (sc == 0x0E) { /* Backspace */ + if (input_cursor > 0) { + for (int i = input_cursor - 1; i < input_len - 1; i++) + input_buf[i] = input_buf[i+1]; + input_len--; input_cursor--; + input_buf[input_len] = 0; + } + redraw_input_line(); + continue; + } + char c = da_scan_to_ascii(sc, shift); + if (c && c >= 32 && c <= 126 && input_len < 255) { + for (int i = input_len; i > input_cursor; i--) + input_buf[i] = input_buf[i-1]; + input_buf[input_cursor++] = c; + input_len++; + redraw_input_line(); + } + } + redraw_all(); + } +} diff --git a/RE/docs/desktop_architecture.md b/RE/docs/desktop_architecture.md new file mode 100644 index 0000000..466cf99 --- /dev/null +++ b/RE/docs/desktop_architecture.md @@ -0,0 +1,461 @@ +# Deshab 桌面环境架构设计 + +## 1. 概述 + +Deshab 桌面环境(Deshab Desktop Environment, DDE)是基于 UTSM/DSK 内核的图形桌面系统, +运行在 SAS-R0(单地址空间 Ring0)任务模型上,使用 "Sealed Arc" 视觉风格系统。 + +**设计原则**: +- **SAS-R0 适配**:无内存隔离,所有应用共享地址空间,信任模型为 Ring0 全信任 +- **单屏幕协作**:初期采用"多窗口前台 + 桌面后台"模式,桌面管理器始终驻留 +- **应用独立开发**:内置应用使用回调模式;未来工具应用为独立 .elf,通过 ELF 加载方式载入 +- **帧缓冲直绘**:无 GPU 加速,所有渲染通过 du_context 直接写 framebuffer + +**当前实现状态**:desktop.elf 已实现完整的桌面管理器框架,包括窗口管理、事件路由、 +PS/2 鼠标初始化+轮询、光标 save/restore、任务栏、桌面图标和 3 个内置应用。 + +## 2. 系统架构 + +``` +┌─────────────────────────────────────────────────────┐ +│ Framebuffer │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ Desktop Manager (desktop.elf) │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ +│ │ │ Window 1 │ │ Window 2 │ │ Window 3 │ │ │ +│ │ │ (bash) │ │ (editor) │ │ (calc) │ │ │ +│ │ └──────────┘ └──────────┘ └──────────┘ │ │ +│ │ │ │ +│ │ ┌─────────────────────────────────────────┐│ │ +│ │ │ Taskbar (40px) ││ │ +│ │ └─────────────────────────────────────────┘│ │ +│ └─────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────┘ +``` + +## 3. 启动流程 + +``` +UEFI → Limine → utsm.elf → DKM 驱动加载 + → deshab.elf (DSK) → spinner → firstInit.txt 检查 + → mouseInit.elf → netman.elf → [首次] FirstInit.elf + → desktop.elf ← 桌面管理器 +``` + +DSK 在 FirstInit 完成或 firstInit=1 时,加载 `desktop.elf`。 +desktop.elf 通过 `dsk_entry(dsk_boot_context *ctx)` 入口接收系统上下文, +自行初始化 PS/2 鼠标、注册应用、进入主事件循环。 + +## 4. Desktop Manager 核心设计 + +### 4.1 窗口模型 + +```c +typedef struct desktop_window { + int id; /* 窗口 ID(自增分配) */ + int x, y, w, h; /* 窗口位置和尺寸 */ + int visible; /* 可见性 */ + int focused; /* 焦点 */ + int minimized; /* 最小化 */ + char title[48]; /* 窗口标题 */ + + /* 应用关联 */ + int app_id; /* 关联的应用 ID(g_apps 索引) */ + void *app_state; /* 应用私有状态(on_create 返回值) */ + int closing; /* 关闭中标记 */ + int dirty; /* 脏标记(需要重绘) */ +} desktop_window; +``` + +**Z 序**:窗口按数组顺序排列,数组末尾的窗口在最上层。`win_bring_to_front()` 将窗口移到数组末尾。 + +**窗口限制**:最多 8 个窗口(`MAX_WINDOWS`)。 + +**固定地址分配**:当前内置应用使用固定地址分配状态结构,避免堆依赖: +- `0x4000000` — bash_state(终端) +- `0x5000000` — editor_state(编辑器) +- `0x6000000` — calc_state(计算器) + +### 4.2 事件系统 + +```c +typedef enum { + EV_MOUSE_MOVE = 1, + EV_MOUSE_DOWN, + EV_MOUSE_UP, + EV_KEY_DOWN, + EV_KEY_UP, +} ev_type; + +typedef struct { + ev_type type; + int mx, my; /* 鼠标全局坐标 */ + int button; /* 0=左, 1=右 */ + u8 scancode; + int shift, ctrl, alt; +} desktop_event; +``` + +**事件路由**: +1. **鼠标事件** → 从后往前命中测试 → 找到目标窗口 → 检查关闭按钮/标题栏/客户区 → 路由到窗口回调 +2. **键盘事件** → 当前焦点窗口的 `on_event` 回调 +3. **任务栏点击** → 桌面管理器直接处理(快速启动/窗口按钮) +4. **桌面图标点击** → 桌面管理器处理(`launch_app()`) + +**拖拽机制**: +- 鼠标按下 + 命中标题栏 → 进入拖拽模式(`g_dragging=1`) +- 拖拽期间鼠标移动 → 更新窗口位置(`w->x = mouse_x - offset`) +- 鼠标释放 → 退出拖拽模式 + +**关闭机制**: +- 鼠标点击窗口右上角红色 X 按钮(24×20 圆角矩形,DP_ERROR 底色) +- `win_destroy()` 调用应用的 `on_destroy`,从窗口数组移除,焦点转移到最后一个窗口 + +### 4.3 应用 ABI(回调模式) + +```c +typedef struct app_ctx { + du_context *fb; /* 全局帧缓冲渲染上下文 */ + desktop_win *win; /* 关联窗口 */ + int client_x, client_y, client_w, client_h; /* 客户区坐标 */ + int should_exit; /* 退出标志 */ + desktop_block_read_fn block_read; /* FAT32 块读取函数 */ + const void *kernel_api; /* DKM kernel_api 分发表 */ +} app_ctx; + +typedef void *(*app_create_fn)(app_ctx *ctx); +typedef void (*app_event_fn)(void *state, app_ctx *ctx, desktop_event *ev); +typedef void (*app_draw_fn)(void *state, app_ctx *ctx); +typedef void (*app_destroy_fn)(void *state); + +typedef struct { + const char *name; /* 应用标识名 */ + const char *display_name; /* UI 显示名 */ + u32 default_w, default_h; /* 默认窗口尺寸 */ + app_create_fn on_create; + app_event_fn on_event; + app_draw_fn on_draw; + app_destroy_fn on_destroy; +} app_descriptor; +``` + +**应用生命周期**: +1. 用户点击桌面图标或快速启动 → `launch_app(app_id)` +2. 创建窗口 → 构建 `app_ctx` → 调用 `on_create(ctx)` → 返回 `app_state` +3. 事件循环中:`on_event(state, ctx, ev)` 处理输入 +4. 绘制时:`on_draw(state, ctx)` 渲染客户区 +5. 关闭窗口 → `on_destroy(state)` 清理 + +**当前注册的应用**: + +| app_id | name | display_name | 默认尺寸 | 状态 | +|--------|------|-------------|----------|------| +| 0 | bash | Terminal | 640×440 | 已实现 | +| 1 | editor | Editor | 600×450 | 已实现 | +| 2 | fileman | Files | 500×400 | 占位(无回调) | +| 3 | calc | Calculator | 280×400 | 已实现 | + +### 4.4 任务栏 (Taskbar) + +位置:屏幕底部,高度 40px。 + +布局: +``` +┌──────────────────────────────────────────────────────────┐ +│ [D] │ 🖥️ 📝 📁 🧮 │ │ 12:34 │ +│Start│ 快速启动(4图标) │ 窗口按钮 │ RTC时钟 │ +└──────────────────────────────────────────────────────────┘ +``` + +分区: +- **Start 按钮**(48×32,DP_ABYSS_600 圆角矩形):显示 "D" 字符(DS_DARK_ACCENT_LIGHT) +- **快速启动**(4 图标 × 40px):按 app_id 顺序绘制程序化图标(终端/编辑器/文件夹/计算器) +- **窗口按钮区**:每个打开窗口一个 100×32 按钮,焦点窗口用 DS_DARK_ACCENT 底色,其余用 DP_ABYSS_600 +- **RTC 时钟**(右侧 80px 区域):通过 CMOS I/O(端口 0x70/0x71)读取 BCD 时分,转换为 HH:MM 格式 + +### 4.5 桌面图标 + +位置:桌面右侧垂直排列。 + +参数: +- 列宽 80px(`DESKTOP_ICON_W`),行高 72px(`DESKTOP_ICON_H`) +- 左边距 16px(`ICON_GUTTER_X`),上边距 16px(`ICON_GUTTER_Y`) +- 起始 X = `fb_width - 80 - 16` + +图标:32×32 像素,程序化生成(无需位图文件),通过 `draw_icon_*` 函数绘制: +- **终端图标**:DP_ABYSS_700 圆角矩形 + DP_SEAL_300 的 >_ 符号 + DP_SEAL_500 边框 +- **编辑器图标**:DP_ABYSS_700 圆角矩形 + DP_SEAL_500 文本行 + DP_SEAL_300 边框 +- **文件夹图标**:DP_ARC_500 圆角矩形 + DP_ARC_300 文件夹形状 + DP_ARC_700 边框 +- **计算器图标**:DP_ABYSS_700 圆角矩形 + DP_WARNING 按键 + DP_NEUTRAL_400 网格 + +标签:图标下方 36px 处,居中绘制应用 display_name,使用 DS_DARK_TEXT_PRIMARY 颜色。 + +### 4.6 窗口装饰 + +**标题栏**(28px,`TITLEBAR_H`): +- 焦点窗口:DP_ABYSS_600 背景 +- 非焦点窗口:DP_ABYSS_800 背景 +- 标题文字:左侧 DU_SPACE_SM 偏移,DS_DARK_TEXT_PRIMARY 颜色 +- 关闭按钮:右上角,窗口右边 -28px 处,24×20 圆角矩形,DP_ERROR (红色) 底色 + 白色 "X" + +**边框**(2px,`BORDER_W`): +- 焦点窗口:DS_DARK_BORDER(Seal Cyan 发光效果) +- 非焦点窗口:DS_DARK_BORDER +- 焦点发光:`du_rect_glow()` 3px 扩展范围,10px 衰减 + +**标题栏底线**:1px 分隔线,颜色同边框色。 + +**圆角**:窗口主体使用 `DU_RADIUS_LG` 圆角,关闭按钮使用 `DU_RADIUS_SM` 圆角。 + +### 4.7 应用加载模型 + +**方式一:回调式(当前实现)** + +应用编译进 desktop.elf 内部,通过 `app_descriptor` 注册回调函数。 +桌面管理器在事件循环中调用 `on_event()`/`on_draw()`,应用通过 `app_ctx` 访问系统服务。 + +特点: +- 应用与桌面管理器在同一 ELF 中,共享代码和全局变量 +- 应用状态使用固定地址分配(避免堆依赖) +- 应用可以直接访问 g_fb 全局帧缓冲 + +**方式二:独立 ELF 加载式(未来实现)** + +工具应用独立开发为单独 .elf 文件(如 `CODE/tools/bash/`),通过 DSK 的 FAT32 + ELF loader 加载。 +独立工具实现 `dsk_entry(dsk_boot_context *ctx)` 入口点,接收与 DSK 相同的系统上下文。 + +未来桌面管理器将通过 ELF 加载方式载入工具 .elf,需要实现: +- ELF 加载到内存 + PIE 重定位 +- 传入 dsk_boot_context(framebuffer、kernel_api、block_read 等) +- 返回机制(在 ELF 入口点保存返回地址) +- 独立工具的公共辅助代码(`CODE/tools/desktop_app.h`) + +## 5. 渲染管线 + +``` +redraw_all() 完整管线: + 1. 渐变背景(du_fill_bg_gradient: DP_ABYSS_900 → DP_ABYSS_800) + 2. 桌面图标(draw_desktop_icons) + 3. 窗口循环(从底到顶): + a. 应用 on_draw() 绘制客户区内容 + b. draw_window_frame() 绘制窗口装饰(在应用内容之上) + 4. 任务栏(draw_taskbar) + 5. 鼠标光标(cursor_save_bg + cursor_draw) +``` + +**渲染策略**: +- 每帧全屏重绘(无增量更新) +- 鼠标光标使用 save/restore 机制:移动前恢复旧位置背景,重绘后保存新位置背景 +- 应用直接写入全局 framebuffer(无窗口后缓冲),窗口装饰在应用内容之上绘制 +- 重绘触发:鼠标移动/点击、键盘事件、窗口状态变化 + +## 6. 像素格式 + +与现有 framebuffer 一致:32bpp ARGB(0xAARRGGBB),小端存储。 +所有颜色值使用 deshab_ui.h 中的语义色令牌。 + +## 7. 鼠标光标 + +24×24 像素箭头光标: +- 热点在左上角 (0,0) +- 形状定义:`cursor_shape[24][24]` 位图数组(1=前景,0=透明) +- 前景色:DS_DARK_TEXT_PRIMARY +- save/restore 实现无闪烁移动: + - `cursor_save_bg()`: 保存光标位置 24×24 像素到 `g_cursor_save[]` + - `cursor_restore_bg()`: 恢复上次保存的像素 + - 每帧重绘时:先 restore 旧位置 → 全屏绘制 → save + draw 新位置 +- 边界限制:光标不超出屏幕范围(减去 CURSOR_SIZE) + +## 8. 输入处理 + +### 8.1 PS/2 鼠标 + +**初始化**(`ps2_mouse_init()`): +1. 启用 AUX 设备(命令 0xA8 → 0x64) +2. 读取控制器配置字节(0x20 → 0x64),设置 bit1(AUX IRQ12)+ 清除 bit5(AUX clock) +3. 写回配置(0x60 → 0x64) +4. 复位鼠标(0xD4 → 0x64,0xFF → 0x60) +5. 启用 streaming(0xD4 → 0x64,0xF4 → 0x60) + +**轮询**(`ps2_mouse_poll()`): +- 读取端口 0x64 状态,检查 bit0(数据就绪)和 bit5(AUX 标记) +- 读取 0x60 数据,累积 3 字节组成一个鼠标包 +- 校验 byte0 bit3 同步位 +- 解析 X/Y 位移(带符号扩展)和左键状态(byte0 bit0) +- Y 轴翻转处理(鼠标 Y 向上为正,屏幕 Y 向下为正) +- 边界裁剪:光标限制在屏幕范围内 + +### 8.2 PS/2 键盘 + +- IRQ1 → scan code set 1 +- 扩展键 E0 前缀追踪(`g_e0` 标记) +- Shift 修饰键追踪(0x2A/0x36 按下,0xAA/0xB6 释放) +- `scan_to_ascii()` 映射:normal[58] + shifted[58] 查找表 +- 键盘事件发送到焦点窗口的 `on_event` 回调 + +**全局快捷键**(规划中): +- Alt+Tab:窗口切换 +- Alt+F4:关闭窗口 + +## 9. 文件系统接口 + +工具应用通过 `app_ctx.block_read` 访问 FAT32 文件系统。 + +`block_read` 函数类型: +```c +typedef int (*desktop_block_read_fn)(void *ctx, unsigned long long lba, unsigned int count, void *buffer); +``` + +从 `dkm_kernel_api + 0xA8` 偏移处获取函数指针。 + +暂不实现 VFS 系统调用——所有文件操作通过直接 FAT32 路径解析完成。 + +## 10. 网络接口 + +网络工具(curl、ping、browser)依赖 netman 已实现的: +- DHCP 客户端 +- ARP 解析 +- DNS 查询 +- e1000/virtio_net TX/RX + +工具应用通过 `kernel_api.net` 访问网络功能。 + +## 11. 内置应用详情 + +### 11.1 终端 (Bash) + +- 状态结构:`bash_state` @ 0x4000000 +- 终端缓冲:80 列 × 40 行(TERM_MAX_COLS × TERM_MAX_ROWS) +- 每格存储:字符(u8) + 前景色(u32) +- 输入缓冲:256 字节,支持插入模式和光标移动 +- 提示符:`deshab# ` +- 命令:help, clear, echo, version/ver, uname, date, about, reboot, halt, pwd, whoami, id +- 渲染:先绘制历史内容,再绘制当前输入行+光标 +- 光标:2px 高下划线,DS_DARK_CURSOR 颜色 +- 配色:DS_DARK_* 暗色主题 + +### 11.2 文本编辑器 (Editor) + +- 状态结构:`editor_state` @ 0x5000000 +- 文本缓冲:65536 字节(EDITOR_BUF_SIZE) +- 编辑操作:插入/删除字符、换行、方向键移动、Home/End +- 行号:3 位数字 + 分隔线(DS_DARK_TEXT_DIM 颜色) +- 自动滚动:光标超出可视区域时调整 scroll_y +- 状态栏:底部 20px,显示 L:行 C:列 + 修改标记(*) +- 光标:2px 宽竖线,DS_DARK_CURSOR 颜色 +- 编辑区背景:DS_DARK_BG_SECONDARY +- 窗口标题:`Editor - untitled` + +### 11.3 计算器 (Calculator) + +- 状态结构:`calc_state` @ 0x6000000 +- 运算:四则运算(+/-/×/÷),i64 精度 +- 显示屏:圆角矩形,右对齐数字 +- 按钮网格:4×5 布局(C/+/-/%/÷, 7/8/9/×, 4/5/6/-, 1/2/3/+, 0/./=) +- 按键颜色:功能键 DP_ABYSS_600,运算符 DS_DARK_ACCENT,等号 DP_SUCCESS +- 输入:键盘数字键 + 运算符,C 清零,Enter/= 计算 + +## 12. 分阶段实现计划 + +### Phase 1 — 桌面基础(已完成) +- ✅ Desktop Manager 核心框架 +- ✅ 任务栏 + 时钟 + 桌面图标 +- ✅ 窗口管理(创建/销毁/移动/焦点/拖拽) +- ✅ 事件路由系统 +- ✅ PS/2 鼠标初始化 + 轮询 +- ✅ 光标 save/restore +- ✅ 终端应用(Bash) +- ✅ 文本编辑器(Editor) +- ✅ 计算器(Calculator) + +### Phase 2 — 文件操作与独立应用 +- 工具应用独立开发框架(CODE/tools/ + desktop_app.h) +- FAT32 路径解析增强(目录遍历) +- 文件管理器(独立 .elf) +- 编辑器独立化(从内置 → 独立 tools/editor/) +- 文件打开器(关联 .txt → 编辑器) +- Ctrl+S 保存(需 FAT32 write API) + +### Phase 3 — 网络 +- TCP/IP 最小实现(基于 e1000/virtio_net) +- ping 工具(ICMP Echo) +- curl 工具(HTTP GET) + +### Phase 4 — 高级应用 +- 浏览器(HTTP + 简单 HTML 渲染) +- Bash 增强版(命令历史↑↓、ls、cat、Tab 补全) +- 更多系统工具 + +## 13. 目录结构 + +``` +CODE/ + desktop/ 桌面管理器(当前实现) + main.c 主循环、事件路由、compositing、3个内置应用 + linker.ld PIE 链接脚本 + MAKEFILE 构建脚本(输出 desktop.elf) + + tools/ 独立工具应用(未来实现) + desktop_app.h 公共辅助头文件 + bash/ 独立终端 + main.c + MAKEFILE + linker.ld + editor/ 独立文本编辑器 + main.c + MAKEFILE + linker.ld + fileman/ 文件管理器 + main.c + MAKEFILE + linker.ld + browser/ 浏览器 + main.c + MAKEFILE + linker.ld + curl/ HTTP 客户端 + main.c + MAKEFILE + linker.ld + ping/ ICMP 工具 + main.c + MAKEFILE + linker.ld +``` + +## 14. 构建与打包 + +### 当前构建 + +desktop.elf 编译输出到 `SYSTEM/system/deshab64/desktop.elf`。 + +编译参数(与 DSK / shell / FirstInit 一致): +``` +clang -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin + -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 + -mno-mmx -msoft-float -m64 -Wall -Wextra -I../UTSM/include -O2 +ld.lld -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld +``` + +### 未来工具应用构建 + +独立工具 .elf 将输出到 `SYSTEM/system/deshab64/tools/`,构建集成: +- 每个工具有独立 MAKEFILE(与 desktop 相同模式) +- build.ps1 中添加工具构建步骤 +- mkfat32.c 中添加工具 .elf 入口 + +``` +SYSTEM/system/deshab64/ + desktop.elf ← 桌面管理器 + tools/ + bash.elf ← 独立终端 + editor.elf ← 独立文本编辑器 + fileman.elf ← 文件管理器 + browser.elf ← 浏览器 + curl.elf ← HTTP 客户端 + ping.elf ← ICMP 工具 +``` + +DSK 启动流程: +- 首次启动:mouseInit → netman → FirstInit → desktop +- 非首次启动:mouseInit → netman → desktop diff --git a/RE/docs/tool_apps_design.md b/RE/docs/tool_apps_design.md new file mode 100644 index 0000000..0019252 --- /dev/null +++ b/RE/docs/tool_apps_design.md @@ -0,0 +1,528 @@ +# Deshab 工具应用设计文档 + +## 1. 概述 + +Deshab 工具应用是独立开发、独立编译的 .elf 可执行文件,运行在桌面管理器(Desktop Manager)环境中。 +每个工具在 `CODE/tools/{name}/` 下拥有独立的 `main.c` + `MAKEFILE` + `linker.ld`, +通过 DSK 的 FAT32 + ELF loader 加载,接收 `dsk_boot_context` 系统上下文。 + +**核心约束**: +- 所有应用运行在 Ring0,单地址空间,无内存隔离 +- 独立工具通过 `dsk_entry(dsk_boot_context *ctx)` 入口点启动 +- 工具可以直接访问 framebuffer、PS/2 输入、kernel_api +- 网络工具依赖 kernel_api.net,文件工具依赖 FAT32 block_read +- 公共辅助代码通过 `CODE/tools/desktop_app.h` 共享 + +**开发模式演进**: +- **当前阶段**:3 个内置应用(Bash/Editor/Calculator)使用回调模式编译在 desktop.elf 内 +- **目标阶段**:工具应用独立开发为单独 .elf,桌面管理器通过 ELF 加载方式载入 + +## 2. 应用 ABI + +### 2.1 独立工具入口 + +独立工具 .elf 实现以下入口点: + +```c +__attribute__((visibility("default"))) +void dsk_entry(const dsk_boot_context *ctx); +``` + +工具接收完整的 `dsk_boot_context`,包含: + +```c +typedef struct dsk_boot_context { + u64 magic; /* 0x44534B31424F4F54 ("DSK1BOOT") */ + u32 abi_version; /* 1 */ + u32 size; + + u64 flags; + u64 hhdm_offset; + u64 rsdp_address; + + u64 framebuffer_address; /* 帧缓冲物理地址 (+ HHDM) */ + u64 framebuffer_width; + u64 framebuffer_height; + u64 framebuffer_pitch; + u32 framebuffer_bpp; + + u64 boot_modules_response; + u64 dkm_kernel_api; /* DKM kernel_api 分发表 */ + u64 dkm_driver_table; + u64 dkm_driver_count; + + u64 utsm_state; + u64 drr_state; + u64 memory_map; + u64 memory_map_count; + u64 memory_map_entry_size; + u64 kernel_stack_top; +} dsk_boot_context; +``` + +### 2.2 公共辅助头文件 + +`CODE/tools/desktop_app.h` 提供所有工具共享的定义和辅助函数: + +```c +/* 类型定义 */ +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; +typedef long long i64; + +/* COM1 串口日志 */ +#define COM1 0x3F8 +static inline void outb(u16 port, u8 value); +static inline u8 inb(u16 port); +void sputc(char c); +void swrite(const char *s); +void slog(const char *s); + +/* PS/2 键盘轮询 */ +int ps2kbd_poll(u8 *scancode); /* 返回 1=有数据 */ + +/* PS/2 鼠标轮询 */ +int ps2mouse_poll(int *dx, int *dy, int *btn); /* 返回 1=有数据包 */ + +/* scan code set 1 → ASCII 映射 */ +char scan_to_ascii(u8 sc, int shift); + +/* 帧缓冲绘制辅助 */ +void fb_init(u64 addr, u64 w, u64 h, u64 pitch); +void fb_pixel(int x, int y, u32 color); +void fb_rect(int x, int y, int w, int h, u32 color); +void fb_string(const char *s, int x, int y, u32 fg, u32 bg, int step); + +/* FAT32 文件读取辅助 */ +typedef int (*block_read_fn)(u32 index, u64 lba, u32 count, void *buf); +int fat32_read_file(block_read_fn br, const char *path, u8 **data, u32 *size); + +/* 窗口概念(标题栏+关闭按钮+客户区) */ +#define WIN_TITLEBAR_H 28 +#define WIN_BORDER_W 2 +void win_draw_frame(int x, int y, int w, int h, const char *title, int focused); +int win_hit_close(int wx, int wy, int ww, int wh, int mx, int my); +``` + +### 2.3 当前内置应用 ABI(回调模式) + +当前 3 个内置应用编译在 desktop.elf 内,使用回调模式: + +```c +typedef struct app_ctx { + du_context *fb; /* 全局帧缓冲渲染上下文 */ + desktop_win *win; /* 关联窗口 */ + int client_x, client_y, client_w, client_h; /* 客户区坐标 */ + int should_exit; /* 退出标志 */ + desktop_block_read_fn block_read; /* FAT32 块读取函数 */ + const void *kernel_api; /* DKM kernel_api 分发表 */ +} app_ctx; + +typedef void *(*app_create_fn)(app_ctx *ctx); +typedef void (*app_event_fn)(void *state, app_ctx *ctx, desktop_event *ev); +typedef void (*app_draw_fn)(void *state, app_ctx *ctx); +typedef void (*app_destroy_fn)(void *state); +``` + +### 2.4 应用生命周期 + +**回调式(当前)**: +``` +1. 用户点击图标/快速启动按钮 +2. 桌面管理器创建窗口 +3. 桌面调用 app->on_create(ctx) → 返回 app_state +4. 事件循环:桌面调用 app->on_event(app_state, ctx, ev) +5. 绘制:桌面调用 app->on_draw(app_state, ctx) +6. 用户关闭窗口 → 桌面调用 app->on_destroy(app_state) +7. 桌面销毁窗口,回收资源 +``` + +**独立 ELF 式(未来)**: +``` +1. 桌面管理器通过 FAT32 读取工具 .elf +2. PIE ELF loader 加载 + 重定位 +3. 调用 dsk_entry(ctx) +4. 工具自行接管输入/渲染/主循环 +5. 工具返回 → 桌面管理器恢复控制 +``` + +## 3. 工具应用详细设计 + +### 3.1 终端 (Bash) — CODE/tools/bash/ + +**状态**:内置版本已实现(回调模式);独立版本待开发 + +**功能**: +- 接收 dsk_boot_context,全屏暗色终端 +- PS/2 键盘输入,命令历史(↑↓键) +- 内置命令:help, clear, echo, version/ver, uname, date, about, reboot, halt, pwd, whoami, id, ls, cat +- Sealed Arc 暗色主题(DS_DARK_* 色系) + +**UI 设计**: +``` +┌─ deshab# ────────────────────────────[✕]─┐ +│ deshab# echo "Hello, Deshab!" │ +│ Hello, Deshab! │ +│ deshab# _ │ +│ │ +│ │ +│ │ +├──────────────────────────────────────────┤ +│ / 12:34 │ +└──────────────────────────────────────────┘ +``` + +**独立版本结构**: +- 标题栏:`deshab#` 标题 + 右侧红色 X 关闭按钮 +- 主区域:暗色终端输出区 + 当前输入行 + 闪烁光标 +- 状态栏(底部 20px):左侧当前目录 `/`,右侧 RTC 时间 + +**数据结构**: +```c +typedef struct { + u8 ch[TERM_MAX_CHARS]; /* 字符缓冲 */ + u32 fg[TERM_MAX_CHARS]; /* 前景色缓冲 */ + int cols, rows; /* 终端尺寸 */ + int cur_col, cur_row; /* 当前光标位置 */ + char input_buf[256]; /* 当前输入行 */ + int input_len, input_cursor; /* 输入长度和光标 */ + int prompt_len; /* 提示符长度 */ + char history[16][256]; /* 命令历史 */ + int history_count; /* 历史条数 */ + int history_pos; /* 历史浏览位置 */ +} bash_state; +``` + +**命令实现**: + +| 命令 | 功能 | 依赖 | +|------|------|------| +| help | 显示可用命令列表 | 无 | +| clear | 清屏 | 无 | +| echo | 回显参数 | 无 | +| version / ver | 显示版本 "Deshab OS v0.1.0" | 无 | +| uname | 显示系统名 "Deshab" | 无 | +| date | 显示 RTC 日期时间 | CMOS | +| about | 显示系统信息 | 无 | +| reboot | 重启(0x64→0xFE) | 键盘控制器 | +| halt | 停机(HLT) | 无 | +| pwd | 显示当前目录 "/" | 无 | +| whoami | 显示 "root" | 无 | +| id | 显示 "uid=0(root) gid=0(root)" | 无 | +| ls | 列出目录内容 | FAT32 增强遍历 | +| cat | 显示文件内容 | FAT32 读取 | + +--- + +### 3.2 文本编辑器 (Editor) — CODE/tools/editor/ + +**状态**:内置版本已实现(回调模式);独立版本待开发 + +**功能**: +- 全屏编辑器,行号 + 编辑区 + 状态栏 +- FAT32 文件打开(通过 kernel_api block_read) +- 编辑操作:插入/删除字符、换行、光标移动(方向键/Home/End) +- PageUp/PageDown 翻页 +- 文本选择:Shift+方向键 +- Ctrl+S 保存(需 FAT32 write API) +- 状态栏:行:列、文件名、修改标记 + +**UI 设计**: +``` +┌─ Editor — README.TXT ───────────────[✕]─┐ +│ 1│ # Deshab OS │ +│ 2│ 单地址空间 Ring0 内核实验系统 │ +│ 3│ │ +│ 4│ ## 概述 │ +│ 5│ Deshab 是一个...█ │ +│ │ │ │ +│ │ │ │ +├───┴──────────────────────────────────────┤ +│ L:5 C:15 README.TXT * │ +└──────────────────────────────────────────┘ +``` + +**数据结构**: +```c +typedef struct { + char text[EDITOR_BUF_SIZE]; /* 文本缓冲 (64KB) */ + int text_len; /* 文本长度 */ + int cursor_pos; /* 光标位置(字节偏移) */ + int scroll_y; /* 垂直滚动偏移(行数) */ + int cursor_line; /* 光标行号 */ + int cursor_col; /* 光标列号 */ + int sel_start, sel_end; /* 选择范围 */ + int modified; /* 修改标记 */ + char filename[128]; /* 当前文件名 */ +} editor_state; +``` + +**渲染细节**: +- 行号:3 位数字宽度(`DS_DARK_TEXT_DIM` 颜色)+ 1px 分隔线(`DS_DARK_DIVIDER`) +- 编辑区背景:`DS_DARK_BG_SECONDARY` +- 光标:2px 宽竖线,`DS_DARK_CURSOR` 颜色 +- 状态栏:底部 20px,`DP_ABYSS_800` 背景,显示 `L:行 C:列 文件名 *修改标记` + +--- + +### 3.3 文件管理器 (FileManager) — CODE/tools/fileman/ + +**状态**:占位(当前 desktop.elf 中 fileman 无回调);独立版本待开发 + +**功能**: +- 双栏布局:左侧目录树、右侧文件列表 +- FAT32 目录遍历(需增强到子目录) +- 文件图标区分:目录/ELF/TXT/CONF +- 双击打开:.txt/.conf → 编辑器,.elf → 执行 +- 路径栏 + 返回按钮 + +**UI 设计**: +``` +┌─ 文件管理器 ────────────────────────[✕]─┐ +│ [←] /system/deshab64/ │ +├──────────┬───────────────────────────────┤ +│ / │ 📁 tools/ DIR │ +│ /system │ 📄 desktop.elf 53KB ELF │ +│ /driver │ 📄 editor.elf 28KB ELF │ +│ │ 📄 deshab.elf 131KB ELF │ +│ │ 📄 README.TXT 2KB TXT │ +│ │ 📄 user.conf 1KB CONF │ +├──────────┴───────────────────────────────┤ +│ 6 项 │ +└──────────────────────────────────────────┘ +``` + +**文件图标颜色**: + +| 类型 | 图标 | 主色调 | +|------|------|--------| +| 目录 | 📁 | DP_ARC_500 (紫) | +| ELF | 📄 + 可执行标记 | DP_SEAL_300 (青) | +| TXT | 📄 + 文本标记 | DP_NEUTRAL_300 (灰) | +| CONF | 📄 + 齿轮标记 | DP_WARNING (琥珀) | + +**依赖**:FAT32 子目录遍历(当前 DSK 仅支持根目录读取,需增强) + +--- + +### 3.4 浏览器 (Browser) — CODE/tools/browser/ + +**状态**:Phase 4 实现(依赖 TCP/IP + HTTP + HTML 渲染) + +**功能**: +- 地址栏 + 导航按钮(后退/前进/刷新) +- HTTP GET 请求(通过 kernel_api.net) +- 简单 HTML 渲染:标题、段落、链接、列表 +- 超链接点击导航 +- 历史记录(后退/前进栈) + +**UI 设计**: +``` +┌─ 浏览器 ────────────────────────────[✕]─┐ +│ [←][→][↻] [http://example.com ][Go] │ +├──────────────────────────────────────────┤ +│ Example Domain │ +│ │ +│ This domain is for use in illustrative │ +│ examples in documents. You may use this │ +│ domain in literature... │ +│ │ +│ More information... │ +└──────────────────────────────────────────┘ +``` + +**依赖**: +- TCP/IP 栈(需实现 TCP 连接、发送/接收、拥塞控制) +- HTTP/1.1 客户端(GET 请求、响应解析) +- DNS 解析(netman 已实现) +- HTML 解析器(简化版,仅处理常见标签) + +--- + +### 3.5 HTTP 客户端 (curl) — CODE/tools/curl/ + +**状态**:Phase 3 实现(依赖 TCP/IP 最小实现) + +**功能**: +- 命令行界面运行(终端风格) +- HTTP GET/HEAD 请求 +- URL 解析(host:port/path) +- 响应头显示(`-i` 选项) +- 超时处理 + +**命令行**: +``` +curl http://example.com +curl -i http://example.com +curl --head http://example.com +``` + +**UI 设计**: +``` +┌─ curl ──────────────────────────────[✕]─┐ +│ > curl http://example.com │ +│ HTTP/1.1 200 OK │ +│ Content-Type: text/html │ +│ │ +│ │ +│ │ +│ ... │ +└──────────────────────────────────────────┘ +``` + +**依赖**:TCP/IP 栈 + DNS 解析 + +**实现方式**:作为窗口化终端风格应用运行,输出 HTTP 响应内容。 + +--- + +### 3.6 ICMP 工具 (ping) — CODE/tools/ping/ + +**状态**:Phase 3 实现(依赖 ICMP + ARP) + +**功能**: +- 发送 ICMP Echo Request +- 接收 ICMP Echo Reply +- RTT 计算(基于 PIT 已校准计时器) +- 统计信息显示(发送/接收/丢包率/平均 RTT) + +**命令行**: +``` +ping 192.168.1.1 +ping -c 4 10.0.2.2 +``` + +**UI 设计**: +``` +┌─ ping ──────────────────────────────[✕]─┐ +│ PING 192.168.1.1 │ +│ 64 bytes: seq=0 time=2ms │ +│ 64 bytes: seq=1 time=1ms │ +│ 64 bytes: seq=2 time=3ms │ +│ │ +│ --- 192.168.1.1 ping statistics --- │ +│ 3 packets transmitted, 3 received, 0% │ +│ RTT min/avg/max = 1/2/3 ms │ +└──────────────────────────────────────────┘ +``` + +**依赖**: +- ICMP 协议实现(需通过 kernel_api.net 发送原始 IP 包) +- ARP 解析(netman 已实现) +- 计时器(PIT 已校准) + +--- + +### 3.7 更多应用(规划中) + +| 应用 | 描述 | Phase | 依赖 | +|------|------|-------|------| +| System Monitor | CPU/内存/网络状态 | 2 | kernel_api | +| Paint | 简单绘图工具 | 2 | 无 | +| Settings | 系统偏好设置 | 2 | FAT32 write | +| Screenshot | 屏幕截图保存 | 3 | FAT32 write | +| Music Player | PC 喇叭/AC97 音频 | 4 | 音频驱动 | + +## 4. 依赖矩阵 + +``` + FAT32 Read | FAT32 Write | TCP/IP | ICMP | DNS | ARP | PS/2输入 +bash ✓ | | | | | | ✓ +editor ✓ | (保存) | | | | | ✓ +fileman ✓ | | | | | | ✓ +browser ✓ | | ✓ | | ✓ | ✓ | ✓ +curl ✓ | | ✓ | | ✓ | ✓ | ✓ +ping ✓ | | | ✓ | | ✓ | ✓ +calculator | | | | | | | ✓ +``` + +## 5. 应用图标设计 + +每个应用使用 32×32 像素图标,程序化生成(C 函数绘制,无需位图文件)。 + +| 应用 | 图标描述 | 主色调 | 绘制函数 | +|------|----------|--------|----------| +| bash | 终端窗口 >_ | DP_SEAL_300 (亮青) | `draw_icon_terminal()` | +| editor | 笔记本+文本行 | DP_SEAL_500 (青) | `draw_icon_editor()` | +| fileman | 文件夹 | DP_ARC_500 (紫) | `draw_icon_folder()` | +| calculator | 计算器按键 | DP_WARNING (琥珀) | `draw_icon_calc()` | +| browser | 地球/指南针 | DP_SEAL_300 (亮青) | 待实现 | +| curl | 箭头+网络 | DP_SEAL_700 (深青) | 待实现 | +| ping | 信号波 | DP_SUCCESS (绿) | 待实现 | + +**图标绘制规范**: +- 圆角矩形背景(6px 圆角,`DU_RADIUS_SM`) +- 1px 描边(使用边框色) +- 前景图形使用应用主色调 +- 背景色:DP_ABYSS_700 + +## 6. 构建集成 + +### 6.1 独立工具 MAKEFILE + +每个工具有独立的 MAKEFILE(与 desktop.elf 相同模式): + +```makefile +# tools/bash/MAKEFILE 示例 +CC := clang +LD := ld.lld + +ROOT := ../../.. +OUT := $(ROOT)/SYSTEM/system/deshab64/tools/bash.elf + +CFLAGS := -target x86_64-unknown-none -std=c11 -ffreestanding -fno-builtin \ + -fno-stack-protector -fpie -mno-red-zone -mno-sse -mno-sse2 \ + -mno-mmx -msoft-float -m64 -Wall -Wextra -I.. -I../../UTSM/include -O2 +LDFLAGS := -nostdlib -static -pie -z max-page-size=0x1000 -T linker.ld + +C_SOURCES := main.c +OBJECTS := $(C_SOURCES:.c=.o) + +.PHONY: all clean + +all: $(OUT) + +$(OUT): $(OBJECTS) linker.ld + @if not exist "$(ROOT)\SYSTEM\system\deshab64\tools" mkdir "$(ROOT)\SYSTEM\system\deshab64\tools" + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ +``` + +### 6.2 build.ps1 集成 + +在 `build.ps1` 中添加工具构建步骤: + +```powershell +# 构建工具应用 +$tools = @("bash", "editor", "fileman", "curl", "ping", "browser") +foreach ($tool in $tools) { + Push-Location "CODE/tools/$tool" + & make -f MAKEFILE + Pop-Location +} +``` + +### 6.3 FAT32 镜像更新 + +`mkfat32.c` 中添加工具 .elf 入口: + +``` +SYSTEM/system/deshab64/tools/bash.elf +SYSTEM/system/deshab64/tools/editor.elf +SYSTEM/system/deshab64/tools/fileman.elf +SYSTEM/system/deshab64/tools/curl.elf +SYSTEM/system/deshab64/tools/ping.elf +SYSTEM/system/deshab64/tools/browser.elf +``` + +## 7. 测试策略 + +- **单元测试**:每个应用的事件处理回调可用模拟事件测试 +- **QEMU 集成测试**:完整的启动→桌面→打开应用→操作→关闭流程 +- **串口日志**:所有应用通过 COM1 输出调试信息(`[bash]`、`[editor]` 等前缀) +- **FAT32 验证**:使用 `driver/test.fat32` 测试镜像验证文件读取功能 +- **独立工具验证**:DSK 直接加载工具 .elf,检查 dsk_entry 入口调用和上下文传递 diff --git a/SYSTEM/boot/utsm.elf b/SYSTEM/boot/utsm.elf index 68557ecd9ebd4e72e78b61b2ea8c8be13a82de68..49b8d3c22848a45167c077e5ddd6182b5e576e1e 100644 GIT binary patch delta 18822 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 wmD3+muutw%W#|O4dw>S; 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 diff --git a/SYSTEM/system/deshab64/deshab.elf b/SYSTEM/system/deshab64/deshab.elf index 692c2181053c6713c987701bdaca42d38f914625..f69adafe33f61e48628e43728795ccba09b0e55f 100644 GIT binary patch delta 433 zcmZ3mS$Dx^-3`tHj4vm<2ox|{Y~Cuch>=M`VRNL=OLj*0$<9*Rj87-$OUW~~OzxM` zS926+VDRW=wUhu-{4PIU^f5CqH2gN$&B(yOz~8z7NO|Z#a-g&*DHjEt`*x2l+k?BHWy@acT#)9Je6#q?ib_uSrm zR;7iJ@y+HSHGO7A>&;D?$JsS=@)J`s^U@VkQj4=o@(UDFGK*4^OL8g|H1hKEbkj16 zic1ud^7BhHCubSvPWCjEVTY>Re8EtNak7B1pHNn5ZUM-Ml6;sclf#W`CciQ+XihL` kPcUHwVkRJF24WTV!Z delta 420 zcmZ3mS$Dx^-3`tHj87-K2ox}~ZQd%dh>_`;{N_lZm+XvMlbxls88=VPmy%~}nA|U= zuf{LVz~Ir#$}Rz<_+5Uy=woJJX!vcgn~{NmfxmSFkn-rYT`>8HR1Txb=4uTu4I{D7z476SA!F_55MxKtpkG`Ao#FI}M|Ujd@~rExh6(CG}#aVG6?CX7JL f1jNih%mT!$K+Fcj>_E%`#GF9PwLQ*++esAwaNCOJ diff --git a/SYSTEM/system/deshab64/desktop.elf b/SYSTEM/system/deshab64/desktop.elf new file mode 100644 index 0000000000000000000000000000000000000000..e4ccfa533a48be27c652f1bba63e7a9367a8476c GIT binary patch literal 69016 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≈_lZl%i2A7*`X0}aAH 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 h-Tm_yG?V#f`ICTExr#Oes4xHh&86n0 literal 0 HcmV?d00001 diff --git a/SYSTEM/system/deshab64/shell.elf b/SYSTEM/system/deshab64/shell.elf index de56ea03aa2c16c48a74073e40f0fbf300e21c8d..4b6d81448ce624cd9401adb29f6264687d493e09 100644 GIT binary patch literal 196632 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%0DQ5mh+nD$t<}T?!52^ego&VRwj{g|6jRpU+VEK~# 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 literal 180160 zcmeEP349bq)}IM8%8}5pCL9tW-k{8RagJ( zy;t>Ky{f+7mN_iWXf!B)%?tw#jB3ME2K#T!em_phVi;`bV`zr|6AZ0bUNhbC=MVjL z@8@+Zzsh||HL-I3dfNp5CS6H44F=^mu-qwhW12sI$genzZW;{AuTqYuHvQj(KobH@ z2s9zkgg_GlO$an0(1bt}0!;`sA<%??J`or#O5aEgxkq*t-IwkvGm75V(H;C zjh{+qOr&zzTQVor-8^yP#7wD?hN23v$p6>Ox;W?}J@7XESKcUND!;o@o z%Fz`3*`-X^Z}i_0(OF^=2P{u881lX;H=l}<^jB`a?RkE+XAxjpGXQDD^2Zb9J++@2 z1IZtQ$sI(dsF^<|(yEdl5II35Ung?9N-iVv7L}Y&2$dW~ zjieV6_E_sXJ${6 z_Z+DNSxoQC$e)OOR3*P6@_UusM5Ie4ONe|yCEp;Dy#lN1IU>)eRx$EXBH1f2vVchT z3XIGnlDz^WClWbLEqgVQT2&&E+Jn-GWPHWSUO?nBwN;&n)EXI2B

a`RFE)OVrH! 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 literal 0 HcmV?d00001 diff --git a/SYSTEM/system/deshab64/tools/curl.elf b/SYSTEM/system/deshab64/tools/curl.elf new file mode 100644 index 0000000000000000000000000000000000000000..ec3da1c55f1e6d6dea3f077754f5c028a6449953 GIT binary patch literal 125152 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

;q$Q}!;58Xrb&od$O{8jnlLo6AzwWw?=Aqpj6NR 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 literal 0 HcmV?d00001 diff --git a/SYSTEM/system/deshab64/tools/fileman.elf b/SYSTEM/system/deshab64/tools/fileman.elf new file mode 100644 index 0000000000000000000000000000000000000000..ea25f321183e960b6adac9db65a16f903ad4b995 GIT binary patch literal 47384 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

s`_a=7!~s~qicEG(cCta@I-0X7i>*u6Cjz1cJr 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 literal 0 HcmV?d00001 diff --git a/build.ps1 b/build.ps1 index 8608bb3..16fe132 100644 --- a/build.ps1 +++ b/build.ps1 @@ -567,6 +567,46 @@ if (-not (Test-Path $ShellOutput)) { } Write-Host "[build] shell Output: $ShellOutput" +$DesktopDir = Join-Path $Root 'CODE\desktop' +$DesktopOutput = Join-Path $SystemDir 'system\deshab64\desktop.elf' + +Write-Host '[build] Building desktop.elf...' +try { + & $make -C "$DesktopDir" -f MAKEFILE "CC=$clang" "LD=$lld" + if ($LASTEXITCODE -ne 0) { + throw "make desktop failed with exit code $LASTEXITCODE" + } +} +finally { +} + +if (-not (Test-Path $DesktopOutput)) { + throw "desktop build did not produce $DesktopOutput" +} +Write-Host "[build] desktop Output: $DesktopOutput" + +# ---------- Tool applications ---------- +$ToolsDir = Join-Path $Root 'CODE\tools' +$ToolsOutDir = Join-Path $SystemDir 'system\deshab64\tools' + +$toolApps = @('bash', 'editor', 'fileman', 'browser', 'curl', 'ping') + +foreach ($tool in $toolApps) { + $toolDir = Join-Path $ToolsDir $tool + $toolMake = Join-Path $toolDir 'MAKEFILE' + if (Test-Path $toolMake) { + Write-Host "[build] Building ${tool}.elf..." + try { + & $make -C "$toolDir" -f MAKEFILE "CC=$clang" "LD=$lld" + if ($LASTEXITCODE -ne 0) { + Write-Host "[build] WARNING: make $tool failed with exit code $LASTEXITCODE" + } + } + finally { + } + } +} + Write-Host '[build] Building DKM network drivers...' Build-DkmDriver (Join-Path $Root 'CODE\DKM\e1000\e1000.c') (Join-Path $SystemDir 'driver\net\e1000.drv') Build-DkmDriver (Join-Path $Root 'CODE\DKM\virtio_net\virtio_net.c') (Join-Path $SystemDir 'driver\net\virtio_net.drv') @@ -581,19 +621,13 @@ Write-Host "[build] Done: $ImagePath" # Rebuild SATA FAT32 disk image (DSK reads deshab.elf/FirstInit.elf/mouseInit.elf from here) Write-Host '[build] Rebuilding SATA FAT32 disk image...' $BuildTmp = Join-Path $Root '.build_tmp' -$MkFat32 = Join-Path $BuildTmp 'mkfat32.exe' -if (Test-Path $MkFat32) { - Push-Location $BuildTmp - try { - & $MkFat32 - if ($LASTEXITCODE -ne 0) { - Write-Host "[build] WARNING: mkfat32 exited with $LASTEXITCODE" - } - } - finally { - Pop-Location +$MkFat32Ps = Join-Path $BuildTmp 'mkfat32.ps1' +if (Test-Path $MkFat32Ps) { + & powershell -ExecutionPolicy Bypass -File $MkFat32Ps + if ($LASTEXITCODE -ne 0) { + Write-Host "[build] WARNING: mkfat32.ps1 exited with $LASTEXITCODE" } Write-Host "[build] SATA IMG: $(Join-Path $BuildTmp 'sata_fat32_dsk.img')" } else { - Write-Host '[build] WARNING: mkfat32.exe not found, SATA image not rebuilt' + Write-Host '[build] WARNING: mkfat32.ps1 not found, SATA image not rebuilt' }