node-gtk 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -589,6 +589,35 @@ function emitMethods(info, nFn, getFn, ctx, ownerName, inherited) {
589
589
  return lines
590
590
  }
591
591
 
592
+ // Virtual functions, emitted as the `virtual_*` override surface that
593
+ // registerClass() wires into the GObject vtable (lib/register-class.js). A
594
+ // subclass overrides a vfunc by defining `virtual_<camelCaseVfuncName>` — e.g.
595
+ // the `size_allocate` vfunc is overridden as `virtual_sizeAllocate`. We emit ALL
596
+ // vfuncs (even invoker-backed ones whose public method is also emitted) so the
597
+ // override surface is explicit and `super.virtual_<name>(...)` chain-up resolves.
598
+ //
599
+ // The signature is modelled exactly like a method's (signature(): IN params,
600
+ // OUT/INOUT folded into a return tuple) because node-gtk marshals a vfunc
601
+ // implementation's out-params from its JS return value (src/callback.cc). The
602
+ // `virtual_` prefix keeps these names distinct from the public invoker method of
603
+ // the same vfunc, so the two never collide (issue #457).
604
+ // LIMITATION: non-primitive OUT params of a vfunc are passed in as objects to be
605
+ // mutated rather than returned; they are surfaced in the return tuple here to
606
+ // match the public-method convention, which is the common (all-primitive) case.
607
+ function emitVFuncs(info, nFn, getFn, ctx, ownerName) {
608
+ const lines = []
609
+ for (const v of each(info, nFn, getFn)) {
610
+ try {
611
+ const rawName = baseName(v)
612
+ const name = 'virtual_' + camelCase(rawName)
613
+ const sig = signature(v, ctx, {})
614
+ const doc = docBlock(ctx, DocKey.fn(ownerName || '', rawName), ' ', { callable: true, deprecated: isDeprecated(v) })
615
+ lines.push(`${doc} ${name}(${sig.params}): ${sig.ret}`)
616
+ } catch (e) { /* skip unrepresentable vfunc */ }
617
+ }
618
+ return lines
619
+ }
620
+
592
621
  function emitProperties(info, nFn, getFn, ctx, inherited, containerName) {
593
622
  const lines = []
594
623
  const writable = []
@@ -723,6 +752,7 @@ function emitObject(info, ctx) {
723
752
  const inherited = collectInheritedMethods(info, ctx)
724
753
  const props = emitProperties(info, GI.object_info_get_n_properties, GI.object_info_get_property, ctx, inherited, name)
725
754
  const methods = emitMethods(info, GI.object_info_get_n_methods, GI.object_info_get_method, ctx, name, inherited)
755
+ const vfuncs = emitVFuncs(info, GI.object_info_get_n_vfuncs, GI.object_info_get_vfunc, ctx, name)
726
756
  // When the class merges interfaces, declare the signal API in the companion
727
757
  // interface (unified across the whole hierarchy) instead of the class body.
728
758
  const signals = hasIfaces ? [] : renderSignals(collectSignals(info, ctx), ctx)
@@ -746,7 +776,7 @@ function emitObject(info, ctx) {
746
776
  }
747
777
 
748
778
  const dedup = makeMemberDedup()
749
- const body = [...constants, ...props.lines, ...methods, ...signals, ...signalApi].filter(dedup)
779
+ const body = [...constants, ...props.lines, ...methods, ...vfuncs, ...signals, ...signalApi].filter(dedup)
750
780
 
751
781
  const out = []
752
782
  const ext = parentRef ? ` extends ${parentRef}` : ''
@@ -787,6 +817,13 @@ function emitInterface(info, ctx) {
787
817
  const inherited = collectInterfaceInheritedMethods(info, ctx)
788
818
  const props = emitProperties(info, GI.interface_info_get_n_properties, GI.interface_info_get_property, ctx, inherited, name)
789
819
  const methods = emitMethods(info, GI.interface_info_get_n_methods, GI.interface_info_get_method, ctx, name, inherited)
820
+ // NOTE: interface vfuncs are intentionally NOT emitted. A class implementing
821
+ // several interfaces declaration-merges them into its companion interface, and
822
+ // emitting `virtual_*` members on interfaces that collide across that diamond
823
+ // (e.g. GTK3's Atk Component/TableCell/Action accessibility stack) produces
824
+ // TS2320. Overriding an interface vfunc is rare; object (class) vfuncs — which
825
+ // cover the issue-#457 cases (Widget, GObject lifecycle, LayoutManager) — are
826
+ // emitted in emitObject() and are unaffected.
790
827
 
791
828
  const ext = prereqs.length ? ` extends ${prereqs.join(', ')}` : ''
792
829
  const dedup = makeMemberDedup()
@@ -946,7 +983,6 @@ const SHIM_STATIC_API = ` export function isLoaded(ns: string, version?: string
946
983
  export function prependLibraryPath(path: string): void
947
984
  export function listAvailableModules(): Promise<{ name: string, version: string }[]>
948
985
  export function registerClass(klass: Function): Function
949
- export function startLoop(): void
950
986
  export function getGType(value: Function | object | bigint): bigint
951
987
  export const System: any`
952
988
 
@@ -966,6 +1002,25 @@ function writeShim(outdir, nsVersions) {
966
1002
  lines.push(` export function require(ns: string, version?: string): any`)
967
1003
  lines.push(SHIM_STATIC_API)
968
1004
  lines.push(`}`)
1005
+
1006
+ // Ambient declarations for the `gi:` ESM import scheme, so
1007
+ // `import Gtk from 'gi:Gtk-4.0'` is typed as the namespace object (the same
1008
+ // type `gi.require('Gtk','4.0')` returns). The versionless `gi:Gtk` alias
1009
+ // resolves to the version generated here. The `gi:*` fallback does NOT make
1010
+ // un-generated namespaces `any` — its default export is typed as an
1011
+ // instruction string, so any use of one is a TypeScript error that names the
1012
+ // fix (a more specific `gi:<Ns>-<v>` declaration above always wins over it).
1013
+ lines.push(``)
1014
+ for (const [ns, version] of nsVersions) {
1015
+ const type = `typeof import('./${ns}-${version}.js')`
1016
+ lines.push(`declare module 'gi:${ns}-${version}' { const ns: ${type}; export default ns }`)
1017
+ lines.push(`declare module 'gi:${ns}' { const ns: ${type}; export default ns }`)
1018
+ }
1019
+ lines.push(`declare module 'gi:*' {`)
1020
+ lines.push(` const ns: 'node-gtk: no generated types for this gi: namespace. Run: node-gtk generate-types <Namespace>-<version>'`)
1021
+ lines.push(` export default ns`)
1022
+ lines.push(`}`)
1023
+
969
1024
  fs.writeFileSync(path.join(outdir, 'node-gtk.d.ts'), lines.join('\n') + '\n')
970
1025
  }
971
1026
 
@@ -1007,6 +1062,16 @@ const DEFAULT_OUTDIR = ['node_modules', '.node-gtk-types']
1007
1062
  // Doc comments are pulled from .gir XML; toggled off with --no-docs.
1008
1063
  let DOCS_ENABLED = true
1009
1064
 
1065
+ const INSTALL_URL = 'https://github.com/romgrk/node-gtk#installing'
1066
+
1067
+ // A missing typelib means the native GI libraries (gtk4, libadwaita, …) aren't
1068
+ // installed on this machine. g_irepository_require surfaces that as a "Typelib
1069
+ // file for namespace … not found" error — recognize it so we can replace the
1070
+ // opaque stack trace with actionable install guidance.
1071
+ function isMissingTypelibError(err) {
1072
+ return /Typelib file for namespace/i.test((err && err.message) || '')
1073
+ }
1074
+
1010
1075
  const USAGE = `Usage: node-gtk generate-types <Namespace-Version> [...] [options]
1011
1076
 
1012
1077
  Generates TypeScript declarations for the given GObject-Introspection
@@ -1036,7 +1101,19 @@ function run(argv) {
1036
1101
  roots.push(argv[i])
1037
1102
  }
1038
1103
  if (roots.length === 0) { console.error(USAGE); process.exit(1) }
1039
- generate(roots, outdir)
1104
+ try {
1105
+ generate(roots, outdir)
1106
+ } catch (err) {
1107
+ if (isMissingTypelibError(err)) {
1108
+ console.error(
1109
+ `\nnode-gtk generate-types: ${err.message}\n\n` +
1110
+ `The native GTK / GObject-Introspection libraries (e.g. gtk4, libadwaita)\n` +
1111
+ `don't seem to be installed. Install them for your platform, then retry:\n` +
1112
+ ` ${INSTALL_URL}\n`)
1113
+ process.exit(1)
1114
+ }
1115
+ throw err
1116
+ }
1040
1117
  }
