v0.7.1 (build 925)

Use DBus as a client or export service interfaces from JavaScript. Both sides support typed variants, arrays, structs, and nested dictionaries.

Import #

import * as DBus from 'Module/dbus';

Independent bus handles #

session and system expose the same client and service functions as the module-level connection. Their connections, exports, pending calls, match rules, and callbacks are independent. Both can be used concurrently.

import { session, system } from 'Module/dbus';

await Promise.all([session.connect(), system.connect()]);
session.addMatch("type='signal',interface='org.example.Desktop'");
session.onSignal(sig => {
  if (sig.member === 'HardwareButton') {
    const button = sig.args[0]; // decoded a{sv} dictionary
  }
});
const settings = await system.call(
  'org.freedesktop.NetworkManager',
  '/org/freedesktop/NetworkManager/Settings/1',
  'org.freedesktop.NetworkManager.Settings.Connection',
  'GetSettings'
); // decoded a{sa{sv}} object

Functions #

connect
Connect to the DBus daemon. Defaults to session bus.
  • Module-level functions use a single legacy connection. Once connected, changing its bus is an error; use the independent session and system handles for simultaneous access.
  • Await connect() before calling addMatch, call, or callComplex.
  • After the daemon disconnects, call connect() again to reopen the connection. Signal callbacks and exported handlers remain registered, and remembered match rules are restored. Re-request well-known service names; pending calls and delayed invocations from the old connection cannot be resumed.
connect(bus?: 'session' | 'system'): Promise<void>
addMatch
Adds a DBus match rule to receive specific signals.
addMatch(rule: string): void
call
Sends a method call with an optional basic-type signature.
  • Supported basic signature chars: s (string), o (object path), g (signature), b (bool), y (byte), n (int16), q (uint16), i (int32), u (uint32), x (int64), t (uint64), d (double).
  • Basic replies resolve to strings.
  • Byte arrays ay resolve to number[].
  • Composite replies (arrays/structs/dicts) resolve to parsed JS objects (JSON is handled internally).
  • Rejects with an error string containing the remote error name and description: ERROR:org.example.Error: description.
call(dest: string, path: string, interface: string, method: string, signature?: string, ...args): Promise<string | object | number[]>
callComplex
Sends a method call with support for complex container types and variants.
  • Supports basic types, arrays, structs, dictionaries, and variants, including a{oa{sa{sv}}} ObjectManager trees.
  • Arrays and structs use JS arrays. Dictionaries use JS objects. Variants can carry an explicit type: { _t: "(ub)", _v: [73, true] } (aliases t and v also accepted). Strings, booleans and numbers infer s, b, and i; numeric arrays infer ay, other arrays infer as.
  • Signature, argument count, and value types must match. Explicit empty ay and ao arrays retain their declared type. Return decoding is the same as for call.
callComplex(dest: string, path: string, interface: string, method: string, signature: string, ...args): Promise<string | object | number[]>
onSignal
Register a callback for incoming DBus signals.
  • args contains every argument in wire order. Basic values retain their JS types; variants are unwrapped, dictionaries become objects, and arrays/structs become arrays.
  • body preserves the first argument as text for compatibility; composite values are JSON text. Empty signals have args: [] and body: ''.
onSignal(cb: (sig: { sender: string, path: string, interface: string, member: string, signature: string, body: string, args: unknown[] }): void) : void
offSignal
Remove a previously registered signal callback.
offSignal(cb?: Function): void
exportObject
Export one object interface on the connected bus.
  • An Invocation contains sender, path, interface, member, signature, and decoded args, plus reply(signature, ...args) and error(name, message).
  • Reply exactly once. Replies can be delayed while asynchronous work completes. Catch rejected promises and use call.error(...); synchronous handler exceptions automatically return org.freedesktop.DBus.Error.Failed.
  • Handlers run on the engine thread. Variants in incoming arguments are unwrapped, as they are in signal arguments. No automatic Properties, Introspectable, or ObjectManager interfaces are added; export their handlers explicitly when needed.
  • Own a well-known service name using the daemon RequestName method. Duplicate exports throw; separate bus handles and runtimes have independent exports.
