1 /// Thin OO wrapper around DBus types 2 module ddbus.thin; 3 4 import core.time : Duration; 5 6 import ddbus.attributes : isAllowedField; 7 import ddbus.c_lib; 8 import ddbus.conv; 9 import ddbus.exception : TypeMismatchException; 10 import ddbus.util; 11 12 import std.meta : ApplyRight, Filter, staticIndexOf; 13 import std..string; 14 import std.typecons; 15 import std.exception; 16 import std.traits; 17 import std.conv; 18 import std.range; 19 import std.algorithm; 20 21 // This import is public for backwards compatibility 22 public import ddbus.exception : wrapErrors, DBusException; 23 24 struct ObjectPath { 25 private string _value; 26 27 this(string objPath) pure @safe { 28 enforce(isValid(objPath)); 29 _value = objPath; 30 } 31 32 string toString() const { 33 return _value; 34 } 35 36 /++ 37 Returns the string representation of this ObjectPath. 38 +/ 39 string value() const pure @nogc nothrow @safe { 40 return _value; 41 } 42 43 size_t toHash() const pure @nogc nothrow @trusted { 44 return hashOf(_value); 45 } 46 47 bool opEquals(ref const typeof(this) b) const pure @nogc nothrow @safe { 48 return _value == b._value; 49 } 50 51 ObjectPath opBinary(string op : "~")(string rhs) const pure @safe { 52 if (!rhs.startsWith("/")) { 53 return opBinary!"~"(ObjectPath("/" ~ rhs)); 54 } else { 55 return opBinary!"~"(ObjectPath(rhs)); 56 } 57 } 58 59 ObjectPath opBinary(string op : "~")(ObjectPath rhs) const pure @safe 60 in { 61 assert(ObjectPath.isValid(_value) && ObjectPath.isValid(rhs._value)); 62 } 63 out (v) { 64 assert(ObjectPath.isValid(v._value)); 65 } 66 do { 67 ObjectPath ret; 68 69 if (_value == "/") { 70 ret._value = rhs._value; 71 } else { 72 ret._value = _value ~ rhs._value; 73 } 74 75 return ret; 76 } 77 78 void opOpAssign(string op : "~")(string rhs) pure @safe { 79 _value = opBinary!"~"(rhs)._value; 80 } 81 82 void opOpAssign(string op : "~")(ObjectPath rhs) pure @safe { 83 _value = opBinary!"~"(rhs)._value; 84 } 85 86 /++ 87 Returns: `false` for empty strings or strings that don't match the 88 pattern `(/[0-9A-Za-z_]+)+|/`. 89 +/ 90 static bool isValid(string objPath) pure @nogc nothrow @safe { 91 import std.ascii : isAlphaNum; 92 93 if (!objPath.length) { 94 return false; 95 } 96 97 if (objPath == "/") { 98 return true; 99 } 100 101 if (objPath[0] != '/' || objPath[$ - 1] == '/') { 102 return false; 103 } 104 105 // .representation to avoid unicode exceptions -> @nogc & nothrow 106 return objPath.representation.splitter('/').drop(1).all!(a => a.length 107 && a.all!(c => c.isAlphaNum || c == '_')); 108 } 109 } 110 111 unittest { 112 import dunit.toolkit; 113 114 ObjectPath("some.invalid/object_path").assertThrow(); 115 ObjectPath("/path/with/TrailingSlash/").assertThrow(); 116 ObjectPath("/path/without/TrailingSlash").assertNotThrown(); 117 string path = "/org/freedesktop/DBus"; 118 auto obj = ObjectPath(path); 119 obj.value.assertEqual(path); 120 obj.toHash().assertEqual(path.hashOf); 121 } 122 123 unittest { 124 import dunit.toolkit; 125 126 ObjectPath a = ObjectPath("/org/freedesktop"); 127 a.assertEqual(ObjectPath("/org/freedesktop")); 128 a ~= ObjectPath("/UPower"); 129 a.assertEqual(ObjectPath("/org/freedesktop/UPower")); 130 a ~= "Device"; 131 a.assertEqual(ObjectPath("/org/freedesktop/UPower/Device")); 132 (a ~ "0").assertEqual(ObjectPath("/org/freedesktop/UPower/Device/0")); 133 a.assertEqual(ObjectPath("/org/freedesktop/UPower/Device")); 134 } 135 136 /// Structure allowing typeless parameters 137 struct DBusAny { 138 /// DBus type of the value (never 'v'), see typeSig!T 139 int type; 140 /// Child signature for Arrays & Tuples 141 string signature; 142 /// If true, this value will get serialized as variant value, otherwise it is serialized like it wasn't in a DBusAny wrapper. 143 /// Same functionality as Variant!T but with dynamic types if true. 144 bool explicitVariant; 145 146 union { 147 /// 148 byte int8; 149 /// 150 short int16; 151 /// 152 ushort uint16; 153 /// 154 int int32; 155 /// 156 uint uint32; 157 /// 158 long int64; 159 /// 160 ulong uint64; 161 /// 162 double float64; 163 /// 164 string str; 165 /// 166 bool boolean; 167 /// 168 ObjectPath obj; 169 /// 170 DBusAny[] array; 171 /// 172 alias tuple = array; 173 /// 174 DictionaryEntry!(DBusAny, DBusAny)* entry; 175 /// 176 ubyte[] binaryData; 177 } 178 179 /++ 180 Manually creates a DBusAny object using a type, signature and explicit 181 variant specifier. 182 183 Direct use of this constructor from user code should be avoided. 184 +/ 185 this(int type, string signature, bool explicit) { 186 this.type = type; 187 this.signature = signature; 188 this.explicitVariant = explicit; 189 } 190 191 /++ 192 Automatically creates a DBusAny object with fitting parameters from a D 193 type or Variant!T. 194 195 Pass a `Variant!T` to make this an explicit variant. 196 +/ 197 this(T)(T value) { 198 static if (is(T == byte) || is(T == ubyte)) { 199 this(typeCode!byte, null, false); 200 int8 = cast(byte) value; 201 } else static if (is(T == short)) { 202 this(typeCode!short, null, false); 203 int16 = cast(short) value; 204 } else static if (is(T == ushort)) { 205 this(typeCode!ushort, null, false); 206 uint16 = cast(ushort) value; 207 } else static if (is(T == int)) { 208 this(typeCode!int, null, false); 209 int32 = cast(int) value; 210 } else static if (is(T == uint)) { 211 this(typeCode!uint, null, false); 212 uint32 = cast(uint) value; 213 } else static if (is(T == long)) { 214 this(typeCode!long, null, false); 215 int64 = cast(long) value; 216 } else static if (is(T == ulong)) { 217 this(typeCode!ulong, null, false); 218 uint64 = cast(ulong) value; 219 } else static if (is(T == double)) { 220 this(typeCode!double, null, false); 221 float64 = cast(double) value; 222 } else static if (isSomeString!T) { 223 this(typeCode!string, null, false); 224 str = value.to!string; 225 } else static if (is(T == bool)) { 226 this(typeCode!bool, null, false); 227 boolean = cast(bool) value; 228 } else static if (is(T == ObjectPath)) { 229 this(typeCode!ObjectPath, null, false); 230 obj = value; 231 } else static if (is(T == Variant!R, R)) { 232 static if (is(R == DBusAny)) { 233 type = value.data.type; 234 signature = value.data.signature; 235 explicitVariant = true; 236 if (type == 'a' || type == 'r') { 237 if (signature == ['y']) { 238 binaryData = value.data.binaryData; 239 } else { 240 array = value.data.array; 241 } 242 } else if (type == 's') { 243 str = value.data.str; 244 } else if (type == 'e') { 245 entry = value.data.entry; 246 } else { 247 uint64 = value.data.uint64; 248 } 249 } else { 250 this(value.data); 251 explicitVariant = true; 252 } 253 } else static if (is(T : DictionaryEntry!(K, V), K, V)) { 254 this('e', null, false); 255 entry = new DictionaryEntry!(DBusAny, DBusAny)(); 256 static if (is(K == DBusAny)) { 257 entry.key = value.key; 258 } else { 259 entry.key = DBusAny(value.key); 260 } 261 static if (is(V == DBusAny)) { 262 entry.value = value.value; 263 } else { 264 entry.value = DBusAny(value.value); 265 } 266 } else static if (is(T == ubyte[]) || is(T == byte[])) { 267 this('a', ['y'], false); 268 binaryData = cast(ubyte[]) value; 269 } else static if (isInputRange!T) { 270 this.type = 'a'; 271 272 static assert(!is(ElementType!T == DBusAny), 273 "Array must consist of the same type, use Variant!DBusAny or DBusAny(tuple(...)) instead"); 274 275 static assert(.typeSig!(ElementType!T) != "y"); 276 277 this.signature = .typeSig!(ElementType!T); 278 this.explicitVariant = false; 279 280 foreach (elem; value) { 281 array ~= DBusAny(elem); 282 } 283 } else static if (isTuple!T) { 284 this.type = 'r'; 285 this.signature = ['(']; 286 this.explicitVariant = false; 287 288 foreach (index, R; value.Types) { 289 auto var = DBusAny(value[index]); 290 tuple ~= var; 291 292 if (var.explicitVariant) { 293 this.signature ~= 'v'; 294 } else { 295 if (var.type != 'r') { 296 this.signature ~= cast(char) var.type; 297 } 298 299 if (var.type == 'a' || var.type == 'r') { 300 this.signature ~= var.signature; 301 } 302 } 303 } 304 305 this.signature ~= ')'; 306 } else static if (is(T == struct) && canDBus!T) { 307 this.type = 'r'; 308 this.signature = ['(']; 309 this.explicitVariant = false; 310 foreach (index, R; Fields!T) { 311 static if (isAllowedField!(value.tupleof[index])) { 312 auto var = DBusAny(value.tupleof[index]); 313 tuple ~= var; 314 if (var.explicitVariant) 315 this.signature ~= 'v'; 316 else { 317 if (var.type != 'r') 318 this.signature ~= cast(char) var.type; 319 if (var.type == 'a' || var.type == 'r') 320 this.signature ~= var.signature; 321 } 322 } 323 } 324 this.signature ~= ')'; 325 } else static if (isAssociativeArray!T) { 326 this(value.byDictionaryEntries); 327 } else { 328 static assert(false, T.stringof ~ " not convertible to a Variant"); 329 } 330 } 331 332 /// 333 string toString() const { 334 string valueStr; 335 switch (type) { 336 case typeCode!byte: 337 valueStr = int8.to!string; 338 break; 339 case typeCode!short: 340 valueStr = int16.to!string; 341 break; 342 case typeCode!ushort: 343 valueStr = uint16.to!string; 344 break; 345 case typeCode!int: 346 valueStr = int32.to!string; 347 break; 348 case typeCode!uint: 349 valueStr = uint32.to!string; 350 break; 351 case typeCode!long: 352 valueStr = int64.to!string; 353 break; 354 case typeCode!ulong: 355 valueStr = uint64.to!string; 356 break; 357 case typeCode!double: 358 valueStr = float64.to!string; 359 break; 360 case typeCode!string: 361 valueStr = '"' ~ str ~ '"'; 362 break; 363 case typeCode!ObjectPath: 364 valueStr = '"' ~ obj.to!string ~ '"'; 365 break; 366 case typeCode!bool: 367 valueStr = boolean ? "true" : "false"; 368 break; 369 case 'a': 370 import std.digest.digest : toHexString; 371 372 if (signature == ['y']) { 373 valueStr = "binary(" ~ binaryData.toHexString ~ ')'; 374 } else { 375 valueStr = '[' ~ array.map!(a => a.toString).join(", ") ~ ']'; 376 } 377 378 break; 379 case 'r': 380 valueStr = '(' ~ tuple.map!(a => a.toString).join(", ") ~ ')'; 381 break; 382 case 'e': 383 valueStr = entry.key.toString ~ ": " ~ entry.value.toString; 384 break; 385 default: 386 valueStr = "unknown"; 387 break; 388 } 389 390 return "DBusAny(" ~ cast(char) type ~ ", \"" ~ signature.idup ~ "\", " ~ (explicitVariant 391 ? "explicit" : "implicit") ~ ", " ~ valueStr ~ ")"; 392 } 393 394 /++ 395 Get the value stored in the DBusAny object. 396 397 Parameters: 398 T = The requested type. The currently stored value must match the 399 requested type exactly. 400 401 Returns: 402 The current value of the DBusAny object. 403 404 Throws: 405 TypeMismatchException if the DBus type of the current value of the 406 DBusAny object is not the same as the DBus type used to represent T. 407 +/ 408 T get(T)() @property const 409 if (staticIndexOf!(T, BasicTypes) >= 0) { 410 enforce(type == typeCode!T, new TypeMismatchException( 411 "Cannot get a " ~ T.stringof ~ " from a DBusAny with" ~ " a value of DBus type '" ~ typeSig ~ "'.", 412 typeCode!T, type)); 413 414 static if (isIntegral!T) { 415 enum memberName = (isUnsigned!T ? "uint" : "int") ~ (T.sizeof * 8).to!string; 416 return __traits(getMember, this, memberName); 417 } else static if (is(T == double)) { 418 return float64; 419 } else static if (is(T == string)) { 420 return str; 421 } else static if (is(T == ObjectPath)) { 422 return obj; 423 } else static if (is(T == bool)) { 424 return boolean; 425 } else { 426 static assert(false); 427 } 428 } 429 430 /// ditto 431 T get(T)() @property const 432 if (is(T == const(DBusAny)[])) { 433 enforce((type == 'a' && signature != "y") || type == 'r', new TypeMismatchException( 434 "Cannot get a " ~ T.stringof ~ " from a DBusAny with" ~ " a value of DBus type '" ~ this.typeSig ~ "'.", 435 'a', type)); 436 437 return array; 438 } 439 440 /// ditto 441 T get(T)() @property const 442 if (is(T == const(ubyte)[])) { 443 enforce(type == 'a' && signature == "y", new TypeMismatchException( 444 "Cannot get a " ~ T.stringof ~ " from a DBusAny with" ~ " a value of DBus type '" ~ this.typeSig ~ "'.", 445 'a', type)); 446 447 return binaryData; 448 } 449 450 /// If the value is an array of DictionaryEntries this will return a HashMap 451 deprecated("Please use to!(V[K])") DBusAny[DBusAny] toAA() { 452 enforce(type == 'a' && signature && signature[0] == '{'); 453 DBusAny[DBusAny] aa; 454 455 foreach (val; array) { 456 enforce(val.type == 'e'); 457 aa[val.entry.key] = val.entry.value; 458 } 459 460 return aa; 461 } 462 463 /++ 464 Get the DBus type signature of the value stored in the DBusAny object. 465 466 Returns: 467 The type signature of the value stored in this DBusAny object. 468 +/ 469 string typeSig() @property const pure nothrow @safe { 470 if (type == 'a') { 471 return "a" ~ signature; 472 } else if (type == 'r') { 473 return signature; 474 } else if (type == 'e') { 475 return () @trusted{ 476 return "{" ~ entry.key.signature ~ entry.value.signature ~ "}"; 477 }(); 478 } else { 479 return [cast(char) type]; 480 } 481 } 482 483 /++ 484 Converts a basic type, a tuple or an array to the D type with type checking. 485 486 Tuples can be converted to an array of DBusAny, but not to any other array. 487 +/ 488 T to(T)() @property const pure { 489 // Just use `get` if possible 490 static if (canDBus!T && __traits(compiles, get!T)) { 491 if (this.typeSig == .typeSig!T) 492 return get!T; 493 } 494 495 // If we get here, we need some type conversion 496 static if (is(T == Variant!R, R)) { 497 static if (is(R == DBusAny)) { 498 auto v = to!R; 499 v.explicitVariant = false; 500 return Variant!R(v); 501 } else { 502 return Variant!R(to!R); 503 } 504 } else static if (is(T == DBusAny)) { 505 return this; 506 } else { 507 // In here are all static if blocks that may fall through to the throw 508 // statement at the bottom of this block. 509 510 static if (is(T == DictionaryEntry!(K, V), K, V)) { 511 if (type == 'e') { 512 static if (is(T == typeof(entry))) { 513 return entry; 514 } else { 515 return DictionaryEntry(entry.key.to!K, entry.value.to!V); 516 } 517 } 518 } else static if (isAssociativeArray!T) { 519 if (type == 'a' && (!array.length || array[0].type == 'e')) { 520 alias K = Unqual!(KeyType!T); 521 alias V = Unqual!(ValueType!T); 522 V[K] ret; 523 524 foreach (pair; array) { 525 assert(pair.type == 'e'); 526 ret[pair.entry.key.to!K] = pair.entry.value.to!V; 527 } 528 529 return cast(T) ret; 530 } 531 } else static if (isDynamicArray!T && !isSomeString!T) { 532 alias E = Unqual!(ElementType!T); 533 534 if (typeSig == "ay") { 535 auto data = get!(const(ubyte)[]); 536 static if (is(E == ubyte) || is(E == byte)) { 537 return cast(T) data.dup; 538 } else { 539 return cast(T) data.map!(elem => elem.to!E).array; 540 } 541 } else if (type == 'a' || (type == 'r' && is(E == DBusAny))) { 542 return cast(T) get!(const(DBusAny)[]).map!(elem => elem.to!E).array; 543 } 544 } else static if (isTuple!T) { 545 if (type == 'r') { 546 T ret; 547 548 foreach (i, T; ret.Types) { 549 ret[i] = tuple[i].to!T; 550 } 551 552 return ret; 553 } 554 } else static if (is(T == struct) && canDBus!T) { 555 if (type == 'r') { 556 T ret; 557 size_t j; 558 559 foreach (i, F; Fields!T) { 560 static if (isAllowedField!(ret.tupleof[i])) { 561 ret.tupleof[i] = tuple[j++].to!F; 562 } 563 } 564 565 return ret; 566 } 567 } else { 568 alias isPreciselyConvertible = ApplyRight!(isImplicitlyConvertible, T); 569 570 template isUnpreciselyConvertible(S) { 571 enum isUnpreciselyConvertible = !isPreciselyConvertible!S 572 && __traits(compiles, get!S.to!T); 573 } 574 575 // Try to be precise 576 foreach (B; Filter!(isPreciselyConvertible, BasicTypes)) { 577 if (type == typeCode!B) 578 return get!B; 579 } 580 581 // Try to convert 582 foreach (B; Filter!(isUnpreciselyConvertible, BasicTypes)) { 583 if (type == typeCode!B) 584 return get!B.to!T; 585 } 586 } 587 588 throw new ConvException("Cannot convert from DBus type '" ~ this.typeSig ~ "' to " 589 ~ T.stringof); 590 } 591 } 592 593 bool opEquals(ref in DBusAny b) const { 594 if (b.type != type || b.explicitVariant != explicitVariant) { 595 return false; 596 } 597 598 if ((type == 'a' || type == 'r') && b.signature != signature) { 599 return false; 600 } 601 602 if (type == 'a' && signature == ['y']) { 603 return binaryData == b.binaryData; 604 } 605 606 if (type == 'a') { 607 return array == b.array; 608 } else if (type == 'r') { 609 return tuple == b.tuple; 610 } else if (type == 's') { 611 return str == b.str; 612 } else if (type == 'o') { 613 return obj == b.obj; 614 } else if (type == 'e') { 615 return entry == b.entry || (entry && b.entry && *entry == *b.entry); 616 } else { 617 return uint64 == b.uint64; 618 } 619 } 620 } 621 622 unittest { 623 import dunit.toolkit; 624 625 DBusAny set(string member, T)(DBusAny v, T value) { 626 mixin("v." ~ member ~ " = value;"); 627 return v; 628 } 629 630 void test(T)(T value, DBusAny b) { 631 assertEqual(DBusAny(value), b); 632 assertEqual(b.to!T, value); 633 b.toString(); 634 } 635 636 test(cast(ubyte) 184, set!"int8"(DBusAny('y', null, false), cast(byte) 184)); 637 test(cast(short) 184, set!"int16"(DBusAny('n', null, false), cast(short) 184)); 638 test(cast(ushort) 184, set!"uint16"(DBusAny('q', null, false), cast(ushort) 184)); 639 test(cast(int) 184, set!"int32"(DBusAny('i', null, false), cast(int) 184)); 640 test(cast(uint) 184, set!"uint32"(DBusAny('u', null, false), cast(uint) 184)); 641 test(cast(long) 184, set!"int64"(DBusAny('x', null, false), cast(long) 184)); 642 test(cast(ulong) 184, set!"uint64"(DBusAny('t', null, false), cast(ulong) 184)); 643 test(1.84, set!"float64"(DBusAny('d', null, false), 1.84)); 644 test(true, set!"boolean"(DBusAny('b', null, false), true)); 645 test("abc", set!"str"(DBusAny('s', null, false), "abc")); 646 test(ObjectPath("/foo/Bar"), set!"obj"(DBusAny('o', null, false), ObjectPath("/foo/Bar"))); 647 test(cast(ubyte[])[1, 2, 3], set!"binaryData"(DBusAny('a', ['y'], false), 648 cast(ubyte[])[1, 2, 3])); 649 650 test(variant(cast(ubyte) 184), set!"int8"(DBusAny('y', null, true), cast(byte) 184)); 651 test(variant(cast(short) 184), set!"int16"(DBusAny('n', null, true), cast(short) 184)); 652 test(variant(cast(ushort) 184), set!"uint16"(DBusAny('q', null, true), cast(ushort) 184)); 653 test(variant(cast(int) 184), set!"int32"(DBusAny('i', null, true), cast(int) 184)); 654 test(variant(cast(uint) 184), set!"uint32"(DBusAny('u', null, true), cast(uint) 184)); 655 test(variant(cast(long) 184), set!"int64"(DBusAny('x', null, true), cast(long) 184)); 656 test(variant(cast(ulong) 184), set!"uint64"(DBusAny('t', null, true), cast(ulong) 184)); 657 test(variant(1.84), set!"float64"(DBusAny('d', null, true), 1.84)); 658 test(variant(true), set!"boolean"(DBusAny('b', null, true), true)); 659 test(variant("abc"), set!"str"(DBusAny('s', null, true), "abc")); 660 test(variant(ObjectPath("/foo/Bar")), set!"obj"(DBusAny('o', null, true), 661 ObjectPath("/foo/Bar"))); 662 test(variant(cast(ubyte[])[1, 2, 3]), set!"binaryData"(DBusAny('a', ['y'], 663 true), cast(ubyte[])[1, 2, 3])); 664 665 test(variant(DBusAny(5)), set!"int32"(DBusAny('i', null, true), 5)); 666 667 test([1, 2, 3], set!"array"(DBusAny('a', ['i'], false), [DBusAny(1), DBusAny(2), DBusAny(3)])); 668 test(variant([1, 2, 3]), set!"array"(DBusAny('a', ['i'], true), [DBusAny(1), 669 DBusAny(2), DBusAny(3)])); 670 671 test(tuple("a", 4, [1, 2]), set!"tuple"(DBusAny('r', "(siai)".dup, false), 672 [DBusAny("a"), DBusAny(4), DBusAny([1, 2])])); 673 test(tuple("a", variant(4), variant([1, 2])), set!"tuple"(DBusAny('r', 674 "(svv)", false), [DBusAny("a"), DBusAny(variant(4)), DBusAny(variant([1, 2]))])); 675 676 test(["a" : "b"], set!"array"(DBusAny('a', "{ss}", false), 677 [DBusAny(DictionaryEntry!(DBusAny, DBusAny)(DBusAny("a"), DBusAny("b")))])); 678 test([variant("a") : 4], set!"array"(DBusAny('a', "{vi}", false), 679 [DBusAny(DictionaryEntry!(DBusAny, DBusAny)(DBusAny(variant("a")), DBusAny(4)))])); 680 } 681 682 /// Marks the data as variant on serialization 683 struct Variant(T) { 684 /// 685 T data; 686 } 687 688 Variant!T variant(T)(T data) { 689 return Variant!T(data); 690 } 691 692 enum MessageType { 693 Invalid = 0, 694 Call, 695 Return, 696 Error, 697 Signal 698 } 699 700 struct Message { 701 DBusMessage* msg; 702 703 this(string dest, string path, string iface, string method) { 704 msg = dbus_message_new_method_call(dest.toStringz(), path.toStringz(), 705 iface.toStringz(), method.toStringz()); 706 } 707 708 this(DBusMessage* m) { 709 msg = m; 710 } 711 712 this(this) { 713 dbus_message_ref(msg); 714 } 715 716 ~this() { 717 dbus_message_unref(msg); 718 } 719 720 void build(TS...)(TS args) 721 if (allCanDBus!TS) { 722 DBusMessageIter iter; 723 dbus_message_iter_init_append(msg, &iter); 724 buildIter(&iter, args); 725 } 726 727 /** 728 Reads the first argument of the message. 729 Note that this creates a new iterator every time so calling it multiple times will always 730 read the first argument. This is suitable for single item returns. 731 To read multiple arguments use readTuple. 732 */ 733 T read(T)() 734 if (canDBus!T) { 735 DBusMessageIter iter; 736 dbus_message_iter_init(msg, &iter); 737 return readIter!T(&iter); 738 } 739 740 alias read to; 741 742 Tup readTuple(Tup)() 743 if (isTuple!Tup && allCanDBus!(Tup.Types)) { 744 DBusMessageIter iter; 745 dbus_message_iter_init(msg, &iter); 746 Tup ret; 747 readIterTuple(&iter, ret); 748 return ret; 749 } 750 751 Message createReturn() { 752 return Message(dbus_message_new_method_return(msg)); 753 } 754 755 MessageType type() { 756 return cast(MessageType) dbus_message_get_type(msg); 757 } 758 759 bool isCall() { 760 return type() == MessageType.Call; 761 } 762 763 // Various string members 764 // TODO: make a mixin to avoid this copy-paste 765 string signature() { 766 const(char)* cStr = dbus_message_get_signature(msg); 767 assert(cStr != null); 768 return cStr.fromStringz().idup; 769 } 770 771 string path() { 772 const(char)* cStr = dbus_message_get_path(msg); 773 assert(cStr != null); 774 return cStr.fromStringz().idup; 775 } 776 777 string iface() { 778 const(char)* cStr = dbus_message_get_interface(msg); 779 assert(cStr != null); 780 return cStr.fromStringz().idup; 781 } 782 783 string member() { 784 const(char)* cStr = dbus_message_get_member(msg); 785 assert(cStr != null); 786 return cStr.fromStringz().idup; 787 } 788 789 string sender() { 790 const(char)* cStr = dbus_message_get_sender(msg); 791 assert(cStr != null); 792 return cStr.fromStringz().idup; 793 } 794 } 795 796 unittest { 797 import dunit.toolkit; 798 799 auto msg = Message("org.example.test", "/test", "org.example.testing", "testMethod"); 800 msg.path().assertEqual("/test"); 801 } 802 803 struct Connection { 804 DBusConnection* conn; 805 this(DBusConnection* connection) { 806 conn = connection; 807 } 808 809 this(this) { 810 dbus_connection_ref(conn); 811 } 812 813 ~this() { 814 dbus_connection_unref(conn); 815 } 816 817 void close() { 818 dbus_connection_close(conn); 819 } 820 821 void send(Message msg) { 822 dbus_connection_send(conn, msg.msg, null); 823 } 824 825 void sendBlocking(Message msg) { 826 send(msg); 827 dbus_connection_flush(conn); 828 } 829 830 Message sendWithReplyBlocking(Message msg, int timeout = -1) { 831 DBusMessage* dbusMsg = msg.msg; 832 dbus_message_ref(dbusMsg); 833 DBusMessage* reply = wrapErrors((err) { 834 auto ret = dbus_connection_send_with_reply_and_block(conn, dbusMsg, timeout, err); 835 dbus_message_unref(dbusMsg); 836 return ret; 837 }); 838 return Message(reply); 839 } 840 841 Message sendWithReplyBlocking(Message msg, Duration timeout) { 842 return sendWithReplyBlocking(msg, timeout.total!"msecs"().to!int); 843 } 844 } 845 846 unittest { 847 import dunit.toolkit; 848 import ddbus.attributes : dbusMarshaling, MarshalingFlag; 849 850 struct S1 { 851 private int a; 852 private @(Yes.DBusMarshal) double b; 853 string s; 854 } 855 856 @dbusMarshaling(MarshalingFlag.manualOnly) 857 struct S2 { 858 int h, i; 859 @(Yes.DBusMarshal) int j, k; 860 int* p; 861 } 862 863 @dbusMarshaling(MarshalingFlag.includePrivateFields) 864 struct S3 { 865 private Variant!int c; 866 string d; 867 S1 e; 868 S2 f; 869 @(No.DBusMarshal) uint g; 870 } 871 872 Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth3"); 873 874 __gshared int dummy; 875 876 enum testStruct = S3(variant(5), "blah", S1(-7, 63.5, "test"), S2(84, -123, 877 78, 432, &dummy), 16); 878 879 // Non-marshaled fields should appear as freshly initialized 880 enum expectedResult = S3(variant(5), "blah", S1(int.init, 63.5, "test"), 881 S2(int.init, int.init, 78, 432, null), uint.init); 882 883 // Test struct conversion in building/reading messages 884 msg.build(testStruct); 885 msg.read!S3().assertEqual(expectedResult); 886 887 // Test struct conversion in DBusAny 888 DBusAny(testStruct).to!S3.assertEqual(expectedResult); 889 } 890 891 Connection connectToBus(DBusBusType bus = DBusBusType.DBUS_BUS_SESSION) { 892 DBusConnection* conn = wrapErrors((err) { return dbus_bus_get(bus, err); }); 893 return Connection(conn); 894 } 895 896 unittest { 897 import dunit.toolkit; 898 899 // This test will only pass if DBus is installed. 900 Connection conn = connectToBus(); 901 conn.conn.assertTruthy(); 902 // We can only count on no system bus on OSX 903 version (OSX) { 904 connectToBus(DBusBusType.DBUS_BUS_SYSTEM).assertThrow!DBusException(); 905 } 906 }