diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 4161c025c1..5941725f5c 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -16,7 +16,7 @@ import { on, onCleanup, } from "solid-js" -import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" +import { win32DisableProcessedInput, win32Init, win32InstallCtrlCGuard } from "./win32" import { Flag } from "@/flag/flag" import semver from "semver" import { DialogProvider, useDialog } from "@tui/ui/dialog" @@ -176,6 +176,7 @@ export function tui(input: { }) { // promise to prevent immediate exit return new Promise(async (resolve) => { + await win32Init() const unguard = win32InstallCtrlCGuard() win32DisableProcessedInput() diff --git a/packages/opencode/src/cli/cmd/tui/win32.ts b/packages/opencode/src/cli/cmd/tui/win32.ts index 23e9f44857..11659a7d64 100644 --- a/packages/opencode/src/cli/cmd/tui/win32.ts +++ b/packages/opencode/src/cli/cmd/tui/win32.ts @@ -1,24 +1,34 @@ -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 mod: typeof import("bun:ffi") | undefined +type K32 = { + GetStdHandle: (n: number) => bigint | number + GetConsoleMode: (h: bigint | number, mode: unknown) => number + SetConsoleMode: (h: bigint | number, mode: number) => number + FlushConsoleInputBuffer: (h: bigint | number) => number +} -let k32: ReturnType | undefined +let k32: K32 | undefined +let ready: boolean | undefined -function load() { +type Ptr = typeof import("bun:ffi").ptr + +export async function win32Init() { if (process.platform !== "win32") return false + if (ready !== undefined) return ready try { - k32 ??= kernel() + mod = await import("bun:ffi") + k32 = (mod.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" }, + }).symbols as unknown as K32) + ready = true return true } catch { + ready = false return false } } @@ -29,15 +39,17 @@ function load() { export function win32DisableProcessedInput() { if (process.platform !== "win32") return if (!process.stdin.isTTY) return - if (!load()) return + if (!k32 || !mod) return + const ptr = mod.ptr as Ptr - const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE) + const lib = k32 + const handle = lib.GetStdHandle(STD_INPUT_HANDLE) const buf = new Uint32Array(1) - if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return + if (lib.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) + lib.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT) } /** @@ -46,10 +58,11 @@ export function win32DisableProcessedInput() { export function win32FlushInputBuffer() { if (process.platform !== "win32") return if (!process.stdin.isTTY) return - if (!load()) return + if (!k32) return - const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE) - k32!.symbols.FlushConsoleInputBuffer(handle) + const lib = k32 + const handle = lib.GetStdHandle(STD_INPUT_HANDLE) + lib.FlushConsoleInputBuffer(handle) } let unhook: (() => void) | undefined @@ -68,23 +81,25 @@ let unhook: (() => void) | undefined export function win32InstallCtrlCGuard() { if (process.platform !== "win32") return if (!process.stdin.isTTY) return - if (!load()) return + if (!k32 || !mod) return if (unhook) return unhook + const ptr = mod.ptr as Ptr + const lib = k32 const stdin = process.stdin as any const original = stdin.setRawMode - const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE) + const handle = lib.GetStdHandle(STD_INPUT_HANDLE) const buf = new Uint32Array(1) - if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return + if (lib.GetConsoleMode(handle, ptr(buf)) === 0) return const initial = buf[0]! const enforce = () => { - if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return + if (lib.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) + lib.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT) } // Some runtimes can re-apply console modes on the next tick; enforce twice. @@ -121,7 +136,7 @@ export function win32InstallCtrlCGuard() { stdin.setRawMode = original } - k32!.symbols.SetConsoleMode(handle, initial) + lib.SetConsoleMode(handle, initial) unhook = undefined }