react-x11 2.16.0 → 2.17.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.
Files changed (62) hide show
  1. package/README.md +38 -23
  2. package/package.json +3 -1
  3. package/src/Reconciler.js +82 -23
  4. package/src/a11y.js +18 -1
  5. package/src/acceleratorhooks.js +40 -6
  6. package/src/anchor.js +20 -2
  7. package/src/appcontext.js +8 -0
  8. package/src/appearance.js +36 -0
  9. package/src/{cocoa → backend}/context2d.js +27 -7
  10. package/src/capabilities.js +99 -1
  11. package/src/cocoa/app.js +204 -6
  12. package/src/cocoa/fonts.js +1 -1
  13. package/src/cocoa/overlay.js +2 -2
  14. package/src/cocoa/panewindow.js +2 -2
  15. package/src/cocoa/presenter.js +2 -2
  16. package/src/cocoa/surface.js +3 -3
  17. package/src/cocoa/window.js +2 -2
  18. package/src/events.js +21 -0
  19. package/src/foreignnodes.js +8 -3
  20. package/src/frame/index.js +30 -4
  21. package/src/glnodes.js +12 -1
  22. package/src/idle.js +59 -1
  23. package/src/index.d.ts +51 -1
  24. package/src/index.js +30 -3
  25. package/src/keysymchars.js +47 -0
  26. package/src/keysyms.d.ts +19 -1
  27. package/src/keysyms.js +107 -8
  28. package/src/launcher.js +17 -8
  29. package/src/launcherhooks.js +24 -10
  30. package/src/node.d.ts +1 -1
  31. package/src/nodes/cascade.js +9 -0
  32. package/src/nodes/node.js +6 -1
  33. package/src/nodes/window/hints.js +21 -2
  34. package/src/nodes/window/window.js +2 -2
  35. package/src/notifications.js +39 -14
  36. package/src/screens.js +159 -24
  37. package/src/taskbarhooks.js +164 -0
  38. package/src/transfer.js +20 -1
  39. package/src/trayhooks.js +1 -1
  40. package/src/types/capabilities.d.ts +32 -3
  41. package/src/types/elements.d.ts +23 -1
  42. package/src/types/events.d.ts +21 -0
  43. package/src/types/filedialog.d.ts +3 -1
  44. package/src/types/launcher.d.ts +20 -6
  45. package/src/types/taskbar.d.ts +79 -0
  46. package/src/wayland/context2d.js +1 -1
  47. package/src/wayland/xkb.js +170 -59
  48. package/src/win32/a11y.js +604 -0
  49. package/src/win32/app.js +768 -0
  50. package/src/win32/bezels.js +158 -0
  51. package/src/win32/dnd.js +283 -0
  52. package/src/win32/fonts.js +497 -0
  53. package/src/win32/glarea.js +548 -0
  54. package/src/win32/ime.js +267 -0
  55. package/src/win32/keymap.js +116 -0
  56. package/src/win32/native.js +54 -0
  57. package/src/win32/panehost.js +106 -0
  58. package/src/win32/panewindow.js +343 -0
  59. package/src/win32/shell.js +426 -0
  60. package/src/win32/surface.js +192 -0
  61. package/src/win32/window.js +659 -0
  62. package/src/windowid.js +128 -20
