sl/plan9 OK, uriel.

date: 2017-06-10 layout: post title: An introduction to Wayland tags: [wayland, instructional]

Wayland is the new hotness on the Linux graphics stack. There are plenty of introductions to Wayland that give you the high level details on how the stack is laid out how applications talk directly to the kernel with EGL and so on, but that doesn't give you much practical knowledge. I'd like to instead share with you details about how the protocol actually works and how you can use it.

Let's set aside the idea that Wayland has anything to do with graphics. Instead we'll treat it like a generic protocol for two parties to share and talk about resources. These resources are at the heart of the Wayland protocol - resources like a keyboard or a surface to draw on. Each of these resources exposes an API for engaging with it, including functions you can call and events you can listen to.

Some of these resources are globals, which are exactly what they sound like. These resources include things like wloutputs, which are the displays connected to your graphics card. Other resources, like wlsurface, require the client to ask the server to allocate new resources when needed. Negotiating for new resources is generally possible through the API of some global resource.

Your Wayland client gets started by obtaining a reference to the wldisplay like so:

`c struct wl_display *display = wl_display_connect(NULL); ”`

This establishes a connection to the Wayland server. The most important role of the display, from the client perspective, is to provide the wlregistry. The registry enumerates the globals available on the server.

`c struct wl_registry *registry = wl_display_get_registry(display); ”`

The registry emits an event every time the server adds or removes a global. Listening to these events is done by providing an implementation of a wlregistrylistener, like so:

c void global_add(void *our_data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { // TODO }

void globalremove(void ourdata, struct wlregistry registry, uint32t name) { // TODO }

struct wlregistrylistener registrylistener = { .global = globaladd, .globalremove = globalremove }; ”

Interfaces like this are used to listen to events from all kinds of resources. Attaching the listener to the registry is done like this:

`c void *our_data = /* arbitrary state you want to keep around */; wl_registry_add_listener(registry, &registry_listener, our_data); wl_display_dispatch(display); ”`

During the wl_display_dispatch, the global_add function is called for each global on the server. Subsequent calls to wl_display_dispatch may call global_remove when the server destroys globals. The name passed into global_add is more like an ID, and identifies this resource. The interface tells you what API the resource implements, and distinguishes things like a wloutput from a wlseat. The API these resources implement are described with XML files like this:

`xml <?xml version="1.0" encoding="UTF-8"?> ”`

A typical Wayland server implementing this protocol would create a gamma_control_manager global and add it to the registry. The client then binds to this interface in our global_add function like so:

`c include "wayland-gamma-control-client-protocol.h" // ... struct wl_output *example; // is a constant: "gamma_control_manager" if (strcmp(interface, == 0) { struct gamma_control_manager *mgr = wl_registry_bind(registry, name, &gamma_control_manager_interface, version); struct gamma_control *control = gamma_control_manager_get_gamma_control(mgr, example); gamma_control_set_gamma(control, ...); } ”`

These functions are generated by running the XML file through wayland-scanner, which outputs a header and C glue code. These XML files are called "protocol extensions" and let you add arbitrary extensions to the protocol. The core Wayland protocols themselves are described with similar XML files.

Using the Wayland protocol to create a surface to display pixels with consists of these steps:

  1. Obtain a wldisplay and use it to obtain a wlregistry.
  2. Scan the registry for globals and grab a wlcompositor and a wlshmpool.
  3. Use the wlcompositor interface to create a wlsurface.
  4. Use the wlshell interface to describe your surface's role.
  5. Use the wlshm interface to allocate shared memory to store pixels in.
  6. Draw something into your shared memory buffers.
  7. Attach your shared memory buffers to the wlsurface.

Let's break this down.

The wlcompositor provides an interface for interacting with the compositor, that is the part of the Wayland server that composites surfaces onto the screen. It's responsible for creating surface resources for clients to use via wl_compositor_create_surface. This creates a wlsurface resource, which you can attach pixels to for the compositor to render.

The role of a surface is undefined by default - it's just a place to put pixels. In order to get the compositor to do anything with them, you must give the surface a role. Roles could be anything - desktop background, system tray, etc - but the most common role is a shell surface. To create these, you take your wlsurface and hand it to the wlshell interface. You'll get back a wlshellsurface resource, which defines your surface's purpose and gives you an interface to do things like set the window title.

Attaching pixel buffers to a wlsurface is pretty straightforward. There are two primary ways of creating a buffer that both you and the compositor can use: EGL and shared memory. EGL lets you use an OpenGL context that renders directly on the GPU with minimal compositor involvement (fast) and shared memory (via wlshm) allows you to simply dump pixels in memory and hand them to the compositor (flexible). There are many other Wayland interfaces I haven't covered, giving you everything from input devices (via wlseat) to clipboard access (via wldatasource), plus many protocol extensions. Learning more about these is an exercise left to the reader.

Before we wrap this article up, let's take a brief moment to discuss the server. Most of the concepts here are already familiar to you by now. The Wayland server also utilizes a wldisplay, but differently from the client. The display on the server has ownership over the event loop, via wleventloop. The event loop of a Wayland server might look like this:

`c struct wl_display *display = wl_display_create(); // ... struct wl_event_loop *event_loop = wl_display_get_event_loop(display); while (true) { wl_event_loop_dispatch(event_loop, 0); } ”`

The event loop has a lot of helpful utilities for the Wayland server to take advantage of, including internal event sources, timers, and file descriptor monitoring. Before starting the event loop the server is going to start obtaining its own resources and creating Wayland globals for them with wl_global_create:

`c struct wl_global *global = wl_global_create( display, &wl_output_interface, 1 /* version */, our_data, wl_output_bind); ”`

The wl_output_bind function here is going to be called when a client attempts to bind to this resource via wl_registry_bind, and will look something like this:

`c void wl_output_bind(struct wl_client *client, void *our_data, uint32_t their_version, uint32_t id) { struct wl_resource *resource = wl_resource_create_checked( client, wl_output_interface, their_version, our_version, id); // ...send output modes or whatever else you need to do } ”`

Some of the resources a server is going to be managing might include:

  • DRM state for direct access to outputs
  • GLES context (or another GL implementation) for rendering
  • libinput for input devices
  • udev for hotplugging

Through the Wayland protocol, the server provides an abstraction on top of these resources and offers them to clients. Some servers go further, with novel ways of compositing clients or handling input. Some provide additional interactivity, such as desktop shells that are actually running in the compositor rather than external clients. Other servers are designed for mobile use and provide a user experience that more closely matches the mobile experience than the traditional desktop experience. Wayland is designed to be flexible!