Squashed 'third_party/pi/' content from commit d981de122

git-subtree-dir: third_party/pi
git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
This commit is contained in:
2026-09-14 10:40:08 -04:00
commit 67b361f0e9
1669 changed files with 397627 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
# Windows native prebuilds
Build both Windows architectures from the repository root:
```sh
npm --prefix packages/tui run build:native:win32
```
On Windows, the build uses the Microsoft C++ Build Tools from Visual Studio. `build.mjs` locates `VsDevCmd.bat`, initializes a developer environment for `amd64` and `arm64`, and builds the addon with `cl.exe`/`link.exe`.
Install the "Desktop development with C++" workload, or at minimum the MSVC toolset and Windows SDK components. No Node headers are required; the addon resolves N-API symbols from the host process.
For non-Windows cross-builds, or for custom Windows toolchains, set `PI_TUI_WIN32_TOOLCHAIN=mingw` and provide MinGW-compatible compilers:
```sh
PI_TUI_WIN32_TOOLCHAIN=mingw \
CC_X64=/path/to/x86_64-w64-mingw32-gcc \
CC_ARM64=/path/to/aarch64-w64-mingw32-gcc \
npm --prefix packages/tui run build:native:win32
```
The addon intentionally avoids the C runtime and links only against `kernel32`.

View File

