diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index a8d4c19..11683d7 100644 --- a/source/ddbus/conv.d +++ b/source/ddbus/conv.d @@ -5,6 +5,7 @@ import ddbus.exception : InvalidValueException, TypeMismatchException; import ddbus.util; import ddbus.thin; import std.exception : enforce; +import std.meta: allSatisfy; import std.string; import std.typecons; import std.range; @@ -112,6 +113,26 @@ void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) { const(char)* subSig = typeSig!(VariantType!T).toStringz(); dbus_message_iter_open_container(iter, 'v', subSig, &sub); buildIter(&sub, arg.data); + dbus_message_iter_close_container(iter, &sub); + } else static if(is(T == struct)) { + DBusMessageIter sub; + dbus_message_iter_open_container(iter, 'r', null, &sub); + + // Following failed because of missing 'this' for members of arg. + // That sucks. It worked without Filter. + // Reported: https://issues.dlang.org/show_bug.cgi?id=17692 +// buildIter(&sub, Filter!(isAllowedField, arg.tupleof)); + + // Using foreach to work around the issue + foreach(i, member; arg.tupleof) { + // Ugly, but we need to use tupleof again in the condition, because when + // we use `member`, isAllowedField will fail because it'll find this + // nice `buildIter` function instead of T when it looks up the parent + // scope of its argument. + static if (isAllowedField!(arg.tupleof[i])) + buildIter(&sub, member); + } + dbus_message_iter_close_container(iter, &sub); } else static if(basicDBus!T) { dbus_message_iter_append_basic(iter,typeCode!T,&arg); @@ -265,6 +286,10 @@ T readIter(T)(DBusMessageIter *iter) if (!is(T == enum) && !isInstanceOf!(BitFla ret.entry.key = readIter!DBusAny(&sub); ret.entry.value = readIter!DBusAny(&sub); } + } else static if(is(T == struct)) { + DBusMessageIter sub; + dbus_message_iter_recurse(iter, &sub); + readIterStruct!T(&sub, ret); } else static if(basicDBus!T) { dbus_message_iter_get_basic(iter, &ret); } @@ -279,6 +304,73 @@ void readIterTuple(Tup)(DBusMessageIter *iter, ref Tup tuple) if(isTuple!Tup && } } +void readIterStruct(S)(DBusMessageIter *iter, ref S s) if(is(S == struct) && allCanDBus!(Fields!S)) { + foreach(index, T; Fields!S) { + static if (isAllowedField!(s.tupleof[index])) { + s.tupleof[index] = readIter!T(iter); + } + } +} + +/++ + Flags for use with dbusMarshaling UDA + + Default is to include public fields only ++/ +enum MarshalingFlag : ubyte { + includePrivateFields = 1 << 0, /// Automatically include private fields + manualOnly = 1 << 7 /// Only include fields with explicit + /// `@Yes.DBusMarshal`. This overrides any + /// `include` flags. +} + +private template isMarshalingFlag(T) { + enum isMarshalingFlag = is(T == MarshalingFlag); +} + +/++ + UDA for specifying DBus marshaling options on structs ++/ +auto dbusMarshaling(Args)(Args args ...) + if (allSatisfy!(isMarshalingFlag, Args)) { + return BitFlags!MarshalingFlag(args); +} + +private template marshalingFlags(S) if (is(S == struct)) { + private alias getUDAs!(S, BitFlags!MarshalingFlag) UDAs; + + static if (UDAs.length == 0) + enum marshalingFlags = BitFlags!MarshalingFlag.init; + else { + static assert (UDAs.length == 1, + "Only one @dbusMarshaling UDA allowed on type."); + static assert (is(typeof(UDAs[0]) == BitFlags!MarshalingFlag), + "Huh? Did you intend to use @dbusMarshaling UDA?"); + enum marshalingFlags = UDAs[0]; + } +} + +private template isAllowedField(alias field) { + private enum flags = marshalingFlags!(__traits(parent, field)); + private alias getUDAs!(field, Flag!"DBusMarshal") UDAs; + + static if (UDAs.length != 0) { + static assert (UDAs.length == 1, + "Only one UDA of type Flag!\"DBusMarshal\" allowed on struct field."); + static assert (is(typeof(UDAs[0]) == Flag!"DBusMarshal"), + "Did you intend to add UDA Yes.DBusMarshal or No.DBusMarshal?"); + enum isAllowedField = cast(bool) UDAs[0]; + } else static if (!(flags & MarshalingFlag.manualOnly)) { + static if (__traits(getProtection, field) == "public") + enum isAllowedField = true; + else static if (cast(bool) (flags & MarshalingFlag.includePrivateFields)) + enum isAllowedField = true; + else + enum isAllowedField = false; + } else + enum isAllowedField = false; +} + unittest { import dunit.toolkit; import ddbus.thin; @@ -355,3 +447,4 @@ unittest { readIter!F(&iter).assertThrow!InvalidValueException(); readIter!(BitFlags!F)(&iter2).assertThrow!InvalidValueException(); } + diff --git a/source/ddbus/thin.d b/source/ddbus/thin.d index de60eba..c98527e 100644 --- a/source/ddbus/thin.d +++ b/source/ddbus/thin.d @@ -570,6 +570,54 @@ struct Connection { } } +unittest { + import dunit.toolkit; + + struct S1 { + private int a; + private @(Yes.DBusMarshal) double b; + string s; + } + + @dbusMarshaling(MarshalingFlag.manualOnly) + struct S2 { + int h, i; + @(Yes.DBusMarshal) int j, k; + } + + @dbusMarshaling(MarshalingFlag.includePrivateFields) + struct S3 { + private Variant!int c; + string d; + S1 e; + S2 f; + @(No.DBusMarshal) uint g; + } + + Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth3"); + + enum testStruct = S3( + variant(5), "blah", + S1(-7, 63.5, "test"), + S2(84, -123, 78, 432), + 16 + ); + + msg.build(testStruct); + + // Non-marshaled fields should appear as freshly initialized + enum expectedResult = S3( + variant(5), "blah", + S1(int.init, 63.5, "test"), + S2(int.init, int.init, 78, 432), + uint.init + ); + + msg.read!S3().assertEqual(expectedResult); + + +} + Connection connectToBus(DBusBusType bus = DBusBusType.DBUS_BUS_SESSION) { DBusConnection *conn = wrapErrors((err) { return dbus_bus_get(bus,err); }); return Connection(conn); diff --git a/source/ddbus/util.d b/source/ddbus/util.d index 968e89a..960624d 100644 --- a/source/ddbus/util.d +++ b/source/ddbus/util.d @@ -72,6 +72,8 @@ template canDBus(T) { } } else static if(isAssociativeArray!T) { enum canDBus = basicDBus!(KeyType!T) && canDBus!(ValueType!T); + } else static if(is(T == struct) && !isInstanceOf!(DictionaryEntry, T)) { + enum canDBus = allCanDBus!(Fields!T); } else { enum canDBus = false; } @@ -130,6 +132,13 @@ string typeSig(T)() if(canDBus!T) { return "a" ~ typeSig!(ElementType!T)(); } else static if(isAssociativeArray!T) { return "a{" ~ typeSig!(KeyType!T) ~ typeSig!(ValueType!T) ~ "}"; + } else static if(is(T == struct)) { + string sig = "("; + foreach(i, S; Fields!T) { + sig ~= typeSig!S(); + } + sig ~= ")"; + return sig; } } @@ -163,10 +172,8 @@ string[] typeSigArr(TS...)() if(allCanDBus!TS) { } int typeCode(T)() if(canDBus!T) { - static if (isTuple!T) - return 'r'; - else - return typeSig!T()[0]; + int code = typeSig!T()[0]; + return (code != '(') ? code : 'r'; } int typeCode(T)() if(isInstanceOf!(DictionaryEntry, T) && canDBus!(T[])) { @@ -188,9 +195,14 @@ unittest { // bit flags enum F : uint { a = 1, b = 2, c = 4 } typeSig!(BitFlags!F)().assertEqual(typeSig!uint()); - // structs + // tuples (represented as structs in DBus) typeSig!(Tuple!(int,string,string)).assertEqual("(iss)"); typeSig!(Tuple!(int,string,Variant!int,Tuple!(int,"k",double,"x"))).assertEqual("(isv(id))"); + // structs + struct S1 { int a; double b; string s; } + typeSig!S1.assertEqual("(ids)"); + struct S2 { Variant!int c; string d; S1 e; uint f; } + typeSig!S2.assertEqual("(vs(ids)u)"); // arrays typeSig!(int[]).assertEqual("ai"); typeSig!(Variant!int[]).assertEqual("av");