react-x11 2.13.0 → 2.15.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 (70) 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/glnodes.js +15 -3
  6. package/src/index.d.ts +5 -0
  7. package/src/nodes/boxpaint.js +9 -0
  8. package/src/nodes/preedit.js +64 -14
  9. package/src/ntk.js +10 -1
  10. package/src/scale.js +52 -22
  11. package/src/screencolor.js +104 -17
  12. package/src/types/system.d.ts +14 -1
  13. package/src/wayland/app.js +574 -0
  14. package/src/wayland/backendwindow.js +1199 -0
  15. package/src/wayland/clipboard.js +326 -0
  16. package/src/wayland/connection.js +485 -0
  17. package/src/wayland/context2d.js +2290 -0
  18. package/src/wayland/decorations.js +476 -0
  19. package/src/wayland/device.js +55 -0
  20. package/src/wayland/dmabuf.js +89 -0
  21. package/src/wayland/dnd.js +581 -0
  22. package/src/wayland/fdutil.js +108 -0
  23. package/src/wayland/framestyle.js +257 -0
  24. package/src/wayland/framewatch.js +190 -0
  25. package/src/wayland/glarea.js +372 -0
  26. package/src/wayland/glcontext.js +415 -0
  27. package/src/wayland/glyphatlas.js +237 -0
  28. package/src/wayland/input.js +417 -0
  29. package/src/wayland/keysymnames.js +35 -0
  30. package/src/wayland/layershell.js +363 -0
  31. package/src/wayland/outputs.js +601 -0
  32. package/src/wayland/protocols/cursor-shape-v1.json +1 -0
  33. package/src/wayland/protocols/ext-idle-notify-v1.json +1 -0
  34. package/src/wayland/protocols/ext-image-capture-source-v1.json +1 -0
  35. package/src/wayland/protocols/ext-image-copy-capture-v1.json +1 -0
  36. package/src/wayland/protocols/fractional-scale-v1.json +1 -0
  37. package/src/wayland/protocols/index.json +131 -0
  38. package/src/wayland/protocols/kde-server-decoration.json +1 -0
  39. package/src/wayland/protocols/keyboard-shortcuts-inhibit-unstable-v1.json +1 -0
  40. package/src/wayland/protocols/linux-dmabuf-v1.json +1 -0
  41. package/src/wayland/protocols/pointer-constraints-unstable-v1.json +1 -0
  42. package/src/wayland/protocols/presentation-time.json +1 -0
  43. package/src/wayland/protocols/primary-selection-unstable-v1.json +1 -0
  44. package/src/wayland/protocols/relative-pointer-unstable-v1.json +1 -0
  45. package/src/wayland/protocols/tablet-v2.json +1 -0
  46. package/src/wayland/protocols/text-input-unstable-v3.json +1 -0
  47. package/src/wayland/protocols/viewporter.json +1 -0
  48. package/src/wayland/protocols/wayland.json +1 -0
  49. package/src/wayland/protocols/wlr-layer-shell-unstable-v1.json +1 -0
  50. package/src/wayland/protocols/wlr-screencopy-unstable-v1.json +1 -0
  51. package/src/wayland/protocols/xdg-activation-v1.json +1 -0
  52. package/src/wayland/protocols/xdg-decoration-unstable-v1.json +1 -0
  53. package/src/wayland/protocols/xdg-output-unstable-v1.json +1 -0
  54. package/src/wayland/protocols/xdg-shell.json +1 -0
  55. package/src/wayland/protocols/xdg-toplevel-icon-v1.json +1 -0
  56. package/src/wayland/readback.js +99 -0
  57. package/src/wayland/screencopy.js +584 -0
  58. package/src/wayland/seat.js +584 -0
  59. package/src/wayland/shm.js +226 -0
  60. package/src/wayland/ssd.js +234 -0
  61. package/src/wayland/surface.js +141 -0
  62. package/src/wayland/swapchain.js +411 -0
  63. package/src/wayland/tablet.js +522 -0
  64. package/src/wayland/target.js +269 -0
  65. package/src/wayland/text.js +113 -0
  66. package/src/wayland/textinput.js +671 -0
  67. package/src/wayland/touch.js +284 -0
  68. package/src/wayland/window.js +854 -0
  69. package/src/wayland/xkb.js +425 -0
  70. package/src/windowstate.js +41 -1
