Replace sb-posix:termios raw mode with stty-based approach

set-raw-mode now uses (stty raw -echo ...) via sb-ext:run-program
instead of sb-posix:tcgetattr/tcsetattr + termios flag manipulation.
The sb-posix termios API changed between SBCL versions (termios-cc
accessor went from 2-arg to 1-arg), and tcgetattr fails in some
container/PTY environments.

Stty is available on every Unix and is independent of SBCL's
sb-posix version.  set-raw-mode errors if stty -g returns empty
(no real terminal attached).  restore-terminal-state is a no-op
when called with nil.
This commit is contained in:
Hermes
2026-05-12 01:35:25 +00:00
parent 4594d40a9c
commit 2649dbeb79

View File

@@ -42,39 +42,27 @@
(raw nil :type (or string null)))
;;; ---------------------------------------------------------------------------
;;; Terminal raw mode
;;; Terminal raw mode (stty-based — portable across Unices)
;;; ---------------------------------------------------------------------------
(defun save-terminal-state ()
(sb-posix:tcgetattr 0))
(defun make-raw-termios (termios)
(flet ((clear-flag (flags mask)
(logand flags (lognot mask))))
(setf (sb-posix:termios-iflag termios)
(clear-flag (sb-posix:termios-iflag termios)
(logior sb-posix:brkint sb-posix:ignpar
sb-posix:istrip sb-posix:inlcr
sb-posix:igncr sb-posix:icrnl
sb-posix:ixon)))
(setf (sb-posix:termios-oflag termios)
(clear-flag (sb-posix:termios-oflag termios)
sb-posix:opost))
(setf (sb-posix:termios-lflag termios)
(clear-flag (sb-posix:termios-lflag termios)
(logior sb-posix:icanon sb-posix:echo
sb-posix:isig sb-posix:iexten)))
(let ((cc-array (sb-posix:termios-cc termios)))
(setf (aref cc-array sb-posix:vmin) 1)
(setf (aref cc-array sb-posix:vtime) 0))
termios))
"Save current terminal settings via stty -g. Returns a string."
(string-trim '(#\Newline #\Space)
(with-output-to-string (s)
(sb-ext:run-program "stty" '("-g") :output s :wait t))))
(defun set-raw-mode ()
(let ((raw (make-raw-termios (save-terminal-state))))
(sb-posix:tcsetattr 0 sb-posix:tcsanow raw)
raw))
"Put terminal in raw mode via stty. Returns the saved state string."
(let ((saved (save-terminal-state)))
(when (zerop (length saved))
(error "stty -g failed: stdout is not a terminal"))
(sb-ext:run-program "stty" '("raw" "-echo" "isig" "icanon" "min" "1" "time" "0")
:wait t :input :stdin :output t)
saved))
(defun restore-terminal-state (termios)
(sb-posix:tcsetattr 0 sb-posix:tcsanow termios))
(defun restore-terminal-state (saved)
"Restore saved terminal state (a string from stty -g, or nil)."
(when saved
(sb-ext:run-program "stty" (list saved) :wait t :input :stdin :output t)))
(defmacro with-raw-terminal (&body body)
(let ((saved (gensym "SAVED")))