react-x11 1.2.0 → 2.0.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 +330 -81
- package/package.json +132 -14
- package/src/ClickToComponent.js +9 -2
- package/src/DevToolsIntegration.js +440 -32
- package/src/Reconciler.js +463 -165
- package/src/a11y.js +1299 -0
- package/src/acceleratorhooks.js +86 -0
- package/src/accelerators.js +128 -0
- package/src/activate.js +168 -0
- package/src/anchor.js +360 -0
- package/src/appcontext.js +161 -0
- package/src/appearance.js +748 -0
- package/src/appearancehooks.js +96 -0
- package/src/apphooks.js +73 -0
- package/src/application.js +748 -0
- package/src/atspi.js +1953 -0
- package/src/bus.js +545 -0
- package/src/bushooks.js +136 -0
- package/src/clientmessage.js +140 -0
- package/src/clipboard.js +121 -0
- package/src/components/Button.js +148 -33
- package/src/components/Checkbox.js +66 -32
- package/src/components/Dialog.js +96 -29
- package/src/components/FileDialog.js +491 -0
- package/src/components/Icon.js +329 -0
- package/src/components/Menu.js +797 -133
- package/src/components/PasswordInput.js +434 -0
- package/src/components/ProgressBar.js +91 -12
- package/src/components/Radio.js +78 -35
- package/src/components/Select.js +255 -65
- package/src/components/Slider.js +102 -41
- package/src/components/SplitPane.js +206 -0
- package/src/components/Switch.js +100 -22
- package/src/components/Table.js +529 -0
- package/src/components/Tabs.js +211 -0
- package/src/components/Tooltip.js +528 -54
- package/src/components/anchor.js +154 -107
- package/src/components/change.js +34 -0
- package/src/components/dnd.js +103 -0
- package/src/components/index.js +16 -3
- package/src/components/keys.js +17 -20
- package/src/components/scribble.js +247 -0
- package/src/components/theme.js +350 -42
- package/src/components/typeahead.js +12 -2
- package/src/compose.js +868 -0
- package/src/compositing.js +224 -0
- package/src/dbusmenu.js +384 -0
- package/src/debug.d.ts +63 -0
- package/src/debug.js +642 -0
- package/src/decorations.js +486 -0
- package/src/desktopsettings.js +209 -0
- package/src/desktopsettingshooks.js +62 -0
- package/src/dnd.js +1725 -0
- package/src/editmenu.js +272 -0
- package/src/errors.js +98 -0
- package/src/events.js +1406 -162
- package/src/extensions.js +45 -0
- package/src/filedialog.js +375 -0
- package/src/filedialoghooks.js +132 -0
- package/src/fonthooks.js +64 -0
- package/src/fonts.js +301 -0
- package/src/foreignnodes.js +519 -0
- package/src/frame/child.js +33 -0
- package/src/frame/childmain.js +274 -0
- package/src/frame/env.js +140 -0
- package/src/frame/index.js +444 -0
- package/src/frame/lifecycle.js +67 -0
- package/src/frame/protocol.js +179 -0
- package/src/frames.js +73 -0
- package/src/glbackend.js +114 -0
- package/src/glnodes.js +125 -67
- package/src/globalmenu.js +683 -0
- package/src/host.d.ts +88 -0
- package/src/host.js +42 -0
- package/src/idle.js +490 -0
- package/src/idlehooks.js +100 -0
- package/src/imagesource.js +349 -0
- package/src/index.d.ts +475 -0
- package/src/index.js +83 -9
- package/src/inputtime.js +206 -0
- package/src/jsx-dev-runtime.d.ts +21 -0
- package/src/jsx-dev-runtime.js +2 -0
- package/src/jsx-runtime.d.ts +39 -0
- package/src/jsx-runtime.js +6 -0
- package/src/keyboard.js +256 -0
- package/src/keyboardstate.js +278 -0
- package/src/keyboardstatehooks.js +58 -0
- package/src/keysyms.d.ts +149 -0
- package/src/keysyms.js +270 -0
- package/src/locale.js +170 -0
- package/src/localehooks.js +47 -0
- package/src/menuitem.js +223 -0
- package/src/node.d.ts +597 -0
- package/src/node.js +44 -0
- package/src/nodes.js +9546 -690
- package/src/ntk.d.ts +44 -0
- package/src/ntk.js +25 -0
- package/src/paintcache.js +366 -0
- package/src/palette.js +380 -0
- package/src/pastestate.js +66 -0
- package/src/portal.js +461 -0
- package/src/priority.js +26 -0
- package/src/refresh/index.d.ts +40 -0
- package/src/refresh/index.js +122 -0
- package/src/refresh/loader.d.ts +37 -0
- package/src/refresh/loader.js +401 -0
- package/src/refresh/register.d.ts +5 -0
- package/src/refresh/register.js +13 -0
- package/src/registry.js +232 -0
- package/src/scale.js +626 -0
- package/src/scalehooks.js +27 -0
- package/src/screencolor.js +640 -0
- package/src/screencolorhooks.js +101 -0
- package/src/screens.js +754 -0
- package/src/screenshooks.js +137 -0
- package/src/startup.js +302 -0
- package/src/style.d.ts +126 -0
- package/src/style.js +33 -0
- package/src/styles.js +1482 -6
- package/src/svgnodes.js +306 -0
- package/src/testing/a11y.js +484 -0
- package/src/testing/components.js +414 -0
- package/src/testing/events.js +407 -0
- package/src/testing/harness.js +455 -0
- package/src/testing/index.d.ts +558 -0
- package/src/testing/index.js +85 -0
- package/src/testing/mock-app.js +463 -0
- package/src/testing/pixels.js +152 -0
- package/src/testing/queries.js +224 -0
- package/src/textrange.js +83 -0
- package/src/textselection.js +439 -0
- package/src/trace-registry.js +63 -0
- package/src/transfer.js +93 -0
- package/src/types/appearance.d.ts +84 -0
- package/src/types/application.d.ts +173 -0
- package/src/types/components.d.ts +820 -0
- package/src/types/dbus.d.ts +177 -0
- package/src/types/elements.d.ts +897 -0
- package/src/types/events.d.ts +549 -0
- package/src/types/filedialog.d.ts +260 -0
- package/src/types/fonts.d.ts +124 -0
- package/src/types/frame.d.ts +146 -0
- package/src/types/globalmenu.d.ts +42 -0
- package/src/types/nodes.d.ts +199 -0
- package/src/types/screencolor.d.ts +84 -0
- package/src/types/style.d.ts +434 -0
- package/src/types/system.d.ts +287 -0
- package/src/windowid.js +151 -0
- package/src/windowstate.js +393 -0
- package/src/xsettings.js +336 -0
- package/src/yoga.d.ts +55 -0
- package/src/yoga.js +135 -0
- package/src/components/Canvas3D.js +0 -28
- package/src/geometry3d.js +0 -223
- package/src/pointer3d.js +0 -158
- package/src/raycast3d.js +0 -146
- package/src/richnodes.js +0 -436
- package/src/scene3d.js +0 -683
package/README.md
CHANGED
|
@@ -2,10 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/sidorares/react-x11/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
**[Documentation](https://sidorares.github.io/react-x11/)** ·
|
|
6
|
+
**[Playground](https://sidorares.github.io/react-x11/playground)** — edit
|
|
7
|
+
react-x11 and watch it render, in your browser, against a JavaScript X server
|
|
8
|
+
running on the page.
|
|
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 +
|
|
9
18
|
[XQuartz](https://www.xquartz.org/)) with your React / React Native
|
|
10
19
|
experience — flexbox layout, components, hooks, synthetic events.
|
|
11
20
|
|
|
@@ -19,20 +28,86 @@ in-process pure-JS X server that the tests render into and read pixels back
|
|
|
19
28
|
from; every screenshot below was rendered that way too, by driving the real
|
|
20
29
|
examples through the real event pipeline).
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
### The wire carries drawing, not pixels
|
|
32
|
+
|
|
33
|
+
react-x11 does not rasterize a frame on the client and ship the buffer
|
|
34
|
+
across. React reconciles the component tree, the renderer turns that diff
|
|
35
|
+
into **drawing operations** — rounded rectangles, composited gradients, clip
|
|
36
|
+
regions, runs of glyph indices — and the X server executes them. The server
|
|
37
|
+
owns the pixels; the client never had them.
|
|
38
|
+
|
|
39
|
+
That is what X's RENDER extension is for. Text is shaped once and its glyphs
|
|
40
|
+
uploaded once, so drawing a line afterwards names them by index, about a byte
|
|
41
|
+
per glyph; gradients, scaling, alpha compositing and clipping are single
|
|
42
|
+
server-side requests rather than loops over a pixel array; nothing is read
|
|
43
|
+
back. An update costs what the _drawing_ costs, not what the window's area
|
|
44
|
+
costs. Going full-screen on a 4K panel does not multiply your bandwidth,
|
|
45
|
+
because you were not sending pixels at 1080p either — which is why this
|
|
46
|
+
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.
|
|
48
|
+
|
|
49
|
+
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.
|
|
52
|
+
|
|
53
|
+
### Where this fits
|
|
54
|
+
|
|
55
|
+
X11 is the wire protocol, which means the display can be somewhere the
|
|
56
|
+
program is not, and the program can be somewhere a browser engine cannot go.
|
|
57
|
+
That is the shape of the problem this is good at:
|
|
58
|
+
|
|
59
|
+
- **the display is elsewhere** — a headless server over `ssh -X`, a
|
|
60
|
+
container pointed at the host, a thin client, an X terminal, a
|
|
61
|
+
deliberately dumb workstation. A whole window appears for under four
|
|
62
|
+
kilobytes, because what crosses the link is drawing rather than pixels
|
|
63
|
+
([docs/remote.md](docs/remote.md));
|
|
64
|
+
- **the machine cannot afford a browser engine** — a kiosk, an appliance, an
|
|
65
|
+
instrument panel, an ARM board with 512 MB, a locked-down box where
|
|
66
|
+
installing must not compile anything and root is not on offer;
|
|
67
|
+
- **you want the UI in the same process as the rest of your program** —
|
|
68
|
+
`fs`, `serialport`, `pg` and your components in one heap, one event loop,
|
|
69
|
+
no IPC bridge and no second bundler;
|
|
70
|
+
- **you want GUI tests that run in CI with no display server** — `npm test`
|
|
71
|
+
here renders real pixels through the real protocol into node-x11's
|
|
72
|
+
in-process X server, on a machine with no `$DISPLAY`, on macOS. That
|
|
73
|
+
harness is published as `react-x11/test`
|
|
74
|
+
([docs/testing.md](docs/testing.md)).
|
|
75
|
+
|
|
76
|
+
And the shape it is not good at, so you can stop here rather than in week
|
|
77
|
+
three:
|
|
78
|
+
|
|
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.
|
|
82
|
+
- **native Wayland.** There is no Wayland backend and there is not going to
|
|
83
|
+
be one; that would be a different renderer, not a flag. Ordinary
|
|
84
|
+
application windows work fine on a Wayland desktop through Xwayland, which
|
|
85
|
+
is not going away — but the desktop-shell half of X11 (panel struts,
|
|
86
|
+
global key grabs, screen capture, and the window-manager example below)
|
|
87
|
+
needs a real X session.
|
|
88
|
+
- **reusing web components.** There is no DOM. Your MUI, your Tailwind and
|
|
89
|
+
your `recharts` do not come with you; the state, data-fetching, validation
|
|
90
|
+
and math libraries mostly do. [docs/ecosystem.md](docs/ecosystem.md) says
|
|
91
|
+
which is which, and what the failure looks like when it is the wrong one.
|
|
92
|
+
- **rendering HTML.** There is no HTML element and no webview. Rich
|
|
93
|
+
documents are markdown, through `<Markdown>` in
|
|
94
|
+
[`@react-x11/components`](https://github.com/sidorares/react-x11-components).
|
|
95
|
+
- **text entry outside Latin.** See [Known issues](#known-issues).
|
|
96
|
+
|
|
97
|
+
| `examples/dashboard.jsx` — context theming, hooks | `examples/tasks.jsx` — useReducer, textinput, scrolling |
|
|
98
|
+
| ------------------------------------------------- | ------------------------------------------------------- |
|
|
99
|
+
|  |  |
|
|
25
100
|
|
|
26
101
|
| `examples/form.jsx` — textinput + Select | the open Select menu (a real `<popup>` window) |
|
|
27
102
|
| ---------------------------------------- | ---------------------------------------------- |
|
|
28
103
|
|  |  |
|
|
29
104
|
|
|
30
|
-
`examples/
|
|
31
|
-
|
|
32
|
-
|
|
105
|
+
`examples/viewer3d.jsx` — a model viewer over **indirect GLX**: the GL
|
|
106
|
+
protocol sent over the X connection, geometry compiled into a display list,
|
|
107
|
+
a frame costing two matrices and one `CallList`. No native bindings, no GPU
|
|
33
108
|
driver bindings — the same "JavaScript all the way down" story as the rest.
|
|
34
|
-
|
|
35
|
-
|
|
109
|
+
(GL renders where `GetImage` cannot read it, so there is no screenshot of it
|
|
110
|
+
here; see [docs/glx.md](docs/glx.md).)
|
|
36
111
|
|
|
37
112
|
## Quick start
|
|
38
113
|
|
|
@@ -47,17 +122,32 @@ import { createRoot } from 'react-x11';
|
|
|
47
122
|
function Counter() {
|
|
48
123
|
const [n, setN] = useState(0);
|
|
49
124
|
return (
|
|
50
|
-
<window
|
|
51
|
-
|
|
52
|
-
|
|
125
|
+
<window
|
|
126
|
+
width={240}
|
|
127
|
+
height={120}
|
|
128
|
+
title="counter"
|
|
129
|
+
style={{ backgroundColor: '#f4f4f4' }}
|
|
130
|
+
>
|
|
131
|
+
<box
|
|
132
|
+
style={{
|
|
133
|
+
flexGrow: 1,
|
|
134
|
+
alignItems: 'center',
|
|
135
|
+
justifyContent: 'center',
|
|
136
|
+
gap: 10,
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
<text style={{ fontSize: 24 }}>{String(n)}</text>
|
|
53
140
|
<box
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
141
|
+
style={{
|
|
142
|
+
backgroundColor: '#2980b9',
|
|
143
|
+
borderRadius: 6,
|
|
144
|
+
padding: 8,
|
|
145
|
+
cursor: 'pointer',
|
|
146
|
+
':hover': { backgroundColor: '#1f6693' },
|
|
147
|
+
}}
|
|
58
148
|
onClick={() => setN(n + 1)}
|
|
59
149
|
>
|
|
60
|
-
<text color
|
|
150
|
+
<text style={{ color: 'white' }}>+1</text>
|
|
61
151
|
</box>
|
|
62
152
|
</box>
|
|
63
153
|
</window>
|
|
@@ -80,53 +170,80 @@ Everything else is laid out by yoga and drawn client-side into the window's
|
|
|
80
170
|
double-buffered 2d context — see [NEXT_STEPS.md](NEXT_STEPS.md) for the architecture rationale
|
|
81
171
|
and [docs/](docs/README.md) for the full API reference.
|
|
82
172
|
|
|
83
|
-
| element
|
|
84
|
-
|
|
|
85
|
-
| `<window>`
|
|
86
|
-
| `<popup>`
|
|
87
|
-
| `<box>`
|
|
88
|
-
| `<
|
|
89
|
-
| `<
|
|
90
|
-
| `<
|
|
91
|
-
| `<
|
|
92
|
-
| `<
|
|
93
|
-
| `<
|
|
94
|
-
| `<html>` | ntk HtmlView: CSS cascade, block/flex layout, images; `onLink` |
|
|
95
|
-
| `<svg>` | static SVG through ntk SvgView, sized like `<image>` |
|
|
96
|
-
| `<tex>` | a KaTeX formula (ntk `layoutTex`), intrinsically sized |
|
|
97
|
-
| `<glarea>` | an OpenGL surface over indirect GLX; the 3D scene below lives inside it |
|
|
173
|
+
| element | what it is |
|
|
174
|
+
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
175
|
+
| `<window>` | a real X11 window; the flex, paint and event root |
|
|
176
|
+
| `<popup>` | an override-redirect window at screen coordinates — menus, tooltips, dropdowns |
|
|
177
|
+
| `<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 |
|
|
178
|
+
| `<text>` | shaped, wrapped text (bidi, ligatures, font fallback); nested `<text>` elements are style spans |
|
|
179
|
+
| `<textinput>` | single-line editor: caret/selection via ntk's TextLayout caret API, clipboard (CLIPBOARD + X11 PRIMARY), word select, undo/redo, right-click menu |
|
|
180
|
+
| `<image>` | PNG/JPEG from `src`, natural-size aware |
|
|
181
|
+
| `<canvas>` | escape hatch: `onDraw={(ctx, {width, height}) => …}` with ntk's canvas-like 2d context (XRender-backed) |
|
|
182
|
+
| `<svg>` | static SVG through ntk SvgView, sized like `<image>` |
|
|
183
|
+
| `<glarea>` | an OpenGL surface over indirect GLX; the 3D scene below lives inside it |
|
|
98
184
|
|
|
99
185
|
Widget **components** (plain React on top of the primitives, themable via
|
|
100
186
|
`ThemeProvider`): `Button`, `Checkbox`, `Radio`/`RadioGroup`, `Switch`,
|
|
101
|
-
`Slider`, `ProgressBar`, `Select`, `Tooltip`, `MenuBar`/`ContextMenu
|
|
102
|
-
`
|
|
103
|
-
|
|
187
|
+
`Slider`, `ProgressBar`, `Select`, `Tooltip`, `MenuBar`/`ContextMenu`,
|
|
188
|
+
`Calendar`/`DatePicker` — one date or a range, with days blockable and a
|
|
189
|
+
seam for drawing into the cells — `PasswordInput`, whose mask is a scribble
|
|
190
|
+
that moves on every keystroke rather than a countable row of bullets —
|
|
191
|
+
`Dialog` — a modal built on
|
|
192
|
+
`<popup trapFocus>`, which traps Tab and restores focus when it closes —
|
|
193
|
+
plus the two containers an application window is built from: `Tabs`, `Tree`
|
|
194
|
+
and `SplitPane`. See
|
|
195
|
+
[docs/components.md](docs/components.md).
|
|
196
|
+
|
|
197
|
+
**The menu bar goes where the desktop keeps menus.** On Plasma, on a panel
|
|
198
|
+
running `vala-panel-appmenu`, or under one of the GNOME extensions that add a
|
|
199
|
+
menu bar, `<MenuBar menus={…}/>` hands its menu to the panel over
|
|
200
|
+
`com.canonical.dbusmenu` and stops drawing one; everywhere else it draws the
|
|
201
|
+
bar itself. No configuration, and the same line of JSX either way — the item
|
|
202
|
+
descriptors are dbusmenu's own vocabulary, so the array that draws the menu is
|
|
203
|
+
the array that serialises. `npm run globalmenu:host` is a panel in a terminal,
|
|
204
|
+
for seeing it work on a desktop that has none. See
|
|
205
|
+
[docs/globalmenu.md](docs/globalmenu.md).
|
|
206
|
+
|
|
207
|
+
**`myapp://` links open the app that is already running.**
|
|
208
|
+
`registerApplication({ appId, schemes })` before `createRoot()` owns the app's
|
|
209
|
+
name on the bus, exports `org.freedesktop.Application`, and — on the half of
|
|
210
|
+
all desktops where `xdg-open` ignores D-Bus activation and spawns a second copy
|
|
211
|
+
with the URI in `argv` — forwards it to the first copy and tells that copy to
|
|
212
|
+
exit. `useAppOpen()` receives the link, buffered and replayed if it arrived
|
|
213
|
+
before the tree mounted, and `activateWindow()` brings the window forward with
|
|
214
|
+
the launch's own timestamp, without which a window manager declines and the
|
|
215
|
+
taskbar entry just blinks. The OAuth redirect this is for has an easier answer
|
|
216
|
+
that needs none of it, and the page says so first. See
|
|
217
|
+
[docs/uri-schemes.md](docs/uri-schemes.md).
|
|
218
|
+
|
|
219
|
+
**Drag and drop** works with the rest of the desktop: `<box dropAccept={['files']}
|
|
220
|
+
onDrop={…}>` takes a file dragged out of Nautilus, and `<box draggable dragData={…}>`
|
|
221
|
+
can be dropped into a file manager or an editor. Drags that stay inside the
|
|
222
|
+
app never touch the wire and hand their payload over by reference, so a
|
|
223
|
+
reorderable list and a cross-application file drop are the same two props.
|
|
224
|
+
See [docs/drag-and-drop.md](docs/drag-and-drop.md).
|
|
104
225
|
|
|
105
226
|
### 3D
|
|
106
227
|
|
|
107
|
-
|
|
108
|
-
|
|
228
|
+
`<glarea>` is a GL surface in the layout, drawn over **indirect GLX**: the GL
|
|
229
|
+
protocol encoded into the X connection, with no native bindings and no GPU
|
|
230
|
+
driver bindings — the same "JavaScript all the way down" story as the rest.
|
|
231
|
+
Geometry belongs in a server-side display list, so a frame costs matrices and
|
|
232
|
+
one `CallList` whatever the triangle count.
|
|
109
233
|
|
|
110
234
|
```jsx
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
</mesh>
|
|
118
|
-
</Canvas3D>
|
|
235
|
+
<glarea
|
|
236
|
+
style={{ flexGrow: 1 }}
|
|
237
|
+
frameLoop="always"
|
|
238
|
+
onCreated={(gl) => gl.Enable(gl.DEPTH_TEST)}
|
|
239
|
+
onDraw={(gl, { width, height }) => drawFrame(gl, width, height)}
|
|
240
|
+
/>
|
|
119
241
|
```
|
|
120
242
|
|
|
121
|
-
`<mesh>`,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
matrices plus one `CallList` per mesh whatever the triangle count. What the
|
|
126
|
-
protocol cannot do — shaders, instancing, post-processing, shadows — throws
|
|
127
|
-
with the reason rather than half-working. See
|
|
128
|
-
[docs/elements.md](docs/elements.md#3d-scene-mesh-group-geometries-materials)
|
|
129
|
-
and [docs/glx-plan.md](docs/glx-plan.md).
|
|
243
|
+
A scene graph over it — `<mesh>`, geometries, materials, lights, and shaders
|
|
244
|
+
or post-processing on the direct backend — is
|
|
245
|
+
[`@react-x11/components/three`](https://github.com/sidorares/react-x11-components).
|
|
246
|
+
See [docs/gl.md](docs/gl.md) and [docs/glx.md](docs/glx.md).
|
|
130
247
|
|
|
131
248
|
Events are synthetic with capture/bubble phases and hit testing over the
|
|
132
249
|
drawn tree: `onClick` (with DOM-style `detail` click counting),
|
|
@@ -134,43 +251,76 @@ drawn tree: `onClick` (with DOM-style `detail` click counting),
|
|
|
134
251
|
`focusable` + `onFocus`/`onBlur` + Tab traversal, and a `cursor` prop.
|
|
135
252
|
User handlers run before element default actions and can
|
|
136
253
|
`ev.preventDefault()` — stopping a `<textinput>` from editing or a
|
|
137
|
-
|
|
254
|
+
a scroll container from scrolling, like the DOM.
|
|
138
255
|
|
|
139
256
|
## Examples
|
|
140
257
|
|
|
141
|
-
All need an X server
|
|
258
|
+
All need an X server — but "an X server" is a broader thing than it sounds:
|
|
259
|
+
your Linux desktop, XQuartz on macOS, `Xvfb` for automation, `Xephyr` for a
|
|
260
|
+
disposable screen, a VNC server, or your own display reached over `ssh -X`
|
|
261
|
+
from wherever the program actually runs — see
|
|
262
|
+
[docs/remote.md](docs/remote.md), which is the case this architecture is
|
|
263
|
+
categorically better at.
|
|
264
|
+
[`examples/README.md`](examples/README.md) describes each one and how to
|
|
265
|
+
explore them:
|
|
142
266
|
|
|
143
267
|
```sh
|
|
144
268
|
npm run examples:simple # hello world (JSX via tsx)
|
|
269
|
+
npm run examples:app # the showcase: Tabs + SplitPane hosting the rest
|
|
270
|
+
npm run examples:theming # three themes x light/dark, and a size query
|
|
145
271
|
npm run examples:simple-nojsx # the same, plain node — no build step
|
|
146
272
|
npm run examples:xeyes # canvas drawing + hooks
|
|
147
273
|
npm run examples:dashboard # context theming, custom hooks, components
|
|
148
|
-
npm run examples:tasks # useReducer, textinput,
|
|
274
|
+
npm run examples:tasks # useReducer, textinput, scrolling
|
|
149
275
|
npm run examples:menu # right-click context menu via <popup>
|
|
276
|
+
npm run examples:transparent # rounded translucent <popup transparent>
|
|
150
277
|
npm run examples:form # <textinput> + Select dropdowns
|
|
151
|
-
npm run examples:
|
|
152
|
-
npm run examples:
|
|
278
|
+
npm run examples:datepicker # Calendar/DatePicker: ranges, blocked days, events
|
|
279
|
+
npm run examples:password # PasswordInput: the scribble mask, and a custom one
|
|
280
|
+
npm run examples:viewer3d # raw GL in a <glarea>: a model viewer over indirect GLX
|
|
281
|
+
npm run examples:wm # a reparenting window manager (see below)
|
|
153
282
|
```
|
|
154
283
|
|
|
155
284
|
The two GL examples additionally need a server with **indirect GLX**
|
|
156
285
|
enabled (`+iglx` / `AllowIndirectGLX` — off by default on many).
|
|
157
286
|
|
|
287
|
+
### Window manager
|
|
288
|
+
|
|
289
|
+
`examples:wm` is a real reparenting window manager: it takes over the root
|
|
290
|
+
window, puts every application's window inside a frame it draws, and moves,
|
|
291
|
+
resizes, focuses and closes them. The frames are ordinary react-x11
|
|
292
|
+
`<window>`s — the titlebar, the buttons and the eight resize handles are
|
|
293
|
+
components with `onMouseDown` handlers, and the application is a foreign X
|
|
294
|
+
window reparented inside.
|
|
295
|
+
|
|
296
|
+
Only one window manager may own a display, so run it against a nested
|
|
297
|
+
server rather than the one managing your desktop:
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
Xephyr :10 -screen 1200x800 &
|
|
301
|
+
DISPLAY=:10 npm run examples:wm
|
|
302
|
+
DISPLAY=:10 xterm & # give it something to manage
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
To have it manage your real session instead — including replacing
|
|
306
|
+
`quartz-wm` on macOS, which XQuartz has a hook for — see
|
|
307
|
+
[`examples/README.md`](examples/README.md#the-window-manager).
|
|
308
|
+
|
|
158
309
|
### Hot reloading
|
|
159
310
|
|
|
160
311
|
```sh
|
|
161
312
|
npm run examples:tasks:hot # then edit examples/tasks.jsx while it runs
|
|
162
313
|
```
|
|
163
314
|
|
|
164
|
-
Runs the tasks example under
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
the hot entry — the pattern works for any example split the same way.
|
|
315
|
+
Runs the tasks example under the supported hot-reload entry point,
|
|
316
|
+
`react-x11/refresh` (`node --import react-x11/refresh/register`, Node ≥
|
|
317
|
+
22.15), with React **Fast Refresh**: saving `examples/tasks.jsx` updates
|
|
318
|
+
the edited components in place. The X11 connection, the mounted window,
|
|
319
|
+
and component state — the task list, even half-typed text in the input —
|
|
320
|
+
survive the reload (a component whose hook signature changed remounts
|
|
321
|
+
alone). Any app adopts it the same way — no accept handlers, no loader
|
|
322
|
+
files to copy; see
|
|
323
|
+
[docs/ecosystem/dev-tooling.md](docs/ecosystem/dev-tooling.md#react-refresh).
|
|
174
324
|
|
|
175
325
|
## React DevTools
|
|
176
326
|
|
|
@@ -180,10 +330,14 @@ REACT_X11_DEVTOOLS=1 npm run examples:dashboard # 2. run any example with the
|
|
|
180
330
|
```
|
|
181
331
|
|
|
182
332
|
The component tree, props and hook state show up live in the DevTools
|
|
183
|
-
window
|
|
184
|
-
tree tints its rect in the X11 window
|
|
185
|
-
|
|
186
|
-
|
|
333
|
+
window, and editing any of them re-renders the app. Hovering an element in
|
|
334
|
+
the tree tints its rect in the X11 window; the toolbar's crosshair picks an
|
|
335
|
+
element by clicking it in the app; "highlight updates when components
|
|
336
|
+
render" outlines the rects that just re-rendered; the style editor edits a
|
|
337
|
+
selected element's style live; and the Profiler can restart the app to
|
|
338
|
+
record its mount. `REACT_X11_DEVTOOLS_HOST` / `REACT_X11_DEVTOOLS_PORT`
|
|
339
|
+
override the default `localhost:8097`. See
|
|
340
|
+
[docs/devtools.md](docs/devtools.md).
|
|
187
341
|
|
|
188
342
|
Two more debugging aids:
|
|
189
343
|
|
|
@@ -205,23 +359,118 @@ and enables the feature in one go; `REACT_X11_CLICK_TO_COMPONENT=1` does
|
|
|
205
359
|
the same with the default editor
|
|
206
360
|
(`cursor`). See [docs/click-to-component.md](docs/click-to-component.md).
|
|
207
361
|
|
|
362
|
+
## TypeScript
|
|
363
|
+
|
|
364
|
+
Types ship with the package — no `@types/react-x11` to install. Point JSX at
|
|
365
|
+
react-x11 and the X11 elements type-check:
|
|
366
|
+
|
|
367
|
+
```jsonc
|
|
368
|
+
// tsconfig.json
|
|
369
|
+
{
|
|
370
|
+
"compilerOptions": {
|
|
371
|
+
"jsx": "react-jsx",
|
|
372
|
+
"jsxImportSource": "react-x11",
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
import { createRoot, createStyles, Button } from 'react-x11';
|
|
379
|
+
import type { MouseEvent } from 'react-x11';
|
|
380
|
+
|
|
381
|
+
const s = createStyles({
|
|
382
|
+
root: { flexGrow: 1, padding: 12, gap: 8 },
|
|
383
|
+
// ^ flexDirection: 'sideways' would not compile,
|
|
384
|
+
// and neither would a layout property inside ':hover'
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
<window title="hi" width={320} height={200} style={s.root}>
|
|
388
|
+
<box onClick={(ev: MouseEvent) => console.log(ev.detail)}>
|
|
389
|
+
<text style={{ fontSize: 18 }}>hello</text>
|
|
390
|
+
</box>
|
|
391
|
+
<Button primary onPress={() => {}}>
|
|
392
|
+
ok
|
|
393
|
+
</Button>
|
|
394
|
+
</window>;
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`jsxImportSource` is what makes `<div>` an error rather than something that
|
|
398
|
+
compiles and then throws at runtime: react-x11 owns the JSX namespace
|
|
399
|
+
instead of adding to React's, which it could not do anyway — `text`,
|
|
400
|
+
`image`, `canvas`, `html` and `svg` are all DOM element names too, with
|
|
401
|
+
incompatible props.
|
|
402
|
+
|
|
403
|
+
Style props, element props, event objects, ref types and every widget are
|
|
404
|
+
typed; `style` accepts the same nested arrays with falsy entries the runtime
|
|
405
|
+
does. See [docs/typescript.md](docs/typescript.md).
|
|
406
|
+
|
|
208
407
|
## Developing
|
|
209
408
|
|
|
210
409
|
```sh
|
|
211
410
|
npm test # hermetic: mock smoke tests + in-process X server pixels
|
|
411
|
+
npm run typecheck # tsc over the declarations and the type tests
|
|
212
412
|
npm run lint # ESLint
|
|
213
413
|
npm run format # Prettier
|
|
214
414
|
npm run screenshots # regenerate docs/img/*.png headlessly (no X server)
|
|
415
|
+
npm run docs:dev # the documentation site (npm install in website/ first)
|
|
215
416
|
```
|
|
216
417
|
|
|
217
|
-
|
|
218
|
-
|
|
418
|
+
3D is the exception: the headless path has no GL, so a 3D shot has to be
|
|
419
|
+
captured by hand from a real server
|
|
219
420
|
with indirect GLX.
|
|
220
421
|
|
|
221
422
|
See [AGENTS.md](AGENTS.md) for architecture notes and contributor/agent
|
|
222
423
|
guidance, [docs/](docs/README.md) for API documentation, and
|
|
223
424
|
[NEXT_STEPS.md](NEXT_STEPS.md) for the roadmap.
|
|
224
425
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
426
|
+
The [documentation site](https://sidorares.github.io/react-x11/) lives in
|
|
427
|
+
[`website/`](website/) and renders `docs/` rather than repeating it — edit
|
|
428
|
+
`docs/` and the site follows. Its playground bundles this repo's `src/` for
|
|
429
|
+
the browser, so `npm run docs:test` fails when a demo stops matching the API.
|
|
430
|
+
|
|
431
|
+
## Known issues
|
|
432
|
+
|
|
433
|
+
Two that are worth knowing before you hit them, both predating the current
|
|
434
|
+
release and both tracked:
|
|
435
|
+
|
|
436
|
+
- **`sans-serif` can resolve to a CJK font on macOS**
|
|
437
|
+
([#86](https://github.com/sidorares/react-x11/issues/86)). Font families
|
|
438
|
+
are resolved by shelling out to `fc-match`, which follows `PATH`;
|
|
439
|
+
Homebrew's fontconfig ships no macOS system-font aliases and answers
|
|
440
|
+
Hiragino Sans. Latin looks fine, Cyrillic comes out on full-width
|
|
441
|
+
advances. Put `/opt/X11/bin` first on `PATH` until this is fixed.
|
|
442
|
+
- **AltGr levels do not work, and there is no input method.** `@` on a
|
|
443
|
+
German layout, `€`, `ł`, `ã` on US-International: the core keyboard map
|
|
444
|
+
cannot say whether the third and fourth keysyms on a key are a second
|
|
445
|
+
layout or a third and fourth level, so they are read as a layout and left
|
|
446
|
+
alone. Dead keys, Compose and layout switching all work
|
|
447
|
+
([docs/events.md](docs/events.md)); what is absent is an ibus/fcitx/XIM
|
|
448
|
+
backend, so CJK is not partially working — it is structurally absent.
|
|
449
|
+
|
|
450
|
+
## Security
|
|
451
|
+
|
|
452
|
+
X11 has no isolation between clients: any program on a display can read any
|
|
453
|
+
other program's window contents, grab the keyboard, and synthesize or record
|
|
454
|
+
input. That is the 1987 design, not a gap in this library, and it cuts both
|
|
455
|
+
ways — do not run untrusted programs on a display you use, and do not treat
|
|
456
|
+
a react-x11 window as a confidential surface. Your `$XAUTHORITY` cookie is a
|
|
457
|
+
bearer token; treat it like a password.
|
|
458
|
+
|
|
459
|
+
`ssh -X` runs your app as an untrusted client and restricts most of the
|
|
460
|
+
above; `ssh -Y` turns the restrictions off. **Prefer `-X` — react-x11 should
|
|
461
|
+
work under it, and if it does not, that is a bug worth filing.**
|
|
462
|
+
|
|
463
|
+
The full threat model, including what react-x11 does and does not defend
|
|
464
|
+
against, is [docs/security.md](docs/security.md). To report something,
|
|
465
|
+
[SECURITY.md](SECURITY.md).
|
|
466
|
+
|
|
467
|
+
## See also
|
|
468
|
+
|
|
469
|
+
- [awesome-react-renderer](https://github.com/chentsulin/awesome-react-renderer)
|
|
470
|
+
— the catalogue of React renderers, which is how most people find things
|
|
471
|
+
like this.
|
|
472
|
+
- [ntk](https://github.com/sidorares/ntk) — the toolkit underneath: windows,
|
|
473
|
+
the XRender-backed 2d context, the text stack, the frame clock.
|
|
474
|
+
- [node-x11](https://github.com/sidorares/node-x11) — the X11 protocol in
|
|
475
|
+
JavaScript, including the in-process X server the tests and the playground
|
|
476
|
+
run against.
|