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.

OwnerPublic responsibility
Node.jsJavaScript execution, event loop, CommonJS modules, asynchronous work, timers, files, networking, processes, cryptography and buffers
GDI.jsPublic $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): string returns 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): number
  • kill(id): number
  • sload(id, file, tag): number
  • splay(id, tag): number
  • stream(id, file): number
  • playd(id, file): number applies 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): number returns a positive connection ID or 0 when the file does not exist. It does not create a missing file.
  • close(id): 0 | 1
  • query(id, sql): string returns a JSON array string. Field values are strings; SQL NULL becomes an empty string.
  • queryEx(file, sql, callback): void uses an independent worker-thread connection.
  • exec(id, sql): 0 | 1 executes 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), and onMessage(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(): string returns JSON records with port, desc, and busy.
  • open(port, baud): number, close(id): 0 | 1
  • write(id, data): 0 | 1, read(id): string
  • inbuf(id): number reports queued input bytes; available(id): boolean checks 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 | -1 preserves the JSON-string contract; enum(): object[] | -1 returns the array directly.
  • on(event, callback, deviceName?) supports arrival and removal, optionally filtered by a name substring.
  • off(listenerId): 0 | 1
  • wlan() 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), and nokeys(id) provide concise key-down callbacks.
  • keystates(callback) and nokeystates(id) provide down/up state, scan code, extended-key, and Alt state.
  • mouse(callback) and nomouse(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(), and isFullScreen(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): bigint returns the matching HWND or 0n.
  • send(windowHandle, data): 0 | 1 accepts 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?), and listBle(maximum?) return native device records.
  • getBattery(address) returns a percentage or null.
  • scan(seconds, callback, maximum?) starts an asynchronous scan and returns a job ID.
  • watchStart/Stop handles radio, connection, pairing, battery, and sensor events.
  • broadcastStart/Stop handles BLE advertisements. Raw advertisement bytes are a Node Buffer.
  • 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, and isNumeric.
  • Parsers and path helpers such as parseBit, parseBool, parseNumber, parseNMEA, parseSeconds, humanizeSeconds, extractFileExt, extractFileName, and extractFilePath.
  • Objects including Calculator, Conversion, Bio, Color, Location, Sun, Temp, and GraphWatcher.
  • Extensions on Array, Date, Number, String, Object, and Boolean.
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.