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

View File

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