Merge remote-tracking branch 'origin/farm/158a3a86/osc-99-probe-tmux-gate'

This commit is contained in:
can1357
2026-07-15 18:49:52 +02:00
3 changed files with 21 additions and 14 deletions
+1
View File
@@ -11,6 +11,7 @@
### Fixed
- Fixed SIXEL image rendering stripping a horizontal slice from images when the cell height was not a multiple of 6. SIXEL encodes in 6-pixel vertical bands; a non-multiple-of-6 height was padded, causing the terminal to allocate an extra row that the TUI did not reserve — the next line of content then overwrote the bottom of the image. The encode height is now rounded down to the largest multiple of 6 within the requested row budget, eliminating padding without exceeding the caller's height cap, and the width is scaled by the same ratio to preserve the image aspect ratio.
- Fixed the Kitty OSC 99 desktop-notification capability probe (`p=?`) firing under tmux/screen, where the multiplexer forwards the passthrough query to the outer terminal but cannot route the reply back to the sending pane — the capability list leaked into the pane as text and its bytes switched panes ([#5582](https://github.com/can1357/oh-my-pi/issues/5582)). The probe is now suppressed inside a multiplexer (rich notifications fall back to the single-line OSC 99 form until confirmed), mirroring the graphics-probe fix in #5381.
## [16.5.2] - 2026-07-14
+13 -9
View File
@@ -12,12 +12,12 @@ import {
import { setKittyProtocolActive } from "./keys";
import { StdinBuffer } from "./stdin-buffer";
import {
isInsideTerminalMultiplexer,
isInsideTmux,
NotifyProtocol,
setCellDimensions,
setOsc99Supported,
TERMINAL,
wrapTmuxPassthrough,
} from "./terminal-capabilities";
import { type HangulCompatibilityJamoWidth, setHangulCompatibilityJamoWidth } from "./utils";
@@ -1042,6 +1042,15 @@ export class ProcessTerminal implements Terminal {
#shouldQueryOsc99Support(): boolean {
if (TERMINAL.notifyProtocol !== NotifyProtocol.Osc99) return false;
// Never probe inside a terminal multiplexer. tmux/screen forward the
// passthrough-wrapped `p=?` query to the outer terminal, but cannot route
// the capability reply back to the pane that sent it (tmux/tmux#4386,
// tmux/tmux#3964), so the reply leaks into the pane as literal text and
// its bytes perturb input (issue #5582 — the notification sibling of the
// graphics-probe leak #5381). Rich notifications fall back to the
// single-line OSC 99 form until confirmation, and delivery still uses the
// passthrough/BEL path (#3395).
if (isInsideTerminalMultiplexer($env)) return false;
return !isBunTestRuntime() || $env.PI_TUI_OSC99_PROBE === "1";
}
@@ -1055,14 +1064,9 @@ export class ProcessTerminal implements Terminal {
const id = `omp-probe-${nextOsc99ProbeId++}`;
this.#osc99PendingId = id;
this.#da1SentinelOwners.push({ kind: "osc99Probe", id });
// Wrap the probe under tmux so terminals behind `allow-passthrough on`
// can still respond (mirroring how `TerminalInfo.sendNotification`
// wraps notification deliveries). Without it the probe is swallowed
// inside tmux even when the outer terminal speaks OSC 99, and rich
// notifications stay permanently downgraded to the single-line fallback.
const probe = `\x1b]99;i=${id}:p=?;\x1b\\`;
const sequence = isInsideTmux() ? wrapTmuxPassthrough(probe) : probe;
this.#safeWrite(`${sequence}\x1b[c`);
// The probe never runs under a multiplexer (see #shouldQueryOsc99Support),
// so it is always sent directly to the terminal.
this.#safeWrite(`\x1b]99;i=${id}:p=?;\x1b\\\x1b[c`);
}
#handleOsc99CapabilityResponse(metaRaw: string, payload: string): boolean {
+7 -5
View File
@@ -291,16 +291,18 @@ describe("terminal notifications", () => {
expect(writes).toEqual(["\x1b]99;;ping\x1b\\\x07"]);
});
it("under tmux, the OSC 99 capability probe is wrapped in DCS passthrough", () => {
it("under tmux, the OSC 99 capability probe is suppressed (reply cannot route back to the pane)", () => {
Bun.env.PI_TUI_OSC99_PROBE = "1";
Bun.env.TMUX = "/tmp/tmux-1000/default,1234,0";
mutableTerminal.notifyProtocol = NotifyProtocol.Osc99;
const { terminal, writes } = setupProcessTerminal();
try {
const probe = writes.find(
w => w.startsWith("\x1bPtmux;\x1b\x1b]99;i=omp-probe-") && w.endsWith("\x1b\x1b\\\x1b\\\x1b[c"),
);
expect(probe).toBeDefined();
// tmux forwards the passthrough probe to the outer terminal but cannot
// route the `p=?` reply back to the sending pane, so the reply would
// leak into the pane as text (#5582). The probe must not fire at all.
const probe = writes.find(w => w.includes("]99;i=omp-probe-") && w.includes(":p=?"));
expect(probe).toBeUndefined();
expect(isOsc99Supported()).toBe(false);
} finally {
terminal.stop();
}