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/gl.md CHANGED
@@ -26,35 +26,27 @@ and the display-list discipline the indirect backend demands.
26
26
  these are different APIs, not three spellings of one, and which one a
27
27
  connection has decides what `onDraw` can do.
28
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:
29
+ | | **direct** (X11) | **indirect** (X11) | **Cocoa** |
30
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
31
+ | how it draws | OpenGL ES 2 on the GPU (DRI3 + Present on Linux, Apple-DRI on macOS/XQuartz) | GL 1.x commands encoded into the X connection | CGL — OpenGL 4.1 core on Metal — presented as a sublayer |
32
+ | shaders | **yes**, GLSL ES 1.00 | none | **yes** |
33
+ | render targets | **yes** — framebuffer objects | none | **yes** |
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
+ | 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 |
37
+
38
+ The Cocoa column hands `onDraw` the same WebGL-shaped `gl` table as X11
39
+ direct. `glPolicy: 'indirect'` is an error there, and says so: there is no X
40
+ server to speak GLX to.
41
+
42
+ **win32** and **Wayland** have no column because they hand `onDraw` the same
43
+ direct API and report `gl.backend === 'direct'` too. On win32 a `<glarea>` is
44
+ a WGL context composited under the window's own visual, so it composites with
45
+ the window's alpha where an X11 surface does not. On Wayland it is drawn into
46
+ the window's own backing target, between the tree's 2D paint and its
47
+ children's panes, rather than being a surface of its own.
48
+
49
+ On X11 the default is indirect. Turn direct on per app:
58
50
 
59
51
  ```jsx
60
52
  const root = await createRoot({ glPolicy: 'auto' });
@@ -100,17 +92,36 @@ PascalCase OpenGL 1.x. Nothing translates between them. Branch on
100
92
  ```
101
93
 
102
94
  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.
95
+ the default X11 policy does not switch under an app that never asked for it.
96
+ A scene written for the direct API runs unchanged on the Cocoa backend; one
97
+ written for indirect GLX does not run there at all.
108
98
 
109
99
  **On the indirect backend, geometry belongs in a display list.** Every
110
100
  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.
101
+ costs kilobytes per frame (about 96 KB for a 1 000-triangle mesh) while a
102
+ compiled list costs one `CallList`. Names are yours to choose — `GenLists` is
103
+ a round trip, and this is the backend where round trips are the thing to
104
+ avoid.
105
+
106
+ ### What indirect GLX encodes
107
+
108
+ Indirect GLX is a fixed-function OpenGL 1.x pipeline, and these are the
109
+ commands it can carry:
110
+
111
+ | available | |
112
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
113
+ | immediate mode | `Begin`/`End`, `Vertex3f`, `Normal3f`, `Color3f/4f`, `TexCoord2f` |
114
+ | matrices | `MatrixMode`, `LoadIdentity`, `LoadMatrixf`, `MultMatrixf`, `Push/PopMatrix`, `Translatef`, `Rotatef`, `Scalef`, `Frustum`, `Ortho` |
115
+ | lighting | `Lightfv`, `LightModelf`, `Materialfv`, `Materialf`, `ColorMaterial`, `ShadeModel` |
116
+ | texturing | `TexImage2D`, `BindTexture`, `TexParameter*`, `TexEnv*`, `TexGen*` |
117
+ | state | `Enable`/`Disable`, `Clear*`, `DepthFunc`, `DepthMask`, `BlendFunc`, `AlphaFunc`, `CullFace`, `FrontFace`, `PolygonMode`, `Viewport`, `Scissor`, `Fog*` |
118
+ | display lists | `CallList`, `ListBase`, `NewList`/`EndList`/`DeleteLists` |
119
+
120
+ There are **no GLSL shaders, no VBOs, no vertex arrays** (`DrawArrays` and
121
+ `VertexPointer` are not encoded), **no framebuffer objects and no
122
+ instancing**, and ARB assembly programs are not supported. These are limits
123
+ of the protocol, not of an implementation: shader materials and
124
+ post-processing need the direct backend.
114
125
 
115
126
  ## Input
116
127
 
@@ -126,23 +137,17 @@ nothing should: on X11 a listener there takes the event away from the tree
126
137
 
127
138
  A `<glarea>`'s children are drawn above it — a legend, a toolbar, a label
128
139
  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).
