@@ -2,12 +2,61 @@ import type { CliRenderer } from '@opentui/core'
22
33let renderer : CliRenderer | null = null
44let handlersInstalled = false
5+ let terminalStateReset = false
6+
7+ /**
8+ * Terminal escape sequences to reset terminal state.
9+ * These are written directly to stdout to ensure they're sent even if the renderer is in a bad state.
10+ *
11+ * Sequences:
12+ * - \x1b[?1000l: Disable X10 mouse mode
13+ * - \x1b[?1002l: Disable button event mouse mode
14+ * - \x1b[?1003l: Disable any-event mouse mode (all motion tracking)
15+ * - \x1b[?1006l: Disable SGR extended mouse mode
16+ * - \x1b[?1004l: Disable focus reporting
17+ * - \x1b[?2004l: Disable bracketed paste mode
18+ * - \x1b[?25h: Show cursor (safety measure)
19+ */
20+ const TERMINAL_RESET_SEQUENCES =
21+ '\x1b[?1000l' + // Disable X10 mouse mode
22+ '\x1b[?1002l' + // Disable button event mouse mode
23+ '\x1b[?1003l' + // Disable any-event mouse mode (all motion)
24+ '\x1b[?1006l' + // Disable SGR extended mouse mode
25+ '\x1b[?1004l' + // Disable focus reporting
26+ '\x1b[?2004l' + // Disable bracketed paste mode
27+ '\x1b[?25h' // Show cursor
28+
29+ /**
30+ * Reset terminal state by writing escape sequences directly to stdout.
31+ * This is called BEFORE renderer.destroy() to ensure sequences are sent
32+ * even if the renderer is in a bad state.
33+ *
34+ * This is especially important on Windows where signals like SIGTERM and SIGHUP
35+ * don't work, so we rely on the 'exit' event which is guaranteed to run.
36+ */
37+ function resetTerminalState ( ) : void {
38+ if ( terminalStateReset ) return
39+ terminalStateReset = true
40+
41+ try {
42+ // Write directly to stdout - this is synchronous and will complete
43+ // before the process exits, ensuring the terminal is reset
44+ process . stdout . write ( TERMINAL_RESET_SEQUENCES )
45+ } catch {
46+ // Ignore errors - stdout may already be closed
47+ }
48+ }
549
650/**
751 * Clean up the renderer by calling destroy().
852 * This resets terminal state to prevent garbled output after exit.
953 */
1054function cleanup ( ) : void {
55+ // FIRST: Reset terminal state by writing escape sequences directly to stdout.
56+ // This ensures mouse mode, focus reporting, etc. are disabled even if
57+ // renderer.destroy() fails or doesn't fully clean up.
58+ resetTerminalState ( )
59+
1160 if ( renderer && ! renderer . isDestroyed ) {
1261 try {
1362 renderer . destroy ( )
0 commit comments