Fix vterm Ctrl-D, add process-exit banner, improve crash reporter

ober

3923f26757f4f3c7dd3e1011de10d562dd4ea0b1

diff --git a/scripts/patch-jerboa-aws-compat.sh b/scripts/patch-jerboa-aws-compat.sh
index caacc27..e1ffab0 100755
--- a/scripts/patch-jerboa-aws-compat.sh
+++ b/scripts/patch-jerboa-aws-compat.sh
@@ -1,5 +1,5 @@
-#!/usr/bin/env bash
-set -euo pipefail
+#!/bin/sh
+set -eu
 
 root="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
 jaws="$root/vendor/jerboa-aws/lib/jerboa-aws"
diff --git a/vendor/qt_shim.cpp b/vendor/qt_shim.cpp
index da72ae1..c6cc9f6 100644
--- a/vendor/qt_shim.cpp
+++ b/vendor/qt_shim.cpp
@@ -264,13 +264,9 @@ static inline void safe_write_str(int fd, const char* s) {
     (void)write(fd, s, len);
 }
 
-static void crash_write_report(int sig, siginfo_t* si) {
+static void crash_write_report_to_fd(int fd, int sig, siginfo_t* si) {
     char numbuf[32];
 
-    // Write to crash log file
-    int fd = open(s_crash_log_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
-    if (fd < 0) fd = STDERR_FILENO;  // fallback to stderr
-
     safe_write_str(fd, "=== JEMACS CRASH REPORT ===\n");
     safe_write_str(fd, "Signal: ");
     safe_itoa(numbuf, sizeof(numbuf), (long)sig);
@@ -325,14 +321,19 @@ static void crash_write_report(int sig, siginfo_t* si) {
     safe_write_str(fd, "\n--- Native Backtrace ---\n");
     safe_write_str(fd, "(not captured in signal handler; run stress-burn-static for gdb bt full)\n");
     safe_write_str(fd, "\n=== END CRASH REPORT ===\n");
+}
 
-    // Also dump to stderr if we wrote to a file
-    if (fd != STDERR_FILENO) {
+static void crash_write_report(int sig, siginfo_t* si) {
+    int fd = open(s_crash_log_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+    if (fd >= 0) {
+        crash_write_report_to_fd(fd, sig, si);
         close(fd);
-        safe_write_str(STDERR_FILENO, "\n[jemacs] CRASH — report written to ");
+    }
+    crash_write_report_to_fd(STDERR_FILENO, sig, si);
+    if (fd >= 0) {
+        safe_write_str(STDERR_FILENO, "\n[jemacs] crash report also written to ");
         safe_write_str(STDERR_FILENO, s_crash_log_path);
         safe_write_str(STDERR_FILENO, "\n");
-
     }
 }
 
@@ -7867,6 +7868,17 @@ protected:
         if (vtkey != VTERM_KEY_NONE) {
             vterm_keyboard_key(m_vt, vtkey, mod);
             handled = true;
+        } else if ((mod & VTERM_MOD_CTRL) &&
+                   ev->key() >= Qt::Key_A && ev->key() <= Qt::Key_Z) {
+            // Qt's ev->text() for Ctrl+letter is already the raw control byte
+            // (e.g. 0x04 for Ctrl-D). Falling into the generic unichar branch
+            // below would pass that 0x04 + VTERM_MOD_CTRL to libvterm, which
+            // interprets it as "modifyOtherKeys" and emits an ESC[4;5u CSI-u
+            // sequence instead of the raw 0x04. Re-encode as the letter
+            // codepoint so libvterm emits the canonical control byte.
+            uint32_t letter = ev->key() - Qt::Key_A + 'a';
+            vterm_keyboard_unichar(m_vt, letter, mod);
+            handled = true;
         } else if (!ev->text().isEmpty()) {
             QString text = ev->text();
             for (int i = 0; i < text.size(); i++) {
@@ -7982,9 +7994,31 @@ private:
             int status;
             pid_t result = waitpid(m_child_pid, &status, WNOHANG);
             if (result == m_child_pid || result < 0) {
+                // Build a visible "[Process exited]" banner so the user
+                // sees that the shell actually died — otherwise the vterm
+                // just freezes at the last prompt and looks like a hang.
+                char banner[128];
+                int n;
+                if (result == m_child_pid && WIFEXITED(status)) {
+                    n = snprintf(banner, sizeof(banner),
+                                 "\r\n[Process exited (status %d)]\r\n",
+                                 WEXITSTATUS(status));
+                } else if (result == m_child_pid && WIFSIGNALED(status)) {
+                    n = snprintf(banner, sizeof(banner),
+                                 "\r\n[Process killed by signal %d]\r\n",
+                                 WTERMSIG(status));
+                } else {
+                    n = snprintf(banner, sizeof(banner),
+                                 "\r\n[Process exited]\r\n");
+                }
+                if (n > 0 && m_vt) {
+                    vterm_input_write(m_vt, banner, (size_t)n);
+                    if (m_screen) vterm_screen_flush_damage(m_screen);
+                }
+
                 m_running = false;
                 m_timer->stop();
-                ::close(m_master_fd);
+                if (m_master_fd >= 0) ::close(m_master_fd);
                 m_master_fd  = -1;
                 m_child_pid  = -1;
                 update();