react-x11 2.52.0 → 2.54.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.
Files changed (115) hide show
  1. package/README.md +10 -10
  2. package/docs/README.md +160 -314
  3. package/docs/accessibility.md +176 -229
  4. package/docs/appearance.md +77 -176
  5. package/docs/click-to-component.md +18 -51
  6. package/docs/clipboard.md +40 -59
  7. package/docs/components.md +518 -1036
  8. package/docs/dbus.md +61 -133
  9. package/docs/debugging.md +152 -245
  10. package/docs/desktop-calendar.md +51 -88
  11. package/docs/desktop.md +222 -347
  12. package/docs/devtools.md +50 -167
  13. package/docs/drag-and-drop.md +126 -204
  14. package/docs/ecosystem/animation.md +91 -125
  15. package/docs/ecosystem/data-fetching.md +38 -53
  16. package/docs/ecosystem/dev-tooling.md +159 -202
  17. package/docs/ecosystem/forms.md +61 -97
  18. package/docs/ecosystem/headless.md +64 -89
  19. package/docs/ecosystem/i18n.md +18 -21
  20. package/docs/ecosystem/icons.md +93 -126
  21. package/docs/ecosystem/layout.md +42 -52
  22. package/docs/ecosystem/routing.md +18 -29
  23. package/docs/ecosystem/state.md +71 -88
  24. package/docs/ecosystem/testing.md +131 -157
  25. package/docs/ecosystem/theming.md +119 -165
  26. package/docs/ecosystem.md +145 -179
  27. package/docs/elements.md +1096 -1774
  28. package/docs/embedding.md +83 -132
  29. package/docs/events.md +412 -695
  30. package/docs/extending.md +784 -1326
  31. package/docs/eyedropper.md +63 -122
  32. package/docs/filedialog.md +71 -107
  33. package/docs/frame.md +195 -339
  34. package/docs/gl.md +65 -62
  35. package/docs/globalmenu.md +63 -146
  36. package/docs/macos.md +531 -3234
  37. package/docs/notifications.md +33 -47
  38. package/docs/packaging.md +166 -309
  39. package/docs/permissions.md +9 -14
  40. package/docs/react-features.md +81 -106
  41. package/docs/remote.md +32 -76
  42. package/docs/scale.md +72 -186
  43. package/docs/styling.md +329 -547
  44. package/docs/system.md +200 -280
  45. package/docs/testing.md +63 -83
  46. package/docs/typescript.md +6 -48
  47. package/docs/uri-schemes.md +67 -107
  48. package/docs/wayland-backend.md +200 -459
  49. package/docs/windows-integrations.md +75 -131
  50. package/docs/windows.md +343 -1367
  51. package/package.json +3 -2
  52. package/src/Reconciler.js +2 -2
  53. package/src/a11y.js +1 -1
  54. package/src/backend/context2d.js +3 -3
  55. package/src/bootstrap.js +1 -1
  56. package/src/cocoa/app.js +6 -6
  57. package/src/cocoa/bezels.js +1 -1
  58. package/src/cocoa/fonts.js +1 -1
  59. package/src/cocoa/globalmenu.js +1 -1
  60. package/src/cocoa/keymap.js +1 -1
  61. package/src/cocoa/panewindow.js +1 -1
  62. package/src/cocoa/player.js +1 -1
  63. package/src/cocoa/presenter.js +2 -2
  64. package/src/cocoa/promotion.js +1 -1
  65. package/src/cocoa/relaunch.js +1 -1
  66. package/src/cocoa/threaded.js +1 -1
  67. package/src/cocoa/video.js +1 -1
  68. package/src/cocoa/window.js +4 -4
  69. package/src/components/Menu.js +7 -2
  70. package/src/components/Switch.js +1 -1
  71. package/src/components/native.js +1 -1
  72. package/src/dnd.js +2 -2
  73. package/src/glnodes.js +1 -1
  74. package/src/grid.js +1 -1
  75. package/src/imagedecode-worker.js +32 -0
  76. package/src/imagedecode.js +140 -2
  77. package/src/layouts.js +1 -1
  78. package/src/nodes/animation.js +1 -1
  79. package/src/nodes/invalidate.js +1 -1
  80. package/src/nodes/layouthost.js +1 -1
  81. package/src/nodes/video.js +19 -8
  82. package/src/nodes/window/droptarget.js +1 -1
  83. package/src/ntk.d.ts +12 -0
  84. package/src/pacing.js +1 -1
  85. package/src/refresh/index.js +2 -1
  86. package/src/types/video.d.ts +5 -2
  87. package/src/videobackend.js +28 -0
  88. package/src/videoframes.js +29 -16
  89. package/src/wayland/app.js +27 -0
  90. package/src/wayland/video.js +376 -0
  91. package/src/win32/a11y.js +1 -1
  92. package/src/win32/app.js +2 -2
  93. package/src/win32/fonts.js +1 -1
  94. package/src/win32/glarea.js +4 -4
  95. package/src/win32/overlay.js +1 -1
  96. package/src/win32/panewindow.js +3 -3
  97. package/src/win32/shell.js +1 -1
  98. package/src/win32/window.js +2 -2
  99. package/src/windowid.js +1 -1
  100. package/src/xvideo.js +189 -0
  101. package/docs/architecture/accessibility.md +0 -470
  102. package/docs/architecture/animation.md +0 -707
  103. package/docs/architecture/container-queries.md +0 -525
  104. package/docs/architecture/custom-layout.md +0 -334
  105. package/docs/architecture/drag-and-drop.md +0 -941
  106. package/docs/architecture/element-layer-contents.md +0 -622
  107. package/docs/architecture/frame-pacing.md +0 -255
  108. package/docs/architecture/grid-layout.md +0 -358
  109. package/docs/architecture/protocol-efficiency.md +0 -357
  110. package/docs/architecture/video.md +0 -1149
  111. package/docs/architecture/windowed-regions.md +0 -854
  112. package/docs/glx.md +0 -137
  113. package/docs/wayland.md +0 -545
  114. package/docs/windows-embedding.md +0 -271
  115. 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
