@devscholar/node-with-gjs 0.0.0 → 0.0.1
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 +1 -81
- package/package.json +9 -3
- package/scripts/host.js +3 -1
- package/src/index.ts +39 -1
- package/examples/adwaita/counter/counter.ts +0 -66
- package/examples/console/await-delay/await-delay.ts +0 -11
- package/examples/console/console-input/console-input.ts +0 -27
- package/examples/gtk/counter/counter.ts +0 -62
- package/examples/gtk/drag-box/drag-box.ts +0 -77
- package/examples/gtk-webkit/counter/counter.html +0 -47
- package/examples/gtk-webkit/counter/counter.ts +0 -101
- package/gi-loader.ts +0 -13
- package/hook.js +0 -42
- package/start.js +0 -105
package/README.md
CHANGED
|
@@ -31,87 +31,7 @@ pacman -S gtk4 webkitgtk-6.0 gjs
|
|
|
31
31
|
|
|
32
32
|
# Usage
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
| Runtime | `gi://` URL Syntax | Loader Hooks |
|
|
37
|
-
|---------|-------------------|--------------|
|
|
38
|
-
| Node.js | ✅ Supported | ✅ `--experimental-loader` |
|
|
39
|
-
| Bun | ❌ Not supported | ❌ No hooks mechanism |
|
|
40
|
-
| Deno | ❌ Not supported | ❌ No hooks mechanism |
|
|
41
|
-
|
|
42
|
-
## Node.js
|
|
43
|
-
|
|
44
|
-
Node.js supports the `gi://` URL syntax, which is consistent with GJS:
|
|
45
|
-
|
|
46
|
-
```typescript
|
|
47
|
-
import Gtk from 'gi://Gtk?version=4.0';
|
|
48
|
-
import WebKit from 'gi://WebKit?version=6.0';
|
|
49
|
-
|
|
50
|
-
const app = new Gtk.Application({ application_id: 'org.example.app' });
|
|
51
|
-
// ...
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Bun / Deno
|
|
55
|
-
|
|
56
|
-
Bun and Deno do not support Node.js loader hooks, so you need to use the `loadGi` function instead:
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
import { loadGi } from './gi-loader.ts';
|
|
60
|
-
|
|
61
|
-
const Gtk = loadGi('Gtk', '4.0');
|
|
62
|
-
const WebKit = loadGi('WebKit', '6.0');
|
|
63
|
-
|
|
64
|
-
const app = new Gtk.Application({ application_id: 'org.example.app' });
|
|
65
|
-
// ...
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**Note:** The `loadGi` function also works with Node.js, useful for older Node.js versions or when not using experimental loader flags.
|
|
69
|
-
|
|
70
|
-
# Examples
|
|
71
|
-
|
|
72
|
-
## Console Apps
|
|
73
|
-
|
|
74
|
-
### Console Input App
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
node start.js examples/console/console-input/console-input.ts
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Await Delay App
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
node start.js examples/console/await-delay/await-delay.ts
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## GUI Apps
|
|
87
|
-
|
|
88
|
-
### GTK4 Counter App
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
node start.js examples/gtk/counter/counter.ts
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### GTK4 Drag Box App
|
|
95
|
-
|
|
96
|
-
A drag box example that demonstrates high frequency IPC.
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
node start.js examples/gtk/drag-box/drag-box.ts
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### GTK4 WebKit Counter App
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
node start.js examples/gtk-webkit/counter/counter.ts
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Adwaita Counter App (libadwaita)
|
|
109
|
-
|
|
110
|
-
A counter example based on [libadwaita](https://gnome.pages.gitlab.gnome.org/libadwaita/), demonstrating how to use Adwaita-specific components like `Adw.ApplicationWindow` and `Adw.Clamp`.
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
node start.js examples/adwaita/counter/counter.ts
|
|
114
|
-
```
|
|
34
|
+
For more examples and details, see the [node-with-gjs-examples README](https://github.com/devscholar/node-with-gjs-examples).
|
|
115
35
|
|
|
116
36
|
# License
|
|
117
37
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devscholar/node-with-gjs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"description": "Node.js IPC Bridge for GJS",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
|
|
12
|
+
},
|
|
7
13
|
"scripts": {
|
|
8
14
|
|
|
9
15
|
},
|
|
@@ -11,4 +17,4 @@
|
|
|
11
17
|
"@types/node": "^20.0.0",
|
|
12
18
|
"typescript": "^5.0.0"
|
|
13
19
|
}
|
|
14
|
-
}
|
|
20
|
+
}
|
package/scripts/host.js
CHANGED
|
@@ -83,7 +83,9 @@ function ResolveArg(arg) {
|
|
|
83
83
|
|
|
84
84
|
function executeCommand(cmd) {
|
|
85
85
|
if (cmd.action === 'LoadNamespace') {
|
|
86
|
-
|
|
86
|
+
if (cmd.version) {
|
|
87
|
+
imports.gi.versions[cmd.namespace] = cmd.version;
|
|
88
|
+
}
|
|
87
89
|
const ns = imports.gi[cmd.namespace];
|
|
88
90
|
return ConvertToProtocol(ns);
|
|
89
91
|
}
|
package/src/index.ts
CHANGED
|
@@ -173,8 +173,46 @@ export function init() {
|
|
|
173
173
|
initialize();
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
// Internal function - not exposed to users
|
|
177
|
+
function loadGiNamespace(namespace: string, version: string | undefined) {
|
|
177
178
|
initialize();
|
|
178
179
|
const res = ipc!.send({ action: 'LoadNamespace', namespace, version });
|
|
179
180
|
return createProxy(res);
|
|
180
181
|
}
|
|
182
|
+
|
|
183
|
+
// Namespace cache to avoid creating multiple proxies for the same namespace
|
|
184
|
+
const namespaceCache = new Map<string, any>();
|
|
185
|
+
|
|
186
|
+
// GI namespace versions
|
|
187
|
+
const giVersions: Record<string, string> = {};
|
|
188
|
+
|
|
189
|
+
// Create the gi proxy with lazy loading and caching
|
|
190
|
+
const giProxy = new Proxy({} as any, {
|
|
191
|
+
get(_, namespace: string) {
|
|
192
|
+
if (namespace === 'versions') {
|
|
193
|
+
return new Proxy(giVersions, {
|
|
194
|
+
set(target, prop, value) {
|
|
195
|
+
target[prop as string] = value;
|
|
196
|
+
// Clear cache for this namespace when version changes
|
|
197
|
+
const cacheKey = `${prop as string}@default`;
|
|
198
|
+
namespaceCache.delete(cacheKey);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const version = giVersions[namespace];
|
|
205
|
+
const cacheKey = `${namespace}@${version || 'default'}`;
|
|
206
|
+
|
|
207
|
+
if (!namespaceCache.has(cacheKey)) {
|
|
208
|
+
namespaceCache.set(cacheKey, loadGiNamespace(namespace, version));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return namespaceCache.get(cacheKey);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// The main exports object - compatible with GJS imports
|
|
216
|
+
export const imports = {
|
|
217
|
+
gi: giProxy
|
|
218
|
+
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// Run: node start.js examples/adwaita/counter/counter.ts
|
|
2
|
-
import { loadGi } from '../../../gi-loader.ts';
|
|
3
|
-
|
|
4
|
-
const Gtk = loadGi('Gtk', '4.0');
|
|
5
|
-
const Adw = loadGi('Adw', '1');
|
|
6
|
-
|
|
7
|
-
let clickCount = 0;
|
|
8
|
-
|
|
9
|
-
console.log("--- Adwaita Counter ---");
|
|
10
|
-
|
|
11
|
-
const app = new Adw.Application({ application_id: 'org.adwaita.counter' });
|
|
12
|
-
|
|
13
|
-
app.connect('activate', () => {
|
|
14
|
-
const window = new Adw.ApplicationWindow({
|
|
15
|
-
application: app,
|
|
16
|
-
title: 'Adwaita Counter App',
|
|
17
|
-
default_width: 400,
|
|
18
|
-
default_height: 300
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const toolbarView = new Adw.ToolbarView();
|
|
22
|
-
|
|
23
|
-
const headerBar = new Adw.HeaderBar({
|
|
24
|
-
title_widget: new Gtk.Label({ label: 'Adwaita Counter App' })
|
|
25
|
-
});
|
|
26
|
-
toolbarView.add_top_bar(headerBar);
|
|
27
|
-
|
|
28
|
-
const box = new Gtk.Box({
|
|
29
|
-
orientation: Gtk.Orientation.VERTICAL,
|
|
30
|
-
spacing: 10,
|
|
31
|
-
halign: Gtk.Align.CENTER,
|
|
32
|
-
valign: Gtk.Align.CENTER
|
|
33
|
-
});
|
|
34
|
-
box.set_margin_start(20);
|
|
35
|
-
box.set_margin_end(20);
|
|
36
|
-
box.set_margin_top(20);
|
|
37
|
-
box.set_margin_bottom(20);
|
|
38
|
-
|
|
39
|
-
const label = new Gtk.Label({
|
|
40
|
-
label: 'Clicks: 0',
|
|
41
|
-
css_classes: ['title-1']
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const button = new Gtk.Button({
|
|
45
|
-
label: 'Click to Add',
|
|
46
|
-
css_classes: ['suggested-action']
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
button.connect('clicked', () => {
|
|
50
|
-
clickCount++;
|
|
51
|
-
const message = `Clicked ${clickCount} times`;
|
|
52
|
-
label.set_label(message);
|
|
53
|
-
console.log(message);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
box.append(label);
|
|
57
|
-
box.append(button);
|
|
58
|
-
|
|
59
|
-
toolbarView.set_content(box);
|
|
60
|
-
window.set_content(toolbarView);
|
|
61
|
-
window.present();
|
|
62
|
-
|
|
63
|
-
console.log("Click the button to increase the counter...");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
app.run([]);
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// Run: node start.js examples/console/await-delay/await-delay.ts
|
|
2
|
-
import { loadGi } from '../../../gi-loader.ts';
|
|
3
|
-
|
|
4
|
-
loadGi('GLib', '2.0');
|
|
5
|
-
|
|
6
|
-
print('0s');
|
|
7
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
8
|
-
print("1s");
|
|
9
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
10
|
-
print("2s");
|
|
11
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// Run: node start.js examples/console/console-input/console-input.ts
|
|
2
|
-
import { loadGi } from '../../../gi-loader.ts';
|
|
3
|
-
|
|
4
|
-
const Gio = loadGi('Gio', '2.0');
|
|
5
|
-
|
|
6
|
-
let GioUnix;
|
|
7
|
-
try {
|
|
8
|
-
GioUnix = loadGi('GioUnix', '2.0');
|
|
9
|
-
} catch {
|
|
10
|
-
GioUnix = Gio;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
print("=== Greeting Program ===");
|
|
14
|
-
print("Please enter your name: ");
|
|
15
|
-
|
|
16
|
-
const stdin = new GioUnix.InputStream({ fd: 0, close_fd: false });
|
|
17
|
-
const dataInput = new Gio.DataInputStream({ base_stream: stdin });
|
|
18
|
-
|
|
19
|
-
const [name] = dataInput.read_line_utf8(null);
|
|
20
|
-
|
|
21
|
-
if (name && name.trim() !== "") {
|
|
22
|
-
print(`Hello, ${name}! Welcome to this program!`);
|
|
23
|
-
} else {
|
|
24
|
-
print("Hello, friend! Welcome to this program!");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
print("Program ended.");
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// Run: node start.js examples/gtk/counter/counter.ts
|
|
2
|
-
import { loadGi } from '../../../gi-loader.ts';
|
|
3
|
-
|
|
4
|
-
const Gtk = loadGi('Gtk', '4.0');
|
|
5
|
-
|
|
6
|
-
let clickCount = 0;
|
|
7
|
-
|
|
8
|
-
console.log("--- GTK4 Counter ---");
|
|
9
|
-
|
|
10
|
-
const app = new Gtk.Application({ application_id: 'org.gtk.counter' });
|
|
11
|
-
|
|
12
|
-
app.connect('activate', () => {
|
|
13
|
-
const window = new Gtk.ApplicationWindow({
|
|
14
|
-
application: app,
|
|
15
|
-
title: 'GTK Counter App',
|
|
16
|
-
default_width: 400,
|
|
17
|
-
default_height: 300
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
window.connect('close-request', () => {
|
|
21
|
-
app.quit();
|
|
22
|
-
return false;
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const box = new Gtk.Box({
|
|
26
|
-
orientation: Gtk.Orientation.VERTICAL,
|
|
27
|
-
spacing: 10,
|
|
28
|
-
halign: Gtk.Align.CENTER,
|
|
29
|
-
valign: Gtk.Align.CENTER
|
|
30
|
-
});
|
|
31
|
-
box.set_margin_start(20);
|
|
32
|
-
box.set_margin_end(20);
|
|
33
|
-
box.set_margin_top(20);
|
|
34
|
-
box.set_margin_bottom(20);
|
|
35
|
-
|
|
36
|
-
const label = new Gtk.Label({
|
|
37
|
-
label: 'Clicks: 0',
|
|
38
|
-
css_classes: ['title-1']
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const button = new Gtk.Button({
|
|
42
|
-
label: 'Click to Add',
|
|
43
|
-
css_classes: ['suggested-action']
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
button.connect('clicked', () => {
|
|
47
|
-
clickCount++;
|
|
48
|
-
const message = `Clicked ${clickCount} times`;
|
|
49
|
-
label.set_label(message);
|
|
50
|
-
console.log(message);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
box.append(label);
|
|
54
|
-
box.append(button);
|
|
55
|
-
|
|
56
|
-
window.set_child(box);
|
|
57
|
-
window.present();
|
|
58
|
-
|
|
59
|
-
console.log("Click the button to increase the counter...");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
app.run([]);
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// Run: node start.js examples/gtk/drag-box/drag-box.ts
|
|
2
|
-
import { loadGi } from '../../../gi-loader.ts';
|
|
3
|
-
|
|
4
|
-
const Gtk = loadGi('Gtk', '4.0');
|
|
5
|
-
|
|
6
|
-
console.log("--- GTK4 Flicker-free Draggable Square (Cairo) ---");
|
|
7
|
-
|
|
8
|
-
const app = new Gtk.Application({ application_id: 'org.gtk.dragbox' });
|
|
9
|
-
|
|
10
|
-
app.connect('activate', () => {
|
|
11
|
-
const window = new Gtk.ApplicationWindow({
|
|
12
|
-
application: app,
|
|
13
|
-
title: 'Drag Example (High Frequency IPC) ',
|
|
14
|
-
default_width: 600,
|
|
15
|
-
default_height: 400
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const fixed = new Gtk.Fixed();
|
|
19
|
-
fixed.set_hexpand(true);
|
|
20
|
-
fixed.set_vexpand(true);
|
|
21
|
-
|
|
22
|
-
const squareSize = 80;
|
|
23
|
-
const drawingArea = new Gtk.DrawingArea();
|
|
24
|
-
drawingArea.set_size_request(squareSize, squareSize);
|
|
25
|
-
|
|
26
|
-
const drawFunction = (area: any, cr: any, width: number, height: number) => {
|
|
27
|
-
cr.setSourceRGB(1.0, 0.2, 0.2);
|
|
28
|
-
cr.rectangle(0, 0, width, height);
|
|
29
|
-
cr.fill();
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
drawingArea.set_draw_func(drawFunction);
|
|
33
|
-
|
|
34
|
-
let currentX = 260;
|
|
35
|
-
let currentY = 160;
|
|
36
|
-
fixed.put(drawingArea, currentX, currentY);
|
|
37
|
-
|
|
38
|
-
const drag = new Gtk.GestureDrag();
|
|
39
|
-
|
|
40
|
-
let isDragging = false;
|
|
41
|
-
let dragStartX = 0;
|
|
42
|
-
let dragStartY = 0;
|
|
43
|
-
|
|
44
|
-
drag.connect('drag-begin', (gesture: any, startX: number, startY: number) => {
|
|
45
|
-
if (startX >= currentX && startX <= currentX + squareSize &&
|
|
46
|
-
startY >= currentY && startY <= currentY + squareSize) {
|
|
47
|
-
isDragging = true;
|
|
48
|
-
dragStartX = currentX;
|
|
49
|
-
dragStartY = currentY;
|
|
50
|
-
console.log(`✅ Drag started at: (${startX}, ${startY})`);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
drag.connect('drag-update', (gesture: any, offsetX: number, offsetY: number) => {
|
|
55
|
-
if (!isDragging) return;
|
|
56
|
-
const newX = dragStartX + offsetX;
|
|
57
|
-
const newY = dragStartY + offsetY;
|
|
58
|
-
fixed.move(drawingArea, newX, newY);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
drag.connect('drag-end', (gesture: any, offsetX: number, offsetY: number) => {
|
|
62
|
-
if (!isDragging) return;
|
|
63
|
-
isDragging = false;
|
|
64
|
-
currentX = dragStartX + offsetX;
|
|
65
|
-
currentY = dragStartY + offsetY;
|
|
66
|
-
console.log(`🛑 Drag ended at position: (${currentX}, ${currentY})`);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
fixed.add_controller(drag);
|
|
70
|
-
|
|
71
|
-
window.set_child(fixed);
|
|
72
|
-
window.present();
|
|
73
|
-
|
|
74
|
-
console.log("Window loaded. Try dragging the red square smoothly!");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
app.run([]);
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Counter</title>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<style>
|
|
7
|
-
body { font-family: Arial, sans-serif; padding: 20px; text-align: center; }
|
|
8
|
-
h1 { color: #333; }
|
|
9
|
-
#display { font-size: 24px; margin: 20px 0; }
|
|
10
|
-
button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
|
|
11
|
-
</style>
|
|
12
|
-
<script>
|
|
13
|
-
(function() {
|
|
14
|
-
const originalConsole = window.console;
|
|
15
|
-
window.console = {
|
|
16
|
-
log: function(...args) {
|
|
17
|
-
window.webkit.messageHandlers.console.postMessage(args.join(' '));
|
|
18
|
-
},
|
|
19
|
-
error: function(...args) {
|
|
20
|
-
window.webkit.messageHandlers.console.postMessage('[ERROR] ' + args.join(' '));
|
|
21
|
-
},
|
|
22
|
-
warn: function(...args) {
|
|
23
|
-
window.webkit.messageHandlers.console.postMessage('[WARN] ' + args.join(' '));
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
})();
|
|
27
|
-
</script>
|
|
28
|
-
</head>
|
|
29
|
-
<body>
|
|
30
|
-
<h1>Counter App (WebKit)</h1>
|
|
31
|
-
<p id="display">Clicks: 0</p>
|
|
32
|
-
<button id="btn">Click to Add</button>
|
|
33
|
-
|
|
34
|
-
<script>
|
|
35
|
-
let clickCount = 0;
|
|
36
|
-
const display = document.getElementById('display');
|
|
37
|
-
const button = document.getElementById('btn');
|
|
38
|
-
|
|
39
|
-
button.addEventListener('click', function() {
|
|
40
|
-
clickCount++;
|
|
41
|
-
const message = 'Button clicked ' + clickCount + ' times';
|
|
42
|
-
display.textContent = message;
|
|
43
|
-
console.log(message);
|
|
44
|
-
});
|
|
45
|
-
</script>
|
|
46
|
-
</body>
|
|
47
|
-
</html>
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
// Run: node start.js examples/gtk-webkit/counter/counter.ts
|
|
2
|
-
import { loadGi } from '../../../gi-loader.ts';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
|
|
6
|
-
const Gtk = loadGi('Gtk', '4.0');
|
|
7
|
-
const WebKit = loadGi('WebKit', '6.0');
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = path.dirname(__filename);
|
|
11
|
-
|
|
12
|
-
console.log('--- GTK4 WebKit Counter ---');
|
|
13
|
-
|
|
14
|
-
const app = new Gtk.Application({ application_id: 'org.gtk.webkitcounter' });
|
|
15
|
-
|
|
16
|
-
app.connect('activate', () => {
|
|
17
|
-
const window = new Gtk.ApplicationWindow({
|
|
18
|
-
application: app,
|
|
19
|
-
title: 'WebKit Counter App',
|
|
20
|
-
default_width: 500,
|
|
21
|
-
default_height: 400
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const box = new Gtk.Box({
|
|
25
|
-
orientation: Gtk.Orientation.VERTICAL,
|
|
26
|
-
spacing: 0
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const toolbar = new Gtk.Box({
|
|
30
|
-
orientation: Gtk.Orientation.HORIZONTAL,
|
|
31
|
-
spacing: 5
|
|
32
|
-
});
|
|
33
|
-
toolbar.set_margin_start(5);
|
|
34
|
-
toolbar.set_margin_end(5);
|
|
35
|
-
toolbar.set_margin_top(5);
|
|
36
|
-
toolbar.set_margin_bottom(5);
|
|
37
|
-
|
|
38
|
-
const htmlPath = path.join(__dirname, 'counter.html');
|
|
39
|
-
const htmlUri = 'file://' + htmlPath;
|
|
40
|
-
|
|
41
|
-
const backButton = new Gtk.Button({ label: '← Back' });
|
|
42
|
-
const forwardButton = new Gtk.Button({ label: 'Forward →' });
|
|
43
|
-
const urlEntry = new Gtk.Entry({
|
|
44
|
-
placeholder_text: 'Enter URL or use default',
|
|
45
|
-
text: htmlUri,
|
|
46
|
-
hexpand: true
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
toolbar.append(backButton);
|
|
50
|
-
toolbar.append(forwardButton);
|
|
51
|
-
toolbar.append(urlEntry);
|
|
52
|
-
|
|
53
|
-
const contentManager = new WebKit.UserContentManager();
|
|
54
|
-
|
|
55
|
-
contentManager.connect('script-message-received', (manager, value) => {
|
|
56
|
-
const message = value.to_string();
|
|
57
|
-
if (message) print(`[WebView] ${message}`);
|
|
58
|
-
});
|
|
59
|
-
contentManager.register_script_message_handler('console', null);
|
|
60
|
-
|
|
61
|
-
const webView = new WebKit.WebView({
|
|
62
|
-
vexpand: true,
|
|
63
|
-
hexpand: true,
|
|
64
|
-
user_content_manager: contentManager
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
webView.load_uri(htmlUri);
|
|
68
|
-
|
|
69
|
-
webView.connect('load-changed', (webview, loadEvent) => {
|
|
70
|
-
if (loadEvent === WebKit.LoadEvent.FINISHED) {
|
|
71
|
-
console.log('Page Loaded Successfully');
|
|
72
|
-
urlEntry.set_text(webview.get_uri() || '');
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
backButton.connect('clicked', () => {
|
|
77
|
-
if (webView.can_go_back()) webView.go_back();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
forwardButton.connect('clicked', () => {
|
|
81
|
-
if (webView.can_go_forward()) webView.go_forward();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
urlEntry.connect('activate', () => {
|
|
85
|
-
let uri = urlEntry.get_text();
|
|
86
|
-
if (!uri.startsWith('http://') && !uri.startsWith('https://') && !uri.startsWith('file://')) {
|
|
87
|
-
uri = 'https://' + uri;
|
|
88
|
-
}
|
|
89
|
-
webView.load_uri(uri);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
box.append(toolbar);
|
|
93
|
-
box.append(webView);
|
|
94
|
-
|
|
95
|
-
window.set_child(box);
|
|
96
|
-
window.present();
|
|
97
|
-
|
|
98
|
-
console.log("Click the button in the web view to increase the counter...");
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
app.run([]);
|
package/gi-loader.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// gi-loader.ts
|
|
2
|
-
// Universal GI namespace loader for Node.js, Bun, and Deno
|
|
3
|
-
// Usage:
|
|
4
|
-
// import { loadGi } from './gi-loader.ts';
|
|
5
|
-
// const Gtk = loadGi('Gtk', '4.0');
|
|
6
|
-
|
|
7
|
-
import { init, loadGiNamespace } from './src/index.ts';
|
|
8
|
-
|
|
9
|
-
init();
|
|
10
|
-
|
|
11
|
-
export function loadGi(namespace: string, version: string = '') {
|
|
12
|
-
return loadGiNamespace(namespace, version);
|
|
13
|
-
}
|
package/hook.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// hook.js
|
|
2
|
-
export async function resolve(specifier, context, nextResolve) {
|
|
3
|
-
if (specifier.startsWith('gi://')) {
|
|
4
|
-
return {
|
|
5
|
-
url: specifier,
|
|
6
|
-
shortCircuit: true,
|
|
7
|
-
format: 'module'
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
return nextResolve(specifier, context);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function load(url, context, nextLoad) {
|
|
14
|
-
if (url.startsWith('gi://')) {
|
|
15
|
-
// Safely parse 'gi://Gtk?version=4.0'
|
|
16
|
-
const bareUrl = url.replace('gi://', '');
|
|
17
|
-
const [namespacePart, queryPart] = bareUrl.split('?');
|
|
18
|
-
|
|
19
|
-
const namespace = namespacePart;
|
|
20
|
-
let version = '';
|
|
21
|
-
|
|
22
|
-
if (queryPart) {
|
|
23
|
-
const params = new URLSearchParams(queryPart);
|
|
24
|
-
version = params.get('version') || '';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const coreUrl = new URL('./src/index.ts', import.meta.url).href;
|
|
28
|
-
|
|
29
|
-
const source = `
|
|
30
|
-
import { init, loadGiNamespace } from '${coreUrl}';
|
|
31
|
-
init();
|
|
32
|
-
export default loadGiNamespace('${namespace}', '${version}');
|
|
33
|
-
`;
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
format: 'module',
|
|
37
|
-
shortCircuit: true,
|
|
38
|
-
source: source
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return nextLoad(url, context);
|
|
42
|
-
}
|
package/start.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// start.js
|
|
2
|
-
import { spawn } from 'child_process';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
8
|
-
const args = process.argv.slice(2);
|
|
9
|
-
|
|
10
|
-
if (args.length === 0) {
|
|
11
|
-
console.error('Usage: node start.js <script.ts> [--runtime=node|bun|deno]');
|
|
12
|
-
console.error(' bun start.js <script.ts> [--runtime=node|bun|deno]');
|
|
13
|
-
console.error(' deno run start.js <script.ts> [--runtime=node|bun|deno]');
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let runtime = null;
|
|
18
|
-
let targetScript = null;
|
|
19
|
-
|
|
20
|
-
for (let i = 0; i < args.length; i++) {
|
|
21
|
-
if (args[i] === '--runtime' && args[i + 1]) {
|
|
22
|
-
runtime = args[i + 1].toLowerCase();
|
|
23
|
-
i++;
|
|
24
|
-
} else if (!args[i].startsWith('--')) {
|
|
25
|
-
targetScript = args[i];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!targetScript) {
|
|
30
|
-
console.error('Error: No script specified');
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
targetScript = path.resolve(targetScript);
|
|
35
|
-
const hookUrl = new URL('./hook.js', import.meta.url).href;
|
|
36
|
-
|
|
37
|
-
if (!runtime) {
|
|
38
|
-
runtime = detectRuntime();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const validRuntimes = ['node', 'bun', 'deno'];
|
|
42
|
-
if (!validRuntimes.includes(runtime)) {
|
|
43
|
-
console.error(`Error: Invalid runtime "${runtime}". Must be one of: ${validRuntimes.join(', ')}`);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
console.log(`Starting ${runtime.charAt(0).toUpperCase() + runtime.slice(1)}-GJS execution context...`);
|
|
48
|
-
|
|
49
|
-
let proc;
|
|
50
|
-
|
|
51
|
-
switch (runtime) {
|
|
52
|
-
case 'bun':
|
|
53
|
-
proc = spawnBun(targetScript);
|
|
54
|
-
break;
|
|
55
|
-
case 'deno':
|
|
56
|
-
proc = spawnDeno(targetScript);
|
|
57
|
-
break;
|
|
58
|
-
case 'node':
|
|
59
|
-
default:
|
|
60
|
-
proc = spawnNode(targetScript, hookUrl);
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
proc.on('exit', (code) => {
|
|
65
|
-
process.exit(code || 0);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
function detectRuntime() {
|
|
69
|
-
if (typeof Bun !== 'undefined') return 'bun';
|
|
70
|
-
if (typeof Deno !== 'undefined') return 'deno';
|
|
71
|
-
return 'node';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function spawnNode(targetScript, hookUrl) {
|
|
75
|
-
return spawn(process.execPath, [
|
|
76
|
-
'--no-warnings',
|
|
77
|
-
'--experimental-loader', hookUrl,
|
|
78
|
-
'--experimental-transform-types',
|
|
79
|
-
targetScript
|
|
80
|
-
], {
|
|
81
|
-
stdio: 'inherit',
|
|
82
|
-
env: process.env
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function spawnBun(targetScript) {
|
|
87
|
-
return spawn('bun', [
|
|
88
|
-
'run',
|
|
89
|
-
targetScript
|
|
90
|
-
], {
|
|
91
|
-
stdio: 'inherit',
|
|
92
|
-
env: process.env
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function spawnDeno(targetScript) {
|
|
97
|
-
return spawn('deno', [
|
|
98
|
-
'run',
|
|
99
|
-
'--allow-all',
|
|
100
|
-
targetScript
|
|
101
|
-
], {
|
|
102
|
-
stdio: 'inherit',
|
|
103
|
-
env: process.env
|
|
104
|
-
});
|
|
105
|
-
}
|