react-x11 2.52.0 → 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 +10 -10
- package/docs/README.md +160 -314
- package/docs/accessibility.md +176 -229
- package/docs/appearance.md +77 -176
- package/docs/click-to-component.md +18 -51
- package/docs/clipboard.md +40 -59
- package/docs/components.md +518 -1036
- package/docs/dbus.md +61 -133
- package/docs/debugging.md +152 -245
- package/docs/desktop-calendar.md +51 -88
- package/docs/desktop.md +222 -347
- package/docs/devtools.md +50 -167
- package/docs/drag-and-drop.md +126 -204
- package/docs/ecosystem/animation.md +91 -125
- package/docs/ecosystem/data-fetching.md +38 -53
- package/docs/ecosystem/dev-tooling.md +159 -202
- package/docs/ecosystem/forms.md +61 -97
- package/docs/ecosystem/headless.md +64 -89
- package/docs/ecosystem/i18n.md +18 -21
- package/docs/ecosystem/icons.md +93 -126
- package/docs/ecosystem/layout.md +42 -52
- package/docs/ecosystem/routing.md +18 -29
- package/docs/ecosystem/state.md +71 -88
- package/docs/ecosystem/testing.md +131 -157
- package/docs/ecosystem/theming.md +119 -165
- package/docs/ecosystem.md +145 -179
- package/docs/elements.md +1091 -1774
- package/docs/embedding.md +83 -132
- package/docs/events.md +412 -695
- package/docs/extending.md +784 -1326
- package/docs/eyedropper.md +63 -122
- package/docs/filedialog.md +71 -107
- package/docs/frame.md +195 -339
- package/docs/gl.md +65 -62
- package/docs/globalmenu.md +63 -146
- package/docs/macos.md +531 -3234
- package/docs/notifications.md +33 -47
- package/docs/packaging.md +166 -309
- package/docs/permissions.md +9 -14
- package/docs/react-features.md +81 -106
- package/docs/remote.md +32 -76
- package/docs/scale.md +72 -186
- package/docs/styling.md +329 -547
- package/docs/system.md +200 -280
- package/docs/testing.md +63 -83
- package/docs/typescript.md +6 -48
- package/docs/uri-schemes.md +67 -107
- package/docs/wayland-backend.md +200 -459
- package/docs/windows-integrations.md +75 -131
- package/docs/windows.md +343 -1367
- package/package.json +3 -2
- 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/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/dnd.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/layouts.js +1 -1
- 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/refresh/index.js +2 -1
- 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 +27 -0
- package/src/wayland/video.js +376 -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/docs/architecture/accessibility.md +0 -470
- package/docs/architecture/animation.md +0 -707
- package/docs/architecture/container-queries.md +0 -525
- package/docs/architecture/custom-layout.md +0 -334
- package/docs/architecture/drag-and-drop.md +0 -941
- package/docs/architecture/element-layer-contents.md +0 -622
- package/docs/architecture/frame-pacing.md +0 -255
- package/docs/architecture/grid-layout.md +0 -358
- package/docs/architecture/protocol-efficiency.md +0 -357
- package/docs/architecture/video.md +0 -1149
- package/docs/architecture/windowed-regions.md +0 -854
- package/docs/glx.md +0 -137
- package/docs/wayland.md +0 -545
- package/docs/windows-embedding.md +0 -271
- package/docs/windows-gl.md +0 -221
package/docs/eyedropper.md
CHANGED
|
@@ -24,47 +24,34 @@ function StrokePicker({ value, onChange }) {
|
|
|
24
24
|
|
|
25
25
|
Cancelling — Escape, the desktop dialog's own cancel, a dismissed macOS
|
|
26
26
|
sampler — resolves to `null`. It is an ordinary outcome, not an exception,
|
|
27
|
-
on every rung, so the whole error path is one `if
|
|
28
|
-
[file dialogs](filedialog.md)
|
|
27
|
+
on every rung, so the whole error path is one `if`, as with the
|
|
28
|
+
[file dialogs](filedialog.md).
|
|
29
29
|
|
|
30
30
|
## The ladder
|
|
31
31
|
|
|
32
|
-
| | |
|
|
33
|
-
| --- | ---------------------- |
|
|
34
|
-
| 1 | **the system sampler** | `NSColorSampler` on the [cocoa backend](macos.md)
|
|
35
|
-
| 2 | **the portal** | `org.freedesktop.portal.Screenshot.PickColor` over D-Bus. The desktop draws its own magnifier and hands the colour back. GNOME and KDE ship it; it is also GNOME's only route on Wayland.
|
|
36
|
-
| 3 | **the Wayland picker** | On the [Wayland backend](wayland-backend.md)
|
|
37
|
-
| 4 | **X11** |
|
|
32
|
+
| | | |
|
|
33
|
+
| --- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| 1 | **the system sampler** | `NSColorSampler` on the [cocoa backend](macos.md) (`@windowkit/appkit` >= 0.9). macOS draws the loupe out of process, so the app needs no Screen Recording grant and the user gets the usual Mac magnifier. |
|
|
35
|
+
| 2 | **the portal** | `org.freedesktop.portal.Screenshot.PickColor` over D-Bus. The desktop draws its own magnifier and hands the colour back. GNOME and KDE ship it; it is also GNOME's only route on Wayland. |
|
|
36
|
+
| 3 | **the Wayland picker** | On the [Wayland backend](wayland-backend.md) where the compositor offers screen capture and layer-shell but no portal (wlroots: sway, labwc, …): the output is frozen into a capture overlay and the click is taken on that. |
|
|
37
|
+
| 4 | **X11** | Grab the pointer with a crosshair, wait for the click, read the pixel under it. Works under a bare WM, over ssh, on XQuartz — everywhere there is a display and nothing else. |
|
|
38
38
|
|
|
39
39
|
`screenColorBackend()` reports which rung this machine lands on without
|
|
40
40
|
grabbing anything; `useEyedropper().supported` is the same answer as render
|
|
41
41
|
state.
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
There is no rung to _draw_, and that is the difference from the file dialog's
|
|
49
|
-
ladder: the thing being read — the whole screen — is precisely what an
|
|
50
|
-
application cannot draw itself. So the hook adds binding, not a fallback of
|
|
51
|
-
its own — and the ladder can therefore run out. Where it does, the floor is
|
|
52
|
-
the typed rejection and `supported: false`, never a crash. A cocoa app on a
|
|
53
|
-
bridge **older than 0.9** is that place: its `app.X` is a stub, so there is
|
|
54
|
-
no pointer to grab and no root window to read either ([macos.md](macos.md)).
|
|
55
|
-
Both system rungs are gated on what they are built out of — one bridge verb,
|
|
56
|
-
three X requests — rather than on there being an app, so a picker there draws
|
|
57
|
-
no eyedropper button instead of one that throws on its first press.
|
|
43
|
+
There is no rung that react-x11 draws itself — the screen is exactly what an
|
|
44
|
+
app cannot draw — so the ladder can run out. Where it does, `supported` is
|
|
45
|
+
`false` and a pick rejects with a typed error, never a crash. A cocoa app on a
|
|
46
|
+
bridge **older than 0.9** is that place ([macos.md](macos.md)): hide the
|
|
47
|
+
button there.
|
|
58
48
|
|
|
59
49
|
### The version gate
|
|
60
50
|
|
|
61
|
-
`PickColor` arrived in **version 2** of the Screenshot interface, and
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
and every PickColor would fail. So the probe reads the interface's own
|
|
66
|
-
`version` property, via `portalVersion()` (exported, since the next portal
|
|
67
|
-
feature will need the same question answered):
|
|
51
|
+
`PickColor` arrived in **version 2** of the Screenshot interface, and a portal
|
|
52
|
+
on the bus may not have it (XFCE's ships no Screenshot interface at all). The
|
|
53
|
+
probe reads the interface's own `version` with `portalVersion()`, which is
|
|
54
|
+
exported:
|
|
68
55
|
|
|
69
56
|
```js
|
|
70
57
|
import { portalVersion } from 'react-x11';
|
|
@@ -73,8 +60,7 @@ const v = await portalVersion('org.freedesktop.portal.Screenshot');
|
|
|
73
60
|
// 0 — not there at all; 1 — there, but before PickColor; 2+ — usable
|
|
74
61
|
```
|
|
75
62
|
|
|
76
|
-
|
|
77
|
-
rather than calling a method the portal would refuse.
|
|
63
|
+
Below 2, the ladder falls through to the X11 rung.
|
|
78
64
|
|
|
79
65
|
## `useEyedropper()`
|
|
80
66
|
|
|
@@ -94,21 +80,17 @@ picking: boolean; // a pick is in flight — the button's pressed state
|
|
|
94
80
|
|
|
95
81
|
Options given to the hook are defaults for every pick; options given to a
|
|
96
82
|
call win. The portal's dialog is parented to the window the component is in,
|
|
97
|
-
resolved when the pick starts — the
|
|
83
|
+
resolved when the pick starts — the
|
|
84
|
+
[`useFileDialog()` inference](filedialog.md#the-dialog-parents-itself), with
|
|
98
85
|
`parentWindow` as the override for a tree with several top-level windows.
|
|
99
86
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
a
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
connection by definition, and the connection _is_ the fallback rung; a
|
|
108
|
-
cocoa tree has the system sampler. It goes false when a forced
|
|
109
|
-
`backend: 'portal'` finds no portal, and on a cocoa app whose bridge
|
|
110
|
-
predates the sampler — which is what keeps the button honest on whatever
|
|
111
|
-
platform comes next.
|
|
87
|
+
- **`picking` is the pressed state.** While it is true, a second `pick()`
|
|
88
|
+
returns the promise already in flight rather than starting a second grab,
|
|
89
|
+
so a double-clicked button cannot make the user click twice.
|
|
90
|
+
- **`supported` is almost always true.** An X11 tree's connection _is_ the
|
|
91
|
+
fallback rung, and a cocoa tree has the system sampler. It goes false when a
|
|
92
|
+
forced `backend: 'portal'` finds no portal, and on a cocoa app whose bridge
|
|
93
|
+
predates the sampler.
|
|
112
94
|
|
|
113
95
|
## The bare function
|
|
114
96
|
|
|
@@ -118,82 +100,55 @@ import { pickScreenColor, screenColorBackend } from 'react-x11';
|
|
|
118
100
|
const hex = await pickScreenColor({ app });
|
|
119
101
|
```
|
|
120
102
|
|
|
121
|
-
Same options, same results. The one addition is `app
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
app
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
through are asked, the way `openFile()` finds its native panel. Where no
|
|
128
|
-
rung answers it rejects with `NoScreenColorError` — a **typed** rejection,
|
|
129
|
-
so it reads as "hide the button", not as a crash.
|
|
103
|
+
Same options, same results. The one addition is `app`, because a bare function
|
|
104
|
+
has no tree to take a connection from: pass the app `createRoot()` returned
|
|
105
|
+
(or `useApp()` in a component, or a `parentWindow` that points at a mounted
|
|
106
|
+
node). With no app named, the connections the renderer is currently drawing
|
|
107
|
+
through are asked. Where no rung answers it rejects with `NoScreenColorError`
|
|
108
|
+
— a **typed** rejection, so it reads as "hide the button", not as a crash.
|
|
130
109
|
|
|
131
110
|
One pick per connection at a time **on the X11 rung**: a second concurrent
|
|
132
|
-
`pickScreenColor()` on the same app is refused loudly
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
`NSColorSampler` "begins or attaches to an existing color sampling session",
|
|
137
|
-
so a second pick joins the loupe that is up and both callers get the same
|
|
138
|
-
colour.
|
|
111
|
+
`pickScreenColor()` on the same app is refused loudly (two grabs from one
|
|
112
|
+
client would steal each other's click). The hook's shared in-flight promise is
|
|
113
|
+
the friendly version of the same rule. On the system sampler a second pick
|
|
114
|
+
joins the loupe that is up, and both callers get the same colour.
|
|
139
115
|
|
|
140
116
|
## What differs between the rungs
|
|
141
117
|
|
|
142
|
-
- **The system rungs draw a magnifier; the X11 rung
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
screen
|
|
147
|
-
colour management.
|
|
148
|
-
- **Only the system rungs need no permission.** Both the portal and
|
|
149
|
-
`NSColorSampler` run the picker in another process and hand back a
|
|
150
|
-
colour, so nothing here asks for a screen-capture grant — on macOS that
|
|
151
|
-
is the difference between a dropper button and a TCC prompt for Screen
|
|
152
|
-
Recording.
|
|
118
|
+
- **The system rungs draw a magnifier; the X11 rung does not.** On X11 it is
|
|
119
|
+
grab, crosshair, click — and Escape cancels.
|
|
120
|
+
- **Only the system rungs need no permission.** The portal and
|
|
121
|
+
`NSColorSampler` run the picker in another process, so nothing asks for a
|
|
122
|
+
screen-capture grant (on macOS, no TCC prompt for Screen Recording).
|
|
153
123
|
- **On the X11 rung the keyboard also picks.** Return, KP Enter or space
|
|
154
|
-
samples the pixel under the pointer without a click
|
|
155
|
-
|
|
156
|
-
focus happens to be.
|
|
124
|
+
samples the pixel under the pointer without a click, and the keyboard is
|
|
125
|
+
grabbed for the duration so Escape works wherever the focus is.
|
|
157
126
|
- **What is sampled is what is on the screen.** The X11 rung reads the
|
|
158
127
|
rendered pixel, so a colour under a translucent overlay is the blend, not
|
|
159
|
-
the window's own value.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
is what an abort has to guarantee where something _is_ held.
|
|
169
|
-
- **Every rung answers `'#rrggbb'`.** The portal and the sampler hand back
|
|
170
|
-
sRGB floats and X11 hands back server-layout words; every consumer wants a
|
|
171
|
-
CSS colour it can paint with, so the conversion happens once, here — the
|
|
172
|
-
same function for the two float rungs. If something later needs the
|
|
173
|
-
unrounded values, an option can widen the return — starting with channels
|
|
174
|
-
would have made the common case do arithmetic.
|
|
128
|
+
the window's own value. The portal's pickers (GNOME's, KDE's) read the
|
|
129
|
+
composited screen too.
|
|
130
|
+
- **`signal` means less on the cocoa rung.** AppKit cannot dismiss a sampler
|
|
131
|
+
from code, so an abort there ends the wait and nothing else: the loupe stays
|
|
132
|
+
up until the user picks or presses Escape, its colour is dropped, and until
|
|
133
|
+
then the pending sample holds the process's event loop open. On the portal
|
|
134
|
+
and X11 rungs an abort closes the request or releases the grab.
|
|
135
|
+
- **Every rung answers `'#rrggbb'`**, converted from the portal's and
|
|
136
|
+
sampler's sRGB floats or X11's server-layout pixel.
|
|
175
137
|
|
|
176
138
|
## The grab, and what it displaces
|
|
177
139
|
|
|
178
|
-
The X11 rung holds a **server pointer grab** while it waits
|
|
179
|
-
|
|
180
|
-
desktop that has stopped answering clicks — and it is why the grab's whole
|
|
181
|
-
lifecycle belongs to core rather than to apps:
|
|
140
|
+
The X11 rung holds a **server pointer grab** while it waits, and core owns its
|
|
141
|
+
whole lifecycle:
|
|
182
142
|
|
|
183
143
|
- Every way out — click, Escape, abort, error — releases the grab before the
|
|
184
|
-
promise settles.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
calls `UngrabPointer`, which releases whatever the client's active grab is
|
|
193
|
-
— is detected (the server announces it with a `LeaveNotify` of mode
|
|
194
|
-
`Ungrab` on the grab window) and taken back, rather than leaving a pick
|
|
195
|
-
that never resolves while the app answers clicks as if nothing were
|
|
196
|
-
happening.
|
|
144
|
+
promise settles.
|
|
145
|
+
- A `<popup grab>` that was open when the pick started (an open `Select`, a
|
|
146
|
+
menu, the colour panel the button sits in) gets its grab back when the pick
|
|
147
|
+
ends, so dismiss-on-outside-click still works afterwards.
|
|
148
|
+
- If this client's own popup teardown releases the grab mid-pick, the pick
|
|
149
|
+
takes it back rather than hanging.
|
|
150
|
+
|
|
151
|
+
Design notes: [design/eyedropper.md](../design/eyedropper.md).
|
|
197
152
|
|
|
198
153
|
## Running it anywhere
|
|
199
154
|
|
|
@@ -207,17 +162,3 @@ lifecycle belongs to core rather than to apps:
|
|
|
207
162
|
| Node 20, where npm skips `dbus-native` | X11 |
|
|
208
163
|
| the Wayland backend, a portal with Screenshot ≥ 2 | portal |
|
|
209
164
|
| the Wayland backend on wlroots (sway, labwc, …) | wayland — the backend's own picker; there is no root window to read, so no X11 rung under it |
|
|
210
|
-
|
|
211
|
-
## Building a colour picker on it
|
|
212
|
-
|
|
213
|
-
The eyedropper is the one part of a colour picker that cannot be composed
|
|
214
|
-
out of public elements, which is why it lives in core and the picker panel
|
|
215
|
-
does not. A `<ColorPicker>` in `@react-x11/components` consumes it as a
|
|
216
|
-
function prop:
|
|
217
|
-
|
|
218
|
-
```jsx
|
|
219
|
-
<ColorPicker eyedropper={pickScreenColor} value={fill} onChange={setFill} />
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
and once that component exists, this function is its default and no app
|
|
223
|
-
changes.
|
package/docs/filedialog.md
CHANGED
|
@@ -28,27 +28,19 @@ every backend — so the whole error path for a file dialog is one `if`.
|
|
|
28
28
|
|
|
29
29
|
## The ladder
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
runs on, so this is a ladder and the rung is chosen for you:
|
|
31
|
+
The rung is chosen for you, top to bottom:
|
|
33
32
|
|
|
34
|
-
| | |
|
|
35
|
-
| --- | ----------------------- |
|
|
36
|
-
| 1 | **the native panel** | `NSOpenPanel` / `NSSavePanel`
|
|
37
|
-
| 2 | **the portal** | `org.freedesktop.portal.FileChooser` over D-Bus
|
|
38
|
-
| 3 | **`osascript`** | macOS on the X11 backend with no portal
|
|
39
|
-
| 4 | **the built-in dialog** | A browser react-x11 draws itself. ssh, a bare `startx`, a container: everywhere there is a display and nothing else.
|
|
33
|
+
| | | |
|
|
34
|
+
| --- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| 1 | **the native panel** | `NSOpenPanel` / `NSSavePanel` on the [cocoa backend](macos.md) (`@windowkit/appkit` ≥ 0.5): a **sheet** on the window that asked, with every filter the OS type database knows. On the [win32 backend](windows.md), Windows' own open/save dialog, owned by the window that asked, takes the same rung (`fileDialogBackend()` reports `'cocoa'` for both). |
|
|
36
|
+
| 2 | **the portal** | `org.freedesktop.portal.FileChooser` over D-Bus: the desktop's own GTK or KDE dialog, with the user's bookmarks and recent files. |
|
|
37
|
+
| 3 | **`osascript`** | macOS on the X11 backend with no portal (every ordinary XQuartz install). `choose file` _is_ `NSOpenPanel`, in a third process. |
|
|
38
|
+
| 4 | **the built-in dialog** | A browser react-x11 draws itself. ssh, a bare `startx`, a container: everywhere there is a display and nothing else. |
|
|
40
39
|
|
|
41
|
-
The top rung is
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`fileDialogBackend()` reports which one this machine lands on, without showing
|
|
46
|
-
anything — useful for a diagnostics panel, and for a menu that wants to say so.
|
|
47
|
-
|
|
48
|
-
**The ladder is the design, not a fallback chain bolted on.** react-x11's
|
|
49
|
-
flagship case is an app running on one machine and drawing to another, and a
|
|
50
|
-
file dialog that only worked on a full GNOME session would be useless to
|
|
51
|
-
exactly the person the renderer exists for.
|
|
40
|
+
The top rung is chosen by the window the dialog belongs to, never by naming a
|
|
41
|
+
backend. `fileDialogBackend()` reports which rung this machine lands on,
|
|
42
|
+
without showing anything — for a diagnostics panel, or a menu that wants to
|
|
43
|
+
say so.
|
|
52
44
|
|
|
53
45
|
## `useFileDialog()`
|
|
54
46
|
|
|
@@ -60,8 +52,8 @@ saveFile(options?): Promise<string | null>; // one path, may not exist yet
|
|
|
60
52
|
selectFolder(options?): Promise<string[] | null>;
|
|
61
53
|
```
|
|
62
54
|
|
|
63
|
-
The hook is the surface to reach for
|
|
64
|
-
|
|
55
|
+
The hook is the surface to reach for: it has a tree to draw the built-in dialog
|
|
56
|
+
in, so it never runs out of rungs and never rejects for lack of a dialog.
|
|
65
57
|
|
|
66
58
|
| option | |
|
|
67
59
|
| ----------------------------- | ------------------------------------------------- |
|
|
@@ -80,39 +72,29 @@ win.
|
|
|
80
72
|
|
|
81
73
|
### The dialog parents itself
|
|
82
74
|
|
|
83
|
-
The dialog is `transientFor` the window the component is in
|
|
84
|
-
for the portal
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- several with nothing to separate them: the most recently opened, plus a
|
|
107
|
-
development warning naming `parentWindow` as the way to be exact.
|
|
108
|
-
- **none at all: the root-level `<popup>` that took the keyboard.** A menu-bar
|
|
109
|
-
app is a tray item and the popover a click on it opens, and nothing else
|
|
110
|
-
([elements.md](elements.md#a-popover-under-a-tray-item)) — so the popup is
|
|
111
|
-
the top of that tree, and answering nothing left every hook that reads this
|
|
112
|
-
one with no owner to belong to.
|
|
113
|
-
|
|
114
|
-
So `parentWindow` survives as the **override for multi-window apps**, and takes
|
|
115
|
-
a `<window>` ref, an XID, or anything `windowIdOf()` accepts:
|
|
75
|
+
The dialog is `transientFor` the window the component is in (`parent_window`
|
|
76
|
+
for the portal), so the window manager stacks it above your window and keeps
|
|
77
|
+
it out of the task switcher. **You do not pass anything for that**:
|
|
78
|
+
`useFileDialog()` resolves the owner window through `useTopLevelWindow()` when
|
|
79
|
+
the dialog opens.
|
|
80
|
+
|
|
81
|
+
On the [native Wayland backend](wayland-backend.md) the portal is handed an
|
|
82
|
+
**xdg-foreign** export of the window (`wayland:<handle>`), which GNOME and KDE
|
|
83
|
+
both parent to. Where the compositor has no xdg-foreign the dialog opens
|
|
84
|
+
unparented.
|
|
85
|
+
|
|
86
|
+
The owner is inferred from the top-level windows this connection is rendering:
|
|
87
|
+
|
|
88
|
+
- **one top-level window** — that one; nearly every app.
|
|
89
|
+
- several, one focused — the focused one.
|
|
90
|
+
- several with nothing to separate them — the most recently opened, plus a
|
|
91
|
+
development warning naming `parentWindow`.
|
|
92
|
+
- **none at all** — the root-level `<popup>` that took the keyboard (a
|
|
93
|
+
menu-bar app's popover,
|
|
94
|
+
[elements.md](elements.md#a-popover-under-a-tray-item)).
|
|
95
|
+
|
|
96
|
+
`parentWindow` is the **override for multi-window apps**, and takes a
|
|
97
|
+
`<window>` ref, an XID, or anything `windowIdOf()` accepts:
|
|
116
98
|
|
|
117
99
|
```jsx
|
|
118
100
|
const win = useRef(null);
|
|
@@ -126,8 +108,9 @@ first render, so a value read then would be `null` on the render that matters
|
|
|
126
108
|
which drops into `parentWindow`, `transientFor`, or `windowIdOf()`.
|
|
127
109
|
|
|
128
110
|
**The portal never embeds the dialog.** It is a top-level window in another
|
|
129
|
-
process
|
|
130
|
-
|
|
111
|
+
process; the file list never appears inside your window.
|
|
112
|
+
|
|
113
|
+
Design notes: [design/filedialog.md](../design/filedialog.md).
|
|
131
114
|
|
|
132
115
|
## The imperative functions
|
|
133
116
|
|
|
@@ -145,41 +128,30 @@ to hang off. In a component, use the hook.
|
|
|
145
128
|
|
|
146
129
|
## What differs between the rungs
|
|
147
130
|
|
|
148
|
-
Most of the API is the same everywhere. These are the places it genuinely is
|
|
149
|
-
not, stated here rather than discovered:
|
|
150
|
-
|
|
151
131
|
- **MIME-type filters do not reach `osascript`.** `extensions` translate
|
|
152
132
|
exactly to every rung; `mimeTypes` reach the portal and the native panel
|
|
153
|
-
(the cocoa backend
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
file with no way to get at it. Give `extensions` where you can.
|
|
133
|
+
(the cocoa backend maps `image/png` to `public.png` through the OS type
|
|
134
|
+
database) and are dropped by `osascript`, where a wrong UTI guess would hide
|
|
135
|
+
the user's file. Give `extensions` where you can.
|
|
157
136
|
- **`osascript` ignores `parentWindow`; the native panel is a sheet on it.**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
the panel is a **sheet** on that window, and the app keeps running while it
|
|
163
|
-
is up. With no window at all — a bare `openFile()` with nothing in
|
|
137
|
+
The `osascript` panel appears over the app but is not attached to it;
|
|
138
|
+
application modality holds only because your code is awaiting the promise.
|
|
139
|
+
On the cocoa backend the panel is a **sheet**, and the app keeps running
|
|
140
|
+
while it is up. With no window at all — a bare `openFile()` with nothing in
|
|
164
141
|
`parentWindow` and no tree to infer one from — the native panel is
|
|
165
142
|
app-modal and **blocks the process**, timers included, until it is
|
|
166
143
|
dismissed. `useFileDialog()` always names the window, so it never lands
|
|
167
144
|
there.
|
|
168
145
|
- **The built-in dialog has never seen the user's bookmarks.** It offers the
|
|
169
|
-
filesystem, a filter, hidden files, and a path you can type into
|
|
170
|
-
|
|
146
|
+
filesystem, a filter, hidden files, and a path you can type into — not a
|
|
147
|
+
re-creation of GTK's chooser.
|
|
171
148
|
- **`multiple` in the built-in dialog is a tick column**, not ctrl-click.
|
|
172
149
|
|
|
173
150
|
In the built-in dialog a double click opens a directory or picks a file, Enter
|
|
174
|
-
does the same to the selected row, `Up` goes to the parent, and Escape cancels
|
|
175
|
-
— and a selected directory is drawn in the selection's own text colour rather
|
|
176
|
-
than the accent it wears at rest, since the selected row is filled with that
|
|
177
|
-
accent in both schemes.
|
|
151
|
+
does the same to the selected row, `Up` goes to the parent, and Escape cancels.
|
|
178
152
|
|
|
179
153
|
## Running it where there is no bus
|
|
180
154
|
|
|
181
|
-
The interesting configurations, and what each one does:
|
|
182
|
-
|
|
183
155
|
| where | rung |
|
|
184
156
|
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
|
|
185
157
|
| macOS, the **cocoa backend** | the native panel |
|
|
@@ -190,17 +162,13 @@ The interesting configurations, and what each one does:
|
|
|
190
162
|
| Node 20, where npm skips `dbus-native` | built-in on Linux, `osascript` on a Mac |
|
|
191
163
|
|
|
192
164
|
The `osascript` rung is reached by _not_ finding a portal, so it also covers a
|
|
193
|
-
Mac that has a session bus for other reasons.
|
|
194
|
-
|
|
195
|
-
wins on the X11 backend, which is what you asked for by running it; on the
|
|
196
|
-
cocoa backend the native panel is above it either way.
|
|
165
|
+
Mac that has a session bus for other reasons. A portal shim on a Mac wins on
|
|
166
|
+
the X11 backend; on the cocoa backend the native panel is above it either way.
|
|
197
167
|
|
|
198
168
|
## The portal machinery, if you need it directly
|
|
199
169
|
|
|
200
|
-
`portalRequest()` is exported for building other portals on.
|
|
201
|
-
method that shows UI
|
|
202
|
-
a `Request.Response` signal — and the path is predictable **by design**, so the
|
|
203
|
-
client subscribes _before_ it calls and cannot lose the answer:
|
|
170
|
+
`portalRequest()` is exported for building other portals on. It calls a portal
|
|
171
|
+
method that shows UI and resolves with its `Request.Response`:
|
|
204
172
|
|
|
205
173
|
```js
|
|
206
174
|
const { response, results } = await portalRequest(busRef, {
|
|
@@ -212,16 +180,15 @@ const { response, results } = await portalRequest(busRef, {
|
|
|
212
180
|
});
|
|
213
181
|
```
|
|
214
182
|
|
|
215
|
-
It
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
183
|
+
It subscribes to the response before calling, so the answer cannot be lost;
|
|
184
|
+
an abort sends `Request.Close()` and settles the promise locally (the portal
|
|
185
|
+
emits no `Response` for it); and only the initial call has a deadline — the
|
|
186
|
+
answer has none, because a dialog can legitimately be open for an hour.
|
|
219
187
|
|
|
220
|
-
The default argument shape
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
no title, is the worked example:
|
|
188
|
+
The default argument shape, `(s parent_window, s title, a{sv} options)`, is
|
|
189
|
+
FileChooser's alone. Any other Request-shaped portal states its leading
|
|
190
|
+
arguments as `signature` and `args` together; the
|
|
191
|
+
[eyedropper](eyedropper.md)'s `PickColor`, which has no title:
|
|
225
192
|
|
|
226
193
|
```js
|
|
227
194
|
await portalRequest(busRef, {
|
|
@@ -232,21 +199,18 @@ await portalRequest(busRef, {
|
|
|
232
199
|
});
|
|
233
200
|
```
|
|
234
201
|
|
|
235
|
-
The signature must end in `a{sv}`
|
|
236
|
-
|
|
237
|
-
|
|
202
|
+
The signature must end in `a{sv}` (the options dict carries `handle_token`); a
|
|
203
|
+
method without one does not answer through a `Request` and wants a plain
|
|
204
|
+
`bus.invoke` instead.
|
|
238
205
|
|
|
239
206
|
`hasService(name)` answers whether a service is reachable — owned now **or
|
|
240
|
-
activatable on demand**. `NameHasOwner` alone is the wrong question:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
is not there at all. Capability lives in the interface's own `version`
|
|
248
|
-
property, and gating on the service alone breaks exactly on the desktops that
|
|
249
|
-
provide some portals and not others (XFCE has FileChooser and no Screenshot).
|
|
207
|
+
activatable on demand**. (`NameHasOwner` alone is the wrong question: the
|
|
208
|
+
portal is D-Bus-activatable and often has no owner until first use.)
|
|
209
|
+
|
|
210
|
+
`portalVersion(iface)` answers whether the portal's backends provide one
|
|
211
|
+
_interface_, and at what version — `0` when it is not there at all. Gate on
|
|
212
|
+
this rather than on the service: desktops provide some portals and not others
|
|
213
|
+
(XFCE has FileChooser and no Screenshot).
|
|
250
214
|
|
|
251
215
|
See [dbus.md](dbus.md) for the connection these sit on.
|
|
252
216
|
|