fix(web-ssh): stop private CSI sequences turning into SGR attributes
Typing in vi left the whole session underlined. The stream vi sends contains xterm's modifyOtherKeys, which carries a private ">" prefix: CSI > 4 ; m and CSI > 4 ; 2 m A CSI sequence has four private markers — ? < = > — and the parser only knew "?". The others were dropped on the floor, so their parameters leaked into the final byte and ">4;m" was read as SGR 4 (underline on) with nothing left to turn it off. All four markers are recognised now, and a private sequence is never treated as SGR. The secondary device-attributes query (CSI > c) also gets its own reply, so a program asking for it does not sit out its timeout. Found by capturing the real byte stream from vi over a PTY and replaying it through the emulator: the flags now come out clean, while a plain CSI 4 m still turns underline on as it should. Reported alongside it: after leaving insert mode with Escape the shell seemed to stop accepting keys until the window was clicked. That does not reproduce against a bare browser here — the same sequence types ":wq" straight through — so the likely culprit is an input method leaving its composing flag set, which would swallow every later key. The guard now trusts our own compositionstart/end tracking instead of the browser's flag, and a committed composition still reaches the shell.
This commit is contained in:
1 parent
e3c2b851e4
commit
08cc747b32
1 file changed
+18
-4
@@ -290,7 +290,13 @@
|
||||
break;
|
||||
case "csi":
|
||||
if (code >= 0x40 && code <= 0x7e) { this._csi(ch); this.state = "ground"; }
|
||||
else if (code === 0x3f) this.priv = true;
|
||||
else if (code === 0x3f || code === 0x3c || code === 0x3d || code === 0x3e) {
|
||||
// The private/extension markers are ? < = >. All four have to be
|
||||
// recognised: ignoring one lets its parameters leak into the final
|
||||
// byte, which is how xterm's modifyOtherKeys (CSI > 4 ; 2 m) used to
|
||||
// arrive as "underline on" and stay on for the rest of the session.
|
||||
this.priv = true;
|
||||
}
|
||||
else if (code >= 0x30 && code <= 0x39) this.buf += ch;
|
||||
else if (code === 0x3b) { this.params.push(parseInt(this.buf, 10)); this.buf = ""; }
|
||||
break;
|
||||
@@ -371,14 +377,16 @@
|
||||
if (this.scrollBot <= this.scrollTop) { this.scrollTop = 0; this.scrollBot = this.rows - 1; }
|
||||
this.cx = 0; this.cy = this.scrollTop;
|
||||
break;
|
||||
case "m": this._sgr(p); break;
|
||||
case "m": if (!this.priv) this._sgr(p); break;
|
||||
case "h": this._mode(p, true); break;
|
||||
case "l": this._mode(p, false); break;
|
||||
case "n":
|
||||
if (p[0] === 6 && this.opts.onReply) this.opts.onReply("\x1b[" + (this.cy + 1) + ";" + (this.cx + 1) + "R");
|
||||
break;
|
||||
case "c":
|
||||
if (this.opts.onReply) this.opts.onReply("\x1b[?1;2c");
|
||||
// A private ">" query asks for the secondary attributes; answering with
|
||||
// the primary form (or not at all) makes a program wait for its timeout.
|
||||
if (this.opts.onReply) this.opts.onReply(this.priv ? "\x1b[>0;0;0c" : "\x1b[?1;2c");
|
||||
break;
|
||||
}
|
||||
this._scheduleRender();
|
||||
@@ -602,7 +610,13 @@
|
||||
|
||||
Term.prototype._onKey = function (e) {
|
||||
if (this.exited) return;
|
||||
if (e.isComposing || e.keyCode === 229) return; // the IME owns this keystroke
|
||||
// The IME owns a keystroke while a composition is running. Which that is
|
||||
// has to come from our own compositionstart/end tracking rather than from
|
||||
// e.isComposing alone: a browser can leave that flag set after an IME
|
||||
// candidate is cancelled (Escape), and then every later key is dropped
|
||||
// until the user clicks somewhere else. Same for a key the IME is
|
||||
// consuming, which browsers report as "Process".
|
||||
if (this.composing || e.key === "Process" || e.key === "Unidentified") return;
|
||||
var m = this.modes;
|
||||
var send = null;
|
||||
|
||||
|
||||
Reference in new issue
Block a user