227 lines
6.1 KiB
Nim
227 lines
6.1 KiB
Nim
# Nexus Membrane: Virtual Terminal Emulator
|
|
import term_font
|
|
import ion_client
|
|
|
|
const TERM_COLS* = 100
|
|
const TERM_ROWS* = 37
|
|
|
|
# Sovereign Palette (Phased Aesthetics)
|
|
const
|
|
COLOR_SOVEREIGN_BLUE = 0xFF401010'u32
|
|
COLOR_PHOSPHOR_AMBER = 0xFF00B0FF'u32
|
|
COLOR_SCANLINE_DIM = 0xFF300808'u32
|
|
|
|
type
|
|
Cell = object
|
|
ch: char
|
|
fg: uint32
|
|
bg: uint32
|
|
dirty: bool
|
|
|
|
var grid: array[TERM_ROWS, array[TERM_COLS, Cell]]
|
|
var cursor_x, cursor_y: int
|
|
var color_fg: uint32 = COLOR_PHOSPHOR_AMBER
|
|
var color_bg: uint32 = COLOR_SOVEREIGN_BLUE
|
|
var term_dirty*: bool = true
|
|
|
|
var fb_ptr: ptr UncheckedArray[uint32]
|
|
var fb_w, fb_h, fb_stride: int
|
|
|
|
# ANSI State Machine
|
|
type AnsiState = enum
|
|
Normal, Escape, CSI, Param
|
|
|
|
var cur_state: AnsiState = Normal
|
|
var ansi_params: array[8, int]
|
|
var cur_param_idx: int
|
|
|
|
const PALETTE: array[16, uint32] = [
|
|
0xFF000000'u32, # 0: Black
|
|
0xFF0000AA'u32, # 1: Red
|
|
0xFF00AA00'u32, # 2: Green
|
|
0xFF00AAAA'u32, # 3: Brown/Yellow
|
|
0xFFAA0000'u32, # 4: Blue
|
|
0xFFAA00AA'u32, # 5: Magenta
|
|
0xFFAAAA00'u32, # 6: Cyan
|
|
0xFFAAAAAA'u32, # 7: Gray
|
|
0xFF555555'u32, # 8: Bright Black
|
|
0xFF5555FF'u32, # 9: Bright Red
|
|
0xFF55FF55'u32, # 10: Bright Green
|
|
0xFF55FFFF'u32, # 11: Bright Yellow
|
|
0xFFFF5555'u32, # 12: Bright Blue
|
|
0xFFFF55FF'u32, # 13: Bright Magenta
|
|
0xFFFFFF55'u32, # 14: Bright Cyan
|
|
0xFFFFFFFF'u32 # 15: White
|
|
]
|
|
|
|
proc handle_sgr() =
|
|
## Handle Select Graphic Rendition (m)
|
|
if cur_param_idx == 0: # reset
|
|
color_fg = COLOR_PHOSPHOR_AMBER
|
|
color_bg = COLOR_SOVEREIGN_BLUE
|
|
return
|
|
|
|
for i in 0..<cur_param_idx:
|
|
let p = ansi_params[i]
|
|
if p == 0:
|
|
color_fg = COLOR_PHOSPHOR_AMBER
|
|
color_bg = COLOR_SOVEREIGN_BLUE
|
|
elif p >= 30 and p <= 37:
|
|
color_fg = PALETTE[p - 30]
|
|
elif p >= 40 and p <= 47:
|
|
color_bg = PALETTE[p - 40]
|
|
elif p >= 90 and p <= 97:
|
|
color_fg = PALETTE[p - 90 + 8]
|
|
elif p >= 100 and p <= 107:
|
|
color_bg = PALETTE[p - 100 + 8]
|
|
|
|
|
|
proc term_clear*() =
|
|
for row in 0..<TERM_ROWS:
|
|
for col in 0..<TERM_COLS:
|
|
grid[row][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
|
|
cursor_x = 0
|
|
cursor_y = 0
|
|
cur_state = Normal
|
|
term_dirty = true
|
|
|
|
proc term_scroll() =
|
|
for row in 0..<(TERM_ROWS-1):
|
|
grid[row] = grid[row + 1]
|
|
for col in 0..<TERM_COLS: grid[row][col].dirty = true
|
|
for col in 0..<TERM_COLS:
|
|
grid[TERM_ROWS-1][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
|
|
term_dirty = true
|
|
|
|
proc term_putc*(ch: char) =
|
|
case cur_state
|
|
of Normal:
|
|
if ch == '\x1b':
|
|
cur_state = Escape
|
|
return
|
|
|
|
if ch == '\n':
|
|
cursor_x = 0
|
|
cursor_y += 1
|
|
elif ch == '\r':
|
|
cursor_x = 0
|
|
elif ch >= ' ':
|
|
if cursor_x >= TERM_COLS:
|
|
cursor_x = 0
|
|
cursor_y += 1
|
|
if cursor_y >= TERM_ROWS:
|
|
term_scroll()
|
|
cursor_y = TERM_ROWS - 1
|
|
grid[cursor_y][cursor_x] = Cell(ch: ch, fg: color_fg, bg: color_bg, dirty: true)
|
|
cursor_x += 1
|
|
term_dirty = true
|
|
|
|
of Escape:
|
|
if ch == '[':
|
|
cur_state = CSI
|
|
cur_param_idx = 0
|
|
for i in 0..<ansi_params.len: ansi_params[i] = 0
|
|
else:
|
|
cur_state = Normal
|
|
|
|
of CSI:
|
|
if ch >= '0' and ch <= '9':
|
|
ansi_params[cur_param_idx] = (ch.int - '0'.int)
|
|
cur_state = Param
|
|
elif ch == ';':
|
|
if cur_param_idx < ansi_params.len - 1: cur_param_idx += 1
|
|
elif ch == 'm':
|
|
if cur_state == Param or cur_param_idx > 0 or ch == 'm': # Handle single m or param m
|
|
if cur_state == Param: cur_param_idx += 1
|
|
handle_sgr()
|
|
cur_state = Normal
|
|
elif ch == 'H' or ch == 'f': # Cursor Home
|
|
cursor_x = 0; cursor_y = 0
|
|
cur_state = Normal
|
|
elif ch == 'J': # Clear Screen
|
|
term_clear()
|
|
cur_state = Normal
|
|
else:
|
|
cur_state = Normal
|
|
|
|
of Param:
|
|
if ch >= '0' and ch <= '9':
|
|
ansi_params[cur_param_idx] = ansi_params[cur_param_idx] * 10 + (ch.int - '0'.int)
|
|
elif ch == ';':
|
|
if cur_param_idx < ansi_params.len - 1: cur_param_idx += 1
|
|
elif ch == 'm':
|
|
cur_param_idx += 1
|
|
handle_sgr()
|
|
cur_state = Normal
|
|
elif ch == 'H' or ch == 'f':
|
|
# pos logic here if we wanted it
|
|
cursor_x = 0; cursor_y = 0
|
|
cur_state = Normal
|
|
else:
|
|
cur_state = Normal
|
|
|
|
# --- THE GHOST RENDERER ---
|
|
proc draw_char(cx, cy: int, cell: Cell) =
|
|
if fb_ptr == nil: return
|
|
|
|
let glyph_idx = uint8(cell.ch)
|
|
let fg = cell.fg
|
|
let bg = cell.bg
|
|
let px_start = cx * 8
|
|
let py_start = cy * 16
|
|
|
|
for y in 0..15:
|
|
let is_scanline = (y mod 4) == 3
|
|
let row_bits = FONT_BITMAP[glyph_idx][y]
|
|
let screen_y = py_start + y
|
|
if screen_y >= fb_h: break
|
|
|
|
let row_offset = screen_y * (fb_stride div 4)
|
|
for x in 0..7:
|
|
let screen_x = px_start + x
|
|
if screen_x >= fb_w: break
|
|
let pixel_idx = row_offset + screen_x
|
|
let is_pixel = ((int(row_bits) shr (7 - x)) and 1) != 0
|
|
|
|
if is_pixel:
|
|
fb_ptr[pixel_idx] = if is_scanline: (fg and 0xFFE0E0E0'u32) else: fg
|
|
else:
|
|
fb_ptr[pixel_idx] = if is_scanline: COLOR_SCANLINE_DIM else: bg
|
|
|
|
proc term_render*() =
|
|
if fb_ptr == nil or not term_dirty: return
|
|
for row in 0..<TERM_ROWS:
|
|
for col in 0..<TERM_COLS:
|
|
if grid[row][col].dirty:
|
|
draw_char(col, row, grid[row][col])
|
|
grid[row][col].dirty = false
|
|
term_dirty = false
|
|
|
|
proc term_init*() =
|
|
let sys = cast[ptr SysTable](SYS_TABLE_ADDR)
|
|
fb_ptr = cast[ptr UncheckedArray[uint32]](sys.fb_addr)
|
|
fb_w = int(sys.fb_width)
|
|
fb_h = int(sys.fb_height)
|
|
fb_stride = int(sys.fb_stride)
|
|
cursor_x = 0
|
|
cursor_y = 0
|
|
cur_state = Normal
|
|
term_dirty = true
|
|
|
|
when defined(TERM_PROFILE_minimal):
|
|
proc console_write(p: pointer, len: uint) {.importc, cdecl.}
|
|
var msg = "[TERM] Profile: MINIMAL (IBM VGA/Hack)\n"
|
|
console_write(addr msg[0], uint(msg.len))
|
|
elif defined(TERM_PROFILE_standard):
|
|
proc console_write(p: pointer, len: uint) {.importc, cdecl.}
|
|
var msg = "[TERM] Profile: STANDARD (Spleen/Nerd)\n"
|
|
console_write(addr msg[0], uint(msg.len))
|
|
|
|
for row in 0..<TERM_ROWS:
|
|
for col in 0..<TERM_COLS:
|
|
grid[row][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
|
|
|
|
# Test Colors
|
|
let test_msg = "\x1b[31mN\x1b[32mE\x1b[33mX\x1b[34mU\x1b[35mS\x1b[0m\n"
|
|
for ch in test_msg: term_putc(ch)
|