react-x11 2.52.0 → 2.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +10 -10
  2. package/docs/README.md +160 -314
  3. package/docs/accessibility.md +176 -229
  4. package/docs/appearance.md +77 -176
  5. package/docs/click-to-component.md +18 -51
  6. package/docs/clipboard.md +40 -59
  7. package/docs/components.md +518 -1036
  8. package/docs/dbus.md +61 -133
  9. package/docs/debugging.md +152 -245
  10. package/docs/desktop-calendar.md +51 -88
  11. package/docs/desktop.md +222 -347
  12. package/docs/devtools.md +50 -167
  13. package/docs/drag-and-drop.md +126 -204
  14. package/docs/ecosystem/animation.md +91 -125
  15. package/docs/ecosystem/data-fetching.md +38 -53
  16. package/docs/ecosystem/dev-tooling.md +159 -202
  17. package/docs/ecosystem/forms.md +61 -97
  18. package/docs/ecosystem/headless.md +64 -89
  19. package/docs/ecosystem/i18n.md +18 -21
  20. package/docs/ecosystem/icons.md +93 -126
  21. package/docs/ecosystem/layout.md +42 -52
  22. package/docs/ecosystem/routing.md +18 -29
  23. package/docs/ecosystem/state.md +71 -88
  24. package/docs/ecosystem/testing.md +131 -157
  25. package/docs/ecosystem/theming.md +119 -165
  26. package/docs/ecosystem.md +145 -179
  27. package/docs/elements.md +1096 -1774
  28. package/docs/embedding.md +83 -132
  29. package/docs/events.md +412 -695
  30. package/docs/extending.md +784 -1326
  31. package/docs/eyedropper.md +63 -122
  32. package/docs/filedialog.md +71 -107
  33. package/docs/frame.md +195 -339
  34. package/docs/gl.md +65 -62
  35. package/docs/globalmenu.md +63 -146
  36. package/docs/macos.md +531 -3234
  37. package/docs/notifications.md +33 -47
  38. package/docs/packaging.md +166 -309
  39. package/docs/permissions.md +9 -14
  40. package/docs/react-features.md +81 -106
  41. package/docs/remote.md +32 -76
  42. package/docs/scale.md +72 -186
  43. package/docs/styling.md +329 -547
  44. package/docs/system.md +200 -280
  45. package/docs/testing.md +63 -83
  46. package/docs/typescript.md +6 -48
  47. package/docs/uri-schemes.md +67 -107
  48. package/docs/wayland-backend.md +200 -459
  49. package/docs/windows-integrations.md +75 -131
  50. package/docs/windows.md +343 -1367
  51. package/package.json +3 -2
  52. package/src/Reconciler.js +2 -2
  53. package/src/a11y.js +1 -1
  54. package/src/backend/context2d.js +3 -3
  55. package/src/bootstrap.js +1 -1
  56. package/src/cocoa/app.js +6 -6
  57. package/src/cocoa/bezels.js +1 -1
  58. package/src/cocoa/fonts.js +1 -1
  59. package/src/cocoa/globalmenu.js +1 -1
  60. package/src/cocoa/keymap.js +1 -1
  61. package/src/cocoa/panewindow.js +1 -1
  62. package/src/cocoa/player.js +1 -1
  63. package/src/cocoa/presenter.js +2 -2
  64. package/src/cocoa/promotion.js +1 -1
  65. package/src/cocoa/relaunch.js +1 -1
  66. package/src/cocoa/threaded.js +1 -1
  67. package/src/cocoa/video.js +1 -1
  68. package/src/cocoa/window.js +4 -4
  69. package/src/components/Menu.js +7 -2
  70. package/src/components/Switch.js +1 -1
  71. package/src/components/native.js +1 -1
  72. package/src/dnd.js +2 -2
  73. package/src/glnodes.js +1 -1
  74. package/src/grid.js +1 -1
  75. package/src/imagedecode-worker.js +32 -0
  76. package/src/imagedecode.js +140 -2
  77. package/src/layouts.js +1 -1
  78. package/src/nodes/animation.js +1 -1
  79. package/src/nodes/invalidate.js +1 -1
  80. package/src/nodes/layouthost.js +1 -1
  81. package/src/nodes/video.js +19 -8
  82. package/src/nodes/window/droptarget.js +1 -1
  83. package/src/ntk.d.ts +12 -0
  84. package/src/pacing.js +1 -1
  85. package/src/refresh/index.js +2 -1
  86. package/src/types/video.d.ts +5 -2
  87. package/src/videobackend.js +28 -0
  88. package/src/videoframes.js +29 -16
  89. package/src/wayland/app.js +27 -0
  90. package/src/wayland/video.js +376 -0
  91. package/src/win32/a11y.js +1 -1
  92. package/src/win32/app.js +2 -2
  93. package/src/win32/fonts.js +1 -1
  94. package/src/win32/glarea.js +4 -4
  95. package/src/win32/overlay.js +1 -1
  96. package/src/win32/panewindow.js +3 -3
  97. package/src/win32/shell.js +1 -1
  98. package/src/win32/window.js +2 -2
  99. package/src/windowid.js +1 -1
  100. package/src/xvideo.js +189 -0
  101. package/docs/architecture/accessibility.md +0 -470
  102. package/docs/architecture/animation.md +0 -707
  103. package/docs/architecture/container-queries.md +0 -525
  104. package/docs/architecture/custom-layout.md +0 -334
  105. package/docs/architecture/drag-and-drop.md +0 -941
  106. package/docs/architecture/element-layer-contents.md +0 -622
  107. package/docs/architecture/frame-pacing.md +0 -255
  108. package/docs/architecture/grid-layout.md +0 -358
  109. package/docs/architecture/protocol-efficiency.md +0 -357
  110. package/docs/architecture/video.md +0 -1149
  111. package/docs/architecture/windowed-regions.md +0 -854
  112. package/docs/glx.md +0 -137
  113. package/docs/wayland.md +0 -545
  114. package/docs/windows-embedding.md +0 -271
  115. package/docs/windows-gl.md +0 -221
