@devscholar/node-with-gjs 0.0.0 → 0.0.2
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 +5 -75
- package/__tests__/basic.test.ts +41 -0
- package/__tests__/glib.test.ts +72 -0
- package/__tests__/gtk4.test.ts +73 -0
- package/dist/index.js +211 -0
- package/dist/ipc.js +103 -0
- package/docs/testing.md +100 -0
- package/hook.js +44 -42
- package/jest.config.js +32 -0
- package/package.json +23 -14
- package/scripts/host.js +3 -1
- package/src/index.ts +40 -2
- package/tsconfig.json +22 -11
- package/types/index.d.ts +4 -0
- package/types/ipc.d.ts +10 -0
- 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/start.js +0 -105
package/hook.js
CHANGED
|
@@ -1,42 +1,44 @@
|
|
|
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('./
|
|
28
|
-
|
|
29
|
-
const source = `
|
|
30
|
-
import { init,
|
|
31
|
-
init();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
1
|
+
// hook.js - Node.js module loader hook for gi:// protocol
|
|
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, nextResolve);
|
|
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('./dist/index.js', import.meta.url).href;
|
|
28
|
+
|
|
29
|
+
const source = `
|
|
30
|
+
import { init, imports } from '${coreUrl}';
|
|
31
|
+
init();
|
|
32
|
+
imports.gi.versions['${namespace}'] = '${version}';
|
|
33
|
+
const ns = imports.gi['${namespace}'];
|
|
34
|
+
export default ns;
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
format: 'module',
|
|
39
|
+
shortCircuit: true,
|
|
40
|
+
source: source
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return nextLoad(url, context, nextLoad);
|
|
44
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
preset: 'ts-jest/presets/default-esm',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
5
|
+
moduleNameMapper: {
|
|
6
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
7
|
+
},
|
|
8
|
+
transform: {
|
|
9
|
+
'^.+\\.ts$': [
|
|
10
|
+
'ts-jest',
|
|
11
|
+
{
|
|
12
|
+
useESM: true,
|
|
13
|
+
tsconfig: {
|
|
14
|
+
module: 'ESNext',
|
|
15
|
+
moduleResolution: 'bundler',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
testMatch: [
|
|
21
|
+
'**/__tests__/**/*.ts',
|
|
22
|
+
'**/?(*.)+(spec|test).ts',
|
|
23
|
+
],
|
|
24
|
+
collectCoverageFrom: [
|
|
25
|
+
'src/**/*.ts',
|
|
26
|
+
'!src/**/*.d.ts',
|
|
27
|
+
],
|
|
28
|
+
coverageDirectory: 'coverage',
|
|
29
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
|
30
|
+
testTimeout: 60000,
|
|
31
|
+
maxWorkers: 1,
|
|
32
|
+
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@devscholar/node-with-gjs",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Node.js IPC Bridge for GJS",
|
|
5
|
-
"main": "
|
|
6
|
-
"type": "module",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@devscholar/node-with-gjs",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Node.js IPC Bridge for GJS",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.0.0",
|
|
19
|
+
"jest": "^29.7.0",
|
|
20
|
+
"ts-jest": "^29.1.0",
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
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
|
@@ -4,7 +4,7 @@ import * as path from 'node:path';
|
|
|
4
4
|
import * as cp from 'node:child_process';
|
|
5
5
|
import * as os from 'node:os';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { IpcSync } from './ipc.
|
|
7
|
+
import { IpcSync } from './ipc.js';
|
|
8
8
|
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = path.dirname(__filename);
|
|
@@ -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
|
+
};
|
package/tsconfig.json
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
4
|
-
"module": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationDir": "./types",
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"strict": false,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"noEmit": false,
|
|
18
|
+
"allowSyntheticDefaultImports": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist"]
|
|
22
|
+
}
|
package/types/index.d.ts
ADDED
package/types/ipc.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function readLineSync(fd: number): string | null;
|
|
2
|
+
export declare class IpcSync {
|
|
3
|
+
private fdRead;
|
|
4
|
+
private fdWrite;
|
|
5
|
+
private onEvent;
|
|
6
|
+
private exited;
|
|
7
|
+
constructor(fdRead: number, fdWrite: number, onEvent: (msg: any) => any);
|
|
8
|
+
send(cmd: any): any;
|
|
9
|
+
close(): void;
|
|
10
|
+
}
|
|
@@ -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([]);
|