Fix Phase 5 terminal key interception and static build infrastructure

ober

20454f01145efda1d15f466bfa0899b2a9d952fe

diff --git a/Makefile b/Makefile
index 7c1bbe0..7d39761 100644
--- a/Makefile
+++ b/Makefile
@@ -256,7 +256,9 @@ PCRE2_SRC    ?= $(HOME)/mine/chez-pcre2
 SCI_SRC      ?= $(HOME)/mine/chez-scintilla
 QT_SRC       ?= $(CURDIR)/vendor/chez-qt
 QTSHIM_SRC   ?= $(HOME)/mine/gerbil-qt
-JSH_COREUTILS_LIB ?= $(JSH_SRC)/rust-coreutils/target/x86_64-unknown-linux-musl/release/libjsh_coreutils.a
+# Use stub if the Rust musl build hasn't been compiled yet (regular file check)
+_RUST_COREUTILS := $(JSH_SRC)/rust-coreutils/target/x86_64-unknown-linux-musl/release/libjsh_coreutils.a
+JSH_COREUTILS_LIB ?= $(shell test -f $(_RUST_COREUTILS) && echo $(_RUST_COREUTILS) || echo $(CURDIR)/vendor/libjsh_coreutils_stub.a)
 
 DEPS_IMAGE := jemacs-deps:$(ARCH)
 
@@ -320,6 +322,9 @@ build-jemacs-qt-static: check-root
 	  ar rcs /deps/gerbil-qt/vendor/libqt_shim.a \
 	    /deps/gerbil-qt/vendor/qt_shim_static.o; \
 	fi && \
