logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Introduction-to-Wayland.md (9677B)


  1. ---
  2. date: 2017-06-10
  3. layout: post
  4. title: An introduction to Wayland
  5. tags: [wayland, instructional]
  6. ---
  7. Wayland is the new hotness on the Linux graphics stack. There are plenty of
  8. introductions to Wayland that give you the high level details on how the stack
  9. is laid out how applications talk directly to the kernel with EGL and so on, but
  10. that doesn't give you much practical knowledge. I'd like to instead share with
  11. you details about how the protocol actually works and how you can use it.
  12. Let's set aside the idea that Wayland has anything to do with graphics. Instead
  13. we'll treat it like a generic protocol for two parties to share and talk about
  14. resources. These resources are at the heart of the Wayland protocol - resources
  15. like a keyboard or a surface to draw on. Each of these resources exposes an API
  16. for engaging with it, including functions you can call and *events* you can
  17. listen to.
  18. Some of these resources are *globals*, which are exactly what they sound like.
  19. These resources include things like **wl_outputs**, which are the displays
  20. connected to your graphics card. Other resources, like **wl_surface**, require
  21. the client to ask the server to allocate new resources when needed. Negotiating
  22. for new resources is generally possible through the API of some global resource.
  23. Your Wayland client gets started by obtaining a reference to the **wl_display**
  24. like so:
  25. ```c
  26. struct wl_display *display = wl_display_connect(NULL);
  27. ```
  28. This establishes a connection to the Wayland server. The most important role of
  29. the display, from the client perspective, is to provide the **wl_registry**.
  30. The registry enumerates the globals available on the server.
  31. ```c
  32. struct wl_registry *registry = wl_display_get_registry(display);
  33. ```
  34. The registry emits an *event* every time the server adds or removes a global.
  35. *Listening* to these events is done by providing an implementation of a
  36. **wl_registry_listener**, like so:
  37. ```c
  38. void global_add(void *our_data,
  39. struct wl_registry *registry,
  40. uint32_t name,
  41. const char *interface,
  42. uint32_t version) {
  43. // TODO
  44. }
  45. void global_remove(void *our_data,
  46. struct wl_registry *registry,
  47. uint32_t name) {
  48. // TODO
  49. }
  50. struct wl_registry_listener registry_listener = {
  51. .global = global_add,
  52. .global_remove = global_remove
  53. };
  54. ```
  55. Interfaces like this are used to listen to events from all kinds of resources.
  56. Attaching the listener to the registry is done like this:
  57. ```c
  58. void *our_data = /* arbitrary state you want to keep around */;
  59. wl_registry_add_listener(registry, &registry_listener, our_data);
  60. wl_display_dispatch(display);
  61. ```
  62. During the `wl_display_dispatch`, the `global_add` function is called for each
  63. global on the server. Subsequent calls to `wl_display_dispatch` may call
  64. `global_remove` when the server destroys globals. The `name` passed into
  65. `global_add` is more like an ID, and identifies this resource. The `interface`
  66. tells you what API the resource implements, and distinguishes things like a
  67. **wl_output** from a **wl_seat**. The API these resources implement are
  68. described with XML files like this:
  69. ```xml
  70. <?xml version="1.0" encoding="UTF-8"?>
  71. <!-- For copyright information, see https://git.io/vHyIB -->
  72. <protocol name="gamma_control">
  73. <interface name="gamma_control_manager" version="1">
  74. <request name="destroy" type="destructor"/>
  75. <request name="get_gamma_control">
  76. <arg name="id" type="new_id" interface="gamma_control"/>
  77. <arg name="output" type="object" interface="wl_output"/>
  78. </request>
  79. </interface>
  80. <interface name="gamma_control" version="1">
  81. <enum name="error">
  82. <entry name="invalid_gamma" value="0"/>
  83. </enum>
  84. <request name="destroy" type="destructor"/>
  85. <request name="set_gamma">
  86. <arg name="red" type="array"/>
  87. <arg name="green" type="array"/>
  88. <arg name="blue" type="array"/>
  89. </request>
  90. <request name="reset_gamma"/>
  91. <event name="gamma_size">
  92. <arg name="size" type="uint"/>
  93. </event>
  94. </interface>
  95. </protocol>
  96. ```
  97. A typical Wayland server implementing this protocol would create a
  98. `gamma_control_manager` global and add it to the registry. The client then binds
  99. to this interface in our `global_add` function like so:
  100. ```c
  101. #include "wayland-gamma-control-client-protocol.h"
  102. // ...
  103. struct wl_output *example;
  104. // gamma_control_manager.name is a constant: "gamma_control_manager"
  105. if (strcmp(interface, gamma_control_manager.name) == 0) {
  106. struct gamma_control_manager *mgr =
  107. wl_registry_bind(registry, name,
  108. &gamma_control_manager_interface, version);
  109. struct gamma_control *control =
  110. gamma_control_manager_get_gamma_control(mgr, example);
  111. gamma_control_set_gamma(control, ...);
  112. }
  113. ```
  114. These functions are generated by running the XML file through `wayland-scanner`,
  115. which outputs a header and C glue code. These XML files are called "protocol
  116. extensions" and let you add arbitrary extensions to the protocol. The core
  117. Wayland protocols themselves are described with similar XML files.
  118. Using the Wayland protocol to create a surface to display pixels with consists
  119. of these steps:
  120. 1. Obtain a **wl_display** and use it to obtain a **wl_registry**.
  121. 2. Scan the registry for globals and grab a **wl_compositor** and
  122. a **wl_shm_pool**.
  123. 3. Use the **wl_compositor** interface to create a **wl_surface**.
  124. 4. Use the **wl_shell** interface to describe your surface's role.
  125. 5. Use the **wl_shm** interface to allocate shared memory to store pixels in.
  126. 6. Draw something into your shared memory buffers.
  127. 7. Attach your shared memory buffers to the **wl_surface**.
  128. Let's break this down.
  129. The **wl_compositor** provides an interface for interacting with the
  130. *compositor*, that is the part of the Wayland server that *composites* surfaces
  131. onto the screen. It's responsible for creating surface resources for clients to
  132. use via `wl_compositor_create_surface`. This creates a **wl_surface** resource,
  133. which you can attach pixels to for the compositor to render.
  134. The role of a surface is undefined by default - it's just a place to put pixels.
  135. In order to get the compositor to do anything with them, you must give the
  136. surface a *role*. Roles could be anything - desktop background, system tray, etc -
  137. but the most common role is a *shell surface*. To create these, you take your
  138. wl_surface and hand it to the **wl_shell** interface. You'll get back a
  139. **wl_shell_surface** resource, which defines your surface's purpose and gives
  140. you an interface to do things like set the window title.
  141. Attaching pixel buffers to a wl_surface is pretty straightforward. There are two
  142. primary ways of creating a buffer that both you and the compositor can use: EGL
  143. and shared memory. EGL lets you use an OpenGL context that renders directly on
  144. the GPU with minimal compositor involvement (fast) and shared memory (via
  145. **wl_shm**) allows you to simply dump pixels in memory and hand them to the
  146. compositor (flexible). There are many other Wayland interfaces I haven't
  147. covered, giving you everything from input devices (via **wl_seat**) to clipboard
  148. access (via **wl_data_source**), plus many protocol extensions. Learning more
  149. about these is an exercise left to the reader.
  150. Before we wrap this article up, let's take a brief moment to discuss the server.
  151. Most of the concepts here are already familiar to you by now. The Wayland server
  152. also utilizes a **wl_display**, but differently from the client. The display on
  153. the server has ownership over the *event loop*, via **wl_event_loop**. The event
  154. loop of a Wayland server might look like this:
  155. ```c
  156. struct wl_display *display = wl_display_create();
  157. // ...
  158. struct wl_event_loop *event_loop = wl_display_get_event_loop(display);
  159. while (true) {
  160. wl_event_loop_dispatch(event_loop, 0);
  161. }
  162. ```
  163. The event loop has a lot of helpful utilities for the Wayland server to take
  164. advantage of, including internal event sources, timers, and file descriptor
  165. monitoring. Before starting the event loop the server is going to start
  166. obtaining its own resources and creating Wayland globals for them with
  167. `wl_global_create`:
  168. ```c
  169. struct wl_global *global = wl_global_create(
  170. display,
  171. &wl_output_interface,
  172. 1 /* version */,
  173. our_data,
  174. wl_output_bind);
  175. ```
  176. The `wl_output_bind` function here is going to be called when a client attempts
  177. to bind to this resource via `wl_registry_bind`, and will look something like
  178. this:
  179. ```c
  180. void wl_output_bind(struct wl_client *client,
  181. void *our_data,
  182. uint32_t their_version,
  183. uint32_t id) {
  184. struct wl_resource *resource =
  185. wl_resource_create_checked(
  186. client,
  187. wl_output_interface,
  188. their_version,
  189. our_version,
  190. id);
  191. // ...send output modes or whatever else you need to do
  192. }
  193. ```
  194. Some of the resources a server is going to be managing might include:
  195. - DRM state for direct access to outputs
  196. - GLES context (or another GL implementation) for rendering
  197. - libinput for input devices
  198. - udev for hotplugging
  199. Through the Wayland protocol, the server provides an abstraction on top of these
  200. resources and offers them to clients. Some servers go further, with novel ways
  201. of compositing clients or handling input. Some provide additional interactivity,
  202. such as desktop shells that are actually running in the compositor rather than
  203. external clients. Other servers are designed for mobile use and provide a user
  204. experience that more closely matches the mobile experience than the traditional
  205. desktop experience. Wayland is designed to be flexible!