novac 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +1574 -597
- package/bin/novac +468 -171
- package/bin/nvc +522 -0
- package/bin/nvml +78 -17
- package/demo.nv +0 -0
- package/demo_builtins.nv +0 -0
- package/demo_http.nv +0 -0
- package/examples/bf.nv +69 -0
- package/examples/math.nv +21 -0
- package/kits/birdAPI/kitdef.js +954 -0
- package/kits/kitRNG/kitdef.js +740 -0
- package/kits/kitSSH/kitdef.js +1272 -0
- package/kits/kitadb/kitdef.js +606 -0
- package/kits/kitai/kitdef.js +2185 -0
- package/kits/kitansi/kitdef.js +1402 -0
- package/kits/kitcanvas/kitdef.js +914 -0
- package/kits/kitclippy/kitdef.js +925 -0
- package/kits/kitformat/kitdef.js +1485 -0
- package/kits/kitgps/kitdef.js +1862 -0
- package/kits/kitlibproc/kitdef.js +3 -2
- package/kits/kitmatrix/ex.js +19 -0
- package/kits/kitmatrix/kitdef.js +960 -0
- package/kits/kitmorse/kitdef.js +229 -0
- package/kits/kitmpatch/kitdef.js +906 -0
- package/kits/kitnet/kitdef.js +1401 -0
- package/kits/kitnovacweb/README.md +1416 -143
- package/kits/kitnovacweb/kitdef.js +92 -2
- package/kits/kitnovacweb/nvml/executor.js +578 -176
- package/kits/kitnovacweb/nvml/index.js +2 -2
- package/kits/kitnovacweb/nvml/lexer.js +72 -69
- package/kits/kitnovacweb/nvml/parser.js +328 -159
- package/kits/kitnovacweb/nvml/renderer.js +770 -270
- package/kits/kitparse/kitdef.js +1688 -0
- package/kits/kitproto/kitdef.js +613 -0
- package/kits/kitqr/kitdef.js +637 -0
- package/kits/kitregex++/kitdef.js +1353 -0
- package/kits/kitrequire/kitdef.js +1599 -0
- package/kits/kitx11/kitdef.js +1 -0
- package/kits/kitx11/kitx11.js +2472 -0
- package/kits/kitx11/kitx11_conn.js +948 -0
- package/kits/kitx11/kitx11_worker.js +121 -0
- package/kits/libtea/kitdef.js +2691 -0
- package/kits/libterm/ex.js +285 -0
- package/kits/libterm/kitdef.js +1927 -0
- package/novac/LICENSE +21 -0
- package/novac/README.md +1823 -0
- package/novac/bin/novac +950 -0
- package/novac/bin/nvc +522 -0
- package/novac/bin/nvml +542 -0
- package/novac/demo.nv +245 -0
- package/novac/demo_builtins.nv +209 -0
- package/novac/demo_http.nv +62 -0
- package/novac/examples/bf.nv +69 -0
- package/novac/examples/math.nv +21 -0
- package/novac/kits/kitai/kitdef.js +2185 -0
- package/novac/kits/kitansi/kitdef.js +1402 -0
- package/novac/kits/kitformat/kitdef.js +1485 -0
- package/novac/kits/kitgps/kitdef.js +1862 -0
- package/novac/kits/kitlibfs/kitdef.js +231 -0
- package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
- package/novac/kits/kitmatrix/ex.js +19 -0
- package/novac/kits/kitmatrix/kitdef.js +960 -0
- package/novac/kits/kitmpatch/kitdef.js +906 -0
- package/novac/kits/kitnovacweb/README.md +1572 -0
- package/novac/kits/kitnovacweb/demo.nv +12 -0
- package/novac/kits/kitnovacweb/demo.nvml +71 -0
- package/novac/kits/kitnovacweb/index.nova +12 -0
- package/novac/kits/kitnovacweb/kitdef.js +692 -0
- package/novac/kits/kitnovacweb/nova.kit.json +8 -0
- package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
- package/novac/kits/kitnovacweb/nvml/index.js +67 -0
- package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
- package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
- package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
- package/novac/kits/kitparse/kitdef.js +1688 -0
- package/novac/kits/kitregex++/kitdef.js +1353 -0
- package/novac/kits/kitrequire/kitdef.js +1599 -0
- package/novac/kits/kitx11/kitdef.js +1 -0
- package/novac/kits/kitx11/kitx11.js +2472 -0
- package/novac/kits/kitx11/kitx11_conn.js +948 -0
- package/novac/kits/kitx11/kitx11_worker.js +121 -0
- package/novac/kits/libtea/tf.js +2691 -0
- package/novac/kits/libterm/ex.js +285 -0
- package/novac/kits/libterm/kitdef.js +1927 -0
- package/novac/node_modules/chalk/license +9 -0
- package/novac/node_modules/chalk/package.json +83 -0
- package/novac/node_modules/chalk/readme.md +297 -0
- package/novac/node_modules/chalk/source/index.d.ts +325 -0
- package/novac/node_modules/chalk/source/index.js +225 -0
- package/novac/node_modules/chalk/source/utilities.js +33 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/novac/node_modules/commander/LICENSE +22 -0
- package/novac/node_modules/commander/Readme.md +1176 -0
- package/novac/node_modules/commander/esm.mjs +16 -0
- package/novac/node_modules/commander/index.js +24 -0
- package/novac/node_modules/commander/lib/argument.js +150 -0
- package/novac/node_modules/commander/lib/command.js +2777 -0
- package/novac/node_modules/commander/lib/error.js +39 -0
- package/novac/node_modules/commander/lib/help.js +747 -0
- package/novac/node_modules/commander/lib/option.js +380 -0
- package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/novac/node_modules/commander/package-support.json +19 -0
- package/novac/node_modules/commander/package.json +82 -0
- package/novac/node_modules/commander/typings/esm.d.mts +3 -0
- package/novac/node_modules/commander/typings/index.d.ts +1113 -0
- package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
- package/novac/node_modules/node-addon-api/README.md +95 -0
- package/novac/node_modules/node-addon-api/common.gypi +21 -0
- package/novac/node_modules/node-addon-api/except.gypi +25 -0
- package/novac/node_modules/node-addon-api/index.js +14 -0
- package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
- package/novac/node_modules/node-addon-api/napi.h +3364 -0
- package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
- package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
- package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/novac/node_modules/node-addon-api/package-support.json +21 -0
- package/novac/node_modules/node-addon-api/package.json +480 -0
- package/novac/node_modules/node-addon-api/tools/README.md +73 -0
- package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/novac/node_modules/serialize-javascript/LICENSE +27 -0
- package/novac/node_modules/serialize-javascript/README.md +149 -0
- package/novac/node_modules/serialize-javascript/index.js +297 -0
- package/novac/node_modules/serialize-javascript/package.json +33 -0
- package/novac/package.json +27 -0
- package/novac/scripts/update-bin.js +24 -0
- package/novac/src/core/bstd.js +1035 -0
- package/novac/src/core/config.js +155 -0
- package/novac/src/core/describe.js +187 -0
- package/novac/src/core/emitter.js +499 -0
- package/novac/src/core/error.js +86 -0
- package/novac/src/core/executor.js +5606 -0
- package/novac/src/core/formatter.js +686 -0
- package/novac/src/core/lexer.js +1026 -0
- package/novac/src/core/nova_builtins.js +717 -0
- package/novac/src/core/nova_thread_worker.js +166 -0
- package/novac/src/core/parser.js +2181 -0
- package/novac/src/core/types.js +112 -0
- package/novac/src/index.js +28 -0
- package/novac/src/runtime/stdlib.js +244 -0
- package/package.json +6 -3
- package/scripts/update-bin.js +0 -0
- package/src/core/bstd.js +838 -362
- package/src/core/executor.js +2578 -170
- package/src/core/lexer.js +502 -54
- package/src/core/nova_builtins.js +21 -3
- package/src/core/parser.js +413 -72
- package/src/core/types.js +30 -2
- package/src/index.js +0 -0
- package/examples/example-project/README.md +0 -3
- package/examples/example-project/src/main.nova +0 -3
- package/src/core/environment.js +0 -0
- /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// ============================================================
|
|
3
|
+
// kitx11_conn.js — Synchronous X11 connection + drawing API
|
|
4
|
+
//
|
|
5
|
+
// Uses a Worker thread to own the async socket, then bridges
|
|
6
|
+
// back to the main thread with receiveMessageOnPort() so all
|
|
7
|
+
// calls appear synchronous to the caller.
|
|
8
|
+
//
|
|
9
|
+
// Usage:
|
|
10
|
+
// const X11 = require('./kitx11_conn');
|
|
11
|
+
// const conn = X11.connect(); // sync
|
|
12
|
+
// const win = conn.createWindow({...}); // sync
|
|
13
|
+
// conn.mapWindow(win); // sync
|
|
14
|
+
// conn.flush(); // sync — sends queued packets
|
|
15
|
+
// const ev = conn.nextEvent(); // sync — blocks until event
|
|
16
|
+
// ============================================================
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
Worker,
|
|
20
|
+
MessageChannel,
|
|
21
|
+
receiveMessageOnPort,
|
|
22
|
+
isMainThread,
|
|
23
|
+
} = require('worker_threads');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const os = require('os');
|
|
26
|
+
|
|
27
|
+
const { kitdef } = require('./kitx11');
|
|
28
|
+
const {
|
|
29
|
+
Request, ByteOrder,
|
|
30
|
+
WindowClass, EventMask, EventType,
|
|
31
|
+
GCMask, GCFunction,
|
|
32
|
+
PropMode, PropFormat, Atom,
|
|
33
|
+
buildConnectionRequest,
|
|
34
|
+
buildCreateWindowRequest,
|
|
35
|
+
buildMapWindowRequest,
|
|
36
|
+
buildChangePropertyRequest,
|
|
37
|
+
buildInternAtomRequest,
|
|
38
|
+
buildQueryExtensionRequest,
|
|
39
|
+
parseQueryExtensionReply,
|
|
40
|
+
colorByName,
|
|
41
|
+
} = kitdef;
|
|
42
|
+
|
|
43
|
+
// ── Internal helpers ──────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/** Pad a byte length up to the next 4-byte boundary. */
|
|
46
|
+
function pad4(n) { return (4 - (n % 4)) % 4; }
|
|
47
|
+
|
|
48
|
+
/** Read a 32-bit unsigned int from a Uint8Array (host endian). */
|
|
49
|
+
function u32(buf, off) {
|
|
50
|
+
const v = new DataView(buf.buffer, buf.byteOffset + off, 4);
|
|
51
|
+
return v.getUint32(0, kitdef.hostIsLittleEndian);
|
|
52
|
+
}
|
|
53
|
+
function u16(buf, off) {
|
|
54
|
+
const v = new DataView(buf.buffer, buf.byteOffset + off, 2);
|
|
55
|
+
return v.getUint16(0, kitdef.hostIsLittleEndian);
|
|
56
|
+
}
|
|
57
|
+
function u8(buf, off) { return buf[off]; }
|
|
58
|
+
|
|
59
|
+
// ── SyncSocket ───────────────────────────────────────────────
|
|
60
|
+
//
|
|
61
|
+
// Wraps the socket worker with a fully synchronous read/write API.
|
|
62
|
+
// Connect blocks via Atomics.wait on a SharedArrayBuffer signal.
|
|
63
|
+
// Reads block via receiveMessageOnPort() on a per-request MessageChannel.
|
|
64
|
+
|
|
65
|
+
class SyncSocket {
|
|
66
|
+
/**
|
|
67
|
+
* Synchronously create and connect a SyncSocket.
|
|
68
|
+
* Blocks the calling thread until the TCP/Unix connection is established.
|
|
69
|
+
* @param {string} display e.g. ':0', 'hostname:1'
|
|
70
|
+
* @returns {SyncSocket}
|
|
71
|
+
*/
|
|
72
|
+
static create(display) {
|
|
73
|
+
// SAB[0]: 0 = waiting, 1 = connected, 2 = error
|
|
74
|
+
const sab = new SharedArrayBuffer(4);
|
|
75
|
+
const signal = new Int32Array(sab);
|
|
76
|
+
|
|
77
|
+
const worker = new Worker(path.join(__dirname, 'kitx11_worker.js'), {
|
|
78
|
+
workerData: { display, signalSab: sab },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Block synchronously until the worker signals connect result
|
|
82
|
+
Atomics.wait(signal, 0, 0);
|
|
83
|
+
|
|
84
|
+
if (signal[0] === 2) {
|
|
85
|
+
// Worker posted an error message on parentPort before notifying
|
|
86
|
+
worker.terminate();
|
|
87
|
+
throw new Error(`X11 connection failed (DISPLAY=${display})`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const sock = Object.create(SyncSocket.prototype);
|
|
91
|
+
sock._worker = worker;
|
|
92
|
+
return sock;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Write bytes WITHOUT transferring (buffer stays valid after call). */
|
|
96
|
+
writeCopy(uint8Array) {
|
|
97
|
+
const copy = uint8Array.slice();
|
|
98
|
+
this._worker.postMessage({ cmd: 'write', data: copy }, [copy.buffer]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Synchronously read exactly n bytes. Blocks until available. */
|
|
102
|
+
read(n) {
|
|
103
|
+
const { port1, port2 } = new MessageChannel();
|
|
104
|
+
this._worker.postMessage({ cmd: 'read', n, port: port2 }, [port2]);
|
|
105
|
+
let msg;
|
|
106
|
+
// Spin on receiveMessageOnPort — this is the sync blocking point
|
|
107
|
+
do { msg = receiveMessageOnPort(port1); } while (!msg);
|
|
108
|
+
if (!msg.message.ok) throw new Error(`X11 read error: ${msg.message.error}`);
|
|
109
|
+
return msg.message.data; // Uint8Array
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
close() {
|
|
113
|
+
this._worker.postMessage({ cmd: 'close' });
|
|
114
|
+
this._worker.terminate();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── X11Connection ─────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
class X11Connection {
|
|
121
|
+
constructor(sock, info) {
|
|
122
|
+
this._sock = sock;
|
|
123
|
+
this._seq = 0; // sequence number counter
|
|
124
|
+
this._xidBase = info.resourceIdBase;
|
|
125
|
+
this._xidMask = info.resourceIdMask;
|
|
126
|
+
this._xidNext = 0;
|
|
127
|
+
this._rootWin = info.rootWindow;
|
|
128
|
+
this._rootVisual = info.rootVisual;
|
|
129
|
+
this._rootDepth = info.rootDepth;
|
|
130
|
+
this._whitePixel = info.whitePixel;
|
|
131
|
+
this._blackPixel = info.blackPixel;
|
|
132
|
+
this._screen = info;
|
|
133
|
+
this._writeQueue = []; // buffered packets for flush()
|
|
134
|
+
this._atoms = {}; // intern cache name→id
|
|
135
|
+
this._extensions = {}; // extension info cache
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Resource ID allocation ──────────────────────────────────
|
|
139
|
+
_newXid() {
|
|
140
|
+
const id = this._xidBase | (this._xidNext & this._xidMask);
|
|
141
|
+
this._xidNext++;
|
|
142
|
+
return id;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Low-level I/O ───────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/** Queue a packet for the next flush(). */
|
|
148
|
+
_queue(buf) {
|
|
149
|
+
this._writeQueue.push(buf);
|
|
150
|
+
this._seq++;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Send all queued packets to the server immediately. */
|
|
154
|
+
flush() {
|
|
155
|
+
for (const buf of this._writeQueue) {
|
|
156
|
+
this._sock.writeCopy(buf);
|
|
157
|
+
}
|
|
158
|
+
this._writeQueue = [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Send one packet immediately (bypasses the queue). */
|
|
162
|
+
_send(buf) {
|
|
163
|
+
this._sock.writeCopy(buf);
|
|
164
|
+
this._seq++;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Send a request and synchronously read back the reply.
|
|
169
|
+
* X11 replies are at least 32 bytes; additional data is indicated
|
|
170
|
+
* by bytes 4-7 (length in 4-byte units beyond the first 32 bytes).
|
|
171
|
+
*/
|
|
172
|
+
_roundTrip(buf) {
|
|
173
|
+
this.flush(); // flush any pending writes first
|
|
174
|
+
this._send(buf);
|
|
175
|
+
return this._readReply();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
_readReply() {
|
|
179
|
+
const header = this._sock.read(32);
|
|
180
|
+
const type = u8(header, 0);
|
|
181
|
+
|
|
182
|
+
if (type === 0) {
|
|
183
|
+
// Error reply
|
|
184
|
+
const code = u8(header, 1);
|
|
185
|
+
const seq = u16(header, 2);
|
|
186
|
+
const bad = u32(header, 4);
|
|
187
|
+
const minorOp = u16(header, 8);
|
|
188
|
+
const majorOp = u8(header, 10);
|
|
189
|
+
throw new Error(
|
|
190
|
+
`X11 error ${code} (major=${majorOp} minor=${minorOp} bad_resource=0x${bad.toString(16)} seq=${seq})`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (type === 1) {
|
|
195
|
+
// Normal reply
|
|
196
|
+
const extraWords = u32(header, 4);
|
|
197
|
+
if (extraWords > 0) {
|
|
198
|
+
const extra = this._sock.read(extraWords * 4);
|
|
199
|
+
const full = new Uint8Array(32 + extra.length);
|
|
200
|
+
full.set(header, 0);
|
|
201
|
+
full.set(extra, 32);
|
|
202
|
+
return full;
|
|
203
|
+
}
|
|
204
|
+
return header;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// It's an event — push it aside and keep waiting for the reply
|
|
208
|
+
// (simplified: real client would queue events)
|
|
209
|
+
return this._readReply();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Read the next event from the server (blocks synchronously). */
|
|
213
|
+
nextEvent() {
|
|
214
|
+
this.flush();
|
|
215
|
+
const buf = this._sock.read(32);
|
|
216
|
+
const type = u8(buf, 0) & 0x7F; // strip SendEvent bit
|
|
217
|
+
return this._parseEvent(type, buf);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
_parseEvent(type, buf) {
|
|
221
|
+
const ev = { type, typeName: kitdef.EventTypeName[type] || `unknown(${type})` };
|
|
222
|
+
|
|
223
|
+
switch (type) {
|
|
224
|
+
case EventType.KEY_PRESS:
|
|
225
|
+
case EventType.KEY_RELEASE:
|
|
226
|
+
ev.detail = u8(buf, 1); // keycode
|
|
227
|
+
ev.time = u32(buf, 4);
|
|
228
|
+
ev.root = u32(buf, 8);
|
|
229
|
+
ev.event = u32(buf, 12);
|
|
230
|
+
ev.child = u32(buf, 16);
|
|
231
|
+
ev.rootX = u16(buf, 20);
|
|
232
|
+
ev.rootY = u16(buf, 22);
|
|
233
|
+
ev.eventX = u16(buf, 24);
|
|
234
|
+
ev.eventY = u16(buf, 26);
|
|
235
|
+
ev.state = u16(buf, 28);
|
|
236
|
+
ev.keysym = kitdef.keysymToChar(ev.detail) ?? ev.detail;
|
|
237
|
+
break;
|
|
238
|
+
|
|
239
|
+
case EventType.BUTTON_PRESS:
|
|
240
|
+
case EventType.BUTTON_RELEASE:
|
|
241
|
+
ev.button = u8(buf, 1);
|
|
242
|
+
ev.time = u32(buf, 4);
|
|
243
|
+
ev.event = u32(buf, 12);
|
|
244
|
+
ev.eventX = u16(buf, 24);
|
|
245
|
+
ev.eventY = u16(buf, 26);
|
|
246
|
+
ev.state = u16(buf, 28);
|
|
247
|
+
break;
|
|
248
|
+
|
|
249
|
+
case EventType.MOTION_NOTIFY:
|
|
250
|
+
ev.time = u32(buf, 4);
|
|
251
|
+
ev.event = u32(buf, 12);
|
|
252
|
+
ev.eventX = u16(buf, 24);
|
|
253
|
+
ev.eventY = u16(buf, 26);
|
|
254
|
+
ev.state = u16(buf, 28);
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case EventType.EXPOSE:
|
|
258
|
+
ev.window = u32(buf, 4);
|
|
259
|
+
ev.x = u16(buf, 8);
|
|
260
|
+
ev.y = u16(buf, 10);
|
|
261
|
+
ev.width = u16(buf, 12);
|
|
262
|
+
ev.height = u16(buf, 14);
|
|
263
|
+
ev.count = u16(buf, 16);
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
case EventType.CONFIGURE_NOTIFY:
|
|
267
|
+
ev.window = u32(buf, 4);
|
|
268
|
+
ev.x = u16(buf, 16);
|
|
269
|
+
ev.y = u16(buf, 18);
|
|
270
|
+
ev.width = u16(buf, 20);
|
|
271
|
+
ev.height = u16(buf, 22);
|
|
272
|
+
break;
|
|
273
|
+
|
|
274
|
+
case EventType.DESTROY_NOTIFY:
|
|
275
|
+
ev.window = u32(buf, 8);
|
|
276
|
+
break;
|
|
277
|
+
|
|
278
|
+
case EventType.CLIENT_MESSAGE:
|
|
279
|
+
ev.window = u32(buf, 4);
|
|
280
|
+
ev.type = u32(buf, 8);
|
|
281
|
+
ev.data = buf.slice(12, 32);
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
default:
|
|
285
|
+
ev.raw = buf;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
return ev;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ── Atom interning ──────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
/** Synchronously intern an atom name, with local cache. */
|
|
294
|
+
internAtom(name, onlyIfExists = false) {
|
|
295
|
+
if (this._atoms[name] !== undefined) return this._atoms[name];
|
|
296
|
+
const reply = this._roundTrip(buildInternAtomRequest(name, onlyIfExists));
|
|
297
|
+
const id = u32(reply, 8);
|
|
298
|
+
this._atoms[name] = id;
|
|
299
|
+
return id;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── Color resolution ────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Resolve a color to a pixel value.
|
|
306
|
+
* Accepts:
|
|
307
|
+
* - An X11 named color string ('cornflowerblue')
|
|
308
|
+
* - '#RRGGBB' hex string
|
|
309
|
+
* - A pixel integer directly
|
|
310
|
+
*/
|
|
311
|
+
color(spec) {
|
|
312
|
+
if (typeof spec === 'number') return spec;
|
|
313
|
+
|
|
314
|
+
const rgb = colorByName(spec) || kitdef.parseColor(spec);
|
|
315
|
+
if (!rgb) throw new Error(`Unknown color: ${spec}`);
|
|
316
|
+
|
|
317
|
+
const r = Array.isArray(rgb) ? rgb[0] : rgb.r;
|
|
318
|
+
const g = Array.isArray(rgb) ? rgb[1] : rgb.g;
|
|
319
|
+
const b = Array.isArray(rgb) ? rgb[2] : rgb.b;
|
|
320
|
+
|
|
321
|
+
// AllocColor request (opcode 84)
|
|
322
|
+
const colormapId = this._screen.defaultColormap;
|
|
323
|
+
const buf = new Uint8Array(16);
|
|
324
|
+
const v = new DataView(buf.buffer);
|
|
325
|
+
const le = kitdef.hostIsLittleEndian;
|
|
326
|
+
v.setUint8(0, 84); // ALLOC_COLOR
|
|
327
|
+
v.setUint8(1, 0);
|
|
328
|
+
v.setUint16(2, 4, le);
|
|
329
|
+
v.setUint32(4, colormapId, le);
|
|
330
|
+
v.setUint16(8, r * 257, le); // 8-bit → 16-bit
|
|
331
|
+
v.setUint16(10, g * 257, le);
|
|
332
|
+
v.setUint16(12, b * 257, le);
|
|
333
|
+
v.setUint16(14, 0, le);
|
|
334
|
+
const reply = this._roundTrip(buf);
|
|
335
|
+
return u32(reply, 16); // pixel
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ── Window management ───────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Create a top-level window.
|
|
342
|
+
* @param {object} opts
|
|
343
|
+
* @param {number} [opts.x=0]
|
|
344
|
+
* @param {number} [opts.y=0]
|
|
345
|
+
* @param {number} [opts.width=400]
|
|
346
|
+
* @param {number} [opts.height=300]
|
|
347
|
+
* @param {string|number} [opts.background='white']
|
|
348
|
+
* @param {string} [opts.title]
|
|
349
|
+
* @param {number} [opts.eventMask] defaults to common events
|
|
350
|
+
* @returns {number} window XID
|
|
351
|
+
*/
|
|
352
|
+
createWindow(opts = {}) {
|
|
353
|
+
const {
|
|
354
|
+
x = 0, y = 0,
|
|
355
|
+
width = 400, height = 300,
|
|
356
|
+
background = 'white',
|
|
357
|
+
title,
|
|
358
|
+
eventMask = (
|
|
359
|
+
EventMask.EXPOSURE_MASK |
|
|
360
|
+
EventMask.KEY_PRESS_MASK |
|
|
361
|
+
EventMask.BUTTON_PRESS_MASK |
|
|
362
|
+
EventMask.BUTTON_RELEASE_MASK |
|
|
363
|
+
EventMask.POINTER_MOTION_MASK |
|
|
364
|
+
EventMask.STRUCTURE_NOTIFY_MASK
|
|
365
|
+
),
|
|
366
|
+
} = opts;
|
|
367
|
+
|
|
368
|
+
const wid = this._newXid();
|
|
369
|
+
const bgPix = this.color(background);
|
|
370
|
+
|
|
371
|
+
const values = [bgPix, eventMask];
|
|
372
|
+
const valueMask = kitdef.CW.BACK_PIXEL | kitdef.CW.EVENT_MASK;
|
|
373
|
+
|
|
374
|
+
const req = buildCreateWindowRequest({
|
|
375
|
+
wid, parent: this._rootWin,
|
|
376
|
+
x, y, width, height,
|
|
377
|
+
borderWidth: 0,
|
|
378
|
+
windowClass: WindowClass.INPUT_OUTPUT,
|
|
379
|
+
visual: 0, // CopyFromParent
|
|
380
|
+
valueMask,
|
|
381
|
+
values,
|
|
382
|
+
});
|
|
383
|
+
this._queue(req);
|
|
384
|
+
|
|
385
|
+
if (title) this.setTitle(wid, title);
|
|
386
|
+
|
|
387
|
+
// Register WM_DELETE_WINDOW protocol so close button works
|
|
388
|
+
this._setupWMDelete(wid);
|
|
389
|
+
|
|
390
|
+
return wid;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
_setupWMDelete(wid) {
|
|
394
|
+
const wmProto = this.internAtom('WM_PROTOCOLS');
|
|
395
|
+
const wmDelete = this.internAtom('WM_DELETE_WINDOW');
|
|
396
|
+
this._queue(buildChangePropertyRequest({
|
|
397
|
+
wid,
|
|
398
|
+
mode: PropMode.REPLACE,
|
|
399
|
+
property: wmProto,
|
|
400
|
+
type: Atom.ATOM,
|
|
401
|
+
format: 32,
|
|
402
|
+
data: [wmDelete],
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** Map (show) a window. */
|
|
407
|
+
mapWindow(wid) {
|
|
408
|
+
this._queue(buildMapWindowRequest(wid));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Unmap (hide) a window. */
|
|
412
|
+
unmapWindow(wid) {
|
|
413
|
+
const buf = new Uint8Array(8);
|
|
414
|
+
const v = new DataView(buf.buffer);
|
|
415
|
+
const le = kitdef.hostIsLittleEndian;
|
|
416
|
+
v.setUint8(0, Request.UNMAP_WINDOW);
|
|
417
|
+
v.setUint8(1, 0);
|
|
418
|
+
v.setUint16(2, 2, le);
|
|
419
|
+
v.setUint32(4, wid, le);
|
|
420
|
+
this._queue(buf);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Destroy a window and free its resources. */
|
|
424
|
+
destroyWindow(wid) {
|
|
425
|
+
const buf = new Uint8Array(8);
|
|
426
|
+
const v = new DataView(buf.buffer);
|
|
427
|
+
const le = kitdef.hostIsLittleEndian;
|
|
428
|
+
v.setUint8(0, Request.DESTROY_WINDOW);
|
|
429
|
+
v.setUint8(1, 0);
|
|
430
|
+
v.setUint16(2, 2, le);
|
|
431
|
+
v.setUint32(4, wid, le);
|
|
432
|
+
this._queue(buf);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Set a window's title (_NET_WM_NAME + WM_NAME). */
|
|
436
|
+
setTitle(wid, title) {
|
|
437
|
+
const enc = new TextEncoder().encode(title);
|
|
438
|
+
const data = Array.from(enc);
|
|
439
|
+
// WM_NAME (legacy, Latin-1)
|
|
440
|
+
this._queue(buildChangePropertyRequest({
|
|
441
|
+
wid, mode: PropMode.REPLACE,
|
|
442
|
+
property: Atom.WM_NAME, type: Atom.STRING, format: 8, data,
|
|
443
|
+
}));
|
|
444
|
+
// _NET_WM_NAME (modern, UTF-8)
|
|
445
|
+
const netName = this.internAtom('_NET_WM_NAME');
|
|
446
|
+
const utf8Atom = this.internAtom('UTF8_STRING');
|
|
447
|
+
this._queue(buildChangePropertyRequest({
|
|
448
|
+
wid, mode: PropMode.REPLACE,
|
|
449
|
+
property: netName, type: utf8Atom, format: 8, data,
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Move and/or resize a window. */
|
|
454
|
+
configureWindow(wid, opts = {}) {
|
|
455
|
+
const fields = { x:1, y:2, width:4, height:8, borderWidth:16, sibling:32, stackMode:64 };
|
|
456
|
+
let mask = 0;
|
|
457
|
+
const values = [];
|
|
458
|
+
for (const [key, bit] of Object.entries(fields)) {
|
|
459
|
+
if (opts[key] !== undefined) { mask |= bit; values.push(opts[key]); }
|
|
460
|
+
}
|
|
461
|
+
const len = 3 + values.length;
|
|
462
|
+
const buf = new Uint8Array(len * 4);
|
|
463
|
+
const v = new DataView(buf.buffer);
|
|
464
|
+
const le = kitdef.hostIsLittleEndian;
|
|
465
|
+
v.setUint8(0, Request.CONFIGURE_WINDOW);
|
|
466
|
+
v.setUint8(1, 0);
|
|
467
|
+
v.setUint16(2, len, le);
|
|
468
|
+
v.setUint32(4, wid, le);
|
|
469
|
+
v.setUint16(8, mask, le);
|
|
470
|
+
values.forEach((val, i) => v.setUint32(12 + i*4, val, le));
|
|
471
|
+
this._queue(buf);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ── Graphics Context ────────────────────────────────────────
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Create a Graphics Context.
|
|
478
|
+
* @param {number} drawable window or pixmap XID
|
|
479
|
+
* @param {object} opts
|
|
480
|
+
* @param {string|number} [opts.foreground='black']
|
|
481
|
+
* @param {string|number} [opts.background='white']
|
|
482
|
+
* @param {number} [opts.lineWidth=1]
|
|
483
|
+
* @param {number} [opts.function] GCFunction value
|
|
484
|
+
* @returns {number} GC XID
|
|
485
|
+
*/
|
|
486
|
+
createGC(drawable, opts = {}) {
|
|
487
|
+
const gcid = this._newXid();
|
|
488
|
+
const le = kitdef.hostIsLittleEndian;
|
|
489
|
+
|
|
490
|
+
const fg = this.color(opts.foreground ?? 'black');
|
|
491
|
+
const bg = this.color(opts.background ?? 'white');
|
|
492
|
+
|
|
493
|
+
let mask = GCMask.FOREGROUND | GCMask.BACKGROUND;
|
|
494
|
+
const values = [fg, bg];
|
|
495
|
+
|
|
496
|
+
if (opts.lineWidth !== undefined) {
|
|
497
|
+
mask |= GCMask.LINE_WIDTH; values.push(opts.lineWidth);
|
|
498
|
+
}
|
|
499
|
+
if (opts.function !== undefined) {
|
|
500
|
+
mask |= GCMask.FUNCTION; values.push(opts.function);
|
|
501
|
+
}
|
|
502
|
+
if (opts.lineStyle !== undefined) {
|
|
503
|
+
mask |= GCMask.LINE_STYLE; values.push(opts.lineStyle);
|
|
504
|
+
}
|
|
505
|
+
if (opts.fillStyle !== undefined) {
|
|
506
|
+
mask |= GCMask.FILL_STYLE; values.push(opts.fillStyle);
|
|
507
|
+
}
|
|
508
|
+
if (opts.graphicsExposures !== undefined) {
|
|
509
|
+
mask |= GCMask.GRAPHICS_EXPOSURES; values.push(opts.graphicsExposures ? 1 : 0);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const len = 4 + values.length;
|
|
513
|
+
const buf = new Uint8Array(len * 4);
|
|
514
|
+
const v = new DataView(buf.buffer);
|
|
515
|
+
v.setUint8(0, Request.CREATE_GC);
|
|
516
|
+
v.setUint8(1, 0);
|
|
517
|
+
v.setUint16(2, len, le);
|
|
518
|
+
v.setUint32(4, gcid, le);
|
|
519
|
+
v.setUint32(8, drawable, le);
|
|
520
|
+
v.setUint32(12, mask, le);
|
|
521
|
+
values.forEach((val, i) => v.setUint32(16 + i*4, val, le));
|
|
522
|
+
this._queue(buf);
|
|
523
|
+
return gcid;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** Change attributes of an existing GC. */
|
|
527
|
+
changeGC(gcid, opts = {}) {
|
|
528
|
+
const le = kitdef.hostIsLittleEndian;
|
|
529
|
+
let mask = 0;
|
|
530
|
+
const values = [];
|
|
531
|
+
const add = (flag, val) => { mask |= flag; values.push(val); };
|
|
532
|
+
|
|
533
|
+
if (opts.foreground !== undefined) add(GCMask.FOREGROUND, this.color(opts.foreground));
|
|
534
|
+
if (opts.background !== undefined) add(GCMask.BACKGROUND, this.color(opts.background));
|
|
535
|
+
if (opts.lineWidth !== undefined) add(GCMask.LINE_WIDTH, opts.lineWidth);
|
|
536
|
+
if (opts.function !== undefined) add(GCMask.FUNCTION, opts.function);
|
|
537
|
+
if (opts.lineStyle !== undefined) add(GCMask.LINE_STYLE, opts.lineStyle);
|
|
538
|
+
if (opts.fillStyle !== undefined) add(GCMask.FILL_STYLE, opts.fillStyle);
|
|
539
|
+
|
|
540
|
+
if (!values.length) return;
|
|
541
|
+
const len = 3 + values.length;
|
|
542
|
+
const buf = new Uint8Array(len * 4);
|
|
543
|
+
const v = new DataView(buf.buffer);
|
|
544
|
+
v.setUint8(0, Request.CHANGE_GC);
|
|
545
|
+
v.setUint8(1, 0);
|
|
546
|
+
v.setUint16(2, len, le);
|
|
547
|
+
v.setUint32(4, gcid, le);
|
|
548
|
+
v.setUint32(8, mask, le);
|
|
549
|
+
values.forEach((val, i) => v.setUint32(12 + i*4, val, le));
|
|
550
|
+
this._queue(buf);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** Free a GC. */
|
|
554
|
+
freeGC(gcid) {
|
|
555
|
+
const buf = new Uint8Array(8);
|
|
556
|
+
const v = new DataView(buf.buffer);
|
|
557
|
+
const le = kitdef.hostIsLittleEndian;
|
|
558
|
+
v.setUint8(0, Request.FREE_GC);
|
|
559
|
+
v.setUint8(1, 0);
|
|
560
|
+
v.setUint16(2, 2, le);
|
|
561
|
+
v.setUint32(4, gcid, le);
|
|
562
|
+
this._queue(buf);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// ── Drawing primitives ──────────────────────────────────────
|
|
566
|
+
|
|
567
|
+
/** Fill a rectangle with the GC's foreground color. */
|
|
568
|
+
fillRect(drawable, gc, x, y, width, height) {
|
|
569
|
+
const buf = new Uint8Array(20);
|
|
570
|
+
const v = new DataView(buf.buffer);
|
|
571
|
+
const le = kitdef.hostIsLittleEndian;
|
|
572
|
+
v.setUint8(0, Request.POLY_FILL_RECTANGLE);
|
|
573
|
+
v.setUint8(1, 0);
|
|
574
|
+
v.setUint16(2, 5, le);
|
|
575
|
+
v.setUint32(4, drawable, le);
|
|
576
|
+
v.setUint32(8, gc, le);
|
|
577
|
+
v.setInt16(12, x, le);
|
|
578
|
+
v.setInt16(14, y, le);
|
|
579
|
+
v.setUint16(16, width, le);
|
|
580
|
+
v.setUint16(18, height, le);
|
|
581
|
+
this._queue(buf);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** Draw a rectangle outline. */
|
|
585
|
+
drawRect(drawable, gc, x, y, width, height) {
|
|
586
|
+
const buf = new Uint8Array(20);
|
|
587
|
+
const v = new DataView(buf.buffer);
|
|
588
|
+
const le = kitdef.hostIsLittleEndian;
|
|
589
|
+
v.setUint8(0, Request.POLY_RECTANGLE);
|
|
590
|
+
v.setUint8(1, 0);
|
|
591
|
+
v.setUint16(2, 5, le);
|
|
592
|
+
v.setUint32(4, drawable, le);
|
|
593
|
+
v.setUint32(8, gc, le);
|
|
594
|
+
v.setInt16(12, x, le);
|
|
595
|
+
v.setInt16(14, y, le);
|
|
596
|
+
v.setUint16(16, width, le);
|
|
597
|
+
v.setUint16(18, height, le);
|
|
598
|
+
this._queue(buf);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** Draw a line. */
|
|
602
|
+
drawLine(drawable, gc, x1, y1, x2, y2) {
|
|
603
|
+
const buf = new Uint8Array(20);
|
|
604
|
+
const v = new DataView(buf.buffer);
|
|
605
|
+
const le = kitdef.hostIsLittleEndian;
|
|
606
|
+
v.setUint8(0, Request.POLY_LINE);
|
|
607
|
+
v.setUint8(1, 1); // CoordModeOrigin
|
|
608
|
+
v.setUint16(2, 5, le);
|
|
609
|
+
v.setUint32(4, drawable, le);
|
|
610
|
+
v.setUint32(8, gc, le);
|
|
611
|
+
v.setInt16(12, x1, le);
|
|
612
|
+
v.setInt16(14, y1, le);
|
|
613
|
+
v.setInt16(16, x2, le);
|
|
614
|
+
v.setInt16(18, y2, le);
|
|
615
|
+
this._queue(buf);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** Draw a filled polygon. points = [{x,y}, ...] */
|
|
619
|
+
fillPoly(drawable, gc, points) {
|
|
620
|
+
const nPts = points.length;
|
|
621
|
+
const len = 4 + nPts; // 4 header words + 1 word per point
|
|
622
|
+
const buf = new Uint8Array(len * 4);
|
|
623
|
+
const v = new DataView(buf.buffer);
|
|
624
|
+
const le = kitdef.hostIsLittleEndian;
|
|
625
|
+
v.setUint8(0, Request.FILL_POLY);
|
|
626
|
+
v.setUint8(1, 0); // Complex shape
|
|
627
|
+
v.setUint16(2, len, le);
|
|
628
|
+
v.setUint32(4, drawable, le);
|
|
629
|
+
v.setUint32(8, gc, le);
|
|
630
|
+
v.setUint8(12, 0); // shape: Complex
|
|
631
|
+
v.setUint8(13, 0); // CoordModeOrigin
|
|
632
|
+
points.forEach(({ x, y }, i) => {
|
|
633
|
+
v.setInt16(16 + i*4, x, le);
|
|
634
|
+
v.setInt16(18 + i*4, y, le);
|
|
635
|
+
});
|
|
636
|
+
this._queue(buf);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** Draw a filled arc (ellipse/circle segment). */
|
|
640
|
+
fillArc(drawable, gc, x, y, width, height, angle1 = 0, angle2 = 360 * 64) {
|
|
641
|
+
const buf = new Uint8Array(24);
|
|
642
|
+
const v = new DataView(buf.buffer);
|
|
643
|
+
const le = kitdef.hostIsLittleEndian;
|
|
644
|
+
v.setUint8(0, Request.POLY_FILL_ARC);
|
|
645
|
+
v.setUint8(1, 0);
|
|
646
|
+
v.setUint16(2, 6, le);
|
|
647
|
+
v.setUint32(4, drawable, le);
|
|
648
|
+
v.setUint32(8, gc, le);
|
|
649
|
+
v.setInt16(12, x, le);
|
|
650
|
+
v.setInt16(14, y, le);
|
|
651
|
+
v.setUint16(16, width, le);
|
|
652
|
+
v.setUint16(18, height, le);
|
|
653
|
+
v.setInt16(20, angle1, le);
|
|
654
|
+
v.setInt16(22, angle2, le);
|
|
655
|
+
this._queue(buf);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** Draw an arc outline. */
|
|
659
|
+
drawArc(drawable, gc, x, y, width, height, angle1 = 0, angle2 = 360 * 64) {
|
|
660
|
+
const buf = new Uint8Array(24);
|
|
661
|
+
const v = new DataView(buf.buffer);
|
|
662
|
+
const le = kitdef.hostIsLittleEndian;
|
|
663
|
+
v.setUint8(0, Request.POLY_ARC);
|
|
664
|
+
v.setUint8(1, 0);
|
|
665
|
+
v.setUint16(2, 6, le);
|
|
666
|
+
v.setUint32(4, drawable, le);
|
|
667
|
+
v.setUint32(8, gc, le);
|
|
668
|
+
v.setInt16(12, x, le);
|
|
669
|
+
v.setInt16(14, y, le);
|
|
670
|
+
v.setUint16(16, width, le);
|
|
671
|
+
v.setUint16(18, height, le);
|
|
672
|
+
v.setInt16(20, angle1, le);
|
|
673
|
+
v.setInt16(22, angle2, le);
|
|
674
|
+
this._queue(buf);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** Draw points. points = [{x,y}, ...] */
|
|
678
|
+
drawPoints(drawable, gc, points) {
|
|
679
|
+
const len = 3 + points.length;
|
|
680
|
+
const buf = new Uint8Array(len * 4);
|
|
681
|
+
const v = new DataView(buf.buffer);
|
|
682
|
+
const le = kitdef.hostIsLittleEndian;
|
|
683
|
+
v.setUint8(0, Request.POLY_POINT);
|
|
684
|
+
v.setUint8(1, 0); // CoordModeOrigin
|
|
685
|
+
v.setUint16(2, len, le);
|
|
686
|
+
v.setUint32(4, drawable, le);
|
|
687
|
+
v.setUint32(8, gc, le);
|
|
688
|
+
points.forEach(({ x, y }, i) => {
|
|
689
|
+
v.setInt16(12 + i*4, x, le);
|
|
690
|
+
v.setInt16(14 + i*4, y, le);
|
|
691
|
+
});
|
|
692
|
+
this._queue(buf);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Draw text using X11's core (bitmap) fonts.
|
|
697
|
+
* For scalable fonts, use the RENDER extension instead.
|
|
698
|
+
*/
|
|
699
|
+
drawText(drawable, gc, x, y, text) {
|
|
700
|
+
const enc = new TextEncoder().encode(text);
|
|
701
|
+
if (enc.length > 255) throw new Error('drawText: text too long (max 255 bytes; split it up)');
|
|
702
|
+
const itemLen = 2 + enc.length;
|
|
703
|
+
const itemPad = pad4(itemLen);
|
|
704
|
+
const len = 4 + Math.ceil((itemLen + itemPad) / 4);
|
|
705
|
+
const buf = new Uint8Array(len * 4);
|
|
706
|
+
const v = new DataView(buf.buffer);
|
|
707
|
+
const le = kitdef.hostIsLittleEndian;
|
|
708
|
+
v.setUint8(0, Request.IMAGE_TEXT_8);
|
|
709
|
+
v.setUint8(1, enc.length); // string length
|
|
710
|
+
v.setUint16(2, len, le);
|
|
711
|
+
v.setUint32(4, drawable, le);
|
|
712
|
+
v.setUint32(8, gc, le);
|
|
713
|
+
v.setInt16(12, x, le);
|
|
714
|
+
v.setInt16(14, y, le);
|
|
715
|
+
buf.set(enc, 16);
|
|
716
|
+
this._queue(buf);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** Load a named X11 core font and return its font XID. */
|
|
720
|
+
loadFont(name) {
|
|
721
|
+
const fid = this._newXid();
|
|
722
|
+
const enc = new TextEncoder().encode(name);
|
|
723
|
+
const pad = pad4(enc.length);
|
|
724
|
+
const len = 3 + Math.ceil((enc.length + pad) / 4);
|
|
725
|
+
const buf = new Uint8Array(len * 4);
|
|
726
|
+
const v = new DataView(buf.buffer);
|
|
727
|
+
const le = kitdef.hostIsLittleEndian;
|
|
728
|
+
v.setUint8(0, Request.OPEN_FONT);
|
|
729
|
+
v.setUint8(1, 0);
|
|
730
|
+
v.setUint16(2, len, le);
|
|
731
|
+
v.setUint32(4, fid, le);
|
|
732
|
+
v.setUint16(8, enc.length, le);
|
|
733
|
+
buf.set(enc, 12);
|
|
734
|
+
this._queue(buf);
|
|
735
|
+
return fid;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** Assign a font to a GC. */
|
|
739
|
+
setFont(gcid, fontId) {
|
|
740
|
+
this.changeGC(gcid, {}); // no-op flush guard
|
|
741
|
+
const buf = new Uint8Array(16);
|
|
742
|
+
const v = new DataView(buf.buffer);
|
|
743
|
+
const le = kitdef.hostIsLittleEndian;
|
|
744
|
+
v.setUint8(0, Request.CHANGE_GC);
|
|
745
|
+
v.setUint8(1, 0);
|
|
746
|
+
v.setUint16(2, 4, le);
|
|
747
|
+
v.setUint32(4, gcid, le);
|
|
748
|
+
v.setUint32(8, GCMask.FONT, le);
|
|
749
|
+
v.setUint32(12, fontId, le);
|
|
750
|
+
this._queue(buf);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** Clear the entire window to its background. */
|
|
754
|
+
clearWindow(wid) {
|
|
755
|
+
const buf = new Uint8Array(16);
|
|
756
|
+
const v = new DataView(buf.buffer);
|
|
757
|
+
const le = kitdef.hostIsLittleEndian;
|
|
758
|
+
v.setUint8(0, Request.CLEAR_AREA);
|
|
759
|
+
v.setUint8(1, 0); // exposures = false
|
|
760
|
+
v.setUint16(2, 4, le);
|
|
761
|
+
v.setUint32(4, wid, le);
|
|
762
|
+
// x=0, y=0, w=0, h=0 means entire window
|
|
763
|
+
v.setInt16(8, 0, le); v.setInt16(10, 0, le);
|
|
764
|
+
v.setUint16(12, 0, le); v.setUint16(14, 0, le);
|
|
765
|
+
this._queue(buf);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** Copy a rectangle from one drawable to another (BitBlt). */
|
|
769
|
+
copyArea(src, dst, gc, srcX, srcY, dstX, dstY, width, height) {
|
|
770
|
+
const buf = new Uint8Array(28);
|
|
771
|
+
const v = new DataView(buf.buffer);
|
|
772
|
+
const le = kitdef.hostIsLittleEndian;
|
|
773
|
+
v.setUint8(0, Request.COPY_AREA);
|
|
774
|
+
v.setUint8(1, 0);
|
|
775
|
+
v.setUint16(2, 7, le);
|
|
776
|
+
v.setUint32(4, src, le);
|
|
777
|
+
v.setUint32(8, dst, le);
|
|
778
|
+
v.setUint32(12, gc, le);
|
|
779
|
+
v.setInt16(16, srcX, le); v.setInt16(18, srcY, le);
|
|
780
|
+
v.setInt16(20, dstX, le); v.setInt16(22, dstY, le);
|
|
781
|
+
v.setUint16(24, width, le); v.setUint16(26, height, le);
|
|
782
|
+
this._queue(buf);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// ── Pixmap (off-screen buffer) ──────────────────────────────
|
|
786
|
+
|
|
787
|
+
/** Create an off-screen pixmap (for double-buffering etc.) */
|
|
788
|
+
createPixmap(drawable, width, height, depth = this._rootDepth) {
|
|
789
|
+
const pid = this._newXid();
|
|
790
|
+
const buf = new Uint8Array(16);
|
|
791
|
+
const v = new DataView(buf.buffer);
|
|
792
|
+
const le = kitdef.hostIsLittleEndian;
|
|
793
|
+
v.setUint8(0, Request.CREATE_PIXMAP);
|
|
794
|
+
v.setUint8(1, depth);
|
|
795
|
+
v.setUint16(2, 4, le);
|
|
796
|
+
v.setUint32(4, pid, le);
|
|
797
|
+
v.setUint32(8, drawable, le);
|
|
798
|
+
v.setUint16(12, width, le);
|
|
799
|
+
v.setUint16(14, height, le);
|
|
800
|
+
this._queue(buf);
|
|
801
|
+
return pid;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/** Free a pixmap. */
|
|
805
|
+
freePixmap(pid) {
|
|
806
|
+
const buf = new Uint8Array(8);
|
|
807
|
+
const v = new DataView(buf.buffer);
|
|
808
|
+
const le = kitdef.hostIsLittleEndian;
|
|
809
|
+
v.setUint8(0, Request.FREE_PIXMAP);
|
|
810
|
+
v.setUint8(1, 0);
|
|
811
|
+
v.setUint16(2, 2, le);
|
|
812
|
+
v.setUint32(4, pid, le);
|
|
813
|
+
this._queue(buf);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// ── Extension probe ─────────────────────────────────────────
|
|
817
|
+
|
|
818
|
+
/** Synchronously query whether an extension is present. */
|
|
819
|
+
queryExtension(name) {
|
|
820
|
+
if (this._extensions[name] !== undefined) return this._extensions[name];
|
|
821
|
+
const reply = this._roundTrip(buildQueryExtensionRequest(name));
|
|
822
|
+
const result = parseQueryExtensionReply(reply);
|
|
823
|
+
this._extensions[name] = result;
|
|
824
|
+
return result;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// ── Convenience ─────────────────────────────────────────────
|
|
828
|
+
|
|
829
|
+
/** Expose screen/root info. */
|
|
830
|
+
get root() { return this._rootWin; }
|
|
831
|
+
get rootDepth() { return this._rootDepth; }
|
|
832
|
+
get rootVisual() { return this._rootVisual; }
|
|
833
|
+
get whitePixel() { return this._whitePixel; }
|
|
834
|
+
get blackPixel() { return this._blackPixel; }
|
|
835
|
+
|
|
836
|
+
/** Disconnect from the X server. */
|
|
837
|
+
close() { this._sock.close(); }
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// ── Connection setup (parse server greeting) ──────────────────
|
|
841
|
+
|
|
842
|
+
function _parseSetupReply(sock, authName, authData) {
|
|
843
|
+
const req = buildConnectionRequest({ authName, authData });
|
|
844
|
+
sock.writeCopy(req);
|
|
845
|
+
|
|
846
|
+
// Read fixed 8-byte header first
|
|
847
|
+
const hdr = sock.read(8);
|
|
848
|
+
const status = u8(hdr, 0);
|
|
849
|
+
|
|
850
|
+
if (status === 0) {
|
|
851
|
+
// Failed
|
|
852
|
+
const reasonLen = u8(hdr, 1);
|
|
853
|
+
const extra = sock.read(Math.ceil((u16(hdr, 6) * 4)));
|
|
854
|
+
const reason = new TextDecoder().decode(extra.slice(0, reasonLen));
|
|
855
|
+
throw new Error(`X11 connection refused: ${reason}`);
|
|
856
|
+
}
|
|
857
|
+
if (status === 2) {
|
|
858
|
+
// Authenticate — we don't support SASL etc.
|
|
859
|
+
throw new Error('X11 server requires additional authentication');
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Status 1 = success. Read the rest.
|
|
863
|
+
const additionalWords = u16(hdr, 6);
|
|
864
|
+
const rest = sock.read(additionalWords * 4);
|
|
865
|
+
|
|
866
|
+
// Parse screen info from the greeting
|
|
867
|
+
// Offsets are relative to `rest` (the 8-byte header is already consumed)
|
|
868
|
+
const le = kitdef.hostIsLittleEndian;
|
|
869
|
+
const dv = new DataView(rest.buffer, rest.byteOffset, rest.byteLength);
|
|
870
|
+
|
|
871
|
+
const releaseNum = dv.getUint32(0, le);
|
|
872
|
+
const resourceIdBase = dv.getUint32(4, le);
|
|
873
|
+
const resourceIdMask = dv.getUint32(8, le);
|
|
874
|
+
const imageByteOrder = u8(rest, 30);
|
|
875
|
+
const bitmapBitOrder = u8(rest, 31);
|
|
876
|
+
|
|
877
|
+
const vendorLen = dv.getUint16(16, le);
|
|
878
|
+
const numFormats = u8(rest, 21);
|
|
879
|
+
// Skip vendor + formats to get to screen list
|
|
880
|
+
const vendorPad = pad4(vendorLen);
|
|
881
|
+
let off = 32 + vendorLen + vendorPad + numFormats * 8; // FORMAT is 8 bytes each
|
|
882
|
+
|
|
883
|
+
// Screen structure
|
|
884
|
+
const rootWindow = dv.getUint32(off, le); off += 4;
|
|
885
|
+
const defaultColormap= dv.getUint32(off, le); off += 4;
|
|
886
|
+
const whitePixel = dv.getUint32(off, le); off += 4;
|
|
887
|
+
const blackPixel = dv.getUint32(off, le); off += 4;
|
|
888
|
+
const currentInput = dv.getUint32(off, le); off += 4;
|
|
889
|
+
const widthPixels = dv.getUint16(off, le); off += 2;
|
|
890
|
+
const heightPixels = dv.getUint16(off, le); off += 2;
|
|
891
|
+
off += 4; // width/height in mm
|
|
892
|
+
off += 4; // min/max installed maps
|
|
893
|
+
const rootVisual = dv.getUint32(off, le); off += 4;
|
|
894
|
+
const backingStores = u8(rest, off); off += 1;
|
|
895
|
+
const saveUnders = u8(rest, off); off += 1;
|
|
896
|
+
const rootDepth = u8(rest, off); off += 1;
|
|
897
|
+
const numDepths = u8(rest, off); off += 1;
|
|
898
|
+
|
|
899
|
+
return {
|
|
900
|
+
releaseNum, resourceIdBase, resourceIdMask,
|
|
901
|
+
rootWindow, defaultColormap, whitePixel, blackPixel,
|
|
902
|
+
rootVisual, rootDepth, widthPixels, heightPixels,
|
|
903
|
+
imageByteOrder, bitmapBitOrder,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// ── Public API ────────────────────────────────────────────────
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Synchronously connect to an X11 server.
|
|
911
|
+
*
|
|
912
|
+
* @param {object} [opts]
|
|
913
|
+
* @param {string} [opts.display] Defaults to $DISPLAY
|
|
914
|
+
* @param {string} [opts.authName] e.g. 'MIT-MAGIC-COOKIE-1'
|
|
915
|
+
* @param {Uint8Array} [opts.authData] Raw cookie bytes
|
|
916
|
+
* @returns {X11Connection}
|
|
917
|
+
*
|
|
918
|
+
* @example
|
|
919
|
+
* const X11 = require('./kitx11_conn');
|
|
920
|
+
* const conn = X11.connect();
|
|
921
|
+
* const win = conn.createWindow({ width: 640, height: 480, title: 'Hello' });
|
|
922
|
+
* const gc = conn.createGC(win, { foreground: 'cornflowerblue' });
|
|
923
|
+
* conn.mapWindow(win);
|
|
924
|
+
* conn.flush();
|
|
925
|
+
*
|
|
926
|
+
* // Event loop
|
|
927
|
+
* while (true) {
|
|
928
|
+
* const ev = conn.nextEvent();
|
|
929
|
+
* if (ev.typeName === 'EXPOSE') {
|
|
930
|
+
* conn.fillRect(win, gc, 0, 0, 640, 480);
|
|
931
|
+
* conn.drawText(win, gc, 50, 50, 'Hello X11!');
|
|
932
|
+
* conn.flush();
|
|
933
|
+
* }
|
|
934
|
+
* if (ev.typeName === 'CLIENT_MESSAGE') break; // WM close
|
|
935
|
+
* }
|
|
936
|
+
* conn.close();
|
|
937
|
+
*/
|
|
938
|
+
function connect(opts = {}) {
|
|
939
|
+
const display = opts.display || process.env.DISPLAY || ':0';
|
|
940
|
+
const authName = opts.authName ?? '';
|
|
941
|
+
const authData = opts.authData ?? new Uint8Array(0);
|
|
942
|
+
|
|
943
|
+
const sock = SyncSocket.create(display);
|
|
944
|
+
const info = _parseSetupReply(sock, authName, authData);
|
|
945
|
+
return new X11Connection(sock, info);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
module.exports = { connect, X11Connection, kitdef };
|