v0.15.1: EOF/Escape fixes, box title rendering, full feature verification
Bug fixes:
- read-raw-byte now returns (values nil :eof) on stdin EOF
instead of just nil, so callers can distinguish EOF from
timeout. Previously, non-TTY stdin (pipes, /dev/null)
caused a busy-spin: sb-posix:read returned 0 immediately,
read-raw-byte returned nil, the demo loop treated nil as
'no event yet' and spun at 100% CPU producing 86MB of
repeated rendering frames.
- %read-escape-sequence now uses a 50ms timeout on the first
follow-up byte to resolve the classic Escape-key ambiguity:
a lone Escape press returned an :escape key-event instead of
blocking indefinitely on VMIN=1 VTIME=0. All callers
(SS3, CSI, Alt+char) propagate :eof instead of faking
:escape events when EOF occurs mid-sequence.
- parse-csi-params now uses multiple-value-bind on read-raw-byte
to preserve the :eof signal through CSI parsing.
- simple-backend draw-border now renders :title on the top
edge instead of declaring it (ignore). The title was
silently swallowed — the box rendered with the right border
frame but the title text was never written.
- demo.lisp: removed 'q' as quit key (conflicted with text
input). Only Esc and Ctrl+C quit. Widget event forwarding
scoped to tab 1 (Widgets tab). EOF handling in main loop.
- Stale help text (still said 'q/esc: quit') updated.
Verification infrastructure:
- PTY-based demo test (17 checks) spawns the demo in a real
pseudo-terminal, sends actual keystrokes, reads terminal
output back. Verifies: startup rendering, tab switching,
key dispatch, 'q' doesn't quit, Escape quits via timeout,
Ctrl+C quits, EOF clean exit, no busy-spin.
- API feature verification (29 checks) exercises every major
component through the actual exported API: Simple backend,
Box with title, Text attributes, draw-rect, TextInput
(insert/backspace/cursor/Ctrl-A/E), TextArea, key/mouse
events, Layout flex, Markdown, Theme presets (dark/light/
nord), Select filtering, Dialog stack, Mouse hit-test,
Framebuffer, Dirty tracking, Modern backend, draw-ellipsis/
draw-link, Render dispatch, Detection, Capabilities.
- Testing pattern saved as skill (tui-pty-testing) for reuse.
Unit tests: 392/392 passing. All 12 test suites green.
This commit is contained in:
@@ -59,7 +59,7 @@ class. Programs never call terminal codes directly:
|
|||||||
(draw-link backend x y string url &key fg bg)
|
(draw-link backend x y string url &key fg bg)
|
||||||
|
|
||||||
;; Input
|
;; Input
|
||||||
(read-event backend &key timeout) → key-event or mouse-event
|
(read-event backend &key timeout) → key-event, mouse-event, :eof, or nil
|
||||||
(backend-size backend) → (values columns lines)
|
(backend-size backend) → (values columns lines)
|
||||||
|
|
||||||
;; Cursor
|
;; Cursor
|
||||||
@@ -86,7 +86,8 @@ class. Programs never call terminal codes directly:
|
|||||||
(setf running nil)))
|
(setf running nil)))
|
||||||
(mouse-event
|
(mouse-event
|
||||||
;; handle mouse
|
;; handle mouse
|
||||||
))))
|
))
|
||||||
|
(when (eq event :eof) (setf running nil))))
|
||||||
(shutdown-backend be))
|
(shutdown-backend be))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -41,14 +41,24 @@ POS is :top-left, :top-right, :bottom-left, :bottom-right,
|
|||||||
|
|
||||||
(defmethod draw-border ((b simple-backend) x y width height
|
(defmethod draw-border ((b simple-backend) x y width height
|
||||||
&key style fg bg title title-align)
|
&key style fg bg title title-align)
|
||||||
(declare (ignore style fg bg title title-align))
|
(declare (ignore style fg bg title-align))
|
||||||
(let ((h (%simple-border-char nil :horizontal))
|
(let ((h (%simple-border-char nil :horizontal))
|
||||||
(v (%simple-border-char nil :vertical)))
|
(v (%simple-border-char nil :vertical)))
|
||||||
;; Position cursor with newlines and spaces (no escape sequences)
|
;; Position cursor with newlines and spaces (no escape sequences)
|
||||||
(dotimes (row y) (backend-write b (string #\Newline)))
|
(dotimes (row y) (backend-write b (string #\Newline)))
|
||||||
;; Top edge
|
;; Top edge with optional title
|
||||||
(backend-write b (make-string x :initial-element #\space))
|
(backend-write b (make-string x :initial-element #\space))
|
||||||
(backend-write b (make-string width :initial-element h))
|
(if title
|
||||||
|
(let* ((tlen (length title))
|
||||||
|
(space-left (- width tlen 2))
|
||||||
|
(left (max 0 (floor space-left 2)))
|
||||||
|
(right (max 0 (- space-left left))))
|
||||||
|
(backend-write b (make-string left :initial-element h))
|
||||||
|
(backend-write b (string #\space))
|
||||||
|
(backend-write b title)
|
||||||
|
(backend-write b (string #\space))
|
||||||
|
(backend-write b (make-string right :initial-element h)))
|
||||||
|
(backend-write b (make-string width :initial-element h)))
|
||||||
;; Sides
|
;; Sides
|
||||||
(loop for i from 1 below (1- height)
|
(loop for i from 1 below (1- height)
|
||||||
do (backend-write b (string #\Newline))
|
do (backend-write b (string #\Newline))
|
||||||
|
|||||||
20
demo.lisp
20
demo.lisp
@@ -50,7 +50,7 @@
|
|||||||
(draw-text backend (+ x 2) (+ y 10) " deps: zero FFI, zero ncurses, pure CL" :bright-cyan nil)
|
(draw-text backend (+ x 2) (+ y 10) " deps: zero FFI, zero ncurses, pure CL" :bright-cyan nil)
|
||||||
(draw-text backend (+ x 2) (+ y 12) "Controls" :bright-white nil :bold t)
|
(draw-text backend (+ x 2) (+ y 12) "Controls" :bright-white nil :bold t)
|
||||||
(draw-text backend (+ x 2) (+ y 13) " Tab / arrows switch tabs" nil nil)
|
(draw-text backend (+ x 2) (+ y 13) " Tab / arrows switch tabs" nil nil)
|
||||||
(draw-text backend (+ x 2) (+ y 14) " q / Ctrl+C / Esc quit" nil nil)
|
(draw-text backend (+ x 2) (+ y 14) " Ctrl+C / Esc quit" nil nil)
|
||||||
(draw-text backend (+ x 2) (+ y 15) " mouse click/drag select text (test SGR mouse)" nil nil))
|
(draw-text backend (+ x 2) (+ y 15) " mouse click/drag select text (test SGR mouse)" nil nil))
|
||||||
|
|
||||||
(defun render-tab-widgets (backend x y w h input ta)
|
(defun render-tab-widgets (backend x y w h input ta)
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
(ctrl (key-event-ctrl event)))
|
(ctrl (key-event-ctrl event)))
|
||||||
(log-append "Key: ~a (ctrl=~a alt=~a shift=~a)" key ctrl (key-event-alt event) (key-event-shift event))
|
(log-append "Key: ~a (ctrl=~a alt=~a shift=~a)" key ctrl (key-event-alt event) (key-event-shift event))
|
||||||
(cond
|
(cond
|
||||||
((or (eql key :|Q|) (and ctrl (eql key :|C|)) (eql key :escape))
|
((or (and ctrl (eql key :|C|)) (eql key :escape))
|
||||||
(setf (getf *app* :running) nil) t)
|
(setf (getf *app* :running) nil) t)
|
||||||
((eql key :tab)
|
((eql key :tab)
|
||||||
(incf (getf *app* :tab))
|
(incf (getf *app* :tab))
|
||||||
@@ -108,10 +108,11 @@
|
|||||||
((eql key :right)
|
((eql key :right)
|
||||||
(incf (getf *app* :tab))
|
(incf (getf *app* :tab))
|
||||||
(when (> (getf *app* :tab) 2) (setf (getf *app* :tab) 0)) t)
|
(when (> (getf *app* :tab) 2) (setf (getf *app* :tab) 0)) t)
|
||||||
;; Forward key to widgets for testing
|
;; Forward key to widgets only when on the Widgets tab
|
||||||
(t (handle-text-input (getf *app* :input) event)
|
(t (when (= (getf *app* :tab) 1)
|
||||||
(handle-textarea-input (getf *app* :textarea) event)
|
(handle-text-input (getf *app* :input) event)
|
||||||
t))))
|
(handle-textarea-input (getf *app* :textarea) event))
|
||||||
|
t))))
|
||||||
(mouse-event
|
(mouse-event
|
||||||
(log-append "Mouse: ~a btn=~a pos=(~d,~d)" (mouse-event-type event)
|
(log-append "Mouse: ~a btn=~a pos=(~d,~d)" (mouse-event-type event)
|
||||||
(mouse-event-button event) (mouse-event-x event) (mouse-event-y event))
|
(mouse-event-button event) (mouse-event-x event) (mouse-event-y event))
|
||||||
@@ -133,7 +134,7 @@
|
|||||||
(backend-clear backend)
|
(backend-clear backend)
|
||||||
;; Title bar
|
;; Title bar
|
||||||
(draw-border backend 2 1 (- w 4) 3 :style :double :title " cl-tty v0.15.0 ")
|
(draw-border backend 2 1 (- w 4) 3 :style :double :title " cl-tty v0.15.0 ")
|
||||||
(draw-text backend 4 2 "arrows/tab: tabs type: test input mouse: test SGR q/esc: quit"
|
(draw-text backend 4 2 "arrows/tab: tabs type: test input mouse: test SGR Esc/Ctrl+C: quit"
|
||||||
:bright-white nil)
|
:bright-white nil)
|
||||||
;; Tab bar
|
;; Tab bar
|
||||||
(loop for (label . idx) in '((" Home " . 0) (" Widgets " . 1) (" Console " . 2))
|
(loop for (label . idx) in '((" Home " . 0) (" Widgets " . 1) (" Console " . 2))
|
||||||
@@ -164,8 +165,9 @@
|
|||||||
(finish-output *standard-output*)
|
(finish-output *standard-output*)
|
||||||
;; Read event — blocks until a key or mouse event arrives
|
;; Read event — blocks until a key or mouse event arrives
|
||||||
(let ((event (read-event backend)))
|
(let ((event (read-event backend)))
|
||||||
(when event
|
(cond
|
||||||
(handle-event event))))
|
((eq event :eof) (setf (getf *app* :running) nil))
|
||||||
|
(event (handle-event event)))))
|
||||||
(shutdown-backend backend))))
|
(shutdown-backend backend))))
|
||||||
|
|
||||||
(run-demo)
|
(run-demo)
|
||||||
|
|||||||
@@ -57,9 +57,10 @@ SBCL's ~sb-posix~ provides the POSIX terminal APIs (~tcgetattr~,
|
|||||||
~with-raw-terminal &body body~ — macro. Save → set raw → body → restore
|
~with-raw-terminal &body body~ — macro. Save → set raw → body → restore
|
||||||
(via ~unwind-protect~).
|
(via ~unwind-protect~).
|
||||||
|
|
||||||
~read-raw-byte &key timeout~ → byte or NIL.
|
~read-raw-byte &key timeout~ → (values byte-or-nil reason).
|
||||||
Read one byte from fd 0. Blocks indefinitely when timeout=NIL.
|
Read one byte from fd 0. Blocks indefinitely when timeout=NIL.
|
||||||
Returns NIL on timeout. Uses ~sb-posix:read~.
|
Returns (values byte NIL) on success, (values NIL :TIMEOUT) on timeout,
|
||||||
|
(values NIL :EOF) when stdin is closed or /dev/null.
|
||||||
|
|
||||||
~parse-csi-params~ → (values params final-byte raw-string).
|
~parse-csi-params~ → (values params final-byte raw-string).
|
||||||
Read bytes from stdin until a final CSI byte (0x40-0x7E).
|
Read bytes from stdin until a final CSI byte (0x40-0x7E).
|
||||||
@@ -70,14 +71,17 @@ SBCL's ~sb-posix~ provides the POSIX terminal APIs (~tcgetattr~,
|
|||||||
Converts button codes (0=left, 1=middle, 2=right, 32=motion)
|
Converts button codes (0=left, 1=middle, 2=right, 32=motion)
|
||||||
and tracks press vs release vs drag.
|
and tracks press vs release vs drag.
|
||||||
|
|
||||||
~%read-escape-sequence~ → key-event.
|
~%read-escape-sequence~ → key-event or :eof.
|
||||||
Called after reading ESC (0x1b). Dispatches:
|
Called after reading ESC (0x1b). Uses a 50ms timeout on the first
|
||||||
|
follow-up byte to resolve Escape ambiguity (lone Escape vs start of
|
||||||
|
CSI/SS3 sequence). Dispatches:
|
||||||
|
- timeout → :escape key event
|
||||||
- ESC O X → SS3 (F1-F4)
|
- ESC O X → SS3 (F1-F4)
|
||||||
- ESC [ ... → CSI (cursors, function keys, mouse)
|
- ESC [ ... → CSI (cursors, function keys, mouse)
|
||||||
- ESC ESC → Alt+Escape
|
- ESC ESC → Alt+Escape
|
||||||
- ESC printable → Alt+letter
|
- ESC printable → Alt+letter
|
||||||
|
|
||||||
~%read-event &key timeout~ → key-event, mouse-event, or NIL.
|
~%read-event &key timeout~ → key-event, mouse-event, :eof, or NIL.
|
||||||
Top-level reader. Handles:
|
Top-level reader. Handles:
|
||||||
- Printable ASCII (0x20-0x7e) → key :A, :B, ..., :~
|
- Printable ASCII (0x20-0x7e) → key :A, :B, ..., :~
|
||||||
- Ctrl letters (0x01-0x1a) → :A with ctrl=T
|
- Ctrl letters (0x01-0x1a) → :A with ctrl=T
|
||||||
|
|||||||
@@ -82,27 +82,33 @@
|
|||||||
;;; Low-level byte reading
|
;;; Low-level byte reading
|
||||||
;;; ---------------------------------------------------------------------------
|
;;; ---------------------------------------------------------------------------
|
||||||
(defun read-raw-byte (&key timeout)
|
(defun read-raw-byte (&key timeout)
|
||||||
|
"Read one raw byte from stdin.
|
||||||
|
Returns:
|
||||||
|
(values byte nil) on success (byte is 0-255)
|
||||||
|
(values nil :timeout) on timeout
|
||||||
|
(values nil :eof) on EOF (stdin closed or /dev/null)"
|
||||||
(flet ((read-one ()
|
(flet ((read-one ()
|
||||||
(let ((buf (make-array 1 :element-type '(unsigned-byte 8))))
|
(let ((buf (make-array 1 :element-type '(unsigned-byte 8))))
|
||||||
;; Use sb-sys:with-pinned-objects so sb-posix:read can access the buffer
|
;; Use sb-sys:with-pinned-objects so sb-posix:read can access the buffer
|
||||||
(sb-sys:with-pinned-objects (buf)
|
(sb-sys:with-pinned-objects (buf)
|
||||||
(let ((n (sb-posix:read 0 (sb-sys:vector-sap buf) 1)))
|
(let ((n (sb-posix:read 0 (sb-sys:vector-sap buf) 1)))
|
||||||
(when (plusp n)
|
(cond
|
||||||
(return-from read-raw-byte (aref buf 0))))))))
|
((plusp n) (return-from read-raw-byte (aref buf 0)))
|
||||||
|
((zerop n) (return-from read-raw-byte (values nil :eof)))))))))
|
||||||
(if timeout
|
(if timeout
|
||||||
(let ((deadline (+ (get-universal-time) timeout)))
|
(let ((deadline (+ (get-universal-time) timeout)))
|
||||||
(loop while (< (get-universal-time) deadline)
|
(loop while (< (get-universal-time) deadline)
|
||||||
do (handler-case
|
do (handler-case
|
||||||
(read-one)
|
(read-one)
|
||||||
(sb-posix:syscall-error ()
|
(sb-posix:syscall-error ()
|
||||||
(return-from read-raw-byte nil)))
|
(return-from read-raw-byte (values nil :timeout))))
|
||||||
(sleep 0.01))
|
(sleep 0.01))
|
||||||
nil)
|
(values nil :timeout))
|
||||||
(handler-case
|
(handler-case
|
||||||
(read-one)
|
(read-one)
|
||||||
(sb-posix:syscall-error (e)
|
(sb-posix:syscall-error (e)
|
||||||
(format *error-output* "read error: ~A~%" e)
|
(format *error-output* "read error: ~A~%" e)
|
||||||
nil)))))
|
(values nil :eof))))))
|
||||||
|
|
||||||
;;; ---------------------------------------------------------------------------
|
;;; ---------------------------------------------------------------------------
|
||||||
;;; CSI parameter parser
|
;;; CSI parameter parser
|
||||||
@@ -113,8 +119,12 @@
|
|||||||
:fill-pointer 0 :adjustable t))
|
:fill-pointer 0 :adjustable t))
|
||||||
(current 0))
|
(current 0))
|
||||||
(loop
|
(loop
|
||||||
(let ((b (read-raw-byte)))
|
(multiple-value-bind (b reason) (read-raw-byte)
|
||||||
(unless b (return (values nil nil nil)))
|
(unless b
|
||||||
|
(return-from parse-csi-params
|
||||||
|
(if (eq reason :eof)
|
||||||
|
(values nil nil :eof)
|
||||||
|
(values nil nil nil))))
|
||||||
(vector-push-extend b raw)
|
(vector-push-extend b raw)
|
||||||
(cond
|
(cond
|
||||||
((and (>= b #x30) (<= b #x3f))
|
((and (>= b #x30) (<= b #x3f))
|
||||||
@@ -186,10 +196,15 @@
|
|||||||
;;; Escape sequence reader
|
;;; Escape sequence reader
|
||||||
;;; ---------------------------------------------------------------------------
|
;;; ---------------------------------------------------------------------------
|
||||||
(defun %read-escape-sequence ()
|
(defun %read-escape-sequence ()
|
||||||
(let ((b (read-raw-byte)))
|
"Read the remainder of an escape sequence after the initial ESC (0x1b).
|
||||||
|
Uses a 50ms timeout on the first follow-up byte to resolve the classic
|
||||||
|
Escape ambiguity: a lone Escape press returns immediately as an :escape
|
||||||
|
key event rather than blocking indefinitely."
|
||||||
|
(multiple-value-bind (b reason) (read-raw-byte :timeout 0.05)
|
||||||
(unless b
|
(unless b
|
||||||
(return-from %read-escape-sequence
|
(return-from %read-escape-sequence
|
||||||
(make-key-event :key :escape :raw (string #\Esc))))
|
(if (eq reason :eof) :eof
|
||||||
|
(make-key-event :key :escape :raw (string #\Esc)))))
|
||||||
(case b
|
(case b
|
||||||
;; SS3: ESC O X
|
;; SS3: ESC O X
|
||||||
(#x4f
|
(#x4f
|
||||||
@@ -200,59 +215,64 @@
|
|||||||
(#\R . :f3) (#\S . :f4))))))
|
(#\R . :f3) (#\S . :f4))))))
|
||||||
(make-key-event :key (or key :unknown)
|
(make-key-event :key (or key :unknown)
|
||||||
:raw (format nil "~C~C~C" #\Esc #\O (code-char b2))))
|
:raw (format nil "~C~C~C" #\Esc #\O (code-char b2))))
|
||||||
(make-key-event :key :escape :raw (string #\Esc)))))
|
:eof)))
|
||||||
;; CSI: ESC [ ...
|
;; CSI: ESC [ ...
|
||||||
(#x5b
|
(#x5b
|
||||||
(multiple-value-bind (params final-byte raw) (parse-csi-params)
|
(multiple-value-bind (params final-byte raw) (parse-csi-params)
|
||||||
(if (null final-byte)
|
(cond
|
||||||
(make-key-event :key :escape :raw (string #\Esc))
|
((null final-byte)
|
||||||
;; SGR mouse: ESC [ < ... m/M
|
;; EOF during CSI parsing — propagate it
|
||||||
(if (and raw (plusp (length raw)) (char= (char raw 0) #\<))
|
(if (eq raw :eof)
|
||||||
(or (parse-sgr-mouse raw)
|
:eof
|
||||||
(make-key-event :key :unknown :raw raw))
|
(make-key-event :key :escape :raw (string #\Esc))))
|
||||||
(if (and (char= (code-char final-byte) #\M)
|
;; SGR mouse: ESC [ < ... m/M
|
||||||
(>= (length params) 3))
|
((and raw (plusp (length raw)) (char= (char raw 0) #\<))
|
||||||
(let* ((p0 (first params)))
|
(or (parse-sgr-mouse raw)
|
||||||
(if (zerop (logand p0 #x40))
|
(make-key-event :key :unknown :raw raw)))
|
||||||
(let* ((x (second params))
|
((and (char= (code-char final-byte) #\M)
|
||||||
(y (third params))
|
(>= (length params) 3))
|
||||||
(button (logand p0 #x03))
|
(let* ((p0 (first params)))
|
||||||
(motion (logand p0 #x20))
|
(if (zerop (logand p0 #x40))
|
||||||
(release (= button 3)))
|
(let* ((x (second params))
|
||||||
(make-mouse-event
|
(y (third params))
|
||||||
:type (cond (release :release)
|
(button (logand p0 #x03))
|
||||||
(motion :drag)
|
(motion (logand p0 #x20))
|
||||||
(t :press))
|
(release (= button 3)))
|
||||||
:button (let ((b button)) (cond ((= b 0) :left) ((= b 1) :middle) ((= b 2) :right) (t :none)))
|
(make-mouse-event
|
||||||
:x x :y y :raw (format nil "~C[<~d;~d;~d~C" #\Esc p0 x y (code-char final-byte))))
|
:type (cond (release :release)
|
||||||
(let* ((tilde-p (char= (code-char final-byte) #\~))
|
(motion :drag)
|
||||||
(param (or p0 0))
|
(t :press))
|
||||||
(key (if tilde-p
|
:button (let ((b button)) (cond ((= b 0) :left) ((= b 1) :middle) ((= b 2) :right) (t :none)))
|
||||||
(cdr (assoc param *csi-tilde-table*))
|
:x x :y y :raw (format nil "~C[<~d;~d;~d~C" #\Esc p0 x y (code-char final-byte))))
|
||||||
(cdr (assoc (code-char final-byte) *csi-key-table*))))
|
(let* ((tilde-p (char= (code-char final-byte) #\~))
|
||||||
(modifier (when (> (length params) 1) (second params))))
|
(param (or p0 0))
|
||||||
(let ((ctrl nil) (alt nil) (shift nil))
|
(key (if tilde-p
|
||||||
(when modifier
|
(cdr (assoc param *csi-tilde-table*))
|
||||||
(setf shift (logtest modifier 1)
|
(cdr (assoc (code-char final-byte) *csi-key-table*))))
|
||||||
alt (logtest modifier 2)
|
(modifier (when (> (length params) 1) (second params))))
|
||||||
ctrl (logtest modifier 4)))
|
(let ((ctrl nil) (alt nil) (shift nil))
|
||||||
(make-key-event :key (or key :unknown)
|
(when modifier
|
||||||
:ctrl ctrl :alt alt :shift shift
|
(setf shift (logtest modifier 1)
|
||||||
:raw (format nil "~C[~d~C" #\Esc param (code-char final-byte))))))
|
alt (logtest modifier 2)
|
||||||
(let* ((tilde-p (char= (code-char final-byte) #\~))
|
ctrl (logtest modifier 4)))
|
||||||
(param (or (first params) 0))
|
(make-key-event :key (or key :unknown)
|
||||||
(key (if tilde-p
|
:ctrl ctrl :alt alt :shift shift
|
||||||
(cdr (assoc param *csi-tilde-table*))
|
:raw (format nil "~C[~d~C" #\Esc param (code-char final-byte)))))))
|
||||||
(cdr (assoc (code-char final-byte) *csi-key-table*))))
|
(t
|
||||||
(modifier (when (> (length params) 1) (second params))))
|
(let* ((tilde-p (char= (code-char final-byte) #\~))
|
||||||
(let ((ctrl nil) (alt nil) (shift nil))
|
(param (or (first params) 0))
|
||||||
(when modifier
|
(key (if tilde-p
|
||||||
(setf shift (logtest modifier 1)
|
(cdr (assoc param *csi-tilde-table*))
|
||||||
alt (logtest modifier 2)
|
(cdr (assoc (code-char final-byte) *csi-key-table*))))
|
||||||
ctrl (logtest modifier 4)))
|
(modifier (when (> (length params) 1) (second params))))
|
||||||
(make-key-event :key (or key :unknown)
|
(let ((ctrl nil) (alt nil) (shift nil))
|
||||||
:ctrl ctrl :alt alt :shift shift
|
(when modifier
|
||||||
:raw (format nil "~C[~d~C" #\Esc param (code-char final-byte)))))))))))
|
(setf shift (logtest modifier 1)
|
||||||
|
alt (logtest modifier 2)
|
||||||
|
ctrl (logtest modifier 4)))
|
||||||
|
(make-key-event :key (or key :unknown)
|
||||||
|
:ctrl ctrl :alt alt :shift shift
|
||||||
|
:raw (format nil "~C[~d~C" #\Esc param (code-char final-byte))))))))))
|
||||||
;; ESC ESC
|
;; ESC ESC
|
||||||
(#x1b
|
(#x1b
|
||||||
(make-key-event :key :escape :alt t :raw "\\e\\e"))
|
(make-key-event :key :escape :alt t :raw "\\e\\e"))
|
||||||
@@ -270,9 +290,9 @@
|
|||||||
;;; Top-level event reader
|
;;; Top-level event reader
|
||||||
;;; ---------------------------------------------------------------------------
|
;;; ---------------------------------------------------------------------------
|
||||||
(defun %read-event (&key timeout)
|
(defun %read-event (&key timeout)
|
||||||
(let ((b (read-raw-byte :timeout timeout)))
|
(multiple-value-bind (b reason) (read-raw-byte :timeout timeout)
|
||||||
(unless b
|
(unless b
|
||||||
(return-from %read-event nil))
|
(return-from %read-event (if (eq reason :eof) :eof nil)))
|
||||||
(cond
|
(cond
|
||||||
((= b #x1b)
|
((= b #x1b)
|
||||||
(%read-escape-sequence))
|
(%read-escape-sequence))
|
||||||
|
|||||||
Reference in New Issue
Block a user