From 2b2119a2f1602355e81db7b67846b56eab2f307b Mon Sep 17 00:00:00 2001 From: Hermes Date: Tue, 12 May 2026 01:43:52 +0000 Subject: [PATCH] Shell wrapper for terminal raw mode, demo no longer sets raw mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added ./demo shell script that sets raw mode via stty before running the Lisp demo and restores it on exit (including SIGINT/SIGTERM). demo.lisp no longer attempts to set raw mode from inside SBCL — terminal raw mode is the shell's responsibility. This avoids the recurring problem of sb-ext:run-program + stty not being able to access the controlling terminal from inside sbcl --script. --- demo | 20 +++++++++++ demo.lisp | 101 ++++++++++++++++++++++++++---------------------------- 2 files changed, 68 insertions(+), 53 deletions(-) create mode 100755 demo diff --git a/demo b/demo new file mode 100755 index 0000000..0e2542e --- /dev/null +++ b/demo @@ -0,0 +1,20 @@ +#!/bin/sh +# cl-tty demo launcher +# Sets raw terminal mode, runs the demo, restores terminal on exit. +# This is needed because SBCL's --script mode + run-program combo +# can't reliably set raw mode from inside the Lisp process. + +SAVED=$(stty -g 2>/dev/null) +if [ -z "$SAVED" ]; then + echo "ERROR: Not running in a real terminal." >&2 + echo " Try: sbcl --script demo.lisp" >&2 + exit 1 +fi + +cleanup() { + stty "$SAVED" 2>/dev/null +} +trap cleanup EXIT INT TERM + +stty raw -echo -isig -icanon min 1 time 0 2>/dev/null +sbcl --script "$(dirname "$0")/demo.lisp" diff --git a/demo.lisp b/demo.lisp index d7eb880..fdb1d5c 100644 --- a/demo.lisp +++ b/demo.lisp @@ -124,60 +124,55 @@ t))) (defun run-demo () - (let ((saved (ignore-errors (set-raw-mode)))) - (unless saved - (format *error-output* "~&ERROR: Cannot set terminal to raw mode.~%") - (format *error-output* " Make sure you are in a real terminal (not a pipe/redirect).~%") - (format *error-output* " Try: sbcl --script demo.lisp~%") - (return-from run-demo)) + "Run the demo. Assumes raw terminal mode is already set by the +shell wrapper (./demo) or by running: + stty raw -echo -isig -icanon min 1 time 0 + sbcl --script demo.lisp" + (init-app-state) + (let* ((backend (detect-backend)) + (w 80) (h 24)) + (declare (ignore h)) + (initialize-backend backend) (unwind-protect - (progn - (init-app-state) - (let* ((backend (detect-backend)) - (w 80) (h 24)) - (initialize-backend backend) - (unwind-protect - (loop while (getf *app* :running) - do - (backend-clear backend) - ;; Title bar - (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" - :bright-white nil) - ;; Tab bar - (loop for (label . idx) in '((" Home " . 0) (" Widgets " . 1) (" Console " . 2)) - for x-pos = 4 then (+ x-pos label-len 2) - for label-len = (length label) - do (let ((active (eql idx (getf *app* :tab)))) - (if active - (draw-text backend x-pos 4 label :bright-white :accent :bold t) - (draw-text backend x-pos 4 label :text-muted nil)))) - ;; Content area - (case (getf *app* :tab) - (0 (render-tab-home backend 4 6 72 20)) - (1 (render-tab-widgets backend 4 6 72 24 - (getf *app* :input) - (getf *app* :textarea))) - (2 (render-tab-console backend 4 6 72 16))) - ;; Mouse cursor indicator - (let ((mx (getf *app* :mouse-x)) - (my (getf *app* :mouse-y))) - (when (and (>= mx 0) (>= my 0)) - (draw-text backend mx my "@" :bright-cyan nil))) - ;; Status bar - (draw-rect backend 2 23 (- w 4) 1 :bg :blue) - (draw-text backend 4 23 - (format nil " Tab ~d/3 | ~d events " - (1+ (getf *app* :tab)) (length *log*)) - :bright-white :blue :bold t) - (finish-output *standard-output*) - ;; Read event — blocks until a key or mouse event arrives - (let ((event (read-event backend))) - (when event - (handle-event event)))) - (shutdown-backend backend)))) - (when saved - (restore-terminal-state saved))))) + (loop while (getf *app* :running) + do + (backend-clear backend) + ;; Title bar + (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" + :bright-white nil) + ;; Tab bar + (loop for (label . idx) in '((" Home " . 0) (" Widgets " . 1) (" Console " . 2)) + for x-pos = 4 then (+ x-pos label-len 2) + for label-len = (length label) + do (let ((active (eql idx (getf *app* :tab)))) + (if active + (draw-text backend x-pos 4 label :bright-white :accent :bold t) + (draw-text backend x-pos 4 label :text-muted nil)))) + ;; Content area + (case (getf *app* :tab) + (0 (render-tab-home backend 4 6 72 20)) + (1 (render-tab-widgets backend 4 6 72 24 + (getf *app* :input) + (getf *app* :textarea))) + (2 (render-tab-console backend 4 6 72 16))) + ;; Mouse cursor indicator + (let ((mx (getf *app* :mouse-x)) + (my (getf *app* :mouse-y))) + (when (and (>= mx 0) (>= my 0)) + (draw-text backend mx my "@" :bright-cyan nil))) + ;; Status bar + (draw-rect backend 2 23 (- w 4) 1 :bg :blue) + (draw-text backend 4 23 + (format nil " Tab ~d/3 | ~d events " + (1+ (getf *app* :tab)) (length *log*)) + :bright-white :blue :bold t) + (finish-output *standard-output*) + ;; Read event — blocks until a key or mouse event arrives + (let ((event (read-event backend))) + (when event + (handle-event event)))) + (shutdown-backend backend)))) (run-demo) (uiop:quit 0)