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.
Files changed (112) hide show
  1. package/README.md +10 -10
  2. package/docs/README.md +160 -314
  3. package/docs/accessibility.md +176 -229
  4. package/docs/appearance.md +77 -176
  5. package/docs/click-to-component.md +18 -51
  6. package/docs/clipboard.md +40 -59
  7. package/docs/components.md +518 -1036
  8. package/docs/dbus.md +61 -133
  9. package/docs/debugging.md +152 -245
  10. package/docs/desktop-calendar.md +51 -88
  11. package/docs/desktop.md +222 -347
  12. package/docs/devtools.md +50 -167
  13. package/docs/drag-and-drop.md +126 -204
  14. package/docs/ecosystem/animation.md +91 -125
  15. package/docs/ecosystem/data-fetching.md +38 -53
  16. package/docs/ecosystem/dev-tooling.md +159 -202
  17. package/docs/ecosystem/forms.md +61 -97
  18. package/docs/ecosystem/headless.md +64 -89
  19. package/docs/ecosystem/i18n.md +18 -21
  20. package/docs/ecosystem/icons.md +93 -126
  21. package/docs/ecosystem/layout.md +42 -52
  22. package/docs/ecosystem/routing.md +18 -29
  23. package/docs/ecosystem/state.md +71 -88
  24. package/docs/ecosystem/testing.md +131 -157
  25. package/docs/ecosystem/theming.md +119 -165
  26. package/docs/ecosystem.md +145 -179
  27. package/docs/elements.md +1091 -1774
  28. package/docs/embedding.md +83 -132
  29. package/docs/events.md +412 -695
  30. package/docs/extending.md +784 -1326
  31. package/docs/eyedropper.md +63 -122
  32. package/docs/filedialog.md +71 -107
  33. package/docs/frame.md +195 -339
  34. package/docs/gl.md +65 -62
  35. package/docs/globalmenu.md +63 -146
  36. package/docs/macos.md +531 -3234
  37. package/docs/notifications.md +33 -47
  38. package/docs/packaging.md +166 -309
  39. package/docs/permissions.md +9 -14
  40. package/docs/react-features.md +81 -106
  41. package/docs/remote.md +32 -76
  42. package/docs/scale.md +72 -186
  43. package/docs/styling.md +329 -547
  44. package/docs/system.md +200 -280
  45. package/docs/testing.md +63 -83
  46. package/docs/typescript.md +6 -48
  47. package/docs/uri-schemes.md +67 -107
  48. package/docs/wayland-backend.md +200 -459
  49. package/docs/windows-integrations.md +75 -131
  50. package/docs/windows.md +343 -1367
  51. package/package.json +3 -2
  52. package/src/Reconciler.js +2 -2
  53. package/src/a11y.js +1 -1
  54. package/src/backend/context2d.js +3 -3
  55. package/src/bootstrap.js +1 -1
  56. package/src/cocoa/app.js +6 -6
  57. package/src/cocoa/bezels.js +1 -1
  58. package/src/cocoa/fonts.js +1 -1
  59. package/src/cocoa/globalmenu.js +1 -1
  60. package/src/cocoa/keymap.js +1 -1
  61. package/src/cocoa/panewindow.js +1 -1
  62. package/src/cocoa/player.js +1 -1
  63. package/src/cocoa/presenter.js +2 -2
  64. package/src/cocoa/promotion.js +1 -1
  65. package/src/cocoa/relaunch.js +1 -1
  66. package/src/cocoa/threaded.js +1 -1
  67. package/src/cocoa/video.js +1 -1
  68. package/src/cocoa/window.js +4 -4
  69. package/src/components/Menu.js +7 -2
  70. package/src/components/Switch.js +1 -1
  71. package/src/components/native.js +1 -1
  72. package/src/dnd.js +2 -2
  73. package/src/glnodes.js +1 -1
  74. package/src/grid.js +1 -1
  75. package/src/layouts.js +1 -1
  76. package/src/nodes/animation.js +1 -1
  77. package/src/nodes/invalidate.js +1 -1
  78. package/src/nodes/layouthost.js +1 -1
  79. package/src/nodes/video.js +19 -8
  80. package/src/nodes/window/droptarget.js +1 -1
  81. package/src/pacing.js +1 -1
  82. package/src/refresh/index.js +2 -1
  83. package/src/types/video.d.ts +5 -2
  84. package/src/videobackend.js +28 -0
  85. package/src/videoframes.js +29 -16
  86. package/src/wayland/app.js +27 -0
  87. package/src/wayland/video.js +376 -0
  88. package/src/win32/a11y.js +1 -1
  89. package/src/win32/app.js +2 -2
  90. package/src/win32/fonts.js +1 -1
  91. package/src/win32/glarea.js +4 -4
  92. package/src/win32/overlay.js +1 -1
  93. package/src/win32/panewindow.js +3 -3
  94. package/src/win32/shell.js +1 -1
  95. package/src/win32/window.js +2 -2
  96. package/src/windowid.js +1 -1
  97. package/src/xvideo.js +189 -0
  98. package/docs/architecture/accessibility.md +0 -470
  99. package/docs/architecture/animation.md +0 -707
  100. package/docs/architecture/container-queries.md +0 -525
  101. package/docs/architecture/custom-layout.md +0 -334
  102. package/docs/architecture/drag-and-drop.md +0 -941
  103. package/docs/architecture/element-layer-contents.md +0 -622
  104. package/docs/architecture/frame-pacing.md +0 -255
  105. package/docs/architecture/grid-layout.md +0 -358
  106. package/docs/architecture/protocol-efficiency.md +0 -357
  107. package/docs/architecture/video.md +0 -1149
  108. package/docs/architecture/windowed-regions.md +0 -854
  109. package/docs/glx.md +0 -137
  110. package/docs/wayland.md +0 -545
  111. package/docs/windows-embedding.md +0 -271
  112. package/docs/windows-gl.md +0 -221
