Skip to content

Commit

Permalink
Merge pull request #10 from vixalien/overrides
Browse files Browse the repository at this point in the history
Support overrides
  • Loading branch information
ahgilak authored Jan 21, 2024
2 parents 4593680 + 3a81cc0 commit 5e0aa29
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 63 deletions.
6 changes: 6 additions & 0 deletions src/base_utils/convert.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ExtendedDataView } from "../utils/dataview.js";
import type { TypedArray } from "./ffipp.js";

const encoder = new TextEncoder();
Expand Down Expand Up @@ -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());
}
1 change: 1 addition & 0 deletions src/bindings/girepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
42 changes: 41 additions & 1 deletion src/gi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +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 { hasOverride, loadOverride } from "./overrides/mod.ts";
import { peek_ptr } from "./base_utils/convert.ts";

const repos = new Map();

Expand Down Expand Up @@ -46,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)) {
Expand Down Expand Up @@ -94,7 +104,37 @@ export function require(namespace, version) {
g.base_info.unref(info);
}

loadOverride(namespace, repo);

repos.set(key, repo);

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;
}
68 changes: 68 additions & 0 deletions src/overrides/GObject.ts
Original file line number Diff line number Diff line change
@@ -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);
}
28 changes: 28 additions & 0 deletions src/overrides/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 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;
});

if (override) {
override.module._init(repo);
}
}
78 changes: 18 additions & 60 deletions src/types/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@ 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";
import { GType } from "../bindings/enums.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);
g.base_info.unref(parent);

const ParentClass = objectByGType(gType);
Object.setPrototypeOf(target.prototype, ParentClass.prototype);
//Object.assign(target.__signals__, ParentClass.__signals__);

g.base_info.unref(parent);
return ParentClass;
}
}

Expand Down Expand Up @@ -102,63 +100,24 @@ 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;
}
const ParentClass = getParentClass(info) ?? Object;

disconnect(handler) {
g.signal.handler_disconnect(
Reflect.getOwnMetadata("gi:ref", this),
handler,
);
}

emit(action) {
g.signal.emit_by_name(
Reflect.getOwnMetadata("gi:ref", this),
action,
);
}
const ObjectClass = class extends ParentClass {
constructor(props = {}) {
super(props);

on(action, callback) {
return this.connect(action, callback);
}
if (gType == GType.OBJECT) {
const gType = Reflect.getOwnMetadata("gi:gtype", this.constructor);

once(action, callback) {
const handler = this.connect(action, (...args) => {
callback(...args);
this.off(handler);
});
if (!gType) {
throw new Error("Tried to construct an object without a GType");
}

return handler;
}

off(handler) {
return this.disconnect(handler);
Reflect.defineMetadata("gi:ref", g.object.new(gType, null), this);
Object.entries(props).forEach(([key, value]) => {
this[key] = value;
});
}
}
};

Expand All @@ -172,7 +131,6 @@ export function createObject(info, gType) {
defineVFuncs(ObjectClass, info);
defineSignals(ObjectClass, info);
defineProps(ObjectClass, info);
extendObject(ObjectClass, info);
inheritInterfaces(ObjectClass, info);
defineClassStructMethods(ObjectClass, info);

Expand Down
28 changes: 26 additions & 2 deletions src/utils/gobject.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -7,12 +7,36 @@ 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);
}

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);
Expand Down

0 comments on commit 5e0aa29

Please sign in to comment.