140
+ elements. On the Cocoa backend they composite with the GL frame,
141
+ translucency and all. On X11 each is drawn on an opaque pane over the
142
+ surface, so what a child leaves unpainted shows the surface's `clearColor`
143
+ ([elements.md](elements.md#glarea) has the table).
134
144
  `useSupports('glOverlay')` asks whether a connection draws them;
135
145
  `examples/labs/gl-overlay.jsx` runs one on both backends.
136
146
 
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.
147
+ The answer is no on XQuartz: the macOS window server composites every GL
148
+ surface there above everything the X server draws. The children are laid out
149
+ and not drawn, and a development build warns once. On macOS the Cocoa backend
150
+ is the one that composites a HUD over GL.
146
151
 
147
152
  ## When there is no surface at all
148
153
 
@@ -155,16 +160,14 @@ the reader can do.
155
160
 
156
161
  ## Testing
157
162
 
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)):
163
+ Test a GL app on **what it emits**, not on pixels: GL renders where `GetImage`
164
+ cannot read it (on XQuartz a capture reads back white), so a screenshot of a
165
+ window with a `<glarea>` has a hole where the scene is. On the direct
166
+ backend, `gl.readPixels` inside `onDraw` is the only way to see the frame.
161
167
 
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
+ This repo's `test/glarea.test.js` shows the pattern: node-x11's in-process X
169
+ server with its GLX emulator registered, so a frame's GL calls land on a
170
+ `RecordingBackend` — no display, no GPU, no addon.
168
171
 
169
- See also [glx.md](glx.md) for the indirect backend's design, and what its
170
- transport can never do.
172
+ Design notes: [design/gl.md](../design/gl.md), and the indirect backend's
173
+ design in [design/glx.md](../design/glx.md).
@@ -25,10 +25,8 @@ import { MenuBar } from 'react-x11';
25
25
  />;
26
26
  ```
27
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.
28
+ Where the platform has somewhere to put a menu, `MenuBar` renders nothing and
29
+ the platform shows this one; where it does not, `MenuBar` draws the bar itself.
32
30
 
33
31
  | where the menu goes | when |
34
32
  | ------------------------------------------------------ | ------------------------------------------------------------------------------------- |
@@ -36,20 +34,23 @@ exactly as before.
36
34
  | **the desktop's panel**, over `com.canonical.dbusmenu` | a panel is running one of the applets above |
37
35
  | **the window**, drawn by `MenuBar` | everything else — a stock GNOME session, XQuartz, an ssh session, a bare `startx`, CI |
38
36
 
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.
37
+ On macOS the menu bar takes the menu as soon as `MenuBar` mounts and the drawn
38
+ fallback never renders; see
39
+ [macos.md §Menus](macos.md#menus-the-global-menu-finally-at-home).
40
+
41
+ On Linux the panel row is decided live. The menu leaves the window only once a
42
+ **running** owner of `com.canonical.AppMenu.Registrar` has accepted it (a
43
+ registrar that is merely D-Bus-activatable, as Ubuntu ships it, does not
44
+ count), and it moves into the panel when one starts and back into the window
45
+ when one exits, with no re-render on the app's part. KDE's
46
+ `_KDE_NET_WM_APPMENU_*` window properties are written as well. Design notes:
47
+ [design/globalmenu.md](../design/globalmenu.md).
46
48
 
47
49
  ## The item vocabulary is the protocol's
48
50
 
49
51
  `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.
52
+ `com.canonical.dbusmenu`'s: **the identical array serialises**, so the drawn
53
+ menu and the exported one cannot drift apart.
53
54
 
54
55
  | property | meaning |
55
56
  | -------------------------- | ----------------------------------------------------------- |
@@ -67,13 +68,16 @@ an exported one that drift apart.
67
68
  | `key` | identity, when the label is not stable |
68
69
 