package/docs/dbus.md CHANGED
@@ -18,19 +18,14 @@ function ColourScheme() {
18
18
  ```
19
19
 
20
20
  There is **nothing to wrap**. No provider, no prop drilling, no setup step
21
- for the app author — a component library can call this, which is the whole
22
- reason it is shaped this way.
21
+ for the app author — a component library can call this.
23
22
 
24
23
  ## The rule everything else is subordinate to
25
24
 
26
25
  **Nothing throws at import time, and nothing crashes an app running where
27
- there is no session bus.**
28
-
29
- Concretely, that has to hold on XQuartz, under a bare `startx`, in CI, in an
30
- ssh session on a headless server, in a container, and on Node 20 where the
31
- transport is not installed at all. The ssh case is not an edge case — it is
32
- react-x11's flagship persona. **"No bus" is a first-class configuration, not
33
- a degraded one.**
26
+ there is no session bus** — XQuartz, a bare `startx`, CI, an ssh session on a
27
+ headless server, a container, Node 20 where the transport is not installed.
28
+ **"No bus" is a first-class configuration, not a degraded one.**
34
29
 
35
30
  So `sessionBus()` answers `null` rather than rejecting, and `useSessionBus()`
36
31
  reports `'unavailable'` rather than throwing, when:
@@ -63,27 +58,16 @@ interface BusHandle {
63
58
 
64
59
  **The terse path is correct on its own.** `const { bus } = useSessionBus()`
65
60
  followed by `if (!bus) return fallback` is right, with no reference to
66
- `status`; if reading `status` were _required_ to avoid a bug the shape would
67
- be wrong. For a colour-scheme subscriber, `'connecting'` and `'unavailable'`
68
- render identically anyway. The states earn their keep in diagnostics ("why is
69
- my tray icon missing") and for an app that is itself a service.
70
-
71
- **"Idle" is not a fourth state.** The first render happens before the effect
72
- runs, so nothing has been attempted yet — calling that `'connecting'` is a
73
- small inaccuracy that becomes true within a tick, and an honest fourth state
74
- would give every consumer a branch that never needs different handling.
61
+ `status`. The states are for diagnostics ("why is my tray icon missing") and
62
+ for an app that is itself a service.
75
63
 
76
64
  **`'unavailable'` is a snapshot, not a verdict.** Failure is not cached: a
77
- session bus can appear later — `$XDG_RUNTIME_DIR/bus` exists the moment
78
- something starts one — and the next acquisition retries. Do not permanently
79
- disable a feature on seeing it. The name invites the opposite reading, which
80
- is why this paragraph exists.
65
+ session bus can appear later, and the next acquisition retries. Do not
66
+ permanently disable a feature on seeing it.
81
67
 
82
- **`'closed'` does not auto-retry, deliberately.** Silently re-acquiring would
83
- hand you a fresh `bus` under the same variable and hide the unique-name change
84
- (see [the sharing contract](#the-sharing-contract)). A feature-level hook that
85
- knows how to rebuild its bus-side state can call `retry()`; a generic one
86
- cannot, so the decision is yours.
68
+ **`'closed'` does not auto-retry.** A new connection is a new unique name (see
69
+ [the sharing contract](#the-sharing-contract)), so a hook that knows how to
70
+ rebuild its bus-side state calls `retry()` itself.
87
71
 
88
72
  ## The imperative pair
89
73
 
@@ -100,58 +84,34 @@ interface BusRef {
100
84
  }
101
85
  ```
102
86
 
103
- This is what the hooks are built on, and it is public for two reasons that are
104
- not "completeness":
105
-
106
- - **Host-side code has no component to hang off.** The plumbing that later
107
- features add — a settings subscriber, a notification sender — is module code
108
- and event-loop glue, some of which must run with no root mounted at all.
109
- - **A click handler wants to resolve at click time**, not at render time:
110
-
111
- ```jsx
112
- <Button
113
- onClick={async () => {
114
- await using ref = await sessionBus({ required: true });
115
- await ref.bus.proxy(/* … */);
116
- }}
117
- />
118
- ```
119
-
120
- `required: true` is the service-author escape hatch. `null` is right for
121
- feature-probing plumbing and hides _why_ from an app whose purpose is the bus,
122
- so `required` converts unavailability into a rejection carrying the cause. The
123
- default stays never-rejecting; the hooks use `required` internally purely to
124
- obtain the reason for `cause`.
125
-
126
- There is **no `busAddress` parameter**, deliberately: a per-call address is
127
- incoherent under sharing — two callers, two addresses, one socket, which
128
- would win? The address seam is D-Bus's own, `$DBUS_SESSION_BUS_ADDRESS`, plus
129
- an `$XDG_RUNTIME_DIR/bus` fallback until
130
- [dbus-native#389](https://github.com/sidorares/dbus-native/pull/389) lands.
131
-
132
- On **macOS** there is a third source and it is not a variable: D-Bus
133
- advertises the socket through launchd, and finding it means running
134
- `launchctl getenv`. That lookup is asynchronous and happens once per process,
135
- which is the whole of
136
- [#417](https://github.com/sidorares/react-x11/issues/417) — `dbus-native`
137
- would otherwise do it from a synchronous constructor with `spawnSync`, and
138
- 120–150 ms of blocked event loop in front of the first frame is not a price
139
- for finding out whether there is a bus. See
140
- [desktop.md](desktop.md#not-blocking-the-first-frame-on-the-bus).
87
+ For host-side code with no component to hang off, and for a click handler that
88
+ wants to resolve at click time rather than render time:
141
89
 
142
- ## One connection, on purpose
90
+ ```jsx
91
+ <Button
92
+ onClick={async () => {
93
+ await using ref = await sessionBus({ required: true });
94
+ await ref.bus.proxy(/* … */);
95
+ }}
96
+ />
97
+ ```
143
98
 
144
- `dbus-native`'s own `sessionBus()` is a constructor in disguise: every call
145
- opens a new socket with its own unique name. A menu, a tray and an app's own
146
- exported service would be three connections and three identities, and the
147
- desktop would see three half-applications.
99
+ `required: true` converts unavailability into a rejection carrying the cause —
100
+ for an app whose purpose is the bus. The default never rejects.
148
101
 
149
- A D-Bus connection is **process** identity, so every consumer in the process
150
- shares one socket and one unique name. That is also why these hooks need no
151
- provider, where `useApp()` does: the X connection really is per-tree —
152
- `createRoot()` opens one per root and a process can drive several — but two
153
- roots on two X displays still share one session bus. Putting a process-global
154
- on a tree-scoped context would assert a relationship that does not exist.
102
+ There is **no `busAddress` parameter**: the connection is shared, so the
103
+ address comes from `$DBUS_SESSION_BUS_ADDRESS`, falling back to
104
+ `$XDG_RUNTIME_DIR/bus`. On **macOS** it is looked up from launchd
105
+ (`launchctl getenv`), asynchronously and once per process, so it does not
106
+ block the first frame
107
+ ([desktop.md](desktop.md#not-blocking-the-first-frame-on-the-bus)).
108
+
109
+ ## One connection, on purpose
110
+
111
+ Every consumer in the process — the global menu, a tray, your own exported
112
+ service — shares **one socket and one unique name**, so the desktop sees one
113
+ application. That is also why these hooks need no provider: two roots on two X
114
+ displays still share one session bus.
155
115
 
156
116
  ## Lifecycle: connect on first use, stay connected
157
117
 
@@ -162,35 +122,23 @@ The ref count drives the socket's **event-loop hold**, not its existence:
162
122
  | `>0` | connected, `ref()`d | held open — someone is using the bus |
163
123
  | `0` | connected, `unref()`d | not held; the connection stays warm and costs one idle FD |
164
124
 
165
- Releasing the last ref does **not** close the connection. A file dialog that
166
- mounts on click and unmounts on dismiss would otherwise tear the connection
167
- down and rebuild it every time — and the real cost is not the handshake, it is
168
- that **every reconnect is a new unique name**, with the daemon forgetting
169
- match rules and well-known names along with the old connection. StrictMode's
170
- double-mount becomes two counter flips rather than a full connect/disconnect
171
- cycle.
125
+ Releasing the last ref does **not** close the connection, so a dialog that
126
+ mounts and unmounts (or StrictMode's double mount) does not reconnect and get
127
+ a new unique name each time.
172
128
 
173
129
  `closeBus(kind)` is the way out for an app that genuinely wants the connection
174
130
  gone. Rare, explicit, and never the automatic consequence of an unmount.
175
131
 
176
132
  ## The sharing contract
177
133
 
178
- One connection with many consumers only works if the things one consumer can
179
- break for another are named:
180
-
181
134
  - **Never close a shared bus.** `release()` is the only lifecycle verb a ref
182
135
  holder has. `ref.bus.close()` or `bus.connection.end()` tears the connection
183
- out from under every other holder — libdbus makes closing a shared
184
- connection a hard application error, and here it is a rule.
185
- - **react-x11 owns the connection's one `'error'` listener.** `dbus-native`
186
- deliberately attaches none, because an unlistened `'error'` crashing the
187
- process is the right contract for a caller-owned connection and the wrong
188
- one for a shared one. Do not attach your own.
189
- - **A dead connection is not resurrected.** `dbus-native`'s reconnect stays
190
- off: a new socket is a new unique name, and the daemon forgets names and
191
- match rules with the old one. Refs on the dead connection fail their calls
192
- with `ConnectionClosedError` and `release()` harmlessly; the next
193
- `sessionBus()` dials fresh, and mounted hooks move to `'closed'`.
136
+ out from under every other holder.
137
+ - **react-x11 owns the connection's one `'error'` listener.** Do not attach
138
+ your own.
139
+ - **A dead connection is not resurrected.** Refs on it fail their calls with
140
+ `ConnectionClosedError` and `release()` harmlessly; the next `sessionBus()`
141
+ dials fresh (a new unique name), and mounted hooks move to `'closed'`.
194
142
  - **App-global stays app-global.** Well-known names (`RequestName` has no
195
143
  per-consumer refcount), exported object paths (re-exporting the same path
196
144
  and interface replaces silently) and `setValueShapes()` (which flips reply
@@ -202,7 +150,7 @@ break for another are named:
202
150
 
203
151
  An app that wants to _be_ on the bus gets the full `dbus-native` surface
204
152
  through `ref.bus`, on the **same connection and unique name** as react-x11's
205
- own plumbing — one socket, one identity on the desktop:
153
+ own plumbing:
206
154
 
207
155
  ```js
208
156
  const ref = await sessionBus({ required: true });
@@ -211,16 +159,12 @@ ref.bus.exportInterface(impl, '/org/example/MyApp', description);
211
159
  // hold the ref for as long as you serve: it is what keeps the process alive
212
160
  ```
213
161
 
214
- Holding the ref is not bookkeeping — it is the event-loop hold above. Release
215
- it and nothing keeps the process running.
162
+ Release the ref and nothing keeps the process running.
216
163
 
217
164
  The one service react-x11 exports for you is `org.freedesktop.Application`, so
218
165
  that a `myapp://…` link opens the app that is already running —
219
- [uri-schemes.md](uri-schemes.md). It is on this connection for exactly the
220
- reason above: a separately-dialled one would own the app's name under a
221
- different unique name than the one the global menu registers under and the one
222
- portals build Request paths from, and the desktop's model of "this
223
- application" would come apart.
166
+ [uri-schemes.md](uri-schemes.md). It is on this same connection, so the app's
167
+ name, its global menu and its portal requests share one identity.
224
168
 
225
169
  ## Installing, and Node 20
226
170
 
@@ -229,15 +173,12 @@ application" would come apart.
229
173
  ```
230
174
 
231
175
  `dbus-native` declares `engines: ">=22.12.0"` where react-x11 declares
232
- `">=20.19"`, so **npm skips it on Node 20** and the install succeeds exactly
233
- as before. There is no compiler and no install script either way — `npm i
234
- react-x11` still needs no toolchain.
176
+ `">=20.19"`, so **npm skips it on Node 20** and the install still succeeds. No
177
+ compiler and no install script either way.
235
178
 
236
179
  On Node 20, `useSessionBus()` reports `'unavailable'` with a `cause` that says
237
- the transport is missing. That is the same code path an ssh session or a Mac
238
- without a bus already takes: **"no transport installed" collapses into "no bus
239
- here" rather than adding a mode.** To have it on Node 20 anyway, install it
240
- explicitly:
180
+ the transport is missing — the same path as an ssh session or a Mac without a
181
+ bus. To have it on Node 20 anyway, install it explicitly:
241
182
 
242
183
  ```bash
243
184
  npm i dbus-native
@@ -251,27 +192,14 @@ directly and cast; it is the same object.
251
192
 
252
193
  ## Known sharp edges
253
194
 
254
- - **The `Hello` window is the one crash this layer cannot absorb.** A daemon
255
- that authenticates and then never answers `Hello` throws from inside
256
- `dbus-native` where nothing can catch it, 25 s in, from a timer tick —
257
- [dbus-native#393](https://github.com/sidorares/dbus-native/issues/393). Small
258
- window, real bug, tracked upstream.
259
- - **Identical match rules are refcounted by the daemon, not by the client** —
260
- [dbus-native#394](https://github.com/sidorares/dbus-native/issues/394).
261
- Dormant here, since reconnect is off; recorded so that nobody enables
262
- reconnect without reading it.
195
+ - **A daemon that authenticates and never answers `Hello`** crashes the
196
+ process from inside `dbus-native`, 25 s in, where nothing can catch it. A
197
+ small window, tracked upstream.
263
198
 
264
199
  ## Seeing it
265
200
 
266
- The bus explorer that used to live in `examples/` — session and system side
267
- by side, a tree of names → object paths → interfaces built from live
268
- introspection, a pane of an object's methods, signals and property values —
269
- has moved to
270
- [`@react-x11/components`](https://github.com/sidorares/react-x11-components).
271
- It was built on `Tree`, which now lives there too, and a browser of that shape
272
- is a component rather than a demonstration of this API.
273
-
274
- What it demonstrated of _this_ page still holds and is worth knowing: point
275
- `DBUS_SESSION_BUS_ADDRESS` at nothing and a well-built consumer renders the
276
- `'unavailable'` state rather than failing to start. That is the degradation
277
- rule above, and it is the thing to check in your own app.
201
+ Point `DBUS_SESSION_BUS_ADDRESS` at nothing: a well-built consumer renders the
202
+ `'unavailable'` state rather than failing to start. That is the thing to check
203
+ in your own app.
204
+
205
+ Design notes: [design/dbus.md](../design/dbus.md).