dbus-native
Version:
D-bus protocol implementation in native javascript
192 lines (177 loc) • 7.39 kB
JavaScript
const xml2js = require('xml2js');
module.exports = function(obj, callback) {
var bus = obj.service.bus;
bus.invoke(
{
destination: obj.service.name,
path: obj.name,
interface: 'org.freedesktop.DBus.Introspectable',
member: 'Introspect'
},
function(err, xml) {
if (err) return callback(err);
var parser = new xml2js.Parser({ explicitArray: true });
parser.parseString(xml, function(err, result) {
if (err) return callback(err);
if (!result.interface) {
if (result.node && result.node.length > 0 && result.node[0]['@']) {
var subObj = Object.assign(obj, {});
if (subObj.name.slice(-1) !== '/') subObj.name += '/';
subObj.name += result.node[0]['@'].name;
return module.exports(subObj, callback);
}
return callback(new Error('No such interface found'));
}
var proxy = {};
var nodes = [];
var ifaceName, method, property, iface, arg, signature, currentIface;
var ifaces = result['interface'];
var xmlnodes = result['node'] || [];
for (var n = 1; n < xmlnodes.length; ++n) {
// Start at 1 because we want to skip the root node
nodes.push(xmlnodes[n]['@']['name']);
}
for (var i = 0; i < ifaces.length; ++i) {
iface = ifaces[i];
ifaceName = iface['@'].name;
currentIface = proxy[ifaceName] = {};
(function() {
var callbacks = [];
var sigHandlers = [];
function getSigHandler(callback) {
var index;
if ((index = callbacks.indexOf(callback)) === -1) {
index = callbacks.push(callback) - 1;
sigHandlers[index] = function(messageBody) {
callback.apply(null, messageBody);
};
}
return sigHandlers[index];
}
function getMatchRule(objName, ifName, signame) {
return `type='signal',path='${objName}',interface='${
ifName
}',member='${signame}'`;
}
currentIface.addListener = currentIface.on = (function(ifName) {
return function(signame, callback) {
// http://dbus.freedesktop.org/doc/api/html/group__DBusBus.html#ga4eb6401ba014da3dbe3dc4e2a8e5b3ef
// An example is "type='signal',sender='org.freedesktop.DBus', interface='org.freedesktop.DBus',member='Foo', path='/bar/foo',destination=':452345.34'" ...
var signalFullName = bus.mangle(obj.name, ifName, signame);
if (!bus.signals.listeners(signalFullName).length) {
// This is the first time, so call addMatch
var match = getMatchRule(obj.name, ifName, signame);
bus.addMatch(match, function(err) {
if (err) throw new Error(err);
bus.signals.on(signalFullName, getSigHandler(callback));
});
} else {
// The match is already there, just add event listener
bus.signals.on(signalFullName, getSigHandler(callback));
}
};
})(ifaceName);
currentIface.removeListener = (function(ifName) {
return function(signame, callback) {
var signalFullName = bus.mangle(obj.name, ifName, signame);
bus.signals.removeListener(
signalFullName,
getSigHandler(callback)
);
if (!bus.signals.listeners(signalFullName).length) {
// There is no event handlers for this match
var match = getMatchRule(obj.name, ifName, signame);
bus.removeMatch(match, function(err) {
if (err) throw new Error(err);
// Now it is safe to empty these arrays
callbacks.length = 0;
sigHandlers.length = 0;
});
}
};
})(ifaceName);
})();
for (var m = 0; iface.method && m < iface.method.length; ++m) {
method = iface.method[m];
signature = '';
var methodName = method['@'].name;
for (var a = 0; method.arg && a < method.arg.length; ++a) {
arg = method.arg[a]['@'];
if (arg.direction === 'in') signature += arg.type;
}
// add method
currentIface[methodName] = (function(ifName, mName, signature) {
return function() {
var args = Array.prototype.slice.apply(arguments);
var callback =
typeof args[args.length - 1] === 'function'
? args.pop()
: function() {};
var msg = {
destination: obj.service.name,
path: obj.name,
interface: ifName,
member: methodName
};
if (signature !== '') {
msg.signature = signature;
msg.body = args;
}
bus.invoke(msg, callback);
};
})(ifaceName, methodName, signature);
}
for (var p = 0; iface.property && p < iface.property.length; ++p) {
property = iface.property[p];
var propertyName = property['@'].name;
//TODO: move up
var addReadProp = function(iface, ifName, propName, property) {
Object.defineProperty(iface, propName, {
enumerable: true,
get: function() {
return function(callback) {
bus.invoke(
{
destination: obj.service.name,
path: obj.name,
interface: 'org.freedesktop.DBus.Properties',
member: 'Get',
signature: 'ss',
body: [ifName, propName]
},
function(err, val) {
if (err) {
callback(err);
} else {
var signature = val[0];
if (signature.length === 1) {
callback(err, val[1][0]);
} else {
callback(err, val[1]);
}
}
}
);
};
},
set: function(val) {
bus.invoke({
destination: obj.service.name,
path: obj.name,
interface: 'org.freedesktop.DBus.Properties',
member: 'Set',
signature: 'ssv',
body: [ifName, propName, [property['@'].type, val]]
});
}
});
};
addReadProp(currentIface, ifaceName, propertyName, property);
}
// TODO: introspect signals
}
callback(null, proxy, nodes);
});
}
);
};