+	cp -a /src/vendor/chez-qt/. /deps/chez-qt/ && \
+	find /deps/chez-qt -name '*.so' -delete && \
+	find /deps/chez-qt -name '*.wpo' -delete && \
 	/opt/chez/bin/scheme --libdirs /deps/chez-qt \
 	  --compile-imported-libraries --script /deps/chez-qt/compile-libs.ss && \
 	rm -f /deps/chez-qt/chez-qt/*.wpo && \
diff --git a/lib/jerboa-emacs/async.sls b/lib/jerboa-emacs/async.sls
index ad44161..aea2f02 100644
--- a/lib/jerboa-emacs/async.sls
+++ b/lib/jerboa-emacs/async.sls
@@ -552,8 +552,8 @@
        (let ([v (hashtable-ref
                   cache
                   key
-                  '#{miss l89txjuduef6khmnseoln0n2k-1})])
-         (if (eq? v '#{miss l89txjuduef6khmnseoln0n2k-2})
+                  '#{miss j40h7yge6hu7zd2spbldcd8pr-1})])
+         (if (eq? v '#{miss j40h7yge6hu7zd2spbldcd8pr-2})
              (if (null? default) #f (car default))
              v)))
   (def (weak-cache-set! cache key value)
diff --git a/lib/jerboa-emacs/qt/app.sls b/lib/jerboa-emacs/qt/app.sls
index d18b981..2980e88 100644
--- a/lib/jerboa-emacs/qt/app.sls
+++ b/lib/jerboa-emacs/qt/app.sls
@@ -1806,7 +1806,15 @@
            (app-state-key-handler-set!
              app
              (lambda (editor)
-               (qt-on-key-press-consuming! editor key-handler))))
+               (qt-on-key-press-consuming! editor key-handler)))
+           (automation-set-key-target-fn!
+             (lambda (fr)
+               (let* ([buf (qt-current-buffer fr)]
+                      [term (and buf
+                                 (hash-get *terminal-widget-map* buf))])
+                 (if term
+                     (qt-terminal-widget term)
+                     (qt-current-editor fr))))))
          (schedule-periodic!
            'repl-poll
            50
diff --git a/lib/jerboa-emacs/qt/automation.sls b/lib/jerboa-emacs/qt/automation.sls
index 5ac91ba..d0cb650 100644
--- a/lib/jerboa-emacs/qt/automation.sls
+++ b/lib/jerboa-emacs/qt/automation.sls
@@ -5,7 +5,8 @@
 (library (jerboa-emacs qt automation)
   (export automation-send-keys! automation-send-keys-async!
     automation-screenshot! automation-state automation-wait!
-    automation-set-qt-app! emacs-key->qt-event)
+    automation-set-qt-app! automation-set-key-target-fn!
+    emacs-key->qt-event)
   (import
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
@@ -162,13 +163,20 @@
        (let-values ([(code mods text)
                      (emacs-key->qt-event key-str)])
          (let* ([fr (app-state-frame app)]
-                [target (if *minibuffer-active?*
-                            (and *mb-input* *mb-input*)
-                            (qt-current-editor fr))])
+                [get-target (lambda ()
+                              (cond
+                                [*minibuffer-active?*
+                                 (and *mb-input* *mb-input*)]
+                                [*automation-key-target-fn*
+                                 (*automation-key-target-fn* fr)]
+                                [else (qt-current-editor fr)]))]
+                [target (get-target)])
            (when target
              (qt-send-key-press! target code mods text)
              (when drain? (qt-drain-pending-callbacks!))
-             (qt-send-key-release! target code mods text)
+             (let ([release-target (get-target)])
+               (when release-target
+                 (qt-send-key-release! release-target code mods text)))
              (when drain? (qt-drain-pending-callbacks!))))))
   (def (automation-screenshot! app path)
        (let* ([fr (app-state-frame app)]
@@ -220,4 +228,7 @@
          (+ (* (time-second t) 1000)
             (quotient (time-nanosecond t) 1000000))))
   (def *qt-app-ref* #f)
-  (def (automation-set-qt-app! app) (set! *qt-app-ref* app)))
+  (def (automation-set-qt-app! app) (set! *qt-app-ref* app))
+  (def *automation-key-target-fn* #f)
+  (def (automation-set-key-target-fn! fn)
+       (set! *automation-key-target-fn* fn)))
diff --git a/lib/jerboa-emacs/qt/commands-core.sls b/lib/jerboa-emacs/qt/commands-core.sls
index 996c885..eb0aba6 100644
--- a/lib/jerboa-emacs/qt/commands-core.sls
+++ b/lib/jerboa-emacs/qt/commands-core.sls
@@ -1130,10 +1130,14 @@
                  (echo-message! echo (string-append "Reverted " path))))
              (echo-error! echo "Buffer is not visiting a file"))))
   (def (cmd-select-all app)
-       (qt-plain-text-edit-select-all! (current-qt-editor app))
-       (echo-message!
-         (app-state-echo app)
-         "Mark set (whole buffer)"))
+       (let* ([ed (current-qt-editor app)]
+              [buf (current-qt-buffer app)]
+              [len (qt-plain-text-edit-text-length ed)])
+         (qt-plain-text-edit-set-selection! ed 0 len)
+         (buffer-mark-set! buf 0)
+         (echo-message!
+           (app-state-echo app)
+           "Mark set (whole buffer)")))
   (def (cmd-goto-line app)
        (let* ([echo (app-state-echo app)]
               [input (qt-echo-read-string app "Goto line: ")])
diff --git a/src/jerboa-emacs/qt/app.ss b/src/jerboa-emacs/qt/app.ss
index a115557..ff3d440 100644
--- a/src/jerboa-emacs/qt/app.ss
+++ b/src/jerboa-emacs/qt/app.ss
@@ -1378,7 +1378,20 @@
         ;; Store installer so split-window can install on new editors
         (set! (app-state-key-handler app)
               (lambda (editor)
-                (qt-on-key-press-consuming! editor key-handler))))
+                (qt-on-key-press-consuming! editor key-handler)))
+
+        ;; Tell automation which widget to target for key events.
+        ;; When a terminal buffer is active, the QTerminalWidget is the visible
+        ;; focused widget; the QScintilla editor is hidden behind it in the
+        ;; QStackedWidget. Sending sendEvent to a non-current QStackedWidget
+        ;; page is unreliable — use the visible QTerminalWidget instead.
+        (automation-set-key-target-fn!
+          (lambda (fr)
+            (let* ((buf (qt-current-buffer fr))
+                   (term (and buf (hash-get *terminal-widget-map* buf))))
+              (if term
+                (qt-terminal-widget term)
+                (qt-current-editor fr))))))
 
       ;; ================================================================
       ;; Periodic tasks — registered with schedule-periodic!, driven
diff --git a/src/jerboa-emacs/qt/automation.ss b/src/jerboa-emacs/qt/automation.ss
index 551bee3..c3c2d20 100644
--- a/src/jerboa-emacs/qt/automation.ss
+++ b/src/jerboa-emacs/qt/automation.ss
@@ -11,6 +11,7 @@
         automation-state
         automation-wait!
         automation-set-qt-app!
+        automation-set-key-target-fn!
         emacs-key->qt-event)
 
 (import :std/sugar
@@ -203,13 +204,24 @@
 ;;; If the minibuffer is active, sends to the minibuffer input widget instead.
 ;;; When drain? is #t, drains the deferred callback queue after each event
 ;;; so the Scheme key handler fires immediately.
+;;;
+;;; Target selection priority:
+;;;   1. Minibuffer input widget (when minibuffer is active)
+;;;   2. *automation-key-target-fn* override (e.g. QTerminalWidget when
+;;;      terminal buffer is active — the QScintilla is hidden behind it in
+;;;      the QStackedWidget and sendEvent to a non-current page is unreliable)
+;;;   3. qt-current-editor (the QScintilla editor for the current window)
 (def (send-one-key! app key-str drain?)
   (let-values (((code mods text) (emacs-key->qt-event key-str)))
     (let* ((fr (app-state-frame app))
            (get-target (lambda ()
-                         (if *minibuffer-active?*
-                           (and *mb-input* *mb-input*)
-                           (qt-current-editor fr))))
+                         (cond
+                           (*minibuffer-active?*
+                            (and *mb-input* *mb-input*))
+                           (*automation-key-target-fn*
+                            (*automation-key-target-fn* fr))
+                           (else
+                            (qt-current-editor fr)))))
            (target (get-target)))
       (when target
         (qt-send-key-press! target code mods text)
@@ -300,3 +312,14 @@
 
 (def (automation-set-qt-app! app)
   (set! *qt-app-ref* app))
+
+;;; Key-target override: called with (fr) to find the active input widget.
+;;; When #f, falls back to (qt-current-editor fr).
+;;; Set from app.ss after terminal widget map is available.
+;;; Purpose: when a terminal buffer is active, the focused widget is the
+;;; QTerminalWidget (not the hidden QScintilla). Sending key events to a
+;;; hidden widget via sendEvent may not reliably trigger event filters.
+(def *automation-key-target-fn* #f)
+
+(def (automation-set-key-target-fn! fn)
+  (set! *automation-key-target-fn* fn))
diff --git a/vendor/libjsh_coreutils_stub.a b/vendor/libjsh_coreutils_stub.a
new file mode 100644
index 0000000..e2e1854
Binary files /dev/null and b/vendor/libjsh_coreutils_stub.a differ