Terminal Session and Raw Mode¶
This page covers the OS-facing layer that puts the terminal into the right state
for a full-screen application and reliably restores it afterward: raw mode, the
window-size query, and the with-terminal-session lifecycle helper.
SBCL only
Raw mode, terminal-size, and with-terminal-session's raw-mode path are
SBCL-specific — they use sb-posix for termios control and SBCL's ioctl
wrapper. cl-tty-kit requires SBCL throughout, so these are available on
every build that loads. See Compatibility.
Raw mode¶
By default a terminal is in cooked (canonical) mode: it buffers a line, echoes
keystrokes, and interprets control characters like C-c locally. A full-screen
application needs the opposite — every byte delivered immediately, no echo, no
signal interpretation — which is raw mode.
enable-raw-mode and disable-raw-mode toggle it for a file descriptor
(defaulting to standard input, fd 0). enable-raw-mode snapshots the current
termios settings before changing them, and disable-raw-mode restores that
snapshot exactly.
with-raw-mode¶
Prefer the scoped macro, which guarantees restoration with unwind-protect even
if the body unwinds:
(with-raw-mode (0)
;; standard input is now byte-transparent; read and decode here
...)
;; the terminal is restored to its prior settings on the way out
Reference-counted nesting¶
Nested with-raw-mode scopes, or repeated enable-raw-mode calls on the same
fd, are reference-counted. Each enable increments a depth counter; the
terminal is only restored when the outermost scope exits. This
lets independent components each request raw mode without fighting over who
restores it:
(with-raw-mode (0) ; depth 1 — terminal enters raw mode
(with-raw-mode (0) ; depth 2 — no-op, just increments
...)
...) ; inner exit — depth back to 1, still raw
;; outer exit — depth 0, terminal restored
Failure reporting¶
If a termios operation fails, both functions signal
raw-mode-operation-failed carrying the operation
(:enable or :disable), the fd, and the underlying reason condition — so
a caller can report precisely what failed on which descriptor.
More raw than cfmakeraw
The raw configuration clears a strict superset of the classic
cfmakeraw input flags — additionally disabling break handling, input
marking, stripping, and CR/NL translation. That leaves the input stream
fully byte-transparent, which is exactly what a terminal multiplexer needs
when it feeds the bytes verbatim to a child PTY. Every added flag only
removes input processing, so it remains a valid raw mode for ordinary
callers.
terminal-size¶
terminal-size queries the current window size with ioctl(TIOCGWINSZ) and
returns (values columns rows):
It returns (values nil nil) — rather than signaling — when the fd is not a
terminal, the ioctl fails, the report is zero-sized, or the platform's
TIOCGWINSZ constant is unknown. Treat nil as "size unavailable" and fall
back to a default (commonly 80×24) or a cursor-position probe:
(multiple-value-bind (cols rows) (terminal-size)
(let ((cols (or cols 80))
(rows (or rows 24)))
...))
To react to live resizes, re-query on SIGWINCH.
with-terminal-session¶
with-terminal-session is a thin composition helper that wraps a body in the
common full-screen lifecycle: enter the alternate screen, hide the cursor, flush
output, run the body, and then guarantee teardown with unwind-protect — in
reverse order — no matter how the body exits.
The macro binds a stream variable and takes keyword options:
(with-terminal-session (session :stream *standard-output*)
(write-string (render-screen screen) session))
;; on exit: cursor shown, alternate screen exited, output flushed
Options¶
| Option | Default | Effect |
|---|---|---|
:stream |
*standard-output* |
Where session escapes and body output are written. |
:alternate-screen |
t |
Enter/leave the alternate screen buffer. |
:hide-cursor |
t |
Hide the cursor for the session, restore on exit. |
:bracketed-paste |
nil |
Enable bracketed-paste markers, scoped to the session. |
:keyboard-enhancements |
nil |
Push kitty keyboard enhancement <flags>; auto-popped on cleanup. |
:raw-mode |
nil |
Wrap the whole session in with-raw-mode. |
:fd |
0 |
The fd used when :raw-mode t. |
Setup steps are attempted defensively: if one step fails, it is left un-done so its matching teardown is skipped and the body still runs. Teardown always emits the reverse sequence for whatever setup actually succeeded and flushes the stream.
Bracketed paste and keyboard enhancements¶
:bracketed-paste t turns on the DEC private-mode paste markers for the duration
of the body and disables them on exit, so paste input arrives bracketed only
while your session owns the terminal. :keyboard-enhancements <flags> pushes a
kitty keyboard progressive-enhancement scope with ansi-push-keyboard-enhancements
on entry and pops it with ansi-pop-keyboard-enhancements on exit — a clean
push/pop pair so you never leak enhancement state to the shell.
Composing with raw mode¶
Pass :raw-mode t (and, if not fd 0, :fd n) to wrap the entire session in
with-raw-mode. This is the usual full-screen setup — raw input plus alternate
screen plus hidden cursor — in one form:
(with-terminal-session (session :raw-mode t :fd 0
:bracketed-paste t
:keyboard-enhancements 1)
;; input is raw here; drive your event loop and render frames
(write-string (render-screen screen) session))
Example: a scoped session¶
examples/terminal-session.lisp renders a small screen inside a session with
bracketed paste and keyboard enhancements enabled. Note that everything —
alternate screen, cursor hide, paste markers, and enhancement flags — is scoped
to the body and torn down automatically:
(let ((screen (make-screen 18 3)))
(screen-write-string screen 0 0 "TTY" :style '(:bold))
(screen-write-string screen 4 0 "demo")
(screen-write-string screen 0 1 "Press")
(screen-write-string screen 6 1 "q to")
(screen-write-string screen 11 1 "exit")
(with-terminal-session (session :stream *standard-output*
:bracketed-paste t
:keyboard-enhancements 1)
(write-string (render-screen screen) session)))
The companion macro with-terminal-session-output captures the emitted escape
sequences into a returned string instead of writing to a live terminal, which is
handy in tests and examples.
See also¶
- PTY — spawning and driving a child process under a PTY.
- Screen and Rendering — the
render-screen/render-diffoutput you emit inside a session. - ANSI Helpers — the individual escape builders
with-terminal-sessioncomposes. - Conditions —
raw-mode-operation-failedand the condition hierarchy. - Compatibility — the SBCL-only rationale.