Table of Contents
Getting Started
Requirements
- SBCL.
libwayland-client1.20 or newer, the shared library only. On Debian/Ubuntu this islibwayland-client0.cl-wl/cursoralso needslibwayland-cursor, which comes with it (libwayland-cursor0).- A running Wayland compositor, found through
$WAYLAND_DISPLAY. The test compositor needs none; see Testing with the Test Compositor.
No C compiler, Wayland headers or wayland-scanner is needed. The
bindings are generated ahead of time and committed.
Installing
cl-wl is not in Quicklisp. With stift,
require it in your project's stiftfile:
stift get https://fakehacker.cc/dxtr/cl-wl.git
cl-wl's own stiftfile requires the one library cl-wl depends on,
cl-ffi, which is not in Quicklisp
either, so it comes along as an indirect entry.
cl-wl/test-compositor also needs
cl-threads, which comes along
the same way, and libwayland-server.
Without stift, clone the repositories somewhere ASDF looks, such as
~/common-lisp/.
Loading
Load the system for each protocol you use. cl-wl includes the core
protocol. The others each load cl-wl:
(asdf:load-system :cl-wl) ; core protocol only
(asdf:load-system :cl-wl/xdg-shell) ; + windows
(asdf:load-system :cl-wl/xdg-decoration) ; + server-side decorations
(asdf:load-system :cl-wl/viewporter)
(asdf:load-system :cl-wl/fractional-scale)
(asdf:load-system :cl-wl/cursor-shape) ; also loads cl-wl/tablet
(asdf:load-system :cl-wl/text-input)
(asdf:load-system :cl-wl/pointer-constraints)
(asdf:load-system :cl-wl/relative-pointer)
(asdf:load-system :cl-wl/xdg-dialog)
(asdf:load-system :cl-wl/cursor) ; cursor themes, through libwayland-cursor
Packages
| Package | Holds |
|---|---|
cl-wl |
The runtime: connect, bind, dispatch, set-event-handlers, destroy-proxy, shared memory, conditions. Usually given the local nickname wl. |
cl-wl/wayland |
The core protocol: wl-compositor, wl-surface-commit, wl-shm-format, … |
cl-wl/xdg-shell |
xdg-wm-base, xdg-toplevel-set-title, … |
cl-wl/xdg-decoration |
zxdg-decoration-manager-v1, … |
cl-wl/viewporter |
wp-viewporter, wp-viewport-set-destination, … |
cl-wl/fractional-scale |
wp-fractional-scale-manager-v1, … |
cl-wl/tablet |
zwp-tablet-manager-v2, zwp-tablet-tool-v2, … |
cl-wl/cursor-shape |
wp-cursor-shape-manager-v1, wp-cursor-shape-device-v1-set-shape, … |
cl-wl/text-input |
zwp-text-input-manager-v3, zwp-text-input-v3-enable, … |
cl-wl/pointer-constraints |
zwp-pointer-constraints-v1, zwp-locked-pointer-v1, … |
cl-wl/relative-pointer |
zwp-relative-pointer-manager-v1, zwp-relative-pointer-v1, … |
cl-wl/xdg-dialog |
xdg-wm-dialog-v1, xdg-dialog-v1-set-modal, … |
cl-wl/cursor |
load-cursor-theme, find-cursor, cursor-image-buffer, …: not a protocol, but cursor themes. |
Give each package you use a local nickname, as the example on Home does:
(defpackage #:my-client
(:use #:cl)
(:local-nicknames (#:wl #:cl-wl)
(#:wlp #:cl-wl/wayland)
(#:xdg #:cl-wl/xdg-shell)))
Protocol names never clash between the protocol packages, because every
Wayland protocol prefixes its interface names, so you may also :use
them and nickname only cl-wl, as the program below does. Do not :use
cl-wl itself. Its bind, flush and display are names you will want
for yourself.
A first window
A window needs pixels. The simplest source of pixels is a shared-memory
buffer, wl_shm. A shared-memory object is a block of memory the
client and the compositor both map. The function below fills one with a
single colour and cuts a buffer from it:
(asdf:load-system :cl-wl/xdg-shell)
(defpackage #:shm-window
(:use #:cl #:cl-wl/wayland #:cl-wl/xdg-shell)
(:local-nicknames (#:wl #:cl-wl)))
(in-package #:shm-window)
(defun make-shm-buffer (shm width height)
"Return a WIDTH x HEIGHT XRGB8888 wl_buffer filled with one colour."
(let* ((memory (wl:make-shared-memory (* 4 width height)))
(pool (wl-shm-create-pool shm (wl:shared-memory-fd memory)
(wl:shared-memory-size memory)))
(buffer (wl-shm-pool-create-buffer pool 0 width height (* 4 width) :xrgb8888)))
(wl:write-shared-memory memory (make-array (* width height)
:element-type '(unsigned-byte 32)
:initial-element #xff3070c0))
;; The buffer keeps the pool's memory alive in the compositor.
(wl-shm-pool-destroy pool)
(wl:destroy-shared-memory memory)
buffer))
(defun run (&key (seconds 5))
(wl:with-display (display)
(let* ((compositor (wl:bind display 'wl-compositor))
(shm (wl:bind display 'wl-shm))
(wm-base (wl:bind display 'xdg-wm-base))
(surface (wl-compositor-create-surface compositor))
(xdg-surface (xdg-wm-base-get-xdg-surface wm-base surface))
(toplevel (xdg-surface-get-toplevel xdg-surface))
(width 320)
(height 240)
(closed nil))
;; The compositor pings to see whether the client is alive.
(wl:set-event-handlers wm-base
:ping (lambda (wm-base serial) (xdg-wm-base-pong wm-base serial)))
;; It suggests a size (0 x 0: our choice) and asks us to close.
(wl:set-event-handlers toplevel
:configure (lambda (toplevel w h states)
(declare (ignore toplevel states))
(when (plusp w) (setf width w height h)))
:close (lambda (toplevel)
(declare (ignore toplevel))
(setf closed t)))
;; Each configure sequence ends with xdg_surface.configure: ack it,
;; then draw at the size it settled on.
(wl:set-event-handlers xdg-surface
:configure (lambda (xdg-surface serial)
(xdg-surface-ack-configure xdg-surface serial)
(let ((buffer (make-shm-buffer shm width height)))
(wl:set-event-handlers buffer :release #'wl:destroy-proxy)
(wl-surface-attach surface buffer 0 0)
(wl-surface-damage-buffer surface 0 0 width height)
(wl-surface-commit surface))))
(xdg-toplevel-set-title toplevel "cl-wl")
(xdg-toplevel-set-app-id toplevel "example.shm-window")
;; A commit without a buffer asks for the first configure.
(wl-surface-commit surface)
(let ((deadline (+ (get-universal-time) seconds)))
(loop until (or closed (> (get-universal-time) deadline))
do (wl:dispatch display :timeout 0.1)))
;; Children first: the protocol requires this order.
(dolist (object (list toplevel xdg-surface surface wm-base))
(wl:destroy-proxy object)))))
(run)
A blue window titled "cl-wl" stays on screen until you close it or five seconds have passed.
The program shows the shape most Wayland clients have:
with-displayconnects, and disconnects on exit.bindgets the globals the client needs: here the compositor, shared memory and the window manager base. See Globals.- Requests are ordinary functions such as
wl-compositor-create-surface. A request that creates an object returns its proxy. set-event-handlerssays what to do with each event, such as:pingand:configure. See Requests and Events.dispatchwaits for events and runs their handlers. See Event Loops.destroy-proxydestroys an object, sending its interface's destructor request when it has one.disconnect, whichwith-displaycalls, destroys whatever is left. See Lifetimes and Threads.
Where next
- Concepts explains the vocabulary.
- Recipes has frame callbacks, keyboards, pointers, outputs, decorations and Vulkan surfaces.
- The documentation of every proxy class lists its events, and that of
every request function lists its arguments:
(documentation 'xdg-toplevel 'type),(documentation 'wl-surface-attach 'function).