Puppets at work#
Fri, 01 Mar 2013 11:12:54 +0000
Another huge long gap between updates. I'd say that you might as well
get used to it, but you probably are already. This is a short note to say that I have developed a Capistrano extension which runs Puppet as part of an app deploy, and you can read about it on the Simply Business tech blog
In other news, having confirmed that the X11 touchpad
support is broken by design - the
choice of whether to emulate mouse events (left click, middle click,
drag etc) is set at a global level in the driver and cannot be overridden
per client, so legacy mouse-only clients cannot meaningfully coexist
with clients that usefully could handle touch events directly - I am
working on a client implementation of the Wayland protocol in Clojure.
I hope this will eventually will turn into a habitable text editor.
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.
One more step along the Wayland#
Tue, 12 Mar 2013 13:57:03 +0000
Yesterday's lunchtime hacking was all about splitting the project into
multiple files and getting it into git and onto Github - note that the mere fact of
it being publically browsable does not imply that it will run,
build, walk, make tea, perform any other useful function, or even
forbear from exploding inside your computer and rendering the SSD to
molten slag. Nor that I'm not still ashamed of it. It just keeps me
slightly more honest.
Today I implemented enough of pack-message
to be able to recreate
the initial client->compositor message that we observed weston-info
send last week. Still taking extraordinary liberties with signed vs
unsigned longs, and plase note that all this code will work only on
little-endian machines (there are any big-endian machines left?).
Lessons, puzzles
Leiningen does not need you to list the source files in your
repository individually: it finds them magically. I believed
otherwise for a while, but it turned out (slightly embarrassingly)
that I had a parenthesis in the wrong place. My working hypothesis is
that it assumes there is one namespace for each file, and that any
reference to a namespace it doesn't know about it can be satisfied by
loading a file with that name.
If I type (in-ns 'psadan.core)
at the repl and that ns does not
include a (:refer-clojure)
form, I can't use the symbols in
clojure.core
at the repl. I have not observed a similar issue wrt
uses of clojure.core/foo
in core.clj itself, just at the repl.
atoms! An atom is dead simple, really - conceptually at least, if not
also under the hood: it's a wrapper for an object that lets you look
inside with deref
and lets you change what's inside with swap!
.
For each connection we use an atom holding a mapping from object ids
to the corresponding objects, which starts out holding the singleton
object for wl_display
and then needs to be updated each time we
generate an object locally and each time we learn of a new object from
the peer.
(defn open-connection [name]
(let [s (cx.ath.matthew.unix.UnixSocket. name)
in (. s getInputStream)
out (. s getOutputStream)
wl-display (global-object-factory)
]
{:socket s
:input in
:output out
:display wl-display
:objects (atom (assoc {} 1 wl-display))
}))(defn remember-object [conn id object]
;; (swap r fn args...) gets the current value of the atom inside r,
;; which for the sake of argument we shall call oldval, then sets the atom
;; to the result of calling (fn oldval args...)
(swap! (:objects conn) assoc id object)
object)
(defn get-object [conn id]
;; foo is another way to write (deref foo)
(let [o (get
(:objects conn) id)]
o))
I have probably not chosen the fastest possible way of building up the
messages I plan to send, in terms of fiddling around sticking vectors
of bytes together. Will worry about that later if it turns out to be
a bottleneck (but suggestions are welcome).
There was not a lot of Wayland learning this time. In the next round
we shall be sending it the messages we have so lovingly composed from
whole cloth and see if it reacts the same way as it did when the same
bytes were sent from weston-info
Finding a Way(land)#
Thu, 14 Mar 2013 13:32:01 +0000
In the next round we shall be sending it the messages we have so lovingly composed from whole cloth and see if it reacts the same way as it did when the same bytes were sent from weston-info
And the answer is ... yes, pretty much. We had to fix up our string
parsing to make sense of the replies, but watch:
- 'psadan.core/connection
psadan.core> (defn test-send-message []
(let [registry
(conn/remember-object
connection
{:id 2 :interface (proto/find-interface-by-name :wlregistry)})
done-cb
(conn/remember-object
connection
{:id 3 :interface (proto/find-interface-by-name :wlcallback)})
]
(conn/write-buffer connection
(buf/pack-message connection (:display connection)
:requests :get_registry [registry]))
(conn/write-buffer connection
(buf/pack-message connection (:display connection)
:requests :sync [done-cb]))
registry))
- 'psadan.core/test-send-message
psadan.core> (test-send-message)
{:id 2, :interface {:index 1, :name :wlregistry, :requests ({:index 0, :name :bind, :summary "bind an object to the display", :args ({:name "name", :type :uint, :interface nil} {:name "id", :type :newid, :interface nil})}), :events ({:index 0, :name :global, :summary "announce global object", :args ({:name "name", :type :uint, :interface nil} {:name "interface", :type :string, :interface nil} {:name "version", :type :uint, :interface nil})} {:index 1, :name :global_remove, :summary "announce removal of global object", :args ({:name "name", :type :uint, :interface nil})}), :enums ()}}
psadan.core> (def received (read-buffer connection))
- 'psadan.core/received
psadan.core> (pprint (map (fn [x] [(:object-id x) (:message x) (:args x)]) (buf/parse-messages received connection :events)))
nil
([2 :global (1 "wldisplay" 1)]
[2 :global (2 "wlcompositor" 2)]
[2 :global (3 "screenshooter" 1)]
[2 :global (4 "textcursorposition" 1)]
[2 :global (5 "textmodelfactory" 1)]
[2 :global (6 "wldatadevicemanager" 1)]
[2 :global (7 "wlshm" 1)]
[2 :global (8 "wlseat" 1)]
[2 :global (9 "inputmethod" 1)]
[2 :global (10 "wloutput" 1)]
[2 :global (11 "wldrm" 1)]
[2 :global (12 "wlshell" 1)]
[2 :global (13 "desktopshell" 1)]
[2 :global (14 "screensaver" 1)]
[2 :global (15 "inputpanel" 1)]
[2 :global (16 "workspacemanager" 1)]
[3 :done (58)]
[1 :delete_id (3)])
My interpretation of what's happening here is that we're sending to
the server a 'tell object 2 about all your global objects' message,
followed by a 'tell object 3 done
when you're finished doing stuff'
message, and as you can see from the output, the reply is a bunch of
ids for global objects sent to object 2, a done
event sent to object
3, and then a delete_id
event for object 3 sent to object 1. I'm
actually not sure why that last one triggers, as I don't think I asked
it to. Perhaps it's just tidying up for me.
I'm also handwaving - if not actually handdrowning - a litle bit,
because really ... are these :global
messages sent to object 2 or
from object 2? For the moment, I am using the two directions
interchangeably, which is probably not a recipe for an easier future
life, but in the meantime I can continue to tread water.
It's instructive, or at least reassuring, to compare this stuff with
what weston-info
says:
interface: 'wl_display', version: 1, name: 1
interface: 'wl_compositor', version: 2, name: 2
interface: 'screenshooter', version: 1, name: 3
interface: 'text_cursor_position', version: 1, name: 4
interface: 'text_model_factory', version: 1, name: 5
interface: 'wl_data_device_manager', version: 1, name: 6
interface: 'wl_shm', version: 1, name: 7
formats: XRGB8888 ARGB8888
interface: 'wl_seat', version: 1, name: 8
capabilities: pointer keyboard
interface: 'input_method', version: 1, name: 9
interface: 'wl_output', version: 1, name: 10
x: 0, y: 0,
physical_width: 1024 mm, physical_height: 640 mm,
make: 'xwayland', model: 'none',
subpixel_orientation: unknown, output_tranform: normal,
mode:
width: 1024 px, height: 640 px, refresh: 60 Hz,
flags: current preferred
interface: 'wl_drm', version: 1, name: 11
interface: 'wl_shell', version: 1, name: 12
interface: 'desktop_shell', version: 1, name: 13
interface: 'screensaver', version: 1, name: 14
interface: 'input_panel', version: 1, name: 15
interface: 'workspace_manager', version: 1, name: 16
Top Wayland tip for today: it appears to be the case that you can make
the C library clients log protocol exchanges to stderr by putting
WAYLAND_DEBUG=client
in the environment. When doing that it's clear
to see that weston-info
is making a couple of additional requests
that we're not. We could add them, but I think the more pressing
concern is to make it do something with the events we're getting
already - if it's sending us details of global objects that we might
need to know about, we should at a minimum be storing those details
somewhere instead of throwing them away ...
The long way(land) round#
Mon, 18 Mar 2013 21:07:47 +0000
The latest round of psadan hacking was motivated by two goals
- that it would be good to actually remember the globals we're
receiving when we send
get_registry
to the magic registry object
- that if we're going to ask for a callback when all the globals are
notified, we should wait for it before continuing.
We went down a couple or three dead ends on our way to this goal, but
eventually we settled on creating an agent responsible for listening
to the connection (I called it channel
, in the absence of any better
ideas) and dispatching (using a handle-message
multimethod) to some
code appropriate for each kind of message we're receiving.
Learnings
- we were trying to map
handle-message
onto each of the messages
parsed out of the buffer, but not doing anything with the result.
Given that map
is lazy, this meant our handle-message
code was
for the most part not being called. Surrounding the map
form with
a dorun
fixed this.
Next up? At some point we need to decide whether sending messages
should be done by the channel or whether it's OK to carry on doing
that directly in whatever thread we happen to be in. But what would
be much more fun is to see if we can actually render a window...
In mysterious way(land)s#
Thu, 21 Mar 2013 13:29:35 +0000
Why are our bind
messages to wl_registry
erroring out?
15:19 < daniels> yeah, wlregistrybind is a special case - i think it's the
one non-autogenerated marshaller we have
15:32 < daniels> actually no, sorry, i'm lying through my teeth
15:32 < daniels> it's no longer autogenerated
15:32 < daniels> but the parser expands newid to interface + version + id
15:33 < daniels> it used to be hand-written, but is now autogenerated
15:33 < daniels> http://cgit.freedesktop.org/wayland/wayland/tree/src/scanner.c#n614
16:28 < jekstrand> danb: It is a somewhat-special case. Basically, every time
there's a newid that does not have any type information
specified, two aditional pieces of information (interface
name and version) get added.
16:29 < jekstrand> danb: That really should be documented, but it's not. I
had to grep through the scanner.c file to find it all.
Armed with this additional info the fix was fairly straightforward: I
augmented the XML parsing/protocol generation to stick an additional
pair of arguments into each message that contains a new_id and no interface.
Thanks to both daniels and jekstrand on irc for clearing it up.