- Six hooks for the things an application has to read off the system rather than
4
- work out for itself: the monitors, what the window manager did with the
5
- window, whether anyone is at the desk, the state of the keyboard, how the
6
- desktop wants an app to feel, and the language it was launched in — and a
7
- store for what the app remembers itself, between launches.
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, and it is worth knowing before reading any one of them:
22
+ They share a shape:
23
23
 
24
- - **Nothing is asked of the server until a component asks.** Every one of
25
- these costs round trips or an event selection, and an app that never calls
26
- them pays for none of it.
27
- - **Every one has an answer with no extension present.** X is a protocol with
28
- thirty years of optional pieces, and "this display cannot say" is a real
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), which has a ladder of its own.
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
- a retina lid and an office monitor genuinely differ. The root's own scale
71
- is the primary monitor's; see [scale.md](scale.md).
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**, which is one round trip; the names, the primary flag and the
77
- physical sizes take a **RandR** walk — `GetScreenResourcesCurrent`, then a
78
- `GetOutputInfo` and a `GetCrtcInfo` per output, ten or more round trips on an
79
- ordinary two-head desktop — and that deliberately does not hold startup up.
80
-
81
- So a component can render once with `source: 'xinerama'` and null names, and
82
- again a moment later with `source: 'randr'` and real ones. The rects do not
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 docks and panels that reserved space —
95
- is published **for the whole virtual desktop**, not per monitor. That is an
96
- EWMH weakness rather than a choice made here. So it is applied as a _per-axis_
97
- bound on top of the monitor rect: exact on one head, and on several it still
98
- takes a top or bottom panel off the height without pretending to know which
99
- head the panel is on.
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
- The protocol has no primary — the entry at the origin is flagged — and no
112
- work area: `available` is each monitor's own rect, less what the compositor
113
- told a window on it to fit in (`xdg_toplevel.configure_bounds`, the monitor
114
- minus its panels), so it is exact per monitor, as on macOS, once a window has
115
- appeared there. `scale` is the monitor's own, the fraction under fractional
116
- scaling. `source` reads `'test'` on both native backends — the stamp of the
117
- seam they publish through.
118
-
119
- **On macOS it is exact.** `NSScreen.visibleFrame` is per screen already — that
120
- display's own menu bar and Dock taken off — so each monitor carries its real
121
- usable rect and no approximation runs. `workArea` there is still one rect,
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
- Every other backend is told: RandR sends an event, a `wl_output` announces
130
- itself. macOS has no such event for a client — the bridge keeps its
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**, which is where a stale rect would be visible — a
135
- popup flipped and clamped into a monitor that has moved, an auto-sized
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 for an
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 share one CRTC and are reported as **one**
151
- monitor, with both names in `outputs`. A laptop mirroring to a projector is one
152
- screen to put a window on, and `screens.length === 2` there would be a wrong
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**. The
186
- two part company routinely — a user hits a maximize hotkey, a tiling WM
187
- declines a fullscreen request — and an app that mirrors the state in its own
188
- UI has nowhere else to read it.
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 (@windowkit/appkit 0.30.0): `fullscreen` is the title
192
- bar's green button, or `<window fullscreen>`, `maximized` the zoom
193
- (`maximized_vert` and `maximized_horz` together), `minimized` the Dock
194
- (`hidden`), and `states` lists `focused` for the key window. `fullscreen`
195
- turns as AppKit's transition **begins**, before the animation, so a toolbar an
196
- app hides in full screen goes while the window grows rather than after it:
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 — and on a bridge older than 0.30.0 only `focused` moves.
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 for
217
- a round trip. A window opens focused and on screen far more often than not, and
218
- a title bar that renders dimmed on frame one and un-dims on frame two is a
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, and `VisibilityNotify` says so. One is running on every stock
226
- GNOME and KDE session. That is X working as designed and there is no protocol
227
- answer to it.
228
-
229
- So `obscured` is a bare-WM optimisation, and `visible` — which folds in
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 will not tell you a surface is covered either — but it
237
- stops sending it **frame callbacks**, and on that backend a frame callback is
238
- the only thing that paces the frame loop. So a window nobody is showing draws
239
- once and then stops, while React above it goes on re-rendering into a tree
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 — the compositor still holds the one we asked for, and answers
247
- it the moment the surface is shown again. It folds into `visible`, so an
248
- animation already paused on `visible` pauses here too:
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: two seconds of
265
- silence is what makes it certain, so nothing that has to be exact should read
266
- it. On X11, macOS and the headless mock it is `true` and stays there — their
267
- frame clocks do not depend on a compositor's goodwill, and `obscured` is the
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 a
284
- presence indicator, an auto-save, or a dashboard that stops refreshing when the
285
- desk is empty — and the wrong one for "has the user ignored _my_ window", which
286
- is `useWindowState().focused`.
287
-
288
- Where the X server carries an `IDLETIME` counter — Xorg does — this costs **no
289
- timer at all**: a SYNC alarm fires when the counter crosses `timeout` and
290
- another when input pulls it back down, which is what every idle daemon on the
291
- desktop is built on. On a server without one (XQuartz) it falls back to polling
292
- MIT-SCREEN-SAVER, scheduled against the remaining time rather than on a tick —
293
- and on win32, `GetLastInputInfo` on the same schedule, which answers for the
294
- whole session the way the X counter does. On macOS it is the same schedule
295
- over the session's idle time: the `@windowkit/appkit` bridge's `lastInputMs()`
296
- (CoreGraphics' count, a call) where it has one, and otherwise `ioreg`'s
297
- `HIDIdleTime` — a short-lived child process per check, which is one per
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
- `timeout` is a dependency, so hold it in a constant rather than building it
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 on an unmount mid-playback, which is the case that leaves a desktop
316
- permanently un-blanking when this is done by hand.
317
-
318
- Four rungs. On [win32](windows-integrations.md), `SetThreadExecutionState`
319
- with `ES_CONTINUOUS`, which is what makes it a state rather than a one-shot
320
- nudge, and which is counted here so that one caller finishing does not speak
321
- for another. Elsewhere: the settings portal's `Inhibit`, the older
322
- `org.freedesktop.ScreenSaver` service, and `ScreenSaverSuspend`, which is a
323
- plain X request and needs no session bus at all. **The three freedesktop
324
- rungs inhibit screen blanking only.** Nothing there stops a suspend — only
325
- the portal rung could, and a seam that works on one desktop in three is worse
326
- than not offering it. The Windows rung asks for both the display and the
327
- system, which is the same call either way.
328
-
329
- `reason` is shown by desktops that list what is holding the screen on, so it
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, and a video player is not the place to
337
- surface that. `keepAwake()` is the imperative twin, and resolves to the release.
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 point of the first row is that it is true of the **keyboard** rather than
360
- of an event. A key event carries the modifier state at the moment it was
361
- pressed, so an app can see that Caps Lock was on for a key it received — which
362
- is no help at all in the case that matters, a password field that wants to warn
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 half is for a status-bar indicator, and for the other thing that
366
- costs people real time: a password typed in the wrong script, which looks
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 field
373
- to check when the difference between "off" and "not known" matters. Changes
374
- arrive as `XkbStateNotify`, with no polling, even while another application has
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: the locks and the group from `wl_keyboard.modifiers`, the layout
379
- codes from the keymap the compositor sent. One difference is the protocol's
380
- rather than this library's — a compositor tells only the focused client, so a
381
- lock toggled while another app has the keyboard is seen when focus returns.
382
- For a password field, the case this hook exists for, the window is focused
383
- already.
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 click
409
- counting behind `ev.detail`, and the drag threshold all read the same values, so
410
- an app that uses the widget set gets this for nothing. The hook is for an app
411
- drawing its own: a `<canvas>` with a text cursor in it, a custom gesture, a
412
- diagram editor with its own drag. Reach for it so that one widget on the screen
413
- does not feel unlike every other.
414
-
415
- `animations` is the desktop's "reduce motion", from GTK's
416
- `gtk-enable-animations`. **Looping animations already honour it** — a
417
- [`style.animation`](styling.md#loops) never starts while it is false, so
418
- `<ProgressBar indeterminate>` and anything else built on the loop primitive
419
- comes to rest with nothing to do. Transitions are left alone: a 120ms fade
420
- that ends is not the unbounded motion the setting exists for. Read the field
421
- where the _resting_ look is a design decision your app has to make — a
422
- spinner that does not spin should still say it is working, which is what
423
- `<ProgressBar indeterminate>` does by filling its track instead.
424
-
425
- `caretBlink` deserves the callout. A caret that ignores it is not a cosmetic
426
- miss: a moving thing on screen is what some people cannot read past, which is
427
- why the desktop offers the switch at all. **Draw the caret solid rather than not
428
- at all** — that is what the built-in controls do.
429
-
430
- [XSETTINGS](appearance.md#what-each-rung-can-actually-answer) is the only source on X11; there is no
431
- standard portal interface for any of this. On the
432
- [native Wayland backend](wayland-backend.md), which has no XSETTINGS to read,
433
- they come off the settings portal's GNOME namespaces instead —
434
- `org.gnome.desktop.interface` (`cursor-blink`, `cursor-blink-time`,
435
- `enable-animations`) and `org.gnome.desktop.peripherals.mouse`
436
- (`double-click`, `drag-threshold`), the same GSettings keys gsd-xsettings
437
- translates for X11 — with `source: 'portal'`, live, and only while
438
- `createRoot({ desktop })` leaves `appearance` on. A portal that does not pass
439
- those through (KDE's, a bare compositor's) leaves the defaults. On the macOS backend the one of these the
440
- system publishes is read from it: **Reduce motion** (System Settings ›
441
- Accessibility › Display) arrives as `animations: false` with
442
- `source: 'macos'`, live — flipping the switch stops a spinner that is already
443
- going round — and the other fields keep the defaults there, since macOS has
444
- no equivalent to hand over. On a desktop with no settings daemon — a bare
445
- `startx`, most window managers, XQuartz — `source` is null and the numbers above
446
- are this renderer's own defaults. They are deliberately not GTK's, which blinks
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, in a module a hot reload
491
- evaluates again or in two call sites, is one store with one set of values.
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 that is then renamed over the old
501
- one, so a crash mid-write leaves the file as it was, never half a file;
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. `await
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, and one it cannot, a
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.** node resolves
536
- `Intl.DateTimeFormat().resolvedOptions().locale` through ICU, and ICU answers
537
- what it has compiled in — a small-ICU build reports `en-US` for every
538
- environment there is. The POSIX variables are what the person who launched the
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 deliberately no
543
- subscription behind it: a process's environment is fixed at exec and ICU's
544
- resolution with it, so a desktop that switches language announces it for the
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.** There is no X-level answer, so it is necessarily D-Bus or
558
- netlink, and the honest version of the API would be a hint rather than a fact —
559
- a failed request is always the better truth than a flag that says the link is
560
- up. Not ruled out; not built.
561
-
562
- **Battery, brightness, volume, media keys.** These are D-Bus services a third
563
- party can wrap without anything from here. Two things once on this list — the
564
- two desktop standards with a claim on core — are now built: **notifications**
565
- ([notifications.md](notifications.md)), and the **tray**, whose menu is
566
- [dbusmenu](globalmenu.md) this renderer already speaks — on the cocoa backend
567
- [`useTray()`](desktop.md) over `NSStatusItem`
568
- ([#463](https://github.com/sidorares/react-x11/issues/463)), and on a
569
- freedesktop session over `org.kde.StatusNotifierItem`
570
- ([#353](https://github.com/sidorares/react-x11/issues/353)) — D-Bus only, so
571
- the same rung serves X11, XWayland and Wayland — and on win32 over
572
- `Shell_NotifyIcon`.
573
-
574
- **The user's calendar.** Their real events, from the accounts the desktop
575
- already holds — EventKit on macOS, Evolution Data Server on a freedesktop
576
- session — are [desktop-calendar.md](desktop-calendar.md):
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).