@@ -0,0 +1,548 @@
1
+ // `<glarea>` on Windows: a WGL core context, composited as a visual.
2
+ //
3
+ // docs/windows-gl.md is the report behind this, and the route is the X11
4
+ // model rather than ANGLE — a GL surface covers its rect and the tree draws
5
+ // under it, positioned from the parent's yoga rect. src/glnodes.js already
6
+ // expects exactly that, including the rule that the surface selects no
7
+ // pointer input so a press over it reaches the tree by propagation.
8
+ //
9
+ // Where it parts from X11 is how the surface reaches the screen. It is not a
10
+ // child window: a window presenting through DirectComposition is shown from
11
+ // its visual tree, and a child HWND's pixels go to a redirection bitmap that
12
+ // is then no part of what is composited. That was built and measured before
13
+ // it was believed — see the commit and the report. So the bridge gives each
14
+ // surface a composition swap chain under the window's own visual, and GL
15
+ // draws into a Direct3D texture the frame copies into it. Which means a
16
+ // `<glarea>` here *does* composite with the window's alpha, where on X11 it
17
+ // does not.
18
+
19
+ /**
20
+ * The window object a `<glarea>` gets. It is *not* a Win32Window: it has no
21
+ * 2d context and no frame clock of the window's — GL draws into its own
22
+ * composition surface and presents it, which is the whole difference.
23
+ */
24
+ export class Win32GlWindow {
25
+ constructor(app, attributes = {}) {
26
+ this.app = app;
27
+ this._native = app._native;
28
+ this.attributes = attributes;
29
+ this.x = Math.round(attributes.x ?? 0);
30
+ this.y = Math.round(attributes.y ?? 0);
31
+ this.width = Math.max(1, Math.round(attributes.width ?? 1));
32
+ this.height = Math.max(1, Math.round(attributes.height ?? 1));
33
+ this.mapped = false;
34
+ this.destroyed = false;
35
+ this._handlers = {};
36
+ this._gl = null;
37
+ /** The surface exists on the UI thread; until it does there is nothing to
38
+ * draw into, and a frame asked for meanwhile is refused rather than
39
+ * dropped — `glnodes.js` re-requests it from `onFrameAvailable`. */
40
+ this._ready = false;
41
+
42
+ // The parent window's id, not its handle: the surface is composited as a
43
+ // visual inside that window's tree rather than parented as a child HWND.
44
+ const parentId = attributes.parent ? attributes.parent.id : 0;
45
+ this.id = this._native.glCreateSurface(
46
+ parentId,
47
+ this.x,
48
+ this.y,
49
+ this.width,
50
+ this.height,
51
+ );
52
+ app._glWindows.set(this.id, this);
53
+ this.parent = attributes.parent ?? null;
54
+ }
55
+
56
+ /**
57
+ * The bridge reports the surface. `ok` is false where this machine has no
58
+ * vendor driver: opengl32.dll is then the 1.1 software rasterizer, with no
59
+ * shaders and no framebuffer objects — which is not a degraded direct
60
+ * backend but the indirect one's feature set without its portability, and
61
+ * is docs/windows-gl.md's rung 2 rather than this one.
62
+ */
63
+ _onReady(ok, why = 0) {
64
+ if (!ok) {
65
+ // The bridge says which step refused, because "no GL surface" covers
66
+ // four quite different machines and only one of them is the one this
67
+ // error used to describe.
68
+ const REASONS = {
69
+ 1: 'the surface window could not be created',
70
+ 2:
71
+ 'no pixel format on this device supports OpenGL — a remote ' +
72
+ 'session or a display driver without an OpenGL ICD',
73
+ 3: 'a pixel format was set but no OpenGL context could be made on it',
74
+ 4:
75
+ 'this machine has no vendor OpenGL driver, so only the 1.1 ' +
76
+ 'software rasterizer is available, which has no shaders',
77
+ };
78
+ this.emit(
79
+ 'error',
80
+ new Error(
81
+ `react-x11: no OpenGL surface — ${REASONS[why] ?? 'the bridge refused it'}. ` +
82
+ 'docs/windows-gl.md rung 2 (ANGLE) covers this case and is not built yet.',
83
+ ),
84
+ );
85
+ return;
86
+ }
87
+ this._ready = true;
88
+ // The frame this surface refused while it was being made.
89
+ this._gl?.onFrameAvailable?.();
90
+ this.emit('expose', {});
91
+ }
92
+
93
+ /**
94
+ * The WebGL-shaped table, plus the two calls `glnodes.js` makes around a
95
+ * frame. `SwapBuffers` keeps its PascalCase because that is the one name
96
+ * the element spells the same way on both backends.
97
+ */
98
+ getContext(kind) {
99
+ if (kind !== 'opengl') return null;
100
+ if (this._gl) return this._gl;
101
+ const gl = this._native.glTable();
102
+ // What `glnodes.js` branches on to choose the camelCase spelling over the
103
+ // indirect backend's PascalCase one — `gl.backend === 'direct'`. This is a
104
+ // real direct context (shaders, framebuffer objects, vertex buffers), and
105
+ // saying otherwise sends every call to a name this table does not have.
106
+ gl.backend = 'direct';
107
+ gl.makeCurrent = () => this._native.glMakeCurrent(this.id);
108
+ gl.SwapBuffers = () => this._native.glSwapBuffers(this.id);
109
+ // A WGL swap does not hold a frame back the way a DRI3 Present or a CGL
110
+ // flush does, so the only reason to refuse one is that the surface does
111
+ // not exist yet. `onFrameAvailable` is the channel glnodes.js installs to
112
+ // be told when it does — declared here so it finds the property.
113
+ gl.canRender = () => this._ready;
114
+ gl.onFrameAvailable = null;
115
+ Object.assign(gl, CONSTANTS);
116
+ this._gl = GL_DEBUG ? instrument(gl) : gl;
117
+ return this._gl;
118
+ }
119
+
120
+ /**
121
+ * The frame clock, which is the parent window's — the same delegation the
122
+ * Cocoa surface makes (src/cocoa/glarea.js). A `<glarea>` has no clock of
123
+ * its own here: it is a visual inside its parent's composition tree, so
124
+ * the parent's tick is the one its frames belong to. Without this
125
+ * `glnodes.js` falls back to `setImmediate`, which is not a frame clock at
126
+ * all — it is "as fast as the event loop will go", which paces a still
127
+ * scene against nothing.
128
+ */
129
+ requestAnimationFrame(cb) {
130
+ return this.parent?.requestAnimationFrame?.(cb) ?? setImmediate(cb);
131
+ }
132
+ resize(width, height) {
133
+ this.width = Math.max(1, Math.round(width));
134
+ this.height = Math.max(1, Math.round(height));
135
+ this._native.glResizeSurface(
136
+ this.id,
137
+ this.x,
138
+ this.y,
139
+ this.width,
140
+ this.height,
141
+ );
142
+ }
143
+
144
+ move(x, y) {
145
+ this.x = Math.round(x);
146
+ this.y = Math.round(y);
147
+ this._native.glResizeSurface(
148
+ this.id,
149
+ this.x,
150
+ this.y,
151
+ this.width,
152
+ this.height,
153
+ );
154
+ }
155
+
156
+ map() {
157
+ this.mapped = true;
158
+ }
159
+
160
+ unmap() {
161
+ this.mapped = false;
162
+ }
163
+
164
+ destroy() {
165
+ if (this.destroyed) return;
166
+ this.destroyed = true;
167
+ this.app._glWindows.delete(this.id);
168
+ this._native.glDestroySurface(this.id);
169
+ }
170
+
171
+ on(name, fn) {
172
+ (this._handlers[name] ??= []).push(fn);
173
+ }
174
+
175
+ emit(name, ev) {
176
+ for (const fn of this._handlers[name] ?? []) fn(ev);
177
+ }
178
+
179
+ // A GL surface has no 2d context and no backing store; these exist so the
180
+ // node layer's feature tests answer no rather than throwing.
181
+ getContext2D() {
182
+ return null;
183
+ }
184
+ setCursor() {}
185
+ raise() {}
186
+ }
187
+
188
+ /**
189
+ * `REACT_X11_GL_DEBUG=1` — every call checked against glGetError, and every
190
+ * name the caller reaches for that this table does not have.
191
+ *
192
+ * A missing verb is the failure this exists for: the table is written by hand
193
+ * against the WebGL names, and a renderer that asks for one that is not here
194
+ * gets `undefined is not a function` somewhere deep inside its own frame,
195
+ * where an example's error handling may swallow it. Asking the object what it
196
+ * was asked for turns that into a list.
197
+ */
198
+ const GL_DEBUG = process.env.REACT_X11_GL_DEBUG === '1';
199
+
200
+ function instrument(gl) {
201
+ // REACT_X11_GL_FORCE_CLEAR=1 paints the GL surface a colour nothing else in
202
+ // the window uses. It answers the one question a screenshot of a map cannot:
203
+ // whether what is on screen is the GL child or the 2D layer behind it, which
204
+ // in this app are both the style's background colour.
205
+ if (process.env.REACT_X11_GL_FORCE_CLEAR === '1') {
206
+ const realClearColor = gl.clearColor;
207
+ gl.clearColor = () => realClearColor(1, 0, 1, 1);
208
+ }
209
+
210
+ const counts = new Map();
211
+ const missing = new Set();
212
+ let swaps = 0;
213
+ // Frame timing, split at the only place it can be split from here: the work
214
+ // between making the context current and presenting is the GL frame; the
215
+ // rest of the interval is everything else — the tile and label work, the
216
+ // React render, the 2D layer, and whatever the pacer decides to wait.
217
+ let frameStart = 0;
218
+ let drawTotal = 0;
219
+ let gapTotal = 0;
220
+ let lastSwap = 0;
221
+ let presentTotal = 0;
222
+ let errors = 0;
223
+ const raw = gl.getError;
224
+ const rawRead = gl.readPixels;
225
+ const raw_getShaderInfoLog = gl.getShaderInfoLog;
226
+ const raw_getProgramInfoLog = gl.getProgramInfoLog;
227
+ // REACT_X11_GL_PEEK=<n> reads the stencil and the colour at the middle of
228
+ // the surface after each draw. A fill pass that lays down no winding and a
229
+ // cover pass that paints nothing look identical from outside; the stencil
230
+ // buffer is the only place that tells them apart.
231
+ const peek = Number(process.env.REACT_X11_GL_PEEK ?? 0);
232
+ let peeked = 0;
233
+
234
+ const describe = (v) =>
235
+ ArrayBuffer.isView(v)
236
+ ? `${v.constructor.name}(${v.length})`
237
+ : typeof v === 'string'
238
+ ? JSON.stringify(v.length > 30 ? `${v.slice(0, 30)}…` : v)
239
+ : String(v);
240
+
241
+ // REACT_X11_GL_TRACE=<n> also prints the first n calls in order, with their
242
+ // arguments: the only way to see a frame that draws everything correctly
243
+ // into a place nothing is looking at.
244
+ const trace = Number(process.env.REACT_X11_GL_TRACE ?? 0);
245
+ // Startup frames draw nothing — the tiles are still arriving — so the
246
+ // interesting frame is never the first one.
247
+ const traceAfter = Number(process.env.REACT_X11_GL_TRACE_AFTER ?? 0);
248
+ let traced = 0;
249
+
250
+ for (const name of Object.keys(gl)) {
251
+ const fn = gl[name];
252
+ if (typeof fn !== 'function' || name === 'getError') continue;
253
+ gl[name] = (...args) => {
254
+ if (name === 'makeCurrent') frameStart = performance.now();
255
+ const out = fn(...args);
256
+ counts.set(name, (counts.get(name) ?? 0) + 1);
257
+ if (traced < trace && swaps >= traceAfter) {
258
+ traced += 1;
259
+ process.stderr.write(
260
+ `[gl] ${String(traced).padStart(3)} ${name}(${args.map(describe).join(', ')})` +
261
+ `${out === undefined ? '' : ` = ${describe(out)}`}
262
+ `,
263
+ );
264
+ }
265
+ // readPixels answers in an out-parameter, so the trace has to look at it
266
+ // after the call or the most interesting result in the API is invisible.
267
+ if (
268
+ trace > 0 &&
269
+ (name === 'bufferData' || name === 'bufferSubData') &&
270
+ traced <= trace
271
+ ) {
272
+ const buf = args.find((a) => ArrayBuffer.isView(a));
273
+ process.stderr.write(
274
+ `[gl] upload: ${buf ? `${buf.constructor.name} ${buf.byteLength}B` : 'no view argument'}
275
+ `,
276
+ );
277
+ }
278
+ if (name === 'readPixels' && trace > 0 && ArrayBuffer.isView(args[6])) {
279
+ process.stderr
280
+ .write(`[gl] read back: [${[...args[6].slice(0, 16)].join(', ')}]
281
+ `);
282
+ }
283
+ if (peek > 0 && peeked < peek && name.startsWith('draw')) {
284
+ peeked += 1;
285
+ // A whole scanline rather than one pixel: geometry that misses the
286
+ // point sampled and geometry that was never rasterised look alike.
287
+ const st = new Uint8Array(1600);
288
+ rawRead(100, 600, 1600, 1, 0x1901 /* STENCIL_INDEX */, 0x1401, st);
289
+ const rgba = new Uint8Array(1600 * 4);
290
+ rawRead(100, 600, 1600, 1, 0x1908, 0x1401, rgba);
291
+ let set = 0;
292
+ let peak = 0;
293
+ for (const v of st) {
294
+ if (v !== 0) set += 1;
295
+ if (v > peak) peak = v;
296
+ }
297
+ const colours = new Set();
298
+ for (let i = 0; i < rgba.length; i += 4) {
299
+ colours.add(`${rgba[i]},${rgba[i + 1]},${rgba[i + 2]}`);
300
+ }
301
+ process.stderr.write(
302
+ `[gl] after ${name}: stencil set on ${set}/1600 (max ${peak}), ` +
303
+ `${colours.size} colours ${[...colours].slice(0, 3).join(' | ')}\n`,
304
+ );
305
+ }
306
+ // A shader that will not compile, or a program that will not link, is
307
+ // not a GL *error* — glGetError stays clean and the caller is expected
308
+ // to ask. An app that asks and then throws its own message leaves
309
+ // nothing behind, so the log is printed here where it still exists.
310
+ if (
311
+ (name === 'getShaderParameter' || name === 'getProgramParameter') &&
312
+ out === false
313
+ ) {
314
+ const log =
315
+ name === 'getShaderParameter'
316
+ ? raw_getShaderInfoLog(args[0])
317
+ : raw_getProgramInfoLog(args[0]);
318
+ process.stderr.write(`[gl] ${name} false — ${log || '(no log)'}\n`);
319
+ }
320
+ const err = raw();
321
+ if (err !== 0 && errors < 40) {
322
+ errors += 1;
323
+ process.stderr.write(
324
+ `[gl] ${name}(${args.map(describe).join(', ')}) -> 0x${err.toString(16)}\n`,
325
+ );
326
+ }
327
+ return out;
328
+ };
329
+ }
330
+
331
+ const swap = gl.SwapBuffers;
332
+ gl.SwapBuffers = (...args) => {
333
+ swaps += 1;
334
+ const now = performance.now();
335
+ if (frameStart) drawTotal += now - frameStart;
336
+ if (lastSwap) gapTotal += now - lastSwap;
337
+ lastSwap = now;
338
+ if (swaps % 30 === 0) {
339
+ const top = [...counts.entries()]
340
+ .sort((a, b) => b[1] - a[1])
341
+ .slice(0, Number(process.env.REACT_X11_GL_TOP ?? 12))
342
+ .map(([k, v]) => `${k}=${v}`)
343
+ .join(' ');
344
+ process.stderr.write(
345
+ `[gl] after ${swaps} swaps: GL frame ${(drawTotal / 30).toFixed(1)} ms, ` +
346
+ `present ${(presentTotal / 30).toFixed(1)} ms, interval ${(gapTotal / 30).toFixed(1)} ms — ${top}
347
+ `,
348
+ );
349
+ drawTotal = 0;
350
+ gapTotal = 0;
351
+ presentTotal = 0;
352
+ if (missing.size > 0) {
353
+ process.stderr.write(`[gl] NOT IN TABLE: ${[...missing].join(', ')}\n`);
354
+ }
355
+ }
356
+ const before = performance.now();
357
+ const result = swap(...args);
358
+ presentTotal += performance.now() - before;
359
+ return result;
360
+ };
361
+
362
+ return new Proxy(gl, {
363
+ get(target, prop) {
364
+ if (
365
+ typeof prop === 'string' &&
366
+ !(prop in target) &&
367
+ // a constant would be SHOUTED; anything else read off this object is
368
+ // being read to be called
369
+ prop !== 'then' &&
370
+ !missing.has(prop)
371
+ ) {
372
+ missing.add(prop);
373
+ process.stderr.write(`[gl] NOT IN TABLE: ${prop}\n`);
374
+ }
375
+ return target[prop];
376
+ },
377
+ });
378
+ }
379
+
380
+ /**
381
+ * The GL enumerants, as numbers. They are constants of the API rather than of
382
+ * a context, so they live here rather than being asked of the driver — which
383
+ * is also what every WebGL implementation does.
384
+ */
385
+
386
+ /**
387
+ * The texture units, as a range rather than one entry.
388
+ *
389
+ * `TEXTURE0` alone was here, and a scene that binds a second texture —
390
+ * anything with a base map and an overlay, which is most of them — asked for
391
+ * `gl.TEXTURE1`, got `undefined`, and passed it to `activeTexture` as an
392
+ * invalid enum. The bind then landed on unit 0 and the sampler read black.
393
+ *
394
+ * GL guarantees the units are consecutive from TEXTURE0, so this is the
395
+ * definition rather than a table of 32 lines.
396
+ */
397
+ const TEXTURE_UNITS = Object.fromEntries(
398
+ Array.from({ length: 32 }, (_, i) => [`TEXTURE${i}`, 0x84c0 + i]),
399
+ );
400
+ const CONSTANTS = Object.freeze({
401
+ DEPTH_BUFFER_BIT: 0x0100,
402
+ STENCIL_BUFFER_BIT: 0x0400,
403
+ COLOR_BUFFER_BIT: 0x4000,
404
+ POINTS: 0,
405
+ LINES: 1,
406
+ LINE_LOOP: 2,
407
+ LINE_STRIP: 3,
408
+ TRIANGLES: 4,
409
+ TRIANGLE_STRIP: 5,
410
+ TRIANGLE_FAN: 6,
411
+ ZERO: 0,
412
+ ONE: 1,
413
+ SRC_COLOR: 0x0300,
414
+ ONE_MINUS_SRC_COLOR: 0x0301,
415
+ SRC_ALPHA: 0x0302,
416
+ ONE_MINUS_SRC_ALPHA: 0x0303,
417
+ DST_ALPHA: 0x0304,
418
+ ONE_MINUS_DST_ALPHA: 0x0305,
419
+ DST_COLOR: 0x0306,
420
+ ONE_MINUS_DST_COLOR: 0x0307,
421
+ FRONT: 0x0404,
422
+ BACK: 0x0405,
423
+ FRONT_AND_BACK: 0x0408,
424
+ CULL_FACE: 0x0b44,
425
+ DEPTH_TEST: 0x0b71,
426
+ STENCIL_TEST: 0x0b90,
427
+ BLEND: 0x0be2,
428
+ SCISSOR_TEST: 0x0c11,
429
+ TEXTURE_2D: 0x0de1,
430
+ NEVER: 0x0200,
431
+ LESS: 0x0201,
432
+ EQUAL: 0x0202,
433
+ LEQUAL: 0x0203,
434
+ GREATER: 0x0204,
435
+ NOTEQUAL: 0x0205,
436
+ GEQUAL: 0x0206,
437
+ ALWAYS: 0x0207,
438
+ KEEP: 0x1e00,
439
+ REPLACE: 0x1e01,
440
+ INCR: 0x1e02,
441
+ DECR: 0x1e03,
442
+ INVERT: 0x150a,
443
+ INCR_WRAP: 0x8507,
444
+ DECR_WRAP: 0x8508,
445
+ BYTE: 0x1400,
446
+ UNSIGNED_BYTE: 0x1401,
447
+ SHORT: 0x1402,
448
+ UNSIGNED_SHORT: 0x1403,
449
+ INT: 0x1404,
450
+ UNSIGNED_INT: 0x1405,
451
+ UNPACK_ALIGNMENT: 0x0cf5,
452
+ FLOAT: 0x1406,
453
+ RGB: 0x1907,
454
+ RGBA: 0x1908,
455
+ LUMINANCE: 0x1909,
456
+ LUMINANCE_ALPHA: 0x190a,
457
+ NEAREST: 0x2600,
458
+ LINEAR: 0x2601,
459
+ NEAREST_MIPMAP_NEAREST: 0x2700,
460
+ LINEAR_MIPMAP_NEAREST: 0x2701,
461
+ NEAREST_MIPMAP_LINEAR: 0x2702,
462
+ LINEAR_MIPMAP_LINEAR: 0x2703,
463
+ TEXTURE_MAG_FILTER: 0x2800,
464
+ TEXTURE_MIN_FILTER: 0x2801,
465
+ TEXTURE_WRAP_S: 0x2802,
466
+ TEXTURE_WRAP_T: 0x2803,
467
+ REPEAT: 0x2901,
468
+ CLAMP_TO_EDGE: 0x812f,
469
+ MIRRORED_REPEAT: 0x8370,
470
+ ...TEXTURE_UNITS,
471
+ ARRAY_BUFFER: 0x8892,
472
+ ELEMENT_ARRAY_BUFFER: 0x8893,
473
+ STREAM_DRAW: 0x88e0,
474
+ STATIC_DRAW: 0x88e4,
475
+ DYNAMIC_DRAW: 0x88e8,
476
+ FRAGMENT_SHADER: 0x8b30,
477
+ VERTEX_SHADER: 0x8b31,
478
+ COMPILE_STATUS: 0x8b81,
479
+ LINK_STATUS: 0x8b82,
480
+ VALIDATE_STATUS: 0x8b83,
481
+ INFO_LOG_LENGTH: 0x8b84,
482
+ DELETE_STATUS: 0x8b80,
483
+ FRAMEBUFFER: 0x8d40,
484
+ RENDERBUFFER: 0x8d41,
485
+ RGBA4: 0x8056,
486
+ DEPTH_COMPONENT16: 0x81a5,
487
+ DEPTH24_STENCIL8: 0x88f0,
488
+ STENCIL_INDEX8: 0x8d48,
489
+ COLOR_ATTACHMENT0: 0x8ce0,
490
+ DEPTH_ATTACHMENT: 0x8d00,
491
+ STENCIL_ATTACHMENT: 0x8d20,
492
+ DEPTH_STENCIL_ATTACHMENT: 0x821a,
493
+ FRAMEBUFFER_COMPLETE: 0x8cd5,
494
+ FRAMEBUFFER_BINDING: 0x8ca6,
495
+ RENDERBUFFER_BINDING: 0x8ca7,
496
+ VERTEX_ARRAY_BINDING: 0x85b5,
497
+ ARRAY_BUFFER_BINDING: 0x8894,
498
+ CURRENT_PROGRAM: 0x8b8d,
499
+ MAX_TEXTURE_SIZE: 0x0d33,
500
+ VIEWPORT: 0x0ba2,
501
+ NO_ERROR: 0,
502
+ });
503
+
504
+ /**
505
+ * The GL seams on the app — asked, not answered.
506
+ *
507
+ * Probing for a context is not free: making one, asking it its version and
508
+ * throwing it away costs about 120ms on a vendor driver, and it used to be
509
+ * spent by every app during `createRoot`, whether or not it would ever hold
510
+ * a `<glarea>`. Most never do. So this installs the seams and nothing else,
511
+ * and the probe happens the first time something actually asks.
512
+ *
513
+ * `glCapabilities()` answering a promise is what makes that safe:
514
+ * `watchDirectGL` (src/glbackend.js) already calls it and re-reads when it
515
+ * settles, which is the machinery for an answer that is not ready at the
516
+ * first render. `useSupports('shaders')` is false for a tick and then true,
517
+ * rather than costing every app a fifth of a second at startup.
518
+ */
519
+ export function installGl(app) {
520
+ let settled = null;
521
+
522
+ const probe = () => {
523
+ if (settled) return settled;
524
+ const result = app._native.glProbe?.();
525
+ // A core context is the line. Below it there is no vendor driver, and
526
+ // the software rasterizer's GL 1.1 is not a degraded direct backend.
527
+ const direct = Boolean(result && result.core);
528
+ app._glCapsResolved = { direct, indirect: false, probe: result ?? null };
529
+ settled = Promise.resolve(app._glCapsResolved);
530
+ return settled;
531
+ };
532
+
533
+ // A statement of intent, not of capability — `hasDirectGL` reads the
534
+ // policy *and* the capabilities, and only the second needs the probe.
535
+ app.glPolicy = { mode: 'direct' };
536
+ app.glCapabilities = () => probe();
537
+ app.chooseGLConfig = () =>
538
+ probe().then((caps) => {
539
+ if (caps.direct) return { backend: 'direct', visual: 0, depth: 32 };
540
+ throw new Error(
541
+ 'react-x11: <glarea> has no GL surface — this machine has no vendor ' +
542
+ 'OpenGL driver, so only the 1.1 software rasterizer is available, ' +
543
+ 'which has no shaders. docs/windows-gl.md rung 2 (ANGLE) covers ' +
544
+ 'this case and is not built yet.',
545
+ );
546
+ });
547
+ return true;
548
+ }