seat: support for curstom cursor images

This commit is contained in:
Victor Berger 2018-11-22 16:01:29 +01:00 committed by Victor Berger
parent 60bb5e8d5a
commit f3a68fb1af
5 changed files with 205 additions and 58 deletions

View File

@ -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<SurfaceData, Roles, (), (), fn(&SurfaceAttributes<SurfaceData>) -> Option<(i32, i32)>>;

View File

@ -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()))

View File

@ -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| {

View File

@ -10,15 +10,22 @@
//! ```
//! # 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
//! 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<L>(display: &mut Display, name: String, logger: L) -> (Seat, Global<wl_seat::WlSeat>)
pub fn new<U, R, L>(
display: &mut Display,
name: String,
token: CompositorToken<U, R>,
logger: L,
) -> (Seat, Global<wl_seat::WlSeat>)
where
U: 'static,
R: Role<CursorImageRole> + 'static,
L: Into<Option<::slog::Logger>>,
{
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<U, R, F>(&mut self, token: CompositorToken<U, R>, cb: F) -> PointerHandle
where
U: 'static,
R: Role<CursorImageRole> + '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<wl_seat::WlSeat>, arc: Arc<SeatArc>) -> Resource<wl_seat::WlSeat> {
fn implement_seat<U, R>(
new_seat: NewResource<wl_seat::WlSeat>,
arc: Arc<SeatArc>,
token: CompositorToken<U, R>,
) -> Resource<wl_seat::WlSeat>
where
R: Role<CursorImageRole> + 'static,
U: 'static,
{
let dest_arc = arc.clone();
new_seat.implement(
move |request, seat| {
@ -311,7 +353,7 @@ fn implement_seat(new_seat: NewResource<wl_seat::WlSeat>, arc: Arc<SeatArc>) ->
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 {

View File

@ -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<WlSurface>),
}
enum GrabStatus {
None,
@ -22,10 +40,35 @@ struct PointerInternal {
location: (f64, f64),
grab: GrabStatus,
pressed_buttons: Vec<u32>,
image_callback: Box<FnMut(CursorImageStatus) + Send>,
}
impl PointerInternal {
fn new() -> PointerInternal {
fn new<F, U, R>(token: CompositorToken<U, R>, mut cb: F) -> PointerInternal
where
U: 'static,
R: Role<CursorImageRole> + '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::<CursorImageRole>(&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<F, U, R>(token: CompositorToken<U, R>, cb: F) -> PointerHandle
where
R: Role<CursorImageRole> + '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<U, R>(
new_pointer: NewResource<WlPointer>,
handle: Option<&PointerHandle>,
) -> Resource<WlPointer> {
let destructor = match handle {
Some(h) => {
let inner = h.inner.clone();
Some(move |pointer: Resource<_>| {
token: CompositorToken<U, R>,
) -> Resource<WlPointer>
where
R: Role<CursorImageRole> + '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