1 /// Thin OO wrapper around DBus types
2 module ddbus.thin;
3 
4 import ddbus.c_lib;
5 import ddbus.conv;
6 import ddbus.util;
7 import std.string;
8 import std.typecons;
9 import std.exception;
10 
11 class DBusException : Exception {
12   this(DBusError *err) {
13     super(err.message.fromStringz().idup);
14   }
15 }
16 
17 T wrapErrors(T)(T delegate(DBusError *err) del) {
18   DBusError error;
19   dbus_error_init(&error);
20   T ret = del(&error);
21   if(dbus_error_is_set(&error)) {
22     auto ex = new DBusException(&error);
23     dbus_error_free(&error);
24     throw ex;
25   }
26   return ret;
27 }
28 
29 enum MessageType {
30   Invalid = 0,
31   Call, Return, Error, Signal
32 }
33 
34 struct Message {
35   DBusMessage *msg;
36 
37   this(string dest, string path, string iface, string method) {
38     msg = dbus_message_new_method_call(dest.toStringz(), path.toStringz(), iface.toStringz(), method.toStringz());
39   }
40 
41   this(DBusMessage *m) {
42     msg = m;
43   }
44 
45   this(this) {
46     dbus_message_ref(msg);
47   }
48 
49   ~this() {
50     dbus_message_unref(msg);
51   }
52 
53   void build(TS...)(TS args) if(allCanDBus!TS) {
54     DBusMessageIter iter;
55     dbus_message_iter_init_append(msg, &iter);
56     buildIter(&iter, args);
57   }
58 
59   /**
60      Reads the first argument of the message.
61      Note that this creates a new iterator every time so calling it multiple times will always
62      read the first argument. This is suitable for single item returns.
63      To read multiple arguments use readTuple.
64   */
65   T read(T)() if(canDBus!T) {
66     DBusMessageIter iter;
67     dbus_message_iter_init(msg, &iter);
68     return readIter!T(&iter);
69   }
70   alias read to;
71 
72   Tup readTuple(Tup)() if(isTuple!Tup && allCanDBus!(Tup.Types)) {
73     DBusMessageIter iter;
74     dbus_message_iter_init(msg, &iter);
75     Tup ret;
76     readIterTuple(&iter, ret);
77     return ret;
78   }
79 
80   Message createReturn() {
81     return Message(dbus_message_new_method_return(msg));
82   }
83 
84   MessageType type() {
85     return cast(MessageType)dbus_message_get_type(msg);
86   }
87 
88   bool isCall() {
89     return type() == MessageType.Call;
90   }
91 
92   // Various string members
93   // TODO: make a mixin to avoid this copy-paste
94   string signature() {
95     const(char)* cStr = dbus_message_get_signature(msg);
96     assert(cStr != null);
97     return cStr.fromStringz().idup;
98   }
99   string path() {
100     const(char)* cStr = dbus_message_get_path(msg);
101     assert(cStr != null);
102     return cStr.fromStringz().idup;
103   }
104   string iface() {
105     const(char)* cStr = dbus_message_get_interface(msg);
106     assert(cStr != null);
107     return cStr.fromStringz().idup;
108   }
109   string member() {
110     const(char)* cStr = dbus_message_get_member(msg);
111     assert(cStr != null);
112     return cStr.fromStringz().idup;
113   }
114   string sender() {
115     const(char)* cStr = dbus_message_get_sender(msg);
116     assert(cStr != null);
117     return cStr.fromStringz().idup;
118   }
119 }
120 
121 unittest {
122   import dunit.toolkit;
123   auto msg = Message("org.example.test", "/test","org.example.testing","testMethod");
124   msg.path().assertEqual("/test");
125 }
126 
127 struct Connection {
128   DBusConnection *conn;
129   this(DBusConnection *connection) {
130     conn = connection;
131   }
132 
133   this(this) {
134     dbus_connection_ref(conn);
135   }
136 
137   ~this() {
138     dbus_connection_unref(conn);
139   }
140 
141   void close() {
142     dbus_connection_close(conn);
143   }
144 
145   void send(Message msg) {
146     dbus_connection_send(conn,msg.msg, null);
147   }
148 
149   void sendBlocking(Message msg) {
150     send(msg);
151     dbus_connection_flush(conn);
152   }
153 
154   Message sendWithReplyBlocking(Message msg, int timeout = -1) {
155     DBusMessage *dbusMsg = msg.msg;
156     dbus_message_ref(dbusMsg);
157     DBusMessage *reply = wrapErrors((err) {
158         auto ret = dbus_connection_send_with_reply_and_block(conn,dbusMsg,timeout,err);
159         dbus_message_unref(dbusMsg);
160         return ret;
161       });
162     return Message(reply);
163   }
164 }
165 
166 Connection connectToBus(DBusBusType bus = DBusBusType.DBUS_BUS_SESSION) {
167   DBusConnection *conn = wrapErrors((err) { return dbus_bus_get(bus,err); });
168   return Connection(conn);
169 }
170 
171 unittest {
172   import dunit.toolkit;
173   // This test will only pass if DBus is installed.
174   Connection conn = connectToBus();
175   conn.conn.assertTruthy();
176   // We can only count on no system bus on OSX
177   version(OSX) {
178     connectToBus(DBusBusType.DBUS_BUS_SYSTEM).assertThrow!DBusException();
179   }
180 }