First of all, Wayland itself is just a wire protocol specification. It is designed to work over UNIX sockets and to carry basic types for two processes to communicate. These two processes are called the client and the compositor (or the server).
It is very important to notice that the Wayland protocol is fully asynchronuous. That means once you sent a message, you are done with it. There is no “return value”.
But it is not limited to basic types. Wayland also define the concept of objects and interfaces. A Wayland object is represented on the wire by an id, and will implement an interface. These interfaces are defined using an XML specification file. The object id is only meanningful in the context of one connection, so you cannot share an id between processes and assume it will identify the same object.
An interface has a name, a version, and contains a set of: - requests - events - enum - request: they are messages sent by a client to the compositor. They have names and can have arguments. - event: they are messages sent by the compositor to a client. Like requests, they have names and can have arguments. - enum: they are lists of predefined integer values that may then used by their names.
Let’s introduce our very first interface specification:
<interface name="wcc_cat" version="1">
</interface>
It is quite bare-boned for now, but we can see its name (wcc_cat) and version (1). The name is there to namespace all the things the interface can do. The version is used as a backward compatibility system. Each time you add something to an interface, you bump the version. The compositor will advertise the version it supports (we will see later how) and the client will tell the compositor which version it will use. This way, both sides know exactly what to expect from the other. When extending an interface, make sure to do that at the end of its tag, since the wire opcodes are derived from the XML ordering.
Now, adding a request, an event and an enum:
<interface name="wcc_cat" version="2">
<enum name="place" since="2">
<entry name="head" value="1" />
<entry name="behing_ears" value="2" />
</enum>
<request name="pet" since="2">
<arg name="where" type="uint" enum="place" />
</request>
<event name="purr" since="2">
</event>
</interface>
We now have a request named pet, an event called purr and an enum named place. This interface can be used to pet a cat in different places. The cat can purr whenever it likes. As you can see, we now have the interface in version 2, and all of the added elements have since="2" to indicate they are present since version 2 only.
We now know how to use an object, but to do so, we need one. A client can create an object using a request. This request needs a special argument of type new_id. Let’s check on our cat’s collar:
<interface name="wcc_cat" version="3">
<enum name="place" since="2">
<entry name="head" value="1" />
<entry name="behing_ears" value="2" />
</enum>
<request name="pet" since="2">
<arg name="where" type="uint" enum="place" />
</request>
<event name="purr" since="2">
</event>
<request name="pet" since="3">
<arg name="id" type="new_id" interface="wcc_collar" />
</request>
</interface>
<interface name="wcc_collar" version="3">
</interface>
Now, do you remember how requests can not have return values? That means the compositor cannot refuse to create an object. The client will create a new id and send it to the compositor as the id for the created object. In the C API, it is translated to a return value, but this value is always usable, as we saw, and you must handle that properly. Some interfaces where it makes sense have a special event to “accept” or “reject” the created object, so you must read the protocol specification to know what to expect and what to do.
Also, you probably noticed that we bumped the wcc_cat version and that wcc_collar have the same one. It’s because wcc_cat is what we call a factory, it has a request to create objects of the wcc_collar interface. A factory and all interfaces it can create objects of will have the same version. Each time you add a request, an event or an enum in a factory or one of its linked interface, you have to bump the version for all of them.
True. But luckily for us, Wayland has that covered.
Whenever you create a new Wayland connection, a root object is created. It’s called wl_display and it represents the connection itself. This object is a singleton, and its interface is defined in the Wayland core protocol. Although it is automatically created and always present, it is not our entry point per se. Its main usage will be to get the wl_registry object (also a singleton), that will lead us to the useful stuff.
The registry is the root factory of a Wayland connection. It is there to advertise global objects. These are objects without a factory, they do not depend on another object to live.
The Wayland core protocol also defines a number of useful interfaces that would be of interest for all clients.
Now that you (hopefully) have a basic overview of how Wayland is working, we can continue to the client and compositor courses.