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/system.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# The machine around the app
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Hooks for what an application reads off the system rather than working out
|
|
4
|
+
itself: the monitors, what the window manager did with the window, whether
|
|
5
|
+
anyone is at the desk, the state of the keyboard, how the desktop wants an app
|
|
6
|
+
to feel, and the language it was launched in — and a store for what the app
|
|
7
|
+
remembers itself, between launches.
|
|
8
8
|
|
|
9
9
|
```jsx
|
|
10
10
|
import {
|
|
@@ -19,17 +19,15 @@ import {
|
|
|
19
19
|
} from 'react-x11';
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
They share a shape
|
|
22
|
+
They share a shape:
|
|
23
23
|
|
|
24
|
-
- **Nothing is asked of the server until a component asks.**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
state rather than an error. Each hook below names what it reports then, and
|
|
30
|
-
which field tells that apart from a genuine "no".
|
|
24
|
+
- **Nothing is asked of the server until a component asks.** An app that
|
|
25
|
+
never calls them pays for none of it.
|
|
26
|
+
- **Every one has an answer with no extension present.** "This display cannot
|
|
27
|
+
say" is a real state rather than an error. Each hook below names what it
|
|
28
|
+
reports then, and which field tells that apart from a genuine "no".
|
|
31
29
|
- **The colours are somewhere else.** Light or dark, the accent and reduced
|
|
32
|
-
motion are [system appearance](appearance.md)
|
|
30
|
+
motion are [system appearance](appearance.md).
|
|
33
31
|
|
|
34
32
|
---
|
|
35
33
|
|
|
@@ -66,24 +64,20 @@ Re-renders when a monitor is plugged in or unplugged, when the arrangement
|
|
|
66
64
|
changes, and when a panel appears, moves or auto-hides.
|
|
67
65
|
|
|
68
66
|
The rects are in **logical pixels**, like every other length an app touches,
|
|
69
|
-
and `scale` is the per-monitor answer the display-scale ladder arrived at —
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
and `scale` is the per-monitor answer the display-scale ladder arrived at — a
|
|
68
|
+
retina lid and an office monitor genuinely differ. The root's own scale is the
|
|
69
|
+
primary monitor's; see [scale.md](scale.md).
|
|
72
70
|
|
|
73
71
|
### The names arrive after the geometry
|
|
74
72
|
|
|
75
73
|
`source` says which tier answered. The geometry resolves during `createRoot()`
|
|
76
|
-
from **Xinerama
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
move between the two: Xinerama on any modern server _is_ RandR's emulation of
|
|
84
|
-
it, so what arrives late is added rather than corrected. Where a name is what
|
|
85
|
-
gets persisted — "reopen on the monitor I was on" — treat null as _not known
|
|
86
|
-
yet_ and keep the last one rather than writing it.
|
|
74
|
+
from **Xinerama**; the names, the primary flag and the physical sizes come
|
|
75
|
+
from **RandR** a moment later, without holding startup up. So a component can
|
|
76
|
+
render once with `source: 'xinerama'` and null names, and again with
|
|
77
|
+
`source: 'randr'` and real ones. The rects do not move between the two: what
|
|
78
|
+
arrives late is added, not corrected. Where a name is persisted — "reopen on
|
|
79
|
+
the monitor I was on" — treat null as _not known yet_ and keep the last one
|
|
80
|
+
rather than writing it.
|
|
87
81
|
|
|
88
82
|
`'screen'` is the tier under both: one entry covering the whole display, for a
|
|
89
83
|
server with neither extension. `null` is a headless mock with no display at
|
|
@@ -91,49 +85,37 @@ all.
|
|
|
91
85
|
|
|
92
86
|
### `available` is an approximation on X11, and the only one here
|
|
93
87
|
|
|
94
|
-
`_NET_WORKAREA` — the desktop minus the
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
A true per-monitor work area means reading `_NET_WM_STRUT_PARTIAL` off every
|
|
102
|
-
window on the screen and intersecting the reservations that land on each head
|
|
103
|
-
— a full window-tree walk, redone whenever any panel changes. If you need
|
|
104
|
-
that, `useApp().X` is the escape hatch.
|
|
88
|
+
`_NET_WORKAREA` — the desktop minus the panels that reserved space — is
|
|
89
|
+
published for the **whole virtual desktop**, not per monitor. So it is applied
|
|
90
|
+
as a per-axis bound on each monitor's rect: exact on one head; on several, a
|
|
91
|
+
top or bottom panel comes off the height without knowing which head it is on.
|
|
92
|
+
For a true per-monitor work area, read `_NET_WM_STRUT_PARTIAL` off every
|
|
93
|
+
window yourself through `useApp().X`.
|
|
105
94
|
|
|
106
95
|
### On Wayland
|
|
107
96
|
|
|
108
97
|
The same shape, from `wl_output` ([wayland-backend.md](wayland-backend.md)):
|
|
109
98
|
`name` is the compositor's connector name (`DP-1`, `eDP-1`) and arrives with
|
|
110
99
|
the geometry rather than after it; the rects are xdg_output's logical ones.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
because the field is one rect by definition; it is the primary screen's, and
|
|
123
|
-
on a multi-display Mac `screens[n].available` is the answer to prefer.
|
|
124
|
-
`primary` is the screen with the menu bar, `refreshRate` the panel's own rate,
|
|
125
|
-
and `name` is null — the bridge does not report one.
|
|
100
|
+
There is no primary — the entry at the origin is flagged — and no work area:
|
|
101
|
+
`available` is each monitor's own rect less the bounds the compositor gave a
|
|
102
|
+
window on it, so it is exact per monitor once a window has appeared there.
|
|
103
|
+
`scale` is the monitor's own, a fraction under fractional scaling. `source`
|
|
104
|
+
reads `'test'` on both native backends.
|
|
105
|
+
|
|
106
|
+
**On macOS it is exact.** Each monitor's `available` is its `visibleFrame` —
|
|
107
|
+
that display's own menu bar and Dock taken off. `workArea` is one rect by
|
|
108
|
+
definition, the primary screen's; on a multi-display Mac prefer
|
|
109
|
+
`screens[n].available`. `primary` is the screen with the menu bar,
|
|
110
|
+
`refreshRate` the panel's own rate, and `name` is null.
|
|
126
111
|
|
|
127
112
|
### On macOS the layout is asked for, not announced
|
|
128
113
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
`NSScreen` copy current on the system's own notification and says nothing —
|
|
132
|
-
so react-x11 re-reads the layout instead, at the moments it matters:
|
|
114
|
+
macOS sends a client no event when the displays change, so react-x11 re-reads
|
|
115
|
+
the layout at the moments it matters:
|
|
133
116
|
|
|
134
|
-
- **before a placement
|
|
135
|
-
|
|
136
|
-
window capped by one that is gone;
|
|
117
|
+
- **before a placement** — a popup flipped and clamped into a monitor, an
|
|
118
|
+
auto-sized window capped by one;
|
|
137
119
|
- **when a window reports that it moved**, at most once a second, because a
|
|
138
120
|
display arriving or leaving moves the windows that were on it;
|
|
139
121
|
- **on a clock while a `useScreens()` subscriber is mounted**, every
|
|
@@ -142,15 +124,13 @@ so react-x11 re-reads the layout instead, at the moments it matters:
|
|
|
142
124
|
|
|
143
125
|
So a component watching the layout sees a monitor plugged in about half a
|
|
144
126
|
second later than an X11 one does, and everything else sees it at the moment
|
|
145
|
-
it asks. `useApp().refreshScreens()` publishes the layout right now
|
|
146
|
-
app that knows before any of those.
|
|
127
|
+
it asks. `useApp().refreshScreens()` publishes the layout right now.
|
|
147
128
|
|
|
148
129
|
### Mirroring
|
|
149
130
|
|
|
150
|
-
Two outputs showing the same pixels
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
answer an app would act on.
|
|
131
|
+
Two outputs showing the same pixels are reported as **one** monitor, with both
|
|
132
|
+
names in `outputs`. A laptop mirroring to a projector is one screen to put a
|
|
133
|
+
window on.
|
|
154
134
|
|
|
155
135
|
---
|
|
156
136
|
|
|
@@ -182,18 +162,18 @@ useEffect(() => {
|
|
|
182
162
|
| `states` | the raw `_NET_WM_STATE` names |
|
|
183
163
|
| `desktop` | the workspace index, or null |
|
|
184
164
|
|
|
185
|
-
`<window states>` is what a window **asks for**; this is what it **got**.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
165
|
+
`<window states>` is what a window **asks for**; this is what it **got**. They
|
|
166
|
+
part company routinely — a user hits a maximize hotkey, a tiling WM declines a
|
|
167
|
+
fullscreen request — and an app that mirrors the state in its own UI has
|
|
168
|
+
nowhere else to read it.
|
|
189
169
|
|
|
190
170
|
On the **Cocoa** backend the same rows come from AppKit, in the same
|
|
191
|
-
`_NET_WM_STATE` names
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
171
|
+
`_NET_WM_STATE` names: `fullscreen` is the title bar's green button or
|
|
172
|
+
`<window fullscreen>`, `maximized` the zoom (`maximized_vert` and
|
|
173
|
+
`maximized_horz` together), `minimized` the Dock (`hidden`), and `states`
|
|
174
|
+
lists `focused` for the key window. `fullscreen` turns as AppKit's transition
|
|
175
|
+
**begins**, so a toolbar an app hides in full screen goes while the window
|
|
176
|
+
grows rather than after it:
|
|
197
177
|
|
|
198
178
|
```jsx
|
|
199
179
|
const { fullscreen } = useWindowState();
|
|
@@ -201,7 +181,7 @@ return fullscreen ? <Page /> : [<Toolbar />, <Page />];
|
|
|
201
181
|
```
|
|
202
182
|
|
|
203
183
|
`obscured` and `desktop` keep their initial values there — a Space is not a
|
|
204
|
-
numbered workspace
|
|
184
|
+
numbered workspace.
|
|
205
185
|
|
|
206
186
|
With no argument it reads the window the component is in, inferred the way
|
|
207
187
|
[`useTopLevelWindow()`](filedialog.md) infers it: exact for a one-window app,
|
|
@@ -213,39 +193,32 @@ const { fullscreen } = useWindowState(win);
|
|
|
213
193
|
return <window ref={win}>…</window>;
|
|
214
194
|
```
|
|
215
195
|
|
|
216
|
-
The first render answers `focused: true, visible: true` rather than waiting
|
|
217
|
-
a round trip
|
|
218
|
-
|
|
219
|
-
flash on every launch.
|
|
196
|
+
The first render answers `focused: true, visible: true` rather than waiting
|
|
197
|
+
for a round trip, so a title bar does not render dimmed on frame one and
|
|
198
|
+
un-dim on frame two.
|
|
220
199
|
|
|
221
200
|
### `obscured` and the compositor
|
|
222
201
|
|
|
223
202
|
**A composited window is never obscured.** The compositor redirects it to an
|
|
224
203
|
offscreen pixmap, so the server considers it entirely visible whatever is
|
|
225
|
-
stacked on top
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
`minimized`, the signal that _does_ survive compositing — is the field to
|
|
231
|
-
branch on. An animation paused on `visible` stops when the window is minimized
|
|
232
|
-
everywhere, and additionally when it is buried on a desktop with no compositor.
|
|
204
|
+
stacked on top — and every stock GNOME and KDE session runs one. So `obscured`
|
|
205
|
+
is a bare-WM optimisation, and `visible`, which folds in `minimized` (that
|
|
206
|
+
_does_ survive compositing), is the field to branch on. An animation paused on
|
|
207
|
+
`visible` stops when the window is minimized everywhere, and additionally when
|
|
208
|
+
it is buried on a desktop with no compositor.
|
|
233
209
|
|
|
234
210
|
### `presenting`, and the Wayland frame clock
|
|
235
211
|
|
|
236
|
-
A Wayland compositor
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
that reaches no screen. That is the compositor being right (painting into a
|
|
241
|
-
surface nobody sees is work for nothing) and it used to be invisible: no
|
|
242
|
-
error, no warning, and nothing an app could ask.
|
|
212
|
+
A Wayland compositor stops sending **frame callbacks** to a surface it is not
|
|
213
|
+
showing, and on that backend frame callbacks pace the frame loop. So a window
|
|
214
|
+
nobody is showing draws once and then stops, while React above it goes on
|
|
215
|
+
re-rendering into a tree that reaches no screen.
|
|
243
216
|
|
|
244
217
|
`presenting` is that state, published. It goes false after a couple of
|
|
245
218
|
seconds of silence with a repaint waiting, and true again on the very next
|
|
246
|
-
frame callback
|
|
247
|
-
|
|
248
|
-
|
|
219
|
+
frame callback, which the compositor sends the moment the surface is shown
|
|
220
|
+
again. It folds into `visible`, so an animation already paused on `visible`
|
|
221
|
+
pauses here too:
|
|
249
222
|
|
|
250
223
|
```jsx
|
|
251
224
|
const { visible, presenting } = useWindowState();
|
|
@@ -261,13 +234,10 @@ useEffect(() => {
|
|
|
261
234
|
const status = presenting ? 'live' : 'paused — this window is not on screen';
|
|
262
235
|
```
|
|
263
236
|
|
|
264
|
-
It is a signal to **stop work** on, not a frame-accurate one:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
question to ask there instead. The backend also says it once on stderr in
|
|
269
|
-
development, because "the app is alive and has drawn once" is otherwise an
|
|
270
|
-
unguessable state; see
|
|
237
|
+
It is a signal to **stop work** on, not a frame-accurate one: nothing that has
|
|
238
|
+
to be exact should read it. On X11, macOS and the headless mock it is `true`
|
|
239
|
+
and stays there; `obscured` is the question to ask there instead. The Wayland
|
|
240
|
+
backend also says it once on stderr in development; see
|
|
271
241
|
[wayland-backend.md](wayland-backend.md#a-surface-the-compositor-stops-showing-stops-drawing).
|
|
272
242
|
|
|
273
243
|
---
|
|
@@ -280,30 +250,26 @@ const away = useIdle(5 * 60_000);
|
|
|
280
250
|
```
|
|
281
251
|
|
|
282
252
|
Idleness is the **whole display's**, not this window's: it counts input on
|
|
283
|
-
every device, whichever application it went to. That is the right meaning for
|
|
284
|
-
presence indicator, an auto-save, or a dashboard that stops refreshing when
|
|
285
|
-
desk is empty — and the wrong one for "has the user ignored _my_ window",
|
|
286
|
-
is `useWindowState().focused`.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
`timeout` while the user is there and one every quarter of it, at most every
|
|
299
|
-
half minute, while they are away.
|
|
300
|
-
On an X display with neither extension it stays `false`.
|
|
253
|
+
every device, whichever application it went to. That is the right meaning for
|
|
254
|
+
a presence indicator, an auto-save, or a dashboard that stops refreshing when
|
|
255
|
+
the desk is empty — and the wrong one for "has the user ignored _my_ window",
|
|
256
|
+
which is `useWindowState().focused`.
|
|
257
|
+
|
|
258
|
+
- **Xorg**, with an `IDLETIME` counter: SYNC alarms fire when the counter
|
|
259
|
+
crosses `timeout` and when input pulls it back — **no timer at all**.
|
|
260
|
+
- **XQuartz**, with none: MIT-SCREEN-SAVER, polled on a schedule against the
|
|
261
|
+
remaining time rather than on a tick.
|
|
262
|
+
- **Windows**: `GetLastInputInfo`, the whole session's, on the same schedule.
|
|
263
|
+
- **macOS**: the session's idle time, on the same schedule — the bridge's
|
|
264
|
+
`lastInputMs()`, or else `ioreg`'s `HIDIdleTime`, a child process per check
|
|
265
|
+
(one per `timeout` while the user is there; one every quarter of it, at most
|
|
266
|
+
every half minute, while away).
|
|
267
|
+
- **An X display with neither extension**: always `false`.
|
|
301
268
|
|
|
302
269
|
Nothing is armed until a component subscribes, and the alarm or the poll
|
|
303
|
-
stops when the last subscriber to that `timeout` unmounts.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
inline from state that moves.
|
|
270
|
+
stops when the last subscriber to that `timeout` unmounts. `timeout` is a
|
|
271
|
+
dependency, so hold it in a constant rather than building it inline from state
|
|
272
|
+
that moves.
|
|
307
273
|
|
|
308
274
|
### Keeping the screen on
|
|
309
275
|
|
|
@@ -312,29 +278,25 @@ useKeepAwake(playing, 'Playing a video');
|
|
|
312
278
|
```
|
|
313
279
|
|
|
314
280
|
Held while the flag is true and the component is mounted, released on either —
|
|
315
|
-
including
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
`org.freedesktop.ScreenSaver
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
reads best as a sentence about the app's state rather than the app's name,
|
|
331
|
-
which they already show. Windows takes no string with the call —
|
|
332
|
-
`powercfg /requests` names the process — so there it is dropped rather than
|
|
333
|
-
put somewhere the system will not show it.
|
|
281
|
+
including an unmount mid-playback, the case that leaves a desktop permanently
|
|
282
|
+
un-blanking when this is done by hand.
|
|
283
|
+
|
|
284
|
+
- **Windows**: `SetThreadExecutionState` with `ES_CONTINUOUS`
|
|
285
|
+
([windows-integrations.md](windows-integrations.md)) — the display **and**
|
|
286
|
+
the system; counted, so one caller finishing does not release another's.
|
|
287
|
+
- **Elsewhere**: the first that answers of the desktop portal's `Inhibit`,
|
|
288
|
+
`org.freedesktop.ScreenSaver`, and `ScreenSaverSuspend` (a plain X request,
|
|
289
|
+
no session bus). These inhibit **screen blanking only** — nothing stops a
|
|
290
|
+
suspend.
|
|
291
|
+
|
|
292
|
+
`reason` is shown by desktops that list what is holding the screen on, so
|
|
293
|
+
write it as a sentence about the app's state rather than the app's name, which
|
|
294
|
+
they already show. Windows takes no string — `powercfg /requests` names the
|
|
295
|
+
process — so there it is dropped.
|
|
334
296
|
|
|
335
297
|
Failure is silent by design: a machine with no portal, no screensaver service
|
|
336
|
-
and no MIT-SCREEN-SAVER cannot be asked
|
|
337
|
-
|
|
298
|
+
and no MIT-SCREEN-SAVER cannot be asked. `keepAwake()` is the imperative twin,
|
|
299
|
+
and resolves to the release.
|
|
338
300
|
|
|
339
301
|
---
|
|
340
302
|
|
|
@@ -356,36 +318,28 @@ const { capsLock, layout } = useKeyboardState();
|
|
|
356
318
|
| `layout` | that group's layout code, `'ru'` — or null |
|
|
357
319
|
| `layouts` | every configured layout, `['us', 'ru']` |
|
|
358
320
|
|
|
359
|
-
The
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
while it is merely focused and before anything has been typed.
|
|
321
|
+
The locks are true of the **keyboard**, not of an event: a key event carries
|
|
322
|
+
the modifier state at the moment it was pressed, which is no help to a
|
|
323
|
+
password field that wants to warn while merely focused, before anything has
|
|
324
|
+
been typed.
|
|
364
325
|
|
|
365
|
-
The layout
|
|
366
|
-
|
|
367
|
-
identical to a wrong password. `layout` is a code rather than a description
|
|
326
|
+
The layout is for a status-bar indicator, and for a password typed in the
|
|
327
|
+
wrong script, which looks identical to a wrong password. `layout` is a code
|
|
368
328
|
(`'ru'`, not `'Russian'`) because that is what an indicator shows; uppercase it
|
|
369
329
|
for display.
|
|
370
330
|
|
|
371
331
|
Everything here needs **XKB**, which Xorg and XQuartz both have. Where it is
|
|
372
|
-
missing the locks read false and `layouts` is empty — so `layouts` is the
|
|
373
|
-
to check when
|
|
374
|
-
|
|
375
|
-
the keyboard.
|
|
332
|
+
missing the locks read false and `layouts` is empty — so `layouts` is the
|
|
333
|
+
field to check when "off" and "not known" differ. Changes arrive with no
|
|
334
|
+
polling, even while another application has the keyboard.
|
|
376
335
|
|
|
377
336
|
The [native Wayland backend](wayland-backend.md) answers the same fields from
|
|
378
|
-
its seat
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
Which bit is which lock is worth writing down: Caps Lock is `LockMask`, fixed
|
|
386
|
-
by the core protocol, and **Num Lock is not fixed by anything** — it is wherever
|
|
387
|
-
the modifier map puts it, which is `Mod2` on every Linux and BSD desktop, on
|
|
388
|
-
XQuartz, and in every toolkit that hardcodes it. This takes the convention.
|
|
337
|
+
its seat. A compositor tells only the focused client, so a lock toggled while
|
|
338
|
+
another app has the keyboard is seen when focus returns — for a password
|
|
339
|
+
field, the window is focused already.
|
|
340
|
+
|
|
341
|
+
Caps Lock is `LockMask`; Num Lock is taken to be `Mod2`, where every Linux and
|
|
342
|
+
BSD desktop and XQuartz put it.
|
|
389
343
|
|
|
390
344
|
---
|
|
391
345
|
|
|
@@ -405,53 +359,45 @@ const { doubleClickMs, dragThreshold } = useDesktopSettings();
|
|
|
405
359
|
| `animations` | **false means reduce motion** — see below |
|
|
406
360
|
| `source` | `'xsettings'`, `'portal'`, `'macos'`, or null where nothing answered |
|
|
407
361
|
|
|
408
|
-
**The built-in controls already follow these.** `<textinput>`'s caret, the
|
|
409
|
-
counting behind `ev.detail`, and the drag threshold all read the same
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
`
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
`org.gnome.desktop.
|
|
435
|
-
`
|
|
436
|
-
(
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
on a 1200ms cycle and drags at 8px: the point is to follow a desktop that
|
|
448
|
-
expressed a preference, not to change what happens on one that did not.
|
|
449
|
-
|
|
450
|
-
One seam worth knowing about. `createRoot()` starts XSETTINGS but does not
|
|
451
|
-
**await** it — five round trips on every app's startup path, to answer a
|
|
452
|
-
question that is not asked until the first interaction, is a bad trade. A field
|
|
453
|
-
focused on the very first frame therefore gets the built-in default and the
|
|
454
|
-
desktop's cadence from its next focus onward.
|
|
362
|
+
**The built-in controls already follow these.** `<textinput>`'s caret, the
|
|
363
|
+
click counting behind `ev.detail`, and the drag threshold all read the same
|
|
364
|
+
values. The hook is for an app drawing its own: a `<canvas>` with a text
|
|
365
|
+
cursor in it, a custom gesture, a diagram editor with its own drag.
|
|
366
|
+
|
|
367
|
+
`animations` is the desktop's "reduce motion" — GTK's
|
|
368
|
+
`gtk-enable-animations` on X11. **Looping animations already
|
|
369
|
+
honour it** — a [`style.animation`](styling.md#loops) never starts while it is
|
|
370
|
+
false, so `<ProgressBar indeterminate>` and anything else built on the loop
|
|
371
|
+
primitive comes to rest. Transitions are left alone: a 120ms fade that ends is
|
|
372
|
+
not the unbounded motion the setting exists for. Read the field where the
|
|
373
|
+
_resting_ look is your decision — a spinner that does not spin should still
|
|
374
|
+
say it is working, which `<ProgressBar indeterminate>` does by filling its
|
|
375
|
+
track instead.
|
|
376
|
+
|
|
377
|
+
`caretBlink` is not cosmetic: a moving thing on screen is what some people
|
|
378
|
+
cannot read past. **Draw the caret solid rather than not at all** — that is
|
|
379
|
+
what the built-in controls do.
|
|
380
|
+
|
|
381
|
+
Where the values come from:
|
|
382
|
+
|
|
383
|
+
- **X11** (`'xsettings'`): [XSETTINGS](appearance.md#what-each-rung-can-actually-answer),
|
|
384
|
+
the only source; there is no standard portal interface for any of this.
|
|
385
|
+
- **[Native Wayland](wayland-backend.md)** (`'portal'`): the settings portal's
|
|
386
|
+
GNOME keys — `org.gnome.desktop.interface` (`cursor-blink`,
|
|
387
|
+
`cursor-blink-time`, `enable-animations`) and
|
|
388
|
+
`org.gnome.desktop.peripherals.mouse` (`double-click`, `drag-threshold`) —
|
|
389
|
+
live, while `createRoot({ desktop })` leaves `appearance` on. A portal that
|
|
390
|
+
does not pass them through (KDE's, a bare compositor's) leaves the defaults.
|
|
391
|
+
- **macOS** (`'macos'`): **Reduce motion** only (System Settings ›
|
|
392
|
+
Accessibility › Display), as `animations: false`, live — flipping it stops
|
|
393
|
+
a spinner already going round. The other fields keep the defaults.
|
|
394
|
+
- **No settings daemon** — a bare `startx`, most window managers, XQuartz
|
|
395
|
+
(`null`): this renderer's own defaults above, deliberately not GTK's 1200ms
|
|
396
|
+
blink and 8px drag.
|
|
397
|
+
|
|
398
|
+
`createRoot()` starts XSETTINGS but does not wait for it, so a field focused
|
|
399
|
+
on the very first frame gets the built-in default, and the desktop's cadence
|
|
400
|
+
from its next focus onward.
|
|
455
401
|
|
|
456
402
|
---
|
|
457
403
|
|
|
@@ -487,8 +433,8 @@ covers a key with no default.
|
|
|
487
433
|
|
|
488
434
|
The `appId` is a reverse-DNS name no other app uses, the shape
|
|
489
435
|
[`registerApplication`](uri-schemes.md) takes; the store registers nothing
|
|
490
|
-
with it. Creating the store twice with the same id
|
|
491
|
-
evaluates again or in two call sites
|
|
436
|
+
with it. Creating the store twice with the same id — in a module a hot reload
|
|
437
|
+
evaluates again, or in two call sites — is one store with one set of values.
|
|
492
438
|
|
|
493
439
|
**It is read synchronously, the first time a value is asked for**, so the
|
|
494
440
|
first render already has what was saved rather than the defaults and then a
|
|
@@ -497,15 +443,15 @@ jump. It is written asynchronously:
|
|
|
497
443
|
- **coalesced** — a quarter second after the last change, and at least once a
|
|
498
444
|
second while changes keep coming, so a drag on a slider is a few writes and
|
|
499
445
|
not one per pointer move;
|
|
500
|
-
- **atomically** — into a temporary file
|
|
501
|
-
|
|
446
|
+
- **atomically** — into a temporary file renamed over the old one, so a crash
|
|
447
|
+
mid-write leaves the file as it was;
|
|
502
448
|
- **on exit** — whatever is still unsaved when the process exits is written
|
|
503
|
-
on the way out, including a change whose write was in flight.
|
|
504
|
-
settings.flush()` writes it now.
|
|
449
|
+
on the way out, including a change whose write was in flight.
|
|
450
|
+
`await settings.flush()` writes it now.
|
|
505
451
|
|
|
506
452
|
Outside React, `settings.get(key)`, `settings.set(key, value)`,
|
|
507
453
|
`settings.reset(key)` — the default again — and `settings.subscribe(listener)`
|
|
508
|
-
are the same store. A value is whatever JSON can keep
|
|
454
|
+
are the same store. A value is whatever JSON can keep; one it cannot, a
|
|
509
455
|
function or a `BigInt`, is a `TypeError` at `set`. A file that is not JSON is
|
|
510
456
|
reported once and the defaults stand; the next change replaces it.
|
|
511
457
|
|
|
@@ -532,17 +478,14 @@ const { locale, weekStartsOn } = useLocale();
|
|
|
532
478
|
| `timeZone` | `'Europe/London'`, or null |
|
|
533
479
|
| `source` | `'env'` when `LANG` and friends said, `'intl'` when ICU did |
|
|
534
480
|
|
|
535
|
-
**`LC_ALL` / `LC_MESSAGES` / `LANG` win over `Intl`'s own answer
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
app actually said. `C` and `POSIX` are not locales but the _absence_ of one, so
|
|
540
|
-
they fall through to ICU rather than becoming a tag.
|
|
481
|
+
**`LC_ALL` / `LC_MESSAGES` / `LANG` win over `Intl`'s own answer**, which is
|
|
482
|
+
whatever ICU has compiled in — a small-ICU build of node reports `en-US`
|
|
483
|
+
everywhere. `C` and `POSIX` are the _absence_ of a locale, so they fall
|
|
484
|
+
through to ICU rather than becoming a tag.
|
|
541
485
|
|
|
542
|
-
**This does not change while the app runs**, and there is
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
_next_ login and nothing this process can observe has moved.
|
|
486
|
+
**This does not change while the app runs**, and there is no subscription
|
|
487
|
+
behind it: the environment is fixed at exec, so a desktop that switches
|
|
488
|
+
language does so for the _next_ login.
|
|
546
489
|
|
|
547
490
|
For text direction inside a component prefer `useDirection()` over
|
|
548
491
|
`useLocale().direction` — it also honours `<ThemeProvider direction>` and the
|
|
@@ -554,46 +497,23 @@ an app started under `LANG=ar_EG.UTF-8` mirrors without being told to.
|
|
|
554
497
|
|
|
555
498
|
## What is not here
|
|
556
499
|
|
|
557
|
-
**Network status
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
([
|
|
566
|
-
[
|
|
567
|
-
|
|
568
|
-
([
|
|
569
|
-
freedesktop
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
`useDesktopCalendarEvents()`, keyed the way a calendar grid asks. It is not a
|
|
578
|
-
system _setting_, which is why it is its own page rather than a hook here,
|
|
579
|
-
but it is the same kind of thing as the rest of this one: something the
|
|
580
|
-
desktop knows that an app would otherwise ask the user to type in again.
|
|
581
|
-
|
|
582
|
-
**A launcher badge, and permission prompts.** Both are built, and neither is
|
|
583
|
-
a system hook: an unread count on the icon (freedesktop
|
|
584
|
-
`com.canonical.Unity.LauncherEntry`, one D-Bus signal; macOS
|
|
585
|
-
`dockTile.badgeLabel`; a taskbar overlay icon on Windows) is `useBadge()` in
|
|
586
|
-
[desktop.md](desktop.md#the-badge), and camera/mic/location authorization is
|
|
587
|
-
`usePermission()` in [permissions.md](permissions.md) — macOS TCC today, with
|
|
588
|
-
the freedesktop device portals still
|
|
589
|
-
[#466](https://github.com/sidorares/react-x11/issues/466)'s remaining half. Attention (the taskbar urgency / Dock
|
|
590
|
-
bounce) is `states: ['demands_attention']`
|
|
591
|
-
([desktop.md](desktop.md#attention)).
|
|
592
|
-
|
|
593
|
-
**Session lifecycle** — "we are suspending, save now", and "I have unsaved work,
|
|
594
|
-
do not log out yet". The X-native answer (XSMP) is effectively dead and the live
|
|
595
|
-
one is logind. The shape worth having is probably not a system hook but a
|
|
596
|
-
`useBeforeQuit()` that unifies it with `<window onCloseRequest>`. On the cocoa
|
|
597
|
-
backend the second half already lands there: a quit request from the Dock,
|
|
598
|
-
⌘Q or a logout is routed to the primary window's `onCloseRequest`
|
|
599
|
-
([uri-schemes.md](uri-schemes.md), "On macOS").
|
|
500
|
+
- **Network status** — not built: there is no X-level answer, and a failed
|
|
501
|
+
request is a better truth than a flag that says the link is up.
|
|
502
|
+
- **Battery, brightness, volume, media keys** — D-Bus services a third party
|
|
503
|
+
can wrap without anything from here ([dbus.md](dbus.md)).
|
|
504
|
+
- **Elsewhere:** notifications ([notifications.md](notifications.md)); the
|
|
505
|
+
tray, [`useTray()`](desktop.md#the-tray); a launcher badge,
|
|
506
|
+
[`useBadge()`](desktop.md#the-badge); attention — the taskbar urgency or
|
|
507
|
+
Dock bounce — `states: ['demands_attention']`
|
|
508
|
+
([desktop.md](desktop.md#attention)); the user's calendar,
|
|
509
|
+
`useDesktopCalendarEvents()` ([desktop-calendar.md](desktop-calendar.md));
|
|
510
|
+
camera/mic/location authorization, `usePermission()`
|
|
511
|
+
([permissions.md](permissions.md)) — macOS TCC today, not yet the
|
|
512
|
+
freedesktop device portals.
|
|
513
|
+
- **Session lifecycle** — "we are suspending, save now", "I have unsaved work,
|
|
514
|
+
do not log out yet" — not built. On the cocoa backend a quit request from
|
|
515
|
+
the Dock, ⌘Q or a logout already reaches the primary window's
|
|
516
|
+
`onCloseRequest` ([uri-schemes.md](uri-schemes.md), "On macOS").
|
|
517
|
+
|
|
518
|
+
Why, and what was considered:
|
|
519
|
+
[design/system.md](../design/system.md#what-is-not-here).
|