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/components/Menu.js
CHANGED
|
@@ -984,7 +984,9 @@ function useMenuAccelerators(
|
|
|
984
984
|
*
|
|
985
985
|
* The wrapper is focusable and takes focus when the menu opens, because the
|
|
986
986
|
* popup is override-redirect and never gets focus itself — the same trick
|
|
987
|
-
* `Select` uses to keep arrow keys working while its menu is open.
|
|
987
|
+
* `Select` uses to keep arrow keys working while its menu is open. It is not
|
|
988
|
+
* a Tab stop: it has no role or name, so Tab landing on it would announce
|
|
989
|
+
* nothing, and the controls inside are stops of their own.
|
|
988
990
|
*/
|
|
989
991
|
export function ContextMenu({
|
|
990
992
|
items = [],
|
|
@@ -1087,7 +1089,10 @@ export function ContextMenu({
|
|
|
1087
1089
|
'box',
|
|
1088
1090
|
{
|
|
1089
1091
|
ref,
|
|
1090
|
-
focusable
|
|
1092
|
+
// focusable by a press and by `focus()` above, but out of the Tab
|
|
1093
|
+
// cycle: a stop here is a nameless "filler" around content whose own
|
|
1094
|
+
// controls are already reachable
|
|
1095
|
+
tabIndex: -1,
|
|
1091
1096
|
onMouseDown: (ev) => {
|
|
1092
1097
|
if (ev.button !== 3 && rect) close();
|
|
1093
1098
|
},
|
package/src/components/Switch.js
CHANGED
|
@@ -98,7 +98,7 @@ export function Switch({
|
|
|
98
98
|
);
|
|
99
99
|
|
|
100
100
|
// NSSwitch's own pixels, on/off as two cached bezels. The knob slide is
|
|
101
|
-
// the fidelity this loses for now (
|
|
101
|
+
// the fidelity this loses for now (design/macos.md flags the switch as the
|
|
102
102
|
// one control worth revisiting if that disappoints); the press keeps its
|
|
103
103
|
// answer as the wash, since NSSwitch renders no pressed state offscreen.
|
|
104
104
|
if (nativeControls) {
|
package/src/components/native.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The native half of the widget set (
|
|
1
|
+
// The native half of the widget set (design/macos.md §Native controls).
|
|
2
2
|
//
|
|
3
3
|
// House rule: **default native, seam out.** On a backend that renders the
|
|
4
4
|
// platform's own control bezels — today the Cocoa backend — the core
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
//
|
|
9
9
|
// `appearance` the light/dark, accent, contrast and reduced-motion ladder
|
|
10
10
|
// (src/appearance.js) — the settings portal, then macOS, then
|
|
11
|
-
// XSETTINGS
|
|
11
|
+
// XSETTINGS — and, on the Wayland backend, the interaction
|
|
12
|
+
// settings read off the same portal (src/desktopsettings.js)
|
|
12
13
|
// `a11y` the AT-SPI bridge, started from `createRoot()`
|
|
13
14
|
// (src/a11y.js, docs/accessibility.md)
|
|
14
15
|
// `globalMenu` a `MenuBar` handing its menu to the panel instead of drawing
|
package/src/desktopsettings.js
CHANGED
|
@@ -11,17 +11,30 @@
|
|
|
11
11
|
//
|
|
12
12
|
// `appearance.js` climbs three sources for colour scheme and accent, because
|
|
13
13
|
// those are answered by three different things and the portal is the modern
|
|
14
|
-
// one. Nothing here has a portal equivalent:
|
|
15
|
-
// four keys and none of them is a timing.
|
|
16
|
-
//
|
|
17
|
-
// XQuartz — the defaults
|
|
14
|
+
// one. Nothing here has a *standard* portal equivalent:
|
|
15
|
+
// `org.freedesktop.appearance` has four keys and none of them is a timing.
|
|
16
|
+
// On X11, XSETTINGS is the whole story, and where there is no settings
|
|
17
|
+
// daemon — a bare `startx`, most window managers, XQuartz — the defaults
|
|
18
|
+
// below stand.
|
|
18
19
|
//
|
|
19
|
-
// So this reads the map `xsettings.js` already maintains, rather than
|
|
20
|
-
// standing up a source of its own.
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
20
|
+
// So on X11 this reads the map `xsettings.js` already maintains, rather than
|
|
21
|
+
// standing up a source of its own. There are two exceptions, one per backend
|
|
22
|
+
// that has no X server to ask:
|
|
23
|
+
//
|
|
24
|
+
// - **macOS** answers exactly one of these — "reduce motion", through
|
|
25
|
+
// `NSWorkspace` — so the Cocoa app exposes that behind a two-method seam
|
|
26
|
+
// (`accessibilityDisplayOptions`, `watchAccessibilityDisplay`) and this
|
|
27
|
+
// module reads it the way it reads the map: synchronously, cached, live.
|
|
28
|
+
//
|
|
29
|
+
// - **The Wayland backend** has no XSETTINGS either, but the settings portal
|
|
30
|
+
// is not limited to `org.freedesktop.appearance`: the GNOME and GTK portal
|
|
31
|
+
// backends pass whole GSettings schemas through it, and
|
|
32
|
+
// `org.gnome.desktop.interface` and `org.gnome.desktop.peripherals.mouse`
|
|
33
|
+
// are exactly what gsd-xsettings translates into the XSETTINGS keys this
|
|
34
|
+
// reads on X11. Same numbers, one hop closer to the source. Read once at
|
|
35
|
+
// startup and again on `SettingChanged`; a desktop whose portal does not
|
|
36
|
+
// carry them (KDE, a bare compositor) keeps the defaults, as a bare
|
|
37
|
+
// `startx` does.
|
|
25
38
|
//
|
|
26
39
|
// ## Two of these are read synchronously, which is what shapes the module
|
|
27
40
|
//
|
|
@@ -34,8 +47,26 @@
|
|
|
34
47
|
// desktop's cadence from the next focus onward; that is the one seam, and it
|
|
35
48
|
// is worth more than the round trips on every app's startup path.
|
|
36
49
|
|
|
50
|
+
import { sessionBus } from './bus.js';
|
|
51
|
+
import { desktopIntegrationEnabled } from './desktopintegration.js';
|
|
52
|
+
import { PORTAL_NAME, PORTAL_PATH } from './portal.js';
|
|
37
53
|
import { beginXSettings, watchXSettings, xsettings } from './xsettings.js';
|
|
38
54
|
|
|
55
|
+
const SETTINGS_IFACE = 'org.freedesktop.portal.Settings';
|
|
56
|
+
const INTERFACE_NS = 'org.gnome.desktop.interface';
|
|
57
|
+
const MOUSE_NS = 'org.gnome.desktop.peripherals.mouse';
|
|
58
|
+
|
|
59
|
+
/** The keys a `SettingChanged` has to name for a re-read to be worth it —
|
|
60
|
+
* `org.gnome.desktop.interface` also carries the clock format, the font
|
|
61
|
+
* and the icon theme, and none of those is a reason to re-render. */
|
|
62
|
+
const WATCHED = new Set([
|
|
63
|
+
`${INTERFACE_NS}/cursor-blink`,
|
|
64
|
+
`${INTERFACE_NS}/cursor-blink-time`,
|
|
65
|
+
`${INTERFACE_NS}/enable-animations`,
|
|
66
|
+
`${MOUSE_NS}/double-click`,
|
|
67
|
+
`${MOUSE_NS}/drag-threshold`,
|
|
68
|
+
]);
|
|
69
|
+
|
|
39
70
|
/**
|
|
40
71
|
* What is true with no settings daemon to ask.
|
|
41
72
|
*
|
|
@@ -71,24 +102,15 @@ const sessions = new WeakMap();
|
|
|
71
102
|
* export a key with a string value or a nonsense one, and a caret with a
|
|
72
103
|
* blink period of `'fast'` would never come back on. */
|
|
73
104
|
function positive(map, key) {
|
|
74
|
-
|
|
105
|
+
return positiveValue(map?.get(key));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function positiveValue(value) {
|
|
75
109
|
return typeof value === 'number' && Number.isFinite(value) && value > 0
|
|
76
110
|
? value
|
|
77
111
|
: undefined;
|
|
78
112
|
}
|
|
79
113
|
|
|
80
|
-
/**
|
|
81
|
-
* An XSETTINGS map → the settings. Pure, and exported for the test: the
|
|
82
|
-
* in-process X server has no settings daemon, so this is the part of the
|
|
83
|
-
* path worth pinning without one.
|
|
84
|
-
*
|
|
85
|
-
* **`Net/CursorBlinkTime` is a full cycle**, on and off together — that is
|
|
86
|
-
* what GTK means by `gtk-cursor-blink-time` and what every daemon writes —
|
|
87
|
-
* and `caretBlinkMs` is how long the caret stays in *each* state, because
|
|
88
|
-
* that is the number that goes into a timer. Halving it is the whole
|
|
89
|
-
* conversion, and forgetting to is a caret that blinks at half speed and
|
|
90
|
-
* looks broken rather than wrong.
|
|
91
|
-
*/
|
|
92
114
|
/**
|
|
93
115
|
* macOS's accessibility display options → the settings. Only `animations`
|
|
94
116
|
* comes from the system there: it is the one of these macOS publishes at all
|
|
@@ -105,6 +127,18 @@ export function fromMacOS(options) {
|
|
|
105
127
|
});
|
|
106
128
|
}
|
|
107
129
|
|
|
130
|
+
/**
|
|
131
|
+
* An XSETTINGS map → the settings. Pure, and exported for the test: the
|
|
132
|
+
* in-process X server has no settings daemon, so this is the part of the
|
|
133
|
+
* path worth pinning without one.
|
|
134
|
+
*
|
|
135
|
+
* **`Net/CursorBlinkTime` is a full cycle**, on and off together — that is
|
|
136
|
+
* what GTK means by `gtk-cursor-blink-time` and what every daemon writes —
|
|
137
|
+
* and `caretBlinkMs` is how long the caret stays in *each* state, because
|
|
138
|
+
* that is the number that goes into a timer. Halving it is the whole
|
|
139
|
+
* conversion, and forgetting to is a caret that blinks at half speed and
|
|
140
|
+
* looks broken rather than wrong.
|
|
141
|
+
*/
|
|
108
142
|
export function fromXSettings(map) {
|
|
109
143
|
if (!map) return DEFAULTS;
|
|
110
144
|
const cycle = positive(map, 'Net/CursorBlinkTime');
|
|
@@ -134,6 +168,39 @@ export function fromXSettings(map) {
|
|
|
134
168
|
});
|
|
135
169
|
}
|
|
136
170
|
|
|
171
|
+
/**
|
|
172
|
+
* The settings portal's GNOME namespaces → the settings. Pure, and exported
|
|
173
|
+
* for the test. `all` is a `ReadAll` reply, namespace → key → value.
|
|
174
|
+
*
|
|
175
|
+
* The same keys gsd-xsettings turns into the XSETTINGS ones above, so the
|
|
176
|
+
* same rules: `cursor-blink-time` is a full cycle and is halved, only an
|
|
177
|
+
* explicit `false` turns blinking or animation off, and a value of the wrong
|
|
178
|
+
* type is no answer. GNOME has no double-click *distance*; GTK's is a
|
|
179
|
+
* constant there, so the default stands.
|
|
180
|
+
*
|
|
181
|
+
* `null` when neither namespace came back — a portal that does not pass
|
|
182
|
+
* GSettings through has not answered, and the source stays null.
|
|
183
|
+
*/
|
|
184
|
+
export function fromGnomePortal(all) {
|
|
185
|
+
const iface = all?.[INTERFACE_NS];
|
|
186
|
+
const mouse = all?.[MOUSE_NS];
|
|
187
|
+
if (!iface && !mouse) return null;
|
|
188
|
+
const cycle = positiveValue(iface?.['cursor-blink-time']);
|
|
189
|
+
return Object.freeze({
|
|
190
|
+
...DEFAULTS,
|
|
191
|
+
caretBlink: iface?.['cursor-blink'] !== false,
|
|
192
|
+
caretBlinkMs: cycle
|
|
193
|
+
? Math.max(1, Math.round(cycle / 2))
|
|
194
|
+
: DEFAULTS.caretBlinkMs,
|
|
195
|
+
doubleClickMs:
|
|
196
|
+
positiveValue(mouse?.['double-click']) ?? DEFAULTS.doubleClickMs,
|
|
197
|
+
dragThreshold:
|
|
198
|
+
positiveValue(mouse?.['drag-threshold']) ?? DEFAULTS.dragThreshold,
|
|
199
|
+
animations: iface?.['enable-animations'] !== false,
|
|
200
|
+
source: 'portal',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
137
204
|
/**
|
|
138
205
|
* The settings for a connection, **synchronously**. Always a complete answer:
|
|
139
206
|
* the defaults before the daemon has been read, and on a display with none.
|
|
@@ -148,7 +215,9 @@ export function desktopSettings(app) {
|
|
|
148
215
|
if (!session.snapshot) {
|
|
149
216
|
session.snapshot = session.macos
|
|
150
217
|
? fromMacOS(app.accessibilityDisplayOptions())
|
|
151
|
-
:
|
|
218
|
+
: session.wayland
|
|
219
|
+
? (fromGnomePortal(session.portal) ?? DEFAULTS)
|
|
220
|
+
: fromXSettings(xsettings(app));
|
|
152
221
|
}
|
|
153
222
|
return session.snapshot;
|
|
154
223
|
}
|
|
@@ -173,6 +242,20 @@ export function beginDesktopSettings(app) {
|
|
|
173
242
|
});
|
|
174
243
|
return;
|
|
175
244
|
}
|
|
245
|
+
// The Wayland backend: no X server, so no XSETTINGS — the portal instead,
|
|
246
|
+
// behind the same switch as the appearance ladder that reads the portal
|
|
247
|
+
// too. `createRoot({ desktop: false })` keeps the defaults, which is what a
|
|
248
|
+
// test or an embedder that asked for determinism wants.
|
|
249
|
+
if (app?.backend === 'wayland') {
|
|
250
|
+
session.wayland = true;
|
|
251
|
+
if (desktopIntegrationEnabled('appearance')) {
|
|
252
|
+
portalSettings(app, session).catch(() => {
|
|
253
|
+
// no bus, no portal, a portal without the GNOME namespaces: the
|
|
254
|
+
// defaults stand, which is what they are for
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
176
259
|
beginXSettings(app).then(
|
|
177
260
|
() => {
|
|
178
261
|
if (!sessions.has(app)) return;
|
|
@@ -190,6 +273,92 @@ export function beginDesktopSettings(app) {
|
|
|
190
273
|
);
|
|
191
274
|
}
|
|
192
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Read the GNOME namespaces off the settings portal, and again whenever one
|
|
278
|
+
* of the keys this cares about changes.
|
|
279
|
+
*
|
|
280
|
+
* The order is `appearance.js`'s portal rung, for the same reasons: the
|
|
281
|
+
* match rule and the handler go in **before** the first read, so a change
|
|
282
|
+
* that lands while it is in flight is not lost; and reads are sequenced, so
|
|
283
|
+
* a slow reply never publishes over a newer one. The bus ref is released as
|
|
284
|
+
* soon as the first read settles — the subscription outlives it, and holding
|
|
285
|
+
* it would keep the process alive for as long as the app cared how fast its
|
|
286
|
+
* caret blinks.
|
|
287
|
+
*/
|
|
288
|
+
async function portalSettings(app, session) {
|
|
289
|
+
const ref = await sessionBus();
|
|
290
|
+
if (!ref) return;
|
|
291
|
+
const { bus } = ref;
|
|
292
|
+
let sub = null;
|
|
293
|
+
let handler = null;
|
|
294
|
+
const key = bus.mangle(PORTAL_PATH, SETTINGS_IFACE, 'SettingChanged');
|
|
295
|
+
const stop = () => {
|
|
296
|
+
if (handler) bus.signals.removeListener(key, handler);
|
|
297
|
+
handler = null;
|
|
298
|
+
sub?.remove().catch(() => {});
|
|
299
|
+
sub = null;
|
|
300
|
+
};
|
|
301
|
+
try {
|
|
302
|
+
sub = await bus.watch(
|
|
303
|
+
`type='signal',sender='${PORTAL_NAME}',` +
|
|
304
|
+
`interface='${SETTINGS_IFACE}',member='SettingChanged'`,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
let started = 0;
|
|
308
|
+
let published = 0;
|
|
309
|
+
const refresh = async () => {
|
|
310
|
+
const mine = ++started;
|
|
311
|
+
const all = await bus.invoke(
|
|
312
|
+
{
|
|
313
|
+
destination: PORTAL_NAME,
|
|
314
|
+
path: PORTAL_PATH,
|
|
315
|
+
interface: SETTINGS_IFACE,
|
|
316
|
+
member: 'ReadAll',
|
|
317
|
+
signature: 'as',
|
|
318
|
+
body: [[INTERFACE_NS, MOUSE_NS]],
|
|
319
|
+
},
|
|
320
|
+
{ timeout: 5_000 },
|
|
321
|
+
);
|
|
322
|
+
if (mine <= published || sessions.get(app) !== session) return;
|
|
323
|
+
published = mine;
|
|
324
|
+
const next = fromGnomePortal(all);
|
|
325
|
+
if (!next) return;
|
|
326
|
+
const before = desktopSettings(app);
|
|
327
|
+
session.portal = all;
|
|
328
|
+
session.snapshot = null;
|
|
329
|
+
// A re-read that changed nothing this reads notifies nobody: the hook
|
|
330
|
+
// re-renders on identity, and so would every subscriber.
|
|
331
|
+
if (!sameSettings(before, desktopSettings(app))) notify(session);
|
|
332
|
+
else session.snapshot = before;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
handler = ([namespace, name]) => {
|
|
336
|
+
if (!WATCHED.has(`${namespace}/${name}`)) return;
|
|
337
|
+
refresh().catch(() => {
|
|
338
|
+
// the portal went away mid-session; the last answer stands
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
bus.signals.on(key, handler);
|
|
342
|
+
|
|
343
|
+
// Torn down while the bus was connecting: undo, and publish nothing.
|
|
344
|
+
if (sessions.get(app) !== session) return stop();
|
|
345
|
+
session.stop = stop;
|
|
346
|
+
await refresh();
|
|
347
|
+
} catch (err) {
|
|
348
|
+
stop();
|
|
349
|
+
throw err;
|
|
350
|
+
} finally {
|
|
351
|
+
await ref.release();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function sameSettings(a, b) {
|
|
356
|
+
for (const key of Object.keys(DEFAULTS)) {
|
|
357
|
+
if (a[key] !== b[key]) return false;
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
193
362
|
function notify(session) {
|
|
194
363
|
for (const fn of [...session.listeners]) {
|
|
195
364
|
try {
|
package/src/dnd.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// XDND drag and drop, the drop-target half: react-x11 windows accept drags
|
|
2
2
|
// from other X11 applications (file managers, editors, browsers).
|
|
3
3
|
//
|
|
4
|
-
// Design:
|
|
4
|
+
// Design: design/architecture/drag-and-drop.md; protocol: XDND version 5.
|
|
5
5
|
// The advertisement (`XdndAware`) is a 4-byte property written
|
|
6
6
|
// unconditionally on every top-level window at realize time — it creates no
|
|
7
7
|
// X windows, so it is orthogonal to the windowed/windowless split.
|
|
@@ -1080,7 +1080,7 @@ export class DropSession {
|
|
|
1080
1080
|
// ---------------------------------------------------------------------------
|
|
1081
1081
|
// The drag source.
|
|
1082
1082
|
//
|
|
1083
|
-
// One drag session, two transports (
|
|
1083
|
+
// One drag session, two transports (design/architecture/drag-and-drop.md §6.3):
|
|
1084
1084
|
// while the pointer is over this process's own windows the drag is local —
|
|
1085
1085
|
// no wire, live JS payloads, the DropSession above delivering to the same
|
|
1086
1086
|
// handlers with `e.source === 'internal'`. The moment it leaves them, the
|
package/src/filedialog.js
CHANGED
|
@@ -46,14 +46,14 @@ import {
|
|
|
46
46
|
RESPONSE_CANCELLED,
|
|
47
47
|
RESPONSE_OK,
|
|
48
48
|
hasService,
|
|
49
|
-
parentWindowHandle,
|
|
50
49
|
pathBytes,
|
|
50
|
+
portalParentFor,
|
|
51
51
|
portalRequest,
|
|
52
52
|
variant,
|
|
53
53
|
} from './portal.js';
|
|
54
54
|
import { sessionBus } from './bus.js';
|
|
55
55
|
import { liveApps } from './trace-registry.js';
|
|
56
|
-
import {
|
|
56
|
+
import { windowOf } from './windowid.js';
|
|
57
57
|
|
|
58
58
|
const FILE_CHOOSER = 'org.freedesktop.portal.FileChooser';
|
|
59
59
|
|
|
@@ -149,7 +149,7 @@ async function portalDialog(kind, opts, ref) {
|
|
|
149
149
|
const { response, results } = await portalRequest(ref, {
|
|
150
150
|
iface: FILE_CHOOSER,
|
|
151
151
|
member,
|
|
152
|
-
parentWindow:
|
|
152
|
+
parentWindow: await portalParentFor(opts.parentWindow),
|
|
153
153
|
title: opts.title ?? defaultTitle(kind),
|
|
154
154
|
options: await portalOptions(opts, {
|
|
155
155
|
save: kind === 'save',
|
package/src/glnodes.js
CHANGED
|
@@ -95,7 +95,7 @@ export function glxConfig(app, spec) {
|
|
|
95
95
|
process.platform === 'win32'
|
|
96
96
|
? 'react-x11: <glarea> is not built on the win32 backend yet — it ' +
|
|
97
97
|
'needs ANGLE (EGL and GLES over Direct3D 11), which ' +
|
|
98
|
-
'
|
|
98
|
+
'design/windows.md plans as an optional dependency the way ' +
|
|
99
99
|
'x11-dri is on X11. Everything else on this backend works ' +
|
|
100
100
|
'without it; for GL content today, use the X11 backend.'
|
|
101
101
|
: 'react-x11: <glarea> needs ntk >= 3.6.0 (app.chooseGLConfig)',
|
package/src/grid.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// CSS grid, built in (docs/styling.md "Grid"; the design record is
|
|
2
|
-
//
|
|
2
|
+
// design/architecture/grid-layout.md). A box with `display: 'grid'` — or
|
|
3
3
|
// `layout: 'grid'`, the same request under the name every layout goes by —
|
|
4
4
|
// arranges its children by CSS Grid's placement and track sizing algorithms
|
|
5
5
|
// (css-grid-2 §8.5 and §12), inside the pass that lays the window out. It is
|
package/src/keyboardstate.js
CHANGED
|
@@ -32,6 +32,18 @@
|
|
|
32
32
|
// it answers atoms that have to be interned back one round trip each, and
|
|
33
33
|
// what it returns is a description like `English (US)` where an indicator in
|
|
34
34
|
// a status bar wants `us`.
|
|
35
|
+
//
|
|
36
|
+
// ## A backend with no X server
|
|
37
|
+
//
|
|
38
|
+
// The native Wayland backend has no XKB extension to ask, but its seat holds
|
|
39
|
+
// the same three answers: the locked modifiers and the group from
|
|
40
|
+
// `wl_keyboard.modifiers`, and the layout codes from the name of the keymap
|
|
41
|
+
// the compositor sent (src/wayland/xkb.js, `layoutsFromKeymap`). It hands
|
|
42
|
+
// them over through a two-method seam — `keyboardState()` and
|
|
43
|
+
// `watchKeyboardState(fn)` — and this module publishes what it says the way
|
|
44
|
+
// it publishes a StateNotify. The one difference is the protocol's:
|
|
45
|
+
// a compositor tells only the focused client, so the locks are as of the last
|
|
46
|
+
// time this app had the keyboard.
|
|
35
47
|
|
|
36
48
|
import { requireExtension } from './extensions.js';
|
|
37
49
|
|
|
@@ -71,6 +83,7 @@ class KeyboardSession {
|
|
|
71
83
|
this.armed = false;
|
|
72
84
|
this.stopped = false;
|
|
73
85
|
this._handler = null;
|
|
86
|
+
this._unwatch = null;
|
|
74
87
|
}
|
|
75
88
|
|
|
76
89
|
publish(values) {
|
|
@@ -106,6 +119,8 @@ class KeyboardSession {
|
|
|
106
119
|
|
|
107
120
|
stop() {
|
|
108
121
|
this.stopped = true;
|
|
122
|
+
this._unwatch?.();
|
|
123
|
+
this._unwatch = null;
|
|
109
124
|
if (this._handler && this.app?.X?.off) {
|
|
110
125
|
try {
|
|
111
126
|
this.app.X.off('event', this._handler);
|
|
@@ -157,6 +172,19 @@ async function arm(session) {
|
|
|
157
172
|
if (session.armed) return;
|
|
158
173
|
session.armed = true;
|
|
159
174
|
const app = session.app;
|
|
175
|
+
|
|
176
|
+
// A backend that answers for itself (the Wayland seat): subscribe, then
|
|
177
|
+
// read, for the same reason the XKB path below does.
|
|
178
|
+
if (typeof app?.watchKeyboardState === 'function') {
|
|
179
|
+
const read = () => {
|
|
180
|
+
const state = !session.stopped && app.keyboardState?.();
|
|
181
|
+
if (state) session.publish(state);
|
|
182
|
+
};
|
|
183
|
+
session._unwatch = app.watchKeyboardState(read);
|
|
184
|
+
read();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
160
188
|
const xkb = await requireExtension(app, 'xkb');
|
|
161
189
|
if (!xkb || session.stopped) return;
|
|
162
190
|
|
package/src/layouts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Layout algorithms and positions from outside the renderer — the two seams
|
|
2
|
-
//
|
|
2
|
+
// design/architecture/custom-layout.md is the design record for.
|
|
3
3
|
//
|
|
4
4
|
// A **layout** arranges a box's children. It is asked how big the box is for
|
|
5
5
|
// the room on offer, and then where each child goes in the room the box got.
|
package/src/node.d.ts
CHANGED
|
@@ -39,12 +39,13 @@ export interface ImageDataLike {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* The 2d context a node paints into — the canvas-shaped subset **
|
|
43
|
-
*
|
|
44
|
-
* `BackendContext2D` over CoreGraphics
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
42
|
+
* The 2d context a node paints into — the canvas-shaped subset **every**
|
|
43
|
+
* backend implements: ntk's `RenderingContext2D` over XRender on X11, the
|
|
44
|
+
* Wayland backend's GLES rasterizer, and `BackendContext2D` over CoreGraphics
|
|
45
|
+
* on macOS and Direct2D on Windows. Declared as the contract an element may
|
|
46
|
+
* rely on rather than as any one class: what is here is on all of them, and
|
|
47
|
+
* a member some backends have and others do not is optional here or
|
|
48
|
+
* absent. Coordinates are device pixels in the owning window's
|
|
48
49
|
* space — `abs`, `contentBox()` — unless a transform says otherwise.
|
|
49
50
|
* Anything ntk documents beyond this is reachable at runtime and is
|
|
50
51
|
* ntk's to change.
|
|
@@ -67,10 +68,9 @@ export interface Context2D {
|
|
|
67
68
|
shadowOffsetY: number;
|
|
68
69
|
shadowColor: string;
|
|
69
70
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* `'globalCompositeOperation' in ctx`.
|
|
71
|
+
* The compositing operator — `'source-over'` (the default), `'copy'`,
|
|
72
|
+
* and the rest ntk documents on X11. A native context (macOS, Windows)
|
|
73
|
+
* takes only the modes its bridge draws: assign and read back to know.
|
|
74
74
|
*/
|
|
75
75
|
globalCompositeOperation?: string;
|
|
76
76
|
/**
|
package/src/nodes/animation.js
CHANGED
|
@@ -310,7 +310,7 @@ export class NodeAnimation {
|
|
|
310
310
|
// loop-stop rule and `sameAnimation` all see it — but it contributes no
|
|
311
311
|
// value to the style, is skipped by the tick, and keeps the node out of the
|
|
312
312
|
// window's animating set. The X11 path has none of these hooks and is
|
|
313
|
-
// byte-identical (
|
|
313
|
+
// byte-identical (design/architecture/animation.md §4).
|
|
314
314
|
|
|
315
315
|
_offload(prop, entry) {
|
|
316
316
|
const wnd = this.root?.window;
|
package/src/nodes/invalidate.js
CHANGED
|
@@ -361,7 +361,7 @@ export class WindowInvalidate {
|
|
|
361
361
|
}
|
|
362
362
|
// A retained presenter keeps a per-node diff instead of damage rects,
|
|
363
363
|
// and this is the one channel every change already announces itself on
|
|
364
|
-
// (
|
|
364
|
+
// (design/macos.md §"One renderer, two presenters"). Feature-detected: an
|
|
365
365
|
// ntk window has no ear here and the X11 path is byte-identical. A
|
|
366
366
|
// presenter that answers `true` has taken the claim onto a layer of its
|
|
367
367
|
// own (src/cocoa/promotion.js): the bitmap owes nothing for it, and the
|
package/src/nodes/layouthost.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// in layouts.js arranges its children itself. Each child is a yoga root of
|
|
3
3
|
// its own, and the host is a measured leaf in its parent — so what the
|
|
4
4
|
// algorithm decides is on screen in the frame that asked.
|
|
5
|
-
//
|
|
5
|
+
// design/architecture/custom-layout.md is the design record.
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
createLayoutNode,
|
package/src/nodes/video.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// <video>: frames an application decodes (`frames`, a VideoFrames sink), or a
|
|
2
2
|
// file or URL the platform plays (`src`, where there is a player), fitted
|
|
3
|
-
// into the node's box (
|
|
3
|
+
// into the node's box (design/architecture/video.md §6–7).
|
|
4
4
|
//
|
|
5
5
|
// A drawn node like `<image>`: it joins yoga, it is in the paint order and it
|
|
6
6
|
// is hit like anything else — which is the whole point. A video in a form
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
// - **drawn** — everywhere else, and in every frame the presenter declines
|
|
19
19
|
// the part (something is painted over it, it fades, a rounded card does
|
|
20
20
|
// not hold it): the newest frame is copied into a surface of the node's
|
|
21
|
-
// own at paint, natively where the backend can (`
|
|
22
|
-
// through `frameToRGBA` where it cannot, and
|
|
23
|
-
// rect like an `<image>` whose pixels
|
|
21
|
+
// own at paint, natively where the backend can (`writeVideoFrame`,
|
|
22
|
+
// src/videobackend.js) and through `frameToRGBA` where it cannot, and
|
|
23
|
+
// composited at the picture's rect like an `<image>` whose pixels
|
|
24
|
+
// changed. A push claims the picture.
|
|
24
25
|
//
|
|
25
26
|
// `src` needs a platform player, which this backend has when
|
|
26
27
|
// `useSupports('mediaPlayback')` says so — the app's `createPlayer`, on macOS
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
} from '../imagesource.js';
|
|
42
43
|
import { canPlayMedia, NoMediaPlaybackError } from '../mediaplayback.js';
|
|
43
44
|
import { Surface } from '../offscreen.js';
|
|
45
|
+
import { videoBackendOf } from '../videobackend.js';
|
|
44
46
|
import { frameToRGBA, isVideoFrames } from '../videoframes.js';
|
|
45
47
|
import { fitRect, objectFitOf } from './fit.js';
|
|
46
48
|
import { intrinsicSize } from './layout.js';
|
|
@@ -592,10 +594,18 @@ export class VideoNode extends Node {
|
|
|
592
594
|
}
|
|
593
595
|
|
|
594
596
|
_upload(surface, sink, frame) {
|
|
595
|
-
// natively where the backend converts in its own code
|
|
596
|
-
// bridge's writeVideoSurface
|
|
597
|
-
// colour-matched to what a layer shows)
|
|
598
|
-
|
|
597
|
+
// natively where the backend converts in its own code: the Cocoa
|
|
598
|
+
// bridge's writeVideoSurface (a copy for BGRA, VideoToolbox for YCbCr,
|
|
599
|
+
// colour-matched to what a layer shows), the Wayland context's shader,
|
|
600
|
+
// an Xv port on X11 (src/videobackend.js)
|
|
601
|
+
if (
|
|
602
|
+
videoBackendOf(this.app)?.writeVideoFrame?.(surface, sink, frame, this)
|
|
603
|
+
) {
|
|
604
|
+
// a frame converted in JavaScript earlier — the first, on X11, before
|
|
605
|
+
// the window was mapped — left a buffer the native path does not need
|
|
606
|
+
this._rgba = null;
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
599
609
|
const w = sink.width;
|
|
600
610
|
const h = sink.height;
|
|
601
611
|
if (this._rgba?.width !== w || this._rgba?.height !== h) {
|
|
@@ -632,6 +642,7 @@ export class VideoNode extends Node {
|
|
|
632
642
|
this._surface = null;
|
|
633
643
|
this._rgba = null;
|
|
634
644
|
this._drawn = -1;
|
|
645
|
+
if (surface) videoBackendOf(this.app)?.releaseVideoFrame?.(surface);
|
|
635
646
|
try {
|
|
636
647
|
surface?.destroy();
|
|
637
648
|
} catch {
|
package/src/pacing.js
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
// Off by default: `'display'` is the built-in, and paints every frame the
|
|
50
50
|
// clock gives, after React's own batching — a UI answers its input as
|
|
51
51
|
// quickly as it can unless it says otherwise. `'adaptive'` is what a window
|
|
52
|
-
// that streams asks for.
|
|
52
|
+
// that streams asks for. design/architecture/frame-pacing.md is the account.
|
|
53
53
|
|
|
54
54
|
export const DEFAULT_FRAME_RATE = 'display';
|
|
55
55
|
|
package/src/portal.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
// re-solve.
|
|
30
30
|
|
|
31
31
|
import { loadTransport, sessionBus } from './bus.js';
|
|
32
|
+
import { windowIdOf, windowOf } from './windowid.js';
|
|
32
33
|
|
|
33
34
|
export const PORTAL_NAME = 'org.freedesktop.portal.Desktop';
|
|
34
35
|
export const PORTAL_PATH = '/org/freedesktop/portal/desktop';
|
|
@@ -439,6 +440,36 @@ export function parentWindowHandle(xid) {
|
|
|
439
440
|
return typeof xid === 'number' && xid > 0 ? `x11:${xid.toString(16)}` : '';
|
|
440
441
|
}
|
|
441
442
|
|
|
443
|
+
/**
|
|
444
|
+
* The `parent_window` handle for whatever names a window — a ref, a node, a
|
|
445
|
+
* backend window, an XID — on whichever backend it belongs to.
|
|
446
|
+
*
|
|
447
|
+
* - **X11**: `x11:<xid>`, as `parentWindowHandle` spells it.
|
|
448
|
+
* - **Wayland**: `wayland:<handle>`, from an xdg-foreign export of the
|
|
449
|
+
* toplevel (src/wayland/app.js, `exportHandle`) — what the portal needs to
|
|
450
|
+
* make the dialog modal to the window and stack it there. `''` where the
|
|
451
|
+
* compositor has no xdg-foreign, or the window is a popup; never an `x11:`
|
|
452
|
+
* handle, because a Wayland window's id names no X window, and on a desktop
|
|
453
|
+
* running Xwayland it could name somebody else's.
|
|
454
|
+
*
|
|
455
|
+
* `app` is the fallback for a target that resolves to no window object — a
|
|
456
|
+
* bare number on Wayland is that app's id for one of its own windows.
|
|
457
|
+
*
|
|
458
|
+
* @param {any} target
|
|
459
|
+
* @param {any} [app]
|
|
460
|
+
* @returns {Promise<string>}
|
|
461
|
+
*/
|
|
462
|
+
export async function portalParentFor(target, app = null) {
|
|
463
|
+
let win = windowOf(target);
|
|
464
|
+
const owner = win?.app ?? app;
|
|
465
|
+
if (owner?.backend !== 'wayland') {
|
|
466
|
+
return parentWindowHandle(windowIdOf(target));
|
|
467
|
+
}
|
|
468
|
+
if (!win && typeof target === 'number') win = owner.windowById?.(target);
|
|
469
|
+
const handle = win ? await owner.exportHandle?.(win) : null;
|
|
470
|
+
return handle ? `wayland:${handle}` : '';
|
|
471
|
+
}
|
|
472
|
+
|
|
442
473
|
/**
|
|
443
474
|
* `Variant`, from the one copy of the transport the bus layer loaded.
|
|
444
475
|
*
|
package/src/refresh/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// react-x11/refresh — the runtime half of state-preserving hot reload
|
|
2
2
|
// (Fast Refresh). The loader half is ./loader.js; `react-x11/refresh/register`
|
|
3
|
-
// wires both up for `node --import`. See docs/ecosystem/dev-tooling.md
|
|
3
|
+
// wires both up for `node --import`. See docs/ecosystem/dev-tooling.md, and
|
|
4
|
+
// design/ecosystem/dev-tooling.md §"How it reloads" for the loader's rules.
|
|
4
5
|
//
|
|
5
6
|
// Import order is what this module exists to own:
|
|
6
7
|
//
|