From fa06681a8418b7e31af96e010bb424e178402f4d Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 31 Jul 2026 17:19:09 +0200 Subject: tty + vt terminal split, rsdosedit editor (1:1 UNIX console) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kernel now has the classic UNIX console split, so full-screen ANSI apps can run in ring 0: - vt.rs: terminal emulator owning the text screen — cell grid (char + fg/bg), logical cursor (inverse-video), scrolling, and a small ANSI CSI parser (cursor position H/f, erase J/K, moves A-D, SGR 16-colour with correct ANSI→VGA mapping, cursor show/hide ?25). Blits only changed cells; scroll shifts cell grids + pixels and re-rasterises only the exposed bottom row. gop_println!/clear_screen now render through the vt; gop.rs is the raw rasteriser (colored glyph blits, pixel shift). - tty.rs: line discipline — canonical read_line (echo, line buffer, ^C cancel, ^D EOF) moved out of the shell, and raw read_byte that translates KeyEvents to the terminal byte protocol (arrows → ESC[A-D, Backspace → 0x7f, ^X → 0x18, Ctrl+arrows → ESC[1;5A-D, Home/End/PgUp/ PgDn/Delete). interrupt_pressed() stays for streaming commands. - ps2: generic Ctrl(char) + CtrlArrow events (was: only CtrlC/CtrlD). - rsdosedit: no_std port of the RNG rsedit editor as the first real app — undo, syntax highlighting (verified green/gray/yellow on the framebuffer), search, word nav, smart indent, ^S save / ^X exit / ^X again to discard. termios → tty raw, stdout → vt, std::fs → VFS (ramfs), SIGWINCH → fixed geometry, force-exit window → key counter. - shell: 'edit ' command; read_line/strbuf moved to rsnix. - CONTEXT.md: layout + milestones + known-issue perf notes. Verified in QEMU: full edit → type → arrow-move → insert → save → exit round trip with byte-exact file content; ANSI colors via screendump. --- rsnix/src/gop.rs | 152 ++++++++++++++++++++++--------------------------------- 1 file changed, 60 insertions(+), 92 deletions(-) (limited to 'rsnix/src/gop.rs') diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs index 9645bb0..81b8945 100644 --- a/rsnix/src/gop.rs +++ b/rsnix/src/gop.rs @@ -1,4 +1,8 @@ /// GOP framebuffer driver — renders text via a bitmap font. +/// +/// Text surface (cursor, scrolling, colours) lives in `crate::vt`; this +/// module is the low-level rasteriser: glyph blits and pixel shifts on the +/// linear framebuffer. use crate::font; use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; use core::fmt; @@ -13,11 +17,7 @@ pub struct Framebuffer { pitch: usize, // bytes per scan line bytes_per_pixel: usize, bg: u32, // packed BGRA or RGBA background colour - fg: u32, // packed foreground colour - cursor_x: usize, - cursor_y: usize, - rows: usize, - cols: usize, + bgr: bool, } static mut FB: Option = None; @@ -38,9 +38,8 @@ pub fn init(gop: &mut GraphicsOutputProtocol) { PIXEL_RGB => (0x00_FF_FF_FFu32, 0x00_00_00_00u32), _ => (0x00_FF_FF_FFu32, 0x00_00_00_00u32), }; - - let rows = height / 16; - let cols = width / 8; + let bgr = info.pixel_format == PIXEL_BGR; + let _ = fg; let fb = Framebuffer { base, @@ -49,11 +48,7 @@ pub fn init(gop: &mut GraphicsOutputProtocol) { pitch, bytes_per_pixel: bpp, bg, - fg, - cursor_x: 0, - cursor_y: 0, - rows, - cols, + bgr, }; fb.clear(); @@ -63,79 +58,45 @@ pub fn init(gop: &mut GraphicsOutputProtocol) { } } -/// Draw a character at the current cursor position and advance. -pub fn put_char(c: char) { - let fb = unsafe { - match FB.as_mut() { - Some(f) => f, - None => return, - } - }; +// --------------------------------------------------------------------------- +// Public text API — the text surface now lives in `crate::vt`; these wrappers +// keep callers (shell, macros) unchanged. +// --------------------------------------------------------------------------- - if c == '\x08' { - // Backspace - move cursor back and erase - if fb.cursor_x > 0 { - fb.cursor_x -= 1; - } else if fb.cursor_y > 0 { - fb.cursor_y -= 1; - fb.cursor_x = fb.cols - 1; - } else { - return; - } - // Draw glyph 32 (space) to erase — glyph 0 is NUL, not blank - fb.draw_glyph(32, fb.cursor_x, fb.cursor_y); - return; - } - if c == '\n' { - fb.cursor_x = 0; - fb.cursor_y += 1; - if fb.cursor_y >= fb.rows { - fb.scroll(); - } - return; - } - if c == '\r' { - fb.cursor_x = 0; - return; - } +/// Draw a character at the current terminal cursor position and advance. +pub fn put_char(c: char) { + let b = if (c as u32) < 0x100 { c as u8 } else { b'?' }; + crate::vt::put_char(b); +} - let idx = c as usize; - if idx >= 256 { - // Unmapped character: draw a space - fb.draw_glyph(0, fb.cursor_x, fb.cursor_y); - } else { - fb.draw_glyph(idx, fb.cursor_x, fb.cursor_y); - } +/// Write a string to the terminal. +pub fn write_str(s: &str) { + crate::vt::write_str(s); +} - fb.cursor_x += 1; - if fb.cursor_x >= fb.cols { - fb.cursor_x = 0; - fb.cursor_y += 1; - if fb.cursor_y >= fb.rows { - fb.scroll(); - } - } +/// Clear the terminal and home the cursor. +pub fn clear_screen() { + crate::vt::clear_all(); } -/// Write a string to the framebuffer. -pub fn write_str(s: &str) { - for c in s.chars() { - put_char(c); +/// (cols, rows) of the text surface, from the current framebuffer mode. +pub fn dims() -> (usize, usize) { + unsafe { + match FB.as_ref() { + Some(f) => (f.width / 8, f.height / 16), + None => (0, 0), + } } } -/// Clear the entire screen to background colour and reset cursor to (0, 0). -pub fn clear_screen() { - let fb = unsafe { - match FB.as_mut() { - Some(f) => f, - None => return, +/// Pack an (r, g, b) colour for the framebuffer's pixel format. +pub fn rgb(r: u8, g: u8, b: u8) -> u32 { + unsafe { + match FB.as_ref() { + Some(f) if f.bgr => ((r as u32) << 16) | ((g as u32) << 8) | b as u32, + _ => ((b as u32) << 16) | ((g as u32) << 8) | r as u32, } - }; - fb.clear(); - // Reset cursor back to top-left so the next prompt appears at (0, 0) - fb.cursor_x = 0; - fb.cursor_y = 0; + } } // --------------------------------------------------------------------------- @@ -155,7 +116,10 @@ impl Framebuffer { } } - fn scroll(&mut self) { + /// Shift the whole framebuffer up one row (16 pixels) and clear the + /// exposed bottom row. Called by `vt::scroll_up`; the vt then re-blits + /// only the new bottom row. + pub(crate) fn shift_up(&self) { // Move everything up one row (16 pixels) let row_bytes = self.pitch * 16; let total = self.pitch * self.height; @@ -167,40 +131,44 @@ impl Framebuffer { ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg); } } - self.cursor_y -= 1; } +} - fn draw_glyph(&self, glyph_idx: usize, col: usize, row: usize) { +/// Blit one glyph cell at (col, row) with explicit fg/bg colours. +pub(crate) fn blit_glyph(col: usize, row: usize, ch: u16, fg: (u8, u8, u8), bg: (u8, u8, u8)) { + if let Some(fb) = unsafe { FB.as_ref() } { + let glyph_idx = if ch < 256 { ch as usize } else { 32 }; let glyph = font::GLYPH_8X16; let data = match glyph.get(glyph_idx) { Some(d) => *d, None => return, }; + let fg = rgb(fg.0, fg.1, fg.2); + let bg = rgb(bg.0, bg.1, bg.2); let x = col * 8; let y = row * 16; for cy in 0..16 { let row_bits = data[cy]; for cx in 0..8 { - if row_bits & (0x80 >> cx) != 0 { - // Set foreground pixel - let offset = (y + cy) * self.pitch + (x + cx) * self.bytes_per_pixel; - unsafe { - ptr::write_volatile(self.base.add(offset) as *mut u32, self.fg); - } - } else { - // Set background pixel - let offset = (y + cy) * self.pitch + (x + cx) * self.bytes_per_pixel; - unsafe { - ptr::write_volatile(self.base.add(offset) as *mut u32, self.bg); - } + let offset = (y + cy) * fb.pitch + (x + cx) * fb.bytes_per_pixel; + unsafe { + let px = if row_bits & (0x80 >> cx) != 0 { fg } else { bg }; + ptr::write_volatile(fb.base.add(offset) as *mut u32, px); } } } } } +/// Shift the framebuffer up one row and clear the bottom row. +pub(crate) fn shift_up() { + if let Some(fb) = unsafe { FB.as_ref() } { + fb.shift_up(); + } +} + // --------------------------------------------------------------------------- // fmt::Write wrapper // --------------------------------------------------------------------------- -- cgit v1.2.3