react-x11 2.51.0 → 2.52.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 (91) hide show
  1. package/README.md +87 -54
  2. package/docs/README.md +410 -0
  3. package/docs/accessibility.md +628 -0
  4. package/docs/appearance.md +416 -0
  5. package/docs/architecture/accessibility.md +470 -0
  6. package/docs/architecture/animation.md +707 -0
  7. package/docs/architecture/container-queries.md +525 -0
  8. package/docs/architecture/custom-layout.md +334 -0
  9. package/docs/architecture/drag-and-drop.md +941 -0
  10. package/docs/architecture/element-layer-contents.md +622 -0
  11. package/docs/architecture/frame-pacing.md +255 -0
  12. package/docs/architecture/grid-layout.md +358 -0
  13. package/docs/architecture/protocol-efficiency.md +357 -0
  14. package/docs/architecture/video.md +1149 -0
  15. package/docs/architecture/windowed-regions.md +854 -0
  16. package/docs/click-to-component.md +125 -0
  17. package/docs/clipboard.md +281 -0
  18. package/docs/components.md +1749 -0
  19. package/docs/dbus.md +277 -0
  20. package/docs/debugging.md +464 -0
  21. package/docs/desktop-calendar.md +284 -0
  22. package/docs/desktop.md +648 -0
  23. package/docs/devtools.md +250 -0
  24. package/docs/drag-and-drop.md +663 -0
  25. package/docs/ecosystem/animation.md +394 -0
  26. package/docs/ecosystem/data-fetching.md +201 -0
  27. package/docs/ecosystem/dev-tooling.md +436 -0
  28. package/docs/ecosystem/forms.md +313 -0
  29. package/docs/ecosystem/headless.md +219 -0
  30. package/docs/ecosystem/i18n.md +89 -0
  31. package/docs/ecosystem/icons.md +410 -0
  32. package/docs/ecosystem/layout.md +216 -0
  33. package/docs/ecosystem/routing.md +146 -0
  34. package/docs/ecosystem/state.md +361 -0
  35. package/docs/ecosystem/testing.md +500 -0
  36. package/docs/ecosystem/theming.md +540 -0
  37. package/docs/ecosystem.md +473 -0
  38. package/docs/elements.md +2762 -0
  39. package/docs/embedding.md +288 -0
  40. package/docs/events.md +1025 -0
  41. package/docs/extending.md +2242 -0
  42. package/docs/eyedropper.md +223 -0
  43. package/docs/filedialog.md +258 -0
  44. package/docs/frame.md +580 -0
  45. package/docs/gl.md +170 -0
  46. package/docs/globalmenu.md +278 -0
  47. package/docs/glx.md +137 -0
  48. package/docs/macos.md +3295 -0
  49. package/docs/notifications.md +164 -0
  50. package/docs/packaging.md +611 -0
  51. package/docs/permissions.md +148 -0
  52. package/docs/react-features.md +430 -0
  53. package/docs/remote.md +223 -0
  54. package/docs/scale.md +339 -0
  55. package/docs/security.md +132 -0
  56. package/docs/styling.md +1928 -0
  57. package/docs/system.md +599 -0
  58. package/docs/testing.md +514 -0
  59. package/docs/typescript.md +117 -0
  60. package/docs/uri-schemes.md +380 -0
  61. package/docs/wayland-backend.md +591 -0
  62. package/docs/wayland.md +545 -0
  63. package/docs/windows-embedding.md +271 -0
  64. package/docs/windows-gl.md +221 -0
  65. package/docs/windows-integrations.md +233 -0
  66. package/docs/windows.md +1404 -0
  67. package/package.json +10 -3
  68. package/src/bus.js +39 -26
  69. package/src/cli/create.d.ts +43 -0
  70. package/src/cli/create.js +305 -0
  71. package/src/cli/env.js +484 -0
  72. package/src/cli/index.js +29 -2
  73. package/src/cli/templates/AGENTS.template.md +195 -0
  74. package/src/cli/templates/DESIGN.template.md +269 -0
  75. package/src/desktopintegration.js +2 -1
  76. package/src/desktopsettings.js +193 -24
  77. package/src/filedialog.js +3 -3
  78. package/src/keyboardstate.js +28 -0
  79. package/src/node.d.ts +10 -10
  80. package/src/portal.js +31 -0
  81. package/src/screencolor.js +5 -10
  82. package/src/types/system.d.ts +8 -6
  83. package/src/wayland/app.js +59 -0
  84. package/src/wayland/clipboard.js +14 -7
  85. package/src/wayland/connection.js +3 -0
  86. package/src/wayland/context2d.js +129 -10
  87. package/src/wayland/protocols/index.json +6 -0
  88. package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
  89. package/src/wayland/seat.js +28 -1
  90. package/src/wayland/swapchain.js +23 -0
  91. package/src/wayland/xkb.js +137 -0
