react-x11 2.10.2 → 2.12.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 (100) hide show
  1. package/README.md +278 -129
  2. package/package.json +10 -3
  3. package/src/Reconciler.js +15 -17
  4. package/src/a11y.js +2 -2
  5. package/src/anchor.js +7 -5
  6. package/src/bootstrap.js +14 -0
  7. package/src/clientmessage.js +1 -1
  8. package/src/cocoa/app.js +304 -49
  9. package/src/cocoa/bezels.js +175 -30
  10. package/src/cocoa/dnd.js +27 -13
  11. package/src/cocoa/fonts.js +3 -3
  12. package/src/cocoa/glarea.js +20 -3
  13. package/src/cocoa/main.d.ts +8 -0
  14. package/src/cocoa/main.js +43 -0
  15. package/src/cocoa/panehost.js +15 -5
  16. package/src/cocoa/presenter.js +13 -9
  17. package/src/cocoa/promotion.js +4 -7
  18. package/src/cocoa/relaunch.js +207 -0
  19. package/src/cocoa/screencolor.js +62 -0
  20. package/src/cocoa/threaded.js +246 -0
  21. package/src/cocoa/window.js +256 -42
  22. package/src/components/Select.js +2 -2
  23. package/src/components/anchor.js +3 -3
  24. package/src/components/native.js +12 -7
  25. package/src/components/theme.js +2 -2
  26. package/src/debug.js +1 -1
  27. package/src/decorations.js +1 -1
  28. package/src/editmenu.js +2 -2
  29. package/src/errors.js +46 -0
  30. package/src/events.js +6 -6
  31. package/src/foreignnodes.js +3 -2
  32. package/src/frames.js +2 -2
  33. package/src/glnodes.js +1 -1
  34. package/src/grid.js +1653 -0
  35. package/src/host.d.ts +230 -0
  36. package/src/host.js +11 -3
  37. package/src/imagesource.js +1 -1
  38. package/src/index.d.ts +21 -4
  39. package/src/index.js +9 -1
  40. package/src/layouts.js +721 -0
  41. package/src/node.d.ts +4 -2
  42. package/src/node.js +19 -21
  43. package/src/nodes/animation.js +644 -0
  44. package/src/nodes/box.js +21 -0
  45. package/src/nodes/boxpaint.js +473 -0
  46. package/src/nodes/canvas.js +269 -0
  47. package/src/nodes/cascade.js +600 -0
  48. package/src/nodes/damage.js +183 -0
  49. package/src/nodes/edithistory.js +124 -0
  50. package/src/nodes/editmenupopup.js +260 -0
  51. package/src/nodes/hittest.js +185 -0
  52. package/src/nodes/image.js +266 -0
  53. package/src/nodes/install.js +75 -0
  54. package/src/nodes/invalidate.js +465 -0
  55. package/src/nodes/kinds.js +31 -0
  56. package/src/nodes/layout.js +439 -0
  57. package/src/nodes/layouthost.js +949 -0
  58. package/src/nodes/node.js +868 -0
  59. package/src/nodes/paint.js +466 -0
  60. package/src/nodes/position.js +366 -0
  61. package/src/nodes/preedit.js +127 -0
  62. package/src/nodes/queries.js +330 -0
  63. package/src/nodes/rects.js +102 -0
  64. package/src/nodes/scrollable.js +891 -0
  65. package/src/nodes/scrollbars.js +138 -0
  66. package/src/nodes/scrollblit.js +1034 -0
  67. package/src/nodes/selectable.js +142 -0
  68. package/src/nodes/styling.js +225 -0
  69. package/src/nodes/text.js +649 -0
  70. package/src/nodes/textarea.js +391 -0
  71. package/src/nodes/textinput.js +1146 -0
  72. package/src/nodes/util.js +17 -0
  73. package/src/nodes/window/anchoring.js +161 -0
  74. package/src/nodes/window/capabilities.js +190 -0
  75. package/src/nodes/window/debugpaint.js +83 -0
  76. package/src/nodes/window/droptarget.js +145 -0
  77. package/src/nodes/window/floors.js +577 -0
  78. package/src/nodes/window/flush.js +334 -0
  79. package/src/nodes/window/hints.js +482 -0
  80. package/src/nodes/window/listeners.js +222 -0
  81. package/src/nodes/window/popup.js +71 -0
  82. package/src/nodes/window/size.js +591 -0
  83. package/src/nodes/window/window.js +945 -0
  84. package/src/palette.js +1 -1
  85. package/src/registry.js +7 -3
  86. package/src/screencolor.js +212 -38
  87. package/src/screencolorhooks.js +6 -2
  88. package/src/styles.js +137 -15
  89. package/src/svgnodes.js +2 -1
  90. package/src/testing/harness.js +2 -2
  91. package/src/textselection.js +5 -3
  92. package/src/trace-registry.js +1 -1
  93. package/src/types/components.d.ts +38 -6
  94. package/src/types/elements.d.ts +11 -1
  95. package/src/types/nodes.d.ts +33 -5
  96. package/src/types/screencolor.d.ts +20 -14
  97. package/src/types/style.d.ts +94 -3
  98. package/src/windowstate.js +1 -1
  99. package/src/yoga.js +1 -1
  100. package/src/nodes.js +0 -13120
package/README.md CHANGED
@@ -7,28 +7,61 @@
7
7
  react-x11 and watch it render, in your browser, against a JavaScript X server
8
8
  running on the page.
9
9
 
