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.
- package/package.json +11 -8
- package/src/Reconciler.js +34 -21
- package/src/components/Select.js +8 -2
- package/src/events.js +8 -2
- package/src/glnodes.js +15 -3
- package/src/index.d.ts +5 -0
- package/src/nodes/boxpaint.js +9 -0
- package/src/nodes/preedit.js +64 -14
- package/src/ntk.js +10 -1
- package/src/scale.js +52 -22
- package/src/screencolor.js +104 -17
- package/src/types/system.d.ts +14 -1
- package/src/wayland/app.js +574 -0
- package/src/wayland/backendwindow.js +1199 -0
- package/src/wayland/clipboard.js +326 -0
- package/src/wayland/connection.js +485 -0
- package/src/wayland/context2d.js +2290 -0
- package/src/wayland/decorations.js +476 -0
- package/src/wayland/device.js +55 -0
- package/src/wayland/dmabuf.js +89 -0
- package/src/wayland/dnd.js +581 -0
- package/src/wayland/fdutil.js +108 -0
- package/src/wayland/framestyle.js +257 -0
- package/src/wayland/framewatch.js +190 -0
- package/src/wayland/glarea.js +372 -0
- package/src/wayland/glcontext.js +415 -0
- package/src/wayland/glyphatlas.js +237 -0
- package/src/wayland/input.js +417 -0
- package/src/wayland/keysymnames.js +35 -0
- package/src/wayland/layershell.js +363 -0
- package/src/wayland/outputs.js +601 -0
- package/src/wayland/protocols/cursor-shape-v1.json +1 -0
- package/src/wayland/protocols/ext-idle-notify-v1.json +1 -0
- package/src/wayland/protocols/ext-image-capture-source-v1.json +1 -0
- package/src/wayland/protocols/ext-image-copy-capture-v1.json +1 -0
- package/src/wayland/protocols/fractional-scale-v1.json +1 -0
- package/src/wayland/protocols/index.json +131 -0
- package/src/wayland/protocols/kde-server-decoration.json +1 -0
- package/src/wayland/protocols/keyboard-shortcuts-inhibit-unstable-v1.json +1 -0
- package/src/wayland/protocols/linux-dmabuf-v1.json +1 -0
- package/src/wayland/protocols/pointer-constraints-unstable-v1.json +1 -0
- package/src/wayland/protocols/presentation-time.json +1 -0
- package/src/wayland/protocols/primary-selection-unstable-v1.json +1 -0
- package/src/wayland/protocols/relative-pointer-unstable-v1.json +1 -0
- package/src/wayland/protocols/tablet-v2.json +1 -0
- package/src/wayland/protocols/text-input-unstable-v3.json +1 -0
- package/src/wayland/protocols/viewporter.json +1 -0
- package/src/wayland/protocols/wayland.json +1 -0
- package/src/wayland/protocols/wlr-layer-shell-unstable-v1.json +1 -0
- package/src/wayland/protocols/wlr-screencopy-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-activation-v1.json +1 -0
- package/src/wayland/protocols/xdg-decoration-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-output-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-shell.json +1 -0
- package/src/wayland/protocols/xdg-toplevel-icon-v1.json +1 -0
- package/src/wayland/readback.js +99 -0
- package/src/wayland/screencopy.js +584 -0
- package/src/wayland/seat.js +584 -0
- package/src/wayland/shm.js +226 -0
- package/src/wayland/ssd.js +234 -0
- package/src/wayland/surface.js +141 -0
- package/src/wayland/swapchain.js +411 -0
- package/src/wayland/tablet.js +522 -0
- package/src/wayland/target.js +269 -0
- package/src/wayland/text.js +113 -0
- package/src/wayland/textinput.js +671 -0
- package/src/wayland/touch.js +284 -0
- package/src/wayland/window.js +854 -0
- package/src/wayland/xkb.js +425 -0
- 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
|
+
}
|