@domicile-desktop/sdk 0.0.0-alpha-028d5f9f87e0 → 0.0.0-alpha-e040773499e5

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 CHANGED
@@ -51,7 +51,7 @@ See [docs/ELEMENTS.md](docs/ELEMENTS.md) for `<app>` input and focus, and the
51
51
 
52
52
  | Module | What it is |
53
53
  | --- | --- |
54
- | `./domicile-host` | `DomicileHost`: the type of the desktop `Shell` is handed, mirroring the engine's IDL. |
54
+ | `./domicile-host` | `DomicileHost`: the type of the desktop `Shell` is handed. Generated from the engine's IDL; see [Generated types](#generated-types). |
55
55
  | `./shell` | `Shell`: the export a shell module must provide. Domicile calls it once with the element to draw in. |
56
56
  | `./app-element`, `./webview-element` | Types and event names for `<app>` and `<webview>`. |
57
57
  | `./bind-keys` | `bindKeys`: grab a shell's own chords by name and handle them by mode. `./key-action` and `./own-keybindings` are its parts. |
@@ -66,6 +66,22 @@ The compositor's JSON socket protocol lives in
66
66
  [`@domicile-desktop/e2e-harness`](../e2e-harness/README.md); a page never
67
67
  speaks it.
68
68
 
69
+ ## Generated types
70
+
71
+ `src/domicile-host.ts` is generated from the engine's WebIDL in
72
+ `packages/domicile-engine/src/third_party/blink/renderer/modules/domicile/`,
73
+ with the IDL's comments as its docs. Do not edit it.
74
+
75
+ ```sh
76
+ bun run generate # after changing the IDL
77
+ ```
78
+
79
+ - `codegen/` holds the generator: a parser for the IDL subset the engine uses,
80
+ and the TypeScript it emits. It throws on syntax outside that subset.
81
+ - `EVENT_TYPES` in `codegen/generate-domicile-host.ts` names the event type
82
+ each `on<name>` handler dispatches, which WebIDL cannot express.
83
+ - `scripts/test-host-types-match-the-idl.sh` fails when the file is stale.
84
+
69
85
  ## Dependencies
70
86
 
71
87
  - `zod` only. Its schemas parse the keywords and rows a newer engine may send
@@ -1,540 +1,775 @@
1
- import type { CursorShape } from "./cursor-shape";
2
- import type { Theme } from "./theme";
3
- import type { TrayAction } from "./tray";
4
1
  /**
5
- * A key combination the shell claims, matching {@link DomicileShortcutEvent}.
2
+ * The cursors a client can ask the chrome to show over its window.
6
3
  *
7
- * An omitted modifier defaults to `false`, so it must not be held.
4
+ * The `wp_cursor_shape_v1` shapes, named by the CSS `cursor` keyword the
5
+ * chrome assigns, plus `none` for a hidden cursor. The same set is defined by
6
+ * `domicile_protocol::CursorShape`, `components/domicile/common/cursor_shape.h`
7
+ * and `cursorShapeSchema` in `@domicile/chrome-sdk`.
8
+ *
9
+ * An enum, not a DOMString, so the bindings reject unknown values. CSS ignores
10
+ * an unknown cursor keyword, so a bad value would otherwise show the wrong
11
+ * cursor with no error.
12
+ *
13
+ * Keep the values in the same order as the other lists:
14
+ * `scripts/test-cursor-shapes-agree.sh` compares all four as sequences.
8
15
  */
9
- export type DomicileShortcut = {
10
- /** An evdev code. Zero is not a key and the engine refuses it. */
11
- keycode: number;
12
- altKey?: boolean;
13
- ctrlKey?: boolean;
14
- shiftKey?: boolean;
15
- metaKey?: boolean;
16
- };
16
+ export type DomicileCursorShape = "none" | "default" | "context-menu" | "help" | "pointer" | "progress" | "wait" | "cell" | "crosshair" | "text" | "vertical-text" | "alias" | "copy" | "move" | "no-drop" | "not-allowed" | "grab" | "grabbing" | "e-resize" | "n-resize" | "ne-resize" | "nw-resize" | "s-resize" | "se-resize" | "sw-resize" | "w-resize" | "ew-resize" | "ns-resize" | "nesw-resize" | "nwse-resize" | "col-resize" | "row-resize" | "all-scroll" | "zoom-in" | "zoom-out";
17
17
  /**
18
- * One screen of the desktop.
18
+ * The desktop's light or dark theme.
19
19
  *
20
- * Sizes are integers because they come from the config, not from layout.
20
+ * One of four copies of this set: `domicile_protocol::Theme`,
21
+ * `components/domicile/common/theme.h`, this enum, and `themeSchema` in
22
+ * `@domicile/chrome-sdk`.
23
+ *
24
+ * An enum rather than a `DOMString` because `setTheme()` takes it, so the
25
+ * bindings reject an invalid value at the call. There is no `system` value:
26
+ * the compositor owns the theme.
27
+ *
28
+ * Keep the order in sync with the other copies. `mojom::Theme` is numbered by
29
+ * position, and `scripts/test-themes-agree.sh` compares the order.
21
30
  */
