(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)))))