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) {