@gjsify/adwaita-app 0.51.1 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -2
- package/lib/esm/about-dialog.js +1 -0
- package/lib/esm/appdata-xml.js +1 -0
- package/lib/esm/appdata.js +2 -0
- package/lib/esm/appearance/darwin.js +1 -0
- package/lib/esm/appearance/gnome-settings.js +1 -0
- package/lib/esm/appearance/index.js +1 -0
- package/lib/esm/appearance/mapping.js +1 -0
- package/lib/esm/appearance/portal.js +1 -0
- package/lib/esm/appearance/reader.js +1 -0
- package/lib/esm/appearance/run-tool.js +1 -0
- package/lib/esm/appearance/win32.js +1 -0
- package/lib/esm/application.js +1 -1
- package/lib/esm/icon-theme.js +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/esm/loading-stack.js +1 -1
- package/lib/esm/locale-dir.js +1 -1
- package/lib/esm/locale.js +1 -1
- package/lib/esm/nav-shell.js +1 -1
- package/lib/esm/system-accent.js +1 -0
- package/lib/types/about-dialog.d.ts +127 -0
- package/lib/types/about-dialog.spec.d.ts +2 -0
- package/lib/types/appdata-xml.d.ts +38 -0
- package/lib/types/appdata-xml.spec.d.ts +2 -0
- package/lib/types/appdata.d.ts +78 -0
- package/lib/types/appdata.spec.d.ts +2 -0
- package/lib/types/appearance/darwin.d.ts +5 -0
- package/lib/types/appearance/gnome-settings.d.ts +23 -0
- package/lib/types/appearance/index.d.ts +9 -0
- package/lib/types/appearance/mapping.d.ts +102 -0
- package/lib/types/appearance/mapping.spec.d.ts +2 -0
- package/lib/types/appearance/portal.d.ts +25 -0
- package/lib/types/appearance/reader.d.ts +26 -0
- package/lib/types/appearance/reader.spec.d.ts +2 -0
- package/lib/types/appearance/run-tool.d.ts +6 -0
- package/lib/types/appearance/win32.d.ts +5 -0
- package/lib/types/application.d.ts +12 -0
- package/lib/types/index.d.ts +8 -2
- package/lib/types/locale-dir.d.ts +32 -4
- package/lib/types/locale.d.ts +7 -2
- package/lib/types/nav-shell.d.ts +27 -1
- package/lib/types/nav-shell.spec.d.ts +2 -0
- package/lib/types/system-accent.d.ts +30 -0
- package/lib/types/system-accent.spec.d.ts +2 -0
- package/package.json +31 -13
package/README.md
CHANGED
|
@@ -64,7 +64,8 @@ promise-job queue under `run()`).
|
|
|
64
64
|
|
|
65
65
|
- `runAdwaitaApp(options): Promise<number>` — construct + run, resolve with the
|
|
66
66
|
exit code. `AdwaitaAppOptions`: `applicationId`, `createWindow`, optional
|
|
67
|
-
`flags`, `css`, `about` (`AboutInfo`)
|
|
67
|
+
`flags`, `css`, `about` (`AboutInfo`) or `aboutAppdata`
|
|
68
|
+
(`CreateAboutDialogOptions`, see below), `quitAction` (default on, `<primary>q`),
|
|
68
69
|
`devtools` (`true` | `InstallDevtoolsOptions` | omitted = env-gated),
|
|
69
70
|
`bundledIcons` (default on — see below), `onStartup`.
|
|
70
71
|
- `AdwaitaApp` — the configured `Adw.Application` subclass, if you need the
|
|
@@ -128,15 +129,150 @@ has nothing behind it but GTK's own builtins. Register your own with
|
|
|
128
129
|
> the guarantee is built on the icon THEME rather than on handing widgets an
|
|
129
130
|
> imported SVG: for most of them there is no property to hand it to.
|
|
130
131
|
|
|
132
|
+
### About dialog from AppStream metainfo
|
|
133
|
+
|
|
134
|
+
`Adw.AboutDialog.new_from_appdata()` builds the whole dialog out of the metainfo
|
|
135
|
+
file an application already ships — name, developer, licence, urls, version,
|
|
136
|
+
release notes, translated. **It does not exist in the Windows GTK runtime.**
|
|
137
|
+
gvsbuild applies `patches/libadwaita/0001-remove-appstream-dependency.patch`,
|
|
138
|
+
which wraps every `*_from_appdata` entry point in `#ifndef G_OS_WIN32`, because
|
|
139
|
+
libadwaita 1.9.x parses AppStream through the heavyweight `appstream` library
|
|
140
|
+
and gvsbuild defines no project for it. Homebrew's formula `depends_on
|
|
141
|
+
"appstream"`, so **both darwin bundles have it and win32-x64 does not** —
|
|
142
|
+
measured symbol by symbol out of each published bundle's own `Adw-1.typelib`
|
|
143
|
+
([gjsify/gjsify#1662](https://github.com/gjsify/gjsify/issues/1662)). On Windows
|
|
144
|
+
11 the ordinary call site reads `no static method 'new_from_appdata'` and the
|
|
145
|
+
About dialog does not open at all.
|
|
146
|
+
|
|
147
|
+
`createAboutDialog` is the drop-in that works on all three:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { createAboutDialog } from '@gjsify/adwaita-app';
|
|
151
|
+
|
|
152
|
+
createAboutDialog({
|
|
153
|
+
appdataResource: '/org/example/App/metainfo/org.example.App.metainfo.xml',
|
|
154
|
+
releaseNotesVersion: PACKAGE_VERSION,
|
|
155
|
+
version: PACKAGE_VERSION,
|
|
156
|
+
}).present(window);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Or let the shell wire the `app.about` action to it:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
runAdwaitaApp({
|
|
163
|
+
applicationId: 'org.example.App',
|
|
164
|
+
createWindow,
|
|
165
|
+
aboutAppdata: { appdataResource: '/org/example/App/metainfo/org.example.App.metainfo.xml' },
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Where the constructor exists it is used, unchanged. Where it does not, the same
|
|
170
|
+
dialog is assembled from the same document by `parseAppdata` — which **ports
|
|
171
|
+
`ministream`'s selection rules rule for rule** (the small parser libadwaita 1.10
|
|
172
|
+
replaced the `appstream` dependency with): BCP-47 segment scoring for
|
|
173
|
+
translations, the `+1024` shift that ranks `<developer><name>` over the legacy
|
|
174
|
+
`<developer_name>`, the `.desktop` id rule, per-language release-note buffers.
|
|
175
|
+
Picking fields by our own taste would show a *different* About dialog on Windows
|
|
176
|
+
than on the two platforms that run the real constructor, which is the whole
|
|
177
|
+
failure this closes.
|
|
178
|
+
|
|
179
|
+
It sets exactly what upstream's `populate_from_appdata` sets and nothing else —
|
|
180
|
+
application icon (from `<id>`), application name, developer name, version,
|
|
181
|
+
website, support url, issue url, licence **type**, and the release notes of one
|
|
182
|
+
named release. No `comments`, no `copyright`, no `translator-credits`, no
|
|
183
|
+
`developers`: AppStream's `<summary>` is not `Adw.AboutDialog:comments` and
|
|
184
|
+
upstream never treats it as one. Set those on the returned dialog yourself.
|
|
185
|
+
|
|
186
|
+
Three things it does that upstream does not, each for a measured reason:
|
|
187
|
+
|
|
188
|
+
| | why |
|
|
189
|
+
|---|---|
|
|
190
|
+
| checks the resource with `Gio.resources_get_info` **before** calling the constructor | libadwaita reports an unreadable resource with `g_error()`, which **aborts the process** — a mistyped resource path would kill the app the moment the user opens About, with no exception for any `catch` to see |
|
|
191
|
+
| accepts `appdataPath`, a plain file | a `gjsify ship` artifact may carry the metainfo beside the bundle rather than inside a GResource; upstream's constructor cannot read that at all, so such an app would have no About dialog on *any* platform |
|
|
192
|
+
| `forceParsedAppdata: true` | the parsed path is what every Windows user sees and what no Linux or macOS run would otherwise execute — this is how you look at it before shipping, and how the suite covers it |
|
|
193
|
+
|
|
194
|
+
**Two divergences, both named.** Releases are read in document order rather than
|
|
195
|
+
sorted by ministream's dpkg-style version comparison (AppStream requires
|
|
196
|
+
newest-first, and `appstreamcli validate` reports `releases-not-in-order`
|
|
197
|
+
otherwise) — pass `version` to remove the question on every platform at once. And
|
|
198
|
+
an SPDX id GTK does not know becomes `Gtk.License.CUSTOM` with **no** licence
|
|
199
|
+
text, exactly as upstream leaves it; filling the text with the raw id would be
|
|
200
|
+
more helpful and would show a licence line on Windows that Linux does not.
|
|
201
|
+
|
|
202
|
+
- `createAboutDialog(options?): Adw.AboutDialog` — `appdataResource`,
|
|
203
|
+
`appdataPath`, `releaseNotesVersion`, `version`, `applicationIcon`,
|
|
204
|
+
`forceParsedAppdata`.
|
|
205
|
+
- `buildAboutDialogFromAppdata(xml, { releaseNotesVersion?, locale? }): Adw.AboutDialog`
|
|
206
|
+
— the parsed path on its own, given the document.
|
|
207
|
+
- `applyAppdataFields(dialog, fields, releaseNotesVersion?)` — for an app that
|
|
208
|
+
already has a dialog.
|
|
209
|
+
- `parseAppdata(xml, { locale?, releaseNotesVersion? }): AppdataFields` and
|
|
210
|
+
`appdataLocale(GLib.get_language_names(), { LANG })` — the reading half, with
|
|
211
|
+
**no GI imports**, so it runs (and is tested) under Node as well as GJS.
|
|
212
|
+
- `licenseTypeFor(spdxId): Gtk.License`, `hasAppdataConstructor(namespace?): boolean`.
|
|
213
|
+
|
|
131
214
|
### Navigation shell
|
|
132
215
|
|
|
133
216
|
- `createNavShell(window, options): NavShell` — builds the
|
|
134
217
|
`Adw.NavigationSplitView` (sidebar `Gtk.ListBox` + content `Gtk.Stack`) into
|
|
135
218
|
`window` (which owns the responsive `Adw.Breakpoint`, default `max-width: 720px`).
|
|
136
|
-
Returns `{ widget, stack, contentHeader, selectById, selectByIndex }`.
|
|
219
|
+
Returns `{ widget, stack, contentHeader, setItems, selectById, selectByIndex }`.
|
|
137
220
|
- `resolveInitialNavIndex(items, wantedId?)`, `findNavItem(items, id)` — pure
|
|
138
221
|
helpers (e.g. to turn a `${PREFIX}_VIEW` dev hook into a start index).
|
|
139
222
|
|
|
223
|
+
`NavShellOptions` beyond `items` / `onSelect`: `sidebarTitle`, `sidebarHeaderStart`,
|
|
224
|
+
`sidebarHeaderEnd`, `collapseWidth`, and the three below.
|
|
225
|
+
|
|
226
|
+
#### A sidebar that fills asynchronously
|
|
227
|
+
|
|
228
|
+
`setItems(items)` rebuilds the rows in place and replaces the items behind them, so
|
|
229
|
+
`row-selected` → `onSelect(item, index)` and `selectById` / `selectByIndex` always
|
|
230
|
+
resolve against the items you last passed. A hand-rolled `append` does not: inserting a
|
|
231
|
+
row shifts every row below it, and an index read against the array the shell was built
|
|
232
|
+
with hands `onSelect` the wrong item with no error anywhere.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const shell = createNavShell(this, {
|
|
236
|
+
items: [],
|
|
237
|
+
onSelect: (item) => shell.stack.set_visible_child_name(item.id),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
loadInbox().then((threads) => {
|
|
241
|
+
shell.setItems(threads.map((t) => ({ id: t.id, label: t.subject, icon: 'mail-inbox-symbolic' })));
|
|
242
|
+
shell.selectById(threads[0].id);
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The `Gtk.Stack` stays yours: `setItems` does not touch it, so a page outliving its item
|
|
247
|
+
is your call to keep or drop.
|
|
248
|
+
|
|
249
|
+
The selected id survives a `setItems` if the new items still carry it, and `onSelect`
|
|
250
|
+
does **not** fire — a rebuild is not a selection, and re-announcing the same item would
|
|
251
|
+
make `onSelect` fire on every list update. An item that is gone leaves nothing selected;
|
|
252
|
+
`selectById` is how you pick the replacement deliberately.
|
|
253
|
+
|
|
254
|
+
#### Group headers
|
|
255
|
+
|
|
256
|
+
`headerFunc(row, before)` goes straight into `Gtk.ListBox.set_header_func`. `before` is
|
|
257
|
+
the `NavItem` of the row above `row`, or `null` for the first row — a header is a
|
|
258
|
+
comparison against the previous section, so that is what it hands you. Return `null` to
|
|
259
|
+
draw no header. (The `NavItem` is passed, not the row: the row is right there as `row`.)
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
createNavShell(this, {
|
|
263
|
+
items: NAV,
|
|
264
|
+
onSelect: (item) => shell.stack.set_visible_child_name(item.id),
|
|
265
|
+
headerFunc: (_row, before) =>
|
|
266
|
+
before && before.id.startsWith('pinned/') ? null : new Gtk.Label({ cssClasses: ['heading'], label: 'Threads' }),
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### Bottom bars
|
|
271
|
+
|
|
272
|
+
`sidebarBottomBar` and `contentBottomBar` are the respective `Adw.ToolbarView`s'
|
|
273
|
+
`add_bottom_bar` — a control under the sidebar list, and one under the content stack
|
|
274
|
+
rather than inside it.
|
|
275
|
+
|
|
140
276
|
### Async view mounting
|
|
141
277
|
|
|
142
278
|
- `LoadToken` + `loadIntoStack({ stack, token, load, fill, onError? })` — show a
|
|
@@ -155,12 +291,65 @@ has nothing behind it but GTK's own builtins. Register your own with
|
|
|
155
291
|
- `pickFile(parent, { title?, filters? }): Promise<string | null>` /
|
|
156
292
|
`saveFile(parent, { title?, filters?, initialName? }): Promise<string | null>`
|
|
157
293
|
|
|
294
|
+
### macOS system accent
|
|
295
|
+
|
|
296
|
+
A GTK window needs nothing here: libadwaita ≥ 1.6 follows the macOS accent itself,
|
|
297
|
+
and `Adw.StyleManager:accent-color` is read-only. This is for what has no libadwaita
|
|
298
|
+
in the process — a web view (WebKit resolves CSS `AccentColor` to blue whatever the
|
|
299
|
+
setting) or a headless companion process — and it is GTK-free, so import it from
|
|
300
|
+
the `@gjsify/adwaita-app/system-accent` subpath there.
|
|
301
|
+
|
|
302
|
+
- `readMacosAccentColor(): AdwAccentColorName | null` — runs
|
|
303
|
+
`defaults read -g AppleAccentColor` and maps it with `@gjsify/adwaita-core`'s
|
|
304
|
+
`adwAccentFromAppleAccentColor` (graphite → `slate`, Multicolor → `blue`).
|
|
305
|
+
`null` off macOS or for a value macOS does not define.
|
|
306
|
+
- `onMacosAccentColorChanged(listener, { intervalSeconds? }): () => void` — calls
|
|
307
|
+
`listener` with the new accent when it changes; re-reads every 5 s by default
|
|
308
|
+
(there is no change signal GI can reach). Hold and call the returned unsubscribe.
|
|
309
|
+
|
|
158
310
|
### Dev hooks
|
|
159
311
|
|
|
160
312
|
- `readAppDevHooks({ prefix, env? }): { view?, file?, debug }` — the
|
|
161
313
|
`${PREFIX}_VIEW` / `${PREFIX}_FILE` / `${PREFIX}_DEBUG` env pattern (e.g.
|
|
162
314
|
`MYAPP_VIEW=reports myapp` to open straight to a view in dev).
|
|
163
315
|
|
|
316
|
+
## Desktop appearance (`@gjsify/adwaita-app/appearance`)
|
|
317
|
+
|
|
318
|
+
The desktop's accent colour and colour-scheme preference, for a process that never
|
|
319
|
+
opens a window: a server behind a web UI, a bridge, a CLI. A GTK app does not need
|
|
320
|
+
it, because `Adw.StyleManager` already applies the same values to its widgets. This
|
|
321
|
+
subpath loads only Gio and GLib ([ADR 0078](../../../docs/adr/0078-the-desktop-appearance-reaches-a-web-page-through-a-handoff.md)).
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import { readDesktopAppearance, renderAppearanceMeta, watchDesktopAppearance } from '@gjsify/adwaita-app/appearance';
|
|
325
|
+
|
|
326
|
+
const appearance = await readDesktopAppearance();
|
|
327
|
+
// { accent: 'purple', accentRgb: '#9141ac', colorScheme: 'dark' }; absent fields are unknown
|
|
328
|
+
|
|
329
|
+
const head = renderAppearanceMeta(appearance);
|
|
330
|
+
// <meta name="adw-accent" content="purple">
|
|
331
|
+
// <meta name="adw-color-scheme" content="dark">
|
|
332
|
+
|
|
333
|
+
const stop = watchDesktopAppearance((next) => broadcast(JSON.stringify(next))); // hold `stop`
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
A web server must `listen()` before the module's first top-level await. On GJS a server
|
|
337
|
+
started after one exits as soon as the module settles; see `status/open-todos/README.md`.
|
|
338
|
+
[`examples/node/net-adwaita-appearance`](../../../examples/node/net-adwaita-appearance)
|
|
339
|
+
serves a complete page that way.
|
|
340
|
+
|
|
341
|
+
A reported colour is snapped to one of libadwaita's nine accents with
|
|
342
|
+
`nearestAccent`, as libadwaita does. The reader never throws. Sources per OS:
|
|
343
|
+
|
|
344
|
+
| OS | Reads | Follows changes by | Measured |
|
|
345
|
+
|---|---|---|---|
|
|
346
|
+
| Linux (GNOME, KDE, Flatpak) | XDG Settings portal `org.freedesktop.appearance`, then GSettings `org.gnome.desktop.interface` for what the portal left unknown, only when `XDG_CURRENT_DESKTOP` names GNOME (elsewhere the schema answers its defaults) | `SettingChanged`, `changed::` | yes, in CI, against a fake portal on a peer D-Bus connection |
|
|
347
|
+
| Windows | `reg.exe query`: `Explorer\Accent AccentPalette` (else `DWM AccentColor`), `Personalize AppsUseLightTheme` | polling every 3 s | mapping only; reg.exe not run on Windows |
|
|
348
|
+
| macOS | `defaults read -g AppleAccentColor / AppleInterfaceStyle / AppleInterfaceStyleSwitchesAutomatically` | a debounced monitor on `~/Library/Preferences` | mapping only; not run end to end on a Mac |
|
|
349
|
+
|
|
350
|
+
On macOS with Auto appearance and no `AppleInterfaceStyle` key, `colorScheme` stays
|
|
351
|
+
absent: the system switches by time of day, so a missing key does not prove light.
|
|
352
|
+
|
|
164
353
|
## See also
|
|
165
354
|
|
|
166
355
|
- [`@gjsify/devtools`](../devtools) — the DBus control plane wired in on startup.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{appdataLocale as e,parseAppdata as t}from"./appdata.js";import n from"gi://Adw?version=1";import r from"gi://Gio?version=2.0";import i from"gi://GLib?version=2.0";import a from"gi://Gtk?version=4.0";const o={"GPL-2.0-or-later":a.License.GPL_2_0,"GPL-3.0-or-later":a.License.GPL_3_0,"LGPL-2.1-or-later":a.License.LGPL_2_1,"LGPL-3.0-or-later":a.License.LGPL_3_0,"BSD-2-Clause":a.License.BSD,MIT:a.License.MIT_X11,"Artistic-2.0":a.License.ARTISTIC,"GPL-2.0-only":a.License.GPL_2_0_ONLY,"GPL-3.0-only":a.License.GPL_3_0_ONLY,"LGPL-2.1-only":a.License.LGPL_2_1_ONLY,"LGPL-3.0-only":a.License.LGPL_3_0_ONLY,"AGPL-3.0-or-later":a.License.AGPL_3_0,"AGPL-3.0-only":a.License.AGPL_3_0_ONLY,"BSD-3-Clause":a.License.BSD_3,"Apache-2.0":a.License.APACHE_2_0,"MPL-2.0":a.License.MPL_2_0,"0BSD":a.License[`0BSD`],"GPL-2.0":a.License.GPL_2_0_ONLY,"GPL-3.0":a.License.GPL_3_0_ONLY};function licenseTypeFor(e){return o[e]??a.License.CUSTOM}function hasAppdataConstructor(e=n){try{return typeof e?.AboutDialog?.new_from_appdata==`function`}catch{return!1}}function applyAppdataFields(e,t,n){return t.applicationId!==null&&(e.applicationIcon=t.applicationId),t.applicationName!==null&&(e.applicationName=t.applicationName),t.developerName!==null&&(e.developerName=t.developerName),t.website!==null&&(e.website=t.website),t.supportUrl!==null&&(e.supportUrl=t.supportUrl),t.issueUrl!==null&&(e.issueUrl=t.issueUrl),t.license!==null&&(e.licenseType=licenseTypeFor(t.license)),n!==void 0&&n!==``&&(e.releaseNotesVersion=n),t.releaseNotes!==null&&(e.releaseNotes=t.releaseNotes),t.version!==null&&(e.version=t.version),e}function buildAboutDialogFromAppdata(r,a={}){let o=t(r,{locale:a.locale??e(i.get_language_names(),{LANG:i.getenv(`LANG`)??void 0}),releaseNotesVersion:a.releaseNotesVersion});return applyAppdataFields(new n.AboutDialog,o,a.releaseNotesVersion)}function resourceExists(e){try{return r.resources_get_info(e,r.ResourceLookupFlags.NONE),!0}catch{return!1}}function readResource(e){try{let t=r.resources_lookup_data(e,r.ResourceLookupFlags.NONE);return new TextDecoder().decode(t.toArray())}catch{return null}}function readFile(e){try{let[t,n]=i.file_get_contents(e);return t?new TextDecoder().decode(n):null}catch{return null}}let s=!1;function createAboutDialog(e={}){let{appdataResource:t,appdataPath:r,releaseNotesVersion:i}=e,a=t!==void 0&&resourceExists(t),o;if(!e.forceParsedAppdata&&a&&hasAppdataConstructor())o=n.AboutDialog.new_from_appdata(t,i??``);else{let c=(a?readResource(t):null)??(r===void 0?null:readFile(r));c===null?(console.error(`@gjsify/adwaita-app: no AppStream metainfo found for the About dialog; looked at resource ${t??`(none given)`} and file ${r??`(none given)`}`),o=new n.AboutDialog):(!s&&!e.forceParsedAppdata&&a&&(s=!0,console.log(`@gjsify/adwaita-app: Adw.AboutDialog.new_from_appdata is not in this GTK runtime (expected on Windows — gjsify/gjsify#1662); building the About dialog from the AppStream metainfo directly.`)),o=buildAboutDialogFromAppdata(c,{releaseNotesVersion:i}))}return e.applicationIcon!==void 0&&(o.applicationIcon=e.applicationIcon),e.version!==void 0&&(o.version=e.version),o}export{applyAppdataFields,buildAboutDialogFromAppdata,createAboutDialog,hasAppdataConstructor,licenseTypeFor};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";const e={amp:`&`,lt:`<`,gt:`>`,quot:`"`,apos:`'`};function decodeXmlEntities(t){return t.includes(`&`)?t.replace(/&(#[Xx][0-9A-Fa-f]+|#[0-9]+|[A-Za-z][A-Za-z0-9]*);/g,(t,n)=>{if(n.charAt(0)!==`#`)return e[n]??t;let r=n.charAt(1)===`x`||n.charAt(1)===`X`,i=Number.parseInt(r?n.slice(2):n.slice(1),r?16:10);return!Number.isFinite(i)||i<1||i>1114111||i>=55296&&i<=57343?t:String.fromCodePoint(i)}):t}function findTagEnd(e,t){let n=``;for(let r=t+1;r<e.length;r++){let t=e.charAt(r);if(n)t===n&&(n=``);else if(t===`"`||t===`'`)n=t;else if(t===`>`)return r}return-1}const t=/([^\s=/<>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;function parseAttributes(e){let n={};t.lastIndex=0;let r=t.exec(e);for(;r!==null;)r[1]in n||(n[r[1]]=decodeXmlEntities(r[2]??r[3]??``)),r=t.exec(e);return n}function scanXml(e){let t=[],pushText=e=>{t.push({kind:`text`,text:decodeXmlEntities(e)})},n=0;for(;n<e.length;){let r=e.indexOf(`<`,n);if(r<0){pushText(e.slice(n));break}if(r>n&&pushText(e.slice(n,r)),e.startsWith(`<!--`,r)){let t=e.indexOf(`-->`,r+4);n=t<0?e.length:t+3;continue}if(e.startsWith(`<![CDATA[`,r)){let i=e.indexOf(`]]>`,r+9);t.push({kind:`text`,text:e.slice(r+9,i<0?e.length:i)}),n=i<0?e.length:i+3;continue}if(e.startsWith(`<?`,r)){let t=e.indexOf(`?>`,r+2);n=t<0?e.length:t+2;continue}if(e.startsWith(`<!`,r)){let t=e.indexOf(`>`,r+2);n=t<0?e.length:t+1;continue}let i=findTagEnd(e,r);if(i<0){pushText(e.slice(r));break}let a=e.slice(r+1,i);if(n=i+1,a.charAt(0)===`/`){let e=a.slice(1).trim();e!==``&&t.push({kind:`close`,name:e});continue}let o=a,s=!1;o.endsWith(`/`)&&(s=!0,o=o.slice(0,-1));let c=/^([^\s/<>]+)/.exec(o);if(c===null)continue;let l=c[1];t.push({kind:`open`,name:l,attributes:parseAttributes(o.slice(l.length)),selfClosing:s})}return t}export{decodeXmlEntities,scanXml};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{scanXml as e}from"./appdata-xml.js";const t={applicationId:null,applicationName:null,developerName:null,version:null,website:null,supportUrl:null,issueUrl:null,license:null,releaseNotes:null};function localeScore(e,t){let n=0;for(let r=0;;r++){let i=r<e.length?e.charAt(r):`\0`,a=r<t.length?t.charAt(r):`\0`;if((i===`-`||i===`\0`)&&(a===`-`||a===`\0`)&&n++,i===`\0`||a===`\0`)break;if(i!==a)return 0}return n}function appdataLocale(e,t={}){let n,r=e[0];r!==void 0&&!r.includes(`_`)&&t.LANG!==void 0&&t.LANG.includes(`_`)&&(n=t.LANG),n=n??r??`C`,n.endsWith(`.UTF-8`)&&(n=n.slice(0,-6));let i=``,a=n.indexOf(`@`);if(a>=0){let e=n.slice(a);e.startsWith(`@cyrillic`)?i=`-Cyrl`:e.startsWith(`@devanagari`)?i=`-Deva`:e.startsWith(`@latin`)?i=`-Latn`:e.startsWith(`@shaw`)?i=`-Shaw`:e.startsWith(`@euro`)||(i=`-euro`),n=n.slice(0,a)}return n.replace(/_/g,`-`)+i}function childElements(e,t,n){let r=[],i=t;for(;i<n;){let t=e[i];if(t.kind!==`open`){i+=1;continue}let a=t;if(a.selfClosing){r.push({name:a.name,attributes:a.attributes,start:i+1,end:i+1}),i+=1;continue}let o=1,s=i+1;for(;s<n&&o>0;){let t=e[s];t.kind===`open`?t.selfClosing||(o+=1):t.kind===`close`&&--o,s+=1}let c=o===0?s-1:n;r.push({name:a.name,attributes:a.attributes,start:i+1,end:c}),i=o===0?s:n}return r}function elementText(e,t){let n=0,r=``;for(let i=t.start;i<t.end;i++){let t=e[i];t.kind===`open`?t.selfClosing||(n+=1):t.kind===`close`?--n:n===0&&(r+=t.text)}return r.trim()}const lang=e=>e.attributes[`xml:lang`]??`C`;function considerTranslatable(e,t,n,r){(e.value===null&&lang(t)===`C`||r>e.score)&&(e.score=r,e.value=n)}function escapeMarkup(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/'/g,`'`).replace(/"/g,`"`)}function inlineMarkup(e,t){let n=``,r=!0,i=0,a=0;for(let o=t.start;o<t.end;o++){let t=e[o];if(t.kind===`open`){let e=t,r=!e.selfClosing;if(r&&(a+=1),i>0)continue;e.name===`em`||e.name===`code`?n+=`<${e.name}>`:r&&(i=a)}else if(t.kind===`close`)i>0&&a===i?i=0:i===0&&(t.name===`em`||t.name===`code`)&&(n+=`</${t.name}>`),--a;else if(i===0){let e=``;for(let n of t.text){if(n===`
|
|
2
|
+
`||n===` `||n===` `){if(r)continue;e+=` `,r=!0;continue}e+=n,r=!1}n+=escapeMarkup(e)}}return n}function parseDescription(e,t,n){let r=new Map,bucketFor=(e,t)=>{if(localeScore(n,e)===0&&e!==`C`)return null;let i=r.get(e);return i===void 0&&(i=[],r.set(e,i),t!==void 0&&i.push(`<${t}>\n`)),i};for(let n of childElements(e,t.start,t.end))if(n.name===`p`)bucketFor(lang(n))?.push(`<p>${inlineMarkup(e,n)}</p>\n`);else if(n.name===`ul`||n.name===`ol`){for(let e of r.values())e.push(`<${n.name}>\n`);for(let t of childElements(e,n.start,n.end))t.name===`li`&&bucketFor(lang(t),n.name)?.push(`<li>${inlineMarkup(e,t)}</li>\n`);for(let e of r.values())e.push(`</${n.name}>\n`)}let i=r.get(`C`),a=0;for(let[e,t]of r){let r=localeScore(n,e);r>a&&(a=r,i=t)}return i===void 0?null:i.join(``)}function parseAppdata(n,r={}){let i=r.locale??`C`,a=e(n),o=childElements(a,0,a.length).find(e=>e.name===`component`||e.name===`application`);if(o===void 0)return{...t};let s={...t},c={value:null,score:0},l={value:null,score:0},u=[],d=null;for(let e of childElements(a,o.start,o.end))switch(e.name){case`id`:s.applicationId===null&&(s.applicationId=elementText(a,e));break;case`name`:considerTranslatable(c,e,elementText(a,e),localeScore(i,lang(e)));break;case`developer`:for(let t of childElements(a,e.start,e.end)){if(t.name!==`name`)continue;let e=localeScore(i,lang(t))+1024;e>l.score&&(lang(t)===`C`||e>1024)&&(l.score=e,l.value=elementText(a,t))}break;case`developer_name`:considerTranslatable(l,e,elementText(a,e),localeScore(i,lang(e)));break;case`url`:{let t=elementText(a,e);e.attributes.type===`homepage`?s.website=t:e.attributes.type===`bugtracker`?s.issueUrl=t:e.attributes.type===`help`&&(s.supportUrl=t);break}case`launchable`:e.attributes.type===`desktop-id`&&u.push(elementText(a,e));break;case`project_license`:s.license===null&&(s.license=elementText(a,e));break;case`releases`:d===null&&(d=e);break;default:break}if(s.applicationName=c.value,s.developerName=l.value,d!==null){let e=childElements(a,d.start,d.end).filter(e=>e.name===`release`);s.version=e[0]?.attributes.version??null;let t=r.releaseNotesVersion;if(t!==void 0&&t!==``){let n=e.find(e=>e.attributes.version===t),r=n&&childElements(a,n.start,n.end).find(e=>e.name===`description`);r&&(s.releaseNotes=parseDescription(a,r,i))}}s.applicationId!==null&&s.applicationId.endsWith(`.desktop`)&&(u.includes(`${s.applicationId}.desktop`)||(s.applicationId=s.applicationId.slice(0,-8)));for(let e of Object.keys(s))s[e]===``&&(s[e]=null);return s}export{appdataLocale,localeScore,parseAppdata};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{appearanceFromMacDefaults as e}from"./mapping.js";import{runTool as t}from"./run-tool.js";import n from"gi://Gio?version=2.0";import r from"gi://GLib?version=2.0";const i=`.GlobalPreferences.plist`,a=750;async function readDefault(e){let n=await t([`defaults`,`read`,`-g`,e]);return n===null?null:n.trim()}async function readDarwinAppearance(){let[t,n,r]=await Promise.all([readDefault(`AppleAccentColor`),readDefault(`AppleInterfaceStyle`),readDefault(`AppleInterfaceStyleSwitchesAutomatically`)]);return e({appleAccentColor:t,appleInterfaceStyle:n,appleInterfaceStyleSwitchesAutomatically:r})}function watchDarwinAppearance(e){let t=n.File.new_for_path(r.build_filenamev([r.get_home_dir(),`Library`,`Preferences`])),a;try{a=t.monitor_directory(n.FileMonitorFlags.WATCH_MOVES,null)}catch{return()=>void 0}let o=0,s=a.connect(`changed`,(t,n,a)=>{n.get_basename()!==i&&a?.get_basename()!==i||(o&&r.source_remove(o),o=r.timeout_add(r.PRIORITY_DEFAULT,750,()=>(o=0,e(),r.SOURCE_REMOVE)))});return()=>{o&&r.source_remove(o),a.disconnect(s),a.cancel()}}export{a as DARWIN_DEBOUNCE_MS,readDarwinAppearance,watchDarwinAppearance};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{appearanceFromGnomeSettings as e}from"./mapping.js";import t from"gi://Gio?version=2.0";const n=`org.gnome.desktop.interface`;function gnomeSettingsSource(){let e=t.SettingsSchemaSource.get_default()?.lookup(n,!0);if(!e)return null;let r=[`accent-color`,`color-scheme`].filter(t=>e.has_key(t));return r.length===0?null:{settings:new t.Settings({settings_schema:e}),keys:r}}function readGnomeSettings(t){let read=e=>t.keys.includes(e)?t.settings.get_string(e):null;return e({accentColor:read(`accent-color`),colorScheme:read(`color-scheme`)})}function watchGnomeSettings(e,t){let n=e.keys.map(n=>e.settings.connect(`changed::${n}`,()=>t()));return()=>{for(let t of n)e.settings.disconnect(t)}}export{n as GNOME_INTERFACE_SCHEMA,gnomeSettingsSource,readGnomeSettings,watchGnomeSettings};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{MACOS_ACCENT_COLORS as e,appearanceFromGnomeSettings as t,appearanceFromMacDefaults as n,appearanceFromPortal as r,appearanceFromWindowsRegistry as i,isGnomeDesktop as a,mergeAppearance as o,parseRegQuery as s,sameAppearance as c}from"./mapping.js";import{readPortalAppearance as l,watchPortalAppearance as u}from"./portal.js";import{readDesktopAppearance as d,watchDesktopAppearance as f}from"./reader.js";import{parseDesktopAppearance as p,renderAppearanceMeta as m}from"@gjsify/adwaita-core";export{e as MACOS_ACCENT_COLORS,t as appearanceFromGnomeSettings,n as appearanceFromMacDefaults,r as appearanceFromPortal,i as appearanceFromWindowsRegistry,a as isGnomeDesktop,o as mergeAppearance,p as parseDesktopAppearance,s as parseRegQuery,d as readDesktopAppearance,l as readPortalAppearance,m as renderAppearanceMeta,c as sameAppearance,f as watchDesktopAppearance,u as watchPortalAppearance};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{formatAdwRgb as e,isAdwAccentColorName as t,nearestAccent as n,parseAdwRgb as r}from"@gjsify/adwaita-core";function fromRgb(t){return{accent:n(t),accentRgb:e(t)}}function compact(e){let t={};return e.accent&&(t.accent=e.accent),e.accentRgb&&(t.accentRgb=e.accentRgb),e.colorScheme&&(t.colorScheme=e.colorScheme),t}function mergeAppearance(e,t){let n=e.accent!==void 0;return compact({accent:e.accent??t.accent,accentRgb:n?e.accentRgb:t.accentRgb,colorScheme:e.colorScheme??t.colorScheme})}function sameAppearance(e,t){return e.accent===t.accent&&e.accentRgb===t.accentRgb&&e.colorScheme===t.colorScheme}function appearanceFromPortal(e){let t=e[`color-scheme`],n=t===0?`no-preference`:t===1?`dark`:t===2?`light`:void 0,r=e[`accent-color`],i;return Array.isArray(r)&&r.length===3&&r.every(e=>typeof e==`number`&&e>=0&&e<=1)&&(i={red:r[0],green:r[1],blue:r[2]}),compact({...i?fromRgb(i):{},colorScheme:n})}function isGnomeDesktop(e){return(e??``).split(`:`).some(e=>e.trim().toUpperCase()===`GNOME`)}function appearanceFromGnomeSettings(e){return compact({accent:e.accentColor&&t(e.accentColor)?e.accentColor:void 0,colorScheme:e.colorScheme==="default"?`no-preference`:e.colorScheme===`prefer-dark`?`dark`:e.colorScheme===`prefer-light`?`light`:void 0})}function appearanceFromWindowsRegistry(e){let t,n=e.accentPalette;if(n&&n.length>=16)t={red:n[12]/255,green:n[13]/255,blue:n[14]/255};else if(typeof e.dwmAccentColor==`number`){let n=e.dwmAccentColor;t={red:(n&255)/255,green:(n>>>8&255)/255,blue:(n>>>16&255)/255}}let r=e.appsUseLightTheme,i=r===0?`dark`:r===1?`light`:void 0;return compact({...t?fromRgb(t):{},colorScheme:i})}const i={"-1":`#8c8c8c`,0:`#ff5257`,1:`#f7821a`,2:`#ffc602`,3:`#62ba46`,4:`#017bff`,5:`#a550a7`,6:`#f8509e`};function appearanceFromMacDefaults(e){let t=e.appleAccentColor?.trim(),n=t==null||t===``?i[4]:i[t],a=n?r(n):null,o=e.appleInterfaceStyle?.trim(),s=e.appleInterfaceStyleSwitchesAutomatically?.trim()===`1`,c=o===`Dark`?`dark`:o||s?void 0:`light`;return compact({...a?fromRgb(a):{},colorScheme:c})}function parseRegQuery(e,t){for(let n of e.split(/\r?\n/)){let e=/^\s+(.+?)\s{2,}(REG_\w+)\s{2,}(\S*)\s*$/.exec(n);if(!e||e[1]!==t)continue;let[,,r,i]=e;if(r===`REG_DWORD`){let e=Number.parseInt(i,16);return Number.isFinite(e)?e>>>0:null}if(r===`REG_BINARY`){if(i.length%2!=0||!/^[0-9a-f]*$/i.test(i))return null;let e=new Uint8Array(i.length/2);for(let t=0;t<e.length;t++)e[t]=Number.parseInt(i.slice(t*2,t*2+2),16);return e}return null}return null}export{i as MACOS_ACCENT_COLORS,appearanceFromGnomeSettings,appearanceFromMacDefaults,appearanceFromPortal,appearanceFromWindowsRegistry,isGnomeDesktop,mergeAppearance,parseRegQuery,sameAppearance};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{appearanceFromPortal as e}from"./mapping.js";import t from"gi://Gio?version=2.0";import n from"gi://GLib?version=2.0";const r=`org.freedesktop.portal.Desktop`,i=`/org/freedesktop/portal/desktop`,a=`org.freedesktop.portal.Settings`,o=`org.freedesktop.appearance`;function readPortalAppearance(r){return new Promise(s=>{r.connection.call(r.busName,i,a,`ReadAll`,new n.Variant(`(as)`,[[o]]),new n.VariantType(`(a{sa{sv}})`),t.DBusCallFlags.NONE,5e3,null,(t,n)=>{try{let[r]=t.call_finish(n).recursiveUnpack();s(e(r[`org.freedesktop.appearance`]??{}))}catch{s(null)}})})}function watchPortalAppearance(e,n){let r=e.connection.signal_subscribe(e.busName,a,`SettingChanged`,i,o,t.DBusSignalFlags.NONE,()=>n());return()=>e.connection.signal_unsubscribe(r)}function sessionPortalEndpoint(){return new Promise(e=>{t.bus_get(t.BusType.SESSION,null,(n,i)=>{try{e({connection:t.bus_get_finish(i),busName:r})}catch{e(null)}})})}export{o as APPEARANCE_NAMESPACE,r as PORTAL_BUS_NAME,i as PORTAL_OBJECT_PATH,a as PORTAL_SETTINGS_INTERFACE,readPortalAppearance,sessionPortalEndpoint,watchPortalAppearance};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{isGnomeDesktop as e,mergeAppearance as t,sameAppearance as n}from"./mapping.js";import{readDarwinAppearance as r,watchDarwinAppearance as i}from"./darwin.js";import{gnomeSettingsSource as a,readGnomeSettings as o,watchGnomeSettings as s}from"./gnome-settings.js";import{readPortalAppearance as c,sessionPortalEndpoint as l,watchPortalAppearance as u}from"./portal.js";import{readWin32Appearance as d,watchWin32Appearance as f}from"./win32.js";import p from"gi://GLib?version=2.0";function currentPlatform(e){return e.platform?e.platform:typeof process>`u`?void 0:process.platform}function linuxBackend(n){let r,sources=()=>(r??=(async()=>({portal:n.portal===void 0?await l():n.portal,settings:e(n.currentDesktop===void 0?p.getenv(`XDG_CURRENT_DESKTOP`):n.currentDesktop)?n.gnomeSettings===void 0?a():n.gnomeSettings:null}))(),r);return{async read(){let{portal:e,settings:n}=await sources(),r=e?await c(e):null,i=n?o(n):{};return t(r??{},i)},watch(e){let t=[],n=!1;return sources().then(({portal:r,settings:i})=>{n||(r&&t.push(u(r,e)),i&&t.push(s(i,e)))}),()=>{n=!0;for(let e of t)e();t=[]}}}}function backendFor(e){let t=currentPlatform(e);return t===`win32`?{read:d,watch:f}:t===`darwin`?{read:r,watch:i}:linuxBackend(e)}function readDesktopAppearance(e={}){return backendFor(e).read()}function watchDesktopAppearance(e,t={}){let r=backendFor(t),i,a=!1,o=Promise.resolve(),refresh=()=>{o=o.then(async()=>{let t=await r.read();a||i&&n(i,t)||(i=t,e(t))})},s=r.watch(refresh);return refresh(),()=>{a=!0,s()}}export{readDesktopAppearance,watchDesktopAppearance};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import e from"gi://Gio?version=2.0";function runTool(t){return new Promise(n=>{let r;try{r=e.Subprocess.new([...t],e.SubprocessFlags.STDOUT_PIPE|e.SubprocessFlags.STDERR_SILENCE)}catch{n(null);return}r.communicate_utf8_async(null,null,(e,t)=>{try{let[,r]=e.communicate_utf8_finish(t);n(e.get_successful()?r??``:null)}catch{n(null)}})})}export{runTool};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{appearanceFromWindowsRegistry as e,parseRegQuery as t}from"./mapping.js";import{runTool as n}from"./run-tool.js";import r from"gi://GLib?version=2.0";const i=3;async function queryValue(e,r){let i=await n([`reg`,`query`,e,`/v`,r]);return i===null?null:t(i,r)}async function readWin32Appearance(){let[t,n]=await Promise.all([queryValue(`HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Accent`,`AccentPalette`),queryValue(`HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize`,`AppsUseLightTheme`)]),r=t instanceof Uint8Array?t:null,i=r?null:await queryValue(`HKCU\\Software\\Microsoft\\Windows\\DWM`,`AccentColor`);return e({accentPalette:r,dwmAccentColor:typeof i==`number`?i:null,appsUseLightTheme:typeof n==`number`?n:null})}function watchWin32Appearance(e){let t=r.timeout_add_seconds(r.PRIORITY_DEFAULT,3,()=>(e(),r.SOURCE_CONTINUE));return()=>r.source_remove(t)}export{i as WIN32_POLL_INTERVAL_S,readWin32Appearance,watchWin32Appearance};
|
package/lib/esm/application.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{createAboutDialog as e}from"./about-dialog.js";import{installBundledIconTheme as t}from"./icon-theme.js";import n from"gi://Adw?version=1";import r from"gi://Gio?version=2.0";import i from"gi://Gtk?version=4.0";import a from"gi://Gdk?version=4.0";import o from"gi://GObject?version=2.0";import s from"system";import{installDevtools as c}from"@gjsify/devtools";var l=class AdwaitaApp extends n.Application{static{o.registerClass({GTypeName:`GjsifyAdwaitaApp`},AdwaitaApp)}constructor(e){super({application_id:e.applicationId,flags:e.flags??r.ApplicationFlags.DEFAULT_FLAGS}),this._window=null,this._options=e,this._initActions(),this.connect(`startup`,()=>this._onStartup()),this.connect(`activate`,()=>this._onActivate())}_initActions(){if(this._options.quitAction!==!1){let e=new r.SimpleAction({name:`quit`});e.connect(`activate`,()=>this.quit()),this.add_action(e),this.set_accels_for_action(`app.quit`,[`<primary>q`])}if(this._options.about||this._options.aboutAppdata){let e=new r.SimpleAction({name:`about`});e.connect(`activate`,()=>this._showAbout()),this.add_action(e)}}_onStartup(){this._installBundledIcons(),this._options.css&&this._loadCss(this._options.css),this._installDevtools(),this._options.onStartup?.(this)}_installBundledIcons(){let e=this._options.bundledIcons;e!==!1&&t(typeof e==`object`?e:{})}_loadCss(e){let t=a.Display.get_default();if(!t){console.error(`@gjsify/adwaita-app: no default display; CSS not applied`);return}let n=new i.CssProvider;n.load_from_string(e),i.StyleContext.add_provider_for_display(t,n,i.STYLE_PROVIDER_PRIORITY_APPLICATION)}_installDevtools(){let e=this._options.devtools;if(e===!1)return;let t=typeof e==`object`?e:{enabled:e||void 0};c(this,t)}_onActivate(){this._window||=this._options.createWindow(this),this._window.present()}_showAbout(){let t=this._options.aboutAppdata;if(t){e(t).present(this.get_active_window());return}let r=this._options.about;if(!r)return;let i=new n.AboutDialog({application_name:r.applicationName,application_icon:r.applicationIcon??this._options.applicationId});r.version&&(i.version=r.version),r.developerName&&(i.developer_name=r.developerName),r.website&&(i.website=r.website),r.issueUrl&&(i.issue_url=r.issueUrl),r.license&&(i.license=r.license),r.comments&&(i.comments=r.comments),r.copyright&&(i.copyright=r.copyright),r.developers?.length&&i.set_developers(r.developers),i.present(this.get_active_window())}};async function runAdwaitaApp(e){return runApplication(new l(e),[s.programInvocationName,...s.programArgs])}async function runApplication(e,t){let n=!1;try{e.register(null),n=e.get_is_remote()}catch{n=!1}let r=await e.runAsync(t);return n&&console.error(`${e.applicationId??`This application`} is already running — brought the existing instance to the front instead of opening a second window.`),r}o.type_ensure(l.$gtype);export{l as AdwaitaApp,runAdwaitaApp,runApplication};
|
package/lib/esm/icon-theme.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{BUNDLED_ICON_COUNT as e,BUNDLED_ICON_GRESOURCE_BASE64 as t,BUNDLED_ICON_RESOURCE_PATH as n}from"./icons.generated.js";import r from"gi://
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{BUNDLED_ICON_COUNT as e,BUNDLED_ICON_GRESOURCE_BASE64 as t,BUNDLED_ICON_RESOURCE_PATH as n}from"./icons.generated.js";import r from"gi://Gio?version=2.0";import i from"gi://GLib?version=2.0";import a from"gi://Gtk?version=4.0";import o from"gi://Gdk?version=4.0";const s=`gjsify-adwaita-bundled`;let c=null;function registerBundledIconResource(){if(c)return c;let e=i.base64_decode(t);return c=r.Resource.new_from_data(new i.Bytes(e)),r.resources_register(c),c}function addBundledIconsToTheme(e){registerBundledIconResource(),e.add_resource_path(n)}function installBundledIconTheme(e={}){let t=e.prefer??`fallback`;if(t===`host`)return!1;let n=e.display===void 0?o.Display.get_default():e.display;return n?(addBundledIconsToTheme(a.IconTheme.get_for_display(n)),t===`bundled`&&a.Settings.get_for_display(n).set_property(`gtk-icon-theme-name`,s),!0):!1}export{e as BUNDLED_ICON_COUNT,n as BUNDLED_ICON_RESOURCE_PATH,s as BUNDLED_ICON_THEME_NAME,addBundledIconsToTheme,installBundledIconTheme,registerBundledIconResource};
|
package/lib/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{appdataLocale as e,parseAppdata as t}from"./appdata.js";import{applyAppdataFields as n,buildAboutDialogFromAppdata as r,createAboutDialog as i,hasAppdataConstructor as a,licenseTypeFor as o}from"./about-dialog.js";import{BUNDLED_ICON_COUNT as s,BUNDLED_ICON_RESOURCE_PATH as c}from"./icons.generated.js";import{BUNDLED_ICON_THEME_NAME as l,addBundledIconsToTheme as u,installBundledIconTheme as d,registerBundledIconResource as f}from"./icon-theme.js";import{AdwaitaApp as p,runAdwaitaApp as m,runApplication as h}from"./application.js";import{readAppDevHooks as g}from"./dev-hooks.js";import{confirmDialog as _,errorDialog as v}from"./dialogs.js";import{pickFile as y,saveFile as b}from"./file-dialog.js";import{createNavShell as x}from"./nav-shell.js";import{LoadToken as S,loadIntoStack as C}from"./view-loader.js";import{LoadingStack as w}from"./loading-stack.js";import{registerToastOverlay as T,showToast as E}from"./toast.js";import{SYSTEM_LOCALE_DIR as D,resolveLocaleDir as O,systemLocaleDir as k}from"./locale-dir.js";import{initLocale as A}from"./locale.js";import{APPLE_ACCENT_COLOR_ARGV as j,MACOS_ACCENT_POLL_SECONDS as M,onMacosAccentColorChanged as N,readMacosAccentColor as P}from"./system-accent.js";import{findNavItem as F,resolveInitialNavIndex as I}from"./nav-model.js";export{j as APPLE_ACCENT_COLOR_ARGV,p as AdwaitaApp,s as BUNDLED_ICON_COUNT,c as BUNDLED_ICON_RESOURCE_PATH,l as BUNDLED_ICON_THEME_NAME,S as LoadToken,w as LoadingStack,M as MACOS_ACCENT_POLL_SECONDS,D as SYSTEM_LOCALE_DIR,u as addBundledIconsToTheme,e as appdataLocale,n as applyAppdataFields,r as buildAboutDialogFromAppdata,_ as confirmDialog,i as createAboutDialog,x as createNavShell,v as errorDialog,F as findNavItem,a as hasAppdataConstructor,A as initLocale,d as installBundledIconTheme,o as licenseTypeFor,C as loadIntoStack,N as onMacosAccentColorChanged,t as parseAppdata,y as pickFile,g as readAppDevHooks,P as readMacosAccentColor,f as registerBundledIconResource,T as registerToastOverlay,I as resolveInitialNavIndex,O as resolveLocaleDir,m as runAdwaitaApp,h as runApplication,b as saveFile,E as showToast,k as systemLocaleDir};
|
package/lib/esm/loading-stack.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import e from"gi://Adw?version=1";import t from"gi://
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"gi://Adw?version=1";import t from"gi://Gtk?version=4.0";import n from"gi://GObject?version=2.0";var r=class LoadingStack extends t.Stack{static{n.registerClass({GTypeName:`GjsifyLoadingStack`},LoadingStack)}constructor(n={}){super(n);let r=new t.Box({orientation:t.Orientation.VERTICAL,halign:t.Align.CENTER,valign:t.Align.CENTER,hexpand:!0,vexpand:!0});r.append(new e.Spinner),this.add_named(r,`loading`),this._content=new e.Bin({hexpand:!0,vexpand:!0}),this.add_named(this._content,`content`),this._error=new e.StatusPage({iconName:`dialog-error-symbolic`,title:`Something went wrong`}),this.add_named(this._error,`error`),this.set_visible_child_name(`loading`)}setContent(e){this._content.set_child(e)}setError(e,t){this._error.set_title(e),this._error.set_description(t??``)}showLoading(){this.set_visible_child_name(`loading`)}showContent(){this.set_visible_child_name(`content`)}showError(){this.set_visible_child_name(`error`)}};n.type_ensure(r.$gtype);export{r as LoadingStack};
|
package/lib/esm/locale-dir.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";const e=`/usr/share/locale
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";const e=`/usr/share/locale`,t=[`darwin`,`win32`];function systemLocaleDir(n){return n!==void 0&&t.includes(n)?void 0:e}function resolveLocaleDir(e={}){return nonEmpty(e.localeDir)??nonEmpty(e.env?.GJSIFY_LOCALE_DIR)??nonEmpty(e.fallbackDir)??systemLocaleDir(e.platform)}function nonEmpty(e){if(e===void 0)return;let t=e.trim();return t===``?void 0:t}export{e as SYSTEM_LOCALE_DIR,resolveLocaleDir,systemLocaleDir};
|
package/lib/esm/locale.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{resolveLocaleDir as e}from"./locale-dir.js";import t from"gi://GLib?version=2.0";import n from"gettext";function initLocale(r,i={}){let a=e({...i,env:{GJSIFY_LOCALE_DIR:t.getenv(`GJSIFY_LOCALE_DIR`)??void 0}});return n.setlocale(n.LocaleCategory.ALL,``),n.bindtextdomain(r,a),n.textdomain(r),Object.assign(e=>n.dgettext(r,e),{plural:(e,t,i)=>n.dngettext(r,e,t,i),context:(e,t)=>n.dpgettext(r,e,t),localeDir:a,domain:r})}export{initLocale};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{resolveLocaleDir as e}from"./locale-dir.js";import t from"gi://GLib?version=2.0";import n from"gettext";function initLocale(r,i={}){let a=e({...i,env:{GJSIFY_LOCALE_DIR:t.getenv(`GJSIFY_LOCALE_DIR`)??void 0},platform:typeof process>`u`?void 0:process.platform});return n.setlocale(n.LocaleCategory.ALL,``),a!==void 0&&n.bindtextdomain(r,a),n.textdomain(r),Object.assign(e=>n.dgettext(r,e),{plural:(e,t,i)=>n.dngettext(r,e,t,i),context:(e,t)=>n.dpgettext(r,e,t),localeDir:a,domain:r})}export{initLocale};
|
package/lib/esm/nav-shell.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import e from"gi://Adw?version=1";import t from"gi://Gtk?version=4.0";function buildNavRow(e){let n=new t.ListBoxRow,r=new t.Box({orientation:t.Orientation.HORIZONTAL,spacing:12,marginTop:8,marginBottom:8,marginStart:12,marginEnd:12});e.icon&&r.append(new t.Image({iconName:e.icon}));let i=new t.Box({orientation:t.Orientation.VERTICAL,hexpand:!0});return i.append(new t.Label({label:e.label,xalign:0})),e.subtitle&&i.append(new t.Label({label:e.subtitle,xalign:0,cssClasses:[`dim-label`,`caption`]})),r.append(i),n.set_child(r),n}function createNavShell(n,r){let i=new e.NavigationSplitView,a=new t.Stack({transitionType:t.StackTransitionType.CROSSFADE,hexpand:!0,vexpand:!0}),o=new t.ListBox({cssClasses:[`navigation-sidebar`]}),s=new e.HeaderBar,c=new e.HeaderBar;r.sidebarTitle&&
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"gi://Adw?version=1";import t from"gi://Gtk?version=4.0";function buildNavRow(e){let n=new t.ListBoxRow,r=new t.Box({orientation:t.Orientation.HORIZONTAL,spacing:12,marginTop:8,marginBottom:8,marginStart:12,marginEnd:12});e.icon&&r.append(new t.Image({iconName:e.icon}));let i=new t.Box({orientation:t.Orientation.VERTICAL,hexpand:!0});return i.append(new t.Label({label:e.label,xalign:0})),e.subtitle&&i.append(new t.Label({label:e.subtitle,xalign:0,cssClasses:[`dim-label`,`caption`]})),r.append(i),n.set_child(r),n}function createNavShell(n,r){let i=new e.NavigationSplitView,a=new t.Stack({transitionType:t.StackTransitionType.CROSSFADE,hexpand:!0,vexpand:!0}),o=new t.ListBox({cssClasses:[`navigation-sidebar`]}),s=new e.HeaderBar,c=new WeakMap,l=[],u=[],d=null,f=!1,p=new e.HeaderBar;r.sidebarTitle&&p.set_title_widget(new e.WindowTitle({title:r.sidebarTitle,subtitle:``})),r.sidebarHeaderStart&&p.pack_start(r.sidebarHeaderStart),r.sidebarHeaderEnd&&p.pack_end(r.sidebarHeaderEnd);let m=new e.ToolbarView;m.add_top_bar(p),r.sidebarBottomBar&&m.add_bottom_bar(r.sidebarBottomBar),m.set_content(o),i.set_sidebar(new e.NavigationPage({title:r.sidebarTitle??`Menu`,child:m}));let h=new e.ToolbarView;if(h.add_top_bar(s),r.contentBottomBar&&h.add_bottom_bar(r.contentBottomBar),h.set_content(a),i.set_content(new e.NavigationPage({title:r.sidebarTitle??``,child:h})),o.connect(`row-selected`,(e,t)=>{if(!t){f||(d=null);return}let n=c.get(t);if(!n)return;let a=t.get_index();a<0||(d=n.id,!f&&(r.onSelect(n,a),i.get_collapsed()&&i.set_show_content(!0)))}),r.headerFunc){let e=r.headerFunc;o.set_header_func((t,n)=>e(t,n?c.get(n)??null:null))}let setItems=e=>{let t=d;l=e,f=!0;for(let e of u)o.remove(e);u=l.map(e=>{let t=buildNavRow(e);return c.set(t,e),o.append(t),t});let n=t===null?-1:l.findIndex(e=>e.id===t);n>=0&&o.select_row(u[n]),f=!1};setItems(r.items);let g=r.collapseWidth??720,_=e.BreakpointCondition.parse(`max-width: ${g}px`);if(_){let t=new e.Breakpoint({condition:_});t.add_setter(i,`collapsed`,!0),n.add_breakpoint(t)}let selectByIndex=e=>{let t=o.get_row_at_index(e);t&&o.select_row(t)};return{widget:i,stack:a,contentHeader:s,setItems,selectByIndex,selectById(e){let t=l.findIndex(t=>t.id===e);selectByIndex(t>=0?t:0)}}}export{createNavShell};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"gi://Gio?version=2.0";import t from"gi://GLib?version=2.0";import{adwAccentFromAppleAccentColor as n}from"@gjsify/adwaita-core";const r=[`defaults`,`read`,`-g`,`AppleAccentColor`],i=5,a=/Could not find key|does not exist/;function readMacosAccentColor(t={}){let i=[...t.argv??r],o;try{let t=e.Subprocess.new(i,e.SubprocessFlags.STDOUT_PIPE|e.SubprocessFlags.STDERR_PIPE),[,n,r]=t.communicate_utf8(null,null);o={ok:t.get_successful(),stdout:n??``,stderr:r??``}}catch{return null}return o.ok?n(o.stdout):a.test(o.stderr)?n(null):null}function onMacosAccentColorChanged(e,n={}){let r=n.read??(()=>readMacosAccentColor()),i=r(),a=t.timeout_add_seconds(t.PRIORITY_LOW,n.intervalSeconds??5,()=>{let n=r();return n!==i&&(i=n,e(n)),t.SOURCE_CONTINUE});return()=>{a!==null&&(t.source_remove(a),a=null)}}export{r as APPLE_ACCENT_COLOR_ARGV,i as MACOS_ACCENT_POLL_SECONDS,onMacosAccentColorChanged,readMacosAccentColor};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import Adw from 'gi://Adw?version=1';
|
|
2
|
+
import Gtk from 'gi://Gtk?version=4.0';
|
|
3
|
+
import { type AppdataFields } from './appdata.js';
|
|
4
|
+
/**
|
|
5
|
+
* The `Gtk.License` an SPDX id names, or `Gtk.License.CUSTOM` for one GTK does not know.
|
|
6
|
+
*
|
|
7
|
+
* CUSTOM is returned WITHOUT also setting a licence text, which is what upstream does. Putting
|
|
8
|
+
* the raw SPDX id in `Adw.AboutDialog:license` instead would be more informative and would
|
|
9
|
+
* therefore show a licence line on Windows that Linux does not show — the divergence this
|
|
10
|
+
* whole module exists to prevent.
|
|
11
|
+
*/
|
|
12
|
+
export declare function licenseTypeFor(spdxId: string): Gtk.License;
|
|
13
|
+
/**
|
|
14
|
+
* Whether this GTK runtime has `Adw.AboutDialog.new_from_appdata()`.
|
|
15
|
+
*
|
|
16
|
+
* Asked as a QUESTION rather than discovered by calling and catching, for two reasons. A
|
|
17
|
+
* `try` around the call would also swallow a genuine failure — a metainfo file the parser
|
|
18
|
+
* rejects would silently become "the constructor is missing" and the app would ship a
|
|
19
|
+
* quietly degraded dialog forever. And it would not work anyway: libadwaita reports an
|
|
20
|
+
* unreadable or unparseable resource with `g_error()`, which ABORTS the process, and no
|
|
21
|
+
* catch runs after that.
|
|
22
|
+
*
|
|
23
|
+
* The `try` that IS here wraps the LOOKUP, because the absence has two shapes and only one
|
|
24
|
+
* of them is a plain `undefined`: node-gi reports an entry point missing from the typelib by
|
|
25
|
+
* throwing `no static method 'new_from_appdata'` at property access, which is exactly how the
|
|
26
|
+
* Windows 11 measurement in #1662 reads.
|
|
27
|
+
*
|
|
28
|
+
* `namespace` defaults to the real `Adw` and is a parameter so both shapes can be held
|
|
29
|
+
* against the real question on a platform that has neither. It substitutes a namespace and
|
|
30
|
+
* never means "no namespace": passing `undefined` is the default parameter and therefore asks
|
|
31
|
+
* about `Adw`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hasAppdataConstructor(namespace?: unknown): boolean;
|
|
34
|
+
/** Where {@link createAboutDialog} may read the metainfo document from. */
|
|
35
|
+
export interface AppdataSource {
|
|
36
|
+
/**
|
|
37
|
+
* GResource path of the metainfo file, e.g.
|
|
38
|
+
* `/org/example/App/metainfo/org.example.App.metainfo.xml`.
|
|
39
|
+
*
|
|
40
|
+
* This is the only form the upstream constructor accepts, so an app that registers its
|
|
41
|
+
* metainfo as a resource takes the upstream path wherever it exists.
|
|
42
|
+
*/
|
|
43
|
+
appdataResource?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Filesystem path of the metainfo file, consulted when no resource answers.
|
|
46
|
+
*
|
|
47
|
+
* Upstream has no equivalent — and that is the point. A `gjsify ship` artifact may carry
|
|
48
|
+
* the metainfo beside the bundle rather than inside a GResource (a `.app` is dragged
|
|
49
|
+
* wherever the user likes, a Windows program directory is unzipped to an arbitrary path),
|
|
50
|
+
* and such an app would otherwise have no About dialog on ANY platform. Where the
|
|
51
|
+
* directory is remains the caller's question: this module takes a path, exactly as
|
|
52
|
+
* `locale-dir.ts` takes a directory rather than going looking for one.
|
|
53
|
+
*/
|
|
54
|
+
appdataPath?: string;
|
|
55
|
+
}
|
|
56
|
+
/** Options for {@link createAboutDialog}. */
|
|
57
|
+
export interface CreateAboutDialogOptions extends AppdataSource {
|
|
58
|
+
/**
|
|
59
|
+
* Show the release notes of this exact version, matched by string equality — the second
|
|
60
|
+
* argument of `Adw.AboutDialog.new_from_appdata()`, with the same semantics.
|
|
61
|
+
*/
|
|
62
|
+
releaseNotesVersion?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Version shown in the dialog, overriding the metainfo's newest release.
|
|
65
|
+
*
|
|
66
|
+
* Worth passing. The metainfo answers this question only as well as it is maintained,
|
|
67
|
+
* and it is the one field where this module's reading and ministream's can disagree —
|
|
68
|
+
* ministream sorts releases by a dpkg-style version comparison, `appdata.ts` takes the
|
|
69
|
+
* first one written. An explicit version removes the question on every platform at once.
|
|
70
|
+
*/
|
|
71
|
+
version?: string;
|
|
72
|
+
/** Icon name, overriding the one derived from `<id>`. */
|
|
73
|
+
applicationIcon?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Build the dialog from parsed AppStream even where the upstream constructor exists.
|
|
76
|
+
*
|
|
77
|
+
* Not a test hook — or not only one. The parsed path is what every Windows user sees and
|
|
78
|
+
* what NO Linux or macOS run would otherwise execute, so a developer who wants to check
|
|
79
|
+
* their About dialog before shipping has no other way to look at it. The suite beside
|
|
80
|
+
* this file uses it for the same reason.
|
|
81
|
+
*/
|
|
82
|
+
forceParsedAppdata?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Set on `dialog` exactly what `populate_from_appdata` sets, and nothing else.
|
|
86
|
+
*
|
|
87
|
+
* The upstream list, read off `adw-about-dialog.c` rather than guessed: application icon
|
|
88
|
+
* (from `<id>`), application name, developer name, version, website, support url, issue url,
|
|
89
|
+
* licence TYPE, and the release notes of one named release. It sets no `comments`, no
|
|
90
|
+
* `copyright`, no `translator-credits` and no `developers` — AppStream's `<summary>` is not
|
|
91
|
+
* `Adw.AboutDialog:comments` and upstream never treats it as one — so neither does this.
|
|
92
|
+
* An app that wants those sets them on the returned dialog itself.
|
|
93
|
+
*/
|
|
94
|
+
export declare function applyAppdataFields(dialog: Adw.AboutDialog, fields: AppdataFields, releaseNotesVersion?: string): Adw.AboutDialog;
|
|
95
|
+
/**
|
|
96
|
+
* Build the dialog from a metainfo document, without consulting libadwaita's constructor.
|
|
97
|
+
*
|
|
98
|
+
* This is the Windows path, exported so it is callable on a platform that has the constructor:
|
|
99
|
+
* a fallback only one OS can reach is a fallback no suite covers, and the class of defect that
|
|
100
|
+
* hides there is a restructured metainfo file that empties the About dialog on Windows while
|
|
101
|
+
* every Linux run stays green.
|
|
102
|
+
*/
|
|
103
|
+
export declare function buildAboutDialogFromAppdata(xml: string, options?: {
|
|
104
|
+
releaseNotesVersion?: string;
|
|
105
|
+
locale?: string;
|
|
106
|
+
}): Adw.AboutDialog;
|
|
107
|
+
/**
|
|
108
|
+
* Create an `Adw.AboutDialog` from the application's AppStream metainfo.
|
|
109
|
+
*
|
|
110
|
+
* A drop-in for `Adw.AboutDialog.new_from_appdata(resource, version)` that works on Windows:
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* const dialog = createAboutDialog({
|
|
114
|
+
* appdataResource: '/org/example/App/metainfo/org.example.App.metainfo.xml',
|
|
115
|
+
* releaseNotesVersion: PACKAGE_VERSION,
|
|
116
|
+
* version: PACKAGE_VERSION,
|
|
117
|
+
* });
|
|
118
|
+
* dialog.present(window);
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* THE RESOURCE IS CHECKED BEFORE THE CONSTRUCTOR IS CALLED, and that is not a tidiness step.
|
|
122
|
+
* `populate_from_appdata` reports a resource it cannot read with `g_error()`, which aborts the
|
|
123
|
+
* process — a typo in a resource path would kill the application when the user opens About,
|
|
124
|
+
* with no exception for anyone to catch. Asking `Gio.resources_get_info` first turns that into
|
|
125
|
+
* a dialog assembled from whatever source did answer.
|
|
126
|
+
*/
|
|
127
|
+
export declare function createAboutDialog(options?: CreateAboutDialogOptions): Adw.AboutDialog;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** An opening (or self-closing) element. */
|
|
2
|
+
export interface XmlOpenTag {
|
|
3
|
+
kind: 'open';
|
|
4
|
+
/** Element name exactly as written, prefix included (`xml:lang` is an attribute, not this). */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Attribute values with entities already decoded. */
|
|
7
|
+
attributes: Record<string, string>;
|
|
8
|
+
/** `true` for `<foo/>`, which emits no matching {@link XmlCloseTag}. */
|
|
9
|
+
selfClosing: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** A closing element. */
|
|
12
|
+
export interface XmlCloseTag {
|
|
13
|
+
kind: 'close';
|
|
14
|
+
name: string;
|
|
15
|
+
}
|
|
16
|
+
/** Character data between elements, with entities already decoded. */
|
|
17
|
+
export interface XmlText {
|
|
18
|
+
kind: 'text';
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
export type XmlToken = XmlOpenTag | XmlCloseTag | XmlText;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the entity references a metainfo file may contain.
|
|
24
|
+
*
|
|
25
|
+
* An UNKNOWN reference is returned verbatim rather than dropped. A metainfo file is
|
|
26
|
+
* hand-written and `&` is the character most often left unescaped in one; turning
|
|
27
|
+
* `Tom & Jerry` into `Tom ` would silently truncate a developer name, which is exactly
|
|
28
|
+
* the kind of Windows-only difference this module exists to prevent.
|
|
29
|
+
*/
|
|
30
|
+
export declare function decodeXmlEntities(text: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Tokenise an XML document.
|
|
33
|
+
*
|
|
34
|
+
* Returns an array rather than a generator on purpose: a metainfo file is a few kilobytes,
|
|
35
|
+
* three bundler targets (`gjs`, `node`, library) have to agree on the output, and an array
|
|
36
|
+
* is the form that needs no iteration-protocol support from any of them.
|
|
37
|
+
*/
|
|
38
|
+
export declare function scanXml(xml: string): XmlToken[];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Fields {@link parseAppdata} reads. `null` means the document does not carry the field. */
|
|
2
|
+
export interface AppdataFields {
|
|
3
|
+
/** `<id>`, with the `.desktop` suffix resolved the way libadwaita resolves it. */
|
|
4
|
+
applicationId: string | null;
|
|
5
|
+
/** `<name>`, resolved against the locale. */
|
|
6
|
+
applicationName: string | null;
|
|
7
|
+
/** `<name>` inside `<developer>`, or the legacy `<developer_name>`. */
|
|
8
|
+
developerName: string | null;
|
|
9
|
+
/** `version` of the first `<release>` — see {@link parseAppdata} on ordering. */
|
|
10
|
+
version: string | null;
|
|
11
|
+
/** `<url type="homepage">`. */
|
|
12
|
+
website: string | null;
|
|
13
|
+
/** `<url type="help">`. */
|
|
14
|
+
supportUrl: string | null;
|
|
15
|
+
/** `<url type="bugtracker">`. */
|
|
16
|
+
issueUrl: string | null;
|
|
17
|
+
/** `<project_license>`, an SPDX id. */
|
|
18
|
+
license: string | null;
|
|
19
|
+
/**
|
|
20
|
+
* The `<description>` of the release named by `releaseNotesVersion`, as the Pango-ish
|
|
21
|
+
* markup `Adw.AboutDialog:release-notes` parses. `null` when no version was asked for,
|
|
22
|
+
* or when no release matches it.
|
|
23
|
+
*/
|
|
24
|
+
releaseNotes: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** Options for {@link parseAppdata}. */
|
|
27
|
+
export interface ParseAppdataOptions {
|
|
28
|
+
/**
|
|
29
|
+
* BCP-47 locale that translatable elements are resolved against. Default `C`, the
|
|
30
|
+
* untranslated source string. {@link appdataLocale} derives it from the platform.
|
|
31
|
+
*/
|
|
32
|
+
locale?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Which release's notes to extract, matched by EXACT string equality — the same
|
|
35
|
+
* `g_strcmp0` libadwaita uses, so no version-comparison semantics are implied and
|
|
36
|
+
* `1.0` never matches `1.0.0`.
|
|
37
|
+
*/
|
|
38
|
+
releaseNotesVersion?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* How well `lang` answers for `locale`, as a count of matching BCP-47 segments; `0` is no
|
|
42
|
+
* match at all.
|
|
43
|
+
*
|
|
44
|
+
* Ported from `ms_locale_score`, including the property that is easy to get wrong: a
|
|
45
|
+
* mismatch INSIDE a segment scores 0 rather than crediting the segments before it, so
|
|
46
|
+
* `de-DE` against `de-AT` is 0 and against `de` is 1. Anything looser would let a Swiss
|
|
47
|
+
* German string win over the untranslated one for an Austrian user.
|
|
48
|
+
*/
|
|
49
|
+
export declare function localeScore(locale: string, lang: string): number;
|
|
50
|
+
/**
|
|
51
|
+
* The BCP-47 locale AppStream matching uses, from `GLib.get_language_names()` and the
|
|
52
|
+
* environment.
|
|
53
|
+
*
|
|
54
|
+
* Ported from `ms_get_current_locale`, quirks included: the most specific name wins, `LANG`
|
|
55
|
+
* is consulted when that name carries no region (GLib answers `de` for `LANG=de_DE` in some
|
|
56
|
+
* configurations, and a `de-DE` translation must still win there), `.UTF-8` is dropped
|
|
57
|
+
* because AppStream assumes it, and an `@variant` becomes a BCP-47 script subtag.
|
|
58
|
+
*/
|
|
59
|
+
export declare function appdataLocale(languageNames: readonly string[], env?: {
|
|
60
|
+
LANG?: string;
|
|
61
|
+
}): string;
|
|
62
|
+
/**
|
|
63
|
+
* Read an AppStream metainfo document.
|
|
64
|
+
*
|
|
65
|
+
* Mirrors `adw_about_dialog_new_from_appdata()`'s `populate_from_appdata`, whose whole field
|
|
66
|
+
* list is: application icon (from `<id>`), application name, developer name, version,
|
|
67
|
+
* website, support url, issue url, licence type, and the release notes of ONE named release.
|
|
68
|
+
* It sets nothing else — no `comments`, no `copyright`, no `translator-credits`, no
|
|
69
|
+
* `developers` — so neither does this.
|
|
70
|
+
*
|
|
71
|
+
* RELEASE ORDER IS THE ONE DIVERGENCE. ministream sorts `<releases>` by a dpkg-style version
|
|
72
|
+
* comparison and reports the highest; this takes the first `<release>` in document order and
|
|
73
|
+
* does not re-implement that comparison. AppStream requires releases to be written
|
|
74
|
+
* newest-first (`appstreamcli validate` reports `releases-not-in-order` otherwise), so the
|
|
75
|
+
* two agree on every valid document — and a caller that will not rely on that passes its own
|
|
76
|
+
* version to `createAboutDialog`, which overrides this on every platform alike.
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseAppdata(xml: string, options?: ParseAppdataOptions): AppdataFields;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DesktopAppearance } from '@gjsify/adwaita-core';
|
|
2
|
+
/** Quiet time after the last file event before re-reading — cfprefsd writes in bursts. */
|
|
3
|
+
export declare const DARWIN_DEBOUNCE_MS = 750;
|
|
4
|
+
export declare function readDarwinAppearance(): Promise<DesktopAppearance>;
|
|
5
|
+
export declare function watchDarwinAppearance(changed: () => void): () => void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DesktopAppearance } from '@gjsify/adwaita-core';
|
|
2
|
+
export declare const GNOME_INTERFACE_SCHEMA = "org.gnome.desktop.interface";
|
|
3
|
+
/** The slice of `Gio.Settings` this reader touches, so a test can hand in a fake. */
|
|
4
|
+
export interface GnomeSettingsLike {
|
|
5
|
+
get_string(key: string): string;
|
|
6
|
+
connect(signal: string, callback: () => void): number;
|
|
7
|
+
disconnect(id: number): void;
|
|
8
|
+
}
|
|
9
|
+
/** A GSettings source and which of the two keys its installed schema actually has. */
|
|
10
|
+
export interface GnomeSettingsSource {
|
|
11
|
+
readonly settings: GnomeSettingsLike;
|
|
12
|
+
readonly keys: readonly ('accent-color' | 'color-scheme')[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The installed schema, or `null`. Looked up in the schema source FIRST because
|
|
16
|
+
* `new Gio.Settings` on a missing schema aborts the whole process instead of
|
|
17
|
+
* throwing, and a missing KEY aborts `get_string` the same way — so each key is
|
|
18
|
+
* checked too (`accent-color` exists only since GNOME 47).
|
|
19
|
+
*/
|
|
20
|
+
export declare function gnomeSettingsSource(): GnomeSettingsSource | null;
|
|
21
|
+
export declare function readGnomeSettings(source: GnomeSettingsSource): DesktopAppearance;
|
|
22
|
+
/** Call `changed` on a change to either key. Returns the unsubscribe. */
|
|
23
|
+
export declare function watchGnomeSettings(source: GnomeSettingsSource, changed: () => void): () => void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { readDesktopAppearance, watchDesktopAppearance } from './reader.js';
|
|
2
|
+
export type { DesktopAppearanceOptions } from './reader.js';
|
|
3
|
+
export { appearanceFromGnomeSettings, appearanceFromMacDefaults, appearanceFromPortal, appearanceFromWindowsRegistry, isGnomeDesktop, MACOS_ACCENT_COLORS, mergeAppearance, parseRegQuery, sameAppearance, } from './mapping.js';
|
|
4
|
+
export type { MacDefaultsValues, WindowsRegistryValues } from './mapping.js';
|
|
5
|
+
export { readPortalAppearance, watchPortalAppearance } from './portal.js';
|
|
6
|
+
export type { PortalEndpoint } from './portal.js';
|
|
7
|
+
export type { GnomeSettingsLike, GnomeSettingsSource } from './gnome-settings.js';
|
|
8
|
+
export { parseDesktopAppearance, renderAppearanceMeta } from '@gjsify/adwaita-core';
|
|
9
|
+
export type { AdwSystemColorScheme, DesktopAppearance } from '@gjsify/adwaita-core';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type DesktopAppearance } from '@gjsify/adwaita-core';
|
|
2
|
+
/** Field by field: `primary` wins, `fallback` fills what it left unknown. */
|
|
3
|
+
export declare function mergeAppearance(primary: DesktopAppearance, fallback: DesktopAppearance): DesktopAppearance;
|
|
4
|
+
/** Whether two appearances say the same thing — what a watcher dedupes on. */
|
|
5
|
+
export declare function sameAppearance(a: DesktopAppearance, b: DesktopAppearance): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* The XDG Settings portal's `org.freedesktop.appearance` namespace, as
|
|
8
|
+
* `ReadAll` returns it unpacked.
|
|
9
|
+
*
|
|
10
|
+
* - `color-scheme` (`u`): 0 no preference, 1 prefer dark, 2 prefer light. Any
|
|
11
|
+
* other number is invalid and dropped (libadwaita warns and uses the default).
|
|
12
|
+
* - `accent-color` (`(ddd)`): sRGB in 0…1. The spec says a value outside that
|
|
13
|
+
* range means "not set" — xdg-desktop-portal-gnome sends `(-1,-1,-1)` for a
|
|
14
|
+
* desktop with no accent — so it is dropped, not clamped.
|
|
15
|
+
*
|
|
16
|
+
* Reference: xdg-desktop-portal `org.freedesktop.impl.portal.Settings` docs
|
|
17
|
+
* Reference: refs/libadwaita/src/adw-settings-impl-portal.c#get_fdo_accent_color
|
|
18
|
+
*/
|
|
19
|
+
export declare function appearanceFromPortal(namespace: Readonly<Record<string, unknown>>): DesktopAppearance;
|
|
20
|
+
/**
|
|
21
|
+
* Whether GSettings `org.gnome.desktop.interface` describes THIS desktop, from
|
|
22
|
+
* `XDG_CURRENT_DESKTOP` (a colon-separated list such as `ubuntu:GNOME`).
|
|
23
|
+
*
|
|
24
|
+
* The schema is installed far beyond GNOME: KDE and Xfce ship
|
|
25
|
+
* gsettings-desktop-schemas, and so does Homebrew on macOS. There nobody writes
|
|
26
|
+
* the keys, so `get_string` answers the schema DEFAULT, and a reader that took it
|
|
27
|
+
* would report `blue` for a desktop set to purple (measured on macOS with
|
|
28
|
+
* `AppleAccentColor = 5`, JumpLink/beifahrer#19). Outside GNOME the answer is
|
|
29
|
+
* therefore "unknown", never the default.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isGnomeDesktop(currentDesktop: string | null | undefined): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* GSettings `org.gnome.desktop.interface` — the fallback where no portal
|
|
34
|
+
* answers. `accent-color` (GNOME 47+) is already one of the nine NAMES, so no
|
|
35
|
+
* snapping and no colour; `color-scheme` (GNOME 42+) is `default`,
|
|
36
|
+
* `prefer-dark` or `prefer-light`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function appearanceFromGnomeSettings(values: {
|
|
39
|
+
readonly accentColor?: string | null;
|
|
40
|
+
readonly colorScheme?: string | null;
|
|
41
|
+
}): DesktopAppearance;
|
|
42
|
+
/** The raw registry values the Windows reader fetches; `null` where the value does not exist. */
|
|
43
|
+
export interface WindowsRegistryValues {
|
|
44
|
+
/** `HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Accent` `AccentPalette` (REG_BINARY, 32 bytes). */
|
|
45
|
+
readonly accentPalette?: Uint8Array | null;
|
|
46
|
+
/** `HKCU\Software\Microsoft\Windows\DWM` `AccentColor` (REG_DWORD, `0xAABBGGRR`). */
|
|
47
|
+
readonly dwmAccentColor?: number | null;
|
|
48
|
+
/** `HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize` `AppsUseLightTheme` (REG_DWORD). */
|
|
49
|
+
readonly appsUseLightTheme?: number | null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Windows. The accent libadwaita reads is WinRT's `UIColorType_Accent`
|
|
53
|
+
* (`adw-settings-impl-win32.c`), which Explorer persists as entry 3 of the
|
|
54
|
+
* eight RGBA quads in `AccentPalette` (light 3/2/1, ACCENT, dark 1/2/3,
|
|
55
|
+
* unused). Where that value is missing, DWM's `AccentColor` — the title-bar
|
|
56
|
+
* accent, stored `0xAABBGGRR` — is the closest the registry offers.
|
|
57
|
+
*
|
|
58
|
+
* `AppsUseLightTheme` is the "choose your app mode" switch: 0 dark, 1 light.
|
|
59
|
+
* Absent (Windows before 10 1809) leaves the scheme unknown.
|
|
60
|
+
*
|
|
61
|
+
* NOT MEASURED ON WINDOWS: the palette layout is Microsoft's undocumented
|
|
62
|
+
* persistence format, taken from its long-standing public descriptions; the
|
|
63
|
+
* registry paths match JumpLink/beifahrer#18.
|
|
64
|
+
*/
|
|
65
|
+
export declare function appearanceFromWindowsRegistry(values: WindowsRegistryValues): DesktopAppearance;
|
|
66
|
+
/**
|
|
67
|
+
* The colour each `AppleAccentColor` index stands for. macOS stores an INDEX,
|
|
68
|
+
* not a colour, so these are the `NSColor.controlAccentColor` values libadwaita's
|
|
69
|
+
* own test uses for its macOS cases (`tests/test-accent-color.c`) — the same
|
|
70
|
+
* colours `adw-settings-impl-macos.c` snaps at runtime. An ABSENT key is
|
|
71
|
+
* "multicolour", whose accent is blue (Firefox 156 on macOS 27 reports CSS
|
|
72
|
+
* `AccentColor` as `rgb(0, 122, 255)` there, which snaps to blue as well).
|
|
73
|
+
*/
|
|
74
|
+
export declare const MACOS_ACCENT_COLORS: Readonly<Record<string, string>>;
|
|
75
|
+
/** The raw `defaults read -g <key>` answers; `null` where the key does not exist. */
|
|
76
|
+
export interface MacDefaultsValues {
|
|
77
|
+
readonly appleAccentColor?: string | null;
|
|
78
|
+
readonly appleInterfaceStyle?: string | null;
|
|
79
|
+
readonly appleInterfaceStyleSwitchesAutomatically?: string | null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* macOS global defaults. Measured on macOS 27 (JumpLink/beifahrer#18): with
|
|
83
|
+
* default settings all three keys are ABSENT, and absence is itself a value —
|
|
84
|
+
* multicolour accent, light appearance.
|
|
85
|
+
*
|
|
86
|
+
* One exception makes absence ambiguous: with "Auto" appearance
|
|
87
|
+
* (`AppleInterfaceStyleSwitchesAutomatically = 1`) the system flips between
|
|
88
|
+
* light and dark by time of day, and a missing `AppleInterfaceStyle` no longer
|
|
89
|
+
* proves light. The scheme is then left UNKNOWN, so a page falls back to
|
|
90
|
+
* `prefers-color-scheme`, which the browser tracks live.
|
|
91
|
+
*/
|
|
92
|
+
export declare function appearanceFromMacDefaults(values: MacDefaultsValues): DesktopAppearance;
|
|
93
|
+
/**
|
|
94
|
+
* Parse one value out of `reg.exe query <key> /v <name>` output:
|
|
95
|
+
*
|
|
96
|
+
* HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM
|
|
97
|
+
* AccentColor REG_DWORD 0xffd47800
|
|
98
|
+
*
|
|
99
|
+
* Returns a number for `REG_DWORD`, bytes for `REG_BINARY`, `null` when the
|
|
100
|
+
* value is not in the output (reg.exe then exits 1 with an error instead).
|
|
101
|
+
*/
|
|
102
|
+
export declare function parseRegQuery(stdout: string, name: string): number | Uint8Array | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Gio from 'gi://Gio?version=2.0';
|
|
2
|
+
import type { DesktopAppearance } from '@gjsify/adwaita-core';
|
|
3
|
+
export declare const PORTAL_BUS_NAME = "org.freedesktop.portal.Desktop";
|
|
4
|
+
export declare const PORTAL_OBJECT_PATH = "/org/freedesktop/portal/desktop";
|
|
5
|
+
export declare const PORTAL_SETTINGS_INTERFACE = "org.freedesktop.portal.Settings";
|
|
6
|
+
export declare const APPEARANCE_NAMESPACE = "org.freedesktop.appearance";
|
|
7
|
+
/** Where to reach the portal. `busName` is `null` on a peer-to-peer connection (the tests). */
|
|
8
|
+
export interface PortalEndpoint {
|
|
9
|
+
readonly connection: Gio.DBusConnection;
|
|
10
|
+
readonly busName: string | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The appearance the portal reports, or `null` when no portal answered — the
|
|
14
|
+
* caller then falls back. A portal that answers with neither key yields `{}`,
|
|
15
|
+
* which is an answer ("this desktop says nothing"), not a failure.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readPortalAppearance(endpoint: PortalEndpoint): Promise<DesktopAppearance | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Call `changed` whenever the portal announces a change in the appearance
|
|
20
|
+
* namespace. Returns the unsubscribe. The caller re-reads rather than applying
|
|
21
|
+
* the signal's one value, so a burst of changes collapses into one answer.
|
|
22
|
+
*/
|
|
23
|
+
export declare function watchPortalAppearance(endpoint: PortalEndpoint, changed: () => void): () => void;
|
|
24
|
+
/** The session bus, or `null` where there is none (no `DBUS_SESSION_BUS_ADDRESS`, a CI container). */
|
|
25
|
+
export declare function sessionPortalEndpoint(): Promise<PortalEndpoint | null>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DesktopAppearance } from '@gjsify/adwaita-core';
|
|
2
|
+
import { type GnomeSettingsSource } from './gnome-settings.js';
|
|
3
|
+
import { type PortalEndpoint } from './portal.js';
|
|
4
|
+
/** Overrides for the sources, mainly so a test can point the reader at a fake. */
|
|
5
|
+
export interface DesktopAppearanceOptions {
|
|
6
|
+
/** `process.platform` by default. Anything other than `win32`/`darwin` takes the portal route. */
|
|
7
|
+
readonly platform?: string;
|
|
8
|
+
/** Linux: the portal to ask. Default: `org.freedesktop.portal.Desktop` on the session bus; `null` skips it. */
|
|
9
|
+
readonly portal?: PortalEndpoint | null;
|
|
10
|
+
/** Linux: the GSettings fallback. Default: `org.gnome.desktop.interface` if installed; `null` skips it. */
|
|
11
|
+
readonly gnomeSettings?: GnomeSettingsSource | null;
|
|
12
|
+
/**
|
|
13
|
+
* Linux: `XDG_CURRENT_DESKTOP`, read from the environment by default. GSettings is
|
|
14
|
+
* consulted only when it names GNOME: elsewhere the schema answers its defaults.
|
|
15
|
+
*/
|
|
16
|
+
readonly currentDesktop?: string | null;
|
|
17
|
+
}
|
|
18
|
+
/** The desktop's accent and colour-scheme preference, as far as this OS can tell. Never rejects. */
|
|
19
|
+
export declare function readDesktopAppearance(options?: DesktopAppearanceOptions): Promise<DesktopAppearance>;
|
|
20
|
+
/**
|
|
21
|
+
* Call `callback` with the current appearance, then again whenever it changes
|
|
22
|
+
* (duplicate reports are dropped). Returns the unsubscribe — HOLD it: the
|
|
23
|
+
* returned closure is what keeps the bus subscription, the settings object or
|
|
24
|
+
* the file monitor alive.
|
|
25
|
+
*/
|
|
26
|
+
export declare function watchDesktopAppearance(callback: (appearance: DesktopAppearance) => void, options?: DesktopAppearanceOptions): () => void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stdout of `argv` when it exits 0, `null` otherwise — including when the tool
|
|
3
|
+
* is missing. Both tools exit non-zero for "no such value", which the readers
|
|
4
|
+
* treat as absence, so the two cases need no distinction here.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runTool(argv: readonly string[]): Promise<string | null>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DesktopAppearance } from '@gjsify/adwaita-core';
|
|
2
|
+
/** How often a watcher re-reads. Settings change by hand, so seconds are fast enough. */
|
|
3
|
+
export declare const WIN32_POLL_INTERVAL_S = 3;
|
|
4
|
+
export declare function readWin32Appearance(): Promise<DesktopAppearance>;
|
|
5
|
+
export declare function watchWin32Appearance(changed: () => void): () => void;
|
|
@@ -2,6 +2,7 @@ import Adw from 'gi://Adw?version=1';
|
|
|
2
2
|
import Gio from 'gi://Gio?version=2.0';
|
|
3
3
|
import Gtk from 'gi://Gtk?version=4.0';
|
|
4
4
|
import { type InstallDevtoolsOptions } from '@gjsify/devtools';
|
|
5
|
+
import { type CreateAboutDialogOptions } from './about-dialog.js';
|
|
5
6
|
import { type BundledIconThemeOptions } from './icon-theme.js';
|
|
6
7
|
import type { AboutInfo } from './types.js';
|
|
7
8
|
/** Options for {@link AdwaitaApp} / {@link runAdwaitaApp}. */
|
|
@@ -31,6 +32,17 @@ export interface AdwaitaAppOptions {
|
|
|
31
32
|
bundledIcons?: boolean | BundledIconThemeOptions;
|
|
32
33
|
/** When set, wires an `app.about` action opening an `Adw.AboutDialog`. */
|
|
33
34
|
about?: AboutInfo;
|
|
35
|
+
/**
|
|
36
|
+
* Build the `app.about` dialog from the application's AppStream metainfo instead of from
|
|
37
|
+
* literal fields. Wins over {@link AdwaitaAppOptions.about} when both are given.
|
|
38
|
+
*
|
|
39
|
+
* Prefer it: the metainfo is already written, already translated and already installed, so
|
|
40
|
+
* restating its name, developer, licence and urls in `about` is a second copy that drifts
|
|
41
|
+
* — and the drift is invisible until somebody opens the dialog. See
|
|
42
|
+
* {@link CreateAboutDialogOptions}; it works where `Adw.AboutDialog.new_from_appdata()`
|
|
43
|
+
* does not exist.
|
|
44
|
+
*/
|
|
45
|
+
aboutAppdata?: CreateAboutDialogOptions;
|
|
34
46
|
/** Wire `app.quit` (`<primary>q`). Default `true`. */
|
|
35
47
|
quitAction?: boolean;
|
|
36
48
|
/**
|
package/lib/types/index.d.ts
CHANGED
|
@@ -3,10 +3,14 @@ export type { AdwaitaAppOptions } from './application.js';
|
|
|
3
3
|
export { BUNDLED_ICON_COUNT, BUNDLED_ICON_RESOURCE_PATH, BUNDLED_ICON_THEME_NAME, addBundledIconsToTheme, installBundledIconTheme, registerBundledIconResource, } from './icon-theme.js';
|
|
4
4
|
export type { BundledIconPreference, BundledIconThemeOptions } from './icon-theme.js';
|
|
5
5
|
export { createNavShell } from './nav-shell.js';
|
|
6
|
-
export type { NavShell, NavShellOptions } from './nav-shell.js';
|
|
6
|
+
export type { NavShell, NavShellHeaderFunc, NavShellOptions } from './nav-shell.js';
|
|
7
7
|
export { LoadToken, loadIntoStack } from './view-loader.js';
|
|
8
8
|
export type { LoadIntoStackOptions } from './view-loader.js';
|
|
9
9
|
export { LoadingStack } from './loading-stack.js';
|
|
10
|
+
export { applyAppdataFields, buildAboutDialogFromAppdata, createAboutDialog, hasAppdataConstructor, licenseTypeFor, } from './about-dialog.js';
|
|
11
|
+
export type { AppdataSource, CreateAboutDialogOptions } from './about-dialog.js';
|
|
12
|
+
export { appdataLocale, parseAppdata } from './appdata.js';
|
|
13
|
+
export type { AppdataFields, ParseAppdataOptions } from './appdata.js';
|
|
10
14
|
export { confirmDialog, errorDialog } from './dialogs.js';
|
|
11
15
|
export type { ConfirmOptions } from './dialogs.js';
|
|
12
16
|
export { registerToastOverlay, showToast } from './toast.js';
|
|
@@ -14,8 +18,10 @@ export { pickFile, saveFile } from './file-dialog.js';
|
|
|
14
18
|
export type { FileFilterSpec, PickFileOptions } from './file-dialog.js';
|
|
15
19
|
export { initLocale } from './locale.js';
|
|
16
20
|
export type { InitLocaleOptions, Translator } from './locale.js';
|
|
17
|
-
export { SYSTEM_LOCALE_DIR, resolveLocaleDir } from './locale-dir.js';
|
|
21
|
+
export { SYSTEM_LOCALE_DIR, resolveLocaleDir, systemLocaleDir } from './locale-dir.js';
|
|
18
22
|
export type { ResolveLocaleDirOptions } from './locale-dir.js';
|
|
23
|
+
export { APPLE_ACCENT_COLOR_ARGV, MACOS_ACCENT_POLL_SECONDS, onMacosAccentColorChanged, readMacosAccentColor, } from './system-accent.js';
|
|
24
|
+
export type { OnMacosAccentColorChangedOptions, ReadMacosAccentColorOptions } from './system-accent.js';
|
|
19
25
|
export { readAppDevHooks } from './dev-hooks.js';
|
|
20
26
|
export type { AppDevHooks, ReadAppDevHooksOptions } from './dev-hooks.js';
|
|
21
27
|
export { findNavItem, resolveInitialNavIndex } from './nav-model.js';
|
|
@@ -1,5 +1,26 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* `bindtextdomain`'s directory for a system install — **on Linux**, where it exists.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately not a cross-platform answer: see {@link systemLocaleDir}.
|
|
5
|
+
*/
|
|
2
6
|
export declare const SYSTEM_LOCALE_DIR = "/usr/share/locale";
|
|
7
|
+
/**
|
|
8
|
+
* The system catalogue directory for `platform`, or `undefined` where there is none.
|
|
9
|
+
*
|
|
10
|
+
* `undefined` for **darwin** and **win32**, and the darwin half is the reason this function
|
|
11
|
+
* exists rather than a constant. `/usr/share/locale` does not exist on Windows, so naming it
|
|
12
|
+
* there was merely useless — but it DOES exist on macOS, holding Apple's own locale data and
|
|
13
|
+
* never an application's catalogues. So the old unconditional fallback bound a real directory
|
|
14
|
+
* that could not resolve a single msgid: no error, no translation, and `Translator.localeDir`
|
|
15
|
+
* reporting a plausible path as if the lookup had somewhere to go. A directory that is absent
|
|
16
|
+
* is the better answer of the two, because it is the true one.
|
|
17
|
+
*
|
|
18
|
+
* Any OTHER platform keeps {@link SYSTEM_LOCALE_DIR}. `process.platform` has more values than
|
|
19
|
+
* the three desktops this project targets — `freebsd`, `openbsd`, `sunos`, … — and it is also
|
|
20
|
+
* `undefined` in a GJS bundle built with `--globals none`. Answering those with "no directory"
|
|
21
|
+
* would change behaviour nobody measured; answering them as today changes nothing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function systemLocaleDir(platform?: string): string | undefined;
|
|
3
24
|
export interface ResolveLocaleDirOptions {
|
|
4
25
|
/** Wins over everything — a dev tree (`dist/locale`), or a test fixture. */
|
|
5
26
|
localeDir?: string;
|
|
@@ -7,12 +28,19 @@ export interface ResolveLocaleDirOptions {
|
|
|
7
28
|
env?: Record<string, string | undefined>;
|
|
8
29
|
/** Used when neither the option nor the environment names a directory. */
|
|
9
30
|
fallbackDir?: string;
|
|
31
|
+
/** `process.platform`, for the last step only. Omitted keeps the Linux answer. */
|
|
32
|
+
platform?: string;
|
|
10
33
|
}
|
|
11
34
|
/**
|
|
12
|
-
* Resolve the directory to bind a text domain to.
|
|
35
|
+
* Resolve the directory to bind a text domain to, or `undefined` when there is none to bind.
|
|
13
36
|
*
|
|
14
37
|
* Precedence: explicit option, then `GJSIFY_LOCALE_DIR` (exported by the `gjsify ship` launcher,
|
|
15
38
|
* which is the only party that knows whether the payload became `/usr`, a `--prefix` tree or
|
|
16
|
-
* `/app`), then the caller's fallback, then the system directory.
|
|
39
|
+
* `/app`), then the caller's fallback, then the platform's system directory — if it has one.
|
|
40
|
+
*
|
|
41
|
+
* A shipped app reaches an answer at step two on every OS, so `undefined` means the app carries
|
|
42
|
+
* no catalogues AND the platform has no system ones. `locale.ts` then skips `bindtextdomain`
|
|
43
|
+
* rather than binding somewhere arbitrary, and every lookup returns its msgid — which is what an
|
|
44
|
+
* untranslated application is supposed to do.
|
|
17
45
|
*/
|
|
18
|
-
export declare function resolveLocaleDir(options?: ResolveLocaleDirOptions): string;
|
|
46
|
+
export declare function resolveLocaleDir(options?: ResolveLocaleDirOptions): string | undefined;
|
package/lib/types/locale.d.ts
CHANGED
|
@@ -7,8 +7,13 @@ export interface Translator {
|
|
|
7
7
|
plural(singular: string, plural: string, n: number): string;
|
|
8
8
|
/** Disambiguating lookup, for a msgid whose meaning depends on where it appears. */
|
|
9
9
|
context(context: string, msgid: string): string;
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The bound directory and domain — for a diagnostic line, never for a lookup.
|
|
12
|
+
*
|
|
13
|
+
* `undefined` when nothing was bound: no catalogues were staged and the platform has no
|
|
14
|
+
* system directory either (macOS, Windows). Every lookup then returns its msgid.
|
|
15
|
+
*/
|
|
16
|
+
readonly localeDir: string | undefined;
|
|
12
17
|
readonly domain: string;
|
|
13
18
|
}
|
|
14
19
|
/**
|
package/lib/types/nav-shell.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import Adw from 'gi://Adw?version=1';
|
|
2
2
|
import Gtk from 'gi://Gtk?version=4.0';
|
|
3
3
|
import type { NavItem } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Per-row section header, wired straight into `Gtk.ListBox.set_header_func`.
|
|
6
|
+
*
|
|
7
|
+
* `before` is the item of the row ABOVE `row`, or `null` for the first row — the
|
|
8
|
+
* consumer's decision input, since a header is a comparison against the previous
|
|
9
|
+
* section. Return `null` to draw no header.
|
|
10
|
+
*/
|
|
11
|
+
export type NavShellHeaderFunc = (row: Gtk.ListBoxRow, before: NavItem | null) => Gtk.Widget | null;
|
|
4
12
|
/** Options for {@link createNavShell}. */
|
|
5
13
|
export interface NavShellOptions {
|
|
6
14
|
/** Sidebar rows + stack child ids, in order. */
|
|
@@ -15,6 +23,12 @@ export interface NavShellOptions {
|
|
|
15
23
|
sidebarHeaderEnd?: Gtk.Widget;
|
|
16
24
|
/** Collapse the split view below this width (px). Default `720`. */
|
|
17
25
|
collapseWidth?: number;
|
|
26
|
+
/** Per-row group header, mapped onto `Gtk.ListBox.set_header_func`. */
|
|
27
|
+
headerFunc?: NavShellHeaderFunc;
|
|
28
|
+
/** Widget below the sidebar list (e.g. an account row). */
|
|
29
|
+
sidebarBottomBar?: Gtk.Widget;
|
|
30
|
+
/** Widget below the content stack, under the conversation rather than in it. */
|
|
31
|
+
contentBottomBar?: Gtk.Widget;
|
|
18
32
|
}
|
|
19
33
|
/** The pieces a consumer wires into its window + views. */
|
|
20
34
|
export interface NavShell {
|
|
@@ -24,6 +38,18 @@ export interface NavShell {
|
|
|
24
38
|
stack: Gtk.Stack;
|
|
25
39
|
/** Content header bar — add title widgets / buttons. */
|
|
26
40
|
contentHeader: Adw.HeaderBar;
|
|
41
|
+
/**
|
|
42
|
+
* Replace the sidebar rows, in place. The `Gtk.Stack` is left alone: its pages
|
|
43
|
+
* belong to the consumer (`add_named(view, item.id)`), so a page outliving its
|
|
44
|
+
* item is theirs to keep or drop.
|
|
45
|
+
*
|
|
46
|
+
* The selected id survives if the new items still carry it, and `onSelect` does
|
|
47
|
+
* NOT fire either way — a rebuild is not a user selection, and re-announcing
|
|
48
|
+
* the same item would make `onSelect` fire on every list update. An item that
|
|
49
|
+
* is gone leaves nothing selected; `selectById` is how a consumer picks the
|
|
50
|
+
* replacement deliberately.
|
|
51
|
+
*/
|
|
52
|
+
setItems(items: readonly NavItem[]): void;
|
|
27
53
|
/** Select a nav item by its id (falls back to the first item). */
|
|
28
54
|
selectById(id: string): void;
|
|
29
55
|
/** Select a nav item by index. */
|
|
@@ -33,6 +59,6 @@ export interface NavShell {
|
|
|
33
59
|
* Build the nav shell into `window` (which owns the responsive breakpoint).
|
|
34
60
|
* Returns the split view to set as content plus the stack + content header to
|
|
35
61
|
* fill. Row selection drives `onSelect`; a collapsed shell reveals the content
|
|
36
|
-
* pane on selection.
|
|
62
|
+
* pane on selection. Items may be replaced afterwards — see {@link NavShell.setItems}.
|
|
37
63
|
*/
|
|
38
64
|
export declare function createNavShell(window: Adw.ApplicationWindow, options: NavShellOptions): NavShell;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type AdwAccentColorName } from '@gjsify/adwaita-core';
|
|
2
|
+
/** The command that prints the accent: `AppleAccentColor` in the global domain. */
|
|
3
|
+
export declare const APPLE_ACCENT_COLOR_ARGV: readonly string[];
|
|
4
|
+
/** How often {@link onMacosAccentColorChanged} re-reads the preference by default. */
|
|
5
|
+
export declare const MACOS_ACCENT_POLL_SECONDS = 5;
|
|
6
|
+
export interface ReadMacosAccentColorOptions {
|
|
7
|
+
/** The command to run instead of {@link APPLE_ACCENT_COLOR_ARGV}. For tests. */
|
|
8
|
+
readonly argv?: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The macOS system accent as one of Adwaita's nine, or `null` where there is
|
|
12
|
+
* none to follow: not macOS (no `defaults`), or a value macOS does not define.
|
|
13
|
+
* "Multicolor" gives blue — an Adwaita app's own accent, as libadwaita shows it.
|
|
14
|
+
*
|
|
15
|
+
* Synchronous: it waits for one short-lived `defaults` process (~7 ms).
|
|
16
|
+
*/
|
|
17
|
+
export declare function readMacosAccentColor(options?: ReadMacosAccentColorOptions): AdwAccentColorName | null;
|
|
18
|
+
export interface OnMacosAccentColorChangedOptions {
|
|
19
|
+
/** Seconds between reads. Defaults to {@link MACOS_ACCENT_POLL_SECONDS}. */
|
|
20
|
+
readonly intervalSeconds?: number;
|
|
21
|
+
/** The reader. Defaults to {@link readMacosAccentColor}; tests pass their own. */
|
|
22
|
+
readonly read?: () => AdwAccentColorName | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Call `listener` with the new accent whenever the macOS system accent changes.
|
|
26
|
+
* Reads once now as the baseline (without calling `listener`), then re-reads
|
|
27
|
+
* every `intervalSeconds` on the default main context. Returns the unsubscribe,
|
|
28
|
+
* which the caller must hold and call: the timer lives until then.
|
|
29
|
+
*/
|
|
30
|
+
export declare function onMacosAccentColorChanged(listener: (accent: AdwAccentColorName | null) => void, options?: OnMacosAccentColorChangedOptions): () => void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/adwaita-app",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Native Adwaita application shell for GJS/GTK apps — Adw.Application lifecycle (runAsync + devtools + CSS), a data-driven NavigationSplitView nav shell, an async-view mounter,
|
|
3
|
+
"version": "0.53.0",
|
|
4
|
+
"description": "Native Adwaita application shell for GJS/GTK apps — Adw.Application lifecycle (runAsync + devtools + CSS), a data-driven NavigationSplitView nav shell, an async-view mounter, promise dialog/toast/file helpers, and a display-less reader of the desktop accent and colour scheme (`./appearance`)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./lib/types/index.d.ts",
|
|
11
11
|
"default": "./lib/esm/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./appearance": {
|
|
14
|
+
"types": "./lib/types/appearance/index.d.ts",
|
|
15
|
+
"default": "./lib/esm/appearance/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./system-accent": {
|
|
18
|
+
"types": "./lib/types/system-accent.d.ts",
|
|
19
|
+
"default": "./lib/esm/system-accent.js"
|
|
12
20
|
}
|
|
13
21
|
},
|
|
14
22
|
"files": [
|
|
@@ -42,19 +50,20 @@
|
|
|
42
50
|
"framework"
|
|
43
51
|
],
|
|
44
52
|
"dependencies": {
|
|
45
|
-
"@girs/adw-1": "5.
|
|
46
|
-
"@girs/gdk-4.0": "5.
|
|
47
|
-
"@girs/gio-2.0": "5.
|
|
48
|
-
"@girs/gjs": "5.
|
|
49
|
-
"@girs/glib-2.0": "5.
|
|
50
|
-
"@girs/gobject-2.0": "5.
|
|
51
|
-
"@girs/gtk-4.0": "5.
|
|
52
|
-
"@gjsify/
|
|
53
|
+
"@girs/adw-1": "5.4.0",
|
|
54
|
+
"@girs/gdk-4.0": "5.4.0",
|
|
55
|
+
"@girs/gio-2.0": "5.4.0",
|
|
56
|
+
"@girs/gjs": "5.4.0",
|
|
57
|
+
"@girs/glib-2.0": "5.4.0",
|
|
58
|
+
"@girs/gobject-2.0": "5.4.0",
|
|
59
|
+
"@girs/gtk-4.0": "5.4.0",
|
|
60
|
+
"@gjsify/adwaita-core": "^0.53.0",
|
|
61
|
+
"@gjsify/devtools": "^0.53.0"
|
|
53
62
|
},
|
|
54
63
|
"devDependencies": {
|
|
55
|
-
"@gjsify/cli": "^0.
|
|
56
|
-
"@gjsify/storybook": "^0.
|
|
57
|
-
"@gjsify/unit": "^0.
|
|
64
|
+
"@gjsify/cli": "^0.53.0",
|
|
65
|
+
"@gjsify/storybook": "^0.53.0",
|
|
66
|
+
"@gjsify/unit": "^0.53.0",
|
|
58
67
|
"@types/node": "^25.9.2",
|
|
59
68
|
"typescript": "^6.0.3"
|
|
60
69
|
},
|
|
@@ -66,6 +75,15 @@
|
|
|
66
75
|
"nativescript": "none"
|
|
67
76
|
},
|
|
68
77
|
"tier": 2,
|
|
78
|
+
"os": {
|
|
79
|
+
"linux": "supported",
|
|
80
|
+
"darwin": "partial",
|
|
81
|
+
"win32": "partial"
|
|
82
|
+
},
|
|
83
|
+
"osNotes": {
|
|
84
|
+
"darwin": "`locale.ts` reads `process.platform` to decide whether `initLocale` binds a text domain at all, so this package owes an OS claim (ADR 0018). The DECISION is complete and asserted from any host: the platform is an argument to `systemLocaleDir(platform)`, and `locale-dir.spec.ts` drives darwin, win32, linux, an unmeasured platform and an absent `process.platform` through it from Linux. What no runner observes is the SELECTION — `locale.ts` reading the real `process.platform` on a real Mac and skipping `bindtextdomain`. NO LEG OF THIS PACKAGE'S SUITE HAS EVER RUN ON DARWIN: `gtk-os-suites.yml` runs `@gjsify/gtk-host` (gating) and `@gjsify/react-native` (probe) on the darwin legs and nothing else, and `macos-suites.yml` is a Node-pillar workflow that states outright that `gtk4` is not installed. This is exactly the position `@gjsify/path` records for win32 — semantics exercised from every host, selection observable nowhere — and it takes the same word. Promote from the first run of this package's own suite on a darwin leg. The `./appearance` subpath (ADR 0078) branches on `process.platform` too: its macOS reader runs `defaults read -g` and watches `~/Library/Preferences`. Its mapping (`appearanceFromMacDefaults`) is asserted on every host by `appearance/mapping.spec.ts`; spawning `defaults` and the directory monitor have NOT run on a Mac. Verified on real hardware separately, not in CI.",
|
|
85
|
+
"win32": "As darwin for why a claim is owed and why it is not `supported`: `locale-dir.spec.ts` asserts the win32 answer from Linux, and nothing runs this package's suite on a Windows host — `gtk-os-suites.yml`'s win32 leg carries `@gjsify/gtk-host` and `@gjsify/react-native` as probes and nothing else. One thing is BETTER here than on darwin and is worth recording so the promotion is not blocked on the wrong obstacle: there is no GJS host on Windows at all (ADR 0024 § 4), so the route is Node, where `process.platform` comes from the host itself and is authoritative — the unexercised `uname` probe that keeps `@gjsify/process` at win32 `partial` is not on this path. What is unexercised is this package, not the platform read underneath it. Promote from the first run of this package's own suite on a win32 leg. The `./appearance` subpath (ADR 0078) reads the accent and app mode with `reg.exe query` and polls for changes, because `GWin32RegistryKey` returns values through a `gpointer` out-parameter introspection cannot marshal. The parser and mapping (`parseRegQuery`, `appearanceFromWindowsRegistry`) are asserted on every host; spawning reg.exe on Windows has NOT been measured."
|
|
86
|
+
},
|
|
69
87
|
"storybook": {
|
|
70
88
|
"applicationId": "org.gjsify.AdwaitaAppStorybook",
|
|
71
89
|
"title": "Adwaita App",
|