Preserves foreground and background colors across draw calls.
Without this, every draw-text resets terminal to default grey
background, causing grey-background artifacts in the TUI.
- %read-escape-sequence: increase b1 timeout 0.05→0.1, pass timeout to
parse-csi-sequence and all read-next calls (OCR branch was using nil
timeout, blocking forever)
- parse-csi-sequence: accept :timeout keyword, pass to all read-raw-byte
calls, return :escape on timeout instead of blocking
- %read-digits: accept timeout, check nil from read-raw-byte before (>= b 48)
- %parse-sgr-mouse: accept timeout, return nil if first byte times out
- read-param in parse-csi-sequence: check b for nil before comparing
- parse-csi-params: map Kitty protocol u-terminator cursor codes (1=up,
2=down, 3=right, 4=left, 5=page-up, 6=page-down) before falling to
:codepoint. Convert terminator byte to char via code-char for key table
lookups.
- select-next/select-prev: remove (not (getf opt :category)) check.
All items have :category in the unified command list, so navigation
skipped every item and selection stayed at index 0 permanently.
- parse-csi-sequence: use multiple-value-bind to capture both params
list and terminator byte (let* only takes primary value, discarding
terminator, causing destructuring-bind to fail on empty list)
- parse-csi-params: convert terminator byte to char via code-char
for key table lookups and comparisons
- read-raw-byte: check unix-simple-poll result before calling
unix-read. When poll times out, returns nil immediately instead
of blocking forever on unix-read
The OR pattern inside backend-size used (or (multiple-value-bind ...)
...), but multiple-value-bind only returns the primary value of its
body. When the env-var shortcut was removed, both calls to backend-size
(the cols nth-value 0 and rows nth-value 1) returned the same primary
value, making rows always nil.
Restructure with nested multiple-value-bind/values chains so both
return values propagate correctly through all fallback stages.
Also remove MY_TERM_COLS/ROWS env-var pre-check — it returned stale
startup dimensions after terminal resize.
backend-write flushed output after every single draw-text/draw-rect
call, causing hundreds of individual flushes per frame. This caused
visible flicker on slow terminals.
Remove finish-output from backend-write — all critical flush points
(initialize-backend, shutdown-backend, enable-mouse, enable-bracketed-paste,
end-sync) already call finish-output explicitly.
DECICM sync (begin-sync/end-sync) wraps every frame boundary,
making the frame render atomically with a single flush at end-sync.
or in Common Lisp only preserves the primary value — secondary
values from the truthy branch are lost. return-from preserves
all values, so both cols and rows are returned correctly.
When all ioctl methods return rows=0 (SBCL process context), try
/ from the shell environment. These are set by bash
and may survive SBCL's env filtering in some configurations.
The parent's fd 0 IS the real terminal when running from a shell.
This directly queries the terminal size without subprocess or
alien complexity. Added proper when guard on the unix-ioctl result.
unix-ioctl returns NIL on failure, but the code still reads the
uninitialized alien winsize buffer, getting garbage values for
cols and rows (often 0 for rows). Now checks 'ok' before reading.
:input :inherit preserves the parent's fd 0 (the terminal) in the
child process, so stty can query it via ioctl. Previous approaches
(:input :interactive, /dev/tty) all failed because uiop's process
setup redirects stdin away from the terminal.
stty size returns incomplete data when run through uiop:run-program
(the child may not have terminal access). tput is a terminfo utility
that outputs a single number per call, avoiding parsing issues.
Works reliably in any subprocess context.
SBCL's stdin during --load is the load file, NOT the terminal.
When uiop:run-program creates a subprocess, it inherits this
stdin, so 'stty size' reads from the load file and fails.
:input :interactive opens /dev/tty for the child's stdin,
matching the behavior of 'stty size' from an interactive shell.
stty size via subprocess is the most reliable method — it
returns the correct 59x83 from the user's terminal. Move it
before ioctl to ensure it's tried first.
uiop:run-program may redirect the child's stdin, preventing stty
from querying the terminal. 'stty size < /dev/tty' explicitly
reads from the controlling terminal regardless of stdin setup.
stty size via uiop:run-program is the most reliable method —
it works from the shell on every Unix system and bypasses
alien/ioctl quirks. Placed between stdout ioctl and /dev/tty
ioctl in the fallback chain.
uiop:run-program can inherit different terminal state than the
interactive shell. Opening /dev/tty directly and calling ioctl
on that fd is equivalent to what the shell's stty does, and
works regardless of SBCL's fd inheritance quirks.
SBCL unconditionally strips COLUMNS and LINES from the environment,
so posix-getenv always returns nil for those names. stty size is
the reliable cross-platform fallback for terminal dimensions.
- stty size returns 'rows cols'. Old code set only one dimension
when both env vars were missing; new code sets both.
- Added tput cols/lines as final env-var fallback for systems
where COLUMNS/LINES are not exported and stty is unavailable.
- Added 'export COLUMNS LINES' to the passepartout script so
SBCL can read them from the environment.
sb-posix is a built-in SBCL contrib, available via require.
Without it, sb-posix:sigwinch causes a reader error and the
eval-when for the SIGWINCH handler never executes, making
*terminal-resized-p* always nil and resize detection broken.
ioctl on stdout's fd can return 80x24 even when the terminal is
larger. Add COLUMNS/LINES from the shell as a fallback. Also adds
ioctl-based sizing to simple-backend (was hardcoded 80x24).
The try-ioctl function returns (values cols rows) only when both
are valid integers > 0. or propagates complete pairs. This avoids
the nil-in-h crash from partial ioctl results.
ignore-errors + ioctl can return (values 80 nil) when the fd exists
but isn't a terminal. or propagates partial values, causing nil in
w or h. Wrap with multiple-value-bind + when to filter.
The user's terminal reports 186x60 via stty (which uses stdin fd)
and via COLUMNS/LINES, but ioctl on stdout's fd returns 80x24.
Priority: fd 0 → backend output fd → env vars → 80x24 fallback.
The previous logic (check ioctl result, prefer env when 80x24)
added complexity and crashes. Simple or with env vars after ioctl
is safe: ioctl returns 80x24 on stdout fd mismatch, env vars
(COLUMNS/LINES from shell) provide the correct initial size.
ioctl on stdout's fd can return the default 80x24 even when the
terminal is much larger (fd mismatch). The new logic:
1. Try ioctl — if it returns >80x24, trust it (correct at runtime).
2. If ioctl returned 80x24 (suspicious default), try COLUMNS/LINES
from the shell environment instead.
3. If both fail, return whatever ioctl gave us (80x24).
This fixes initial sizing on terminals where ioctl disagrees with
the real TTY size, without breaking runtime SIGWINCH resize
(which always re-queries ioctl, and that is correct after resize).
ioctl on stdout's fd can disagree with the real terminal size when
the process is started with stdout redirected or in some terminal
multiplexer configurations. / are set by every POSIX
shell at process start and reflect the actual terminal dimensions.
Priority: ioctl → env vars → 80x24 fallback.
This covers both initial sizing and dynamic SIGWINCH-driven resize.
The CSI 18t query leaks into the threaded keyboard reader because
the response arrives on stdin after the reader thread starts. The
response bytes get queued as key events and inserted as text into
the TUI input buffer. Removing the query entirely — ioctl is
sufficient for terminal size detection on all modern terminals.
The blocking read-char in %query-terminal-size could hang if the
terminal doesn't respond to CSI 18 t. Wrapped in
sb-ext:with-timeout 0.3 to abort if no response.
%query-terminal-size uses blocking read-char on an fd 0 stream
to read the terminal's response to \033[18t. This works even when
unix-simple-poll on fd 0 returns NIL (unlike read-char-no-hang).
Added as fallback in both modern and simple backends.
The earlier fix that checked unix-simple-poll before unix-read
broke input: poll on fd 0 always returns NIL in this SBCL, so
read-raw-byte always returned nil. Reverted to original: call
unix-simple-poll (for timeout) then unix-read unconditionally.
unix-read blocks until data arrives, which is correct for a TUI.
The %query-terminal-size function sent \033[18t and tried to read
the response via read-char-no-hang on fd 0, which always returns nil
in this SBCL environment. The response leaked into user input,
displaying garbled CSI sequences. Rely on ioctl only.
- Disabled \033[?u kitty keyboard protocol in modern-backend
(converts all keys to escape sequences, breaking Ctrl+letter dispatch)
- Fixed parse-csi-sequence: use multiple-value-bind instead of let*
with destructuring-bind (lost secondary return value from read-param)
- Fixed parse-csi-params format string: pass char-code of terminator
as distinct argument for ~d, keeping the character for ~C
- Added %query-terminal-size in classes.lisp: ANSI CSI 18t fallback
for terminal size detection when ioctl fails or returns zero
The original code called unix-simple-poll then unconditionally
called unix-read, ignoring the poll result. When poll returned
nil (no data), unix-read would block indefinitely. Fixed by
checking poll result: only read if poll says data is ready.
Same pattern as the draw-text array fix. Application code may call
backend-clear with a framebuffer array instead of a backend instance.
The array method clears all cells to default blank state.
- Replace make-alien unsigned-char buffer with make-array + vector-sap
to avoid SBCL alien type mismatch between signed-char and unsigned-char
- Convert timeout seconds to fixnum milliseconds for unix-simple-poll
(was passing float 0.1, broke on fixnum-typed sb-unix:to-msec)
- Both fixes make read-raw-byte work on SBCL 2.5.2.debian
Application code (passepartout TUI) calls draw-text with a framebuffer
(2D array) as the first argument, but draw-text only had methods for
framebuffer-backend CLOS instances. Added a method on array that sets
cells directly on the framebuffer array, matching make-framebuffer's
return type.
with-terminal macro was only in tangled .lisp (not .org). suspend-backend
and resume-backend generics + simple-backend methods + tests were also
in hand-edited .lisp only. All three added to org/backend-protocol.org
with proper prose, following the literate programming discipline.
Also added suspend/resume assertions to simple-backend-lifecycle test suite.