Merge remote-tracking branch 'origin/farm/9322146b/skip-ssh-modifyotherkeys-fallback'

This commit is contained in:
can1357
2026-07-02 23:28:57 +02:00
3 changed files with 56 additions and 9 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed the modifyOtherKeys keyboard fallback enabling on unknown SSH terminals, avoiding broken Shift input in iOS SSH clients such as Redock ([#4325](https://github.com/can1357/oh-my-pi/issues/4325)).
## [16.3.0] - 2026-07-02
### Fixed
+17 -9
View File
@@ -35,6 +35,11 @@ export function resolveHangulCompatibilityJamoWidthFromTerminalIdentity(
return "platform";
}
function shouldEnableModifyOtherKeysFallback(env: NodeJS.ProcessEnv = Bun.env): boolean {
if (!env.SSH_CONNECTION && !env.SSH_TTY && !env.SSH_CLIENT) return true;
return TERMINAL.id !== "base" && TERMINAL.id !== "trueColor";
}
/**
* Maximum encoded UTF-8 bytes per `process.stdout.write` call on Windows.
*
@@ -840,13 +845,13 @@ export class ProcessTerminal implements Terminal {
break;
}
case "keyboard": {
// Keyboard probe sentinel: kitty reply never arrived → fall back to modifyOtherKeys.
if (!this.#kittyProtocolActive && !this.#modifyOtherKeysActive && this.#modifyOtherKeysTimeout) {
// Keyboard probe sentinel: kitty reply never arrived → fall back to modifyOtherKeys
// only where the resolved terminal is known enough to tolerate it.
if (this.#modifyOtherKeysTimeout) {
clearTimeout(this.#modifyOtherKeysTimeout);
this.#modifyOtherKeysTimeout = undefined;
this.#safeWrite("\x1b[>4;2m");
this.#modifyOtherKeysActive = true;
}
this.#enableModifyOtherKeysFallback();
break;
}
case "osc99Probe": {
@@ -1054,6 +1059,13 @@ export class ProcessTerminal implements Terminal {
}
}
#enableModifyOtherKeysFallback(): void {
if (this.#kittyProtocolActive || this.#modifyOtherKeysActive) return;
if (!shouldEnableModifyOtherKeysFallback()) return;
this.#safeWrite("\x1b[>4;2m");
this.#modifyOtherKeysActive = true;
}
/**
* Query terminal for Kitty keyboard protocol support and enable if available.
*
@@ -1073,11 +1085,7 @@ export class ProcessTerminal implements Terminal {
this.#safeWrite("\x1b[?u\x1b[c");
this.#modifyOtherKeysTimeout = setTimeout(() => {
this.#modifyOtherKeysTimeout = undefined;
if (this.#kittyProtocolActive || this.#modifyOtherKeysActive) {
return;
}
this.#safeWrite("\x1b[>4;2m");
this.#modifyOtherKeysActive = true;
this.#enableModifyOtherKeysFallback();
}, 150);
}
@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it } from "bun:test";
import type { Component } from "@oh-my-pi/pi-tui";
import { TERMINAL } from "@oh-my-pi/pi-tui/terminal-capabilities";
import {
createProcessTerminalRenderHarness,
type ProcessTerminalRenderHarness,
@@ -18,12 +19,29 @@ class ModalProbe implements Component {
return ["modal"];
}
}
const originalSshConnection = Bun.env.SSH_CONNECTION;
const originalSshTty = Bun.env.SSH_TTY;
const originalSshClient = Bun.env.SSH_CLIENT;
const originalTerminalId = TERMINAL.id;
function restoreEnv(name: "SSH_CONNECTION" | "SSH_TTY" | "SSH_CLIENT", value: string | undefined): void {
if (value === undefined) {
delete Bun.env[name];
return;
}
Bun.env[name] = value;
}
describe("ProcessTerminal kitty keyboard progressive-enhancement ordering", () => {
let harness: ProcessTerminalRenderHarness | undefined;
afterEach(() => {
harness?.dispose();
harness = undefined;
restoreEnv("SSH_CONNECTION", originalSshConnection);
restoreEnv("SSH_TTY", originalSshTty);
restoreEnv("SSH_CLIENT", originalSshClient);
Object.defineProperty(TERMINAL, "id", { value: originalTerminalId, configurable: true });
});
it("enables kitty when the kitty reply arrives before the DA1 sentinel", async () => {
@@ -75,6 +93,23 @@ describe("ProcessTerminal kitty keyboard progressive-enhancement ordering", () =
expect(out).not.toContain("\x1b[>1u");
});
it("skips modifyOtherKeys fallback for SSH_CONNECTION-only unknown terminals", async () => {
Bun.env.SSH_CONNECTION = "192.0.2.10 54321 192.0.2.20 22";
delete Bun.env.SSH_TTY;
delete Bun.env.SSH_CLIENT;
Object.defineProperty(TERMINAL, "id", { value: "base", configurable: true });
harness = createProcessTerminalRenderHarness(100, 30);
await harness.settle();
harness.writes.length = 0;
await harness.feed("\x1b[?1;2c");
const out = harness.writes.join("");
expect(harness.terminal.kittyProtocolActive).toBe(false);
expect(out).not.toContain("\x1b[>4;2m");
expect(harness.terminal.keyboardEnhancementEnterSequence).toBeNull();
});
it("reasserts modifyOtherKeys fallback when fullscreen overlays enter the alternate screen", async () => {
harness = createProcessTerminalRenderHarness(100, 30);
await harness.settle();