// Copyright (c) 2023-2026 Chris (boreddevnl) // This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This header needs to maintain in any file it is present in, as per the GPL license terms. #include #include #include "libc/libui.h" #include "libc/input.h" #include #include #define DEFAULT_COLS 116 #define DEFAULT_ROWS 41 #define SCROLLBACK_LINES 800 #define SCROLLBACK_COLS 256 #define CHAR_W 8 #define DEFAULT_LINE_H 10 #define TAB_BAR_H 20 #define CONTENT_PAD_BOTTOM 20 #define TAB_CLOSE_W 12 #define TAB_CLOSE_PAD 6 #define MAX_TABS 4 #define TTY_READ_CHUNK 512T #define LINE_MAX 256 typedef struct { char c; uint32_t color; } CharCell; typedef struct { int tty_id; int bsh_pid; CharCell *cells; CharCell *scrollback; int *scroll_cols; int scroll_head; int scroll_count; int scroll_offset; int cursor_row; int cursor_col; uint32_t fg_color; uint32_t bg_color; uint32_t input_color; int ansi_state; int ansi_params[8]; int ansi_param_count; int saved_row; int saved_col; // for color char current_input[LINE_MAX]; int input_len; } TerminalSession; static ui_window_t g_win; static TerminalSession g_tabs[MAX_TABS]; static int g_tab_count = 0; static int g_active_tab = 0; static int g_cols = DEFAULT_COLS; static int g_rows = DEFAULT_ROWS; static int g_line_h = DEFAULT_LINE_H; static int g_win_w = DEFAULT_COLS * CHAR_W; static int g_win_h = TAB_BAR_H + (DEFAULT_ROWS * DEFAULT_LINE_H); static void str_copy(char *dst, const char *src, int max_len) { int i = 0; if (max_len <= 0) return; while (i < max_len - 1 && src && src[i]) { dst[i] = src[i]; i++; } dst[i] = 0; } static void str_append(char *dst, const char *src, int max_len) { if (!dst || !src || max_len <= 0) return; int dlen = (int)strlen(dst); int i = 0; while (dlen + i < max_len - 1 && src[i]) { dst[dlen + i] = src[i]; i++; } dst[dlen + i] = 0; } static void trim_end(char *s) { if (!s) return; int len = (int)strlen(s); while (len > 0 && (s[len - 1] == '\n' || s[len - 1] == '\r' || s[len - 1] == ' ' || s[len - 1] == '\t')) { s[len - 1] = 0; len--; } } static void scrollback_init(TerminalSession *s) { s->scroll_head = 0; s->scroll_count = 0; s->scroll_offset = 0; if (s->scrollback) { for (int i = 0; i < SCROLLBACK_LINES * SCROLLBACK_COLS; i++) { s->scrollback[i].c = ' '; s->scrollback[i].color = s->fg_color; } } if (s->scroll_cols) { for (int i = 0; i < SCROLLBACK_LINES; i++) s->scroll_cols[i] = 0; } } static void scrollback_push_row(TerminalSession *s, const CharCell *row, int cols) { if (!s->scrollback || !s->scroll_cols) return; int idx = s->scroll_head; CharCell *dest = s->scrollback + (idx * SCROLLBACK_COLS); int copy_cols = cols; if (copy_cols > SCROLLBACK_COLS) copy_cols = SCROLLBACK_COLS; for (int i = 0; i < SCROLLBACK_COLS; i++) { dest[i].c = ' '; dest[i].color = s->fg_color; } for (int i = 0; i < copy_cols; i++) { dest[i] = row[i]; } s->scroll_cols[idx] = copy_cols; s->scroll_head = (idx + 1) % SCROLLBACK_LINES; if (s->scroll_count < SCROLLBACK_LINES) { s->scroll_count++; } else if (s->scroll_offset > 0) { s->scroll_offset--; } if (s->scroll_offset > 0) { s->scroll_offset++; } } static int scrollback_max_offset(TerminalSession *s) { int total = s->scroll_count + g_rows; if (total <= g_rows) return 0; return total - g_rows; } static void session_adjust_scroll(TerminalSession *s, int delta) { if (!s) return; int max_offset = scrollback_max_offset(s); s->scroll_offset += delta; if (s->scroll_offset < 0) s->scroll_offset = 0; if (s->scroll_offset > max_offset) s->scroll_offset = max_offset; } static CharCell *scrollback_get_line(TerminalSession *s, int line_index, int *out_cols) { if (!s || line_index < 0 || line_index >= s->scroll_count) return NULL; int start = s->scroll_head - s->scroll_count; if (start < 0) start += SCROLLBACK_LINES; int idx = (start + line_index) % SCROLLBACK_LINES; if (out_cols) *out_cols = s->scroll_cols[idx]; return s->scrollback + (idx * SCROLLBACK_COLS); } static uint32_t ansi_color_16(int idx) { static uint32_t base[16] = { 0xFF000000, 0xFFAA0000, 0xFF00AA00, 0xFFAA5500, 0xFF0000AA, 0xFFAA00AA, 0xFF00AAAA, 0xFFAAAAAA, 0xFF555555, 0xFFFF5555, 0xFF55FF55, 0xFFFFFF55, 0xFF5555FF, 0xFFFF55FF, 0xFF55FFFF, 0xFFFFFFFF }; if (idx < 0) idx = 0; if (idx > 15) idx = 15; return base[idx]; } static void session_reset_colors(TerminalSession *s) { s->fg_color = 0xFFFFFFFF; s->bg_color = 0xFF1E1E1E; } static void session_clear(TerminalSession *s) { for (int i = 0; i < g_cols * g_rows; i++) { s->cells[i].c = ' '; s->cells[i].color = s->fg_color; } s->cursor_row = 0; s->cursor_col = 0; s->scroll_offset = 0; } static void session_scroll(TerminalSession *s) { int row_bytes = g_cols * (int)sizeof(CharCell); if (g_rows > 0) { scrollback_push_row(s, s->cells, g_cols); } memmove(s->cells, s->cells + g_cols, row_bytes * (g_rows - 1)); for (int i = 0; i < g_cols; i++) { int idx = (g_rows - 1) * g_cols + i; s->cells[idx].c = ' '; s->cells[idx].color = s->fg_color; } s->cursor_row = g_rows - 1; s->cursor_col = 0; } static void session_put_char(TerminalSession *s, char c) { if (c == '\n') { s->cursor_row++; s->cursor_col = 0; if (s->cursor_row >= g_rows) session_scroll(s); return; } if (c == '\r') { s->cursor_col = 0; return; } if (c == '\b') { if (s->cursor_col > 0) s->cursor_col--; int idx = s->cursor_row * g_cols + s->cursor_col; if (idx >= 0 && idx < g_cols * g_rows) { s->cells[idx].c = ' '; s->cells[idx].color = s->fg_color; } return; } if (c == '\t') { int next = ((s->cursor_col / 4) + 1) * 4; while (s->cursor_col < next) { session_put_char(s, ' '); } return; } if (c < 32) return; int idx = s->cursor_row * g_cols + s->cursor_col; if (idx >= 0 && idx < g_cols * g_rows) { s->cells[idx].c = c; s->cells[idx].color = s->fg_color; } s->cursor_col++; if (s->cursor_col >= g_cols) { s->cursor_col = 0; s->cursor_row++; if (s->cursor_row >= g_rows) session_scroll(s); } } static void ansi_handle_sgr(TerminalSession *s) { if (s->ansi_param_count == 0) { session_reset_colors(s); return; } for (int i = 0; i < s->ansi_param_count; i++) { int p = s->ansi_params[i]; if (p == 0) session_reset_colors(s); else if (p >= 30 && p <= 37) s->fg_color = ansi_color_16(p - 30); else if (p >= 90 && p <= 97) s->fg_color = ansi_color_16(8 + (p - 90)); else if (p >= 40 && p <= 47) s->bg_color = ansi_color_16(p - 40); else if (p >= 100 && p <= 107) s->bg_color = ansi_color_16(8 + (p - 100)); else if (p == 38 || p == 48) { if (i + 4 < s->ansi_param_count && s->ansi_params[i + 1] == 2) { int r = s->ansi_params[i + 2] & 0xFF; int g = s->ansi_params[i + 3] & 0xFF; int b = s->ansi_params[i + 4] & 0xFF; uint32_t color = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b; if (p == 38) s->fg_color = color; else s->bg_color = color; i += 4; } } } } static void ansi_finalize(TerminalSession *s, char cmd) { if (cmd == 'm') { ansi_handle_sgr(s); } else if (cmd == 'J') { int mode = s->ansi_param_count > 0 ? s->ansi_params[0] : 0; if (mode == 2 || mode == 0) session_clear(s); } else if (cmd == 'K') { int row = s->cursor_row; for (int col = s->cursor_col; col < g_cols; col++) { int idx = row * g_cols + col; s->cells[idx].c = ' '; s->cells[idx].color = s->fg_color; } } else if (cmd == 'H' || cmd == 'f') { int row = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1; int col = (s->ansi_param_count > 1) ? s->ansi_params[1] : 1; if (row < 1) row = 1; if (col < 1) col = 1; s->cursor_row = row - 1; s->cursor_col = col - 1; } else if (cmd == 'A') { int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1; s->cursor_row -= n; if (s->cursor_row < 0) s->cursor_row = 0; } else if (cmd == 'B') { int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1; s->cursor_row += n; if (s->cursor_row >= g_rows) s->cursor_row = g_rows - 1; } else if (cmd == 'C') { int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1; s->cursor_col += n; if (s->cursor_col >= g_cols) s->cursor_col = g_cols - 1; } else if (cmd == 'D') { int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1; s->cursor_col -= n; if (s->cursor_col < 0) s->cursor_col = 0; } else if (cmd == 's') { s->saved_row = s->cursor_row; s->saved_col = s->cursor_col; } else if (cmd == 'u') { s->cursor_row = s->saved_row; s->cursor_col = s->saved_col; } s->ansi_state = 0; s->ansi_param_count = 0; } static void session_process_char(TerminalSession *s, char c) { if (s->ansi_state == 0) { if (c == 27) { s->ansi_state = 1; s->ansi_param_count = 0; s->ansi_params[0] = 0; return; } session_put_char(s, c); return; } if (s->ansi_state == 1) { if (c == '[') { s->ansi_state = 2; s->ansi_param_count = 0; s->ansi_params[0] = 0; return; } s->ansi_state = 0; session_put_char(s, c); return; } if (s->ansi_state == 2) { if (c >= '0' && c <= '9') { int idx = s->ansi_param_count; s->ansi_params[idx] = s->ansi_params[idx] * 10 + (c - '0'); return; } if (c == ';') { if (s->ansi_param_count < 7) { s->ansi_param_count++; s->ansi_params[s->ansi_param_count] = 0; } return; } s->ansi_param_count++; ansi_finalize(s, c); return; } } static void session_process_output(TerminalSession *s, const char *buf, int len) { for (int i = 0; i < len; i++) { session_process_char(s, buf[i]); } } static int get_tab_width(void) { if (g_tab_count <= 0) return g_win_w; int tab_w = g_win_w / g_tab_count; if (tab_w < 60) tab_w = 60; return tab_w; } static int read_proc_field(int pid, const char *field, char *out, int max_len) { if (!out || max_len <= 0) return -1; char path[64]; path[0] = 0; str_append(path, "/proc/", sizeof(path)); char pid_buf[16]; itoa(pid, pid_buf); str_append(path, pid_buf, sizeof(path)); str_append(path, "/", sizeof(path)); str_append(path, field, sizeof(path)); int fd = sys_open(path, "r"); if (fd < 0) return -1; int bytes = sys_read(fd, out, max_len - 1); sys_close(fd); if (bytes <= 0) return -1; out[bytes] = 0; trim_end(out); return 0; } static void truncate_label(const char *src, char *out, int max_chars) { if (!out || max_chars <= 0) return; int len = (int)strlen(src); if (len <= max_chars) { str_copy(out, src, max_chars + 1); return; } if (max_chars <= 3) { for (int i = 0; i < max_chars; i++) out[i] = src[i]; out[max_chars] = 0; return; } for (int i = 0; i < max_chars - 3; i++) out[i] = src[i]; out[max_chars - 3] = '.'; out[max_chars - 2] = '.'; out[max_chars - 1] = '.'; out[max_chars] = 0; } static void get_tab_title(TerminalSession *s, char *out, int max_len) { if (!s || !out) return; int fg = sys_tty_get_fg(s->tty_id); if (fg > 0) { if (read_proc_field(fg, "name", out, max_len) == 0) return; } if (s->bsh_pid > 0) { if (read_proc_field(s->bsh_pid, "cwd", out, max_len) == 0) return; } str_copy(out, "Bsh", max_len); } static int read_config_value(const char *key, char *out, int max_len) { if (!key || !out || max_len <= 0) return -1; int fd = sys_open("/Library/bsh/bshrc", "r"); if (fd < 0) return -1; char buf[4096]; int bytes = sys_read(fd, buf, sizeof(buf) - 1); sys_close(fd); if (bytes <= 0) return -1; buf[bytes] = 0; char *line = buf; while (*line) { char *end = line; while (*end && *end != '\n' && *end != '\r') end++; char saved = *end; *end = 0; trim_end(line); if (line[0] != '#' && line[0] != 0) { char *sep = line; while (*sep && *sep != '=') sep++; if (*sep == '=') { *sep = 0; if (strcmp(line, key) == 0) { str_copy(out, sep + 1, max_len); return 0; } } } line = end + (saved ? 1 : 0); if (saved == '\r' && *line == '\n') line++; } return -1; } static void resolve_bsh_path(char *out, int max_len) { char path_line[256]; if (read_config_value("PATH", path_line, sizeof(path_line)) != 0) { str_copy(path_line, "/bin", sizeof(path_line)); } int i = 0; int start = 0; while (1) { if (path_line[i] == ':' || path_line[i] == 0) { int len = i - start; if (len > 0) { char base[128]; if (len >= (int)sizeof(base)) len = (int)sizeof(base) - 1; for (int j = 0; j < len; j++) base[j] = path_line[start + j]; base[len] = 0; char candidate[160]; candidate[0] = 0; str_append(candidate, base, sizeof(candidate)); if (candidate[0] && candidate[strlen(candidate) - 1] != '/') str_append(candidate, "/", sizeof(candidate)); str_append(candidate, "bsh.elf", sizeof(candidate)); if (sys_exists(candidate)) { str_copy(out, candidate, max_len); return; } candidate[0] = 0; str_append(candidate, base, sizeof(candidate)); if (candidate[0] && candidate[strlen(candidate) - 1] != '/') str_append(candidate, "/", sizeof(candidate)); str_append(candidate, "bsh", sizeof(candidate)); if (sys_exists(candidate)) { str_copy(out, candidate, max_len); return; } } start = i + 1; } if (path_line[i] == 0) break; i++; } str_copy(out, "/bin/bsh.elf", max_len); } static bool has_space(const char *s) { if (!s) return false; while (*s) { if (*s == ' ') return true; s++; } return false; } static void terminal_resize(int w, int h) { int min_w = CHAR_W * 40; int min_h = TAB_BAR_H + (g_line_h * 10); if (w < min_w) w = min_w; if (h < min_h) h = min_h; g_win_w = w; g_win_h = h; int new_cols = w / CHAR_W; int content_h = h - TAB_BAR_H - CONTENT_PAD_BOTTOM; if (g_line_h <= 0) g_line_h = DEFAULT_LINE_H; if (content_h < g_line_h) content_h = g_line_h; int new_rows = content_h / g_line_h; if (new_cols < 10) new_cols = 10; if (new_rows < 5) new_rows = 5; if (new_cols == g_cols && new_rows == g_rows) return; int old_cols = g_cols; int old_rows = g_rows; g_cols = new_cols; g_rows = new_rows; for (int i = 0; i < g_tab_count; i++) { TerminalSession *s = &g_tabs[i]; int old_scroll_count = s->scroll_count; int old_scroll_offset = s->scroll_offset; int old_total_lines = old_scroll_count + old_rows; int old_bottom_line = old_total_lines - 1 - old_scroll_offset; int old_top_line = old_bottom_line - (old_rows - 1); CharCell *old_cells = s->cells; int old_cursor_row = s->cursor_row; int old_cursor_col = s->cursor_col; s->cells = (CharCell *)malloc(sizeof(CharCell) * g_cols * g_rows); for (int j = 0; j < g_cols * g_rows; j++) { s->cells[j].c = ' '; s->cells[j].color = s->fg_color; } int row_start = 0; if (old_rows > g_rows) { if (old_cursor_row >= g_rows) { row_start = old_cursor_row - (g_rows - 1); } else { row_start = 0; } int max_start = old_rows - g_rows; if (row_start > max_start) row_start = max_start; if (row_start < 0) row_start = 0; } int dropped = 0; if (row_start > 0) { int projected = old_scroll_count + row_start; if (projected > SCROLLBACK_LINES) dropped = projected - SCROLLBACK_LINES; } int desired_top_line = old_top_line - dropped; if (desired_top_line < 0) desired_top_line = 0; for (int r = 0; r < row_start; r++) { scrollback_push_row(s, old_cells + (r * old_cols), old_cols); } int copy_rows = old_rows; if (copy_rows > g_rows) copy_rows = g_rows; int copy_cols = old_cols < g_cols ? old_cols : g_cols; for (int r = 0; r < copy_rows; r++) { CharCell *src = old_cells + ((row_start + r) * old_cols); CharCell *dst = s->cells + (r * g_cols); for (int c = 0; c < copy_cols; c++) { dst[c] = src[c]; } } s->cursor_row = old_cursor_row - row_start; if (old_rows <= g_rows) s->cursor_row = old_cursor_row; if (s->cursor_row < 0) s->cursor_row = 0; if (s->cursor_row >= g_rows) s->cursor_row = g_rows - 1; s->cursor_col = old_cursor_col; if (s->cursor_col < 0) s->cursor_col = 0; if (s->cursor_col >= g_cols) s->cursor_col = g_cols - 1; if (old_scroll_offset == 0) { s->scroll_offset = 0; } else { int new_total_lines = s->scroll_count + g_rows; int desired_bottom = desired_top_line + (g_rows - 1); s->scroll_offset = (new_total_lines - 1) - desired_bottom; } session_adjust_scroll(s, 0); if (old_cells) free(old_cells); } } static void draw_tabs(void) { if (g_tab_count <= 0) return; ui_draw_rect(g_win, 0, 0, g_win_w, TAB_BAR_H, 0xFF1A1A1A); int tab_w = get_tab_width(); for (int i = 0; i < g_tab_count; i++) { int x = i * tab_w; uint32_t bg = (i == g_active_tab) ? 0xFF333333 : 0xFF222222; ui_draw_rect(g_win, x, 0, tab_w - 2, TAB_BAR_H, bg); int close_size = TAB_CLOSE_W; int close_x = x + tab_w - TAB_CLOSE_PAD - close_size; int close_y = (TAB_BAR_H - close_size) / 2; uint32_t close_bg = (i == g_active_tab) ? 0xFF444444 : 0xFF333333; ui_draw_rect(g_win, close_x, close_y, close_size, close_size, close_bg); ui_draw_string(g_win, close_x + 2, close_y + 1, "x", 0xFFFFFFFF); char title[64]; char label[64]; get_tab_title(&g_tabs[i], title, sizeof(title)); int text_w = tab_w - 10 - (TAB_CLOSE_PAD + TAB_CLOSE_W); int max_chars = text_w / CHAR_W; if (max_chars < 4) max_chars = 4; truncate_label(title, label, max_chars); ui_draw_string(g_win, x + 6, 4, label, 0xFFFFFFFF); } } static void draw_session(TerminalSession *s) { int base_y = TAB_BAR_H; ui_draw_rect(g_win, 0, base_y, g_win_w, g_win_h - base_y, s->bg_color); int max_offset = scrollback_max_offset(s); if (s->scroll_offset > max_offset) s->scroll_offset = max_offset; int total_lines = s->scroll_count + g_rows; int bottom_line = total_lines - 1 - s->scroll_offset; int top_line = bottom_line - (g_rows - 1); for (int row = 0; row < g_rows; row++) { int line_index = top_line + row; CharCell *line = NULL; int line_cols = 0; if (line_index >= 0 && line_index < s->scroll_count) { line = scrollback_get_line(s, line_index, &line_cols); } else if (line_index >= s->scroll_count && line_index < total_lines) { int live_row = line_index - s->scroll_count; if (live_row >= 0 && live_row < g_rows) { line = s->cells + (live_row * g_cols); line_cols = g_cols; } } int input_start = s->cursor_col - s->input_len; if (input_start < 0) input_start = 0; if (input_start >= g_cols) input_start = g_cols - 1; // lenght of a command int cmd_len = 0; while (cmd_len < s->input_len && s->current_input[cmd_len] != ' ') { cmd_len++; } int input_end = input_start + cmd_len; if (input_end > g_cols) input_end = g_cols; for (int col = 0; col < g_cols; col++) { char ch = ' '; uint32_t color = s->fg_color; if (line && col < line_cols) { ch = line[col].c; if (ch == 0) ch = ' '; color = line[col].color; } if (s->scroll_offset == 0 && row == s->cursor_row) { if (col >= input_start && col < input_end) { color = s->input_color; } } char str[2] = { ch, 0 }; int x = col * CHAR_W; int y = base_y + row * g_line_h; ui_draw_string_bitmap(g_win, x, y, str, color); } } if (s->scroll_offset == 0) { int cx = s->cursor_col * CHAR_W; int cy = base_y + s->cursor_row * g_line_h; ui_draw_rect(g_win, cx, cy + g_line_h - 2, CHAR_W, 2, 0xFFFFFFFF); } ui_mark_dirty(g_win, 0, 0, g_win_w, g_win_h); } static void tab_init(TerminalSession *s, int tty_id, int bsh_pid) { s->tty_id = tty_id; s->bsh_pid = bsh_pid; s->cells = (CharCell *)malloc(sizeof(CharCell) * g_cols * g_rows); s->scrollback = (CharCell *)malloc(sizeof(CharCell) * SCROLLBACK_LINES * SCROLLBACK_COLS); s->scroll_cols = (int *)malloc(sizeof(int) * SCROLLBACK_LINES); s->cursor_row = 0; s->cursor_col = 0; s->ansi_state = 0; s->ansi_param_count = 0; s->saved_row = 0; s->saved_col = 0; s->input_len = 0; s->current_input[0] = 0; session_reset_colors(s); scrollback_init(s); session_clear(s); } static void tab_free(TerminalSession *s) { if (!s) return; if (s->cells) { free(s->cells); s->cells = NULL; } if (s->scrollback) { free(s->scrollback); s->scrollback = NULL; } if (s->scroll_cols) { free(s->scroll_cols); s->scroll_cols = NULL; } } static void close_tab(int idx) { if (idx < 0 || idx >= g_tab_count) return; TerminalSession *s = &g_tabs[idx]; sys_tty_kill_all(s->tty_id); sys_tty_destroy(s->tty_id); tab_free(s); for (int i = idx; i < g_tab_count - 1; i++) { g_tabs[i] = g_tabs[i + 1]; } g_tab_count--; if (g_tab_count <= 0) { sys_exit(0); } if (g_active_tab > idx) { g_active_tab--; } else if (g_active_tab == idx) { if (g_active_tab >= g_tab_count) g_active_tab = g_tab_count - 1; } } static int create_tab(void) { if (g_tab_count >= MAX_TABS) return -1; int tty_id = sys_tty_create(); if (tty_id < 0) return -1; char start_dir[256]; start_dir[0] = 0; if (g_tab_count > 0) { TerminalSession *active = &g_tabs[g_active_tab]; if (active->bsh_pid > 0) { read_proc_field(active->bsh_pid, "cwd", start_dir, sizeof(start_dir)); } } char args[32]; args[0] = 0; str_append(args, "-t ", sizeof(args)); char id_buf[8]; itoa(tty_id, id_buf); str_append(args, id_buf, sizeof(args)); if (start_dir[0]) { str_append(args, " -d ", sizeof(args)); if (has_space(start_dir)) { str_append(args, "\"", sizeof(args)); str_append(args, start_dir, sizeof(args)); str_append(args, "\"", sizeof(args)); } else { str_append(args, start_dir, sizeof(args)); } } char bsh_path[128]; resolve_bsh_path(bsh_path, sizeof(bsh_path)); int pid = sys_spawn(bsh_path, args, SPAWN_FLAG_TERMINAL | SPAWN_FLAG_TTY_ID, tty_id); if (pid < 0) return -1; tab_init(&g_tabs[g_tab_count], tty_id, pid); g_tab_count++; return g_tab_count - 1; } // checks if a command exist in /bin static bool command_exists(const char *cmd) { char path[256]; // test /bin/cmd path[0] = 0; str_append(path, "/bin/", sizeof(path)); str_append(path, cmd, sizeof(path)); if (sys_exists(path)) return true; // test /bin/cmd.elf path[0] = 0; str_append(path, "/bin/", sizeof(path)); str_append(path, cmd, sizeof(path)); str_append(path, ".elf", sizeof(path)); if (sys_exists(path)) return true; return false; } static bool command_starts_with(const char *prefix) { if (!prefix || !prefix[0]) return false; FAT32_FileInfo entries[128]; int count = sys_list("/bin", entries, 128); if (count <= 0) return false; for (int i = 0; i < count; i++) { if (entries[i].is_directory) { continue; } char name[256]; str_copy(name, entries[i].name, sizeof(name)); int len = (int)strlen(name); if (len > 4 && strcmp(name + len - 4, ".elf") == 0) { name[len - 4] = 0; } int j = 0; while (prefix[j] && name[j] && prefix[j] == name[j]) { j++; } if (prefix[j] == 0) { return true; } } return false; } static void update_input_color(TerminalSession *s) { if (s->input_len == 0) { s->input_color = 0xFFFFFFFF; return; } char cmd[64]; int i = 0; while (i < s->input_len && s->current_input[i] != ' ' && i < 63) { cmd[i] = s->current_input[i]; i++; } cmd[i] = 0; if (command_exists(cmd)) { s->input_color = 0xFF55FF55; // green } else if (command_starts_with(cmd)) { s->input_color = 0xFFFFFF55; // yellow } else { s->input_color = 0xFFFF5555; // red } } static void handle_key(gui_event_t *ev) { TerminalSession *s = &g_tabs[g_active_tab]; char c = (char)ev->arg1; bool ctrl = ev->arg3 != 0; // create new tab with ctrl + t if (ctrl && c == 't') { int idx = create_tab(); if (idx >= 0) g_active_tab = idx; return; } // switch to tab right, with ctrl + arrow right if (ctrl && c == KEY_RIGHT) { if (g_tab_count > 0) g_active_tab = (g_active_tab + 1) % g_tab_count; return; } // switch to tab left, with ctrl + arrow left if (ctrl && c == KEY_LEFT) { if (g_tab_count > 0) g_active_tab = (g_active_tab + g_tab_count - 1) % g_tab_count; return; } // kill the processus with ctrl + c if (ctrl && (c == 'c' || c == 'C')) { int fg = sys_tty_get_fg(s->tty_id); if (fg > 0) { sys_tty_kill_fg(s->tty_id); sys_tty_set_fg(s->tty_id, 0); return; } char ch = 3; sys_tty_write_in(s->tty_id, &ch, 1); return; } if (c == KEY_ENTER) { s->input_color = 0xFFFFFFFF; s->input_len = 0; s->current_input[0] = 0; } sys_tty_write_in(s->tty_id, &c, 1); } int main(void) { g_win = ui_window_create("Terminal", 60, 60, g_win_w, g_win_h); ui_window_set_resizable(g_win, true); int fh = (int)ui_get_font_height(); if (fh > 0) g_line_h = fh; terminal_resize(g_win_w, g_win_h); int idx = create_tab(); if (idx < 0) return 1; g_active_tab = idx; gui_event_t ev; char out_buf[TTY_READ_CHUNK]; while (1) { bool dirty = false; for (int i = 0; i < g_tab_count; i++) { TerminalSession *s = &g_tabs[i]; int read = 0; while ((read = sys_tty_read_out(s->tty_id, out_buf, sizeof(out_buf))) > 0) { session_process_output(s, out_buf, read); if (i == g_active_tab) dirty = true; } } while (ui_get_event(g_win, &ev)) { if (ev.type == GUI_EVENT_CLOSE) { for (int i = 0; i < g_tab_count; i++) { if (g_tabs[i].bsh_pid > 0) sys_kill(g_tabs[i].bsh_pid); } sys_exit(0); } else if (ev.type == GUI_EVENT_KEY) { handle_key(&ev); dirty = true; } else if (ev.type == GUI_EVENT_CLICK) { if (ev.arg2 < TAB_BAR_H) { int tab_w = get_tab_width(); int tab = ev.arg1 / tab_w; if (tab >= 0 && tab < g_tab_count) { int close_size = TAB_CLOSE_W; int close_x = tab * tab_w + tab_w - TAB_CLOSE_PAD - close_size; int close_y = (TAB_BAR_H - close_size) / 2; if (ev.arg1 >= close_x && ev.arg1 < close_x + close_size && ev.arg2 >= close_y && ev.arg2 < close_y + close_size) { close_tab(tab); } else { g_active_tab = tab; } dirty = true; } } } else if (ev.type == GUI_EVENT_MOUSE_WHEEL) { int lines = ev.arg1 * 3; if (lines != 0) { session_adjust_scroll(&g_tabs[g_active_tab], lines); dirty = true; } } else if (ev.type == GUI_EVENT_RESIZE) { terminal_resize(ev.arg1, ev.arg2); dirty = true; } else if (ev.type == GUI_EVENT_PAINT) { dirty = true; } } if (dirty) { draw_tabs(); draw_session(&g_tabs[g_active_tab]); } else { sys_yield(); } } return 0; }