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 import std.traits; 11 import std.conv; 12 import std.range; 13 import std.algorithm; 14 15 class DBusException : Exception { 16 this(DBusError *err) { 17 super(err.message.fromStringz().idup); 18 } 19 } 20 21 T wrapErrors(T)(T delegate(DBusError *err) del) { 22 DBusError error; 23 dbus_error_init(&error); 24 T ret = del(&error); 25 if(dbus_error_is_set(&error)) { 26 auto ex = new DBusException(&error); 27 dbus_error_free(&error); 28 throw ex; 29 } 30 return ret; 31 } 32 33 /// Structure allowing typeless parameters 34 struct DBusAny { 35 /// DBus type of the value (never 'v'), see typeSig!T 36 int type; 37 /// Child signature for Arrays & Tuples 38 char[] signature; 39 /// If true, this value will get serialized as variant value, otherwise it is serialized like it wasn't in a DBusAny wrapper. 40 /// Same functionality as Variant!T but with dynamic types if true. 41 bool explicitVariant; 42 43 union 44 { 45 /// 46 byte int8; 47 /// 48 short int16; 49 /// 50 ushort uint16; 51 /// 52 int int32; 53 /// 54 uint uint32; 55 /// 56 long int64; 57 /// 58 ulong uint64; 59 /// 60 double float64; 61 /// 62 string str; 63 /// 64 bool boolean; 65 /// 66 DBusAny[] array; 67 /// 68 DBusAny[] tuple; 69 /// 70 DictionaryEntry!(DBusAny, DBusAny)* entry; 71 /// 72 ubyte[] binaryData; 73 } 74 75 /// Manually creates a DBusAny object using a type, signature and implicit specifier. 76 this(int type, char[] signature, bool explicit) { 77 this.type = type; 78 this.signature = signature; 79 this.explicitVariant = explicit; 80 } 81 82 /// Automatically creates a DBusAny object with fitting parameters from a D type or Variant!T. 83 /// Pass a `Variant!T` to make this an explicit variant. 84 this(T)(T value) { 85 static if(is(T == byte) || is(T == ubyte)) { 86 this(typeCode!byte, null, false); 87 int8 = cast(byte) value; 88 } else static if(is(T == short)) { 89 this(typeCode!short, null, false); 90 int16 = cast(short) value; 91 } else static if(is(T == ushort)) { 92 this(typeCode!ushort, null, false); 93 uint16 = cast(ushort) value; 94 } else static if(is(T == int)) { 95 this(typeCode!int, null, false); 96 int32 = cast(int) value; 97 } else static if(is(T == uint)) { 98 this(typeCode!uint, null, false); 99 uint32 = cast(uint) value; 100 } else static if(is(T == long)) { 101 this(typeCode!long, null, false); 102 int64 = cast(long) value; 103 } else static if(is(T == ulong)) { 104 this(typeCode!ulong, null, false); 105 uint64 = cast(ulong) value; 106 } else static if(is(T == double)) { 107 this(typeCode!double, null, false); 108 float64 = cast(double) value; 109 } else static if(isSomeString!T) { 110 this(typeCode!string, null, false); 111 str = value.to!string; 112 } else static if(is(T == bool)) { 113 this(typeCode!bool, null, false); 114 boolean = cast(bool) value; 115 } else static if(is(T == Variant!R, R)) { 116 static if(is(R == DBusAny)) { 117 type = value.data.type; 118 signature = value.data.signature; 119 explicitVariant = true; 120 if(type == 'a' || type == 'r') { 121 if(signature == ['y']) 122 binaryData = value.data.binaryData; 123 else 124 array = value.data.array; 125 } else if(type == 's') 126 str = value.data.str; 127 else if(type == 'e') 128 entry = value.data.entry; 129 else 130 uint64 = value.data.uint64; 131 } else { 132 this(value.data); 133 explicitVariant = true; 134 } 135 } else static if(is(T : DictionaryEntry!(K, V), K, V)) { 136 this('e', null, false); 137 entry = new DictionaryEntry!(DBusAny, DBusAny)(); 138 static if(is(K == DBusAny)) 139 entry.key = value.key; 140 else 141 entry.key = DBusAny(value.key); 142 static if(is(V == DBusAny)) 143 entry.value = value.value; 144 else 145 entry.value = DBusAny(value.value); 146 } else static if(is(T == ubyte[]) || is(T == byte[])) { 147 this('a', ['y'], false); 148 binaryData = cast(ubyte[]) value; 149 } else static if(isInputRange!T) { 150 this.type = 'a'; 151 static assert(!is(ElementType!T == DBusAny), "Array must consist of the same type, use Variant!DBusAny or DBusAny(tuple(...)) instead"); 152 static assert(typeSig!(ElementType!T) != "y"); 153 this.signature = typeSig!(ElementType!T).dup; 154 this.explicitVariant = false; 155 foreach(elem; value) 156 array ~= DBusAny(elem); 157 } else static if(isTuple!T) { 158 this.type = 'r'; 159 this.signature = ['(']; 160 this.explicitVariant = false; 161 foreach(index, R; value.Types) { 162 auto var = DBusAny(value[index]); 163 tuple ~= var; 164 if(var.explicitVariant) 165 this.signature ~= 'v'; 166 else { 167 if (var.type != 'r') 168 this.signature ~= cast(char) var.type; 169 if(var.type == 'a' || var.type == 'r') 170 this.signature ~= var.signature; 171 } 172 } 173 this.signature ~= ')'; 174 } else static if(isAssociativeArray!T) { 175 this(value.byDictionaryEntries); 176 } else static assert(false, T.stringof ~ " not convertible to a Variant"); 177 } 178 179 /// 180 string toString() const { 181 string valueStr; 182 switch(type) { 183 case typeCode!byte: 184 valueStr = int8.to!string; 185 break; 186 case typeCode!short: 187 valueStr = int16.to!string; 188 break; 189 case typeCode!ushort: 190 valueStr = uint16.to!string; 191 break; 192 case typeCode!int: 193 valueStr = int32.to!string; 194 break; 195 case typeCode!uint: 196 valueStr = uint32.to!string; 197 break; 198 case typeCode!long: 199 valueStr = int64.to!string; 200 break; 201 case typeCode!ulong: 202 valueStr = uint64.to!string; 203 break; 204 case typeCode!double: 205 valueStr = float64.to!string; 206 break; 207 case typeCode!string: 208 valueStr = '"' ~ str ~ '"'; 209 break; 210 case typeCode!bool: 211 valueStr = boolean ? "true" : "false"; 212 break; 213 case 'a': 214 import std.digest.digest : toHexString; 215 216 if(signature == ['y']) 217 valueStr = "binary(" ~ binaryData.toHexString ~ ')'; 218 else 219 valueStr = '[' ~ array.map!(a => a.toString).join(", ") ~ ']'; 220 break; 221 case 'r': 222 valueStr = '(' ~ array.map!(a => a.toString).join(", ") ~ ')'; 223 break; 224 case 'e': 225 valueStr = entry.key.toString ~ ": " ~ entry.value.toString; 226 break; 227 default: 228 valueStr = "unknown"; 229 break; 230 } 231 return "DBusAny(" ~ cast(char) type 232 ~ ", \"" ~ signature.idup 233 ~ "\", " ~ (explicitVariant ? "explicit" : "implicit") 234 ~ ", " ~ valueStr ~ ")"; 235 } 236 237 /// If the value is an array of DictionaryEntries this will return a HashMap 238 DBusAny[DBusAny] toAA() { 239 enforce(type == 'a' && signature && signature[0] == '{'); 240 DBusAny[DBusAny] aa; 241 foreach(val; array) { 242 enforce(val.type == 'e'); 243 aa[val.entry.key] = val.entry.value; 244 } 245 return aa; 246 } 247 248 /// Converts a basic type, a tuple or an array to the D type with type checking. Tuples can get converted to an array too. 249 T to(T)() { 250 static if(is(T == Variant!R, R)) { 251 static if(is(R == DBusAny)) { 252 auto v = to!R; 253 v.explicitVariant = false; 254 return Variant!R(v); 255 } else 256 return Variant!R(to!R); 257 } else static if(is(T == DBusAny)) { 258 return this; 259 } else static if(isIntegral!T || isFloatingPoint!T) { 260 switch(type) { 261 case typeCode!byte: 262 return cast(T) int8; 263 case typeCode!short: 264 return cast(T) int16; 265 case typeCode!ushort: 266 return cast(T) uint16; 267 case typeCode!int: 268 return cast(T) int32; 269 case typeCode!uint: 270 return cast(T) uint32; 271 case typeCode!long: 272 return cast(T) int64; 273 case typeCode!ulong: 274 return cast(T) uint64; 275 case typeCode!double: 276 return cast(T) float64; 277 default: 278 throw new Exception("Can't convert type " ~ cast(char) type ~ " to " ~ T.stringof); 279 } 280 } else static if(is(T == bool)) { 281 if(type == 'b') 282 return boolean; 283 else 284 throw new Exception("Can't convert type " ~ cast(char) type ~ " to " ~ T.stringof); 285 } else static if(isSomeString!T) { 286 if(type == 's') 287 return str.to!T; 288 else 289 throw new Exception("Can't convert type " ~ cast(char) type ~ " to " ~ T.stringof); 290 } else static if(isDynamicArray!T) { 291 if(type != 'a' && type != 'r') 292 throw new Exception("Can't convert type " ~ cast(char) type ~ " to an array"); 293 T ret; 294 if(signature == ['y']) { 295 static if(isIntegral!(ElementType!T)) 296 foreach(elem; binaryData) 297 ret ~= elem.to!(ElementType!T); 298 } else 299 foreach(elem; array) 300 ret ~= elem.to!(ElementType!T); 301 return ret; 302 } else static if(isTuple!T) { 303 if(type != 'r') 304 throw new Exception("Can't convert type " ~ cast(char) type ~ " to " ~ T.stringof); 305 T ret; 306 enforce(ret.Types.length == tuple.length, "Tuple length mismatch"); 307 foreach(index, T; ret.Types) 308 ret[index] = tuple[index].to!T; 309 return ret; 310 } else static if(isAssociativeArray!T) { 311 if(type != 'a' || !signature || signature[0] != '{') 312 throw new Exception("Can't convert type " ~ cast(char) type ~ " to " ~ T.stringof); 313 T ret; 314 foreach(pair; array) { 315 enforce(pair.type == 'e'); 316 ret[pair.entry.key.to!(KeyType!T)] = pair.entry.value.to!(ValueType!T); 317 } 318 return ret; 319 } else static assert(false, "Can't convert variant to " ~ T.stringof); 320 } 321 322 bool opEquals(ref in DBusAny b) const { 323 if(b.type != type || b.explicitVariant != explicitVariant) 324 return false; 325 if((type == 'a' || type == 'r') && b.signature != signature) 326 return false; 327 if(type == 'a' && signature == ['y']) 328 return binaryData == b.binaryData; 329 if(type == 'a') 330 return array == b.array; 331 else if(type == 'r') 332 return tuple == b.tuple; 333 else if(type == 's') 334 return str == b.str; 335 else if(type == 'e') 336 return entry == b.entry || (entry && b.entry && *entry == *b.entry); 337 else 338 return uint64 == b.uint64; 339 } 340 } 341 342 unittest { 343 import dunit.toolkit; 344 DBusAny set(string member, T)(DBusAny v, T value) { 345 mixin("v." ~ member ~ " = value;"); 346 return v; 347 } 348 349 void test(T)(T value, DBusAny b) { 350 assertEqual(DBusAny(value), b); 351 assertEqual(b.to!T, value); 352 b.toString(); 353 } 354 355 test(cast(ubyte) 184, set!"int8"(DBusAny('y', null, false), cast(byte) 184)); 356 test(cast(short) 184, set!"int16"(DBusAny('n', null, false), cast(short) 184)); 357 test(cast(ushort) 184, set!"uint16"(DBusAny('q', null, false), cast(ushort) 184)); 358 test(cast(int) 184, set!"int32"(DBusAny('i', null, false), cast(int) 184)); 359 test(cast(uint) 184, set!"uint32"(DBusAny('u', null, false), cast(uint) 184)); 360 test(cast(long) 184, set!"int64"(DBusAny('x', null, false), cast(long) 184)); 361 test(cast(ulong) 184, set!"uint64"(DBusAny('t', null, false), cast(ulong) 184)); 362 test(true, set!"boolean"(DBusAny('b', null, false), true)); 363 test(cast(ubyte[]) [1, 2, 3], set!"binaryData"(DBusAny('a', ['y'], false), cast(ubyte[]) [1, 2, 3])); 364 365 test(variant(cast(ubyte) 184), set!"int8"(DBusAny('y', null, true), cast(byte) 184)); 366 test(variant(cast(short) 184), set!"int16"(DBusAny('n', null, true), cast(short) 184)); 367 test(variant(cast(ushort) 184), set!"uint16"(DBusAny('q', null, true), cast(ushort) 184)); 368 test(variant(cast(int) 184), set!"int32"(DBusAny('i', null, true), cast(int) 184)); 369 test(variant(cast(uint) 184), set!"uint32"(DBusAny('u', null, true), cast(uint) 184)); 370 test(variant(cast(long) 184), set!"int64"(DBusAny('x', null, true), cast(long) 184)); 371 test(variant(cast(ulong) 184), set!"uint64"(DBusAny('t', null, true), cast(ulong) 184)); 372 test(variant(true), set!"boolean"(DBusAny('b', null, true), true)); 373 test(variant(cast(ubyte[]) [1, 2, 3]), set!"binaryData"(DBusAny('a', ['y'], true), cast(ubyte[]) [1, 2, 3])); 374 375 test(variant(DBusAny(5)), set!"int32"(DBusAny('i', null, true), 5)); 376 377 test([1, 2, 3], set!"array"(DBusAny('a', ['i'], false), [DBusAny(1), DBusAny(2), DBusAny(3)])); 378 test(variant([1, 2, 3]), set!"array"(DBusAny('a', ['i'], true), [DBusAny(1), DBusAny(2), DBusAny(3)])); 379 380 test(tuple("a", 4, [1, 2]), set!"tuple"(DBusAny('r', "(siai)".dup, false), [DBusAny("a"), DBusAny(4), DBusAny([1, 2])])); 381 test(tuple("a", variant(4), variant([1, 2])), set!"tuple"(DBusAny('r', "(svv)".dup, false), [DBusAny("a"), DBusAny(variant(4)), DBusAny(variant([1, 2]))])); 382 383 test(["a": "b"], set!"array"(DBusAny('a', "{ss}".dup, false), [DBusAny(DictionaryEntry!(DBusAny, DBusAny)(DBusAny("a"), DBusAny("b")))])); 384 test([variant("a"): 4], set!"array"(DBusAny('a', "{vi}".dup, false), [DBusAny(DictionaryEntry!(DBusAny, DBusAny)(DBusAny(variant("a")), DBusAny(4)))])); 385 } 386 387 /// Marks the data as variant on serialization 388 struct Variant(T) { 389 /// 390 T data; 391 } 392 393 Variant!T variant(T)(T data) { 394 return Variant!T(data); 395 } 396 397 enum MessageType { 398 Invalid = 0, 399 Call, Return, Error, Signal 400 } 401 402 struct Message { 403 DBusMessage *msg; 404 405 this(string dest, string path, string iface, string method) { 406 msg = dbus_message_new_method_call(dest.toStringz(), path.toStringz(), iface.toStringz(), method.toStringz()); 407 } 408 409 this(DBusMessage *m) { 410 msg = m; 411 } 412 413 this(this) { 414 dbus_message_ref(msg); 415 } 416 417 ~this() { 418 dbus_message_unref(msg); 419 } 420 421 void build(TS...)(TS args) if(allCanDBus!TS) { 422 DBusMessageIter iter; 423 dbus_message_iter_init_append(msg, &iter); 424 buildIter(&iter, args); 425 } 426 427 /** 428 Reads the first argument of the message. 429 Note that this creates a new iterator every time so calling it multiple times will always 430 read the first argument. This is suitable for single item returns. 431 To read multiple arguments use readTuple. 432 */ 433 T read(T)() if(canDBus!T) { 434 DBusMessageIter iter; 435 dbus_message_iter_init(msg, &iter); 436 return readIter!T(&iter); 437 } 438 alias read to; 439 440 Tup readTuple(Tup)() if(isTuple!Tup && allCanDBus!(Tup.Types)) { 441 DBusMessageIter iter; 442 dbus_message_iter_init(msg, &iter); 443 Tup ret; 444 readIterTuple(&iter, ret); 445 return ret; 446 } 447 448 Message createReturn() { 449 return Message(dbus_message_new_method_return(msg)); 450 } 451 452 MessageType type() { 453 return cast(MessageType)dbus_message_get_type(msg); 454 } 455 456 bool isCall() { 457 return type() == MessageType.Call; 458 } 459 460 // Various string members 461 // TODO: make a mixin to avoid this copy-paste 462 string signature() { 463 const(char)* cStr = dbus_message_get_signature(msg); 464 assert(cStr != null); 465 return cStr.fromStringz().idup; 466 } 467 string path() { 468 const(char)* cStr = dbus_message_get_path(msg); 469 assert(cStr != null); 470 return cStr.fromStringz().idup; 471 } 472 string iface() { 473 const(char)* cStr = dbus_message_get_interface(msg); 474 assert(cStr != null); 475 return cStr.fromStringz().idup; 476 } 477 string member() { 478 const(char)* cStr = dbus_message_get_member(msg); 479 assert(cStr != null); 480 return cStr.fromStringz().idup; 481 } 482 string sender() { 483 const(char)* cStr = dbus_message_get_sender(msg); 484 assert(cStr != null); 485 return cStr.fromStringz().idup; 486 } 487 } 488 489 unittest { 490 import dunit.toolkit; 491 auto msg = Message("org.example.test", "/test","org.example.testing","testMethod"); 492 msg.path().assertEqual("/test"); 493 } 494 495 struct Connection { 496 DBusConnection *conn; 497 this(DBusConnection *connection) { 498 conn = connection; 499 } 500 501 this(this) { 502 dbus_connection_ref(conn); 503 } 504 505 ~this() { 506 dbus_connection_unref(conn); 507 } 508 509 void close() { 510 dbus_connection_close(conn); 511 } 512 513 void send(Message msg) { 514 dbus_connection_send(conn,msg.msg, null); 515 } 516 517 void sendBlocking(Message msg) { 518 send(msg); 519 dbus_connection_flush(conn); 520 } 521 522 Message sendWithReplyBlocking(Message msg, int timeout = -1) { 523 DBusMessage *dbusMsg = msg.msg; 524 dbus_message_ref(dbusMsg); 525 DBusMessage *reply = wrapErrors((err) { 526 auto ret = dbus_connection_send_with_reply_and_block(conn,dbusMsg,timeout,err); 527 dbus_message_unref(dbusMsg); 528 return ret; 529 }); 530 return Message(reply); 531 } 532 } 533 534 Connection connectToBus(DBusBusType bus = DBusBusType.DBUS_BUS_SESSION) { 535 DBusConnection *conn = wrapErrors((err) { return dbus_bus_get(bus,err); }); 536 return Connection(conn); 537 } 538 539 unittest { 540 import dunit.toolkit; 541 // This test will only pass if DBus is installed. 542 Connection conn = connectToBus(); 543 conn.conn.assertTruthy(); 544 // We can only count on no system bus on OSX 545 version(OSX) { 546 connectToBus(DBusBusType.DBUS_BUS_SYSTEM).assertThrow!DBusException(); 547 } 548 }