Files
cl-tty/src/backend/detection.lisp
Amr Gharbeia e8b37f6268 fix: add CSI positioning and ioctl sizing to simple-backend
- backend-size now uses TIOCGWINSZ ioctl (like modern-backend)
- draw-text adds \033[row;colH CSI cursor positioning
- draw-rect fills background with space characters at position
- draw-border uses CSI positioning instead of raw newlines+spaces
- Added cursor-hide/cursor-show, cursor-move, initialize/shutdown
- Detection: broader DA1 check (any ANSI response, not just kitty)
- Detection: added TERM-based fallback for modern terminal detection
2026-05-14 08:55:56 -04:00

67 lines
2.8 KiB
Common Lisp

(in-package :cl-tty.backend)
(defvar *detected-backend* nil
"Cached backend instance from detect-backend. Nil = not yet detected.")
(defun detect-backend-by-env ()
"Check COLORTERM environment variable for modern terminal support.
Returns :modern if COLORTERM contains 'truecolor' or '24bit', nil otherwise."
(let ((colorterm (sb-ext:posix-getenv "COLORTERM")))
(when (and colorterm
(or (search "truecolor" colorterm :test #'char-equal)
(search "24bit" colorterm :test #'char-equal)))
:modern)))
(defun detect-backend-by-tty ()
"Check if stdout is a real terminal (not a pipe/redirect).
Returns T if stdout is interactive, nil otherwise."
(interactive-stream-p *standard-output*))
(defun query-terminal (query &optional (timeout 0.1))
"Send QUERY string to terminal and return any response received within
TIMEOUT seconds. Returns the response string, or nil if no response."
(write-string query *standard-output*)
(force-output *standard-output*)
(sleep timeout)
(let ((response (make-array 0 :element-type 'character
:fill-pointer 0 :adjustable t)))
(loop while (listen *standard-input*)
do (vector-push-extend (read-char-no-hang *standard-input*) response))
(when (plusp (length response))
response)))
(defun detect-backend-by-da1 ()
"Send DA1 (ESC[c) query and check for any terminal response.
Returns T if the terminal responds to DA1 (indicating an ANSI-compatible terminal)."
(let ((response (query-terminal (format nil "~C[c" (code-char 27)))))
(when response
;; Any DA1 response (ESC [ ? digits ... c) means the terminal
;; understands ANSI escape sequences — good enough for modern-backend
(> (length response) 0))))
(defun detect-backend-by-term ()
"Check TERM environment variable for modern terminal types.
Returns :modern if TERM contains xterm, tmux, screen, kitty,
alacritty, foot, wezterm, or ghostty."
(let ((term (sb-ext:posix-getenv "TERM")))
(when term
;; Known non-modern terms
(unless (or (string-equal term "dumb")
(string-equal term "dump")
(string-equal term "emacs")
(search "52" term)) ; VT52 — no ANSI
:modern))))
(defun detect-backend ()
"Auto-detect the appropriate backend for the current terminal.
Returns a backend instance (modern-backend or simple-backend).
Result is cached in *detected-backend* for subsequent calls."
(or *detected-backend*
(setf *detected-backend*
(if (and (detect-backend-by-tty)
(or (eql (detect-backend-by-env) :modern)
(detect-backend-by-da1)
(eql (detect-backend-by-term) :modern)))
(make-modern-backend)
(make-simple-backend)))))