FUCK
This commit is contained in:
+299
-60
@@ -1,22 +1,170 @@
|
||||
/* Deshab Browser — 全屏浏览器 (Stub)
|
||||
* 地址栏 + 导航按钮 + 内容渲染区域。
|
||||
* 当前显示 stub 提示,Esc 退出。
|
||||
/* Deshab Browser — 全屏浏览器
|
||||
* 接入 net_stack.h 共享协议栈,实现 HTTP GET 与简单内容显示。
|
||||
* 地址栏输入 URL(如 http://10.0.2.2:8000/ 或 10.0.2.2),
|
||||
* Enter 发起请求,内容区显示 HTTP 响应(分离 header/body,移除 HTML 标签)。
|
||||
* Esc 退出。
|
||||
*/
|
||||
|
||||
#include "../../firstInit/ascii_bitmaps.c"
|
||||
#include "../desktop_app.h"
|
||||
#include "../net_stack.h"
|
||||
|
||||
#define CHAR_STEP 12
|
||||
#define CHAR_H 18
|
||||
#define URL_BUF_SIZE 128
|
||||
#define CONTENT_BUF_SIZE 32768
|
||||
#define DISPLAY_LINES 200
|
||||
|
||||
/* 前向声明(do_fetch 引用 redraw_all_imm 和 br_ns_init) */
|
||||
static void redraw_all_imm(void);
|
||||
static int br_ns_init(void);
|
||||
|
||||
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);
|
||||
/* 地址栏 */
|
||||
static char url_buf[URL_BUF_SIZE] = "http://10.0.2.2:8000/";
|
||||
static int url_len = 22;
|
||||
static int url_focused = 1; /* 启动时地址栏聚焦 */
|
||||
|
||||
/* 标题栏 */
|
||||
/* 内容缓冲 */
|
||||
static u8 content_buf[CONTENT_BUF_SIZE];
|
||||
static u32 content_len = 0;
|
||||
static int content_scroll = 0;
|
||||
static int fetching = 0; /* 1=正在请求 */
|
||||
static char status_msg[64] = "Ready";
|
||||
|
||||
/* ---- 状态消息 ---- */
|
||||
static void br_set_status(const char *s) {
|
||||
int i = 0;
|
||||
while (*s && i < 63) status_msg[i++] = *s++;
|
||||
status_msg[i] = 0;
|
||||
}
|
||||
|
||||
/* ---- HTTP 请求(复制 shell cmd_curl 核心逻辑) ---- */
|
||||
static void do_fetch(void) {
|
||||
if (url_len == 0) { br_set_status("Empty URL"); return; }
|
||||
|
||||
br_set_status("Initializing net...");
|
||||
redraw_all_imm();
|
||||
|
||||
if (br_ns_init() != 0) {
|
||||
br_set_status("Net init failed");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 解析 URL: [http://]host[:port][/path] */
|
||||
const char *p = url_buf;
|
||||
if (p[0]=='h'&&p[1]=='t'&&p[2]=='t'&&p[3]=='p'&&p[4]==':'&&p[5]=='/'&&p[6]=='/') p += 7;
|
||||
|
||||
char host[64]; int hl = 0;
|
||||
while (*p && *p != ':' && *p != '/' && hl < 63) host[hl++] = *p++;
|
||||
host[hl] = 0;
|
||||
if (!hl) { br_set_status("Bad host"); return; }
|
||||
|
||||
u16 port = 80;
|
||||
if (*p == ':') {
|
||||
p++;
|
||||
u32 pv = 0; int digits = 0;
|
||||
while (*p >= '0' && *p <= '9') { pv = pv * 10 + (u32)(*p - '0'); p++; digits++; }
|
||||
if (digits && pv <= 65535) port = (u16)pv;
|
||||
}
|
||||
char path[128]; int pl = 0;
|
||||
if (*p == '/') {
|
||||
while (*p && *p != ' ' && pl < 127) path[pl++] = *p++;
|
||||
}
|
||||
if (!pl) { path[0] = '/'; pl = 1; }
|
||||
path[pl] = 0;
|
||||
|
||||
/* DNS 解析 */
|
||||
br_set_status("Resolving...");
|
||||
redraw_all_imm();
|
||||
u32 ip;
|
||||
if (ns_dns_resolve(host, &ip) != 0) {
|
||||
br_set_status("DNS failed");
|
||||
return;
|
||||
}
|
||||
|
||||
/* TCP 连接 */
|
||||
br_set_status("Connecting...");
|
||||
redraw_all_imm();
|
||||
if (ns_tcp_connect(ip, port, 3000) != 0) {
|
||||
br_set_status("TCP connect failed");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 构造 HTTP/1.0 请求 */
|
||||
static char req[512];
|
||||
int r = 0;
|
||||
const char *m = "GET "; while (*m) req[r++] = *m++;
|
||||
for (int i = 0; i < pl; i++) req[r++] = path[i];
|
||||
m = " HTTP/1.0\r\nHost: "; while (*m) req[r++] = *m++;
|
||||
for (int i = 0; i < hl; i++) req[r++] = host[i];
|
||||
m = "\r\nConnection: close\r\n\r\n"; while (*m) req[r++] = *m++;
|
||||
req[r] = 0;
|
||||
|
||||
br_set_status("Sending request...");
|
||||
redraw_all_imm();
|
||||
if (ns_tcp_send((const u8 *)req, (u32)r) != 0) {
|
||||
br_set_status("Send failed");
|
||||
ns_tcp_close();
|
||||
return;
|
||||
}
|
||||
|
||||
br_set_status("Receiving...");
|
||||
redraw_all_imm();
|
||||
int n = ns_tcp_recv(0, 0, 5000);
|
||||
ns_tcp_close();
|
||||
|
||||
if (n < 0) { br_set_status("Connection reset"); return; }
|
||||
if (n == 0) { br_set_status("No response (timeout)"); return; }
|
||||
|
||||
/* 复制响应到 content_buf */
|
||||
u32 total = ns_tcp_rx_len;
|
||||
u32 copy = (total < CONTENT_BUF_SIZE) ? total : CONTENT_BUF_SIZE;
|
||||
for (u32 i = 0; i < copy; i++) content_buf[i] = ns_tcp_rx[i];
|
||||
content_len = copy;
|
||||
content_scroll = 0;
|
||||
|
||||
char sm[32]; int sp = 0;
|
||||
const char *ok = "OK "; while (*ok && sp < 30) sm[sp++] = *ok++;
|
||||
char nb[12]; ns_u32_dec(nb, copy);
|
||||
for (int i = 0; nb[i] && sp < 30; i++) sm[sp++] = nb[i];
|
||||
sm[sp] = 0;
|
||||
br_set_status(sm);
|
||||
}
|
||||
|
||||
/* ---- 极简 HTML 文本提取:移除 <...> 标签,保留纯文本 ---- */
|
||||
static int html_to_text(const u8 *in, u32 in_len, u8 *out, u32 out_cap) {
|
||||
u32 oi = 0;
|
||||
int in_tag = 0;
|
||||
int prev_space = 0;
|
||||
for (u32 i = 0; i < in_len && oi < out_cap - 1; i++) {
|
||||
u8 c = in[i];
|
||||
if (c == '<') { in_tag = 1; continue; }
|
||||
if (c == '>') { in_tag = 0; continue; }
|
||||
if (in_tag) continue;
|
||||
/* 跳过 HTTP header(找到 \r\n\r\n 后才开始) */
|
||||
/* 将 等实体简化为空格 */
|
||||
if (c == '\r') continue;
|
||||
if (c == '\t') c = ' ';
|
||||
/* 连续空格压缩 */
|
||||
if (c == ' ') {
|
||||
if (prev_space) continue;
|
||||
prev_space = 1;
|
||||
} else {
|
||||
prev_space = 0;
|
||||
}
|
||||
out[oi++] = c;
|
||||
}
|
||||
out[oi] = 0;
|
||||
return (int)oi;
|
||||
}
|
||||
|
||||
/* ---- 立即重绘(无鼠标光标保存/恢复,用于 fetching 过程) ---- */
|
||||
static void redraw_all_imm(void) {
|
||||
da_fill_bg(&g_ac, DA_BG_PRIMARY);
|
||||
da_draw_titlebar(&g_ac, "Browser", (i64)g_ac.fb_w);
|
||||
|
||||
/* 地址栏 */
|
||||
@@ -24,69 +172,100 @@ static void redraw_all(void) {
|
||||
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);
|
||||
u32 bar_bg = url_focused ? DA_BG_TERTIARY : DA_BG_SECONDARY;
|
||||
da_fill_rounded_rect(&g_ac, bar_x, bar_y, bar_w, bar_h, bar_bg, 6);
|
||||
da_rect_outline(&g_ac, bar_x, bar_y, bar_w, bar_h, url_focused ? DA_ACCENT : 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 + 12;
|
||||
da_draw_string(&g_ac, url_buf, url_x, bar_y + 5, DA_TEXT_PRIMARY, bar_bg, CHAR_STEP);
|
||||
if (url_focused) {
|
||||
int cx = url_x + url_len * CHAR_STEP;
|
||||
da_fill_rect(&g_ac, cx, bar_y + 5, 2, CHAR_H, DA_ACCENT_LIGHT);
|
||||
}
|
||||
|
||||
/* 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 hint_y = bar_y + bar_h + 4;
|
||||
const char *hint = "Enter: fetch Esc: exit Up/Down: scroll";
|
||||
da_draw_string(&g_ac, hint, 12, hint_y, DA_TEXT_DIM, DA_BG_PRIMARY, CHAR_STEP);
|
||||
|
||||
/* 内容区域 */
|
||||
int content_y = bar_y + bar_h + 12;
|
||||
int content_x = 16;
|
||||
int content_w = (int)g_ac.fb_w - 32;
|
||||
/* 内容区 */
|
||||
int content_y = hint_y + CHAR_H + 4;
|
||||
int content_x = 12;
|
||||
int content_w = (int)g_ac.fb_w - 24;
|
||||
int content_h = (int)g_ac.fb_h - content_y - DA_STATUSBAR_H - 8;
|
||||
da_fill_rounded_rect(&g_ac, content_x, content_y, content_w, content_h, DA_BG_SECONDARY, 4);
|
||||
da_rect_outline(&g_ac, content_x, content_y, content_w, content_h, DA_BORDER, 4);
|
||||
|
||||
/* 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);
|
||||
/* 内容显示 */
|
||||
if (content_len > 0) {
|
||||
/* 提取 body(跳过 HTTP header) */
|
||||
u32 body_off = 0;
|
||||
for (u32 i = 0; i + 3 < content_len; i++) {
|
||||
if (content_buf[i]=='\r' && content_buf[i+1]=='\n' &&
|
||||
content_buf[i+2]=='\r' && content_buf[i+3]=='\n') {
|
||||
body_off = i + 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* HTML 转纯文本 */
|
||||
static u8 plain[CONTENT_BUF_SIZE];
|
||||
int plain_len = html_to_text(content_buf + body_off, content_len - body_off, plain, CONTENT_BUF_SIZE);
|
||||
|
||||
/* 居中图标 */
|
||||
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);
|
||||
/* 按行显示 */
|
||||
int visible_cols = (content_w - 16) / CHAR_STEP;
|
||||
int visible_rows = (content_h - 8) / CHAR_H;
|
||||
int y = content_y + 4;
|
||||
int pos = 0;
|
||||
int line = 0;
|
||||
/* 跳过 scroll 行 */
|
||||
int skip = content_scroll;
|
||||
while (pos < plain_len && skip > 0) {
|
||||
if (plain[pos] == '\n') skip--;
|
||||
pos++;
|
||||
}
|
||||
while (pos < plain_len && line < visible_rows) {
|
||||
int ls = pos;
|
||||
while (pos < plain_len && plain[pos] != '\n' && pos - ls < visible_cols) pos++;
|
||||
int llen = pos - ls;
|
||||
for (int i = 0; i < llen; i++) {
|
||||
char c = (char)plain[ls + i];
|
||||
if (c < ' ' || c > '~') c = '.';
|
||||
char buf[2]; buf[0] = c; buf[1] = 0;
|
||||
da_draw_string(&g_ac, buf, content_x + 8 + i * CHAR_STEP, y, DA_TEXT_PRIMARY, DA_BG_SECONDARY, CHAR_STEP);
|
||||
}
|
||||
y += CHAR_H;
|
||||
line++;
|
||||
if (pos < plain_len && plain[pos] == '\n') pos++;
|
||||
}
|
||||
} else if (!fetching) {
|
||||
const char *empty = "No content. Enter URL and press Enter to fetch.";
|
||||
da_draw_string(&g_ac, empty, content_x + 16, content_y + 16, 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);
|
||||
char status[96]; int p = 0;
|
||||
const char *s1 = "Browser | "; while (*s1 && p < 95) status[p++] = *s1++;
|
||||
for (int i = 0; status_msg[i] && p < 95; i++) status[p++] = status_msg[i];
|
||||
status[p] = 0;
|
||||
da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h);
|
||||
}
|
||||
|
||||
/* 鼠标光标 */
|
||||
static void redraw_all(void) {
|
||||
redraw_all_imm();
|
||||
da_cursor_save(&g_ac, &g_cursor);
|
||||
da_cursor_draw(&g_ac, &g_cursor, DA_CURSOR_COLOR);
|
||||
}
|
||||
|
||||
/* ---- net_stack.h 的 ns_init 需要 dkm_kernel_api ---- */
|
||||
/* 重写 ns_init 以从 da_app_context 获取 kernel_api */
|
||||
static int br_ns_init(void) {
|
||||
if (ns_ready) return 0;
|
||||
u64 dkm_kernel_api = (u64)g_ac.kernel_api;
|
||||
if (!dkm_kernel_api) return -1;
|
||||
return ns_init(dkm_kernel_api);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
void dsk_entry(const da_boot_context *ctx) {
|
||||
__asm__ volatile("cli");
|
||||
@@ -100,25 +279,85 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
da_init(&g_ac, ctx);
|
||||
da_cursor_init(&g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h);
|
||||
da_mouse_init();
|
||||
|
||||
/* 初始化网络协议栈 */
|
||||
br_ns_init();
|
||||
|
||||
br_set_status("Ready");
|
||||
redraw_all();
|
||||
|
||||
int e0 = 0, shift = 0;
|
||||
for (;;) {
|
||||
int need_redraw = 0;
|
||||
|
||||
/* 鼠标轮询 */
|
||||
if (da_mouse_poll(&g_mouse, &g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h))
|
||||
if (da_mouse_poll(&g_mouse, &g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h)) {
|
||||
need_redraw = 1;
|
||||
/* 点击地址栏聚焦 */
|
||||
int bar_y = DA_TITLEBAR_H + 6;
|
||||
int bar_h = 28;
|
||||
if (g_cursor.btn &&
|
||||
g_cursor.my >= bar_y && g_cursor.my < bar_y + bar_h &&
|
||||
g_cursor.mx >= 12 && g_cursor.mx < (int)g_ac.fb_w - 12) {
|
||||
url_focused = 1;
|
||||
need_redraw = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 键盘轮询 */
|
||||
u8 st = inb(0x64);
|
||||
if ((st & 1) && !(st & 0x20)) {
|
||||
u8 data = inb(0x60);
|
||||
if (!(data & 0x80) && data == 0x01) {
|
||||
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) { /* Esc */
|
||||
da_slog("browser", "exit");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* 地址栏输入模式 */
|
||||
if (url_focused) {
|
||||
if (sc == 0x1C) { /* Enter - 发起请求 */
|
||||
url_focused = 0;
|
||||
fetching = 1;
|
||||
br_set_status("Fetching...");
|
||||
redraw_all();
|
||||
do_fetch();
|
||||
fetching = 0;
|
||||
need_redraw = 1;
|
||||
goto skip;
|
||||
}
|
||||
if (sc == 0x0E) { /* Backspace */
|
||||
if (url_len > 0) { url_len--; url_buf[url_len] = 0; }
|
||||
need_redraw = 1;
|
||||
goto skip;
|
||||
}
|
||||
char c = da_scan_to_ascii(sc, shift);
|
||||
if (c && c >= 32 && c <= 126 && url_len < URL_BUF_SIZE - 1) {
|
||||
/* 小写转大写便于 URL 输入(除协议头) */
|
||||
url_buf[url_len++] = c;
|
||||
url_buf[url_len] = 0;
|
||||
need_redraw = 1;
|
||||
}
|
||||
goto skip;
|
||||
}
|
||||
|
||||
/* 内容浏览模式 */
|
||||
if (e0) {
|
||||
if (sc == 0x48) { if (content_scroll > 0) content_scroll--; need_redraw = 1; }
|
||||
else if (sc == 0x50) { content_scroll++; need_redraw = 1; }
|
||||
e0 = 0;
|
||||
goto skip;
|
||||
}
|
||||
if (sc == 0x49) { content_scroll -= 10; if (content_scroll < 0) content_scroll = 0; need_redraw = 1; }
|
||||
else if (sc == 0x51) { content_scroll += 10; need_redraw = 1; }
|
||||
else if (sc == 0x1C) { url_focused = 1; need_redraw = 1; } /* Enter 回到地址栏 */
|
||||
}
|
||||
skip:
|
||||
if (need_redraw) {
|
||||
da_cursor_restore(&g_ac, &g_cursor);
|
||||
redraw_all();
|
||||
|
||||
+18
-10
@@ -40,8 +40,9 @@ typedef struct {
|
||||
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);
|
||||
/* block 设备函数类型(与 shell/fat32_io.h 一致:u32 index 固定 0) */
|
||||
typedef int (*da_block_read_fn)(u32 index, u64 lba, u32 count, void *buffer);
|
||||
typedef int (*da_block_write_fn)(u32 index, u64 lba, u32 count, const void *buffer);
|
||||
|
||||
/* 应用上下文 — 从 dsk_boot_context 提取的常用数据 */
|
||||
typedef struct {
|
||||
@@ -49,22 +50,27 @@ typedef struct {
|
||||
u64 fb_w; /* 宽度 */
|
||||
u64 fb_h; /* 高度 */
|
||||
u64 fb_pitch; /* 行宽(字节) */
|
||||
da_block_read_fn block_read;
|
||||
da_block_read_fn block_read;
|
||||
da_block_write_fn block_write;
|
||||
const void *kernel_api;
|
||||
} da_app_context;
|
||||
|
||||
/* 从 dsk_boot_context 初始化 da_app_context */
|
||||
/* 从 dsk_boot_context 初始化 da_app_context。
|
||||
* block API 从 kernel_api + 0xA8 获取:read @ +0x10,write @ +0x18。 */
|
||||
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 获取 */
|
||||
ac->block_read = 0;
|
||||
ac->block_write = 0;
|
||||
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);
|
||||
u64 blk_api = *(u64 *)(api + 0xA8);
|
||||
if (blk_api) {
|
||||
ac->block_read = (da_block_read_fn)*(u64 *)(blk_api + 0x10);
|
||||
ac->block_write = (da_block_write_fn)*(u64 *)(blk_api + 0x18);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 帧缓冲绘制 ========== */
|
||||
@@ -285,10 +291,12 @@ static inline int da_mouse_poll(da_mouse *m, da_cursor *c, i64 fb_w, i64 fb_h) {
|
||||
if (m->idx < 3) return 0;
|
||||
m->idx = 0;
|
||||
if (!(m->buf[0] & 0x08)) return 0;
|
||||
/* PS/2 鼠标包:buf[1]/buf[2] 已是 9 位补码的低 8 位,
|
||||
* (signed char) 强转即可正确符号扩展,无需再依据 buf[0] 的 sign bit 反转。
|
||||
* X 直接相加;Y 需翻转一次(PS/2 Y 向上为正,屏幕 Y 向下为正)。
|
||||
* 与 desktop/main.c:ps2_mouse_poll 保持一致。 */
|
||||
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;
|
||||
|
||||
+123
-19
@@ -1,14 +1,18 @@
|
||||
/* Deshab Editor — 全屏文本编辑器
|
||||
* PS/2 键盘输入,行号 + 光标 + 垂直滚动。
|
||||
* Ctrl+O 打开文件,Ctrl+S 保存(暂不支持),Esc 退出。
|
||||
* Ctrl+O 打开文件(输入 8.3 名,从 FAT32 根目录读取)
|
||||
* Ctrl+S 保存文件(输入 8.3 名,写入 FAT32 根目录)
|
||||
* Esc 退出。
|
||||
*/
|
||||
|
||||
#include "../../firstInit/ascii_bitmaps.c"
|
||||
#include "../desktop_app.h"
|
||||
#include "../fat32_io.h"
|
||||
|
||||
#define CHAR_STEP 12
|
||||
#define CHAR_H 18
|
||||
#define EDITOR_BUF_SIZE 65536
|
||||
#define INPUT_BUF_SIZE 16
|
||||
|
||||
static da_app_context g_ac;
|
||||
|
||||
@@ -22,6 +26,11 @@ static int modified = 0;
|
||||
static char filename[64] = "untitled";
|
||||
static int ctrl_down = 0;
|
||||
|
||||
/* 输入模式:Ctrl+O/Ctrl+S 时进入,在状态栏输入文件名 */
|
||||
static int input_mode = 0; /* 0=编辑, 1=open, 2=save */
|
||||
static char input_buf[INPUT_BUF_SIZE];
|
||||
static int input_len = 0;
|
||||
|
||||
static int line_start(int line) {
|
||||
int l = 0;
|
||||
for (int i = 0; i < text_len; i++) {
|
||||
@@ -47,6 +56,63 @@ static void init_default_text(void) {
|
||||
recalc_cursor();
|
||||
}
|
||||
|
||||
/* ---- 文件打开/保存 ---- */
|
||||
static void do_open(void) {
|
||||
if (input_len == 0) return;
|
||||
char name11[11];
|
||||
if (f32_name_to_83(input_buf, name11) != 0) {
|
||||
da_slog("editor", "open: bad name");
|
||||
return;
|
||||
}
|
||||
u8 *data = 0; u32 size = 0;
|
||||
if (f32_read_root_file(name11, &data, &size) != 0) {
|
||||
da_slog("editor", "open: read failed");
|
||||
return;
|
||||
}
|
||||
if (size > EDITOR_BUF_SIZE) size = EDITOR_BUF_SIZE;
|
||||
text_len = (int)size;
|
||||
for (int i = 0; i < text_len; i++) text_buf[i] = (char)data[i];
|
||||
cursor_pos = 0;
|
||||
modified = 0;
|
||||
/* 更新 filename 显示 */
|
||||
int p = 0;
|
||||
for (int i = 0; i < input_len && p < 60; i++) filename[p++] = input_buf[i];
|
||||
filename[p] = 0;
|
||||
recalc_cursor();
|
||||
da_slog("editor", "open: ok");
|
||||
}
|
||||
|
||||
static void do_save(void) {
|
||||
if (input_len == 0) return;
|
||||
char name11[11];
|
||||
if (f32_name_to_83(input_buf, name11) != 0) {
|
||||
da_slog("editor", "save: bad name");
|
||||
return;
|
||||
}
|
||||
if (f32_write_root_file(name11, (const u8 *)text_buf, (u32)text_len) != 0) {
|
||||
da_slog("editor", "save: write failed");
|
||||
return;
|
||||
}
|
||||
modified = 0;
|
||||
int p = 0;
|
||||
for (int i = 0; i < input_len && p < 60; i++) filename[p++] = input_buf[i];
|
||||
filename[p] = 0;
|
||||
da_slog("editor", "save: ok");
|
||||
}
|
||||
|
||||
static void enter_input_mode(int mode) {
|
||||
input_mode = mode;
|
||||
input_len = 0;
|
||||
input_buf[0] = 0;
|
||||
}
|
||||
|
||||
static void commit_input(void) {
|
||||
int mode = input_mode;
|
||||
input_mode = 0;
|
||||
if (mode == 1) do_open();
|
||||
else if (mode == 2) do_save();
|
||||
}
|
||||
|
||||
static void redraw_all(void) {
|
||||
da_fill_bg(&g_ac, DA_BG_SECONDARY);
|
||||
char title[80];
|
||||
@@ -74,16 +140,13 @@ static void redraw_all(void) {
|
||||
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],
|
||||
@@ -97,25 +160,35 @@ static void redraw_all(void) {
|
||||
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) {
|
||||
/* 光标(编辑模式) */
|
||||
if (!input_mode && 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++; }
|
||||
char status[96]; p = 0;
|
||||
if (input_mode) {
|
||||
const char *prompt = (input_mode == 1) ? "Open: " : "Save: ";
|
||||
while (*prompt) status[p++] = *prompt++;
|
||||
for (int i = 0; i < input_len; i++) status[p++] = input_buf[i];
|
||||
status[p++] = '_';
|
||||
const char *hint = " Enter:confirm Esc:cancel";
|
||||
while (*hint && p < 95) status[p++] = *hint++;
|
||||
} else {
|
||||
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++; }
|
||||
const char *s3 = " Ctrl+O:Open Ctrl+S:Save Esc:Exit";
|
||||
while (*s3 && p < 95) status[p++] = *s3++;
|
||||
}
|
||||
status[p] = 0;
|
||||
da_draw_statusbar(&g_ac, status, (i64)g_ac.fb_w, (i64)g_ac.fb_h);
|
||||
}
|
||||
@@ -131,6 +204,7 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
}
|
||||
|
||||
da_init(&g_ac, ctx);
|
||||
f32_init((f32_block_read_fn)g_ac.block_read, (f32_block_write_fn)g_ac.block_write);
|
||||
init_default_text();
|
||||
redraw_all();
|
||||
|
||||
@@ -148,6 +222,36 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
if (sc == 0x9D) { ctrl_down = 0; continue; }
|
||||
if (sc & 0x80) { e0 = 0; continue; }
|
||||
|
||||
/* Esc:输入模式取消 / 编辑模式退出 */
|
||||
if (sc == 0x01 && !e0) {
|
||||
if (input_mode) { input_mode = 0; redraw_all(); continue; }
|
||||
da_slog("editor", "exit");
|
||||
return;
|
||||
}
|
||||
|
||||
/* ---- 输入模式:输入文件名 ---- */
|
||||
if (input_mode) {
|
||||
if (sc == 0x1C) { /* Enter - 确认 */
|
||||
commit_input();
|
||||
redraw_all();
|
||||
continue;
|
||||
}
|
||||
if (sc == 0x0E) { /* Backspace */
|
||||
if (input_len > 0) input_len--;
|
||||
input_buf[input_len] = 0;
|
||||
redraw_all();
|
||||
continue;
|
||||
}
|
||||
char c = da_scan_to_ascii(sc, shift);
|
||||
if (c && c >= 32 && c <= 126 && input_len < INPUT_BUF_SIZE - 1) {
|
||||
input_buf[input_len++] = c;
|
||||
input_buf[input_len] = 0;
|
||||
redraw_all();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ---- 编辑模式 ---- */
|
||||
/* Esc 退出 */
|
||||
if (sc == 0x01 && !e0) { da_slog("editor", "exit"); return; }
|
||||
|
||||
@@ -212,16 +316,16 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
modified = 1;
|
||||
}
|
||||
} else {
|
||||
/* Ctrl+O: 打开文件(提示暂不支持) */
|
||||
/* Ctrl+O: 打开文件 */
|
||||
if (ctrl_down && (sc == 0x18)) {
|
||||
/* Ctrl+O - stub */
|
||||
da_slog("editor", "Ctrl+O - open not yet");
|
||||
enter_input_mode(1);
|
||||
redraw_all();
|
||||
continue;
|
||||
}
|
||||
/* Ctrl+S: 保存(提示暂不支持) */
|
||||
/* Ctrl+S: 保存文件 */
|
||||
if (ctrl_down && (sc == 0x1F)) {
|
||||
da_slog("editor", "Ctrl+S - save not yet");
|
||||
modified = 0;
|
||||
enter_input_mode(2);
|
||||
redraw_all();
|
||||
continue;
|
||||
}
|
||||
char c = da_scan_to_ascii(sc, shift);
|
||||
|
||||
@@ -0,0 +1,557 @@
|
||||
/* fat32_io.h — Deshab 用户态共享 FAT32 读写库(static inline,无库)
|
||||
*
|
||||
* 前置条件:包含者已定义 u8/u16/u32/u64/i64 基础类型。
|
||||
* 通过 f32_init() 注入 block_read/block_write 函数指针(来自 dkm_kernel_api + 0xA8)。
|
||||
*
|
||||
* 能力:FAT32 根目录文件的读/写/删/列举,支持 FAT 链跟随、多簇文件。
|
||||
* 限制:仅根目录(不支持子目录,Phase 2.2 fileman 将扩展);单文件最大 256KB。
|
||||
*
|
||||
* 用法:
|
||||
* f32_init(block_read, block_write) — 初始化(block_write 可为 NULL,仅读场景)
|
||||
* f32_name_to_83("file.txt", name11) — "file.txt" → 11 字符 8.3 名
|
||||
* f32_read_root_file(name11, &data, &size) — 读根目录文件到 f32_data 缓冲
|
||||
* f32_write_root_file(name11, data, size) — 写/替换根目录文件
|
||||
* f32_delete_root_file(name11) — 删除根目录文件
|
||||
* f32_list_root(emit_cb, user_data) — 列举根目录
|
||||
*
|
||||
* 缓冲区:每个包含本头的 .elf 独立持有 256KB g_disk + 4KB g_cluster + 256KB g_fdata(共 ~516KB BSS)。
|
||||
* 与 net_stack.h 同模式,接受 static 缓冲的代价以换取无库依赖。
|
||||
*/
|
||||
|
||||
#ifndef DESHAB_FAT32_IO_H
|
||||
#define DESHAB_FAT32_IO_H
|
||||
|
||||
/* ---- block 设备函数类型(与 shell/DSK 一致:index 参数固定 0) ---- */
|
||||
typedef int (*f32_block_read_fn)(u32 index, u64 lba, u32 count, void *buf);
|
||||
typedef int (*f32_block_write_fn)(u32 index, u64 lba, u32 count, const void *buf);
|
||||
|
||||
static f32_block_read_fn f32_blk_read = 0;
|
||||
static f32_block_write_fn f32_blk_write = 0;
|
||||
|
||||
/* ---- FAT32 BPB / 目录项结构(packed,与 DSK/shell 一致) ---- */
|
||||
typedef struct __attribute__((packed)) {
|
||||
u8 jmp[3]; char oem[8]; u16 bps; u8 spc; u16 rsvd; u8 fc; u16 root_ent;
|
||||
u16 ts16; u8 media; u16 spf16; u16 spt; u16 heads; u32 hidden; u32 ts32;
|
||||
u32 spf; u16 flags; u16 ver; u32 root_clus; u16 fsi; u16 bkboot;
|
||||
u8 res[12]; u8 drv; u8 ntfl; u8 sig; u32 ser; char lbl[11]; char typ[8];
|
||||
u8 code[420]; u16 boot_sig;
|
||||
} f32_bpb;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
char name[11]; u8 attr; u8 ntr; u8 ctenth;
|
||||
u16 ctime; u16 cdate; u16 adate; u16 chigh;
|
||||
u16 wtime; u16 wdate; u16 clow; u32 fsize;
|
||||
} f32_dirent;
|
||||
|
||||
/* ---- 内部小工具 ---- */
|
||||
static inline u32 f32_r32(const u8 *p) { return (u32)p[0]|((u32)p[1]<<8)|((u32)p[2]<<16)|((u32)p[3]<<24); }
|
||||
static inline u16 f32_r16(const u8 *p) { return (u16)p[0]|((u16)p[1]<<8); }
|
||||
static inline int f32_neq11(const char *a, const char *b) {
|
||||
for (int i = 0; i < 11; i++) if (a[i] != b[i]) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- 批量读写缓冲区(与 DSK/shell 对齐:256 扇区覆盖 BPB+FAT+根目录+小文件) ---- */
|
||||
static u8 f32_disk[131072]; /* 256 扇区 BPB+FAT+根目录缓存 */
|
||||
static u8 f32_cluster[4096]; /* 单簇缓冲 */
|
||||
static u8 f32_data[262144]; /* 文件数据缓冲(最大 256KB) */
|
||||
static int f32_disk_loaded = 0; /* f32_disk 是否已加载 BPB+FAT */
|
||||
|
||||
/* 初始化:注入 block_read/block_write 函数指针 */
|
||||
static inline void f32_init(f32_block_read_fn rd, f32_block_write_fn wr) {
|
||||
f32_blk_read = rd;
|
||||
f32_blk_write = wr;
|
||||
f32_disk_loaded = 0;
|
||||
}
|
||||
|
||||
static inline int f32_read_sectors(u32 lba, u32 count, u8 *out) {
|
||||
return f32_blk_read ? f32_blk_read(0, lba, count, out) : -1;
|
||||
}
|
||||
static inline int f32_write_sectors(u32 lba, u32 count, const u8 *buf) {
|
||||
return f32_blk_write ? f32_blk_write(0, lba, count, buf) : -1;
|
||||
}
|
||||
|
||||
/* 确保 f32_disk 已加载 BPB+FAT+根目录(256 扇区) */
|
||||
static inline int f32_disk_load(void) {
|
||||
if (f32_disk_loaded) return 0;
|
||||
if (f32_read_sectors(0, 256, f32_disk) != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
if (bpb->boot_sig != 0xAA55 || bpb->bps != 512) return -2;
|
||||
if (bpb->spf == 0 || bpb->root_clus < 2) return -3;
|
||||
f32_disk_loaded = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 8.3 名字转换 ---- */
|
||||
|
||||
/* "file.txt" / "NAME" → 11 字符 8.3 名(大写,空格填充)
|
||||
* 返回 0 成功,-1 失败(过长或非法字符) */
|
||||
static inline int f32_name_to_83(const char *in, char out[11]) {
|
||||
for (int i = 0; i < 11; i++) out[i] = ' ';
|
||||
int inlen = 0;
|
||||
while (in[inlen] && inlen < 13) inlen++;
|
||||
if (inlen == 0 || inlen > 12) return -1;
|
||||
int dot = -1;
|
||||
for (int i = 0; i < inlen; i++) if (in[i] == '.') dot = i;
|
||||
int base_end = (dot >= 0) ? dot : inlen;
|
||||
if (base_end == 0 || base_end > 8) return -1;
|
||||
for (int j = 0; j < base_end; j++) {
|
||||
char c = in[j];
|
||||
if (c >= 'a' && c <= 'z') c -= 32;
|
||||
if (c == '.' || c == ' ' || c == '/' || c == '\\') return -1;
|
||||
out[j] = c;
|
||||
}
|
||||
if (dot >= 0) {
|
||||
int extlen = inlen - dot - 1;
|
||||
if (extlen > 3) return -1;
|
||||
for (int j = 0; j < extlen; j++) {
|
||||
char c = in[dot + 1 + j];
|
||||
if (c >= 'a' && c <= 'z') c -= 32;
|
||||
if (c == '.' || c == ' ' || c == '/' || c == '\\') return -1;
|
||||
out[8 + j] = c;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 11 字符 8.3 名 → 可显示字符串(如 "README.TXT") */
|
||||
static inline void f32_name_from_83(const char in[11], char out[13]) {
|
||||
int p = 0;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (in[j] == ' ') break;
|
||||
out[p++] = in[j];
|
||||
}
|
||||
if (in[8] != ' ') {
|
||||
out[p++] = '.';
|
||||
for (int j = 8; j < 11; j++) {
|
||||
if (in[j] == ' ') break;
|
||||
out[p++] = in[j];
|
||||
}
|
||||
}
|
||||
out[p] = 0;
|
||||
}
|
||||
|
||||
/* ---- 根目录文件查找 ---- */
|
||||
|
||||
/* 在根目录簇链中查找 11 字符名,输出首簇号与文件大小 */
|
||||
static inline int f32_find_in_root(const u8 *clus, u32 clus_sectors, const char *target,
|
||||
u32 *out_clus, u32 *out_size, u32 *out_idx) {
|
||||
const f32_dirent *dir = (const f32_dirent *)clus;
|
||||
for (u32 e = 0; e * 32 < clus_sectors * 512; e++) {
|
||||
if (dir[e].name[0] == 0) break;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (f32_neq11(dir[e].name, target)) {
|
||||
*out_clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
*out_size = dir[e].fsize;
|
||||
if (out_idx) *out_idx = e;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 读取根目录下指定 8.3 名文件到 f32_data。
|
||||
* 返回 0 成功,*out_data 指向 f32_data,*out_size 为字节数;非 0 失败。 */
|
||||
static inline int f32_read_root_file(const char *name11, u8 **out_data, u32 *out_size) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
|
||||
u32 clus = bpb->root_clus;
|
||||
u32 found_clus = 0, found_size = 0;
|
||||
int found = 0;
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8 && !found) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
if (f32_find_in_root(cb, spc, name11, &found_clus, &found_size, 0) == 0) { found = 1; break; }
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
if (!found) return -5;
|
||||
if (found_size > sizeof(f32_data)) return -6;
|
||||
|
||||
u8 *dst = f32_data; u32 remaining = found_size; u32 fc = found_clus;
|
||||
while (fc >= 2 && fc < 0x0FFFFFF8 && remaining > 0) {
|
||||
u32 fc_lba = data_lba + (fc - 2) * spc;
|
||||
u32 fc_bytes = spc * 512;
|
||||
if (fc_bytes > remaining) fc_bytes = remaining;
|
||||
const u8 *fb;
|
||||
if ((u64)fc_lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
fb = f32_disk + (u64)fc_lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(fc_lba, 8, f32_cluster) != 0) return -7;
|
||||
fb = f32_cluster;
|
||||
}
|
||||
for (u32 b = 0; b < fc_bytes; b++) dst[b] = fb[b];
|
||||
dst += fc_bytes; remaining -= fc_bytes;
|
||||
u32 fo = fat_byte_off + fc * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
fc = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
*out_data = f32_data; *out_size = found_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 写入根目录下指定 8.3 名文件(存在则替换,不存在则新建)。
|
||||
* 依赖 f32_disk 已加载 BPB+FAT。返回 0 成功。 */
|
||||
static inline int f32_write_root_file(const char *name11, const u8 *data, u32 size) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
if (!f32_blk_write) return -10;
|
||||
/* 写入前重新加载 BPB+FAT,保证与盘上一致 */
|
||||
if (f32_read_sectors(0, 256, f32_disk) != 0) return -2;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 spc = bpb->spc;
|
||||
u32 fat_lba = bpb->rsvd;
|
||||
u32 fat_sectors = bpb->spf;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 root_clus = bpb->root_clus;
|
||||
u32 cluster_bytes = spc * 512;
|
||||
|
||||
u32 root_lba = data_lba + (root_clus - 2) * spc;
|
||||
u8 *root_buf = f32_disk + (u64)root_lba * 512;
|
||||
u32 max_entries = cluster_bytes / 32;
|
||||
f32_dirent *dir = (f32_dirent *)root_buf;
|
||||
int free_entry = -1;
|
||||
u32 existing_clus = 0;
|
||||
int found = 0;
|
||||
u32 existing_idx = 0;
|
||||
for (u32 e = 0; e < max_entries; e++) {
|
||||
if (dir[e].name[0] == 0) { if (free_entry < 0) free_entry = (int)e; break; }
|
||||
if ((u8)dir[e].name[0] == 0xE5) { if (free_entry < 0) free_entry = (int)e; continue; }
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (f32_neq11(dir[e].name, name11)) {
|
||||
existing_clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
found = 1; existing_idx = e;
|
||||
if (free_entry < 0) free_entry = (int)e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u32 bytes_needed = size > 0 ? size : 1;
|
||||
u32 clusters_needed = (bytes_needed + cluster_bytes - 1) / cluster_bytes;
|
||||
u32 first_clus = 0;
|
||||
u32 prev_clus = 0;
|
||||
|
||||
if (found && existing_clus >= 2) {
|
||||
first_clus = existing_clus;
|
||||
u32 cur = existing_clus;
|
||||
u32 count = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && count < clusters_needed) {
|
||||
prev_clus = cur;
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
count++;
|
||||
}
|
||||
while (count < clusters_needed) {
|
||||
u32 newc = 0;
|
||||
for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) {
|
||||
u32 fo = fat_byte_off + c * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; }
|
||||
}
|
||||
if (newc == 0) return -3;
|
||||
u32 pfo = fat_byte_off + prev_clus * 4;
|
||||
if (pfo + 4 <= sizeof(f32_disk)) {
|
||||
f32_disk[pfo] = (u8)(newc & 0xFF);
|
||||
f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF);
|
||||
f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF);
|
||||
f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F);
|
||||
}
|
||||
prev_clus = newc;
|
||||
count++;
|
||||
}
|
||||
if (prev_clus >= 2) {
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
if (fo + 4 <= sizeof(f32_disk)) {
|
||||
f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F;
|
||||
}
|
||||
}
|
||||
/* 释放多余旧簇 */
|
||||
u32 next = 0;
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
if (fo + 4 <= sizeof(f32_disk)) next = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
while (next >= 2 && next < 0x0FFFFFF8) {
|
||||
u32 nfo = fat_byte_off + next * 4;
|
||||
u32 nn = 0;
|
||||
if (nfo + 4 <= sizeof(f32_disk)) nn = f32_r32(f32_disk + nfo) & 0x0FFFFFFF;
|
||||
f32_disk[nfo] = 0; f32_disk[nfo+1] = 0; f32_disk[nfo+2] = 0; f32_disk[nfo+3] = 0;
|
||||
next = nn;
|
||||
}
|
||||
} else {
|
||||
if (free_entry < 0) return -4;
|
||||
for (u32 i = 0; i < clusters_needed; i++) {
|
||||
u32 newc = 0;
|
||||
for (u32 c = 2; c < (fat_sectors * 512) / 4; c++) {
|
||||
u32 fo = fat_byte_off + c * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
if ((f32_r32(f32_disk + fo) & 0x0FFFFFFF) == 0) { newc = c; break; }
|
||||
}
|
||||
if (newc == 0) return -3;
|
||||
if (i == 0) first_clus = newc;
|
||||
if (prev_clus >= 2) {
|
||||
u32 pfo = fat_byte_off + prev_clus * 4;
|
||||
f32_disk[pfo] = (u8)(newc & 0xFF);
|
||||
f32_disk[pfo+1] = (u8)((newc >> 8) & 0xFF);
|
||||
f32_disk[pfo+2] = (u8)((newc >> 16) & 0xFF);
|
||||
f32_disk[pfo+3] = (u8)((newc >> 24) & 0x0F);
|
||||
}
|
||||
prev_clus = newc;
|
||||
}
|
||||
if (prev_clus >= 2) {
|
||||
u32 fo = fat_byte_off + prev_clus * 4;
|
||||
f32_disk[fo] = 0xF8; f32_disk[fo+1] = 0xFF; f32_disk[fo+2] = 0xFF; f32_disk[fo+3] = 0x0F;
|
||||
}
|
||||
}
|
||||
|
||||
/* 写数据到簇 */
|
||||
u32 remaining = size;
|
||||
const u8 *src = data;
|
||||
u32 cur = first_clus;
|
||||
u32 ci = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) {
|
||||
u32 clba = data_lba + (cur - 2) * spc;
|
||||
u32 chunk = remaining < cluster_bytes ? remaining : cluster_bytes;
|
||||
if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
u8 *dst = f32_disk + (u64)clba * 512;
|
||||
for (u32 b = 0; b < chunk; b++) dst[b] = src[b];
|
||||
for (u32 b = chunk; b < cluster_bytes; b++) dst[b] = 0;
|
||||
} else {
|
||||
if (f32_read_sectors(clba, spc, f32_cluster) != 0) return -5;
|
||||
for (u32 b = 0; b < chunk; b++) f32_cluster[b] = src[b];
|
||||
for (u32 b = chunk; b < cluster_bytes; b++) f32_cluster[b] = 0;
|
||||
if (f32_write_sectors(clba, spc, f32_cluster) != 0) return -6;
|
||||
}
|
||||
src += chunk; remaining -= chunk; ci++;
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
|
||||
/* 更新目录项 */
|
||||
u32 entry_idx = found ? existing_idx : (u32)free_entry;
|
||||
f32_dirent *e = &dir[entry_idx];
|
||||
if (!found) {
|
||||
for (int i = 0; i < 11; i++) e->name[i] = name11[i];
|
||||
e->attr = 0x20; e->ntr = 0; e->ctenth = 0; e->ctime = 0; e->cdate = 0; e->adate = 0;
|
||||
}
|
||||
e->chigh = (u16)((first_clus >> 16) & 0xFFFF);
|
||||
e->clow = (u16)(first_clus & 0xFFFF);
|
||||
e->fsize = size;
|
||||
|
||||
/* 回写 FAT(两份)+ 根目录 + 缓冲区内的数据簇 */
|
||||
for (u32 f = 0; f < bpb->fc; f++) {
|
||||
u32 flba = fat_lba + f * fat_sectors;
|
||||
if (f32_write_sectors(flba, fat_sectors, f32_disk + (u64)flba * 512) != 0) return -7;
|
||||
}
|
||||
u32 root_dir_lba = data_lba + (root_clus - 2) * spc;
|
||||
if ((u64)root_dir_lba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
if (f32_write_sectors(root_dir_lba, spc, f32_disk + (u64)root_dir_lba * 512) != 0) return -8;
|
||||
}
|
||||
cur = first_clus; ci = 0;
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8 && ci < clusters_needed) {
|
||||
u32 clba = data_lba + (cur - 2) * spc;
|
||||
if ((u64)clba * 512 + cluster_bytes <= 256ULL * 512) {
|
||||
if (f32_write_sectors(clba, spc, f32_disk + (u64)clba * 512) != 0) return -9;
|
||||
}
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
cur = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
ci++;
|
||||
}
|
||||
f32_disk_loaded = 0; /* 下次操作重新加载,避免使用脏缓存 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 删除根目录下指定 8.3 名文件:清空簇链 + 标记目录项为 0xE5。
|
||||
* 返回 0 成功,-1 未找到,其他负值失败。 */
|
||||
static inline int f32_delete_root_file(const char *name11) {
|
||||
if (f32_disk_load() != 0) return -2;
|
||||
if (!f32_blk_write) return -10;
|
||||
if (f32_read_sectors(0, 256, f32_disk) != 0) return -3;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 spc = bpb->spc;
|
||||
u32 fat_lba = bpb->rsvd;
|
||||
u32 fat_sectors = bpb->spf;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 root_clus = bpb->root_clus;
|
||||
|
||||
u32 root_lba = data_lba + (root_clus - 2) * spc;
|
||||
u8 *root_buf = f32_disk + (u64)root_lba * 512;
|
||||
u32 max_entries = (spc * 512) / 32;
|
||||
f32_dirent *dir = (f32_dirent *)root_buf;
|
||||
u32 found_idx = 0xFFFFFFFFu;
|
||||
for (u32 e = 0; e < max_entries; e++) {
|
||||
if (dir[e].name[0] == 0) break;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
if (f32_neq11(dir[e].name, name11)) { found_idx = e; break; }
|
||||
}
|
||||
if (found_idx == 0xFFFFFFFFu) return -1;
|
||||
|
||||
/* 释放簇链 */
|
||||
u32 cur = (u32)f32_r16((const u8*)&dir[found_idx].clow) | ((u32)f32_r16((const u8*)&dir[found_idx].chigh) << 16);
|
||||
while (cur >= 2 && cur < 0x0FFFFFF8) {
|
||||
u32 fo = fat_byte_off + cur * 4;
|
||||
u32 next = 0;
|
||||
if (fo + 4 <= sizeof(f32_disk)) next = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
f32_disk[fo] = 0; f32_disk[fo+1] = 0; f32_disk[fo+2] = 0; f32_disk[fo+3] = 0;
|
||||
cur = next;
|
||||
}
|
||||
/* 标记目录项为已删除 */
|
||||
dir[found_idx].name[0] = (char)0xE5;
|
||||
|
||||
/* 回写 FAT + 根目录 */
|
||||
for (u32 f = 0; f < bpb->fc; f++) {
|
||||
u32 flba = fat_lba + f * fat_sectors;
|
||||
if (f32_write_sectors(flba, fat_sectors, f32_disk + (u64)flba * 512) != 0) return -4;
|
||||
}
|
||||
if (f32_write_sectors(root_lba, spc, f32_disk + (u64)root_lba * 512) != 0) return -5;
|
||||
f32_disk_loaded = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 列举根目录条目回调签名:返回 0 继续,非 0 停止 */
|
||||
typedef int (*f32_list_cb)(const char *name, u32 size, u8 attr, void *user_data);
|
||||
|
||||
/* 列出根目录所有文件/目录,调用回调输出。
|
||||
* 回调返回非 0 立即停止。返回 0 成功(含回调提前停止),负值失败。 */
|
||||
static inline int f32_list_root(f32_list_cb emit, void *user_data) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 clus = bpb->root_clus;
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
const f32_dirent *dir = (const f32_dirent *)cb;
|
||||
for (u32 e = 0; e * 32 < spc * 512; e++) {
|
||||
if (dir[e].name[0] == 0) goto done;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
char disp[13];
|
||||
f32_name_from_83(dir[e].name, disp);
|
||||
if (emit(disp, dir[e].fsize, dir[e].attr, user_data) != 0) goto done;
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 子目录遍历支持(Phase 2.2 fileman 扩展) ---- */
|
||||
|
||||
/* 目录条目(供 fileman 等工具使用) */
|
||||
typedef struct {
|
||||
char name[13]; /* 8.3 显示名 */
|
||||
u32 clus; /* 首簇号 */
|
||||
u32 size; /* 文件大小(目录为 0) */
|
||||
u8 attr;
|
||||
u8 is_dir;
|
||||
} f32_entry;
|
||||
|
||||
typedef int (*f32_list_entry_cb)(const f32_entry *e, void *user_data);
|
||||
|
||||
/* 列举指定目录簇的内容。
|
||||
* dir_clus = 0 表示根目录(自动从 BPB 取 root_clus)。
|
||||
* 回调返回非 0 立即停止。返回 0 成功,负值失败。 */
|
||||
static inline int f32_list_dir(u32 dir_clus, f32_list_entry_cb emit, void *user_data) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 clus = (dir_clus == 0) ? bpb->root_clus : dir_clus;
|
||||
while (clus >= 2 && clus < 0x0FFFFFF8) {
|
||||
u32 lba = data_lba + (clus - 2) * spc;
|
||||
const u8 *cb;
|
||||
if ((u64)lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
cb = f32_disk + (u64)lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(lba, 8, f32_cluster) != 0) return -4;
|
||||
cb = f32_cluster;
|
||||
}
|
||||
const f32_dirent *dir = (const f32_dirent *)cb;
|
||||
for (u32 e = 0; e * 32 < spc * 512; e++) {
|
||||
if (dir[e].name[0] == 0) goto done;
|
||||
if ((u8)dir[e].name[0] == 0xE5) continue;
|
||||
if (dir[e].attr == 0x0F) continue;
|
||||
if (dir[e].attr & 0x08) continue;
|
||||
/* 跳过 "." 和 ".." 条目 */
|
||||
if (dir[e].name[0] == '.' && (dir[e].name[1] == ' ' || dir[e].name[1] == '.'))
|
||||
continue;
|
||||
f32_entry fe;
|
||||
f32_name_from_83(dir[e].name, fe.name);
|
||||
fe.clus = (u32)f32_r16((const u8*)&dir[e].clow) | ((u32)f32_r16((const u8*)&dir[e].chigh) << 16);
|
||||
fe.size = dir[e].fsize;
|
||||
fe.attr = dir[e].attr;
|
||||
fe.is_dir = (dir[e].attr & 0x10) ? 1 : 0;
|
||||
if (emit(&fe, user_data) != 0) goto done;
|
||||
}
|
||||
u32 fo = fat_byte_off + clus * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
clus = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 按首簇号 + 大小读取文件内容到 out_buf。
|
||||
* out_buf 至少 size 字节容量。返回 0 成功,负值失败。 */
|
||||
static inline int f32_read_file_by_clus(u32 clus, u32 size, u8 *out_buf, u32 buf_cap) {
|
||||
if (f32_disk_load() != 0) return -1;
|
||||
if (size > buf_cap) size = buf_cap;
|
||||
const f32_bpb *bpb = (const f32_bpb *)f32_disk;
|
||||
u32 data_lba = bpb->rsvd + (u32)bpb->fc * bpb->spf;
|
||||
u32 fat_byte_off = bpb->rsvd * 512;
|
||||
u32 spc = bpb->spc;
|
||||
u32 remaining = size;
|
||||
u8 *dst = out_buf;
|
||||
u32 fc = clus;
|
||||
while (fc >= 2 && fc < 0x0FFFFFF8 && remaining > 0) {
|
||||
u32 fc_lba = data_lba + (fc - 2) * spc;
|
||||
u32 fc_bytes = spc * 512;
|
||||
if (fc_bytes > remaining) fc_bytes = remaining;
|
||||
const u8 *fb;
|
||||
if ((u64)fc_lba * 512 + (u64)spc * 512 <= 256ULL * 512)
|
||||
fb = f32_disk + (u64)fc_lba * 512;
|
||||
else {
|
||||
if (f32_read_sectors(fc_lba, 8, f32_cluster) != 0) return -7;
|
||||
fb = f32_cluster;
|
||||
}
|
||||
for (u32 b = 0; b < fc_bytes; b++) dst[b] = fb[b];
|
||||
dst += fc_bytes; remaining -= fc_bytes;
|
||||
u32 fo = fat_byte_off + fc * 4;
|
||||
if (fo + 4 > sizeof(f32_disk)) break;
|
||||
fc = f32_r32(f32_disk + fo) & 0x0FFFFFFF;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* DESHAB_FAT32_IO_H */
|
||||
+237
-72
@@ -1,80 +1,107 @@
|
||||
/* Deshab File Manager — 全屏文件管理器
|
||||
* 文件列表视图,鼠标点击选择,键盘导航。
|
||||
* FAT32 根目录遍历,Esc 退出。
|
||||
* 接入 fat32_io.h 共享头,支持:
|
||||
* - 子目录遍历(路径栈,Enter 进入 / Backspace 返回)
|
||||
* - FAT 链跟随(多簇文件/目录)
|
||||
* - 文件查看(Enter 打开文本文件,只读浏览,Esc 返回列表)
|
||||
* - 鼠标点击选择 + 键盘导航
|
||||
* Esc 退出返回 DSK/desktop。
|
||||
*/
|
||||
|
||||
#include "../../firstInit/ascii_bitmaps.c"
|
||||
#include "../desktop_app.h"
|
||||
#include "../fat32_io.h"
|
||||
|
||||
#define CHAR_STEP 12
|
||||
#define CHAR_H 18
|
||||
#define MAX_FILES 256
|
||||
#define MAX_DEPTH 16
|
||||
#define VIEW_BUF 24576 /* 文件查看器缓冲 24KB */
|
||||
|
||||
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;
|
||||
char name[13];
|
||||
u32 clus;
|
||||
u32 size;
|
||||
} file_entry;
|
||||
u8 is_dir;
|
||||
} fm_entry;
|
||||
|
||||
static file_entry files[MAX_FILES];
|
||||
static fm_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;
|
||||
/* 路径栈:每层记录目录首簇(0=根)与显示名 */
|
||||
static u32 path_clus[MAX_DEPTH];
|
||||
static char path_name[MAX_DEPTH][13];
|
||||
static int path_depth = 0; /* 0 = 根目录 */
|
||||
|
||||
u8 bpb[512];
|
||||
if (g_ac.block_read(0, 0, 1, bpb) != 0) return;
|
||||
if (bpb[510] != 0x55 || bpb[511] != 0xAA) return;
|
||||
/* 文件查看器状态 */
|
||||
static int view_mode = 0;
|
||||
static u8 view_buf[VIEW_BUF];
|
||||
static u32 view_size = 0;
|
||||
static int view_scroll = 0;
|
||||
|
||||
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 int fm_collect_cb(const f32_entry *e, void *user_data) {
|
||||
(void)user_data;
|
||||
if (file_count >= MAX_FILES) return 1;
|
||||
fm_entry *f = &files[file_count];
|
||||
for (int i = 0; i < 13; i++) f->name[i] = e->name[i];
|
||||
f->clus = e->clus;
|
||||
f->size = e->size;
|
||||
f->is_dir = e->is_dir;
|
||||
file_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void redraw_all(void) {
|
||||
static void load_current_dir(void) {
|
||||
file_count = 0;
|
||||
selected = 0;
|
||||
scroll_off = 0;
|
||||
u32 dir_clus = (path_depth == 0) ? 0 : path_clus[path_depth - 1];
|
||||
f32_list_dir(dir_clus, fm_collect_cb, 0);
|
||||
}
|
||||
|
||||
/* ---- 路径显示 ---- */
|
||||
static void build_path_str(char *out, int cap) {
|
||||
int p = 0;
|
||||
out[p++] = '/';
|
||||
for (int i = 0; i < path_depth && p < cap - 13; i++) {
|
||||
const char *n = path_name[i];
|
||||
while (*n && p < cap - 2) out[p++] = *n++;
|
||||
out[p++] = '/';
|
||||
}
|
||||
out[p] = 0;
|
||||
}
|
||||
|
||||
/* ---- 数字转字符串 ---- */
|
||||
static void fm_u32_dec(char *buf, u32 v) {
|
||||
char tmp[12]; int n = 0;
|
||||
if (!v) { buf[0] = '0'; buf[1] = 0; return; }
|
||||
while (v && n < 11) { tmp[n++] = '0' + (v % 10); v /= 10; }
|
||||
for (int i = 0; i < n; i++) buf[i] = tmp[n - 1 - i];
|
||||
buf[n] = 0;
|
||||
}
|
||||
|
||||
/* ---- 列表视图绘制 ---- */
|
||||
static void redraw_list(void) {
|
||||
da_fill_bg(&g_ac, DA_BG_PRIMARY);
|
||||
da_draw_titlebar(&g_ac, "Files", (i64)g_ac.fb_w);
|
||||
|
||||
/* 当前路径 */
|
||||
char path_str[200];
|
||||
build_path_str(path_str, sizeof(path_str));
|
||||
da_draw_string(&g_ac, path_str, 12, DA_TITLEBAR_H + 4, DA_ACCENT, DA_BG_PRIMARY, CHAR_STEP);
|
||||
|
||||
int margin = 12;
|
||||
int top_y = DA_TITLEBAR_H + 4;
|
||||
int top_y = DA_TITLEBAR_H + 4 + CHAR_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_size_x = (int)g_ac.fb_w - 160;
|
||||
int col_type_x = (int)g_ac.fb_w - 60;
|
||||
|
||||
/* 列标题 */
|
||||
@@ -83,9 +110,10 @@ static void redraw_all(void) {
|
||||
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 (file_count > visible_rows && scroll_off > 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++) {
|
||||
@@ -93,28 +121,23 @@ static void redraw_all(void) {
|
||||
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;
|
||||
char sz[16];
|
||||
fm_u32_dec(sz, files[idx].size);
|
||||
int sl = 0; while (sz[sl]) sl++;
|
||||
sz[sl++] = ' '; sz[sl++] = 'B'; sz[sl] = 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 {
|
||||
@@ -122,7 +145,6 @@ static void redraw_all(void) {
|
||||
(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);
|
||||
@@ -134,18 +156,129 @@ static void redraw_all(void) {
|
||||
}
|
||||
|
||||
/* 状态栏 */
|
||||
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++;
|
||||
char status[80]; int p = 0;
|
||||
char nc[12]; fm_u32_dec(nc, (u32)file_count);
|
||||
const char *s1 = "items: "; while (*s1) status[p++] = *s1++;
|
||||
for (int i = 0; nc[i]; i++) status[p++] = nc[i];
|
||||
const char *s2 = " | Enter:open Backspace:up Esc:exit";
|
||||
while (*s2) status[p++] = *s2++;
|
||||
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);
|
||||
}
|
||||
|
||||
/* ---- 文件查看器绘制 ---- */
|
||||
static void redraw_view(void) {
|
||||
da_fill_bg(&g_ac, DA_BG_PRIMARY);
|
||||
|
||||
/* 标题栏显示文件名 */
|
||||
char title[32]; int p = 0;
|
||||
const char *t = "View: ";
|
||||
while (*t && p < 30) title[p++] = *t++;
|
||||
if (selected >= 0 && selected < file_count) {
|
||||
const char *fn = files[selected].name;
|
||||
while (*fn && p < 30) title[p++] = *fn++;
|
||||
}
|
||||
title[p] = 0;
|
||||
da_draw_titlebar(&g_ac, title, (i64)g_ac.fb_w);
|
||||
|
||||
int margin = 12;
|
||||
int top_y = DA_TITLEBAR_H + 4;
|
||||
int row_h = CHAR_H + 2;
|
||||
int visible_rows = ((int)g_ac.fb_h - top_y - DA_STATUSBAR_H - 8) / row_h;
|
||||
|
||||
/* 按行显示文本内容 */
|
||||
int y = top_y;
|
||||
u32 pos = 0;
|
||||
int line = 0;
|
||||
/* 跳过 view_scroll 行 */
|
||||
int skip = view_scroll;
|
||||
while (pos < view_size && skip > 0) {
|
||||
if (view_buf[pos] == '\n') { skip--; }
|
||||
pos++;
|
||||
}
|
||||
while (pos < view_size && line < visible_rows) {
|
||||
/* 提取一行 */
|
||||
int line_start = pos;
|
||||
while (pos < view_size && view_buf[pos] != '\n' && pos - line_start < 80) pos++;
|
||||
int line_len = pos - line_start;
|
||||
/* 绘制字符 */
|
||||
for (int i = 0; i < line_len && i < 80; i++) {
|
||||
char c = (char)view_buf[line_start + i];
|
||||
if (c == '\r') continue;
|
||||
if (c < ' ' || c > '~') c = '.';
|
||||
char buf[2]; buf[0] = c; buf[1] = 0;
|
||||
da_draw_string(&g_ac, buf, margin + i * CHAR_STEP, y, DA_TEXT_PRIMARY, DA_BG_PRIMARY, CHAR_STEP);
|
||||
}
|
||||
y += row_h;
|
||||
line++;
|
||||
if (pos < view_size && view_buf[pos] == '\n') pos++;
|
||||
/* 处理 \r\n */
|
||||
if (pos < view_size && view_buf[pos] == '\r') pos++;
|
||||
}
|
||||
|
||||
/* 状态栏 */
|
||||
char status[80]; p = 0;
|
||||
char sz[12]; fm_u32_dec(sz, view_size);
|
||||
const char *s1 = "size: "; while (*s1) status[p++] = *s1++;
|
||||
for (int i = 0; sz[i]; i++) status[p++] = sz[i];
|
||||
const char *s2 = "B | Up/Down:scroll Esc:back";
|
||||
while (*s2) status[p++] = *s2++;
|
||||
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);
|
||||
}
|
||||
|
||||
static void redraw_all(void) {
|
||||
if (view_mode) redraw_view();
|
||||
else redraw_list();
|
||||
}
|
||||
|
||||
/* ---- 打开文件/目录 ---- */
|
||||
static void open_selected(void) {
|
||||
if (selected < 0 || selected >= file_count) return;
|
||||
fm_entry *f = &files[selected];
|
||||
|
||||
if (f->is_dir) {
|
||||
/* 进入子目录 */
|
||||
if (path_depth < MAX_DEPTH - 1) {
|
||||
path_clus[path_depth] = f->clus;
|
||||
for (int i = 0; i < 13; i++) path_name[path_depth][i] = f->name[i];
|
||||
path_name[path_depth][12] = 0;
|
||||
path_depth++;
|
||||
load_current_dir();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* 文件:读取内容进查看器 */
|
||||
if (f->size == 0) {
|
||||
view_size = 0;
|
||||
} else {
|
||||
u32 rd = f->size;
|
||||
if (rd > VIEW_BUF) rd = VIEW_BUF;
|
||||
if (f32_read_file_by_clus(f->clus, f->size, view_buf, VIEW_BUF) != 0) {
|
||||
da_slog("fileman", "read file failed");
|
||||
return;
|
||||
}
|
||||
view_size = rd;
|
||||
}
|
||||
view_mode = 1;
|
||||
view_scroll = 0;
|
||||
}
|
||||
|
||||
/* ---- 返回上级目录 ---- */
|
||||
static void go_up(void) {
|
||||
if (path_depth > 0) {
|
||||
path_depth--;
|
||||
load_current_dir();
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
void dsk_entry(const da_boot_context *ctx) {
|
||||
__asm__ volatile("cli");
|
||||
@@ -160,7 +293,11 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
da_cursor_init(&g_cursor, (i64)g_ac.fb_w, (i64)g_ac.fb_h);
|
||||
da_mouse_init();
|
||||
|
||||
load_root_dir();
|
||||
/* 初始化 fat32_io:注入 block_read/block_write */
|
||||
f32_init((f32_block_read_fn)g_ac.block_read, (f32_block_write_fn)g_ac.block_write);
|
||||
|
||||
path_depth = 0;
|
||||
load_current_dir();
|
||||
redraw_all();
|
||||
|
||||
int e0 = 0, shift = 0; (void)shift;
|
||||
@@ -170,10 +307,8 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
/* 鼠标轮询 */
|
||||
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;
|
||||
if (g_cursor.btn && !view_mode) {
|
||||
int top_y = DA_TITLEBAR_H + 4 + CHAR_H + 4;
|
||||
int row_h = CHAR_H + 6;
|
||||
int click_y = g_cursor.my - top_y - CHAR_H - 6;
|
||||
if (click_y >= 0) {
|
||||
@@ -196,11 +331,36 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
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 (sc == 0x01) { /* Esc */
|
||||
if (view_mode) {
|
||||
view_mode = 0;
|
||||
need_redraw = 1;
|
||||
} else {
|
||||
da_slog("fileman", "exit");
|
||||
return;
|
||||
}
|
||||
goto skip;
|
||||
}
|
||||
|
||||
if (view_mode) {
|
||||
/* 查看器模式:Up/Down 滚动 */
|
||||
if (e0) {
|
||||
if (sc == 0x48) { if (view_scroll > 0) view_scroll--; need_redraw = 1; }
|
||||
else if (sc == 0x50) { view_scroll++; need_redraw = 1; }
|
||||
e0 = 0;
|
||||
goto skip;
|
||||
}
|
||||
/* PageUp/PageDown */
|
||||
if (sc == 0x49) { view_scroll -= 10; if (view_scroll < 0) view_scroll = 0; need_redraw = 1; }
|
||||
else if (sc == 0x51) { view_scroll += 10; need_redraw = 1; }
|
||||
goto skip;
|
||||
}
|
||||
|
||||
/* 列表模式 */
|
||||
if (e0) {
|
||||
if (sc == 0x48) { /* Up */
|
||||
if (selected > 0) selected--;
|
||||
if (selected < scroll_off) scroll_off = selected;
|
||||
need_redraw = 1;
|
||||
} else if (sc == 0x50) { /* Down */
|
||||
if (selected < file_count - 1) selected++;
|
||||
@@ -211,12 +371,17 @@ void dsk_entry(const da_boot_context *ctx) {
|
||||
}
|
||||
|
||||
if (sc == 0x1C) { /* Enter - 打开 */
|
||||
if (selected >= 0 && selected < file_count) {
|
||||
da_slog("fileman", "open file (stub)");
|
||||
}
|
||||
open_selected();
|
||||
need_redraw = 1;
|
||||
} else if (sc == 0x0E) { /* Backspace - 返回上级 */
|
||||
da_slog("fileman", "back (root only)");
|
||||
go_up();
|
||||
need_redraw = 1;
|
||||
} else if (sc == 0x49) { /* PageUp */
|
||||
selected -= 10; if (selected < 0) selected = 0;
|
||||
if (selected < scroll_off) scroll_off = selected;
|
||||
need_redraw = 1;
|
||||
} else if (sc == 0x51) { /* PageDown */
|
||||
selected += 10; if (selected >= file_count) selected = file_count - 1;
|
||||
need_redraw = 1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user