From f3a68fb1af52cd784ccabe1c5fe069552dc16ff8 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Thu, 22 Nov 2018 16:01:29 +0100 Subject: [PATCH] seat: support for curstom cursor images --- anvil/src/shell.rs | 7 +- anvil/src/udev.rs | 9 ++- anvil/src/winit.rs | 4 +- src/wayland/seat/mod.rs | 106 +++++++++++++++++++--------- src/wayland/seat/pointer.rs | 137 ++++++++++++++++++++++++++++++------ 5 files changed, 205 insertions(+), 58 deletions(-) diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index 07bc0d9..c2955d7 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -9,6 +9,7 @@ use rand; use smithay::{ wayland::{ compositor::{compositor_init, CompositorToken, SurfaceAttributes, SurfaceEvent}, + seat::CursorImageRole, shell::{ legacy::{ wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind, ShellSurfaceRole, @@ -27,7 +28,11 @@ use smithay::{ use window_map::{Kind as SurfaceKind, WindowMap}; -define_roles!(Roles => [ XdgSurface, XdgSurfaceRole ] [ ShellSurface, ShellSurfaceRole<()>] ); +define_roles!(Roles => + [ XdgSurface, XdgSurfaceRole ] + [ ShellSurface, ShellSurfaceRole<()>] + [ CursorImage, CursorImageRole ] +); pub type MyWindowMap = WindowMap) -> Option<(i32, i32)>>; diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index ffe09fa..0d9bac1 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -146,9 +146,14 @@ pub fn run_udev(mut display: Display, mut event_loop: EventLoop<()>, log: Logger /* * Initialize wayland input object */ - let (mut w_seat, _) = Seat::new(&mut display.borrow_mut(), session.seat(), log.clone()); + let (mut w_seat, _) = Seat::new( + &mut display.borrow_mut(), + session.seat(), + compositor_token.clone(), + log.clone(), + ); - let pointer = w_seat.add_pointer(); + let pointer = w_seat.add_pointer(compositor_token.clone(), |_| {}); let keyboard = w_seat .add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| { set_data_device_focus(seat, focus.and_then(|s| s.client())) diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index f405e4a..a299e4b 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -52,9 +52,9 @@ pub fn run_winit(display: &mut Display, event_loop: &mut EventLoop<()>, log: Log init_data_device(display, |_| {}, default_action_chooser, log.clone()); - let (mut seat, _) = Seat::new(display, "winit".into(), log.clone()); + let (mut seat, _) = Seat::new(display, "winit".into(), compositor_token.clone(), log.clone()); - let pointer = seat.add_pointer(); + let pointer = seat.add_pointer(compositor_token.clone(), |_| {}); let keyboard = seat .add_keyboard(XkbConfig::default(), 1000, 500, |seat, focus| { diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index 68a73b0..d699667 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -10,16 +10,23 @@ //! ``` //! # extern crate wayland_server; //! # #[macro_use] extern crate smithay; -//! use smithay::wayland::seat::Seat; +//! use smithay::wayland::seat::{Seat, CursorImageRole}; +//! # use smithay::wayland::compositor::compositor_init; +//! +//! // You need to insert the `CursorImageRole` into your roles, to handle requests from clients +//! // to set a surface as a cursor image +//! define_roles!(Roles => [CursorImage, CursorImageRole]); //! //! # fn main(){ //! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap(); //! # let mut display = wayland_server::Display::new(event_loop.handle()); +//! # let (compositor_token, _, _) = compositor_init::<(), Roles, _, _>(&mut display, |_, _, _| {}, None); //! // insert the seat: //! let (seat, seat_global) = Seat::new( -//! &mut display, // the display -//! "seat-0".into(), // the name of the seat, will be advertized to clients -//! None // insert a logger here +//! &mut display, // the display +//! "seat-0".into(), // the name of the seat, will be advertized to clients +//! compositor_token.clone(), // the compositor token +//! None // insert a logger here //! ); //! # } //! ``` @@ -31,27 +38,8 @@ //! Currently, only pointer and keyboard capabilities are supported by //! smithay. //! -//! You can add these capabilities via methods of the `Seat` struct: -//! -//! ``` -//! # extern crate wayland_server; -//! # #[macro_use] extern crate smithay; -//! # -//! # use smithay::wayland::seat::Seat; -//! # -//! # fn main(){ -//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap(); -//! # let mut display = wayland_server::Display::new(event_loop.handle()); -//! # let (mut seat, seat_global) = Seat::new( -//! # &mut display, -//! # "seat-0".into(), -//! # None -//! # ); -//! let pointer_handle = seat.add_pointer(); -//! # } -//! ``` -//! -//! These handles can be cloned and sent across thread, so you can keep one around +//! You can add these capabilities via methods of the `Seat` struct: `add_keyboard`, `add_pointer`. +//! These methods return handles that can be cloned and sent across thread, so you can keep one around //! in your event-handling code to forward inputs to your clients. use std::sync::{Arc, Mutex}; @@ -61,9 +49,13 @@ mod pointer; pub use self::{ keyboard::{keysyms, Error as KeyboardError, KeyboardHandle, Keysym, ModifiersState, XkbConfig}, - pointer::{AxisFrame, PointerGrab, PointerHandle, PointerInnerHandle}, + pointer::{ + AxisFrame, CursorImageRole, CursorImageStatus, PointerGrab, PointerHandle, PointerInnerHandle, + }, }; +use wayland::compositor::{roles::Role, CompositorToken}; + use wayland_commons::utils::UserDataMap; use wayland_server::{ @@ -129,8 +121,15 @@ impl Seat { /// You are provided with the state token to retrieve it (allowing /// you to add or remove capabilities from it), and the global handle, /// in case you want to remove it. - pub fn new(display: &mut Display, name: String, logger: L) -> (Seat, Global) + pub fn new( + display: &mut Display, + name: String, + token: CompositorToken, + logger: L, + ) -> (Seat, Global) where + U: 'static, + R: Role + 'static, L: Into>, { let log = ::slog_or_stdlog(logger); @@ -146,7 +145,7 @@ impl Seat { }); let seat = Seat { arc: arc.clone() }; let global = display.create_global(5, move |new_seat, _version| { - let seat = implement_seat(new_seat, arc.clone()); + let seat = implement_seat(new_seat, arc.clone(), token.clone()); let mut inner = arc.inner.lock().unwrap(); if seat.version() >= 2 { seat.send(wl_seat::Event::Name { @@ -179,9 +178,44 @@ impl Seat { /// Calling this method on a seat that already has a pointer capability /// will overwrite it, and will be seen by the clients as if the /// mouse was unplugged and a new one was plugged. - pub fn add_pointer(&mut self) -> PointerHandle { + /// + /// You need to provide a compositor token, as well as a callback that will be notified + /// whenever a client requests to set a custom cursor image. + /// + /// # Examples + /// + /// ``` + /// # extern crate wayland_server; + /// # #[macro_use] extern crate smithay; + /// # + /// # use smithay::wayland::{seat::{Seat, CursorImageRole}, compositor::compositor_init}; + /// # + /// # define_roles!(Roles => [CursorImage, CursorImageRole]); + /// # + /// # fn main(){ + /// # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap(); + /// # let mut display = wayland_server::Display::new(event_loop.handle()); + /// # let (compositor_token, _, _) = compositor_init::<(), Roles, _, _>(&mut display, |_, _, _| {}, None); + /// # let (mut seat, seat_global) = Seat::new( + /// # &mut display, + /// # "seat-0".into(), + /// # compositor_token.clone(), + /// # None + /// # ); + /// let pointer_handle = seat.add_pointer( + /// compositor_token.clone(), + /// |new_status| { /* a closure handling requests from clients tot change the cursor icon */ } + /// ); + /// # } + /// ``` + pub fn add_pointer(&mut self, token: CompositorToken, cb: F) -> PointerHandle + where + U: 'static, + R: Role + 'static, + F: FnMut(CursorImageStatus) + Send + 'static, + { let mut inner = self.arc.inner.lock().unwrap(); - let pointer = self::pointer::create_pointer_handler(); + let pointer = self::pointer::create_pointer_handler(token, cb); if inner.pointer.is_some() { // there is already a pointer, remove it and notify the clients // of the change @@ -303,7 +337,15 @@ impl ::std::cmp::PartialEq for Seat { } } -fn implement_seat(new_seat: NewResource, arc: Arc) -> Resource { +fn implement_seat( + new_seat: NewResource, + arc: Arc, + token: CompositorToken, +) -> Resource +where + R: Role + 'static, + U: 'static, +{ let dest_arc = arc.clone(); new_seat.implement( move |request, seat| { @@ -311,7 +353,7 @@ fn implement_seat(new_seat: NewResource, arc: Arc) -> let inner = arc.inner.lock().unwrap(); match request { wl_seat::Request::GetPointer { id } => { - let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref()); + let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref(), token.clone()); if let Some(ref ptr_handle) = inner.pointer { ptr_handle.new_pointer(pointer); } else { diff --git a/src/wayland/seat/pointer.rs b/src/wayland/seat/pointer.rs index 39978d3..e72d318 100644 --- a/src/wayland/seat/pointer.rs +++ b/src/wayland/seat/pointer.rs @@ -1,13 +1,31 @@ use std::sync::{Arc, Mutex}; use wayland_server::{ protocol::{ - wl_pointer::{Axis, AxisSource, ButtonState, Event, Request, WlPointer}, + wl_pointer::{self, Axis, AxisSource, ButtonState, Event, Request, WlPointer}, wl_surface::WlSurface, }, NewResource, Resource, }; -// TODO: handle pointer surface role +use wayland::compositor::{roles::Role, CompositorToken}; + +/// The role representing a surface set as the pointer cursor +#[derive(Default, Copy, Clone)] +pub struct CursorImageRole { + /// Location of the hotspot of the pointer in the surface + pub hotspot: (i32, i32), +} + +/// Possible status of a cursor as requested by clients +#[derive(Clone)] +pub enum CursorImageStatus { + /// The cursor should be hidden + Hidden, + /// The compositor should draw its cursor + Default, + /// The cursor should be drawn using this surface as an image + Image(Resource), +} enum GrabStatus { None, @@ -22,10 +40,35 @@ struct PointerInternal { location: (f64, f64), grab: GrabStatus, pressed_buttons: Vec, + image_callback: Box, } impl PointerInternal { - fn new() -> PointerInternal { + fn new(token: CompositorToken, mut cb: F) -> PointerInternal + where + U: 'static, + R: Role + 'static, + F: FnMut(CursorImageStatus) + Send + 'static, + { + let mut old_status = CursorImageStatus::Default; + let wrapper = move |new_status: CursorImageStatus| { + if let CursorImageStatus::Image(surface) = + ::std::mem::replace(&mut old_status, new_status.clone()) + { + match new_status { + CursorImageStatus::Image(ref new_surface) if new_surface == &surface => { + // don't remove the role, we are just re-binding the same surface + } + _ => { + if surface.is_alive() { + token.remove_role::(&surface).unwrap(); + } + } + } + } + cb(new_status) + }; + PointerInternal { known_pointers: Vec::new(), focus: None, @@ -33,6 +76,7 @@ impl PointerInternal { location: (0.0, 0.0), grab: GrabStatus::None, pressed_buttons: Vec::new(), + image_callback: Box::new(wrapper) as Box<_>, } } @@ -297,6 +341,7 @@ impl<'a> PointerInnerHandle<'a> { } }); self.inner.focus = None; + (self.inner.image_callback)(CursorImageStatus::Default); } // do we enter one ? @@ -431,6 +476,7 @@ pub struct AxisFrame { } impl AxisFrame { + /// Create a new frame of axis events pub fn new(time: u32) -> Self { AxisFrame { source: None, @@ -501,34 +547,83 @@ impl AxisFrame { } } -pub(crate) fn create_pointer_handler() -> PointerHandle { +pub(crate) fn create_pointer_handler(token: CompositorToken, cb: F) -> PointerHandle +where + R: Role + 'static, + U: 'static, + F: FnMut(CursorImageStatus) + Send + 'static, +{ PointerHandle { - inner: Arc::new(Mutex::new(PointerInternal::new())), + inner: Arc::new(Mutex::new(PointerInternal::new(token, cb))), } } -pub(crate) fn implement_pointer( +pub(crate) fn implement_pointer( new_pointer: NewResource, handle: Option<&PointerHandle>, -) -> Resource { - let destructor = match handle { - Some(h) => { - let inner = h.inner.clone(); - Some(move |pointer: Resource<_>| { - inner - .lock() - .unwrap() - .known_pointers - .retain(|p| !p.equals(&pointer)) - }) - } + token: CompositorToken, +) -> Resource +where + R: Role + 'static, + U: 'static, +{ + let inner = handle.map(|h| h.inner.clone()); + let destructor = match inner.clone() { + Some(inner) => Some(move |pointer: Resource<_>| { + inner + .lock() + .unwrap() + .known_pointers + .retain(|p| !p.equals(&pointer)) + }), None => None, }; new_pointer.implement( - |request, _pointer| { + move |request, pointer| { match request { - Request::SetCursor { .. } => { - // TODO + Request::SetCursor { + serial: _, + surface, + hotspot_x, + hotspot_y, + } => { + if let Some(ref inner) = inner { + let mut guard = inner.lock().unwrap(); + // only allow setting the cursor icon if the current pointer focus + // is of the same client + let PointerInternal { + ref mut image_callback, + ref focus, + .. + } = *guard; + if let Some((ref focus, _)) = *focus { + if focus.same_client_as(&pointer) { + match surface { + Some(surface) => { + let role_data = CursorImageRole { + hotspot: (hotspot_x, hotspot_y), + }; + // we gracefully tolerate the client to provide a surface that + // already had the "CursorImage" role, as most clients will + // always reuse the same surface (and they are right to do so!) + if token.with_role_data(&surface, |data| *data = role_data).is_err() + && token.give_role_with(&surface, role_data).is_err() + { + pointer.post_error( + wl_pointer::Error::Role as u32, + "Given wl_surface has another role.".into(), + ); + return; + } + image_callback(CursorImageStatus::Image(surface)); + } + None => { + image_callback(CursorImageStatus::Hidden); + } + } + } + } + } } Request::Release => { // Our destructors already handle it