Still some way(land) to go#
Thu, 07 Mar 2013 22:01:54 +0000
Ignoring, because I can, this whole Ubuntu Mir thing completely, I have begun to learn stuff about the Wayland protocol (and about Clojure, with which I am still at the constantly-having-to-google-stuff stage). Some random notes on what I have learnt so far
First off: Java has no builtin support for talking to unix-domain
(AF_FILE
) sockets. And nobody seems to make a Maven-packaged library
that does it either. This is a shame because Leiningen makes Maven
manageable, but anything unmavened involved tedious mucking around.
Eventually I did
:; apt-get install libunixsocket-java :; cat project.clj (defproject psadan "0.1.0-SNAPSHOT" :description "FIXME: write description" :resource-paths ["/usr/share/java/unix-0.5.jar"] :url "http://example.com/FIXME" :jvm-opts ["-Djava.library.path=native/:/usr/lib/jni/"] :main psadan.core :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.3.0"] [org.clojure/data.zip "0.1.1"] ])
which seems to be holding up. Then I opened a socket and tried reading from it in the hope of getting some lovely protocoly stuff
(defn open-socket [name] (let [s (cx.ath.matthew.unix.UnixSocket. name) in (. s getInputStream) out (. s getOutputStream) ] {:socket s :input in :output out}))(def socket (open-socket "/home/dan/private/wayland-0"))
(defn rd [] (let [buf (byte-array 1024)] (. (:input socket) (read buf)) buf))
and watched it hang. After some time looking at documentation and
mucking about with strace to see if it was trying to read the full
buffer instead of doing the short read I wanted it to, I eventually
thought to use socat
instead of clojure. It turns out that the
client is expected to make the first request before the server sends
anything, and with the use of strace weston-info
I was able to find
out what.
26335 sendmsg(3, {msg_name(0)=NULL, msg_iov(1)=[{"\1\0\0\0\1\0\f\0\2\0\0\0\1\0\0\0\0\0\f\0\3\0\0\0", 24}], msg_controllen=0, msg_flags=0}, MSG_DONTWAIT|MSG_NOSIGNAL) = 24
Time to start writing some code to parse wayland.xml so we can actually find out what this means. The usual way to do XML parsing in clojure seems to be using zippers and the easy examples seem to be somewhat lacking or slightly ungooglable. You need a project dependency on org.clojure/data.zip and a bunch of package requires, then you call clojure.zip/xml-zip on the return value of clojure.xml/parse and that gets you a zipper
(ns psadan.core (:require [clojure.xml] [clojure.data.zip :as dz] [clojure.data.zip.xml :as x] [clojure.walk] [clojure.zip :as z]));; [...]
(def the-protocol (-> "/home/dan/wayland/source/wayland/protocol/wayland.xml" clojure.xml/parse z/xml-zip parse-protocol))
where I have conveniently left out the definition of parse-protocol and everything it calls because it's longwinded and tedious (but the code will be on github as soon as I'm not ashamed of it) but it might hypothetically do things like
(x/xml-> my-zipper :protocol :interface :request)
to descend the tree through <protocol> <interface> <request>
and return all the elements. Use the similarly named x/xml1->
to
get the first matching element only. The return values from these things
are themselves zippers and you can call up
, down
etc - or xml->
again - to traverse the tree further, then eventually call node
when
you want to get the element itself. So e.g.
(defn parse-interface [i n] ;; n is a badly named zippered xml object thingy (let [el (z/node n) requests (map-indexed parse-message (x/xml-> n :request)) events (map-indexed parse-message (x/xml-> n :event)) enums (map parse-enum (x/xml-> n :enum))] {:index i :name (:name (:attrs el)) :requests requests :events events :enums enums }))
So let's handwave over the details and take it on trust that I have parsed the whole file. There are two other things I discovered - mostly thanks to the #wayland IRC channel participants - about the wayland wire protocol that the docs don't mention:
- where it says "The first word is the sender's object id (32-bit)", when it's describing a message sent from client to compositor, what it means is "The first word is the target object's id (32-bit)".
- object id 1 is special: it refers to the core global singleton
object, which implements the interface
wl_display
Given which, we can attempt to parse that first client->compositor message
psadan.core> (parse-messages-from-buf (vec (.getBytes "\1\0\0\0\1\0\f\0\2\0\0\0\1\0\0\0\0\0\f\0\3\0\0\0")) :requests) ({:object-id 1, :bytes 12, :interface "wl_display", :message "get_registry", :opcode 1, :args (2)} {:object-id 1, :bytes 12, :interface "wl_display", :message "sync", :opcode 0, :args (3)})
Looks plausible so far ...
Next up, some code to create messages. And something, which may involve an atom, to map object ids to their corresponding interfaces as we learn them. After that, we find out what the Wayland FAQ really means by "shareable buffer"
Addendum, penned the following morning
Because this is supposed to be a log of what I am learning, and I had
never previously learned about the ->
operator (seen it, never used
it), for completeness' sake:
(-> x fn1 fn2 fn3)is the same thing as
(fn3 (fn2 (fn1 x)))
Googling for clojure ->
is not a terribly fulfilling endeavour, so
it's helpful to know that it's also referred to as the 'arrow'
operator, the 'threading' operator, and apparently also the 'thrush'
operator. This
blog article
from Fogus explains further.