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.