@@ -0,0 +1,229 @@
import { spawnSync } from "node:child_process";
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const sourceFile = path.join(scriptDir, "src", "win32-console-mode.c");
const temporaryDir = mkdtempSync(path.join(tmpdir(), "pi-tui-win32-"));
const targets = [
{
arch: "x64",
msvcArch: "x64",
mingwTarget: "x86_64-w64-windows-gnu",
prefixedCompilers: ["x86_64-w64-mingw32-gcc", "x86_64-w64-mingw32-clang"],
envCompiler: "CC_X64",
},
{
arch: "arm64",
msvcArch: "arm64",
mingwTarget: "aarch64-w64-windows-gnu",
prefixedCompilers: ["aarch64-w64-mingw32-gcc", "aarch64-w64-mingw32-clang"],
envCompiler: "CC_ARM64",
},
];
function removeTemporaryDir() {
rmSync(temporaryDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
}
process.on("exit", removeTemporaryDir);
process.on("SIGINT", () => {
removeTemporaryDir();
process.exit(130);
});
function commandExists(command) {
const result = spawnSync(command, ["--version"], { stdio: "ignore" });
return !result.error || result.error.code !== "ENOENT";
}
function run(command, args, options = {}) {
const result = spawnSync(command, args, { stdio: "inherit", ...options });
if (result.error) throw result.error;
if (result.status !== 0) throw new Error(`${command} failed with exit code ${result.status ?? 1}`);
}
function quoteBatch(value) {
return `"${value.replaceAll('"', '""')}"`;
}
function msvcHostArch() {
return process.arch === "arm64" ? "arm64" : "x64";
}
function findVisualStudioDevCmd() {
const candidates = [];
const programFilesX86 = process.env["ProgramFiles(x86)"];
if (programFilesX86) {
const vswhere = path.join(programFilesX86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
if (existsSync(vswhere)) {
const result = spawnSync(
vswhere,
[
"-latest",
"-products",
"*",
"-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"-property",
"installationPath",
],
{ encoding: "utf8" },
);
for (const line of result.stdout.split(/\r?\n/)) {
const installationPath = line.trim();
if (installationPath) candidates.push(path.join(installationPath, "Common7", "Tools", "VsDevCmd.bat"));
}
}
}
for (const base of [process.env.ProgramFiles, programFilesX86]) {
if (!base) continue;
for (const edition of ["BuildTools", "Community", "Professional", "Enterprise"]) {
candidates.push(path.join(base, "Microsoft Visual Studio", "2022", edition, "Common7", "Tools", "VsDevCmd.bat"));
}
}
return candidates.find((candidate) => existsSync(candidate));
}
function canUseMsvc(vsDevCmd) {
const probeDir = path.join(temporaryDir, "msvc-probe");
mkdirSync(probeDir, { recursive: true });
const batchFile = path.join(probeDir, "probe.cmd");
writeFileSync(
batchFile,
[
"@echo off",
`call ${quoteBatch(vsDevCmd)} -no_logo -arch=x64 -host_arch=${msvcHostArch()} >nul`,
"if errorlevel 1 exit /b %errorlevel%",
"where cl.exe >nul 2>nul",
].join("\r\n"),
);
const result = spawnSync("cmd.exe", ["/d", "/s", "/c", batchFile], { cwd: probeDir, stdio: "ignore" });
return !result.error && result.status === 0;
}
function resolveMingwCompiler(target) {
const envCompiler = process.env[target.envCompiler];
if (envCompiler) return { command: envCompiler, useTargetFlag: false };
if (process.env.CC) return { command: process.env.CC, useTargetFlag: true };
for (const command of target.prefixedCompilers) {
if (commandExists(command)) return { command, useTargetFlag: false };
}
if (commandExists("clang")) return { command: "clang", useTargetFlag: true };
return undefined;
}
function installOutput(temporaryOutput, target) {
const outputDir = path.join(scriptDir, "prebuilds", `win32-${target.arch}`);
mkdirSync(outputDir, { recursive: true });
const output = path.join(outputDir, "win32-console-mode.node");
copyFileSync(temporaryOutput, output);
chmodSync(output, 0o755);
console.log(`Built ${path.relative(process.cwd(), output)}`);
}
function buildWithMsvc(target, vsDevCmd) {
const buildDir = path.join(temporaryDir, `msvc-${target.arch}`);
const temporaryOutput = path.join(buildDir, "win32-console-mode.node");
mkdirSync(buildDir, { recursive: true });
const batchFile = path.join(buildDir, "build.cmd");
writeFileSync(
batchFile,
[
"@echo off",
`call ${quoteBatch(vsDevCmd)} -no_logo -arch=${target.msvcArch} -host_arch=${msvcHostArch()}`,
"if errorlevel 1 exit /b %errorlevel%",
[
"cl",
"/nologo",
"/TC",
"/std:c11",
"/W4",
"/O1",
"/GS-",
"/LD",
`/Fe:${quoteBatch(temporaryOutput)}`,
quoteBatch(sourceFile),
"/link",
"/NOENTRY",
"/NODEFAULTLIB",
"kernel32.lib",
"/OPT:REF",
"/OPT:ICF",
].join(" "),
"if errorlevel 1 exit /b %errorlevel%",
].join("\r\n"),
);
run("cmd.exe", ["/d", "/s", "/c", batchFile], { cwd: buildDir });
installOutput(temporaryOutput, target);
}
function buildWithMingw(target) {
const compiler = resolveMingwCompiler(target);
if (!compiler) {
throw new Error(
`No Windows cross-compiler found for ${target.arch}. Install Microsoft C++ Build Tools on Windows, or set ${target.envCompiler} to a MinGW-compatible compiler.`,
);
}
const buildDir = path.join(temporaryDir, `mingw-${target.arch}`);
const temporaryOutput = path.join(buildDir, "win32-console-mode.node");
mkdirSync(buildDir, { recursive: true });
const args = [
...(compiler.useTargetFlag ? [`--target=${target.mingwTarget}`] : []),
"-std=c11",
"-Wall",
"-Wextra",
"-Oz",
"-shared",
"-nostdlib",
"-Wl,--no-entry",
"-Wl,--strip-all",
sourceFile,
"-lkernel32",
"-o",
temporaryOutput,
];
run(compiler.command, args, { cwd: buildDir });
installOutput(temporaryOutput, target);
}
if (process.argv.includes("--help") || process.argv.includes("-h")) {
console.log(`Usage: npm --prefix packages/tui run build:native:win32
Builds win32-x64 and win32-arm64 native prebuilds.
Environment:
PI_TUI_WIN32_TOOLCHAIN=msvc|mingw
CC_X64=/path/to/x86_64-w64-mingw32-gcc
CC_ARM64=/path/to/aarch64-w64-mingw32-gcc
CC=/path/to/clang`);
process.exit(0);
}
const requestedToolchain = process.env.PI_TUI_WIN32_TOOLCHAIN;
if (requestedToolchain && requestedToolchain !== "msvc" && requestedToolchain !== "mingw") {
throw new Error("PI_TUI_WIN32_TOOLCHAIN must be either 'msvc' or 'mingw'");
}
const vsDevCmd = findVisualStudioDevCmd();
const hasMsvc = vsDevCmd ? canUseMsvc(vsDevCmd) : false;
const toolchain = requestedToolchain ?? (process.platform === "win32" && vsDevCmd ? "msvc" : "mingw");
if (toolchain === "msvc") {
if (!vsDevCmd || !hasMsvc) {
throw new Error("Microsoft C++ Build Tools not found. Install the Visual Studio C++ workload or set PI_TUI_WIN32_TOOLCHAIN=mingw.");
}
for (const target of targets) buildWithMsvc(target, vsDevCmd);
} else {
for (const target of targets) buildWithMingw(target);
}

View File

@@ -0,0 +1,119 @@
#include <windows.h>
#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
#endif
#define NAPI_AUTO_LENGTH ((unsigned long long)-1)
#define KEY_PRESSED_MASK 0x8000
typedef void* napi_env;
typedef void* napi_value;
typedef void* napi_callback_info;
typedef napi_value (__cdecl *napi_callback)(napi_env, napi_callback_info);
typedef int (__cdecl *napi_create_function_fn)(napi_env, const char*, unsigned long long, napi_callback, void*, napi_value*);
typedef int (__cdecl *napi_set_named_property_fn)(napi_env, napi_value, const char*, napi_value);
typedef int (__cdecl *napi_get_boolean_fn)(napi_env, int, napi_value*);
typedef int (__cdecl *napi_get_cb_info_fn)(napi_env, napi_callback_info, unsigned long long*, napi_value*, napi_value*, void**);
typedef int (__cdecl *napi_get_value_string_utf8_fn)(napi_env, napi_value, char*, unsigned long long, unsigned long long*);
typedef SHORT (WINAPI *get_async_key_state_fn)(int);
static void* node_symbol(const char* name) {
HMODULE module = GetModuleHandleA(0);
void* proc = module ? (void*)GetProcAddress(module, name) : 0;
if (proc) return proc;
module = GetModuleHandleA("node.dll");
return module ? (void*)GetProcAddress(module, name) : 0;
}
static int string_equals(const char* left, const char* right) {
while (*left && *right && *left == *right) {
left++;
right++;
}
return *left == 0 && *right == 0;
}
static get_async_key_state_fn get_async_key_state_symbol(void) {
static int loaded = 0;
static get_async_key_state_fn get_async_key_state = 0;
if (!loaded) {
HMODULE module = GetModuleHandleA("user32.dll");
if (!module) module = LoadLibraryA("user32.dll");
get_async_key_state = module ? (get_async_key_state_fn)GetProcAddress(module, "GetAsyncKeyState") : 0;
loaded = 1;
}
return get_async_key_state;
}
static int is_key_pressed(int virtual_key) {
get_async_key_state_fn get_async_key_state = get_async_key_state_symbol();
return get_async_key_state && (((unsigned short)get_async_key_state(virtual_key)) & KEY_PRESSED_MASK) != 0;
}
static int is_modifier_name_pressed(const char* name) {
if (string_equals(name, "shift")) return is_key_pressed(VK_SHIFT) || is_key_pressed(VK_LSHIFT) || is_key_pressed(VK_RSHIFT);
if (string_equals(name, "control")) return is_key_pressed(VK_CONTROL) || is_key_pressed(VK_LCONTROL) || is_key_pressed(VK_RCONTROL);
if (string_equals(name, "option") || string_equals(name, "alt")) return is_key_pressed(VK_MENU) || is_key_pressed(VK_LMENU) || is_key_pressed(VK_RMENU);
if (string_equals(name, "command") || string_equals(name, "super") || string_equals(name, "win")) return is_key_pressed(VK_LWIN) || is_key_pressed(VK_RWIN);
return 0;
}
static napi_value __cdecl enable_virtual_terminal_input(napi_env env, napi_callback_info info) {
(void)info;
HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode = 0;
int enabled = handle != INVALID_HANDLE_VALUE &&
GetConsoleMode(handle, &mode) &&
SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_INPUT);
napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
napi_value result = 0;
if (napi_get_boolean) napi_get_boolean(env, enabled, &result);
return result;
}
static napi_value __cdecl is_modifier_pressed(napi_env env, napi_callback_info info) {
napi_get_cb_info_fn napi_get_cb_info = (napi_get_cb_info_fn)node_symbol("napi_get_cb_info");
napi_get_value_string_utf8_fn napi_get_value_string_utf8 = (napi_get_value_string_utf8_fn)node_symbol("napi_get_value_string_utf8");
napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
int pressed = 0;
if (napi_get_cb_info && napi_get_value_string_utf8) {
unsigned long long argc = 1;
napi_value args[1] = {0};
if (napi_get_cb_info(env, info, &argc, args, 0, 0) == 0 && argc >= 1 && args[0]) {
char name[16] = {0};
unsigned long long copied = 0;
if (napi_get_value_string_utf8(env, args[0], name, sizeof(name), &copied) == 0) {
pressed = is_modifier_name_pressed(name);
}
}
}
napi_value result = 0;
if (napi_get_boolean) napi_get_boolean(env, pressed, &result);
return result;
}
static void set_function_export(napi_env env, napi_value exports, const char* name, napi_callback callback) {
napi_create_function_fn napi_create_function = (napi_create_function_fn)node_symbol("napi_create_function");
napi_set_named_property_fn napi_set_named_property = (napi_set_named_property_fn)node_symbol("napi_set_named_property");
napi_value fn = 0;
if (napi_create_function &&
napi_set_named_property &&
napi_create_function(env, name, NAPI_AUTO_LENGTH, callback, 0, &fn) == 0) {
napi_set_named_property(env, exports, name, fn);
}
}
__declspec(dllexport) napi_value __cdecl napi_register_module_v1(napi_env env, napi_value exports) {
set_function_export(env, exports, "enableVirtualTerminalInput", enable_virtual_terminal_input);
set_function_export(env, exports, "isModifierPressed", is_modifier_pressed);
return exports;
}