docs: sync .org with implementation for backend-size, read-raw-byte, SIGWINCH

backend-protocol.org / simple.lisp:
- Replace hard-coded 80x24 prose with full 5-step fallback chain
  (MY_TERM env vars → ioctl fd 0 → ioctl stdout → /dev/tty → 80x24)
- Document return-from pattern (or discards secondary values)

modern-backend.org / modern.lisp:
- Replace simple ioctl-only prose with 4-step fallback chain
- Document env-var pre-check and /dev/tty fallback

text-input.org / input.lisp:
- Update read-raw-byte prose: with-pinned-objects/vector-sap
  instead of alien buffer (code was already correct, prose stale)
- Add missing (require :sb-posix) to SIGWINCH handler code block
- Document :sb-posix requirement in prose
This commit is contained in:
2026-05-14 16:25:45 -04:00
parent b44b4b6aa0
commit 916f473107
6 changed files with 138 additions and 47 deletions

View File

@@ -817,14 +817,71 @@ No-op — simple backend has no terminal state to restore.
*** Backend Size (Simple)
Returns hard-coded 80x24 dimensions. A real implementation would use
ioctl or TIOCGWINSZ, but the simple backend avoids OS-specific calls
for maximum portability.
Queries actual terminal dimensions through a fallback chain, with
a hard-coded 80x24 at the end:
1. **Env var pre-check** — ~MY_TERM_COLS~ / ~MY_TERM_ROWS~, set by the
calling script before ~exec sbcl~. Checked with ~return-from~ so that
/both/ values are preserved (Common Lisp's ~or~ discards secondary
values).
2. **ioctl on fd 0 (stdin)** — the parent's real terminal fd.
3. **ioctl on stdout** — fast and correct after SIGWINCH at runtime.
4. **ioctl on ~/dev/tty~** — fallback when stdin/stdout are pipes.
5. **~(values 80 24)~** — last resort.
#+BEGIN_SRC lisp :tangle ../src/backend/simple.lisp
(defmethod backend-size ((b simple-backend))
;; Try ioctl, fall back to 80x24
(values 80 24))
;; MY_TERM_COLS/MY_TERM_ROWS — set by the calling script.
;; Check with return-from to preserve both values.
(let ((cstr (sb-ext:posix-getenv "MY_TERM_COLS"))
(rstr (sb-ext:posix-getenv "MY_TERM_ROWS")))
(when (and cstr rstr)
(let ((cols (parse-integer cstr :junk-allowed t))
(rows (parse-integer rstr :junk-allowed t)))
(when (and cols rows (> cols 0) (> rows 0))
(return-from backend-size (values cols rows))))))
(or ;; ioctl on fd 0 (stdin) — the parent's own terminal.
(multiple-value-bind (cols rows)
(ignore-errors
(let ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4)))
(unwind-protect
(let ((ok (sb-unix:unix-ioctl 0 21523
(sb-alien:alien-sap winsize))))
(when ok
(let ((c (sb-alien:deref winsize 1))
(r (sb-alien:deref winsize 0)))
(when (and c r (> c 0) (> r 0))
(values c r)))))
(sb-alien:free-alien winsize))))
(when (and cols rows (> cols 0) (> rows 0))
(values cols rows)))
;; ioctl on stdout fd — fast, correct after SIGWINCH at runtime.
(multiple-value-bind (cols rows)
(ignore-errors
(let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4)))
(unwind-protect
(let ((ok (sb-unix:unix-ioctl
(sb-sys:fd-stream-fd (backend-output-stream b))
21523 (sb-alien:alien-sap winsize))))
(when ok
(values (sb-alien:deref winsize 1)
(sb-alien:deref winsize 0))))
(sb-alien:free-alien winsize))))
(when (and cols rows (> cols 0) (> rows 0))
(values cols rows)))
(ignore-errors
(let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0)))
(when (and tty-fd (numberp tty-fd) (> tty-fd 0))
(unwind-protect
(let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4)))
(let ((ok (sb-unix:unix-ioctl tty-fd 21523
(sb-alien:alien-sap winsize))))
(when ok
(values (sb-alien:deref winsize 1)
(sb-alien:deref winsize 0))))
(sb-alien:free-alien winsize))
(sb-unix:unix-close tty-fd)))))
(values 80 24)))
#+END_SRC
*** Backend Write (Simple)

View File

