Fix mobile keybar Ctrl/Shift modifiers not applying to keyboard input

- Apply Ctrl modifier to letters typed via mobile keyboard (e.g., Ctrl+D sends 0x04)
- Apply Shift modifier to uppercase letters typed via mobile keyboard
- Apply modifiers to arrow keys and Tab in keydown handler
- Deactivate modifiers after key is sent

Bump to v0.6.3
This commit is contained in:
GitHub Copilot
2026-01-28 08:36:41 +00:00
parent 0b6288494e
commit 714a5c705c
3 changed files with 44 additions and 16 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "textual-webterm" name = "textual-webterm"
version = "0.6.2" version = "0.6.3"
description = "Serve terminal sessions over the web" description = "Serve terminal sessions over the web"
authors = ["Will McGugan <will@textualize.io>"] authors = ["Will McGugan <will@textualize.io>"]
license = "MIT" license = "MIT"
File diff suppressed because one or more lines are too long
+38 -10
View File
@@ -497,38 +497,66 @@ class WebTerminal {
textarea.addEventListener("input", () => { textarea.addEventListener("input", () => {
const value = textarea.value; const value = textarea.value;
if (value) { if (value) {
this.send(["stdin", value]); let toSend = value;
// Apply Shift modifier (uppercase letters)
if (this.shiftActive && value.length === 1) {
toSend = value.toUpperCase();
}
// Apply Ctrl modifier if active (convert letters to control codes)
if (this.ctrlActive && value.length === 1) {
const code = toSend.toUpperCase().charCodeAt(0);
if (code >= 65 && code <= 90) {
toSend = String.fromCharCode(code - 64); // Ctrl+A = 0x01, Ctrl+D = 0x04, etc.
}
}
this.send(["stdin", toSend]);
textarea.value = ""; textarea.value = "";
this.deactivateModifiers();
} }
}); });
// Handle special navigation keys via keydown (not covered by beforeinput) // Handle special navigation keys via keydown (not covered by beforeinput)
textarea.addEventListener("keydown", (e) => { textarea.addEventListener("keydown", (e) => {
let seq: string | null = null; let seq: string | null = null;
let deactivate = false;
switch (e.key) { switch (e.key) {
case "Escape": case "Escape":
seq = "\x1b"; seq = "\x1b";
deactivate = true;
break; break;
case "ArrowUp": case "ArrowUp":
seq = "\x1b[A";
break;
case "ArrowDown": case "ArrowDown":
seq = "\x1b[B";
break;
case "ArrowRight": case "ArrowRight":
seq = "\x1b[C"; case "ArrowLeft": {
break; const dir = e.key === "ArrowUp" ? "A" : e.key === "ArrowDown" ? "B" : e.key === "ArrowRight" ? "C" : "D";
case "ArrowLeft": if (this.ctrlActive && this.shiftActive) {
seq = "\x1b[D"; seq = `\x1b[1;6${dir}`;
} else if (this.ctrlActive) {
seq = `\x1b[1;5${dir}`;
} else if (this.shiftActive) {
seq = `\x1b[1;2${dir}`;
} else {
seq = `\x1b[${dir}`;
}
deactivate = true;
break; break;
}
case "Tab": case "Tab":
seq = "\t"; if (this.shiftActive) {
seq = "\x1b[Z"; // Back-tab
} else {
seq = "\t";
}
e.preventDefault(); e.preventDefault();
deactivate = true;
break; break;
} }
if (seq) { if (seq) {
e.preventDefault(); e.preventDefault();
this.send(["stdin", seq]); this.send(["stdin", seq]);
if (deactivate) {
this.deactivateModifiers();
}
} }
}); });