FABRIC.md items 4.4v/4.4r/4.4ab: keyboard bridge, and simplify to a
full-screen vt100 terminal 4.4v -- keyboard-to-REPL bridge, real and tested: Refactored KEY-EVENT's per-arch translation logic (keyboard_words.c) into a shared C function, sk_key_event_poll(), so the REPL bridge reuses item 4.3.5f's already-converged Linux-keycode-namespace event stream instead of building separate amd64/aarch64/riscv64 tables. repl.c's sk_kbd_getc() decodes the standard US-QWERTY printable range plus Enter/Backspace/Shift against that stream; sk_readline() polls it as a second source alongside console_getc(). Verified via QEMU monitor sendkey injection, and by Captain Bob typing directly into the live QEMU window over real emulated PS/2 hardware mid-session (1 1 + . -> 2 ok, then a clean BYE shutdown). 4.4ab -- simplify to a full-screen terminal: Captain Bob's call, reverting the 640x480 CANVAS box + independent REPL strip (4.4o/4.4t/4.4x/4.4z) in favor of the simplest shape: the entire framebuffer is one vt100 terminal, g_vt.cols/rows = fb_width()/fb_height() divided by cell size, no origin offset, no box, no strip, no border drawing. The REPL prompt is just the terminal's last scrolling line. Scrollback, TTF rendering, and SGR color are all box-agnostic and keep working unmodified. 4.4r -- reframed as a text/graphics mode toggle: "Hide/show the scroll box" stopped meaning anything once the box was removed; the underlying need survives as a whole-screen mode switch. vt100_toggle_graphics() is a two-state machine (VISIBLE/HIDDEN) -- hidden mode stops the terminal from touching the framebuffer while its logical state keeps advancing, so direct framebuffer/TTF-TEXT drawing can use the whole screen; showing again wipes and reuses scrollback_redraw() to restore the terminal exactly. Reachable two ways, one transition function: physically via Alt+TAB (4.4y revised from Ctrl+TAB) and programmatically via the new ALT+TAB FORTH word. Verified: three-arch clean QEMU boot + logs; amd64 screendump confirms full-width text with no box/strip artifacts. Punch list §25 items 4.4v/4.4r/4.4ab complete; 4.4y revised. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b21aa50a14
commit
af20efaa15
@@ -317,10 +317,10 @@ void console_fb_scroll_fwd(uint32_t n)
|
||||
}
|
||||
}
|
||||
|
||||
void console_fb_strip_draw(const char *text)
|
||||
void console_fb_toggle_graphics(void)
|
||||
{
|
||||
if (fb_is_available()) {
|
||||
vt100_strip_draw(text);
|
||||
vt100_toggle_graphics();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+76
-176
@@ -81,47 +81,28 @@ static ttf_font_t g_ttf_font;
|
||||
static int g_ttf_ready = 0;
|
||||
static ttf_raster_cache_t g_ttf_cache;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* FABRIC.md item 4.4t: confine REPL text (TTF mode only) to the 640x480
|
||||
* CANVAS box computed in 4.4o and pixel-verified in 4.4p. Boot/POST
|
||||
* (bitmap mode) is untouched -- g_origin_x/g_origin_y default to (0,0)
|
||||
* and only change once vt100_enable_ttf() runs.
|
||||
*
|
||||
* FABRIC.md item 4.4x: origin is now DERIVED from fb_width()/fb_height()
|
||||
* at vt100_enable_ttf() time rather than hardcoded per-arch literals --
|
||||
* both the old per-arch numbers (320/104 amd64, 80/4 aarch64+riscv64) and
|
||||
* the new ones below check out against the same formula, so this is a
|
||||
* strict generalization, not a behavior change for X. Y changes because
|
||||
* 4.4u/4.4w replaced 4.4m's 96px/4-line REPL strip with a single-line
|
||||
* strip pinned to the bottom of the screen (see VT100_STRIP_* below) --
|
||||
* the box now sits directly above that strip instead of assuming 96px.
|
||||
* origin_x = (fb_width() - VT100_BOX_W) / 2 -- horizontal center
|
||||
* origin_y = fb_height() - VT100_BOX_H -- box bottom edge
|
||||
* - VT100_STRIP_BOX_GAP_PX -- gap above strip
|
||||
* - (2*VT100_STRIP_GAP_PX + 1) -- strip height
|
||||
* --------------------------------------------------------------------- */
|
||||
#define VT100_BOX_W 640u
|
||||
#define VT100_BOX_H 480u
|
||||
#define VT100_BOX_COLS 53u /* 640 / VT100_TTF_CELL_W_PX, exact */
|
||||
#define VT100_BOX_ROWS 20u /* 480 / VT100_TTF_CELL_H_PX, exact */
|
||||
/* FABRIC.md item 4.4y-revised: Alt+TAB graphics/text toggle. When 0, every
|
||||
* terminal draw call (put_char's glyph blit, erase, scroll) becomes a
|
||||
* pure logical update -- g_vt's cursor/attributes and the scrollback
|
||||
* shadow still advance normally, only the actual framebuffer write is
|
||||
* skipped -- so direct framebuffer/TTF-TEXT drawing shows through
|
||||
* undisturbed, and vt100_toggle_graphics() can restore the terminal
|
||||
* exactly as it logically stands once toggled back. Always 1 in bitmap
|
||||
* mode (boot/POST never hides). */
|
||||
static int g_terminal_visible = 1;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* FABRIC.md items 4.4u/4.4w/4.4x: the REPL prompt/input strip -- a
|
||||
* single-line region pinned to the bottom of the screen, independent of
|
||||
* the scrollback box's own grid (see vt100_strip_draw() below). Numbers
|
||||
* pinned by Captain Bob directly, 2026-08-12 (4.4w).
|
||||
* FABRIC.md items 4.4t/4.4o/4.4u/4.4w/4.4x/4.4z tried a 640x480 CANVAS
|
||||
* box with a separate single-line REPL strip pinned below it -- a
|
||||
* confined region plus independent border-drawing/geometry bookkeeping
|
||||
* for both. Reverted 2026-08-12, simplified back to what this comment
|
||||
* block now describes: the entire framebuffer is one vt100 terminal,
|
||||
* full width and full height, in both bitmap and TTF glyph modes. No
|
||||
* origin offset, no box, no strip, no border drawing -- cols/rows are
|
||||
* simply fb_width()/cell_w() and fb_height()/cell_h(). The REPL prompt is
|
||||
* just the terminal's last line, exactly like any other scrolling
|
||||
* terminal, not a separately-rendered region.
|
||||
* --------------------------------------------------------------------- */
|
||||
#define VT100_STRIP_GAP_PX 15u /* baseline-to-border-line gap, both sides, mirrored */
|
||||
#define VT100_STRIP_BOX_GAP_PX 8u /* gap between the strip's top border and the box's bottom edge */
|
||||
#define VT100_BORDER_GRAY FB_RGB(0xAA, 0xAA, 0xAA) /* FB_ANSI_PALETTE[7]; shared by the
|
||||
strip's border lines (4.4x) and
|
||||
the box's own border (4.4z) --
|
||||
same pinned decision (4.4w) */
|
||||
#define VT100_STRIP_TEXT_WHITE FB_RGB(0xFF, 0xFF, 0xFF)
|
||||
#define VT100_STRIP_LEFT_MARGIN_PX(fbw) (((fbw) - VT100_BOX_W) / 2u) /* aligned under the box */
|
||||
|
||||
static uint32_t g_origin_x = 0; /* box top-left, TTF mode only; 0 in bitmap mode */
|
||||
static uint32_t g_origin_y = 0;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* FABRIC.md item 4.4q: REPL scrollback. Text-only (not pixel snapshots --
|
||||
@@ -239,11 +220,9 @@ static void apply_sgr(void);
|
||||
static uint32_t cell_w(void) { return g_glyph_mode == VT_GLYPH_TTF ? VT100_TTF_CELL_W_PX : fb_cell_w(); }
|
||||
static uint32_t cell_h(void) { return g_glyph_mode == VT_GLYPH_TTF ? VT100_TTF_CELL_H_PX : fb_cell_h(); }
|
||||
|
||||
/* Pixel position of the top-left corner of character cell (cx, cy),
|
||||
* offset by the box origin (4.4t) -- (0,0) in bitmap mode, so boot/POST
|
||||
* is unaffected. */
|
||||
static uint32_t px_of(uint32_t col) { return g_origin_x + col * cell_w(); }
|
||||
static uint32_t py_of(uint32_t row) { return g_origin_y + row * cell_h(); }
|
||||
/* Pixel position of the top-left corner of character cell (cx, cy). */
|
||||
static uint32_t px_of(uint32_t col) { return col * cell_w(); }
|
||||
static uint32_t py_of(uint32_t row) { return row * cell_h(); }
|
||||
|
||||
/* Effective fg / bg accounting for reverse video */
|
||||
static uint32_t eff_fg(void) { return g_vt.reverse ? g_vt.bg : g_vt.fg; }
|
||||
@@ -289,68 +268,6 @@ static void ttf_draw_glyph_cell(uint32_t cell_px, uint32_t cell_py, uint8_t ch,
|
||||
}
|
||||
}
|
||||
|
||||
/* FABRIC.md items 4.4u/4.4w/4.4x: draw the REPL prompt/input strip -- a
|
||||
* single-line region pinned to the bottom of the screen, independent of
|
||||
* the scrollback box's own grid and cursor state (this function never
|
||||
* touches g_vt, and nothing in the box's own put_char()/scroll_up() path
|
||||
* touches the strip). Reuses ttf_draw_glyph_cell() for the actual glyph
|
||||
* blit -- same rasterizer, same cache, as the box's own grid -- rather
|
||||
* than a second text-drawing path.
|
||||
*
|
||||
* Horizontal scroll (4.4u step 4): @p text is drawn tail-anchored once it
|
||||
* exceeds the strip's width. That is equivalent to cursor-anchored
|
||||
* scrolling because the caller (repl.c) only ever appends at, or
|
||||
* backspaces from, the end of the line -- there is no mid-line cursor
|
||||
* movement in this REPL, so "keep the cursor visible" and "keep the tail
|
||||
* visible" are the same window.
|
||||
*
|
||||
* Border lines are drawn LAST, after the glyph loop, not first: a glyph
|
||||
* cell's own opaque background fill (cell_h()=24px) can extend a few
|
||||
* pixels past the strip's 15px baseline-to-line gap for full-ascender
|
||||
* characters -- 4.4u's own record already flags these gap numbers as
|
||||
* "informal... need to be re-checked once actually built." Drawing the
|
||||
* borders last means an oversized cell can never erase them; it can only
|
||||
* be visually clipped by them, which is the correct failure direction.
|
||||
*
|
||||
* No-op before TTF mode is active. */
|
||||
void vt100_strip_draw(const char *text)
|
||||
{
|
||||
uint32_t fbw, fbh, bottom_line_y, top_line_y, baseline_y, cell_py;
|
||||
uint32_t margin_x, avail_px, avail_cols, len, start, i;
|
||||
|
||||
if (!g_ttf_ready || g_glyph_mode != VT_GLYPH_TTF) return;
|
||||
if (!text) text = "";
|
||||
|
||||
fbw = fb_width();
|
||||
fbh = fb_height();
|
||||
bottom_line_y = fbh - 1u;
|
||||
top_line_y = bottom_line_y - (2u * VT100_STRIP_GAP_PX);
|
||||
baseline_y = bottom_line_y - VT100_STRIP_GAP_PX;
|
||||
cell_py = baseline_y - VT100_TTF_SIZE_PX;
|
||||
margin_x = VT100_STRIP_LEFT_MARGIN_PX(fbw);
|
||||
|
||||
/* Clear the whole strip first -- both border lines get redrawn every
|
||||
* call regardless, so clearing border-to-border is simpler than
|
||||
* clearing just the interior and no less correct. */
|
||||
fb_fill_rect(0, top_line_y, fbw, bottom_line_y - top_line_y + 1u, VT100_DEFAULT_BG);
|
||||
|
||||
avail_px = (fbw > 2u * margin_x) ? (fbw - 2u * margin_x) : 0u;
|
||||
avail_cols = cell_w() ? (avail_px / cell_w()) : 0u;
|
||||
|
||||
len = (uint32_t)strlen(text);
|
||||
start = (len > avail_cols) ? (len - avail_cols) : 0u;
|
||||
|
||||
for (i = start; i < len; i++) {
|
||||
uint32_t col = i - start;
|
||||
ttf_draw_glyph_cell(margin_x + col * cell_w(), cell_py,
|
||||
(uint8_t)text[i],
|
||||
VT100_STRIP_TEXT_WHITE, VT100_DEFAULT_BG);
|
||||
}
|
||||
|
||||
fb_fill_rect(0, top_line_y, fbw, 1u, VT100_BORDER_GRAY);
|
||||
fb_fill_rect(0, bottom_line_y, fbw, 1u, VT100_BORDER_GRAY);
|
||||
}
|
||||
|
||||
static void draw_cursor_glyph(uint8_t ch)
|
||||
{
|
||||
uint32_t f = eff_fg();
|
||||
@@ -373,6 +290,9 @@ static void draw_cursor_glyph(uint8_t ch)
|
||||
f = FB_RGB(r, g, bv);
|
||||
}
|
||||
|
||||
if (!g_terminal_visible) return; /* 4.4y-revised: graphics mode -- logical state
|
||||
* only (already updated above), no framebuffer write */
|
||||
|
||||
if (g_glyph_mode == VT_GLYPH_TTF) {
|
||||
ttf_draw_glyph_cell(px_of(g_vt.cx), py_of(g_vt.cy), ch, f, b);
|
||||
} else {
|
||||
@@ -469,28 +389,18 @@ void vt100_enable_ttf(void)
|
||||
|
||||
g_glyph_mode = VT_GLYPH_TTF;
|
||||
|
||||
/* FABRIC.md item 4.4t: switch to the box-confined coordinate system
|
||||
* before anything below derives cols/rows/stride from it -- the
|
||||
* scrollback allocation block just below depends on g_vt.cols via
|
||||
* g_line_stride, so origin/cols/rows MUST be set first or every
|
||||
* shadow/ring index silently corrupts instead of crashing. */
|
||||
/* FABRIC.md item 4.4x: derived from the real framebuffer size, not a
|
||||
* hardcoded per-arch literal -- see the formula in this file's header
|
||||
* comment above VT100_BOX_W. */
|
||||
g_origin_x = VT100_STRIP_LEFT_MARGIN_PX(fb_width());
|
||||
g_origin_y = fb_height() - VT100_BOX_H - VT100_STRIP_BOX_GAP_PX
|
||||
- (2u * VT100_STRIP_GAP_PX + 1u);
|
||||
g_vt.cols = VT100_BOX_COLS;
|
||||
g_vt.rows = VT100_BOX_ROWS;
|
||||
/* Full-screen grid, TTF cell size -- see this file's header comment.
|
||||
* cols/rows MUST be set before anything below derives stride from
|
||||
* them via g_line_stride. */
|
||||
g_vt.cols = fb_width() / cell_w();
|
||||
g_vt.rows = fb_height() / cell_h();
|
||||
g_vt.cx = g_vt.cy = 0;
|
||||
|
||||
/* One-time full-screen clear at the bitmap-to-TTF transition: the boot/
|
||||
* POST scroll left the whole framebuffer covered in font_8x16 text, and
|
||||
* from here on erase_display(2) is box-scoped (see its mode-aware
|
||||
* branch below) -- without this, that debris would sit frozen outside
|
||||
* the box forever instead of leaving clean CANVAS around it. This is
|
||||
* the one and only whole-framebuffer wipe in TTF mode; every later
|
||||
* ESC[2J stays box-scoped. */
|
||||
* POST scroll left the whole framebuffer covered in font_8x16 text.
|
||||
* erase_display(2) below would cover this on its own now that it's
|
||||
* always full-screen, but the explicit fill stays as the "clean slate
|
||||
* before anything else runs" step it always was. */
|
||||
fb_fill_rect(0, 0, fb_width(), fb_height(), g_vt.def_bg);
|
||||
erase_display(2);
|
||||
|
||||
@@ -502,17 +412,6 @@ void vt100_enable_ttf(void)
|
||||
{
|
||||
uint32_t i;
|
||||
g_line_stride = g_vt.cols + 1u;
|
||||
/* 4.4t: cols is now the fixed box width (53) -- verify the stride
|
||||
* came out right rather than assuming the ordering above landed
|
||||
* correctly (advisor-flagged failure mode: silent corruption, not
|
||||
* a crash, if this ever drifts). */
|
||||
if (g_line_stride != VT100_BOX_COLS + 1u) {
|
||||
log_message(LOG_ERROR, "vt100: unexpected line_stride=%u (want %u), scrollback unavailable",
|
||||
g_line_stride, VT100_BOX_COLS + 1u);
|
||||
g_shadow = (void *)0;
|
||||
g_ring = (void *)0;
|
||||
return;
|
||||
}
|
||||
g_shadow = (char *)kmalloc((size_t)g_vt.rows * g_line_stride);
|
||||
g_ring = (char *)kmalloc((size_t)VT100_SCROLLBACK_MAX_LINES * g_line_stride);
|
||||
if (!g_shadow || !g_ring) {
|
||||
@@ -572,6 +471,40 @@ static void scrollback_redraw(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* FABRIC.md item 4.4y-revised: Alt+TAB graphics/text toggle, a two-state
|
||||
* machine (VISIBLE <-> HIDDEN) with exactly one transition function --
|
||||
* both the physical Alt+TAB interception (repl.c) and the ALT+TAB FORTH
|
||||
* word (keyboard_words.c) call this same function, so there is exactly
|
||||
* one place the state actually flips, not two call sites each doing it
|
||||
* independently.
|
||||
*
|
||||
* VISIBLE -> HIDDEN: flip the flag only. draw_cursor_glyph()/
|
||||
* erase_line_range()/erase_display()/scroll_up() all check
|
||||
* g_terminal_visible and skip their framebuffer writes from this point
|
||||
* on, while still advancing g_vt's cursor/attributes and the g_shadow/
|
||||
* g_ring scrollback state normally -- graphics-mode drawing (direct
|
||||
* framebuffer or TTF-TEXT calls) is then free to use the whole screen,
|
||||
* and the terminal's logical state stays perfectly consistent with what
|
||||
* it would have been had it stayed visible.
|
||||
*
|
||||
* HIDDEN -> VISIBLE: flip the flag, then wipe whatever graphics drew and
|
||||
* reuse scrollback_redraw() to repaint the terminal's current on-screen
|
||||
* content from that untouched logical state -- restored exactly as it
|
||||
* logically stood, not merely as it last looked on screen. */
|
||||
void vt100_toggle_graphics(void)
|
||||
{
|
||||
if (!g_ttf_ready || g_glyph_mode != VT_GLYPH_TTF) return;
|
||||
|
||||
if (g_terminal_visible) {
|
||||
g_terminal_visible = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
g_terminal_visible = 1;
|
||||
fb_fill_rect(0, 0, fb_width(), fb_height(), g_vt.def_bg);
|
||||
scrollback_redraw();
|
||||
}
|
||||
|
||||
void vt100_scroll_back(uint32_t n)
|
||||
{
|
||||
if (!g_shadow || !g_ring) return;
|
||||
@@ -654,13 +587,9 @@ static void scroll_up(uint32_t lines)
|
||||
}
|
||||
}
|
||||
|
||||
/* 4.4t: box-scoped scroll in TTF mode (fb_scroll_rows() is a
|
||||
* whole-framebuffer blit and would drag pixels from outside the box);
|
||||
* bitmap mode (boot/POST) keeps the original full-framebuffer path. */
|
||||
if (g_glyph_mode == VT_GLYPH_TTF) {
|
||||
fb_scroll_rect(g_origin_x, g_origin_y, VT100_BOX_W, VT100_BOX_H,
|
||||
lines * cell_h(), g_vt.def_bg);
|
||||
} else {
|
||||
/* Full-framebuffer scroll -- both glyph modes cover the whole screen
|
||||
* now, so there is no box to scope this to. */
|
||||
if (g_terminal_visible) {
|
||||
fb_scroll_rows(lines, g_vt.def_bg);
|
||||
}
|
||||
}
|
||||
@@ -679,29 +608,10 @@ static void scroll_up(uint32_t lines)
|
||||
static void erase_line_range(uint32_t row, uint32_t c0, uint32_t c1)
|
||||
{
|
||||
if (c1 <= c0) return;
|
||||
if (!g_terminal_visible) return; /* 4.4y-revised: graphics mode */
|
||||
fb_fill_rect(px_of(c0), py_of(row), (c1 - c0) * cell_w(), cell_h(), g_vt.bg);
|
||||
}
|
||||
|
||||
/* FABRIC.md item 4.4z: stroke the scroll box's own visible border. 4.4t
|
||||
* confined text to the box's coordinate space but never drew a rectangle
|
||||
* outline on it -- the edges were implicit (where text stops), not
|
||||
* drawn. Four thin edges, 1px each, same gray as the strip's border
|
||||
* lines (4.4x/4.4w -- one shared color, one design decision). Called
|
||||
* from erase_display()'s box-scoped branch below, so the border survives
|
||||
* every box clear (the initial one at vt100_enable_ttf() time and any
|
||||
* later ESC[2J), not just the first. */
|
||||
static void draw_box_border(void)
|
||||
{
|
||||
fb_fill_rect(g_origin_x, g_origin_y,
|
||||
VT100_BOX_W, 1u, VT100_BORDER_GRAY);
|
||||
fb_fill_rect(g_origin_x, g_origin_y + VT100_BOX_H - 1u,
|
||||
VT100_BOX_W, 1u, VT100_BORDER_GRAY);
|
||||
fb_fill_rect(g_origin_x, g_origin_y,
|
||||
1u, VT100_BOX_H, VT100_BORDER_GRAY);
|
||||
fb_fill_rect(g_origin_x + VT100_BOX_W - 1u, g_origin_y,
|
||||
1u, VT100_BOX_H, VT100_BORDER_GRAY);
|
||||
}
|
||||
|
||||
static void erase_display(int mode)
|
||||
{
|
||||
if (mode == 0) {
|
||||
@@ -719,13 +629,8 @@ static void erase_display(int mode)
|
||||
}
|
||||
erase_line_range(g_vt.cy, 0, g_vt.cx + 1);
|
||||
} else {
|
||||
/* erase entire screen -- 4.4t: box-scoped in TTF mode (a
|
||||
* full-framebuffer wipe here would blank CANVAS outside the box),
|
||||
* full-screen in bitmap mode as before (boot/POST unaffected). */
|
||||
if (g_glyph_mode == VT_GLYPH_TTF) {
|
||||
fb_fill_rect(g_origin_x, g_origin_y, VT100_BOX_W, VT100_BOX_H, g_vt.def_bg);
|
||||
draw_box_border();
|
||||
} else {
|
||||
/* erase entire screen -- full framebuffer, both glyph modes. */
|
||||
if (g_terminal_visible) {
|
||||
fb_fill_rect(0, 0, fb_width(), fb_height(), g_vt.def_bg);
|
||||
}
|
||||
g_vt.cx = g_vt.cy = 0;
|
||||
@@ -972,13 +877,8 @@ void vt100_putc(char raw)
|
||||
/* RI reverse index: scroll down (insert line at top) */
|
||||
if (g_vt.cy == 0) {
|
||||
/* Scroll display down: not trivially done without a
|
||||
* full back-buffer; fill top row with bg instead.
|
||||
* 4.4t: box-scoped in TTF mode, full-width in bitmap mode. */
|
||||
if (g_glyph_mode == VT_GLYPH_TTF) {
|
||||
fb_fill_rect(g_origin_x, g_origin_y, VT100_BOX_W, cell_h(), g_vt.def_bg);
|
||||
} else {
|
||||
fb_fill_rect(0, 0, fb_width(), cell_h(), g_vt.def_bg);
|
||||
}
|
||||
* full back-buffer; fill top row with bg instead. */
|
||||
fb_fill_rect(0, 0, fb_width(), cell_h(), g_vt.def_bg);
|
||||
} else {
|
||||
g_vt.cy--;
|
||||
}
|
||||
|
||||
+98
-70
@@ -34,6 +34,7 @@
|
||||
#include "version.h"
|
||||
#include "starkernel/timer.h"
|
||||
#include "starkernel/arch.h"
|
||||
#include "word_source/include/keyboard_words.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -45,71 +46,6 @@
|
||||
|
||||
const char lithos_version[64] = LITHOS_VERSION_STR;
|
||||
|
||||
/*===========================================================================
|
||||
* FABRIC.md items 4.4u/4.4w/4.4x: the bottom REPL strip -- a plain-text
|
||||
* (no ANSI/SGR) mirror of "[VMName] ok> <input so far>", refreshed into
|
||||
* console_fb_strip_draw() on every keystroke. Built separately from
|
||||
* SK_PROMPT_TEXT rather than derived from it, since the strip renders in
|
||||
* one fixed color (4.4u) and has no SGR parser of its own -- the ANSI
|
||||
* version keeps going to console_puts() exactly as before, unaffected,
|
||||
* for serial and for the scrollback box's own history.
|
||||
*===========================================================================*/
|
||||
|
||||
#define SK_STRIP_PROMPT_MAX 80u
|
||||
|
||||
static char g_strip_prompt[SK_STRIP_PROMPT_MAX] = "ok> ";
|
||||
|
||||
/* Builds "[VMName] ok> " or "[VMName] zuse)ok> " (no VM name -> just the
|
||||
* suffix) into g_strip_prompt, bounded, no strcat -- mirrors emit_prefix()'s
|
||||
* bracket format (console.c) so the strip and the box agree on what a
|
||||
* prompt looks like, minus the ANSI color codes the strip doesn't use. */
|
||||
static void set_strip_prompt(VM *vm)
|
||||
{
|
||||
const char *vn = console_get_vm_name();
|
||||
char *p = g_strip_prompt;
|
||||
size_t room = sizeof(g_strip_prompt) - 1u;
|
||||
size_t n;
|
||||
const char *suffix;
|
||||
size_t used, left, sn;
|
||||
|
||||
if (vn && vn[0]) {
|
||||
n = strlen(vn);
|
||||
if (n > room) n = room;
|
||||
*p++ = '[';
|
||||
memcpy(p, vn, n);
|
||||
p += n;
|
||||
*p++ = ']';
|
||||
*p++ = ' ';
|
||||
}
|
||||
|
||||
suffix = (vm && vm->zuse_session) ? "zuse)ok> " : "ok> ";
|
||||
used = (size_t)(p - g_strip_prompt);
|
||||
left = (used < room) ? (room - used) : 0u;
|
||||
sn = strlen(suffix);
|
||||
if (sn > left) sn = left;
|
||||
memcpy(p, suffix, sn);
|
||||
p += sn;
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
/* Concatenates g_strip_prompt + input_so_far (bounded, no strcat) and
|
||||
* hands the result to the strip renderer. Called once with an empty
|
||||
* input_so_far right after set_strip_prompt(), then again on every
|
||||
* keystroke from sk_readline() below. */
|
||||
static void strip_refresh(const char *input_so_far)
|
||||
{
|
||||
char combined[INPUT_BUFFER_SIZE + SK_STRIP_PROMPT_MAX];
|
||||
size_t plen = strlen(g_strip_prompt);
|
||||
size_t ilen = strlen(input_so_far);
|
||||
|
||||
if (plen >= sizeof(combined)) plen = sizeof(combined) - 1u;
|
||||
memcpy(combined, g_strip_prompt, plen);
|
||||
if (plen + ilen >= sizeof(combined)) ilen = sizeof(combined) - 1u - plen;
|
||||
memcpy(combined + plen, input_so_far, ilen);
|
||||
combined[plen + ilen] = '\0';
|
||||
|
||||
console_fb_strip_draw(combined);
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* USE-word dispatch: which VM receives REPL input.
|
||||
@@ -144,6 +80,102 @@ static void sk_repl_idle(void)
|
||||
(void)0;
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* FABRIC.md item 4.4v: keyboard-to-REPL bridge.
|
||||
*
|
||||
* Translates sk_key_event_poll()'s converged Linux-keycode-namespace
|
||||
* stream (keyboard_words.c -- one implementation shared with KEY-EVENT,
|
||||
* live-verified on all three architectures per item 4.3.5f) into the same
|
||||
* byte stream sk_readline() already reads from console_getc(): -1 for
|
||||
* "nothing ready", else a raw ASCII byte with '\n'/0x7F meaning the same
|
||||
* thing they mean for the serial path below.
|
||||
*
|
||||
* Table covers exactly the keys a line editor needs -- letters, digits,
|
||||
* the standard US-QWERTY punctuation row, space, enter, backspace, tab
|
||||
* (for the Ctrl+TAB toggle interception, 4.4y/4.4u step 8) -- not full
|
||||
* keyboard coverage. Keycodes are Linux input-event-codes.h values,
|
||||
* confirmed against this build host's own header, not guessed (§25.0
|
||||
* rule 4). Index 0 means "no mapping"; arrows/F-keys/etc. fall through
|
||||
* unmapped and are silently dropped, consistent with this REPL's
|
||||
* append/backspace-only editing model (4.4u: no mid-line cursor
|
||||
* movement).
|
||||
*===========================================================================*/
|
||||
|
||||
#define SK_KBD_TABLE_SIZE 98u /* highest keycode used below is KEY_RIGHTCTRL=97 */
|
||||
|
||||
static const char sk_kbd_unshifted[SK_KBD_TABLE_SIZE] = {
|
||||
[2]='1',[3]='2',[4]='3',[5]='4',[6]='5',[7]='6',[8]='7',[9]='8',[10]='9',[11]='0',
|
||||
[12]='-',[13]='=',
|
||||
[16]='q',[17]='w',[18]='e',[19]='r',[20]='t',[21]='y',[22]='u',[23]='i',[24]='o',[25]='p',
|
||||
[26]='[',[27]=']',
|
||||
[30]='a',[31]='s',[32]='d',[33]='f',[34]='g',[35]='h',[36]='j',[37]='k',[38]='l',
|
||||
[39]=';',[40]='\'',[41]='`',[43]='\\',
|
||||
[44]='z',[45]='x',[46]='c',[47]='v',[48]='b',[49]='n',[50]='m',
|
||||
[51]=',',[52]='.',[53]='/',
|
||||
[57]=' ',
|
||||
};
|
||||
|
||||
static const char sk_kbd_shifted[SK_KBD_TABLE_SIZE] = {
|
||||
[2]='!',[3]='@',[4]='#',[5]='$',[6]='%',[7]='^',[8]='&',[9]='*',[10]='(',[11]=')',
|
||||
[12]='_',[13]='+',
|
||||
[16]='Q',[17]='W',[18]='E',[19]='R',[20]='T',[21]='Y',[22]='U',[23]='I',[24]='O',[25]='P',
|
||||
[26]='{',[27]='}',
|
||||
[30]='A',[31]='S',[32]='D',[33]='F',[34]='G',[35]='H',[36]='J',[37]='K',[38]='L',
|
||||
[39]=':',[40]='"',[41]='~',[43]='|',
|
||||
[44]='Z',[45]='X',[46]='C',[47]='V',[48]='B',[49]='N',[50]='M',
|
||||
[51]='<',[52]='>',[53]='?',
|
||||
[57]=' ',
|
||||
};
|
||||
|
||||
#define SK_KEY_BACKSPACE 14u
|
||||
#define SK_KEY_TAB 15u
|
||||
#define SK_KEY_ENTER 28u
|
||||
#define SK_KEY_LEFTSHIFT 42u
|
||||
#define SK_KEY_RIGHTSHIFT 54u
|
||||
#define SK_KEY_LEFTALT 56u
|
||||
#define SK_KEY_RIGHTALT 100u
|
||||
|
||||
static int g_kbd_shift_down; /* zero-initialized (BSS) */
|
||||
static int g_kbd_alt_down;
|
||||
|
||||
/* Drains and translates one physically-typed key. Modifier state persists
|
||||
* across calls (a real keyboard's shift/alt state is global, not
|
||||
* per-line). Alt+TAB is intercepted here and drives the graphics/text
|
||||
* toggle directly (console_fb_toggle_graphics(), the same state-machine
|
||||
* transition the ALT+TAB FORTH word calls) -- never reaches the line
|
||||
* buffer as a character either way. */
|
||||
static int sk_kbd_getc(void)
|
||||
{
|
||||
uint16_t keycode;
|
||||
int pressed;
|
||||
|
||||
while (sk_key_event_poll(&keycode, &pressed)) {
|
||||
if (keycode == SK_KEY_LEFTSHIFT || keycode == SK_KEY_RIGHTSHIFT) {
|
||||
g_kbd_shift_down = pressed;
|
||||
continue;
|
||||
}
|
||||
if (keycode == SK_KEY_LEFTALT || keycode == SK_KEY_RIGHTALT) {
|
||||
g_kbd_alt_down = pressed;
|
||||
continue;
|
||||
}
|
||||
if (!pressed) continue; /* only act on press/repeat */
|
||||
|
||||
if (keycode == SK_KEY_TAB) {
|
||||
if (g_kbd_alt_down) console_fb_toggle_graphics();
|
||||
continue; /* bare TAB: not mapped, same as arrows/F-keys */
|
||||
}
|
||||
if (keycode == SK_KEY_ENTER) return '\n';
|
||||
if (keycode == SK_KEY_BACKSPACE) return 0x7F;
|
||||
|
||||
if (keycode < SK_KBD_TABLE_SIZE) {
|
||||
char c = g_kbd_shift_down ? sk_kbd_shifted[keycode] : sk_kbd_unshifted[keycode];
|
||||
if (c) return (unsigned char)c;
|
||||
}
|
||||
/* unmapped keycode -- drop and keep draining */
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* sk_readline - line read from serial console with echo
|
||||
*
|
||||
@@ -158,10 +190,10 @@ static int sk_readline(char *buf, int size)
|
||||
int n = 0;
|
||||
|
||||
buf[0] = '\0';
|
||||
strip_refresh(buf); /* FABRIC.md 4.4x: show the bare prompt before any input */
|
||||
|
||||
for (;;) {
|
||||
int c = console_getc(); /* non-blocking poll */
|
||||
if (c < 0) c = sk_kbd_getc(); /* FABRIC.md 4.4v: second source, same buffer */
|
||||
|
||||
if (c < 0) {
|
||||
/* Service the heartbeat bottom half every idle iteration, not
|
||||
@@ -203,7 +235,6 @@ static int sk_readline(char *buf, int size)
|
||||
console_putc('\b');
|
||||
console_putc(' ');
|
||||
console_putc('\b');
|
||||
strip_refresh(buf);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -213,7 +244,6 @@ static int sk_readline(char *buf, int size)
|
||||
buf[n++] = (char)c;
|
||||
buf[n] = '\0';
|
||||
console_putc((char)c); /* echo */
|
||||
strip_refresh(buf);
|
||||
}
|
||||
|
||||
buf[n] = '\0';
|
||||
@@ -268,7 +298,6 @@ int sk_repl_step(VM *vm)
|
||||
int is_hera = (!vn || (vn[0]=='H' && vn[1]=='e' && vn[2]=='r' && vn[3]=='a' && vn[4]=='\0'));
|
||||
vm->emergency_console = is_hera ? (vm->zuse_session ? 0 : 1) : 0;
|
||||
console_puts(SK_PROMPT_TEXT);
|
||||
set_strip_prompt(vm); /* FABRIC.md 4.4x: bottom strip mirrors the same prompt */
|
||||
}
|
||||
|
||||
sk_readline(input, sizeof(input));
|
||||
@@ -316,7 +345,6 @@ void sk_repl_run(VM *vm)
|
||||
int is_hera = (!vn || (vn[0]=='H' && vn[1]=='e' && vn[2]=='r' && vn[3]=='a' && vn[4]=='\0'));
|
||||
active->emergency_console = is_hera ? (active->zuse_session ? 0 : 1) : 0;
|
||||
console_puts(SK_PROMPT_TEXT);
|
||||
set_strip_prompt(active); /* FABRIC.md 4.4x: bottom strip mirrors the same prompt */
|
||||
}
|
||||
|
||||
sk_readline(input, sizeof(input));
|
||||
|
||||
@@ -50,9 +50,29 @@
|
||||
* -- see the implementation's own doc comment), pressed is 1 or 0. This is
|
||||
* the word the later REPL keyboard-input work (M8) is expected to build
|
||||
* on; KBD-SCAN/VKBD-EVENT remain as lower-level per-device diagnostics.
|
||||
*
|
||||
* @par ALT+TAB ( -- )
|
||||
* FABRIC.md item 4.4y-revised. Programmatic equivalent of the physical
|
||||
* Alt+TAB graphics/text toggle -- calls the same state-machine transition
|
||||
* (console_fb_toggle_graphics() / vt100_toggle_graphics()) the keyboard
|
||||
* interception in repl.c uses, so there is exactly one place the toggle
|
||||
* actually happens.
|
||||
* @}
|
||||
*/
|
||||
|
||||
void register_keyboard_words(VM *vm);
|
||||
|
||||
/**
|
||||
* FABRIC.md item 4.4v: shared C-level poll, one converged event stream
|
||||
* (Linux input keycode namespace, all three architectures) -- see
|
||||
* KEY-EVENT's doc comment above for the full per-arch translation
|
||||
* rationale. Used by both kbw_key_event() (the FORTH word) and the REPL
|
||||
* keyboard bridge (repl.c), so there is exactly one implementation.
|
||||
*
|
||||
* @param keycode Written with the Linux input keycode on success.
|
||||
* @param pressed Written with 1 (press/repeat) or 0 (release) on success.
|
||||
* @return 1 if an event was available, 0 otherwise.
|
||||
*/
|
||||
int sk_key_event_poll(uint16_t *keycode, int *pressed);
|
||||
|
||||
#endif /* KEYBOARD_WORDS_H */
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
#include "starkernel/virtio_input.h"
|
||||
#endif
|
||||
|
||||
#if defined(__STARKERNEL__)
|
||||
#include "starkernel/console.h"
|
||||
#endif
|
||||
|
||||
/* KBD-SCAN ( -- c -1 | 0 ) */
|
||||
static void kbw_scan(VM *vm)
|
||||
{
|
||||
@@ -117,29 +121,60 @@ static void kbw_vdebug(VM *vm)
|
||||
* field already lives in the same Linux keycode namespace; `value` is
|
||||
* mapped 1:1 except autorepeat (value=2), folded into "still pressed"
|
||||
* here since this checkpoint's shape only distinguishes press/release. */
|
||||
static void kbw_key_event(VM *vm)
|
||||
/* FABRIC.md item 4.4v: shared C-level implementation, so the REPL
|
||||
* keyboard bridge (repl.c) and the KEY-EVENT FORTH word below poll the
|
||||
* exact same converged event stream rather than each re-deriving the
|
||||
* per-arch translation kbw_key_event's own doc comment already explains
|
||||
* (XT Set-1 make code == Linux keycode for the non-extended range; break
|
||||
* codes set bit 7 instead of carrying a separate field). */
|
||||
int sk_key_event_poll(uint16_t *keycode, int *pressed)
|
||||
{
|
||||
#if defined(__STARKERNEL__) && defined(ARCH_AMD64)
|
||||
uint8_t sc;
|
||||
if (i8042_pop_scancode(&sc)) {
|
||||
vm_push(vm, (cell_t)(sc & 0x7Fu));
|
||||
vm_push(vm, (cell_t)((sc & 0x80u) ? 0 : 1));
|
||||
vm_push(vm, -1);
|
||||
} else {
|
||||
vm_push(vm, 0);
|
||||
*keycode = (uint16_t)(sc & 0x7Fu);
|
||||
*pressed = (sc & 0x80u) ? 0 : 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
#elif defined(__STARKERNEL__) && (defined(ARCH_RISCV64) || defined(ARCH_AARCH64))
|
||||
uint16_t code;
|
||||
uint32_t value;
|
||||
if (virtio_input_pop_event(&code, &value)) {
|
||||
vm_push(vm, (cell_t)code);
|
||||
vm_push(vm, (cell_t)(value == 0u ? 0 : 1));
|
||||
*keycode = code;
|
||||
*pressed = (value == 0u) ? 0 : 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
(void)keycode; (void)pressed;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void kbw_key_event(VM *vm)
|
||||
{
|
||||
uint16_t keycode;
|
||||
int pressed;
|
||||
|
||||
if (sk_key_event_poll(&keycode, &pressed)) {
|
||||
vm_push(vm, (cell_t)keycode);
|
||||
vm_push(vm, (cell_t)pressed);
|
||||
vm_push(vm, -1);
|
||||
} else {
|
||||
vm_push(vm, 0);
|
||||
}
|
||||
#else
|
||||
vm_push(vm, 0);
|
||||
}
|
||||
|
||||
/* ALT+TAB ( -- ): FABRIC.md item 4.4y-revised. Programmatic equivalent of
|
||||
* the physical Alt+TAB interception (repl.c's sk_kbd_getc()) -- both call
|
||||
* console_fb_toggle_graphics(), so there is exactly one state-machine
|
||||
* transition, invoked two ways. */
|
||||
static void kbw_alt_tab(VM *vm)
|
||||
{
|
||||
(void)vm;
|
||||
#if defined(__STARKERNEL__)
|
||||
console_fb_toggle_graphics();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -150,4 +185,5 @@ void register_keyboard_words(VM *vm)
|
||||
register_word(vm, "VKBD-EVENT", kbw_vevent);
|
||||
register_word(vm, "VKBD-DEBUG", kbw_vdebug);
|
||||
register_word(vm, "KEY-EVENT", kbw_key_event);
|
||||
register_word(vm, "ALT+TAB", kbw_alt_tab);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user