@@ -642,27 +642,58 @@ is responsible for redrawing the full screen after resume.
(values))
#+END_SRC
** Backend-size via ioctl
** Backend-size via ioctl and env vars
*** backend-size
Uses TIOCGWINSZ (=0x5413= = 21523) to query actual terminal dimensions
from the kernel via =ioctl=. The =alien-sap= wrapper ensures
compatibility across SBCL versions. Returns (values cols rows).
Uses a fallback chain to determine terminal dimensions:
1. **Env var pre-check** — ~MY_TERM_COLS~ / ~MY_TERM_ROWS~, set by
the calling script before ~exec sbcl~. Uses ~return-from~ to preserve
both values (~or~ discards secondary values).
2. **ioctl on stdout** — fast, correct after SIGWINCH at runtime.
3. **ioctl on ~/dev/tty~** — fallback when stdout is not a terminal.
4. **~(values 80 24)~** — last resort.
#+BEGIN_SRC lisp :tangle ../src/backend/modern.lisp
(defmethod backend-size ((b modern-backend))
(or (ignore-errors
(let* ((+tiocgwinsz+ 21523) ; 0x5413 on Linux
(winsize (sb-alien:make-alien sb-alien:unsigned-short 4)))
(unwind-protect
(progn
(sb-unix:unix-ioctl (sb-sys:fd-stream-fd (backend-output-stream b))
+tiocgwinsz+
(sb-alien:alien-sap winsize))
(values (sb-alien:deref winsize 1) ;; cols
(sb-alien:deref winsize 0))) ;; rows
(sb-alien:free-alien winsize))))
;; MY_TERM_COLS/MY_TERM_ROWS — set by the calling script.
;; Check FIRST with return-from so both values (cols and rows)
;; are preserved (or discards secondaries).
(let ((cstr (sb-ext:posix-getenv "MY_TERM_COLS"))
(rstr (sb-ext:posix-getenv "MY_TERM_ROWS")))
(when (and cstr rstr)
(let ((cols (parse-integer cstr :junk-allowed t))
(rows (parse-integer rstr :junk-allowed t)))
(when (and cols rows (> cols 0) (> rows 0))
(return-from backend-size (values cols rows))))))
(or
(multiple-value-bind (cols rows)
(ignore-errors
(let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4)))
(unwind-protect
(let ((ok (sb-unix:unix-ioctl
(sb-sys:fd-stream-fd (backend-output-stream b))
21523 (sb-alien:alien-sap winsize))))
(when ok
(values (sb-alien:deref winsize 1) ;; cols
(sb-alien:deref winsize 0)))) ;; rows
(sb-alien:free-alien winsize))))
(when (and cols rows (> cols 0) (> rows 0))
(values cols rows)))
;; Direct ioctl on /dev/tty.
(ignore-errors
(let ((tty-fd (sb-unix:unix-open "/dev/tty" 0 0)))
(when (and tty-fd (numberp tty-fd) (> tty-fd 0))
(unwind-protect
(let* ((winsize (sb-alien:make-alien sb-alien:unsigned-short 4)))
(let ((ok (sb-unix:unix-ioctl tty-fd 21523
(sb-alien:alien-sap winsize))))
(when ok
(let ((cols (sb-alien:deref winsize 1))
(rows (sb-alien:deref winsize 0)))
(values cols rows)))))
(sb-unix:unix-close tty-fd)))))
(values 80 24)))
#+END_SRC

View File

@@ -434,9 +434,11 @@ The ~timeout~ keyword uses ~sb-unix:unix-simple-poll~ to implement
non-blocking reads with a configurable deadline. This is critical for
the 50ms escape sequence ambiguity resolution in ~%read-escape-sequence~.
Memory management: we allocate a 1-byte alien buffer, read into it, then
~free-alien~ in an ~unwind-protect~ to prevent leaks even if the read
is interrupted by a signal.
Memory management: we use ~sb-sys:with-pinned-objects~ to pin a 1-byte
~make-array~ vector in memory, obtain its SAP via ~sb-sys:vector-sap~,
and read directly into the backing storage. This avoids alien allocation
and manual ~free-alien~ while keeping the GC from moving the buffer
during the ~unix-read~ syscall.
#+BEGIN_SRC lisp :tangle ../src/components/input.lisp
(defun read-raw-byte (&key timeout)
@@ -744,7 +746,8 @@ connection. The 500ms gives the terminal ample time to deliver all bytes.
When the terminal emulator window is resized, the kernel sends SIGWINCH
to the foreground process group. SBCL's signal handling facility
(~sb-sys:enable-interrupt~) lets us install a handler that sets this
flag.
flag. The ~:sb-posix~ module must be ~require~d first so that the
~sb-posix:sigwinch~ constant is available.
The main event loop should check this flag after each ~%read-event~
call and, if set, query the new terminal dimensions and redraw. The
@@ -758,6 +761,7 @@ after handling the resize.
#+BEGIN_SRC lisp :tangle ../src/components/input.lisp
#+sbcl
(eval-when (:load-toplevel :execute)
(require :sb-posix)
(sb-sys:enable-interrupt sb-posix:sigwinch
(lambda (signal info context)
(declare (ignore signal info context))