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::Manager
2:
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.