From dc1e5da8e0a3ed71de8581ea47d9b366a9c4d94e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 22:42:03 +0000 Subject: [PATCH] Refactor OS codebase and add plain text editor application - Fix Makefile: remove hardcoded macOS homebrew paths, use generic tool names; remove macOS-only -display cocoa flag - Extract magic numbers into named constants (CHAR_WIDTH, TITLE_BAR_HEIGHT, WINDOW_PADDING, MENU_ITEM_SPACING) - Add 28 symbol font glyphs (punctuation, brackets, operators) to font.h using a struct-based lookup table - Replace manual pixel-drawn symbols (>, -, .) with proper bitmap font data - Add plain text editor app with full keyboard input via PS/2 scancode table, cursor navigation (arrow keys), character insertion, backspace with line joining, and enter for newlines - Editor supports 16 rows x 43 columns of editable text, persists content when returning to menu https://claude.ai/code/session_014qqjnv6Pt778mmNLjvibe3 --- Makefile | 8 +- font.h | 42 +++++++- kernel.c | 307 +++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 318 insertions(+), 39 deletions(-) diff --git a/Makefile b/Makefile index f19ac0f..591c0ad 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ # BaseOS Makefile AS = nasm -CC = /opt/homebrew/opt/x86_64-elf-gcc/bin/x86_64-elf-gcc -LD = /opt/homebrew/opt/x86_64-elf-binutils/bin/x86_64-elf-ld -OBJCOPY = /opt/homebrew/opt/x86_64-elf-binutils/bin/x86_64-elf-objcopy +CC = x86_64-elf-gcc +LD = x86_64-elf-ld +OBJCOPY = x86_64-elf-objcopy CFLAGS = -std=gnu99 -ffreestanding -O0 -g -Wall -Wextra -m32 -nostdlib -I. ASFLAGS = -f elf @@ -33,7 +33,7 @@ baseos.img: boot.bin kernel.bin rm baseos.img.tmp run: baseos.img - qemu-system-i386 -drive file=baseos.img,format=raw,index=0,if=floppy -serial file:serial.out -no-reboot -display cocoa -d int,cpu_reset,guest_errors -D qemu_log.txt + qemu-system-i386 -drive file=baseos.img,format=raw,index=0,if=floppy -serial file:serial.out -no-reboot -d int,cpu_reset,guest_errors -D qemu_log.txt clean: rm -f boot.bin kernel.o kernel_entry.o kernel.elf kernel.bin baseos.img baseos.img.tmp *.o serial.out qemu_log.txt diff --git a/font.h b/font.h index 5b56a9c..56ee4e1 100644 --- a/font.h +++ b/font.h @@ -3,7 +3,8 @@ #include -// 5x7 Bitmap Font +// 5x7 Bitmap Font (column-encoded, LSB = top row) + // A-Z const uint8_t font_alpha[26][5] = { {0x7E, 0x09, 0x09, 0x09, 0x7E}, // A @@ -78,4 +79,43 @@ const uint8_t font_lower[26][5] = { {0x44, 0x64, 0x54, 0x4C, 0x44} // z }; +// Symbols - indexed by struct lookup +struct FontSymbol { + char c; + uint8_t glyph[5]; +}; + +const struct FontSymbol font_symbols[] = { + {'!', {0x00, 0x00, 0x5F, 0x00, 0x00}}, + {'"', {0x00, 0x07, 0x00, 0x07, 0x00}}, + {'#', {0x14, 0x7F, 0x14, 0x7F, 0x14}}, + {'\'', {0x00, 0x05, 0x03, 0x00, 0x00}}, + {'(', {0x00, 0x1C, 0x22, 0x41, 0x00}}, + {')', {0x00, 0x41, 0x22, 0x1C, 0x00}}, + {'*', {0x14, 0x08, 0x3E, 0x08, 0x14}}, + {'+', {0x08, 0x08, 0x3E, 0x08, 0x08}}, + {',', {0x00, 0x50, 0x30, 0x00, 0x00}}, + {'-', {0x08, 0x08, 0x08, 0x08, 0x08}}, + {'.', {0x00, 0x60, 0x60, 0x00, 0x00}}, + {'/', {0x20, 0x10, 0x08, 0x04, 0x02}}, + {':', {0x00, 0x36, 0x36, 0x00, 0x00}}, + {';', {0x00, 0x56, 0x36, 0x00, 0x00}}, + {'<', {0x08, 0x14, 0x22, 0x41, 0x00}}, + {'=', {0x14, 0x14, 0x14, 0x14, 0x14}}, + {'>', {0x00, 0x41, 0x22, 0x14, 0x08}}, + {'?', {0x02, 0x01, 0x51, 0x09, 0x06}}, + {'@', {0x32, 0x49, 0x79, 0x41, 0x3E}}, + {'[', {0x00, 0x7F, 0x41, 0x41, 0x00}}, + {'\\', {0x02, 0x04, 0x08, 0x10, 0x20}}, + {']', {0x00, 0x41, 0x41, 0x7F, 0x00}}, + {'^', {0x04, 0x02, 0x01, 0x02, 0x04}}, + {'_', {0x40, 0x40, 0x40, 0x40, 0x40}}, + {'{', {0x00, 0x08, 0x36, 0x41, 0x00}}, + {'|', {0x00, 0x00, 0x7F, 0x00, 0x00}}, + {'}', {0x00, 0x41, 0x36, 0x08, 0x00}}, + {'~', {0x04, 0x02, 0x04, 0x08, 0x04}}, +}; + +#define FONT_SYMBOL_COUNT (sizeof(font_symbols) / sizeof(font_symbols[0])) + #endif diff --git a/kernel.c b/kernel.c index 605f695..41f0980 100644 --- a/kernel.c +++ b/kernel.c @@ -9,6 +9,13 @@ #define VGA_HEIGHT 200 #define VGA_ADDR ((volatile uint8_t *)0xA0000) +// Layout constants +#define CHAR_WIDTH 6 +#define CHAR_HEIGHT 8 +#define TITLE_BAR_HEIGHT 12 +#define WINDOW_PADDING 10 +#define MENU_ITEM_SPACING 10 + // Colors #define COLOR_BLACK 0 #define COLOR_BLUE 1 @@ -36,9 +43,12 @@ // Keys #define KEY_ESC 0x01 +#define KEY_BACKSPACE 0x0E #define KEY_ENTER 0x1C #define KEY_UP 0x48 #define KEY_DOWN 0x50 +#define KEY_LEFT 0x4B +#define KEY_RIGHT 0x4D static inline void outb(uint16_t port, uint8_t val) { asm volatile("outb %0, %1" : : "a"(val), "Nd"(port)); @@ -141,32 +151,32 @@ void draw_char(char c, int x, int y, uint8_t color) { return; } - // Symbols - if (c == '>') { - for (int i = 0; i < 5; i++) { - for (int j = 0; j <= i; j++) { - put_pixel(x + j, y + i, color); - put_pixel(x + j, y + 8 - i, color); + // Symbol lookup + for (int s = 0; s < (int)FONT_SYMBOL_COUNT; s++) { + if (font_symbols[s].c == c) { + for (int i = 0; i < 5; i++) { + uint8_t col = font_symbols[s].glyph[i]; + for (int j = 0; j < 8; j++) { + if (col & (1 << j)) { + put_pixel(x + i, y + j, color); + } + } } + return; } - } else if (c == '-') { - for (int i = 0; i < 4; i++) - put_pixel(x + i, y + 3, color); - } else if (c == '.') { - put_pixel(x + 2, y + 6, color); } } void draw_string(const char *str, int x, int y, uint8_t color) { while (*str) { draw_char(*str++, x, y, color); - x += 6; + x += CHAR_WIDTH; } } void draw_string_centered(const char *str, int y, uint8_t color) { int len = kstrlen(str); - int x = (VGA_WIDTH - (len * 6)) / 2; + int x = (VGA_WIDTH - (len * CHAR_WIDTH)) / 2; draw_string(str, x, y, color); } @@ -188,10 +198,9 @@ void gui_draw_window(int x, int y, int w, int h, const char *title) { draw_rect(x + w - 1, y, 1, h, border); if (title) { - int title_h = 12; - draw_line(x, y + title_h, x + w, y + title_h, border); + draw_line(x, y + TITLE_BAR_HEIGHT, x + w, y + TITLE_BAR_HEIGHT, border); - int text_len = kstrlen(title) * 6; + int text_len = kstrlen(title) * CHAR_WIDTH; int text_x = x + (w - text_len) / 2; draw_string(title, text_x, y + 3, border); } @@ -203,16 +212,39 @@ uint8_t keyboard_read_scancode() { return inb(KEYBOARD_DATA_PORT); } -// App +// PS/2 Scancode Set 1 to ASCII lookup (unshifted) +static const char scancode_to_ascii[128] = { + 0, 0, '1', '2', '3', '4', '5', '6', // 0x00-0x07 + '7', '8', '9', '0', '-', '=', 0, 0, // 0x08-0x0F + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10-0x17 + 'o', 'p', '[', ']', 0, 0, 'a', 's', // 0x18-0x1F + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20-0x27 + '\'', 0, 0, '\\', 'z', 'x', 'c', 'v', // 0x28-0x2F + 'b', 'n', 'm', ',', '.', '/', 0, '*', // 0x30-0x37 + 0, ' ', 0, 0, 0, 0, 0, 0, // 0x38-0x3F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x40-0x47 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x48-0x4F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x50-0x57 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x58-0x5F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x60-0x67 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x68-0x6F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x70-0x77 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x78-0x7F +}; + +// App states enum AppState { STATE_MENU = 0, STATE_HELLO, STATE_HELP, STATE_ABOUT, - STATE_SETTINGS + STATE_SETTINGS, + STATE_EDITOR }; -const char *MENU_ITEMS[] = {"HELP", "HELLO", "ABOUT", "SETTINGS", "SHUTDOWN"}; -const int MENU_COUNT = 5; + +const char *MENU_ITEMS[] = {"HELP", "HELLO", "ABOUT", "SETTINGS", "EDITOR", + "SHUTDOWN"}; +const int MENU_COUNT = 6; struct Color { const char *name; @@ -230,21 +262,21 @@ int current_bg_color = 0; void draw_menu_items(int x, int y, int selected) { for (int i = 0; i < MENU_COUNT; i++) { - int item_y = y + (i * 10); + int item_y = y + (i * MENU_ITEM_SPACING); if (i == selected) draw_string(">", x, item_y, COLOR_BLACK); - draw_string(MENU_ITEMS[i], x + 10, item_y, COLOR_BLACK); + draw_string(MENU_ITEMS[i], x + WINDOW_PADDING, item_y, COLOR_BLACK); } } void draw_settings_menu(int x, int y, int selected) { for (int i = 0; i < COLOR_COUNT; i++) { - int item_y = y + (i * 10); + int item_y = y + (i * MENU_ITEM_SPACING); if (i == selected) draw_string(">", x, item_y, COLOR_BLACK); - // Preview + // Color preview box draw_rect(x + 10, item_y, 8, 8, COLORS[i].value); draw_rect(x + 10, item_y, 8, 1, COLOR_BLACK); draw_rect(x + 10, item_y + 7, 8, 1, COLOR_BLACK); @@ -259,6 +291,146 @@ void draw_settings_menu(int x, int y, int selected) { } } +// Editor +#define EDITOR_ROWS 16 +#define EDITOR_COLS 43 +#define EDITOR_WIN_X 15 +#define EDITOR_WIN_Y 10 +#define EDITOR_WIN_W 290 +#define EDITOR_WIN_H 180 +#define EDITOR_TEXT_X (EDITOR_WIN_X + 6) +#define EDITOR_TEXT_Y (EDITOR_WIN_Y + TITLE_BAR_HEIGHT + 4) + +static char editor_buf[EDITOR_ROWS][EDITOR_COLS + 1]; +static int editor_cursor_row = 0; +static int editor_cursor_col = 0; +static int editor_initialized = 0; + +void editor_init() { + for (int r = 0; r < EDITOR_ROWS; r++) { + for (int c = 0; c <= EDITOR_COLS; c++) { + editor_buf[r][c] = 0; + } + } + editor_cursor_row = 0; + editor_cursor_col = 0; + editor_initialized = 1; +} + +int editor_line_len(int row) { + int len = 0; + while (len < EDITOR_COLS && editor_buf[row][len]) + len++; + return len; +} + +void draw_editor() { + gui_draw_window(EDITOR_WIN_X, EDITOR_WIN_Y, EDITOR_WIN_W, EDITOR_WIN_H, + "Editor"); + + // Draw text content + for (int r = 0; r < EDITOR_ROWS; r++) { + int y = EDITOR_TEXT_Y + r * (CHAR_HEIGHT + 1); + if (y + CHAR_HEIGHT > EDITOR_WIN_Y + EDITOR_WIN_H - 2) + break; + + for (int c = 0; c < EDITOR_COLS; c++) { + char ch = editor_buf[r][c]; + if (ch == 0) + break; + int x = EDITOR_TEXT_X + c * CHAR_WIDTH; + if (x + CHAR_WIDTH > EDITOR_WIN_X + EDITOR_WIN_W - 2) + break; + draw_char(ch, x, y, COLOR_BLACK); + } + } + + // Draw cursor (solid block) + int cur_x = EDITOR_TEXT_X + editor_cursor_col * CHAR_WIDTH; + int cur_y = EDITOR_TEXT_Y + editor_cursor_row * (CHAR_HEIGHT + 1); + draw_rect(cur_x, cur_y + CHAR_HEIGHT - 1, CHAR_WIDTH - 1, 1, COLOR_BLACK); + + // Status line + draw_string("ESC:MENU", EDITOR_WIN_X + 6, + EDITOR_WIN_Y + EDITOR_WIN_H - 10, COLOR_DARK_GREY); +} + +void editor_insert_char(char c) { + int len = editor_line_len(editor_cursor_row); + if (len >= EDITOR_COLS) + return; + + // Shift characters right from cursor + for (int i = len; i > editor_cursor_col; i--) { + editor_buf[editor_cursor_row][i] = editor_buf[editor_cursor_row][i - 1]; + } + editor_buf[editor_cursor_row][editor_cursor_col] = c; + editor_cursor_col++; +} + +void editor_backspace() { + if (editor_cursor_col > 0) { + // Delete char before cursor + int len = editor_line_len(editor_cursor_row); + for (int i = editor_cursor_col - 1; i < len - 1; i++) { + editor_buf[editor_cursor_row][i] = + editor_buf[editor_cursor_row][i + 1]; + } + editor_buf[editor_cursor_row][len - 1] = 0; + editor_cursor_col--; + } else if (editor_cursor_row > 0) { + // Join with previous line + int prev_len = editor_line_len(editor_cursor_row - 1); + int cur_len = editor_line_len(editor_cursor_row); + if (prev_len + cur_len <= EDITOR_COLS) { + // Append current line to previous + for (int i = 0; i < cur_len; i++) { + editor_buf[editor_cursor_row - 1][prev_len + i] = + editor_buf[editor_cursor_row][i]; + } + editor_buf[editor_cursor_row - 1][prev_len + cur_len] = 0; + // Shift all lines up + for (int r = editor_cursor_row; r < EDITOR_ROWS - 1; r++) { + for (int c = 0; c <= EDITOR_COLS; c++) { + editor_buf[r][c] = editor_buf[r + 1][c]; + } + } + // Clear last line + for (int c = 0; c <= EDITOR_COLS; c++) { + editor_buf[EDITOR_ROWS - 1][c] = 0; + } + editor_cursor_row--; + editor_cursor_col = prev_len; + } + } +} + +void editor_newline() { + if (editor_cursor_row >= EDITOR_ROWS - 1) + return; + + // Shift lines down to make room + for (int r = EDITOR_ROWS - 1; r > editor_cursor_row + 1; r--) { + for (int c = 0; c <= EDITOR_COLS; c++) { + editor_buf[r][c] = editor_buf[r - 1][c]; + } + } + + // Split current line at cursor + int len = editor_line_len(editor_cursor_row); + for (int c = 0; c <= EDITOR_COLS; c++) { + editor_buf[editor_cursor_row + 1][c] = 0; + } + for (int c = editor_cursor_col; c < len; c++) { + editor_buf[editor_cursor_row + 1][c - editor_cursor_col] = + editor_buf[editor_cursor_row][c]; + editor_buf[editor_cursor_row][c] = 0; + } + + editor_cursor_row++; + editor_cursor_col = 0; +} + void kmain() { serial_init(); kprint_debug("Kernel started\n"); @@ -282,11 +454,12 @@ void kmain() { } if (state == STATE_MENU) { - int w = 100, h = 70; + int w = 100, h = 80; int x = (VGA_WIDTH - w) / 2; int y = (VGA_HEIGHT - h) / 2; gui_draw_window(x, y, w, h, "Start"); - draw_menu_items(x + 10, y + 18, selected); + draw_menu_items(x + WINDOW_PADDING, y + TITLE_BAR_HEIGHT + 6, + selected); } else if (state == STATE_HELLO) { int w = 120, h = 50; int x = (VGA_WIDTH - w) / 2; @@ -299,9 +472,12 @@ void kmain() { int x = (VGA_WIDTH - w) / 2; int y = (VGA_HEIGHT - h) / 2; gui_draw_window(x, y, w, h, "HELP"); - draw_string("USE ARROWS TO MOVE", x + 10, y + 20, COLOR_BLACK); - draw_string("ENTER TO SELECT", x + 10, y + 30, COLOR_BLACK); - draw_string("ESC TO RETURN", x + 10, y + 40, COLOR_BLACK); + draw_string("USE ARROWS TO MOVE", x + WINDOW_PADDING, y + 20, + COLOR_BLACK); + draw_string("ENTER TO SELECT", x + WINDOW_PADDING, y + 30, + COLOR_BLACK); + draw_string("ESC TO RETURN", x + WINDOW_PADDING, y + 40, + COLOR_BLACK); } else if (state == STATE_ABOUT) { int w = 120, h = 60; int x = (VGA_WIDTH - w) / 2; @@ -309,13 +485,16 @@ void kmain() { gui_draw_window(x, y, w, h, "ABOUT"); draw_string_centered("BASEOS KERNEL", y + 20, COLOR_BLACK); draw_string_centered("VER 0.1.0", y + 30, COLOR_DARK_GREY); - draw_string_centered("C 2025 CCG", y + 40, COLOR_BLACK); + draw_string_centered("2025 CCG", y + 40, COLOR_BLACK); } else if (state == STATE_SETTINGS) { int w = 140, h = 130; int x = (VGA_WIDTH - w) / 2; int y = (VGA_HEIGHT - h) / 2; gui_draw_window(x, y, w, h, "Settings"); - draw_settings_menu(x + 10, y + 18, settings_selected); + draw_settings_menu(x + WINDOW_PADDING, + y + TITLE_BAR_HEIGHT + 6, settings_selected); + } else if (state == STATE_EDITOR) { + draw_editor(); } dirty = 0; } @@ -332,7 +511,8 @@ void kmain() { selected = (selected + 1) % MENU_COUNT; dirty = 1; } else if (sc == KEY_ENTER) { - if (selected == 4) { + if (selected == 5) { + // Shutdown outw(QEMU_SHUTDOWN_PORT, 0x2000); outw(VBOX_SHUTDOWN_PORT, 0x2000); asm volatile("hlt"); @@ -345,13 +525,19 @@ void kmain() { state = STATE_ABOUT; else if (selected == 3) state = STATE_SETTINGS; + else if (selected == 4) { + if (!editor_initialized) + editor_init(); + state = STATE_EDITOR; + } dirty = 1; full_redraw = 1; } } } else if (state == STATE_SETTINGS) { if (sc == KEY_UP) { - settings_selected = (settings_selected - 1 + COLOR_COUNT) % COLOR_COUNT; + settings_selected = + (settings_selected - 1 + COLOR_COUNT) % COLOR_COUNT; dirty = 1; } else if (sc == KEY_DOWN) { settings_selected = (settings_selected + 1) % COLOR_COUNT; @@ -365,6 +551,59 @@ void kmain() { dirty = 1; full_redraw = 1; } + } else if (state == STATE_EDITOR) { + if (sc == KEY_ESC) { + state = STATE_MENU; + dirty = 1; + full_redraw = 1; + } else if (sc == KEY_BACKSPACE) { + editor_backspace(); + dirty = 1; + full_redraw = 1; + } else if (sc == KEY_ENTER) { + editor_newline(); + dirty = 1; + full_redraw = 1; + } else if (sc == KEY_UP) { + if (editor_cursor_row > 0) { + editor_cursor_row--; + int len = editor_line_len(editor_cursor_row); + if (editor_cursor_col > len) + editor_cursor_col = len; + dirty = 1; + full_redraw = 1; + } + } else if (sc == KEY_DOWN) { + if (editor_cursor_row < EDITOR_ROWS - 1) { + editor_cursor_row++; + int len = editor_line_len(editor_cursor_row); + if (editor_cursor_col > len) + editor_cursor_col = len; + dirty = 1; + full_redraw = 1; + } + } else if (sc == KEY_LEFT) { + if (editor_cursor_col > 0) { + editor_cursor_col--; + dirty = 1; + full_redraw = 1; + } + } else if (sc == KEY_RIGHT) { + int len = editor_line_len(editor_cursor_row); + if (editor_cursor_col < len) { + editor_cursor_col++; + dirty = 1; + full_redraw = 1; + } + } else { + // Printable character + char c = (sc < 128) ? scancode_to_ascii[sc] : 0; + if (c) { + editor_insert_char(c); + dirty = 1; + full_redraw = 1; + } + } } else { if (sc == KEY_ESC) { state = STATE_MENU; @@ -373,4 +612,4 @@ void kmain() { } } } -} \ No newline at end of file +}