Global objects

As I already mentioned, Wayland is object-oriented, meaning that it's all about objects. There are types of objects (again, called interfaces) that are meant to have multiple instances, like wl_buffer (there may be as many buffers as the client needs). Some others only have one instance (this design pattern is known as singleton), for example there may only be one wl_compositor. There also are interfaces that are somewhat in the middle between the two. For example, there is usually a fixed set of displays (represented by the wl_output interface) attached.

This brings us to the concept of global objects. Global objects represent properties of the compositor and the environment it runs in. Most global objects are the entry points to the corresponding API sets. Let's dive in and list all of them.

Put this program into main.c:

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

void registry_global_handler
(
    void *data,
    struct wl_registry *registry,
    uint32_t name,
    const char *interface,
    uint32_t version
) {
    printf("interface: '%s', version: %u, name: %u\n", interface, version, name);
}

void registry_global_remove_handler
(
    void *data,
    struct wl_registry *registry,
    uint32_t name
) {
    printf("removed: %u\n", name);
}

int main(void)
{
    struct wl_display *display = wl_display_connect(NULL);
    struct wl_registry *registry = wl_display_get_registry(display);
    struct wl_registry_listener registry_listener = {
        .global = registry_global_handler,
        .global_remove = registry_global_remove_handler
    };
    wl_registry_add_listener(registry, &registry_listener, NULL);

    while (1) {
        wl_display_dispatch(display);
    }
}

Compile and run it:

$ gcc main.c -l wayland-client -o runme
$ ./runme
interface: 'wl_drm', version: 2, name: 1
interface: 'wl_compositor', version: 3, name: 2
interface: 'wl_shm', version: 1, name: 3
interface: 'wl_output', version: 2, name: 4
interface: 'wl_output', version: 2, name: 5
interface: 'wl_data_device_manager', version: 3, name: 6
interface: 'gtk_primary_selection_device_manager', version: 1, name: 7
interface: 'zxdg_shell_v6', version: 1, name: 8
interface: 'wl_shell', version: 1, name: 9
interface: 'gtk_shell1', version: 1, name: 10
interface: 'wl_subcompositor', version: 1, name: 11
interface: 'zwp_pointer_gestures_v1', version: 1, name: 12
interface: 'wl_seat', version: 5, name: 13
interface: 'zwp_relative_pointer_manager_v1', version: 1, name: 14
interface: 'zwp_pointer_constraints_v1', version: 1, name: 15
^C

So we've just wrote and ran our own simplified version of the weston-info command. You will need to interrupt it with Ctrl-C after some time, because we have no code for stopping it the right way yet.

Let's see what just happened. First, wl_display is a special global singleton that represents the whole connection. It's very special in many ways. It's the only object that you don't have to create: you have it the moment you set up the connection. The wayland-client library returns it from wl_display_connect() function, which is not a method call of a wl_diplay object, despite its name. In mostly the same way, wl_display_dispatch() is also not a "wl_diplay.dispatch" method, but just a function in wayland-client.

On the other hand, wl_display.get_registry is very much a Wayland request. It's using the new_id mechanism, and we get back the wl_registry object.

wl_registry is another global singleton. Its function is to advertize all the other global objects. Instead of an API that would allow clients to query server's state and environment, Wayland has the API (namely, the registry) that notifies clients of the environment, both upon their startup and whenever something changes (like a new display gets plugged in). This way, Wayland is completely hotplug-based. This is also the way clients get information about what extensions and what versions of Wayland APIs the server supports.

The registry advertises new global objects with wl_registry.global event and their removal with wl_registry.global_remove event.

Now, all of this information may be very useful further down the road, but for now we'll just do some magic to get a few global objects we're going to need:

#include <stdio.h>
#include <string.h>

#include <wayland-client.h>

struct wl_compositor *compositor;
struct wl_shm *shm;
struct wl_shell *shell;

void registry_global_handler
(
    void *data,
    struct wl_registry *registry,
    uint32_t name,
    const char *interface,
    uint32_t version
) {
    if (strcmp(interface, "wl_compositor") == 0) {
        compositor = wl_registry_bind(registry, name,
            &wl_compositor_interface, 3);
    } else if (strcmp(interface, "wl_shm") == 0) {
        shm = wl_registry_bind(registry, name,
            &wl_shm_interface, 1);
    } else if (strcmp(interface, "wl_shell") == 0) {
        shell = wl_registry_bind(registry, name,
            &wl_shell_interface, 1);
    }
}

Basically, the "name" wl_registry.global gets passed is not yet the real object ID, and we use wl_registry_bind() to set it up the proper way. The function in wayland-client is a bit different in signature to the underlying wl_registry.bind request due to its unusual nature of creating new objects of statically unknown type.

Let's also move the registry_listener object definition out of main():

void registry_global_remove_handler
(
    void *data,
    struct wl_registry *registry,
    uint32_t name
) {}

const struct wl_registry_listener registry_listener = {
    .global = registry_global_handler,
    .global_remove = registry_global_remove_handler
};

Next, in main() itself, we'll wait for the initial burst of global events, which is guaranteed to include all currently available globals:

int main(void)
{
    struct wl_display *display = wl_display_connect(NULL);
    struct wl_registry *registry = wl_display_get_registry(display);
    wl_registry_add_listener(registry, &registry_listener, NULL);

    // wait for the "initial" set of globals to appear
    wl_display_roundtrip(display);

    // all our objects should be ready!
    if (compositor && shm && shell) {
        printf("Got them all!\n");
    } else {
        printf("Some required globals unavailable\n");
        return 1;
    }

    while (1) {
        wl_display_dispatch(display);
    }
}

Here, wl_display_roundtrip (again, that's not a request on wl_display, but a special wayland-client function which actually uses wl_display.sync request under-the-hood) is a function that blocks the client until all pending methods (that is requests and events) are sent and received, and all the event listeners are executed.

Compile and run it as usual:

$ gcc main.c -l wayland-client -o runme
$ ./runme
Got them all!
^C

Great!

results matching ""

    No results matching ""