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.
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.
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):
libwayland-client0
libwayland-dev
On Ubuntu, the headers are placed into /usr/include
.
Complilation is just
cc -o connect connect.c -lwayland-client
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, ®istry_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
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
"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
.
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, ®istry_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
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
wl_egl_window_create
wl_egl_window_destroy
wl_egl_window_resize
wl_egl_window_get_attached_size
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