package/docs/dbus.md ADDED
@@ -0,0 +1,277 @@
1
+ # D-Bus
2
+
3
+ Everything the desktop wants from an app beyond drawing — a native file
4
+ dialog, the light/dark preference, notifications, a
5
+ [global menu](globalmenu.md), a tray icon
6
+ — is a D-Bus call. This page is the connection those features sit on, and
7
+ nothing else: no service is spoken to here, and the only traffic this layer
8
+ generates is its own handshake.
9
+
10
+ ```jsx
11
+ import { useSessionBus } from 'react-x11';
12
+
13
+ function ColourScheme() {
14
+ const { bus } = useSessionBus();
15
+ if (!bus) return null; // no bus here — nothing to read a preference from
16
+ // ...call org.freedesktop.portal.Settings through `bus`
17
+ }
18
+ ```
19
+
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.
23
+
24
+ ## The rule everything else is subordinate to
25
+
26
+ **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.**
34
+
35
+ So `sessionBus()` answers `null` rather than rejecting, and `useSessionBus()`
36
+ reports `'unavailable'` rather than throwing, when:
37
+
38
+ - `dbus-native` is not installed,
39
+ - `$DBUS_SESSION_BUS_ADDRESS` is unset and there is no `$XDG_RUNTIME_DIR/bus`,
40
+ - the connect fails, authentication fails, or the platform has no bus at all.
41
+
42
+ ## The hooks
43
+
44
+ ```ts
45
+ function useSessionBus(): BusHandle;
46
+ function useSystemBus(): BusHandle;
47
+
48
+ interface BusHandle {
49
+ readonly bus: MessageBus | null; // null unless status === 'ready'
50
+ readonly uniqueName: string | null;
51
+ readonly status: 'connecting' | 'ready' | 'unavailable' | 'closed';
52
+ readonly cause?: BusUnavailableError; // set when status === 'unavailable'
53
+ retry(): void;
54
+ }
55
+ ```
56
+
57
+ | `status` | |
58
+ | --------------- | -------------------------------------------------------- |
59
+ | `'connecting'` | the handshake is in flight — and also the first render |
60
+ | `'ready'` | `bus` and `uniqueName` are set |
61
+ | `'unavailable'` | no daemon, socket or permission here; `cause` says which |
62
+ | `'closed'` | the connection died under you; `retry()` dials a new one |
63
+
64
+ **The terse path is correct on its own.** `const { bus } = useSessionBus()`
65
+ 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.
75
+
76
+ **`'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.
81
+
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.
87
+
88
+ ## The imperative pair
89
+
90
+ ```ts
91
+ function sessionBus(opts?: { required?: boolean }): Promise<BusRef | null>;
92
+ function systemBus(opts?: { required?: boolean }): Promise<BusRef | null>;
93
+ function closeBus(kind: 'session' | 'system'): Promise<void>;
94
+
95
+ interface BusRef {
96
+ readonly bus: MessageBus; // the full dbus-native surface, shared
97
+ readonly uniqueName: string; // this connection's unique name, e.g. ':1.42'
98
+ release(): Promise<void>; // drop this consumer's reference; idempotent
99
+ [Symbol.asyncDispose](): Promise<void>;
100
+ }
101
+ ```
102
+
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).
141
+
142
+ ## One connection, on purpose
143
+
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.
148
+
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.
155
+
156
+ ## Lifecycle: connect on first use, stay connected
157
+
158
+ The ref count drives the socket's **event-loop hold**, not its existence:
159
+
160
+ | refs | socket | process exit |
161
+ | ---- | --------------------- | --------------------------------------------------------- |
162
+ | `>0` | connected, `ref()`d | held open — someone is using the bus |
163
+ | `0` | connected, `unref()`d | not held; the connection stays warm and costs one idle FD |
164
+
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.
172
+
173
+ `closeBus(kind)` is the way out for an app that genuinely wants the connection
174
+ gone. Rare, explicit, and never the automatic consequence of an unmount.
175
+
176
+ ## The sharing contract
177
+
178
+ One connection with many consumers only works if the things one consumer can
179
+ break for another are named:
180
+
181
+ - **Never close a shared bus.** `release()` is the only lifecycle verb a ref
182
+ 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'`.
194
+ - **App-global stays app-global.** Well-known names (`RequestName` has no
195
+ per-consumer refcount), exported object paths (re-exporting the same path
196
+ and interface replaces silently) and `setValueShapes()` (which flips reply
197
+ shapes for _every_ holder) are process-level resources. Owning them is app
198
+ policy; a `BusRef` does not scope them, and react-x11 will never call
199
+ `setValueShapes()` on the shared bus.
200
+
201
+ ## Being a service, not just a client
202
+
203
+ An app that wants to _be_ on the bus gets the full `dbus-native` surface
204
+ through `ref.bus`, on the **same connection and unique name** as react-x11's
205
+ own plumbing — one socket, one identity on the desktop:
206
+
207
+ ```js
208
+ const ref = await sessionBus({ required: true });
209
+ await ref.bus.requestName('org.example.MyApp', 0);
210
+ ref.bus.exportInterface(impl, '/org/example/MyApp', description);
211
+ // hold the ref for as long as you serve: it is what keeps the process alive
212
+ ```
213
+
214
+ Holding the ref is not bookkeeping — it is the event-loop hold above. Release
215
+ it and nothing keeps the process running.
216
+
217
+ The one service react-x11 exports for you is `org.freedesktop.Application`, so
218
+ 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.
224
+
225
+ ## Installing, and Node 20
226
+
227
+ ```json
228
+ "optionalDependencies": { "dbus-native": "^0.16.0" }
229
+ ```
230
+
231
+ `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.
235
+
236
+ 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:
241
+
242
+ ```bash
243
+ npm i dbus-native
244
+ ```
245
+
246
+ The declarations never name `dbus-native`, so they type-check on an install
247
+ where it is absent. `MessageBus` is a structural type covering the members
248
+ documented here, with an index signature over the rest of the package's
249
+ surface — code that wants `dbus-native`'s own checked types can import them
250
+ directly and cast; it is the same object.
251
+
252
+ ## Known sharp edges
253
+
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.
263
+
264
+ ## Seeing it
265
+
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.