22
- export type DomicileDisplay = {
31
+ export type DomicileTheme = "dark" | "light";
32
+ /**
33
+ * Which button clicked a tray icon: StatusNotifierItem's Activate,
34
+ * SecondaryActivate and ContextMenu.
35
+ *
36
+ * An enum rather than a `DOMString` because activateTrayItem() takes it, so
37
+ * the bindings reject an invalid value at the call.
38
+ *
39
+ * Keep the order in sync with `mojom::TrayAction`, which is numbered by
40
+ * position.
41
+ */
42
+ export type DomicileTrayAction = "primary" | "secondary" | "context";
43
+ export type DomicileAppEventInit = EventInit & {
44
+ appId?: string;
45
+ };
46
+ /** What `DomicileHost.previewFile()` resolves with. */
47
+ export type DomicileFilePreview = {
23
48
  /**
24
- * The screen's name from the config, such as `left`, or `domicile-0` when
25
- * Domicile runs in a window instead of driving displays.
49
+ * Which of the rest means anything: "text", "directory", "audio", "binary"
50
+ * or "unreadable". "unreadable" is also what a path outside the compositor's
51
+ * index of the home gets -- see previewFile(). A DOMString rather than an
52
+ * enum: the browser drops an answer whose kind is not one of the five.
26
53
  */
27
- readonly name: string;
54
+ readonly kind: string;
55
+ /** The front of the file, for "text". */
56
+ readonly text: string;
57
+ /** What is in it, for "directory": names, a subdirectory ending in `/`. */
58
+ readonly entries: readonly string[];
28
59
  /**
29
- * The top-left corner in desktop coordinates, which start at the top-left
30
- * of the displays' bounding box. Can be negative.
60
+ * What a song says of itself, for "audio": each tag empty when it does not
61
+ * say it.
31
62
  */
32
- readonly x: number;
33
- readonly y: number;
34
- /** Logical size in CSS pixels. The `wl_output` mode is this × `scale`. */
35
- readonly width: number;
36
- readonly height: number;
63
+ readonly title: string;
64
+ readonly artist: string;
65
+ readonly album: string;
66
+ /** How long it plays, in seconds, for "audio". */
67
+ readonly duration: number;
37
68
  /**
38
- * The scale clients on this screen render at. This is not the shell's
39
- * `devicePixelRatio`; do not use it for layout.
69
+ * The picture it carries of itself, for "audio", as a `data:` URL -- the
70
+ * page cannot read the file to draw one of its own.
40
71
  */
41
- readonly scale: number;
72
+ readonly cover: string;
73
+ };
74
+ /** What `DomicileHost.searchFiles()` resolves with. */
75
+ export type DomicileFileSearch = {
42
76
  /**
43
- * The panel's physical mode in pixels, before rotation.
44
- *
45
- * It cannot be derived from `width`, `height` and `scale`. Zero when the
46
- * compositor reports no mode. For display only.
77
+ * The front of what matched: paths relative to the home directory the
78
+ * desktop is running as -- `Notes/today.org`, not
79
+ * `/home/you/Notes/today.org` -- already in the order they go on screen, and
80
+ * a directory ends in `/`. Empty is a query that matched nothing.
47
81
  */
48
- readonly modeWidth: number;
49
- readonly modeHeight: number;
82
+ readonly files: readonly string[];
83
+ /** How many paths matched, of which `files` is the front. */
84
+ readonly matched: number;
50
85
  /**
51
- * The monitor's rotation: `normal`, `rotate-90`, `rotate-180` or
52
- * `rotate-270`.
86
+ * Whether the compositor is still building the index this was found in.
53
87
  *
54
- * Uses `wl_output` naming, which counts counterclockwise: `rotate-90` turns
55
- * content a quarter turn counterclockwise.
88
+ * THE DIFFERENCE BETWEEN AN INCOMPLETE ANSWER AND A WRONG ONE. The rows are
89
+ * a launcher's whole evidence that a file exists, so an answer from an index
90
+ * still being walked has to arrive saying so. A shell draws this as a line
91
+ * saying the index is being built, and asks again.
56
92
  */
57
- readonly transform: string;
93
+ readonly indexing: boolean;
58
94
  };
59
95
  /**
60
- * One client window, as {@link DomicileHost.windows} lists it: what the
61
- * compositor has said about it so far. Replaced, never edited — any change is
62
- * a new `windows` array and a `windowschanged`.
96
+ * A key combination the desktop grabs.
97
+ *
98
+ * Same fields as `DomicileShortcutEvent`, so a shell can compare a press
99
+ * against it directly. `keycode` is a Linux evdev code; `KeyboardEvent.key` is
100
+ * already a string such as "Enter".
63
101
  */
64
- export type DomicileWindow = {
65
- readonly appId: string;
66
- /** Empty until the client names itself. */
67
- readonly title: string;
68
- /** The size the client last committed, in CSS pixels; `null` until it has. */
69
- readonly width: number | null;
70
- readonly height: number | null;
71
- /** The limits the client asked for; `null` where it asked for none. */
72
- readonly minWidth: number | null;
73
- readonly minHeight: number | null;
74
- readonly maxWidth: number | null;
75
- readonly maxHeight: number | null;
76
- readonly cursor: CursorShape;
77
- /** A popup's parent window; `null` for a toplevel. */
78
- readonly parent: string | null;
79
- /** A popup's place relative to its parent; `null` for a toplevel. */
80
- readonly x: number | null;
81
- readonly y: number | null;
82
- /** Whether a popup grabbed the pointer; `false` for a toplevel. */
83
- readonly grab: boolean;
102
+ export type DomicileShortcut = {
103
+ keycode: number;
104
+ altKey?: boolean;
105
+ ctrlKey?: boolean;
106
+ shiftKey?: boolean;
107
+ metaKey?: boolean;
108
+ };
109
+ export type DomicileShortcutEventInit = EventInit & {
110
+ chord?: string;
111
+ keycode?: number;
112
+ altKey?: boolean;
113
+ ctrlKey?: boolean;
114
+ shiftKey?: boolean;
115
+ metaKey?: boolean;
84
116
  };
85
117
  /**
86
- * A client asked for the keyboard (`focusrequested`). Focus has not moved; the
87
- * shell answers with {@link DomicileHost.focusApp} or ignores it.
118
+ * The event behind DomicileHost.onfocusrequested: a client asked for the
119
+ * keyboard.
88
120
  */
89
121
  export type DomicileAppEvent = Event & {
90
- /** The window's id. */
122
+ /** The window that asked: the id an <app> element names. */
91
123
  readonly appId: string;
92
124
  };
93
- /** A combination claimed with `grabShortcut()` was pressed. */
94
- export type DomicileShortcutEvent = Event & {
95
- /**
96
- * The chord as {@link DomicileHost.grabShortcut} was given it by name —
97
- * `Meta+Shift+l` — or empty for one grabbed as a keycode.
98
- */
99
- readonly chord: string;
100
- readonly keycode: number;
101
- readonly altKey: boolean;
102
- readonly ctrlKey: boolean;
103
- readonly shiftKey: boolean;
104
- readonly metaKey: boolean;
105
- };
106
125
  /**
107
- * The answer {@link DomicileHost.previewFile} resolves with.
126
+ * One of the desk's browser windows: a page the browser holds, drawn with
127
+ * `<webview window="id">`. See components/domicile/mojom/browser_windows.mojom.
108
128
  *
109
- * `kind` is `text`, `directory`, `audio`, `binary` or `unreadable`. Only the
110
- * fields for that kind are filled; the rest are empty.
129
+ * An interface for DomicileDisplay's reason: these are read from the
130
+ * desktop's `browserWindows` attribute, and WebIDL forbids a dictionary as an
131
+ * attribute type.
111
132
  */
112
- export type DomicileFilePreview = {
113
- readonly kind: string;
114
- /** The start of the file, for a `text` preview. */
115
- readonly text: string;
116
- /** The first entries, for a `directory` preview. */
117
- readonly entries: readonly string[];
118
- /** Tags for an `audio` preview; empty when missing. */
133
+ export type DomicileBrowserWindow = {
134
+ /**
135
+ * The id `<webview window>` and closeBrowserWindow() use. Never reused while
136
+ * the browser runs.
137
+ */
138
+ readonly id: string;
139
+ /** The page's address. Empty before it has shown anything. */
140
+ readonly url: string;
141
+ /** The page's title. Empty when the page has none. */
119
142
  readonly title: string;
120
- readonly artist: string;
121
- readonly album: string;
122
- /** Length of an `audio` preview, in seconds. */
123
- readonly duration: number;
124
- /** Cover art of an `audio` preview, as a `data:` URL. */
125
- readonly cover: string;
143
+ /**
144
+ * The extension popup window (chrome.windows) whose one tab this is. Null
145
+ * for a tab of the desk's own window.
146
+ */
147
+ readonly popupWindow: number | null;
148
+ /**
149
+ * The size the popup window asked for, in CSS pixels. 0 on an axis it left
150
+ * unset.
151
+ */
152
+ readonly width: number;
153
+ readonly height: number;
126
154
  };
127
155
  /**
128
- * The answer {@link DomicileHost.searchFiles} resolves with. The home's index
129
- * never crosses into the page.
156
+ * One thing that was copied, as a row of the clipboard's history.
130
157
  *
131
- * Paths are relative to the home directory and sorted; directories end in
132
- * `/`. Only the first matches are included; `matched` counts them all.
158
+ * An interface rather than a dictionary because WebIDL will not have a
159
+ * dictionary as the element type of an attribute's array, which is how these
160
+ * are read -- off DomicileHost.clipboard rather than handed over in a call.
133
161
  */
134
- export type DomicileFileSearch = {
135
- readonly files: readonly string[];
136
- /** The total number of matches; {@link files} holds the first of them. */
137
- readonly matched: number;
138
- /**
139
- * Whether the compositor is still building the index.
140
- *
141
- * When true, the results may be incomplete. Show them and search again.
142
- */
143
- readonly indexing: boolean;
144
- };
145
- /** One entry in the clipboard history. */
146
162
  export type DomicileClipboardEntry = {
147
163
  /**
148
- * The id to pass to {@link DomicileHost.copyClipboardEntry}.
164
+ * What DomicileHost.copyClipboardEntry() names this row by.
149
165
  *
150
- * Never reused, and stable when the list is reordered.
166
+ * Assigned by the compositor and never reused, so an id a shell is holding
167
+ * either names the row it was told about or names nothing at all. NOT a
168
+ * position: the list a copy re-orders keeps every id it had.
151
169
  */
152
170
  readonly id: number;
153
171
  /**
154
- * The copied text, truncated if long. Copying the entry back restores the
155
- * full content.
172
+ * Enough of what was copied to recognize it by, and not necessarily all of
173
+ * it: a long copy is cut down to a row, because this is what a panel draws
174
+ * and the rest of a copied file is not a row.
175
+ *
176
+ * What goes back on the clipboard is always the whole thing, which is the
177
+ * reason a row is an id and a preview rather than the text: the bytes stay
178
+ * in the compositor, and a password manager's copy is a row in this list.
156
179
  */
157
180
  readonly preview: string;
158
181
  };
159
- /** One system tray icon. */
160
- export type DomicileTrayItem = {
161
- /** The id to pass to {@link DomicileHost.activateTrayItem}. */
162
- readonly id: string;
163
- /** A text label. Never empty. */
164
- readonly title: string;
165
- /** The icon as a `data:` URL, or empty if it could not be drawn. */
166
- readonly icon: string;
167
- };
168
- /** One button of a notification. */
169
- export type DomicileNotificationAction = {
170
- /** The key to pass to {@link DomicileHost.invokeNotificationAction}. */
171
- readonly key: string;
172
- /** The button text. */
173
- readonly label: string;
174
- };
175
- /** One notification. */
176
- export type DomicileNotification = {
177
- /** The id the host's dismiss and invoke calls take. */
178
- readonly id: number;
179
- /** The sender's name; may be empty. */
180
- readonly appName: string;
181
- readonly summary: string;
182
- /** Plain text, never markup; may be empty. */
183
- readonly body: string;
184
- /** A `data:` URL, or empty. */
185
- readonly icon: string;
186
- /** `"low"`, `"normal"` or `"critical"`. */
187
- readonly urgency: string;
188
- readonly actions: readonly DomicileNotificationAction[];
189
- /** Whether it offers the `"default"` action, triggered by clicking it. */
190
- readonly clickable: boolean;
191
- /**
192
- * Requested display time in milliseconds: `0` until dismissed, `-1` for
193
- * the shell's default.
194
- */
195
- readonly timeoutMs: number;
196
- /** Milliseconds since the epoch, on the compositor's clock. */
197
- readonly time: number;
198
- };
199
182
  /**
200
- * A browser window, drawn with `<webview window={id}>`.
183
+ * One screen of the desktop.
184
+ *
185
+ * An interface because WebIDL does not allow a dictionary as an attribute type.
201
186
  *
202
- * The engine holds the page, so it outlives the shell's document. After
203
- * `domicile load-shell` the new shell gets the same windows. See
204
- * docs/SHELL-BROWSER-WINDOWS.md.
187
+ * Sizes are integers because the desktop config uses whole pixels. Other sizes
188
+ * on this channel come from layout boxes and are fractional.
205
189
  */
206
- export type DomicileBrowserWindow = {
207
- /**
208
- * The id `<webview window>` and {@link DomicileHost.closeBrowserWindow} take.
209
- * Never reused while the browser runs.
210
- */
211
- readonly id: string;
212
- /** The page's address. Empty before the page has loaded. */
213
- readonly url: string;
214
- /** The page's title. Empty when the page has none. */
215
- readonly title: string;
190
+ export type DomicileDisplay = {
216
191
  /**
217
- * The extension popup window (`chrome.windows` id) whose one tab this is.
218
- * `null` for an ordinary browser window.
192
+ * The compositor's name for this screen from the config, such as `left`.
193
+ * `domicile-0` when Domicile runs in a window instead of driving displays.
219
194
  */
220
- readonly popupWindow: number | null;
195
+ readonly name: string;
221
196
  /**
222
- * The size the popup window asked for, in CSS pixels. 0 on an axis it left
223
- * unset.
197
+ * The top-left corner in desktop coordinates. The origin is the top-left of
198
+ * the displays' bounding box. Signed because the config can place a display
199
+ * left of or above the origin.
224
200
  */
201
+ readonly x: number;
202
+ readonly y: number;
203
+ /** The logical size, in the CSS pixels a shell lays out in. */
225
204
  readonly width: number;
226
205
  readonly height: number;
206
+ /**
207
+ * The scale advertised to Wayland clients on this screen. This is not the
208
+ * shell's `devicePixelRatio`: the shell is one page at one density across
209
+ * all screens.
210
+ */
211
+ readonly scale: number;
212
+ /**
213
+ * The panel's mode in physical pixels, before rotation. A portrait 4K
214
+ * panel has a 3840x2160 mode and an 1800x3200 logical size. The mode does
215
+ * not follow from the size because `scale` is an integer.
216
+ *
217
+ * Zero when the desktop has no modes, such as when running in a window.
218
+ */
219
+ readonly modeWidth: number;
220
+ readonly modeHeight: number;
221
+ /**
222
+ * The monitor's rotation: `normal`, `rotate-90`, `rotate-180` or
223
+ * `rotate-270`, as the config file spells them.
224
+ *
225
+ * Follows the wl_output convention: the name is the clockwise turn applied
226
+ * to content, not the panel's turn. A shell applies it as written.
227
+ *
228
+ * A DOMString because an enum would need another IDL file and another patched
229
+ * entry in `bindings/idl_in_modules.gni`. The browser maps any other name to
230
+ * `normal`.
231
+ */
232
+ readonly transform: string;
227
233
  };
228
234
  /**
229
- * One extension with an action.
235
+ * One extension with an action, as a shell's tray draws it.
236
+ *
237
+ * An interface rather than a dictionary for DomicileClipboardEntry's reason:
238
+ * WebIDL will not have a dictionary as the element type of an attribute's
239
+ * array, and these are read off DomicileHost.extensions.
230
240
  *
231
- * The state is the action's default (tab `-1`), not per browser window.
241
+ * The state is the active tab's -- the <webview> that last had focus -- or the
242
+ * action's default where there is none. See docs/architecture/EXTENSIONS.md.
232
243
  */
233
244
  export type DomicileExtension = {
234
- /** The id to pass to {@link DomicileHost.activateExtension}. */
245
+ /** What DomicileHost.activateExtension() names it by. */
235
246
  readonly id: string;
236
247
  readonly name: string;
237
248
  /** The action's tooltip. */
238
249
  readonly title: string;
239
250
  /**
240
- * A `data:image/png` URL at the page's device pixel ratio. Not a
241
- * `chrome-extension://` URL, because icons set with
242
- * `action.setIcon({imageData})` have none.
251
+ * data:image/png;base64,..., drawn at this page's device pixel ratio. Not a
252
+ * chrome-extension:// URL: action.setIcon({imageData}) sets an icon that has
253
+ * none.
243
254
  */
244
255
  readonly icon: string;
245
256
  readonly badgeText: string;
246
- /** A CSS color, `#rrggbbaa`; transparent when the extension set none. */
257
+ /** A CSS color, #rrggbbaa. Fully transparent when the extension set none. */
247
258
  readonly badgeColor: string;
248
259
  /**
249
- * The popup to open in a `<webview>` on click, or `null` when the click
250
- * fires `action.onClicked`. Either way, call
251
- * {@link DomicileHost.activateExtension}.
260
+ * The popup a click opens, in a <webview> the shell draws; null for an
261
+ * action whose click is its `action.onClicked`. Either way the click is
262
+ * activateExtension().
252
263
  */
253
264
  readonly popup: string | null;
254
- /** `false` after `action.disable()`. */
265
+ /** False after the extension's action.disable(). */
255
266
  readonly enabled: boolean;
256
267
  };
257
- /** Every event the desktop fires, by name. */
268
+ /** Every event `DomicileHost` fires, by name. */
258
269
  export type DomicileHostEventMap = {
259
- /**
260
- * A client asked for keyboard focus. Focus has not moved; the shell may call
261
- * `focusApp()`. See `focus_requested` in `domicile-protocol`.
262
- */
263
- focusrequested: DomicileAppEvent;
270
+ /** A grabbed shortcut fired, as a DomicileShortcutEvent. */
264
271
  shortcut: DomicileShortcutEvent;
265
272
  /**
266
- * An answer to {@link DomicileHost.callSystem}: `data` is the compositor's
267
- * `system_reply`, `system_event` or `system_end` line. See the `system`
268
- * module.
273
+ * A client asked for the keyboard, as a DomicileAppEvent naming it; nothing
274
+ * has moved. The shell answers by calling focusApp(), or does not, which
275
+ * refuses it.
269
276
  */
270
- system: MessageEvent<string>;
271
- /**
272
- * Every unanswered portal request: `data` is the compositor's
273
- * `portal_requests` line. Fired on every change. A listener added later is
274
- * sent the latest line. See `docs/architecture/PORTALS.md`.
275
- */
276
- portalrequests: MessageEvent<string>;
277
+ focusrequested: DomicileAppEvent;
277
278
  /**
278
- * A screen was added, removed, resized or rescaled. Read
279
- * {@link DomicileHost.displays} for the new state.
279
+ * The desktop changed: a screen arrived or left, a display was resized, or
280
+ * its density moved. Read `displays` for what it is now.
280
281
  */
281
282
  displayschanged: Event;
282
283
  /**
283
- * A browser window opened, closed, navigated or changed title. Read
284
- * {@link DomicileHost.browserWindows} for the new list.
285
- */
286
- browserwindowschanged: Event;
287
- /**
288
- * A window appeared, closed, or changed. Bare — read
289
- * {@link DomicileHost.windows} for what they are now.
284
+ * A window appeared, closed, or changed: its title, size, limits, cursor or
285
+ * popup placement. Read `windows` for what they are now.
290
286
  */
291
287
  windowschanged: Event;
292
288
  /**
293
- * The compositor said where the keyboard is. Bare — read
294
- * {@link DomicileHost.focusedWindow}. Every time, not only when it moves, and
295
- * once after the windows already running are replayed to a page that has
296
- * just connected: a window listed after the first of these is one opened
297
- * now.
289
+ * The compositor said where the keyboard is: `focusedWindow` may have
290
+ * changed. Every time, and once after the windows already running are
291
+ * replayed to a page that has just connected -- which is how a page tells a
292
+ * window opened now from one that was running.
298
293
  */
299
294
  focusedwindowchanged: Event;
300
- /** Bare: the attribute it names moved. */
301
295
  clipboardchanged: Event;
302
- /** Bare: the attribute it names moved. */
303
296
  traychanged: Event;
304
- /** Bare: the attribute it names moved. */
305
297
  notificationschanged: Event;
306
- /** Bare: the attribute it names moved. */
307
298
  extensionschanged: Event;
308
- /** Bare: the attribute it names moved. */
309
299
  idlechanged: Event;
310
- /** Bare: the attribute it names moved. */
311
300
  lockedchanged: Event;
312
- /** Bare: the attribute it names moved. */
313
301
  themechanged: Event;
314
- /** Bare: the attribute it names moved. */
315
302
  windowsthemechanged: Event;
316
- /** Bare: the attributes it names moved. */
317
303
  modifierschanged: Event;
304
+ /**
305
+ * Fires when a window opens, closes, navigates or changes title. Read
306
+ * `browserWindows` for the current list.
307
+ */
308
+ browserwindowschanged: Event;
309
+ /**
310
+ * An answer to callSystem(), as a MessageEvent whose `data` is the
311
+ * compositor's `system_reply`, `system_event` or `system_end` line.
312
+ */
313
+ system: MessageEvent<string>;
314
+ /**
315
+ * Every unanswered portal request, as a MessageEvent whose `data` is the
316
+ * compositor's `portal_requests` line. Fired on every change. A listener
317
+ * added later is sent the latest line, so a shell that listens late still
318
+ * sees pending dialogs.
319
+ */
320
+ portalrequests: MessageEvent<string>;
318
321
  };
319
322
  /**
320
- * The shell's control channel to the compositor.
323
+ * The desktop a shell is handed — the shell's control channel to the compositor.
321
324
  *
322
- * In the IDL this is an `EventTarget`. The first `addEventListener` binds the
323
- * channel, so nothing is dispatched before something listens.
325
+ * Not a web standard and not proposed as one. It exists in Domicile's engine
326
+ * fork, on documents served over domicile://, and nowhere else.
324
327
  *
325
- * It is not typed as `EventTarget` here: that would add `lib.dom`'s overload
326
- * and type listeners as plain `Event`. `dispatchEvent` is left out; nothing
327
- * in a page dispatches to the host. As a result, an `EventTarget` does not
328
- * satisfy this type, so test doubles register listeners themselves.
328
+ * This is the typed surface rather than a message pipe: the page calls methods
329
+ * and listens for events, and the wire protocol lives in the browser process.
330
+ * The reason is that the protocol carries Spawn, and a page that cannot
331
+ * construct a message cannot construct a malformed one. The cost, which is
332
+ * real, is that adding a message means an engine release rather than editing a
333
+ * TypeScript file.
329
334
  */
330
335
  export type DomicileHost = {
331
336
  /**
332
- * Run a command on the desktop's machine.
337
+ * Run a command on the machine running the desktop.
333
338
  *
334
- * Takes an argv array because no shell parses it. Throws if empty.
339
+ * Sequence<DOMString> rather than a single string: splitting a command line
340
+ * is a shell's job and there is no shell in this path.
335
341
  */
336
342
  spawn(command: readonly string[]): void;
337
343
  /**
338
- * Search the home directory for `query`. Resolves with the result. A newer
339
- * call rejects this one with an `AbortError`.
344
+ * Ask what in the home matches `query`. Resolves with the answer. A newer
345
+ * call supersedes this one, which rejects with an AbortError: a launcher
346
+ * wants the answer to what is typed now.
347
+ *
348
+ * IT TAKES NO PATH, AND THAT IS THE POINT RATHER THAN AN OVERSIGHT. A shell
349
+ * is served over domicile:// so that it has an origin without a TCP port,
350
+ * not so that it gets a filesystem. A call that named a directory would be
351
+ * one, and every document this engine serves would have it. What gets
352
+ * searched is the compositor's decision -- see `domicile_host::file_search`
353
+ * -- and the query is only words to match.
340
354
  *
341
- * It takes no path on purpose, so pages cannot browse the filesystem. The
342
- * compositor matches against its index and sends only the matches. See
343
- * `domicile_host::file_search`.
355
+ * ONLY WHAT MATCHED CROSSES. The compositor's index is the whole home; a
356
+ * page handed that to filter was a desktop that took no input while it
357
+ * arrived.
344
358
  */
345
359
  searchFiles(query: string): Promise<DomicileFileSearch>;
346
360
  /**
347
- * Preview `path`. Resolves with the result. A newer call rejects this one, as
348
- * with {@link searchFiles}.
361
+ * Ask what is in one file. Resolves with the answer. A newer call
362
+ * supersedes this one, as with searchFiles().
349
363
  *
350
- * Paths outside the compositor's home index are `unreadable`, so a page can
351
- * only read what a search could return. See `domicile_host::file_preview`.
364
+ * THIS ONE TAKES A PATH, which searchFiles() above says is the point of not
365
+ * taking one -- so what it may name is narrow: a path relative to the home,
366
+ * as searchFiles() named it (a directory without its trailing `/`). The
367
+ * compositor answers only for a path in its own index of the home, and
368
+ * anything else comes back "unreadable", so this reads nothing a search
369
+ * could not already have named.
352
370
  */
353
371
  previewFile(path: string): Promise<DomicileFilePreview>;
354
372
  /**
355
- * A system call: `request` is the call as JSON, answered with `system`
356
- * events carrying `id`. The compositor checks every call, the lock
357
- * included. Use the `system` module rather than calling this. See
358
- * `docs/SHELL-SYSTEM-ACCESS.md`.
373
+ * A system call: a file, a directory, a watch or a process. `request` is
374
+ * the call as JSON, such as `{"call":"read_file","path":"/sys/x"}`. Answered
375
+ * with `system` events whose `data` carries `id`. The compositor checks
376
+ * every call, the lock included. See docs/SHELL-SYSTEM-ACCESS.md and
377
+ * the SDK's `system` module, which is the way to call this.
359
378
  */
360
379
  callSystem(id: number, request: string): void;
361
380
  /**
362
- * Answer portal request `id` from a `portalrequests` event. `answer` is JSON
363
- * with a string `kind`, such as `{"kind":"access"}`; the engine drops any
364
- * other. The compositor checks the rest.
381
+ * Answer portal request `id`, which a `portalrequests` event listed.
382
+ * `answer` is JSON with a string `kind`, such as `{"kind":"access"}`. One
383
+ * that is not is dropped in the browser. The compositor checks the rest. See
384
+ * docs/architecture/PORTALS.md.
365
385
  */
366
386
  answerPortalRequest(id: number, answer: string): void;
367
387
  /**
368
- * Put a clipboard history entry back on the clipboard.
388
+ * Put a row of the clipboard's history back on the clipboard.
369
389
  *
370
- * Takes an id from {@link clipboard}, not text, so a page cannot write
371
- * arbitrary data to the clipboard. The compositor then serves the entry,
372
- * even if the client that copied it has exited.
390
+ * IT NAMES A ROW AND CARRIES NO TEXT: a shell is served over domicile:// so
391
+ * that it has an origin, not so that it gets the machine, and a call that
392
+ * took bytes would let this document write the desktop's clipboard rather
393
+ * than choose among what has already been copied on it. `entry` is an id
394
+ * `clipboard` lists.
395
+ *
396
+ * No answer, and none to want: what follows is that the next paste in any
397
+ * window is that row, served by the compositor rather than by whichever
398
+ * client first copied it -- which is the whole of what a clipboard manager
399
+ * is for. An id the history has since dropped sets nothing.
373
400
  */
374
401
  copyClipboardEntry(entry: number): void;
375
402
  /**
376
- * Move the compositor's keyboard focus and where the keys this page hears
377
- * are sent. `focusChrome()` takes it back to the page.
403
+ * Click an icon in the system tray, with the button `action` names.
404
+ *
405
+ * It names an icon `tray` lists and a button, and nothing about
406
+ * what the click does, for copyClipboardEntry's reason: that is the
407
+ * application's. An id that names no icon now is a click that raced the
408
+ * application going away, and does nothing; an empty one throws.
409
+ */
410
+ activateTrayItem(id: string, action: DomicileTrayAction): void;
411
+ /**
412
+ * Clear notifications: ids `notifications` lists. Each application hears
413
+ * its notification was dismissed, and the next `notificationschanged`
414
+ * leaves them out. An id already gone is passed over.
415
+ */
416
+ dismissNotifications(ids: readonly number[]): void;
417
+ /**
418
+ * Press one of a notification's actions -- "default" for the notification
419
+ * itself. It names a notification and a key it offered, and nothing about
420
+ * what the press does, for copyClipboardEntry's reason: that is the
421
+ * application's. An empty action throws.
422
+ */
423
+ invokeNotificationAction(id: number, action: string): void;
424
+ /**
425
+ * Which window has the keyboard: the compositor's seat, and where the keys
426
+ * this page hears are sent. focusChrome() takes it back to the page.
378
427
  */
379
428
  focusApp(appId: string): void;
380
429
  focusChrome(): void;
381
430
  /**
382
- * Move the pointer to `x`, `y` in page coordinates (`clientX`/`clientY`).
431
+ * Put the pointer at a place in this page, in the page's own coordinates --
432
+ * the ones a PointerEvent reports as clientX/clientY.
383
433
  *
384
- * Handled by the engine, which draws the cursor. Does nothing when nested in
385
- * another compositor.
434
+ * THE COMPANION TO MOVING THE KEYBOARD. A desktop whose focus follows the
435
+ * cursor has to take the cursor with it when a key moves the focus, or the
436
+ * next pointer event hands the focus back to whatever the pointer is still
437
+ * over. Every other compositor does this itself; here the shell is a page,
438
+ * and a page can read where the pointer is and cannot put it anywhere.
439
+ *
440
+ * Doubles for resizeApp's reason: a place in a page is a CSS pixel and a CSS
441
+ * pixel is fractional. A coordinate outside this page is clamped to its own
442
+ * box -- what a page may move is the pointer over itself -- and NaN throws.
386
443
  */
387
444
  warpPointer(x: number, y: number): void;
388
- /** Ask a client to close. It may refuse, for example to offer a save. */
445
+ /**
446
+ * Ask a client to close. Not a kill -- the client decides, which is why a
447
+ * window can refuse and show a save dialog instead.
448
+ */
389
449
  closeApp(appId: string): void;
390
450
  /**
391
- * Report where the page put an `<app>`, in page CSS pixels. The client draws
392
- * at the scale of the monitor holding most of that box.
451
+ * The layout box, as a resize. For an <app> the box IS the configure.
452
+ *
453
+ * Doubles because a CSS pixel is fractional and this comes from a layout box.
454
+ */
455
+ resizeApp(appId: string, width: number, height: number): void;
456
+ /**
457
+ * Where the page put an <app>, in the page's CSS pixels. The client draws at
458
+ * the scale of the monitor holding most of that box. Layout still places the
459
+ * window; this only tells the compositor where it ended up.
393
460
  */
394
461
  setAppBounds(appId: string, x: number, y: number, width: number, height: number): void;
395
462
  /**
396
- * Set the desktop theme.
463
+ * Draw the desktop the other way round.
464
+ *
465
+ * THE ONE CALL HERE THAT SAYS WHAT THE DESKTOP IS rather than asking it for
466
+ * something. It reaches the compositor and comes back as `themechanged` --
467
+ * to every chrome on the desk, this one included, which is why a shell
468
+ * renders from `theme` rather than from its own click. Three monitors are
469
+ * three pages and the toggle is on one of them.
397
470
  *
398
- * Answered with `themechanged` to every chrome, including this one, so
399
- * render from {@link theme}. The compositor also serves the theme to clients
400
- * through the settings portal. Not saved to the config.
471
+ * It has to leave the page at all because the compositor is the only process
472
+ * the desk's CLIENTS can hear: it answers the settings portal GTK, Qt and
473
+ * Electron read a color scheme from, out of this same value. A theme kept in
474
+ * the renderer would be a desktop whose panels went dark and whose windows
475
+ * stayed light.
476
+ *
477
+ * A DomicileTheme rather than a DOMString, so a word that is not one of the
478
+ * two is refused at the call instead of at the socket -- see
479
+ * domicile_theme.idl.
401
480
  */
402
- setTheme(theme: Theme): void;
481
+ setTheme(theme: DomicileTheme): void;
403
482
  /**
404
- * Try to unlock the desktop with `passphrase`.
483
+ * Offer a passphrase at a locked desk.
484
+ *
485
+ * THE WAY OUT OF THE ONE STATE THIS PAGE CANNOT CHANGE BY DRAWING. While the
486
+ * desk is locked the compositor puts nothing this page forwards into the
487
+ * Wayland seat -- no client sees a keystroke or a click -- and this call is
488
+ * the only thing that ends it. The page keeps its own keys throughout, which
489
+ * is what makes a lock screen possible: it is the thing forwarding, so it can
490
+ * take a passphrase while nothing it forwards arrives anywhere.
405
491
  *
406
- * Answered with `lockedchanged` to every chrome. Hide the lock screen only
407
- * when {@link locked} says unlocked. A wrong passphrase leaves it `true`.
408
- * There is no retry limit or delay yet.
492
+ * ANSWERED WITH `lockedchanged` AND NOT WITH A RETURN VALUE, and only when
493
+ * the desk actually opened. A wrong passphrase produces nothing at all: the
494
+ * compositor says so in its own log, without the passphrase in it. A page
495
+ * that cleared its own lock screen because it believed its own keystrokes
496
+ * would be a lock anybody with the devtools could open -- and the event goes
497
+ * to every chrome on the desk rather than to this one, because three monitors
498
+ * are three pages and the desk they draw has one lock.
409
499
  */
410
500
  unlock(passphrase: string): void;
411
501
  /**
412
- * Lock the desktop now.
413
- *
414
- * Answered with `lockedchanged` to every chrome. Does nothing when no lock
415
- * is configured.
502
+ * Lock the desk now, whoever is at it. Answered like unlock(): with
503
+ * `lockedchanged` to every chrome, and only when the desk actually shut.
416
504
  */
417
505
  lock(): void;
418
506
  /**
419
- * Tell the compositor this page has captured its old frame for a `theme`
420
- * transition, so client windows can switch theme now.
507
+ * This page's old frame is held for `theme`: turn the desk's windows now.
421
508
  *
422
- * The windows must switch after the capture and before the transition
423
- * starts. The compositor waits for every chrome (with a timeout), updates
424
- * the windows, and fires `windowsthemechanged` once they repaint.
509
+ * Called from inside a shell's wipe, once the frame it wipes away from is
510
+ * captured, so that the wipe passes across windows that turn inside it
511
+ * rather than over windows that had already turned. Answered with a
512
+ * `windowsthemechanged` once every chrome has called it and the windows have
513
+ * had their chance to repaint -- or once the compositor stopped waiting.
425
514
  */
426
- themeCaptured(theme: Theme): void;
515
+ themeCaptured(theme: DomicileTheme): void;
427
516
  /**
428
- * Route a key combination to the page instead of the focused client.
517
+ * Route a key combination to the page rather than to the focused client.
429
518
  *
430
- * By name (`grabShortcut("Meta+Shift+l")`, sway's grammar), the engine finds
431
- * the key the keysym is on, and again on layout change. Presses arrive as
432
- * `shortcut` events whose `chord` is this string, from a `<webview>` or this
433
- * page (taken from the page).
519
+ * The press comes back as a `shortcut` event carrying the same fields, so a
520
+ * shell compares what it grabbed against what fired without parsing a string.
434
521
  *
435
- * As a {@link DomicileShortcut}, presses arrive with the same fields and an
436
- * empty `chord`.
522
+ * The same, by name: `grabShortcut("Meta+Shift+l")`, in sway's grammar. The
523
+ * engine finds the key the keysym is on, from the keyboard the compositor
524
+ * describes, and finds it again whenever the layout changes. The press comes
525
+ * back as a `shortcut` event whose `chord` is this string -- pressed in a
526
+ * `<webview>` or on this page, where it is taken from the page.
437
527
  *
438
- * @throws `SyntaxError` for a malformed chord; `NotFoundError` for a keysym
439
- * the keyboard cannot type, once the compositor has described it.
528
+ * Throws a SyntaxError for a chord written wrong, and a NotFoundError for a
529
+ * keysym the keyboard cannot type; before any keyboard is described, that
530
+ * one is a console warning once it is.
440
531
  */
441
- grabShortcut(shortcut: string | DomicileShortcut): void;
532
+ grabShortcut(shortcut: DomicileShortcut | string): void;
442
533
  /**
443
- * Click an extension's action: grants `activeTab` on the active tab, and
444
- * fires `action.onClicked` if there is no popup. The shell opens any popup
445
- * itself, in a `<webview>` at {@link DomicileExtension.popup}.
534
+ * Click an extension's action, popup or not: the extension is granted
535
+ * activeTab on the active tab, as a toolbar click grants it in Chrome. Then
536
+ * an action with no popup has its `action.onClicked` dispatched; one with a
537
+ * popup is the shell's to open, as a <webview> at the `popup` its
538
+ * `extensions` entry names, and is opened after this call.
539
+ *
540
+ * NOT THE COMPOSITOR'S. An action lives in this browser, so the call goes no
541
+ * further than the browser process, over the tray's own pipe -- see
542
+ * components/domicile/mojom/extension_tray.mojom. An id that names no
543
+ * extension with an action now is a click that raced an uninstall, and does
544
+ * nothing; an empty one throws.
446
545
  */
447
546
  activateExtension(id: string): void;
448
547
  /**
449
548
  * Opens a browser window at `url`, for the shell's own UI such as a `+`
450
- * button. It appears in the next `browserwindowschanged`. Windows that
451
- * programs open (`domicile open-url`, `target="_blank"`, extensions) arrive
452
- * the same way.
549
+ * button or a launcher. It appears in the next `browserwindowschanged`. A
550
+ * domicile:// address shows blocked, as in a <webview>.
453
551
  */
454
552
  openBrowserWindow(url: string): void;
455
553
  /**
456
- * Closes browser window `id`. It leaves the next `browserwindowschanged`.
457
- * Does nothing for an unknown id.
554
+ * Closes window `id`; it leaves the next `browserwindowschanged`. An unknown
555
+ * id does nothing.
458
556
  */
459
557
  closeBrowserWindow(id: string): void;
460
558
  /**
461
- * Click a system tray icon with the button `action` names. Does nothing if
462
- * the application has exited. Throws on an empty id.
463
- */
464
- activateTrayItem(id: string, action: TrayAction): void;
465
- /**
466
- * Dismiss notifications by id (from {@link notifications}) and tell their
467
- * applications. Unknown ids are ignored.
468
- */
469
- dismissNotifications(ids: readonly number[]): void;
470
- /**
471
- * Invoke a notification's action (`"default"` for clicking it), then close
472
- * it. Unknown actions are ignored.
473
- */
474
- invokeNotificationAction(id: number, action: string): void;
475
- /**
476
- * The desktop's screens.
559
+ * The screens of the desktop.
477
560
  *
478
- * An attribute so components that mount late can read it. `null` until the
479
- * compositor reports, which differs from an empty array (no screens). A new
480
- * frozen array after each `displayschanged`.
481
- */
482
- readonly displays: readonly DomicileDisplay[] | null;
483
- /**
484
- * The browser windows, oldest first.
561
+ * An attribute rather than an event payload, because the desktop is a fact
562
+ * and not a stream. A shell reads this when it needs it -- including a
563
+ * component that mounts long after the description arrived, which an event
564
+ * would have left with nothing.
485
565
  *
486
- * An attribute so components that mount late can read it. `null` until the
487
- * browser lists them, which it does as soon as something listens.
566
+ * NULL UNTIL THE COMPOSITOR HAS DESCRIBED THE DESKTOP, and an empty array
567
+ * for a desktop with no screens on it. They are different states and a shell
568
+ * renders them differently: nothing at all is what an unknown screen shows,
569
+ * which is the right answer for "there is no such screen" and the wrong one
570
+ * for "wait". `domicile-protocol` carries that distinction deliberately --
571
+ * an empty `Vec` that serializes to nothing and one that serializes to `[]`
572
+ * both come back empty, and only the second is a desktop a shell can parse
573
+ * -- and an attribute that started empty threw it away again here.
574
+ *
575
+ * The compositor describes at least one output, so it does not send the
576
+ * empty one; the `domicile` daemon, which no desktop has been described to,
577
+ * does. Leaning on "in practice it never happens" is what made the SDK guess,
578
+ * and this is instead of the guess.
579
+ *
580
+ * No [SameObject]: the compositor sends the whole desktop each time it
581
+ * changes and a FrozenArray is frozen, so this is a different array after
582
+ * every `displayschanged`. That is also the shape a shell wants -- an
583
+ * identity that survived a desktop it no longer describes would be worse.
488
584
  */
489
- readonly browserWindows: readonly DomicileBrowserWindow[] | null;
585
+ readonly displays: readonly DomicileDisplay[] | null;
490
586
  /**
491
- * Every client window, in the order they appeared. Reading it binds the
492
- * channel, and the compositor announces the windows already running as it
493
- * does, so a shell that reads late misses nothing: read, then listen for
494
- * `windowschanged`. A different frozen array after every change.
587
+ * Every client window on the desktop, in the order they appeared: what the
588
+ * compositor has said about each so far. Reading it binds the channel, and
589
+ * the compositor announces the windows already running as it does, so a
590
+ * shell that reads late misses nothing -- it reads, then listens for
591
+ * `windowschanged`. A different array after every change, for `displays`'s
592
+ * reason.
495
593
  */
496
594
  readonly windows: readonly DomicileWindow[];
497
- /** The window holding the keyboard, or `null` when the shell's page holds it. */
595
+ /** The window holding the keyboard, or null when the shell's page holds it. */
498
596
  readonly focusedWindow: string | null;
499
597
  /**
500
- * What has been copied on this desktop, newest first. **Not
501
- * `navigator.clipboard`**, which answers out of the browser's own clipboard
502
- * and no Wayland client's.
598
+ * THE DESK'S STATE, AS ATTRIBUTES. Each is what the compositor last said,
599
+ * null until it has said anything, and a bare `<name>changed` event says it
600
+ * moved: a shell reads, then listens, and one that listens late misses
601
+ * nothing. See docs/architecture/WINDOW-DOMICILE.md.
503
602
  */
504
603
  readonly clipboard: readonly DomicileClipboardEntry[] | null;
505
- /** The system tray: every application showing an icon, in the order they registered. */
506
604
  readonly tray: readonly DomicileTrayItem[] | null;
507
- /** The desk's notifications: every one not yet cleared, oldest first. */
508
605
  readonly notifications: readonly DomicileNotification[] | null;
509
- /** The extensions with an action, from this browser rather than the compositor. */
510
606
  readonly extensions: readonly DomicileExtension[] | null;
511
- /**
512
- * Whether anybody is at this desktop. **Not `document.visibilityState`**: a
513
- * shell's document stays visible while the glass is off. `null` on a desktop
514
- * with no idle timeout.
515
- */
516
607
  readonly idle: boolean | null;
517
- /**
518
- * Whether this desk is locked: `true` is a desk that delivers nothing to
519
- * any client. The compositor holds it, so a reload does not open it. `null`
520
- * on a desktop with no passphrase.
521
- */
522
608
  readonly locked: boolean | null;
609
+ readonly theme: DomicileTheme | null;
610
+ readonly windowsTheme: DomicileTheme | null;
523
611
  /**
524
- * Which way round the desktop is drawn. **Not `prefers-color-scheme`**: the
525
- * theme is the compositor's, and {@link setTheme} is how a page moves it.
526
- */
527
- readonly theme: Theme | null;
528
- /**
529
- * Which way round the desk's windows are drawn: {@link theme}'s other half,
530
- * moved once they have turned — see {@link themeCaptured}.
612
+ * The compositor seat's modifiers -- only the keys forwarded to a client;
613
+ * see @domicile-desktop/sdk's README before trusting them over a page's
614
+ * own key events.
531
615
  */
532
- readonly windowsTheme: Theme | null;
533
- /** The compositor seat's — see the README before trusting them. */
534
616
  readonly altKey: boolean | null;
535
617
  readonly ctrlKey: boolean | null;
536
618
  readonly shiftKey: boolean | null;
537
619
  readonly metaKey: boolean | null;
620
+ /**
621
+ * The desk's browser windows, oldest first. Null until the browser lists
622
+ * them, which it does as soon as this page listens. The browser holds each
623
+ * page; draw one with `<webview window="id">`. A shell loaded over this one
624
+ * gets the same windows and pages. See
625
+ * components/domicile/mojom/browser_windows.mojom.
626
+ *
627
+ * A window a program opens (`domicile open-url`, a page's target="_blank",
628
+ * an extension) appears here in the next `browserwindowschanged`. No
629
+ * [SameObject], for `displays`' reason.
630
+ */
631
+ readonly browserWindows: readonly DomicileBrowserWindow[] | null;
632
+ /**
633
+ * Typed by the event map rather than by inheriting `EventTarget`, whose
634
+ * overloads take any name and type every listener's event as `Event`.
635
+ * `dispatchEvent` is left out: a page does not dispatch to it.
636
+ */
538
637
  addEventListener<T extends keyof DomicileHostEventMap>(type: T, listener: (event: DomicileHostEventMap[T]) => void): void;
539
638
  removeEventListener<T extends keyof DomicileHostEventMap>(type: T, listener: (event: DomicileHostEventMap[T]) => void): void;
540
639
  };
640
+ /**
641
+ * One notification: an application's call to org.freedesktop.Notifications,
642
+ * which the compositor serves -- a page's own Web Notification included,
643
+ * because the browser shows one by calling that server.
644
+ *
645
+ * An interface rather than a dictionary for DomicileTrayItem's reason: these
646
+ * are read off DomicileHost.notifications.
647
+ */
648
+ export type DomicileNotification = {
649
+ /**
650
+ * What DomicileHost.dismissNotifications() and invokeNotificationAction()
651
+ * name this notification by. One its application replaced keeps its id.
652
+ */
653
+ readonly id: number;
654
+ /** Who sent it, as it named itself. May be empty. */
655
+ readonly appName: string;
656
+ /** The one line that says what happened. */
657
+ readonly summary: string;
658
+ /** More, as plain text: the compositor tells senders it takes no markup. */
659
+ readonly body: string;
660
+ /**
661
+ * The picture, as a `data:` URL, or empty for nothing to draw. A URL rather
662
+ * than a path for DomicileTrayItem.icon's reason.
663
+ */
664
+ readonly icon: string;
665
+ /**
666
+ * "low", "normal" or "critical": the spec's urgency hint. A word rather than
667
+ * an enum because it is only ever read, and the SDK parses it.
668
+ */
669
+ readonly urgency: string;
670
+ /** Its buttons. A press on the notification itself is `clickable`. */
671
+ readonly actions: readonly DomicileNotificationAction[];
672
+ /** Whether it offers the "default" action: a press on the notification. */
673
+ readonly clickable: boolean;
674
+ /**
675
+ * How long it asked to stay up, in milliseconds: 0 for until it is
676
+ * dismissed, -1 for the shell's choice.
677
+ */
678
+ readonly timeoutMs: number;
679
+ /**
680
+ * When it arrived or was last replaced, in milliseconds since the epoch on
681
+ * the compositor's clock.
682
+ */
683
+ readonly time: number;
684
+ };
685
+ /**
686
+ * One button of a notification.
687
+ *
688
+ * An interface rather than a dictionary because these are read off a
689
+ * DomicileNotification's array; see DomicileTrayItem.
690
+ */
691
+ export type DomicileNotificationAction = {
692
+ /** The id DomicileHost.invokeNotificationAction() takes. */
693
+ readonly key: string;
694
+ /** The button's label. */
695
+ readonly label: string;
696
+ };
697
+ /**
698
+ * A combination claimed with grabShortcut() was pressed.
699
+ *
700
+ * Its own type rather than a DomicileAppEvent with the combination in the
701
+ * `title` slot. A shortcut is not a window's name and does not belong to a
702
+ * window at all: it fires whatever holds the keyboard, which is the point of
703
+ * grabbing it.
704
+ *
705
+ * Only presses. A release changes nothing and would arrive as a second event
706
+ * for one keystroke.
707
+ */
708
+ export type DomicileShortcutEvent = Event & {
709
+ /**
710
+ * The chord as the shell grabbed it -- `Meta+Shift+l` -- or empty for one
711
+ * grabbed as a keycode. See DomicileHost.grabShortcut().
712
+ */
713
+ readonly chord: string;
714
+ /** The Linux evdev code, the same numbering key() forwards in. */
715
+ readonly keycode: number;
716
+ readonly altKey: boolean;
717
+ readonly ctrlKey: boolean;
718
+ readonly shiftKey: boolean;
719
+ readonly metaKey: boolean;
720
+ };
721
+ /**
722
+ * One icon in the system tray.
723
+ *
724
+ * An interface rather than a dictionary for DomicileClipboardEntry's reason:
725
+ * WebIDL will not have a dictionary as the element type of an attribute's
726
+ * array, and these are read off DomicileHost.tray.
727
+ */
728
+ export type DomicileTrayItem = {
729
+ /**
730
+ * What DomicileHost.activateTrayItem() names this icon by: the bus name the
731
+ * application answers on and the object path it answers at, written together.
732
+ */
733
+ readonly id: string;
734
+ /**
735
+ * What the icon is, in words: its tooltip's title, its title, or its id, the
736
+ * first the application said. Never empty.
737
+ */
738
+ readonly title: string;
739
+ /**
740
+ * The picture, as a `data:` URL to draw, or empty for one the compositor
741
+ * could not draw. A URL rather than a path: the page may draw what the
742
+ * compositor read and cannot read it.
743
+ */
744
+ readonly icon: string;
745
+ };
746
+ /**
747
+ * One client window on the desktop, as `DomicileHost.windows` lists it: what
748
+ * the compositor has said about it so far. Replaced, never edited: a change to
749
+ * any window is a new `windows` array and a `windowschanged` event.
750
+ */
751
+ export type DomicileWindow = {
752
+ readonly appId: string;
753
+ /** Empty until the client names itself. */
754
+ readonly title: string;
755
+ /**
756
+ * The size the client last committed, in CSS pixels; null until it has
757
+ * committed one.
758
+ */
759
+ readonly width: number | null;
760
+ readonly height: number | null;
761
+ /** The limits the client asked for; null where it asked for none. */
762
+ readonly minWidth: number | null;
763
+ readonly minHeight: number | null;
764
+ readonly maxWidth: number | null;
765
+ readonly maxHeight: number | null;
766
+ readonly cursor: DomicileCursorShape;
767
+ /**
768
+ * A popup's parent window and its place relative to it, and whether it
769
+ * grabbed the pointer; null and false for a toplevel.
770
+ */
771
+ readonly parent: string | null;
772
+ readonly x: number | null;
773
+ readonly y: number | null;
774
+ readonly grab: boolean;
775
+ };
@@ -1,16 +1,11 @@
1
- // Types for the desktop a shell is handed (`Shell(root, domicile)`), its
2
- // channel to the compositor.
1
+ // Generated from the engine's WebIDL by `codegen/generate-domicile-host.ts`.
2
+ // Do not edit. Change the IDL in
3
+ // `packages/domicile-engine/src/third_party/blink/renderer/modules/domicile/`
4
+ // and run `bun run generate` in `packages/chrome-sdk`.
3
5
  //
4
- // This mirrors the engine's WebIDL in
5
- // `packages/domicile-engine/src/third_party/blink/renderer/modules/domicile/`.
6
- // If they disagree, the IDL is correct. See
7
- // `docs/architecture/WINDOW-DOMICILE.md`.
6
+ // The desktop a shell is handed (`Shell(root, domicile)`). There is no global;
7
+ // a shell keeps what it was handed. See `shell.ts`.
8
8
  //
9
- // The engine runs the shell module and passes the host into its `Shell`
10
- // (`DomicileShell`). There is no `navigator.domicile` or `window.domicile`, so
11
- // no global is declared here; a shell keeps what it was handed. See
12
- // `shell.ts`.
13
- //
14
- // Sizes and coordinates are fractional CSS pixels, except in
15
- // {@link DomicileDisplay}. Keycodes are Linux evdev codes.
9
+ // Sizes and coordinates are fractional CSS pixels, except in `DomicileDisplay`.
10
+ // Keycodes are Linux evdev codes.
16
11
  export {};
package/package.json CHANGED
@@ -91,11 +91,12 @@
91
91
  },
92
92
  "scripts": {
93
93
  "build": "tsc -p tsconfig.build.json",
94
+ "generate": "bun codegen/generate-domicile-host.ts",
94
95
  "test:types": "tsc --noEmit",
95
96
  "test:unit": "bun test"
96
97
  },
97
98
  "type": "module",
98
- "version": "0.0.0-alpha-028d5f9f87e0",
99
+ "version": "0.0.0-alpha-e040773499e5",
99
100
  "repository": {
100
101
  "type": "git",
101
102
  "url": "git+https://github.com/cprussin/domicile.git",