From 5a3b882f93074614f2f12ea06c1518839684dab4 Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Thu, 14 May 2026 10:14:40 -0400 Subject: [PATCH] fix: add blocking-read-based CSI 18t terminal size query fallback %query-terminal-size uses blocking read-char on an fd 0 stream to read the terminal's response to \033[18t. This works even when unix-simple-poll on fd 0 returns NIL (unlike read-char-no-hang). Added as fallback in both modern and simple backends. --- src/backend/classes.lisp | 32 ++++++++++++++++++++++++++++++++ src/backend/modern.lisp | 1 + src/backend/simple.lisp | 1 + 3 files changed, 34 insertions(+) diff --git a/src/backend/classes.lisp b/src/backend/classes.lisp index 677a565..1e4af64 100644 --- a/src/backend/classes.lisp +++ b/src/backend/classes.lisp @@ -1,5 +1,37 @@ (in-package :cl-tty.backend) +(defun %query-terminal-size () + "Query terminal size via ANSI CSI 18 t using blocking read. +Returns (values cols rows) or nil." + (ignore-errors + (let* ((saved (sb-sys:make-fd-stream 0 :input t :buffering :none)) + (response (make-array 0 :element-type 'character + :fill-pointer 0 :adjustable t))) + (format t "~C[18t" #\Esc) + (force-output) + ;; Blocking read-char loop — the response arrives immediately + (loop with deadline = (+ (get-internal-real-time) + (* internal-time-units-per-second 0.5)) + while (< (get-internal-real-time) deadline) + do (let ((ch (read-char saved nil nil))) + (when ch + (vector-push-extend ch response) + (when (char= ch #\t) (return))))) + (when (>= (length response) 8) + (let* ((str (subseq response 1)) + (start (or (position #\[ str) 0)) + (after (subseq str (1+ start))) + (semi (position #\; after))) + (when semi + (let* ((cols-start (1+ semi)) + (cols-end (position #\t after :start cols-start)) + (rows (parse-integer (subseq after 0 semi) :junk-allowed t)) + (cols (when cols-end + (parse-integer (subseq after cols-start cols-end) + :junk-allowed t)))) + (when (and rows cols (> rows 0) (> cols 0)) + (values cols rows))))))))) + (defclass backend () ()) (defgeneric initialize-backend (backend) diff --git a/src/backend/modern.lisp b/src/backend/modern.lisp index 4f975a8..792834b 100644 --- a/src/backend/modern.lisp +++ b/src/backend/modern.lisp @@ -175,6 +175,7 @@ as a fallback when a keyword is not in *named-colors*.") (values (sb-alien:deref winsize 1) ;; cols (sb-alien:deref winsize 0))) ;; rows (sb-alien:free-alien winsize)))) + (%query-terminal-size) (values 80 24))) (defmethod backend-write ((b modern-backend) string) diff --git a/src/backend/simple.lisp b/src/backend/simple.lisp index 0ca931f..e00c091 100644 --- a/src/backend/simple.lisp +++ b/src/backend/simple.lisp @@ -39,6 +39,7 @@ (values (sb-alien:deref winsize 1) (sb-alien:deref winsize 0))) (sb-alien:free-alien winsize)))) + (%query-terminal-size) (values 80 24))) (defmethod backend-write ((b simple-backend) string)