@@ -3,14 +3,17 @@
3
3
  **Status: a working backend, opt-in.** react-x11's own reconciler renders a
4
4
  window on a real Wayland compositor (mutter/GNOME, sway, labwc) with
5
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.
6
+ does not — text, shadows, paths, images, live partial repaints, pointer,
7
+ touch, pen and keyboard input decoded against the compositor's keymap, an
8
+ input method, a clipboard, drag and drop, popups, layer-shell docks and
9
+ panels, and a screen capture with an eyedropper on top — drawn on the GPU
10
+ and handed over as dma-buf, on **Node or Bun**, with no X server and no
11
+ Xwayland anywhere in the path.
12
+
13
+ It implements the research RFC in [design/wayland.md](../design/wayland.md);
14
+ where the two disagree, this page is what ran. How it is built, what was
15
+ measured and what is left are in the
16
+ [design notes](../design/wayland-backend.md).
14
17
 
15
18
  ```bash
16
19
  REACT_X11_BACKEND=wayland node your-app.mjs # x11-dri >= 0.9 (native socket)
@@ -24,7 +27,9 @@ The two packages it needs beyond react-x11's own are optional dependencies,
24
27
  installed with it: [`@windowkit/wayland`](https://github.com/windowkit/wayland),
25
28
  the protocol layer — a fork of `wayland-client` that passes file descriptors
26
29
  — and [`x11-dri`](https://github.com/sidorares/node-x11-dri) 0.9 or later,
27
- the GPU context and, on Node, the socket.
30
+ the GPU context and, on Node, the socket: Wayland passes file descriptors
31
+ (the keymap, shm pools, clipboard pipes) and Node's own sockets cannot. Under
32
+ Bun the socket is node-x11's `bun:ffi` transport instead.
28
33
 
29
34
  The backend is never chosen by `'auto'`: X11 stays the default on Linux
30
35
  because the remote case ([remote.md](remote.md)) is the flagship reason this
@@ -40,36 +45,79 @@ appeared" proves nothing. Three checks that do:
40
45
  ```bash
