Files
LinehuandCursor 8554a82677 feat(panic): FUCK [panic] mode=strict|fallback + realtest image variant
- UTSM panic_full returns in fallback mode (logs [PANIC:FALLBACK] + code + hit
  count); panic_halt keeps always-halt sites (CPU exception, VMM-E11 LA57,
  VMM-E15 DSK jump, UTSM-E42 test hook). Call sites given safe continuations.
- Mode passed to DSK/FirstInit/login/desktop via DSK_BOOT_FLAG_PANIC_FALLBACK;
  DKM kernel_api.panic / kern.log.panic return to driver; ext4 fatal aborts the
  op via setjmp/longjmp and DSK falls back to FAT32 ESP.
- build.ps1/Makefile: dev=strict, release=fallback, new realtest (release
  options + strict); default -Variant all; serialized iso packing under -j.
- Fix '*/' inside comments (dkm.h, kernel_api.h, bus.c) that broke the build.
- Includes pending work: dkm_bus service bus, mt7920/wlan drivers, desktop
  files/startmenu/prank, pe-tools, diskinfo, docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 23:57:19 +08:00

641 lines
24 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* apps_builtin.c — 内置应用 bash/editor/calc + FILES 面板(M0 自 main.c 平移)
*/
#include "desktop.h"
/* ---- FILES 面板全局状态 ---- */
fentry g_files[MAX_FILES];
int g_file_count = 0;
int g_file_sel = -1;
int g_files_loaded = 0;
/* ============================================================
* 终端 (Bash) — 面板/窗口共用核心
* ============================================================ */
const char *BASH_PROMPT = "deshab# ";
void bash_reset(bash_state *s, int w, int h) {
for (int i = 0; i < TERM_MAX_CHARS; i++) {
s->ch[i] = ' '; s->fg[i] = KS_TEXT_PRIMARY;
}
s->cols = (w - (int)DU_SPACE_SM * 2) / (int)DU_ASCII_STEP;
s->rows = (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;
if (s->cols < 10) s->cols = 10;
if (s->rows < 3) s->rows = 3;
s->cur_col = 0; s->cur_row = 0;
s->input_len = 0; s->input_cursor = 0;
s->prompt_len = kstrlen(BASH_PROMPT);
/* 初始化脏行标记:所有行标记为脏,确保首次渲染 */
for (int i = 0; i < TERM_MAX_ROWS; i++) s->dirty_rows[i] = 1;
s->any_dirty = 1;
}
static void bash_putc(bash_state *s, char c, u32 color) {
if (c == '\n') {
s->dirty_rows[s->cur_row] = 1; /* 标记当前行为脏 */
s->any_dirty = 1;
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;
/* 标记所有行为脏 */
for (int r = 0; r < s->rows; r++) s->dirty_rows[r] = 1;
s->any_dirty = 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++;
/* 标记当前行为脏 */
s->dirty_rows[s->cur_row] = 1;
s->any_dirty = 1;
}
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, KS_ACCENT2);
bash_puts(s, cmd, KS_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", KS_ACCENT2);
bash_puts(s, " uname about reboot halt\n", KS_ACCENT2);
bash_puts(s, " pwd whoami id\n", KS_ACCENT2);
} 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 (Winux-Kate shell)\n", KS_ACCENT2);
} else if (cmd[0]=='u'&&cmd[1]=='n'&&cmd[2]=='a') {
bash_puts(s, "Deshab\n", KS_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", KS_ACCENT);
bash_puts(s, "Winux-Kate UI Structure\n", KS_ACCENT2);
} 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, KS_TEXT_PRIMARY);
bash_putc(s, '\n', 0);
} else if (cmd[0]=='p'&&cmd[1]=='w'&&cmd[2]=='d') {
bash_puts(s, "/\n", KS_TEXT_PRIMARY);
} else if (cmd[0]=='w'&&cmd[1]=='h'&&cmd[2]=='o') {
bash_puts(s, "root\n", KS_TEXT_PRIMARY);
} else if (cmd[0]=='i'&&cmd[1]=='d') {
bash_puts(s, "uid=0(root) gid=0(root)\n", KS_TEXT_PRIMARY);
} else if (cmd[0]=='r'&&cmd[1]=='e'&&cmd[2]=='b') {
bash_puts(s, "Rebooting...\n", KS_ACCENT2);
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", KS_ACCENT2);
for(;;) __asm__("hlt");
} else {
bash_puts(s, "unknown: ", KS_DANGER);
bash_puts(s, cmd, KS_DANGER);
bash_putc(s, '\n', 0);
}
}
void bash_key(bash_state *s, u8 sc, int shift) {
if (sc == 0x1C) {
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) {
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, 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++;
}
}
}
/* 绘制终端到指定矩形区域(面板或窗口客户区)
* 性能优化:增量渲染,只绘制脏行 */
void bash_draw_to(bash_state *s, int cx, int cy, int cw, int ch) {
/* 快速退出:终端内容未修改,跳过绘制 */
if (!s->any_dirty) return;
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_BG_PRIMARY);
for (int r = 0; r < s->rows; r++) {
/* 增量渲染:只绘制脏行 */
if (!s->dirty_rows[r]) continue;
for (int c = 0; c < s->cols; c++) {
int idx = r * TERM_MAX_COLS + c;
if (s->ch[idx] == ' ') continue;
int px = cx + (int)DU_SPACE_SM + c * (int)DU_ASCII_STEP;
int py = cy + (int)DU_SPACE_SM + r * (int)DU_ASCII_LINE_H;
if (px + (int)DU_ASCII_CELL_W > cx + cw) continue;
if (py + (int)DU_ASCII_CELL_H > cy + ch) continue;
du_draw_char(&g_fb, s->ch[idx], px, py, s->fg[idx], KS_BG_PRIMARY);
}
/* 清除脏行标记 */
s->dirty_rows[r] = 0;
}
int input_y = cy + (int)DU_SPACE_SM + s->cur_row * (int)DU_ASCII_LINE_H;
if (input_y + (int)DU_ASCII_CELL_H <= cy + ch) {
du_draw_string(&g_fb, BASH_PROMPT,
cx + (int)DU_SPACE_SM, input_y,
KS_ACCENT2, KS_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,
KS_TEXT_PRIMARY, KS_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, KS_ACCENT);
}
/* 清除全局脏标记 */
s->any_dirty = 0;
}
/* ---- 窗口适配 ---- */
__attribute__((unused))
static void *bash_on_create(app_ctx *ctx) {
bash_state *s = (bash_state *)ADDR_TERMWIN;
bash_reset(s, ctx->client_w, ctx->client_h);
bash_puts(s, "Deshab Bash v0.1\nType help for commands\n\n", KS_TEXT_PRIMARY);
return s;
}
__attribute__((unused))
static void bash_on_event(void *state, app_ctx *ctx, desktop_event *ev) {
if (ev->type != EV_KEY_DOWN) return;
bash_key((bash_state *)state, ev->scancode, ev->shift);
ctx->win->dirty = 1;
}
__attribute__((unused))
static void bash_on_draw(void *state, app_ctx *ctx) {
bash_draw_to((bash_state *)state, ctx->client_x, ctx->client_y,
ctx->client_w, ctx->client_h);
}
__attribute__((unused))
static void bash_on_destroy(void *state) { (void)state; }
/* ============================================================
* 文本编辑器 (Editor) — 面板/IDE/窗口共用核心
* ============================================================ */
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;
}
void editor_reset(editor_state *s, const char *sample) {
s->text_len = 0; s->cursor_pos = 0;
s->scroll_y = 0; s->modified = 0;
s->has_file = 0;
s->disp[0] = 0;
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);
}
void editor_key(editor_state *s, u8 sc, int shift) {
if (sc == 0x1C) {
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) {
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) {
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) {
int next_start = editor_line_start(s, s->cursor_line + 1);
if (next_start < s->text_len || s->cursor_line < 999) {
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) {
if (s->cursor_pos > 0) s->cursor_pos--;
} else if (sc == 0x4D) {
if (s->cursor_pos < s->text_len) s->cursor_pos++;
} else if (sc == 0x47) {
s->cursor_pos = editor_line_start(s, s->cursor_line);
} else if (sc == 0x4F) {
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, 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);
}
/* 绘制编辑器到指定矩形区域 */
void editor_draw_to(editor_state *s, int cx, int cy, int cw, int ch) {
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 - 20) / (int)DU_ASCII_LINE_H;
int visible_cols = text_area_w / (int)DU_ASCII_STEP;
if (visible_rows < 1) visible_rows = 1;
if (visible_cols < 4) visible_cols = 4;
du_fill_rect(&g_fb, cx, cy, cw, ch, KS_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,
KS_TEXT_DIM, KS_BG_SECONDARY, DU_ASCII_STEP);
du_fill_rect(&g_fb, cx + line_num_w - 2, screen_y, 1,
(int)DU_ASCII_CELL_H, KS_BORDER_DIM);
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,
KS_TEXT_PRIMARY, KS_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, KS_ACCENT);
}
/* 底部迷你状态栏 */
int status_y = cy + ch - 20;
du_fill_rect(&g_fb, cx, status_y, cw, 20, KS_BG_TERTIARY);
du_divider_h(&g_fb, cx, status_y, cw, KS_BORDER);
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++; }
if (s->has_file) {
const char *m = " "; while(*m) status[sp++] = *m++;
for (int i = 0; s->disp[i] && sp < 60; i++) status[sp++] = s->disp[i];
}
status[sp] = 0;
du_draw_string(&g_fb, status, cx + 4, status_y + 1,
KS_TEXT_DIM, KS_BG_TERTIARY, DU_ASCII_STEP);
}
/* ---- 窗口适配 ---- */
void *editor_on_create(app_ctx *ctx) {
editor_state *s = (editor_state *)ADDR_EDWIN;
editor_reset(s, "# Welcome to Deshab Editor\n#\n\nStart typing here...\n");
for (int i = 0; i < 47; 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];
return s;
}
void editor_on_event(void *state, app_ctx *ctx, desktop_event *ev) {
if (ev->type != EV_KEY_DOWN) return;
editor_key((editor_state *)state, ev->scancode, ev->shift);
ctx->win->dirty = 1;
}
void editor_on_draw(void *state, app_ctx *ctx) {
editor_draw_to((editor_state *)state, ctx->client_x, ctx->client_y,
ctx->client_w, ctx->client_h);
}
void editor_on_destroy(void *state) { (void)state; }
/* 文件资源管理器把文本文件送进窗口编辑器(单实例 ADDR_EDWIN)。 */
void editor_open_text(const char *disp, const u8 *data, u32 size) {
int id = -1;
int found = 0;
int ascii = 1;
editor_state *s;
u32 cap = EDITOR_BUF_SIZE - 1;
if (!disp) disp = "";
if (!data) { data = (const u8 *)""; size = 0; }
for (int i = 0; i < g_app_count; i++) {
const char *n = g_apps[i].name;
if (!n || !g_apps[i].on_create) continue;
if (n[0]=='e' && n[1]=='d' && n[2]=='i' && n[3]=='t' &&
n[4]=='o' && n[5]=='r' && n[6]==0) { id = i; break; }
}
if (id < 0) {
slog("fileman: editor missing");
return;
}
launch_app(id);
for (int i = 0; i < g_win_count; i++) {
if (g_windows[i].app_id == id && g_windows[i].visible) { found = 1; break; }
}
if (!found) return;
s = (editor_state *)ADDR_EDWIN;
if (size > cap) size = cap;
s->text_len = 0;
for (u32 k = 0; k < size && s->text_len < (int)cap; k++) {
char c = (char)data[k];
if (c == '\r') continue;
if ((unsigned char)c >= 32 || c == '\n' || c == '\t')
s->text[s->text_len++] = c;
}
s->text[s->text_len] = 0;
s->cursor_pos = 0;
s->cursor_line = 0;
s->cursor_col = 0;
s->scroll_y = 0;
s->modified = 0;
s->has_file = 0;
s->disp[0] = 0;
for (int k = 0; disp[k]; k++) {
if ((unsigned char)disp[k] < 32 || (unsigned char)disp[k] > 126) ascii = 0;
}
for (int i = 0; i < g_win_count; i++) {
desktop_window *w = &g_windows[i];
int p = 0;
const char *prefix = "Editor";
if (w->app_id != id || !w->visible) continue;
while (prefix[p] && p < 46) { w->title[p] = prefix[p]; p++; }
if (ascii && disp[0]) {
const char *sep = " - ";
while (*sep && p < 46) w->title[p++] = *sep++;
for (int k = 0; disp[k] && p < 47; k++) w->title[p++] = disp[k];
}
w->title[p] = 0;
w->dirty = 1;
}
}
/* ============================================================
* 计算器 (Calculator)
* ============================================================ */
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;
}
void *calc_on_create(app_ctx *ctx) {
calc_state *s = (calc_state *)ADDR_CALC;
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;
}
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;
}
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, KS_BG_SECONDARY);
int disp_h = 40;
du_fill_rounded_rect(&g_fb, cx + 8, cy + 8, cw - 16, disp_h,
KS_BG_PRIMARY, DU_RADIUS_MD);
du_rect_outline(&g_fb, cx + 8, cy + 8, cw - 16, disp_h,
KS_ACCENT, 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,
KS_ACCENT, KS_BG_PRIMARY, DU_ASCII_STEP);
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) ? KS_ACCENT_DIM :
(i % 4 == 3) ? KS_ACCENT :
(i == 18) ? KS_ACCENT2 :
KS_BG_TERTIARY;
u32 fg = ((i % 4 == 3) || (i == 18)) ? KS_TEXT_INVERT : KS_TEXT_PRIMARY;
du_fill_rounded_rect(&g_fb, bx, by, btn_w, btn_h, bg, DU_RADIUS_SM);
du_rect_outline(&g_fb, bx, by, btn_w, btn_h, KS_BORDER, 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,
fg, bg, DU_ASCII_STEP);
}
}
void calc_on_destroy(void *state) { (void)state; }
/* ============================================================
* FILES 面板(FAT32 真实读写)
* ============================================================ */
static int files_list_cb(const char *name, u32 size, u8 attr, void *ud) {
(void)ud;
if (attr & 0x10) return 0; /* 跳过子目录 */
if (g_file_count >= MAX_FILES) return 1; /* 停止 */
/* 只显示 user 文件:跳过 .ELF 可执行文件和系统模块 */
int nl = kstrlen(name);
if (nl >= 4 && name[nl-4] == '.' && name[nl-3] == 'E' &&
name[nl-2] == 'L' && name[nl-1] == 'F') return 0;
/* FIRSTINI* (FirstInit.elf,Name83=FIRSTINIT → 显示名 FIRSTINI.T) */
if (nl >= 8 && name[0] == 'F' && name[1] == 'I' && name[2] == 'R' &&
name[3] == 'S' && name[4] == 'T' && name[5] == 'I' && name[6] == 'N' &&
name[7] == 'I') return 0;
fentry *f = &g_files[g_file_count];
f->size = size;
kstrcpy(f->disp, name, 13);
/* disp (NAME.EXT) → name11 (NAME EXT) */
for (int i = 0; i < 11; i++) f->name11[i] = ' ';
int i = 0, o = 0;
while (name[i] && name[i] != '.' && o < 8) f->name11[o++] = name[i++];
while (name[i] && name[i] != '.') i++;
if (name[i] == '.') {
i++;
o = 8;
while (name[i] && o < 11) f->name11[o++] = name[i++];
}
g_file_count++;
return 0;
}
void files_refresh(void) {
g_file_count = 0;
g_file_sel = -1;
if (!g_block_read) { g_files_loaded = 0; return; }
if (f32_list_root_w(files_list_cb, 0) == 0) g_files_loaded = 1;
else g_files_loaded = 0;
}
/* 双击打开文件 → 载入 DASHBOARD EDITOR */
void files_open(int idx) {
if (idx < 0 || idx >= g_file_count) return;
editor_state *ed = (editor_state *)ADDR_EDDASH;
fentry *f = &g_files[idx];
u8 *data = 0; u32 size = 0;
if (f32_read_root_file_w(f->name11, &data, &size) != 0) {
slog("file open failed");
return;
}
ed->text_len = 0;
u32 cap = EDITOR_BUF_SIZE - 1;
if (size > cap) size = cap;
for (u32 i = 0; i < size; i++) {
char c = (char)data[i];
if ((u8)c >= 32 || c == '\n' || c == '\t')
ed->text[ed->text_len++] = c;
}
for (int i = 0; i < 11; i++) ed->name11[i] = f->name11[i];
kstrcpy(ed->disp, f->disp, 13);
ed->has_file = 1;
ed->modified = 0;
ed->scroll_y = 0;
ed->cursor_pos = 0;
editor_recalc_cursor(ed);
g_dash_focus = 2;
slog("file loaded into editor");
}
/* EDITOR SAVE 回写磁盘 */
void editor_save(void) {
editor_state *ed = (editor_state *)ADDR_EDDASH;
if (!ed->has_file) return;
if (f32_write_root_file_w(ed->name11, (const u8 *)ed->text, (u32)ed->text_len) == 0) {
ed->modified = 0;
slog("file saved");
} else {
slog("file save failed");
}
}