1041
1118
 
1042
1119
  module.exports = { generate, run }
@@ -0,0 +1,125 @@
1
+ /*
2
+ * list-libraries.js — list the GObject-Introspection libraries (typelibs)
3
+ * available on this machine, grouped by namespace with their versions.
4
+ *
5
+ * Driven by the CLI: `node-gtk list [filter] [options]`. The names/versions it
6
+ * prints are exactly what you pass to `require()` / the `gi:` import scheme /
7
+ * `generate-types` (e.g. `Gtk-4.0`).
8
+ */
9
+
10
+ const gi = require('../lib/index.js')
11
+ const GI = gi._GIRepository
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // data
15
+ // ---------------------------------------------------------------------------
16
+
17
+ // Compare dotted versions numerically, ascending ("3.0" < "4.0" < "10.0"),
18
+ // falling back to a string compare for non-numeric segments.
19
+ function compareVersions(a, b) {
20
+ const pa = a.split('.'), pb = b.split('.')
21
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
22
+ const x = pa[i] ?? '', y = pb[i] ?? ''
23
+ const nx = Number(x), ny = Number(y)
24
+ if (Number.isNaN(nx) || Number.isNaN(ny)) { if (x !== y) return x < y ? -1 : 1 }
25
+ else if (nx !== ny) return nx - ny
26
+ }
27
+ return 0
28
+ }
29
+
30
+ // Group the flat [{name, version}] from listAvailableModules() into a sorted
31
+ // list of { name, versions } (names A→Z, versions ascending, deduped).
32
+ async function collect(filter) {
33
+ const modules = await gi.listAvailableModules()
34
+ const byName = new Map()
35
+ for (const { name, version } of modules) {
36
+ if (filter && !name.toLowerCase().includes(filter.toLowerCase())) continue
37
+ if (!byName.has(name)) byName.set(name, new Set())
38
+ if (version) byName.get(name).add(version)
39
+ }
40
+ return [...byName.entries()]
41
+ .map(([name, versions]) => ({ name, versions: [...versions].sort(compareVersions) }))
42
+ .sort((a, b) => a.name.localeCompare(b.name))
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // output
47
+ // ---------------------------------------------------------------------------
48
+
49
+ const useColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR
50
+ const ansi = (open, close) => (s) => useColor ? `\x1b[${open}m${s}\x1b[${close}m` : s
51
+ const bold = ansi(1, 22)
52
+ const dim = ansi(2, 22)
53
+ const cyan = ansi(36, 39)
54
+
55
+ function printTable(libs) {
56
+ if (libs.length === 0) {
57
+ process.stdout.write(`${dim('No libraries found.')}\n`)
58
+ return
59
+ }
60
+ const width = libs.reduce((w, l) => Math.max(w, l.name.length), 0)
61
+ for (const { name, versions } of libs)
62
+ process.stdout.write(` ${bold(name.padEnd(width))} ${dim(versions.join(', '))}\n`)
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // cli — `node-gtk list [filter] [options]`
67
+ // ---------------------------------------------------------------------------
68
+
69
+ const HELP = `node-gtk list — list the GObject-Introspection libraries available on this machine
70
+
71
+ Usage:
72
+ node-gtk list [filter] [options]
73
+
74
+ Arguments:
75
+ filter Only show libraries whose name contains this (case-insensitive)
76
+
77
+ Options:
78
+ --json Output as JSON: [{ "name", "versions": [...] }]
79
+ -h, --help Show this help
80
+
81
+ Examples:
82
+ node-gtk list
83
+ node-gtk list gtk
84
+ node-gtk list --json
85
+ `
86
+
87
+ function run(argv) {
88
+ // Don't crash when the output is piped into a reader that closes early
89
+ // (e.g. `node-gtk list | head`).
90
+ process.stdout.on('error', (err) => { if (err.code === 'EPIPE') process.exit(0); throw err })
91
+
92
+ const opts = { json: false }
93
+ for (const arg of argv) {
94
+ if (arg === '-h' || arg === '--help') { process.stdout.write(HELP); return }
95
+ else if (arg === '--json') opts.json = true
96
+ else if (arg.startsWith('-')) { process.stderr.write(`node-gtk list: unknown option '${arg}'\n\n${HELP}`); process.exit(1) }
97
+ else if (opts.filter === undefined) opts.filter = arg
98
+ else { process.stderr.write(`node-gtk list: unexpected argument '${arg}'\n\n${HELP}`); process.exit(1) }
99
+ }
100
+
101
+ collect(opts.filter).then((libs) => {
102
+ if (opts.json) {
103
+ process.stdout.write(JSON.stringify(libs, null, 2) + '\n')
104
+ return
105
+ }
106
+
107
+ const what = opts.filter ? `matching ${JSON.stringify(opts.filter)}` : 'available'
108
+ process.stdout.write(`\n${bold(`${libs.length}`)} ${libs.length === 1 ? 'library' : 'libraries'} ${what}:\n\n`)
109
+ printTable(libs)
110
+
111
+ if (libs.length) {
112
+ const first = libs.find(l => l.versions.length) || libs[0]
113
+ const example = first.versions.length ? `${first.name}-${first.versions[first.versions.length - 1]}` : first.name
114
+ process.stdout.write(`\n${dim('Use one with, e.g.:')} ${cyan(`node-gtk generate-types ${example}`)}\n\n`)
115
+ } else {
116
+ const paths = (GI.Repository_get_search_path() || [])
117
+ process.stdout.write(`\n${dim('Searched: ' + paths.join(', '))}\n`)
118
+ }
119
+ }).catch((err) => {
120
+ process.stderr.write(`node-gtk list: ${err.message}\n`)
121
+ process.exit(1)
122
+ })
123
+ }
124
+
125
+ module.exports = { run, collect, compareVersions }
@@ -0,0 +1,97 @@
1
+ # __APP_NAME__
2
+
3
+ An [Adwaita](https://gnome.pages.gitlab.gnome.org/libadwaita/) application built
4
+ with [node-gtk](https://github.com/romgrk/node-gtk) and TypeScript.
5
+
6
+ ## Prerequisites
7
+
8
+ You need GTK 4 and libadwaita — plus their GObject-Introspection data — installed
9
+ on your system. node-gtk talks to the libraries you actually have installed.
10
+
11
+ - **Fedora:** `sudo dnf install gtk4-devel libadwaita-devel gobject-introspection-devel`
12
+ - **Debian/Ubuntu:** `sudo apt install libgtk-4-dev libadwaita-1-dev gobject-introspection`
13
+ - **Arch:** `sudo pacman -S gtk4 libadwaita gobject-introspection`
14
+ - **macOS (Homebrew):** `brew install gtk4 libadwaita gobject-introspection`
15
+
16
+ The `-dev`/`-devel` packages also ship the `.gir` files used to embed GNOME's API
17
+ documentation into the generated TypeScript types (shown on hover in your editor).
18
+
19
+ You also need **Node.js ≥ 20.6** (for the `gi:` import hooks).
20
+
21
+ ## Getting started
22
+
23
+ ```sh
24
+ npm install # installs deps and generates TypeScript types (postinstall)
25
+ npm run dev # run the app with live reload
26
+ ```
27
+
28
+ That's it — a window should appear. Edit `style.css` while it runs and the
29
+ window restyles instantly, no restart. To also restart the app when you edit
30
+ `src/`, run `npm run dev:app-reload` instead (it adds `node --watch`).
31
+
32
+ ## How it works
33
+
34
+ Namespaces are imported with the `gi:` scheme, and their default export is the
35
+ namespace object:
36
+
37
+ ```ts
38
+ import Gtk from 'gi:Gtk-4.0'
39
+ import Adw from 'gi:Adw-1'
40
+ ```
41
+
42
+ For those imports to resolve, node-gtk's loader hooks must be installed, which is
43
+ why the app is run with `node --import node-gtk/register`. The `dev`/`start`
44
+ scripts already include this (along with `tsx`, which runs the TypeScript without
45
+ a separate build step). node-gtk integrates the GTK main loop with Node's event
46
+ loop automatically.
47
+
48
+ ## Scripts
49
+
50
+ | command | what it does |
51
+ | ------------------------ | ----------------------------------------------------------------- |
52
+ | `npm run dev` | Run with live CSS reload — edit `style.css`, the window restyles, no restart. |
53
+ | `npm run dev:app-reload` | Like `dev`, but also restarts the app when you edit `src/` (`node --watch`). |
54
+ | `npm start` | Run once without building. |
55
+ | `npm run build` | Type-check and compile `src/` to `dist/` with `tsc`. |
56
+ | `npm run typecheck` | Type-check only, no output. |
57
+ | `npm run generate-types` | (Re)generate TypeScript types for the GI namespaces you use. |
58
+
59
+ ## TypeScript types
60
+
61
+ Types are generated **on your machine** from the GObject-Introspection typelibs
62
+ you have installed, so they match your actual library versions and node-gtk's
63
+ runtime shape (camelCase methods, typed signals, nullability, …). They live in
64
+ `node_modules/.node-gtk-types/` (git-ignored) and `tsconfig.json` points at them.
65
+
66
+ The `postinstall` script regenerates them automatically. If you start using
67
+ another library, add it to the `generate-types` script in `package.json` and
68
+ re-run it:
69
+
70
+ ```jsonc
71
+ // package.json
72
+ "generate-types": "node-gtk generate-types Gtk-4.0 Adw-1 GtkSource-5"
73
+ ```
74
+
75
+ ```sh
76
+ npm run generate-types
77
+ ```
78
+
79
+ ## Project structure
80
+
81
+ ```
82
+ .
83
+ ├── src/
84
+ │ ├── main.ts # the application entry point
85
+ │ └── welcome.ts # a component with its own inline, hot-reloadable styles
86
+ ├── style.css # custom CSS (hot-reloads live under `npm run dev`)
87
+ ├── tsconfig.json
88
+ └── package.json
89
+ ```
90
+
91
+ ## Learn more
92
+
93
+ - [node-gtk](https://github.com/romgrk/node-gtk) — the bindings
94
+ - [Importing libraries](https://github.com/romgrk/node-gtk/blob/master/doc/importing.md) — the `gi:` scheme and ESM details
95
+ - [GTK 4 API reference](https://docs.gtk.org/gtk4/)
96
+ - [libadwaita API reference](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/)
97
+ - [Adwaita style classes](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html)
@@ -0,0 +1,10 @@
1
+ # dependencies (also holds the generated .node-gtk-types/ TypeScript types,
2
+ # which are regenerated on install — never commit them)
3
+ node_modules/
4
+
5
+ # build output
6
+ dist/
7
+
8
+ # misc
9
+ *.log
10
+ .DS_Store
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "__PKG_NAME__",
3
+ "version": "0.1.0",
4
+ "description": "An Adwaita application built with node-gtk.",
5
+ "type": "module",
6
+ "private": true,
7
+ "scripts": {
8
+ "dev": "npm run dev:css-reload",
9
+ "dev:css-reload": "cross-env NODE_ENV=development node --import tsx --import node-gtk/register src/main.ts",
10
+ "dev:app-reload": "cross-env NODE_ENV=development node --import tsx --import node-gtk/register --watch src/main.ts",
11
+ "start": "node --import tsx --import node-gtk/register src/main.ts",
12
+ "build": "tsc",
13
+ "typecheck": "tsc --noEmit",
14
+ "generate-types": "node-gtk generate-types Gtk-4.0 Adw-1",
15
+ "postinstall": "npm run generate-types"
16
+ },
17
+ "dependencies": {
18
+ "node-gtk": "__NODE_GTK_VERSION__"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.0.0",
22
+ "cross-env": "^7.0.3",
23
+ "tsx": "^4.19.0",
24
+ "typescript": "^5.5.0"
25
+ }
26
+ }
@@ -0,0 +1,110 @@
1
+ /*
2
+ * __APP_NAME__ — an Adwaita application built with node-gtk.
3
+ *
4
+ * Namespaces are imported with the `gi:` scheme (`import Gtk from 'gi:Gtk-4.0'`);
5
+ * the app is run with `node --import node-gtk/register …` (see the package.json
6
+ * scripts). node-gtk integrates the GTK main loop with Node's event loop
7
+ * automatically — there's nothing to call to enable it.
8
+ *
9
+ * One ESM caveat: the blocking run call (`loop.run()` / `app.run()`) RETURNS
10
+ * IMMEDIATELY instead of blocking. So `app.run()` is the last statement and we
11
+ * tear everything down from the window's close handler / quit action.
12
+ * See: https://github.com/romgrk/node-gtk/blob/master/doc/importing.md
13
+ */
14
+ import GLib from 'gi:GLib-2.0'
15
+ import Gio from 'gi:Gio-2.0'
16
+ import Gtk from 'gi:Gtk-4.0'
17
+ import Adw from 'gi:Adw-1'
18
+
19
+ import { styles } from 'node-gtk/styles'
20
+
21
+ // A component in its own module (note the `.js` specifier — TypeScript's NodeNext
22
+ // resolution wants the compiled extension even though the file is welcome.ts).
23
+ import { createWelcome } from './welcome.js'
24
+
25
+ const APP_ID = '__APP_ID__'
26
+
27
+ const loop = GLib.MainLoop.new(null, false)
28
+ const app = new Adw.Application({ applicationId: APP_ID, flags: Gio.ApplicationFlags.FLAGS_NONE })
29
+
30
+ app.on('activate', () => {
31
+ // Apply style.css, layered on top of Adwaita. Under `npm run dev` the file is
32
+ // re-read live as you edit it — no restart, no flash. style.css sits at the
33
+ // project root, one level up from both src/ (dev, via tsx) and dist/ (built,
34
+ // via node), so this URL resolves in either case.
35
+ styles.addFile(new URL('../style.css', import.meta.url))
36
+
37
+ const window = new Adw.ApplicationWindow({ application: app })
38
+ window.setTitle('__APP_NAME__')
39
+ window.setDefaultSize(640, 520)
40
+
41
+ // ---- header bar with a primary menu ----------------------------------
42
+ const header = new Adw.HeaderBar()
43
+
44
+ const menu = new Gio.Menu()
45
+ menu.append('About __APP_NAME__', 'app.about')
46
+ menu.append('Quit', 'app.quit')
47
+
48
+ const menuButton = new Gtk.MenuButton({
49
+ iconName: 'open-menu-symbolic',
50
+ menuModel: menu,
51
+ primary: true,
52
+ })
53
+ header.packEnd(menuButton)
54
+
55
+ // ---- content: a welcome screen ---------------------------------------
56
+ // Adw.ToastOverlay lets us pop transient "toast" notifications.
57
+ const toasts = new Adw.ToastOverlay({ vexpand: true })
58
+
59
+ // The welcome screen lives in its own module (src/welcome.ts) so its inline
60
+ // styles.add() CSS hot-reloads on its own — see that file.
61
+ toasts.setChild(createWelcome(() => {
62
+ toasts.addToast(new Adw.Toast({ title: 'Hello from __APP_NAME__ 👋' }))
63
+ }))
64
+
65
+ // Adw.ApplicationWindow has no built-in title bar, so we stack our own
66
+ // header above the content inside a vertical box.
67
+ const content = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL })
68
+ content.append(header)
69
+ content.append(toasts)
70
+ window.setContent(content)
71
+
72
+ // ---- app actions (wired to the menu above) ---------------------------
73
+ const showAbout = () => {
74
+ // Adw.AboutWindow works on libadwaita 1.2+. On 1.5+ you can switch to
75
+ // Adw.AboutDialog (a Gtk.Window-free dialog) if you prefer.
76
+ const about = new Adw.AboutWindow({
77
+ transientFor: window,
78
+ applicationName: '__APP_NAME__',
79
+ applicationIcon: APP_ID,
80
+ developerName: 'Your Name',
81
+ version: '0.1.0',
82
+ comments: 'An Adwaita application built with node-gtk.',
83
+ developers: ['Your Name <you@example.com>'],
84
+ copyright: '© 2026 Your Name',
85
+ licenseType: Gtk.License.MIT_X11,
86
+ })
87
+ about.present()
88
+ }
89
+
90
+ const aboutAction = Gio.SimpleAction.new('about', null)
91
+ aboutAction.on('activate', () => showAbout())
92
+ app.addAction(aboutAction)
93
+
94
+ const quitAction = Gio.SimpleAction.new('quit', null)
95
+ quitAction.on('activate', () => { loop.quit(); app.quit() })
96
+ app.addAction(quitAction)
97
+ app.setAccelsForAction('app.quit', ['<Control>q'])
98
+
99
+ window.on('close-request', () => (loop.quit(), app.quit(), false))
100
+
101
+ styles.install() // flush queued styles and start the dev hot-reload watcher
102
+ window.present()
103
+
104
+ // The loop integration is already running; under ESM this returns immediately.
105
+ // The app keeps running until the close handler (or quit action) stops it.
106
+ loop.run()
107
+ })
108
+
109
+ // Must be the last statement — returns immediately under ESM (see top of file).
110
+ app.run([])
@@ -0,0 +1,41 @@
1
+ /*
2
+ * welcome.ts — the welcome screen, in its own component module.
3
+ *
4
+ * The inline CSS below is registered with styles.add(). Because this module's
5
+ * top level only registers styles and defines an export — it builds no widgets
6
+ * until createWelcome() is called — node-gtk/styles can safely re-execute it on
7
+ * every edit: tweak the CSS and the running window restyles live, no restart.
8
+ * (Editing the widget code instead needs a restart — use `npm run dev:app-reload`.)
9
+ */
10
+ import Gtk from 'gi:Gtk-4.0'
11
+ import Adw from 'gi:Adw-1'
12
+
13
+ import { styles } from 'node-gtk/styles'
14
+
15
+ // Inline, hot-reloadable styles for this component. Edit a value and save to
16
+ // see it apply live under `npm run dev`.
17
+ styles.add(`
18
+ .welcome-button {
19
+ font-weight: bold;
20
+ }
21
+ `)
22
+
23
+ // Build the welcome screen. `onSayHello` runs when the button is clicked.
24
+ export function createWelcome(onSayHello: () => void) {
25
+ // Adw.StatusPage is the idiomatic "empty state" / welcome widget.
26
+ const welcome = new Adw.StatusPage({
27
+ iconName: 'face-smile-symbolic',
28
+ title: 'Welcome to __APP_NAME__',
29
+ description: 'Edit src/welcome.ts (its styles hot-reload) or src/main.ts.',
30
+ })
31
+
32
+ const button = new Gtk.Button({
33
+ label: 'Say hello',
34
+ halign: Gtk.Align.CENTER,
35
+ cssClasses: ['pill', 'suggested-action', 'welcome-button'],
36
+ })
37
+ button.on('clicked', onSayHello)
38
+ welcome.setChild(button)
39
+
40
+ return welcome
41
+ }
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Custom styles for __APP_NAME__.
3
+ *
4
+ * Loaded by src/main.ts (via node-gtk/styles), layered on top of the Adwaita
5
+ * stylesheet. Under `npm run dev` it hot-reloads — edit and save to see changes
6
+ * live, with no restart. See the GTK4 CSS reference:
7
+ * https://docs.gtk.org/gtk4/css-overview.html
8
+ * https://docs.gtk.org/gtk4/css-properties.html
9
+ *
10
+ * Adwaita ships many ready-made style classes (`.pill`, `.suggested-action`,
11
+ * `.title-1`, `.dim-label`, …) — prefer those before writing custom CSS:
12
+ * https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html
13
+ */
14
+
15
+ /* Example: nudge the welcome button. Delete or edit freely. */
16
+ button.suggested-action.pill {
17
+ padding-left: 24px;
18
+ padding-right: 24px;
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "nodenext",
6
+ "moduleResolution": "nodenext",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src",
12
+ "types": ["node"],
13
+ "paths": {
14
+ "node-gtk": ["./node_modules/.node-gtk-types/node-gtk.d.ts"]
15
+ }
16
+ },
17
+ "include": ["src/**/*.ts"],
18
+ "files": ["node_modules/.node-gtk-types/node-gtk.d.ts"]
19
+ }
File without changes