v0.12.0: Terminal capability detection, GPL 3.0 license, roadmap rewrite

LICENSE:
- Added GNU General Public License v3.0
- Updated README.org to reflect GPL 3.0

ROADMAP:
- Complete rewrite to reflect actual project state
- Removed croatoan/ncurses/Yoga FFI references
- Marked all 11 existing versions DONE
- Added v0.12.0-0.14.0 for new features (detection, pipeline, mouse)

DETECTION (v0.12.0):
- detect-backend: auto-detect modern vs simple backend
- detect-backend-by-env: check COLORTERM env var
- detect-backend-by-tty: check interactive-stream-p
- detect-backend-by-da1: query terminal via ESC[c (best-effort)
- *detected-backend* cache for zero-cost subsequent calls
- Added detection.lisp to ASDF and package exports
- Added 2 new tests (360 total, all passing)
- demo.lisp updated to use detect-backend

ORG BACKPORT (pre-existing fixes synced):
- dialog.org: render-dialog/render-toast fixes, class initforms
- scrollbox-tabbar.org: background-element -> bright-black, remove duplicate render
- select.org: remove duplicate render export
- text-input.org: remove duplicate %split-string, undo overflow fix
- layout-engine.org: quoted-literal -> list constructors, normalize-box rewrite
- mouse.org: add missing exports, fix test
This commit is contained in:
Hermes
2026-05-11 22:25:42 +00:00
parent 3ce7f9949c
commit b7df68c436
16 changed files with 1281 additions and 595 deletions

62
backend/detection.lisp Normal file
View File

@@ -0,0 +1,62 @@
(in-package :cl-tty.backend)
;;; ─── Detection cache ────────────────────────────────────────────────────────
(defvar *detected-backend* nil
"Cached backend instance from detect-backend. Nil = not yet detected.")
;;; ─── Environment probe ──────────────────────────────────────────────────────
(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)))
;;; ─── TTY probe ──────────────────────────────────────────────────────────────
(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*))
;;; ─── DA1 terminal query ─────────────────────────────────────────────────────
(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 *query-io*)
(force-output *query-io*)
(sleep timeout)
(let ((response (make-array 0 :element-type 'character
:fill-pointer 0 :adjustable t)))
(loop while (listen *query-io*)
do (vector-push-extend (read-char-no-hang *query-io*) response))
(when (plusp (length response))
response)))
(defun detect-backend-by-da1 ()
"Send DA1 (ESC[c) query and check for kitty terminal response code.
Returns T if terminal reports kitty compatibility codes."
(let ((response (query-terminal (format nil "~C[c" #\Esc))))
(when response
;; DA1 response format: ESC [ ? digits ; digits c
;; Kitty reports code 62 in the response
(search "?62" response))))
;;; ─── Orchestrator ───────────────────────────────────────────────────────────
(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)))
(make-modern-backend)
(make-simple-backend)))))

View File

@@ -21,6 +21,8 @@
#:make-simple-backend
;; Modern backend
#:modern-backend #:make-modern-backend
;; Detection
#:detect-backend #:*detected-backend*
;; Internal (for testing)
#:sgr-fg #:sgr-bg #:sgr-attr
#:cursor-move-escape #:cursor-style-escape

View File

@@ -136,3 +136,16 @@
(shutdown-backend b)
(is (string= (get-output-stream-string s) "")
"draw-rect is a no-op on simple-backend")))
;; ── Detection ──────────────────────────────────────────────────
(test detection-returns-backend-instance
"detect-backend returns a valid backend instance"
(let ((be (cl-tty.backend:detect-backend)))
(is (typep be 'cl-tty.backend:backend))))
(test detection-caches-result
"detect-backend caches the result in *detected-backend*"
(let ((*detected-backend* nil))
(cl-tty.backend:detect-backend)
(is-true (not (null cl-tty.backend::*detected-backend*)))))