home index

wayland ramblings

2025-03-02

I recently switched my linux config over to wayland. In doing so, I decided to learn how the protocol works and how to write simple desktop applications with it. The result was picky, a basic color picker.

obligatory X11 paragraph

I don't have a lot of X11 programming experience. The only interaction I've ever had with it was patching exisiting programs. You don't have to be an expert to be able to tell that it is a horrible mess completely unsuitable for modern use. Oh well, I guess it had an impressively long lifespan anyway, predating linux and somehow still being relevant is no slouch.

protocol basics

Clients talk with servers using messages, usually over unix sockets. The message format is called the "wire protocol", it defines its own types that libraries need to be able to decode. Messages from clients to the server are called "requests". Conversely, the server communicates with its clients using "events". There is usually only one wayland server, referred to as the "compositor", though you can nest them at will.

Wayland is object oriented at its core. Both sides operate using the notion of objects with various capabilities. Messages are really just invocations of methods on those objects.

When you open a new client-server connection, there exists only one object, namely wl_display. Its existence is implicit. It is referred to as the 'core global object'. As part of its interface it defines a get_registry request, which creates a registry object wl_registry. That object can in turn be used to create/register other objects such as wl_shm (shared memory management), wl_seat (input devices), or xdg_wm_base (turning wayland surfaces into desktop windows) etc.

Wayland is in many ways atomic. Changes to surfaces do nothing until commited. Some events are sent in batches with clearly defined boundaries.

enter libwayland

While you could of course connect to the compositor's unix socket and send raw wire protocol messages, in practice most developers are going to use libwayland (or another language's bindings to it).

Handling events received from the server in code is pretty simple. Let's say we want to listen for keyboard input events. We first need to set up a listener for these events using the wl_seat object.

wl_seat_add_listener(wl_seat, &seat_listener, state);

Where:

seat_listener is an instance of another struct defined by libwayland. Listeners are essentially structs of function pointers with fields corresponding to events.

const struct wl_seat_listener seat_listener = {
    .capabilities = seat_capabilities_handler,
    // .. fields omitted ..
};

The capabilties event advertises what input devices are available in a given seat. Let's take a look at seat_capabilites_handler.

static void
seat_capabilities_handler(void *data, struct wl_seat *seat,
                          uint32_t caps)
{
    AppState *state = data;

    if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
        fprintf(stderr, "seat has no keyboard!\n");
        exit(1);
    }

    state->wl_keyboard = wl_seat_get_keyboard(seat);
    wl_keyboard_add_listener(state->wl_keyboard,
                             &keyboard_listener, state);
}

keyboard_listener in turn contains the key event.

const struct wl_keyboard_listener keyboard_listener = {
    .key = keyboard_key_handler,
    // .. fields omitted ..
};
static void
wl_keyboard_key(void *data, struct wl_keyboard *keyboard,
                uint32_t serial, uint32_t time,
                uint32_t key, enum wl_keyboard_key_state s)
{
    if (key == KEY_LEFTSHIFT) {
        // *do stuff*
    }
}

Where KEY_LEFTSHIFT is defined by <linux/input-event-codes.h>

wayland-scanner

What I found interesting is that all wayland protocols are defined in xml files, including the core wayland.xml. wayland-scanner is a code generation library that converts these xml files into C code. There of course exist implementations for other languages, like smithay's wayland-scanner for rust.

what's next?

I might play around with the compositor (server) side of the protocol in the future. The word out on the street is that creating a compositor is not for the faint of heart though, so that challenge might have to wait until a very rainy day indeed.

useful resources

  1. docs and websites

  2. example code