react-x11 2.11.0 → 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.
- package/README.md +278 -129
- package/package.json +10 -3
- package/src/Reconciler.js +15 -17
- package/src/a11y.js +2 -2
- package/src/anchor.js +7 -5
- package/src/bootstrap.js +14 -0
- package/src/clientmessage.js +1 -1
- package/src/cocoa/app.js +292 -49
- package/src/cocoa/bezels.js +175 -30
- package/src/cocoa/dnd.js +27 -13
- package/src/cocoa/fonts.js +3 -3
- package/src/cocoa/glarea.js +20 -3
- package/src/cocoa/main.d.ts +8 -0
- package/src/cocoa/main.js +43 -0
- package/src/cocoa/panehost.js +15 -5
- package/src/cocoa/presenter.js +13 -9
- package/src/cocoa/promotion.js +4 -7
- package/src/cocoa/relaunch.js +207 -0
- package/src/cocoa/threaded.js +246 -0
- package/src/cocoa/window.js +256 -42
- package/src/components/Select.js +2 -2
- package/src/components/anchor.js +3 -3
- package/src/components/native.js +12 -7
- package/src/components/theme.js +2 -2
- package/src/debug.js +1 -1
- package/src/decorations.js +1 -1
- package/src/editmenu.js +2 -2
- package/src/errors.js +46 -0
- package/src/events.js +6 -6
- package/src/foreignnodes.js +3 -2
- package/src/frames.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1653 -0
- package/src/host.d.ts +230 -0
- package/src/host.js +11 -3
- package/src/imagesource.js +1 -1
- package/src/index.d.ts +21 -4
- package/src/index.js +9 -1
- package/src/layouts.js +721 -0
- package/src/node.d.ts +4 -2
- package/src/node.js +19 -21
- package/src/nodes/animation.js +644 -0
- package/src/nodes/box.js +21 -0
- package/src/nodes/boxpaint.js +473 -0
- package/src/nodes/canvas.js +269 -0
- package/src/nodes/cascade.js +600 -0
- package/src/nodes/damage.js +183 -0
- package/src/nodes/edithistory.js +124 -0
- package/src/nodes/editmenupopup.js +260 -0
- package/src/nodes/hittest.js +185 -0
- package/src/nodes/image.js +266 -0
- package/src/nodes/install.js +75 -0
- package/src/nodes/invalidate.js +465 -0
- package/src/nodes/kinds.js +31 -0
- package/src/nodes/layout.js +439 -0
- package/src/nodes/layouthost.js +949 -0
- package/src/nodes/node.js +868 -0
- package/src/nodes/paint.js +466 -0
- package/src/nodes/position.js +366 -0
- package/src/nodes/preedit.js +127 -0
- package/src/nodes/queries.js +330 -0
- package/src/nodes/rects.js +102 -0
- package/src/nodes/scrollable.js +891 -0
- package/src/nodes/scrollbars.js +138 -0
- package/src/nodes/scrollblit.js +1034 -0
- package/src/nodes/selectable.js +142 -0
- package/src/nodes/styling.js +225 -0
- package/src/nodes/text.js +649 -0
- package/src/nodes/textarea.js +391 -0
- package/src/nodes/textinput.js +1146 -0
- package/src/nodes/util.js +17 -0
- package/src/nodes/window/anchoring.js +161 -0
- package/src/nodes/window/capabilities.js +190 -0
- package/src/nodes/window/debugpaint.js +83 -0
- package/src/nodes/window/droptarget.js +145 -0
- package/src/nodes/window/floors.js +577 -0
- package/src/nodes/window/flush.js +334 -0
- package/src/nodes/window/hints.js +482 -0
- package/src/nodes/window/listeners.js +222 -0
- package/src/nodes/window/popup.js +71 -0
- package/src/nodes/window/size.js +591 -0
- package/src/nodes/window/window.js +945 -0
- package/src/palette.js +1 -1
- package/src/registry.js +7 -3
- package/src/styles.js +137 -15
- package/src/svgnodes.js +2 -1
- package/src/testing/harness.js +2 -2
- package/src/textselection.js +5 -3
- package/src/trace-registry.js +1 -1
- package/src/types/components.d.ts +38 -6
- package/src/types/elements.d.ts +11 -1
- package/src/types/nodes.d.ts +17 -2
- package/src/types/style.d.ts +94 -3
- package/src/windowstate.js +1 -1
- package/src/yoga.js +1 -1
- 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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
browser engine underneath: this is not
|
|
16
|
-
`<div>` is not an element that exists.
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
[
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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,
|
|
51
|
-
those composites touch, against a checked-in
|
|
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
|
|
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
|
-
- **
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|  |  |
|
|
@@ -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
|
|
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(); //
|
|
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 `<
|
|
171
|
-
Everything else is
|
|
172
|
-
|
|
173
|
-
|
|
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 each — drawn 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
|
|
178
|
-
| `<popup>` | an
|
|
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
|
|
182
|
-
| `<
|
|
183
|
-
| `<
|
|
184
|
-
| `<
|
|
185
|
-
| `<
|
|
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
|
-
`
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
`
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
[
|
|
198
|
-
|
|
199
|
-
**The menu bar goes where the desktop keeps menus.** On
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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()`
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
[docs/remote.md](docs/remote.md), which is the
|
|
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
|
-
|
|
289
|
-
enabled (`+iglx` / `AllowIndirectGLX` — off by default on
|
|
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
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
|
323
|
-
|
|
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
|
|
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.
|
|
343
|
-
|
|
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
|
-
|
|
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
|
|
352
|
-
|
|
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
|
|
361
|
-
JSX line that created it in your editor. `REACT_X11_EDITOR` picks
|
|
362
|
-
and enables the feature in one go; `REACT_X11_CLICK_TO_COMPONENT=1`
|
|
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
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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,
|
|
428
|
-
[
|
|
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
|
-
|
|
438
|
-
|
|
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
|
|
457
|
-
other program's window contents, grab the keyboard, and
|
|
458
|
-
input. That is the 1987 design, not a gap in this
|
|
459
|
-
ways — do not run untrusted programs on a display
|
|
460
|
-
a react-x11 window as a confidential surface. Your
|
|
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
|
|
477
|
-
the XRender-backed 2d context, the text stack, the frame
|
|
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.
|