fix: use :input :inherit for stty size subprocess

: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.
This commit is contained in:
2026-05-14 14:41:45 -04:00
parent 21c7b1c2d9
commit eedf065e6e
2 changed files with 25 additions and 26 deletions

View File

@@ -162,21 +162,20 @@ as a fallback when a keyword is not in *named-colors*.")
(values))
(defmethod backend-size ((b modern-backend))
(or ;; tput lines / tput cols via subprocess — most reliable across
;; all systems. Each outputs a single number. Works in any
;; subprocess context, no parsing complexity.
(or ;; stty size with :input :inherit — preserves the parent's fd 0
;; (the terminal) so stty can query it via ioctl.
(multiple-value-bind (cols rows)
(values
(ignore-errors
(parse-integer
(string-trim '(#\newline #\space)
(uiop:run-program '("tput" "cols") :output :string
:ignore-error-status t))))
(ignore-errors
(parse-integer
(string-trim '(#\newline #\space)
(uiop:run-program '("tput" "lines") :output :string
:ignore-error-status t)))))
(let* ((out (uiop:run-program '("stty" "size") :output :string
:input :inherit
:ignore-error-status t))
(parts (and out (uiop:split-string
(string-trim '(#\newline #\space) out)))))
(when (and parts (= (length parts) 2))
(let ((r (parse-integer (first parts) :junk-allowed t))
(c (parse-integer (second parts) :junk-allowed t)))
(when (and r c (> r 0) (> c 0))
(values c r))))))
(when (and cols rows (> cols 0) (> rows 0))
(values cols rows)))
;; ioctl on stdout fd — fast, correct after SIGWINCH at runtime.

View File

@@ -22,19 +22,19 @@
(values))
(defmethod backend-size ((b simple-backend))
(or ;; tput cols / tput lines via subprocess
(or ;; stty size with :input :inherit — preserves parent's fd 0.
(multiple-value-bind (cols rows)
(values
(ignore-errors
(parse-integer
(string-trim '(#\newline #\space)
(uiop:run-program '("tput" "cols") :output :string
:ignore-error-status t))))
(ignore-errors
(parse-integer
(string-trim '(#\newline #\space)
(uiop:run-program '("tput" "lines") :output :string
:ignore-error-status t)))))
(let* ((out (uiop:run-program '("stty" "size") :output :string
:input :inherit
:ignore-error-status t))
(parts (and out (uiop:split-string
(string-trim '(#\newline #\space) out)))))
(when (and parts (= (length parts) 2))
(let ((r (parse-integer (first parts) :junk-allowed t))
(c (parse-integer (second parts) :junk-allowed t)))
(when (and r c (> r 0) (> c 0))
(values c r))))))
(when (and cols rows (> cols 0) (> rows 0))
(values cols rows)))
;; ioctl on stdout fd — fast, correct after SIGWINCH at runtime.