exportObject(path: string, interface: string, handler: (call: Invocation): void) : void
unexportObject
Remove an exported interface.
  • Pending invocations for this interface receive UnknownObject errors. Attempts to reply after removal or complete a call twice throw. Engine shutdown releases exports, requests, and the private connection.
unexportObject(path: string, interface: string): void
emitSignal
Emit a signal using the same typed marshalling as callComplex.
emitSignal(path: string, interface: string, member: string, signature: string, ...args): void
emitSignalTo
Send a directed signal from the connection that owns the exported service.
  • Destination must be a valid unique or well-known DBus bus name. Use the requesting client's unique name to keep replies tied to that client even if a well-known name changes owners.
  • Uses the same typed marshalling and argument validation as emitSignal. Other matched connections do not receive the signal.
emitSignalTo(destination: string, path: string, interface: string, member: string, signature: string, ...args): void

Examples #

Basic usage #

Connect to the session bus, subscribe to signals, and make a simple method call. This is the minimal pattern for interacting with any DBus service.

import * as DBus from 'Module/dbus';
import * as Log from 'Helix/Log';

await DBus.connect('session');
DBus.addMatch("type='signal',interface='org.freedesktop.DBus'");
DBus.onSignal(sig => Log.info('signal: ' + sig.member + ' from ' + sig.sender));

try {
  const name = await DBus.call('org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'GetId');
  Log.info('reply: ' + name);
} catch (e) {
  Log.error('dbus failed: ' + e);
}

NetworkManager (simple) #

Query all properties from NetworkManager using the standard org.freedesktop.DBus.Properties.GetAll interface. This pattern works for reading properties from any DBus service on the system bus.

import * as DBus from 'Module/dbus';

await DBus.connect('system');

const props = await DBus.call(
  'org.freedesktop.NetworkManager',
  '/org/freedesktop/NetworkManager',
  'org.freedesktop.DBus.Properties',
  'GetAll',
  's',
  'org.freedesktop.NetworkManager'
);

NetworkManager AddAndActivateConnection (complex) #

Create and activate a new Wi-Fi connection profile using callComplex for marshalling nested dictionaries and variant types. This demonstrates the full power of complex argument support for services like NetworkManager.

import * as DBus from 'Module/dbus';

await DBus.connect('system');

const toUtf8Bytes = (s) => Array.from(new TextEncoder().encode(s));
const ssidBytes = toUtf8Bytes('MySSID');

const settings = {
  'connection': {
    'id': 'home-wifi',
    'type': '802-11-wireless',
    'autoconnect': true,
    'interface-name': 'wlan0'
  },
  '802-11-wireless': {
    'ssid': { _t: 'ay', _v: ssidBytes },
    'mode': 'infrastructure'
  },
  '802-11-wireless-security': {
    'key-mgmt': 'wpa-psk',
    'psk': 'supersecret'
  },
  'ipv4': { 'method': 'auto' },
  'ipv6': { 'method': 'auto' }
};

await DBus.callComplex(
  'org.freedesktop.NetworkManager',
  '/org/freedesktop/NetworkManager',
  'org.freedesktop.NetworkManager',
  'AddAndActivateConnection',
  'a{sa{sv}}oo',
  settings,
  '/org/freedesktop/NetworkManager/Devices/0',
  '/'
);

Exporting a service #

import { session as bus } from 'Module/dbus';

await bus.connect();
bus.exportObject('/org/example/Demo', 'org.example.Demo', call => {
  if (call.member === 'Echo' && call.signature === 's') {
    call.reply('s', call.args[0]);
    bus.emitSignal(call.path, call.interface, 'Changed', 's', call.args[0]);
  } else {
    call.error('org.freedesktop.DBus.Error.UnknownMethod', call.member);
  }
});
const owner = await bus.call('org.freedesktop.DBus', '/org/freedesktop/DBus',
  'org.freedesktop.DBus', 'RequestName', 'su', 'org.example.Demo', 4);
if (owner !== '1') throw new Error('Service name is already owned');

Notes #

  • The module polls on the engine update hook; no manual drain required.
  • Replies and signals are delivered on the JS thread; avoid long work in handlers.
  • Use addMatch to filter which signals you receive; otherwise you may get a high volume of events.
  • Set environment variable KOYA_DBUS_DEBUG=1 to log send/receive activity to stderr.