summaryrefslogtreecommitdiff
path: root/lang/gjs/src/gobject.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lang/gjs/src/gobject.ts')
-rw-r--r--lang/gjs/src/gobject.ts180
1 files changed, 180 insertions, 0 deletions
diff --git a/lang/gjs/src/gobject.ts b/lang/gjs/src/gobject.ts
new file mode 100644
index 0000000..4740764
--- /dev/null
+++ b/lang/gjs/src/gobject.ts
@@ -0,0 +1,180 @@
+export { default as GObject, default as default } from "gi://GObject"
+export { default as Gio } from "gi://Gio"
+export { default as GLib } from "gi://GLib"
+
+import GObject from "gi://GObject"
+const meta = Symbol("meta")
+
+const { ParamSpec, ParamFlags } = GObject
+
+const kebabify = (str: string) => str
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
+ .replaceAll("_", "-")
+ .toLowerCase()
+
+type SignalDeclaration = {
+ flags?: GObject.SignalFlags
+ accumulator?: GObject.AccumulatorType
+ return_type?: GObject.GType
+ param_types?: Array<GObject.GType>
+}
+
+type PropertyDeclaration =
+ | InstanceType<typeof GObject.ParamSpec>
+ | { $gtype: GObject.GType }
+ | typeof String
+ | typeof Number
+ | typeof Boolean
+ | typeof Object
+
+type GObjectConstructor = {
+ [meta]?: {
+ Properties?: { [key: string]: GObject.ParamSpec }
+ Signals?: { [key: string]: GObject.SignalDefinition }
+ }
+ new(...args: any[]): any
+}
+
+type MetaInfo = GObject.MetaInfo<never, Array<{ $gtype: GObject.GType }>, never>
+
+export function register(options: MetaInfo = {}) {
+ return function (cls: GObjectConstructor) {
+ GObject.registerClass({
+ Signals: { ...cls[meta]?.Signals },
+ Properties: { ...cls[meta]?.Properties },
+ ...options,
+ }, cls)
+ }
+}
+
+export function property(declaration: PropertyDeclaration = Object) {
+ return function (target: any, prop: any, desc?: PropertyDescriptor) {
+ target.constructor[meta] ??= {}
+ target.constructor[meta].Properties ??= {}
+
+ const name = kebabify(prop)
+
+ if (!desc) {
+ let value = defaultValue(declaration)
+
+ Object.defineProperty(target, prop, {
+ get() {
+ return value
+ },
+ set(v) {
+ if (v !== value) {
+ value = v
+ this.notify(name)
+ }
+ },
+ })
+
+ Object.defineProperty(target, `set_${name.replace("-", "_")}`, {
+ value: function (v: any) {
+ this[prop] = v
+ },
+ })
+
+ Object.defineProperty(target, `get_${name.replace("-", "_")}`, {
+ value: function () {
+ return this[prop]
+ },
+ })
+
+ target.constructor[meta].Properties[kebabify(prop)] = pspec(name, ParamFlags.READWRITE, declaration)
+ }
+
+ else {
+ let flags = 0
+ if (desc.get) flags |= ParamFlags.READABLE
+ if (desc.set) flags |= ParamFlags.WRITABLE
+
+ target.constructor[meta].Properties[kebabify(prop)] = pspec(name, flags, declaration)
+ }
+ }
+}
+
+export function signal(...params: Array<{ $gtype: GObject.GType } | typeof Object>):
+(target: any, signal: any, desc?: PropertyDescriptor) => void
+
+export function signal(declaration?: SignalDeclaration):
+(target: any, signal: any, desc?: PropertyDescriptor) => void
+
+export function signal(
+ declaration?: SignalDeclaration | { $gtype: GObject.GType } | typeof Object,
+ ...params: Array<{ $gtype: GObject.GType } | typeof Object>
+) {
+ return function (target: any, signal: any, desc?: PropertyDescriptor) {
+ target.constructor[meta] ??= {}
+ target.constructor[meta].Signals ??= {}
+
+ const name = kebabify(signal)
+
+ if (declaration || params.length > 0) {
+ // @ts-expect-error TODO: type assert
+ const arr = [declaration, ...params].map(v => v.$gtype)
+ target.constructor[meta].Signals[name] = {
+ param_types: arr,
+ }
+ }
+ else {
+ target.constructor[meta].Signals[name] = declaration
+ }
+
+ if (!desc) {
+ Object.defineProperty(target, signal, {
+ value: function (...args: any[]) {
+ this.emit(name, ...args)
+ },
+ })
+ }
+ else {
+ const og: ((...args: any[]) => void) = desc.value
+ desc.value = function (...args: any[]) {
+ // @ts-expect-error not typed
+ this.emit(name, ...args)
+ }
+ Object.defineProperty(target, `on_${name.replace("-", "_")}`, {
+ value: function (...args: any[]) {
+ return og(...args)
+ },
+ })
+ }
+ }
+}
+
+function pspec(name: string, flags: number, declaration: PropertyDeclaration) {
+ if (declaration instanceof ParamSpec)
+ return declaration
+
+ switch (declaration) {
+ case String:
+ return ParamSpec.string(name, "", "", flags, "")
+ case Number:
+ return ParamSpec.double(name, "", "", flags, -Number.MAX_VALUE, Number.MAX_VALUE, 0)
+ case Boolean:
+ return ParamSpec.boolean(name, "", "", flags, false)
+ case Object:
+ return ParamSpec.jsobject(name, "", "", flags)
+ default:
+ // @ts-expect-error misstyped
+ return ParamSpec.object(name, "", "", flags, declaration.$gtype)
+ }
+}
+
+function defaultValue(declaration: PropertyDeclaration) {
+ if (declaration instanceof ParamSpec)
+ return declaration.get_default_value()
+
+ switch (declaration) {
+ case String:
+ return "default-string"
+ case Number:
+ return 0
+ case Boolean:
+ return false
+ case Object:
+ default:
+ return null
+ }
+}