[gjs: 1/2] examples: add a dbus-client and dbus-service example



commit 88e1ffd21a7fa49a07cf5b9945e51b9f9ea81f53
Author: Andy Holmes <andrew g r holmes gmail com>
Date:   Fri Feb 21 22:45:21 2020 -0800

    examples: add a dbus-client and dbus-service example

 examples/dbus-client.js  | 167 +++++++++++++++++++++++++++++++++++++++++++++++
 examples/dbus-service.js | 137 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 304 insertions(+)
---
diff --git a/examples/dbus-client.js b/examples/dbus-client.js
new file mode 100644
index 00000000..7cedf69c
--- /dev/null
+++ b/examples/dbus-client.js
@@ -0,0 +1,167 @@
+'use strict';
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+
+
+/*
+ * An XML DBus Interface
+ */
+const ifaceXml = `
+<node>
+  <interface name="org.gnome.gjs.Test">
+    <method name="SimpleMethod"/>
+    <method name="ComplexMethod">
+      <arg type="s" direction="in" name="input"/>
+      <arg type="u" direction="out" name="length"/>
+    </method>
+    <signal name="TestSignal">
+      <arg name="type" type="s"/>
+      <arg name="value" type="b"/>
+    </signal>
+    <property name="ReadOnlyProperty" type="s" access="read"/>
+    <property name="ReadWriteProperty" type="b" access="readwrite"/>
+  </interface>
+</node>`;
+
+
+
+// Pass the XML string to make a re-usable proxy class for an interface proxies.
+const TestProxy = Gio.DBusProxy.makeProxyWrapper(ifaceXml);
+
+
+let proxy = null;
+let proxySignalId = 0;
+let proxyPropId = 0;
+
+
+// Watching a name on DBus. Another option is to create a proxy with the
+// `Gio.DBusProxyFlags.DO_NOT_AUTO_START` flag and watch the `g-name-owner`
+// property.
+function onNameAppeared(connection, name, _owner) {
+    print(`"${name}" appeared on the session bus`);
+
+    // If creating a proxy synchronously, errors will be thrown as normal
+    try {
+        proxy = new TestProxy(
+            Gio.DBus.session,
+            'org.gnome.gjs.Test',
+            '/org/gnome/gjs/Test'
+        );
+    } catch (e) {
+        logError(e);
+        return;
+    }
+
+
+    // Proxy wrapper signals use the special functions `connectSignal()` and
+    // `disconnectSignal()` to avoid conflicting with regular GObject signals.
+    proxySignalId = proxy.connectSignal('TestSignal', (proxy_, name_, args) => {
+        print(`TestSignal: ${args[0]}, ${args[1]}`);
+    });
+
+
+    // To watch property changes, you can connect to the `g-properties-changed`
+    // GObject signal with `connect()`
+    proxyPropId = proxy.connect('g-properties-changed', (proxy_, changed, invalidated) => {
+        for (let [prop, value] of Object.entries(changed.deepUnpack()))
+            print(`Property '${prop}' changed to '${value.deepUnpack()}'`);
+
+        for (let prop of invalidated)
+            print(`Property '${prop}' invalidated`);
+    });
+
+
+    // Reading and writing properties is straight-forward
+    print(`ReadOnlyProperty: ${proxy.ReadOnlyProperty}`);
+
+    print(`ReadWriteProperty: ${proxy.ReadWriteProperty}`);
+
+    proxy.ReadWriteProperty = !proxy.ReadWriteProperty;
+    print(`ReadWriteProperty: ${proxy.ReadWriteProperty}`);
+
+
+    // Both synchronous and asynchronous functions will be generated
+    try {
+        let value = proxy.SimpleMethodSync();
+
+        print(`SimpleMethod: ${value}`);
+    } catch (e) {
+        logError(`SimpleMethod: ${e.message}`);
+    }
+
+    proxy.ComplexMethodRemote('input string', (value, error, fdList) => {
+
+        // If @error is not `null`, then an error occurred
+        if (error !== null) {
+            logError(error);
+            return;
+        }
+
+        print(`ComplexMethod: ${value}`);
+
+        // Methods that return file descriptors are fairly rare, so you should
+        // know to expect one or not.
+        if (fdList !== null) {
+            //
+        }
+    });
+}
+
+function onNameVanished(connection, name) {
+    print(`"${name}" vanished from the session bus`);
+
+    if (proxy !== null) {
+        proxy.disconnectSignal(proxySignalId);
+        proxy.disconnect(proxyPropId);
+        proxy = null;
+    }
+}
+
+let busWatchId = Gio.bus_watch_name(
+    Gio.BusType.SESSION,
+    'org.gnome.gjs.Test',
+    Gio.BusNameWatcherFlags.NONE,
+    onNameAppeared,
+    onNameVanished
+);
+
+// Start an event loop
+let loop = GLib.MainLoop.new(null, false);
+loop.run();
+
+// Unwatching names works just like disconnecting signal handlers.
+Gio.bus_unown_name(busWatchId);
+
+
+/* Asynchronous Usage
+ *
+ * Below is the alternative, asynchronous usage of proxy wrappers. If creating
+ * a proxy asynchronously, you should not consider the proxy ready to use until
+ * the callback is invoked without error.
+ */
+proxy = null;
+
+new TestProxy(
+    Gio.DBus.session,
+    'org.gnome.gjs.Test',
+    '/org/gnome/gjs/Test',
+    (sourceObj, error) => {
+        // If @error is not `null` it will be an Error object indicating the
+        // failure. @proxy will be `null` in this case.
+        if (error !== null) {
+            logError(error);
+            return;
+        }
+
+        // At this point the proxy is initialized and you can start calling
+        // functions, using properties and so on.
+        proxy = sourceObj;
+        print(`ReadOnlyProperty: ${proxy.ReadOnlyProperty}`);
+    },
+    // Optional Gio.Cancellable object. Pass `null` if you need to pass flags.
+    null,
+    // Optional flags passed to the Gio.DBusProxy constructor
+    Gio.DBusProxyFlags.NONE
+);
+
diff --git a/examples/dbus-service.js b/examples/dbus-service.js
new file mode 100644
index 00000000..493ebe54
--- /dev/null
+++ b/examples/dbus-service.js
@@ -0,0 +1,137 @@
+'use strict';
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+
+
+/*
+ * An XML DBus Interface
+ */
+const ifaceXml = `
+<node>
+  <interface name="org.gnome.gjs.Test">
+    <method name="SimpleMethod"/>
+    <method name="ComplexMethod">
+      <arg type="s" direction="in" name="input"/>
+      <arg type="u" direction="out" name="length"/>
+    </method>
+    <signal name="TestSignal">
+      <arg name="type" type="s"/>
+      <arg name="value" type="b"/>
+    </signal>
+    <property name="ReadOnlyProperty" type="s" access="read"/>
+    <property name="ReadWriteProperty" type="b" access="readwrite"/>
+  </interface>
+</node>`;
+
+
+// An example of the service-side implementation of the above interface.
+class Service {
+
+    constructor() {
+        this.dbus = Gio.DBusExportedObject.wrapJSObject(ifaceXml, this);
+    }
+
+    // Properties
+    get ReadOnlyProperty() {
+        return 'a string';
+    }
+
+    get ReadWriteProperty() {
+        if (this._readWriteProperty === undefined)
+            return false;
+
+        return this._readWriteProperty;
+    }
+
+    set ReadWriteProperty(value) {
+        if (this.ReadWriteProperty !== value) {
+            this._readWriteProperty = value;
+
+            // Emitting property changes over DBus
+            this.dbus.emit_property_changed(
+                'ReadWriteProperty',
+                new GLib.Variant('b', value)
+            );
+        }
+    }
+
+    // Methods
+    SimpleMethod() {
+        print('SimpleMethod() invoked');
+    }
+
+    ComplexMethod(input) {
+        print(`ComplexMethod() invoked with "${input}"`);
+
+        return input.length;
+    }
+
+    // Signals
+    emitTestSignal() {
+        this.dbus.emit_signal(
+            'TestSignal',
+            new GLib.Variant('(sb)', ['string', false])
+        );
+    }
+}
+
+
+// Once you've created an instance of your service, you will want to own a name
+// on the bus so clients can connect to it.
+let serviceObj = new Service();
+let serviceSignalId = 0;
+
+
+function onBusAcquired(connection, _name) {
+    // At this point you have acquired a connection to the bus, and you should
+    // export your interfaces now.
+    serviceObj.dbus.export(connection, '/org/gnome/gjs/Test');
+}
+
+function onNameAcquired(_connection, _name) {
+    // Clients will typically start connecting and using your interface now.
+
+    // Emit the TestSignal every few seconds
+    serviceSignalId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3, () => {
+        serviceObj.emitTestSignal();
+
+        return GLib.SOURCE_CONTINUE;
+    });
+}
+
+function onNameLost(_connection, _name) {
+    // Clients will know not to call methods on your interface now. Usually this
+    // callback will only be invoked if you try to own a name on DBus that
+    // already has an owner.
+
+    // Stop emitting the test signal
+    if (serviceSignalId > 0) {
+        GLib.Source.remove(serviceSignalId);
+        serviceSignalId = 0;
+    }
+}
+
+let ownerId = Gio.bus_own_name(
+    Gio.BusType.SESSION,
+    'org.gnome.gjs.Test',
+    Gio.BusNameOwnerFlags.NONE,
+    onBusAcquired,
+    onNameAcquired,
+    onNameLost
+);
+
+
+// Start an event loop
+let loop = GLib.MainLoop.new(null, false);
+loop.run();
+
+// Unowning names works just like disconnecting, but note that `onNameLost()`
+// will not be invoked in this case.
+Gio.bus_unown_name(ownerId);
+
+if (serviceSignalId > 0) {
+    GLib.Source.remove(serviceSignalId);
+    serviceSignalId = 0;
+}
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]