41
46
  env -u DISPLAY REACT_X11_BACKEND=wayland node app.mjs # works only natively
42
47
  ```
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
48
+ - **Ask the app.** `root.app.backend === 'wayland'`, and `root.app.X` is a
49
+ stand-in — no `display.screen`, no `X.require`. The examples print the
50
+ transport they connected through (`connected via x11-dri` or
46
51
  `fdpass-bun`) and never import an X module at all.
47
52
  - **Ask the desktop.** `xlsclients` and `xwininfo -root -tree` list Xwayland
48
53
  clients; a native window is absent from both. GNOME's Looking Glass
49
54
  (`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.
55
+ frame is this backend's own titlebar rather than mutter's Adwaita frame,
56
+ which only Xwayland windows get.
52
57
 
53
58
  ## What runs
54
59
 
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 |
60
+ | piece | what an app gets |
61
+ | ------------------ | --------------------------------------------------------------------------------------- |
62
+ | windows and popups | xdg toplevels and popups, fractional scale ([below](#windows-what-maps-what-bends)) |
63
+ | 2d context | rects, rounded rects, paths, exact clips, gradients, patterns, images, text, glyph runs |
64
+ | `<glarea>` | GLES drawing into a rectangle of the window, its children composited over it |
65
+ | offscreen surfaces | the paint cache and scroll blits; a held `Surface` context draws at any time |
66
+ | input | pointer, keyboard decoded against the compositor's keymap, key repeat |
67
+ | touch and tablet | fingers and pens drive the pointer, with pen fields; raw touches |
68
+ | input methods | the compositor's IME (`zwp_text_input_v3`) into the composition events |
69
+ | decorations | the compositor's frame where it offers one, client-side otherwise |
70
+ | layer surfaces | docks, panels, wallpapers, overlays — wlr-layer-shell |
71
+ | screen capture | wlr-screencopy / ext-image-copy-capture; the eyedropper |
72
+ | clipboard | clipboard and primary selection |
73
+ | drag and drop | both directions, in-app drops keep `e.items` by reference; no drag icon yet |
74
+ | screens, keyboard | `useScreens()` and `useKeyboardState()` ([system.md](system.md#on-wayland)) |
75
+ | tests | `test/wayland/`: an in-process compositor, no display needed |
76
+
77
+ The keymap is the compositor's own, so AltGr and levels 3 and 4 work
78
+ because the keymap says which modifier they are, not because one is
79
+ assumed. Compositors do not repeat keys; the backend repeats them at the
80
+ seat's advertised rate.
81
+
82
+ **Touch and pen.** A finger or a tablet tool drives the same mouse events
83
+ as a mouse, and only those events carry `pointerType` (`'touch'`, `'pen'`,
84
+ `'eraser'`, `'mouse'`), `pressure`, `tiltX`/`tiltY`, `rotation`,
85
+ `distance` and `tangentialPressure`, in the DOM's names and ranges
86
+ ([events.md](events.md#event-object)); a mouse event is unchanged. Only
87
+ the first finger of a gesture emulates the pointer. Every finger also
88
+ arrives raw on the window as `touchstart`/`touchmove`/`touchend`/
89
+ `touchcancel` (the X11 backend's XI2 names, with `touchId`), each with the
90
+ active `touches`, batched per touch frame. Barrel buttons map as the
91
+ desktop does — BTN_STYLUS is button 3, BTN_STYLUS2 button 2 — and a tool
92
+ leaving proximity releases whatever it still held.
93
+
94
+ **Input methods.** IBus or whatever the compositor runs reaches
95
+ `<textinput>` and `onCompositionStart`/`Update`/`End` exactly as a dead key
96
+ does. Keys the IME consumes never arrive as key events. A `sensitive`
97
+ field offers the IME no surrounding text and says `sensitive_data`;
98
+ `inputMode` on a `<textinput>` becomes the content purpose (`email`, `url`,
99
+ `tel`, …).
100
+
101
+ **`<glarea>`** is a rectangle of the window's own GPU target, not a child
102
+ surface: the area gets the shared GLES context with `viewport`, `scissor`
103
+ and `bindFramebuffer(null)` re-based on its rect, the tree's 2D is drawn
104
+ under it and the panes its children paint on are blended over it.
105
+
106
+ **Perspective images.** `ctx.drawImageProjected(image, m)` draws an image
107
+ through a 3×3 projection (a plane turned out of the page, as CSS
108
+ `rotateY()` in a `perspective` turns one) as one exact quad. It answers
109
+ `false` and draws nothing where a corner is behind the viewer, for the
110
+ caller to draw the part in front some other way. Only this backend has it
111
+ (XRender on glamor folds the image, CoreGraphics and Direct2D draw through
112
+ an affine matrix alone): test with `'drawImageProjected' in ctx` and tile
113
+ where it is absent.
114
+
115
+ **Glyph runs.** `ctx.drawGlyphs(op, src, positioned)`, for callers that
116
+ place their own glyphs (a terminal grid, a tabular column), is spelled as on
117
+ X11 and Cocoa: the op `ctx.Render.PictOp.Over`, the source
118
+ `ctx.createSolidPicture(r, g, b, a)` — or, as Cocoa also takes, a CSS
119
+ colour string, a premultiplied `[r, g, b, a]`, or `null` for the fill style
120
+ in force.
73
121
 
74
122
  ### Running the examples
75
123
 
@@ -86,276 +134,67 @@ Wayland counterpart. `examples/viewer3d.jsx` draws its model here through
86
134
  its direct half, GLES on the window's own GPU target — the same half X11
87
135
  uses under `NTK_GL_POLICY=direct`.
88
136
 
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).
137
+ Environment variables, for desktops with no screenshot API and for
138
+ debugging:
139
+
140
+ - `REACT_X11_WAYLAND_TRACE=1` writes a line per presented frame.
141
+ - `REACT_X11_WAYLAND_SNAPSHOT=/tmp/frame.png` writes the backing target of
142
+ the first window at its 30th present (`REACT_X11_WAYLAND_SNAPSHOT_AT`
143
+ changes which) — the pixels the compositor was handed, read back from the
144
+ GPU.
145
+ - `REACT_X11_WAYLAND_FRAME_STALL_MS` is how long a frame callback may go
146
+ unanswered before the window counts as not shown (2000; `0` turns that
147
+ off) — see
148
+ [A surface the compositor stops showing stops drawing](#a-surface-the-compositor-stops-showing-stops-drawing).
149
+ - `REACT_X11_GL_DEVICE` names the DRM node to render on.
98
150
 
99
151
  The GPU context is made on `glPolicy.devicePath`, else on
100
152
  `REACT_X11_GL_DEVICE`, else on the first render node, and a device named
101
153
  either way is the only one tried. A machine with no render node at all — a
102
154
  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:
155
+ card node Mesa renders on in software, so the backend then tries each
156
+ `/dev/dri/card*` in turn; headless weston with its GL renderer accepts what
157
+ it makes on a hosted runner.
158
+
159
+ ## Windows: what maps, what bends
160
+
161
+ The compositor owns placement, stacking and focus, so some of what a
162
+ `<window>` asks for on X11 means less here:
163
+
164
+ - **Position.** A toplevel cannot place itself: `x`/`y` on a `<window>` are
165
+ ignored, not an error, and the compositor's placement is final. A
166
+ toplevel's own position reads `0, 0`; no client knows where it is on
167
+ screen.
168
+ - **Popups** are placed relative to their parent window, and the compositor
169
+ slides and flips them to stay on screen — the policy `anchorRect` gets
170
+ from react-x11 on X11. A press outside a grabbing popup, or Escape, is
171
+ the compositor's to act on; it hides the popup and the tree hears
172
+ `onDismiss` as it would on X11.
173
+ - **Focus and raising** are the compositor's to give. `focus()` asks
174
+ through xdg-activation and is ignored without a recent user interaction;
175
+ attention requests go the same way; `raise()` and `lower()` do nothing.
176
+ - **States.** Maximised, fullscreen and minimised map; there is no
177
+ un-minimise request. Always-on-top, sticky and workspaces have no
178
+ equivalent.
179
+ - **Size.** A window sizes itself, except while the compositor imposes a
180
+ size — maximised, tiled, or mid-resize — when the size it asks for is
181
+ not applied.
182
+ - **Type and class.** `windowType` matters only where layer-shell exists
183
+ ([below](#layer-surfaces-docks-panels-wallpapers)); the window's class
184
+ (`app_id`) is fixed at creation.
185
+ - **A window the compositor is not showing does not draw** — next section.
186
+
187
+ ## A surface the compositor stops showing stops drawing
188
+
189
+ After the first present a window draws only when the compositor sends it a
190
+ frame callback, and a compositor is entitled never to send one to a
191
+ surface it is not showing — covered, on another workspace, minimised, the
192
+ screen locked. There is no timer-driven fallback, on purpose: painting
193
+ into a surface nobody sees is work for nothing.
194
+
195
+ The silence is **not an error**: nothing rejects, and React goes on
196
+ re-rendering into a window that does not draw, which from outside looks
197
+ like a hang. So the stall is reported twice over:
359
198
 
360
199
  - **On stderr, once per window, in development**, when the window still had
361
200
  something to draw. One line, naming the compositor as the reason and saying
@@ -365,26 +204,19 @@ and the parked state is reported twice over:
365
204
  simulating rather than feeding a loop nobody is turning, which is what
366
205
  every map, chart and game on this backend wants anyway.
367
206
 
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.
207
+ Nothing has to be done to recover: the pending frame callback fires the
208
+ moment the surface is shown again, drawing resumes and `presenting` flips
209
+ back. The deadline is two seconds; `REACT_X11_WAYLAND_FRAME_STALL_MS`
210
+ changes it, and `0` turns the watch off entirely, timer, warning and all.
373
211
 
374
212
  ## Decorations: whose frame
375
213
 
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.
214
+ The backend draws its own titlebar where the compositor will not (mutter),
215
+ and asks for the compositor's frame where `zxdg_decoration_manager_v1` is
216
+ offered — wlroots (sway, labwc), KDE. When the compositor agrees, the
217
+ client-side frame goes away and the content grows into the whole surface,
218
+ which the tree sees as a resize; a window not yet shown comes up the size
219
+ it was declared, not a titlebar taller.
388
220
 
389
221
  `createRoot({ decorations })` says what to ask for: `undefined`/`true`/
390
222
  `'server'` prefer the compositor's frame, `'client'` never asks for it, and
@@ -396,8 +228,8 @@ said `client_side`.
396
228
  ## Layer surfaces: docks, panels, wallpapers
397
229
 
398
230
  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:
231
+ `windowType` is one of these is not an `xdg_toplevel` but a layer surface,
232
+ the same `wl_surface`, frame loop and input underneath:
401
233
 
402
234
  | `windowType` | layer | anchored to | exclusive zone |
403
235
  | ---------------- | ------------ | ------------------------------------------------- | -------------- |
@@ -423,56 +255,46 @@ with 56px reserved; anchoring it to both ends of the edge stretches it:
423
255
  (a number, `'auto'` for the thickness, `-1`), `margin`,
424
256
  `keyboardInteractivity` (`'none'`, `'on-demand'`, `'exclusive'`),
425
257
  `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.
258
+ an ordinary toplevel. Popups from a layer surface work (a dock's menus). A
259
+ stretched axis is the compositor's: `resize()` on it is not granted, and
260
+ the configured size wins. Without layer-shell — GNOME — the same JSX is a
261
+ toplevel.
431
262
 
432
263
  ## Screen capture, and the eyedropper
433
264
 
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
265
+ `app.screenCapture` is there when the compositor offers either
266
+ `ext_image_copy_capture_manager_v1` (wlroots 0.19+, KDE 6.2+) or
436
267
  `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.
268
+ (GNOME). `capture({ output, cursor })` resolves to one frame of the output
269
+ as straight RGBA, `{ width, height, data }`; `pixelAt(x, y)` to one device
270
+ pixel of it, as 0–255 channels. Each call is a fresh capture of the whole
271
+ output — for many pixels, `capture()` once; for anything per frame, not at
272
+ all.
273
+
274
+ `pickColor()` is the eyedropper, the way every wlroots colour picker works:
275
+ it freezes the output into a capture, shows it as a full-screen
276
+ layer-shell overlay that takes the keyboard, and reads the pixel under the
277
+ click — so it needs layer-shell as well as a capture protocol. Escape
278
+ cancels; Return, space and KP_Enter pick at the pointer. It answers
279
+ `{ r, g, b }` in 0–1. `useEyedropper()` uses it below the Screenshot portal
280
+ (the desktop's own, user-consented picker — GNOME's only route, and what
281
+ sway with xdg-desktop-portal-wlr uses too), so `useEyedropper().supported`
282
+ is true on a bare sway. Where the compositor has neither the portal's
283
+ picker nor capture plus layer-shell, `supported` is false: leave the
284
+ eyedropper button undrawn.
285
+
286
+ Two limits. With several outputs, the one captured by default is the one
287
+ the compositor advertised last; `capture({ output })` and
288
+ `layerShell.output` take another `wl_output` proxy. And a portal dialog (file chooser, colour picker) is modal
289
+ to its window only where the compositor has **xdg-foreign**; without it, or
290
+ from a popup, the dialog floats.
468
291
 
469
292
  ## Under a second compositor
470
293
 
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:
294
+ The backend runs on sway and labwc as well as mutter, nested
295
+ (`WLR_BACKENDS=wayland`) or headless (`WLR_BACKENDS=headless`). Headless
296
+ sway plus `grim` is a way to screenshot an app with no desktop at all —
297
+ `grim` needs wlr-screencopy, which mutter refuses:
476
298
 
477
299
  ```bash
478
300
  WLR_BACKENDS=headless sway -c sway.conf & # output * resolution 1280x800
@@ -480,83 +302,26 @@ WAYLAND_DISPLAY=wayland-1 REACT_X11_BACKEND=wayland node --import tsx examples/s
480
302
  WAYLAND_DISPLAY=wayland-1 grim shot.png
481
303
  ```
482
304
 
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
305
+ ## Not supported yet
306
+
307
+ - **Multisampling.** Paths filled with a gradient, solid paths larger than
308
+ 512×512 after clipping, and clips to anything but one axis-aligned
309
+ rectangle have aliased edges; rounded rectangles, smaller solid paths and
310
+ text are antialiased.
311
+ - **`<Frame>` panes.** A `<Frame>` fails at once: its `fallback` renders and
312
+ no pane is started ([frame.md](frame.md)).
313
+ - **The drag icon.** Drag and drop works both ways, but the drag shows no
314
+ icon: `<popup dragPreview>` is not drawn.
315
+ - **Input methods, the rest of it.** A preedit that hides its cursor puts
316
+ the caret at the end, and the only content hints sent are `multiline`
317
+ and `sensitive_data`.
318
+ - **Tablet pads** — the buttons, rings and strips on the tablet itself are
319
+ ignored.
320
+ - **`useScreens().source` reads `'test'`**, as it does on macOS and Windows.
321
+ - **`fillText`** uses only the first family of a `font` shorthand, and
556
322
  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.
323
+ - **`ext-image-copy-capture`** has not been run against a compositor yet
324
+ (sway 1.10 offers only wlr-screencopy).
560
325
 
561
326
  ## Things that are gone, not missing
562
327
 
@@ -565,27 +330,3 @@ screen coordinates, `GetImage`, XEmbed/`<foreign>`, and `ssh -X` are
565
330
  compositor-private by design. Panels and docks are `layer-shell`, and a
566
331
  screen read is `screencopy` — both wlroots and KDE, neither GNOME, where the
567
332
  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.