From 69f1e3a47b5de7e8a16dc21f6c1404deecb499f6 Mon Sep 17 00:00:00 2001 From: bluebluesoda <300733005+bluebluesoda@users.noreply.github.com> Date: Thu, 17 Sep 2026 12:33:00 +0000 Subject: [PATCH] fix(web-ssh): send the physical key, and drop a protocol message nobody sends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things the key testing turned up. Alt was wrong on macOS. Option+a arrives as the composed character "å", so sending e.key put that in the shell instead of Meta-a. Both Alt and Ctrl now take the letter from e.code (the physical key), falling back to e.key when a browser does not report it; non-letters keep the plain ASCII mapping, which is what makes Ctrl+[ an Escape. The "bye" message was dead: the page never sends it, so the handler arm could never run. --- src/internal/panel/assets/term.js | 20 ++++++++++++++++---- src/internal/panel/terminal.go | 7 +------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/internal/panel/assets/term.js b/src/internal/panel/assets/term.js index c0abf7f..21b6d95 100644 --- a/src/internal/panel/assets/term.js +++ b/src/internal/panel/assets/term.js @@ -605,11 +605,23 @@ var ss3 = { 1: "P", 2: "Q", 3: "R", 4: "S" }; send = ss3[f] ? "\x1bO" + ss3[f] : "\x1b[" + [15, 17, 18, 19, 20, 21, 23, 24][f - 5] + "~"; } else if (e.ctrlKey && !e.altKey && e.key.length === 1) { - var c = e.key.toUpperCase().charCodeAt(0); - if (c >= 64 && c < 128) send = String.fromCharCode(c & 0x1f); + // The physical key is what a terminal wants: on a layout where Ctrl+A + // composes into something else, e.key is that something else. + var ck = /^Key([A-Z])$/.exec(e.code || ""); + if (ck) send = String.fromCharCode(ck[1].charCodeAt(0) - 64); else if (e.key === " ") send = "\x00"; - } else if (e.altKey && e.key.length === 1) { - send = "\x1b" + e.key; + else { + // Non-letters keep the plain ASCII mapping, which is what makes + // Ctrl+[ an Escape and Ctrl+6 Ctrl+^. + var cc = e.key.toUpperCase().charCodeAt(0); + if (cc >= 64 && cc < 128) send = String.fromCharCode(cc & 0x1f); + } + } else if (e.altKey && !e.metaKey) { + // Again the physical key: on macOS Option+a arrives as "å", so sending + // e.key would put that in the shell instead of Meta-a. + var ak = /^Key([A-Z])$/.exec(e.code || ""); + if (ak) send = "\x1b" + ak[1].toLowerCase(); + else if (e.key.length === 1) send = "\x1b" + e.key; } else if (!e.ctrlKey && !e.metaKey && e.key.length === 1) { send = e.key; } diff --git a/src/internal/panel/terminal.go b/src/internal/panel/terminal.go index 9332620..2d0520e 100644 --- a/src/internal/panel/terminal.go +++ b/src/internal/panel/terminal.go @@ -58,7 +58,7 @@ var termJS []byte // termIn is a message from the browser. Terminal output travels the other way // as raw binary frames; only control messages are JSON. type termIn struct { - T string `json:"t"` // hello | input | resize | takeover | cancel | bye + T string `json:"t"` // hello | input | resize | takeover Token string `json:"token,omitempty"` Data string `json:"data,omitempty"` Cols int `json:"cols,omitempty"` @@ -557,11 +557,6 @@ func (s *Server) serveTermClient(conn *websocket.Conn, sock *termSocket, sess *t _ = s.db.AddAuditLog("000+"+user, "terminal.takeover") } s.notifyOthers(user) - case "bye": - // The window is going away for good: end the shell now rather than - // holding it for the grace period. - sess.close() - return } } }