From 6a7476b437a99dcd74e22680a7422c55fbf66b62 Mon Sep 17 00:00:00 2001 From: Michael Sitarzewski Date: Fri, 5 Jun 2026 01:04:04 -0500 Subject: [PATCH] fix(installer): robust arrow-key reading (bash 3.2 integer timeouts + SS3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit read_key used a fractional -t 0.01 timeout, which bash 3.2 (/bin/bash on macOS) doesn't support — so arrow-key escape bytes ([A/[B) leaked through and were parsed as letter commands (toggling instead of moving). Rewrite to read the sequence byte-by-byte with integer timeouts and handle both CSI ([) and SS3 (O) cursor modes. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/lib.sh | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/scripts/lib.sh b/scripts/lib.sh index 85fb57f..5c20b04 100755 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -124,20 +124,28 @@ tui_end() { # read_key — read one keypress, echo a normalized token: # UP DOWN LEFT RIGHT ENTER SPACE ESC BACKSPACE TAB or the literal character. +# +# Reads escape sequences byte-by-byte with INTEGER timeouts (bash 3.2 has no +# fractional -t). A real arrow sends ESC [ A (or ESC O A in application-cursor +# mode) as one buffered burst, so the follow-up reads return instantly; only a +# lone Esc waits out the 1s timeout. Handles both CSI ('[') and SS3 ('O'). read_key() { - local k rest - IFS= read -rsn1 k || { printf 'EOF'; return; } + local k k2 k3 + IFS= read -rsn1 k 2>/dev/null || { printf 'EOF'; return; } case "$k" in $'\033') - # escape sequence: read up to 2 more bytes (non-blocking) - IFS= read -rsn2 -t 0.01 rest 2>/dev/null - case "$rest" in - '[A') printf 'UP' ;; '[B') printf 'DOWN' ;; - '[C') printf 'RIGHT' ;; '[D') printf 'LEFT' ;; - '') printf 'ESC' ;; *) printf 'ESC' ;; - esac ;; - '') printf 'ENTER' ;; # Enter often reads as empty with -n1 - $'\n'|$'\r') printf 'ENTER' ;; + if ! IFS= read -rsn1 -t 1 k2 2>/dev/null; then printf 'ESC'; return; fi + if [[ "$k2" == '[' || "$k2" == 'O' ]]; then + IFS= read -rsn1 -t 1 k3 2>/dev/null + case "$k3" in + A) printf 'UP' ;; B) printf 'DOWN' ;; + C) printf 'RIGHT' ;; D) printf 'LEFT' ;; + *) printf 'ESC' ;; + esac + else + printf 'ESC' + fi ;; + $'\n'|$'\r'|'') printf 'ENTER' ;; # Enter is CR in raw mode (sometimes empty) ' ') printf 'SPACE' ;; $'\t') printf 'TAB' ;; $'\177'|$'\010') printf 'BACKSPACE' ;;