10
- A React renderer whose host environment is an [X11
11
- server](https://www.x.org/wiki/Documentation/). React's job in a renderer is
12
- to compute what changed; the renderer's job is to turn that into side effects
13
- on some host — in react-dom those are DOM mutations, here they are **X11
14
- protocol requests** written to a socket. There is no DOM, no HTML and no
15
- browser engine underneath: this is not Electron with a different skin, and
16
- `<div>` is not an element that exists. Build GUI programs for the X Window
17
- environment (a linux desktop, or macOS +
18
- [XQuartz](https://www.xquartz.org/)) with your React / React Native
10
+ A React renderer for **desktop applications**, whose host is a display
11
+ system rather than a document. React's job in a renderer is to compute what
12
+ changed; the renderer's job is to turn that into side effects on some host —
13
+ in react-dom those are DOM mutations, here they are **X11 protocol requests**
14
+ written to a socket, or **Core Animation layers and CoreGraphics drawing** on
15
+ macOS. There is no DOM, no HTML and no browser engine underneath: this is not
16
+ Electron with a different skin, and `<div>` is not an element that exists.
17
+ Build GUI programs for a Linux desktop, for a display at the other end of an
18
+ ssh connection, or as a native Mac app, with your React / React Native
19
19
  experience — flexbox layout, components, hooks, synthetic events.
20
20
 
21
- Everything is JavaScript all the way down: ntk /
22
- [node-x11](https://github.com/sidorares/node-x11) implement the X11 protocol
23
- in pure JS (think xlib rewritten in node.js), layout is
24
- [yoga-layout](https://www.npmjs.com/package/yoga-layout), text shaping is
25
- [fontkit](https://github.com/foliojs/fontkit). `npm install` never compiles
26
- anything and `npm test` doesn't even need an X server (node-x11 ships an
27
- in-process pure-JS X server that the tests render into and read pixels back
28
- from; every screenshot below was rendered that way too, by driving the real
29
- examples through the real event pipeline).
30
-
31
- ### The wire carries drawing, not pixels
21
+ ### Two backends, one tree
22
+
23
+ The same components, the same hooks and the same `style` objects run on both:
24
+
25
+ - **X11** ([the flagship](docs/remote.md)) a Linux desktop, a display
26
+ forwarded over `ssh -X`, `Xvfb` in CI, a thin client, or macOS through
27
+ [XQuartz](https://www.xquartz.org/). Everything is JavaScript all the way
28
+ down: ntk / [node-x11](https://github.com/sidorares/node-x11) implement
29
+ the X11 protocol in pure JS (think xlib rewritten in node.js).
30
+ - **Cocoa** ([docs/macos.md](docs/macos.md)) — a real Mac app: `NSWindow`s,
31
+ the menu bar at the top of the screen, AppKit's own control bezels, native
32
+ open/save panels, native notifications, Core Animation compositing. No X
33
+ server anywhere. It rides on
34
+ [`@windowkit/appkit`](https://www.npmjs.com/package/@windowkit/appkit), a
35
+ thin mechanism-only Objective-C++ bridge — an optional dependency shipping
36
+ prebuilds, absent on Linux installs.
37
+
38
+ `createRoot()` picks for you: **Cocoa on macOS** when the bridge is
39
+ installed, X11 via `$DISPLAY` everywhere else, and X11 on a Mac without the
40
+ bridge so an XQuartz setup keeps working. `createRoot({ backend: 'x11' })`
41
+ or `REACT_X11_BACKEND=x11` pins it. On macOS the app's JS runs on a worker
42
+ while AppKit keeps the main thread, so a menu, a drag or a live resize never
43
+ stops it — `node app.js` is enough, and `REACT_X11_THREADED=0` keeps the
44
+ main thread ([docs/macos.md](docs/macos.md#js-on-a-worker-a-ui-thread-of-the-bridges-own)).
45
+
46
+ Two more backends are on the way, and the goal they serve is **full
47
+ cross-platform support**: **Windows** — Win32 windows, Direct2D and
48
+ DirectWrite, DWM compositing — with [docs/windows.md](docs/windows.md) as
49
+ its PRD, and **native Wayland**, with [docs/wayland.md](docs/wayland.md)
50
+ as its RFC. Both arrive as backends beside these two, not replacements for
51
+ either: the same tree, the same components, the same `style` objects.
52
+
53
+ Layout is [yoga-layout](https://www.npmjs.com/package/yoga-layout) (WASM) on
54
+ both, and text shaping is [fontkit](https://github.com/foliojs/fontkit) on
55
+ the X11 side, CoreText on the Cocoa one. **`npm install` never compiles
56
+ anything**: the X11 stack is JavaScript all the way down, and the two native
57
+ addons in the tree — the Cocoa bridge and `x11-dri` for direct GL — are
58
+ optional dependencies that ship prebuilt. And `npm test` doesn't even need
59
+ an X server (node-x11 ships an in-process pure-JS X server that the tests
60
+ render into and read pixels back from; every screenshot below was rendered
61
+ that way too, by driving the real examples through the real event
62
+ pipeline).
63
+
64
+ ### On X11, the wire carries drawing, not pixels
32
65
 
33
66
  react-x11 does not rasterize a frame on the client and ship the buffer
34
67
  across. React reconciles the component tree, the renderer turns that diff
@@ -44,11 +77,22 @@ back. An update costs what the _drawing_ costs, not what the window's area
44
77
  costs. Going full-screen on a 4K panel does not multiply your bandwidth,
45
78
  because you were not sending pixels at 1080p either — which is why this
46
79
  stays comfortable on a display forwarded over ssh. Mounting a window with
47
- forty rows and their labels is 86 requests and 3.6 KB on the wire.
80
+ forty rows and their labels is 110 requests and 4.2 KB on the wire, and
81
+ stalls the pipeline on none of them.
48
82
 
49
83
  Because that is the design it is measured rather than assumed: `npm run
50
- bench` reports requests, bytes, replies, RENDER composites and the pixel area
51
- those composites touch, against a checked-in baseline.
84
+ bench` reports requests, bytes, replies, blocking round trips, RENDER
85
+ composites and the pixel area those composites touch, against a checked-in
86
+ baseline (`scripts/bench/baseline.json`, which is where the numbers in these
87
+ docs come from).
88
+
89
+ The Cocoa backend inverts this, and [docs/macos.md](docs/macos.md) is the
90
+ design record for why: there is no wire, the WindowServer is a retained
91
+ compositor, and a React commit maps onto property mutations of persistent
92
+ objects. Scrolling is a layer's `bounds.origin` rather than a repaint,
93
+ animations run in the render server while the JS thread is busy, and Retina
94
+ is composited at native resolution instead of quadrupling every rasterized
95
+ pixel.
52
96
 
53
97
  ### Where this fits
54
98
 
@@ -58,7 +102,7 @@ That is the shape of the problem this is good at:
58
102
 
59
103
  - **the display is elsewhere** — a headless server over `ssh -X`, a
60
104
  container pointed at the host, a thin client, an X terminal, a
61
- deliberately dumb workstation. A whole window appears for under four
105
+ deliberately dumb workstation. A whole window appears for about four
62
106
  kilobytes, because what crosses the link is drawing rather than pixels
63
107
  ([docs/remote.md](docs/remote.md));
64
108
  - **the machine cannot afford a browser engine** — a kiosk, an appliance, an
@@ -72,21 +116,33 @@ That is the shape of the problem this is good at:
72
116
  in-process X server, on a machine with no `$DISPLAY`, on macOS. That
73
117
  harness is published as `react-x11/test`
74
118
  ([docs/testing.md](docs/testing.md)).
119
+ - **you want a Mac app out of the same source** — the Cocoa backend is a
120
+ real one: system menu bar, native panels and notifications, layer-backed
121
+ compositing, and a Developer-ID-signed `.app` from either node or bun
122
+ (the App Store's sandbox takes node only —
123
+ [docs/packaging.md](docs/packaging.md)).
75
124
 
76
125
  And the shape it is not good at, so you can stop here rather than in week
77
126
  three:
78
127
 
79
- - **three platforms.** X11 only. macOS means XQuartz a separate install, a
80
- non-native look and no menu bar integration. Windows is out; if you need
81
- Windows, use Electron or Tauri.
128
+ - **Windows — today.** There is no Windows backend yet, so an app that has
129
+ to ship on Windows now wants Electron or Tauri. One is coming: a native
130
+ backend over Win32 windows, Direct2D and DirectWrite, composited by DWM
131
+ through DirectComposition, on a mechanism-only bridge shaped like the
132
+ Cocoa one. [docs/windows.md](docs/windows.md) is the PRD, from the
133
+ threading model up to what each desktop integration becomes. Two targets
134
+ ship meanwhile — X11 and Cocoa — and they are not the same app: the
135
+ desktop-shell half of X11 (`<foreign>` embedding, panel struts,
136
+ substructure redirect, the window-manager example below) has no macOS
137
+ equivalent, and `react-x11/test` drives the X11 backend only.
138
+ [docs/macos.md](docs/macos.md) says which is which.
82
139
  - **native Wayland — today.** There is no Wayland backend yet. Ordinary
83
140
  application windows work fine on a Wayland desktop through Xwayland, which
84
141
  is not going away — but the desktop-shell half of X11 (panel struts,
85
142
  global key grabs, screen capture, and the window-manager example below)
86
- needs a real X session. A native backend is researched and planned as a
87
- **second target beside X11, not a migration**:
88
- [docs/wayland.md](docs/wayland.md) is the RFC, from the fd transport up
89
- to what the rendering would ride on.
143
+ needs a real X session. A native backend is coming too, as a **target
144
+ beside X11, not a migration**: [docs/wayland.md](docs/wayland.md) is the
145
+ RFC, from the fd transport up to what the rendering would ride on.
90
146
  - **reusing web components.** There is no DOM. Your MUI, your Tailwind and
91
147
  your `recharts` do not come with you; the state, data-fetching, validation
92
148
  and math libraries mostly do. [docs/ecosystem.md](docs/ecosystem.md) says
@@ -96,6 +152,10 @@ three:
96
152
  [`@react-x11/components`](https://github.com/sidorares/react-x11-components).
97
153
  - **text entry outside Latin.** See [Known issues](#known-issues).
98
154
 
155
+ The screenshots below are the X11 backend, rendered headlessly into
156
+ node-x11's in-process server by `npm run screenshots` — which is why they
157
+ are pixel-stable enough to check in.
158
+
99
159
  | `examples/dashboard.jsx` — context theming, hooks | `examples/tasks.jsx` — useReducer, textinput, scrolling |
100
160
  | ------------------------------------------------- | ------------------------------------------------------- |
101
161
  | ![dashboard](docs/img/dashboard.png) | ![tasks](docs/img/tasks.png) |
@@ -108,8 +168,8 @@ three:
108
168
  protocol sent over the X connection, geometry compiled into a display list,
109
169
  a frame costing two matrices and one `CallList`. No native bindings, no GPU
110
170
  driver bindings — the same "JavaScript all the way down" story as the rest.
111
- (GL renders where `GetImage` cannot read it, so there is no screenshot of it
112
- here; see [docs/glx.md](docs/glx.md).)
171
+ (GL renders where the X server cannot read it back, so there is no
172
+ screenshot of it here; see [docs/glx.md](docs/glx.md).)
113
173
 
114
174
  ## Quick start
115
175
 
@@ -156,7 +216,7 @@ function Counter() {
156
216
  );
157
217
  }
158
218
 
159
- const root = await createRoot(); // connects via $DISPLAY
219
+ const root = await createRoot(); // Cocoa on macOS, else X11 via $DISPLAY
160
220
  root.render(<Counter />);
161
221
  ```
162
222
 
@@ -167,71 +227,115 @@ step).
167
227
 
168
228
  ## Elements
169
229
 
170
- Only `<window>`, `<popup>` and `<glarea>` map to real X11 windows.
171
- Everything else is laid out by yoga and drawn client-side into the window's
172
- double-buffered 2d contextsee [NEXT_STEPS.md](NEXT_STEPS.md) for the architecture rationale
173
- and [docs/](docs/README.md) for the full API reference.
230
+ Only `<window>`, `<popup>`, `<glarea>` and `<foreign>` are real windows of
231
+ the display system. Everything else is a retained lightweight node one
232
+ yoga node eachdrawn client-side into the owning window's double-buffered
233
+ 2d context, with events dispatched by front-to-back hit testing over the
234
+ drawn tree. See [docs/elements.md](docs/elements.md) for the full reference.
174
235
 
175
236
  | element | what it is |
176
237
  | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
- | `<window>` | a real X11 window; the flex, paint and event root |
178
- | `<popup>` | an override-redirect window at screen coordinates — menus, tooltips, dropdowns |
238
+ | `<window>` | a real toplevel — an X11 window, or an `NSWindow`; the flex, paint and event root |
239
+ | `<popup>` | an undecorated window placed at screen coordinates — menus, tooltips, dropdowns. `anchor` places it against a node; `grab` and `trapFocus` make it modal |
179
240
  | `<box>` | flex container: layout props → yoga, plus backgrounds, borders (solid/dashed, radius), overflow clipping, zIndex. `overflow: 'scroll'` makes it a wheel-scrollable viewport with a drawn scrollbar |
180
241
  | `<text>` | shaped, wrapped text (bidi, ligatures, font fallback); nested `<text>` elements are style spans |
181
- | `<textinput>` | single-line editor: caret/selection via ntk's TextLayout caret API, clipboard (CLIPBOARD + X11 PRIMARY), word select, undo/redo, right-click menu |
182
- | `<image>` | PNG/JPEG from `src`, natural-size aware |
183
- | `<canvas>` | escape hatch: `onDraw={(ctx, {width, height}) => …}` with ntk's canvas-like 2d context (XRender-backed) |
184
- | `<svg>` | static SVG through ntk SvgView, sized like `<image>` |
185
- | `<glarea>` | an OpenGL surface over indirect GLX; the 3D scene below lives inside it |
242
+ | `<textinput>` | single-line editor: caret/selection, clipboard, word select, undo/redo, right-click menu |
243
+ | `<textarea>` | the same editor, wrapped and multi-line, scrolling with its content |
244
+ | `<image>` | PNG/JPEG from `src`, natural-size aware; also pixels the server already has |
245
+ | `<canvas>` | escape hatch: `onDraw={(ctx, {width, height}) => …}` with a canvas-like 2d context (XRender-backed on X11, CoreGraphics on Cocoa) |
246
+ | `<svg>` | static SVG (paths, shapes, gradients, transforms), sized like `<image>` |
247
+ | `<glarea>` | an OpenGL surface in the layout — see [3D](#3d) |
248
+ | `<foreign>` | another application's X window, laid out as an element. **X11 only** ([docs/embedding.md](docs/embedding.md)) |
249
+
250
+ `registerElement()` adds one from outside the package —
251
+ [docs/extending.md](docs/extending.md).
186
252
 
187
253
  Widget **components** (plain React on top of the primitives, themable via
188
254
  `ThemeProvider`): `Button`, `Checkbox`, `Radio`/`RadioGroup`, `Switch`,
189
- `Slider`, `ProgressBar`, `Select`, `Tooltip`, `MenuBar`/`ContextMenu`,
190
- `Calendar`/`DatePicker` — one date or a range, with days blockable and a
191
- seam for drawing into the cells — `PasswordInput`, whose mask is a scribble
192
- that moves on every keystroke rather than a countable row of bullets —
193
- `Dialog` a modal built on
194
- `<popup trapFocus>`, which traps Tab and restores focus when it closes —
195
- plus the two containers an application window is built from: `Tabs`, `Tree`
196
- and `SplitPane`. See
197
- [docs/components.md](docs/components.md).
198
-
199
- **The menu bar goes where the desktop keeps menus.** On Plasma, on a panel
200
- running `vala-panel-appmenu`, or under one of the GNOME extensions that add a
201
- menu bar, `<MenuBar menus={…}/>` hands its menu to the panel over
202
- `com.canonical.dbusmenu` and stops drawing one; everywhere else it draws the
203
- bar itself. No configuration, and the same line of JSX either way — the item
204
- descriptors are dbusmenu's own vocabulary, so the array that draws the menu is
205
- the array that serialises. `npm run globalmenu:host` is a panel in a terminal,
206
- for seeing it work on a desktop that has none. See
255
+ `Slider`, `ProgressBar`, `Select`, `Tooltip`, `Icon`, `MenuBar`/`ContextMenu`,
256
+ `PasswordInput` — whose mask is a scribble that moves on every keystroke
257
+ rather than a countable row of bullets — `Dialog`, a modal built on
258
+ `<popup trapFocus>` which traps Tab and restores focus when it closes,
259
+ `FileDialog`, the two containers an application window is built from —
260
+ `Tabs` and `SplitPane` and a virtualized `Table`. See
261
+ [docs/components.md](docs/components.md). Richer widgets `Markdown`,
262
+ `Tree`, `Calendar`/`DatePicker`, a three.js scene graph — live in
263
+ [`@react-x11/components`](https://github.com/sidorares/react-x11-components).
264
+
265
+ **The menu bar goes where the desktop keeps menus.** On the Cocoa backend
266
+ `<MenuBar menus={…}/>` becomes the **macOS menu bar** at the top of the
267
+ screen. On Plasma, on a panel running `vala-panel-appmenu`, or under one of
268
+ the GNOME extensions that add a menu bar, it hands its menu to the panel
269
+ over `com.canonical.dbusmenu`. Everywhere else a stock GNOME session,
270
+ XQuartz, ssh, CI it draws the bar itself. No configuration, and the same
271
+ line of JSX every time: the item descriptors are dbusmenu's own vocabulary,
272
+ so the array that draws the menu is the array that serialises and the array
273
+ that becomes an `NSMenu`. `npm run globalmenu:host` is a panel in a
274
+ terminal, for seeing the D-Bus path work on a desktop that has none. See
207
275
  [docs/globalmenu.md](docs/globalmenu.md).
208
276
 
209
277
  **`myapp://` links open the app that is already running.**
210
- `registerApplication({ appId, schemes })` before `createRoot()` owns the app's
211
- name on the bus, exports `org.freedesktop.Application`, and on the half of
212
- all desktops where `xdg-open` ignores D-Bus activation and spawns a second copy
213
- with the URI in `argv` forwards it to the first copy and tells that copy to
214
- exit. `useAppOpen()` receives the link, buffered and replayed if it arrived
215
- before the tree mounted, and `activateWindow()` brings the window forward with
216
- the launch's own timestamp, without which a window manager declines and the
217
- taskbar entry just blinks. The OAuth redirect this is for has an easier answer
218
- that needs none of it, and the page says so first. See
278
+ `registerApplication({ appId, schemes })` before `createRoot()` takes the two
279
+ dispatch paths a real desktop uses: on Linux it owns the app's name on the
280
+ bus, exports `org.freedesktop.Application`, and on the half of all desktops
281
+ where `xdg-open` ignores D-Bus activation and spawns a second copy with the
282
+ URI in `argv` forwards it to the first copy and tells that copy to exit; on
283
+ macOS the link arrives as an Apple Event through the bridge. `useAppOpen()`
284
+ receives it either way, buffered and replayed if it arrived before the tree
285
+ mounted, and `activateWindow()` brings the window forward with the launch's
286
+ own timestamp on X11, without which a window manager declines and the taskbar
287
+ entry just blinks. The OAuth redirect this is for has an easier answer that
288
+ needs none of it, and the page says so first. See
219
289
  [docs/uri-schemes.md](docs/uri-schemes.md).
220
290
 
221
- **Drag and drop** works with the rest of the desktop: `<box dropAccept={['files']}
222
- onDrop={…}>` takes a file dragged out of Nautilus, and `<box draggable dragData={}>`
223
- can be dropped into a file manager or an editor. Drags that stay inside the
224
- app never touch the wire and hand their payload over by reference, so a
225
- reorderable list and a cross-application file drop are the same two props.
291
+ **Drag and drop** works with the rest of the desktop on both backends —
292
+ XDND on X11, `NSDraggingSession` on Cocoa. `<box dropAccept={['files']}
293
+ onDrop={…}>` takes a file dragged out of Nautilus or Finder, and
294
+ `<box draggable dragData={…}>` can be dropped into a file manager or an
295
+ editor. Drags that stay inside the app never touch the wire and hand their
296
+ payload over by reference, so a reorderable list and a cross-application
297
+ file drop are the same two props.
226
298
  See [docs/drag-and-drop.md](docs/drag-and-drop.md).
227
299
 
300
+ ### The desktop around the app
301
+
302
+ Everything an app does outside its own windows is a **ladder**: the best
303
+ rung the machine actually has, the same hook whichever one answers, and a
304
+ first-class "this desktop cannot" rather than a throw.
305
+
306
+ | what | rungs, best first |
307
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
308
+ | [open/save panels](docs/filedialog.md) `useFileDialog()` | `NSOpenPanel` · the XDG portal · `osascript` · one react-x11 draws |
309
+ | [notifications](docs/notifications.md) `notify()` | UNUserNotificationCenter · `org.freedesktop.Notifications` · `osascript` · `notify-send` |
310
+ | [light/dark, accent](docs/appearance.md) `useSystemAppearance()` | the settings portal · macOS · XSETTINGS — remembered on disk, so the first frame is not a flash |
311
+ | [permissions](docs/permissions.md) `usePermission()` | TCC on macOS · the Settings pane · `'unknown'` on Linux until the device portals land |
312
+ | [the user's real events](docs/desktop-calendar.md) | EventKit through the bridge · EventKit through `osascript` · Evolution Data Server |
313
+ | [sample a screen colour](docs/eyedropper.md) `useEyedropper()` | `NSColorSampler` · the portal's picker · a crosshair grab on plain X11 |
314
+ | a badge on the icon `setBadge()` | `NSDockTile.badgeLabel` · `com.canonical.Unity.LauncherEntry` |
315
+ | a tray icon `useTray()` | `NSStatusItem`. **Cocoa only today** — `available` is false on X11 until StatusNotifierItem lands ([#353](https://github.com/sidorares/react-x11/issues/353)) |
316
+
317
+ [docs/system.md](docs/system.md) is the rest of the machine — monitors,
318
+ window state, idle, keyboard layout, locale — and
319
+ [docs/desktop.md](docs/desktop.md) is what the app tells the desktop about
320
+ itself.
321
+
228
322
  ### 3D
229
323
 
230
- `<glarea>` is a GL surface in the layout, drawn over **indirect GLX**: the GL
231
- protocol encoded into the X connection, with no native bindings and no GPU
232
- driver bindings — the same "JavaScript all the way down" story as the rest.
233
- Geometry belongs in a server-side display list, so a frame costs matrices and
234
- one `CallList` whatever the triangle count.
324
+ `<glarea>` is a GL surface in the layout, with three ways underneath it and
325
+ the same `onDraw` over all three:
326
+
327
+ - **indirect GLX** (the X11 default) the GL protocol encoded into the X
328
+ connection, no native bindings and no GPU driver bindings, the same
329
+ "JavaScript all the way down" story as the rest. Geometry belongs in a
330
+ server-side display list, so a frame costs matrices and one `CallList`
331
+ whatever the triangle count. No shaders and no render targets: the
332
+ protocol encodes neither.
333
+ - **direct** (`glPolicy: 'auto'`, ntk's optional `x11-dri` addon) — GL ES 2
334
+ on the GPU, with GLSL and framebuffer objects. DRI3 + Present on Linux,
335
+ Apple-DRI under XQuartz.
336
+ - **Cocoa** — CGL into IOSurface-backed framebuffers, presented as a
337
+ sublayer of the window; shaders and render targets, on any Mac, with no
338
+ X server involved.
235
339
 
236
340
  ```jsx
237
341
  <glarea
@@ -243,7 +347,7 @@ one `CallList` whatever the triangle count.
243
347
  ```
244
348
 
245
349
  A scene graph over it — `<mesh>`, geometries, materials, lights, and shaders
246
- or post-processing on the direct backend — is
350
+ or post-processing where the backend has them — is
247
351
  [`@react-x11/components/three`](https://github.com/sidorares/react-x11-components).
248
352
  See [docs/gl.md](docs/gl.md) and [docs/glx.md](docs/glx.md).
249
353
 
@@ -257,20 +361,24 @@ a scroll container from scrolling, like the DOM.
257
361
 
258
362
  ## Examples
259
363
 
260
- All need an X server but "an X server" is a broader thing than it sounds:
261
- your Linux desktop, XQuartz on macOS, `Xvfb` for automation, `Xephyr` for a
262
- disposable screen, a VNC server, or your own display reached over `ssh -X`
263
- from wherever the program actually runs see
264
- [docs/remote.md](docs/remote.md), which is the case this architecture is
265
- categorically better at.
364
+ On macOS these run natively, with no X server; everywhere else they need
365
+ one and "an X server" is a broader thing than it sounds: your Linux
366
+ desktop, XQuartz, `Xvfb` for automation, `Xephyr` for a disposable screen, a
367
+ VNC server, or your own display reached over `ssh -X` from wherever the
368
+ program actually runs — see [docs/remote.md](docs/remote.md), which is the
369
+ case this architecture is categorically better at. `REACT_X11_BACKEND=x11`
370
+ in front of any of them runs the X11 path on a Mac too, which is how the two
371
+ get compared.
266
372
  [`examples/README.md`](examples/README.md) describes each one and how to
267
373
  explore them:
268
374
 
269
375
  ```sh
270
376
  npm run examples:simple # hello world (JSX via tsx)
271
377
  npm run examples:app # the showcase: Tabs + SplitPane hosting the rest
378
+ npm run examples:widgets # every built-in control, in one window
272
379
  npm run examples:theming # three themes x light/dark, and a size query
273
380
  npm run examples:container-queries # one card, two panes: '@container', and a named one
381
+ npm run examples:custom-layout # masonry, a justified gallery, CSS grid, and equal-row controls
274
382
  npm run examples:simple-nojsx # the same, plain node — no build step
275
383
  npm run examples:xeyes # canvas drawing + hooks
276
384
  npm run examples:dashboard # context theming, custom hooks, components
@@ -279,23 +387,30 @@ npm run examples:menu # right-click context menu via <popup>
279
387
  npm run examples:transparent # rounded translucent <popup transparent>
280
388
  npm run examples:animation # transitions and loops, and which backend runs them
281
389
  npm run examples:form # <textinput> + Select dropdowns
282
- npm run examples:datepicker # Calendar/DatePicker: ranges, blocked days, events
283
390
  npm run examples:password # PasswordInput: the scribble mask, and a custom one
391
+ npm run examples:calendar # the desktop's own calendar events, no OAuth
392
+ npm run examples:dnd-source # and :dnd-target — drags in and out of the desktop
393
+ npm run examples:frame # <Frame>: a pane of the app in its own process
394
+ npm run examples:stress # the perf scenarios the benches are cut from
284
395
  npm run examples:viewer3d # raw GL in a <glarea>: a model viewer over indirect GLX
285
- npm run examples:wm # a reparenting window manager (see below)
396
+ npm run examples:wm # a reparenting window manager (X11 only, see below)
286
397
  ```
287
398
 
288
- The two GL examples additionally need a server with **indirect GLX**
289
- enabled (`+iglx` / `AllowIndirectGLX` — off by default on many).
399
+ On the X11 backend the GL examples additionally need a server with
400
+ **indirect GLX** enabled (`+iglx` / `AllowIndirectGLX` — off by default on
401
+ many), or `glPolicy: 'auto'` and ntk's `x11-dri` addon. On Cocoa they need
402
+ neither.
290
403
 
291
404
  ### Window manager
292
405
 
293
- `examples:wm` is a real reparenting window manager: it takes over the root
294
- window, puts every application's window inside a frame it draws, and moves,
295
- resizes, focuses and closes them. The frames are ordinary react-x11
296
- `<window>`s the titlebar, the buttons and the eight resize handles are
297
- components with `onMouseDown` handlers, and the application is a foreign X
298
- window reparented inside.
406
+ `examples:wm` is a real reparenting window manager, and it is **X11 only**
407
+ substructure redirect has no macOS equivalent, so this is one of the places
408
+ the two backends are not the same app. It takes over the root window, puts
409
+ every application's window inside a frame it draws, and moves, resizes,
410
+ focuses and closes them. The frames are ordinary react-x11 `<window>`s — the
411
+ titlebar, the buttons and the eight resize handles are components with
412
+ `onMouseDown` handlers, and the application is a foreign X window reparented
413
+ inside.
299
414
 
300
415
  Only one window manager may own a display, so run it against a nested
301
416
  server rather than the one managing your desktop:
@@ -319,8 +434,8 @@ npm run examples:tasks:hot # then edit examples/tasks.jsx while it runs
319
434
  Runs the tasks example under the supported hot-reload entry point,
320
435
  `react-x11/refresh` (`node --import react-x11/refresh/register`, Node ≥
321
436
  22.15), with React **Fast Refresh**: saving `examples/tasks.jsx` updates
322
- the edited components in place. The X11 connection, the mounted window,
323
- and component state — the task list, even half-typed text in the input —
437
+ the edited components in place. The connection, the mounted window, and
438
+ component state — the task list, even half-typed text in the input —
324
439
  survive the reload (a component whose hook signature changed remounts
325
440
  alone). Any app adopts it the same way — no accept handlers, no loader
326
441
  files to copy; see
@@ -335,21 +450,27 @@ REACT_X11_DEVTOOLS=1 npm run examples:dashboard # 2. run any example with the
335
450
 
336
451
  The component tree, props and hook state show up live in the DevTools
337
452
  window, and editing any of them re-renders the app. Hovering an element in
338
- the tree tints its rect in the X11 window; the toolbar's crosshair picks an
339
- element by clicking it in the app; "highlight updates when components
453
+ the tree tints its rect in the app's window; the toolbar's crosshair picks
454
+ an element by clicking it in the app; "highlight updates when components
340
455
  render" outlines the rects that just re-rendered; the style editor edits a
341
456
  selected element's style live; and the Profiler can restart the app to
342
- record its mount. `REACT_X11_DEVTOOLS_HOST` / `REACT_X11_DEVTOOLS_PORT`
343
- override the default `localhost:8097`. See
457
+ record its mount. All of it is above the display system, so it works the
458
+ same on both backends. `REACT_X11_DEVTOOLS_HOST` /
459
+ `REACT_X11_DEVTOOLS_PORT` override the default `localhost:8097`. See
344
460
  [docs/devtools.md](docs/devtools.md).
345
461
 
346
- Two more debugging aids:
462
+ Three more debugging aids:
347
463
 
348
464
  - `REACT_X11_DEBUG_LAYOUT=1` outlines every laid-out node (color = tree
349
465
  depth) — handy when a flexbox doesn't do what you expect.
466
+ - `REACT_X11_DEBUG_PAINT=1` strokes each frame's damage rects in a rotating
467
+ colour, so a region repainting every frame strobes visibly;
468
+ `REACT_X11_TRACE` records the X11 protocol. Both are
469
+ [docs/debugging.md](docs/debugging.md).
350
470
  - refs give you the retained node (`abs` rect, `scrollTo`, …) for drawn
351
- elements, or the live [ntk](https://github.com/sidorares/ntk) window for
352
- `<window>`/`<popup>` the whole ntk API is a ref away.
471
+ elements, or the live window object for `<window>`/`<popup>` — on X11
472
+ that is the [ntk](https://github.com/sidorares/ntk) window and the whole
473
+ ntk API with it.
353
474
 
354
475
  ## Click to component
355
476
 
@@ -357,16 +478,16 @@ Two more debugging aids:
357
478
  REACT_X11_EDITOR=code npm run examples:tasks
358
479
  ```
359
480
 
360
- Alt+Click any rendered element (Option+Click on macOS/XQuartz) to open the
361
- JSX line that created it in your editor. `REACT_X11_EDITOR` picks the CLI
362
- and enables the feature in one go; `REACT_X11_CLICK_TO_COMPONENT=1` does
363
- the same with the default editor
481
+ Alt+Click any rendered element (Option+Click on macOS, either backend) to
482
+ open the JSX line that created it in your editor. `REACT_X11_EDITOR` picks
483
+ the CLI and enables the feature in one go; `REACT_X11_CLICK_TO_COMPONENT=1`
484
+ does the same with the default editor
364
485
  (`cursor`). See [docs/click-to-component.md](docs/click-to-component.md).
365
486
 
366
487
  ## TypeScript
367
488
 
368
489
  Types ship with the package — no `@types/react-x11` to install. Point JSX at
369
- react-x11 and the X11 elements type-check:
490
+ react-x11 and the host elements type-check:
370
491
 
371
492
  ```jsonc
372
493
  // tsconfig.json
@@ -415,17 +536,23 @@ npm test # hermetic: mock smoke tests + in-process X server pixels
415
536
  npm run typecheck # tsc over the declarations and the type tests
416
537
  npm run lint # ESLint
417
538
  npm run format # Prettier
539
+ npm run bench # protocol cost against scripts/bench/baseline.json
540
+ npm run bench:frames # frame timings; bench:pixels for the pixel-hash gate
418
541
  npm run screenshots # regenerate docs/img/*.png headlessly (no X server)
419
542
  npm run docs:dev # the documentation site (npm install in website/ first)
420
543
  ```
421
544
 
422
- 3D is the exception: the headless path has no GL, so a 3D shot has to be
423
- captured by hand from a real server
424
- with indirect GLX.
545
+ The test suite drives the **X11** backend against node-x11's in-process
546
+ server; the Cocoa backend's own tests run against a fake bridge, and its
547
+ pixels are checked by hand on a Mac. 3D is the other exception: the headless
548
+ path has no GL, so a 3D shot has to be captured from a real server with
549
+ indirect GLX.
425
550
 
426
551
  See [AGENTS.md](AGENTS.md) for architecture notes and contributor/agent
427
- guidance, [docs/](docs/README.md) for API documentation, and
428
- [NEXT_STEPS.md](NEXT_STEPS.md) for the roadmap.
552
+ guidance, [docs/](docs/README.md) for API documentation,
553
+ [docs/architecture/](docs/architecture/) for the design records behind the
554
+ bigger pieces, and [NEXT_STEPS.md](NEXT_STEPS.md) for the older roadmap it
555
+ grew out of.
429
556
 
430
557
  The [documentation site](https://sidorares.github.io/react-x11/) lives in
431
558
  [`website/`](website/) and renders `docs/` rather than repeating it — edit
@@ -434,8 +561,10 @@ the browser, so `npm run docs:test` fails when a demo stops matching the API.
434
561
 
435
562
  ## Known issues
436
563
 
437
- Two that are worth knowing before you hit them, both predating the current
438
- release and both tracked:
564
+ Worth knowing before you hit them. Both are **X11-backend** problems that
565
+ come out of X's own keyboard and font models; neither applies to a Mac app
566
+ on the Cocoa backend, where CoreText resolves families and `NSEvent` reports
567
+ the character that was actually typed.
439
568
 
440
569
  - **`sans-serif` can resolve to a CJK font on macOS**
441
570
  ([#86](https://github.com/sidorares/react-x11/issues/86)). Font families
@@ -449,16 +578,28 @@ release and both tracked:
449
578
  layout or a third and fourth level, so they are read as a layout and left
450
579
  alone. Dead keys, Compose and layout switching all work
451
580
  ([docs/events.md](docs/events.md)); what is absent is an ibus/fcitx/XIM
452
- backend, so CJK is not partially working — it is structurally absent.
581
+ backend, so CJK is not partially working — it is structurally absent. On
582
+ Cocoa the character arrives with the event, so AltGr-style levels type;
583
+ an `NSTextInputClient` IME for CJK is not built there either.
584
+
585
+ Two more places the backends genuinely differ, so you can plan around them
586
+ rather than discover them: `react-x11/test` drives the **X11** backend only,
587
+ and the AT-SPI accessibility bridge is X11's — on Cocoa `createRoot()`
588
+ leaves it off, and an `NSAccessibility` bridge is not built yet
589
+ ([docs/accessibility.md](docs/accessibility.md)).
453
590
 
454
591
  ## Security
455
592
 
456
- X11 has no isolation between clients: any program on a display can read any
457
- other program's window contents, grab the keyboard, and synthesize or record
458
- input. That is the 1987 design, not a gap in this library, and it cuts both
459
- ways — do not run untrusted programs on a display you use, and do not treat
460
- a react-x11 window as a confidential surface. Your `$XAUTHORITY` cookie is a
461
- bearer token; treat it like a password.
593
+ **On X11**, there is no isolation between clients: any program on a display
594
+ can read any other program's window contents, grab the keyboard, and
595
+ synthesize or record input. That is the 1987 design, not a gap in this
596
+ library, and it cuts both ways — do not run untrusted programs on a display
597
+ you use, and do not treat a react-x11 window as a confidential surface. Your
598
+ `$XAUTHORITY` cookie is a bearer token; treat it like a password. (macOS
599
+ does isolate applications from one another, so a Cocoa-backend app does not
600
+ inherit this; what it inherits instead is TCC, where the camera, the
601
+ microphone and the screen are permissions the user grants —
602
+ [docs/permissions.md](docs/permissions.md).)
462
603
 
463
604
  `ssh -X` runs your app as an untrusted client and restricts most of the
464
605
  above; `ssh -Y` turns the restrictions off. **Prefer `-X` — react-x11 should
@@ -473,8 +614,16 @@ against, is [docs/security.md](docs/security.md). To report something,
473
614
  - [awesome-react-renderer](https://github.com/chentsulin/awesome-react-renderer)
474
615
  — the catalogue of React renderers, which is how most people find things
475
616
  like this.
476
- - [ntk](https://github.com/sidorares/ntk) — the toolkit underneath: windows,
477
- the XRender-backed 2d context, the text stack, the frame clock.
617
+ - [ntk](https://github.com/sidorares/ntk) — the toolkit under the X11
618
+ backend: windows, the XRender-backed 2d context, the text stack, the frame
619
+ clock.
478
620
  - [node-x11](https://github.com/sidorares/node-x11) — the X11 protocol in
479
621
  JavaScript, including the in-process X server the tests and the playground
480
622
  run against.
623
+ - [`@windowkit/appkit`](https://www.npmjs.com/package/@windowkit/appkit) —
624
+ the Cocoa bridge: `NSWindow`s, CALayer trees, CoreText, CoreGraphics
625
+ surfaces, and an `NSApplication` event pump driven from Node's own loop.
626
+ Mechanism only, no policy — the renderer owns that.
627
+ - [`@react-x11/components`](https://github.com/sidorares/react-x11-components)
628
+ — widgets over the primitives: `Markdown`, `Tree`, `Calendar`, a terminal,
629
+ a three.js scene graph.