react-x11 2.51.1 → 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 +9 -2
  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
@@ -0,0 +1,288 @@
1
+ # Embedding other applications: `<foreign>`
2
+
3
+ > **X11 only.** Cross-process window embedding does not exist on macOS:
4
+ > there is no way to take another application's window into your own view
5
+ > hierarchy, and this is not a gap waiting to be filled — the platform does
6
+ > not offer the primitive. On the Cocoa backend `<foreign>` refuses: it lays
7
+ > out as an empty box, never calls `onReady`, and calls `onError` once — and
8
+ > `useSupports('embedding')` is false there, so a component can ask before
9
+ > rendering one. What does work there is [`<Frame>`](frame.md), which embeds
10
+ > a pane of _your own_ application, and reaches the same place through
11
+ > shared surfaces instead of reparenting.
12
+
13
+ Everything else react-x11 draws, it draws itself. `<foreign>` is the one
14
+ element that shows somebody else's pixels: another process's top-level X
15
+ window, reparented into yours and laid out like any other child.
16
+
17
+ ```jsx
18
+ <foreign
19
+ windowId={terminal.windowId}
20
+ style={{ flexGrow: 1, backgroundColor: '#101014' }}
21
+ onEmbedded={({ xembed }) => setMode(xembed ? 'xembed' : 'reparented')}
22
+ onClientGone={() => respawn()}
23
+ />
24
+ ```
25
+
26
+ That is a terminal pane, a video surface, a docked tray icon and a
27
+ control-panel applet — four things an application cannot draw for itself and
28
+ has no business reimplementing.
29
+
30
+ When the other process is a module of **your own application**, don't wire
31
+ this up by hand: [`<Frame>`](frame.md) is `<foreign>` plus the process — the
32
+ fork, the props, the callbacks, the bridged theme, the crash fallback. This
33
+ page is the window half, and everything below about stacking, focus and
34
+ teardown holds for a `<Frame>` pane too. The pane's own root window opts in
35
+ with `<window embeddable>`: created **unmapped** — a window waiting to be
36
+ embedded is unmapped, that is what waiting looks like — and mapped by the
37
+ embedder once reparented. On X11 it is an XEmbed client for Tab: `FOCUS_IN`
38
+ with `FIRST` or `LAST` lands on its first or last stop, and Tab off an end of
39
+ its order sends `FOCUS_NEXT` or `FOCUS_PREV`, so Tab goes through a
40
+ react-x11 guest as it goes through any subtree of the host's.
41
+
42
+ **Being the guest yourself** — a react-x11 app embedded in somebody else's UI
43
+ — is that same prop plus one accessor: `windowHandleOf(ref)` (or
44
+ `useWindowHandle(ref)`) is the number you hand the host, out of band, the way
45
+ `xterm -into` is handed one. On X11 it is the window's XID and the host
46
+ reparents it. On Windows it is a composition surface handle and the host
47
+ binds it to a visual of its own, because a window there stops presenting the
48
+ moment it becomes a child ([windows-embedding.md](windows-embedding.md) has
49
+ the measurements). `null` means this backend has no way to be a guest — the
50
+ capability, not a failure.
51
+
52
+ The protocol underneath is [XEmbed](http://specifications.freedesktop.org/xembed/0.5/),
53
+ implemented in ntk (`XEmbedSocket`, ntk ≥ 7.4.0). This page is the React half:
54
+ what the element does, what it promises about a window it does not own, and
55
+ what it does with the keyboard.
56
+
57
+ ## Two ways in
58
+
59
+ **You have a window id.** Pass it as `windowId`. The client is added to the
60
+ save set, reparented into the node's own X window, sized to the node's rect
61
+ and told where it is.
62
+
63
+ **You are about to start the program.** Then there is no id yet — a program
64
+ like `xterm -into WID` or `mpv --wid=WID` has to be _given_ a window before it
65
+ makes one. Leave `windowId` out, and the node adopts whatever turns up inside
66
+ it; `onReady` hands you the id to spawn into:
67
+
68
+ ```jsx
69
+ <foreign
70
+ style={{ flexGrow: 1 }}
71
+ onReady={({ windowId }) => spawn('xterm', ['-into', String(windowId)])}
72
+ onClientGone={() => close(paneId)}
73
+ />
74
+ ```
75
+
76
+ `onReady` fires as soon as the node has an X window, which is before anything
77
+ is in it — that is the point.
78
+
79
+ ## Most clients do not speak XEmbed
80
+
81
+ `xterm -into`, `mpv --wid`, VLC and Wine set no `_XEMBED_INFO` and want plain
82
+ reparenting. A missing property is not an error and not a fallback: it is the
83
+ ordinary case, and it is what makes a terminal pane work at all. `onEmbedded`
84
+ reports which happened:
85
+
86
+ ```jsx
87
+ onEmbedded={({ id, xembed, version }) => { … }}
88
+ ```
89
+
90
+ `xembed: false` means plain reparenting — the client was mapped immediately
91
+ and no `_XEMBED` messages will pass. `xembed: true` means the client answered,
92
+ `version` is the protocol version both sides settled on, and from then on the
93
+ client is mapped and unmapped as its own `XEMBED_MAPPED` bit asks.
94
+
95
+ ## The window is not yours
96
+
97
+ Unmounting a `<foreign>` **reparents the client back to the root window and
98
+ drops it from the save set. It never destroys it.** A React tree changing
99
+ shape is not a reason for another application to lose its window, and the
100
+ first test in `test/foreign.test.js` is there to keep it that way.
101
+
102
+ The same holds for `windowId` changing: the client that is leaving is handed
103
+ back before the new one is taken.
104
+
105
+ What this costs is an ordering rule, and it is the one thing worth knowing
106
+ about the implementation: the reparent is issued **synchronously** during
107
+ teardown, because `WindowNode` destroys its own X window in the same turn and
108
+ `DestroyWindow` takes every inferior with it. The save set does not cover
109
+ that — X processes it when a _connection_ closes, not when a window is
110
+ destroyed.
111
+
112
+ `onClientGone` fires when the client is destroyed by its own process, or
113
+ reparented away by someone else. Nothing further is sent to a dead id.
114
+
115
+ ### Letting go really means letting go
116
+
117
+ A released client is a top-level window again, and **a running window manager
118
+ will manage it** — frame it, put it in the taskbar, place it wherever its
119
+ policy says. That is the right outcome for an unmount (`examples/foreign.jsx`
120
+ is built on it: detach the pane and the terminal is simply a terminal window
121
+ on the desktop), and it is a trap for one thing:
122
+
123
+ > **Do not hand a client from one `<foreign>` to another by unmounting the
124
+ > first and mounting the second with the same `windowId`.**
125
+
126
+ The window is parked at the root for the moment in between, which is long
127
+ enough to be offered to the window manager. The WM's `ReparentWindow` into a
128
+ frame of its own then arrives _after_ the second pane has taken the client,
129
+ undoes it, and the second pane reports `onClientGone` — for a client that is
130
+ alive and now framed on the desktop. It is a race with another process, so it
131
+ does not always happen, which is worse than if it always did.
132
+
133
+ Change `windowId` on **one** node instead. That path releases and re-embeds
134
+ in order and is tested; the client is only ever at the root while nothing
135
+ else wants it. (Re-embedding a window the WM has already framed also works —
136
+ the reparent pulls it out and the WM unmanages it — the hazard is only the
137
+ moment right after a release.)
138
+
139
+ ## Layout and stacking
140
+
141
+ The rect comes from yoga like any other child, and every change moves the
142
+ client and sends it the synthetic ICCCM 4.1.5 `ConfigureNotify` — root-relative
143
+ coordinates, which is the question a client actually asks and which its own
144
+ `ConfigureNotify` (relative to the container it now sits in) cannot answer.
145
+
146
+ Two consequences of it being a real X window, both shared with
147
+ [`<glarea>`](elements.md#glarea):
148
+
149
+ - **Nothing drawn can overlap it.** A child X window is stacked above
150
+ everything painted in its parent. An overlay — a HUD, a "reconnecting…"
151
+ banner — belongs in a sibling `<popup>`. `<foreign>` takes no children and
152
+ says so rather than laying out something that could never be seen.
153
+ - **Pointer events over it are the client's.** They never reach react-x11's
154
+ hit testing, which is exactly right for a terminal or a video surface.
155
+
156
+ `backgroundColor` in the `style` is what shows in the rect before a client
157
+ arrives and after one leaves — it is the container window's background, painted
158
+ by the server. There is no separate prop for it, so the name is the one used
159
+ everywhere else.
160
+
161
+ ## Focus, and who gets the keys
162
+
163
+ X gives the input focus to one window. XEmbed's answer is that the embedder
164
+ holds the real focus and gives the client a _logical_ one by message, and the
165
+ classic implementation of that is a **focus proxy**: an invisible InputOnly
166
+ window that takes the X focus and forwards keystrokes into the client with
167
+ `SendEvent`.
168
+
169
+ **react-x11 does not use a proxy**, and the reason matters if you are reading
170
+ the code expecting one:
171
+
172
+ - A proxy inside our own toplevel means the toplevel sees `FocusOut` the moment
173
+ the client is focused. react-x11 reads that as the window losing focus —
174
+ carets stop blinking, `<window onBlur>` fires — and the element would be told
175
+ to blur the client it had just focused.
176
+ - A key that reaches a proxy has already gone past every handler in the React
177
+ tree, so an application chord could never be checked first.
178
+
179
+ So the X focus stays on your window, and the rule is:
180
+
181
+ > While a `<foreign>` holds focus, the application's handlers see every key
182
+ > first. Anything they do not consume is forwarded to the client.
183
+
184
+ `preventDefault()` is how they consume it — the same word, and the same
185
+ ordering, that lets a `<textinput>` keep Tab as an indent key:
186
+
187
+ ```jsx
188
+ <window
189
+ onKeyDown={(ev) => {
190
+ if (ev.ctrlKey && ev.keysym === XK_t) {
191
+ ev.preventDefault(); // the app's chord; the terminal never sees it
192
+ newTab();
193
+ }
194
+ }}
195
+ >
196
+ <foreign windowId={id} style={{ flexGrow: 1 }} />
197
+ </window>
198
+ ```
199
+
200
+ **A menu accelerator is not a handler**, and this is the one place that shows.
201
+ [Accelerators](events.md#accelerators) run _after_ the focused element's own
202
+ default action, and `<foreign>`'s is to forward the key and consume it — so
203
+ while an embedded terminal has focus its Ctrl+C is the terminal's, not the
204
+ Edit menu's, which is what a user of that terminal expects. An application
205
+ chord that must win everywhere goes in an `onKeyDown` like the one above,
206
+ which is checked before either.
207
+
208
+ Everything else about focus is the ordinary focus manager:
209
+
210
+ | what happens | what the client is told |
211
+ | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
212
+ | the node takes focus by click or `focus()` | `XEMBED_WINDOW_ACTIVATE`, then `XEMBED_FOCUS_IN` detail `CURRENT` |
213
+ | Tab into it | `FOCUS_IN` detail `FIRST` — focus your first widget |
214
+ | Shift+Tab into it | `FOCUS_IN` detail `LAST` |
215
+ | focus leaves, or the window deactivates | `FOCUS_OUT`, then `WINDOW_DEACTIVATE` |
216
+ | the client sends `XEMBED_REQUEST_FOCUS` | the node is focused _through the focus manager_, so `:focus`, `:focus-within`, the previous node's `onBlur` and the AT-SPI bridge all see it |
217
+ | the client sends `FOCUS_NEXT` / `FOCUS_PREV` | focus moves to the next/previous react-x11 focusable — Tab crosses the boundary as if it were one application |
218
+
219
+ A `<foreign>` is focusable by default, like a `<textinput>`. `focusable={false}`
220
+ opts out, which is what a video surface wants.
221
+
222
+ Tab is _forwarded_ rather than acted on: an XEmbed client has a tab chain of its
223
+ own and says so with `FOCUS_NEXT` when it runs off the end of it. A client that
224
+ speaks no XEmbed — a terminal — keeps Tab for good, which is what a terminal is
225
+ for; click elsewhere to leave.
226
+
227
+ **Dead keys and Compose stay out of it.** react-x11's own composition
228
+ ([events.md](events.md#composition)) does not run while a `<foreign>` holds
229
+ focus: the client is another program with an input method of its own, and an
230
+ accent composed on this side would swallow the `KeyPress` on its way there
231
+ and hand back a character with nowhere to put it. The raw key is forwarded
232
+ and the client composes it, which is also what makes `é` work in an embedded
233
+ terminal whatever the toolkit inside it is.
234
+
235
+ ### The one gap
236
+
237
+ X delivers a key to the deepest descendant of the focus window that contains the
238
+ pointer. So while the pointer is _over_ the embedded client, the client receives
239
+ keys directly and no handler of yours runs — application chords do not work
240
+ there. They work everywhere else in the window, which is the trade a focus proxy
241
+ would make in the other direction.
242
+
243
+ ## Reparenting window managers
244
+
245
+ A `<foreign>` inside a toplevel that the window manager has reparented into a
246
+ frame is offset by that frame, and the root-relative coordinates in the
247
+ synthetic `ConfigureNotify` have to account for it. react-x11 tracks the
248
+ toplevel's screen origin from `ReparentNotify`/`ConfigureNotify` and asks the
249
+ server rather than trusting reported geometry — the same problem
250
+ `src/components/anchor.js` solves for popup placement.
251
+
252
+ ## Props
253
+
254
+ | prop | |
255
+ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------- |
256
+ | `windowId` | the X window to embed. Changing it hands the old client back first. Omit it to adopt whatever is put inside the node instead. |
257
+ | `onReady` | `({ windowId })` — the node's own container id, offered before anything is embedded, to spawn a program into |
258
+ | `onEmbedded` | `({ id, xembed, version })` — a client is in |
259
+ | `onClientGone` | destroyed, or reparented away by someone else |
260
+ | `onRequestFocus` | the client asked for the focus (it is then given through the focus manager) |
261
+ | `onError` | the embed failed — no such window, or it went away mid-handshake. Without a handler, a console warning. |
262
+ | `focusable` | default `true`; takes part in the focus manager and the tab chain |
263
+ | `style` | layout as usual; `backgroundColor` is what shows with no client in |
264
+
265
+ ## Example
266
+
267
+ `examples/foreign.jsx` spawns a program into a pane and then lets go of it,
268
+ which is the whole element in one click: the pane disappears and the terminal
269
+ is still there, framed by the window manager, as if it had started on its own.
270
+
271
+ ```sh
272
+ npm run examples:foreign # xterm, if you have it
273
+ FOREIGN_CMD="mpv --wid=%WID% video.mp4" npm run examples:foreign
274
+ ```
275
+
276
+ ## What is not here yet
277
+
278
+ - **`<TrayHost>`** — owning the `_NET_SYSTEM_TRAY_S<screen>` manager selection
279
+ and rendering a `<foreign>` per docked icon, which is what makes a react-x11
280
+ panel a real desktop shell. The system tray is XEmbed's biggest surviving
281
+ consumer.
282
+ - **The plug side** — a react-x11 root that renders _into_ someone else's
283
+ socket, for shipping as a tray icon or a panel applet. Lower value and
284
+ deliberately last: GTK4 removed `GtkPlug` and Qt dropped `QX11Embed*` after
285
+ Qt4, so the population of hosts willing to embed us is shrinking while the
286
+ population of clients we can host is not.
287
+
288
+ Both are tracked in [issue #269](https://github.com/sidorares/react-x11/issues/269).