Module/dbus
DBus method calls, signal subscriptions, and JavaScript service exports.
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}} objectFunctions #
- Module-level functions use a single legacy connection. Once connected, changing its bus is an error; use the independent
sessionandsystemhandles for simultaneous access. - Await
connect()before callingaddMatch,call, orcallComplex. - 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(rule: string): void- 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
ayresolve tonumber[]. - 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[]>- 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] }(aliasestandvalso accepted). Strings, booleans and numbers infers,b, andi; numeric arrays inferay, other arrays inferas. - Signature, argument count, and value types must match. Explicit empty
ayandaoarrays retain their declared type. Return decoding is the same as forcall.
callComplex(dest: string, path: string, interface: string, method: string, signature: string, ...args): Promise<string | object | number[]>argscontains every argument in wire order. Basic values retain their JS types; variants are unwrapped, dictionaries become objects, and arrays/structs become arrays.bodypreserves the first argument as text for compatibility; composite values are JSON text. Empty signals haveargs: []andbody: ''.
onSignal(cb: (sig: { sender: string, path: string, interface: string, member: string, signature: string, body: string, args: unknown[] }): void) : voidoffSignal(cb?: Function): void- An Invocation contains
sender,path,interface,member,signature, and decodedargs, plusreply(signature, ...args)anderror(name, message). - Reply exactly once. Replies can be delayed while asynchronous work completes. Catch rejected promises and use
call.error(...); synchronous handler exceptions automatically returnorg.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- 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): voidemitSignal(path: string, interface: string, member: string, signature: string, ...args): void- 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): voidExamples #
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
addMatchto filter which signals you receive; otherwise you may get a high volume of events. - Set environment variable
KOYA_DBUS_DEBUG=1to log send/receive activity to stderr.