react-x11 2.51.1 → 2.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -61
- package/docs/README.md +256 -0
- package/docs/accessibility.md +575 -0
- package/docs/appearance.md +317 -0
- package/docs/click-to-component.md +92 -0
- package/docs/clipboard.md +262 -0
- package/docs/components.md +1231 -0
- package/docs/dbus.md +205 -0
- package/docs/debugging.md +371 -0
- package/docs/desktop-calendar.md +247 -0
- package/docs/desktop.md +523 -0
- package/docs/devtools.md +133 -0
- package/docs/drag-and-drop.md +585 -0
- package/docs/ecosystem/animation.md +360 -0
- package/docs/ecosystem/data-fetching.md +186 -0
- package/docs/ecosystem/dev-tooling.md +393 -0
- package/docs/ecosystem/forms.md +277 -0
- package/docs/ecosystem/headless.md +194 -0
- package/docs/ecosystem/i18n.md +86 -0
- package/docs/ecosystem/icons.md +377 -0
- package/docs/ecosystem/layout.md +206 -0
- package/docs/ecosystem/routing.md +135 -0
- package/docs/ecosystem/state.md +344 -0
- package/docs/ecosystem/testing.md +474 -0
- package/docs/ecosystem/theming.md +494 -0
- package/docs/ecosystem.md +439 -0
- package/docs/elements.md +2079 -0
- package/docs/embedding.md +239 -0
- package/docs/events.md +742 -0
- package/docs/extending.md +1700 -0
- package/docs/eyedropper.md +164 -0
- package/docs/filedialog.md +222 -0
- package/docs/frame.md +436 -0
- package/docs/gl.md +173 -0
- package/docs/globalmenu.md +195 -0
- package/docs/macos.md +592 -0
- package/docs/notifications.md +150 -0
- package/docs/packaging.md +468 -0
- package/docs/permissions.md +143 -0
- package/docs/react-features.md +405 -0
- package/docs/remote.md +179 -0
- package/docs/scale.md +225 -0
- package/docs/security.md +132 -0
- package/docs/styling.md +1710 -0
- package/docs/system.md +519 -0
- package/docs/testing.md +494 -0
- package/docs/typescript.md +75 -0
- package/docs/uri-schemes.md +340 -0
- package/docs/wayland-backend.md +332 -0
- package/docs/windows-integrations.md +177 -0
- package/docs/windows.md +380 -0
- package/package.json +11 -3
- package/src/Reconciler.js +2 -2
- package/src/a11y.js +1 -1
- package/src/backend/context2d.js +3 -3
- package/src/bootstrap.js +1 -1
- package/src/bus.js +39 -26
- package/src/cli/create.d.ts +43 -0
- package/src/cli/create.js +305 -0
- package/src/cli/env.js +484 -0
- package/src/cli/index.js +29 -2
- package/src/cli/templates/AGENTS.template.md +195 -0
- package/src/cli/templates/DESIGN.template.md +269 -0
- package/src/cocoa/app.js +6 -6
- package/src/cocoa/bezels.js +1 -1
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/globalmenu.js +1 -1
- package/src/cocoa/keymap.js +1 -1
- package/src/cocoa/panewindow.js +1 -1
- package/src/cocoa/player.js +1 -1
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/promotion.js +1 -1
- package/src/cocoa/relaunch.js +1 -1
- package/src/cocoa/threaded.js +1 -1
- package/src/cocoa/video.js +1 -1
- package/src/cocoa/window.js +4 -4
- package/src/components/Menu.js +7 -2
- package/src/components/Switch.js +1 -1
- package/src/components/native.js +1 -1
- package/src/desktopintegration.js +2 -1
- package/src/desktopsettings.js +193 -24
- package/src/dnd.js +2 -2
- package/src/filedialog.js +3 -3
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/keyboardstate.js +28 -0
- package/src/layouts.js +1 -1
- package/src/node.d.ts +10 -10
- package/src/nodes/animation.js +1 -1
- package/src/nodes/invalidate.js +1 -1
- package/src/nodes/layouthost.js +1 -1
- package/src/nodes/video.js +19 -8
- package/src/nodes/window/droptarget.js +1 -1
- package/src/pacing.js +1 -1
- package/src/portal.js +31 -0
- package/src/refresh/index.js +2 -1
- package/src/screencolor.js +5 -10
- package/src/types/system.d.ts +8 -6
- package/src/types/video.d.ts +5 -2
- package/src/videobackend.js +28 -0
- package/src/videoframes.js +29 -16
- package/src/wayland/app.js +86 -0
- package/src/wayland/clipboard.js +14 -7
- package/src/wayland/connection.js +3 -0
- package/src/wayland/context2d.js +129 -10
- package/src/wayland/protocols/index.json +6 -0
- package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
- package/src/wayland/seat.js +28 -1
- package/src/wayland/swapchain.js +23 -0
- package/src/wayland/video.js +376 -0
- package/src/wayland/xkb.js +137 -0
- package/src/win32/a11y.js +1 -1
- package/src/win32/app.js +2 -2
- package/src/win32/fonts.js +1 -1
- package/src/win32/glarea.js +4 -4
- package/src/win32/overlay.js +1 -1
- package/src/win32/panewindow.js +3 -3
- package/src/win32/shell.js +1 -1
- package/src/win32/window.js +2 -2
- package/src/windowid.js +1 -1
- package/src/xvideo.js +189 -0
package/src/screencolor.js
CHANGED
|
@@ -65,12 +65,11 @@ import {
|
|
|
65
65
|
PortalCancelledError,
|
|
66
66
|
RESPONSE_OK,
|
|
67
67
|
hasService,
|
|
68
|
-
|
|
68
|
+
portalParentFor,
|
|
69
69
|
portalRequest,
|
|
70
70
|
portalVersion,
|
|
71
71
|
} from './portal.js';
|
|
72
72
|
import { liveApps } from './trace-registry.js';
|
|
73
|
-
import { windowIdOf } from './windowid.js';
|
|
74
73
|
|
|
75
74
|
export const SCREENSHOT_IFACE = 'org.freedesktop.portal.Screenshot';
|
|
76
75
|
/** `PickColor` arrived in version 2 of the Screenshot interface. */
|
|
@@ -220,14 +219,10 @@ async function portalPick(opts, ref) {
|
|
|
220
219
|
// options)` — no title — which is the whole reason portalRequest takes a
|
|
221
220
|
// signature now.
|
|
222
221
|
signature: 'sa{sv}',
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
args: [
|
|
227
|
-
appFor(opts)?.backend === 'wayland'
|
|
228
|
-
? ''
|
|
229
|
-
: parentWindowHandle(windowIdOf(opts.parentWindow)),
|
|
230
|
-
],
|
|
222
|
+
// An X window's handle on X11, an xdg-foreign export on Wayland, and
|
|
223
|
+
// nothing — a floating dialog, which the portal allows — where neither
|
|
224
|
+
// can be had.
|
|
225
|
+
args: [await portalParentFor(opts.parentWindow, appFor(opts))],
|
|
231
226
|
options: {},
|
|
232
227
|
signal: opts.signal,
|
|
233
228
|
});
|
package/src/types/system.d.ts
CHANGED
|
@@ -253,16 +253,18 @@ export interface DesktopSettings {
|
|
|
253
253
|
/** How far a press moves before it is a drag rather than a click. */
|
|
254
254
|
readonly dragThreshold: number;
|
|
255
255
|
/** **False means hold still** — the desktop asked for less motion:
|
|
256
|
-
* `Gtk/EnableAnimations` off over XSETTINGS,
|
|
256
|
+
* `Gtk/EnableAnimations` off over XSETTINGS, `enable-animations` off on
|
|
257
|
+
* the settings portal (Wayland), reduce motion on macOS.
|
|
257
258
|
* Core's loops do not start then (docs/styling.md, "Loops"); an element
|
|
258
259
|
* that animates on a timer of its own holds still too, and draws its
|
|
259
260
|
* resting frame. True wherever the desktop said nothing. */
|
|
260
261
|
readonly animations: boolean;
|
|
261
|
-
/** `'xsettings'`; `'
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* defaults
|
|
265
|
-
|
|
262
|
+
/** `'xsettings'`; `'portal'` on the Wayland backend, from the settings
|
|
263
|
+
* portal's GNOME namespaces; `'macos'` on the Cocoa backend, where only
|
|
264
|
+
* `animations` comes from the system (reduce motion) and the rest are the
|
|
265
|
+
* defaults; or null where nothing answered and these are the renderer's
|
|
266
|
+
* own defaults. */
|
|
267
|
+
readonly source: 'xsettings' | 'portal' | 'macos' | 'test' | null;
|
|
266
268
|
}
|
|
267
269
|
|
|
268
270
|
/**
|
package/src/types/video.d.ts
CHANGED
|
@@ -69,8 +69,11 @@ export class VideoFrames {
|
|
|
69
69
|
readonly colorSpace: VideoColorSpace;
|
|
70
70
|
readonly range: VideoRange;
|
|
71
71
|
/** This display's formats, cheapest first: what a decoder that can choose
|
|
72
|
-
* should emit (`ffmpeg -pix_fmt nv12`). NV12 leads
|
|
73
|
-
*
|
|
72
|
+
* should emit (`ffmpeg -pix_fmt nv12`). NV12 leads wherever something
|
|
73
|
+
* native takes it as it is — a layer on macOS, a shader on Wayland, an Xv
|
|
74
|
+
* port on X11 — and BGRA where a frame is converted in JavaScript. On X11
|
|
75
|
+
* it is BGRA, and not kept, until the port has answered, a few round
|
|
76
|
+
* trips after the first sink is made. */
|
|
74
77
|
readonly preferredFormats: readonly VideoFormat[];
|
|
75
78
|
/** Frames pushed so far. */
|
|
76
79
|
readonly version: number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Who writes a `<video>`'s frames natively on an app, if anyone — the seam
|
|
2
|
+
// `VideoFrames.preferredFormats` and the drawn presentation ask through
|
|
3
|
+
// (design/architecture/video.md §6.3).
|
|
4
|
+
//
|
|
5
|
+
// The app itself where its backend has the verbs: the Cocoa app over the
|
|
6
|
+
// bridge's video surfaces, the Wayland app over its own GL context. On X11
|
|
7
|
+
// the app is ntk's, which knows nothing of sinks, so the answer is the Xv
|
|
8
|
+
// writer react-x11 keeps for the connection (src/xvideo.js). A feature test,
|
|
9
|
+
// not a question about which backend this is — `typeof app.xv` is an ntk
|
|
10
|
+
// with XVideo discovery. Null where nothing will, and every frame is
|
|
11
|
+
// converted in JavaScript (`frameToRGBA`, src/videoframes.js).
|
|
12
|
+
import { xvVideoFor } from './xvideo.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `{ videoFormats(), writeVideoFrame(surface, sink, frame, node),
|
|
16
|
+
* releaseVideoFrame?(surface), prepare?(sink) }` for `app`, or null.
|
|
17
|
+
*/
|
|
18
|
+
export function videoBackendOf(app) {
|
|
19
|
+
if (!app) return null;
|
|
20
|
+
if (
|
|
21
|
+
typeof app.writeVideoFrame === 'function' ||
|
|
22
|
+
typeof app.videoFormats === 'function'
|
|
23
|
+
) {
|
|
24
|
+
return app;
|
|
25
|
+
}
|
|
26
|
+
if (typeof app.xv === 'function') return xvVideoFor(app);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
package/src/videoframes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// `VideoFrames`: a sink an application pushes decoded video frames into,
|
|
2
|
-
// and a `<video frames>` shows (
|
|
2
|
+
// and a `<video frames>` shows (design/architecture/video.md §6.3).
|
|
3
3
|
//
|
|
4
4
|
// react-x11 decodes nothing. Where the bytes come from — an `ffmpeg` child
|
|
5
5
|
// writing rawvideo to a pipe, a WASM decoder, an addon over libmpv — is the
|
|
@@ -33,12 +33,16 @@
|
|
|
33
33
|
// a decoder that can choose (`ffmpeg -pix_fmt`); one that cannot still
|
|
34
34
|
// works, with the conversion charged where it happens.
|
|
35
35
|
//
|
|
36
|
-
//
|
|
37
|
-
// (
|
|
38
|
-
//
|
|
36
|
+
// No node and no backend: the one question asked of the display is its
|
|
37
|
+
// formats, through `videoBackendOf` (src/videobackend.js) — the Cocoa app's
|
|
38
|
+
// answer from the bridge, the Wayland app's own, and on X11 the Xv writer's,
|
|
39
|
+
// which a sink starts asking for when it is made.
|
|
40
|
+
|
|
41
|
+
import { videoBackendOf } from './videobackend.js';
|
|
39
42
|
|
|
40
43
|
export const VIDEO_FORMATS = Object.freeze(['NV12', 'I420', 'BGRA']);
|
|
41
44
|
const COLOR_SPACES = ['bt709', 'bt601', 'bt2020'];
|
|
45
|
+
const BGRA_ONLY = Object.freeze(['BGRA']);
|
|
42
46
|
const RANGES = ['video', 'full'];
|
|
43
47
|
// The largest side a frame may have: a surface's limit on every backend, and
|
|
44
48
|
// a number past which a size is a bug rather than a video.
|
|
@@ -209,22 +213,30 @@ export class VideoFrames {
|
|
|
209
213
|
this._frame = null;
|
|
210
214
|
this._listeners = new Set();
|
|
211
215
|
this._preferred = null;
|
|
216
|
+
// On X11 the formats are a port's, and asking for one is round trips:
|
|
217
|
+
// started here, so they are known by the time a decoder that waits for
|
|
218
|
+
// anything at all — a probe of the file, a spawn — reads them.
|
|
219
|
+
videoBackendOf(app)?.prepare?.(this);
|
|
212
220
|
}
|
|
213
221
|
|
|
214
222
|
/**
|
|
215
223
|
* This display's formats, cheapest first: what a decoder that can choose
|
|
216
|
-
* should emit.
|
|
217
|
-
*
|
|
224
|
+
* should emit. YCbCr first wherever something native takes it as it is —
|
|
225
|
+
* a layer on Cocoa, a shader on Wayland, an Xv port on X11 — and BGRA
|
|
226
|
+
* first where a frame is converted in JavaScript on its way to a 2D
|
|
227
|
+
* context. On X11 the answer is BGRA until the port has answered, a few
|
|
228
|
+
* round trips after the first sink is made, and is not kept until then.
|
|
218
229
|
*/
|
|
219
230
|
get preferredFormats() {
|
|
220
|
-
if (this._preferred
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
231
|
+
if (this._preferred !== null) return this._preferred;
|
|
232
|
+
const asked = videoBackendOf(this.app)?.videoFormats?.();
|
|
233
|
+
// null: still being asked — answer for now, and ask again next time
|
|
234
|
+
if (asked === null) return BGRA_ONLY;
|
|
235
|
+
this._preferred = Object.freeze(
|
|
236
|
+
Array.isArray(asked) && asked.length !== 0
|
|
237
|
+
? asked.filter((f) => VIDEO_FORMATS.includes(f))
|
|
238
|
+
: ['BGRA'],
|
|
239
|
+
);
|
|
228
240
|
return this._preferred;
|
|
229
241
|
}
|
|
230
242
|
|
|
@@ -281,8 +293,9 @@ export const isVideoFrames = (v) => v instanceof VideoFrames;
|
|
|
281
293
|
|
|
282
294
|
// --- converting a frame where nothing native will ----------------------------
|
|
283
295
|
//
|
|
284
|
-
// The drawn path
|
|
285
|
-
// Cocoa bridge older than its video surfaces
|
|
296
|
+
// The drawn path where nothing native writes the frame (win32, an X server
|
|
297
|
+
// with no Xv adaptor, a Cocoa bridge older than its video surfaces, and on
|
|
298
|
+
// any backend a frame the native writer declines): the frame converted here to
|
|
286
299
|
// the straight RGBA `putImageData` takes. Not free — two million pixels a
|
|
287
300
|
// frame at 1080p — which is why `preferredFormats` says BGRA first there.
|
|
288
301
|
// The matrix and the range are the frame's; no transfer function is
|
package/src/wayland/app.js
CHANGED
|
@@ -30,6 +30,7 @@ import { releaseDevice } from './device.js';
|
|
|
30
30
|
import { WaylandSeat } from './seat.js';
|
|
31
31
|
import { WaylandBackendWindow } from './backendwindow.js';
|
|
32
32
|
import { WaylandSurface } from './surface.js';
|
|
33
|
+
import { releaseVideoFrame, writeVideoFrame } from './video.js';
|
|
33
34
|
import { InputRouter } from './input.js';
|
|
34
35
|
import { WaylandTouch } from './touch.js';
|
|
35
36
|
import { WaylandTablet } from './tablet.js';
|
|
@@ -104,6 +105,9 @@ export class WaylandApp extends EventEmitter {
|
|
|
104
105
|
this.viewporter = null;
|
|
105
106
|
this.fractionalScale = null;
|
|
106
107
|
this.activation = null;
|
|
108
|
+
/** xdg-foreign's exporter: a toplevel's handle for another client to
|
|
109
|
+
* parent to (`exportHandle`); null where the compositor has none */
|
|
110
|
+
this.exporter = null;
|
|
107
111
|
/** the monitors, published into src/screens.js (outputs.js) */
|
|
108
112
|
this.outputs = null;
|
|
109
113
|
/** xdg-decoration, layer-shell, shm: null where the compositor has none */
|
|
@@ -153,6 +157,7 @@ export class WaylandApp extends EventEmitter {
|
|
|
153
157
|
app.viewporter = await conn.bind('wp_viewporter');
|
|
154
158
|
app.fractionalScale = await conn.bind('wp_fractional_scale_manager_v1');
|
|
155
159
|
app.activation = await conn.bind('xdg_activation_v1');
|
|
160
|
+
app.exporter = await conn.bind('zxdg_exporter_v2');
|
|
156
161
|
// The scale session first, so the outputs have a per-monitor map to
|
|
157
162
|
// fill; then the outputs, awaited like the X11 backend's Xinerama tier:
|
|
158
163
|
// an auto-sized window clamps to a monitor synchronously inside
|
|
@@ -304,6 +309,9 @@ export class WaylandApp extends EventEmitter {
|
|
|
304
309
|
}
|
|
305
310
|
|
|
306
311
|
_forgetWindow(win) {
|
|
312
|
+
// before the surface goes: the export is a handle *to* it
|
|
313
|
+
win._exported?.proxy.$.destroy();
|
|
314
|
+
win._exported = null;
|
|
307
315
|
if (this.windows.get(win.wl.surface.id) === win)
|
|
308
316
|
this.windows.delete(win.wl.surface.id);
|
|
309
317
|
const i = this.toplevels.indexOf(win);
|
|
@@ -433,6 +441,32 @@ export class WaylandApp extends EventEmitter {
|
|
|
433
441
|
return new WaylandSurface(this, options);
|
|
434
442
|
}
|
|
435
443
|
|
|
444
|
+
/**
|
|
445
|
+
* The formats a `VideoFrames` sink is cheapest in here
|
|
446
|
+
* (`preferredFormats`, src/videoframes.js): all three, YCbCr first. Every
|
|
447
|
+
* one goes up as textures as it is and is converted by a shader
|
|
448
|
+
* (src/wayland/video.js), and NV12 or I420 is less than half of BGRA's
|
|
449
|
+
* bytes on the way.
|
|
450
|
+
*/
|
|
451
|
+
videoFormats() {
|
|
452
|
+
return ['NV12', 'I420', 'BGRA'];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* A `<video>`'s frame into `surface`, its drawn presentation, on the GPU:
|
|
457
|
+
* planes as textures, the matrix in a fragment shader. False where the
|
|
458
|
+
* frame cannot be — a size past the GPU's textures — and the node
|
|
459
|
+
* converts it in JavaScript instead (src/nodes/video.js).
|
|
460
|
+
*/
|
|
461
|
+
writeVideoFrame(surface, sink, frame) {
|
|
462
|
+
return writeVideoFrame(this, surface, sink, frame);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** The node let `surface` go: its plane textures go too. */
|
|
466
|
+
releaseVideoFrame(surface) {
|
|
467
|
+
releaseVideoFrame(this, surface);
|
|
468
|
+
}
|
|
469
|
+
|
|
436
470
|
/**
|
|
437
471
|
* What the machine can do, as a promise like the X11 and Cocoa answers
|
|
438
472
|
* (src/glbackend.js chains on it): here the answer was settled when the
|
|
@@ -510,6 +544,37 @@ export class WaylandApp extends EventEmitter {
|
|
|
510
544
|
return new WaylandOverlayPane(this, attributes);
|
|
511
545
|
}
|
|
512
546
|
|
|
547
|
+
/**
|
|
548
|
+
* A handle another client can parent a window of its own to — the
|
|
549
|
+
* portal's `parent_window`, spelled `wayland:<handle>` there — or null.
|
|
550
|
+
*
|
|
551
|
+
* xdg-foreign's `export_toplevel`, made once per window on first ask and
|
|
552
|
+
* kept for its lifetime: the handle is valid for as long as the export
|
|
553
|
+
* object lives, and a file dialog opened twice from one window should name
|
|
554
|
+
* it the same way both times. Only an xdg_toplevel can be exported — a
|
|
555
|
+
* popup or a layer surface is a protocol error, so those answer null, as
|
|
556
|
+
* does a compositor without the protocol. The dialog then floats, which the
|
|
557
|
+
* portal allows; what it must never get is an `x11:` handle naming a
|
|
558
|
+
* Wayland window, which is what it got before this existed.
|
|
559
|
+
*/
|
|
560
|
+
exportHandle(win) {
|
|
561
|
+
if (!this.exporter || !win?.wl?.surface || win.isPopup || win.isLayer) {
|
|
562
|
+
return Promise.resolve(null);
|
|
563
|
+
}
|
|
564
|
+
if (win._destroyed) return Promise.resolve(null);
|
|
565
|
+
if (!win._exported) {
|
|
566
|
+
const proxy = this.exporter.$.export_toplevel(win.wl.surface.id);
|
|
567
|
+
const handle = new Promise((resolve) => {
|
|
568
|
+
proxy.on('handle', (value) => resolve(value || null));
|
|
569
|
+
// the answer is one round trip away; a compositor that never sends
|
|
570
|
+
// it is a floating dialog, not a hung one
|
|
571
|
+
setTimeout(() => resolve(null), 1000).unref?.();
|
|
572
|
+
});
|
|
573
|
+
win._exported = { proxy, handle };
|
|
574
|
+
}
|
|
575
|
+
return win._exported.handle;
|
|
576
|
+
}
|
|
577
|
+
|
|
513
578
|
/**
|
|
514
579
|
* Ask the compositor to activate a window. Stacking is the compositor's;
|
|
515
580
|
* xdg-activation lets a client ask with a token from a recent interaction,
|
|
@@ -542,6 +607,27 @@ export class WaylandApp extends EventEmitter {
|
|
|
542
607
|
|
|
543
608
|
cancelAttention() {}
|
|
544
609
|
|
|
610
|
+
/**
|
|
611
|
+
* `useKeyboardState()`'s source on this backend: there is no XKB extension
|
|
612
|
+
* to ask, and the seat has the same answers from `wl_keyboard`
|
|
613
|
+
* (src/keyboardstate.js, `WaylandSeat.keyboardState`).
|
|
614
|
+
*/
|
|
615
|
+
keyboardState() {
|
|
616
|
+
return this.seat?.keyboardState() ?? null;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** Call `fn` when the locks, the group or the keymap change. */
|
|
620
|
+
watchKeyboardState(fn) {
|
|
621
|
+
const seat = this.seat;
|
|
622
|
+
if (!seat) return () => {};
|
|
623
|
+
seat.on('modifiers', fn);
|
|
624
|
+
seat.on('keymap', fn);
|
|
625
|
+
return () => {
|
|
626
|
+
seat.off('modifiers', fn);
|
|
627
|
+
seat.off('keymap', fn);
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
545
631
|
async close() {
|
|
546
632
|
for (const w of [...this.windows.values()]) w.destroy();
|
|
547
633
|
this.windows.clear();
|
package/src/wayland/clipboard.js
CHANGED
|
@@ -67,17 +67,22 @@ class Selection {
|
|
|
67
67
|
/** our own source while we own the selection */
|
|
68
68
|
this.source = null;
|
|
69
69
|
this.ownData = null;
|
|
70
|
-
this._pendingOffers = new Map(); // offer id -> types
|
|
70
|
+
this._pendingOffers = new Map(); // offer id -> { offer, types }
|
|
71
71
|
/** `watch()` handlers, told when the selection changes hands */
|
|
72
72
|
this.watchers = new Set();
|
|
73
73
|
|
|
74
74
|
device.on('data_offer', (offer) => {
|
|
75
75
|
const types = [];
|
|
76
|
-
this._pendingOffers.set(offer.id, types);
|
|
76
|
+
this._pendingOffers.set(offer.id, { offer, types });
|
|
77
77
|
offer.on('offer', (mime) => types.push(mime));
|
|
78
78
|
});
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
// The offer arrives as an id: an `object` argument is a number off the
|
|
80
|
+
// wire, where `data_offer`'s `new_id` was a proxy. The proxy is what a
|
|
81
|
+
// read calls `receive` on, so it is found by the id — keeping the number
|
|
82
|
+
// made every read throw, and nothing another application copied could
|
|
83
|
+
// be pasted here.
|
|
84
|
+
device.on('selection', (offerArg) => {
|
|
85
|
+
const id = offerArg?.id ?? offerArg ?? 0;
|
|
81
86
|
if (this.offer && this.offer.id !== id) {
|
|
82
87
|
try {
|
|
83
88
|
this.offer.$.destroy();
|
|
@@ -85,15 +90,17 @@ class Selection {
|
|
|
85
90
|
/* gone */
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
|
-
|
|
93
|
+
const pending = id ? this._pendingOffers.get(id) : null;
|
|
94
|
+
this._pendingOffers.delete(id);
|
|
95
|
+
const offer = pending?.offer ?? (offerArg?.$ ? offerArg : null);
|
|
96
|
+
if (!offer) {
|
|
89
97
|
this.offer = null;
|
|
90
98
|
this.offerTypes = [];
|
|
91
99
|
this._notify('closed');
|
|
92
100
|
return;
|
|
93
101
|
}
|
|
94
102
|
this.offer = offer;
|
|
95
|
-
this.offerTypes =
|
|
96
|
-
this._pendingOffers.delete(id);
|
|
103
|
+
this.offerTypes = pending?.types ?? [];
|
|
97
104
|
// an offer from our own source means we still own it; anyone else's
|
|
98
105
|
// means we lost it
|
|
99
106
|
if (this.source && !this._isOurs(offer)) this._dropSource();
|
|
@@ -65,6 +65,9 @@ export const PROTOCOLS = [
|
|
|
65
65
|
'cursor-shape-v1',
|
|
66
66
|
'tablet-v2',
|
|
67
67
|
'xdg-activation-v1',
|
|
68
|
+
// a handle another client can parent a dialog to — the portal's
|
|
69
|
+
// `parent_window` (src/portal.js, `portalParentFor`)
|
|
70
|
+
'xdg-foreign-unstable-v2',
|
|
68
71
|
'xdg-decoration-unstable-v1',
|
|
69
72
|
// the older KDE protocol that does the same job, for the compositors that
|
|
70
73
|
// have only that one (ssd.js picks between them)
|
package/src/wayland/context2d.js
CHANGED
|
@@ -67,6 +67,8 @@ const MODE_RRECT = 3;
|
|
|
67
67
|
const MODE_GRADIENT = 4;
|
|
68
68
|
/** A texture read through a projection: `vParams` is (s/w, t/w, 1/w). */
|
|
69
69
|
const MODE_PROJECTIVE = 5;
|
|
70
|
+
/** A pattern: an image laid down from the fragment's device position. */
|
|
71
|
+
const MODE_PATTERN = 6;
|
|
70
72
|
|
|
71
73
|
/**
|
|
72
74
|
* The stencil buffer, shared between two uses: bit 7 is the clip (set where
|
|
@@ -132,6 +134,12 @@ uniform highp vec2 uViewport;
|
|
|
132
134
|
uniform int uGradKind;
|
|
133
135
|
uniform vec4 uGradA;
|
|
134
136
|
uniform vec4 uGradB;
|
|
137
|
+
// a pattern: device position to its pixels (the inverse transform it is
|
|
138
|
+
// filled under, a b c d and then e f), its size, and which axes repeat (1 x,
|
|
139
|
+
// 2 y) and whether its rows run bottom up (4: a render target)
|
|
140
|
+
uniform highp vec4 uPatInv;
|
|
141
|
+
uniform highp vec4 uPatOff;
|
|
142
|
+
uniform int uPatFlags;
|
|
135
143
|
|
|
136
144
|
float sdRoundBox(vec2 p, vec2 b, float r) {
|
|
137
145
|
vec2 q = abs(p) - b + r;
|
|
@@ -167,6 +175,28 @@ void main() {
|
|
|
167
175
|
// Interpolated across the quad in the plane of the screen, s/w, t/w and
|
|
168
176
|
// 1/w are each exact, and so is their quotient: a texture in perspective.
|
|
169
177
|
gl_FragColor = vColor * sampleTex(vParams.xy / vParams.z);
|
|
178
|
+
} else if (uMode == 6) {
|
|
179
|
+
// A pattern, from the fragment's device position as a gradient is, so
|
|
180
|
+
// any shape can be filled with one: back through the transform it was
|
|
181
|
+
// filled under to its own pixels, wrapped along the axes it repeats on
|
|
182
|
+
// and clear off the ones it does not, and read at the centre of the
|
|
183
|
+
// texel it lands in, so a tile drawn at its own size lands pixel for
|
|
184
|
+
// pixel — a grid's dots do not smear across a seam.
|
|
185
|
+
highp vec2 p = vec2(gl_FragCoord.x, uViewport.y - gl_FragCoord.y);
|
|
186
|
+
highp vec2 q = vec2(uPatInv.x * p.x + uPatInv.z * p.y + uPatOff.x,
|
|
187
|
+
uPatInv.y * p.x + uPatInv.w * p.y + uPatOff.y);
|
|
188
|
+
highp vec2 size = uPatOff.zw;
|
|
189
|
+
highp vec2 t = q / size;
|
|
190
|
+
float flags = float(uPatFlags);
|
|
191
|
+
if (mod(flags, 2.0) >= 1.0) t.x = fract(t.x);
|
|
192
|
+
if (mod(floor(flags / 2.0), 2.0) >= 1.0) t.y = fract(t.y);
|
|
193
|
+
if (t.x < 0.0 || t.x >= 1.0 || t.y < 0.0 || t.y >= 1.0) {
|
|
194
|
+
gl_FragColor = vec4(0.0);
|
|
195
|
+
} else {
|
|
196
|
+
t = (floor(t * size) + 0.5) / size;
|
|
197
|
+
if (flags >= 4.0) t.y = 1.0 - t.y;
|
|
198
|
+
gl_FragColor = vColor * sampleTex(t);
|
|
199
|
+
}
|
|
170
200
|
} else {
|
|
171
201
|
// Gradients are evaluated per fragment from its device position, so any
|
|
172
202
|
// shape — a cover quad over a path included — can be filled with one.
|
|
@@ -415,6 +445,43 @@ export function parseFont(font) {
|
|
|
415
445
|
*/
|
|
416
446
|
const fontIds = new WeakMap();
|
|
417
447
|
let nextFontId = 1;
|
|
448
|
+
const REPETITIONS = new Set(['repeat', 'repeat-x', 'repeat-y', 'no-repeat']);
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* A pattern: an image or a surface, and the axes it repeats along. Its
|
|
452
|
+
* texture is looked up as it is filled rather than made here, so a surface
|
|
453
|
+
* drawn on again after the pattern was made fills with what it holds then,
|
|
454
|
+
* as a canvas pattern of a canvas does.
|
|
455
|
+
*/
|
|
456
|
+
class Pattern {
|
|
457
|
+
constructor(source, repetition) {
|
|
458
|
+
this.source = source;
|
|
459
|
+
this.repetition = repetition;
|
|
460
|
+
/** its own transform, under the one it is filled with (`setTransform`) */
|
|
461
|
+
this.m = null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
setTransform(m) {
|
|
465
|
+
this.m = m
|
|
466
|
+
? [m.a ?? 1, m.b ?? 0, m.c ?? 0, m.d ?? 1, m.e ?? 0, m.f ?? 0]
|
|
467
|
+
: null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function invert(m) {
|
|
472
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
473
|
+
if (!det) return null;
|
|
474
|
+
const a = m[3] / det;
|
|
475
|
+
const b = -m[1] / det;
|
|
476
|
+
const c = -m[2] / det;
|
|
477
|
+
const d = m[0] / det;
|
|
478
|
+
return [a, b, c, d, -(a * m[4] + c * m[5]), -(b * m[4] + d * m[5])];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** A paint that is not a colour: drawn from the fragment's position. */
|
|
482
|
+
const isPaint = (style) =>
|
|
483
|
+
style instanceof Gradient || style instanceof Pattern;
|
|
484
|
+
|
|
418
485
|
function fontKey(font) {
|
|
419
486
|
let id = fontIds.get(font);
|
|
420
487
|
if (!id) fontIds.set(font, (id = `f${nextFontId++}`));
|
|
@@ -571,6 +638,9 @@ export class WaylandContext2D {
|
|
|
571
638
|
uGradKind: u('uGradKind'),
|
|
572
639
|
uGradA: u('uGradA'),
|
|
573
640
|
uGradB: u('uGradB'),
|
|
641
|
+
uPatInv: u('uPatInv'),
|
|
642
|
+
uPatOff: u('uPatOff'),
|
|
643
|
+
uPatFlags: u('uPatFlags'),
|
|
574
644
|
};
|
|
575
645
|
shared.buffer = gl.createBuffer();
|
|
576
646
|
gl.bindBuffer(gl.ARRAY_BUFFER, shared.buffer);
|
|
@@ -847,9 +917,23 @@ export class WaylandContext2D {
|
|
|
847
917
|
return new Gradient('radial', [x0, y0, r0, x1, y1, r1]);
|
|
848
918
|
}
|
|
849
919
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
920
|
+
/**
|
|
921
|
+
* A pattern of `image` — an ImageData, an ntk `Image`, a surface or a
|
|
922
|
+
* render target. Null for one with nothing in it, as canvas answers for an
|
|
923
|
+
* image it cannot use; `repetition` is canvas's, null and `''` meaning
|
|
924
|
+
* `'repeat'`, and anything else a `SyntaxError`.
|
|
925
|
+
*/
|
|
926
|
+
createPattern(image, repetition) {
|
|
927
|
+
const rep =
|
|
928
|
+
repetition == null || repetition === '' ? 'repeat' : String(repetition);
|
|
929
|
+
if (!REPETITIONS.has(rep)) {
|
|
930
|
+
throw new SyntaxError(
|
|
931
|
+
`createPattern: ${JSON.stringify(rep)} is not a repetition — ` +
|
|
932
|
+
"expected 'repeat', 'repeat-x', 'repeat-y' or 'no-repeat'.",
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
if (!((image?.width | 0) > 0 && (image?.height | 0) > 0)) return null;
|
|
936
|
+
return new Pattern(image, rep);
|
|
853
937
|
}
|
|
854
938
|
|
|
855
939
|
// ---- paths ------------------------------------------------------------
|
|
@@ -1088,7 +1172,7 @@ export class WaylandContext2D {
|
|
|
1088
1172
|
|
|
1089
1173
|
_rect(x, y, w, h, radius, style, strokeWidth) {
|
|
1090
1174
|
if (!(w > 0 && h > 0)) return;
|
|
1091
|
-
if (style
|
|
1175
|
+
if (isPaint(style)) {
|
|
1092
1176
|
if (radius <= 0 && strokeWidth === 0)
|
|
1093
1177
|
return this._gradientQuad(x, y, w, h, style);
|
|
1094
1178
|
// rounded/stroked gradient: stencil the shape, cover with the gradient
|
|
@@ -1226,8 +1310,40 @@ export class WaylandContext2D {
|
|
|
1226
1310
|
}
|
|
1227
1311
|
}
|
|
1228
1312
|
|
|
1313
|
+
/** Bind a gradient or a pattern for the next quads; false where there is
|
|
1314
|
+
* nothing to draw with — a pattern of an image that has gone. */
|
|
1315
|
+
_usePaint(paint) {
|
|
1316
|
+
if (paint instanceof Pattern) return this._usePattern(paint);
|
|
1317
|
+
this._useGradient(paint);
|
|
1318
|
+
return true;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/** Bind a pattern's texture, and the way from a device pixel to one of
|
|
1322
|
+
* its own, as the current batch's uniforms. */
|
|
1323
|
+
_usePattern(pat) {
|
|
1324
|
+
const src = this._textureFor(pat.source);
|
|
1325
|
+
if (!src) return false;
|
|
1326
|
+
const inv = invert(pat.m ? mul(this._m, pat.m) : this._m);
|
|
1327
|
+
if (!inv) return false;
|
|
1328
|
+
const gl = this.gl;
|
|
1329
|
+
this._flush();
|
|
1330
|
+
this._mode = MODE_PATTERN;
|
|
1331
|
+
this._texture = src.tex;
|
|
1332
|
+
this._swizzle = src.swizzle;
|
|
1333
|
+
const rep = pat.repetition;
|
|
1334
|
+
const flags =
|
|
1335
|
+
(rep === 'repeat' || rep === 'repeat-x' ? 1 : 0) +
|
|
1336
|
+
(rep === 'repeat' || rep === 'repeat-y' ? 2 : 0) +
|
|
1337
|
+
(src.flipY ? 4 : 0);
|
|
1338
|
+
gl.useProgram(this._program);
|
|
1339
|
+
gl.uniform4f(this._loc.uPatInv, inv[0], inv[1], inv[2], inv[3]);
|
|
1340
|
+
gl.uniform4f(this._loc.uPatOff, inv[4], inv[5], src.w, src.h);
|
|
1341
|
+
gl.uniform1i(this._loc.uPatFlags, flags);
|
|
1342
|
+
return true;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1229
1345
|
_gradientQuad(x, y, w, h, grad) {
|
|
1230
|
-
this.
|
|
1346
|
+
if (!this._usePaint(grad)) return;
|
|
1231
1347
|
const m = this._m;
|
|
1232
1348
|
const alpha = this.globalAlpha;
|
|
1233
1349
|
this._quad(
|
|
@@ -1277,7 +1393,7 @@ export class WaylandContext2D {
|
|
|
1277
1393
|
// only once the device has been claimed.
|
|
1278
1394
|
this._claim();
|
|
1279
1395
|
let color = null;
|
|
1280
|
-
if (!(style
|
|
1396
|
+
if (!isPaint(style)) {
|
|
1281
1397
|
color = this._color(style);
|
|
1282
1398
|
if (color[3] === 0) return;
|
|
1283
1399
|
if (this._fillCoverage(polys, rule, bounds, color)) return;
|
|
@@ -1297,15 +1413,18 @@ export class WaylandContext2D {
|
|
|
1297
1413
|
const { x, y, w, h } = bounds;
|
|
1298
1414
|
const saved = this._m;
|
|
1299
1415
|
this._m = IDENTITY;
|
|
1300
|
-
if (style
|
|
1301
|
-
this._m = saved; // gradient endpoints
|
|
1302
|
-
this.
|
|
1416
|
+
if (isPaint(style)) {
|
|
1417
|
+
this._m = saved; // a gradient's endpoints, a pattern's origin: user space
|
|
1418
|
+
const painted = this._usePaint(style);
|
|
1303
1419
|
this._m = IDENTITY;
|
|
1304
1420
|
const a = this.globalAlpha;
|
|
1421
|
+
// a pattern with nothing to draw with still covers, clear, so the
|
|
1422
|
+
// counts the winding pass left are zeroed as the cover always does
|
|
1423
|
+
if (!painted) this._setMode(MODE_SOLID, null, SWIZZLE_RGBA);
|
|
1305
1424
|
this._quad(
|
|
1306
1425
|
[x, y, x + w, y, x + w, y + h, x, y + h],
|
|
1307
1426
|
UV_UNIT,
|
|
1308
|
-
[a, a, a, a],
|
|
1427
|
+
painted ? [a, a, a, a] : CLEAR,
|
|
1309
1428
|
ZERO4,
|
|
1310
1429
|
);
|
|
1311
1430
|
} else {
|
|
@@ -127,5 +127,11 @@
|
|
|
127
127
|
"keyboard-shortcuts-inhibit-unstable-v1": [
|
|
128
128
|
"zwp_keyboard_shortcuts_inhibit_manager_v1",
|
|
129
129
|
"zwp_keyboard_shortcuts_inhibitor_v1"
|
|
130
|
+
],
|
|
131
|
+
"xdg-foreign-unstable-v2": [
|
|
132
|
+
"zxdg_exporter_v2",
|
|
133
|
+
"zxdg_importer_v2",
|
|
134
|
+
"zxdg_exported_v2",
|
|
135
|
+
"zxdg_imported_v2"
|
|
130
136
|
]
|
|
131
137
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"name":"zxdg_exporter_v2","version":1,"requests":[{"name":"destroy","type":"destructor","args":[]},{"name":"export_toplevel","args":[{"name":"id","type":"new_id","interface":"zxdg_exported_v2"},{"name":"surface","type":"object","interface":"wl_surface"}]}],"events":[],"enums":{"error":{"entries":[{"name":"invalid_surface","value":0}]}}},{"name":"zxdg_importer_v2","version":1,"requests":[{"name":"destroy","type":"destructor","args":[]},{"name":"import_toplevel","args":[{"name":"id","type":"new_id","interface":"zxdg_imported_v2"},{"name":"handle","type":"string"}]}],"events":[],"enums":{}},{"name":"zxdg_exported_v2","version":1,"requests":[{"name":"destroy","type":"destructor","args":[]}],"events":[{"name":"handle","args":[{"name":"handle","type":"string"}]}],"enums":{}},{"name":"zxdg_imported_v2","version":1,"requests":[{"name":"destroy","type":"destructor","args":[]},{"name":"set_parent_of","args":[{"name":"surface","type":"object","interface":"wl_surface"}]}],"events":[{"name":"destroyed","args":[]}],"enums":{"error":{"entries":[{"name":"invalid_surface","value":0}]}}}]
|