Upto: Table of Contents of full book "Programming Wayland Clients"

This project hasn't been updated since mid-2017. Wayland has moved on since then, so the information here may be out of date, and there is no guarantee the programs still work. You are recommended to look at The Wayland Protocol for more up-todate information, or at A better way to read Wayland documentation . .

Programming a Wayland Client

Wayland uses clients talking to a Wayland compositor, which talks to the hardware. This chapter looks at what can be done using the Wayland API alone to build clients.

Resources

Introduction

Wayland has a client-server model. The clients are typically applications that interact with a user, such as clocks, editors, web browsers. The server is a Wayland compositor which takes the output from clients, builds them into a display and shows them on a screen of some kind.

For a client, some parts of this are similar to X: open a connection to a server create windows and draw. There is a significant difference: "create windows and draw" is not part of the Wayland model! That belongs to a later chapter, after we have looked at things like EGL.

Wayland is a "work in progress". What this generally means is that the documentation is completely out of sync with the code, examples you find on the net don't work and getting things to work is - at best - tricky. Wayland conforms to this. The Wayland reference manual describes the client API in its Chapter 5. You might as well ignore that, because it isn't helpful as well as being incorrect. Unless I keep updating it, this chapter will be out of date soon too. The Wayland source doesn't have client examples. The best source I have found for examples is the Weston source! For the record, this chapter looks at Wayland v1.4 as found in Ubuntu 14.04.

Connecting to a server

The simplest task is to connect to a server and then disconnect. This is accomplished by the program connect.c.

#include <stdio.h>
#include <stdlib.h>
#include <wayland-client.h>

struct wl_display *display = NULL;

int main(int argc, char **argv) {

    display = wl_display_connect(NULL);
    if (display == NULL) {
	fprintf(stderr, "Can't connect to display\n");
	exit(1);
    }
    printf("connected to display\n");

    wl_display_disconnect(display);
    printf("disconnected from display\n");
    
    exit(0);
}

      

Packages required (Ubuntu):

On Ubuntu, the headers are placed into /usr/include. Complilation is just

	
cc -o connect connect.c -lwayland-client
	
      

Server information

Clients dispatch messages to the server. These will be responded to, generally asynchronously. Like many other situations like this, the responses are managed by handlers or listeners which are registered with the framework.

Wayland can wait for a synchronous response using the wl_display_roundtrip call, which will block until the server responds. Of course, this must be used with care, as it can slow down the system dramatically, or in the worst case simply deadlock.

The server has control of a number of objects. In Wayland, these are quite high-level, such as a DRM manager, a compositor, a text input manager and so on. In earlier versions of Wayland these were regarded as "global" objects, but now they are accessible through a registry.

The registry objects exist on the server. The client often needs to get handles to these, as proxy objects. The first handle is to the registry itself, which is done by a dedicated call, wl_display_get_registry. Two listeners are added to this by the call wl_registry_add_listener. The listeners are functions, one for new proxy objects and the other to remove proxy objects. They are both wrapped up in a struct of type wl_registry_listener which actually contains both functions.

There isn't much that a client can do until it gets hold of proxies for important things like the compositor. Consequently it makes sense to make a blocking round-trip call to get the registry objects.

A registry object has a string name, the interface and an integer id, the id. The id is what is used in further calls, while the interface allows the program to work out which registry object it is. A proxy for a registry object is obtained by binding the id to a suitable data type (such as wl_compositor_interface).

A program to find a proxy for the compositor, while just listing the other registry objects is registry.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>

struct wl_display *display = NULL;
struct wl_compositor *compositor = NULL;

static void
global_registry_handler(void *data, struct wl_registry *registry, uint32_t id,
	       const char *interface, uint32_t version)
{
    printf("Got a registry event for %s id %d\n", interface, id);
    if (strcmp(interface, "wl_compositor") == 0)
        compositor = wl_registry_bind(registry, 
				      id, 
				      &wl_compositor_interface, 
				      1);
}

static void
global_registry_remover(void *data, struct wl_registry *registry, uint32_t id)
{
    printf("Got a registry losing event for %d\n", id);
}

static const struct wl_registry_listener registry_listener = {
    global_registry_handler,
    global_registry_remover
};


int main(int argc, char **argv) {

    display = wl_display_connect(NULL);
    if (display == NULL) {
	fprintf(stderr, "Can't connect to display\n");
	exit(1);
    }
    printf("connected to display\n");

    struct wl_registry *registry = wl_display_get_registry(display);
    wl_registry_add_listener(registry, &registry_listener, NULL);

    wl_display_dispatch(display);
    wl_display_roundtrip(display);

    if (compositor == NULL) {
	fprintf(stderr, "Can't find compositor\n");
	exit(1);
    } else {
	fprintf(stderr, "Found compositor\n");
    }

    wl_display_disconnect(display);
    printf("disconnected from display\n");
    
    exit(0);
}

      
The output looks like
Registry list
Registry

