From eedf065e6ed6c5867bcb2a22783fe17e9b12b239 Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Thu, 14 May 2026 14:41:45 -0400 Subject: [PATCH] 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. --- src/backend/modern.lisp | 27 +++++++++++++-------------- src/backend/simple.lisp | 24 ++++++++++++------------ 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/backend/modern.lisp b/src/backend/modern.lisp index db4c992..02a7441 100644 --- a/src/backend/modern.lisp +++ b/src/backend/modern.lisp @@ -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))))) + (ignore-errors + (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. diff --git a/src/backend/simple.lisp b/src/backend/simple.lisp index 3a76769..f67e65d 100644 --- a/src/backend/simple.lisp +++ b/src/backend/simple.lisp @@ -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))))) + (ignore-errors + (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.