The registry and globals
The Wayland protocol is designed in a modular fashion: all capabilities proposed by the server to clients are each represented by modules, that are called "globals" in the Wayland jargon.
A global is a blueprint advertised by the Wayland server to its clients, that the client can instantiate into a Wayland protocol object, which will expose the appropriate requests and events for interacting with the capability they represent. This process of advertising and instantiating is done with another special protocol object: the registry.
The registry
The registry is a protocol object with interface wl_registry
. It is created from
an attached wl_display
via its get_registry()
method. Upon creation, the server will send it a stream
of events telling the client about which globals are available.
A global advertisement is composed of 3 values:
- the global name, an
u32
identifier which represents the global within the globals list (it is not the same thing as the protocol id of the objects created by instantiating this global) - the global interface, a string containing the name of the interface of the protocol objects created from this global
- the global version, an
u32
greater or equal to 1, which is used for protocol versioning
The Wayland protocol can evolve, and the interfaces are versioned. The number the server sends is the highest version it supports. The server must support all lower versions as well.
Upon receiving this list, the client can then instantiate the globals it wishes to use into
protocol objects using the bind
method of the registry.
The two kinds of globals
The various globals that a Wayland server can advertise can be roughly classified in two kinds, depending on whether they can be advertised multiple times by the server.
Singleton globals represent a capability of the compositor. This is something the Wayland server makes possible for clients to do. They generally exist from the start, never change and are advertised only once.
Multi globals however represent some device the server has access to. For example a monitor,
or an input device. These globals can thus exist with multiplicity. For example, the
server will advertise one wl_output
global for each monitor that is plugged in the computer,
each with a different name
. Furthermore, these globals can appear or disappear during the
lifetime of the app, as such devices are plugged in or out.
The registry Global
event signals a new global, while its GlobalRemove
event signals that a
given global has been removed. When a such a global is removed, all the protocol objects derived
from it will generally become inert, and the client is then expected to do cleanup by destroying
them using the appropriate requests.
Global helpers in wayland-client
Tracking the list of globals, their versions, and instantiating them requires some work. wayland-client
provides GlobalList
helper that can automate part of this, by providing you with an initial list of
globals at the startup of your app. This allows you proceed to the initialization of your app and state
in a linear fashion, rather than doing so in the callbacks of your Dispatch
implementation for registry.
This does not replace that implementation though, and you still need to provide it to handle dynamic global
creation or destruction.
Using this abstraction, we can put together a small app that connects to the wayland server, receives the list of globals, and prints them to the console.
The main entry point for using this global helpers is the registry_queue_init
function. This function
takes a reference to your [Connection
] as argument, an will internally:
- Create an [
EventQueue
] - Create a
wl_registry
object, and do an initial blocking roundtrip with the server to retrieve the list of globals - Return a
GlobalList
containing this list of globals, as well as the created [EventQueue
] that you can then use in your app.
The created registry is registered to that event queue, and a proxy to it can be accessed via the
[GlobalList::registry()
] method of the returned GlobalList
.
use wayland_client::{ Connection, Dispatch, QueueHandle, globals::{registry_queue_init, Global, GlobalListContents}, protocol::wl_registry, }; // We need a State struct even if we don't use it struct State; // You need to provide a Dispatch<WlRegistry, GlobalListContents> impl for your app impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State { fn event( state: &mut State, proxy: &wl_registry::WlRegistry, event: wl_registry::Event, // The `GlobalListContents` is a container with an up-to-date list of // the currently existing globals data: &GlobalListContents, conn: &Connection, qhandle: &QueueHandle<State>, ) { /* * This simple program does not handle dynamic global events, * so we don't do anything here. */ } } fn main() { let connection = Connection::connect_to_env().unwrap(); let (globals, queue) = registry_queue_init::<State>(&connection).unwrap(); // Print the contents of the list // We cannot iterate the list directly because of thread-safety constraints, // so we clone it and iterate on the returned Vec for global in globals.contents().clone_list() { println!( "Global #{} with interface \"{}\" and version {}", global.name, global.interface, global.version ); } }
This first section gave you a general overview of how the Wayland protocol and the wayland-client
crate
work. In the next section, we'll start to work with SCTK (Smithay's Client ToolKit), a crate designed to
provide several abstractions that simplify all the plumbing needed to create a Wayland app.