react-x11 2.13.0 → 2.14.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 (63) hide show
  1. package/package.json +11 -8
  2. package/src/Reconciler.js +34 -21
  3. package/src/components/Select.js +8 -2
  4. package/src/events.js +8 -2
  5. package/src/index.d.ts +5 -0
  6. package/src/nodes/boxpaint.js +9 -0
  7. package/src/nodes/preedit.js +64 -14
  8. package/src/scale.js +52 -22
  9. package/src/screencolor.js +104 -17
  10. package/src/wayland/app.js +560 -0
  11. package/src/wayland/backendwindow.js +1123 -0
  12. package/src/wayland/clipboard.js +326 -0
  13. package/src/wayland/connection.js +482 -0
  14. package/src/wayland/context2d.js +2133 -0
  15. package/src/wayland/decorations.js +476 -0
  16. package/src/wayland/dmabuf.js +89 -0
  17. package/src/wayland/dnd.js +581 -0
  18. package/src/wayland/fdutil.js +108 -0
  19. package/src/wayland/framestyle.js +257 -0
  20. package/src/wayland/glarea.js +371 -0
  21. package/src/wayland/glcontext.js +415 -0
  22. package/src/wayland/glyphatlas.js +237 -0
  23. package/src/wayland/input.js +417 -0
  24. package/src/wayland/keysymnames.js +35 -0
  25. package/src/wayland/layershell.js +363 -0
  26. package/src/wayland/outputs.js +601 -0
  27. package/src/wayland/protocols/cursor-shape-v1.json +1 -0
  28. package/src/wayland/protocols/ext-idle-notify-v1.json +1 -0
  29. package/src/wayland/protocols/ext-image-capture-source-v1.json +1 -0
  30. package/src/wayland/protocols/ext-image-copy-capture-v1.json +1 -0
  31. package/src/wayland/protocols/fractional-scale-v1.json +1 -0
  32. package/src/wayland/protocols/index.json +127 -0
  33. package/src/wayland/protocols/keyboard-shortcuts-inhibit-unstable-v1.json +1 -0
  34. package/src/wayland/protocols/linux-dmabuf-v1.json +1 -0
  35. package/src/wayland/protocols/pointer-constraints-unstable-v1.json +1 -0
  36. package/src/wayland/protocols/presentation-time.json +1 -0
  37. package/src/wayland/protocols/primary-selection-unstable-v1.json +1 -0
  38. package/src/wayland/protocols/relative-pointer-unstable-v1.json +1 -0
  39. package/src/wayland/protocols/tablet-v2.json +1 -0
  40. package/src/wayland/protocols/text-input-unstable-v3.json +1 -0
  41. package/src/wayland/protocols/viewporter.json +1 -0
  42. package/src/wayland/protocols/wayland.json +1 -0
  43. package/src/wayland/protocols/wlr-layer-shell-unstable-v1.json +1 -0
  44. package/src/wayland/protocols/wlr-screencopy-unstable-v1.json +1 -0
  45. package/src/wayland/protocols/xdg-activation-v1.json +1 -0
  46. package/src/wayland/protocols/xdg-decoration-unstable-v1.json +1 -0
  47. package/src/wayland/protocols/xdg-output-unstable-v1.json +1 -0
  48. package/src/wayland/protocols/xdg-shell.json +1 -0
  49. package/src/wayland/protocols/xdg-toplevel-icon-v1.json +1 -0
  50. package/src/wayland/readback.js +99 -0
  51. package/src/wayland/screencopy.js +584 -0
  52. package/src/wayland/seat.js +584 -0
  53. package/src/wayland/shm.js +226 -0
  54. package/src/wayland/ssd.js +106 -0
  55. package/src/wayland/surface.js +123 -0
  56. package/src/wayland/swapchain.js +411 -0
  57. package/src/wayland/tablet.js +522 -0
  58. package/src/wayland/target.js +263 -0
  59. package/src/wayland/text.js +113 -0
  60. package/src/wayland/textinput.js +671 -0
  61. package/src/wayland/touch.js +284 -0
  62. package/src/wayland/window.js +827 -0
  63. package/src/wayland/xkb.js +425 -0
