Raw mode via stty -F /dev/tty, explicit device path

stty now operates on /dev/tty explicitly (-F flag) instead of
relying on stdin inheritance.  This is more reliable in SBCL's
--script mode where stdin may be handled differently by run-program.
Also ensures stty always targets the controlling terminal regardless
of how the subprocess is spawned.
This commit is contained in:
Hermes
2026-05-12 01:40:24 +00:00
parent 2649dbeb79
commit 0ed7427802

View File

@@ -42,27 +42,31 @@
(raw nil :type (or string null))) (raw nil :type (or string null)))
;;; --------------------------------------------------------------------------- ;;; ---------------------------------------------------------------------------
;;; Terminal raw mode (stty-based — portable across Unices) ;;; Terminal raw mode (stty on /dev/tty — portable across Unices)
;;; --------------------------------------------------------------------------- ;;; ---------------------------------------------------------------------------
(defun stty-run (args)
"Run stty with ARGS on the controlling terminal. Returns stdout as string."
(with-output-to-string (s)
(sb-ext:run-program "stty" (append '("-F" "/dev/tty") args)
:output s :input :stdin :wait t)))
(defun save-terminal-state () (defun save-terminal-state ()
"Save current terminal settings via stty -g. Returns a string." "Save current terminal settings via stty -g. Returns a string."
(string-trim '(#\Newline #\Space) (let ((s (string-trim '(#\Newline #\Space) (stty-run '("-g")))))
(with-output-to-string (s) (when (zerop (length s))
(sb-ext:run-program "stty" '("-g") :output s :wait t)))) (error "stty -g failed — not running in a real terminal"))
s))
(defun set-raw-mode () (defun set-raw-mode ()
"Put terminal in raw mode via stty. Returns the saved state string." "Put terminal in raw mode via stty. Returns the saved state string."
(let ((saved (save-terminal-state))) (let ((saved (save-terminal-state)))
(when (zerop (length saved)) (stty-run '("raw" "-echo" "-isig" "-icanon" "min" "1" "time" "0"))
(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)) saved))
(defun restore-terminal-state (saved) (defun restore-terminal-state (saved)
"Restore saved terminal state (a string from stty -g, or nil)." "Restore saved terminal state (a string from stty -g, or nil)."
(when saved (when (and saved (plusp (length saved)))
(sb-ext:run-program "stty" (list saved) :wait t :input :stdin :output t))) (stty-run (list saved))))
(defmacro with-raw-terminal (&body body) (defmacro with-raw-terminal (&body body)
(let ((saved (gensym "SAVED"))) (let ((saved (gensym "SAVED")))