Conversation
…erminal mouse tracking When Windows Terminal / ConPTY loses focus (alt-tab, minimize, tab switch), it can silently strip DEC private mode escape codes like mouse tracking (?1000/?1002/?1003/?1006), focus tracking (?1004), and bracketed paste (?2004). This adds a restoreTerminalModes function that unconditionally re-sends all currently-active mode sequences when the focus-in event (ESC[I) is received, restoring mouse tracking and other terminal modes.
@opentui/core
@opentui/react
@opentui/solid
@opentui/core-darwin-arm64
@opentui/core-darwin-x64
@opentui/core-linux-arm64
@opentui/core-linux-x64
@opentui/core-win32-arm64
@opentui/core-win32-x64
commit: |
There was a problem hiding this comment.
Pull request overview
This PR addresses a Windows Terminal / ConPTY behavior where DEC private modes (mouse tracking, bracketed paste, focus tracking, etc.) can be silently reset after focus changes by re-sending currently-active mode enable sequences when a focus-in (ESC[I) is received.
Changes:
- Add a Zig
restoreTerminalModes()that re-emits enable sequences for modes tracked as active. - Expose
restoreTerminalModesthrough the Zig renderer, FFI exports, and the TypeScript bindings. - Invoke mode restoration on focus-in before emitting the
"focus"event, and add tests + a manual demo.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/zig/terminal.zig | Adds restoreTerminalModes() that re-sends active terminal mode sequences. |
| packages/core/src/zig/renderer.zig | Adds a renderer method to call terminal.restoreTerminalModes() and write the output. |
| packages/core/src/zig/lib.zig | Exposes restoreTerminalModes as an exported FFI function. |
| packages/core/src/zig.ts | Wires up the new FFI symbol + RenderLib interface + implementation method. |
| packages/core/src/renderer.ts | Calls restoreTerminalModes on focus-in (ESC[I) before emitting "focus". |
| packages/core/src/examples/focus-restore-demo.ts | Adds an interactive demo to validate focus restore behavior manually. |
| packages/core/src/examples/index.ts | Registers the new demo in the example selector. |
| packages/core/src/tests/renderer.focus-restore.test.ts | Adds unit tests validating the focus-in wiring and call ordering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/core/src/zig/terminal.zig
Outdated
| // Re-push kitty keyboard protocol if active | ||
| if (self.state.kitty_keyboard) { | ||
| try tty.print(ansi.ANSI.csiUPush, .{self.opts.kitty_keyboard_flags}); | ||
| } |
There was a problem hiding this comment.
restoreTerminalModes re-pushes the kitty keyboard protocol using self.opts.kitty_keyboard_flags, but the currently-active kitty flags may differ from opts. In particular, setKittyKeyboard(..., flags) only flips state.kitty_keyboard and does not persist the flags, and the public enableKittyKeyboard(flags) path in renderer.zig passes a flags argument without updating opts. On focus-in this can silently change the kitty keyboard flags after a restore. Consider storing the last-pushed kitty flags in terminal state (or updating opts.kitty_keyboard_flags whenever kitty is enabled) and using that stored value here.
| // Re-push kitty keyboard protocol if active | |
| if (self.state.kitty_keyboard) { | |
| try tty.print(ansi.ANSI.csiUPush, .{self.opts.kitty_keyboard_flags}); | |
| } | |
| // NOTE: We intentionally do not re-push kitty keyboard protocol here, | |
| // because we cannot guarantee that self.opts.kitty_keyboard_flags | |
| // reflects the currently active kitty keyboard flags. Restoring with | |
| // incorrect flags would silently change behavior. |
…n-push on restore Store the actually-pushed kitty keyboard flags in state.kitty_keyboard_flags rather than relying on opts.kitty_keyboard_flags, which can diverge if setKittyKeyboardFlags() is called after enable. Use pop-then-push in restoreTerminalModes to prevent unbounded stack growth per the kitty keyboard protocol spec (CSI > flags u pushes onto a stack).
When Windows Terminal / ConPTY loses focus (alt-tab, minimize, tab switch), it silently strips DEC private mode escape codes even though the app's internal state still thinks they're enabled.
Now we listen to the focus signal and resend the escape codes.