69
70
  `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".
71
+ that is on for part of the selection — and draws as a dash rather than as
72
+ unchecked.
73
+
74
+ `icon` is the one thing that does not cross the bus: it is a react-x11 drawing
75
+ callback ([components.md](components.md#menubar--contextmenu)), so an item that
76
+ wants an icon in both menus carries `icon` for ours and `iconName` for the
77
+ panel's.
72
78
 
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.
79
+ Building `menus` inline on every render is fine: only what changed is sent to
80
+ the panel, and item identity (the path of `key ?? label`) survives re-renders.
77
81
 
78
82
  ### Shortcuts are a list, not a string
79
83
 
@@ -85,17 +89,14 @@ The outer array is a list of _alternatives_; each inner one is modifier tokens
85
89
  (`Control`, `Alt`, `Shift`, `Super`) ending in the key. Only the first is
86
90
  drawn — a row has one shortcut column.
87
91
 
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)).
92
+ **On macOS, `Control` means ⌘** — the primary modifier — so `[['Control', 'S']]`
93
+ becomes ⌘S in the menu bar. `Super` maps there too; `Shift` and `Alt` are
94
+ themselves. A chord the `NSMenu` spelling cannot express is not printed beside
95
+ the item, but the binding still fires (the app's window answers it, below).
96
+ The text controls' chords, the edit menu that prints them
97
+ ([events.md](events.md#primary-modifier)) and the window's own bindings
98
+ (`useAccelerator`, a drawn `MenuBar` or `ContextMenu`, which prints `⌘S` beside
99
+ the row — [events.md](events.md#the-chord)) follow the same rule.
99
100
 
100
101
  The key is named the way GDK names it, so `plus` rather than `+` and `Prior`
101
102
  rather than `PgUp`, because that is what a panel's importer parses. Menus print
@@ -106,84 +107,35 @@ its own items' chords: Ctrl+S fires the item's `onSelect` without the menu
106
107
  being opened, gated by that item's own `enabled` and `visible`
107
108
  ([events.md](events.md#accelerators)).
108
109
 
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
110
+ When the panel draws the menu, the **panel does not deliver the key**, so
112
111
  `MenuBar` keeps its bindings when it stops drawing. `accelerators={false}`
113
112
  turns them off for an application with a dispatcher of its own.
114
113
 
115
114
  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.
115
+ item on the ⌘ chord installed as its key equivalent, after the window has seen
116
+ the key. So there `MenuBar` reserves those chords — no binding in the window
117
+ answers them, its own included, and the item runs once — and keeps answering
118
+ the chords no `NSMenu` key equivalent can spell
119
+ ([events.md](events.md#the-macos-menu-bar)).
162
120
 
163
121
  ## Seeing it work, without a desktop that has one
164
122
 
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:
123
+ In a checkout of this repo, a stand-in panel prints every menu handed to it:
168
124
 
169
125
  ```bash
170
126
  npm run globalmenu:host
171
127
  ```
172
128
 
173
- It owns the registrar name — it _is_ the panel — and prints every menu handed
174
- to it. In another terminal:
129
+ In another terminal:
175
130
 
176
131
  ```bash
177
132
  npm run examples:menu
178
133
  ```
179
134
 
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.
135
+ The window's own menu bar disappears and the menu appears in the host. Type an
136
+ item's id and press Enter to activate it; Ctrl-C the host and the bar returns
137
+ to the window. On a machine with a real panel,
138
+ `npm run globalmenu:host -- --watch` attaches to the live registrar instead.
187
139
 
188
140
  ## Turning it off
189
141
 
@@ -200,12 +152,11 @@ await createRoot({ desktop: { globalMenu: false } });
200
152
  ```
201
153
 
202
154
  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.
155
+ variable needs no application change (for a user working around a panel that
156
+ renders something badly), and the `createRoot` option is the one an embedder
157
+ can set without it leaking to child processes — see
158
+ [desktop.md](desktop.md#turning-the-desktop-off), where `desktop: false` turns
159
+ this off along with the other session-bus integrations.
209
160
 
210
161
  ## Drawing your own bar
211
162
 
@@ -224,55 +175,21 @@ function Bar({ menus }) {
224
175
 
225
176
  ## Known limits, so they are not rediscovered as bugs
226
177
 
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.
178
+ - **Stock GNOME has no global menu bar at all**, so the in-window bar stays.
179
+ The extensions that add one work.
180
+ - **`ContextMenu` is not exported** — a right-click menu belongs where the
181
+ pointer is.
182
+ - **One menu per window.** A second `MenuBar` in a window that already has one
183
+ stays drawn in the window until the first unmounts or turns `globalMenu`
184
+ off, and only then takes the panel. A bar remounted with a new `key`, or
185
+ switched off and on again, waits the same way for the one it replaces to
186
+ finish leaving (a round trip or two).
187
+ - **A lazily-built submenu fills in one round trip late.** `onAboutToShow`
188
+ runs when the panel opens the submenu, and the new items reach the panel
189
+ once they have rendered.
190
+ - **`org.gtk.Menus`/`org.gtk.Actions` is not exported**; Plasma and the GNOME
191
+ extensions consume dbusmenu.
252
192
  - **No bus is a first-class configuration.** Everything here degrades to
253
193
  "draw the bar" without logging: no `dbus-native` (Node 20), no
254
194
  `$DBUS_SESSION_BUS_ADDRESS`, a connection that fails. See
255
195
  [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.