react-x11 1.2.0 → 2.0.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 +330 -81
- package/package.json +132 -14
- package/src/ClickToComponent.js +9 -2
- package/src/DevToolsIntegration.js +440 -32
- package/src/Reconciler.js +463 -165
- package/src/a11y.js +1299 -0
- package/src/acceleratorhooks.js +86 -0
- package/src/accelerators.js +128 -0
- package/src/activate.js +168 -0
- package/src/anchor.js +360 -0
- package/src/appcontext.js +161 -0
- package/src/appearance.js +748 -0
- package/src/appearancehooks.js +96 -0
- package/src/apphooks.js +73 -0
- package/src/application.js +748 -0
- package/src/atspi.js +1953 -0
- package/src/bus.js +545 -0
- package/src/bushooks.js +136 -0
- package/src/clientmessage.js +140 -0
- package/src/clipboard.js +121 -0
- package/src/components/Button.js +148 -33
- package/src/components/Checkbox.js +66 -32
- package/src/components/Dialog.js +96 -29
- package/src/components/FileDialog.js +491 -0
- package/src/components/Icon.js +329 -0
- package/src/components/Menu.js +797 -133
- package/src/components/PasswordInput.js +434 -0
- package/src/components/ProgressBar.js +91 -12
- package/src/components/Radio.js +78 -35
- package/src/components/Select.js +255 -65
- package/src/components/Slider.js +102 -41
- package/src/components/SplitPane.js +206 -0
- package/src/components/Switch.js +100 -22
- package/src/components/Table.js +529 -0
- package/src/components/Tabs.js +211 -0
- package/src/components/Tooltip.js +528 -54
- package/src/components/anchor.js +154 -107
- package/src/components/change.js +34 -0
- package/src/components/dnd.js +103 -0
- package/src/components/index.js +16 -3
- package/src/components/keys.js +17 -20
- package/src/components/scribble.js +247 -0
- package/src/components/theme.js +350 -42
- package/src/components/typeahead.js +12 -2
- package/src/compose.js +868 -0
- package/src/compositing.js +224 -0
- package/src/dbusmenu.js +384 -0
- package/src/debug.d.ts +63 -0
- package/src/debug.js +642 -0
- package/src/decorations.js +486 -0
- package/src/desktopsettings.js +209 -0
- package/src/desktopsettingshooks.js +62 -0
- package/src/dnd.js +1725 -0
- package/src/editmenu.js +272 -0
- package/src/errors.js +98 -0
- package/src/events.js +1406 -162
- package/src/extensions.js +45 -0
- package/src/filedialog.js +375 -0
- package/src/filedialoghooks.js +132 -0
- package/src/fonthooks.js +64 -0
- package/src/fonts.js +301 -0
- package/src/foreignnodes.js +519 -0
- package/src/frame/child.js +33 -0
- package/src/frame/childmain.js +274 -0
- package/src/frame/env.js +140 -0
- package/src/frame/index.js +444 -0
- package/src/frame/lifecycle.js +67 -0
- package/src/frame/protocol.js +179 -0
- package/src/frames.js +73 -0
- package/src/glbackend.js +114 -0
- package/src/glnodes.js +125 -67
- package/src/globalmenu.js +683 -0
- package/src/host.d.ts +88 -0
- package/src/host.js +42 -0
- package/src/idle.js +490 -0
- package/src/idlehooks.js +100 -0
- package/src/imagesource.js +349 -0
- package/src/index.d.ts +475 -0
- package/src/index.js +83 -9
- package/src/inputtime.js +206 -0
- package/src/jsx-dev-runtime.d.ts +21 -0
- package/src/jsx-dev-runtime.js +2 -0
- package/src/jsx-runtime.d.ts +39 -0
- package/src/jsx-runtime.js +6 -0
- package/src/keyboard.js +256 -0
- package/src/keyboardstate.js +278 -0
- package/src/keyboardstatehooks.js +58 -0
- package/src/keysyms.d.ts +149 -0
- package/src/keysyms.js +270 -0
- package/src/locale.js +170 -0
- package/src/localehooks.js +47 -0
- package/src/menuitem.js +223 -0
- package/src/node.d.ts +597 -0
- package/src/node.js +44 -0
- package/src/nodes.js +9546 -690
- package/src/ntk.d.ts +44 -0
- package/src/ntk.js +25 -0
- package/src/paintcache.js +366 -0
- package/src/palette.js +380 -0
- package/src/pastestate.js +66 -0
- package/src/portal.js +461 -0
- package/src/priority.js +26 -0
- package/src/refresh/index.d.ts +40 -0
- package/src/refresh/index.js +122 -0
- package/src/refresh/loader.d.ts +37 -0
- package/src/refresh/loader.js +401 -0
- package/src/refresh/register.d.ts +5 -0
- package/src/refresh/register.js +13 -0
- package/src/registry.js +232 -0
- package/src/scale.js +626 -0
- package/src/scalehooks.js +27 -0
- package/src/screencolor.js +640 -0
- package/src/screencolorhooks.js +101 -0
- package/src/screens.js +754 -0
- package/src/screenshooks.js +137 -0
- package/src/startup.js +302 -0
- package/src/style.d.ts +126 -0
- package/src/style.js +33 -0
- package/src/styles.js +1482 -6
- package/src/svgnodes.js +306 -0
- package/src/testing/a11y.js +484 -0
- package/src/testing/components.js +414 -0
- package/src/testing/events.js +407 -0
- package/src/testing/harness.js +455 -0
- package/src/testing/index.d.ts +558 -0
- package/src/testing/index.js +85 -0
- package/src/testing/mock-app.js +463 -0
- package/src/testing/pixels.js +152 -0
- package/src/testing/queries.js +224 -0
- package/src/textrange.js +83 -0
- package/src/textselection.js +439 -0
- package/src/trace-registry.js +63 -0
- package/src/transfer.js +93 -0
- package/src/types/appearance.d.ts +84 -0
- package/src/types/application.d.ts +173 -0
- package/src/types/components.d.ts +820 -0
- package/src/types/dbus.d.ts +177 -0
- package/src/types/elements.d.ts +897 -0
- package/src/types/events.d.ts +549 -0
- package/src/types/filedialog.d.ts +260 -0
- package/src/types/fonts.d.ts +124 -0
- package/src/types/frame.d.ts +146 -0
- package/src/types/globalmenu.d.ts +42 -0
- package/src/types/nodes.d.ts +199 -0
- package/src/types/screencolor.d.ts +84 -0
- package/src/types/style.d.ts +434 -0
- package/src/types/system.d.ts +287 -0
- package/src/windowid.js +151 -0
- package/src/windowstate.js +393 -0
- package/src/xsettings.js +336 -0
- package/src/yoga.d.ts +55 -0
- package/src/yoga.js +135 -0
- package/src/components/Canvas3D.js +0 -28
- package/src/geometry3d.js +0 -223
- package/src/pointer3d.js +0 -158
- package/src/raycast3d.js +0 -146
- package/src/richnodes.js +0 -436
- package/src/scene3d.js +0 -683
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the machine around the app is doing — the monitors, the window
|
|
3
|
+
* manager, the keyboard, whether anyone is at the desk, and the conventions
|
|
4
|
+
* the app was launched under. See docs/system.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RefObject } from 'react';
|
|
8
|
+
|
|
9
|
+
import type { WindowStateName } from './elements.js';
|
|
10
|
+
import type { DrawnNode, NtkApp, NtkWindow, Rect } from './nodes.js';
|
|
11
|
+
|
|
12
|
+
// --------------------------------------------------------------------------
|
|
13
|
+
// Screens
|
|
14
|
+
// --------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Which tier answered.
|
|
18
|
+
*
|
|
19
|
+
* `'xinerama'` is the geometry-only answer that resolves during
|
|
20
|
+
* `createRoot()`; `'randr'` is the same geometry with names, the primary flag
|
|
21
|
+
* and physical sizes, and lands a moment later. `'screen'` is one entry
|
|
22
|
+
* covering the whole display, for a server with neither extension.
|
|
23
|
+
*/
|
|
24
|
+
export type ScreensSource = 'randr' | 'xinerama' | 'screen' | 'test' | null;
|
|
25
|
+
|
|
26
|
+
export interface Screen {
|
|
27
|
+
/** `'HDMI-1'`, `'eDP-1'` — **null** until RandR answers, and on a server
|
|
28
|
+
* without it. Treat null as "not known yet" rather than persisting it. */
|
|
29
|
+
readonly name: string | null;
|
|
30
|
+
/** Every output driving this monitor. Two names means it is mirrored. */
|
|
31
|
+
readonly outputs: readonly string[];
|
|
32
|
+
readonly x: number;
|
|
33
|
+
readonly y: number;
|
|
34
|
+
readonly width: number;
|
|
35
|
+
readonly height: number;
|
|
36
|
+
/**
|
|
37
|
+
* The monitor minus the panels — where a window can actually go.
|
|
38
|
+
*
|
|
39
|
+
* An approximation: `_NET_WORKAREA` is published for the whole virtual
|
|
40
|
+
* desktop rather than per monitor, so it is applied as a per-axis bound.
|
|
41
|
+
* Exact on one head; on several it still takes a top or bottom panel off
|
|
42
|
+
* the height.
|
|
43
|
+
*/
|
|
44
|
+
readonly available: Rect;
|
|
45
|
+
readonly primary: boolean;
|
|
46
|
+
readonly widthMM: number | null;
|
|
47
|
+
readonly heightMM: number | null;
|
|
48
|
+
/** Hz to two decimals (`59.99`), or null. */
|
|
49
|
+
readonly refreshRate: number | null;
|
|
50
|
+
readonly rotation: 0 | 90 | 180 | 270;
|
|
51
|
+
/**
|
|
52
|
+
* This monitor's own device-pixels-per-logical-pixel (docs/scale.md).
|
|
53
|
+
* Where the desktop configured one factor it is the root's on every
|
|
54
|
+
* entry; where the hardware answered, a retina lid and an office monitor
|
|
55
|
+
* really do differ, and an app that places windows can honour that.
|
|
56
|
+
* Geometry above is logical, like every rect the renderer hands out.
|
|
57
|
+
*/
|
|
58
|
+
readonly scale: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface Screens {
|
|
62
|
+
readonly screens: readonly Screen[];
|
|
63
|
+
/** The primary monitor — or the only one, where nothing is flagged. */
|
|
64
|
+
readonly primary: Screen | null;
|
|
65
|
+
/** The desktop-wide `_NET_WORKAREA`, or null. */
|
|
66
|
+
readonly workArea: Rect | null;
|
|
67
|
+
/** The whole virtual screen every monitor sits in. */
|
|
68
|
+
readonly virtual: Rect | null;
|
|
69
|
+
readonly source: ScreensSource;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The monitors this display has, live — re-renders when one is plugged in or
|
|
74
|
+
* unplugged, when the arrangement changes, and when a panel moves.
|
|
75
|
+
*
|
|
76
|
+
* ```tsx
|
|
77
|
+
* const { screens, primary } = useScreens();
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function useScreens(): Screens;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Device pixels per logical pixel for this root — `1` on an ordinary
|
|
84
|
+
* display, `2` on a retina panel, fractional on desktops configured to
|
|
85
|
+
* 1.25/1.5. Resolved once by `createRoot` (see `RootOptions.scale`,
|
|
86
|
+
* docs/scale.md) and static for the life of the root, so there is nothing
|
|
87
|
+
* to subscribe to.
|
|
88
|
+
*
|
|
89
|
+
* Everything the renderer hands an app is already logical; reach for this
|
|
90
|
+
* only to talk about device pixels deliberately — sizing detail in a
|
|
91
|
+
* `<canvas onDraw>` (whose payload carries the same number), or showing
|
|
92
|
+
* the factor in a settings pane.
|
|
93
|
+
*/
|
|
94
|
+
export function useScale(): number;
|
|
95
|
+
|
|
96
|
+
// --------------------------------------------------------------------------
|
|
97
|
+
// Window state
|
|
98
|
+
// --------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
export interface WindowState {
|
|
101
|
+
/** This window has the keyboard. */
|
|
102
|
+
readonly focused: boolean;
|
|
103
|
+
/** **The one to branch on**: not minimized, and not fully covered. */
|
|
104
|
+
readonly visible: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Fully covered by other windows — **always false when a compositing
|
|
107
|
+
* manager is running**, which it is on every stock GNOME and KDE session.
|
|
108
|
+
* A composited window is redirected offscreen and the server considers it
|
|
109
|
+
* entirely visible. Prefer `visible`.
|
|
110
|
+
*/
|
|
111
|
+
readonly obscured: boolean;
|
|
112
|
+
/** `_NET_WM_STATE_HIDDEN`: iconified, or shaded away. */
|
|
113
|
+
readonly minimized: boolean;
|
|
114
|
+
/** Both axes. One axis alone shows up in `states`. */
|
|
115
|
+
readonly maximized: boolean;
|
|
116
|
+
/** What the window manager actually did, not what `<window fullscreen>`
|
|
117
|
+
* asked for. */
|
|
118
|
+
readonly fullscreen: boolean;
|
|
119
|
+
/** The raw `_NET_WM_STATE` names, e.g. `['maximized_vert', 'focused']` —
|
|
120
|
+
* what the window manager put there, where `<window states>` is what was
|
|
121
|
+
* asked for. */
|
|
122
|
+
readonly states: readonly WindowStateName[];
|
|
123
|
+
/** The workspace index, or null. A sticky window shows up in `states`. */
|
|
124
|
+
readonly desktop: number | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* What the window manager has done with a window, live.
|
|
129
|
+
*
|
|
130
|
+
* ```tsx
|
|
131
|
+
* const { focused, visible, fullscreen } = useWindowState();
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* With no argument it reads the window the component is in, inferred the way
|
|
135
|
+
* {@link useTopLevelWindow} infers it. Pass a ref to be exact.
|
|
136
|
+
*/
|
|
137
|
+
export function useWindowState(
|
|
138
|
+
ref?: RefObject<NtkWindow | DrawnNode | null> | null,
|
|
139
|
+
): WindowState;
|
|
140
|
+
|
|
141
|
+
// --------------------------------------------------------------------------
|
|
142
|
+
// Idle and inhibition
|
|
143
|
+
// --------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Whether the user has been away from the keyboard for `timeout`
|
|
147
|
+
* milliseconds.
|
|
148
|
+
*
|
|
149
|
+
* ```tsx
|
|
150
|
+
* const away = useIdle(5 * 60_000);
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* Idleness is the whole display's, not this window's — it counts input on
|
|
154
|
+
* every device, whichever application it went to. For "has the user ignored
|
|
155
|
+
* *my* window", read `useWindowState().focused`.
|
|
156
|
+
*
|
|
157
|
+
* Costs no timer where the X server carries an `IDLETIME` counter (Xorg
|
|
158
|
+
* does): a SYNC alarm fires on each crossing. Falls back to polling
|
|
159
|
+
* MIT-SCREEN-SAVER, and stays `false` on a display with neither.
|
|
160
|
+
*/
|
|
161
|
+
export function useIdle(timeout: number): boolean;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Keep the screen awake while `active` is true, releasing on unmount.
|
|
165
|
+
*
|
|
166
|
+
* ```tsx
|
|
167
|
+
* useKeepAwake(playing, 'Playing a video');
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* Inhibits **screen blanking only** — nothing here stops a suspend. Silent
|
|
171
|
+
* where no rung is available.
|
|
172
|
+
*/
|
|
173
|
+
export function useKeepAwake(active: boolean, reason?: string): void;
|
|
174
|
+
|
|
175
|
+
export interface KeepAwakeOptions {
|
|
176
|
+
/** Shown by desktops that list what is holding the screen on. */
|
|
177
|
+
reason?: string;
|
|
178
|
+
/** The ntk connection, so the `ScreenSaverSuspend` rung can run — the one
|
|
179
|
+
* that needs no session bus. */
|
|
180
|
+
app?: NtkApp;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The imperative twin of {@link useKeepAwake}: resolves to the release.
|
|
185
|
+
*
|
|
186
|
+
* Never rejects — a machine with no portal, no screensaver service and no
|
|
187
|
+
* MIT-SCREEN-SAVER hands back a release that does nothing.
|
|
188
|
+
*/
|
|
189
|
+
export function keepAwake(options?: KeepAwakeOptions): Promise<() => void>;
|
|
190
|
+
|
|
191
|
+
// --------------------------------------------------------------------------
|
|
192
|
+
// Keyboard
|
|
193
|
+
// --------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
export interface KeyboardState {
|
|
196
|
+
/** On or off **now** — not "was on for the last key", which is what an
|
|
197
|
+
* event's modifier state can tell you. */
|
|
198
|
+
readonly capsLock: boolean;
|
|
199
|
+
readonly numLock: boolean;
|
|
200
|
+
/** The active XKB group, 0–3. */
|
|
201
|
+
readonly group: number;
|
|
202
|
+
/** That group's layout code — `'ru'` — or null. */
|
|
203
|
+
readonly layout: string | null;
|
|
204
|
+
/** Every configured layout, `['us', 'ru']`. Empty where XKB could not be
|
|
205
|
+
* asked, which is what distinguishes "off" from "not known". */
|
|
206
|
+
readonly layouts: readonly string[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The keyboard's locks and layout, live.
|
|
211
|
+
*
|
|
212
|
+
* ```tsx
|
|
213
|
+
* const { capsLock, layout } = useKeyboardState();
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* True of the keyboard rather than of an event, so a password field can warn
|
|
217
|
+
* about Caps Lock before the first character is typed. Needs XKB; the locks
|
|
218
|
+
* read false without it.
|
|
219
|
+
*/
|
|
220
|
+
export function useKeyboardState(): KeyboardState;
|
|
221
|
+
|
|
222
|
+
// --------------------------------------------------------------------------
|
|
223
|
+
// Desktop interaction settings
|
|
224
|
+
// --------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
export interface DesktopSettings {
|
|
227
|
+
/** **False means do not blink at all** — an accessibility setting, not a
|
|
228
|
+
* preference. Draw the caret solid rather than not at all. */
|
|
229
|
+
readonly caretBlink: boolean;
|
|
230
|
+
/** How long a caret stays in each state. The XSETTINGS key is a full
|
|
231
|
+
* cycle; this is half of it, which is what goes into a timer. */
|
|
232
|
+
readonly caretBlinkMs: number;
|
|
233
|
+
readonly doubleClickMs: number;
|
|
234
|
+
readonly doubleClickDistance: number;
|
|
235
|
+
/** How far a press moves before it is a drag rather than a click. */
|
|
236
|
+
readonly dragThreshold: number;
|
|
237
|
+
/** `'xsettings'`, or null where no settings daemon answered and these are
|
|
238
|
+
* the renderer's own defaults. */
|
|
239
|
+
readonly source: 'xsettings' | 'test' | null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* How this desktop wants an application to feel, live.
|
|
244
|
+
*
|
|
245
|
+
* ```tsx
|
|
246
|
+
* const { doubleClickMs, dragThreshold } = useDesktopSettings();
|
|
247
|
+
* ```
|
|
248
|
+
*
|
|
249
|
+
* The built-in controls already follow these. This is for an app drawing its
|
|
250
|
+
* own — a `<canvas>` with a text cursor, a custom gesture — so that one
|
|
251
|
+
* widget on the screen does not feel unlike every other.
|
|
252
|
+
*/
|
|
253
|
+
export function useDesktopSettings(): DesktopSettings;
|
|
254
|
+
|
|
255
|
+
// --------------------------------------------------------------------------
|
|
256
|
+
// Locale
|
|
257
|
+
// --------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
export interface SystemLocale {
|
|
260
|
+
/** A BCP-47 tag: `'en-GB'`, `'ru-RU'`. */
|
|
261
|
+
readonly locale: string;
|
|
262
|
+
readonly direction: 'ltr' | 'rtl';
|
|
263
|
+
/** `0` Sunday … `6` Saturday, from CLDR. */
|
|
264
|
+
readonly weekStartsOn: number;
|
|
265
|
+
readonly timeZone: string | null;
|
|
266
|
+
/** `'env'` when `LANG` and friends said, `'intl'` when ICU did. */
|
|
267
|
+
readonly source: 'env' | 'intl' | 'test' | null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Which language and conventions this app was started in.
|
|
272
|
+
*
|
|
273
|
+
* ```tsx
|
|
274
|
+
* const { locale, weekStartsOn } = useLocale();
|
|
275
|
+
* ```
|
|
276
|
+
*
|
|
277
|
+
* `LC_ALL`/`LC_MESSAGES`/`LANG` win over `Intl`'s own answer. **This does not
|
|
278
|
+
* change while the app runs** and there is no subscription behind it — a
|
|
279
|
+
* process's environment is fixed at exec.
|
|
280
|
+
*
|
|
281
|
+
* For text direction inside a component prefer `useDirection()`, which also
|
|
282
|
+
* honours `<ThemeProvider direction>` and the `direction` style property.
|
|
283
|
+
*/
|
|
284
|
+
export function useLocale(): SystemLocale;
|
|
285
|
+
|
|
286
|
+
/** The imperative twin of {@link useLocale}, for code outside a tree. */
|
|
287
|
+
export function systemLocale(): SystemLocale;
|
package/src/windowid.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// One supported way to turn a ref into the XID of the window it belongs to.
|
|
2
|
+
//
|
|
3
|
+
// The walk itself is not new — `anchor.js` has done it for popup placement
|
|
4
|
+
// since the beginning — but it was private, and two features need it at
|
|
5
|
+
// once: `transientFor` resolves its prop through this, and the
|
|
6
|
+
// xdg-desktop-portal work needs it to build a `parent_window` handle.
|
|
7
|
+
|
|
8
|
+
import { useCallback, useMemo } from 'react';
|
|
9
|
+
|
|
10
|
+
import { useAppOrNull } from './appcontext.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The XID of the X11 window a ref points at, or `null` if there is not one
|
|
14
|
+
* (yet). Accepts:
|
|
15
|
+
*
|
|
16
|
+
* - a `<window>` / `<popup>` ref — `getPublicInstance` hands back the live
|
|
17
|
+
* ntk window, so `ref.current.id` is the XID;
|
|
18
|
+
* - a ref to any **drawn** node — resolved to the window that owns it;
|
|
19
|
+
* - a raw XID, returned unchanged;
|
|
20
|
+
* - the ref object itself, so `windowIdOf(ref)` works as well as
|
|
21
|
+
* `windowIdOf(ref.current)`.
|
|
22
|
+
*
|
|
23
|
+
* `null` is a real answer, not a failure: a ref is empty until the node
|
|
24
|
+
* mounts, and a `<window>` has no XID until the commit phase realizes it.
|
|
25
|
+
*/
|
|
26
|
+
export function windowIdOf(target) {
|
|
27
|
+
if (target == null) return null;
|
|
28
|
+
if (typeof target === 'number') return target;
|
|
29
|
+
// a ref object, so callers need not remember which one this takes
|
|
30
|
+
if (typeof target === 'object' && 'current' in target && !target.isWindow) {
|
|
31
|
+
return windowIdOf(target.current);
|
|
32
|
+
}
|
|
33
|
+
// an ntk Window (what a <window>/<popup> ref holds), or a WindowNode that
|
|
34
|
+
// has been realized
|
|
35
|
+
if (typeof target.id === 'number') return target.id;
|
|
36
|
+
if (typeof target.window?.id === 'number') return target.window.id;
|
|
37
|
+
// a drawn node: `root` is the WindowNode that owns it
|
|
38
|
+
if (typeof target.root?.window?.id === 'number') return target.root.window.id;
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `windowIdOf` bound to a ref: returns a **getter**, stable across renders,
|
|
44
|
+
* the same shape `useAnchor` has. It is a getter rather than the id itself
|
|
45
|
+
* because refs attach after the commit that created the window, so a value
|
|
46
|
+
* read during render would be null on the render that matters.
|
|
47
|
+
*
|
|
48
|
+
* ```js
|
|
49
|
+
* const windowId = useWindowId(anchorRef);
|
|
50
|
+
* // …later, in an effect or a handler:
|
|
51
|
+
* const parentWindow = `x11:${windowId().toString(16)}`; // xdg-desktop-portal
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* Note the format: lowercase hex, **no** `0x`. Both shipping portal backends
|
|
55
|
+
* happen to tolerate a prefix, which is exactly why it is easy to get wrong
|
|
56
|
+
* and never notice — and Qt's parser returns 0 on failure with no error
|
|
57
|
+
* path, so a third backend would give an unparented, non-modal dialog rather
|
|
58
|
+
* than an exception.
|
|
59
|
+
*/
|
|
60
|
+
export function useWindowId(ref) {
|
|
61
|
+
return useCallback(() => windowIdOf(ref), [ref]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The top-level windows this connection is currently rendering, in the order
|
|
66
|
+
* they were added.
|
|
67
|
+
*
|
|
68
|
+
* `createContainer(app, …)` passes the app as the container, so
|
|
69
|
+
* `appendChildToContainer` records every top-level node on the app itself —
|
|
70
|
+
* which makes "what windows does this tree have" answerable without the app
|
|
71
|
+
* author wiring anything. Popups are excluded: a `<popup>` is
|
|
72
|
+
* override-redirect and never what a dialog should be transient for.
|
|
73
|
+
*
|
|
74
|
+
* Not public: `useTopLevelWindow()` is the shape a component wants.
|
|
75
|
+
* `activate.js` needs the list itself, to pick the window a raise with no
|
|
76
|
+
* argument goes to.
|
|
77
|
+
*/
|
|
78
|
+
export function topLevelWindows(app) {
|
|
79
|
+
if (!app) return [];
|
|
80
|
+
return (app._rootChildren ?? []).filter(
|
|
81
|
+
(node) => node?.isWindow && !node.isPopup && node.window?.id,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let warnedAboutAmbiguity = false;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The window a component belongs to, resolved when it is read.
|
|
89
|
+
*
|
|
90
|
+
* ```js
|
|
91
|
+
* const owner = useTopLevelWindow();
|
|
92
|
+
* // …later, in a handler, when everything is mounted:
|
|
93
|
+
* const id = windowIdOf(owner);
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* **Why this is inference and not a lookup.** A hook has no position in the
|
|
97
|
+
* host tree: React context cannot come from a host element, the host context
|
|
98
|
+
* `getChildHostContext` builds reaches `createInstance` and not components,
|
|
99
|
+
* and a `<window>` has no XID at all until the commit phase. Everything else
|
|
100
|
+
* in this file walks *up from a node* — which is why the alternative is
|
|
101
|
+
* `useRef` on the window and a line of wiring in every app that wants a
|
|
102
|
+
* parented dialog.
|
|
103
|
+
*
|
|
104
|
+
* So it answers from what the renderer does know, at read time, when the tree
|
|
105
|
+
* is mounted and realized:
|
|
106
|
+
*
|
|
107
|
+
* - **one top-level window — the overwhelmingly common case — is exact.** The
|
|
108
|
+
* component is in that window, because there is nowhere else to be.
|
|
109
|
+
* - several, and one of them holds the X input focus: that one. You clicked
|
|
110
|
+
* in it a moment ago, which is why a dialog is opening.
|
|
111
|
+
* - several with nothing to separate them: the most recently opened, and a
|
|
112
|
+
* development warning naming `parentWindow` as the way to be exact. A
|
|
113
|
+
* guess that says it is guessing beats a guess that does not.
|
|
114
|
+
*
|
|
115
|
+
* Returns a **ref-like object** rather than a number: the window is not
|
|
116
|
+
* realized on the first render, so a value read then would be `null` on the
|
|
117
|
+
* render that matters. It drops into anything that already takes a ref —
|
|
118
|
+
* `parentWindow`, `transientFor` — and `windowIdOf()` resolves it.
|
|
119
|
+
*/
|
|
120
|
+
export function useTopLevelWindow() {
|
|
121
|
+
const app = useAppOrNull();
|
|
122
|
+
return useMemo(
|
|
123
|
+
() => ({
|
|
124
|
+
get current() {
|
|
125
|
+
const windows = topLevelWindows(app);
|
|
126
|
+
if (windows.length <= 1) return windows[0] ?? null;
|
|
127
|
+
|
|
128
|
+
const focused = windows.filter((w) => w.events?.windowFocused);
|
|
129
|
+
if (focused.length === 1) return focused[0];
|
|
130
|
+
|
|
131
|
+
// Nothing separates them. `windowFocused` also defaults to true on an
|
|
132
|
+
// ntk too old to report focus changes, so "all of them" is the same
|
|
133
|
+
// answer as "none of them" and both land here.
|
|
134
|
+
if (process.env.NODE_ENV !== 'production' && !warnedAboutAmbiguity) {
|
|
135
|
+
warnedAboutAmbiguity = true;
|
|
136
|
+
console.warn(
|
|
137
|
+
`react-x11: this tree has ${windows.length} top-level windows and ` +
|
|
138
|
+
'none of them is uniquely focused, so the owner window is a ' +
|
|
139
|
+
'guess (the most recently opened). Pass the window explicitly ' +
|
|
140
|
+
'to be exact:\n' +
|
|
141
|
+
' const win = useRef(null);\n' +
|
|
142
|
+
' const { openFile } = useFileDialog({ parentWindow: win });\n' +
|
|
143
|
+
' return <window ref={win}>…</window>;',
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return windows[windows.length - 1];
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
[app],
|
|
150
|
+
);
|
|
151
|
+
}
|