GDI.js on a Node host
GDI.js began as a standalone V8 JavaScript runtime for Windows. As that V8 hosting layer became outdated, it was replaced with an embedded Node.js host.
Node now owns JavaScript execution, the event loop, modules, timers and standard facilities such as filesystem, HTTP, TCP, UDP, named pipes, processes, cryptography and buffers. The GDI-specific modules remain: Windows desktop integration, devices, serial ports, SAPI, BASS audio, SQLite, Bluetooth, hardware events, machine state and the established GDI.js helper layer.
The practical result is a turbocharged Node.js fork optimized for Windows user-space applications, while standard Node.js remains primarily focused on server-side applications.
GDI.js is intended for entry-level and mid-level JavaScript developers as well as Windows power users. Basic scripts can call the global GDI modules directly. More advanced applications can combine them with the complete Node module set.
const fs = require('node:fs');
const net = require('node:net');
console.log(process.version);
console.log($tts.voices());
console.log($bit.current());
Runtime model
Replacing the V8 host changed who runs JavaScript and common services; it did not replace the GDI module set.
| Owner | Public responsibility |
|---|---|
| Node.js | JavaScript execution, event loop, CommonJS modules, asynchronous work, timers, files, networking, processes, cryptography and buffers |
| GDI.js | Public $name modules, Windows desktop and device integration, callbacks, parsers, helpers and calculations |
Both API families are available in the same script: import standard Node modules with require() and call GDI-specific modules through their global $name objects. The current distribution supports Win64.
The public native surface is $tts, $audio, $sqlite, $disk, $os, $serial, $hw, $usr, $ipc, $bt, and $bit.
Run scripts
Run GDI.js without an argument to start the runtime and wait for shutdown:
GDI.exe
Pass one JavaScript file to run an application:
GDI.exe demo.js
GDI.exe D:\applications\assistant.js
First application
Save the following as system-report.js, then run GDI.exe system-report.js.
const fs = require('node:fs');
const report = {
createdAt: new Date().toISOString(),
userName: $usr.name(),
screenResolution: $usr.resolution(),
serialPorts: JSON.parse($serial.enum()),
bluetoothRadios: $bt.listRadios(),
machineState: $bit.current()
};
fs.writeFileSync(
'system-report.json',
JSON.stringify(report, null, 2)
);
console.log('System report created.');
Facilities owned by Node
The Node host takes over the event loop and general-purpose runtime services. GDI does not duplicate them with custom wrappers.
const fs = require('node:fs');
const http = require('node:http');
const net = require('node:net');
const dgram = require('node:dgram');
const crypto = require('node:crypto');
const { spawn } = require('node:child_process');
// Windows named pipe example
const pipe = net.createConnection('\\\\.\\pipe\\ask');
pipe.write(JSON.stringify({ prompt: 'Describe the active desktop.' }));
This includes files, HTTP, TCP, UDP, named pipes, processes, worker threads, hashing, Base64 through Buffer, and timers. GDI-specific modules continue to use the public $name objects documented below.
$tts
Asynchronous Windows SAPI synthesis with deterministic voice-cache naming.
const path = require('node:path');
const voices = $tts.voices();
const voice = voices.find(name =>
name.toLowerCase().includes('sapiproxy')
);
const synthesisJobId = $tts.render(
'Hello from GDI.js for Windows.',
voice,
path.join(process.cwd(), 'userdata', 'cache', 'voxcache'),
(file, elapsedMs) => console.log(file, elapsedMs)
);
voices(): string[]synchronously returns installed voice descriptions.render(text, voice, folder, callback): stringreturns a job ID immediately and completes synthesis asynchronously.- The voice is matched as a case-insensitive description substring. An empty voice selects the first installed voice.
- The output directory must exist. Output is a 44.1 kHz, 16-bit stereo WAV.
$audio
BASS-backed samples, streams, and Judith speech playback.
const audioId = $audio.init(44100);
$audio.sload(audioId, 'sounds/notify.ogg', 'notification');
const sampleDuration = $audio.splay(audioId, 'notification');
const streamDuration = $audio.stream(audioId, 'music/example.ogg');
const speechDuration = $audio.playd(audioId, 'userdata/cache/voxcache/speech.wav');
$audio.kill(audioId);
init(rate = 44100): numberkill(id): numbersload(id, file, tag): numbersplay(id, tag): numberstream(id, file): numberplayd(id, file): numberapplies speech-oriented echo processing.
Playback methods return the rounded media duration in seconds.
$sqlite
SQLite 3.53.4 database access for synchronous and asynchronous queries.
const connectionId = $sqlite.open('database/application.sqlite');
const rows = JSON.parse($sqlite.query(connectionId,
'select name from sqlite_master order by name'
));
$sqlite.exec(connectionId, "update settings set value='1' where name='active'");
$sqlite.close(connectionId);
$sqlite.queryEx('database/application.sqlite', 'select * from events', rows => {
console.log(rows);
});
open(file): numberreturns a positive connection ID or0when the file does not exist. It does not create a missing file.close(id): 0 | 1query(id, sql): stringreturns a JSON array string. Field values are strings; SQL NULL becomes an empty string.queryEx(file, sql, callback): voiduses an independent worker-thread connection.exec(id, sql): 0 | 1executes one or more statements.
$disk
const disks = JSON.parse($disk.enum());
const driveC = disks['C'.charCodeAt(0) - 'A'.charCodeAt(0)];
enum() returns a JSON string containing 26 positional drive slots, A through Z. Records contain _label, _Char, _type, active, disksize, diskfree, and serial. Sizes are integer MiB.
$os
$os.onEvent(code => console.log('OS event', code));
$os.onDevice(arrival => console.log('device', arrival));
$os.onMessage(message => console.log('message', message));
// Explicitly disruptive operations:
// $os.toggleMonitor(false);
// $os.lock();
toggleMonitor(enabled)changes monitor power state.lock()locks the current Windows session.onEvent(callback),onDevice(callback), andonMessage(callback)register process-lifetime listeners.- OS event codes are command 1, time change 2, power 3, display change 4, and end-session 5.
$serial
const ports = JSON.parse($serial.enum());
const connectionId = $serial.open(ports[0].port, 9600);
$serial.write(connectionId, 'STATUS;');
if ($serial.available(connectionId)) {
console.log($serial.inbuf(connectionId), $serial.read(connectionId));
}
$serial.close(connectionId);
enum(): stringreturns JSON records withport,desc, andbusy.open(port, baud): number,close(id): 0 | 1write(id, data): 0 | 1,read(id): stringinbuf(id): numberreports queued input bytes;available(id): booleanchecks whether the queue is non-empty.- Win64 device paths support COM port numbers above COM9.
$hw
SetupAPI device enumeration and USB arrival/removal detection.
const devices = $hw.enum();
console.log($hw.deviceExists('USB Serial'));
const arrival = $hw.on('arrival', changed => {
console.log('added', changed);
});
const removal = $hw.on('removal', changed => {
console.log('removed', changed);
});
$hw.off(arrival);
$hw.off(removal);
list(): string | -1preserves the JSON-string contract;enum(): object[] | -1returns the array directly.on(event, callback, deviceName?)supportsarrivalandremoval, optionally filtered by a name substring.off(listenerId): 0 | 1wlan()is present but currently has no implementation.
$usr
Desktop state, screenshots, input injection, global hotkeys, keyboard state, mouse buttons, and wheel events.
const keyboardListener = $usr.keystates(event => {
console.log(event.code, event.state, event.scanCode);
});
const mouseListener = $usr.mouse(event => {
console.log(event.action, event.x, event.y, event.delta);
});
$usr.hotkey(1, 'CTRL+ALT', 'F', code => console.log(code));
console.log($usr.name(), $usr.resolution(), $usr.idle());
console.log(JSON.parse($usr.getActiveApp()));
$usr.nokeystates(keyboardListener);
$usr.nomouse(mouseListener);
key(code, callback),keys(callback), andnokeys(id)provide concise key-down callbacks.keystates(callback)andnokeystates(id)provide down/up state, scan code, extended-key, and Alt state.mouse(callback)andnomouse(id)provide button and wheel events in screen coordinates. Mouse movement is not reported.hotkey(code, combination, key, callback)registers a Windows hotkey.idle(),name(),resolution(),getActiveApp(),getActiveWin(), andisFullScreen(hwnd)query desktop state.sendkey(sequence)injects input;screenshot(file)writes a primary-display JPEG.
$ipc
Win32 window discovery and string payload delivery through WM_COPYDATA. This is separate from Node named pipes.
const windowHandle = $ipc.find('Target Application');
if (windowHandle !== 0n) {
const messageSent = $ipc.send(windowHandle, JSON.stringify({ type: 'status' }));
console.log(messageSent);
}
find(windowTitle): bigintreturns the matching HWND or0n.send(windowHandle, data): 0 | 1accepts a BigInt, safe integer, or unsigned decimal handle string.
$bt
Classic Bluetooth and Bluetooth Low Energy access for Windows applications.
console.log($bt.listRadios());
console.log($bt.listClassic());
console.log($bt.listBle());
const scanId = $bt.scan(5, (devices, error) => {
if (error) console.error(error);
else console.log(devices);
});
$bt.watchStart(event => console.log(event.typeName, event.address));
$bt.broadcastStart(event => console.log(event.address, event.data));
$bt.watchStop();
$bt.broadcastStop();
listRadios(maximum?),listClassic(maximum?), andlistBle(maximum?)return native device records.getBattery(address)returns a percentage ornull.scan(seconds, callback, maximum?)starts an asynchronous scan and returns a job ID.watchStart/Stophandles radio, connection, pairing, battery, and sensor events.broadcastStart/Stophandles BLE advertisements. Raw advertisement bytes are a NodeBuffer.- Numeric native event constants are exposed through
$bt.eventTypes.
$bit
The global machine-state watcher emits a packed 24-bit decimal. JavaScript parses it into bits and mnemonic properties.
const machineState = $bit.current();
console.log(machineState.decimal, machineState.hex, machineState.binary);
console.log(machineState.isconnected, machineState.micactive, machineState.islocked);
$bit.start(1000, state => {
console.log('state changed', state);
});
if ($bit.isLocked()) console.log('workstation locked');
$bit.stop();
parse(decimal)converts an unsigned 24-bit value without calling native code.current()reads and parses current native state;state()returns the last parsed state.start(intervalMs?, handler?)starts polling;stop()stops it.get(tag)returns a mnemonic bit;isLocked()returns a Boolean.- Tags include connection, remote session, VPN, multiple monitors, portable device, gamepad, battery, audio playback, microphone, mute, fullscreen, idle, lock, screensaver, CPU, memory, disk, GPU, VRAM, and display-hold states.
Extended JavaScript layer
This is the GDI-specific JavaScript layer carried forward onto the Node host. It provides concise helpers used by GDI applications:
- Constants and checks such as
nil,nl,isset,isJson,isPrime,isDate,isArray,isFloat,isInt, andisNumeric. - Parsers and path helpers such as
parseBit,parseBool,parseNumber,parseNMEA,parseSeconds,humanizeSeconds,extractFileExt,extractFileName, andextractFilePath. - Objects including
Calculator,Conversion,Bio,Color,Location,Sun,Temp, andGraphWatcher. - Extensions on
Array,Date,Number,String,Object, andBoolean.
console.log(Calculator.add(2, 3, 5));
console.log(Temp.toFahrenheit(20));
console.log(Bio.bmi(75, 180));
console.log('gdi node host'.capitalize(true));
console.log(['gdi', 'node', 'gdi', ''].prune());
Package note: some extensions replace built-ins, including Array.prototype.fill and Object.values, and add enumerable prototype methods. Test Node packages inside GDI.js when they depend on untouched ECMAScript prototypes.
Capability demonstration
GDI.exe demo.js
The included demo inventories Node and GDI capabilities; enumerates disks, serial ports, hardware, Bluetooth, machine state, and desktop state; runs a temporary SQLite query; takes and removes a temporary screenshot; renders an English SapiProxy sentence and plays it through BASS; and observes input, USB, OS, Bluetooth, BLE, and machine-state events for ten seconds.
It deliberately does not lock Windows, power off the monitor, inject keystrokes, open arbitrary serial ports, transmit IPC data, or initiate a blocking BLE scan.
Current boundaries
- Win64 is the only supported target.
- The host accepts one optional application script.
- There is no built-in REPL or automatic updater in this prototype.
$hw.wlan()is currently non-operational.- Input events include keyboard state, mouse buttons, and wheels; mouse movement is filtered.
- OS listeners are process-lifetime registrations because the current wrapper has no removal method.
- The GDI.js prototype extensions may conflict with packages expecting untouched ECMAScript prototypes.
- Asynchronous callbacks are delivered through the Node event loop. Hardware-specific behavior depends on the target Windows machine.