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/gl.md ADDED
@@ -0,0 +1,170 @@
1
+ # 3D: `<glarea>` and the three ways underneath it
2
+
3
+ react-x11 gives you a **GL surface in the layout** and nothing above it. A
4
+ scene graph — meshes, materials, lights, post-processing — is
5
+ [`@react-x11/components/three`](ecosystem.md), which brings its own
6
+ reconciler and renders through the surface described here.
7
+
8
+ ```jsx
9
+ <glarea
10
+ style={{ flexGrow: 1 }}
11
+ clearColor="#0b1021"
12
+ frameLoop="always"
13
+ onCreated={(gl) => gl.Enable(gl.DEPTH_TEST)}
14
+ onDraw={(gl, { width, height }) => {
15
+ /* one frame */
16
+ }}
17
+ />
18
+ ```
19
+
20
+ `examples/viewer3d.jsx` is the worked example: a model viewer that orbits,
21
+ and the display-list discipline the indirect backend demands.
22
+
23
+ ## The three ways
24
+
25
+ `<glarea>` and `onDraw` are the same everywhere. What is underneath is not:
26
+ these are different APIs, not three spellings of one, and which one a
27
+ connection has decides what `onDraw` can do.
28
+
29
+ | | **direct** (X11) | **indirect** (X11) | **Cocoa** |
30
+ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
31
+ | how it draws | OpenGL ES 2 on the GPU, in two flavors: on Linux frames reach the server as dma-buf descriptors over DRI3 + Present, and on macOS/XQuartz the server exports the window's surface over Apple-DRI and CGL draws into it | GL commands encoded into the X connection | CGL — OpenGL 4.1 core on Metal — into IOSurface-backed framebuffers, presented as a sublayer |
32
+ | shaders | **yes**, GLSL ES 1.00 | none; the protocol encodes no shader objects | **yes** |
33
+ | render targets | **yes** — framebuffer objects | none; the protocol encodes no framebuffer objects | **yes** — it draws into one |
34
+ | geometry | vertex buffers on the GPU | immediate mode compiled into display lists | vertex buffers on the GPU |
35
+ | lighting | per fragment | per vertex | per fragment |
36
+ | cost per frame | one Present request | matrices, material state and one `CallList` per mesh | a swap between two targets; the WindowServer composites |
37
+ | where it runs | a local connection, plus ntk's optional `x11-dri` addon: a Linux server with DRI3, or macOS/XQuartz with Apple-DRI (ntk 8.4.0) | any server that allows indirect contexts, including over a network | any Mac, on the Cocoa backend — it needs the same optional `x11-dri` addon and nothing else |
38
+
39
+ The Cocoa column is the X11 direct path with the X-specific half swapped
40
+ out: the same `x11-dri` CGL context and the same WebGL-shaped `gl` table,
41
+ but instead of attaching to a surface an X server exported, frames render
42
+ into IOSurfaces and present by handing the surface's id to the area's own
43
+ `CALayer`. `glPolicy: 'indirect'` is the one setting that is an error there
44
+ and says so — GLX is a protocol, and there is no server to speak it to.
45
+
46
+ The two other backends have no column because they hand `onDraw` the same
47
+ direct API the Cocoa one does, and report `gl.backend === 'direct'` too. On
48
+ **win32** a `<glarea>` is a WGL core context drawing into a Direct3D texture
49
+ that is copied into a composition swap chain under the window's own visual
50
+ ([windows-gl.md](windows-gl.md)), so it composites with the window's alpha
51
+ where an X11 surface does not. On **Wayland** it is not a surface of its own
52
+ at all: it is a rectangle of the window's backing target, drawn by the
53
+ window's own GLES context between the tree's 2D paint and its children's
54
+ panes (`src/wayland/glarea.js`).
55
+
56
+ On X11 the default is indirect, because it is what react-x11 has always
57
+ used. Turn the other on per app:
58
+
59
+ ```jsx
60
+ const root = await createRoot({ glPolicy: 'auto' });
61
+ ```
62
+
63
+ `'auto'` uses direct where it is available and indirect otherwise, which is
64
+ usually what you want: most modern desktops **refuse** indirect GLX — Xorg
65
+ 1.17 and later, and Xwayland, ship with it off — and those are exactly the
66
+ machines where direct works. `'direct'` and `'off'` are the strict forms, and
67
+ `'indirect'` is the default. One run can be switched without touching code:
68
+
69
+ ```sh
70
+ NTK_GL_POLICY=direct npm start
71
+ ```
72
+
73
+ Which flavor a connection got is `app.glCapabilities().flavor` — `'dri3'` or
74
+ `'appledri'`. Both spell the context the same way, so nothing above the
75
+ policy has to branch on it; it is worth reading when a machine that should
76
+ have direct does not.
77
+
78
+ Everything about how the backend is chosen and why it might be unavailable —
79
+ the `GLError` codes, `app.glCapabilities()`, the addon — is ntk's, and is
80
+ documented in [ntk's context-gles.md](https://github.com/sidorares/ntk/blob/master/docs/context-gles.md).
81
+
82
+ `npm run labs:direct-gl` is the shader path on a real display, and reports
83
+ both the flavor and which backend actually drew.
84
+
85
+ ## Raw GL through `onDraw`
86
+
87
+ `onDraw(gl, { width, height, node })` hands you the context itself, and
88
+ there are two spellings of GL to write against — camelCase ES 2 against
89
+ PascalCase OpenGL 1.x. Nothing translates between them. Branch on
90
+ `gl.backend`, which is `'direct'` or `'indirect'`; the Cocoa path reports
91
+ `'direct'`, because that is the API it hands you:
92
+
93
+ ```jsx
94
+ <glarea
95
+ onDraw={(gl) => {
96
+ if (gl.backend === 'direct') gl.clear(gl.COLOR_BUFFER_BIT);
97
+ else gl.Clear(gl.COLOR_BUFFER_BIT);
98
+ }}
99
+ />
100
+ ```
101
+
102
+ Code written against one spelling will not run on the other, which is why
103
+ the default X11 policy does not switch under an app that never asked for it
104
+ — and why `examples/viewer3d.jsx` reports which one it got rather than
105
+ pretending it can draw on both. It also means a scene written for the direct
106
+ API runs unchanged on the Cocoa backend, and one written for indirect GLX
107
+ does not run there at all.
108
+
109
+ **On the indirect backend, geometry belongs in a display list.** Every
110
+ immediate-mode vertex is a command on the wire, so a mesh re-sent per frame
111
+ costs kilobytes per frame while a compiled list costs one `CallList`. Names
112
+ are yours to choose — `GenLists` is a round trip, and this is the backend
113
+ where round trips are the thing to avoid.
114
+
115
+ ## Input
116
+
117
+ The pointer over a surface is the tree's, on every backend: `onMouseDown`,
118
+ `onMouseMove`, `onMouseUp`, `onClick`, `onWheel` and the rest fire at the
119
+ `<glarea>` — or at a child drawn over it (below) — and bubble, with
120
+ `ev.localX`/`localY` measured from the target's corner, which is what a
121
+ scene picks with. Nothing needs to listen on the surface's own window, and
122
+ nothing should: on X11 a listener there takes the event away from the tree
123
+ ([elements.md](elements.md#glarea) says why).
124
+
125
+ ## 2D over the surface
126
+
127
+ A `<glarea>`'s children are drawn above it — a legend, a toolbar, a label
128
+ over the scene — laid out in its box and hit before it. They are ordinary
129
+ elements. Core paints them on panes stacked over the surface, from the
130
+ window's own frame. On the Cocoa backend Core Animation composites the
131
+ panes with the GL frame, translucency and all. On X11 a pane is an opaque
132
+ child window, so what a child leaves unpainted shows the surface's
133
+ `clearColor` ([elements.md](elements.md#glarea) has the table).
134
+ `useSupports('glOverlay')` asks whether a connection draws them;
135
+ `examples/labs/gl-overlay.jsx` runs one on both backends.
136
+
137
+ The answer is no on XQuartz. The macOS window server composites every GL
138
+ surface there above everything the X server draws, so nothing can be drawn
139
+ over one. The children are laid out and not drawn, and a development build
140
+ warns once. On macOS the Cocoa backend is the one that composites a HUD
141
+ over GL.
142
+
143
+ So a HUD is not GL's business: its text is set by the app's own text
144
+ engine, its controls take their own input, and nothing is rasterized into a
145
+ texture.
146
+
147
+ ## When there is no surface at all
148
+
149
+ `onError(err)` fires when no GL context could be made: no GLX, indirect
150
+ disabled, no matching visual. `err.code` is one of ntk's `GLXError` values,
151
+ `GLX_INDIRECT_DISABLED` being the usual one. Without a handler the failure is
152
+ a console warning and the element draws nothing, which looks like a bug in
153
+ your scene rather than a fact about the machine — so handle it and say what
154
+ the reader can do.
155
+
156
+ ## Testing
157
+
158
+ A GL app is tested on **what it emits**, not on pixels — which is also the
159
+ only option, because GL renders where `GetImage` cannot read it (see
160
+ [glx.md](glx.md)):
161
+
162
+ - `test/glarea.test.js` drives node-x11's in-process X server with its GLX
163
+ emulator registered, so a frame's GL calls land on a `RecordingBackend`.
164
+ No display, no GPU, no addon.
165
+ - `test/viewer3d.test.js` does the same for the example, and says in its
166
+ header which claim that harness cannot see and where it is measured
167
+ instead.
168
+
169
+ See also [glx.md](glx.md) for the indirect backend's design, and what its
170
+ transport can never do.
@@ -0,0 +1,278 @@
1
+ # The global menu
2
+
3
+ Some desktops put an application's menu bar in their own panel rather than in
4
+ its window — Unity's design, still shipped by KDE Plasma's Application Menu
5
+ widget, by `vala-panel-appmenu`, and by several GNOME extensions. On one of
6
+ those, an app that draws its own menu bar is drawing a second one.
7
+
8
+ react-x11 notices and hands the menu over. There is **nothing to configure and
9
+ nothing to call**:
10
+
11
+ ```jsx
12
+ import { MenuBar } from 'react-x11';
13
+
14
+ <MenuBar
15
+ menus={[
16
+ {
17
+ label: 'File',
18
+ items: [
19
+ { label: 'New', shortcut: [['Control', 'N']], onSelect: newFile },
20
+ { type: 'separator' },
21
+ { label: 'Save As…', enabled: false },
22
+ ],
23
+ },
24
+ ]}
25
+ />;
26
+ ```
27
+
28
+ That is the whole of it, and it is the same line every time. Where the
29
+ platform has somewhere to put a menu, `MenuBar` renders nothing and the
30
+ platform shows this one; where it does not, `MenuBar` draws the bar itself,
31
+ exactly as before.
32
+
33
+ | where the menu goes | when |
34
+ | ------------------------------------------------------ | ------------------------------------------------------------------------------------- |
35
+ | **the macOS menu bar**, at the top of the screen | the Cocoa backend, always |
36
+ | **the desktop's panel**, over `com.canonical.dbusmenu` | a panel is running one of the applets above |
37
+ | **the window**, drawn by `MenuBar` | everything else — a stock GNOME session, XQuartz, an ssh session, a bare `startx`, CI |
38
+
39
+ The rest of this page is about the middle row, which is the one with a
40
+ protocol in it. The macOS row is
41
+ [macos.md §Menus](macos.md#menus-the-global-menu-finally-at-home): the
42
+ adapter is `src/cocoa/globalmenu.js`, it consumes the same `menus` array
43
+ through the same snapshot machinery, and because a menu bar is a platform
44
+ constant rather than a service that may or may not be running, it takes the
45
+ menu the moment it is installed — the drawn fallback never renders there.
46
+
47
+ ## The item vocabulary is the protocol's
48
+
49
+ `menus` is a plain data prop rather than JSX children, and its item shape is
50
+ `com.canonical.dbusmenu`'s. That is what makes the rest work: **the identical
51
+ array serialises**, so there is one authoring model rather than a drawn one and
52
+ an exported one that drift apart.
53
+
54
+ | property | meaning |
55
+ | -------------------------- | ----------------------------------------------------------- |
56
+ | `label` | the row's text |
57
+ | `type` | `'separator'` draws a rule; absent means an ordinary row |
58
+ | `items` | a submenu |
59
+ | `enabled` | defaults to `true`; `false` dims the row and makes it inert |
60
+ | `visible` | defaults to `true`; `false` removes it |
61
+ | `shortcut` | `[['Control', 'S']]` — drawn, exported **and bound** |
62
+ | `toggleType`/`toggleState` | `'checkmark'` or `'radio'`, and `0` / `1` / `-1` |
63
+ | `iconName` | an icon-theme name, for the panel's menu |
64
+ | `iconData` | raw PNG bytes, for an icon that is not in a theme |
65
+ | `disposition` | `'normal'`, `'informative'`, `'warning'`, `'alert'` |
66
+ | `onSelect(item)` | runs whichever menu the click came from |
67
+ | `key` | identity, when the label is not stable |
68
+
69
+ `toggleState` has **three** values, not two: `-1` is _indeterminate_ — a "Bold"
70
+ that is on for part of the selection — and it draws as a dash rather than as
71
+ unchecked, because "off" is a different claim from "it depends".
72
+
73
+ `icon` is the one thing that does not cross the bus. It is a react-x11 drawing
74
+ callback ([components.md](components.md#menubar--contextmenu)) and the desktop
75
+ cannot call a function, so an item that wants an icon in both menus carries
76
+ `icon` for ours and `iconName` for the panel's.
77
+
78
+ ### Shortcuts are a list, not a string
79
+
80
+ ```js
81
+ shortcut: [['Control', 'S']];
82
+ ```
83
+
84
+ The outer array is a list of _alternatives_; each inner one is modifier tokens
85
+ (`Control`, `Alt`, `Shift`, `Super`) ending in the key. Only the first is
86
+ drawn — a row has one shortcut column.
87
+
88
+ **On macOS, `Control` means ⌘.** A cross-platform app writes its shortcuts
89
+ in the primary modifier, and the Mac's primary modifier is Command, not its
90
+ own ⌃ — so `[['Control', 'S']]` becomes ⌘S in the menu bar, which is what a
91
+ Mac user expects Save to be. `Super` maps there too; `Shift` and `Alt` are
92
+ themselves. A chord the `NSMenu` spelling cannot express is simply not
93
+ printed beside the item — the binding still fires, because the app's own
94
+ window answers it (below). The text controls' own chords follow the same rule
95
+ there, and so does the edit menu that prints them
96
+ ([events.md](events.md#primary-modifier)), and so do the window's own
97
+ bindings: `useAccelerator`, and a drawn `MenuBar` or `ContextMenu`, which
98
+ prints `⌘S` beside the row ([events.md](events.md#the-chord)).
99
+
100
+ The key is named the way GDK names it, so `plus` rather than `+` and `Prior`
101
+ rather than `PgUp`, because that is what a panel's importer parses. Menus print
102
+ the friendly form: `[['Control', 'plus']]` draws as `Ctrl++`.
103
+
104
+ **It is a binding**, and the app writes it once. A mounted `MenuBar` answers
105
+ its own items' chords: Ctrl+S fires the item's `onSelect` without the menu
106
+ being opened, gated by that item's own `enabled` and `visible`
107
+ ([events.md](events.md#accelerators)).
108
+
109
+ That matters most in exactly this case. When the panel draws the menu, the
110
+ **panel is not going to deliver the key** — it has no idea the window's
111
+ keyboard is where the user is — so the app's own window still has to, and
112
+ `MenuBar` keeps its bindings when it stops drawing. `accelerators={false}`
113
+ turns them off for an application with a dispatcher of its own.
114
+
115
+ The macOS menu bar is the one delegate that **does** deliver keys: it runs an
116
+ item on the ⌘ chord it installed as its key equivalent, and it does that after
117
+ the window has already seen the key. So there `MenuBar` reserves those chords
118
+ — no binding in the window answers them, its own included, and the item runs
119
+ once — and keeps answering the rest, the chords no `NSMenu` key equivalent can
120
+ spell ([events.md](events.md#the-macos-menu-bar)).
121
+
122
+ ## What happens on the wire
123
+
124
+ The D-Bus path, in enough detail to recognise it in `dbus-monitor` and to
125
+ know what to look at when a menu does not appear. None of this applies on
126
+ the Cocoa backend, where there is no wire and no registrar — installing an
127
+ `NSMenu` is the whole of it.
128
+
129
+ 1. **Detection.** Does something own `com.canonical.AppMenu.Registrar` right
130
+ now? Only ownership counts — see the trap below.
131
+ 2. **Export.** The menu is served at `/com/react_x11/menus/<xid>` on the
132
+ process's existing bus connection ([dbus.md](dbus.md)), so the desktop sees
133
+ one application rather than several.
134
+ 3. **Registration.** `RegisterWindow(xid, path)`. The service name is inferred
135
+ from the sender, so only the path is supplied.
136
+ 4. **The KDE properties**, in addition: `_KDE_NET_WM_APPMENU_SERVICE_NAME` and
137
+ `_KDE_NET_WM_APPMENU_OBJECT_PATH`, written as `STRING`/format 8. Plasma
138
+ reads these off the window instead of asking a registrar, and runs a
139
+ registrar _as well_ — which of the two a given version prefers is not
140
+ something to bet a menu on.
141
+ 5. **Only then** does `MenuBar` stop drawing. `RegisterWindow` returning is the
142
+ evidence; a registrar that refuses the call leaves the menu where the user
143
+ can still reach it.
144
+
145
+ It is followed for the life of the window, not sampled once: `NameOwnerChanged`
146
+ moves the menu into the panel when one starts and back into the window when one
147
+ exits, with no re-render on the app's part.
148
+
149
+ ### The trap: activatable is not running
150
+
151
+ Ubuntu ships `com.canonical.AppMenu.Registrar` as a D-Bus **activatable**
152
+ service, so the name resolves even with no panel installed. This is the exact
153
+ opposite of the rule `hasService()` follows for portals, and getting it wrong
154
+ is silent and total — an app with no menu anywhere.
155
+
156
+ A registrar is not the feature; it is a directory the panel reads. Nothing
157
+ starts one except a panel starting up. So react-x11 asks `ListNames` for a live
158
+ owner, and every call it then makes carries D-Bus's `NO_AUTO_START` flag —
159
+ without which the tidy-up `UnregisterWindow` sent after a panel dies would
160
+ _launch a fresh registrar_, which would own the name with nothing drawing the
161
+ menus it collects.
162
+
163
+ ## Seeing it work, without a desktop that has one
164
+
165
+ `com.canonical.dbusmenu` has no reference client you can install on its own —
166
+ the consumers are panel applets welded into Plasma, into `vala-panel`, or into
167
+ a GNOME extension. So there is one in this repo:
168
+
169
+ ```bash
170
+ npm run globalmenu:host
171
+ ```
172
+
173
+ It owns the registrar name — it _is_ the panel — and prints every menu handed
174
+ to it. In another terminal:
175
+
176
+ ```bash
177
+ npm run examples:menu
178
+ ```
179
+
180
+ The window's own menu bar disappears, which is the feature, and the menu
181
+ appears in the host. Type an item's id and press Enter to activate it; the
182
+ example's own state changes, and the property update comes back the other way.
183
+ Ctrl-C the host and the bar returns to the window.
184
+
185
+ On a machine that really does have a panel, `npm run globalmenu:host -- --watch`
186
+ attaches to the live registrar rather than competing for the name.
187
+
188
+ ## Turning it off
189
+
190
+ ```jsx
191
+ <MenuBar menus={menus} globalMenu={false} />
192
+ ```
193
+
194
+ ```bash
195
+ REACT_X11_NO_GLOBAL_MENU=1
196
+ ```
197
+
198
+ ```jsx
199
+ await createRoot({ desktop: { globalMenu: false } });
200
+ ```
201
+
202
+ The prop is for one bar. The other two are process-wide: the environment
203
+ variable needs no application change, which is what a user working around a
204
+ panel that renders something badly actually has, and the `createRoot` option
205
+ is the one an embedder can reach without also setting it for every child
206
+ process it spawns — see [desktop.md](desktop.md#turning-the-desktop-off),
207
+ where `desktop: false` turns this off along with the other two integrations
208
+ that talk to the session bus.
209
+
210
+ ## Drawing your own bar
211
+
212
+ `MenuBar` does all of this itself. A component that draws its own can have the
213
+ same behaviour:
214
+
215
+ ```jsx
216
+ import { useGlobalMenu } from 'react-x11';
217
+
218
+ function Bar({ menus }) {
219
+ const exported = useGlobalMenu(menus, { onSelect });
220
+ if (exported) return null; // the panel has it
221
+ return; /* … your bar … */
222
+ }
223
+ ```
224
+
225
+ ## Known limits, so they are not rediscovered as bugs
226
+
227
+ - **Stock GNOME has no global menu bar at all**, so nothing owns the registrar
228
+ name and the in-window bar stays. That is correct rather than broken. The
229
+ extensions that add one do own it, and then this works.
230
+ - **`ContextMenu` is not exported**, and should not be. The registrar's unit is
231
+ a window's menu _bar_; a right-click menu belongs where the pointer is.
232
+ - **One menu per window.** The registrar's entry, the object path and the KDE
233
+ properties all belong to the window, so a second `MenuBar` in a window that
234
+ already has one stays drawn in the window until the first unmounts or turns
235
+ `globalMenu` off, and only then takes the panel. A bar remounted with a new
236
+ `key`, or switched off and on again, waits the same way for the one it
237
+ replaces to finish leaving, which takes a round trip or two.
238
+ - **`AboutToShow` cannot be answered honestly.** dbusmenu wants a synchronous
239
+ "do you need to rebuild this submenu", and a React `setState` has not
240
+ rendered by the time the reply must go out. react-x11 answers `false`, runs
241
+ `onAboutToShow`, and emits `LayoutUpdated` when the new items actually
242
+ serialise — shells listen for that unconditionally, so a lazily-built submenu
243
+ fills in one round trip later than a synchronous toolkit manages. Answering
244
+ `true` instead would be worse: the shell would immediately fetch a subtree
245
+ that has not been built and cache the old one.
246
+ - **`org.gtk.Menus`/`org.gtk.Actions` is deliberately not exported.** Handling
247
+ both serialisations is right for a panel and wrong for an app — traffic flows
248
+ _towards_ dbusmenu, not away from it. Plasma ships `gmenu-dbusmenu-proxy` to
249
+ convert GTK's menus into dbusmenu, and the GNOME extensions that render
250
+ third-party menus consume dbusmenu. A second exporter would be roughly double
251
+ the work for no consumer the first does not already reach.
252
+ - **No bus is a first-class configuration.** Everything here degrades to
253
+ "draw the bar" without logging: no `dbus-native` (Node 20), no
254
+ `$DBUS_SESSION_BUS_ADDRESS`, a connection that fails. See
255
+ [dbus.md](dbus.md).
256
+
257
+ ## Performance, and the one thing worth knowing
258
+
259
+ A menu is re-serialised whenever the `menus` array changes identity, which for
260
+ an app that builds it inline is every render. That part is cheap. What is not
261
+ cheap is the signal it produces, and the difference is the reason this feature
262
+ has a diff at all:
263
+
264
+ - ids and children identical, only properties differ → `ItemsPropertiesUpdated`,
265
+ **revision unchanged**;
266
+ - structure changed → `revision++` and `LayoutUpdated(revision, parent)`, with
267
+ `parent` the nearest common ancestor of what changed rather than `0`.
268
+
269
+ Plasma re-fetches a whole subtree on `LayoutUpdated` and only patches on
270
+ `ItemsPropertiesUpdated`. A menu whose one check mark flipped must therefore
271
+ not bump the revision — which is exactly what a naive
272
+ `setState` → `LayoutUpdated(++rev, 0)` does on every keystroke, and it turns one
273
+ property write into a re-walk of every menu in the application.
274
+
275
+ Item ids are keyed on the path of `key ?? label` down the tree — the same
276
+ identity `MenuBar` already uses for its React keys — so they survive a
277
+ re-render, and they are never reused, because a shell has properties cached
278
+ against them.
package/docs/glx.md ADDED
@@ -0,0 +1,137 @@
1
+ # 3D over indirect GLX
2
+
3
+ How a 3D scene works over this transport, and what the transport can never
4
+ do. `<glarea>` and the three ways underneath it are [gl.md](gl.md); the
5
+ scene graph that rides on this one is
6
+ [`@react-x11/components/three`](ecosystem.md). This page is the design
7
+ underneath both, kept because the constraints explain most of the API's
8
+ shape — and because they are permanent: they are properties of a wire
9
+ protocol frozen in the 1990s, not of any implementation.
10
+
11
+ On X11 `<glarea>` is a real X child window on a GLX visual, and under the
12
+ default `glPolicy` everything drawn into it travels as **indirect GLX**: the
13
+ GL protocol encoded into the same X connection as everything else. No direct
14
+ rendering, no native bindings, no GPU driver bindings — the same pure-JS
15
+ story as the rest of the stack. (The direct path, which does take a native
16
+ addon, is [gl.md](gl.md)'s.)
17
+
18
+ The cost of that reach is a **fixed-function OpenGL 1.x pipeline**, and that
19
+ single fact decides the rest.
20
+
21
+ ## What the protocol encodes
22
+
23
+ node-x11's `glxrender.js` encodes 70 GL commands:
24
+
25
+ | available | |
26
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | immediate mode | `Begin`/`End`, `Vertex3f`, `Normal3f`, `Color3f/4f`, `TexCoord2f` |
28
+ | matrices | `MatrixMode`, `LoadIdentity`, `LoadMatrixf`, `MultMatrixf`, `Push/PopMatrix`, `Translatef`, `Rotatef`, `Scalef`, `Frustum`, `Ortho` |
29
+ | lighting | `Lightfv`, `LightModelf`, `Materialfv`, `Materialf`, `ColorMaterial`, `ShadeModel` |
30
+ | texturing | `TexImage2D`, `BindTexture`, `TexParameter*`, `TexEnv*`, `TexGen*` |
31
+ | state | `Enable`/`Disable`, `Clear*`, `DepthFunc`, `DepthMask`, `BlendFunc`, `AlphaFunc`, `CullFace`, `FrontFace`, `PolygonMode`, `Viewport`, `Scissor`, `Fog*` |
32
+ | display lists | `CallList`, `ListBase` (+ `NewList`/`EndList`/`DeleteLists` in `glx.js`) |
33
+
34
+ There are **no GLSL shaders, no VBOs, no vertex arrays** (`DrawArrays` and
35
+ `VertexPointer` are not encoded), **no framebuffer objects and no
36
+ instancing**. `ProgramString`/`BindProgram` expose ARB _assembly_ programs,
37
+ which are out of scope.
38
+
39
+ That rules out, permanently and by protocol: shader materials and
40
+ post-processing. Both are implemented on the direct backend, and asking for
41
+ one here throws an error naming the reason rather than rendering something
42
+ that only looks right — the scene graph keeps the list, as `DIRECT_ONLY_KINDS`.
43
+ (`<instancedMesh>` works on both, but as a loop over transforms rather than
44
+ as hardware instancing, which GLX also encodes no way to ask for.)
45
+
46
+ ## Why display lists are mandatory
47
+
48
+ With no vertex arrays, geometry can only be sent as immediate-mode commands.
49
+ A 1 000-triangle mesh is ~3 000 `Vertex3f` + 3 000 `Normal3f` commands —
50
+ about **96 KB per frame** if re-sent every frame, or 5.7 MB/s at 60fps for
51
+ one modest mesh.
52
+
53
+ So each geometry is compiled into a **server-side display list** once, and a
54
+ frame sends only matrices, material state and one `CallList` per mesh. A
55
+ static scene costs O(meshes) requests per frame instead of O(vertices).
56
+
57
+ This is the AGENTS.md "Protocol efficiency" rule set applied literally — use
58
+ server-side primitives, batch, never re-send what the server already has —
59
+ and because it is a protocol property rather than an intention, the scene
60
+ graph's own tests assert it on the encoded command stream: a 6 000-vertex
61
+ sphere compiles once, and the steady-state frame is under 30 GL commands
62
+ with no vertices in it.
63
+
64
+ The cache is keyed by geometry identity. A geometry prop change recompiles
65
+ that one list; a transform or material change is per-frame state only, so the
66
+ same list is shared by meshes that differ in material.
67
+
68
+ ## The shape of the tree
69
+
70
+ ```
71
+ <glarea> host element -> real X child window with a
72
+ GLX visual + its own colormap
73
+ |
74
+ GLContext (ntk) MakeCurrent -> context tag
75
+ |
76
+ Scene node tree (parallel to the drawn-node tree)
77
+ |
78
+ commit: compile dirty geometries -> display lists
79
+ frame: Clear -> camera matrices -> per mesh: PushMatrix,
80
+ MultMatrixf, material state, CallList, PopMatrix -> SwapBuffers
81
+ ```
82
+
83
+ `<glarea>` is one of the four elements that legitimately need an X window of
84
+ their own — with `<window>`, `<popup>` and `<foreign>` (NEXT_STEPS §4): GLX needs its own visual and cannot share the
85
+ XRender pipeline the 2D elements draw through.
86
+
87
+ A scene tree over this surface is **separate from the drawn-node tree** — no
88
+ yoga, no hit testing against the 2D tree, no painting into the parent's 2d
89
+ context. To layout, `<glarea>` is a leaf sized by ordinary layout props.
90
+
91
+ ## The scene graph moved
92
+
93
+ The r3f-shaped names this page used to map — `<mesh>`, the geometries, the
94
+ materials, the lights, the camera prop — belong to
95
+ [`@react-x11/components/three`](ecosystem.md) now, together with the display-
96
+ list cache described above: it keeps its own reconciler and its own indirect
97
+ renderer, built on exactly the constraints this page documents. What stays
98
+ here is the transport, which is what those constraints come from.
99
+
100
+ ## Testing
101
+
102
+ The primary tests are **hermetic and assert the encoded GLX command
103
+ stream**, not pixels: that a geometry compiles to one display list, that a
104
+ frame emits matrices plus `CallList` rather than thousands of `Vertex3f`,
105
+ and that a transform change re-sends no geometry. That is the property that
106
+ actually matters, and checking it needs no GL at all. Core's half is
107
+ `test/glarea.test.js` and `test/viewer3d.test.js`; the scene graph tests its
108
+ own in its own repo.
109
+
110
+ Pixels are the awkward part. On XQuartz, GL renders into a Metal surface the
111
+ compositor owns rather than into the X drawable, so `GetImage` reads back
112
+ white and a 3D screenshot has to be captured by hand from
113
+ a scene on a real server with indirect GLX — which is why
114
+ `npm run screenshots` skips it. The documentation site's playground is the
115
+ exception that renders GL headlessly: node-x11's browser GLX emulator
116
+ replays the same protocol onto WebGL2.
117
+
118
+ ## Open questions
119
+
120
+ - Is there a depth-buffer-capable visual reachable via `GetVisualConfigs` on
121
+ both XQuartz and Xvfb/llvmpipe?
122
+ - Can a `<glarea>` be composited with 2D content drawn over it, or does the
123
+ GL child window always sit on top? Answered: the GL window is always on
124
+ top of the parent's drawing, so the surface's own children are drawn on
125
+ windows of their own stacked above it — opaque ones, one per region they
126
+ reach, since a child window cannot be translucent without a compositor
127
+ (`src/gloverlay.js`, [elements.md](elements.md#glarea)). Except on
128
+ XQuartz, where the window server composites the surface above every X
129
+ window, those included, and the children are not drawn at all.
130
+
131
+ ## Scope discipline
132
+
133
+ Indirect GLX is a 1990s fixed-function pipeline reached over a network
134
+ protocol. The value is _reach_ — 3D in a pure-JS X11 client with no native
135
+ dependencies — not performance or fidelity. A plausible-looking API that
136
+ quietly fails on anything modern would be worse than a small one with honest
137
+ edges, which is why unsupported r3f names throw with the reason.