A graceful shutdown

Before the compositor can begin listening for keyboard input it needs to listen for keyboards.

For each resource type that can be created there is a manager module that provides a builder and some function signatures for the compositor writer to describe how a resource should be managed. Here is the input device resource manager module.

To specify that a keyboard should be managed by the compositor a function needs to be defined matching the keyboard resource manager signature. This function will be later called by wlroots when a keyboard is announced to the compositor through libinput.

Once the function is defined with the necessary signature it needs to be put into the resource builder and the resource builder passed to the compositor::Builder.

A Minimal Keyboard Handler

This is the simplest function that implements the signature:


# #![allow(unused_variables)]
#fn main() {
fn keyboard_added(_compositor_handle: compositor::Handle,
                  _keyboard_handle: keyboard::Handle)
                  -> Option<Box<keyboard::Handler>> {
    None
}
#}

This is how it's passed to the builder:

fn main() {
    init_logging(WLR_DEBUG, None);
    let input_builder = input::manager::Builder::default()
        .keyboard_added(keyboard_added);
    compositor::Builder::new()
        .input_manager(input_builder)
        .build_auto(())
        .run()
}

With the provided implementation whenever a keyboard is announced wlroots-rs will call keyboard_added. Since the function unconditionally returns None a keyboard resource handler will never be allocated and the resource will be dropped.

Holding on to the resource

In order to hang on to the resource a handler must be defined and allocated using Box to make a trait object. The handler defines how to deal with event the resource can trigger, including when a key is pressed.

Since a resource handler is a trait object each resource handler has a piece of state it holds between callbacks separate from the other resources. It is here where the "shift" and "ctrl" pressed state will be held:


# #![allow(unused_variables)]
#fn main() {
#[derive(Default)]
struct KeyboardHandler {
    shift_pressed: bool,
    ctrl_pressed: bool
}
#}

In order to be able to return a Box-ed version of this struct in the keyboard_added function keyboard::Handler will need to be implemented:


# #![allow(unused_variables)]
#fn main() {
fn keyboard_added(_compositor_handle: compositor::Handle,
                  _keyboard_handle: keyboard::Handle)
                  -> Option<Box<keyboard::Handler>> {
    Some(Box::new(KeyboardHandler::default()))
}

impl keyboard::Handler for KeyboardHandler {
    // All handler methods have a default implementation that does nothing.
    // So because no methods are define here, every event on the keyboard
    // is ignored.
}
#}

Listening for keyboard modifiers

When a key is pressed this method receives the event. The key event has a couple methods but the most important one is pressed_keys. It will provide all the keys as seen by xkb that were pressed when the event fired.

You can also see if the key was pressed or not with key_state. This is necessary to determine the boolean state in KeyboardHandler.

Using the keysyms module from the reexported xkbcommon crate the list of keys can be iterated over and pattern matched. Here is all that put together to toggle the booleans when the appropriate keys are pressed:


# #![allow(unused_variables)]
#fn main() {
impl keyboard::Handler for KeyboardHandler {
    fn on_key(&mut self,
              compositor_handle: compositor::Handle,
              _keyboard_handle: keyboard::Handle,
              key_event: &keyboard::event::Key) {
        for key in key_event.pressed_keys() {
            match key {
                keysyms::KEY_Control_L | keysyms::KEY_Control_R =>
                    self.ctrl_pressed = key_event.key_state() == WLR_KEY_PRESSED,
                keysyms::KEY_Shift_L | keysyms::KEY_Shift_R =>
                    self.shift_pressed = key_event.key_state() == WLR_KEY_PRESSED,
                _ => { /* Do nothing */ }
            }
        }
    }
}
#}

The last piece of the puzzle to stopping the compositor is the terminate function. It can be called at any time and will gracefully kill clients, destroy resource managers, and then wind back up the stack to where run was called.

Here is the complete code for a compositor that will close itself when Ctrl+Shift+Escape is pressed:

extern crate wlroots;

use wlroots::{compositor,
              input::{self, keyboard},
              utils::log::{WLR_DEBUG, init_logging},
              xkbcommon::xkb::keysyms,
              wlr_key_state::WLR_KEY_PRESSED};

fn main() {
    init_logging(WLR_DEBUG, None);
    let input_builder = input::manager::Builder::default()
        .keyboard_added(keyboard_added);
    compositor::Builder::new()
        .input_manager(input_builder)
        .build_auto(())
        .run()
}

fn keyboard_added(_compositor_handle: compositor::Handle,
                  _keyboard_handle: keyboard::Handle)
                  -> Option<Box<keyboard::Handler>> {
    Some(Box::new(KeyboardHandler::default()))
}

#[derive(Default)]
struct KeyboardHandler {
    shift_pressed: bool,
    ctrl_pressed: bool
}

impl keyboard::Handler for KeyboardHandler {
    fn on_key(&mut self,
              compositor_handle: compositor::Handle,
              _keyboard_handle: keyboard::Handle,
              key_event: &keyboard::event::Key) {
        for key in key_event.pressed_keys() {
            match key {
                keysyms::KEY_Control_L | keysyms::KEY_Control_R =>
                    self.ctrl_pressed = key_event.key_state() == WLR_KEY_PRESSED,
                keysyms::KEY_Shift_L | keysyms::KEY_Shift_R =>
                    self.shift_pressed = key_event.key_state() == WLR_KEY_PRESSED,
                keysyms::KEY_Escape => {
                    if self.shift_pressed && self.ctrl_pressed {
                        wlroots::compositor::terminate()
                    }
                },
                _ => { /* Do nothing */ }
            }
        }
    }
}

1 Huge caveat to this: if the system is deadlocked, such as by an innocuous loop {}, then the compositor can no longer process input including the escape sequence. Either test all of your features in a nested instance (where input can still be processed by the parent system) or have ssh as a backup to pkill the process.