@domicile-desktop/sdk 0.0.0-alpha-f2862631a576
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/LICENSE +21 -0
- package/README.md +235 -0
- package/dist/app-element.d.ts +93 -0
- package/dist/app-element.js +63 -0
- package/dist/audio.d.ts +57 -0
- package/dist/audio.js +9 -0
- package/dist/bind-keys.d.ts +63 -0
- package/dist/bind-keys.js +156 -0
- package/dist/chrome-message.d.ts +13 -0
- package/dist/chrome-message.js +26 -0
- package/dist/connect-to-host.d.ts +58 -0
- package/dist/connect-to-host.js +140 -0
- package/dist/cursor-shape.d.ts +40 -0
- package/dist/cursor-shape.js +69 -0
- package/dist/desktop-size.d.ts +38 -0
- package/dist/desktop-size.js +32 -0
- package/dist/device-pixel-ratio.d.ts +35 -0
- package/dist/device-pixel-ratio.js +25 -0
- package/dist/display-transform.d.ts +16 -0
- package/dist/display-transform.js +51 -0
- package/dist/domicile-client.d.ts +296 -0
- package/dist/domicile-client.js +697 -0
- package/dist/domicile-host.d.ts +1146 -0
- package/dist/domicile-host.js +42 -0
- package/dist/element-context.d.ts +25 -0
- package/dist/element-context.js +34 -0
- package/dist/element-transform.d.ts +29 -0
- package/dist/element-transform.js +44 -0
- package/dist/extension.d.ts +12 -0
- package/dist/extension.js +36 -0
- package/dist/file-preview.d.ts +53 -0
- package/dist/file-preview.js +53 -0
- package/dist/focus-app.d.ts +21 -0
- package/dist/focus-app.js +25 -0
- package/dist/focus-chrome.d.ts +21 -0
- package/dist/focus-chrome.js +25 -0
- package/dist/host-message.d.ts +592 -0
- package/dist/host-message.js +347 -0
- package/dist/host-stream.d.ts +9 -0
- package/dist/host-stream.js +65 -0
- package/dist/input.d.ts +7 -0
- package/dist/input.js +179 -0
- package/dist/key-action.d.ts +24 -0
- package/dist/key-action.js +26 -0
- package/dist/keybindings.d.ts +12 -0
- package/dist/keybindings.js +20 -0
- package/dist/keyboard-input.d.ts +3 -0
- package/dist/keyboard-input.js +190 -0
- package/dist/matrix.d.ts +27 -0
- package/dist/matrix.js +69 -0
- package/dist/measure.d.ts +35 -0
- package/dist/measure.js +435 -0
- package/dist/newline-frames.d.ts +2 -0
- package/dist/newline-frames.js +5 -0
- package/dist/notification.d.ts +59 -0
- package/dist/notification.js +23 -0
- package/dist/own-keybindings.d.ts +20 -0
- package/dist/own-keybindings.js +78 -0
- package/dist/pointer-input.d.ts +3 -0
- package/dist/pointer-input.js +164 -0
- package/dist/protocol.d.ts +596 -0
- package/dist/protocol.js +590 -0
- package/dist/register-elements.d.ts +15 -0
- package/dist/register-elements.js +37 -0
- package/dist/shell.d.ts +9 -0
- package/dist/shell.js +2 -0
- package/dist/shortcut-claims.d.ts +14 -0
- package/dist/shortcut-claims.js +35 -0
- package/dist/surface-coordinates.d.ts +15 -0
- package/dist/surface-coordinates.js +32 -0
- package/dist/theme.d.ts +7 -0
- package/dist/theme.js +40 -0
- package/dist/tray.d.ts +29 -0
- package/dist/tray.js +16 -0
- package/dist/webview-element.d.ts +497 -0
- package/dist/webview-element.js +312 -0
- package/dist/wheel-axis.d.ts +15 -0
- package/dist/wheel-axis.js +34 -0
- package/package.json +167 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Connor Prussin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# @domicile-desktop/sdk
|
|
2
|
+
|
|
3
|
+
> Published to npm, and usable outside this repo. If you are writing a shell,
|
|
4
|
+
> start with [/docs/WRITING-A-SHELL.md](/docs/WRITING-A-SHELL.md) — this is the
|
|
5
|
+
> reference for the package, that is the guide to using it.
|
|
6
|
+
>
|
|
7
|
+
> It ships built JavaScript and `.d.ts` from `dist/`, not the TypeScript in
|
|
8
|
+
> `src/`: run `bun run build` before anything outside the workspace resolves it.
|
|
9
|
+
|
|
10
|
+
The in-page half of Domicile. A Domicile chrome is ordinary web content; this
|
|
11
|
+
package is what lets that content talk to the compositor and mount real Wayland
|
|
12
|
+
clients as DOM elements.
|
|
13
|
+
|
|
14
|
+
It provides these:
|
|
15
|
+
|
|
16
|
+
- **`DomicileClient`** (`./domicile-client`) — the client for `window.domicile`, the
|
|
17
|
+
typed control channel the forked engine puts on a document it served. It
|
|
18
|
+
takes a `DomicileHost` and gives back a handler table for what the compositor
|
|
19
|
+
says and a typed call per thing the chrome asks of it. There is no handshake
|
|
20
|
+
and no wire: what it adds over the host itself is that it **registers its own
|
|
21
|
+
listeners in its constructor and holds what arrives before your `on` does** —
|
|
22
|
+
a DOM event dispatched with no listener is gone, and a React shell registers
|
|
23
|
+
tens of milliseconds after the compositor has announced every window already
|
|
24
|
+
running. For the same reason a page must never call `addEventListener` on
|
|
25
|
+
`window.domicile` itself. One message is not the compositor's: `open_url` is
|
|
26
|
+
an address `domicile open-url` asked the desktop to open — what `BROWSER`
|
|
27
|
+
runs inside it. Opening it is the shell's.
|
|
28
|
+
- **`Shell`** (`./shell`) — the type of a shell module's `Shell` export: what
|
|
29
|
+
Domicile calls, once, with the element to draw in. A module without one is
|
|
30
|
+
refused on the screen.
|
|
31
|
+
- **`registerElements`** (`./register-elements`) — the input routing behind the
|
|
32
|
+
engine's `<app>` tag. It forwards the pointer over a window to the client
|
|
33
|
+
underneath in that client's own surface coordinates, and routes the page's
|
|
34
|
+
keystrokes to whichever window was last reached for. Nothing here says how big
|
|
35
|
+
a window is: an `<app>`'s layout box *is* the client's
|
|
36
|
+
`xdg_toplevel.configure`, and the engine states it off the layout it
|
|
37
|
+
performed. All of it is delegated from `document` over
|
|
38
|
+
`closest("app")`: the tag is the engine's, so there is no element class to hang
|
|
39
|
+
any of it on, and nothing here is registered. A click on a window fires a
|
|
40
|
+
cancelable `domicile-focus-requested` and then focuses the client, and a press
|
|
41
|
+
that lands off every window fires a cancelable
|
|
42
|
+
`domicile-focus-release-requested` on the window that holds the keyboard and
|
|
43
|
+
then hands it back to the page — so a shell that wants focus to be its own
|
|
44
|
+
decision calls `preventDefault()` on either, and a shell with no opinion needs
|
|
45
|
+
to know nothing about them. A right-click over a window also loses the
|
|
46
|
+
browser's own context menu, because the press is forwarded and the menu a
|
|
47
|
+
right-click asked for is the client's, drawn inside its surface. Only over an
|
|
48
|
+
`<app>`: a `contextmenu` anywhere else — the desktop, a shell's chrome, the
|
|
49
|
+
page inside a `<webview>` — is left uncanceled and keeps its menu.
|
|
50
|
+
- **`<app>`** (`./app-element`) — the tag name, the two focus events, and the
|
|
51
|
+
TypeScript for the element, which is the fork's. The surface embed and the size
|
|
52
|
+
a client is configured at are both the layout box's and the engine reports
|
|
53
|
+
them; there is nothing to call.
|
|
54
|
+
- **`focusApp`** (`./focus-app`) — put the keyboard on a client without a click.
|
|
55
|
+
Not the same as `DomicileClient.focusApp`, which asks the compositor and stops:
|
|
56
|
+
keyboard events reach `document` rather than an element, so the SDK has to be
|
|
57
|
+
told too.
|
|
58
|
+
- **`<webview>`** (`./webview-element`) — types and event names only. The
|
|
59
|
+
element is the engine's: `src` is the address it loads, `goBack` /
|
|
60
|
+
`goForward` / `stop` / `reload` are what a chrome's address bar drives it
|
|
61
|
+
with, `canGoBack` / `canGoForward` say whether the first two would do
|
|
62
|
+
anything, `loading` says whether a page is still arriving, and `focus` puts
|
|
63
|
+
the keyboard on the embedded page. What this module adds is the TypeScript
|
|
64
|
+
for all of that plus the names of the four events the browser process
|
|
65
|
+
dispatches on it, `domicile-guest-focus`, `domicile-history-change`,
|
|
66
|
+
`domicile-loading-change` and `domicile-new-window`. The last is the only one
|
|
67
|
+
that carries anything — `event.url`, the address a link with
|
|
68
|
+
`target="_blank"` asked to open — because it is the only one that is not about
|
|
69
|
+
state the element already holds: the browser process opens no window for a
|
|
70
|
+
guest, so a shell that ignores it is a desktop where such a link does
|
|
71
|
+
nothing.
|
|
72
|
+
`domicile-close` is the page calling `window.close()` — an extension's popup
|
|
73
|
+
closing itself — and removing the view is the shell's answer.
|
|
74
|
+
`domicile-content-size-change` says `contentWidth` / `contentHeight` moved:
|
|
75
|
+
the size the page's content wants, which Chrome sizes an extension's popup
|
|
76
|
+
from.
|
|
77
|
+
`domicile-popup-window` is an extension's `chrome.windows.create` with a
|
|
78
|
+
popup: `windowId`, `url`, `width` and `height` (0 where it named none). The
|
|
79
|
+
shell opens a window whose view carries `popupwindow="<windowId>"` from its
|
|
80
|
+
first render — the engine reads it once, as the view is connected — and that
|
|
81
|
+
view is then the extension's window.
|
|
82
|
+
- **`Extension`** (`./extension`) — one row of the tray, as
|
|
83
|
+
`DomicileClient.on("extensions", …)` delivers it, and the Zod schema it is
|
|
84
|
+
parsed with. A click on one is `DomicileClient.activateExtension(id)`, which
|
|
85
|
+
grants it `activeTab`; one with a `popup` is then a `<webview>` at that
|
|
86
|
+
address.
|
|
87
|
+
- **`TrayItem`, `TrayAction`** (`./tray`) — one icon of the system tray, as
|
|
88
|
+
`DomicileClient.on("tray", …)` delivers it, and the button a click was. A
|
|
89
|
+
click is `DomicileClient.activateTrayItem(id, action)`.
|
|
90
|
+
- **`bindKeys`** (`./bind-keys`) — a shell's own keys, claimed and answered:
|
|
91
|
+
`bindKeys(domicile, { keybindings, modes }, { onCommand, onModeChanged })`,
|
|
92
|
+
each table a chord (`"Meta+Shift+l"`) to a `KeyAction`. The chords are
|
|
93
|
+
resolved by `./own-keybindings` against the keyboard the compositor
|
|
94
|
+
describes (`shell_config`), again whenever the layout moves. A
|
|
95
|
+
`KeyAction.SendShell(words)` calls `onCommand(words)`, whose meaning is the
|
|
96
|
+
shell's; `KeyAction.Mode(name)` is answered here, and `onModeChanged` says
|
|
97
|
+
the mode moved. It returns `{ unbind, setMode }`: `setMode` is how a desktop
|
|
98
|
+
of several pages keeps one mode across them (one the shell lacks goes back
|
|
99
|
+
to `default`). It owns the `shell_config` and `shortcut` slots of
|
|
100
|
+
`DomicileClient.on`, and the chords it claims are never given back — the
|
|
101
|
+
channel cannot release one. `./keybindings` is its pure half (what a press
|
|
102
|
+
does in a mode) and `./key-action` the action a binding carries.
|
|
103
|
+
- **`Notification`** (`./notification`) — one notification, as
|
|
104
|
+
`DomicileClient.on("notifications", …)` delivers it: an application's or a
|
|
105
|
+
site's. A press is `invokeNotificationAction(id, key)`, a clear
|
|
106
|
+
`dismissNotifications(ids)`.
|
|
107
|
+
- **`connectToHost`** (`./connect-to-host`) — the `DomicileHost` off the
|
|
108
|
+
document, or a stand-in that does nothing when there is none. `hasHost` is
|
|
109
|
+
beside it for code that needs the answer rather than the object.
|
|
110
|
+
- **`reportDesktopSize`** (`./desktop-size`) — tell the host how big the page
|
|
111
|
+
is, and keep telling it. The desktop spans every display and the page is what
|
|
112
|
+
measures it, so a shell that never reported would leave the compositor
|
|
113
|
+
laying windows out against a size it guessed.
|
|
114
|
+
- **`reportDevicePixelRatio`** (`./device-pixel-ratio`) — tell the host what
|
|
115
|
+
density the page is drawing at, and keep telling it. The ratio changes when
|
|
116
|
+
the window moves to another display or the page is zoomed, and the page is
|
|
117
|
+
the only part of Domicile that can see either; a chrome that reported it once
|
|
118
|
+
would leave every client drawing at the old resolution.
|
|
119
|
+
- **Pure helpers** — affine `./matrix` math mirroring the Rust
|
|
120
|
+
`domicile-scene::Transform`, `./domicile-host` mirroring the engine's IDL,
|
|
121
|
+
`./host-message` for what the client delivers and how an event becomes one,
|
|
122
|
+
`./cursor-shape` for the keyword set a client can ask for, and `./input`
|
|
123
|
+
keycode mapping.
|
|
124
|
+
- **The routing parts, published so they can be substituted** — `./measure` is
|
|
125
|
+
what `registerElements` takes an override of, and `./element-transform` and
|
|
126
|
+
`./surface-coordinates` are what invert a window's affine so a click under a
|
|
127
|
+
CSS rotation — or a `zoom`, which is not a transform and is carried
|
|
128
|
+
separately — lands where the user pressed. The one construct they cannot
|
|
129
|
+
invert is a perspective projection, which is not an affine at all; `./measure`
|
|
130
|
+
maps the window flat and says so on the console. A shell needs none of them;
|
|
131
|
+
a test of one does.
|
|
132
|
+
- **The compositor's own JSON wire**, which **a page no longer speaks** —
|
|
133
|
+
`./protocol`, `./chrome-message`, `./newline-frames` and `./host-stream` are
|
|
134
|
+
there for `@domicile-desktop/e2e-harness`, a headless stand-in for a chrome that
|
|
135
|
+
talks to the compositor's socket directly. The one exception is
|
|
136
|
+
`shell_config`, which the engine forwards as the compositor's line:
|
|
137
|
+
`./host-message` parses it with `./protocol`'s schema.
|
|
138
|
+
|
|
139
|
+
## Usage
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { DomicileClient } from "@domicile-desktop/sdk/domicile-client";
|
|
143
|
+
import { connectToHost } from "@domicile-desktop/sdk/connect-to-host";
|
|
144
|
+
import { registerElements } from "@domicile-desktop/sdk/register-elements";
|
|
145
|
+
|
|
146
|
+
const domicile = new DomicileClient(connectToHost(window));
|
|
147
|
+
registerElements(domicile);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
That is the whole of it. There is nothing to await: `connect()` is gone with
|
|
151
|
+
the handshake it performed, the compositor's protocol version is checked in the
|
|
152
|
+
browser process and only logged, and the first call on the channel is what
|
|
153
|
+
binds it. Say what you have to say as soon as you have a domicile.
|
|
154
|
+
|
|
155
|
+
Opened in an ordinary browser there is no `window.domicile` at all —
|
|
156
|
+
`vite dev` on a shell's page is a real thing to do — and `connectToHost` hands
|
|
157
|
+
back a stand-in that does nothing and says so once on the console. Ask
|
|
158
|
+
`hasHost(window)` if your own code needs the answer.
|
|
159
|
+
|
|
160
|
+
`navigator.domicile` is the same object and still reads, so `connectToHost`
|
|
161
|
+
takes either global and prefers neither. `window.domicile` is the spelling the
|
|
162
|
+
guides use.
|
|
163
|
+
|
|
164
|
+
Then render `<app app-id="…">` / `<webview src="…">` as normal DOM and style
|
|
165
|
+
them with ordinary CSS — rounding, blur, transforms, and z-index all apply to the
|
|
166
|
+
live client surface. That is the whole point of Domicile.
|
|
167
|
+
|
|
168
|
+
Both tags are the fork's own HTML elements, so there is nothing to register and
|
|
169
|
+
nothing here to wrap them in — a custom element's name must contain a hyphen, per
|
|
170
|
+
spec, which is exactly why they are the engine's. Note what that costs in a React
|
|
171
|
+
chrome: a tag without a hyphen is an ordinary HTML element to React, so it writes
|
|
172
|
+
no unrecognized property and binds no `on…` prop for an event it has not heard
|
|
173
|
+
of. Bind the events on a ref.
|
|
174
|
+
|
|
175
|
+
### Knowing which modifiers are held
|
|
176
|
+
|
|
177
|
+
**Read them off your own key events.** The desktop is the chrome's window, so
|
|
178
|
+
the page hears every key the user presses whatever the compositor's seat is
|
|
179
|
+
pointed at — that is how `registerElements` forwards a keystroke to a client in
|
|
180
|
+
the first place:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const follow = (event: KeyboardEvent) => {
|
|
184
|
+
// While Alt is held, let the pointer reach the page rather than the window
|
|
185
|
+
// it is over: `pointer-events: none` is what tells the compositor the
|
|
186
|
+
// window is not taking clicks, and it hit-tests accordingly.
|
|
187
|
+
portal.style.pointerEvents = event.altKey ? "none" : "";
|
|
188
|
+
};
|
|
189
|
+
document.addEventListener("keydown", follow);
|
|
190
|
+
document.addEventListener("keyup", follow);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Not off the host's `modifiers` message, today.** It reports the compositor's
|
|
194
|
+
seat, and the seat only knows the keys this page forwarded to it — which is
|
|
195
|
+
only the keys pressed while a client held the keyboard. A modifier pressed
|
|
196
|
+
while the chrome held it never reaches the seat, and the next forwarded key
|
|
197
|
+
makes the message deny it: a shell that believed the message over its own
|
|
198
|
+
keystrokes read a held Alt as let go of. The message is still sent, and is what
|
|
199
|
+
will say so on the day the compositor reads input itself rather than being
|
|
200
|
+
handed it by this page; it cannot know more than this page until then.
|
|
201
|
+
|
|
202
|
+
## Dependencies
|
|
203
|
+
|
|
204
|
+
`zod`, in two places and both of them boundaries. `./protocol` parses the
|
|
205
|
+
compositor's JSON for the headless harness rather than casting it. And
|
|
206
|
+
`./cursor-shape` parses one field off the typed channel — not because the
|
|
207
|
+
engine fails to check it, since `DomicileAppCursorEvent.cursor` is a WebIDL
|
|
208
|
+
`enum` over the same closed set, but because this package and the engine are
|
|
209
|
+
published apart. A shape this list has and the running engine does not arrives
|
|
210
|
+
as a keyword no `DomicileCursorShape` names, and an unknown keyword assigned to
|
|
211
|
+
`style.cursor` fails silently.
|
|
212
|
+
|
|
213
|
+
Nothing else. `@cprussin/option-result` was a dependency for exactly one
|
|
214
|
+
outcome — `connect()` returning `Result<number, HandshakeFailure>` — and there
|
|
215
|
+
is no handshake to fail. Everything here either throws (a bug, per
|
|
216
|
+
[ERRORS.md](/docs/guidelines/ERRORS.md)) or returns `T | undefined` for
|
|
217
|
+
ordinary absence.
|
|
218
|
+
|
|
219
|
+
## Test
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
bun run turbo test --filter @domicile-desktop/sdk
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
DOM-dependent suites run against happy-dom via
|
|
226
|
+
[`@domicile-desktop/test-support`](../test-support/README.md). That DOM performs no
|
|
227
|
+
layout, so the routing tests inject a `measure` stub through
|
|
228
|
+
`registerElements(domicile, { measure })` rather than relying on
|
|
229
|
+
`getBoundingClientRect`.
|
|
230
|
+
|
|
231
|
+
happy-dom has never heard of `<app>`, so it creates one as an
|
|
232
|
+
`HTMLUnknownElement`, which React's development build reports on the console as
|
|
233
|
+
an unrecognized tag. It cannot happen on the fork, where the tag is
|
|
234
|
+
`HTMLAppElement`; React exempts `dialog` and `webview` from that report by name
|
|
235
|
+
and there is no way to add a third.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tag a shell writes.
|
|
3
|
+
*
|
|
4
|
+
* Exported because the SDK's own delegation and a shell's stylesheets and tests
|
|
5
|
+
* all have to name it, and one literal is better than five. Nothing registers
|
|
6
|
+
* it: `customElements.define` cannot take a name without a hyphen, which is
|
|
7
|
+
* exactly why the fork defines the element instead.
|
|
8
|
+
*/
|
|
9
|
+
export declare const APP_TAG_NAME = "app";
|
|
10
|
+
/**
|
|
11
|
+
* Fired on an `<app>` when something asks for the keyboard on its behalf — a
|
|
12
|
+
* click, today — and cancelable, because who holds the keyboard is the
|
|
13
|
+
* shell's to decide rather than the SDK's.
|
|
14
|
+
*
|
|
15
|
+
* Left uncanceled it focuses the client, so a shell with no focus policy of
|
|
16
|
+
* its own needs to know nothing about this. A shell that has one — focus that
|
|
17
|
+
* follows the pointer, a window that may not be interrupted, a click that
|
|
18
|
+
* raises without focusing — calls `preventDefault()` and then does whatever it
|
|
19
|
+
* decided, which is usually `focusApp` a moment later.
|
|
20
|
+
*
|
|
21
|
+
* THE SDK DISPATCHES THIS, unlike `<webview>`'s two events, which the engine
|
|
22
|
+
* does. A click inside an `<app>` is a pointer event in this document — the
|
|
23
|
+
* client is a surface rather than a browsing context — so the page is where the
|
|
24
|
+
* question can be asked at all.
|
|
25
|
+
*
|
|
26
|
+
* It bubbles: a shell renders one `<app>` per window and would otherwise have
|
|
27
|
+
* to bind a listener to each.
|
|
28
|
+
*/
|
|
29
|
+
export declare const APP_FOCUS_REQUESTED_EVENT = "domicile-focus-requested";
|
|
30
|
+
/** The detail of an {@link APP_FOCUS_REQUESTED_EVENT}. */
|
|
31
|
+
export type AppFocusRequest = {
|
|
32
|
+
/** The host's name for the client whose window was reached for. */
|
|
33
|
+
appId: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Fired on the `<app>` that holds the keyboard when a press lands off every
|
|
37
|
+
* window, and cancelable for the same reason its opposite is: the keyboard
|
|
38
|
+
* leaving a window is a move of it, and every move is the shell's to decide.
|
|
39
|
+
*
|
|
40
|
+
* Left uncanceled the keyboard goes back to the page, which is what a press on
|
|
41
|
+
* the desktop means and what a shell with no window chrome of its own wants.
|
|
42
|
+
* A shell that draws chrome *for* a window — a title bar, the sheet a drag is
|
|
43
|
+
* caught on — calls `preventDefault()` when the press landed on it: that is a
|
|
44
|
+
* reach for the window rather than away from it, and the SDK cannot tell the
|
|
45
|
+
* two apart because nothing in the press says which window a `<div>` belongs
|
|
46
|
+
* to. {@link AppFocusReleaseRequest.pressed} is what the shell reads to say.
|
|
47
|
+
*
|
|
48
|
+
* It bubbles, the way {@link APP_FOCUS_REQUESTED_EVENT} does and for the same
|
|
49
|
+
* reason.
|
|
50
|
+
*/
|
|
51
|
+
export declare const APP_FOCUS_RELEASE_REQUESTED_EVENT = "domicile-focus-release-requested";
|
|
52
|
+
/** The detail of an {@link APP_FOCUS_RELEASE_REQUESTED_EVENT}. */
|
|
53
|
+
export type AppFocusReleaseRequest = {
|
|
54
|
+
/** The host's name for the client that is about to lose the keyboard. */
|
|
55
|
+
appId: string;
|
|
56
|
+
/**
|
|
57
|
+
* What the press landed on, or `undefined` where it landed on nothing an
|
|
58
|
+
* element can be read off.
|
|
59
|
+
*/
|
|
60
|
+
pressed: Element | undefined;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* What an `<app>` is, to everything holding one.
|
|
64
|
+
*
|
|
65
|
+
* Global rather than exported, and declared rather than imported, for the same
|
|
66
|
+
* two reasons `<webview>`'s interface is. React resolves a `ref` on a tag to
|
|
67
|
+
* whatever global interface the tag-name map names, so an identically-shaped
|
|
68
|
+
* interface exported from here would be a *different* type that a shell holding
|
|
69
|
+
* the ref could not assign anywhere — the trap `<webview>` fell into, where
|
|
70
|
+
* `@types/react` had already declared the name. Nothing has declared this one,
|
|
71
|
+
* so the SDK is free to; it is declared the same way anyway, because the shape
|
|
72
|
+
* of the answer should not depend on who got there first.
|
|
73
|
+
*
|
|
74
|
+
* And there is nothing to import from: the interface is the fork's, and the
|
|
75
|
+
* engine ships no `.d.ts`. A shell that runs on stock Chromium gets an
|
|
76
|
+
* `HTMLUnknownElement` with none of it — the same trade `<webview>` makes, and
|
|
77
|
+
* the reason the SDK's delegation reads `app-id` off the attribute rather than
|
|
78
|
+
* off this property.
|
|
79
|
+
*/
|
|
80
|
+
declare global {
|
|
81
|
+
interface HTMLAppElement extends HTMLElement {
|
|
82
|
+
/**
|
|
83
|
+
* Which window this element shows. Reflected, so the attribute and the
|
|
84
|
+
* property are one value, the way `<img src>` is — and empty rather than
|
|
85
|
+
* absent when the attribute is not set, which is what a reflected
|
|
86
|
+
* `DOMString` does.
|
|
87
|
+
*/
|
|
88
|
+
appId: string;
|
|
89
|
+
}
|
|
90
|
+
interface HTMLElementTagNameMap {
|
|
91
|
+
app: HTMLAppElement;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// The fork's `<app>`: a Wayland client's window, laid out by the page.
|
|
2
|
+
//
|
|
3
|
+
// There is no element class here, and that is the point. The element belongs to
|
|
4
|
+
// the engine — `app-id` is a reflected content attribute, and the surface embed
|
|
5
|
+
// and the size the client is configured at are both the layout box's, reported
|
|
6
|
+
// by `LayoutAppSurface` without anything in the page asking. What is left for
|
|
7
|
+
// the SDK to say is the part TypeScript cannot read off the fork: what the tag
|
|
8
|
+
// is, and what the SDK dispatches on it.
|
|
9
|
+
//
|
|
10
|
+
// It used to be a `<domicile-app>` custom element, because a custom element's
|
|
11
|
+
// name must contain a hyphen and the SDK predates the fork. That element did
|
|
12
|
+
// far more than forward: it created a `<canvas>` and called
|
|
13
|
+
// `embedExternalSurface` on it, it measured its own box and reported it, it
|
|
14
|
+
// mapped pointer coordinates, and it took five methods and three properties a
|
|
15
|
+
// shell wrote to. The canvas and the embed are the engine's now; the rest moved
|
|
16
|
+
// to document-level delegation over `closest("app")`, which is what
|
|
17
|
+
// `registerElements` installs — see `pointer-input.ts` and `keyboard-input.ts`.
|
|
18
|
+
/**
|
|
19
|
+
* The tag a shell writes.
|
|
20
|
+
*
|
|
21
|
+
* Exported because the SDK's own delegation and a shell's stylesheets and tests
|
|
22
|
+
* all have to name it, and one literal is better than five. Nothing registers
|
|
23
|
+
* it: `customElements.define` cannot take a name without a hyphen, which is
|
|
24
|
+
* exactly why the fork defines the element instead.
|
|
25
|
+
*/
|
|
26
|
+
export const APP_TAG_NAME = "app";
|
|
27
|
+
/**
|
|
28
|
+
* Fired on an `<app>` when something asks for the keyboard on its behalf — a
|
|
29
|
+
* click, today — and cancelable, because who holds the keyboard is the
|
|
30
|
+
* shell's to decide rather than the SDK's.
|
|
31
|
+
*
|
|
32
|
+
* Left uncanceled it focuses the client, so a shell with no focus policy of
|
|
33
|
+
* its own needs to know nothing about this. A shell that has one — focus that
|
|
34
|
+
* follows the pointer, a window that may not be interrupted, a click that
|
|
35
|
+
* raises without focusing — calls `preventDefault()` and then does whatever it
|
|
36
|
+
* decided, which is usually `focusApp` a moment later.
|
|
37
|
+
*
|
|
38
|
+
* THE SDK DISPATCHES THIS, unlike `<webview>`'s two events, which the engine
|
|
39
|
+
* does. A click inside an `<app>` is a pointer event in this document — the
|
|
40
|
+
* client is a surface rather than a browsing context — so the page is where the
|
|
41
|
+
* question can be asked at all.
|
|
42
|
+
*
|
|
43
|
+
* It bubbles: a shell renders one `<app>` per window and would otherwise have
|
|
44
|
+
* to bind a listener to each.
|
|
45
|
+
*/
|
|
46
|
+
export const APP_FOCUS_REQUESTED_EVENT = "domicile-focus-requested";
|
|
47
|
+
/**
|
|
48
|
+
* Fired on the `<app>` that holds the keyboard when a press lands off every
|
|
49
|
+
* window, and cancelable for the same reason its opposite is: the keyboard
|
|
50
|
+
* leaving a window is a move of it, and every move is the shell's to decide.
|
|
51
|
+
*
|
|
52
|
+
* Left uncanceled the keyboard goes back to the page, which is what a press on
|
|
53
|
+
* the desktop means and what a shell with no window chrome of its own wants.
|
|
54
|
+
* A shell that draws chrome *for* a window — a title bar, the sheet a drag is
|
|
55
|
+
* caught on — calls `preventDefault()` when the press landed on it: that is a
|
|
56
|
+
* reach for the window rather than away from it, and the SDK cannot tell the
|
|
57
|
+
* two apart because nothing in the press says which window a `<div>` belongs
|
|
58
|
+
* to. {@link AppFocusReleaseRequest.pressed} is what the shell reads to say.
|
|
59
|
+
*
|
|
60
|
+
* It bubbles, the way {@link APP_FOCUS_REQUESTED_EVENT} does and for the same
|
|
61
|
+
* reason.
|
|
62
|
+
*/
|
|
63
|
+
export const APP_FOCUS_RELEASE_REQUESTED_EVENT = "domicile-focus-release-requested";
|
package/dist/audio.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** A port of a device, or a profile of a card: something to switch it to. */
|
|
2
|
+
export type AudioChoice = {
|
|
3
|
+
/** What `setAudioPort` and `setAudioProfile` name it by. */
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
/**
|
|
7
|
+
* `false` for a port whose jack is empty, or a profile that needs one.
|
|
8
|
+
* Still choosable, as every mixer lets it be.
|
|
9
|
+
*/
|
|
10
|
+
available: boolean;
|
|
11
|
+
};
|
|
12
|
+
/** An output (a sink) or an input (a source). */
|
|
13
|
+
export type AudioDevice = {
|
|
14
|
+
id: string;
|
|
15
|
+
description: string;
|
|
16
|
+
/**
|
|
17
|
+
* The loudest of its channels, as a fraction of the server's 100% — more
|
|
18
|
+
* than 1 for one turned up past it.
|
|
19
|
+
*/
|
|
20
|
+
volume: number;
|
|
21
|
+
muted: boolean;
|
|
22
|
+
/** Whether new streams go to it. */
|
|
23
|
+
default: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether it is an output's monitor — what the output plays, as something
|
|
26
|
+
* to record. Always `false` for an output.
|
|
27
|
+
*/
|
|
28
|
+
monitor: boolean;
|
|
29
|
+
/** Speakers, headphones, a line in. Often empty. */
|
|
30
|
+
ports: readonly AudioChoice[];
|
|
31
|
+
/** The {@link AudioChoice.name} of the port in use, if it has ports. */
|
|
32
|
+
port: string | undefined;
|
|
33
|
+
};
|
|
34
|
+
/** Something playing (a sink input) or recording (a source output). */
|
|
35
|
+
export type AudioStream = {
|
|
36
|
+
id: string;
|
|
37
|
+
/** Who is playing or recording it. */
|
|
38
|
+
application: string;
|
|
39
|
+
/** What it is — a song, a call — where the application said. */
|
|
40
|
+
title: string | undefined;
|
|
41
|
+
volume: number;
|
|
42
|
+
muted: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The {@link AudioDevice.id} it plays to or records from, or `undefined`
|
|
45
|
+
* for one the next message will settle.
|
|
46
|
+
*/
|
|
47
|
+
device: string | undefined;
|
|
48
|
+
};
|
|
49
|
+
/** A sound card, and the profiles that say which of its devices are on. */
|
|
50
|
+
export type AudioCard = {
|
|
51
|
+
id: string;
|
|
52
|
+
description: string;
|
|
53
|
+
/** Best first. */
|
|
54
|
+
profiles: readonly AudioChoice[];
|
|
55
|
+
/** The {@link AudioChoice.name} of the profile in use. */
|
|
56
|
+
profile: string | undefined;
|
|
57
|
+
};
|
package/dist/audio.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The desk's sound, as a shell's mixer draws it and asks of it.
|
|
2
|
+
//
|
|
3
|
+
// Its own module for `tray.ts`'s reason: both halves of the SDK need these
|
|
4
|
+
// shapes, and only one of them is the wire. The compositor reads the sound
|
|
5
|
+
// server with `pactl` — PulseAudio's or PipeWire's — see `domicile_host::audio`.
|
|
6
|
+
//
|
|
7
|
+
// Every id is the compositor's, opaque here: what a request names a device or
|
|
8
|
+
// a stream by, taken from the last `audio` message.
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { DomicileClient } from "./domicile-client";
|
|
2
|
+
import type { HostMessageOf, HostMessageType } from "./host-message";
|
|
3
|
+
import type { ShellKeybindings } from "./own-keybindings";
|
|
4
|
+
/** What a shell is told as its keys are answered. */
|
|
5
|
+
export type KeyHandlers = {
|
|
6
|
+
/** A `send-shell` binding was pressed: the words after `send-shell`. */
|
|
7
|
+
onCommand: (args: readonly string[]) => void;
|
|
8
|
+
/** The keys are read in another binding mode now. */
|
|
9
|
+
onModeChanged: (mode: string) => void;
|
|
10
|
+
};
|
|
11
|
+
/** What {@link bindKeys} hands back. */
|
|
12
|
+
export type KeyBinding = {
|
|
13
|
+
/** Read the keys in `name` from now on — see {@link bindKeys}. */
|
|
14
|
+
setMode: (name: string) => void;
|
|
15
|
+
/** Stop answering. The claims stay. */
|
|
16
|
+
unbind: () => void;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* What `bindKeys` uses of a client: a {@link DomicileClient}, or anything with
|
|
20
|
+
* its handler slots and its claim.
|
|
21
|
+
*/
|
|
22
|
+
type KeyClient = {
|
|
23
|
+
grabShortcut: DomicileClient["grabShortcut"];
|
|
24
|
+
on<T extends HostMessageType>(type: T, handler: (message: HostMessageOf<T>) => void): unknown;
|
|
25
|
+
off<T extends HostMessageType>(type: T, handler: (message: HostMessageOf<T>) => void): unknown;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Answer the keys a shell binds, `own`.
|
|
29
|
+
*
|
|
30
|
+
* **`own` is resolved as each keyboard arrives** — `shell_config`, every
|
|
31
|
+
* keysym the layout types and the key it is on — so a reload that changes the
|
|
32
|
+
* layout moves the keys with it. A chord written wrong, or whose keysym the
|
|
33
|
+
* keyboard cannot type, throws there.
|
|
34
|
+
*
|
|
35
|
+
* **This owns `shell_config` and `shortcut`.** {@link DomicileClient.on} is a
|
|
36
|
+
* single slot per message, so a shell that registers either of its own
|
|
37
|
+
* displaces this.
|
|
38
|
+
*
|
|
39
|
+
* **A claim is never given back.** Every chord of every mode is claimed as
|
|
40
|
+
* each keyboard arrives, because the channel has no way to release one — so a
|
|
41
|
+
* layout change leaves the keys it moved off the desktop's, answering nothing,
|
|
42
|
+
* until the shell's page reloads. And for that reason, a bare key bound in a
|
|
43
|
+
* mode is taken from every client for the whole session, not only while the
|
|
44
|
+
* mode is on.
|
|
45
|
+
*
|
|
46
|
+
* **The mode is tracked here, and a shell can set it.** A desktop of several
|
|
47
|
+
* pages shares one mode across them — a key that entered it may have landed on
|
|
48
|
+
* another page — so `setMode` is how a page is told: the keys are read in that
|
|
49
|
+
* mode from then on, and `onModeChanged` is not called for it, the shell
|
|
50
|
+
* having said so itself. The mode the keys are already in does nothing; one
|
|
51
|
+
* the shell does not have goes back to `default`, and that is reported. Before
|
|
52
|
+
* any keyboard, the mode is kept until one arrives and is checked then.
|
|
53
|
+
*
|
|
54
|
+
* **Bind once per client.** The keyboard arrives once and again only when it
|
|
55
|
+
* changes, so keys bound anew after an unbind answer nothing until the next
|
|
56
|
+
* change of it. A React shell binds in an effect that depends on the client
|
|
57
|
+
* and its keys, and reads anything else it needs when a key is pressed.
|
|
58
|
+
*
|
|
59
|
+
* @returns `unbind`, which stops the answering — the claims stay, as above —
|
|
60
|
+
* and `setMode`.
|
|
61
|
+
*/
|
|
62
|
+
export declare const bindKeys: (domicile: KeyClient, own: ShellKeybindings, { onCommand, onModeChanged }: KeyHandlers) => KeyBinding;
|
|
63
|
+
export {};
|