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/Reconciler.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getCurrentUpdatePriority,
|
|
15
15
|
setCurrentUpdatePriority,
|
|
16
16
|
resolveUpdatePriority,
|
|
17
|
+
setSyncFlush,
|
|
17
18
|
} from './priority.js';
|
|
18
19
|
import {
|
|
19
20
|
WindowNode,
|
|
@@ -23,68 +24,104 @@ import {
|
|
|
23
24
|
TextChunkNode,
|
|
24
25
|
ImageNode,
|
|
25
26
|
CanvasNode,
|
|
26
|
-
ScrollViewNode,
|
|
27
27
|
TextInputNode,
|
|
28
28
|
TextAreaNode,
|
|
29
|
+
appearanceChanged,
|
|
30
|
+
beginWindowMaps,
|
|
31
|
+
flushWindowMaps,
|
|
29
32
|
flushWindowRestacks,
|
|
33
|
+
windowAttributes,
|
|
30
34
|
} from './nodes.js';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
35
|
+
import { hasDropProps } from './dnd.js';
|
|
36
|
+
import { AppProvider } from './appcontext.js';
|
|
37
|
+
import { defaultRootHandlers, setErrorHandler } from './errors.js';
|
|
33
38
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} from './
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
registerApp,
|
|
40
|
+
unregisterApp,
|
|
41
|
+
hooks as traceHooks,
|
|
42
|
+
} from './trace-registry.js';
|
|
43
|
+
import { hooks as a11yHooks, startA11y } from './a11y.js';
|
|
44
|
+
import { beginStartup } from './startup.js';
|
|
45
|
+
import { beginCompose } from './compose.js';
|
|
46
|
+
import { beginFocus } from './events.js';
|
|
47
|
+
import { beginKeyboard } from './keyboard.js';
|
|
48
|
+
import { beginCompositing, endCompositing } from './compositing.js';
|
|
49
|
+
import { beginScreens, endScreens } from './screens.js';
|
|
50
|
+
import { beginScale, scaleOf } from './scale.js';
|
|
51
|
+
import { beginDesktopSettings, endDesktopSettings } from './desktopsettings.js';
|
|
52
|
+
import { endIdle } from './idle.js';
|
|
53
|
+
import { endKeyboardState } from './keyboardstate.js';
|
|
54
|
+
import { endXSettings } from './xsettings.js';
|
|
55
|
+
import { watchAppearance } from './appearance.js';
|
|
56
|
+
import { ForeignNode } from './foreignnodes.js';
|
|
57
|
+
import { GlAreaNode } from './glnodes.js';
|
|
58
|
+
import { createRegisteredNode, registeredElements } from './registry.js';
|
|
59
|
+
import { SvgNode, SvgChildNode } from './svgnodes.js';
|
|
60
|
+
import { loadLayout } from './yoga.js';
|
|
61
|
+
|
|
62
|
+
// The renderer name and version DevTools shows. Read from package.json so
|
|
63
|
+
// they cannot drift — but **guarded**, because a single-file bundle has no
|
|
64
|
+
// package.json beside it to read: `import.meta.url` is then the bundle's,
|
|
65
|
+
// `'../package.json'` resolves to something else or to nothing, and an
|
|
66
|
+
// unguarded require takes the whole app down at import time with
|
|
67
|
+
// `Cannot find module '../package.json'`. Losing the version string in a
|
|
68
|
+
// bundle costs a line in the DevTools panel; throwing costs the app. See
|
|
69
|
+
// docs/packaging.md.
|
|
70
|
+
const PACKAGE_NAME = 'react-x11';
|
|
71
|
+
let PACKAGE_VERSION = '0.0.0-bundled';
|
|
72
|
+
try {
|
|
73
|
+
PACKAGE_VERSION = createRequire(import.meta.url)('../package.json').version;
|
|
74
|
+
} catch {
|
|
75
|
+
// bundled, or installed somewhere without the manifest beside us
|
|
76
|
+
}
|
|
43
77
|
|
|
44
|
-
|
|
78
|
+
// The vocabulary, in the order the unknown-element error lists it. Exported
|
|
79
|
+
// so a test can sweep every element rather than a list of them that was
|
|
80
|
+
// true once (test/element-props.test.js).
|
|
81
|
+
export const HOST_TYPES = [
|
|
45
82
|
'window',
|
|
46
83
|
'popup',
|
|
47
84
|
'box',
|
|
48
85
|
'text',
|
|
49
86
|
'image',
|
|
50
87
|
'canvas',
|
|
51
|
-
'scrollview',
|
|
52
88
|
'textinput',
|
|
53
89
|
'textarea',
|
|
54
|
-
'markdown',
|
|
55
|
-
'html',
|
|
56
90
|
'svg',
|
|
57
|
-
'tex',
|
|
58
91
|
'glarea',
|
|
92
|
+
'foreign',
|
|
59
93
|
];
|
|
60
94
|
|
|
61
|
-
const isEventProp = (name) => /^on[A-Z]/.test(name);
|
|
62
|
-
|
|
63
|
-
// Props forwarded to ntk createWindow. Event handlers are dispatched by the
|
|
64
|
-
// EventManager from current props (never registered at creation, so they
|
|
65
|
-
// cannot go stale) and children are handled by the tree.
|
|
66
|
-
function windowAttributes(props) {
|
|
67
|
-
const attributes = {};
|
|
68
|
-
for (const key of Object.keys(props)) {
|
|
69
|
-
if (key === 'children' || isEventProp(key)) continue;
|
|
70
|
-
attributes[key] = props[key];
|
|
71
|
-
}
|
|
72
|
-
return attributes;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
95
|
const HostConfig = {
|
|
76
96
|
supportsMutation: true,
|
|
77
97
|
supportsPersistence: false,
|
|
78
98
|
supportsHydration: false,
|
|
79
99
|
supportsResources: false,
|
|
80
100
|
supportsSingletons: false,
|
|
101
|
+
// Queries live in `react-x11/test` and walk the retained node tree
|
|
102
|
+
// directly, which keeps the failure messages ours and keeps them off
|
|
103
|
+
// react-reconciler's internal host-config contract. React's own selectors
|
|
104
|
+
// would need eight more functions to answer the same questions.
|
|
81
105
|
supportsTestSelectors: false,
|
|
82
106
|
supportsMicrotasks: true,
|
|
83
107
|
isPrimaryRenderer: true,
|
|
84
|
-
warnsIfNotActing
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
// (`warnsIfNotActing` used to sit here. react-reconciler 0.33 reads it as
|
|
109
|
+
// a bare expression statement and discards it — the act warning is gated
|
|
110
|
+
// on `globalThis.IS_REACT_ACT_ENVIRONMENT`, which `react-x11/test`'s `act`
|
|
111
|
+
// sets. A flag that does nothing is worse than no flag.)
|
|
112
|
+
|
|
113
|
+
// What DevTools shows for this renderer. These are the whole story in
|
|
114
|
+
// react-reconciler 0.33: `injectIntoDevTools()` takes no arguments and
|
|
115
|
+
// reads them from here.
|
|
116
|
+
rendererVersion: PACKAGE_VERSION,
|
|
117
|
+
rendererPackageName: PACKAGE_NAME,
|
|
118
|
+
// Surfaces as `internals.rendererConfig`, which is where a renderer puts
|
|
119
|
+
// things only its own DevTools integration understands — React Native's
|
|
120
|
+
// `getInspectorDataForViewTag` is the archetype. Nothing in the standalone
|
|
121
|
+
// DevTools reads it for a renderer like this one, so there is nothing
|
|
122
|
+
// honest to put here. In particular `findFiberByHostInstance` does not
|
|
123
|
+
// belong here: 0.33 dropped it, and DevTools 7 only ever tested it for
|
|
124
|
+
// *presence*, to tell a fiber renderer from the pre-fiber kind.
|
|
88
125
|
extraDevToolsConfig: null,
|
|
89
126
|
|
|
90
127
|
scheduleTimeout: setTimeout,
|
|
@@ -96,7 +133,6 @@ const HostConfig = {
|
|
|
96
133
|
return {
|
|
97
134
|
isInsideText: false,
|
|
98
135
|
isInsideSvg: false,
|
|
99
|
-
isInsideRichText: false,
|
|
100
136
|
isInside3d: false,
|
|
101
137
|
};
|
|
102
138
|
},
|
|
@@ -106,10 +142,6 @@ const HostConfig = {
|
|
|
106
142
|
isInsideText: parentHostContext.isInsideText || type === 'text',
|
|
107
143
|
// <svg> children are declarative SVG elements, not react-x11 nodes
|
|
108
144
|
isInsideSvg: parentHostContext.isInsideSvg || type === 'svg',
|
|
109
|
-
// <markdown>/<html>/<tex> take their content as a string child
|
|
110
|
-
// (react-markdown style); no elements are allowed inside
|
|
111
|
-
isInsideRichText:
|
|
112
|
-
type === 'markdown' || type === 'html' || type === 'tex',
|
|
113
145
|
// inside <glarea> the children are scene nodes, not drawn nodes
|
|
114
146
|
isInside3d: parentHostContext.isInside3d || type === 'glarea',
|
|
115
147
|
};
|
|
@@ -122,13 +154,24 @@ const HostConfig = {
|
|
|
122
154
|
},
|
|
123
155
|
|
|
124
156
|
prepareForCommit() {
|
|
157
|
+
// …so that a <window> realized during the mutation phase waits to be
|
|
158
|
+
// mapped until React has finished hiding whatever it hides (nodes.js,
|
|
159
|
+
// beginWindowMaps)
|
|
160
|
+
beginWindowMaps();
|
|
161
|
+
traceHooks.commitStart?.();
|
|
125
162
|
return null;
|
|
126
163
|
},
|
|
127
164
|
|
|
128
165
|
// child <window>s that moved in the tree restack here, so a reorder costs
|
|
129
|
-
// one pass instead of one per insertBefore
|
|
166
|
+
// one pass instead of one per insertBefore.
|
|
130
167
|
resetAfterCommit() {
|
|
131
168
|
flushWindowRestacks();
|
|
169
|
+
// after the restacking, so a subtree of windows arrives on screen in its
|
|
170
|
+
// final order rather than shuffling once it is up
|
|
171
|
+
flushWindowMaps();
|
|
172
|
+
traceHooks.commitEnd?.();
|
|
173
|
+
// the AT-SPI bridge flushes its queued tree/state diffs per commit
|
|
174
|
+
a11yHooks.commit?.();
|
|
132
175
|
},
|
|
133
176
|
|
|
134
177
|
createInstance(type, props, rootContainer, hostContext, internalHandle) {
|
|
@@ -140,12 +183,6 @@ const HostConfig = {
|
|
|
140
183
|
node._reactFiber = internalHandle;
|
|
141
184
|
return node;
|
|
142
185
|
}
|
|
143
|
-
if (hostContext.isInsideRichText) {
|
|
144
|
-
throw new Error(
|
|
145
|
-
`react-x11: <${type}> is not allowed inside <markdown>/<html>/<tex>; ` +
|
|
146
|
-
'their content is a string child (or the source prop).',
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
186
|
if (hostContext.isInsideText && type !== 'text') {
|
|
150
187
|
throw new Error(
|
|
151
188
|
`react-x11: <${type}> is not allowed inside <text>; only nested ` +
|
|
@@ -153,21 +190,14 @@ const HostConfig = {
|
|
|
153
190
|
);
|
|
154
191
|
}
|
|
155
192
|
if (hostContext.isInside3d) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
if (UNSUPPORTED_KINDS[type]) {
|
|
162
|
-
throw new Error(
|
|
163
|
-
`react-x11: <${type}> cannot work over indirect GLX — ` +
|
|
164
|
-
`${UNSUPPORTED_KINDS[type]}. See docs/glx-plan.md.`,
|
|
165
|
-
);
|
|
166
|
-
}
|
|
193
|
+
// `<glarea>` is a leaf here: it owns the surface, the frame clock and
|
|
194
|
+
// the swap, and `onDraw` is the escape hatch. A *scene graph* over it
|
|
195
|
+
// — meshes, materials, lights, post-processing, on either backend —
|
|
196
|
+
// is `@react-x11/components/three`, which brings its own reconciler.
|
|
167
197
|
throw new Error(
|
|
168
|
-
`react-x11: <${type}> is not
|
|
169
|
-
|
|
170
|
-
'
|
|
198
|
+
`react-x11: <${type}> is not an element — <glarea> takes no ` +
|
|
199
|
+
'children. Draw through `onDraw`, or use ' +
|
|
200
|
+
'`@react-x11/components/three` for a scene graph. See docs/gl.md.',
|
|
171
201
|
);
|
|
172
202
|
}
|
|
173
203
|
let node;
|
|
@@ -175,17 +205,22 @@ const HostConfig = {
|
|
|
175
205
|
case 'window':
|
|
176
206
|
// No X11 calls here: the render phase may be discarded. The real
|
|
177
207
|
// window is created top-down in the commit phase (realize).
|
|
178
|
-
node = new WindowNode(
|
|
208
|
+
node = new WindowNode(
|
|
209
|
+
rootContainer,
|
|
210
|
+
windowAttributes(props, scaleOf(rootContainer)),
|
|
211
|
+
props,
|
|
212
|
+
);
|
|
179
213
|
break;
|
|
180
214
|
case 'popup':
|
|
181
|
-
node = new PopupNode(
|
|
215
|
+
node = new PopupNode(
|
|
216
|
+
rootContainer,
|
|
217
|
+
windowAttributes(props, scaleOf(rootContainer)),
|
|
218
|
+
props,
|
|
219
|
+
);
|
|
182
220
|
break;
|
|
183
221
|
case 'box':
|
|
184
222
|
node = new BoxNode(props, rootContainer);
|
|
185
223
|
break;
|
|
186
|
-
case 'scrollview':
|
|
187
|
-
node = new ScrollViewNode(props, rootContainer);
|
|
188
|
-
break;
|
|
189
224
|
case 'textinput':
|
|
190
225
|
node = new TextInputNode(props, rootContainer);
|
|
191
226
|
break;
|
|
@@ -203,48 +238,55 @@ const HostConfig = {
|
|
|
203
238
|
case 'canvas':
|
|
204
239
|
node = new CanvasNode(props, rootContainer);
|
|
205
240
|
break;
|
|
206
|
-
case 'markdown':
|
|
207
|
-
node = new MarkdownNode(props, rootContainer);
|
|
208
|
-
break;
|
|
209
|
-
case 'html':
|
|
210
|
-
node = new HtmlNode(props, rootContainer);
|
|
211
|
-
break;
|
|
212
241
|
case 'svg':
|
|
213
242
|
node = new SvgNode(props, rootContainer);
|
|
214
243
|
break;
|
|
215
|
-
case 'tex':
|
|
216
|
-
node = new TexNode(props, rootContainer);
|
|
217
|
-
break;
|
|
218
244
|
case 'glarea':
|
|
219
245
|
node = new GlAreaNode(props, rootContainer);
|
|
220
246
|
break;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
247
|
+
case 'foreign':
|
|
248
|
+
// No X11 calls here either, and for a sharper reason than
|
|
249
|
+
// `<window>`'s: the window this element embeds belongs to another
|
|
250
|
+
// process, and a reparent issued from a render React then discards
|
|
251
|
+
// has moved it for real. WindowNode.realize does it in the commit
|
|
252
|
+
// phase (`_realizeChildWindows`).
|
|
253
|
+
node = new ForeignNode(props, rootContainer);
|
|
254
|
+
break;
|
|
255
|
+
default: {
|
|
256
|
+
// Third-party elements (issue #125). Built-ins stay a switch —
|
|
257
|
+
// they are code, not data — and the registry is consulted only
|
|
258
|
+
// once the switch has run out, so it costs nothing on the way in.
|
|
259
|
+
const registered = createRegisteredNode(
|
|
260
|
+
type,
|
|
261
|
+
props,
|
|
262
|
+
rootContainer,
|
|
263
|
+
hostContext,
|
|
264
|
+
);
|
|
265
|
+
if (registered) {
|
|
266
|
+
registered._reactFiber = internalHandle;
|
|
267
|
+
return registered;
|
|
227
268
|
}
|
|
269
|
+
const custom = registeredElements();
|
|
228
270
|
throw new Error(
|
|
229
271
|
`react-x11: unknown element type <${type}>. Supported: ` +
|
|
230
272
|
HOST_TYPES.map((t) => `<${t}>`).join(', ') +
|
|
231
|
-
|
|
273
|
+
(custom.length > 0
|
|
274
|
+
? `; registered: ${custom.map((t) => `<${t}>`).join(', ')}`
|
|
275
|
+
: '') +
|
|
276
|
+
'. Third-party elements are added with registerElement() from ' +
|
|
277
|
+
'react-x11/host (docs/extending.md).',
|
|
232
278
|
);
|
|
279
|
+
}
|
|
233
280
|
}
|
|
234
281
|
node._reactFiber = internalHandle;
|
|
235
282
|
return node;
|
|
236
283
|
},
|
|
237
284
|
|
|
238
285
|
createTextInstance(text, rootContainer, hostContext) {
|
|
239
|
-
if (
|
|
240
|
-
!hostContext.isInsideText &&
|
|
241
|
-
!hostContext.isInsideRichText &&
|
|
242
|
-
!hostContext.isInsideSvg
|
|
243
|
-
) {
|
|
286
|
+
if (!hostContext.isInsideText && !hostContext.isInsideSvg) {
|
|
244
287
|
throw new Error(
|
|
245
288
|
`react-x11: raw text ${JSON.stringify(text)} must be wrapped in a ` +
|
|
246
|
-
'<text> element (or be the string child of <
|
|
247
|
-
'<tex>/an SVG <text>).',
|
|
289
|
+
'<text> element (or be the string child of an SVG <text>).',
|
|
248
290
|
);
|
|
249
291
|
}
|
|
250
292
|
return new TextChunkNode(text, rootContainer);
|
|
@@ -258,9 +300,13 @@ const HostConfig = {
|
|
|
258
300
|
// Popups are not attached to the container or realized by a parent
|
|
259
301
|
// window; commitMount realizes them against the screen root. autoFocus
|
|
260
302
|
// and trapFocus need commitMount too — the node has to be in the tree
|
|
261
|
-
// first, so it can find the EventManager that owns focus.
|
|
303
|
+
// first, so it can find the EventManager that owns focus. Drop targets
|
|
304
|
+
// likewise: registration needs the root, which insertion assigns.
|
|
262
305
|
return (
|
|
263
|
-
type === 'popup' ||
|
|
306
|
+
type === 'popup' ||
|
|
307
|
+
Boolean(props.autoFocus) ||
|
|
308
|
+
Boolean(props.trapFocus) ||
|
|
309
|
+
hasDropProps(props)
|
|
264
310
|
);
|
|
265
311
|
},
|
|
266
312
|
|
|
@@ -271,6 +317,9 @@ const HostConfig = {
|
|
|
271
317
|
if (props.trapFocus) {
|
|
272
318
|
instance._syncFocusScope?.();
|
|
273
319
|
}
|
|
320
|
+
if (hasDropProps(props)) {
|
|
321
|
+
instance.root?._registerDropTarget?.(instance);
|
|
322
|
+
}
|
|
274
323
|
if (props.autoFocus && typeof instance.focus === 'function') {
|
|
275
324
|
instance.focus();
|
|
276
325
|
}
|
|
@@ -286,6 +335,13 @@ const HostConfig = {
|
|
|
286
335
|
// screen root.
|
|
287
336
|
child.realize(null);
|
|
288
337
|
}
|
|
338
|
+
// React's getPublicRootInstance answers from the root fiber's first
|
|
339
|
+
// child, and only when that child is a host component. `render()` wraps
|
|
340
|
+
// the tree in a context provider, which is not one, so it would answer
|
|
341
|
+
// null — the container keeps the list instead. Same answer as before:
|
|
342
|
+
// the first top-level node the tree put here.
|
|
343
|
+
(container._rootChildren ??= []).push(child);
|
|
344
|
+
a11yHooks.rootMounted?.(child);
|
|
289
345
|
},
|
|
290
346
|
|
|
291
347
|
insertBefore(parentInstance, child, beforeChild) {
|
|
@@ -301,6 +357,11 @@ const HostConfig = {
|
|
|
301
357
|
},
|
|
302
358
|
|
|
303
359
|
removeChildFromContainer(container, child) {
|
|
360
|
+
const roots = container._rootChildren;
|
|
361
|
+
const at = roots ? roots.indexOf(child) : -1;
|
|
362
|
+
if (at !== -1) roots.splice(at, 1);
|
|
363
|
+
// before the destroy, while the subtree is still walkable
|
|
364
|
+
a11yHooks.rootUnmounted?.(child);
|
|
304
365
|
child.destroySubtree();
|
|
305
366
|
},
|
|
306
367
|
|
|
@@ -338,6 +399,7 @@ const HostConfig = {
|
|
|
338
399
|
|
|
339
400
|
detachDeletedInstance(instance) {
|
|
340
401
|
instance.root?.events?.forget(instance);
|
|
402
|
+
instance.root?._forgetDropTarget?.(instance);
|
|
341
403
|
},
|
|
342
404
|
|
|
343
405
|
preparePortalMount() {},
|
|
@@ -401,107 +463,343 @@ const HostConfig = {
|
|
|
401
463
|
|
|
402
464
|
export const Renderer = ReactReconciler(HostConfig);
|
|
403
465
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
// register the renderer with the standalone DevTools app.
|
|
408
|
-
const devtools = await import('./DevToolsIntegration.js');
|
|
409
|
-
await devtools.prepare();
|
|
410
|
-
devtools.connect(Renderer);
|
|
411
|
-
}
|
|
466
|
+
// So the event dispatcher can land a discrete-priority commit before it
|
|
467
|
+
// paints the response to that same input — see flushSyncWork in priority.js.
|
|
468
|
+
setSyncFlush(() => Renderer.flushSyncWork());
|
|
412
469
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
// also set REACT_X11_CLICK_TO_COMPONENT=1 just to pick one.
|
|
416
|
-
const clickToComponent = await import('./ClickToComponent.js');
|
|
417
|
-
clickToComponent.install();
|
|
418
|
-
}
|
|
470
|
+
let integrations = null;
|
|
471
|
+
let integrationsSettled = false;
|
|
419
472
|
|
|
420
|
-
|
|
421
|
-
|
|
473
|
+
/**
|
|
474
|
+
* The opt-in integrations, each behind its environment variable and each
|
|
475
|
+
* dynamically imported so a bundle only carries what it is asked for.
|
|
476
|
+
*
|
|
477
|
+
* This runs from `render()`/`createRoot()` rather than at module scope: a
|
|
478
|
+
* top-level await here would be inherited by every bundle containing
|
|
479
|
+
* react-x11, and esbuild cannot emit CommonJS for a graph that has one —
|
|
480
|
+
* which is what a Node single executable needs (see docs/packaging.md).
|
|
481
|
+
* The ordering guarantee is unchanged, because a React commit can only
|
|
482
|
+
* follow a root, and there is no way to obtain one without going through
|
|
483
|
+
* here first.
|
|
484
|
+
*
|
|
485
|
+
* Returns `null` when there is nothing to install — which is the normal
|
|
486
|
+
* case, and keeps `render(element, callback, container)` synchronous, as
|
|
487
|
+
* callers that read the tree straight after it expect.
|
|
488
|
+
*/
|
|
489
|
+
function loadIntegrations() {
|
|
490
|
+
if (integrationsSettled) return null;
|
|
491
|
+
if (
|
|
492
|
+
!process.env.REACT_X11_DEVTOOLS &&
|
|
493
|
+
!process.env.REACT_X11_CLICK_TO_COMPONENT &&
|
|
494
|
+
!process.env.REACT_X11_EDITOR &&
|
|
495
|
+
!process.env.REACT_X11_TRACE
|
|
496
|
+
) {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
if (integrations) return integrations;
|
|
500
|
+
integrations = (async () => {
|
|
501
|
+
if (process.env.REACT_X11_DEVTOOLS) {
|
|
502
|
+
// Install the DevTools hook before any React commit, and register the
|
|
503
|
+
// renderer with the standalone DevTools app.
|
|
504
|
+
const devtools = await import('./DevToolsIntegration.js');
|
|
505
|
+
await devtools.prepare();
|
|
506
|
+
devtools.connect(Renderer);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (
|
|
510
|
+
process.env.REACT_X11_CLICK_TO_COMPONENT ||
|
|
511
|
+
process.env.REACT_X11_EDITOR
|
|
512
|
+
) {
|
|
513
|
+
// Naming an editor already means you want the feature on — no need to
|
|
514
|
+
// also set REACT_X11_CLICK_TO_COMPONENT=1 just to pick one.
|
|
515
|
+
const clickToComponent = await import('./ClickToComponent.js');
|
|
516
|
+
clickToComponent.install();
|
|
517
|
+
}
|
|
422
518
|
|
|
423
|
-
|
|
424
|
-
|
|
519
|
+
if (process.env.REACT_X11_TRACE) {
|
|
520
|
+
// Protocol tracing (docs/debugging.md). debug.js writes files, which
|
|
521
|
+
// the playground bundle must not drag in. The trace itself attaches
|
|
522
|
+
// per connection, as each root registers the app it connected (or
|
|
523
|
+
// borrowed).
|
|
524
|
+
const debug = await import('./debug.js');
|
|
525
|
+
debug.startEnvTrace(process.env.REACT_X11_TRACE);
|
|
526
|
+
}
|
|
527
|
+
integrationsSettled = true;
|
|
528
|
+
})();
|
|
529
|
+
return integrations;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Open a connection this process owns. The wrapper is here for one reason:
|
|
534
|
+
* to say what is wrong when there is no server, which is the first thing
|
|
535
|
+
* anyone hits and the least self-explanatory failure in the library.
|
|
536
|
+
*/
|
|
537
|
+
async function connect(options) {
|
|
425
538
|
try {
|
|
426
|
-
|
|
539
|
+
return await createClient(options);
|
|
427
540
|
} catch (err) {
|
|
541
|
+
const display = options.display ?? process.env.DISPLAY ?? '<unset>';
|
|
428
542
|
throw new Error(
|
|
429
543
|
'react-x11: could not connect to the X server. Is an X server running ' +
|
|
430
|
-
`and DISPLAY set (DISPLAY=${
|
|
431
|
-
'Original error: ' +
|
|
544
|
+
`and DISPLAY set (DISPLAY=${display})? Original error: ` +
|
|
432
545
|
err.message,
|
|
546
|
+
{ cause: err },
|
|
433
547
|
);
|
|
434
548
|
}
|
|
435
|
-
return cachedNtkApp;
|
|
436
549
|
}
|
|
437
550
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
551
|
+
/** An ntk App, told apart from an options bag by what only an App has. */
|
|
552
|
+
const isNtkApp = (v) =>
|
|
553
|
+
Boolean(v) && typeof v.createWindow === 'function' && typeof v.X === 'object';
|
|
554
|
+
|
|
555
|
+
// What a root that opens its own connection forwards to ntk. `stream` is
|
|
556
|
+
// how you reach a server that is not on the other end of $DISPLAY — an
|
|
557
|
+
// in-process one, a tunnel — and is what the tests connect through.
|
|
558
|
+
const CONNECT_OPTIONS = [
|
|
559
|
+
'display',
|
|
560
|
+
'stream',
|
|
561
|
+
'fontSource',
|
|
562
|
+
'glxVisual',
|
|
563
|
+
// which OpenGL backend <glarea> draws through: 'indirect' (ntk's
|
|
564
|
+
// default), 'auto', 'direct' or 'off'. It has to be passed at connect time
|
|
565
|
+
// rather than set later, because ntk probes for the direct backend during
|
|
566
|
+
// the handshake — see docs/gl.md.
|
|
567
|
+
'glPolicy',
|
|
568
|
+
'onXError',
|
|
569
|
+
];
|
|
455
570
|
|
|
456
|
-
|
|
457
|
-
const publicInstance = Renderer.getPublicRootInstance(root);
|
|
458
|
-
if (callback) {
|
|
459
|
-
callback(publicInstance, container);
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
Renderer.flushSyncWork();
|
|
463
|
-
}
|
|
571
|
+
const DEFAULT_ERROR_HANDLERS = defaultRootHandlers;
|
|
464
572
|
|
|
465
573
|
/**
|
|
466
|
-
*
|
|
467
|
-
*
|
|
574
|
+
* The connection ended without us asking. `end` is the stream closing —
|
|
575
|
+
* server exit, ssh drop, kill. An `error` may be either a transport failure
|
|
576
|
+
* or an X protocol error ntk already reports through `onXError`; only the
|
|
577
|
+
* former ends the connection, and only it carries no `majorOpcode`.
|
|
468
578
|
*/
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
579
|
+
function watchConnection(app, onDisconnect, deliberate) {
|
|
580
|
+
let done = false;
|
|
581
|
+
const fire = (reason, err) => {
|
|
582
|
+
if (done || deliberate()) return;
|
|
583
|
+
done = true;
|
|
584
|
+
onDisconnect(reason, err);
|
|
585
|
+
};
|
|
586
|
+
app.X.on('end', () => fire('closed'));
|
|
587
|
+
app.X.on('error', (err) => {
|
|
588
|
+
if (err?.majorOpcode === undefined) fire('error', err);
|
|
589
|
+
});
|
|
476
590
|
}
|
|
477
591
|
|
|
478
592
|
/**
|
|
479
|
-
*
|
|
593
|
+
* The entry point:
|
|
480
594
|
*
|
|
481
|
-
* const root = await createRoot();
|
|
595
|
+
* const root = await createRoot(); // connects via $DISPLAY
|
|
596
|
+
* const root = await createRoot({ display: ':1' });
|
|
597
|
+
* const root = await createRoot({ app }); // a connection you have
|
|
482
598
|
* root.render(<App />);
|
|
599
|
+
* await root.unmount();
|
|
600
|
+
*
|
|
601
|
+
* Every root without `app` opens **its own** connection and owns it, so two
|
|
602
|
+
* roots are two independent trees; `unmount()` closes what it opened. A root
|
|
603
|
+
* given an `app` borrows it and never closes it — that connection belongs to
|
|
604
|
+
* whoever made it.
|
|
483
605
|
*
|
|
484
|
-
*
|
|
606
|
+
* `display`, `fontSource`, `glxVisual` and `onXError` go straight to ntk.
|
|
607
|
+
* Anything else ntk understands, build the client yourself and pass `app`.
|
|
485
608
|
*/
|
|
486
|
-
export async function createRoot(
|
|
487
|
-
|
|
488
|
-
|
|
609
|
+
export async function createRoot(options = {}) {
|
|
610
|
+
// Before anything builds a node: every drawn node creates a yoga node in
|
|
611
|
+
// its constructor, and the engine's WebAssembly is loaded rather than
|
|
612
|
+
// imported (src/yoga.js — a top-level await here would cost every app the
|
|
613
|
+
// single-executable build). ntk's createClient used to do this while the
|
|
614
|
+
// engine was still ntk's, which also covered `createRoot({ app })`; it is
|
|
615
|
+
// ours now, so this is the one place that has to know. It runs alongside the
|
|
616
|
+
// connection rather than before it — neither needs the other — though "I/O"
|
|
617
|
+
// flatters the engine: most of that load is the loop-blocking instantiate,
|
|
618
|
+
// which is why the order below is what it is.
|
|
619
|
+
//
|
|
620
|
+
// The misuse check comes before anything starts, so a bad call cannot leave
|
|
621
|
+
// a load or a connection in flight with nothing waiting on it.
|
|
622
|
+
if (isNtkApp(options)) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
'react-x11: createRoot takes an options object — pass the connection ' +
|
|
625
|
+
'as createRoot({ app }).',
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
const { app: borrowed, onDisconnect, ...rest } = options;
|
|
629
|
+
const owned = borrowed === undefined;
|
|
630
|
+
// The connection is started first, and the order is the point rather than a
|
|
631
|
+
// detail. `loadLayout()` is only nominally asynchronous: instantiating the
|
|
632
|
+
// engine blocks the event loop for 15-50 ms before it returns its promise
|
|
633
|
+
// (measured), so calling it first delays the socket work by exactly that —
|
|
634
|
+
// the loop cannot run the connect callback that writes the hello while the
|
|
635
|
+
// instantiate is on it. Starting the connection first puts the handshake in
|
|
636
|
+
// flight, and the block then overlaps it instead of preceding it.
|
|
637
|
+
//
|
|
638
|
+
// Worth being honest about the size: the most this can save is the
|
|
639
|
+
// synchronous block, and only where the handshake takes longer than it. It
|
|
640
|
+
// is not measurable on a Unix socket, and it is lost in the noise on a link
|
|
641
|
+
// slow enough to matter. This is the right order, not a fast one.
|
|
642
|
+
const connecting = owned
|
|
643
|
+
? connect(
|
|
644
|
+
Object.fromEntries(
|
|
645
|
+
CONNECT_OPTIONS.filter((k) => rest[k] !== undefined).map((k) => [
|
|
646
|
+
k,
|
|
647
|
+
rest[k],
|
|
648
|
+
]),
|
|
649
|
+
),
|
|
650
|
+
)
|
|
651
|
+
: Promise.resolve(borrowed);
|
|
652
|
+
const layout = loadLayout();
|
|
653
|
+
const integrations = loadIntegrations(); // null when there is nothing to install
|
|
654
|
+
const [app] = await Promise.all([connecting, layout, integrations]);
|
|
655
|
+
|
|
656
|
+
const container = Renderer.createContainer(
|
|
657
|
+
app,
|
|
658
|
+
ConcurrentRoot,
|
|
659
|
+
null,
|
|
660
|
+
false,
|
|
661
|
+
null,
|
|
662
|
+
'',
|
|
663
|
+
rest.onUncaughtError ?? DEFAULT_ERROR_HANDLERS.onUncaughtError,
|
|
664
|
+
rest.onCaughtError ?? DEFAULT_ERROR_HANDLERS.onCaughtError,
|
|
665
|
+
rest.onRecoverableError ?? DEFAULT_ERROR_HANDLERS.onRecoverableError,
|
|
666
|
+
null,
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
// A throw from an event handler has no React on the stack, so it never
|
|
670
|
+
// reaches the container above — the root's handler is reached through the
|
|
671
|
+
// container instead, which is what the nodes carry.
|
|
672
|
+
if (rest.onUncaughtError) setErrorHandler(app, rest.onUncaughtError);
|
|
673
|
+
|
|
674
|
+
let unmounted = false;
|
|
675
|
+
if (onDisconnect) watchConnection(app, onDisconnect, () => unmounted);
|
|
676
|
+
|
|
677
|
+
// borrowed or owned, this is now a connection the renderer draws through,
|
|
678
|
+
// which is what a REACT_X11_TRACE / startTrace() session follows
|
|
679
|
+
registerApp(app);
|
|
680
|
+
|
|
681
|
+
// Climb toward the accessibility bus, once per process and off the
|
|
682
|
+
// critical path — every rung that fails is a normal, silent "off"
|
|
683
|
+
// (docs/accessibility.md). Deliberately not awaited: a root must not
|
|
684
|
+
// wait on a bus that is not there.
|
|
685
|
+
startA11y();
|
|
686
|
+
|
|
687
|
+
// Before anything renders: the launch id has to be on the first toplevel
|
|
688
|
+
// before it maps, and the environment variable has to be consumed whether
|
|
689
|
+
// or not this app ends up using it (src/startup.js).
|
|
690
|
+
beginStartup(app, rest.startupNotification);
|
|
691
|
+
|
|
692
|
+
// Which dead-key and Compose sequences this root types (src/compose.js).
|
|
693
|
+
// Synchronous and normally free: the built-in table is built once per
|
|
694
|
+
// process, and only `compose: 'system'` or a file of your own reads
|
|
695
|
+
// anything from disk.
|
|
696
|
+
beginCompose(app, rest.compose);
|
|
697
|
+
|
|
698
|
+
// Whether a subtree coming back out of hiding — a `<Suspense>` boundary
|
|
699
|
+
// resolving, an `<Activity>` shown again — takes the keyboard back with it
|
|
700
|
+
// (src/events.js, `subtreeRevealed`). On by default; `false` is the
|
|
701
|
+
// browser's answer, where focus stays wherever the hide dropped it.
|
|
702
|
+
beginFocus(app, rest.restoreFocusOnReveal);
|
|
703
|
+
|
|
704
|
+
// Which keysym a shortcut matches when the layout is not Latin
|
|
705
|
+
// (src/keyboard.js). Pure bookkeeping — the work happens per key event,
|
|
706
|
+
// and only for a key that types another script.
|
|
707
|
+
beginKeyboard(app, rest.accelerators);
|
|
708
|
+
|
|
709
|
+
// Awaited, and this is the only place it can be: whether a compositor is
|
|
710
|
+
// running decides what a `transparent` window paints, and a window that
|
|
711
|
+
// realized before the answer landed would paint the wrong thing once and
|
|
712
|
+
// correct itself visibly — and the same shape of question twice more. The
|
|
713
|
+
// display scale multiplies every style length, every font size and every
|
|
714
|
+
// CreateWindow rectangle, so it must be settled before the first node
|
|
715
|
+
// resolves a style — there is no "correct it a frame later" for a tree
|
|
716
|
+
// that laid out at half size; normally free or one round trip (the
|
|
717
|
+
// environment answers, or XSETTINGS does), with the RandR hardware walk
|
|
718
|
+
// only paid on desktops where nothing cheaper answered (src/scale.js).
|
|
719
|
+
// And a `<window>` with no size given is sized from its content and
|
|
720
|
+
// capped at the screen, where the cap has to be a *synchronous* answer
|
|
721
|
+
// because the size is resolved before CreateWindow. The three probes are
|
|
722
|
+
// independent conversations with the server (a selection owner and an
|
|
723
|
+
// extension; the scale ladder; the monitor layout and the work-area
|
|
724
|
+
// property), so they run concurrently: each is internally a chain of one
|
|
725
|
+
// to three round trips, and awaiting them in sequence made every app pay
|
|
726
|
+
// the sum where the slowest chain is the true floor.
|
|
727
|
+
await Promise.all([
|
|
728
|
+
beginCompositing(app),
|
|
729
|
+
beginScale(app, rest.scale),
|
|
730
|
+
beginScreens(app),
|
|
731
|
+
]);
|
|
732
|
+
|
|
733
|
+
// How fast a caret blinks, how long a double click has, how far a press
|
|
734
|
+
// moves before it is a drag. **Not** awaited, unlike the two above: the
|
|
735
|
+
// renderer reads these synchronously but only on an interaction — a focus,
|
|
736
|
+
// a click, a drag — which is always many milliseconds after startup, so
|
|
737
|
+
// making every app wait five round trips for them would buy nothing. A
|
|
738
|
+
// field focused on the very first frame gets the built-in defaults and the
|
|
739
|
+
// desktop's cadence from its next focus on (src/desktopsettings.js).
|
|
740
|
+
beginDesktopSettings(app);
|
|
741
|
+
|
|
742
|
+
// The desktop switching between light and dark reaches the widgets through
|
|
743
|
+
// React — `useTheme()` subscribes — but the other theme route is the node
|
|
744
|
+
// tree, which React does not re-render. This is that half: drop the cached
|
|
745
|
+
// palettes and repaint, above all the window background, which is read from
|
|
746
|
+
// the palette at paint time. Not awaited, and it starts nothing: the store
|
|
747
|
+
// is seeded from disk and the ladder runs when something asks.
|
|
748
|
+
const stopAppearance = watchAppearance(() => appearanceChanged(app));
|
|
749
|
+
|
|
750
|
+
const root = {
|
|
489
751
|
app,
|
|
490
752
|
render(element, callback) {
|
|
491
|
-
|
|
753
|
+
// The provider wraps rather than replaces: it renders no host node, so
|
|
754
|
+
// `getPublicRootInstance` still hands back the tree's own root and
|
|
755
|
+
// nothing downstream can tell it is there. `null` unmounts, and must
|
|
756
|
+
// stay null rather than become a provider around nothing.
|
|
757
|
+
// The provider renders no host node, so it changes nothing about
|
|
758
|
+
// what is drawn — but it does sit between the root fiber and the
|
|
759
|
+
// tree, which is why the public instance comes from the container's
|
|
760
|
+
// own record rather than from getPublicRootInstance. `null` unmounts,
|
|
761
|
+
// and has to stay null rather than become a provider around nothing.
|
|
762
|
+
const tree =
|
|
763
|
+
element == null
|
|
764
|
+
? element
|
|
765
|
+
: React.createElement(AppProvider, { value: app }, element);
|
|
766
|
+
Renderer.updateContainerSync(tree, container, null, () => {
|
|
767
|
+
const rootNode = app._rootChildren?.[0] ?? null;
|
|
768
|
+
callback?.(rootNode && HostConfig.getPublicInstance(rootNode), app);
|
|
769
|
+
});
|
|
770
|
+
Renderer.flushSyncWork();
|
|
492
771
|
},
|
|
493
|
-
|
|
494
|
-
|
|
772
|
+
/** Unmounts, then closes the connection unless `app` was passed in. */
|
|
773
|
+
async unmount() {
|
|
774
|
+
if (unmounted) return;
|
|
775
|
+
unmounted = true;
|
|
776
|
+
Renderer.updateContainerSync(null, container, null, null);
|
|
777
|
+
Renderer.flushSyncWork();
|
|
778
|
+
if (rest.onUncaughtError) setErrorHandler(app, null);
|
|
779
|
+
stopAppearance();
|
|
780
|
+
endCompositing(app);
|
|
781
|
+
endScreens(app);
|
|
782
|
+
endDesktopSettings(app);
|
|
783
|
+
endIdle(app);
|
|
784
|
+
endKeyboardState(app);
|
|
785
|
+
// The XSETTINGS session outlives both of its readers otherwise: it
|
|
786
|
+
// holds a PropertyChange selection on the settings daemon's window and
|
|
787
|
+
// an XFixes watch on the selection, neither of which anything else
|
|
788
|
+
// takes off.
|
|
789
|
+
endXSettings(app);
|
|
790
|
+
if (owned) {
|
|
791
|
+
unregisterApp(app);
|
|
792
|
+
await app.close();
|
|
793
|
+
}
|
|
794
|
+
if (app._reactX11Root === root) app._reactX11Root = null;
|
|
495
795
|
},
|
|
496
796
|
};
|
|
497
|
-
}
|
|
498
797
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
798
|
+
// What a WM close request with no onCloseRequest unmounts (see
|
|
799
|
+
// WindowNode#_defaultCloseRequest). The first root wins, because it is the
|
|
800
|
+
// one whose window the request will be arriving on; a second root sharing a
|
|
801
|
+
// borrowed connection owns its own teardown either way.
|
|
802
|
+
app._reactX11Root ??= root;
|
|
803
|
+
|
|
804
|
+
return root;
|
|
507
805
|
}
|