@@ -0,0 +1,372 @@
1
+ // `<glarea>` on the Wayland backend: a viewport into the window's backing
2
+ // target, not a window of its own.
3
+ //
4
+ // On X11 a `<glarea>` is a child window — a separate drawable the X server
5
+ // stacks above the parent's drawing — and on the Cocoa backend a sublayer
6
+ // the WindowServer composites. Neither exists here. A Wayland client owns
7
+ // one buffer per surface, and a subsurface would be a second swapchain, a
8
+ // second frame clock and a second dma-buf import per frame for what is, on
9
+ // this backend, already one GL context drawing into one target. So the
10
+ // surface is a *rectangle of the backing target*: `makeCurrent()` binds the
11
+ // window's target with the viewport and scissor set to the area's rect, the
12
+ // app's GL calls land inside it, and `SwapBuffers()` is the note that the
13
+ // rect changed. The frame loop orders it — the tree's 2D paint is flushed
14
+ // under it, the area draws over, and the panes its children were painted
15
+ // on are composited over that (backendwindow.js `_runSurfaces`).
16
+ //
17
+ // What `GlAreaNode` (src/glnodes.js) needs is the child-"window" contract:
18
+ // `createWindow({ parent, … })` answering an object with
19
+ // `getContext('opengl', config)`, `setState(rect)`, `map()`, `destroy()`,
20
+ // `requestAnimationFrame`; and from the context `backend`, `ready`,
21
+ // `makeCurrent`, `SwapBuffers`, `canRender`, `onFrameAvailable`, plus the
22
+ // WebGL-shaped GL table itself. The table is the shared one, prototype-
23
+ // delegated as src/cocoa/glarea.js does it, with the entry points that name
24
+ // framebuffer coordinates — `viewport`, `scissor`, `bindFramebuffer`, and
25
+ // the scissor switch — re-based on the rect, so a scene written for a
26
+ // window of its own draws in its corner of ours.
27
+ //
28
+ // Two coordinate systems, as everywhere in this backend: the rect is in the
29
+ // window's content device pixels, y down; GL's viewport and scissor are in
30
+ // the target's pixels from the bottom-left. The content origin (the frame's
31
+ // insets) is added and y is flipped once, here, in `place()`.
32
+ //
33
+ // The pane (`WaylandOverlayPane`) is the other half of the same idea. The
34
+ // children of a `<glarea>` are 2D content drawn *above* its frames
35
+ // (src/gloverlay.js); on a backend that composites, one pane covers the
36
+ // surface and the backend blends it over the GL. Here a pane is an offscreen
37
+ // target the tree paints into during its flush, and the window draws it over
38
+ // the surface with `drawImage` once the surface has drawn — so a translucent
39
+ // legend, an antialiased edge or a shadow blends with the GL frame under it,
40
+ // as on the Cocoa backend and unlike X11's opaque child windows.
41
+
42
+ import { GLTarget } from './target.js';
43
+ import { WaylandContext2D } from './context2d.js';
44
+
45
+ /**
46
+ * The `visual` `chooseGLConfig` answers with. `GlAreaNode` passes the config's
47
+ * visual and depth straight back to `createWindow`, which is how the app
48
+ * tells a `<glarea>`'s child "window" apart from a real one.
49
+ */
50
+ export const GLAREA_VISUAL = 'wayland:glarea';
51
+
52
+ export class WaylandGLArea {
53
+ /**
54
+ * @param {import('./backendwindow.js').WaylandBackendWindow} parent
55
+ * @param {{x?:number,y?:number,width?:number,height?:number}} options
56
+ */
57
+ constructor(parent, options = {}) {
58
+ this.parent = parent;
59
+ this.app = parent.app;
60
+ this.destroyed = false;
61
+ this.mapped = false;
62
+ this._reactX11Node = null;
63
+ this._context = null;
64
+ this._listeners = new Map();
65
+ /** set by `SwapBuffers` for the frame it happened in */
66
+ this.drewThisFrame = false;
67
+ this.rect = {
68
+ x: options.x ?? 0,
69
+ y: options.y ?? 0,
70
+ width: Math.max(1, options.width ?? 1),
71
+ height: Math.max(1, options.height ?? 1),
72
+ };
73
+ parent._surfaces.add(this);
74
+ }
75
+
76
+ get width() {
77
+ return this.rect.width;
78
+ }
79
+
80
+ get height() {
81
+ return this.rect.height;
82
+ }
83
+
84
+ /** Geometry in content device pixels, the unit `GlAreaNode`'s rects are in. */
85
+ setState(rect) {
86
+ if (this.destroyed) return this;
87
+ this.rect = {
88
+ x: rect.x ?? this.rect.x,
89
+ y: rect.y ?? this.rect.y,
90
+ width: Math.max(1, rect.width ?? this.rect.width),
91
+ height: Math.max(1, rect.height ?? this.rect.height),
92
+ };
93
+ return this;
94
+ }
95
+
96
+ move(x, y) {
97
+ return this.setState({ x, y });
98
+ }
99
+
100
+ resize(width, height) {
101
+ return this.setState({ width, height });
102
+ }
103
+
104
+ map() {
105
+ this.mapped = true;
106
+ return this;
107
+ }
108
+
109
+ unmap() {
110
+ this.mapped = false;
111
+ return this;
112
+ }
113
+
114
+ on(name, fn) {
115
+ let set = this._listeners.get(name);
116
+ if (!set) this._listeners.set(name, (set = new Set()));
117
+ set.add(fn);
118
+ return this;
119
+ }
120
+
121
+ off(name, fn) {
122
+ this._listeners.get(name)?.delete(fn);
123
+ return this;
124
+ }
125
+
126
+ emit(name, ev) {
127
+ for (const fn of this._listeners.get(name) ?? []) fn(ev);
128
+ }
129
+
130
+ /**
131
+ * The area's frame is a part of its window's: the callback runs after the
132
+ * tree's paint, in the same frame, so what it draws goes over the 2D and
133
+ * out with the same present.
134
+ */
135
+ requestAnimationFrame(fn) {
136
+ return this.parent.requestSurfaceFrame(fn);
137
+ }
138
+
139
+ getContext(kind, config) {
140
+ if (this.destroyed) return null;
141
+ if (kind !== 'opengl' && kind !== 'gles' && kind !== 'webgl') return null;
142
+ if (!this._context) this._context = createGLAreaContext(this, config);
143
+ return this._context;
144
+ }
145
+
146
+ destroy() {
147
+ if (this.destroyed) return;
148
+ this.destroyed = true;
149
+ this._context?._destroy();
150
+ this._context = null;
151
+ this.parent._surfaces.delete(this);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * The `gl` a `<glarea>`'s `onDraw` receives: the shared WebGL-shaped table,
157
+ * with this area's rect behind the entry points that would otherwise name
158
+ * the whole target.
159
+ */
160
+ function createGLAreaContext(area, config) {
161
+ const parent = area.parent;
162
+ const gl = parent.glctx.gl;
163
+ const ctx = Object.create(gl);
164
+ let destroyed = false;
165
+ // the rect in the target's GL coordinates, as of the last makeCurrent
166
+ let bx = 0;
167
+ let by = 0;
168
+ let bw = 1;
169
+ let bh = 1;
170
+
171
+ const place = () => {
172
+ const o = parent.contentOrigin;
173
+ const r = area.rect;
174
+ const H = parent.glctx.backing?.height ?? 0;
175
+ bx = o.x + r.x;
176
+ bw = r.width;
177
+ bh = r.height;
178
+ by = H - (o.y + r.y + r.height);
179
+ };
180
+
181
+ ctx.backend = 'direct';
182
+ ctx.config = config;
183
+ ctx.glVersion = parent.glctx.glVersion;
184
+ ctx.ready = Promise.resolve();
185
+ ctx.onFrameAvailable = null;
186
+ // No swapchain of its own to be held up by: the frame it draws in is the
187
+ // window's, and the window's frame loop only runs it when there is one.
188
+ ctx.canRender = () => !destroyed && !parent._destroyed;
189
+
190
+ ctx.makeCurrent = () => {
191
+ if (destroyed || parent._destroyed) return;
192
+ parent.app.makeCurrent();
193
+ parent.glctx.bindBacking();
194
+ place();
195
+ // The scissor is what keeps a `clear()` inside the rect. It stays on for
196
+ // the whole draw: `disable(SCISSOR_TEST)` below re-asserts it.
197
+ gl.enable(gl.SCISSOR_TEST);
198
+ gl.scissor(bx, by, bw, bh);
199
+ gl.viewport(bx, by, bw, bh);
200
+ };
201
+
202
+ ctx.viewport = (x, y, w, h) => gl.viewport(bx + x, by + y, w, h);
203
+
204
+ // A scissor of the scene's own is cut to the rect: nothing it asks for
205
+ // can reach outside, and `(0, 0, width, height)` is the whole of it.
206
+ ctx.scissor = (x, y, w, h) => {
207
+ const x0 = Math.max(bx, bx + x);
208
+ const y0 = Math.max(by, by + y);
209
+ const x1 = Math.min(bx + bw, bx + x + w);
210
+ const y1 = Math.min(by + bh, by + y + h);
211
+ gl.scissor(x0, y0, Math.max(0, x1 - x0), Math.max(0, y1 - y0));
212
+ };
213
+
214
+ ctx.disable = (cap) => {
215
+ if (cap === gl.SCISSOR_TEST) {
216
+ gl.scissor(bx, by, bw, bh);
217
+ return;
218
+ }
219
+ gl.disable(cap);
220
+ };
221
+
222
+ // WebGL's "null means the default framebuffer" — and this surface's
223
+ // default is the window's backing target, not GL's framebuffer zero,
224
+ // which here is the swapchain buffer the 2D never draws into. A scene
225
+ // that renders through its own FBO and unbinds at the end lands back on
226
+ // the window (the SSAA pattern, the shadow-map pattern).
227
+ ctx.bindFramebuffer = (target, fb) => {
228
+ gl.bindFramebuffer(
229
+ target,
230
+ fb == null ? (parent.glctx.backing?.fbo ?? null) : fb,
231
+ );
232
+ };
233
+
234
+ // The frame is in the target already; what a swap means here is "this
235
+ // rect changed", for the present's damage and for the panes to be drawn
236
+ // over it again.
237
+ ctx.SwapBuffers = () => {
238
+ if (destroyed) return;
239
+ area.drewThisFrame = true;
240
+ parent._surfaceDrew(area);
241
+ };
242
+
243
+ ctx._destroy = () => {
244
+ destroyed = true;
245
+ };
246
+ // `GlAreaNode.destroySubtree` calls `gl.destroy?.()` before the window's
247
+ ctx.destroy = ctx._destroy;
248
+
249
+ return ctx;
250
+ }
251
+
252
+ /**
253
+ * The pane a `<glarea>`'s children are drawn on: an offscreen target with a
254
+ * 2d context of its own, painted by the tree inside the window's flush and
255
+ * blended over the surface by the window's frame loop.
256
+ *
257
+ * It speaks the verbs the overlay drives (src/gloverlay.js `Pane`):
258
+ * `setState`, `map`, `unmap`, `getContext`, `present`, `destroy`. The
259
+ * context is handed out *inside a frame* — `begin()` has been called — so
260
+ * the overlay can draw at once, as it does on the other backends where a
261
+ * context has no frame; `present()` closes that frame and opens the next.
262
+ */
263
+ export class WaylandOverlayPane {
264
+ /**
265
+ * @param {import('./app.js').WaylandApp} app
266
+ * @param {{parent:object,x?:number,y?:number,width?:number,height?:number}} options
267
+ */
268
+ constructor(app, options) {
269
+ this.app = app;
270
+ this.parent = options.parent;
271
+ this.destroyed = false;
272
+ this.hidden = false;
273
+ /** something has been painted on it: before that there is nothing to composite */
274
+ this.presented = false;
275
+ this.rect = {
276
+ x: options.x ?? 0,
277
+ y: options.y ?? 0,
278
+ width: Math.max(1, options.width ?? 1),
279
+ height: Math.max(1, options.height ?? 1),
280
+ };
281
+ this.target = null;
282
+ this._ctx = null;
283
+ this.parent._panes.add(this);
284
+ }
285
+
286
+ get width() {
287
+ return this.rect.width;
288
+ }
289
+
290
+ get height() {
291
+ return this.rect.height;
292
+ }
293
+
294
+ /** Geometry in content device pixels. A new size is a new (cleared) target
295
+ * and the overlay repaints it whole (`Pane.place` says so). */
296
+ setState(rect) {
297
+ if (this.destroyed) return this;
298
+ const resized =
299
+ rect.width !== this.rect.width || rect.height !== this.rect.height;
300
+ this.rect = { ...rect };
301
+ if (resized && this.target) {
302
+ this.app.makeCurrent();
303
+ this.target.resize(this.rect.width, this.rect.height);
304
+ this.presented = false;
305
+ this._ctx?.begin(this.target.width, this.target.height);
306
+ this.app.rebindWindowTarget();
307
+ }
308
+ return this;
309
+ }
310
+
311
+ map() {
312
+ this.hidden = false;
313
+ return this;
314
+ }
315
+
316
+ unmap() {
317
+ this.hidden = true;
318
+ return this;
319
+ }
320
+
321
+ /** `Pane` listens for 'draw' — pixels lost to a resize. A target's are
322
+ * lost too, and `Pane.place` already repaints whole on a size change. */
323
+ on() {
324
+ return this;
325
+ }
326
+
327
+ getContext(kind = '2d') {
328
+ if (kind !== '2d' || this.destroyed) return null;
329
+ if (!this._ctx) {
330
+ this.app.makeCurrent();
331
+ this.target = new GLTarget(this.app.gl, {
332
+ width: this.rect.width,
333
+ height: this.rect.height,
334
+ stencil: true,
335
+ });
336
+ this._ctx = new WaylandContext2D(this.app.gl, {
337
+ fontManager: this.app.fonts,
338
+ target: this.target,
339
+ makeCurrent: () => this.app.makeCurrent(),
340
+ });
341
+ this._ctx.init();
342
+ this._ctx.begin(this.target.width, this.target.height);
343
+ // the window's own context is mid-frame; its target goes back
344
+ this.app.rebindWindowTarget();
345
+ }
346
+ return this._ctx;
347
+ }
348
+
349
+ /** What was painted is complete; the window blends it over the surface
350
+ * in this frame. The next paint finds the context open again. */
351
+ present() {
352
+ if (this.destroyed || !this._ctx) return;
353
+ this.app.makeCurrent();
354
+ this._ctx.end();
355
+ this.presented = true;
356
+ this.parent._paneDrew(this);
357
+ this._ctx.begin(this.target.width, this.target.height);
358
+ this.app.rebindWindowTarget();
359
+ }
360
+
361
+ destroy() {
362
+ if (this.destroyed) return;
363
+ this.destroyed = true;
364
+ this.parent._panes.delete(this);
365
+ this.app.makeCurrent();
366
+ this._ctx?.destroy();
367
+ this._ctx = null;
368
+ this.target?.destroy();
369
+ this.target = null;
370
+ this.app.rebindWindowTarget();
371
+ }
372
+ }