1 module ddbus.simple;
2 
3 import ddbus.thin;
4 import ddbus.util;
5 import ddbus.c_lib;
6 import ddbus.router;
7 import std..string;
8 import std.traits;
9 
10 class PathIface {
11   this(Connection conn, string dest, ObjectPath path, string iface) {
12     this(conn, dest, path.value, iface);
13   }
14 
15   this(Connection conn, string dest, string path, string iface) {
16     this.conn = conn;
17     this.dest = dest.toStringz();
18     this.path = path.toStringz();
19     this.iface = iface.toStringz();
20   }
21 
22   Ret call(Ret, Args...)(string meth, Args args)
23       if (allCanDBus!Args && canDBus!Ret) {
24     Message msg = Message(dbus_message_new_method_call(dest, path, iface, meth.toStringz()));
25     msg.build(args);
26     Message ret = conn.sendWithReplyBlocking(msg);
27     return ret.read!Ret();
28   }
29 
30   Message opDispatch(string meth, Args...)(Args args) {
31     Message msg = Message(dbus_message_new_method_call(dest, path, iface, meth.toStringz()));
32     msg.build(args);
33     return conn.sendWithReplyBlocking(msg);
34   }
35 
36   Connection conn;
37   const(char)* dest;
38   const(char)* path;
39   const(char)* iface;
40 }
41 
42 unittest {
43   import dunit.toolkit;
44 
45   Connection conn = connectToBus();
46   PathIface obj = new PathIface(conn, "org.freedesktop.DBus",
47       "/org/freedesktop/DBus", "org.freedesktop.DBus");
48   auto names = obj.GetNameOwner("org.freedesktop.DBus").to!string();
49   names.assertEqual("org.freedesktop.DBus");
50   obj.call!string("GetNameOwner", "org.freedesktop.DBus").assertEqual("org.freedesktop.DBus");
51 }
52 
53 enum SignalMethod;
54 
55 /**
56    Registers all *possible* methods of an object in a router.
57    It will not register methods that use types that ddbus can't handle.
58 
59    The implementation is rather hacky and uses the compiles trait to check for things
60    working so if some methods randomly don't seem to be added, you should probably use
61    setHandler on the router directly. It is also not efficient and creates a closure for every method.
62 
63    TODO: replace this with something that generates a wrapper class who's methods take and return messages
64    and basically do what MessageRouter.setHandler does but avoiding duplication. Then this DBusWrapper!Class
65    could be instantiated with any object efficiently and placed in the router table with minimal duplication.
66  */
67 void registerMethods(T : Object)(MessageRouter router, string path, string iface, T obj) {
68   MessagePattern patt = MessagePattern(path, iface, "", false);
69   foreach (member; __traits(allMembers, T)) {
70     // dfmt off
71     static if (__traits(compiles, __traits(getOverloads, obj, member))
72         && __traits(getOverloads, obj, member).length > 0
73         && __traits(compiles, router.setHandler(patt, &__traits(getOverloads, obj, member)[0]))) {
74       patt.method = member;
75       patt.signal = hasUDA!(__traits(getOverloads, obj, member)[0], SignalMethod);
76       router.setHandler(patt, &__traits(getOverloads, obj, member)[0]);
77     }
78     // dfmt on
79   }
80 }
81 
82 unittest {
83   import dunit.toolkit;
84 
85   class Tester {
86     int lol(int x, string s, string[string] map, Variant!DBusAny any) {
87       return 5;
88     }
89 
90     void wat() {
91     }
92 
93     @SignalMethod void signalRecv() {
94     }
95   }
96 
97   auto o = new Tester;
98   auto router = new MessageRouter;
99   registerMethods(router, "/", "ca.thume.test", o);
100   MessagePattern patt = MessagePattern("/", "ca.thume.test", "wat");
101   router.callTable.assertHasKey(patt);
102   patt.method = "signalRecv";
103   patt.signal = true;
104   router.callTable.assertHasKey(patt);
105   patt.method = "lol";
106   patt.signal = false;
107   router.callTable.assertHasKey(patt);
108   auto res = router.callTable[patt];
109   res.argSig.assertEqual(["i", "s", "a{ss}", "v"]);
110   res.retSig.assertEqual(["i"]);
111 }