hook better instead of 100ms loop lol
This commit is contained in:
@@ -3,7 +3,7 @@ import { Clipboard } from "@tui/util/clipboard"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { RouteProvider, useRoute } from "@tui/context/route"
|
||||
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show, on } from "solid-js"
|
||||
import { win32DisableProcessedInput, win32IgnoreCtrlC, win32EnforceCtrlCGuard } from "./win32"
|
||||
import { win32DisableProcessedInput, win32IgnoreCtrlC, win32InstallCtrlCGuard } from "./win32"
|
||||
import { Installation } from "@/installation"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { DialogProvider, useDialog } from "@tui/ui/dialog"
|
||||
@@ -111,6 +111,7 @@ export function tui(input: {
|
||||
}) {
|
||||
// promise to prevent immediate exit
|
||||
return new Promise<void>(async (resolve) => {
|
||||
const unguard = win32InstallCtrlCGuard()
|
||||
win32DisableProcessedInput()
|
||||
win32IgnoreCtrlC()
|
||||
|
||||
@@ -121,6 +122,7 @@ export function tui(input: {
|
||||
win32DisableProcessedInput()
|
||||
|
||||
const onExit = async () => {
|
||||
unguard?.()
|
||||
await input.onExit?.()
|
||||
resolve()
|
||||
}
|
||||
@@ -247,10 +249,6 @@ function App() {
|
||||
|
||||
const args = useArgs()
|
||||
onMount(() => {
|
||||
// opentui reconfigures console mode via native calls which re-enable
|
||||
// ENABLE_PROCESSED_INPUT. Poll and re-clear it so the parent `bun run`
|
||||
// wrapper isn't killed by CTRL_C_EVENT.
|
||||
win32EnforceCtrlCGuard()
|
||||
batch(() => {
|
||||
if (args.agent) local.agent.set(args.agent)
|
||||
if (args.model) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Log } from "@/util/log"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
||||
import type { Event } from "@opencode-ai/sdk/v2"
|
||||
import type { EventSource } from "./context/sdk"
|
||||
import { win32DisableProcessedInput } from "./win32"
|
||||
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
||||
|
||||
declare global {
|
||||
const OPENCODE_WORKER_PATH: string
|
||||
@@ -78,6 +78,10 @@ export const TuiThreadCommand = cmd({
|
||||
describe: "agent to use",
|
||||
}),
|
||||
handler: async (args) => {
|
||||
// Keep ENABLE_PROCESSED_INPUT cleared even if other code flips it.
|
||||
// (Important when running under `bun run` wrappers on Windows.)
|
||||
win32InstallCtrlCGuard()
|
||||
|
||||
// Must be the very first thing — disables CTRL_C_EVENT before any Worker
|
||||
// spawn or async work so the OS cannot kill the process group.
|
||||
win32DisableProcessedInput()
|
||||
|
||||
@@ -55,32 +55,64 @@ export function win32IgnoreCtrlC() {
|
||||
k32!.symbols.SetConsoleCtrlHandler(null, 1)
|
||||
}
|
||||
|
||||
let unhook: (() => void) | undefined
|
||||
|
||||
/**
|
||||
* Continuously enforce ENABLE_PROCESSED_INPUT=off on the console.
|
||||
* Keep ENABLE_PROCESSED_INPUT disabled.
|
||||
*
|
||||
* opentui reconfigures the console mode through native calls (not
|
||||
* process.stdin.setRawMode) so we cannot intercept them. Instead we
|
||||
* poll at a low frequency and re-clear the flag when needed.
|
||||
* On Windows, Ctrl+C becomes a CTRL_C_EVENT (instead of stdin input) when
|
||||
* ENABLE_PROCESSED_INPUT is set. Various runtimes can re-apply console modes
|
||||
* (sometimes on a later tick), and the flag is console-global, not per-process.
|
||||
*
|
||||
* Because ENABLE_PROCESSED_INPUT is a console-level flag (not per-process),
|
||||
* keeping it cleared protects every process attached to this console,
|
||||
* including the parent `bun run` wrapper that we can't otherwise control.
|
||||
*
|
||||
* The fast-path (GetConsoleMode + bitmask check) is sub-microsecond;
|
||||
* SetConsoleMode only fires when something re-enabled the flag.
|
||||
* We combine:
|
||||
* - A `setRawMode(...)` hook to re-clear after known raw-mode toggles.
|
||||
* - A low-frequency poll as a backstop for native/external mode changes.
|
||||
*/
|
||||
export function win32EnforceCtrlCGuard() {
|
||||
export function win32InstallCtrlCGuard() {
|
||||
if (process.platform !== "win32") return
|
||||
if (!process.stdin.isTTY) return
|
||||
if (!load()) return
|
||||
if (unhook) return unhook
|
||||
|
||||
const stdin = process.stdin as any
|
||||
const original = stdin.setRawMode
|
||||
|
||||
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
||||
const buf = new Uint32Array(1)
|
||||
|
||||
setInterval(() => {
|
||||
const enforce = () => {
|
||||
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
||||
const mode = buf[0]!
|
||||
if ((mode & ENABLE_PROCESSED_INPUT) === 0) return
|
||||
k32!.symbols.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// Some runtimes can re-apply console modes on the next tick; enforce twice.
|
||||
const later = () => {
|
||||
enforce()
|
||||
setImmediate(enforce)
|
||||
}
|
||||
|
||||
if (typeof original === "function") {
|
||||
stdin.setRawMode = (mode: boolean) => {
|
||||
const result = original.call(stdin, mode)
|
||||
later()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure it's cleared immediately too (covers any earlier mode changes).
|
||||
later()
|
||||
|
||||
const interval = setInterval(enforce, 100)
|
||||
|
||||
unhook = () => {
|
||||
clearInterval(interval)
|
||||
if (typeof original === "function") {
|
||||
stdin.setRawMode = original
|
||||
}
|
||||
unhook = undefined
|
||||
}
|
||||
|
||||
return unhook
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user