Find a file
Jan Jurzitza c02ad08016 Merge pull request #14 from thaven/fix/const-signature
Change type of DBusAny.signature to const(char)[]
2017-06-05 21:23:48 +02:00
source/ddbus Change signature type from char[] to const(char)[] 2017-06-05 14:28:20 +02:00
.gitignore Documentation updates 2017-04-23 16:39:28 +02:00
dub.json Documentation updates 2017-04-23 16:39:28 +02:00
dub.selections.json Documentation updates 2017-04-23 16:39:28 +02:00
example.d Move example out of default binary 2015-05-09 13:34:33 -04:00
Rakefile Update for latest D 2015-12-12 14:17:08 -05:00
Readme.md Fix readme 2017-04-23 16:15:57 -04:00
transformer.rb Initial commit w/ ported header 2015-04-25 21:48:04 -04:00

ddbus

Dub version Dub downloads

A dbus library for the D programming language.

Provides fancy and convenient highly templated methods that automagically serialize and deserialize things into DBus types so that calling DBus methods is almost as easy as calling local ones.

It currently supports:

  • Calling methods
  • Creating wrapper objects for DBus interfaces
  • Seamlessly converting too and from D types
  • Handling method calls and signals (includes introspection support)

Installation

Before using, you will need to have the DBus C library installed on your computer to link with, and probably also a DBus session bus running so that you can actually do things.

ddbus is available on DUB so you can simply include it in your dub.json:

"dependencies": {
  "ddbus": "~>2.1.0"
}

Usage

Call Interface

The simplest way to call methods over DBus is to create a connection and then a PathIface object which wraps a destination, path and interface. You can then call methods on that object with any parameters which ddbus knows how to serialize and it will return a reply message which you can convert to the correct return type using .to!T(). You can also use the templated call method. Example:

import ddbus;
Connection conn = connectToBus();
PathIface obj = new PathIface(conn, "org.freedesktop.DBus","/org/freedesktop/DBus",
"org.freedesktop.DBus");
// call any method with any parameters and then convert the result to the right type.
auto name = obj.GetNameOwner("org.freedesktop.DBus").to!string();
// alternative method
obj.call!string("GetNameOwner","org.freedesktop.DBus");

Working with properties

import ddbus;
Connection conn = connectToBus();
PathIface obj = new PathIface(conn, "org.freedesktop.secrets", "/org/freedesktop/secrets/collection/login", "org.freedesktop.DBus.Properties");

// read property
string loginLabel = obj.Get("org.freedesktop.Secret.Collection", "Label").to!string();
loginLabel = "Secret"~login;
// write it back (variant type requires variant() wrapper)
obj.Set("org.freedesktop.Secret.Collection", "Label", variant(loginLabel));

Setting read only properties results in a thrown DBusException.

Server Interface

You can register a delegate into a MessageRouter and a main loop in order to handle messages. After that you can request a name so that other DBus clients can connect to your program.

You can return a Tuple!(args) to return multiple values (multiple out values in XML) or return a Variant!DBusAny to support returning any dynamic value.

import ddbus;
MessageRouter router = new MessageRouter();
// create a pattern to register a handler at a path, interface and method
MessagePattern patt = MessagePattern("/root","ca.thume.test","test");
router.setHandler!(int,int,Variant!DBusAny)(patt,(int par, Variant!DBusAny anyArgument) {
  // anyArgument can contain any type now, it must be specified as argument using Variant!DBusAny.
  writeln("Called with ", par, ", ", anyArgument);
  return par;
});
// handle a signal
patt = MessagePattern("/signaler","ca.thume.test","signal",true);
router.setHandler!(void,int)(patt,(int par) {
  writeln("Signalled with ", par);
});
// register all methods of an object
class Tester {
  int lol(int x, string s) {return 5;}
  void wat() {}
}
Tester o = new Tester;
registerMethods(router, "/","ca.thume.test",o);
// get a name and start the server
registerRouter(conn, router);
bool gotem = requestName(conn, "ca.thume.ddbus.test");
simpleMainLoop(conn);

See the Concurrent Updates section for details how to implement this in a custom main loop.

Thin(ish) Wrapper

ddbus also includes a series of thin D struct wrappers over the DBus types.

  • Message: wraps DBusMessage and provides D methods for common functionality.
  • Connection: wraps DBusConnection and provides D methods for common functionality.
  • DBusException: used for errors produced by DBus turned into D exceptions.

Type Marshaling

ddbus includes fancy templated methods for marshaling D types in and out of DBus messages. All DBus-compatible basic types work (except dbus path objects and file descriptors). Any forward range can be marshalled in as DBus array of that type but arrays must be taken out as dynamic arrays. Structures are mapped to Tuple from std.typecons.

Example using the lower level interface, the simple interfaces use these behind the scenes:

Message msg = Message("org.example.wow","/wut","org.test.iface","meth");
bool[] emptyB;
auto args = tuple(5,true,"wow",[6,5],tuple(6.2,4,[["lol"]],emptyB));
msg.build(args.expand);
msg.signature().assertEqual("ibsai(diaasab)");
msg.readTuple!(typeof(args))().assertEqual(args);

Modules

  • thin: thin wrapper types
  • router: message and signal routing based on MessagePattern structs.
  • bus: bus functionality like requesting names and event loops.
  • simple: simpler wrappers around other functionality.
  • conv: low level type marshaling methods.
  • util: templates for working with D type marshaling like canDBus!T.
  • c_lib: a D translation of the DBus C headers.

Importing ddbus publicly imports the thin,router,bus and simple modules. These provide most of the functionality you probably want, you can import the others if you want lower level control.

Nothing is hidden so if ddbus doesn't provide something you can simply import c_lib and use the pointers contained in the thin wrapper structs to do it yourself.

Concurrent Updates

If you want to use the DBus connection concurrently with some other features or library like a GUI or vibe.d you can do so by placing this code in the update/main loop:

// initialize Connection conn; somewhere
// on update:
if (!conn.tick)
  return;

Or in vibe.d:

runTask({
  import vibe.core.core : yield;

  while (conn.tick)
    yield(); // Or sleep(1.msecs);
});

It would be better to watch a file descriptor asynchronously in the event loop instead of checking on a timer, but that hasn't been implemented yet, see Todo.

Todo

ddbus should be complete for everyday use but is missing some fanciness that it easily could and should have:

  • Support for adding file descriptors to event loops like vibe.d so that it only wakes up when messages arrive and not on a timer.
  • Marshaling of DBus path and file descriptor objects
  • Better efficiency in some places, particularly the object wrapping allocates tons of delegates for every method.

Pull requests are welcome, the codebase is pretty small and other than the template metaprogramming for type marshaling is fairly straightforward.