import { dlopen, ptr } from "bun:ffi" const STD_INPUT_HANDLE = -10 const ENABLE_PROCESSED_INPUT = 0x0001 const kernel = () => dlopen("kernel32.dll", { GetStdHandle: { args: ["i32"], returns: "ptr" }, GetConsoleMode: { args: ["ptr", "ptr"], returns: "i32" }, SetConsoleMode: { args: ["ptr", "u32"], returns: "i32" }, FlushConsoleInputBuffer: { args: ["ptr"], returns: "i32" }, }) let k32: ReturnType | undefined function load() { if (process.platform !== "win32") return false try { k32 ??= kernel() return true } catch { return false } } /** * Clear ENABLE_PROCESSED_INPUT on the console stdin handle. */ export function win32DisableProcessedInput() { if (process.platform !== "win32") return if (!process.stdin.isTTY) return if (!load()) return const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE) const buf = new Uint32Array(1) 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) } /** * Discard any queued console input (mouse events, key presses, etc.). */ export function win32FlushInputBuffer() { if (process.platform !== "win32") return if (!process.stdin.isTTY) return if (!load()) return const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE) k32!.symbols.FlushConsoleInputBuffer(handle) } let unhook: (() => void) | undefined /** * Keep ENABLE_PROCESSED_INPUT disabled. * * 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. * * 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 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) if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return const initial = buf[0]! 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) } // Some runtimes can re-apply console modes on the next tick; enforce twice. const later = () => { enforce() setImmediate(enforce) } let wrapped: ((mode: boolean) => unknown) | undefined if (typeof original === "function") { wrapped = (mode: boolean) => { const result = original.call(stdin, mode) later() return result } stdin.setRawMode = wrapped } // Ensure it's cleared immediately too (covers any earlier mode changes). later() const interval = setInterval(enforce, 100) interval.unref() let done = false unhook = () => { if (done) return done = true clearInterval(interval) if (wrapped && stdin.setRawMode === wrapped) { stdin.setRawMode = original } k32!.symbols.SetConsoleMode(handle, initial) unhook = undefined } return unhook }