gnim 1.2.8 → 1.3.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/dist/dbus.ts +868 -0
- package/dist/env.d.ts +9 -0
- package/dist/fetch.ts +411 -0
- package/dist/gnim.gresource +0 -0
- package/dist/gnome/{jsx-runtime.js → jsx-runtime.ts} +63 -51
- package/dist/gnome/signalTracker.ts +10 -0
- package/dist/gobject.ts +489 -0
- package/dist/gtk3/jsx-runtime.ts +170 -0
- package/dist/gtk3/style.ts +35 -0
- package/dist/gtk4/jsx-runtime.ts +181 -0
- package/dist/gtk4/style.ts +35 -0
- package/dist/index.ts +18 -0
- package/dist/jsx/For.ts +115 -0
- package/dist/jsx/Fragment.ts +92 -0
- package/dist/jsx/This.ts +99 -0
- package/dist/jsx/With.ts +67 -0
- package/dist/jsx/env.ts +35 -0
- package/dist/jsx/jsx.ts +256 -0
- package/dist/jsx/scope.ts +185 -0
- package/dist/jsx/state.ts +439 -0
- package/dist/resource.xml +77 -0
- package/dist/util.ts +90 -0
- package/dist/variant.ts +350 -0
- package/package.json +15 -39
- package/dist/dbus.d.ts +0 -130
- package/dist/dbus.js +0 -552
- package/dist/fetch.d.ts +0 -87
- package/dist/fetch.js +0 -303
- package/dist/gnome/jsx-runtime.d.ts +0 -5
- package/dist/gnome/signalTracker.d.ts +0 -6
- package/dist/gnome/signalTracker.js +0 -5
- package/dist/gobject.d.ts +0 -184
- package/dist/gobject.js +0 -309
- package/dist/gtk3/jsx-runtime.d.ts +0 -5
- package/dist/gtk3/jsx-runtime.js +0 -143
- package/dist/gtk3/style.d.ts +0 -4
- package/dist/gtk3/style.js +0 -25
- package/dist/gtk4/jsx-runtime.d.ts +0 -5
- package/dist/gtk4/jsx-runtime.js +0 -143
- package/dist/gtk4/style.d.ts +0 -4
- package/dist/gtk4/style.js +0 -25
- package/dist/jsx/For.d.ts +0 -25
- package/dist/jsx/For.js +0 -67
- package/dist/jsx/Fragment.d.ts +0 -28
- package/dist/jsx/Fragment.js +0 -76
- package/dist/jsx/This.d.ts +0 -28
- package/dist/jsx/This.js +0 -64
- package/dist/jsx/With.d.ts +0 -17
- package/dist/jsx/With.js +0 -35
- package/dist/jsx/env.d.ts +0 -20
- package/dist/jsx/env.js +0 -14
- package/dist/jsx/index.d.ts +0 -7
- package/dist/jsx/index.js +0 -7
- package/dist/jsx/jsx.d.ts +0 -86
- package/dist/jsx/jsx.js +0 -121
- package/dist/jsx/scope.d.ts +0 -99
- package/dist/jsx/scope.js +0 -160
- package/dist/jsx/state.d.ts +0 -149
- package/dist/jsx/state.js +0 -283
- package/dist/util.d.ts +0 -13
- package/dist/util.js +0 -68
- package/dist/variant.d.ts +0 -78
- package/dist/variant.js +0 -4
package/dist/dbus.ts
ADDED
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A {@link Service} currently only allows interfacing with a single interface of a remote object.
|
|
3
|
+
* In the future I want to come up with an API to be able to create Service objects for multiple
|
|
4
|
+
* interfaces of an object at the same time. Example usage would be for example combining
|
|
5
|
+
* "org.mpris.MediaPlayer2" and "org.mpris.MediaPlayer2.Player" into a single object.
|
|
6
|
+
*/
|
|
7
|
+
import Gio from "gi://Gio"
|
|
8
|
+
import GLib from "gi://GLib"
|
|
9
|
+
import GObject from "gi://GObject"
|
|
10
|
+
import { definePropertyGetter, kebabify, xml } from "./util.js"
|
|
11
|
+
import { type DeepInfer } from "./variant.js"
|
|
12
|
+
import {
|
|
13
|
+
register,
|
|
14
|
+
property as gproperty,
|
|
15
|
+
signal as gsignal,
|
|
16
|
+
getter as ggetter,
|
|
17
|
+
setter as gsetter,
|
|
18
|
+
} from "./gobject.js"
|
|
19
|
+
|
|
20
|
+
const DEFAULT_TIMEOUT = 10_000
|
|
21
|
+
|
|
22
|
+
export const Variant = GLib.Variant
|
|
23
|
+
export type Variant<T extends string> = GLib.Variant<T>
|
|
24
|
+
|
|
25
|
+
const info = Symbol("dbus interface info")
|
|
26
|
+
const internals = Symbol("dbus interface internals")
|
|
27
|
+
const remoteMethod = Symbol("proxy remoteMethod")
|
|
28
|
+
const remoteMethodAsync = Symbol("proxy remoteMethodAsync")
|
|
29
|
+
const remotePropertySet = Symbol("proxy remotePropertySet")
|
|
30
|
+
|
|
31
|
+
type Ctx = { private: false; static: false; name: string }
|
|
32
|
+
|
|
33
|
+
// TODO: consider making some parts public
|
|
34
|
+
// - remoteMethod, remoteMethodAsync, remotePropertySet
|
|
35
|
+
// - info, proxy, dbusObject
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Base type for DBus services and proxies. Interface name is set with
|
|
39
|
+
* the {@link iface} decorator which also register it as a GObject type.
|
|
40
|
+
*/
|
|
41
|
+
export class Service extends GObject.Object {
|
|
42
|
+
static [info]?: Gio.DBusInterfaceInfo
|
|
43
|
+
|
|
44
|
+
static {
|
|
45
|
+
GObject.registerClass(this)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[internals]: {
|
|
49
|
+
dbusObject?: Gio.DBusExportedObject
|
|
50
|
+
proxy?: Gio.DBusProxy
|
|
51
|
+
priv: Record<string | symbol, unknown>
|
|
52
|
+
onStop: Set<() => void>
|
|
53
|
+
} = {
|
|
54
|
+
priv: {},
|
|
55
|
+
onStop: new Set<() => void>(),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#info: Gio.DBusInterfaceInfo
|
|
59
|
+
|
|
60
|
+
constructor() {
|
|
61
|
+
super()
|
|
62
|
+
const service = this.constructor as unknown as typeof Service
|
|
63
|
+
if (!service[info]) throw Error("missing interface info")
|
|
64
|
+
this.#info = service[info]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
notify(propertyName: Extract<keyof this, string> | (string & {})): void {
|
|
68
|
+
const prop = this.#info.lookup_property(propertyName)
|
|
69
|
+
|
|
70
|
+
if (prop && this[internals].dbusObject) {
|
|
71
|
+
this[internals].dbusObject.emit_property_changed(
|
|
72
|
+
propertyName,
|
|
73
|
+
new GLib.Variant(prop.signature, this[propertyName as keyof this]),
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
super.notify(prop ? kebabify(propertyName) : propertyName)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
emit(name: string, ...params: unknown[]): void {
|
|
81
|
+
const signal = this.#info.lookup_signal(name)
|
|
82
|
+
|
|
83
|
+
if (signal && this[internals].dbusObject) {
|
|
84
|
+
const signature = `(${signal.args.map((a) => a.signature).join("")})`
|
|
85
|
+
this[internals].dbusObject.emit_signal(name, new GLib.Variant(signature, params))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return super.emit(signal ? kebabify(name) : name, ...params)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// server
|
|
92
|
+
#handlePropertyGet(_: Gio.DBusExportedObject, propertyName: Extract<keyof this, string>) {
|
|
93
|
+
const prop = this.#info.lookup_property(propertyName)
|
|
94
|
+
|
|
95
|
+
if (!prop) {
|
|
96
|
+
throw Error(`${this.constructor.name} has no exported property: "${propertyName}"`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const value = this[propertyName]
|
|
100
|
+
if (typeof value !== "undefined") {
|
|
101
|
+
return new GLib.Variant(prop.signature, value)
|
|
102
|
+
} else {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// server
|
|
108
|
+
#handlePropertySet(
|
|
109
|
+
_: Gio.DBusExportedObject,
|
|
110
|
+
propertyName: Extract<keyof this, string>,
|
|
111
|
+
value: GLib.Variant,
|
|
112
|
+
) {
|
|
113
|
+
const newValue = value.deepUnpack()
|
|
114
|
+
const prop = this.#info.lookup_property(propertyName)
|
|
115
|
+
|
|
116
|
+
if (!prop) {
|
|
117
|
+
throw Error(`${this.constructor.name} has no property: "${propertyName}"`)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this[propertyName] !== newValue) {
|
|
121
|
+
this[propertyName] = value.deepUnpack()
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// server
|
|
126
|
+
#returnError(error: unknown, invocation: Gio.DBusMethodInvocation) {
|
|
127
|
+
console.error(error)
|
|
128
|
+
if (error instanceof GLib.Error) {
|
|
129
|
+
return invocation.return_gerror(error)
|
|
130
|
+
}
|
|
131
|
+
if (error instanceof Error) {
|
|
132
|
+
return invocation.return_dbus_error(
|
|
133
|
+
error.name.includes(".") ? error.name : `gjs.JSError.${error.name}`,
|
|
134
|
+
error.message,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
invocation.return_dbus_error("gjs.DBusService.UnknownError", `${error}`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// server
|
|
141
|
+
#returnValue(value: unknown, methodName: string, invocation: Gio.DBusMethodInvocation) {
|
|
142
|
+
if (value === null || value === undefined) {
|
|
143
|
+
return invocation.return_value(new GLib.Variant("()", []))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const args = this.#info.lookup_method(methodName)?.out_args ?? []
|
|
147
|
+
const signature = `(${args.map((arg) => arg.signature).join("")})`
|
|
148
|
+
if (!Array.isArray(value)) throw Error("value has to be a tuple")
|
|
149
|
+
invocation.return_value(new GLib.Variant(signature, value))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// server
|
|
153
|
+
#handleMethodCall(
|
|
154
|
+
_: Gio.DBusExportedObject,
|
|
155
|
+
methodName: Extract<keyof this, string>,
|
|
156
|
+
parameters: GLib.Variant,
|
|
157
|
+
invocation: Gio.DBusMethodInvocation,
|
|
158
|
+
): void {
|
|
159
|
+
try {
|
|
160
|
+
const value = (this[methodName] as (...args: unknown[]) => unknown)(
|
|
161
|
+
...parameters.deepUnpack<Array<unknown>>(),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if (value instanceof GLib.Variant) {
|
|
165
|
+
invocation.return_value(value)
|
|
166
|
+
} else if (value instanceof Promise) {
|
|
167
|
+
value
|
|
168
|
+
.then((value) => this.#returnValue(value, methodName, invocation))
|
|
169
|
+
.catch((error) => this.#returnError(error, invocation))
|
|
170
|
+
} else {
|
|
171
|
+
this.#returnValue(value, methodName, invocation)
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
this.#returnError(error, invocation)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// server
|
|
179
|
+
async serve({
|
|
180
|
+
busType = Gio.BusType.SESSION,
|
|
181
|
+
name = this.#info.name,
|
|
182
|
+
objectPath = "/" + this.#info.name.split(".").join("/"),
|
|
183
|
+
flags = Gio.BusNameOwnerFlags.NONE,
|
|
184
|
+
timeout = DEFAULT_TIMEOUT,
|
|
185
|
+
}: {
|
|
186
|
+
busType?: Gio.BusType
|
|
187
|
+
name?: string
|
|
188
|
+
objectPath?: string
|
|
189
|
+
flags?: Gio.BusNameOwnerFlags
|
|
190
|
+
timeout?: number
|
|
191
|
+
} = {}): Promise<this> {
|
|
192
|
+
const impl = new Gio.DBusExportedObject(
|
|
193
|
+
// @ts-expect-error missing constructor type
|
|
194
|
+
{ g_interface_info: this.#info },
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
impl.connect("handle-method-call", this.#handleMethodCall.bind(this))
|
|
198
|
+
impl.connect("handle-property-get", this.#handlePropertyGet.bind(this))
|
|
199
|
+
impl.connect("handle-property-set", this.#handlePropertySet.bind(this))
|
|
200
|
+
|
|
201
|
+
this.#info.cache_build()
|
|
202
|
+
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
let source =
|
|
205
|
+
timeout > 0
|
|
206
|
+
? setTimeout(() => {
|
|
207
|
+
reject(Error(`serve timed out`))
|
|
208
|
+
source = null
|
|
209
|
+
}, timeout)
|
|
210
|
+
: null
|
|
211
|
+
|
|
212
|
+
const clear = () => {
|
|
213
|
+
if (source) {
|
|
214
|
+
clearTimeout(source)
|
|
215
|
+
source = null
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const busId = Gio.bus_own_name(
|
|
220
|
+
busType,
|
|
221
|
+
name,
|
|
222
|
+
flags,
|
|
223
|
+
(conn: Gio.DBusConnection) => {
|
|
224
|
+
try {
|
|
225
|
+
impl.export(conn, objectPath)
|
|
226
|
+
this[internals].dbusObject = impl
|
|
227
|
+
this[internals].onStop.add(() => {
|
|
228
|
+
Gio.bus_unown_name(busId)
|
|
229
|
+
impl.unexport()
|
|
230
|
+
this.#info.cache_release()
|
|
231
|
+
delete this[internals].dbusObject
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
resolve(this)
|
|
235
|
+
} catch (error) {
|
|
236
|
+
reject(error)
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
clear,
|
|
240
|
+
clear,
|
|
241
|
+
)
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// proxy
|
|
246
|
+
#handlePropertiesChanged(_: Gio.DBusProxy, changed: GLib.Variant, invalidated: string[]) {
|
|
247
|
+
const set = new Set([...Object.keys(changed.deepUnpack()), ...invalidated])
|
|
248
|
+
for (const prop of set.values()) {
|
|
249
|
+
this.notify(prop as Extract<keyof this, string>)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// proxy
|
|
254
|
+
#handleSignal(
|
|
255
|
+
_: Gio.DBusProxy,
|
|
256
|
+
_sender: string | null,
|
|
257
|
+
signal: string,
|
|
258
|
+
parameters: GLib.Variant,
|
|
259
|
+
) {
|
|
260
|
+
this.emit(kebabify(signal), ...parameters.deepUnpack<Array<unknown>>())
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// proxy
|
|
264
|
+
#remoteMethodParams(
|
|
265
|
+
methodName: string,
|
|
266
|
+
args: unknown[],
|
|
267
|
+
): Parameters<Gio.DBusProxy["call_sync"]> {
|
|
268
|
+
const { proxy } = this[internals]
|
|
269
|
+
if (!proxy) throw Error("invalid remoteMethod invocation: not a proxy")
|
|
270
|
+
|
|
271
|
+
const method = this.#info.lookup_method(methodName)
|
|
272
|
+
if (!method) throw Error("method not found")
|
|
273
|
+
|
|
274
|
+
const signature = `(${method.in_args.map((a) => a.signature).join("")})`
|
|
275
|
+
|
|
276
|
+
return [
|
|
277
|
+
methodName,
|
|
278
|
+
new GLib.Variant(signature, args),
|
|
279
|
+
Gio.DBusCallFlags.NONE,
|
|
280
|
+
DEFAULT_TIMEOUT,
|
|
281
|
+
null,
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// proxy
|
|
286
|
+
[remoteMethod](methodName: string, args: unknown[]): GLib.Variant {
|
|
287
|
+
const params = this.#remoteMethodParams(methodName, args)
|
|
288
|
+
return this[internals].proxy!.call_sync(...params)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// proxy
|
|
292
|
+
[remoteMethodAsync](methodName: string, args: unknown[]): Promise<GLib.Variant> {
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
try {
|
|
295
|
+
const params = this.#remoteMethodParams(methodName, args)
|
|
296
|
+
this[internals].proxy!.call(...params, (_, res) => {
|
|
297
|
+
try {
|
|
298
|
+
resolve(this[internals].proxy!.call_finish(res))
|
|
299
|
+
} catch (error) {
|
|
300
|
+
reject(error)
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
} catch (error) {
|
|
304
|
+
reject(error)
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// proxy
|
|
310
|
+
[remotePropertySet](name: string, value: unknown) {
|
|
311
|
+
const proxy = this[internals].proxy!
|
|
312
|
+
const prop = this.#info.lookup_property(name)!
|
|
313
|
+
|
|
314
|
+
const variant = new GLib.Variant(prop.signature, value)
|
|
315
|
+
proxy.set_cached_property(name, variant)
|
|
316
|
+
|
|
317
|
+
proxy.call(
|
|
318
|
+
"org.freedesktop.DBus.Properties.Set",
|
|
319
|
+
new GLib.Variant("(ssv)", [proxy.gInterfaceName, name, variant]),
|
|
320
|
+
Gio.DBusCallFlags.NONE,
|
|
321
|
+
-1,
|
|
322
|
+
null,
|
|
323
|
+
(_, res) => {
|
|
324
|
+
try {
|
|
325
|
+
proxy.call_finish(res)
|
|
326
|
+
} catch (e) {
|
|
327
|
+
console.error(e)
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// proxy
|
|
334
|
+
async proxy({
|
|
335
|
+
bus = Gio.DBus.session,
|
|
336
|
+
name = this.#info.name,
|
|
337
|
+
objectPath = "/" + this.#info.name.split(".").join("/"),
|
|
338
|
+
flags = Gio.DBusProxyFlags.NONE,
|
|
339
|
+
timeout = DEFAULT_TIMEOUT,
|
|
340
|
+
}: {
|
|
341
|
+
bus?: Gio.DBusConnection
|
|
342
|
+
name?: string
|
|
343
|
+
objectPath?: string
|
|
344
|
+
flags?: Gio.DBusProxyFlags
|
|
345
|
+
timeout?: number
|
|
346
|
+
} = {}): Promise<this> {
|
|
347
|
+
const proxy = new Gio.DBusProxy({
|
|
348
|
+
gConnection: bus,
|
|
349
|
+
gInterfaceName: this.#info.name,
|
|
350
|
+
gInterfaceInfo: this.#info,
|
|
351
|
+
gName: name,
|
|
352
|
+
gFlags: flags,
|
|
353
|
+
gObjectPath: objectPath,
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
return new Promise((resolve, reject) => {
|
|
357
|
+
const cancallable = new Gio.Cancellable()
|
|
358
|
+
|
|
359
|
+
let source =
|
|
360
|
+
timeout > 0
|
|
361
|
+
? setTimeout(() => {
|
|
362
|
+
reject(Error(`proxy timed out`))
|
|
363
|
+
source = null
|
|
364
|
+
cancallable.cancel()
|
|
365
|
+
}, timeout)
|
|
366
|
+
: null
|
|
367
|
+
|
|
368
|
+
proxy.init_async(GLib.PRIORITY_DEFAULT, cancallable, (_, res) => {
|
|
369
|
+
try {
|
|
370
|
+
if (source) {
|
|
371
|
+
clearTimeout(source)
|
|
372
|
+
source = null
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
proxy.init_finish(res)
|
|
376
|
+
this[internals].proxy = proxy
|
|
377
|
+
|
|
378
|
+
const ids = [
|
|
379
|
+
proxy.connect("g-signal", this.#handleSignal.bind(this)),
|
|
380
|
+
proxy.connect(
|
|
381
|
+
"g-properties-changed",
|
|
382
|
+
this.#handlePropertiesChanged.bind(this),
|
|
383
|
+
),
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
this[internals].onStop.add(() => {
|
|
387
|
+
ids.forEach((id) => proxy.disconnect(id))
|
|
388
|
+
delete this[internals].proxy
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
resolve(this)
|
|
392
|
+
} catch (error) {
|
|
393
|
+
reject(error)
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
stop() {
|
|
400
|
+
const { onStop } = this[internals]
|
|
401
|
+
for (const cb of onStop.values()) {
|
|
402
|
+
onStop.delete(cb)
|
|
403
|
+
cb()
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
type InterfaceMeta = {
|
|
409
|
+
dbusMethods?: Record<
|
|
410
|
+
string,
|
|
411
|
+
Array<{
|
|
412
|
+
name?: string
|
|
413
|
+
type: string
|
|
414
|
+
direction: "in" | "out"
|
|
415
|
+
}>
|
|
416
|
+
>
|
|
417
|
+
dbusSignals?: Record<
|
|
418
|
+
string,
|
|
419
|
+
Array<{
|
|
420
|
+
name?: string
|
|
421
|
+
type: string
|
|
422
|
+
}>
|
|
423
|
+
>
|
|
424
|
+
dbusProperties?: Record<
|
|
425
|
+
string,
|
|
426
|
+
{
|
|
427
|
+
name: string
|
|
428
|
+
type: string
|
|
429
|
+
read?: true
|
|
430
|
+
write?: true
|
|
431
|
+
}
|
|
432
|
+
>
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Registers a {@link Service} as a dbus interface.
|
|
437
|
+
*
|
|
438
|
+
* @param name Interface name of the object. For example "org.gnome.Shell.SearchProvider2"
|
|
439
|
+
* @param options optional properties to pass to {@link register}
|
|
440
|
+
*/
|
|
441
|
+
export function iface(name: string, options?: Parameters<typeof register>[0]) {
|
|
442
|
+
return function (cls: { new (...args: any[]): Service }, ctx: ClassDecoratorContext) {
|
|
443
|
+
const meta = ctx.metadata
|
|
444
|
+
if (!meta) throw Error(`${cls.name} is not an interface`)
|
|
445
|
+
|
|
446
|
+
const { dbusMethods = {}, dbusSignals = {}, dbusProperties = {} } = meta as InterfaceMeta
|
|
447
|
+
|
|
448
|
+
const infoXml = xml({
|
|
449
|
+
name: "node",
|
|
450
|
+
children: [
|
|
451
|
+
{
|
|
452
|
+
name: "interface",
|
|
453
|
+
attributes: { name },
|
|
454
|
+
children: [
|
|
455
|
+
...Object.entries(dbusMethods).map(([name, args]) => ({
|
|
456
|
+
name: "method",
|
|
457
|
+
attributes: { name },
|
|
458
|
+
children: args.map((arg) => ({ name: "arg", attributes: arg })),
|
|
459
|
+
})),
|
|
460
|
+
...Object.entries(dbusSignals).map(([name, args]) => ({
|
|
461
|
+
name: "signal",
|
|
462
|
+
attributes: { name },
|
|
463
|
+
children: args.map((arg) => ({ name: "arg", attributes: arg })),
|
|
464
|
+
})),
|
|
465
|
+
...Object.values(dbusProperties).map(({ name, type, read, write }) => ({
|
|
466
|
+
name: "property",
|
|
467
|
+
attributes: {
|
|
468
|
+
...(name && { name }),
|
|
469
|
+
type,
|
|
470
|
+
access: (read ? "read" : "") + (write ? "write" : ""),
|
|
471
|
+
},
|
|
472
|
+
})),
|
|
473
|
+
],
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
Object.assign(cls, { [info]: Gio.DBusInterfaceInfo.new_for_xml(infoXml) })
|
|
479
|
+
register(options)(cls, ctx)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
type DBusType = string | { type: string; name: string }
|
|
484
|
+
|
|
485
|
+
type InferVariantTypes<T extends Array<DBusType>> = {
|
|
486
|
+
[K in keyof T]: T[K] extends string
|
|
487
|
+
? DeepInfer<T[K]>
|
|
488
|
+
: T[K] extends { type: infer S }
|
|
489
|
+
? S extends string
|
|
490
|
+
? DeepInfer<S>
|
|
491
|
+
: never
|
|
492
|
+
: unknown
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function installMethod<Args extends Array<DBusType>>(
|
|
496
|
+
args: Args | [Args, Args?],
|
|
497
|
+
method: (...args: any[]) => unknown,
|
|
498
|
+
ctx: ClassMethodDecoratorContext<Service, typeof method>,
|
|
499
|
+
) {
|
|
500
|
+
const name = ctx.name
|
|
501
|
+
const meta = ctx.metadata! as InterfaceMeta
|
|
502
|
+
const methods = (meta.dbusMethods ??= {})
|
|
503
|
+
|
|
504
|
+
if (typeof name !== "string") {
|
|
505
|
+
throw Error("only string named methods are allowed")
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const [inArgs, outArgs = []] = (Array.isArray(args[0]) ? args : [args]) as [Args, Args]
|
|
509
|
+
|
|
510
|
+
methods[name] = [
|
|
511
|
+
...inArgs.map((arg) => ({
|
|
512
|
+
direction: "in" as const,
|
|
513
|
+
...(typeof arg === "string" ? { type: arg } : arg),
|
|
514
|
+
})),
|
|
515
|
+
...outArgs.map((arg) => ({
|
|
516
|
+
direction: "out" as const,
|
|
517
|
+
...(typeof arg === "string" ? { type: arg } : arg),
|
|
518
|
+
})),
|
|
519
|
+
]
|
|
520
|
+
|
|
521
|
+
return name
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function installProperty<T extends string>(
|
|
525
|
+
type: T,
|
|
526
|
+
ctx: ClassFieldDecoratorContext | ClassGetterDecoratorContext | ClassSetterDecoratorContext,
|
|
527
|
+
) {
|
|
528
|
+
const kind = ctx.kind
|
|
529
|
+
const name = ctx.name
|
|
530
|
+
const meta = ctx.metadata! as InterfaceMeta
|
|
531
|
+
const properties = (meta.dbusProperties ??= {})
|
|
532
|
+
|
|
533
|
+
if (typeof name !== "string") {
|
|
534
|
+
throw Error("only string named properties are allowed")
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const read = kind === "field" || kind === "getter"
|
|
538
|
+
const write = kind === "field" || kind === "setter"
|
|
539
|
+
|
|
540
|
+
if (name in properties) {
|
|
541
|
+
if (write) properties[name].write = true
|
|
542
|
+
if (read) properties[name].read = true
|
|
543
|
+
} else {
|
|
544
|
+
properties[name] = {
|
|
545
|
+
name,
|
|
546
|
+
type,
|
|
547
|
+
...(read && { read }),
|
|
548
|
+
...(write && { write }),
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return name
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function installSignal<Params extends Array<DBusType>>(
|
|
556
|
+
params: Params,
|
|
557
|
+
ctx: ClassMethodDecoratorContext<Service>,
|
|
558
|
+
) {
|
|
559
|
+
const name = ctx.name
|
|
560
|
+
const meta = ctx.metadata! as InterfaceMeta
|
|
561
|
+
const signals = (meta.dbusSignals ??= {})
|
|
562
|
+
|
|
563
|
+
if (typeof name === "symbol") {
|
|
564
|
+
throw Error("symbols are not valid signals")
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
signals[name] = params.map((arg) => (typeof arg === "string" ? { type: arg } : arg))
|
|
568
|
+
|
|
569
|
+
return name
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function inferGTypeFromVariant(type: DBusType): GObject.GType<any> {
|
|
573
|
+
if (typeof type !== "string") return inferGTypeFromVariant(type.type)
|
|
574
|
+
|
|
575
|
+
if (type.startsWith("a") || type.startsWith("(")) {
|
|
576
|
+
return GObject.TYPE_JSOBJECT
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
switch (type) {
|
|
580
|
+
case "v":
|
|
581
|
+
return GObject.TYPE_VARIANT
|
|
582
|
+
case "b":
|
|
583
|
+
return GObject.TYPE_BOOLEAN
|
|
584
|
+
case "y":
|
|
585
|
+
return GObject.TYPE_UINT
|
|
586
|
+
case "n":
|
|
587
|
+
return GObject.TYPE_INT
|
|
588
|
+
case "q":
|
|
589
|
+
return GObject.TYPE_UINT
|
|
590
|
+
case "i":
|
|
591
|
+
return GObject.TYPE_INT
|
|
592
|
+
case "u":
|
|
593
|
+
return GObject.TYPE_UINT
|
|
594
|
+
case "x":
|
|
595
|
+
return GObject.TYPE_INT64
|
|
596
|
+
case "t":
|
|
597
|
+
return GObject.TYPE_UINT64
|
|
598
|
+
case "h":
|
|
599
|
+
return GObject.TYPE_INT
|
|
600
|
+
case "d":
|
|
601
|
+
return GObject.TYPE_DOUBLE
|
|
602
|
+
case "s":
|
|
603
|
+
case "g":
|
|
604
|
+
case "o":
|
|
605
|
+
return GObject.TYPE_STRING
|
|
606
|
+
default:
|
|
607
|
+
break
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
throw Error(`cannot infer GType from variant "${type}"`)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Registers a method.
|
|
615
|
+
* You should prefer using {@link methodAsync} when proxying, due to IO blocking.
|
|
616
|
+
* Note that this is functionally the same as {@link methodAsync} on exported objects.
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
619
|
+
export function method<const InArgs extends Array<DBusType>, const OutArgs extends Array<DBusType>>(
|
|
620
|
+
inArgs: InArgs,
|
|
621
|
+
outArgs: OutArgs,
|
|
622
|
+
): (
|
|
623
|
+
method: (this: Service, ...args: InferVariantTypes<InArgs>) => InferVariantTypes<OutArgs>,
|
|
624
|
+
ctx: ClassMethodDecoratorContext<Service, typeof method>,
|
|
625
|
+
) => void
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Registers a method.
|
|
629
|
+
* You should prefer using {@link methodAsync} when proxying, due to IO blocking.
|
|
630
|
+
* Note that this is functionally the same as {@link methodAsync} on exported objects.
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
633
|
+
export function method<const InArgs extends Array<DBusType>>(
|
|
634
|
+
...inArgs: InArgs
|
|
635
|
+
): (
|
|
636
|
+
method: (this: Service, ...args: InferVariantTypes<InArgs>) => void,
|
|
637
|
+
ctx: ClassMethodDecoratorContext<Service, typeof method>,
|
|
638
|
+
) => void
|
|
639
|
+
|
|
640
|
+
export function method<const InArgs extends Array<DBusType>, const OutArgs extends Array<DBusType>>(
|
|
641
|
+
...args: InArgs | [inArgs: InArgs, outArgs?: OutArgs]
|
|
642
|
+
) {
|
|
643
|
+
return function (
|
|
644
|
+
method: (
|
|
645
|
+
this: Service,
|
|
646
|
+
...args: InferVariantTypes<InArgs>
|
|
647
|
+
) => InferVariantTypes<OutArgs> | void,
|
|
648
|
+
ctx: ClassMethodDecoratorContext<Service, typeof method>,
|
|
649
|
+
): typeof method {
|
|
650
|
+
const name = installMethod(args, method, ctx)
|
|
651
|
+
|
|
652
|
+
return function (...args: InferVariantTypes<InArgs>) {
|
|
653
|
+
if (this[internals].proxy) {
|
|
654
|
+
const value = this[remoteMethod](name, args)
|
|
655
|
+
return value.deepUnpack<InferVariantTypes<OutArgs>>()
|
|
656
|
+
} else {
|
|
657
|
+
return method.apply(this, args)
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Registers a method.
|
|
665
|
+
* You should prefer using this over {@link method} when proxying, since this does not block IO.
|
|
666
|
+
* Note that this is functionally the same as {@link method} on exported objects.
|
|
667
|
+
* ```
|
|
668
|
+
*/
|
|
669
|
+
export function methodAsync<
|
|
670
|
+
const InArgs extends Array<DBusType>,
|
|
671
|
+
const OutArgs extends Array<DBusType>,
|
|
672
|
+
>(
|
|
673
|
+
inArgs: InArgs,
|
|
674
|
+
outArgs: OutArgs,
|
|
675
|
+
): (
|
|
676
|
+
method: (
|
|
677
|
+
this: Service,
|
|
678
|
+
...args: InferVariantTypes<InArgs>
|
|
679
|
+
) => Promise<InferVariantTypes<OutArgs>>,
|
|
680
|
+
ctx: ClassMethodDecoratorContext<Service, typeof method>,
|
|
681
|
+
) => void
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Registers a method.
|
|
685
|
+
* You should prefer using this over {@link method} when proxying, since this does not block IO.
|
|
686
|
+
* Note that this is functionally the same as {@link method} on exported objects.
|
|
687
|
+
* ```
|
|
688
|
+
*/
|
|
689
|
+
export function methodAsync<const InArgs extends Array<DBusType>>(
|
|
690
|
+
...inArgs: InArgs
|
|
691
|
+
): (
|
|
692
|
+
method: (this: Service, ...args: InferVariantTypes<InArgs>) => Promise<void>,
|
|
693
|
+
ctx: ClassMethodDecoratorContext<Service, typeof method>,
|
|
694
|
+
) => void
|
|
695
|
+
|
|
696
|
+
export function methodAsync<
|
|
697
|
+
const InArgs extends Array<DBusType>,
|
|
698
|
+
const OutArgs extends Array<DBusType>,
|
|
699
|
+
>(...args: InArgs | [inArgs: InArgs, outArgs?: OutArgs]) {
|
|
700
|
+
return function (
|
|
701
|
+
method: (
|
|
702
|
+
this: Service,
|
|
703
|
+
...args: InferVariantTypes<InArgs>
|
|
704
|
+
) => Promise<InferVariantTypes<OutArgs> | void>,
|
|
705
|
+
ctx: ClassMethodDecoratorContext<Service, typeof method>,
|
|
706
|
+
): typeof method {
|
|
707
|
+
const name = installMethod(args, method, ctx)
|
|
708
|
+
|
|
709
|
+
return async function (...args: InferVariantTypes<InArgs>) {
|
|
710
|
+
if (this[internals].proxy) {
|
|
711
|
+
const value = await this[remoteMethodAsync](name, args)
|
|
712
|
+
return value.deepUnpack<InferVariantTypes<OutArgs>>()
|
|
713
|
+
} else {
|
|
714
|
+
return method.apply(this, args)
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Registers a read-write property. When a new value is assigned the notify signal
|
|
722
|
+
* is automatically emitted on the local and exported object.
|
|
723
|
+
*
|
|
724
|
+
* Note that new values are checked by reference so assigning the same object will
|
|
725
|
+
* not emit the notify signal.
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
728
|
+
export function property<T extends string>(type: T) {
|
|
729
|
+
return function (
|
|
730
|
+
_: void,
|
|
731
|
+
ctx: ClassFieldDecoratorContext<Service, DeepInfer<T>>,
|
|
732
|
+
): (this: Service, init: DeepInfer<T>) => DeepInfer<T> {
|
|
733
|
+
const name = installProperty(type, ctx)
|
|
734
|
+
|
|
735
|
+
void gproperty({ $gtype: inferGTypeFromVariant(type) })(
|
|
736
|
+
_,
|
|
737
|
+
ctx as ClassFieldDecoratorContext<GObject.Object> & Ctx,
|
|
738
|
+
{ metaOnly: true },
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
ctx.addInitializer(function () {
|
|
742
|
+
Object.defineProperty(this, name, {
|
|
743
|
+
configurable: false,
|
|
744
|
+
enumerable: true,
|
|
745
|
+
set(value: DeepInfer<T>) {
|
|
746
|
+
const { proxy, priv } = this[internals]
|
|
747
|
+
|
|
748
|
+
if (proxy) {
|
|
749
|
+
this[remotePropertySet](name, value)
|
|
750
|
+
return
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (priv[name] !== value) {
|
|
754
|
+
priv[name] = value
|
|
755
|
+
this.notify(name as Extract<keyof Service, string>)
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
get(): DeepInfer<T> {
|
|
759
|
+
const { proxy, priv } = this[internals]
|
|
760
|
+
|
|
761
|
+
return proxy
|
|
762
|
+
? proxy.get_cached_property(name)!.deepUnpack<DeepInfer<T>>()
|
|
763
|
+
: (priv[name] as DeepInfer<T>)
|
|
764
|
+
},
|
|
765
|
+
} satisfies ThisType<Service>)
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
return function (init) {
|
|
769
|
+
const priv = this[internals].priv
|
|
770
|
+
priv[name] = init
|
|
771
|
+
// we don't need to store the value on the object
|
|
772
|
+
return void 0 as unknown as DeepInfer<T>
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Registers a read-only property. Can be used in conjuction with {@link setter} to define
|
|
779
|
+
* read-write properties as accessors.
|
|
780
|
+
*
|
|
781
|
+
* Note that you will need to explicitly emit the notify signal.
|
|
782
|
+
*/
|
|
783
|
+
export function getter<T extends string>(type: T) {
|
|
784
|
+
return function (
|
|
785
|
+
getter: (this: Service) => DeepInfer<T>,
|
|
786
|
+
ctx: ClassGetterDecoratorContext<Service, DeepInfer<T>>,
|
|
787
|
+
): (this: Service) => DeepInfer<T> {
|
|
788
|
+
const name = installProperty(type, ctx)
|
|
789
|
+
|
|
790
|
+
ctx.addInitializer(function () {
|
|
791
|
+
definePropertyGetter(this, name as Extract<keyof Service, string>)
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
void ggetter({ $gtype: inferGTypeFromVariant(type) })(
|
|
795
|
+
() => {},
|
|
796
|
+
ctx as ClassGetterDecoratorContext<GObject.Object> & Ctx,
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
return function () {
|
|
800
|
+
const { proxy } = this[internals]
|
|
801
|
+
return proxy
|
|
802
|
+
? proxy.get_cached_property(name)!.deepUnpack<DeepInfer<T>>()
|
|
803
|
+
: getter.call(this)
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Registers a write-only property. Can be used in conjuction with {@link getter} to define
|
|
810
|
+
* read-write properties as accessors.
|
|
811
|
+
*
|
|
812
|
+
* Note that you will need to explicitly emit the notify signal.
|
|
813
|
+
*/
|
|
814
|
+
export function setter<T extends string>(type: T) {
|
|
815
|
+
return function (
|
|
816
|
+
setter: (this: Service, value: DeepInfer<T>) => void,
|
|
817
|
+
ctx: ClassSetterDecoratorContext<Service, DeepInfer<T>>,
|
|
818
|
+
): (this: Service, value: DeepInfer<T>) => void {
|
|
819
|
+
const name = installProperty(type, ctx)
|
|
820
|
+
|
|
821
|
+
void gsetter({ $gtype: inferGTypeFromVariant(type) })(
|
|
822
|
+
() => {},
|
|
823
|
+
ctx as ClassSetterDecoratorContext<GObject.Object> & Ctx,
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
return function (value: DeepInfer<T>) {
|
|
827
|
+
const { proxy } = this[internals]
|
|
828
|
+
|
|
829
|
+
if (proxy) {
|
|
830
|
+
this[remotePropertySet](name, value)
|
|
831
|
+
} else {
|
|
832
|
+
setter.call(this, value)
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Registers a signal which when invoked will emit the signal
|
|
840
|
+
* on the local object and the exported object.
|
|
841
|
+
*
|
|
842
|
+
* **Note**: its not possible to emit signals on remote objects through proxies.
|
|
843
|
+
*/
|
|
844
|
+
export function signal<const Params extends Array<DBusType>>(...params: Params) {
|
|
845
|
+
return function (
|
|
846
|
+
method: (this: Service, ...params: InferVariantTypes<Params>) => void,
|
|
847
|
+
ctx: ClassMethodDecoratorContext<Service, typeof method>,
|
|
848
|
+
): typeof method {
|
|
849
|
+
const name = installSignal(params, ctx)
|
|
850
|
+
|
|
851
|
+
void gsignal(...params.map(inferGTypeFromVariant))(
|
|
852
|
+
() => {},
|
|
853
|
+
ctx as ClassMethodDecoratorContext<GObject.Object> & Ctx,
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
return function (...params: InferVariantTypes<Params>) {
|
|
857
|
+
if (this[internals].proxy) {
|
|
858
|
+
console.warn(`cannot emit signal "${name}" on remote object`)
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (this[internals].dbusObject || !this[internals].proxy) {
|
|
862
|
+
method.apply(this, params)
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return this.emit(name, ...params)
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|