logo

drewdevault.com

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

Input-handling-in-wlroots.md (15298B)


  1. ---
  2. date: 2018-07-17
  3. layout: post
  4. title: Input handling in wlroots
  5. tags: [wlroots, wayland, instructional]
  6. ---
  7. I've said before that wlroots is a "batteries not included" kind of library, and
  8. one of the places where that is most apparent is with our approach to input
  9. handling. We implemented a very hands-off design for input, in order to support
  10. many use-cases: desktop input, phones with and without USB-OTG HIDs plugged in,
  11. multiple mice bound to a single cursor, multiple keyboards per seat, simulated
  12. input from fake input devices, on-screen keyboards, input which is processed by
  13. the compositor but not sent to clients... we support all of these use-cases and
  14. even more. However, the drawback of our powerful design is that it's confusing.
  15. Very confusing.
  16. Let's begin by forgetting about the Wayland part entirely. After all, wlroots is
  17. flexible enough that you can use it without writing a Wayland compositor at all!
  18. It can be used in a similar fashion to tools like GLFW and SDL, to abstract
  19. low-level input (via e.g. libinput) and graphical output (via e.g. DRM). Let's
  20. start here, simply getting input events from wlroots in the first place.
  21. One of the fundamental building blocks of wlroots is the `wlr_backend`,
  22. which is a resource that abstracts the underlying hardware and exposes a
  23. consistent API for outputs and input devices. Outputs have been discussed
  24. elsewhere, so let's focus just on input devices. Each backend provides an
  25. event: [`wlr_backend.events.new_input`][new_input]. The signal is called with a
  26. reference to a [`wlr_input_device`][wlr_input_device] each time a new input
  27. device appears on the backend - for example, when you plug a mouse into your
  28. computer when using the libinput backend.
  29. [new_input]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/backend.h#L17
  30. [wlr_input_device]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/types/wlr_input_device.h
  31. The input device can be one of five types, appropriately identified by the
  32. `type` field. The types are:
  33. - `WLR_INPUT_DEVICE_KEYBOARD`
  34. - `WLR_INPUT_DEVICE_POINTER`
  35. - `WLR_INPUT_DEVICE_TOUCH`
  36. - `WLR_INPUT_DEVICE_TABLET_TOOL`
  37. - `WLR_INPUT_DEVICE_TABLET_PAD`
  38. The type indicates which member of the anonymous union is valid. If
  39. `wlr_input_device->type == WLR_INPUT_DEVICE_KEYBOARD`, then
  40. `wlr_input_device->keyboard` is a valid pointer to a
  41. [`wlr_keyboard`][wlr_keyboard].
  42. [wlr_keyboard]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/types/wlr_keyboard.h
  43. Let's examine the wlr keyboard more closely now. The keyboard struct also
  44. provides its own events, like `key` and `keymap`. If you want to process input
  45. from this keyboard, you need to set up an [xkbcommon][xkbcommon] context for
  46. ingesting the raw scancodes emitted by the `key` event and converting them to
  47. Unicode and keysyms (e.g. "Up") with an XKB keymap. Most of the wlroots examples
  48. [implement this][examples] if you're looking for a simple reference.
  49. [xkbcommon]: https://xkbcommon.org/doc/current/
  50. [examples]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/examples/simple.c#L114
  51. When these events are sent, we just let you process them as you please. They do
  52. not automatically get propagated to any Wayland clients. Communicating these
  53. events to the clients is your responsibility, though we provide you tools to
  54. help - we'll get into that shortly. You don't even have to source the input you
  55. give to Wayland clients from a `wlr_input_device`, you can just as easily make
  56. them up or get them from the network or anywhere else.
  57. Before we get into details on how to send events to clients, let's examine the
  58. other components in your compositor's input code. First, let's talk about the
  59. cursor.
  60. We provide the [`wlr_pointer`][wlr_pointer] abstraction for getting events from
  61. a "pointer" device, like a mouse. However, because batteries are not included,
  62. you will find that we only tell you what the pointer device is doing - we don't
  63. act on it. If you want to, for example, display a cursor image <img
  64. src="https://sr.ht/hf39.png" style="display: inline; margin: 0; padding: 0;" />
  65. on screen which moves around when the mouse does, you need to wire this up
  66. yourself. We have tools which can help.
  67. [cur]: https://sr.ht/hf39.png
  68. [wlr_pointer]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/types/wlr_pointer.h
  69. First, let's talk about getting the cursor image to show. You can source the
  70. image from anywhere you want, but you will probably want to leverage
  71. [`wlr_xcursor`][xcursor]. This is a small wlroots module (forked from the
  72. `wayland-cursor` library used by Wayland clients) which can read Xcursor themes,
  73. the kind your user will already have installed on their system. Loading up a
  74. cursor theme and getting the pixels from it is pretty straightforward. But what
  75. should you do with those pixels?
  76. [xcursor]: https://github.com/swaywm/wlroots/blob/master/include/wlr/xcursor.h
  77. Well, now we have to introduce hardware cursors. Many backends support
  78. "hardware" cursors, which is a feature provided by your low-level graphics stack
  79. (e.g. GPU drivers) for rendering a cursor on the screen. Hardware cursors are
  80. composited by the GPU, which means you can move the cursor around without
  81. re-drawing the things underneath it. This is the most energy- and CPU-efficient
  82. way of drawing your cursor, and you can do it with
  83. [`wlr_output_cursor_set_image`][cursor_set_image], specifying which `wlr_output`
  84. you want it to appear on and at what coordinates. Not all configurations support
  85. hardware cursors, but `wlr_output` automatically falls back to software cursors
  86. if need be.
  87. [cursor_set_image]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/types/wlr_output.h#L179-L190
  88. Now you have all of the pieces to show a cursor on screen that moves with the
  89. mouse. You can store some X and Y coordinates somewhere, grab an image from an
  90. Xcursor theme, and throw it at your `wlr_output`, then process input events and
  91. move it around. Then... you need to consider multiple outputs. And you need to
  92. make sure that it can't be moved outside of an output. And you need to let the
  93. user move it around with a drawing tablet or touch screen as well. And... well,
  94. it's about to get complicated. That's where our next tool comes in!
  95. [`wlr_cursor`][wlr_cursor] is how wlroots saves you from some of this work. It
  96. can display a cursor image on-screen, tie it to multiple input devices,
  97. constrain it to your outputs and move it across multiple displays. It can also
  98. map input from certain devices to certain outputs or regions of the output
  99. layout, change the geometry of inputs from a drawing tablet, and more.
  100. [wlr_cursor]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/types/wlr_cursor.h
  101. To use `wlr_cursor`, you should create one (`wlr_cursor_create`) and as the
  102. backend emits `new_input` events, bind them to the cursor with
  103. `wlr_cursor_attach_input_device`. `wlr_cursor` then raises aggregated events
  104. from all of its devices, which you can catch and handle accordingly - usually
  105. calling a function like `wlr_cursor_move` and propagating the event to Wayland
  106. clients. You also need to attach a [`wlr_output_layout`][wlr_output_layout] to
  107. the cursor, so it knows how to constrain the cursor movement and can handle
  108. hardware cursors for you.
  109. Aside: the `wlr_output_layout` module allows you to configure an arrangement of
  110. `wlr_output`s in physical space. Its function is fairly straightforward and
  111. largely unrelated to our topic - I suggest reading through the header and asking
  112. questions if you need help. Once you make one of these and hand it to a
  113. `wlr_cursor`, you have a cursor on-screen which moves around when you provide
  114. input and correctly moves throughout a multi-display setup.[^1] [^2]
  115. [wlr_output_layout]: https://github.com/swaywm/wlroots/blob/master/include/wlr/types/wlr_output_layout.h
  116. [^1]: One more quick note: for multi-DPI setups, you need to provide the `wlr_cursor` with different cursor images, one for each scale present on the output layout. We have another tool for sourcing Xcursor images at multiple scale factors, check out [`wlr_xcursor_manager`](https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/types/wlr_xcursor_manager.h).
  117. [^2]: Another thing `wlr_output_layout` is useful for, if you were wondering, is figuring out where to render windows in a multi-output arrangement, where some windows might span multiple outputs. Read the header!
  118. Okay, now that we have all of those pieces in place, we can finally start talking
  119. about sending input events to Wayland clients! Before we get into how *wlroots*
  120. does it, let's talk about how *Wayland* does it in general.
  121. The top-level resource which manages input for a Wayland client is the
  122. `wl_seat`. One seat, in rough terms, maps to a single set of input devices used
  123. by a user (a user who is presumably sitting at a seat in front of their
  124. computer). A seat can have up to one keyboard, pointer, touch device, or drawing
  125. tablet each. Each of these devices can then *enter* or *leave* any of the
  126. client's surfaces at the compositor's orders.
  127. When you bind to a `wl_seat`'s `wl_keyboard` and `wl_keyboard.enter` is raised
  128. on a surface, it means your surface has keyboard focus. The compositor will
  129. follow-up with (or will have already sent) a `wl_keyboard.keymap` signal to let
  130. you know the layout of this keyboard (e.g. `us-intl`, `de`, `ru`, etc) in the
  131. form of an xkbcommon keymap (the same format we were using with `wlr_keyboard`
  132. earlier - hint hint). Some number of `key` and `modifier` events will likely
  133. follow as the user taps away.
  134. When you bind to a `wl_seat`'s `wl_pointer` and `wl_pointer.enter` is raised, it
  135. means a pointer has moved over one of your surfaces. Note that this can be an
  136. entirely separate occasion from receiving keyboard focus. The client is then
  137. expected to provide a cursor image to display (at the moment, Wayland *requires*
  138. client side cursors. They have to do the whole Xcursor dance we did on the
  139. wlroots side earlier, too. We have some plans to correct this...). Some number
  140. of `motion` and `button` events will likely follow as the user wiggles their
  141. mouse and clicks your windows.
  142. So, how does a wlroots-based compositor facilitate these interactions? With
  143. [`wlr_seat`][wlr_seat], our abstraction on top of `wl_seat`. This implements the
  144. whole `wl_seat` state machine, but again leaves it to you to tweak the knobs as
  145. you wish. You need to decide how your compositor is going to deal with focus -
  146. KDE, Sway, the Librem5 phone UI, an in-vehicle infotainment system; all of these
  147. will have a different approach to focus.
  148. [wlr_seat]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/types/wlr_seat.h
  149. [pointer_enter]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/types/wlr_seat.h#L405-L412
  150. [keyboard_enter]: https://github.com/swaywm/wlroots/blob/4984ea49eeaa292d66be9e535d93a4d8185f3e18/include/wlr/types/wlr_seat.h#L405-L412
  151. wlroots doesn't render client surfaces for you, and doesn't know where you put
  152. them. Once you figure out where they go, you need to notice when the
  153. `wlr_cursor` is moved over it and call `wlr_seat_pointer_notify_enter` with the
  154. pointer's coordinates relative to the surface it entered, along with any
  155. appropriate `motion` or `button` events through the relevant `wlr_seat`
  156. functions. The client will also likely send you a cursor image to display - this
  157. is done with the `wlr_seat.events.request_set_cursor` event.
  158. When you decide that a surface should receive keyboard focus, call
  159. `wlr_seat_keyboard_notify_enter`. `wlr_seat` will automatically handle removing
  160. focus from whatever had it last, and will also grab the keymap and send it to
  161. the client for you, assuming you configured it with `wlr_keyboard_set_keymap`...
  162. you did, right? `wlr_seat` also semi-transparently deals with grabs, the sort of
  163. situation where a client wants to keep keyboard focus for longer than it
  164. normally would, to deal with a context menu or something.
  165. Touch events are similar and should be self-explanatory when you read the
  166. header. Drawing tablet events are a bit different - they're not actually
  167. specified by the core Wayland protocol. Instead, we rig these up with the
  168. [tablet][tablet-protocol] protocol extension and [wlr_tablet][wlr_tablet]. It
  169. works in much the same way, but you have to explicitly configure it for a
  170. `wlr_seat` by calling `wlr_tablet_create` yourself.
  171. [tablet-protocol]: https://cgit.freedesktop.org/wayland/wayland-protocols/tree/unstable/tablet/tablet-unstable-v2.xml
  172. [wlr_tablet]: https://github.com/swaywm/wlroots/blob/7f20ab644347b11fd8242beaf7a6fe42c910d014/include/wlr/types/wlr_tablet_v2.h
  173. So, in short, if you wiggle your mouse, here's what happens:
  174. 1. Before you wiggled your mouse, the `libinput` backend noticed it was plugged
  175. in and raised a `new_input` event.
  176. 1. Your compositor attached the resulting `wlr_pointer` to its `wlr_cursor`,
  177. which it had prepared earlier by looking up an appropriate cursor theme and
  178. letting it know about the display layout.
  179. 1. The `wlr_pointer` bubbled up a `motion` event, which was caught by
  180. `wlr_cursor` and bubbled up to your compositor.
  181. 1. Your compositor called `wlr_cursor_move` to apply the resulting motion,
  182. constrained by the output layout, which in turn caused the cursor image on
  183. your display to move.
  184. 1. Your compositor then looked around to see if the pointer had moved over any
  185. new surfaces. Since wlroots doesn't handle rendering or know where anything
  186. is displayed, this was a rather introspective question.
  187. 1. You *did* wiggle it over a new surface, so the compositor called
  188. `wlr_seat_notify_pointer_enter` after translating the pointer coordinates to
  189. surface-local space. It sent a `wlr_seat_notify_pointer_motion` for good
  190. measure.
  191. 1. The client noticed the pointer entered it and sent back a cursor image to
  192. show. The compositor was informed of this via
  193. `wlr_seat.events.request_set_cursor`.
  194. 1. The compositor handled the client's cursor image to `wlr_cursor`, throwing
  195. away all of that hard work loading up a cursor theme just for a client-side
  196. cursor to come in and ruin it.
  197. And there you have it, that's how input works in wlroots. It's really fucking
  198. complicated, isn't it? I think this article puts on display both the incredible
  199. advantages and serious drawbacks of wlroots. Because you have to plug all of
  200. these pieces together yourself, you are afforded an *enormous* amount of
  201. flexibility. However, you have to do a lot of work and understand a whole lot of
  202. different pieces to get there. Libraries like
  203. [wlc](https://github.com/Cloudef/wlc) are much easier to use in this respect,
  204. but if you want to change even a small detail of this process with wlc you are
  205. unable to.
  206. If you have any questions about this article, please reach out to the developers
  207. hanging out in [#sway-devel on irc.freenode.net][#sway-devel]. We know this is
  208. confusing, and we're happy to help.
  209. [#sway-devel]: http://webchat.freenode.net/?channels=sway-devel&uio=d4