From 14b41831c393f046e967a02ad52b5baf1feb1575 Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Thu, 14 May 2026 09:31:09 -0400 Subject: [PATCH] fix: disable kitty keyboard, fix CSI parser crashes - Disabled \033[?u kitty keyboard protocol in modern-backend (converts all keys to escape sequences, breaking Ctrl+letter dispatch) - Fixed parse-csi-sequence: use multiple-value-bind instead of let* with destructuring-bind (lost secondary return value from read-param) - Fixed parse-csi-params format string: pass char-code of terminator as distinct argument for ~d, keeping the character for ~C - Added %query-terminal-size in classes.lisp: ANSI CSI 18t fallback for terminal size detection when ioctl fails or returns zero --- src/backend/classes.lisp | 31 +++++++++++++++++++++++++++++++ src/backend/modern.lisp | 9 ++++++--- src/backend/simple.lisp | 3 ++- src/components/input.lisp | 21 +++++++++++---------- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/backend/classes.lisp b/src/backend/classes.lisp index 677a565..c6c976d 100644 --- a/src/backend/classes.lisp +++ b/src/backend/classes.lisp @@ -1,5 +1,36 @@ (in-package :cl-tty.backend) +(defun %query-terminal-size () + "Query terminal size via ANSI CSI 18 t and parse the response. +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) + (loop with deadline = (+ (get-internal-real-time) + (* internal-time-units-per-second 0.2)) + while (< (get-internal-real-time) deadline) + do (let ((ch (read-char-no-hang 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 7ec33b7..792834b 100644 --- a/src/backend/modern.lisp +++ b/src/backend/modern.lisp @@ -127,14 +127,16 @@ as a fallback when a keyword is not in *named-colors*.") (backend-write b (format nil "~C[?1002h" #\Esc)) ; mouse drag (backend-write b (format nil "~C[?1006h" #\Esc)) ; SGR mouse (backend-write b (format nil "~C[?2004h" #\Esc)) ; bracketed paste - (backend-write b (format nil "~C[?u" #\Esc)) ; kitty keyboard + ;; Kitty keyboard protocol disabled — converts all keys to CSI u-sequences + ;; which the TUI's key mapping doesn't handle for Ctrl+letter combos. + ;; (backend-write b (format nil "~C[?u" #\Esc)) (cursor-hide b) (finish-output (backend-output-stream b)) b) (defmethod shutdown-backend ((b modern-backend)) (cursor-show b) - (backend-write b (format nil "~C[?u" #\Esc)) + ;; (backend-write b (format nil "~C[?u" #\Esc)) ; disabled — never enabled (backend-write b (format nil "~C[?2004l" #\Esc)) (backend-write b (format nil "~C[?1006l" #\Esc)) (backend-write b (format nil "~C[?1002l" #\Esc)) @@ -156,7 +158,7 @@ as a fallback when a keyword is not in *named-colors*.") (backend-write b (format nil "~C[?1002h" #\Esc)) ; mouse drag (backend-write b (format nil "~C[?1006h" #\Esc)) ; SGR mouse (backend-write b (format nil "~C[?2004h" #\Esc)) ; bracketed paste - (backend-write b (format nil "~C[?u" #\Esc)) ; kitty keyboard + ;; (backend-write b (format nil "~C[?u" #\Esc)) ; kitty keyboard — disabled (cursor-hide b) (finish-output (backend-output-stream b)) (values)) @@ -173,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 26e5f4a..e00c091 100644 --- a/src/backend/simple.lisp +++ b/src/backend/simple.lisp @@ -38,7 +38,8 @@ (sb-alien:alien-sap winsize)) (values (sb-alien:deref winsize 1) (sb-alien:deref winsize 0))) - (sb-alien:free-alien winsize)))) + (sb-alien:free-alien winsize)))) + (%query-terminal-size) (values 80 24))) (defmethod backend-write ((b simple-backend) string) diff --git a/src/components/input.lisp b/src/components/input.lisp index ce237cc..7d5f207 100644 --- a/src/components/input.lisp +++ b/src/components/input.lisp @@ -65,7 +65,8 @@ :raw (string (code-char code)))) (make-key-event :key (or key :unknown) :ctrl ctrl :alt alt :shift shift - :raw (format nil "~C[~{~d~};~d~C" #\Esc params terminator))))) + :raw (format nil "~C[~{~d~};~d~C" #\Esc params + (char-code terminator) terminator))))) (defun read-raw-byte (&key timeout) (let* ((buf (make-array 1 :element-type '(unsigned-byte 8))) @@ -162,15 +163,15 @@ Returns a mouse-event struct." (let* ((b2 (read-raw-byte))) (if (= b2 60) ;; < — SGR mouse marker (%parse-sgr-mouse) - (let* ((extended (make-array 8 :element-type 'fixnum :fill-pointer 0)) - (params (if (and (>= b2 48) (<= b2 57)) - (multiple-value-bind (p term) (read-param (lambda () (read-raw-byte))) - (setf (fill-pointer extended) (length p)) - (replace extended p) - (values p term)) - (progn (vector-push-extend b2 extended) (read-param (lambda () (read-raw-byte))))))) - (destructuring-bind (params terminator) params - (parse-csi-params params terminator extended))))))) + (let* ((extended (make-array 8 :element-type 'fixnum :fill-pointer 0))) + (multiple-value-bind (params terminator) + (if (and (>= b2 48) (<= b2 57)) + (multiple-value-bind (p term) (read-param (lambda () (read-raw-byte))) + (setf (fill-pointer extended) (length p)) + (replace extended p) + (values p term)) + (progn (vector-push-extend b2 extended) (read-param (lambda () (read-raw-byte))))) + (parse-csi-params params terminator extended))))))) (defun utf8-decode (bytes) (case (length bytes)