diff --git a/Readme.md b/Readme.md index bc7c86e..a79db36 100644 --- a/Readme.md +++ b/Readme.md @@ -67,8 +67,9 @@ import ddbus; MessageRouter router = new MessageRouter(); // create a pattern to register a handler at a path, interface and method MessagePattern patt = MessagePattern("/root","ca.thume.test","test"); -router.setHandler!(int,int)(patt,(int par) { - writeln("Called with ", par); +router.setHandler!(int,int,Variant!DBusAny)(patt,(int par, Variant!DBusAny anyArgument) { + // anyArgument can contain any type now, it must be specified as argument using Variant!DBusAny. + writeln("Called with ", par, ", ", anyArgument); return par; }); // handle a signal diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index 0eaae72..06e79cc 100644 --- a/source/ddbus/conv.d +++ b/source/ddbus/conv.d @@ -2,6 +2,7 @@ module ddbus.conv; import ddbus.c_lib; import ddbus.util; +import ddbus.thin; import std.string; import std.typecons; import std.range; @@ -37,6 +38,48 @@ void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) { 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 == DBusAny)) { + DBusMessageIter subStore; + DBusMessageIter* sub = &subStore; + char[] sig = [cast(char) arg.type]; + if(arg.type == 'a') + sig ~= arg.signature; + else if(arg.type == 'r') + sig = arg.signature; + sig ~= '\0'; + if (!arg.explicitVariant) + sub = iter; + else + dbus_message_iter_open_container(iter, 'v', sig.ptr, sub); + if(arg.type == 's') { + immutable(char)* cStr = arg.str.toStringz(); + dbus_message_iter_append_basic(sub,'s',&cStr); + } else if(arg.type == 'b') { + dbus_bool_t longerBool = arg.boolean; // dbus bools are ints + dbus_message_iter_append_basic(sub,'b',&longerBool); + } else if(dbus_type_is_basic(arg.type)) { + dbus_message_iter_append_basic(sub,arg.type,&arg.int64); + } else if(arg.type == 'a') { + DBusMessageIter arr; + dbus_message_iter_open_container(sub, 'a', sig[1 .. $].ptr, &arr); + foreach(item; arg.array) + buildIter(&arr, item); + dbus_message_iter_close_container(sub, &arr); + } else if(arg.type == 'r') { + DBusMessageIter arr; + dbus_message_iter_open_container(sub, 'r', null, &arr); + foreach(item; arg.tuple) + buildIter(&arr, item); + dbus_message_iter_close_container(sub, &arr); + } else if(arg.type == 'e') { + DBusMessageIter entry; + dbus_message_iter_open_container(sub, 'e', null, &entry); + buildIter(&entry, arg.entry.key); + buildIter(&entry, arg.entry.value); + dbus_message_iter_close_container(sub, &entry); + } + if(arg.explicitVariant) + dbus_message_iter_close_container(iter, sub); } else static if(is(T == DictionaryEntry!(K, V), K, V)) { DBusMessageIter sub; dbus_message_iter_open_container(iter, 'e', null, &sub); @@ -56,6 +99,8 @@ T readIter(T)(DBusMessageIter *iter) if (canDBus!T) { DBusMessageIter sub; dbus_message_iter_recurse(iter, &sub); ret = readIter!T(&sub); + static if(is(T == DBusAny)) + ret.explicitVariant = true; dbus_message_iter_next(iter); return ret; } @@ -64,7 +109,7 @@ T readIter(T)(DBusMessageIter *iter) if (canDBus!T) { assert(dbus_message_iter_get_arg_type(iter) == 'r'); } else static if(is(T == DictionaryEntry!(K1, V1), K1, V1)) { assert(dbus_message_iter_get_arg_type(iter) == 'e'); - } else { + } else static if(!is(T == DBusAny)) { assert(dbus_message_iter_get_arg_type(iter) == typeCode!T()); } static if(is(T==string)) { @@ -102,6 +147,44 @@ T readIter(T)(DBusMessageIter *iter) if (canDBus!T) { auto entry = readIter!(DictionaryEntry!(KeyType!T, ValueType!T))(&sub); ret[entry.key] = entry.value; } + } else static if(is(T == DBusAny)) { + ret.type = dbus_message_iter_get_arg_type(iter); + ret.explicitVariant = false; + if(ret.type == 's') { + const(char)* cStr; + dbus_message_iter_get_basic(iter, &cStr); + ret.str = cStr.fromStringz().idup; // copy string + } else if(ret.type == 'b') { + dbus_bool_t longerBool; + dbus_message_iter_get_basic(iter, &longerBool); + ret.boolean = cast(bool)longerBool; + } else if(dbus_type_is_basic(ret.type)) { + dbus_message_iter_get_basic(iter, &ret.int64); + } else if(ret.type == 'a') { + DBusMessageIter sub; + dbus_message_iter_recurse(iter, &sub); + auto sig = dbus_message_iter_get_signature(&sub); + ret.signature = sig.fromStringz.dup; + dbus_free(sig); + while(dbus_message_iter_get_arg_type(&sub) != 0) { + ret.array ~= readIter!DBusAny(&sub); + } + } else if(ret.type == 'r') { + auto sig = dbus_message_iter_get_signature(iter); + ret.signature = sig.fromStringz.dup; + dbus_free(sig); + DBusMessageIter sub; + dbus_message_iter_recurse(iter, &sub); + while(dbus_message_iter_get_arg_type(&sub) != 0) { + ret.tuple ~= readIter!DBusAny(&sub); + } + } else if(ret.type == 'e') { + DBusMessageIter sub; + dbus_message_iter_recurse(iter, &sub); + ret.entry = new DictionaryEntry!(DBusAny, DBusAny); + ret.entry.key = readIter!DBusAny(&sub); + ret.entry.value = readIter!DBusAny(&sub); + } } else static if(basicDBus!T) { dbus_message_iter_get_basic(iter, &ret); } @@ -123,9 +206,22 @@ unittest { bool[] emptyB; string[string] map; map["hello"] = "world"; - auto args = tuple(5,true,"wow",var(5.9),[6,5],tuple(6.2,4,[["lol"]],emptyB,var([4,2])),map); + DBusAny anyVar = DBusAny(cast(ulong) 1561); + anyVar.type.assertEqual('t'); + anyVar.uint64.assertEqual(1561); + anyVar.explicitVariant.assertEqual(false); + auto tupleMember = DBusAny(tuple(Variant!int(45), Variant!ushort(5), 32, [1, 2], tuple(variant(4), 5), map)); + DBusAny complexVar = DBusAny(variant([ + "hello world": variant(DBusAny(1337)), + "array value": variant(DBusAny([42, 64])), + "tuple value": variant(tupleMember) + ])); + complexVar.type.assertEqual('a'); + complexVar.signature.assertEqual("{sv}".dup); + tupleMember.signature.assertEqual("(vviai(vi)a{ss})"); + auto args = tuple(5,true,"wow",var(5.9),[6,5],tuple(6.2,4,[["lol"]],emptyB,var([4,2])),map,anyVar,complexVar); msg.build(args.expand); - msg.signature().assertEqual("ibsvai(diaasabv)a{ss}"); + msg.signature().assertEqual("ibsvai(diaasabv)a{ss}tv"); msg.readTuple!(typeof(args))().assertEqual(args); DBusMessageIter iter; dbus_message_iter_init(msg.msg, &iter); @@ -136,4 +232,6 @@ unittest { readIter!(int[])(&iter).assertEqual([6,5]); readIter!(Tuple!(double,int,string[][],bool[],Variant!(int[])))(&iter).assertEqual(tuple(6.2,4,[["lol"]],emptyB,var([4,2]))); readIter!(string[string])(&iter).assertEqual(["hello": "world"]); + readIter!DBusAny(&iter).assertEqual(anyVar); + readIter!DBusAny(&iter).assertEqual(complexVar); } diff --git a/source/ddbus/router.d b/source/ddbus/router.d index a712334..5550d9e 100644 --- a/source/ddbus/router.d +++ b/source/ddbus/router.d @@ -203,16 +203,21 @@ unittest{ patt = MessagePattern("/root/wat","ca.thume.tester","lolwut"); router.setHandler!(int,int)(patt,(int p) {return 6;}); patt = MessagePattern("/root/foo","ca.thume.tester","lolwut"); - router.setHandler!(Tuple!(string,string,int),int)(patt,(int p) {Tuple!(string,string,int) ret; ret[0] = "a"; ret[1] = "b"; ret[2] = p; return ret;}); + router.setHandler!(Tuple!(string,string,int),int,Variant!DBusAny)(patt,(int p, Variant!DBusAny any) {Tuple!(string,string,int) ret; ret[0] = "a"; ret[1] = "b"; ret[2] = p; return ret;}); patt = MessagePattern("/troll","ca.thume.tester","wow"); router.setHandler!(void)(patt,{return;}); + static assert(!__traits(compiles, { + patt = MessagePattern("/root/bar","ca.thume.tester","lolwut"); + router.setHandler!(void, DBusAny)(patt,(DBusAny wrongUsage){return;}); + })); + // TODO: these tests rely on nondeterministic hash map ordering static string introspectResult = ` `; router.introspectXML("/root").assertEqual(introspectResult); static string introspectResult2 = ` -`; +`; router.introspectXML("/root/foo").assertEqual(introspectResult2); router.introspectXML("/").assertEndsWith(``); } diff --git a/source/ddbus/simple.d b/source/ddbus/simple.d index 2ab786b..96cd6aa 100644 --- a/source/ddbus/simple.d +++ b/source/ddbus/simple.d @@ -74,7 +74,7 @@ void registerMethods(T : Object)(MessageRouter router, string path, string iface unittest { import dunit.toolkit; class Tester { - int lol(int x, string s, string[string] map) {return 5;} + int lol(int x, string s, string[string] map, Variant!DBusAny any) {return 5;} void wat() {} @SignalMethod void signalRecv() {} @@ -91,6 +91,6 @@ unittest { patt.signal = false; router.callTable.assertHasKey(patt); auto res = router.callTable[patt]; - res.argSig.assertEqual(["i","s", "a{ss}"]); + res.argSig.assertEqual(["i","s","a{ss}","v"]); res.retSig.assertEqual(["i"]); } diff --git a/source/ddbus/thin.d b/source/ddbus/thin.d index 8f421e2..877b7d6 100644 --- a/source/ddbus/thin.d +++ b/source/ddbus/thin.d @@ -7,6 +7,10 @@ import ddbus.util; import std.string; import std.typecons; import std.exception; +import std.traits; +import std.conv; +import std.range; +import std.algorithm; class DBusException : Exception { this(DBusError *err) { @@ -26,7 +30,265 @@ T wrapErrors(T)(T delegate(DBusError *err) del) { return ret; } +/// Structure allowing typeless parameters +struct DBusAny { + /// DBus type of the value (never 'v'), see typeSig!T + int type; + /// Child signature for Arrays & Tuples + char[] signature; + /// If true, this value will get serialized as variant value, otherwise it is serialized like it wasn't in a DBusAny wrapper. + /// Same functionality as Variant!T but with dynamic types if true. + bool explicitVariant; + + union + { + /// + byte int8; + /// + short int16; + /// + ushort uint16; + /// + int int32; + /// + uint uint32; + /// + long int64; + /// + ulong uint64; + /// + double float64; + /// + string str; + /// + bool boolean; + /// + DBusAny[] array; + /// + DBusAny[] tuple; + /// + DictionaryEntry!(DBusAny, DBusAny)* entry; + } + + /// Manually creates a DBusAny object using a type, signature and implicit specifier. + this(int type, char[] signature, bool explicit) { + this.type = type; + this.signature = signature; + this.explicitVariant = explicit; + } + + /// Automatically creates a DBusAny object with fitting parameters from a D type or Variant!T. + /// Pass a `Variant!T` to make this an explicit variant. + this(T)(T value) { + static if(is(T == byte) || is(T == ubyte)) { + this(typeCode!byte, null, false); + int8 = cast(byte) value; + } else static if(is(T == short)) { + this(typeCode!short, null, false); + int16 = cast(short) value; + } else static if(is(T == ushort)) { + this(typeCode!ushort, null, false); + uint16 = cast(ushort) value; + } else static if(is(T == int)) { + this(typeCode!int, null, false); + int32 = cast(int) value; + } else static if(is(T == uint)) { + this(typeCode!uint, null, false); + uint32 = cast(uint) value; + } else static if(is(T == long)) { + this(typeCode!long, null, false); + int64 = cast(long) value; + } else static if(is(T == ulong)) { + this(typeCode!ulong, null, false); + uint64 = cast(ulong) value; + } else static if(is(T == double)) { + this(typeCode!double, null, false); + float64 = cast(double) value; + } else static if(isSomeString!T) { + this(typeCode!string, null, false); + str = value.to!string; + } else static if(is(T == bool)) { + this(typeCode!bool, null, false); + boolean = cast(bool) value; + } else static if(is(T == Variant!R, R)) { + static if(is(R == DBusAny)) { + type = value.data.type; + signature = value.data.signature; + explicitVariant = true; + if(type == 'a' || type == 'r') + array = value.data.array; + if(type == 's') + str = value.data.str; + else if(type == 'e') + entry = value.data.entry; + else + uint64 = value.data.uint64; + } else { + this(value.data); + explicitVariant = true; + } + } else static if(is(T : DictionaryEntry!(K, V), K, V)) { + this('e', null, false); + entry = new DictionaryEntry!(DBusAny, DBusAny)(); + static if(is(K == DBusAny)) + entry.key = value.key; + else + entry.key = DBusAny(value.key); + static if(is(V == DBusAny)) + entry.value = value.value; + else + entry.value = DBusAny(value.value); + } else static if(isInputRange!T) { + this.type = 'a'; + this.signature = typeSig!(ElementType!T).dup; + this.explicitVariant = false; + foreach(elem; value) + array ~= DBusAny(elem); + } else static if(isTuple!T) { + this.type = 'r'; + this.signature = ['(']; + this.explicitVariant = false; + foreach(index, R; value.Types) { + auto var = DBusAny(value[index]); + tuple ~= var; + if(var.explicitVariant) + this.signature ~= 'v'; + else { + if (var.type != 'r') + this.signature ~= cast(char) var.type; + if(var.type == 'a' || var.type == 'r') + this.signature ~= var.signature; + } + } + this.signature ~= ')'; + } else static if(isAssociativeArray!T) { + this(value.byDictionaryEntries); + } else static assert(false, T.stringof ~ " not convertible to a Variant"); + } + + /// + string toString() const { + string valueStr; + switch(type) { + case typeCode!byte: + valueStr = int8.to!string; + break; + case typeCode!short: + valueStr = int16.to!string; + break; + case typeCode!ushort: + valueStr = uint16.to!string; + break; + case typeCode!int: + valueStr = int32.to!string; + break; + case typeCode!uint: + valueStr = uint32.to!string; + break; + case typeCode!long: + valueStr = int64.to!string; + break; + case typeCode!ulong: + valueStr = uint64.to!string; + break; + case typeCode!double: + valueStr = float64.to!string; + break; + case typeCode!string: + valueStr = '"' ~ str ~ '"'; + break; + case typeCode!bool: + valueStr = boolean ? "true" : "false"; + break; + case 'a': + valueStr = '[' ~ array.map!(a => a.toString).join(", ") ~ ']'; + break; + case 'r': + valueStr = '(' ~ array.map!(a => a.toString).join(", ") ~ ')'; + break; + case 'e': + valueStr = entry.key.toString ~ ": " ~ entry.value.toString; + break; + default: + valueStr = "unknown"; + break; + } + return "DBusAny(" ~ cast(char) type + ~ ", \"" ~ signature.idup + ~ "\", " ~ (explicitVariant ? "explicit" : "implicit") + ~ ", " ~ valueStr ~ ")"; + } + + /// If the value is an array of DictionaryEntries this will return a HashMap + DBusAny[DBusAny] toAA() { + enforce(type == 'a' && signature && signature[0] == '{'); + DBusAny[DBusAny] aa; + foreach(val; array) { + enforce(val.type == 'e'); + aa[val.entry.key] = val.entry.value; + } + return aa; + } + + /// Converts a basic type or an array to the D type with type checking. Tuples can get converted to an array. + T to(T)() { + static if(isIntegral!T || isFloatingPoint!T) { + switch(type) { + case typeCode!byte: + return cast(T) int8; + case typeCode!short: + return cast(T) int16; + case typeCode!ushort: + return cast(T) uint16; + case typeCode!int: + return cast(T) int32; + case typeCode!uint: + return cast(T) uint32; + case typeCode!long: + return cast(T) int64; + case typeCode!ulong: + return cast(T) uint64; + case typeCode!double: + return cast(T) float64; + default: + throw new Exception("Can't convert type " ~ cast(char) type ~ " to " ~ T.stringof); + } + } else static if(isSomeString!T) { + if(type == 's') + return str.to!T; + else + throw new Exception("Can't convert type " ~ cast(char) type ~ " to " ~ T.stringof); + } else static if(isDynamicArray!T) { + if(type != 'a' && type != 'r') + throw new Exception("Can't convert type " ~ cast(char) type ~ " to an array"); + T ret; + foreach(elem; array) + ret ~= elem.to!(ElementType!T); + return ret; + } else static assert(false, "Can't convert variant to " ~ T.stringof); + } + + bool opEquals(ref in DBusAny b) const { + if(b.type != type || b.explicitVariant != explicitVariant) + return false; + if((type == 'a' || type == 'r') && b.signature != signature) + return false; + if(type == 'a') + return array == b.array; + else if(type == 'r') + return tuple == b.tuple; + else if(type == 's') + return str == b.str; + else if(type == 'e') + return entry == b.entry || (entry && b.entry && *entry == *b.entry); + else + return uint64 == b.uint64; + } +} + +/// Marks the data as variant on serialization struct Variant(T) { + /// T data; } diff --git a/source/ddbus/util.d b/source/ddbus/util.d index 19b3abb..6bdaa4f 100644 --- a/source/ddbus/util.d +++ b/source/ddbus/util.d @@ -52,7 +52,7 @@ template basicDBus(T) { } template canDBus(T) { - static if(basicDBus!T) { + static if(basicDBus!T || is(T == DBusAny)) { enum canDBus = true; } else static if(isVariant!T) { enum canDBus = canDBus!(VariantType!T); @@ -101,6 +101,8 @@ string typeSig(T)() if(canDBus!T) { return "s"; } else static if(isVariant!T) { return "v"; + } else static if(is(T == DBusAny)) { + static assert(false, "Cannot determine type signature of DBusAny. Change to Variant!DBusAny if a variant was desired."); } else static if(isTuple!T) { string sig = "("; foreach(i, S; T.Types) {