On the RPi 3 under Fedora 25, the output is

	
connected to display
Got a registry event for wl_drm id 1
Got a registry event for wl_compositor id 2
Got a registry event for wl_shm id 3
Got a registry event for wl_output id 4
Got a registry event for wl_data_device_manager id 5
Got a registry event for gtk_primary_selection_device_manager id 6
Got a registry event for zxdg_shell_v6 id 7
Got a registry event for wl_shell id 8
Got a registry event for gtk_shell1 id 9
Got a registry event for wl_subcompositor id 10
Got a registry event for zwp_pointer_gestures_v1 id 11
Got a registry event for zwp_tablet_manager_v2 id 12
Got a registry event for wl_seat id 13
Got a registry event for zwp_relative_pointer_manager_v1 id 14
Got a registry event for zwp_pointer_constraints_v1 id 15
Got a registry event for zxdg_exporter_v1 id 16
Got a registry event for zxdg_importer_v1 id 17
Found compositor
disconnected from display
	
      

Creating a surface

"A surface is a rectangular area that is displayed on the screen. It has a location, size and pixel contents." (Wayland specification). So to draw anything, we need a Wayland surface to draw into. We build a surface using a compositor by the call to wl_compositor_create_surface.

Creating a shell surface

Surfaces can exist on many different devices, and there can be different Wayland servers for each. For servers with desktop-style interfaces, Wayland supplies a further surface, a shell surface. First we have to get a proxy for a shell from the registry. A shell surface is created from a shell by wl_shell_get_shell_surface. Then in order to show a surface on such a device, the surface must be wrapped in a shell surface which is then set to be a toplevel surface.

This is done by

	
shell_surface = wl_shell_get_shell_surface(shell, surface);
wl_shell_surface_set_toplevel(shell_surface);
	
      

The resulting code is in surface.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-server.h>
#include <wayland-client-protocol.h>
#include <wayland-egl.h>

struct wl_display *display = NULL;
struct wl_compositor *compositor = NULL;
struct wl_surface *surface;
struct wl_shell *shell = NULL;
struct wl_shell_surface *shell_surface;

static void
global_registry_handler(void *data, struct wl_registry *registry, uint32_t id,
	       const char *interface, uint32_t version)
{
    if (strcmp(interface, "wl_compositor") == 0) {
        compositor = wl_registry_bind(registry, 
				      id, 
				      &wl_compositor_interface, 
				      1);
    } else if (strcmp(interface, "wl_shell") == 0) {
        shell = wl_registry_bind(registry, id,
                                 &wl_shell_interface, 1);
    }
}

static void
global_registry_remover(void *data, struct wl_registry *registry, uint32_t id)
{
    printf("Got a registry losing event for %d\n", id);
}

static const struct wl_registry_listener registry_listener = {
    global_registry_handler,
    global_registry_remover
};


int main(int argc, char **argv) {

    display = wl_display_connect(NULL);
    if (display == NULL) {
	fprintf(stderr, "Can't connect to display\n");
	exit(1);
    }
    printf("connected to display\n");

    struct wl_registry *registry = wl_display_get_registry(display);
    wl_registry_add_listener(registry, &registry_listener, NULL);

    wl_display_dispatch(display);
    wl_display_roundtrip(display);

    if (compositor == NULL) {
	fprintf(stderr, "Can't find compositor\n");
	exit(1);
    } else {
	fprintf(stderr, "Found compositor\n");
    }

    surface = wl_compositor_create_surface(compositor);
    if (surface == NULL) {
	fprintf(stderr, "Can't create surface\n");
	exit(1);
    } else {
	fprintf(stderr, "Created surface\n");
    }

    if (shell == NULL) {
	fprintf(stderr, "Haven't got a Wayland shell\n");
	exit(1);
    }
    shell_surface = wl_shell_get_shell_surface(shell, surface);
    if (shell_surface == NULL) {
	fprintf(stderr, "Can't create shell surface\n");
	exit(1);
    } else {
	fprintf(stderr, "Created shell surface\n");
    }
    wl_shell_surface_set_toplevel(shell_surface);

    wl_display_disconnect(display);
    printf("disconnected from display\n");
    
    exit(0);
}

      

The output is

	
connected to display
Found compositor
Created surface
Created shell surface
disconnected from display
	
      

What's next?

To draw anything, we have to create a buffer for a surface, put something into it and inform the compositor that the buffer is ready for drawing. That's when we hit a hiccup: Wayland doesn't know how to create buffers, nor how to draw into them. That's the responsibility of other drawing packages, such as OpenGL.

In principle, any system capable of creating buffers and drawing into them should be usable. At present, Wayland only supports a system called EGL. It does so by the four functions

The rest is upto EGL.

So next we have to detour through EGL.


Copyright © Jan Newmarch, jan@newmarch.name

If you like this book, please contribute using Flattr
or donate using PayPal