react-x11 2.51.0 → 2.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -54
- package/docs/README.md +410 -0
- package/docs/accessibility.md +628 -0
- package/docs/appearance.md +416 -0
- package/docs/architecture/accessibility.md +470 -0
- package/docs/architecture/animation.md +707 -0
- package/docs/architecture/container-queries.md +525 -0
- package/docs/architecture/custom-layout.md +334 -0
- package/docs/architecture/drag-and-drop.md +941 -0
- package/docs/architecture/element-layer-contents.md +622 -0
- package/docs/architecture/frame-pacing.md +255 -0
- package/docs/architecture/grid-layout.md +358 -0
- package/docs/architecture/protocol-efficiency.md +357 -0
- package/docs/architecture/video.md +1149 -0
- package/docs/architecture/windowed-regions.md +854 -0
- package/docs/click-to-component.md +125 -0
- package/docs/clipboard.md +281 -0
- package/docs/components.md +1749 -0
- package/docs/dbus.md +277 -0
- package/docs/debugging.md +464 -0
- package/docs/desktop-calendar.md +284 -0
- package/docs/desktop.md +648 -0
- package/docs/devtools.md +250 -0
- package/docs/drag-and-drop.md +663 -0
- package/docs/ecosystem/animation.md +394 -0
- package/docs/ecosystem/data-fetching.md +201 -0
- package/docs/ecosystem/dev-tooling.md +436 -0
- package/docs/ecosystem/forms.md +313 -0
- package/docs/ecosystem/headless.md +219 -0
- package/docs/ecosystem/i18n.md +89 -0
- package/docs/ecosystem/icons.md +410 -0
- package/docs/ecosystem/layout.md +216 -0
- package/docs/ecosystem/routing.md +146 -0
- package/docs/ecosystem/state.md +361 -0
- package/docs/ecosystem/testing.md +500 -0
- package/docs/ecosystem/theming.md +540 -0
- package/docs/ecosystem.md +473 -0
- package/docs/elements.md +2762 -0
- package/docs/embedding.md +288 -0
- package/docs/events.md +1025 -0
- package/docs/extending.md +2242 -0
- package/docs/eyedropper.md +223 -0
- package/docs/filedialog.md +258 -0
- package/docs/frame.md +580 -0
- package/docs/gl.md +170 -0
- package/docs/globalmenu.md +278 -0
- package/docs/glx.md +137 -0
- package/docs/macos.md +3295 -0
- package/docs/notifications.md +164 -0
- package/docs/packaging.md +611 -0
- package/docs/permissions.md +148 -0
- package/docs/react-features.md +430 -0
- package/docs/remote.md +223 -0
- package/docs/scale.md +339 -0
- package/docs/security.md +132 -0
- package/docs/styling.md +1928 -0
- package/docs/system.md +599 -0
- package/docs/testing.md +514 -0
- package/docs/typescript.md +117 -0
- package/docs/uri-schemes.md +380 -0
- package/docs/wayland-backend.md +591 -0
- package/docs/wayland.md +545 -0
- package/docs/windows-embedding.md +271 -0
- package/docs/windows-gl.md +221 -0
- package/docs/windows-integrations.md +233 -0
- package/docs/windows.md +1404 -0
- package/package.json +10 -3
- package/src/bus.js +39 -26
- package/src/cli/create.d.ts +43 -0
- package/src/cli/create.js +305 -0
- package/src/cli/env.js +484 -0
- package/src/cli/index.js +29 -2
- package/src/cli/templates/AGENTS.template.md +195 -0
- package/src/cli/templates/DESIGN.template.md +269 -0
- package/src/desktopintegration.js +2 -1
- package/src/desktopsettings.js +193 -24
- package/src/filedialog.js +3 -3
- package/src/keyboardstate.js +28 -0
- package/src/node.d.ts +10 -10
- package/src/portal.js +31 -0
- package/src/screencolor.js +5 -10
- package/src/types/system.d.ts +8 -6
- package/src/wayland/app.js +59 -0
- package/src/wayland/clipboard.js +14 -7
- package/src/wayland/connection.js +3 -0
- package/src/wayland/context2d.js +129 -10
- package/src/wayland/protocols/index.json +6 -0
- package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
- package/src/wayland/seat.js +28 -1
- package/src/wayland/swapchain.js +23 -0
- package/src/wayland/xkb.js +137 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
# The Wayland backend
|
|
2
|
+
|
|
3
|
+
**Status: a working backend, opt-in.** react-x11's own reconciler renders a
|
|
4
|
+
window on a real Wayland compositor (mutter/GNOME, sway, labwc) with
|
|
5
|
+
decorations — the compositor's where it draws them, client-side where it
|
|
6
|
+
does not — text, shadows, paths, images, live partial repaints, pointer and
|
|
7
|
+
keyboard input decoded against the compositor's keymap, a clipboard, popups,
|
|
8
|
+
layer-shell docks and panels, and a screen capture with an eyedropper on
|
|
9
|
+
top — drawn on the GPU and handed over as dma-buf, on **Node or Bun**, with
|
|
10
|
+
no X server and no Xwayland anywhere in the path.
|
|
11
|
+
|
|
12
|
+
This is the companion to [wayland.md](wayland.md), the research RFC it
|
|
13
|
+
implements. Where the two disagree, this one is the one that ran.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
REACT_X11_BACKEND=wayland node your-app.mjs # x11-dri >= 0.9 (native socket)
|
|
17
|
+
REACT_X11_BACKEND=wayland bun your-app.mjs # or Bun's bun:ffi transport
|
|
18
|
+
node examples/wayland-gl.mjs # GPU plasma, paced by the compositor
|
|
19
|
+
node examples/wayland-ui.mjs # every drawing primitive, no React
|
|
20
|
+
node --import tsx --test 'test/wayland/*.test.js' # the suite: no display needed
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The two packages it needs beyond react-x11's own are optional dependencies,
|
|
24
|
+
installed with it: [`@windowkit/wayland`](https://github.com/windowkit/wayland),
|
|
25
|
+
the protocol layer — a fork of `wayland-client` that passes file descriptors
|
|
26
|
+
— and [`x11-dri`](https://github.com/sidorares/node-x11-dri) 0.9 or later,
|
|
27
|
+
the GPU context and, on Node, the socket.
|
|
28
|
+
|
|
29
|
+
The backend is never chosen by `'auto'`: X11 stays the default on Linux
|
|
30
|
+
because the remote case ([remote.md](remote.md)) is the flagship reason this
|
|
31
|
+
project exists and Wayland has no network transparency.
|
|
32
|
+
|
|
33
|
+
## How to tell it is not Xwayland
|
|
34
|
+
|
|
35
|
+
A GNOME session answers `$DISPLAY` too, through Xwayland, so "the window
|
|
36
|
+
appeared" proves nothing. Three checks that do:
|
|
37
|
+
|
|
38
|
+
- **Unset `DISPLAY`.** The Wayland backend never opens it; the X11 backend
|
|
39
|
+
cannot start without it.
|
|
40
|
+
```bash
|
|
41
|
+
env -u DISPLAY REACT_X11_BACKEND=wayland node app.mjs # works only natively
|
|
42
|
+
```
|
|
43
|
+
- **Ask the app.** `root.app.backend === 'wayland'`, and `root.app.X` is the
|
|
44
|
+
stand-in from `app.js` — no `display.screen`, no `X.require`. The examples
|
|
45
|
+
print the transport they connected through (`connected via x11-dri` or
|
|
46
|
+
`fdpass-bun`) and never import an X module at all.
|
|
47
|
+
- **Ask the desktop.** `xlsclients` and `xwininfo -root -tree` list Xwayland
|
|
48
|
+
clients; a native window is absent from both. GNOME's Looking Glass
|
|
49
|
+
(`Alt+F2`, `lg`, Windows tab) shows it as a `MetaWindowWayland`, and its
|
|
50
|
+
frame is this backend's own titlebar (`decorations.js`) rather than
|
|
51
|
+
mutter's Adwaita frame, which only Xwayland windows get.
|
|
52
|
+
|
|
53
|
+
## What runs
|
|
54
|
+
|
|
55
|
+
| piece | file | what it does |
|
|
56
|
+
| ------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| connection | `connection.js` | fd-capable transport (native or bun:ffi), vendored protocol JSON |
|
|
58
|
+
| shell | `window.js` | xdg toplevels and popups, deferred `ack_configure`, fractional scale |
|
|
59
|
+
| GPU presentation | `swapchain.js`, `dmabuf.js`, `glcontext.js`, `target.js` | backing target → per-buffer damage → dma-buf |
|
|
60
|
+
| 2d context | `context2d.js`, `glyphatlas.js` | rects/rounded rects (SDF), paths (coverage masks, stencil), exact clips, gradients, patterns, images, text |
|
|
61
|
+
| `<glarea>` | `glarea.js` | a rect of the window's backing target as the area's GL surface; composited panes for its children |
|
|
62
|
+
| input | `seat.js`, `xkb.js`, `keysymnames.js`, `input.js` | pointer frames, keymap parsing, key repeat, routing |
|
|
63
|
+
| touch and tablet | `touch.js`, `tablet.js` | `wl_touch` and `zwp_tablet_v2`, emulating the pointer; raw touches |
|
|
64
|
+
| decorations | `decorations.js`, `ssd.js` | xdg-decoration or KDE's, to hand the frame to the compositor; the client-side frame where neither |
|
|
65
|
+
| layer surfaces | `layershell.js` | docks, panels, wallpapers, overlays — wlr-layer-shell |
|
|
66
|
+
| screen capture | `screencopy.js`, `shm.js` | wlr-screencopy / ext-image-copy-capture into `wl_shm`; the eyedropper |
|
|
67
|
+
| offscreen surfaces | `surface.js`, `device.js` | the paint cache and scroll blits, over a render target; a held context works |
|
|
68
|
+
| clipboard | `clipboard.js`, `fdutil.js` | `wl_data_device` + primary selection, over pipes |
|
|
69
|
+
| screens | `outputs.js` | `wl_output` + xdg_output into `screens.js`: `useScreens()`, the cap |
|
|
70
|
+
| input methods | `textinput.js` | `zwp_text_input_v3`: the compositor's IME into the composition events |
|
|
71
|
+
| the app | `app.js`, `backendwindow.js`, `framewatch.js` | what `createRoot({ backend: 'wayland' })` renders through |
|
|
72
|
+
| tests | `test/wayland/` | an in-process compositor, and the pure parts |
|
|
73
|
+
|
|
74
|
+
### Running the examples
|
|
75
|
+
|
|
76
|
+
Every example runs on this backend the way it runs on X11, with the display
|
|
77
|
+
taken away so nothing can fall back:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
env -u DISPLAY REACT_X11_BACKEND=wayland bun examples/widgets.jsx # or node --import tsx
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Two are X11 by nature and say so: `examples/wm.jsx` is a window manager and
|
|
84
|
+
`examples/xeyes.jsx` reads the root window's pointer — neither has a
|
|
85
|
+
Wayland counterpart. `examples/viewer3d.jsx` draws its model here through
|
|
86
|
+
its direct half, GLES on the window's own GPU target — the same half X11
|
|
87
|
+
uses under `NTK_GL_POLICY=direct`.
|
|
88
|
+
|
|
89
|
+
Two environment variables help when a desktop offers no screenshot API:
|
|
90
|
+
`REACT_X11_WAYLAND_TRACE=1` writes a line per presented frame, and
|
|
91
|
+
`REACT_X11_WAYLAND_SNAPSHOT=/tmp/frame.png` writes the backing target of
|
|
92
|
+
the first window at its 30th present (`REACT_X11_WAYLAND_SNAPSHOT_AT`
|
|
93
|
+
changes which) — the pixels the compositor was handed, read back from the
|
|
94
|
+
GPU. A third, `REACT_X11_WAYLAND_FRAME_STALL_MS`, is how long a frame
|
|
95
|
+
callback may go unanswered before the backend says the compositor has
|
|
96
|
+
stopped showing the window (2000; `0` turns that off) — see
|
|
97
|
+
[A surface the compositor stops showing stops drawing](#a-surface-the-compositor-stops-showing-stops-drawing).
|
|
98
|
+
|
|
99
|
+
The GPU context is made on `glPolicy.devicePath`, else on
|
|
100
|
+
`REACT_X11_GL_DEVICE`, else on the first render node, and a device named
|
|
101
|
+
either way is the only one tried. A machine with no render node at all — a
|
|
102
|
+
GitHub-hosted runner, a Hyper-V or VirtualBox guest — usually still has a
|
|
103
|
+
card node Mesa renders on in software through kms_swrast, so the backend
|
|
104
|
+
then tries each `/dev/dri/card*` in turn. On a hosted runner that is
|
|
105
|
+
`card1`, and headless weston with its GL renderer accepts the buffers it
|
|
106
|
+
makes, as `scripts/wayland-ci-probe.mjs` measured.
|
|
107
|
+
|
|
108
|
+
## What was measured
|
|
109
|
+
|
|
110
|
+
Everything below was run on this machine (mutter, virtio-gpu/virgl over an
|
|
111
|
+
Apple M1 Pro host, 2488×1668 @ 75Hz). Numbers from a VM's GPU are the VM's;
|
|
112
|
+
the protocol numbers are not.
|
|
113
|
+
|
|
114
|
+
### The protocol layer is not the cost
|
|
115
|
+
|
|
116
|
+
Asked first, because it decides whether a native protocol bridge is
|
|
117
|
+
justified. Measured with `@windowkit/wayland`, per request and per
|
|
118
|
+
event:
|
|
119
|
+
|
|
120
|
+
| measure | Bun | Node (native socket) |
|
|
121
|
+
| ---------------------------------------- | ---------------------------- | ------------------------- |
|
|
122
|
+
| encode + write, async API | 0.32 M req/s | 0.20 M req/s |
|
|
123
|
+
| encode + write, `$` synchronous API | 0.49 M req/s | **0.67 M req/s** (1.5 µs) |
|
|
124
|
+
| decode + dispatch | 2.3 M ev/s | **3.0 M ev/s** (0.33 µs) |
|
|
125
|
+
| round trip `sync` → `done`, plain socket | 0.141 ms p50 | 0.119 ms p50 |
|
|
126
|
+
| round trip, fd-capable socket | 0.178 ms p50 (reader thread) | **0.097 ms p50** |
|
|
127
|
+
|
|
128
|
+
A frame issues a handful of requests and input peaks near a thousand events
|
|
129
|
+
a second: the JavaScript protocol layer is one to two orders of magnitude
|
|
130
|
+
below anything visible. **No native protocol bridge was built, on purpose.**
|
|
131
|
+
The one native piece is the socket itself, and only because Node cannot
|
|
132
|
+
receive descriptors at all; its round trip turned out lower than Node's own
|
|
133
|
+
`net.Socket`'s, and the thread-hop cost of the Bun transport (~37 µs) is real
|
|
134
|
+
but not worth acting on.
|
|
135
|
+
|
|
136
|
+
### Where a frame goes
|
|
137
|
+
|
|
138
|
+
| phase | p50 |
|
|
139
|
+
| ---------------------------------- | ------- |
|
|
140
|
+
| `makeCurrent` | 0.04 ms |
|
|
141
|
+
| draw | 0.05 ms |
|
|
142
|
+
| Wayland (attach / damage / commit) | 0.09 ms |
|
|
143
|
+
| **`Surface.swap()`** | 24.5 ms |
|
|
144
|
+
|
|
145
|
+
The cost is `swap()`, and it is environmental: with no compositor at all,
|
|
146
|
+
`swap()` is 0.07 ms and `glFinish()` is ~10 ms — the same at 64×64 as at
|
|
147
|
+
720×480. Size-independent means host round-trip latency, not fill rate; this
|
|
148
|
+
is virgl in a VM. On bare metal the number should mostly disappear and must
|
|
149
|
+
be re-measured there before any conclusion about the backend's performance is
|
|
150
|
+
drawn from it.
|
|
151
|
+
|
|
152
|
+
The 2d context on the UI demo: 252 quads, 239 glyphs and 2 paths in **23
|
|
153
|
+
draw calls**.
|
|
154
|
+
|
|
155
|
+
## The design, where it is not the obvious one
|
|
156
|
+
|
|
157
|
+
**A backing target, not the swapchain, is what gets painted.** react-x11's
|
|
158
|
+
renderer repaints dirty rectangles into a persistent surface, as it would an
|
|
159
|
+
X pixmap. A GBM swapchain rotates, so the buffer a frame lands in last showed
|
|
160
|
+
the frame before last, and a partial repaint into it leaves stale pixels
|
|
161
|
+
everywhere else. So the renderer paints into a persistent render target
|
|
162
|
+
(`target.js`) and the changed rectangles are copied into whichever buffer
|
|
163
|
+
comes round. GBM names the buffer only _after_ the swap, so the copy uses the
|
|
164
|
+
union of what every known buffer still owes — and everything, until a full
|
|
165
|
+
rotation has passed without meeting a new buffer. The tests pin the rule
|
|
166
|
+
(`swapchain.test.js`); a React counter ticking at 400 ms presents frames
|
|
167
|
+
with **one** damage rectangle.
|
|
168
|
+
|
|
169
|
+
**Analytic shapes first, stencil second.** Rounded rectangles — most of a
|
|
170
|
+
UI — are signed distance fields in the fragment shader: antialiased without
|
|
171
|
+
multisampling, and every box in a window batches into one draw. Arbitrary
|
|
172
|
+
paths (`<svg>`, `ctx.fill(path2d)`) are flattened by ntk's own `flattenPath`
|
|
173
|
+
and filled stencil-then-cover on the target's stencil attachment. Strokes
|
|
174
|
+
are the union of segment quads and join polygons under the non-zero rule.
|
|
175
|
+
Box shadows are a feathered SDF (`ctx.fillShadow`, which `boxpaint.js` now
|
|
176
|
+
prefers when a context has it) rather than a CPU blur through the paint
|
|
177
|
+
cache.
|
|
178
|
+
|
|
179
|
+
**An image in perspective is one quad.** `drawImageProjected(image, m)`
|
|
180
|
+
draws an image through a 3×3 projection — a plane turned out of the page,
|
|
181
|
+
as CSS `rotateY()` in a `perspective` turns one — with each corner's texture
|
|
182
|
+
coordinates over its w and the fragment dividing them back, which is exact
|
|
183
|
+
across the quad. A context that can only draw through an affine matrix has
|
|
184
|
+
to cut such a plane into tiles, each drawn through the matrix nearest the
|
|
185
|
+
projection over it and clipped to it, and here every tile is a batch of its
|
|
186
|
+
own because the clip is a scissor: `<Html>` drew Zen Garden 219's sidebar
|
|
187
|
+
as 258 of them a frame, at 11 frames a second on a virgl VM, where seven
|
|
188
|
+
tiles ran at 25. X11 has no such call on purpose: RENDER takes a projective
|
|
189
|
+
picture transform, but glamor — Xwayland's, and most Xorg desktops' —
|
|
190
|
+
divides at the corners and folds the image along its diagonal, and pixman
|
|
191
|
+
runs out of 16.16 precision on a plane of any size. The method answers
|
|
192
|
+
false and draws nothing where a corner is behind the viewer, for the caller
|
|
193
|
+
to draw the part in front some other way.
|
|
194
|
+
|
|
195
|
+
**The ack is deferred to the frame that adopts it.** `xdg_surface.configure`
|
|
196
|
+
is acknowledged immediately before the commit of a frame painted at the
|
|
197
|
+
configured size, not on arrival — acking early tells the compositor the
|
|
198
|
+
next commit is at the new size, and a buffer painted at the old one is then
|
|
199
|
+
shown stretched.
|
|
200
|
+
|
|
201
|
+
**Everything the renderer sees is content-relative device pixels.** The
|
|
202
|
+
surface is larger than the content by the client-side frame, and the
|
|
203
|
+
compositor speaks logical pixels; the two conversions happen in
|
|
204
|
+
`backendwindow.js` and `input.js` and nowhere else.
|
|
205
|
+
|
|
206
|
+
**The keymap is parsed, not dlopen'd.** The compositor sends an XKB keymap
|
|
207
|
+
as a file descriptor; `xkb.js` reads keycodes, types (modifier → level
|
|
208
|
+
maps), symbols per group, and resolves virtual modifiers like `LevelThree`
|
|
209
|
+
to real ones through `interpret` + `modifier_map` — so AltGr works because
|
|
210
|
+
the keymap says which bit it is, not because Mod5 was assumed. The output is
|
|
211
|
+
both the X core `keycode2keysyms` table the accelerator code already speaks
|
|
212
|
+
and a full `decode()` for levels 3 and 4. Key repeat is synthesised at the
|
|
213
|
+
seat's advertised rate; compositors do not repeat.
|
|
214
|
+
|
|
215
|
+
**`useKeyboardState()` is the seat's.** There is no XKB extension to ask, so
|
|
216
|
+
the locks and the group come from `wl_keyboard.modifiers` — Num Lock being
|
|
217
|
+
whichever bit the keymap binds `NumLock` to — and the layout codes from the
|
|
218
|
+
keymap's own `xkb_symbols` name, which libxkbcommon writes from the include
|
|
219
|
+
string the rules built: `pc_us_ru_2_inet(evdev)` is `['us', 'ru']`. A keymap
|
|
220
|
+
not built from the rules has no codes to give, and `layouts` stays empty.
|
|
221
|
+
The protocol's one limit: modifiers go to the focused client only, so a lock
|
|
222
|
+
toggled while another app has the keyboard shows when focus comes back.
|
|
223
|
+
|
|
224
|
+
**The input method is told at the end of the frame.** `zwp_text_input_v3`
|
|
225
|
+
wants to know which field has focus, what kind of text it holds, where its
|
|
226
|
+
caret is and what surrounds it, and every one of those changes repaints the
|
|
227
|
+
field — so the backend window calls `textInput.sync()` once per frame after
|
|
228
|
+
the renderer has painted, when the layout is current, and only what changed
|
|
229
|
+
since the last commit is sent. There is no hook into the event manager and
|
|
230
|
+
nothing per field. What comes back — `preedit_string`, `commit_string`,
|
|
231
|
+
`delete_surrounding_text`, applied together on `done` in the order the spec
|
|
232
|
+
fixes — lands on the same `CompositionStart/Update/End` a dead key raises,
|
|
233
|
+
so `<textinput>` and an application's `onCompositionUpdate` cannot tell IBus
|
|
234
|
+
from `compose.js`. Keys the IME consumes never reach `wl_keyboard.key`, so
|
|
235
|
+
key delivery is untouched; a key that arrives is one the IME declined, and
|
|
236
|
+
the client-side composer has its usual go at it. A `sensitive` field offers
|
|
237
|
+
no surrounding text and says `sensitive_data`; `inputMode` on a
|
|
238
|
+
`<textinput>` becomes the content purpose (`email`, `url`, `tel`, …).
|
|
239
|
+
|
|
240
|
+
**A finger and a pen are the pointer.** On X11 a tablet is an XI2 slave of
|
|
241
|
+
the master pointer and a touch emulates it, so every control in the tree is
|
|
242
|
+
written against `mousedown`/`mousemove`/`mouseup` and nothing else. The
|
|
243
|
+
Wayland seat keeps `wl_touch` and the tablet tools as devices of their own,
|
|
244
|
+
so `touch.js` and `tablet.js` make them the pointer here: the first finger
|
|
245
|
+
of a gesture and a tool in proximity emit the seat's own `enter`/`motion`/
|
|
246
|
+
`buttonpress`/`buttonrelease`/`leave`, carrying the touch `down` or tool
|
|
247
|
+
`down` serial (what `xdg_toplevel.move` wants when the press lands on the
|
|
248
|
+
titlebar), and `input.js` routes them through exactly the path the mouse
|
|
249
|
+
takes — frame hit-testing, popup grabs, the cursor, which a tool in
|
|
250
|
+
proximity follows through cursor-shape's tablet device. The emulated events
|
|
251
|
+
alone carry `pointerType` (`'touch'`, `'pen'`, `'eraser'`, `'mouse'`),
|
|
252
|
+
`pressure`, `tiltX`/`tiltY`, `rotation`, `distance` and
|
|
253
|
+
`tangentialPressure` in the DOM's names and ranges, so a mouse event is
|
|
254
|
+
unchanged and the X11 backend never sees a field it did not send. A second
|
|
255
|
+
finger emulates nothing; every finger also arrives raw on the window as
|
|
256
|
+
`touchstart`/`touchmove`/`touchend`/`touchcancel` — ntk's XI2 names, with
|
|
257
|
+
its `touchId` — each with the active `touches`, batched at `wl_touch.frame`.
|
|
258
|
+
Barrel buttons map as the desktop does: BTN_STYLUS is button 3, BTN_STYLUS2
|
|
259
|
+
button 2; `proximity_out` releases whatever is still held.
|
|
260
|
+
|
|
261
|
+
**`createWindow` is synchronous.** React's commit phase cannot await, and
|
|
262
|
+
that is where windows are realised. Nothing about Wayland needs the
|
|
263
|
+
asynchrony — a request is one-way and the client allocates ids — so the
|
|
264
|
+
library grew a synchronous namespace (`Wl_interface.$`) and the only
|
|
265
|
+
genuinely asynchronous step, binding globals, happens once in `open()`.
|
|
266
|
+
|
|
267
|
+
**The monitors are a fold over the registry.** Each `wl_output` is its own
|
|
268
|
+
global, so `outputs.js` binds every one (through a second `wl_registry`,
|
|
269
|
+
because the library files globals by interface name and keeps one) and
|
|
270
|
+
publishes the layout into `screens.js` the way the cocoa backend does:
|
|
271
|
+
names from `wl_output.name` (`DP-1`, `eDP-1`), logical rects from
|
|
272
|
+
xdg_output where the compositor has it and from the mode over the scale
|
|
273
|
+
where it does not, converted to the renderer's device pixels at
|
|
274
|
+
`app.scale`. Hot-plug is `global`/`global_remove` on that registry. Two
|
|
275
|
+
things the protocol does not have are stood in for: a **primary** (the
|
|
276
|
+
output at the origin), and a **work area** — `xdg_toplevel.configure_bounds`
|
|
277
|
+
is the monitor less its panels, sent to a window ahead of its configure, so
|
|
278
|
+
a window's bounds become the usable rect of the output it has entered, and
|
|
279
|
+
every monitor carries its own rect so no head is clamped by another's. The
|
|
280
|
+
densest output also seeds `app.scale` before the first window exists, which
|
|
281
|
+
is the difference between a first layout at 2x and one corrected a frame
|
|
282
|
+
later; and where a compositor offers neither fractional-scale-v1 nor
|
|
283
|
+
`preferred_buffer_scale`, a surface's buffer scale is that of the output it
|
|
284
|
+
entered.
|
|
285
|
+
|
|
286
|
+
**A `<glarea>` is a rectangle of the backing target.** On X11 it is a child
|
|
287
|
+
window and on macOS a sublayer; a subsurface here would be a second
|
|
288
|
+
swapchain, a second frame clock and a second dma-buf import per frame for
|
|
289
|
+
what is already one GL context drawing into one target. So `glarea.js`
|
|
290
|
+
hands the area the shared GLES table with `viewport`, `scissor` and
|
|
291
|
+
`bindFramebuffer(null)` re-based on its rect (the Cocoa backend's own
|
|
292
|
+
trick, prototype-delegated), and the frame loop orders the three layers:
|
|
293
|
+
the tree's 2D is flushed under, the area draws over, and the panes its
|
|
294
|
+
children were painted on — offscreen targets, `createOverlayPane` — are
|
|
295
|
+
blended over that, translucent. One thing a shared target needs that a
|
|
296
|
+
child window never did: when the tree repaints under a static scene, the
|
|
297
|
+
scene is asked for its frame before the present, so the background never
|
|
298
|
+
shows through for a frame.
|
|
299
|
+
|
|
300
|
+
**One GL device, several contexts, and whoever draws takes it.** A window's
|
|
301
|
+
2d context, the pane a `<glarea>`'s children are painted on and every
|
|
302
|
+
offscreen `Surface` are the same GLES context, differing only in which
|
|
303
|
+
framebuffer they bind and what they left the blend, scissor and stencil
|
|
304
|
+
state saying — unlike an XRender context, which is an encoder aimed at a
|
|
305
|
+
drawable, or a Cocoa one, which carries its state in its bitmap. So
|
|
306
|
+
`device.js` gives the device an owner: a context claims it before it
|
|
307
|
+
touches GL, and claiming is where the target is bound and the state the
|
|
308
|
+
shader was written against is put back. That is what makes ntk's advice —
|
|
309
|
+
hold a surface's context and draw into it whenever you like — true here
|
|
310
|
+
too, where a held context used to draw wherever the device was last pointed
|
|
311
|
+
and say nothing about it (#566); it is also why a node that painted into a
|
|
312
|
+
surface in the middle of the frame does not have to restore the window's
|
|
313
|
+
context afterwards. Foreign GL is the exception it cannot see: a
|
|
314
|
+
`<glarea>` draws through entry points nobody tracks, so the frame loop
|
|
315
|
+
still calls `restoreGLState()` after one.
|
|
316
|
+
|
|
317
|
+
**Paths are antialiased on the CPU, and clips are exact on the stencil.**
|
|
318
|
+
A solid fill that fits the budget (512×512 after cutting to the clip) goes
|
|
319
|
+
through ntk's analytic coverage rasteriser — the same pixels the X11
|
|
320
|
+
backend's local raster and every glyph get — into a per-frame mask atlas,
|
|
321
|
+
drawn like a glyph; gradients and bigger shapes stay on stencil-then-cover.
|
|
322
|
+
`clip()` with anything but one axis-aligned rectangle sets bit 7 of the
|
|
323
|
+
stencil where the shape is (the low seven bits stay the winding count) and
|
|
324
|
+
every batch under it tests that bit; nested clips intersect in place,
|
|
325
|
+
`restore()` re-renders the restored list. The scissor still narrows to the
|
|
326
|
+
bounds, so the test is only paid inside them. `test/wayland/context2d-gpu.test.js`
|
|
327
|
+
reads the pixels back on any machine with a render node.
|
|
328
|
+
|
|
329
|
+
**The glyph seam is public, and spelled the same way on every backend.**
|
|
330
|
+
`drawGlyphs(op, src, positioned)` is how `TextLayout.draw` reaches a
|
|
331
|
+
context, and ntk documents it for callers that place their own glyphs too —
|
|
332
|
+
a terminal grid, a tabular column. The source in that call is spelled
|
|
333
|
+
`ctx.createSolidPicture(r, g, b, a)` and the op `ctx.Render.PictOp.Over`,
|
|
334
|
+
so both of those answer here as they do on X11 and on Cocoa. Implementing
|
|
335
|
+
`drawGlyphs` alone was not half a seam but a silent one: callers
|
|
336
|
+
feature-detected, the test came back false, and the frame drew with every
|
|
337
|
+
cell in its colour and no text in any of them rather than throwing (issue
|
|
338
|
+
#565). What a solid _is_ stays backend-private — an XRender `Picture` on
|
|
339
|
+
X11, the premultiplied colour itself here — but a CSS colour string, a
|
|
340
|
+
premultiplied `[r, g, b, a]` and `null` (meaning the fill style in force)
|
|
341
|
+
are all accepted as sources as well, which is the set the Cocoa backend
|
|
342
|
+
takes.
|
|
343
|
+
|
|
344
|
+
### A surface the compositor stops showing stops drawing
|
|
345
|
+
|
|
346
|
+
After the first present the frame loop has exactly one source of liveness:
|
|
347
|
+
`wl_surface.frame`. A compositor is entitled never to send one to a surface
|
|
348
|
+
it is not showing — covered, on another workspace, minimised, the screen
|
|
349
|
+
locked — and that is the right way to drive a client, because painting into a
|
|
350
|
+
surface nobody sees is work for nothing. There is no timer-driven fallback
|
|
351
|
+
here and there should not be one.
|
|
352
|
+
|
|
353
|
+
What it costs is that the silence is **not an error**. Nothing rejects, the
|
|
354
|
+
frame stays armed, and every later repaint request returns at its guard while
|
|
355
|
+
React goes on re-rendering; from outside the process, an app that has drawn
|
|
356
|
+
one frame and gone quiet is indistinguishable from a hang (#567 — 135 state
|
|
357
|
+
ticks, one present, exit code 0). So the loop is watched (`framewatch.js`),
|
|
358
|
+
and the parked state is reported twice over:
|
|
359
|
+
|
|
360
|
+
- **On stderr, once per window, in development**, when the window still had
|
|
361
|
+
something to draw. One line, naming the compositor as the reason and saying
|
|
362
|
+
the app is not hung.
|
|
363
|
+
- **In the tree**, as [`useWindowState().presenting`](system.md#presenting-and-the-wayland-frame-clock),
|
|
364
|
+
which also folds into `visible` — so an app can stop animating and
|
|
365
|
+
simulating rather than feeding a loop nobody is turning, which is what
|
|
366
|
+
every map, chart and game on this backend wants anyway.
|
|
367
|
+
|
|
368
|
+
Nothing is re-armed or polled to recover: the frame callback we asked for is
|
|
369
|
+
still queued with the compositor and fires the moment the surface is shown
|
|
370
|
+
again, which un-parks the loop and flips `presenting` back. The deadline is
|
|
371
|
+
two seconds, and `REACT_X11_WAYLAND_FRAME_STALL_MS` changes it — `0` turns the
|
|
372
|
+
watch off entirely, timer, warning and all.
|
|
373
|
+
|
|
374
|
+
## Decorations: whose frame
|
|
375
|
+
|
|
376
|
+
`decorations.js` draws a titlebar because mutter will not, and that stays
|
|
377
|
+
the default where nothing better is offered. Where a compositor advertises
|
|
378
|
+
`zxdg_decoration_manager_v1` — wlroots (sway, labwc), KDE — each toplevel
|
|
379
|
+
creates a decoration object before its first commit and asks for
|
|
380
|
+
server-side decorations (`ssd.js`). The compositor's answer is a
|
|
381
|
+
`configure(mode)` that is part of the surface's configure sequence, so it
|
|
382
|
+
is adopted on the same ack as the size it arrived with: 'server' switches
|
|
383
|
+
the client-side frame off (insets to zero, no titlebar painted, the content
|
|
384
|
+
grows into the whole surface, which the tree sees as a resize), 'client'
|
|
385
|
+
leaves it on. Before the first frame, where the compositor imposed no size,
|
|
386
|
+
the surface is refitted to the content the tree asked for, so a floating
|
|
387
|
+
window comes up the size it was declared and not a titlebar taller.
|
|
388
|
+
|
|
389
|
+
`createRoot({ decorations })` says what to ask for: `undefined`/`true`/
|
|
390
|
+
`'server'` prefer the compositor's frame, `'client'` never asks for it, and
|
|
391
|
+
`false` — as does `<window decorations={false}>` — wants no frame at all.
|
|
392
|
+
A frameless window still creates the decoration object where the protocol
|
|
393
|
+
exists, to _decline_ server-side: sway frames every toplevel that has not
|
|
394
|
+
said `client_side`.
|
|
395
|
+
|
|
396
|
+
## Layer surfaces: docks, panels, wallpapers
|
|
397
|
+
|
|
398
|
+
Where the compositor advertises `zwlr_layer_shell_v1`, a `<window>` whose
|
|
399
|
+
`windowType` is one of these is not an `xdg_toplevel` but a layer surface
|
|
400
|
+
(`layershell.js`), the same `wl_surface`, frame loop and input underneath:
|
|
401
|
+
|
|
402
|
+
| `windowType` | layer | anchored to | exclusive zone |
|
|
403
|
+
| ---------------- | ------------ | ------------------------------------------------- | -------------- |
|
|
404
|
+
| `'dock'` | `top` | the edge its shape and position imply (see below) | its thickness |
|
|
405
|
+
| `'desktop'` | `background` | all four edges | -1 |
|
|
406
|
+
| `'notification'` | `top` | top and right, 12px in | 0 |
|
|
407
|
+
| `'splash'` | `overlay` | nothing — the compositor centres it | 0 |
|
|
408
|
+
|
|
409
|
+
A dock's edge is read the way an X window manager reads the same window:
|
|
410
|
+
wide and at `y={0}` is a top panel, wide otherwise a bottom one; tall and at
|
|
411
|
+
`x={0}` a left dock, tall otherwise a right one. Its size is what the tree
|
|
412
|
+
measured (or `width`/`height`), so a 560×56 dock sits centred on its edge
|
|
413
|
+
with 56px reserved; anchoring it to both ends of the edge stretches it:
|
|
414
|
+
|
|
415
|
+
```jsx
|
|
416
|
+
<window windowType="dock" height={56}>…</window>
|
|
417
|
+
<window windowType="dock" height={40} layerShell={{ anchor: ['bottom', 'left', 'right'] }}>…</window>
|
|
418
|
+
<window layerShell={{ layer: 'overlay', anchor: 'bottom', margin: { bottom: 40 } }}>an OSD</window>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
`layerShell` names anything the type's default gets wrong — `layer`,
|
|
422
|
+
`anchor` (an edge, an array of edges, `'all'`, `'none'`), `exclusiveZone`
|
|
423
|
+
(a number, `'auto'` for the thickness, `-1`), `margin`,
|
|
424
|
+
`keyboardInteractivity` (`'none'`, `'on-demand'`, `'exclusive'`),
|
|
425
|
+
`namespace`, `output` — and `layerShell={false}` keeps a dock-typed window
|
|
426
|
+
an ordinary toplevel. Popups from a layer surface work (a dock's menus): the
|
|
427
|
+
`xdg_popup` is created parentless and adopted through
|
|
428
|
+
`zwlr_layer_surface_v1.get_popup`. A stretched axis is the compositor's:
|
|
429
|
+
`resize()` on it is not granted, and the configured size wins. Without
|
|
430
|
+
layer-shell — GNOME — the same JSX is a toplevel, as before.
|
|
431
|
+
|
|
432
|
+
## Screen capture, and the eyedropper
|
|
433
|
+
|
|
434
|
+
`app.screenCapture` (`screencopy.js`) is there when the compositor offers
|
|
435
|
+
either `ext_image_copy_capture_manager_v1` (wlroots 0.19+, KDE 6.2+) or
|
|
436
|
+
`zwlr_screencopy_manager_v1` (sway 1.10 has this one), and null otherwise
|
|
437
|
+
(GNOME). `capture()` answers one frame of the output as straight RGBA, the
|
|
438
|
+
shape `readback.js`'s `encodePNG` takes; `pixelAt(x, y)` one device pixel
|
|
439
|
+
of it. The pixels land in a `wl_shm` pool (`shm.js`) whose memory is a
|
|
440
|
+
memfd (or an unlinked `/dev/shm` file without x11-dri), read back with
|
|
441
|
+
`fs.readSync` — there is no mmap in Node or Bun, and one copy of one screen
|
|
442
|
+
on a path nobody runs per frame is the cheaper trade than a native binding.
|
|
443
|
+
|
|
444
|
+
`pickColor()` is the eyedropper, and it is what every wlroots colour picker
|
|
445
|
+
does: freeze the output into a capture, show that capture as a layer-shell
|
|
446
|
+
overlay anchored to every edge with an exclusive keyboard, take the click
|
|
447
|
+
_on the overlay_ — whose coordinates are the output's — read the pixel out
|
|
448
|
+
of the capture, and take the overlay down. Escape cancels; Return, space and
|
|
449
|
+
KP_Enter pick at the pointer. It answers `{ r, g, b }` in 0–1, the portal's
|
|
450
|
+
units, so `src/screencolor.js` converts it with the same function as the
|
|
451
|
+
portal's and the cocoa sampler's. There it is rung 3 of the ladder: below
|
|
452
|
+
the Screenshot portal (the desktop's own picker, user-consented — GNOME's
|
|
453
|
+
only route, and what sway with xdg-desktop-portal-wlr uses too) and above
|
|
454
|
+
the X11 grab. `useEyedropper().supported` is true on a bare sway; the rung
|
|
455
|
+
is guarded by `app.backend === 'wayland'` because the capability is a
|
|
456
|
+
Wayland object through and through.
|
|
457
|
+
|
|
458
|
+
Two protocol gaps are worth knowing. `@windowkit/wayland` keeps one
|
|
459
|
+
global per interface name, so the output bound here is the one advertised
|
|
460
|
+
last; `capture({ output })` and `layerShell.output` take another proxy, but
|
|
461
|
+
enumerating them waits on `outputs.js`. And the portal's `parent_window`
|
|
462
|
+
handle comes from **xdg-foreign**: the first dialog a toplevel opens exports
|
|
463
|
+
it (`zxdg_exporter_v2`, kept for the window's lifetime), and the portal gets
|
|
464
|
+
`wayland:<handle>` — modal to the window and stacked over it, as `x11:<xid>`
|
|
465
|
+
is on X11. A compositor without the protocol, or a popup, leaves the handle
|
|
466
|
+
empty and the dialog floats; an `x11:` handle never goes out from this
|
|
467
|
+
backend, because a Wayland window's id names no X window.
|
|
468
|
+
|
|
469
|
+
## Under a second compositor
|
|
470
|
+
|
|
471
|
+
mutter was the only compositor this backend had met until sway 1.10.1 and
|
|
472
|
+
labwc 0.8.3 ran here, nested (`WLR_BACKENDS=wayland`) and headless
|
|
473
|
+
(`WLR_BACKENDS=headless`), which is also the first time a screenshot of the
|
|
474
|
+
whole path was possible — `grim` needs wlr-screencopy, which mutter
|
|
475
|
+
refuses:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
WLR_BACKENDS=headless sway -c sway.conf & # output * resolution 1280x800
|
|
479
|
+
WAYLAND_DISPLAY=wayland-1 REACT_X11_BACKEND=wayland node --import tsx examples/simple.jsx &
|
|
480
|
+
WAYLAND_DISPLAY=wayland-1 grim shot.png
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
What it showed: the window comes up in sway's own frame (title bar and 2px
|
|
484
|
+
border, `default_border normal 2`) with the client-side bar gone and the
|
|
485
|
+
content filling the surface; a `windowType="dock"` window is a bottom layer
|
|
486
|
+
surface with a reserved strip the tiled toplevel keeps clear of; the
|
|
487
|
+
capture matches what grim sees; and the eyedropper's overlay maps and the
|
|
488
|
+
pick resolves from a synthesised click. The one protocol bug it surfaced was
|
|
489
|
+
in the client library rather than the compositor: it refused a null object
|
|
490
|
+
argument (`set_parent(null)`, `set_fullscreen(null)`, `get_popup(null, …)`,
|
|
491
|
+
`get_layer_surface(…, null, …)`). `@windowkit/wayland` 3.1.1 takes `null`
|
|
492
|
+
wherever the protocol marks an argument `allow-null`, and the backend passes
|
|
493
|
+
it directly.
|
|
494
|
+
|
|
495
|
+
## The transports, and Node
|
|
496
|
+
|
|
497
|
+
Descriptors are the protocol: the keymap, shm pools and clipboard pipes all
|
|
498
|
+
arrive as fds, and Node's sockets abort on them
|
|
499
|
+
([nodejs/node#53391](https://github.com/nodejs/node/issues/53391), closed).
|
|
500
|
+
|
|
501
|
+
- **`x11-dri` `UnixSocket`** (>= 0.9): a `uv_poll` over an `AF_UNIX` socket,
|
|
502
|
+
`recvmsg`/`sendmsg` with `SCM_RIGHTS`, no thread. Works on Node. Two
|
|
503
|
+
things it had to get right that a first version did not: N-API values made
|
|
504
|
+
from a libuv callback need a handle scope, and callbacks from the loop must
|
|
505
|
+
go through `napi_make_callback` — `napi_call_function` from there runs the
|
|
506
|
+
callback but never drains the promise-job queue, so a client awaiting its
|
|
507
|
+
first reply hangs until an unrelated timer turns the loop. `pipe()`,
|
|
508
|
+
`socketpair()` and `memfdCreate()` came with it.
|
|
509
|
+
- **node-x11's `fdpass-bun.js`**: `bun:ffi` to libc, with a reader thread.
|
|
510
|
+
Bun only. Bun _exports_ libuv's symbol names but several are stubs that
|
|
511
|
+
abort the process (`uv_poll_init` among them), so the native socket is
|
|
512
|
+
never attempted under Bun; there is nothing to probe for safely. Its
|
|
513
|
+
reader thread gave up on a healthy socket when `poll(2)` was interrupted
|
|
514
|
+
sixty-four times in a row, and JavaScriptCore's thread-suspend signal
|
|
515
|
+
(SIGPWR, `tgkill`ed to every JS thread, the poller included) during
|
|
516
|
+
**WebAssembly compilation** — yoga-layout's wasm tiering up ~0.3 s into
|
|
517
|
+
any react-x11 app — produces exactly that burst, as
|
|
518
|
+
`strace -f -e ppoll,tgkill` shows. EINTR is a retry since x11 4.2.1
|
|
519
|
+
([sidorares/node-x11#297](https://github.com/sidorares/node-x11/pull/297)).
|
|
520
|
+
|
|
521
|
+
## What is left
|
|
522
|
+
|
|
523
|
+
- **Multisampling.** Stencil-then-cover (gradients, shapes past the CPU
|
|
524
|
+
budget) and stencil clips are aliased at the edge. MSAA is the fix, and it
|
|
525
|
+
is not the few lines it looks like: GLES 3.0 cannot `blitFramebuffer` _into_
|
|
526
|
+
a multisampled framebuffer, so the scroll blits and the partial repaints the
|
|
527
|
+
backing target exists for would have to be re-expressed as textured draws,
|
|
528
|
+
and every read of the target (a present, `getImageData`, a `drawImage` of a
|
|
529
|
+
surface) as a resolve first. virgl here reports `MAX_SAMPLES` of 1, so none
|
|
530
|
+
of it could be validated; it waits for a machine that can run it.
|
|
531
|
+
- **`<Frame>` panes.** The out-of-process pane path (`createPaneHost`) is
|
|
532
|
+
macOS's and Windows' today — the pane presents an IOSurface id there and a
|
|
533
|
+
composition surface handle on Windows. Linux wants the same over a memfd or
|
|
534
|
+
dma-buf fd on the pane channel; until then `<Frame>` here degrades the way
|
|
535
|
+
it does without embedding.
|
|
536
|
+
- **Drag-and-drop.** Wired (`src/wayland/dnd.js`): the drop side answers
|
|
537
|
+
another application's drag over `wl_data_device` enter/motion/leave/drop,
|
|
538
|
+
and the source side offers a payload and hands the gesture to `start_drag`,
|
|
539
|
+
with an in-app drop keeping `e.items` by reference. What is left is the drag
|
|
540
|
+
_icon_: `start_drag` is given a bare (invisible) surface, and rendering the
|
|
541
|
+
tree's `<popup dragPreview>` into an icon surface the frame loop paints is a
|
|
542
|
+
follow-up.
|
|
543
|
+
- **Input methods, the rest of it.** The preedit's cursor is drawn (a heavier
|
|
544
|
+
underline under the segment being converted) but nothing is done with a
|
|
545
|
+
hidden cursor beyond putting the caret at the end, and the only content
|
|
546
|
+
hints sent are `multiline` and `sensitive_data` — no
|
|
547
|
+
`spellcheck`/`auto_capitalization`, which the tree has no notion of.
|
|
548
|
+
- **Tablet pads.** The buttons, rings and strips on the tablet itself
|
|
549
|
+
(`zwp_tablet_pad_v2`) are accepted and ignored: the tree has no vocabulary
|
|
550
|
+
for them.
|
|
551
|
+
- **`useScreens().source` reads `'test'`**, as it does on macOS and Windows:
|
|
552
|
+
all three backends publish through `setScreensForTests`, the one seam
|
|
553
|
+
`screens.js` has, and it stamps the source. A `source` argument on it is the
|
|
554
|
+
fix.
|
|
555
|
+
- **`fillText` only sees the first font family** of a `font` shorthand and
|
|
556
|
+
strokes text as a fill.
|
|
557
|
+
- **`ext-image-copy-capture`** is implemented to the protocol text but has not
|
|
558
|
+
run against a compositor: sway 1.10 advertises only wlr-screencopy, and
|
|
559
|
+
GNOME neither. wlroots 0.19 (sway 1.11) is where to try it.
|
|
560
|
+
|
|
561
|
+
## Things that are gone, not missing
|
|
562
|
+
|
|
563
|
+
Window managers (`examples/wm.jsx`), global grabs, reading other windows,
|
|
564
|
+
screen coordinates, `GetImage`, XEmbed/`<foreign>`, and `ssh -X` are
|
|
565
|
+
compositor-private by design. Panels and docks are `layer-shell`, and a
|
|
566
|
+
screen read is `screencopy` — both wlroots and KDE, neither GNOME, where the
|
|
567
|
+
same JSX is a toplevel and the same pick is the portal's.
|
|
568
|
+
|
|
569
|
+
## Changes outside `src/wayland/`
|
|
570
|
+
|
|
571
|
+
- `src/Reconciler.js` — a `'wayland'` rung in `resolveBackend()`. Additive;
|
|
572
|
+
`'auto'` never selects it.
|
|
573
|
+
- `src/nodes/boxpaint.js` — prefers `ctx.fillShadow` when a context has it.
|
|
574
|
+
- `src/nodes/preedit.js` — the composition may carry a cursor
|
|
575
|
+
(`cursorBegin`/`cursorEnd` on the composition event); without one the
|
|
576
|
+
caret is at the end of the preedit, as before. `src/events.js` —
|
|
577
|
+
`_composition` passes those extra fields through. Additive on both
|
|
578
|
+
backends.
|
|
579
|
+
- `src/screencolor.js` — rung 3 of the eyedropper's ladder, the Wayland
|
|
580
|
+
backend's own picker, guarded by `app.backend === 'wayland'`; and the
|
|
581
|
+
portal's `parent_window` handle is an xdg-foreign export on that backend
|
|
582
|
+
(`portalParentFor` in `src/portal.js`, shared with the file dialog),
|
|
583
|
+
where an X window id would be a lie.
|
|
584
|
+
- [`@windowkit/wayland`](https://github.com/windowkit/wayland) 3.1.1 — a
|
|
585
|
+
fork of [`wayland-client`](https://github.com/sdumetz/node-wayland-client)
|
|
586
|
+
3.0.0: fd send/receive, the `$` synchronous request namespace, callback
|
|
587
|
+
requests returning their `done` payload, and `null` for `allow-null`
|
|
588
|
+
arguments.
|
|
589
|
+
- `x11-dri` 0.9.0 — `UnixSocket`, `pipe`, `socketpair`, `memfdCreate`; a
|
|
590
|
+
self-test that passes a real descriptor through a socketpair.
|
|
591
|
+
- `x11` 4.2.1 — `fdpass-bun.js` retries a `poll(2)` that EINTR interrupted.
|