@@ -0,0 +1,363 @@
1
+ // Layer surfaces: the panels, docks, wallpapers and overlays that are not
2
+ // windows — wlr-layer-shell, which wlroots compositors and KDE speak and
3
+ // GNOME does not.
4
+ //
5
+ // On X11 a dock is an ordinary window with `_NET_WM_WINDOW_TYPE_DOCK` and a
6
+ // strut, placed by the client at screen coordinates. Neither half survives
7
+ // here: a client cannot place itself, and there is no screen to have
8
+ // coordinates in. What layer-shell offers instead is declarative. A surface
9
+ // names a layer (background, bottom, top, overlay), the output edges it is
10
+ // anchored to, a size — 0 on an axis anchored at both ends means "stretch" —
11
+ // an exclusive zone (the strut: a strip along its edge other windows may not
12
+ // cover), and what it wants of the keyboard; the compositor places it and
13
+ // says in `configure` how big it ended up. That configure/ack handshake is
14
+ // xdg_surface's with a size in it, and the surface underneath is the same
15
+ // `wl_surface` a toplevel has, so the frame loop, the swapchain, fractional
16
+ // scaling and the seat carry over untouched: a layer window differs from a
17
+ // toplevel in its role object and in having no titlebar.
18
+ //
19
+ // Which windows become layer surfaces is read off the props the tree already
20
+ // passes. `windowType` carries the intent — 'dock', 'desktop',
21
+ // 'notification', 'splash' — and the measured size gives a dock its
22
+ // thickness; an app that wants to name the edge, layer or zone itself passes
23
+ // a `layerShell` object (`layerRoleFor` lists the keys). Everything else stays
24
+ // an `xdg_toplevel`. The edge a dock lives on is inferred the way an X window
25
+ // manager would read the same window: wide and at y 0 is a top panel, wide
26
+ // otherwise a bottom one; tall at x 0 is a left dock, tall otherwise a right
27
+ // one.
28
+
29
+ /** `zwlr_layer_shell_v1.layer`. */
30
+ export const LAYER = { BACKGROUND: 0, BOTTOM: 1, TOP: 2, OVERLAY: 3 };
31
+ /** `zwlr_layer_surface_v1.anchor` bits. */
32
+ export const ANCHOR = { TOP: 1, BOTTOM: 2, LEFT: 4, RIGHT: 8 };
33
+ /** `zwlr_layer_surface_v1.keyboard_interactivity`. */
34
+ export const KEYBOARD = { NONE: 0, EXCLUSIVE: 1, ON_DEMAND: 2 };
35
+
36
+ export const ALL_EDGES =
37
+ ANCHOR.TOP | ANCHOR.BOTTOM | ANCHOR.LEFT | ANCHOR.RIGHT;
38
+
39
+ const LAYER_NAMES = { background: 0, bottom: 1, top: 2, overlay: 3 };
40
+ const EDGE_NAMES = { top: 1, bottom: 2, left: 4, right: 8 };
41
+ const KEYBOARD_NAMES = { none: 0, exclusive: 1, 'on-demand': 2, on_demand: 2 };
42
+
43
+ /**
44
+ * The window types that are layer surfaces, and what each defaults to. A
45
+ * `layerShell` object on the window overrides any of these keys.
46
+ */
47
+ const TYPE_DEFAULTS = {
48
+ dock: (attributes, size) => ({
49
+ layer: 'top',
50
+ anchor: dockEdge(attributes, size),
51
+ exclusiveZone: 'auto',
52
+ keyboardInteractivity: 'none',
53
+ }),
54
+ desktop: () => ({
55
+ layer: 'background',
56
+ anchor: 'all',
57
+ exclusiveZone: -1,
58
+ keyboardInteractivity: 'none',
59
+ }),
60
+ notification: () => ({
61
+ layer: 'top',
62
+ anchor: ['top', 'right'],
63
+ margin: 12,
64
+ exclusiveZone: 0,
65
+ keyboardInteractivity: 'none',
66
+ }),
67
+ splash: () => ({
68
+ layer: 'overlay',
69
+ anchor: 'none',
70
+ exclusiveZone: 0,
71
+ keyboardInteractivity: 'none',
72
+ }),
73
+ };
74
+
75
+ /** A `layerShell` object with no window type behind it: a floating overlay. */
76
+ const PLAIN_DEFAULTS = {
77
+ layer: 'top',
78
+ anchor: 'none',
79
+ exclusiveZone: 0,
80
+ keyboardInteractivity: 'none',
81
+ };
82
+
83
+ /** The first of a `windowType` array, or the string itself. */
84
+ export function primaryWindowType(windowType) {
85
+ return Array.isArray(windowType) ? windowType[0] : windowType;
86
+ }
87
+
88
+ function dockEdge(attributes, { width, height }) {
89
+ if (width >= height) return attributes.y === 0 ? 'top' : 'bottom';
90
+ return attributes.x === 0 ? 'left' : 'right';
91
+ }
92
+
93
+ /**
94
+ * The layer role a window's attributes ask for, or null for an ordinary
95
+ * toplevel.
96
+ *
97
+ * `attributes` are what `WindowNode.realize()` built: device pixels of
98
+ * content, so `scale` is needed to speak the compositor's logical ones. The
99
+ * `layerShell` attribute takes:
100
+ *
101
+ * layer 'background' | 'bottom' | 'top' | 'overlay'
102
+ * anchor an edge name, an array of them, 'all' or 'none'
103
+ * exclusiveZone a number, 'auto' (the window's thickness), or -1
104
+ * margin a number, or { top, right, bottom, left }
105
+ * keyboardInteractivity 'none' | 'on-demand' | 'exclusive'
106
+ * namespace what the compositor's rules match the surface by
107
+ * output a `wl_output` proxy; omitted, the compositor picks
108
+ *
109
+ * `layerShell: false` keeps a window with a dock type an ordinary toplevel.
110
+ *
111
+ * @returns {null | { layer:number, anchor:number, exclusiveZone:number,
112
+ * margin:{top:number,right:number,bottom:number,left:number},
113
+ * keyboardInteractivity:number, namespace:string, output:object|null }}
114
+ */
115
+ export function layerRoleFor(attributes = {}, { scale = 1 } = {}) {
116
+ const explicit = attributes.layerShell;
117
+ if (explicit === false) return null;
118
+ const type = primaryWindowType(attributes.windowType);
119
+ const typed = Object.hasOwn(TYPE_DEFAULTS, type);
120
+ if (!explicit && !typed) return null;
121
+ const size = {
122
+ width: (attributes.width ?? 0) / scale,
123
+ height: (attributes.height ?? 0) / scale,
124
+ };
125
+ const defaults = typed
126
+ ? TYPE_DEFAULTS[type](attributes, size)
127
+ : PLAIN_DEFAULTS;
128
+ const spec = {
129
+ ...defaults,
130
+ ...(explicit && typeof explicit === 'object' ? explicit : {}),
131
+ };
132
+ return {
133
+ layer: layerValue(spec.layer),
134
+ anchor: anchorValue(spec.anchor),
135
+ // 'auto' stays symbolic: the zone is the thickness at whatever size the
136
+ // surface ends up, and a resize moves it (`LayerRole.resize`).
137
+ exclusiveZone:
138
+ spec.exclusiveZone === 'auto' ? 'auto' : (spec.exclusiveZone ?? 0) | 0,
139
+ margin: marginValue(spec.margin),
140
+ keyboardInteractivity: keyboardValue(spec.keyboardInteractivity),
141
+ namespace: String(spec.namespace ?? `react-x11-${typed ? type : 'layer'}`),
142
+ output: spec.output ?? null,
143
+ };
144
+ }
145
+
146
+ // ---- value spellings -------------------------------------------------------
147
+
148
+ export function layerValue(v) {
149
+ if (typeof v === 'number') return v;
150
+ const n = LAYER_NAMES[v];
151
+ if (n === undefined) {
152
+ throw new Error(
153
+ `react-x11 (wayland): unknown layer ${JSON.stringify(v)} — expected ${Object.keys(LAYER_NAMES).join(', ')}`,
154
+ );
155
+ }
156
+ return n;
157
+ }
158
+
159
+ export function anchorValue(v) {
160
+ if (typeof v === 'number') return v;
161
+ if (v == null || v === 'none') return 0;
162
+ if (v === 'all') return ALL_EDGES;
163
+ const edges = Array.isArray(v) ? v : [v];
164
+ let bits = 0;
165
+ for (const e of edges) {
166
+ const bit = EDGE_NAMES[e];
167
+ if (bit === undefined) {
168
+ throw new Error(
169
+ `react-x11 (wayland): unknown anchor edge ${JSON.stringify(e)} — expected top, bottom, left, right, 'all' or 'none'`,
170
+ );
171
+ }
172
+ bits |= bit;
173
+ }
174
+ return bits;
175
+ }
176
+
177
+ export function keyboardValue(v) {
178
+ if (typeof v === 'number') return v;
179
+ if (v == null) return KEYBOARD.NONE;
180
+ const n = KEYBOARD_NAMES[v];
181
+ if (n === undefined) {
182
+ throw new Error(
183
+ `react-x11 (wayland): unknown keyboardInteractivity ${JSON.stringify(v)} — expected none, on-demand or exclusive`,
184
+ );
185
+ }
186
+ return n;
187
+ }
188
+
189
+ export function marginValue(v) {
190
+ if (typeof v === 'number') return { top: v, right: v, bottom: v, left: v };
191
+ return {
192
+ top: (v?.top ?? 0) | 0,
193
+ right: (v?.right ?? 0) | 0,
194
+ bottom: (v?.bottom ?? 0) | 0,
195
+ left: (v?.left ?? 0) | 0,
196
+ };
197
+ }
198
+
199
+ /**
200
+ * The strut a surface of this size casts from the edge it is anchored to:
201
+ * its thickness across the one axis it is anchored on one side of. A surface
202
+ * anchored to a corner, or to no edge, reserves nothing.
203
+ */
204
+ export function exclusiveZoneFor(anchor, width, height) {
205
+ const vertical =
206
+ Boolean(anchor & ANCHOR.TOP) !== Boolean(anchor & ANCHOR.BOTTOM);
207
+ const horizontal =
208
+ Boolean(anchor & ANCHOR.LEFT) !== Boolean(anchor & ANCHOR.RIGHT);
209
+ if (vertical && !horizontal) return Math.round(height);
210
+ if (horizontal && !vertical) return Math.round(width);
211
+ return 0;
212
+ }
213
+
214
+ // ---- the role --------------------------------------------------------------
215
+
216
+ /**
217
+ * What a layer window knows that a toplevel does not, hung on the window as
218
+ * `wl.layer`. `stretchX`/`stretchY` are the axes the compositor sizes: a
219
+ * `resize()` on one of those is a request the configure will overrule.
220
+ */
221
+ export class LayerRole {
222
+ constructor(
223
+ win,
224
+ proxy,
225
+ { layer, anchor, exclusiveZone, autoZone = false, namespace },
226
+ ) {
227
+ this.window = win;
228
+ /** the `zwlr_layer_surface_v1` proxy */
229
+ this.proxy = proxy;
230
+ this.layer = layer;
231
+ this.anchor = anchor;
232
+ this.exclusiveZone = exclusiveZone;
233
+ /** the zone is the surface's thickness, and follows its size */
234
+ this.autoZone = autoZone;
235
+ this.namespace = namespace;
236
+ this.stretchX =
237
+ Boolean(anchor & ANCHOR.LEFT) && Boolean(anchor & ANCHOR.RIGHT);
238
+ this.stretchY =
239
+ Boolean(anchor & ANCHOR.TOP) && Boolean(anchor & ANCHOR.BOTTOM);
240
+ }
241
+
242
+ /** Ask for a size, in logical pixels; a stretched axis is left to the compositor. */
243
+ setSize(width, height) {
244
+ this.proxy.$.set_size(
245
+ this.stretchX ? 0 : Math.max(1, Math.round(width)),
246
+ this.stretchY ? 0 : Math.max(1, Math.round(height)),
247
+ );
248
+ }
249
+
250
+ setExclusiveZone(zone) {
251
+ this.exclusiveZone = zone | 0;
252
+ this.proxy.$.set_exclusive_zone(zone | 0);
253
+ }
254
+
255
+ /** A new size, and the strut that follows it when the strut was measured. */
256
+ resize(width, height) {
257
+ this.setSize(width, height);
258
+ if (this.autoZone) {
259
+ this.setExclusiveZone(exclusiveZoneFor(this.anchor, width, height));
260
+ }
261
+ }
262
+
263
+ /** Move to another layer (protocol version 2 and up). */
264
+ setLayer(layer) {
265
+ if (this.proxy.version < 2) return false;
266
+ this.layer = layerValue(layer);
267
+ this.proxy.$.set_layer(this.layer);
268
+ return true;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * A layer surface, built without waiting for anything — the body of
274
+ * `WaylandWindow.createLayerSync`, which passes its class in so this file
275
+ * need not import the one that imports it.
276
+ *
277
+ * As with a toplevel the window is not paintable until the first configure
278
+ * has been acked; `whenConfigured` says when. The compositor may hand back a
279
+ * different size than asked, and will on a stretched axis.
280
+ */
281
+ export function createLayerSurface(
282
+ WaylandWindow,
283
+ {
284
+ conn,
285
+ compositor,
286
+ layerShell,
287
+ width,
288
+ height,
289
+ layer = LAYER.TOP,
290
+ anchor = 0,
291
+ exclusiveZone = 0,
292
+ margin = 0,
293
+ keyboardInteractivity = KEYBOARD.NONE,
294
+ namespace = 'react-x11',
295
+ output = null,
296
+ },
297
+ ) {
298
+ if (!layerShell) {
299
+ throw new Error(
300
+ 'react-x11 (wayland): this compositor does not advertise zwlr_layer_shell_v1, so there are no layer surfaces here',
301
+ );
302
+ }
303
+ const layerId = layerValue(layer);
304
+ const anchorBits = anchorValue(anchor);
305
+ const m = marginValue(margin);
306
+ const w = Math.max(1, Math.round(width || 1));
307
+ const h = Math.max(1, Math.round(height || 1));
308
+ const zone =
309
+ exclusiveZone === 'auto'
310
+ ? exclusiveZoneFor(anchorBits, w, h)
311
+ : (exclusiveZone ?? 0) | 0;
312
+
313
+ const surface = compositor.$.create_surface();
314
+ // a null output: the one the compositor prefers
315
+ const proxy = layerShell.$.get_layer_surface(
316
+ surface.id,
317
+ output ?? null,
318
+ layerId,
319
+ String(namespace),
320
+ );
321
+ const win = new WaylandWindow({
322
+ conn,
323
+ surface,
324
+ xdgSurface: null,
325
+ role: proxy,
326
+ kind: 'layer',
327
+ size: { width: w, height: h },
328
+ });
329
+ win.layerSurface = proxy;
330
+ win.layer = new LayerRole(win, proxy, {
331
+ layer: layerId,
332
+ anchor: anchorBits,
333
+ exclusiveZone: zone,
334
+ autoZone: exclusiveZone === 'auto',
335
+ namespace,
336
+ });
337
+ win._wireCommon();
338
+
339
+ proxy.on('configure', (serial, cw, ch) => {
340
+ // 0 on an axis means the client keeps its own size there.
341
+ let changed = false;
342
+ if (cw > 0 && cw !== win.width) {
343
+ win.width = cw;
344
+ changed = true;
345
+ }
346
+ if (ch > 0 && ch !== win.height) {
347
+ win.height = ch;
348
+ changed = true;
349
+ }
350
+ if (changed) win.emit('resize', { width: win.width, height: win.height });
351
+ win._onShellConfigure(serial);
352
+ });
353
+ proxy.on('closed', () => win.emit('close'));
354
+
355
+ win.layer.setSize(w, h);
356
+ proxy.$.set_anchor(anchorBits);
357
+ proxy.$.set_exclusive_zone(zone);
358
+ proxy.$.set_margin(m.top, m.right, m.bottom, m.left);
359
+ proxy.$.set_keyboard_interactivity(keyboardValue(keyboardInteractivity));
360
+ // the empty commit that asks for the first configure
361
+ surface.$.commit();
362
+ return win;
363
+ }