react-x11 1.2.0 → 2.0.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/README.md +330 -81
- package/package.json +132 -14
- package/src/ClickToComponent.js +9 -2
- package/src/DevToolsIntegration.js +440 -32
- package/src/Reconciler.js +463 -165
- package/src/a11y.js +1299 -0
- package/src/acceleratorhooks.js +86 -0
- package/src/accelerators.js +128 -0
- package/src/activate.js +168 -0
- package/src/anchor.js +360 -0
- package/src/appcontext.js +161 -0
- package/src/appearance.js +748 -0
- package/src/appearancehooks.js +96 -0
- package/src/apphooks.js +73 -0
- package/src/application.js +748 -0
- package/src/atspi.js +1953 -0
- package/src/bus.js +545 -0
- package/src/bushooks.js +136 -0
- package/src/clientmessage.js +140 -0
- package/src/clipboard.js +121 -0
- package/src/components/Button.js +148 -33
- package/src/components/Checkbox.js +66 -32
- package/src/components/Dialog.js +96 -29
- package/src/components/FileDialog.js +491 -0
- package/src/components/Icon.js +329 -0
- package/src/components/Menu.js +797 -133
- package/src/components/PasswordInput.js +434 -0
- package/src/components/ProgressBar.js +91 -12
- package/src/components/Radio.js +78 -35
- package/src/components/Select.js +255 -65
- package/src/components/Slider.js +102 -41
- package/src/components/SplitPane.js +206 -0
- package/src/components/Switch.js +100 -22
- package/src/components/Table.js +529 -0
- package/src/components/Tabs.js +211 -0
- package/src/components/Tooltip.js +528 -54
- package/src/components/anchor.js +154 -107
- package/src/components/change.js +34 -0
- package/src/components/dnd.js +103 -0
- package/src/components/index.js +16 -3
- package/src/components/keys.js +17 -20
- package/src/components/scribble.js +247 -0
- package/src/components/theme.js +350 -42
- package/src/components/typeahead.js +12 -2
- package/src/compose.js +868 -0
- package/src/compositing.js +224 -0
- package/src/dbusmenu.js +384 -0
- package/src/debug.d.ts +63 -0
- package/src/debug.js +642 -0
- package/src/decorations.js +486 -0
- package/src/desktopsettings.js +209 -0
- package/src/desktopsettingshooks.js +62 -0
- package/src/dnd.js +1725 -0
- package/src/editmenu.js +272 -0
- package/src/errors.js +98 -0
- package/src/events.js +1406 -162
- package/src/extensions.js +45 -0
- package/src/filedialog.js +375 -0
- package/src/filedialoghooks.js +132 -0
- package/src/fonthooks.js +64 -0
- package/src/fonts.js +301 -0
- package/src/foreignnodes.js +519 -0
- package/src/frame/child.js +33 -0
- package/src/frame/childmain.js +274 -0
- package/src/frame/env.js +140 -0
- package/src/frame/index.js +444 -0
- package/src/frame/lifecycle.js +67 -0
- package/src/frame/protocol.js +179 -0
- package/src/frames.js +73 -0
- package/src/glbackend.js +114 -0
- package/src/glnodes.js +125 -67
- package/src/globalmenu.js +683 -0
- package/src/host.d.ts +88 -0
- package/src/host.js +42 -0
- package/src/idle.js +490 -0
- package/src/idlehooks.js +100 -0
- package/src/imagesource.js +349 -0
- package/src/index.d.ts +475 -0
- package/src/index.js +83 -9
- package/src/inputtime.js +206 -0
- package/src/jsx-dev-runtime.d.ts +21 -0
- package/src/jsx-dev-runtime.js +2 -0
- package/src/jsx-runtime.d.ts +39 -0
- package/src/jsx-runtime.js +6 -0
- package/src/keyboard.js +256 -0
- package/src/keyboardstate.js +278 -0
- package/src/keyboardstatehooks.js +58 -0
- package/src/keysyms.d.ts +149 -0
- package/src/keysyms.js +270 -0
- package/src/locale.js +170 -0
- package/src/localehooks.js +47 -0
- package/src/menuitem.js +223 -0
- package/src/node.d.ts +597 -0
- package/src/node.js +44 -0
- package/src/nodes.js +9546 -690
- package/src/ntk.d.ts +44 -0
- package/src/ntk.js +25 -0
- package/src/paintcache.js +366 -0
- package/src/palette.js +380 -0
- package/src/pastestate.js +66 -0
- package/src/portal.js +461 -0
- package/src/priority.js +26 -0
- package/src/refresh/index.d.ts +40 -0
- package/src/refresh/index.js +122 -0
- package/src/refresh/loader.d.ts +37 -0
- package/src/refresh/loader.js +401 -0
- package/src/refresh/register.d.ts +5 -0
- package/src/refresh/register.js +13 -0
- package/src/registry.js +232 -0
- package/src/scale.js +626 -0
- package/src/scalehooks.js +27 -0
- package/src/screencolor.js +640 -0
- package/src/screencolorhooks.js +101 -0
- package/src/screens.js +754 -0
- package/src/screenshooks.js +137 -0
- package/src/startup.js +302 -0
- package/src/style.d.ts +126 -0
- package/src/style.js +33 -0
- package/src/styles.js +1482 -6
- package/src/svgnodes.js +306 -0
- package/src/testing/a11y.js +484 -0
- package/src/testing/components.js +414 -0
- package/src/testing/events.js +407 -0
- package/src/testing/harness.js +455 -0
- package/src/testing/index.d.ts +558 -0
- package/src/testing/index.js +85 -0
- package/src/testing/mock-app.js +463 -0
- package/src/testing/pixels.js +152 -0
- package/src/testing/queries.js +224 -0
- package/src/textrange.js +83 -0
- package/src/textselection.js +439 -0
- package/src/trace-registry.js +63 -0
- package/src/transfer.js +93 -0
- package/src/types/appearance.d.ts +84 -0
- package/src/types/application.d.ts +173 -0
- package/src/types/components.d.ts +820 -0
- package/src/types/dbus.d.ts +177 -0
- package/src/types/elements.d.ts +897 -0
- package/src/types/events.d.ts +549 -0
- package/src/types/filedialog.d.ts +260 -0
- package/src/types/fonts.d.ts +124 -0
- package/src/types/frame.d.ts +146 -0
- package/src/types/globalmenu.d.ts +42 -0
- package/src/types/nodes.d.ts +199 -0
- package/src/types/screencolor.d.ts +84 -0
- package/src/types/style.d.ts +434 -0
- package/src/types/system.d.ts +287 -0
- package/src/windowid.js +151 -0
- package/src/windowstate.js +393 -0
- package/src/xsettings.js +336 -0
- package/src/yoga.d.ts +55 -0
- package/src/yoga.js +135 -0
- package/src/components/Canvas3D.js +0 -28
- package/src/geometry3d.js +0 -223
- package/src/pointer3d.js +0 -158
- package/src/raycast3d.js +0 -146
- package/src/richnodes.js +0 -436
- package/src/scene3d.js +0 -683
package/src/frames.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Which windows owe a frame.
|
|
2
|
+
//
|
|
3
|
+
// Shared between the paint root (nodes.js — it records the debt in
|
|
4
|
+
// `invalidate()` and pays it in `flush()`) and the event dispatcher
|
|
5
|
+
// (events.js — it pays early, for discrete input). Its own module because
|
|
6
|
+
// nodes.js already imports events.js, and the dispatcher cannot import the
|
|
7
|
+
// node back without a cycle.
|
|
8
|
+
//
|
|
9
|
+
// The set answers "which windows have unpainted damage" in O(1) at event
|
|
10
|
+
// time. A tree walk would answer it too, but it is walked once per discrete
|
|
11
|
+
// event on a tree that can be thousands of nodes, to find the one or two
|
|
12
|
+
// roots that a `Set.add` in `invalidate()` already knew about.
|
|
13
|
+
|
|
14
|
+
const pending = new Set();
|
|
15
|
+
|
|
16
|
+
/** `node` has damage and no paint yet. Idempotent. */
|
|
17
|
+
export function addPendingFrame(node) {
|
|
18
|
+
pending.add(node);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** `node` is painting (or has nothing left to paint). */
|
|
22
|
+
export function clearPendingFrame(node) {
|
|
23
|
+
pending.delete(node);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Paint every window that owes a frame and whose server is caught up — the
|
|
28
|
+
* synchronous half of the frame clock (issue #141).
|
|
29
|
+
*
|
|
30
|
+
* A discrete input has exactly one visual response: a button darkens, a
|
|
31
|
+
* caret lands, focus moves. Scheduling that response through
|
|
32
|
+
* `requestAnimationFrame` charges it up to a frame interval plus a fence
|
|
33
|
+
* round trip before it reaches the wire, on top of a paint that measures in
|
|
34
|
+
* the hundreds of microseconds. So the dispatcher calls this when the
|
|
35
|
+
* handler unwinds, and the pixels go out with the handler's own requests
|
|
36
|
+
* instead.
|
|
37
|
+
*
|
|
38
|
+
* Two things make that safe to do unconditionally at the call site:
|
|
39
|
+
*
|
|
40
|
+
* - **The frame gate.** A window whose last frame has not landed keeps the
|
|
41
|
+
* frame it already scheduled. Painting it now would be work nobody sees:
|
|
42
|
+
* ntk defers the present until the frame ends, where it coalesces with
|
|
43
|
+
* the one already pending. This is what keeps a burst sane — spin a wheel
|
|
44
|
+
* and the first notch paints immediately, the rest fold into one catch-up
|
|
45
|
+
* frame. "Update instantly, then catch up", with the frame clock as the
|
|
46
|
+
* backpressure.
|
|
47
|
+
*
|
|
48
|
+
* On ntk >= 7 what ends a frame is the display: the server reports the
|
|
49
|
+
* present it executed at a vertical blank, so the gate now means "the
|
|
50
|
+
* display has not shown the last frame" rather than "the socket has not
|
|
51
|
+
* acknowledged it". Same call, later and truer signal — and it is also
|
|
52
|
+
* what keeps us off a backing pixmap the server is still copying from.
|
|
53
|
+
* - **Every window answers for itself.** The set is app-wide, so a click
|
|
54
|
+
* that dirtied both a menu and the window under it paints both, each
|
|
55
|
+
* gated on its own window's frame clock.
|
|
56
|
+
*
|
|
57
|
+
* A window whose scheduled frame this pays off keeps that scheduled
|
|
58
|
+
* callback; it runs and finds `needsPaint` false, which is already a cheap
|
|
59
|
+
* no-op.
|
|
60
|
+
*/
|
|
61
|
+
export function flushPendingFrames() {
|
|
62
|
+
if (pending.size === 0) return;
|
|
63
|
+
for (const node of [...pending]) {
|
|
64
|
+
const wnd = node.window;
|
|
65
|
+
// ntk < 5.2.0 (sidorares/ntk#148) cannot say whether the frame has
|
|
66
|
+
// landed, and a missing answer is not a "no" — without the gate a burst
|
|
67
|
+
// would paint every event, so the paced frame stays the safe choice.
|
|
68
|
+
// Same for a headless mock window that models no frame clock at all.
|
|
69
|
+
if (typeof wnd?.frameInFlight !== 'function') continue;
|
|
70
|
+
if (wnd.frameInFlight()) continue;
|
|
71
|
+
node.flush();
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/glbackend.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Which GL backend a connection got, asked synchronously.
|
|
2
|
+
//
|
|
3
|
+
// The question is about the **connection**, not the machine. ntk's
|
|
4
|
+
// `glCapabilities()` answers the machine one — "could direct rendering work
|
|
5
|
+
// here" — and consults `glPolicy` only far enough to honour `'off'`, so on a
|
|
6
|
+
// DRI3-capable box it resolves `{ direct: true }` under the default
|
|
7
|
+
// `'indirect'` policy too. What actually draws is what the policy asked for,
|
|
8
|
+
// so that is what this module reports: the same rule ntk's own `backendFor()`
|
|
9
|
+
// applies when `getContext` picks a backend, kept in step here because it is
|
|
10
|
+
// not part of ntk's public API (issue #357).
|
|
11
|
+
//
|
|
12
|
+
// ntk settles the probe during its connection handshake — it runs it whenever
|
|
13
|
+
// `glPolicy` could select direct — so by the time any element is created the
|
|
14
|
+
// answer is a property read rather than a round trip. That is what lets
|
|
15
|
+
// `<shaderMaterial>` fail at creation, with a reason, instead of rendering an
|
|
16
|
+
// empty surface.
|
|
17
|
+
//
|
|
18
|
+
// A leaf module on purpose: it imports nothing of ours, so both
|
|
19
|
+
// `appcontext.js` (for `useSupports('shaders')`) and the node layer can use
|
|
20
|
+
// it without the two importing each other.
|
|
21
|
+
|
|
22
|
+
import { GLError } from 'ntk';
|
|
23
|
+
|
|
24
|
+
/** Does this connection's policy ever choose the direct backend? */
|
|
25
|
+
const wantsDirect = (app) => {
|
|
26
|
+
const mode = app?.glPolicy?.mode;
|
|
27
|
+
return mode === 'auto' || mode === 'direct';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** ntk's coded-error shape — `code` to branch on, `hint` for what to do. */
|
|
31
|
+
function glError(code, message, hint) {
|
|
32
|
+
const err = new Error(message);
|
|
33
|
+
err.code = code;
|
|
34
|
+
if (hint) err.hint = hint;
|
|
35
|
+
return err;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Is the direct backend — the one with shaders — what this connection draws
|
|
40
|
+
* through?
|
|
41
|
+
*
|
|
42
|
+
* False under a policy that never asks for it, whatever the machine could do,
|
|
43
|
+
* because the policy is what decides which backend is built.
|
|
44
|
+
*/
|
|
45
|
+
export function hasDirectGL(app) {
|
|
46
|
+
return wantsDirect(app) && !!app._glCapsResolved?.direct;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Why it is not, as a coded error — or null when it is available, or when the
|
|
51
|
+
* probe that would say has not answered yet.
|
|
52
|
+
*
|
|
53
|
+
* Worth surfacing rather than describing: the reasons are genuinely different
|
|
54
|
+
* (a policy that never asked, no GPU addon, a connection that cannot pass
|
|
55
|
+
* descriptors, a server without DRI3) and only one of them is "turn the
|
|
56
|
+
* policy on". A message that guesses sends people to check the wrong thing.
|
|
57
|
+
*
|
|
58
|
+
* The policy reasons are minted here because ntk has none to give: its probe
|
|
59
|
+
* consults the mode only for `'off'`, so a connection left on the default
|
|
60
|
+
* `'indirect'` carries capabilities with no `reason` in them at all — and on
|
|
61
|
+
* a capable machine, with `direct: true` in them.
|
|
62
|
+
*/
|
|
63
|
+
export function directGLFailure(app) {
|
|
64
|
+
const mode = app?.glPolicy?.mode;
|
|
65
|
+
if (mode === 'off') {
|
|
66
|
+
return glError(
|
|
67
|
+
GLError.DISABLED,
|
|
68
|
+
"glPolicy is 'off' on this connection, so no GL context is created at all",
|
|
69
|
+
"No <glarea> draws under this policy. Pass glPolicy: 'auto'\n" +
|
|
70
|
+
'to createRoot(), or drop the option, to get a context back.',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (!wantsDirect(app)) {
|
|
74
|
+
return glError(
|
|
75
|
+
'GL_POLICY_INDIRECT',
|
|
76
|
+
"glPolicy is 'indirect' — ntk's default — so this connection draws " +
|
|
77
|
+
'through indirect GLX, which has no shaders whatever this machine could do',
|
|
78
|
+
"createRoot({ glPolicy: 'auto' }) takes the direct backend where it is\n" +
|
|
79
|
+
'available and this one everywhere else, and NTK_GL_POLICY=auto switches\n' +
|
|
80
|
+
'a single run. app.glCapabilities() still answers what the machine can do.',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const caps = app._glCapsResolved;
|
|
84
|
+
return caps && !caps.direct ? (caps.reason ?? null) : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Call `onChange` when that answer settles, and hand back the unsubscribe —
|
|
89
|
+
* the subscribe half of `useSupports('shaders')`.
|
|
90
|
+
*
|
|
91
|
+
* It settles at most once and then holds still. Under a policy that could
|
|
92
|
+
* pick direct, `createRoot()` waits for ntk's probe before it hands the app
|
|
93
|
+
* back, so the first render already reads the final answer and there is
|
|
94
|
+
* nothing to watch; under one that could not, the answer is false and stays
|
|
95
|
+
* false. What is left is a policy raised after connecting, which has missed
|
|
96
|
+
* the handshake probe — `glCapabilities()` is idempotent and cached, and is
|
|
97
|
+
* the same call ntk makes lazily when a context is created, so asking it here
|
|
98
|
+
* settles the answer rather than leaving one component reading false and the
|
|
99
|
+
* next reading true.
|
|
100
|
+
*/
|
|
101
|
+
export function watchDirectGL(app, onChange) {
|
|
102
|
+
if (!wantsDirect(app) || app._glCapsResolved) return () => {};
|
|
103
|
+
if (typeof app.glCapabilities !== 'function') return () => {};
|
|
104
|
+
let live = true;
|
|
105
|
+
// a probe that throws is a probe that found nothing: the answer stays
|
|
106
|
+
// false, and re-reading a boolean that did not move costs a comparison
|
|
107
|
+
const settled = () => {
|
|
108
|
+
if (live) onChange();
|
|
109
|
+
};
|
|
110
|
+
app.glCapabilities().then(settled, settled);
|
|
111
|
+
return () => {
|
|
112
|
+
live = false;
|
|
113
|
+
};
|
|
114
|
+
}
|
package/src/glnodes.js
CHANGED
|
@@ -5,40 +5,79 @@
|
|
|
5
5
|
// (NEXT_STEPS §4), sized and positioned by the parent's yoga layout like any
|
|
6
6
|
// other drawn node. Everything about the surface is here; the scene graph
|
|
7
7
|
// that draws into it comes later (docs/glx-plan.md).
|
|
8
|
-
import {
|
|
8
|
+
import { cssColorStraight } from 'ntk';
|
|
9
|
+
|
|
10
|
+
// re-exported so the GL element layer stays one import for consumers
|
|
11
|
+
export { directGLFailure, hasDirectGL } from './glbackend.js';
|
|
9
12
|
|
|
10
13
|
import { Node } from './nodes.js';
|
|
11
|
-
import { ScenePointer, sceneWantsPointer } from './pointer3d.js';
|
|
12
|
-
import { SceneRenderer } from './scene3d.js';
|
|
13
14
|
|
|
14
15
|
// One visual query per (app, spec): GetFBConfigs is a round trip and every
|
|
15
16
|
// <glarea> in an app wants the same answer.
|
|
16
17
|
const configCache = new WeakMap();
|
|
17
18
|
|
|
19
|
+
// Whether GL setup has already been found impossible on this connection.
|
|
20
|
+
// Every reason it fails — no GLX extension, indirect GLX disabled, no
|
|
21
|
+
// matching visual — is a property of the X server, not of one surface, so
|
|
22
|
+
// the first <glarea> to find out saves the rest from asking, so a second
|
|
23
|
+
// surface can report `onError` on its first frame instead of showing an
|
|
24
|
+
// empty box for the round trip it would otherwise take.
|
|
25
|
+
const glxFailures = new WeakMap();
|
|
26
|
+
|
|
27
|
+
/** The error that made GL unavailable on `app`, or null if all is well (or
|
|
28
|
+
* not yet known). Carries ntk's `code` — see `GLXError`. */
|
|
29
|
+
export function glxFailure(app) {
|
|
30
|
+
return (app && glxFailures.get(app)) || null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function recordGlxFailure(app, err) {
|
|
34
|
+
if (app && err && !glxFailures.has(app)) glxFailures.set(app, err);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The visual and depth to create the GL child window with.
|
|
39
|
+
*
|
|
40
|
+
* `chooseGLConfig` answers for whichever backend ntk's `glPolicy` selected
|
|
41
|
+
* and tags the result with `backend`; it is the newer name and the one to
|
|
42
|
+
* prefer, because asking the older `chooseGLXConfig` would pin the surface to
|
|
43
|
+
* indirect GLX no matter what the policy says. The fallback keeps `<glarea>`
|
|
44
|
+
* working on an ntk that predates the direct backend.
|
|
45
|
+
*/
|
|
18
46
|
export function glxConfig(app, spec) {
|
|
19
47
|
const key = JSON.stringify(spec ?? null);
|
|
20
48
|
let perApp = configCache.get(app);
|
|
21
49
|
if (!perApp) configCache.set(app, (perApp = new Map()));
|
|
22
50
|
let promise = perApp.get(key);
|
|
23
51
|
if (!promise) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
if (typeof app.chooseGLConfig === 'function') {
|
|
53
|
+
promise = app.chooseGLConfig(spec);
|
|
54
|
+
} else if (typeof app.chooseGLXConfig === 'function') {
|
|
55
|
+
promise = app
|
|
56
|
+
.chooseGLXConfig(spec)
|
|
57
|
+
.then((config) => ({ backend: 'indirect', ...config }));
|
|
58
|
+
} else {
|
|
59
|
+
promise = Promise.reject(
|
|
60
|
+
new Error(
|
|
61
|
+
'react-x11: <glarea> needs ntk >= 3.6.0 (app.chooseGLConfig)',
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
32
65
|
perApp.set(key, promise);
|
|
33
66
|
}
|
|
34
67
|
return promise;
|
|
35
68
|
}
|
|
36
69
|
|
|
37
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* clearColor as a CSS string or an [r, g, b, a] float tuple.
|
|
72
|
+
*
|
|
73
|
+
* Straight alpha, not premultiplied: this goes to `glClearColor`, and GL
|
|
74
|
+
* takes unassociated components. ntk's `cssColor` premultiplies for XRender,
|
|
75
|
+
* which would darken any translucent clear colour.
|
|
76
|
+
*/
|
|
38
77
|
function clearColorOf(props) {
|
|
39
78
|
const value = props.clearColor ?? 'black';
|
|
40
79
|
if (Array.isArray(value)) return value.length === 4 ? value : [...value, 1];
|
|
41
|
-
const parsed =
|
|
80
|
+
const parsed = cssColorStraight(value);
|
|
42
81
|
return parsed ?? [0, 0, 0, 1];
|
|
43
82
|
}
|
|
44
83
|
|
|
@@ -74,8 +113,6 @@ export class GlAreaNode extends Node {
|
|
|
74
113
|
this._realizing = false;
|
|
75
114
|
this._frameScheduled = false;
|
|
76
115
|
this._created = false;
|
|
77
|
-
this.scene = new SceneRenderer(this);
|
|
78
|
-
this.pointer = new ScenePointer(this);
|
|
79
116
|
this._pointerDirty = true;
|
|
80
117
|
}
|
|
81
118
|
|
|
@@ -95,24 +132,55 @@ export class GlAreaNode extends Node {
|
|
|
95
132
|
if (this.window || this.destroyed || this._realizing) return;
|
|
96
133
|
const parent = this.root?.window;
|
|
97
134
|
if (!parent || typeof this.app?.createWindow !== 'function') return;
|
|
135
|
+
// asked and answered — and whoever asked first already reported it
|
|
136
|
+
const known = glxFailure(this.app);
|
|
137
|
+
if (known) return this._failed(known, { reported: true });
|
|
98
138
|
this._realizing = true;
|
|
99
139
|
glxConfig(this.app, this.props.glx).then(
|
|
100
140
|
(config) => {
|
|
101
141
|
this._realizing = false;
|
|
102
142
|
if (this.destroyed || this.window) return;
|
|
103
|
-
|
|
143
|
+
// getContext() throws rather than rejects when the display has no
|
|
144
|
+
// GLX at all, and a throw in here would escape as an unhandled
|
|
145
|
+
// rejection instead of reaching onError
|
|
146
|
+
try {
|
|
147
|
+
this._create(config, parent);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
this._failed(err);
|
|
150
|
+
}
|
|
104
151
|
},
|
|
105
152
|
(err) => {
|
|
106
153
|
this._realizing = false;
|
|
107
|
-
this.
|
|
108
|
-
this.props.onError?.(err);
|
|
109
|
-
if (!this.props.onError) {
|
|
110
|
-
console.warn(`react-x11: <glarea> has no GL surface: ${err.message}`);
|
|
111
|
-
}
|
|
154
|
+
this._failed(err);
|
|
112
155
|
},
|
|
113
156
|
);
|
|
114
157
|
}
|
|
115
158
|
|
|
159
|
+
/**
|
|
160
|
+
* GL is not available: report it once, and give up the X child window if
|
|
161
|
+
* one was created. It would otherwise sit over this rect as an unpainted
|
|
162
|
+
* hole, hiding whatever the fallback draws in its place.
|
|
163
|
+
*
|
|
164
|
+
* `reported` means ntk has already written the diagnosis to the console —
|
|
165
|
+
* it does that for a failed context, and its message is the better one.
|
|
166
|
+
*/
|
|
167
|
+
_failed(err, { reported = false } = {}) {
|
|
168
|
+
this._realizing = false;
|
|
169
|
+
if (this.error) return;
|
|
170
|
+
this.error = err;
|
|
171
|
+
recordGlxFailure(this.app, err);
|
|
172
|
+
this.gl = null;
|
|
173
|
+
if (this.window) {
|
|
174
|
+
this.window.destroy?.();
|
|
175
|
+
this.window = null;
|
|
176
|
+
this.rect = null;
|
|
177
|
+
}
|
|
178
|
+
if (this.props.onError) this.props.onError(err);
|
|
179
|
+
else if (!reported) {
|
|
180
|
+
console.warn(`react-x11: <glarea> has no GL surface: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
116
184
|
_create(config, parent) {
|
|
117
185
|
const rect = this._geometry();
|
|
118
186
|
const wnd = this.app.createWindow({
|
|
@@ -132,6 +200,17 @@ export class GlAreaNode extends Node {
|
|
|
132
200
|
this.config = config;
|
|
133
201
|
wnd._reactX11Node = this;
|
|
134
202
|
this.gl = wnd.getContext('opengl', config);
|
|
203
|
+
// a buffer freed by the display is a frame that can be drawn again
|
|
204
|
+
if (typeof this.gl?.onFrameAvailable !== 'undefined') {
|
|
205
|
+
this.gl.onFrameAvailable = () => this.requestFrame();
|
|
206
|
+
}
|
|
207
|
+
// The context is only usable once MakeCurrent has answered, and that is
|
|
208
|
+
// where a server refusing indirect GLX says so (ntk gives the rejection
|
|
209
|
+
// an err.code — see GLXError). Nothing here awaits it: GL calls queue
|
|
210
|
+
// until the tag arrives, so this only has to catch the failure.
|
|
211
|
+
this.gl?.ready?.catch((err) => {
|
|
212
|
+
if (!this.destroyed) this._failed(err, { reported: true });
|
|
213
|
+
});
|
|
135
214
|
wnd.on?.('expose', () => this.requestFrame());
|
|
136
215
|
wnd.map?.();
|
|
137
216
|
this.requestFrame();
|
|
@@ -191,18 +270,37 @@ export class GlAreaNode extends Node {
|
|
|
191
270
|
_drawFrame() {
|
|
192
271
|
const gl = this.gl;
|
|
193
272
|
if (!gl || this.destroyed) return;
|
|
273
|
+
const direct = gl.backend === 'direct';
|
|
274
|
+
// On the direct backend every buffer may still be held by the display,
|
|
275
|
+
// and drawing into one before it comes back would paint what is on
|
|
276
|
+
// screen. `onFrameAvailable` asks for this frame again when one frees.
|
|
277
|
+
if (direct && gl.canRender && !gl.canRender()) return;
|
|
278
|
+
// binds this surface — the GPU context is shared between every <glarea>
|
|
279
|
+
// on the connection — and picks up a resize
|
|
280
|
+
gl.makeCurrent?.();
|
|
281
|
+
|
|
194
282
|
const { width, height } = this.rect;
|
|
195
|
-
|
|
283
|
+
// x/y are where the node's origin sits in the drawable being drawn
|
|
284
|
+
// into (DrawInfo's contract) — a <glarea> draws into its own X window,
|
|
285
|
+
// so that is the origin.
|
|
286
|
+
const info = { width, height, x: 0, y: 0, node: this };
|
|
196
287
|
if (!this._created) {
|
|
197
288
|
this._created = true;
|
|
198
289
|
this.props.onCreated?.(gl, info);
|
|
199
290
|
}
|
|
200
|
-
this._syncPointerListeners();
|
|
201
|
-
gl.Viewport(0, 0, width, height);
|
|
202
291
|
const [r, g, b, a] = clearColorOf(this.props);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.
|
|
292
|
+
// The two backends spell GL differently — PascalCase OpenGL 1.x against
|
|
293
|
+
// camelCase ES 2 — and neither pretends to be the other, so the handful
|
|
294
|
+
// of calls this element makes itself are written both ways.
|
|
295
|
+
if (direct) {
|
|
296
|
+
gl.viewport(0, 0, width, height);
|
|
297
|
+
gl.clearColor(r, g, b, a);
|
|
298
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
299
|
+
} else {
|
|
300
|
+
gl.Viewport(0, 0, width, height);
|
|
301
|
+
gl.ClearColor(r, g, b, a);
|
|
302
|
+
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
303
|
+
}
|
|
206
304
|
this.props.onDraw?.(gl, info);
|
|
207
305
|
gl.SwapBuffers();
|
|
208
306
|
if (this.props.frameLoop === 'always') this.requestFrame();
|
|
@@ -220,45 +318,6 @@ export class GlAreaNode extends Node {
|
|
|
220
318
|
else this.window?.map?.();
|
|
221
319
|
}
|
|
222
320
|
|
|
223
|
-
/**
|
|
224
|
-
* X pointer events are only worth selecting when something in the scene
|
|
225
|
-
* listens for them — the r3f rule that only handler-bearing objects take
|
|
226
|
-
* part in picking, applied one level up, to the wire.
|
|
227
|
-
*/
|
|
228
|
-
_syncPointerListeners() {
|
|
229
|
-
if (!this._pointerDirty || !this.window || this.pointer.attached) return;
|
|
230
|
-
this._pointerDirty = false;
|
|
231
|
-
if (!sceneWantsPointer(this.children)) return;
|
|
232
|
-
this.pointer.attach(this.window);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/** A scene node was added, removed, or gained/lost pointer handlers. */
|
|
236
|
-
_sceneChanged() {
|
|
237
|
-
this._pointerDirty = true;
|
|
238
|
-
this.requestFrame();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/** scene children (<mesh>, <group>, …) attach to this surface. */
|
|
242
|
-
insertBefore(child, beforeChild) {
|
|
243
|
-
super.insertBefore(child, beforeChild);
|
|
244
|
-
child._setSurface?.(this);
|
|
245
|
-
this._sceneChanged();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
removeChild(child) {
|
|
249
|
-
super.removeChild(child);
|
|
250
|
-
this.invalidateGeometry(child);
|
|
251
|
-
this.pointer.forget(child);
|
|
252
|
-
this._sceneChanged();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** A removed geometry's display list is no longer needed server-side. */
|
|
256
|
-
invalidateGeometry(node) {
|
|
257
|
-
if (!node) return;
|
|
258
|
-
if (node.isGeometry) this.scene.forget(this.gl, node);
|
|
259
|
-
for (const child of node.children ?? []) this.invalidateGeometry(child);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
321
|
// the child window covers this rect: nothing to paint into the parent's
|
|
263
322
|
// 2d context, and no drawn children are allowed under it
|
|
264
323
|
paint() {}
|
|
@@ -266,7 +325,6 @@ export class GlAreaNode extends Node {
|
|
|
266
325
|
destroySubtree() {
|
|
267
326
|
if (this.destroyed) return;
|
|
268
327
|
super.destroySubtree();
|
|
269
|
-
this.scene.dispose(this.gl);
|
|
270
328
|
this.gl?.destroy?.();
|
|
271
329
|
this.gl = null;
|
|
272
330
|
this.window?.destroy?.();
|