Setting up a Cursor and an output::Layout

A Cursor can be created at any time. Cursors use handles just like all resources provided in wlroots callbacks, so to use the rest of the methods it must be upgraded.1

A Cursor can be attached to an output::Layout which will constrain the input to remain within the region. The layout also keeps track of where the outputs are in relation to each other, so when the cursor reaches the edge of two outputs it will automatically warp to the next one.

An output::Layout can be created just like a Cursor.

Here is the new compositor setup code that uses Cursor, output::Layout, and xcursor::Manager2:

pub struct CompositorState {
    xcursor_manager: wlroots::cursor::xcursor::Manager,
    cursor_handle: wlroots::cursor::Handle,
    output_layout_handle: wlroots::output::layout::Handle
}

fn main() {
    init_logging(WLR_DEBUG, None);
    let compositor_state = setup_compositor_state();
    let output_builder = wlroots::output::manager::Builder::default()
        .output_added(output_added);
    let input_builder = wlroots::input::manager::Builder::default()
        .pointer_added(pointer_added)
        .keyboard_added(keyboard_added);
    let compositor = compositor::Builder::new()
        .input_manager(input_builder)
        .output_manager(output_builder)
        .build_auto(compositor_state);
    compositor.run();
}

#[wlroots_dehandle]
pub fn setup_compositor_state() -> CompositorState {
    use wlroots::{cursor::{Cursor, xcursor},
                  output::layout::Layout};
    use crate::{pointer::CursorHandler, output::LayoutHandler};
    let output_layout_handle = Layout::create(Box::new(LayoutHandler));
    let cursor_handle = Cursor::create(Box::new(CursorHandler));
    let xcursor_manager = xcursor::Manager::create("default".to_string(), 24)
        .expect("Could not create xcursor manager");
    xcursor_manager.load(1.0);
    #[dehandle] let output_layout = output_layout_handle.clone();
    #[dehandle] let cursor = cursor_handle.clone();
    cursor.attach_output_layout(output_layout);
    CompositorState { xcursor_manager,
                      cursor_handle,
                      output_layout_handle }
}

Using output::Layout

Outputs can be added to the layout with Layout::add_auto once they are advertised to the compositor:3 This will allow the cursor to warp to the next output when the edge is reached between two outputs in the output layout coordinate space.


# #![allow(unused_variables)]
#fn main() {
#[wlroots_dehandle]
pub fn output_added<'output>(compositor: compositor::Handle,
                             builder: output::Builder<'output>)
                             -> Option<output::BuilderResult<'output>> {
    let result = builder.build_best_mode(OutputHandler);
    #[dehandle] let compositor = compositor;
    let CompositorState { ref output_layout_handle, .. } = compositor.downcast();
    #[dehandle] let output = result.output.clone();
    #[dehandle] let output_layout = output_layout_handle;
    output_layout.add_auto(output);
    Some(result)
}
#}

Moving the Pointer

There is no longer any need to keep track of the current pointer location. This is tracked by the Cursor and can be updated using move_relative and warp.

We also should update the cursor image when a pointer is added so that the correct state can be rendered.

Finally here is the code that updates the cursor when the pointer moves:


# #![allow(unused_variables)]
#fn main() {
pub struct PointerHandler;

impl pointer::Handler for PointerHandler {
    /// Triggered when the pointer is moved on the Wayland and X11 backends.
    #[wlroots_dehandle]
    fn on_motion_absolute(&mut self,
                          compositor_handle: compositor::Handle,
                          _pointer_handle: pointer::Handle,
                          absolute_motion_event: &pointer::event::AbsoluteMotion) {
        #[dehandle] let compositor = compositor_handle;
        let &mut CompositorState { ref cursor_handle, .. } = compositor.downcast();
        #[dehandle] let cursor = cursor_handle;
        let (x, y) = absolute_motion_event.pos();
        cursor.warp_absolute(absolute_motion_event.device(), x, y);
    }

    #[wlroots_dehandle]
    /// Triggered when the pointer is moved in the DRM backend.
    fn on_motion(&mut self,
                 compositor_handle: compositor::Handle,
                 _pointer_handle: pointer::Handle,
                 motion_event: &pointer::event::Motion) {
        #[dehandle] let compositor = compositor_handle;
        let &mut CompositorState { ref cursor_handle, .. } = compositor.downcast();
        #[dehandle] let cursor = cursor_handle;
        let (delta_x, delta_y) = motion_event.delta();
        cursor.move_to(None, delta_x, delta_y);
    }
}

#[wlroots_dehandle]
pub fn pointer_added(compositor_handle: compositor::Handle,
                     pointer_handle: pointer::Handle)
                     -> Option<Box<pointer::Handler>> {
    #[dehandle] let compositor = compositor_handle;
    #[dehandle] let pointer = pointer_handle;
    let CompositorState { ref cursor_handle, ref mut xcursor_manager,
                          .. } = compositor.downcast();
    #[dehandle] let cursor = cursor_handle;
    xcursor_manager.set_cursor_image("left_ptr".to_string(), cursor);
    cursor.attach_input_device(pointer.input_device());
    Some(Box::new(PointerHandler) as Box<pointer::Handler>)
}
#}

1 Unlike most resources in wlroots, Cursor lifetimes are entirely dictated by your code. It will hang around until you destroy it. However, it acts like the other resources for consistency and because their lifetimes are tied to other resources. There are two other types like this: output::Layout and Seat.

2 Since the code is getting very long large parts of it will be elided going forward. The full source can always be found in the book repo.

3 Normally you'd want to add the output at a specific point in the layout. However this requires user configuration, which is out of the scope of this book. Currently there is no xrandr equivalent for Wayland.