react-x11 2.52.0 → 2.53.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 +10 -10
- package/docs/README.md +160 -314
- package/docs/accessibility.md +176 -229
- package/docs/appearance.md +77 -176
- package/docs/click-to-component.md +18 -51
- package/docs/clipboard.md +40 -59
- package/docs/components.md +518 -1036
- package/docs/dbus.md +61 -133
- package/docs/debugging.md +152 -245
- package/docs/desktop-calendar.md +51 -88
- package/docs/desktop.md +222 -347
- package/docs/devtools.md +50 -167
- package/docs/drag-and-drop.md +126 -204
- package/docs/ecosystem/animation.md +91 -125
- package/docs/ecosystem/data-fetching.md +38 -53
- package/docs/ecosystem/dev-tooling.md +159 -202
- package/docs/ecosystem/forms.md +61 -97
- package/docs/ecosystem/headless.md +64 -89
- package/docs/ecosystem/i18n.md +18 -21
- package/docs/ecosystem/icons.md +93 -126
- package/docs/ecosystem/layout.md +42 -52
- package/docs/ecosystem/routing.md +18 -29
- package/docs/ecosystem/state.md +71 -88
- package/docs/ecosystem/testing.md +131 -157
- package/docs/ecosystem/theming.md +119 -165
- package/docs/ecosystem.md +145 -179
- package/docs/elements.md +1091 -1774
- package/docs/embedding.md +83 -132
- package/docs/events.md +412 -695
- package/docs/extending.md +784 -1326
- package/docs/eyedropper.md +63 -122
- package/docs/filedialog.md +71 -107
- package/docs/frame.md +195 -339
- package/docs/gl.md +65 -62
- package/docs/globalmenu.md +63 -146
- package/docs/macos.md +531 -3234
- package/docs/notifications.md +33 -47
- package/docs/packaging.md +166 -309
- package/docs/permissions.md +9 -14
- package/docs/react-features.md +81 -106
- package/docs/remote.md +32 -76
- package/docs/scale.md +72 -186
- package/docs/styling.md +329 -547
- package/docs/system.md +200 -280
- package/docs/testing.md +63 -83
- package/docs/typescript.md +6 -48
- package/docs/uri-schemes.md +67 -107
- package/docs/wayland-backend.md +200 -459
- package/docs/windows-integrations.md +75 -131
- package/docs/windows.md +343 -1367
- package/package.json +3 -2
- package/src/Reconciler.js +2 -2
- package/src/a11y.js +1 -1
- package/src/backend/context2d.js +3 -3
- package/src/bootstrap.js +1 -1
- package/src/cocoa/app.js +6 -6
- package/src/cocoa/bezels.js +1 -1
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/globalmenu.js +1 -1
- package/src/cocoa/keymap.js +1 -1
- package/src/cocoa/panewindow.js +1 -1
- package/src/cocoa/player.js +1 -1
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/promotion.js +1 -1
- package/src/cocoa/relaunch.js +1 -1
- package/src/cocoa/threaded.js +1 -1
- package/src/cocoa/video.js +1 -1
- package/src/cocoa/window.js +4 -4
- package/src/components/Menu.js +7 -2
- package/src/components/Switch.js +1 -1
- package/src/components/native.js +1 -1
- package/src/dnd.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/layouts.js +1 -1
- package/src/nodes/animation.js +1 -1
- package/src/nodes/invalidate.js +1 -1
- package/src/nodes/layouthost.js +1 -1
- package/src/nodes/video.js +19 -8
- package/src/nodes/window/droptarget.js +1 -1
- package/src/pacing.js +1 -1
- package/src/refresh/index.js +2 -1
- package/src/types/video.d.ts +5 -2
- package/src/videobackend.js +28 -0
- package/src/videoframes.js +29 -16
- package/src/wayland/app.js +27 -0
- package/src/wayland/video.js +376 -0
- package/src/win32/a11y.js +1 -1
- package/src/win32/app.js +2 -2
- package/src/win32/fonts.js +1 -1
- package/src/win32/glarea.js +4 -4
- package/src/win32/overlay.js +1 -1
- package/src/win32/panewindow.js +3 -3
- package/src/win32/shell.js +1 -1
- package/src/win32/window.js +2 -2
- package/src/windowid.js +1 -1
- package/src/xvideo.js +189 -0
- package/docs/architecture/accessibility.md +0 -470
- package/docs/architecture/animation.md +0 -707
- package/docs/architecture/container-queries.md +0 -525
- package/docs/architecture/custom-layout.md +0 -334
- package/docs/architecture/drag-and-drop.md +0 -941
- package/docs/architecture/element-layer-contents.md +0 -622
- package/docs/architecture/frame-pacing.md +0 -255
- package/docs/architecture/grid-layout.md +0 -358
- package/docs/architecture/protocol-efficiency.md +0 -357
- package/docs/architecture/video.md +0 -1149
- package/docs/architecture/windowed-regions.md +0 -854
- package/docs/glx.md +0 -137
- package/docs/wayland.md +0 -545
- package/docs/windows-embedding.md +0 -271
- package/docs/windows-gl.md +0 -221
package/docs/permissions.md
CHANGED
|
@@ -25,26 +25,19 @@ function RecordButton() {
|
|
|
25
25
|
|
|
26
26
|
## The ladder
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
one that only points:
|
|
28
|
+
Like the [file dialog](filedialog.md)'s, chosen for you — one rung that answers
|
|
29
|
+
and one that only points:
|
|
30
30
|
|
|
31
31
|
| | | |
|
|
32
32
|
| --- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
33
|
| 1 | **the platform's authorization** | The [cocoa backend](macos.md): macOS's privacy (TCC) status per kind, and the system's own prompt where a framework offers one — `AVCaptureDevice` for the camera and microphone, `CoreLocation`, and the rest. |
|
|
34
34
|
| 2 | **the Settings pane** | `open x-apple.systempreferences:…` — any Mac, bridge or not. It cannot read a status or raise a prompt; it puts the user in front of the switch, which is all `openPrivacySettings` promises. |
|
|
35
35
|
|
|
36
|
-
**On Linux nothing answers yet
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
**typed** `NoPermissionServiceError` — the signal to keep the feature behind
|
|
42
|
-
the answer, not a crash. `usePermission().available` is that branch as render
|
|
43
|
-
state.
|
|
44
|
-
|
|
45
|
-
The top rung is never chosen by naming a backend: the call asks the connection
|
|
46
|
-
the tree renders through whether it has an authorization API, and the cocoa
|
|
47
|
-
backend says so by carrying one.
|
|
36
|
+
**On Linux nothing answers yet** (the per-device portals,
|
|
37
|
+
`org.freedesktop.portal.Camera`, `.Location`, …, are not wired up). A status
|
|
38
|
+
query says `'unknown'` and a request rejects with a **typed**
|
|
39
|
+
`NoPermissionServiceError` — the signal to keep the feature behind the answer,
|
|
40
|
+
not a crash. `usePermission().available` is that branch as render state.
|
|
48
41
|
|
|
49
42
|
## The vocabulary
|
|
50
43
|
|
|
@@ -146,3 +139,5 @@ the request never prompts, or TCC ends the process. See
|
|
|
146
139
|
- **`'unknown'` is not `'prompt'`.** An app that treats them alike will show
|
|
147
140
|
an "Allow" button on Linux that rejects when pressed. Branch on `available`
|
|
148
141
|
first.
|
|
142
|
+
|
|
143
|
+
Design notes: [design/permissions.md](../design/permissions.md).
|
package/docs/react-features.md
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
# React features
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
**Everything in React core behaves as documented** — hooks, context, error
|
|
4
|
+
boundaries, `<Suspense>`, `lazy`, `use`, transitions, `<Profiler>`,
|
|
5
|
+
`<StrictMode>`. react-x11 is a normal React 19 renderer, and where React's
|
|
6
|
+
behaviour does not depend on the DOM, it is the same behaviour. This page is
|
|
7
|
+
the caveats, and the four places where a DOM habit quietly does not work.
|
|
6
8
|
|
|
7
|
-
The
|
|
8
|
-
Hooks, context, error boundaries, `<Suspense>`, `lazy`, `use`, transitions,
|
|
9
|
-
`<Profiler>`, `<StrictMode>` — all of it. react-x11 is a normal React 19
|
|
10
|
-
renderer, and where React's behaviour does not depend on the DOM, it is the
|
|
11
|
-
same behaviour.
|
|
12
|
-
|
|
13
|
-
Everything on this page is also something you can drive by hand — in the
|
|
14
|
-
apps that depend on it rather than in a panel of its own:
|
|
9
|
+
The examples that exercise it:
|
|
15
10
|
|
|
16
11
|
```sh
|
|
17
12
|
npm run examples:monitor # priority: a filter over ~780 live rows
|
|
@@ -19,12 +14,6 @@ npm run examples:chat # <Activity>, useOptimistic, <Suspense>
|
|
|
19
14
|
npm run examples:timer # error boundaries, and where a crash is logged
|
|
20
15
|
```
|
|
21
16
|
|
|
22
|
-
Those replaced an `examples/react-features.jsx` that had a panel per feature.
|
|
23
|
-
A panel proves the API exists; it cannot show you the choice, because nothing
|
|
24
|
-
in it costs anything. `monitor`'s field is expensive because a process list
|
|
25
|
-
is expensive, and that is the only condition under which deferring it is a
|
|
26
|
-
decision rather than a demonstration.
|
|
27
|
-
|
|
28
17
|
What differs is anything the DOM used to define for you:
|
|
29
18
|
|
|
30
19
|
- **Paint timing.** A commit does not put pixels on screen. Frames go out on
|
|
@@ -37,25 +26,26 @@ What differs is anything the DOM used to define for you:
|
|
|
37
26
|
|
|
38
27
|
## At a glance
|
|
39
28
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
| `useState`, `useReducer`, `useContext`, `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
|
|
|
50
|
-
| `<
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
|
|
|
58
|
-
|
|
|
29
|
+
| | |
|
|
30
|
+
| ----------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
31
|
+
| `useState`, `useReducer`, `useContext`, `useRef` | unchanged |
|
|
32
|
+
| `useMemo`, `useCallback`, custom hooks | unchanged |
|
|
33
|
+
| `useImperativeHandle`, `forwardRef`, `ref` as a prop, ref cleanup | unchanged |
|
|
34
|
+
| `use(promise)`, `use(context)`, `React.lazy` | unchanged |
|
|
35
|
+
| `useOptimistic`, `useActionState`, `<Profiler>` | unchanged |
|
|
36
|
+
| `useSyncExternalStore` | unchanged, and the best way to bridge non-React code |
|
|
37
|
+
| `useTransition`, `startTransition`, `useDeferredValue` | work, and really do stay responsive |
|
|
38
|
+
| `useLayoutEffect` vs `useEffect` | the difference is real — one paint versus two |
|
|
39
|
+
| `<Suspense>` | works; [around a `<window>`](#suspense-and-activity) it unmaps it |
|
|
40
|
+
| `<Activity>` | works; `mode="hidden"` around a `<window>` unmaps it |
|
|
41
|
+
| Error boundaries | work — but **where you put one** decides if the window survives |
|
|
42
|
+
| `<StrictMode>` | safe; effects are not double-invoked on the first mount |
|
|
43
|
+
| `useId` | works; there is very little here to use it for |
|
|
44
|
+
| `createPortal` | use [`<popup>`](elements.md) instead — see [Portals](#portals) |
|
|
45
|
+
| `flushSync` | not exported; you almost certainly do not need it |
|
|
46
|
+
| `useFormStatus` | not wired up |
|
|
47
|
+
| React Server Components | not available |
|
|
48
|
+
| anything imported from `react-dom` | not available — see [Not available](#not-available) |
|
|
59
49
|
|
|
60
50
|
## Effects, and the paint boundary
|
|
61
51
|
|
|
@@ -68,27 +58,24 @@ it has a directly observable consequence:
|
|
|
68
58
|
| `useLayoutEffect` | one frame, with the corrected value |
|
|
69
59
|
| `useEffect` | one frame with the old value, then a frame with the new |
|
|
70
60
|
|
|
71
|
-
So an adjustment made in `useEffect` flickers
|
|
72
|
-
|
|
73
|
-
|
|
61
|
+
So an adjustment made in `useEffect` flickers, every time: updates from a
|
|
62
|
+
layout effect are folded into the frame being prepared, and updates from a
|
|
63
|
+
passive effect are not.
|
|
74
64
|
|
|
75
65
|
That holds for a correction computed from React state. One computed from
|
|
76
66
|
**geometry** is a layout behind: a layout effect runs after the commit and
|
|
77
67
|
before the layout pass, so `abs` there is still the previous frame's rect
|
|
78
|
-
(zeros, on mount),
|
|
79
|
-
before. For a size, let the element report it with `onLayout`
|
|
68
|
+
(zeros, on mount). For a size, let the element report it with `onLayout`
|
|
80
69
|
([below](#measuring-a-node)); for a style that depends on one, write a
|
|
81
70
|
container query ([styling.md](styling.md#container-queries)), which the
|
|
82
|
-
pass answers in the same frame. `scrollIntoView`
|
|
83
|
-
|
|
84
|
-
pass
|
|
85
|
-
|
|
86
|
-
produces, so they are safe here ([elements.md](elements.md#scrolling)).
|
|
71
|
+
pass answers in the same frame. `scrollIntoView` and `scrollTo` — including
|
|
72
|
+
`scrollTo(Infinity)` to follow rows this commit added — are safe here: the
|
|
73
|
+
pass answers them against the geometry it produces
|
|
74
|
+
([elements.md](elements.md#scrolling)).
|
|
87
75
|
|
|
88
|
-
The same boundary is why an event handler feels instant:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
apart.
|
|
76
|
+
The same boundary is why an event handler feels instant: the React update
|
|
77
|
+
caused by a click or a keystroke lands **before** the frame goes out, so the
|
|
78
|
+
response and the default action paint together.
|
|
92
79
|
|
|
93
80
|
```jsx
|
|
94
81
|
// good — `tooTall` and `fits` are React state, so the corrected rows paint
|
|
@@ -101,9 +88,8 @@ useLayoutEffect(() => {
|
|
|
101
88
|
## Measuring a node
|
|
102
89
|
|
|
103
90
|
**There is no forced synchronous layout.** Nothing you can call will make
|
|
104
|
-
layout run so you can read the result
|
|
105
|
-
|
|
106
|
-
reads _and_ triggers.
|
|
91
|
+
layout run so you can read the result, as `getBoundingClientRect()` does in a
|
|
92
|
+
browser.
|
|
107
93
|
|
|
108
94
|
A node's `abs` rect (and `getClientRects()`) reports what the **last** layout
|
|
109
95
|
produced. On the very first render of a node, that is nothing yet.
|
|
@@ -179,11 +165,10 @@ say (see [Known gaps](#known-gaps)).
|
|
|
179
165
|
really do keep the UI responsive — React will interrupt a transition render
|
|
180
166
|
to handle an incoming click or keystroke.
|
|
181
167
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
its input with `useDeferredValue`) is not a micro-optimisation, it is the
|
|
168
|
+
**Transitions are the only mechanism that does this.** An ordinary
|
|
169
|
+
`setState` — from a handler, a timer, a socket callback — renders in one
|
|
170
|
+
uninterruptible pass, however much work it is. If a render is big enough to
|
|
171
|
+
drop a frame, `startTransition` (or `useDeferredValue` on its input) is the
|
|
187
172
|
difference between a responsive window and a frozen one.
|
|
188
173
|
|
|
189
174
|
```jsx
|
|
@@ -203,17 +188,13 @@ Two smaller notes:
|
|
|
203
188
|
- A `startTransition` inside a click handler is still a transition. It does
|
|
204
189
|
not inherit the handler's urgency.
|
|
205
190
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
changes every row on screen. Without the deferral the caret waits for the
|
|
214
|
-
table; with it the field stays under the fingers and the rows arrive a beat
|
|
215
|
-
later. Two things there are worth copying, and both are one-line mistakes in
|
|
216
|
-
the other direction: the expensive subtree is
|
|
191
|
+
Input is already classified for you: presses, releases, keys, focus
|
|
192
|
+
changes, window close and drops are urgent; pointer motion and drag-over are
|
|
193
|
+
not, which keeps a motion burst from flooding the renderer.
|
|
194
|
+
|
|
195
|
+
[`examples/monitor.jsx`](../examples/monitor.jsx) is the worked case: a
|
|
196
|
+
filter over hundreds of live rows, where one keystroke changes every row on
|
|
197
|
+
screen. Two things there are worth copying: the expensive subtree is
|
|
217
198
|
`memo`ised (or the urgent render rebuilds it anyway, and deferring buys
|
|
218
199
|
nothing), and no prop that changes on every update — not even a "stale"
|
|
219
200
|
border — is passed into it.
|
|
@@ -221,22 +202,18 @@ border — is passed into it.
|
|
|
221
202
|
### Bridging non-React code
|
|
222
203
|
|
|
223
204
|
`useSyncExternalStore` is the right tool for anything that lives outside
|
|
224
|
-
React — a timer, a D-Bus signal, a socket, an ntk event
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
waits a turn longer.
|
|
228
|
-
|
|
205
|
+
React — a timer, a D-Bus signal, a socket, an ntk event — and faster than a
|
|
206
|
+
hand-rolled `setState` bridge: a store notification is applied urgently, in
|
|
207
|
+
the next microtask, where the equivalent `setState` waits a turn longer.
|
|
229
208
|
Every mainstream store (zustand, jotai, valtio, redux, XState) works with no
|
|
230
|
-
adapter
|
|
231
|
-
|
|
232
|
-
store, and the UI subscribes to it.
|
|
209
|
+
adapter. The [window-manager example](../examples/wm.jsx) is the worked
|
|
210
|
+
case: the WM core is a plain store, and the UI subscribes to it.
|
|
233
211
|
|
|
234
212
|
## Suspense and Activity
|
|
235
213
|
|
|
236
|
-
`<Suspense>` works.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
to the fallback and jumps back on reveal.
|
|
214
|
+
`<Suspense>` works. Plan for layout: a hidden subtree gives up its space
|
|
215
|
+
entirely, so **size your fallback deliberately**, or the window collapses to
|
|
216
|
+
the fallback and jumps back on reveal.
|
|
240
217
|
|
|
241
218
|
```jsx
|
|
242
219
|
<Suspense fallback={<box style={{ height: 240 }} />}>
|
|
@@ -251,12 +228,11 @@ something else took focus meanwhile, so a boundary that re-suspends
|
|
|
251
228
|
mid-edit does not drop the user out of what they were typing. See
|
|
252
229
|
[events.md](events.md#focus-and-visibility) for the rules and the way out.
|
|
253
230
|
|
|
254
|
-
**Scroll positions come back too.**
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
row the user was reading.
|
|
231
|
+
**Scroll positions come back too.** A hidden boundary's content is not laid
|
|
232
|
+
out while hidden (as under `display: 'none'`), so a scroll pane in it is not
|
|
233
|
+
clamped, reports no `onViewport` or `onScroll` until shown, and reappears at
|
|
234
|
+
the offset it was left at — a tab kept under `<Activity>` reopens on the row
|
|
235
|
+
the user was reading.
|
|
260
236
|
|
|
261
237
|
If a `<window>` is inside the boundary, hiding it **unmaps the real window**.
|
|
262
238
|
The window manager treats the reveal as a new window: it may re-place it,
|
|
@@ -267,12 +243,11 @@ lost. Prefer suspending _inside_ a window over suspending the window itself.
|
|
|
267
243
|
`import()` gets inlined — you keep lazy _evaluation_ but get no code
|
|
268
244
|
splitting; see [packaging.md](packaging.md).
|
|
269
245
|
|
|
270
|
-
`<Activity>` works, and `mode="hidden"` around a toplevel `<window>`
|
|
271
|
-
the same way
|
|
272
|
-
|
|
273
|
-
window
|
|
274
|
-
|
|
275
|
-
manager's to remember, and it may not.
|
|
246
|
+
`<Activity>` works, and `mode="hidden"` around a toplevel `<window>` unmaps
|
|
247
|
+
it the same way; one mounted hidden is created and laid out but never
|
|
248
|
+
mapped until the reveal. The same caveat applies: what the user did to the
|
|
249
|
+
window before it was hidden is the window manager's to remember, and it may
|
|
250
|
+
not.
|
|
276
251
|
|
|
277
252
|
**Testing Suspense**: a promise that settles outside `act()` is not always
|
|
278
253
|
picked up by an `await act()`, because React throttles the commit when a
|
|
@@ -281,11 +256,10 @@ see [testing.md](testing.md).
|
|
|
281
256
|
|
|
282
257
|
## Portals
|
|
283
258
|
|
|
284
|
-
**Use `<popup
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
and can extend past the window edge.
|
|
259
|
+
**Use `<popup>`** where the DOM would reach for `createPortal`. A `<popup>`
|
|
260
|
+
is an ordinary child in your JSX — it sees context, state and props where
|
|
261
|
+
you wrote it — but a real top-level window, so it escapes its parent's
|
|
262
|
+
clipping and can extend past the window edge.
|
|
289
263
|
|
|
290
264
|
```jsx
|
|
291
265
|
<box>
|
|
@@ -323,8 +297,9 @@ yourself wanting it, you almost certainly want a second `<window>` or a
|
|
|
323
297
|
|
|
324
298
|
## Where to put an error boundary
|
|
325
299
|
|
|
326
|
-
Boundaries work exactly as documented. The
|
|
327
|
-
**placement**, and it decides whether the user's window
|
|
300
|
+
Boundaries work exactly as documented. The decision specific to a desktop
|
|
301
|
+
renderer is **placement**, and it decides whether the user's window
|
|
302
|
+
survives:
|
|
328
303
|
|
|
329
304
|
```jsx
|
|
330
305
|
// good — the window and everything the WM knows about it survives
|
|
@@ -403,12 +378,12 @@ and the [hot-reload example](../examples/tasks.jsx), which is
|
|
|
403
378
|
`flushSync`, `findDOMNode`, `hydrateRoot` and `unstable_batchedUpdates` are
|
|
404
379
|
not part of react-x11.
|
|
405
380
|
|
|
406
|
-
The trap
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
381
|
+
The trap: `react-dom` is a required peer of some libraries, so it can end up
|
|
382
|
+
installed anyway. Then the import resolves, and `flushSync()` runs your
|
|
383
|
+
callback **without flushing anything**, because it talks to a renderer you
|
|
384
|
+
are not using. If a library depends on synchronous flushing, check
|
|
385
|
+
`npm ls react-dom`; the [ecosystem register](ecosystem.md) records which
|
|
386
|
+
packages hit this.
|
|
412
387
|
|
|
413
388
|
**Server Components** are not available: there is no Flight client wired up.
|
|
414
389
|
|
package/docs/remote.md
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# Remote display
|
|
2
2
|
|
|
3
|
-
This is the case the architecture is categorically better at, and the one
|
|
4
|
-
nothing else on the page has told you about.
|
|
5
|
-
|
|
6
3
|
A react-x11 app **draws over the wire**. It does not render pixels and ship
|
|
7
4
|
them; it sends X11 drawing requests — rectangles, glyph runs, composites —
|
|
8
|
-
and the X server on the far end turns them into pixels. So
|
|
9
|
-
somewhere else
|
|
5
|
+
and the X server on the far end turns them into pixels. So running the app
|
|
6
|
+
somewhere else is not a feature bolted on top: it is what the protocol is,
|
|
10
7
|
and the program does not know the difference.
|
|
11
8
|
|
|
12
9
|
```sh
|
|
@@ -18,13 +15,12 @@ Nothing in the app changes. No VNC, no browser, no streaming daemon, no port
|
|
|
18
15
|
to forward, no second copy of the UI.
|
|
19
16
|
|
|
20
17
|
**This is the X11 backend**, and it is the reason X11 stays the flagship
|
|
21
|
-
target
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
which you want:
|
|
18
|
+
target: the Cocoa, win32 and Wayland backends have no wire at all, so
|
|
19
|
+
nothing on this page applies to them. That matters in one case — **an app
|
|
20
|
+
running on a Mac or on Windows and drawing somewhere else**. `$DISPLAY` in
|
|
21
|
+
the environment does not choose the backend there; `backend: 'auto'` sees
|
|
22
|
+
darwin and picks Cocoa, or win32 and picks the win32 backend, and the window
|
|
23
|
+
opens on the local machine. Say which you want:
|
|
28
24
|
|
|
29
25
|
```sh
|
|
30
26
|
REACT_X11_BACKEND=x11 DISPLAY=thin-client:0 npm start
|
|
@@ -37,67 +33,31 @@ and drawing to a Mac's XQuartz is not affected: both ends are X11 already.
|
|
|
37
33
|
|
|
38
34
|
## What it costs, measured
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| a paragraph of text | 39 | 6.0 kB | 7 | 0 |
|
|
52
|
-
| 10 wheel notches over 500 rows | 224 | 11.2 kB | 22 | 4 |
|
|
53
|
-
| 5 drag steps of a card of widgets | 72 | 4.3 kB | 11 | 0 |
|
|
54
|
-
|
|
55
|
-
**A whole window appears for about four kilobytes.** A single 1280×800 frame
|
|
56
|
-
of VNC, even well compressed, is tens to hundreds of kilobytes, and it is
|
|
57
|
-
sent again every time anything moves. Scrolling a 500-row table ten notches
|
|
58
|
-
costs 11 kB here because only the damaged strip is redrawn (the
|
|
59
|
-
[scroll-blit path](../AGENTS.md), issue #138); the same scroll under pixel
|
|
60
|
-
streaming is ten full frames. Dragging a card with a background of its own
|
|
61
|
-
is the same trade: each step copies the card where it went and draws the
|
|
62
|
-
strip it uncovered, so its widgets are not drawn again
|
|
63
|
-
([extending.md](extending.md#an-element-that-covers-its-box), issue #681) —
|
|
64
|
-
the five steps in the table would be 24 kB drawn.
|
|
65
|
-
|
|
66
|
-
What react-x11 pays instead is **latency sensitivity**. Bandwidth is not the
|
|
67
|
-
scarce resource on a link like this — round trips are. A request that waits
|
|
68
|
-
for a reply stalls the pipeline for a full RTT, so on a 60 ms link every
|
|
69
|
-
reply is 60 ms of nothing.
|
|
70
|
-
|
|
71
|
-
The last two columns are how that is watched, and the difference between
|
|
72
|
-
them is the whole point. `replies` counts requests that carry an answer;
|
|
73
|
-
`stalls` counts the ones whose reply arrived with **nothing pipelined behind
|
|
74
|
-
them** — the request was the last thing sent, so the connection really did
|
|
75
|
-
sit idle for a round trip. A pipelined reply costs a reply and no time: the
|
|
76
|
-
mount's 32 are almost all `InternAtom`, fired off together while the window
|
|
77
|
-
is built, and it stalls on none of them. (The bench's own `settle()` fences
|
|
78
|
-
are a fixed floor in that column, so read growth rather than the absolute
|
|
79
|
-
number.)
|
|
80
|
-
|
|
81
|
-
So the rule — "avoid round trips", rule 4 in
|
|
82
|
-
[AGENTS.md](../AGENTS.md#protocol-efficiency) — is really "avoid _blocking_
|
|
83
|
-
round trips", which is why atoms, geometry and glyph pages are cached rather
|
|
84
|
-
than re-asked, and why `stalls` is the column to watch on a slow link.
|
|
36
|
+
Bandwidth is small: **a whole window appears for about four kilobytes** of
|
|
37
|
+
requests, and scrolling a 500-row table ten notches costs 11 kB, because
|
|
38
|
+
only the damaged strip is redrawn — where a single frame of pixel streaming
|
|
39
|
+
(VNC, RDP) is tens to hundreds of kilobytes, sent again whenever anything
|
|
40
|
+
moves.
|
|
41
|
+
|
|
42
|
+
What react-x11 pays instead is **latency**. A request that waits for a
|
|
43
|
+
reply with nothing pipelined behind it stalls the connection for a full
|
|
44
|
+
round trip, so on a 60 ms link every such reply is 60 ms of nothing. That is
|
|
45
|
+
why atoms, geometry and glyph pages are cached rather than re-asked, and
|
|
46
|
+
why the column to watch on a slow link is `stalls`, not `replies`:
|
|
85
47
|
`npm run bench -- --hotspots` attributes them, request by request.
|
|
86
48
|
|
|
87
|
-
Rules of thumb, from the
|
|
88
|
-
|
|
49
|
+
Rules of thumb, reasoned from the request and reply counts rather than
|
|
50
|
+
measured over a shaped link:
|
|
89
51
|
|
|
90
52
|
- **Under ~20 ms RTT** (a LAN, a nearby VPS): indistinguishable from local.
|
|
91
53
|
- **20–80 ms** (a continent away): typing and scrolling stay responsive;
|
|
92
54
|
window mapping and font setup have a visible beat.
|
|
93
55
|
- **Above ~150 ms** or on a lossy link: X11's chattiness starts to show, and
|
|
94
56
|
a pixel-streaming protocol designed for high latency will feel better.
|
|
95
|
-
This is not
|
|
96
|
-
to say so.
|
|
57
|
+
This is X11's limit, not react-x11's.
|
|
97
58
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
netem` between two hosts; if you do it, the numbers belong in this table.
|
|
59
|
+
The per-scenario numbers are in the
|
|
60
|
+
[design notes](../design/remote.md#what-it-costs-measured).
|
|
101
61
|
|
|
102
62
|
## `ssh -X` and `ssh -Y`
|
|
103
63
|
|
|
@@ -114,8 +74,7 @@ that does not":
|
|
|
114
74
|
|
|
115
75
|
**Prefer `-X`.** react-x11 should work under it — it draws into windows it
|
|
116
76
|
created, which is what untrusted mode is for. If something does not, that is
|
|
117
|
-
a bug worth filing rather than a reason to reach for `-Y
|
|
118
|
-
documentation says "use `-Y` if `-X` misbehaves"; that advice hands every
|
|
77
|
+
a bug worth filing rather than a reason to reach for `-Y`, which hands every
|
|
119
78
|
program on the remote host the ability to read your screen and log your
|
|
120
79
|
keystrokes.
|
|
121
80
|
|
|
@@ -178,15 +137,12 @@ the compositor is willing to expose), and `WM_TRANSIENT_FOR` and the
|
|
|
178
137
|
`_NET_WM_STATE` work are among the things that survive the trip — see
|
|
179
138
|
[elements.md](elements.md#transientfor--a-window-that-belongs-to-another).
|
|
180
139
|
|
|
181
|
-
**Native Wayland: not remotely
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
therefore a different program under the same API — it rasterizes locally —
|
|
188
|
-
which is why it is opt-in only and never what `'auto'` picks: on Linux the
|
|
189
|
-
default stays X11, and with it everything on this page.
|
|
140
|
+
**Native Wayland: not remotely.** Wayland has no drawing protocol: a client
|
|
141
|
+
renders pixels into a buffer itself and hands the compositor a handle, so
|
|
142
|
+
there is no drawing to send over a wire. The
|
|
143
|
+
[Wayland backend](wayland-backend.md) rasterizes locally, which is why it is
|
|
144
|
+
opt-in and never what `'auto'` picks: on Linux the default stays X11, and
|
|
145
|
+
with it everything on this page.
|
|
190
146
|
|
|
191
147
|
**XQuartz notes**, since it is the most common macOS path:
|
|
192
148
|
|
|
@@ -196,7 +152,7 @@ default stays X11, and with it everything on this page.
|
|
|
196
152
|
- Put `/opt/X11/bin` first on `PATH`. Font family resolution shells out to
|
|
197
153
|
`fc-match`, and Homebrew's fontconfig — which is usually found first —
|
|
198
154
|
ships no macOS system-font aliases, so `sans-serif` resolves to a CJK
|
|
199
|
-
face.
|
|
155
|
+
face.
|
|
200
156
|
|
|
201
157
|
## Making it feel better
|
|
202
158
|
|