dbus.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
  2. //
  3. // SPDX-License-Identifier: GPL-2.0-or-later
  4. 'use strict';
  5. const Gio = imports.gi.Gio;
  6. const GjsPrivate = imports.gi.GjsPrivate;
  7. const GLib = imports.gi.GLib;
  8. const GObject = imports.gi.GObject;
  9. /*
  10. * Some utility methods
  11. */
  12. function toDBusCase(string) {
  13. return string.replace(/(?:^\w|[A-Z]|\b\w)/g, (ltr, offset) => {
  14. return ltr.toUpperCase();
  15. }).replace(/[\s_-]+/g, '');
  16. }
  17. function toHyphenCase(string) {
  18. return string.replace(/(?:[A-Z])/g, (ltr, offset) => {
  19. return (offset > 0) ? `-${ltr.toLowerCase()}` : ltr.toLowerCase();
  20. }).replace(/[\s_]+/g, '');
  21. }
  22. function toUnderscoreCase(string) {
  23. return string.replace(/(?:^\w|[A-Z]|_|\b\w)/g, (ltr, offset) => {
  24. if (ltr === '_')
  25. return '';
  26. return (offset > 0) ? `_${ltr.toLowerCase()}` : ltr.toLowerCase();
  27. }).replace(/[\s-]+/g, '');
  28. }
  29. /**
  30. * DBus.Interface represents a DBus interface bound to an object instance, meant
  31. * to be exported over DBus.
  32. */
  33. var Interface = GObject.registerClass({
  34. GTypeName: 'GSConnectDBusInterface',
  35. Implements: [Gio.DBusInterface],
  36. Properties: {
  37. 'g-instance': GObject.ParamSpec.object(
  38. 'g-instance',
  39. 'Instance',
  40. 'The delegate GObject',
  41. GObject.ParamFlags.READWRITE,
  42. GObject.Object.$gtype
  43. ),
  44. },
  45. }, class Interface extends GjsPrivate.DBusImplementation {
  46. _init(params) {
  47. super._init({
  48. g_instance: params.g_instance,
  49. g_interface_info: params.g_interface_info,
  50. });
  51. // Cache member lookups
  52. this._instanceHandlers = [];
  53. this._instanceMethods = {};
  54. this._instanceProperties = {};
  55. const info = this.get_info();
  56. this.connect('handle-method-call', this._call.bind(this._instance, info));
  57. this.connect('handle-property-get', this._get.bind(this._instance, info));
  58. this.connect('handle-property-set', this._set.bind(this._instance, info));
  59. // Automatically forward known signals
  60. const id = this._instance.connect('notify', this._notify.bind(this));
  61. this._instanceHandlers.push(id);
  62. for (const signal of info.signals) {
  63. const type = `(${signal.args.map(arg => arg.signature).join('')})`;
  64. const id = this._instance.connect(
  65. signal.name,
  66. this._emit.bind(this, signal.name, type)
  67. );
  68. this._instanceHandlers.push(id);
  69. }
  70. // Export if connection and object path were given
  71. if (params.g_connection && params.g_object_path)
  72. this.export(params.g_connection, params.g_object_path);
  73. }
  74. get g_instance() {
  75. if (this._instance === undefined)
  76. this._instance = null;
  77. return this._instance;
  78. }
  79. set g_instance(instance) {
  80. this._instance = instance;
  81. }
  82. /**
  83. * Invoke an instance's method for a DBus method call.
  84. *
  85. * @param {Gio.DBusInterfaceInfo} info - The DBus interface
  86. * @param {DBus.Interface} iface - The DBus interface
  87. * @param {string} name - The DBus method name
  88. * @param {GLib.Variant} parameters - The method parameters
  89. * @param {Gio.DBusMethodInvocation} invocation - The method invocation info
  90. */
  91. async _call(info, iface, name, parameters, invocation) {
  92. let retval;
  93. // Invoke the instance method
  94. try {
  95. const args = parameters.unpack().map(parameter => {
  96. if (parameter.get_type_string() === 'h') {
  97. const message = invocation.get_message();
  98. const fds = message.get_unix_fd_list();
  99. const idx = parameter.deepUnpack();
  100. return fds.get(idx);
  101. } else {
  102. return parameter.recursiveUnpack();
  103. }
  104. });
  105. retval = await this[name](...args);
  106. } catch (e) {
  107. if (e instanceof GLib.Error) {
  108. invocation.return_gerror(e);
  109. } else {
  110. // likely to be a normal JS error
  111. if (!e.name.includes('.'))
  112. e.name = `org.gnome.gjs.JSError.${e.name}`;
  113. invocation.return_dbus_error(e.name, e.message);
  114. }
  115. logError(e, `${this}: ${name}`);
  116. return;
  117. }
  118. // `undefined` is an empty tuple on DBus
  119. if (retval === undefined)
  120. retval = new GLib.Variant('()', []);
  121. // Return the instance result or error
  122. try {
  123. if (!(retval instanceof GLib.Variant)) {
  124. const args = info.lookup_method(name).out_args;
  125. retval = new GLib.Variant(
  126. `(${args.map(arg => arg.signature).join('')})`,
  127. (args.length === 1) ? [retval] : retval
  128. );
  129. }
  130. invocation.return_value(retval);
  131. } catch (e) {
  132. invocation.return_dbus_error(
  133. 'org.gnome.gjs.JSError.ValueError',
  134. 'Service implementation returned an incorrect value type'
  135. );
  136. logError(e, `${this}: ${name}`);
  137. }
  138. }
  139. _nativeProp(obj, name) {
  140. if (this._instanceProperties[name] === undefined) {
  141. let propName = name;
  142. if (propName in obj)
  143. this._instanceProperties[name] = propName;
  144. if (this._instanceProperties[name] === undefined) {
  145. propName = toUnderscoreCase(name);
  146. if (propName in obj)
  147. this._instanceProperties[name] = propName;
  148. }
  149. }
  150. return this._instanceProperties[name];
  151. }
  152. _emit(name, type, obj, ...args) {
  153. this.emit_signal(name, new GLib.Variant(type, args));
  154. }
  155. _get(info, iface, name) {
  156. const nativeValue = this[iface._nativeProp(this, name)];
  157. const propertyInfo = info.lookup_property(name);
  158. if (nativeValue === undefined || propertyInfo === null)
  159. return null;
  160. return new GLib.Variant(propertyInfo.signature, nativeValue);
  161. }
  162. _set(info, iface, name, value) {
  163. const nativeValue = value.recursiveUnpack();
  164. this[iface._nativeProp(this, name)] = nativeValue;
  165. }
  166. _notify(obj, pspec) {
  167. const name = toDBusCase(pspec.name);
  168. const propertyInfo = this.get_info().lookup_property(name);
  169. if (propertyInfo === null)
  170. return;
  171. this.emit_property_changed(
  172. name,
  173. new GLib.Variant(
  174. propertyInfo.signature,
  175. // Adjust for GJS's '-'/'_' conversion
  176. this._instance[pspec.name.replace(/-/gi, '_')]
  177. )
  178. );
  179. }
  180. destroy() {
  181. try {
  182. for (const id of this._instanceHandlers)
  183. this._instance.disconnect(id);
  184. this._instanceHandlers = [];
  185. this.flush();
  186. this.unexport();
  187. } catch (e) {
  188. logError(e);
  189. }
  190. }
  191. });
  192. /**
  193. * Get a new, dedicated DBus connection on @busType
  194. *
  195. * @param {Gio.BusType} [busType] - a Gio.BusType constant
  196. * @param {Gio.Cancellable} [cancellable] - an optional Gio.Cancellable
  197. * @return {Promise<Gio.DBusConnection>} A new DBus connection
  198. */
  199. function newConnection(busType = Gio.BusType.SESSION, cancellable = null) {
  200. return new Promise((resolve, reject) => {
  201. Gio.DBusConnection.new_for_address(
  202. Gio.dbus_address_get_for_bus_sync(busType, cancellable),
  203. Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT |
  204. Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION,
  205. null,
  206. cancellable,
  207. (connection, res) => {
  208. try {
  209. resolve(Gio.DBusConnection.new_for_address_finish(res));
  210. } catch (e) {
  211. reject(e);
  212. }
  213. }
  214. );
  215. });
  216. }