From 14901635303294570f227d5ad51925d1cc51e0c7 Mon Sep 17 00:00:00 2001 From: Angelo Verlain Date: Sun, 10 Dec 2023 22:15:33 +0200 Subject: [PATCH 1/6] support overrides also move gobject.object overrides to new file allowing to construct objects by extending remove girepository symbol --- src/gi.js | 3 ++ src/overrides/GObject.ts | 68 ++++++++++++++++++++++++++++++++++++ src/overrides/mod.ts | 22 ++++++++++++ src/types/object.js | 74 +++++++--------------------------------- 4 files changed, 105 insertions(+), 62 deletions(-) create mode 100644 src/overrides/GObject.ts create mode 100644 src/overrides/mod.ts diff --git a/src/gi.js b/src/gi.js index fc77569..fff740d 100644 --- a/src/gi.js +++ b/src/gi.js @@ -2,6 +2,7 @@ import { cast_u64_ptr, deref_buf, deref_str } from "./base_utils/convert.ts"; import g from "./bindings/mod.js"; import handleInfo from "./handleInfo.js"; import { ExtendedDataView } from "./utils/dataview.js"; +import { loadOverride } from "./overrides/mod.ts"; const repos = new Map(); @@ -94,6 +95,8 @@ export function require(namespace, version) { g.base_info.unref(info); } + loadOverride(namespace, repo); + repos.set(key, repo); return repo; diff --git a/src/overrides/GObject.ts b/src/overrides/GObject.ts new file mode 100644 index 0000000..4b1c637 --- /dev/null +++ b/src/overrides/GObject.ts @@ -0,0 +1,68 @@ +// deno-lint-ignore-file no-explicit-any +import { GConnectFlags } from "../bindings/enums.js"; +import g from "../bindings/mod.js"; +import { createCallback } from "../types/callback.js"; + +type Handler = (...args: unknown[]) => unknown; + +function addObjectMethods(object: any) { + object.prototype.connect = function ( + action: string, + callback: Handler, + ) { + const signalInfo = Reflect.getMetadata( + "gi:signals", + this.constructor, + action.split("::")[0], + ); + + const cb = createCallback(signalInfo, callback, this); + const handler = g.signal.connect_data( + Reflect.getOwnMetadata("gi:ref", this), + action, + cb.pointer, + null, + null, + GConnectFlags.SWAPPED, + ); + + return handler; + }; + + object.prototype.on = function ( + action: string, + callback: Handler, + ) { + return this.connect(action, callback); + }; + + object.prototype.once = function ( + action: string, + callback: Handler, + ) { + const handler = this.connect(action, (...args: unknown[]) => { + callback(...args); + this.off(handler); + }); + + return handler; + }; + + object.prototype.off = function (handler: Handler) { + g.signal.handler_disconnect( + Reflect.getOwnMetadata("gi:ref", this), + handler as any, + ); + }; + + object.prototype.emit = function (action: string) { + g.signal.emit_by_name( + Reflect.getOwnMetadata("gi:ref", this), + action, + ); + }; +} + +export function _init(GObject: any) { + addObjectMethods(GObject.Object); +} diff --git a/src/overrides/mod.ts b/src/overrides/mod.ts new file mode 100644 index 0000000..23f857f --- /dev/null +++ b/src/overrides/mod.ts @@ -0,0 +1,22 @@ +import * as GObject from "./GObject.ts"; + +export interface GIOverride { + name: string; + version: string; + // deno-lint-ignore no-explicit-any + module: any; +} + +export const overrides: GIOverride[] = [ + { name: "GObject", version: "2.0", module: GObject }, +]; + +export function loadOverride(namespace: string, repo: unknown) { + const override = overrides.find((override) => { + return override.name === namespace; + }); + + if (override) { + override.module._init(repo); + } +} diff --git a/src/types/object.js b/src/types/object.js index 81463f5..6afa40c 100644 --- a/src/types/object.js +++ b/src/types/object.js @@ -2,22 +2,18 @@ import g from "../bindings/mod.js"; import { getName } from "../utils/string.ts"; import { handleCallable, handleStructCallable } from "./callable.js"; import { objectByGType } from "../utils/gobject.js"; -import { GConnectFlags } from "../bindings/enums.js"; -import { createCallback } from "./callback.js"; import { handleSignal } from "./signal.js"; import { handleProp } from "./prop.js"; -function extendObject(target, info) { +function getParentClass(info) { const parent = g.object_info.get_parent(info); if (parent) { const gType = g.registered_type_info.get_g_type(parent); const ParentClass = objectByGType(gType); - Object.setPrototypeOf(target.prototype, ParentClass.prototype); - //Object.assign(target.__signals__, ParentClass.__signals__); - g.base_info.unref(parent); + return ParentClass; } } @@ -102,63 +98,18 @@ function defineClassStructMethods(target, info) { } export function createObject(info, gType) { - const ObjectClass = class { - constructor(props = {}) { - Reflect.defineMetadata("gi:ref", g.object.new(gType, null), this); - Object.entries(props).forEach(([key, value]) => { - this[key] = value; - }); - } - - connect(action, callback) { - const signalInfo = Reflect.getMetadata( - "gi:signals", - ObjectClass, - action.split("::")[0], - ); - - const cb = createCallback(signalInfo, callback, this); - const handler = g.signal.connect_data( - Reflect.getOwnMetadata("gi:ref", this), - action, - cb.pointer, - null, - null, - GConnectFlags.SWAPPED, - ); - - return handler; - } - - disconnect(handler) { - g.signal.handler_disconnect( - Reflect.getOwnMetadata("gi:ref", this), - handler, - ); - } + const ParentClass = getParentClass(info) ?? Object; - emit(action) { - g.signal.emit_by_name( - Reflect.getOwnMetadata("gi:ref", this), - action, - ); - } - - on(action, callback) { - return this.connect(action, callback); - } + const ObjectClass = class extends ParentClass { + constructor(props = {}, init = true) { + super(props, false); - once(action, callback) { - const handler = this.connect(action, (...args) => { - callback(...args); - this.off(handler); - }); - - return handler; - } - - off(handler) { - return this.disconnect(handler); + if (init) { + Reflect.defineMetadata("gi:ref", g.object.new(gType, null), this); + Object.entries(props).forEach(([key, value]) => { + this[key] = value; + }); + } } }; @@ -172,7 +123,6 @@ export function createObject(info, gType) { defineVFuncs(ObjectClass, info); defineSignals(ObjectClass, info); defineProps(ObjectClass, info); - extendObject(ObjectClass, info); inheritInterfaces(ObjectClass, info); defineClassStructMethods(ObjectClass, info); From 6f71318645f5c60a2fd118f4e71a49b1158a2ae6 Mon Sep 17 00:00:00 2001 From: Angelo Verlain Date: Thu, 21 Dec 2023 14:50:41 +0200 Subject: [PATCH 2/6] ensure the object constructed is of the descendant gitype --- src/types/object.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/types/object.js b/src/types/object.js index 6afa40c..ff63ba7 100644 --- a/src/types/object.js +++ b/src/types/object.js @@ -4,6 +4,7 @@ import { handleCallable, handleStructCallable } from "./callable.js"; import { objectByGType } from "../utils/gobject.js"; import { handleSignal } from "./signal.js"; import { handleProp } from "./prop.js"; +import { GType } from "../bindings/enums.js"; function getParentClass(info) { const parent = g.object_info.get_parent(info); @@ -101,10 +102,16 @@ export function createObject(info, gType) { const ParentClass = getParentClass(info) ?? Object; const ObjectClass = class extends ParentClass { - constructor(props = {}, init = true) { - super(props, false); + constructor(props = {}) { + super(props); + + if (gType == GType.OBJECT) { + const gType = Reflect.getOwnMetadata("gi:gtype", this.constructor); + + if (!gType) { + throw new Error("Tried to construct an object without a GType"); + } - if (init) { Reflect.defineMetadata("gi:ref", g.object.new(gType, null), this); Object.entries(props).forEach(([key, value]) => { this[key] = value; From d86f83920f066c1880915fb9c414da3276f1d376 Mon Sep 17 00:00:00 2001 From: Angelo Verlain Date: Wed, 17 Jan 2024 15:24:36 +0200 Subject: [PATCH 3/6] require depencies of loaded namespaces so that their overrides are loaded previously, doing `require("Adw", "1")` would fail to register `GObject.Object.connect`, as the `GObject` namespace would not be loaded. Currently, now `require` will also load the given dependencies of the loaded namespace, so that their overrides can be loaded. --- src/base_utils/convert.ts | 6 ++++++ src/bindings/girepository.js | 1 + src/gi.js | 41 ++++++++++++++++++++++++++++++++++-- src/overrides/mod.ts | 6 ++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/base_utils/convert.ts b/src/base_utils/convert.ts index 6807ce7..883e7c5 100644 --- a/src/base_utils/convert.ts +++ b/src/base_utils/convert.ts @@ -1,3 +1,4 @@ +import { ExtendedDataView } from "../utils/dataview.js"; import type { TypedArray } from "./ffipp.js"; const encoder = new TextEncoder(); @@ -46,3 +47,8 @@ export function deref_str(pointer: Deno.PointerValue, offset = 0) { offset, ); } + +export function peek_ptr(pointer: Deno.PointerObject, offset = 0) { + return cast_u64_ptr(new ExtendedDataView(deref_buf(pointer, 8, offset)) + .getBigUint64()); +} diff --git a/src/bindings/girepository.js b/src/bindings/girepository.js index 964123a..7833662 100644 --- a/src/bindings/girepository.js +++ b/src/bindings/girepository.js @@ -21,6 +21,7 @@ const { g } = openLib(libName("girepository-1.0", 1), { enumerate_versions: $pointer($pointer, $string), get_version: $string($pointer, $string), is_registered: $bool($pointer, $string, $string), + get_dependencies: $pointer($pointer, $string), }, registered_type_info: { get_g_type: $i64($pointer), diff --git a/src/gi.js b/src/gi.js index fff740d..9aa2a34 100644 --- a/src/gi.js +++ b/src/gi.js @@ -2,7 +2,8 @@ import { cast_u64_ptr, deref_buf, deref_str } from "./base_utils/convert.ts"; import g from "./bindings/mod.js"; import handleInfo from "./handleInfo.js"; import { ExtendedDataView } from "./utils/dataview.js"; -import { loadOverride } from "./overrides/mod.ts"; +import { hasOverride, loadOverride } from "./overrides/mod.ts"; +import { peek_ptr } from "./base_utils/convert.ts"; const repos = new Map(); @@ -47,11 +48,19 @@ function getLatestVersion(namespace) { * @returns */ export function require(namespace, version) { + // if no version is specified, the latest if (!version) { version = getLatestVersion(namespace); } - // if no version is specified, the latest + const repo = load(namespace, version); + + loadDependencies(namespace, version); + + return repo; +} + +function load(namespace, version) { const key = `${namespace}-${version}`; if (repos.has(key)) { @@ -101,3 +110,31 @@ export function require(namespace, version) { return repo; } + +function loadDependencies(namespace) { + const dependencies = get_cstr_array( + g.irepository.get_dependencies(null, namespace), + ); + + for (const dependency of dependencies) { + const [namespace, version] = dependency.split("-"); + + // only load dependencies that have overrides, as it saves time + // otherwise they will be loaded when required + if (hasOverride(namespace, version)) { + load(namespace, version); + } + } +} + +function get_cstr_array(pointer) { + const strings = []; + + for (let i = 0; true; i++) { + const str = deref_str(peek_ptr(pointer, i * 8)); + if (!str) break; + strings.push(str); + } + + return strings; +} diff --git a/src/overrides/mod.ts b/src/overrides/mod.ts index 23f857f..ff9c03f 100644 --- a/src/overrides/mod.ts +++ b/src/overrides/mod.ts @@ -11,6 +11,12 @@ export const overrides: GIOverride[] = [ { name: "GObject", version: "2.0", module: GObject }, ]; +export function hasOverride(namespace: string, version: string) { + return overrides.some((override) => { + return override.name === namespace && override.version === version; + }); +} + export function loadOverride(namespace: string, repo: unknown) { const override = overrides.find((override) => { return override.name === namespace; From 901dabfceb4e3368331eb4fbec9de3908dcd741f Mon Sep 17 00:00:00 2001 From: Angelo Verlain Date: Sat, 6 Jan 2024 04:43:25 +0200 Subject: [PATCH 4/6] add getBaseGType method this method gets the closest parent gtype that is registered in gobject-introspection. this is because classes registed manually (in deno_gi) can't be constructed from their GBaseInfo. In the future, we will probably need to maintain a reference to the manually registed classes so they can be constructed from reference. --- src/utils/gobject.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/utils/gobject.js b/src/utils/gobject.js index 2ec1ca0..3de92c8 100644 --- a/src/utils/gobject.js +++ b/src/utils/gobject.js @@ -1,4 +1,4 @@ -import { GIInfoType } from "../bindings/enums.js"; +import { GIInfoType, GType } from "../bindings/enums.js"; import g from "../bindings/mod.js"; import { createEnum } from "../types/enum.js"; import { createInterface } from "../types/interface.js"; @@ -7,7 +7,24 @@ import { createStruct } from "../types/struct.js"; export const cache = new Map(); -export function objectByGType(gType) { +// find the closest registed parent type. sometimes gTypes may not be visible +// in gobject-introspection for one of two reasons: 1. they were registered +// as by the user as a static type or 2. the namespace they are in is not +// loaded. +function getBaseGType(gType) { + if (!g.type.is_a(gType, GType.OBJECT)) return gType; + + let baseGType = gType; + while (!g.irepository.find_by_gtype(null, baseGType)) { + baseGType = g.type.parent(baseGType); + } + + return baseGType; +} + +export function objectByGType(_gType) { + const gType = getBaseGType(_gType); + if (cache.has(gType)) { return cache.get(gType); } From 65e1715e3a4c03b80e6a3c4aa1c56566c0d01e37 Mon Sep 17 00:00:00 2001 From: Angelo Verlain Date: Tue, 16 Jan 2024 00:07:45 +0200 Subject: [PATCH 5/6] throw an error when an object info can't be got for a gtype instead of soft crashing, this method instead bails immediately --- src/utils/gobject.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/utils/gobject.js b/src/utils/gobject.js index 3de92c8..0013557 100644 --- a/src/utils/gobject.js +++ b/src/utils/gobject.js @@ -30,6 +30,13 @@ export function objectByGType(_gType) { } const info = g.irepository.find_by_gtype(null, gType); + + if (!info) { + throw new Error( + `Could not find GObjectInfo for ${_gType}. Is it a valid registered type?`, + ); + } + const result = createGObject(info, gType); Object.freeze(result); cache.set(gType, result); From 3a81cc0467644bb161f1a4cc14e61e3740736201 Mon Sep 17 00:00:00 2001 From: Angelo Verlain Date: Sun, 21 Jan 2024 19:17:33 +0200 Subject: [PATCH 6/6] unref parent class info --- src/types/object.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/object.js b/src/types/object.js index ff63ba7..17c5b40 100644 --- a/src/types/object.js +++ b/src/types/object.js @@ -11,6 +11,7 @@ function getParentClass(info) { if (parent) { const gType = g.registered_type_info.get_g_type(parent); + g.base_info.unref(parent); const ParentClass = objectByGType(gType);