Requests and Events
Requests
Each request is a function named interface-request. It takes the proxy
first and then the request's arguments in protocol order:
(wl-surface-attach surface buffer 0 0)
(wl-surface-damage-buffer surface 0 0 width height)
(xdg-toplevel-set-title toplevel "Hello")
(wl-surface-commit surface)
Before anything is sent, each request:
-
checks the type of each argument against the function's declaimed
ftype, so a wrong argument gives atype-error, and SBCL warns at compile time where it can tell; -
signals
destroyed-proxy-errorif the proxy has been destroyed; -
signals
version-errorif the request is newer than the proxy's version:WL-SURFACE-OFFSET needs version 5 of wl_surface, but #<WL-SURFACE id 6 v3> has version 3.
What a request returns:
- A request that creates an object returns the new proxy, which has the
parent's version:
(wl-compositor-create-surface compositor)returns awl-surface. wl-registry-bind, whose new object's interface is chosen by the caller, takes the proxy class and version as extra arguments.- Every other request returns no values.
A destructor request, such as wl-surface-destroy or
wl-pointer-release, destroys the proxy after sending it. You rarely
need to pick one by hand: destroy-proxy sends the interface's destructor
request if the proxy's version has it, and otherwise destroys the proxy
on the client side only. wl-pointer-release, for instance, exists only
from version 3. See Lifetimes and Threads.
Requests are buffered in libwayland until flush, dispatch,
read-events or roundtrip sends them. Call flush when you need the
compositor to see a request now and are not about to dispatch, for
example after a commit from a render thread.
The documentation of each request function lists its arguments and repeats the protocol's description:
(documentation 'wl-registry-bind 'function)
;; "bind an object to the display
;;
;; Binds a new, client-created object to the server using the
;; specified name as the identifier.
;;
;; Arguments:
;; NAME -- unique numeric name of the object
;; INTERFACE -- the proxy class of the interface to create
;; VERSION -- the interface version to create
;;
;; Returns the new proxy."
Events
Each event of a proxy can have its own handler. A handler is called with the proxy and the event's arguments:
(wl:set-event-handlers pointer
:enter (lambda (pointer serial surface x y)
(declare (ignore pointer serial))
(format t "entered ~A at ~,1F,~,1F~%" surface x y))
:motion (lambda (pointer time x y)
(declare (ignore pointer time))
(setf *cursor* (cons x y)))
:button (lambda (pointer serial time button state)
(declare (ignore pointer serial time))
(format t "button ~D ~S~%" button state))) ; state is :pressed or :released
-
set-event-handlerssets several handlers from alternating keywords and functions and returns the proxy. -
(setf (wl:event-handler proxy :motion) #'on-motion)sets one handler.(wl:event-handler proxy :motion)reads it back. -
A handler may be a function or a symbol naming one, and NIL removes it.
-
Events without a handler are dropped.
-
An event the interface does not have signals
wl-errorand lists the events it does have:wl_seat has no :CAPABILITY event. Its events are :CAPABILITIES, :NAME.
The documentation of each proxy class lists its events, the order of their arguments, and the Lisp type of each argument:
(documentation 'wl-pointer 'type)
;; …
;; Events, each passed to its EVENT-HANDLER with the proxy and
;; the arguments shown:
;; (:enter SERIAL SURFACE SURFACE-X SURFACE-Y) -- enter event
;; SERIAL -- serial number of the enter event
;; SURFACE -- surface entered by the pointer (wl-surface)
;; SURFACE-X -- surface-local x coordinate (single-float)
;; SURFACE-Y -- surface-local y coordinate (single-float)
;; …
;; (:frame) -- end of a pointer event sequence (since version 5)
When handlers run
libwayland decodes events inside a C callback. cl-wl's callback only
turns the arguments into Lisp values and queues the event. The handlers
run later, from dispatch, dispatch-pending or roundtrip, after
libwayland has returned. As a result:
- Handlers may signal and unwind freely. If a handler unwinds, the event that was being handled has already left the queue. The events after it stay queued and are delivered by the next dispatch.
- Handlers may send requests, create and destroy proxies, and set other handlers. They may even dispatch again.
- Handlers run in the thread that dispatches.
- If a handler destroys a proxy, events still queued for that proxy are dropped.
Destructor events
Some events end their object: once the compositor has sent one, the
object is gone on its side. The protocol marks them as destructors. In
the protocols cl-wl ships, that is wl_callback's :done. cl-wl destroys
the proxy after its handler has run, whether the handler returns or
unwinds, and also when the event has no handler. The handler can still use
the proxy, and you never destroy it yourself:
(wl:set-event-handlers (wl-surface-frame surface)
:done (lambda (callback time)
(declare (ignore callback))
(draw-next-frame time))) ; the callback is destroyed afterwards
Calling destroy-proxy in the handler anyway, as code ported from C
does, is harmless. A class's documentation marks these events with
"destroys the proxy after its handler".
Events that create objects
Some events create objects. wl_data_device.data_offer, for instance,
announces a new wl_data_offer. The handler receives the new proxy.
Set its handlers in the handler itself, before you return, so that none
of its events are missed.
Values
| Wire type | In events | In requests |
|---|---|---|
int |
(signed-byte 32) |
(signed-byte 32) |
uint |
(unsigned-byte 32) |
(unsigned-byte 32) |
int/uint with an enum |
keyword; for a bitfield, a list of keywords; an unknown value as an integer | keyword, list of keywords for a bitfield, or integer |
fixed |
single-float |
any real in [-8388608, 8388608), rounded to 1/256 |
string |
string; NIL if the protocol allows null | string; NIL if allowed |
object |
proxy; NIL if allowed | proxy; NIL if allowed |
new_id |
the new proxy | not an argument: the request returns the proxy |
array |
see below | see below |
fd |
integer; you own it and must close it | integer; libwayland duplicates it, so you keep ownership |
Strings are UTF-8 both ways. An event string that is not valid UTF-8
causes an event-error; see Errors.
Fixed-point numbers
Wayland's fixed is 24.8 fixed point. Events deliver it as a
single-float, which allocates nothing. It is exact for magnitudes below
65536, which covers surface coordinates, and is rounded to single-float
precision above. Requests take any real in the type cl-wl:fixed and
round it to the nearest 1/256.
Enums
An argument that the protocol ties to an enum arrives as a keyword:
:button (lambda (pointer serial time button state) …) ; STATE is :PRESSED or :RELEASED
A bitfield enum arrives as a list of keywords:
:capabilities (lambda (seat capabilities) …) ; e.g. (:POINTER :KEYBOARD)
A value the bindings do not know, from a compositor newer than them, arrives as an integer rather than being lost. In a bitfield, the unknown bits are appended to the list as one integer:
(wl:enum-keywords 'wl-seat-capability 11) ; => (:POINTER :KEYBOARD 8)
Requests accept a keyword, a list of keywords for a bitfield, or a raw integer:
(wl-shm-pool-create-buffer pool 0 w h stride :xrgb8888)
(xdg-toplevel-resize toplevel seat serial :bottom-right)
Each enum is a Lisp type, so you can write (typep x 'wl-shm-format).
For a bitfield the type is list. enum-value and enum-keywords convert
by hand:
(wl:enum-value 'wl-seat-capability '(:pointer :keyboard)) ; => 3
(wl:enum-value 'xdg-toplevel-resize-edge :bottom-right) ; => 10
(wl:enum-keywords 'wl-output-transform 3) ; => :|270|
Entries whose names are numbers read as numbers when written plainly, so
they are written with bars: :|90|, :|180|, :|270|.
The keyword lists of bitfields in events are cached and shared per display. Do not modify them destructively; copy one first if you need to.
Arrays
The protocol format does not say what an array holds. The prose says it, so cl-wl lists the arrays whose contents are known and decodes them:
| Array | Delivered as |
|---|---|
xdg_toplevel.configure states |
list of xdg-toplevel-state keywords, such as (:maximized :activated) |
xdg_toplevel.wm_capabilities capabilities |
list of xdg-toplevel-wm-capabilities keywords |
wl_keyboard.enter keys |
(vector (unsigned-byte 32)) of keycodes |
| any other array | (vector (unsigned-byte 8)) |
In requests, an array of integers or enum values takes any sequence of
them, and any other array takes an octet vector.
(cl-wl/scanner:shipped-array-elements) returns the list. See
The Scanner to extend it.
File descriptors
An fd you receive, such as wl_keyboard.keymap's, is yours: close it
with close-fd when you are done. read-keymap reads a keymap fd and
closes it for you, so call one or the other:
(wl:set-event-handlers
keyboard
:keymap (lambda (keyboard format fd size)
(declare (ignore keyboard))
(if (eq format :xkb-v1)
(use-keymap (wl:read-keymap fd size))
(wl:close-fd fd))))
An fd you send, such as wl_shm.create_pool's, is duplicated by
libwayland, so it stays yours too. A shared-memory's fd belongs to the
shared memory, and destroy-shared-memory closes it.
Pipes
Some requests and events pass the end of a pipe, through which data moves
outside the protocol: the clipboard's text, for one. make-pipe makes a
pipe, receive-text reads text until the writer closes its end, and
send-text writes text. Each closes its fd and gives up after :timeout
seconds, so a client that never writes, or never reads, cannot hold up
yours:
(multiple-value-bind (in out) (wl:make-pipe)
(wl-data-offer-receive offer "text/plain;charset=utf-8" out)
(wl:flush display)
(wl:close-fd out) ; the other client holds its own copy
(wl:receive-text in :timeout 1)) ; => the text, or NIL after a second
Do not wait in receive-text for text your own client sends: it is sent
only while your client dispatches events. See
Recipes for the whole clipboard.