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 }