Exploring Desktop Objects #2
In the first part of this text, I explained a couple of reasons why I created edelib-dbus-explorer. In the mean time, this tool got a couple of quite handy features I will try to explain here.
IMHO one of the killer features of Common Lisp, Python or Clojure are docstrings. You write the code and document it at the same time; the documentation becames a part of code, meaning you can easily get it at any point: from REPL or from some GUI fronted.
That is why I added docstring support in edelib-script (based on tinyscheme), so:
(define (foo a b)
(+ a b))
you can write as:
(defun foo (a b)
"This is foo function."
(+ a b))
Yes, it is quite Common Lisp-like, but to avoid messing with interpreter internals and to leave some room for turning off docstring collection in case of optimizations, I put it as defun macro. Maybe I could use different name to reduce confusion, like s7 which uses define*, but I'm not going to bother with that right now.
Now imagine in similar fashion you can document DBus methods or signals. This feature would be extremely useful for tools like edelib-dbus-explorer which, for example, could display method/signal prototype and description as tooltip when user hovers over the name.
Actually this was implemented in edelib-dbus-explorer from the start, but without descriptions, as DBus does not have official way how methods and signals should be documented. However, searching through the various implementations, I noticed EggDBusand GDBus can provide them via annotations. Then I became extremely happy, meaning edelib-dbus-explorer got immediate support for them.
Since both EggDBus and GDBus put docstrings in different namespaces (but ends them with DocString), edelib-dbus-explorer will simply scan for annotations whose names ends with .DocString and pick the value. With this, I don't have to change it when I extend edelib DBus binding to generate introspections with e.g. org.equinoxproject.DBus.DocString annotation name.
Unfortunately, not all (gtk+) applications uses this feature yet, and the worst of all, this will not work with Qt applications. I'm not sure what is official way from Qt/KDE standpoint, but I noticed some of projects are using <tp:docstring> tag inside introspection xml for documentation. I'm not sure even if it is transmitted to DBus server or is stripped when binding tool is creating source code from introspection xml. This needs further investigation.
Beside of this, I added builtin help (you can invoke it by evaluating (help) function) where are described common functions and how is done mapping between DBus and Scheme types. I didn't have enough time to explain it briefly in previous post, so it gets deserved space now.
Tinyscheme (thus edelib-script) is a R5RS based implementation and as most of Scheme implementations have a few object types: string, number, list, vector and some of them hashmaps. Opposite to this, DBus have much richer types and is quite strict about them: if method call expects variable with int32 type, it will refuse to be called with int16 type. Which can be a problem as Scheme does not have notion of 32-bit or 16-bit integers; it only understaind number which is often largest possible integer (sometimes even seen in bignum form).
So I added type hints, inspired from Emacs DBus hints: int16 is represented as :int16, string as :string and so on. All hints must be added to method or signal call arguments if that call requires arguments. For example, dbus-call will call DBus method and using hints it can look like:
(dbus-call "org.foo.baz" "/org/foo/baz" "org.foo.Interface" "Method"
:string "Some string"
where the first 4 parameters are service name, object path, interface and method. Things gets complicated when we have DBus complex types (like array or struct), but thanks to Scheme/LISP lists, these problems are easy to overcome. So, to represent an array of 4 uint16 types, this is used:
'(:array (:uint16 1 :uint16 2 :uint16 3 :uint16 4))
I know, it is messy, so I cooked a function called dbus-make-array that will simplify this like:
(dbus-make-array :uint16 1 2 3 4)
On other hand, DBus struct type behaves much like C/C++ structs and can have many different types inside own collection, much like Scheme lists. Unfortunately, you still needs to explicitly add types, like:
'(:struct (:uint16 1
Dictionaries are list of lists, where the first element is key and the second is value; both again must have types:
'(:dict (:string "key1" :bool 1)
(:string "key2" :bool 0))
And any of these can be a part of each other, creating even more complex combinations. Truth to be told, from syntax stand of point, things can be improved, but it is all matter of creating additional functions or macros. For example, we can simplify struct syntax like:
(make-struct :uint16 1
I didn't cover other supported types, like Variant or ObjectPath and I'm leaving documentations to do that. However, one thing I would like to mention is the plan to add support for implicit conversion; for example, all positive Scheme numbers could be uint32 type and the negative int32. This would make typing and overall things much easier.
Anyway, since video speaks more than thousand images, here is a small demo of the main edelib-dbus-explorer features in usage. If you would like to get hands on it, you will need to download edelib svn source code and compile it.
Feel free to ping me if you find some issues :)