#include #include #include #include #include #include // Use the shared terminal API/definitions so TERM_ROWS/TERM_COLS stay consistent #include #include #include #include #include // shell_current_path is maintained by the main shell code extern char shell_current_path[128]; #define INPUT_BUF_LEN 200 #define VTERM_MAX_LINE_ICONS 16 #define VTERM_HISTORY_ROWS 256 static inline int vterm_row_slot(int abs_row) { if (abs_row < 0) return 0; return abs_row % VTERM_HISTORY_ROWS; } typedef struct { // Ring buffer of rows: stores visible rows + scrollback. char buf[VTERM_HISTORY_ROWS][TERM_COLS+1]; int cur_x; // Absolute cursor row (monotonic; rendering uses absolute indices). int cur_y; // Oldest absolute row still retained in the ring buffer. int head_row; int scroll; int active; // input line buffer and history index for this virtual terminal char input_buf[INPUT_BUF_LEN]; int input_pos; int history_idx; // -1 when not browsing // saved input when browsing history char saved_input[INPUT_BUF_LEN]; // per-vterm current working directory (isolate cd per terminal) char cwd[128]; // per-line and per-char colour metadata for rendering uint8_t line_r[VTERM_HISTORY_ROWS]; uint8_t line_g[VTERM_HISTORY_ROWS]; uint8_t line_b[VTERM_HISTORY_ROWS]; // per-character colour (row x col) uint8_t char_r[VTERM_HISTORY_ROWS][TERM_COLS]; uint8_t char_g[VTERM_HISTORY_ROWS][TERM_COLS]; uint8_t char_b[VTERM_HISTORY_ROWS][TERM_COLS]; // Per-line icon metadata (used by tiler to draw small file/dir icons) // icon_key: base icon name without extension, e.g. "file_txt" or "dir_full" uint8_t line_icon_count[VTERM_HISTORY_ROWS]; char line_icon_key[VTERM_HISTORY_ROWS][VTERM_MAX_LINE_ICONS][16]; uint8_t line_icon_anchor_col[VTERM_HISTORY_ROWS][VTERM_MAX_LINE_ICONS]; // Legacy: previously used to shift the whole line right in pixels. // Now expected to be 0; reserve space in the text stream with leading spaces instead. uint8_t line_indent_px[VTERM_HISTORY_ROWS]; unsigned int version; // increments when content changes // Simple selection for current input line: active flag and [start,end) columns int sel_active; int sel_start_col; int sel_end_col; // input editing render anchors int input_row; // absolute row where current input is being edited int input_start_col; // column where input begins (after the prompt) // Stdin buffer for ring3 user tasks: when a user task is waiting for stdin input, // the TUI routes keyboard characters here. The syscall handler consumes from this buffer. char stdin_buf[256]; volatile int stdin_len; // current bytes in stdin_buf volatile int stdin_ready; // 1 when line is complete (Enter pressed), 0 otherwise volatile int stdin_raw_mode; // 1 for byte-stream mode, 0 for canonical line mode uint16_t tty_rows; uint16_t tty_cols; } vterm_t; static vterm_t vterms[4]; static int vterm_ctx_allow(uint32 caps) { command_context_t* ctx = current_command_context; if (ctx && !cap_check(ctx->caps, caps)) return 0; if (ctx) { scheduler_account(ctx->wo, SCHED_COST_CONSOLE); scheduler_yield_if_needed(ctx->wo); if (sched_det_is_enabled()) ctx->det_seq++; } return 1; } static void vterm_clear_line_icons(vterm_t* t, int row) { if (!t) return; if (row < 0 || row >= VTERM_HISTORY_ROWS) return; t->line_icon_count[row] = 0; for (int i = 0; i < VTERM_MAX_LINE_ICONS; ++i) { t->line_icon_key[row][i][0] = '\0'; t->line_icon_anchor_col[row][i] = 0; } t->line_indent_px[row] = 0; } static int vterm_input_is_clear_command(const char* input) { if (!input) return 0; while (*input == ' ' || *input == '\t' || *input == '\r' || *input == '\n') { input++; } if (input[0] != 'c' || input[1] != 'l' || input[2] != 'e' || input[3] != 'a' || input[4] != 'r') { return 0; } input += 5; while (*input == ' ' || *input == '\t' || *input == '\r' || *input == '\n') { input++; } return *input == '\0'; } const char* vterm_get_cwd(int idx) { if (idx < 0 || idx >= 4) return "/"; return vterms[idx].cwd; } void vterm_set_cwd(int idx, const char* cwd) { if (idx < 0 || idx >= 4) return; if (!cwd || cwd[0] != '/') { strncpy(vterms[idx].cwd, "/", sizeof(vterms[idx].cwd) - 1); vterms[idx].cwd[sizeof(vterms[idx].cwd) - 1] = '\0'; return; } strncpy(vterms[idx].cwd, cwd, sizeof(vterms[idx].cwd) - 1); vterms[idx].cwd[sizeof(vterms[idx].cwd) - 1] = '\0'; } void vterm_init_all() { for (int i = 0; i < 4; ++i) { vterms[i].cur_x = 0; vterms[i].cur_y = 0; vterms[i].head_row = 0; vterms[i].scroll = 0; vterms[i].active = 0; vterms[i].input_buf[0] = '\0'; vterms[i].input_pos = 0; vterms[i].history_idx = -1; // default cwd is root strncpy(vterms[i].cwd, "/", sizeof(vterms[i].cwd)-1); vterms[i].cwd[sizeof(vterms[i].cwd)-1] = '\0'; for (int r = 0; r < VTERM_HISTORY_ROWS; ++r) { vterms[i].buf[r][0] = '\0'; vterms[i].line_r[r] = 200; vterms[i].line_g[r] = 200; vterms[i].line_b[r] = 200; vterm_clear_line_icons(&vterms[i], r); for (int c = 0; c < TERM_COLS; ++c) { vterms[i].char_r[r][c] = 200; vterms[i].char_g[r][c] = 200; vterms[i].char_b[r][c] = 200; } } vterms[i].version = 1; vterms[i].sel_active = 0; vterms[i].sel_start_col = 0; vterms[i].sel_end_col = 0; vterms[i].input_row = 0; vterms[i].input_start_col = 0; // Initialize stdin buffer for user tasks vterms[i].stdin_buf[0] = '\0'; vterms[i].stdin_len = 0; vterms[i].stdin_ready = 0; vterms[i].stdin_raw_mode = 0; vterms[i].tty_rows = TERM_ROWS; vterms[i].tty_cols = TERM_COLS; } } // Set scrollback offset for a vterm. 0 = follow the tail (latest). Positive values scroll up. void vterm_set_scroll(int idx, int scroll) { if (idx < 0 || idx >= 4) return; if (scroll < 0) scroll = 0; // Maximum scroll is limited by how much history we retain. int max_scroll = vterms[idx].cur_y - vterms[idx].head_row; if (max_scroll < 0) max_scroll = 0; if (scroll > max_scroll) scroll = max_scroll; if (vterms[idx].scroll != scroll) { vterms[idx].scroll = scroll; vterms[idx].version++; } } int vterm_get_scroll(int idx) { if (idx < 0 || idx >= 4) return 0; return vterms[idx].scroll; } // Print the standard shell prompt into the vterm (drive:path! ) void vterm_print_prompt(int idx) { if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; /* Print prompt at the current cursor position */ extern uint8_t g_current_drive; extern uint8 ata_physical_to_logical(uint8 physical_drive); char prompt[64]; int n; if (g_current_drive == VFS_DRIVE_RAM) { n = snprintf(prompt, sizeof(prompt), "RAM:%s! ", t->cwd); } else { uint8 logical = ata_physical_to_logical(g_current_drive); if (logical == 0xFF) logical = 0; n = snprintf(prompt, sizeof(prompt), "%d:%s! ", logical, t->cwd); } for (int i = 0; i < n; ++i) { if ((i & 0x7) == 0) vterm_ctx_allow(CAP_WRITE_CONSOLE); // write the char then override its colour to match prompt styling vterm_write_char(idx, prompt[i]); // colour drive and leading ':' and '/' as gray, '!' as yellow, rest default int drive_and_sep_len = 2; // e.g. "0:" // find position of '!' in prompt int bang_pos = -1; for (int j = 0; j < n; ++j) if (prompt[j] == '!') { bang_pos = j; break; } // determine colour for this char int rr = 200, gg = 200, bb = 200; if (i < drive_and_sep_len) { rr = 150; gg = 150; bb = 150; } if (i == bang_pos) { rr = 255; gg = 255; bb = 0; } // set colour for the character we just wrote (cur_x was incremented) int written_row = t->cur_y; int written_col = t->cur_x - 1; if (written_row >= t->head_row && written_row <= t->cur_y && written_col >= 0 && written_col < TERM_COLS) { int slot = vterm_row_slot(written_row); t->char_r[slot][written_col] = rr; t->char_g[slot][written_col] = gg; t->char_b[slot][written_col] = bb; } } // After printing prompt, initialize input edit anchors t->input_row = t->cur_y; t->input_start_col = t->cur_x; t->input_buf[0] = '\0'; t->input_pos = 0; t->sel_active = 0; } // Helper: re-render the current input buffer onto the screen after the prompt static void vterm_render_input(int idx) { if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; int abs_row = t->input_row; if (abs_row < t->head_row || abs_row > t->cur_y) return; int row = vterm_row_slot(abs_row); // Copy input buffer into screen buffer at input_start_col int plen = t->input_start_col; int ilen = strlen(t->input_buf); if (plen + ilen >= TERM_COLS) ilen = TERM_COLS - plen - 1; for (int i = 0; i < ilen; ++i) { t->buf[row][plen + i] = t->input_buf[i]; t->char_r[row][plen + i] = 200; t->char_g[row][plen + i] = 200; t->char_b[row][plen + i] = 200; } // Terminate the line right after input t->buf[row][plen + ilen] = '\0'; // Position caret t->cur_y = abs_row; t->cur_x = t->input_start_col + t->input_pos; t->version++; } void vterm_write_char(int idx, char ch) { if (!vterm_ctx_allow(CAP_WRITE_CONSOLE)) return; if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; if (!t->active) t->active = 1; // New output should follow the tail (cancel scrollback view). if (t->scroll != 0) { t->scroll = 0; t->version++; } // Carriage return: move to column 0 on the current row so callers can // update in-place status/progress lines. if (ch == '\r') { t->cur_x = 0; t->version++; return; } // Explicit newline if (ch == '\n') { t->cur_x = 0; t->cur_y++; if (t->cur_y - t->head_row >= VTERM_HISTORY_ROWS) { t->head_row = t->cur_y - (VTERM_HISTORY_ROWS - 1); } int row = vterm_row_slot(t->cur_y); t->buf[row][0] = '\0'; t->line_r[row] = 200; t->line_g[row] = 200; t->line_b[row] = 200; for (int c = 0; c < TERM_COLS; ++c) { t->char_r[row][c] = 200; t->char_g[row][c] = 200; t->char_b[row][c] = 200; } vterm_clear_line_icons(t, row); t->version++; return; } // Soft wrap if (t->cur_x >= TERM_COLS) { t->cur_x = 0; t->cur_y++; if (t->cur_y - t->head_row >= VTERM_HISTORY_ROWS) { t->head_row = t->cur_y - (VTERM_HISTORY_ROWS - 1); } int row = vterm_row_slot(t->cur_y); t->buf[row][0] = '\0'; t->line_r[row] = 200; t->line_g[row] = 200; t->line_b[row] = 200; for (int c = 0; c < TERM_COLS; ++c) { t->char_r[row][c] = 200; t->char_g[row][c] = 200; t->char_b[row][c] = 200; } vterm_clear_line_icons(t, row); } int row = vterm_row_slot(t->cur_y); t->buf[row][t->cur_x] = ch; t->buf[row][t->cur_x + 1] = '\0'; /* set per-char colour from current redirect colour if available (non-zero), else default */ int use_r = (shell_redirect_colour_r > 0) ? shell_redirect_colour_r : 200; int use_g = (shell_redirect_colour_g > 0) ? shell_redirect_colour_g : 200; int use_b = (shell_redirect_colour_b > 0) ? shell_redirect_colour_b : 200; t->char_r[row][t->cur_x] = use_r; t->char_g[row][t->cur_x] = use_g; t->char_b[row][t->cur_x] = use_b; t->cur_x++; /* ensure this line has a sensible default colour if not already set */ if (t->line_r[row] == 0 && t->line_g[row] == 0 && t->line_b[row] == 0) { t->line_r[row] = 200; t->line_g[row] = 200; t->line_b[row] = 200; } t->version++; // If a ring3 task is active, mark UI dirty so IRQ0 will repaint all tiles. if (g_user_task_active) { g_user_task_ui_dirty = 1; } } void vterm_register_line_icon(int idx, const char* icon_key) { if (!vterm_ctx_allow(CAP_WRITE_CONSOLE)) return; if (idx < 0 || idx >= 4) return; if (!icon_key || !icon_key[0]) return; vterm_t* t = &vterms[idx]; if (!t->active) t->active = 1; int slot = vterm_row_slot(t->cur_y); int slot_idx = (int)t->line_icon_count[slot]; if (slot_idx >= VTERM_MAX_LINE_ICONS) return; strncpy(t->line_icon_key[slot][slot_idx], icon_key, sizeof(t->line_icon_key[slot][slot_idx]) - 1); t->line_icon_key[slot][slot_idx][sizeof(t->line_icon_key[slot][slot_idx]) - 1] = '\0'; if (t->cur_x < 0) t->cur_x = 0; if (t->cur_x >= TERM_COLS) t->cur_x = TERM_COLS - 1; t->line_icon_anchor_col[slot][slot_idx] = (uint8_t)t->cur_x; t->line_icon_count[slot] = (uint8_t)(slot_idx + 1); t->line_indent_px[slot] = 0; t->version++; if (g_user_task_active) { g_user_task_ui_dirty = 1; } } void vterm_backspace_output(int idx) { if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; if (!t->active) t->active = 1; // Only support simple "erase previous char on current line". if (t->cur_x <= 0) return; // Move cursor back one and erase that character. t->cur_x--; if (t->cur_x < 0) t->cur_x = 0; int row = vterm_row_slot(t->cur_y); t->buf[row][t->cur_x] = '\0'; // Reset per-char colour at erased position to the line default. uint8 rr = t->line_r[row] ? t->line_r[row] : 200; uint8 gg = t->line_g[row] ? t->line_g[row] : 200; uint8 bb = t->line_b[row] ? t->line_b[row] : 200; t->char_r[row][t->cur_x] = rr; t->char_g[row][t->cur_x] = gg; t->char_b[row][t->cur_x] = bb; t->version++; // If a ring3 task is active, mark UI dirty so IRQ0 will repaint all tiles. if (g_user_task_active) { g_user_task_ui_dirty = 1; } } const char* vterm_get_line(int idx, int row) { if (idx < 0 || idx >= 4) return ""; vterm_t* t = &vterms[idx]; if (row < t->head_row || row > t->cur_y) return ""; return t->buf[vterm_row_slot(row)]; } void vterm_feed_input(int idx, int key) { // backcompat: simple feed (kept for other users) if (key >= 32 && key <= 126) vterm_write_char(idx, (char)key); else if (key == '\n' || key == 10) vterm_write_char(idx, '\n'); } const char* vterm_get_line_icon_key(int idx, int row, int* out_indent_px, int* out_anchor_col) { if (idx < 0 || idx >= 4) return NULL; vterm_t* t = &vterms[idx]; if (row < t->head_row || row > t->cur_y) return NULL; int slot = vterm_row_slot(row); if (out_indent_px) *out_indent_px = (int)t->line_indent_px[slot]; if (t->line_icon_count[slot] == 0) return NULL; if (out_anchor_col) *out_anchor_col = (int)t->line_icon_anchor_col[slot][0]; if (t->line_icon_key[slot][0][0] == '\0') return NULL; return t->line_icon_key[slot][0]; } int vterm_get_line_icon_count(int idx, int row) { if (idx < 0 || idx >= 4) return 0; vterm_t* t = &vterms[idx]; if (row < t->head_row || row > t->cur_y) return 0; return (int)t->line_icon_count[vterm_row_slot(row)]; } const char* vterm_get_line_icon_key_n(int idx, int row, int n, int* out_anchor_col) { if (idx < 0 || idx >= 4) return NULL; if (n < 0 || n >= VTERM_MAX_LINE_ICONS) return NULL; vterm_t* t = &vterms[idx]; if (row < t->head_row || row > t->cur_y) return NULL; int slot = vterm_row_slot(row); if (n >= (int)t->line_icon_count[slot]) return NULL; if (out_anchor_col) *out_anchor_col = (int)t->line_icon_anchor_col[slot][n]; if (t->line_icon_key[slot][n][0] == '\0') return NULL; return t->line_icon_key[slot][n]; } void vterm_clear(int idx) { if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; for (int r = 0; r < VTERM_HISTORY_ROWS; ++r) { t->buf[r][0] = '\0'; t->line_r[r] = 200; t->line_g[r] = 200; t->line_b[r] = 200; vterm_clear_line_icons(t, r); for (int c = 0; c < TERM_COLS; ++c) { t->char_r[r][c] = 200; t->char_g[r][c] = 200; t->char_b[r][c] = 200; } } t->cur_x = 0; t->cur_y = 0; t->head_row = 0; t->scroll = 0; t->input_buf[0] = '\0'; t->input_pos = 0; t->version++; } // Each vterm can accept full key handling similar to readStr_with_history: editing, history, enter to execute void vterm_handle_key(int idx, int key) { if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; if (!t->active) t->active = 1; // Handle arrow keys: Up/Down for history, Left/Right for cursor movement if (key == 0x2104) { // Ctrl+A -> select all of current input buffer int len = strlen(t->input_buf); t->sel_active = (len > 0); t->sel_start_col = 0; t->sel_end_col = len; t->version++; return; } if (key == 0x2105) { // Ctrl+L -> select current line (same as all for single-line input) int len = strlen(t->input_buf); t->sel_active = (len > 0); t->sel_start_col = 0; t->sel_end_col = len; t->version++; return; } // Shift+Arrow selection extension indicated via 0x3000 bit combined with base 0x1001..0x1004 if ((key & 0x3000) && ((key & 0x0FFF) >= 0x1001 && (key & 0x0FFF) <= 0x1004)) { int base = key & 0x0FFF; // Start selection if not active if (!t->sel_active) { t->sel_active = 1; t->sel_start_col = t->input_pos; t->sel_end_col = t->input_pos; } if (base == 0x1003) { // Left: move caret left and extend selection if (t->input_pos > 0) { t->input_pos--; if (t->cur_x > t->input_start_col) t->cur_x--; t->sel_end_col = t->input_pos; } } else if (base == 0x1004) { // Right int len = strlen(t->input_buf); if (t->input_pos < len) { t->input_pos++; t->cur_x = t->input_start_col + t->input_pos; t->sel_end_col = t->input_pos; } } else if (base == 0x1001 || base == 0x1002) { // Up/Down not meaningful for single-line input; ignore } t->version++; return; } if (key == 0x1001) { // Up if (g_command_history.count > 0) { if (t->history_idx == -1) { // first time browsing - save current input strncpy(t->saved_input, t->input_buf, INPUT_BUF_LEN - 1); t->saved_input[INPUT_BUF_LEN - 1] = '\0'; t->history_idx = g_command_history.count - 1; } else if (t->history_idx > 0) { t->history_idx--; } // clear current displayed input (preserve prompt) t->input_pos = 0; t->buf[vterm_row_slot(t->cur_y)][t->input_start_col] = '\0'; t->cur_x = t->input_start_col; // load history entry (per-vterm browsing) strncpy(t->input_buf, g_command_history.commands[t->history_idx], INPUT_BUF_LEN - 1); t->input_buf[INPUT_BUF_LEN - 1] = '\0'; t->input_pos = strlen(t->input_buf); vterm_render_input(idx); } return; } if (key == 0x1002) { // Down if (t->history_idx != -1) { t->history_idx++; // clear current displayed input (preserve prompt) t->input_pos = 0; t->buf[vterm_row_slot(t->cur_y)][t->input_start_col] = '\0'; t->cur_x = t->input_start_col; if (t->history_idx >= g_command_history.count) { // restore saved input strncpy(t->input_buf, t->saved_input, INPUT_BUF_LEN - 1); t->input_buf[INPUT_BUF_LEN - 1] = '\0'; t->input_pos = strlen(t->input_buf); vterm_render_input(idx); t->history_idx = -1; } else { // load next history entry strncpy(t->input_buf, g_command_history.commands[t->history_idx], INPUT_BUF_LEN - 1); t->input_buf[INPUT_BUF_LEN - 1] = '\0'; t->input_pos = strlen(t->input_buf); vterm_render_input(idx); } } return; } if (key == 0x1003) { // Left if (t->input_pos > 0) { // move cursor left logically if (t->cur_x > t->input_start_col) t->cur_x--; t->input_pos--; } // clear selection on plain move t->sel_active = 0; // force a redraw so caret overlay updates when moving within existing text t->version++; return; } if (key == 0x1004) { // Right int len = strlen(t->input_buf); if (t->input_pos < len) { t->input_pos++; t->cur_x = t->input_start_col + t->input_pos; } t->sel_active = 0; // force a redraw so caret overlay updates when moving within existing text t->version++; return; } // Printable if (key >= 32 && key <= 126) { int len = strlen(t->input_buf); // If selection active, delete selected region first if (t->sel_active) { int a = t->sel_start_col, b = t->sel_end_col; if (a > b) { int tmp = a; a = b; b = tmp; } if (a < 0) a = 0; if (b > len) b = len; int tail = len - b; memmove(&t->input_buf[a], &t->input_buf[b], tail); t->input_buf[a + tail] = '\0'; t->input_pos = a; t->sel_active = 0; len = strlen(t->input_buf); } if (len < INPUT_BUF_LEN - 1) { // insert at input_pos memmove(&t->input_buf[t->input_pos + 1], &t->input_buf[t->input_pos], len - t->input_pos + 1); t->input_buf[t->input_pos] = (char)key; t->input_pos++; vterm_render_input(idx); } return; } // Backspace if (key == '\b' || key == 8) { int len = strlen(t->input_buf); if (t->sel_active) { int a = t->sel_start_col, b = t->sel_end_col; if (a > b) { int tmp = a; a = b; b = tmp; } if (a < 0) a = 0; if (b > len) b = len; int tail = len - b; memmove(&t->input_buf[a], &t->input_buf[b], tail); t->input_buf[a + tail] = '\0'; t->input_pos = a; t->sel_active = 0; vterm_render_input(idx); return; } if (t->input_pos > 0) { memmove(&t->input_buf[t->input_pos - 1], &t->input_buf[t->input_pos], len - t->input_pos + 1); t->input_pos--; vterm_render_input(idx); } return; } // Enter - execute command in this vterm if (key == '\n' || key == 10) { // End any active history-browsing session before command execution. // This guarantees the next Up starts from the newest history entry. t->history_idx = -1; t->saved_input[0] = '\0'; // append newline visually vterm_write_char(idx, '\n'); // handle command: use existing handle_shell_command, but capture output via shell redirect if (strlen(t->input_buf) > 0) { while (pipeline_resume_pending()) { // Drain any previously armed pipeline before processing input. } char raw_input[INPUT_BUF_LEN]; strncpy(raw_input, t->input_buf, sizeof(raw_input) - 1); raw_input[sizeof(raw_input) - 1] = '\0'; // Add to global history before command execution so parser/tokenizer mutations // in command handlers cannot alter what Up/Down navigation recalls. add_to_history(&g_command_history, raw_input); if (vterm_input_is_clear_command(raw_input)) { vterm_clear(idx); vterm_print_prompt(idx); t->history_idx = -1; t->sel_active = 0; t->version++; return; } // temporarily redirect shell output to vterm buffer by using start_shell_redirect / shell_redirect_buf // Swap global shell_current_path into this vterm's cwd so commands (like cd) operate per-vterm char saved_global_cwd[128]; int saved_user_task_term = g_user_task_term; strncpy(saved_global_cwd, shell_current_path, sizeof(saved_global_cwd)-1); saved_global_cwd[sizeof(saved_global_cwd)-1] = '\0'; // set global to this vterm's cwd for command execution strncpy(shell_current_path, t->cwd, 127); shell_current_path[127] = '\0'; g_user_task_term = idx; start_shell_redirect(); handle_shell_command(t->input_buf); while (pipeline_resume_pending()) { // Continue newly armed pipelines in tiling-terminal mode. } // Capture the redirect length before stopping, since stop() resets the position int captured_redirect_pos = shell_redirect_pos; stop_shell_redirect(); // after command execution, copy back any cwd changes into this vterm and restore global cwd strncpy(t->cwd, shell_current_path, sizeof(t->cwd)-1); t->cwd[sizeof(t->cwd)-1] = '\0'; // restore previous global cwd strncpy(shell_current_path, saved_global_cwd, sizeof(saved_global_cwd)-1); shell_current_path[sizeof(saved_global_cwd)-1] = '\0'; g_user_task_term = saved_user_task_term; // append redirected output to vterm if (shell_redirect_buf[0]) { // The redirect buffer may contain multiple lines; append each line separately const char* p = shell_redirect_buf; const char* start = p; while (*p) { if (*p == '\n') { int len = p - start; // Clamp to available redirect data first, then to TERM_COLS int base = start - shell_redirect_buf; int available = captured_redirect_pos - base; if (available < 0) available = 0; if (len > available) len = available; if (len > TERM_COLS) len = TERM_COLS; if (len < 0) len = 0; // If any icon marker falls within this output line, record it on this vterm row. // Icons are anchored to the character column where the marker falls. int out_row = t->cur_y; int out_start_col = t->cur_x; { int out_slot = vterm_row_slot(out_row); vterm_clear_line_icons(t, out_slot); for (int ii = 0; ii < shell_redirect_icon_count; ++ii) { int ip = shell_redirect_icons[ii].pos; if (ip < base || ip >= base + len) continue; int rel = ip - base; int anchor = out_start_col + rel; if (anchor < 0 || anchor >= TERM_COLS) continue; int slot_idx = (int)t->line_icon_count[out_slot]; if (slot_idx >= VTERM_MAX_LINE_ICONS) break; strncpy(t->line_icon_key[out_slot][slot_idx], shell_redirect_icons[ii].ext, sizeof(t->line_icon_key[out_slot][slot_idx]) - 1); t->line_icon_key[out_slot][slot_idx][sizeof(t->line_icon_key[out_slot][slot_idx]) - 1] = '\0'; t->line_icon_anchor_col[out_slot][slot_idx] = (uint8_t)anchor; t->line_icon_count[out_slot] = (uint8_t)(slot_idx + 1); } // No pixel indentation; reserve space in the text stream with leading spaces. t->line_indent_px[out_slot] = 0; } // write characters with per-char colours for (int i = 0; i < len; ++i) { vterm_write_char(idx, start[i]); int wr = shell_redirect_r[base + i] ? shell_redirect_r[base + i] : 200; int wg = shell_redirect_g[base + i] ? shell_redirect_g[base + i] : 200; int wb = shell_redirect_b[base + i] ? shell_redirect_b[base + i] : 200; int rr = t->cur_y; int cc = t->cur_x - 1; if (cc >= 0 && cc < TERM_COLS) { int rslot = vterm_row_slot(rr); t->char_r[rslot][cc] = wr; t->char_g[rslot][cc] = wg; t->char_b[rslot][cc] = wb; } } // append newline vterm_write_char(idx, '\n'); start = p + 1; } p++; } // If any remaining text after last newline if (start < p) { int len = p - start; int base = start - shell_redirect_buf; int available = captured_redirect_pos - base; if (available < 0) available = 0; if (len > available) len = available; if (len > TERM_COLS) len = TERM_COLS; if (len < 0) len = 0; int out_row = t->cur_y; int out_start_col = t->cur_x; { int out_slot = vterm_row_slot(out_row); vterm_clear_line_icons(t, out_slot); for (int ii = 0; ii < shell_redirect_icon_count; ++ii) { int ip = shell_redirect_icons[ii].pos; if (ip < base || ip >= base + len) continue; int rel = ip - base; int anchor = out_start_col + rel; if (anchor < 0 || anchor >= TERM_COLS) continue; int slot_idx = (int)t->line_icon_count[out_slot]; if (slot_idx >= VTERM_MAX_LINE_ICONS) break; strncpy(t->line_icon_key[out_slot][slot_idx], shell_redirect_icons[ii].ext, sizeof(t->line_icon_key[out_slot][slot_idx]) - 1); t->line_icon_key[out_slot][slot_idx][sizeof(t->line_icon_key[out_slot][slot_idx]) - 1] = '\0'; t->line_icon_anchor_col[out_slot][slot_idx] = (uint8_t)anchor; t->line_icon_count[out_slot] = (uint8_t)(slot_idx + 1); } t->line_indent_px[out_slot] = 0; } for (int i = 0; i < len; ++i) { vterm_write_char(idx, start[i]); int wr = shell_redirect_r[base + i] ? shell_redirect_r[base + i] : 200; int wg = shell_redirect_g[base + i] ? shell_redirect_g[base + i] : 200; int wb = shell_redirect_b[base + i] ? shell_redirect_b[base + i] : 200; int rr = t->cur_y; int cc = t->cur_x - 1; if (cc >= 0 && cc < TERM_COLS) { int rslot = vterm_row_slot(rr); t->char_r[rslot][cc] = wr; t->char_g[rslot][cc] = wg; t->char_b[rslot][cc] = wb; } } } // reset redirect buffer shell_redirect_pos = 0; shell_redirect_buf[0] = '\0'; shell_redirect_icon_count = 0; } // reset this vterm's browsing state so next Up starts from the most-recent entry t->history_idx = -1; } // reset input buffer via new prompt t->sel_active = 0; // After executing, print a fresh prompt line (this will also reset input anchors) vterm_print_prompt(idx); t->version++; return; } // Arrow keys for local navigation in future (ignored for now) } int vterm_get_version(int idx) { if (idx < 0 || idx >= 4) return 0; return vterms[idx].version; } // Return the tail-visible line for a vterm given visible_count. const char* vterm_get_tail_line(int idx, int visible_index, int visible_count) { if (idx < 0 || idx >= 4) return ""; vterm_t* t = &vterms[idx]; // Compute which lines are currently visible: last visible_count lines ending at cur_y int end = t->cur_y; if (end < t->head_row) end = t->head_row; int start = end - visible_count + 1; if (start < t->head_row) start = t->head_row; int target = start + visible_index; if (target < t->head_row || target > t->cur_y) return ""; return t->buf[vterm_row_slot(target)]; } void vterm_get_tail_line_colour(int idx, int visible_index, int visible_count, int* r, int* g, int* b) { if (r) *r = 200; if (g) *g = 200; if (b) *b = 200; if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; int end = t->cur_y; if (end < t->head_row) end = t->head_row; int start = end - visible_count + 1; if (start < t->head_row) start = t->head_row; int target = start + visible_index; if (target < t->head_row || target > t->cur_y) return; int slot = vterm_row_slot(target); if (r) *r = t->line_r[slot]; if (g) *g = t->line_g[slot]; if (b) *b = t->line_b[slot]; } // Return colour for a specific character in the tail view void vterm_get_tail_char_colour(int idx, int visible_index, int visible_count, int char_col, int* r, int* g, int* b) { if (r) *r = 200; if (g) *g = 200; if (b) *b = 200; if (idx < 0 || idx >= 4) return; if (char_col < 0 || char_col >= TERM_COLS) return; vterm_t* t = &vterms[idx]; int end = t->cur_y; if (end < t->head_row) end = t->head_row; int start = end - visible_count + 1; if (start < t->head_row) start = t->head_row; int target = start + visible_index; if (target < t->head_row || target > t->cur_y) return; int slot = vterm_row_slot(target); if (r) *r = t->char_r[slot][char_col]; if (g) *g = t->char_g[slot][char_col]; if (b) *b = t->char_b[slot][char_col]; } void vterm_set_active(int idx, int active) { if (idx < 0 || idx >= 4) return; vterms[idx].active = active; } int vterm_is_active(int idx) { if (idx < 0 || idx >= 4) return 0; return vterms[idx].active; } void vterm_move_state(int dst_idx, int src_idx) { if (dst_idx < 0 || dst_idx >= 4) return; if (src_idx < 0 || src_idx >= 4) return; if (dst_idx == src_idx) return; vterms[dst_idx] = vterms[src_idx]; vterm_t* src = &vterms[src_idx]; src->cur_x = 0; src->cur_y = 0; src->head_row = 0; src->scroll = 0; src->active = 0; src->input_buf[0] = '\0'; src->input_pos = 0; src->history_idx = -1; src->saved_input[0] = '\0'; strncpy(src->cwd, "/", sizeof(src->cwd) - 1); src->cwd[sizeof(src->cwd) - 1] = '\0'; for (int r = 0; r < VTERM_HISTORY_ROWS; ++r) { src->buf[r][0] = '\0'; src->line_r[r] = 200; src->line_g[r] = 200; src->line_b[r] = 200; vterm_clear_line_icons(src, r); for (int c = 0; c < TERM_COLS; ++c) { src->char_r[r][c] = 200; src->char_g[r][c] = 200; src->char_b[r][c] = 200; } } src->version++; src->sel_active = 0; src->sel_start_col = 0; src->sel_end_col = 0; src->input_row = 0; src->input_start_col = 0; src->stdin_buf[0] = '\0'; src->stdin_len = 0; src->stdin_ready = 0; src->stdin_raw_mode = 0; src->tty_rows = TERM_ROWS; src->tty_cols = TERM_COLS; } int vterm_get_cursor_row(int idx) { if (idx < 0 || idx >= 4) return 0; return vterms[idx].cur_y; } void vterm_get_char_colour_abs(int idx, int row, int col, int* r, int* g, int* b) { if (r) *r = 200; if (g) *g = 200; if (b) *b = 200; if (idx < 0 || idx >= 4) return; if (col < 0 || col >= TERM_COLS) return; vterm_t* t = &vterms[idx]; if (row < t->head_row || row > t->cur_y) return; int slot = vterm_row_slot(row); if (r) *r = t->char_r[slot][col]; if (g) *g = t->char_g[slot][col]; if (b) *b = t->char_b[slot][col]; } int vterm_get_cursor_col(int idx) { if (idx < 0 || idx >= 4) return 0; return vterms[idx].cur_x; } void vterm_clear_selection(int idx) { if (idx < 0 || idx >= 4) return; vterms[idx].sel_active = 0; } int vterm_is_selected(int idx, int row, int col) { if (idx < 0 || idx >= 4) return 0; vterm_t* t = &vterms[idx]; if (!t->sel_active) return 0; if (row != t->input_row) return 0; int a = t->sel_start_col; int b = t->sel_end_col; if (a > b) { int tmp = a; a = b; b = tmp; } int abs_a = t->input_start_col + a; int abs_b = t->input_start_col + b; return (col >= abs_a && col < abs_b); } // User task stdin buffer API // When a ring3 user task is active and waiting for input, the TUI routes // keyboard characters to this buffer. The syscall READ (fd=0) consumes from here. // Clear the stdin buffer for a vterm (called when starting a new user task) void vterm_stdin_clear(int idx) { if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; t->stdin_buf[0] = '\0'; t->stdin_len = 0; t->stdin_ready = 0; } // Append a character to the stdin buffer (called from TUI input handler). // Returns 1 if the character was a newline (line is complete), 0 otherwise. int vterm_stdin_putchar(int idx, char ch) { if (idx < 0 || idx >= 4) return 0; vterm_t* t = &vterms[idx]; if (t->stdin_raw_mode) { if (t->stdin_len < (int)sizeof(t->stdin_buf) - 1) { t->stdin_buf[t->stdin_len++] = ch; t->stdin_buf[t->stdin_len] = '\0'; t->stdin_ready = 1; return 1; } return 0; } // Handle backspace if (ch == '\b' || ch == 127) { if (t->stdin_len > 0) { t->stdin_len--; t->stdin_buf[t->stdin_len] = '\0'; } return 0; } // Handle newline - mark line as ready if (ch == '\n' || ch == '\r') { if (t->stdin_len < (int)sizeof(t->stdin_buf) - 1) { t->stdin_buf[t->stdin_len++] = '\n'; t->stdin_buf[t->stdin_len] = '\0'; } t->stdin_ready = 1; return 1; } // Append printable character if (t->stdin_len < (int)sizeof(t->stdin_buf) - 2) { // Leave room for \n and \0 t->stdin_buf[t->stdin_len++] = ch; t->stdin_buf[t->stdin_len] = '\0'; } return 0; } // Check if a complete line is ready in the stdin buffer int vterm_stdin_ready(int idx) { if (idx < 0 || idx >= 4) return 0; return vterms[idx].stdin_ready; } // Get pointer to stdin buffer data (for copying to user space) const char* vterm_stdin_data(int idx) { if (idx < 0 || idx >= 4) return ""; return vterms[idx].stdin_buf; } // Get length of data in stdin buffer int vterm_stdin_len(int idx) { if (idx < 0 || idx >= 4) return 0; return vterms[idx].stdin_len; } // Consume/clear the stdin buffer after the syscall has read the data void vterm_stdin_consume(int idx) { if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; t->stdin_buf[0] = '\0'; t->stdin_len = 0; t->stdin_ready = 0; } void vterm_stdin_consume_bytes(int idx, int count) { if (idx < 0 || idx >= 4) return; if (count <= 0) return; vterm_t* t = &vterms[idx]; if (count >= t->stdin_len) { vterm_stdin_consume(idx); return; } int remain = t->stdin_len - count; memmove(t->stdin_buf, t->stdin_buf + count, (size_t)remain); t->stdin_len = remain; t->stdin_buf[remain] = '\0'; t->stdin_ready = t->stdin_raw_mode ? (remain > 0 ? 1 : 0) : 0; } void vterm_stdin_set_raw(int idx, int enabled) { if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; t->stdin_raw_mode = enabled ? 1 : 0; if (!t->stdin_raw_mode) { // Canonical mode expects complete lines. t->stdin_ready = 0; } else if (t->stdin_len > 0) { t->stdin_ready = 1; } } int vterm_stdin_is_raw(int idx) { if (idx < 0 || idx >= 4) return 0; return vterms[idx].stdin_raw_mode; } void vterm_stdin_set_winsize(int idx, uint16_t rows, uint16_t cols) { if (idx < 0 || idx >= 4) return; if (rows == 0 || cols == 0) return; vterm_t* t = &vterms[idx]; t->tty_rows = rows; t->tty_cols = cols; } void vterm_stdin_get_winsize(int idx, uint16_t* out_rows, uint16_t* out_cols) { if (out_rows) *out_rows = (uint16_t)TERM_ROWS; if (out_cols) *out_cols = (uint16_t)TERM_COLS; if (idx < 0 || idx >= 4) return; vterm_t* t = &vterms[idx]; if (out_rows) *out_rows = t->tty_rows; if (out_cols) *out_cols = t->tty_cols; }