From b35ead76e2c11c38e6c8dd31bee058f50cb8f9d2 Mon Sep 17 00:00:00 2001 From: ashwinhegde19 Date: Fri, 6 Feb 2026 21:04:08 +0530 Subject: [PATCH] fix: disable mouse tracking on Windows terminal exit Add explicit terminal state reset sequences after renderer cleanup to ensure SGR mouse tracking is properly disabled on exit. On Windows, the terminal may still report mouse events as raw escape sequences if mouse tracking wasn't fully disabled. This fix adds: 1. resetTerminalState() function that writes disable sequences for: - Normal and button-event mouse tracking - Any-event mouse tracking (all movement) - SGR extended mouse mode - RXVT mouse mode - Kitty keyboard protocol - Text attributes 2. Calls resetTerminalState() in three exit paths: - Normal exit via ExitProvider (exit/quit/:q commands) - Error boundary Ctrl+C handler - process.on('exit') safety net for all other exits Fixes #151 --- packages/opencode/src/cli/cmd/tui/app.tsx | 5 +++++ .../opencode/src/cli/cmd/tui/context/exit.tsx | 2 ++ .../opencode/src/cli/cmd/tui/util/terminal.ts | 22 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 42c5b413dc..29a2f7214f 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -40,6 +40,7 @@ import { writeHeapSnapshot } from "v8" import { PromptRefProvider, usePromptRef } from "./context/prompt" import { registerKiloCommands } from "@/kilocode/kilo-commands" // kilocode_change import { initializeTUIDependencies } from "@kilocode/kilo-gateway/tui" // kilocode_change +import { resetTerminalState } from "@tui/util/terminal" async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { // can't set raw mode if not a TTY @@ -120,6 +121,9 @@ export function tui(input: { resolve() } + // Safety net: ensure mouse tracking is disabled regardless of exit path + process.on("exit", resetTerminalState) + render( () => { return ( @@ -714,6 +718,7 @@ function ErrorComponent(props: { const handleExit = async () => { renderer.setTerminalTitle("") renderer.destroy() + resetTerminalState() props.onExit() } diff --git a/packages/opencode/src/cli/cmd/tui/context/exit.tsx b/packages/opencode/src/cli/cmd/tui/context/exit.tsx index 2aac152204..c463073e6f 100644 --- a/packages/opencode/src/cli/cmd/tui/context/exit.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/exit.tsx @@ -1,6 +1,7 @@ import { useRenderer } from "@opentui/solid" import { createSimpleContext } from "./helper" import { FormatError, FormatUnknownError } from "@/cli/error" +import { resetTerminalState } from "@tui/util/terminal" type Exit = ((reason?: unknown) => Promise) & { message: { set: (value?: string) => () => void @@ -32,6 +33,7 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({ // Reset window title before destroying renderer renderer.setTerminalTitle("") renderer.destroy() + resetTerminalState() await input.onExit?.() if (reason) { const formatted = FormatError(reason) ?? FormatUnknownError(reason) diff --git a/packages/opencode/src/cli/cmd/tui/util/terminal.ts b/packages/opencode/src/cli/cmd/tui/util/terminal.ts index 2b81068b3f..541f204182 100644 --- a/packages/opencode/src/cli/cmd/tui/util/terminal.ts +++ b/packages/opencode/src/cli/cmd/tui/util/terminal.ts @@ -1,5 +1,27 @@ import { RGBA } from "@opentui/core" +/** + * Write escape sequences to disable all mouse tracking modes and reset terminal state. + * This is a safety net to ensure the terminal is clean after exit, even if the renderer's + * cleanup didn't flush properly (e.g. on Windows). + */ +export function resetTerminalState() { + const sequences = [ + "\x1b[?1000l", // disable normal mouse tracking + "\x1b[?1002l", // disable button-event mouse tracking + "\x1b[?1003l", // disable any-event mouse tracking (all movement) + "\x1b[?1006l", // disable SGR extended mouse mode + "\x1b[?1015l", // disable RXVT mouse mode + "\x1b[> /**