Squashed 'third_party/pi/' content from commit d981de122
git-subtree-dir: third_party/pi git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
This commit is contained in:
20
packages/tui/native/darwin/README.md
Normal file
20
packages/tui/native/darwin/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Darwin native prebuilds
|
||||
|
||||
Build both macOS architectures from the repository root:
|
||||
|
||||
```sh
|
||||
npm --prefix packages/tui run build:native:darwin
|
||||
```
|
||||
|
||||
The build uses macOS 11.0 as the arm64 deployment target and macOS 10.15 as the x86_64 deployment target. On macOS, `build.sh` finds Apple clang and the active macOS SDK through `xcrun`. Either an Intel or Apple Silicon host can build both outputs.
|
||||
|
||||
A non-macOS host needs a complete Darwin cross-toolchain, including a macOS SDK and a Mach-O linker. For example, an osxcross installation can be selected with `CC` and `SDKROOT`:
|
||||
|
||||
```sh
|
||||
CC=/path/to/osxcross/clang SDKROOT=/path/to/MacOSX.sdk \
|
||||
npm --prefix packages/tui run build:native:darwin
|
||||
```
|
||||
|
||||
The SDK must be obtained and used in accordance with Apple's license. Plain Linux or Windows clang is not enough because the addon includes and links CoreGraphics.
|
||||
|
||||
Zig is not used here because it does not provide the Apple SDK or CoreGraphics framework stubs. It therefore does not make this build SDK-independent, and its clang driver does not currently handle this Mach-O bundle recipe as a drop-in replacement for Apple clang.
|
||||
62
packages/tui/native/darwin/build.sh
Executable file
62
packages/tui/native/darwin/build.sh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source_file="$script_dir/src/darwin-modifiers.c"
|
||||
|
||||
if [[ -n "${CC:-}" ]]; then
|
||||
compiler="$CC"
|
||||
elif [[ "$(uname -s)" == "Darwin" ]] && command -v xcrun >/dev/null 2>&1; then
|
||||
compiler="$(xcrun --find clang)"
|
||||
else
|
||||
compiler="clang"
|
||||
fi
|
||||
|
||||
if ! command -v "$compiler" >/dev/null 2>&1; then
|
||||
echo "Darwin C compiler not found: $compiler" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sdk_flags=()
|
||||
if [[ -n "${SDKROOT:-}" ]]; then
|
||||
if [[ ! -d "$SDKROOT" ]]; then
|
||||
echo "SDKROOT does not exist: $SDKROOT" >&2
|
||||
exit 1
|
||||
fi
|
||||
sdk_flags=(-isysroot "$SDKROOT" "-F$SDKROOT/System/Library/Frameworks")
|
||||
elif [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
sdkroot="$(xcrun --sdk macosx --show-sdk-path)"
|
||||
sdk_flags=(-isysroot "$sdkroot" "-F$sdkroot/System/Library/Frameworks")
|
||||
fi
|
||||
|
||||
temporary_dir="$(mktemp -d "${TMPDIR:-/tmp}/pi-tui-darwin.XXXXXX")"
|
||||
trap 'rm -rf "$temporary_dir"' EXIT
|
||||
|
||||
build() {
|
||||
local arch="$1"
|
||||
local target="$2"
|
||||
local output_dir="$script_dir/prebuilds/darwin-$arch"
|
||||
local temporary_output="$temporary_dir/darwin-$arch/darwin-modifiers.node"
|
||||
|
||||
mkdir -p "$(dirname "$temporary_output")"
|
||||
"$compiler" \
|
||||
-std=c11 \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-O2 \
|
||||
"--target=$target" \
|
||||
"${sdk_flags[@]}" \
|
||||
-bundle \
|
||||
-undefined dynamic_lookup \
|
||||
-framework CoreGraphics \
|
||||
"$source_file" \
|
||||
-o "$temporary_output"
|
||||
|
||||
mkdir -p "$output_dir"
|
||||
install -m 755 "$temporary_output" "$output_dir/darwin-modifiers.node"
|
||||
echo "Built $output_dir/darwin-modifiers.node"
|
||||
}
|
||||
|
||||
build arm64 arm64-apple-macos11.0
|
||||
build x64 x86_64-apple-macos10.15
|
||||
BIN
packages/tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node
Executable file
BIN
packages/tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node
Executable file
Binary file not shown.
BIN
packages/tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node
Executable file
BIN
packages/tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node
Executable file
Binary file not shown.
70
packages/tui/native/darwin/src/darwin-modifiers.c
Normal file
70
packages/tui/native/darwin/src/darwin-modifiers.c
Normal file
@@ -0,0 +1,70 @@
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NAPI_AUTO_LENGTH ((size_t)-1)
|
||||
|
||||
typedef void* napi_env;
|
||||
typedef void* napi_value;
|
||||
typedef void* napi_callback_info;
|
||||
typedef napi_value (*napi_callback)(napi_env, napi_callback_info);
|
||||
typedef int (*napi_create_function_fn)(napi_env, const char*, size_t, napi_callback, void*, napi_value*);
|
||||
typedef int (*napi_set_named_property_fn)(napi_env, napi_value, const char*, napi_value);
|
||||
typedef int (*napi_get_boolean_fn)(napi_env, bool, napi_value*);
|
||||
typedef int (*napi_get_cb_info_fn)(napi_env, napi_callback_info, size_t*, napi_value*, napi_value*, void**);
|
||||
typedef int (*napi_get_value_string_utf8_fn)(napi_env, napi_value, char*, size_t, size_t*);
|
||||
|
||||
static void* node_symbol(const char* name) {
|
||||
return dlsym(RTLD_DEFAULT, name);
|
||||
}
|
||||
|
||||
static CGEventFlags modifier_mask_for_name(const char* name) {
|
||||
if (strcmp(name, "shift") == 0) return kCGEventFlagMaskShift;
|
||||
if (strcmp(name, "command") == 0) return kCGEventFlagMaskCommand;
|
||||
if (strcmp(name, "control") == 0) return kCGEventFlagMaskControl;
|
||||
if (strcmp(name, "option") == 0) return kCGEventFlagMaskAlternate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static napi_value 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");
|
||||
|
||||
bool pressed = false;
|
||||
if (napi_get_cb_info && napi_get_value_string_utf8) {
|
||||
size_t 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};
|
||||
size_t copied = 0;
|
||||
if (napi_get_value_string_utf8(env, args[0], name, sizeof(name), &copied) == 0) {
|
||||
CGEventFlags mask = modifier_mask_for_name(name);
|
||||
if (mask != 0) {
|
||||
CGEventFlags flags = CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState);
|
||||
pressed = (flags & mask) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
napi_value result = 0;
|
||||
if (napi_get_boolean) napi_get_boolean(env, pressed, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) napi_value napi_register_module_v1(napi_env env, napi_value exports) {
|
||||
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, "isModifierPressed", NAPI_AUTO_LENGTH, is_modifier_pressed, 0, &fn) == 0) {
|
||||
napi_set_named_property(env, exports, "isModifierPressed", fn);
|
||||
}
|
||||
|
||||
return exports;
|
||||
}
|
||||
22
packages/tui/native/win32/README.md
Normal file
22
packages/tui/native/win32/README.md
Normal 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`.
|
||||
229
packages/tui/native/win32/build.mjs
Normal file
229
packages/tui/native/win32/build.mjs
Normal 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);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
119
packages/tui/native/win32/src/win32-console-mode.c
Normal file
119
packages/tui/native/win32/src/win32-console-mode.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user