diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a754f2c..8d7940a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - Make `DataDeviceManagerState`'s `create_{copy_paste,drag_and_drop}_source` accept `IntoIterator`. - Add support for `zwp_primary_selection_v1`. +- `CursorShapeManager` providing handling for `cursor-shape-v1` protocol. +- `SeatState::get_pointer_with_theme` will now automatically use `wp_cursor_shape_v1` when available. ## 0.17.0 - 2023-03-28 diff --git a/Cargo.toml b/Cargo.toml index b403e7ec9..751dfd6a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ thiserror = "1.0.30" wayland-backend = "0.1.0" wayland-client = "0.30.1" wayland-cursor = "0.30.0" -wayland-protocols = { version = "0.30.0", features = ["client", "unstable"] } +wayland-protocols = { version = "0.30.1", features = ["client", "staging", "unstable"] } wayland-protocols-wlr = { version = "0.1.0", features = ["client"] } wayland-scanner = "0.30.0" wayland-csd-frame = "0.1.0" diff --git a/src/seat/mod.rs b/src/seat/mod.rs index 6f9bf117a..ecdbcc443 100644 --- a/src/seat/mod.rs +++ b/src/seat/mod.rs @@ -1,10 +1,3 @@ -#[cfg(feature = "xkbcommon")] -pub mod keyboard; -pub mod pointer; -pub mod pointer_constraints; -pub mod relative_pointer; -pub mod touch; - use std::{ fmt::{self, Display, Formatter}, sync::{ @@ -13,21 +6,29 @@ use std::{ }, }; -use wayland_client::{ - globals::GlobalList, - protocol::{wl_pointer, wl_seat, wl_shm, wl_surface, wl_touch}, +use crate::reexports::client::{ + globals::{Global, GlobalList}, + protocol::{wl_pointer, wl_registry::WlRegistry, wl_seat, wl_shm, wl_surface, wl_touch}, Connection, Dispatch, Proxy, QueueHandle, }; - +use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; +use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1; use crate::{ compositor::SurfaceDataExt, + globals::GlobalData, registry::{ProvidesRegistryState, RegistryHandler}, }; -use self::{ - pointer::{PointerData, PointerDataExt, PointerHandler, ThemeSpec, ThemedPointer, Themes}, - touch::{TouchData, TouchDataExt, TouchHandler}, -}; +#[cfg(feature = "xkbcommon")] +pub mod keyboard; +pub mod pointer; +pub mod pointer_constraints; +pub mod relative_pointer; +pub mod touch; + +use pointer::cursor_shape::CursorShapeManager; +use pointer::{PointerData, PointerDataExt, PointerHandler, ThemeSpec, ThemedPointer, Themes}; +use touch::{TouchData, TouchDataExt, TouchHandler}; #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -64,6 +65,14 @@ pub enum SeatError { pub struct SeatState { // (name, seat) seats: Vec, + cursor_shape_manager_state: CursorShapeManagerState, +} + +#[derive(Debug)] +enum CursorShapeManagerState { + NotPresent, + Pending { registry: WlRegistry, global: Global }, + Bound(CursorShapeManager), } impl SeatState { @@ -71,18 +80,34 @@ impl SeatState { global_list: &GlobalList, qh: &QueueHandle, ) -> SeatState { - let seats = global_list.contents().with_list(|globals| { - crate::registry::bind_all(global_list.registry(), globals, qh, 1..=7, |id| SeatData { - has_keyboard: Arc::new(AtomicBool::new(false)), - has_pointer: Arc::new(AtomicBool::new(false)), - has_touch: Arc::new(AtomicBool::new(false)), - name: Arc::new(Mutex::new(None)), - id, - }) - .expect("failed to bind global") + let (seats, cursor_shape_manager) = global_list.contents().with_list(|globals| { + let global = globals + .iter() + .find(|global| global.interface == WpCursorShapeManagerV1::interface().name) + .map(|global| CursorShapeManagerState::Pending { + registry: global_list.registry().clone(), + global: global.clone(), + }) + .unwrap_or(CursorShapeManagerState::NotPresent); + + ( + crate::registry::bind_all(global_list.registry(), globals, qh, 1..=7, |id| { + SeatData { + has_keyboard: Arc::new(AtomicBool::new(false)), + has_pointer: Arc::new(AtomicBool::new(false)), + has_touch: Arc::new(AtomicBool::new(false)), + name: Arc::new(Mutex::new(None)), + id, + } + }) + .expect("failed to bind global"), + global, + ) }); - let mut state = SeatState { seats: vec![] }; + let mut state = + SeatState { seats: vec![], cursor_shape_manager_state: cursor_shape_manager }; + for seat in seats { let data = seat.data::().unwrap().clone(); @@ -130,6 +155,8 @@ impl SeatState { /// Creates a pointer from a seat with the provided theme. /// + /// This will use [`CursorShapeManager`] under the hood when it's available. + /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a pointer. @@ -144,6 +171,8 @@ impl SeatState { where D: Dispatch + Dispatch + + Dispatch + + Dispatch + PointerHandler + 'static, S: SurfaceDataExt + 'static, @@ -200,6 +229,8 @@ impl SeatState { where D: Dispatch + Dispatch + + Dispatch + + Dispatch + PointerHandler + 'static, S: SurfaceDataExt + 'static, @@ -213,11 +244,33 @@ impl SeatState { } let wl_ptr = seat.get_pointer(qh, pointer_data); + + if let CursorShapeManagerState::Pending { registry, global } = + &self.cursor_shape_manager_state + { + self.cursor_shape_manager_state = + match crate::registry::bind_one(registry, &[global.clone()], qh, 1..=1, GlobalData) + { + Ok(bound) => { + CursorShapeManagerState::Bound(CursorShapeManager::from_existing(bound)) + } + Err(_) => CursorShapeManagerState::NotPresent, + } + } + + let shape_device = + if let CursorShapeManagerState::Bound(ref bound) = self.cursor_shape_manager_state { + Some(bound.get_shape_device(&wl_ptr, qh)) + } else { + None + }; + Ok(ThemedPointer { themes: Arc::new(Mutex::new(Themes::new(theme))), pointer: wl_ptr, shm: shm.clone(), surface, + shape_device, _marker: std::marker::PhantomData, _surface_data: std::marker::PhantomData, }) @@ -458,7 +511,7 @@ where interface: &str, _: u32, ) { - if interface == "wl_seat" { + if interface == wl_seat::WlSeat::interface().name { let seat = state .registry() .bind_specific( @@ -489,7 +542,7 @@ where name: u32, interface: &str, ) { - if interface == "wl_seat" { + if interface == wl_seat::WlSeat::interface().name { if let Some(seat) = state.seat_state().seats.iter().find(|inner| inner.data.id == name) { let seat = seat.seat.clone(); diff --git a/src/seat/pointer/cursor_shape.rs b/src/seat/pointer/cursor_shape.rs new file mode 100644 index 000000000..d6129745a --- /dev/null +++ b/src/seat/pointer/cursor_shape.rs @@ -0,0 +1,118 @@ +use cursor_icon::CursorIcon; + +use crate::globals::GlobalData; +use crate::reexports::client::globals::{BindError, GlobalList}; +use crate::reexports::client::protocol::wl_pointer::WlPointer; +use crate::reexports::client::{Connection, Dispatch, Proxy, QueueHandle}; +use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape; +use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; +use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1; + +#[derive(Debug)] +pub struct CursorShapeManager { + cursor_shape_manager: WpCursorShapeManagerV1, +} + +impl CursorShapeManager { + pub fn bind( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result + where + State: Dispatch + 'static, + { + let cursor_shape_manager = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { cursor_shape_manager }) + } + + pub(crate) fn from_existing(cursor_shape_manager: WpCursorShapeManagerV1) -> Self { + Self { cursor_shape_manager } + } + + pub fn get_shape_device( + &self, + pointer: &WlPointer, + queue_handle: &QueueHandle, + ) -> WpCursorShapeDeviceV1 + where + State: Dispatch + 'static, + { + self.cursor_shape_manager.get_pointer(pointer, queue_handle, GlobalData) + } + + pub fn inner(&self) -> &WpCursorShapeManagerV1 { + &self.cursor_shape_manager + } +} + +impl Dispatch for CursorShapeManager +where + State: Dispatch, +{ + fn event( + _: &mut State, + _: &WpCursorShapeManagerV1, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + unreachable!("wl_cursor_shape_manager_v1 has no events") + } +} + +impl Dispatch for CursorShapeManager +where + State: Dispatch, +{ + fn event( + _: &mut State, + _: &WpCursorShapeDeviceV1, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + unreachable!("wl_cursor_shape_device_v1 has no events") + } +} + +pub(crate) fn cursor_icon_to_shape(cursor_icon: CursorIcon) -> Shape { + match cursor_icon { + CursorIcon::Default => Shape::Default, + CursorIcon::ContextMenu => Shape::ContextMenu, + CursorIcon::Help => Shape::Help, + CursorIcon::Pointer => Shape::Pointer, + CursorIcon::Progress => Shape::Progress, + CursorIcon::Wait => Shape::Wait, + CursorIcon::Cell => Shape::Cell, + CursorIcon::Crosshair => Shape::Crosshair, + CursorIcon::Text => Shape::Text, + CursorIcon::VerticalText => Shape::VerticalText, + CursorIcon::Alias => Shape::Alias, + CursorIcon::Copy => Shape::Copy, + CursorIcon::Move => Shape::Move, + CursorIcon::NoDrop => Shape::NoDrop, + CursorIcon::NotAllowed => Shape::NotAllowed, + CursorIcon::Grab => Shape::Grab, + CursorIcon::Grabbing => Shape::Grabbing, + CursorIcon::EResize => Shape::EResize, + CursorIcon::NResize => Shape::NResize, + CursorIcon::NeResize => Shape::NeResize, + CursorIcon::NwResize => Shape::NwResize, + CursorIcon::SResize => Shape::SResize, + CursorIcon::SeResize => Shape::SeResize, + CursorIcon::SwResize => Shape::SwResize, + CursorIcon::WResize => Shape::WResize, + CursorIcon::EwResize => Shape::EwResize, + CursorIcon::NsResize => Shape::NsResize, + CursorIcon::NeswResize => Shape::NeswResize, + CursorIcon::NwseResize => Shape::NwseResize, + CursorIcon::ColResize => Shape::ColResize, + CursorIcon::RowResize => Shape::RowResize, + CursorIcon::AllScroll => Shape::AllScroll, + CursorIcon::ZoomIn => Shape::ZoomIn, + CursorIcon::ZoomOut => Shape::ZoomOut, + _ => Shape::Default, + } +} diff --git a/src/seat/pointer/mod.rs b/src/seat/pointer/mod.rs index 6ed8ed143..aec62342b 100644 --- a/src/seat/pointer/mod.rs +++ b/src/seat/pointer/mod.rs @@ -15,6 +15,7 @@ use wayland_client::{ Connection, Dispatch, Proxy, QueueHandle, WEnum, }; use wayland_cursor::{Cursor, CursorTheme}; +use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; use crate::{ compositor::{SurfaceData, SurfaceDataExt}, @@ -26,6 +27,10 @@ use super::SeatState; #[doc(inline)] pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError}; +pub mod cursor_shape; + +use cursor_shape::cursor_icon_to_shape; + /* From linux/input-event-codes.h - the buttons usually used by mice */ pub const BTN_LEFT: u32 = 0x110; pub const BTN_RIGHT: u32 = 0x111; @@ -171,7 +176,17 @@ macro_rules! delegate_pointer { $crate::reexports::client::protocol::wl_pointer::WlPointer: $crate::seat::pointer::PointerData ] => $crate::seat::SeatState ); - }; + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1: $crate::globals::GlobalData + ] => $crate::seat::pointer::cursor_shape::CursorShapeManager + ); + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1: $crate::globals::GlobalData + ] => $crate::seat::pointer::cursor_shape::CursorShapeManager + ); + }; ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, pointer: [$($pointer_data:ty),* $(,)?]) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ @@ -180,6 +195,16 @@ macro_rules! delegate_pointer { )* ] => $crate::seat::SeatState ); + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1: $crate::globals::GlobalData + ] => $crate::seat::pointer::cursor_shape::CursorShapeManager + ); + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1: $crate::globals::GlobalData + ] => $crate::seat::pointer::cursor_shape::CursorShapeManager + ); }; } @@ -401,6 +426,7 @@ pub struct ThemedPointer { pub(super) shm: WlShm, /// The surface owned by the cursor to present the icon. pub(super) surface: WlSurface, + pub(super) shape_device: Option, pub(super) _marker: std::marker::PhantomData, pub(super) _surface_data: std::marker::PhantomData, } @@ -410,6 +436,31 @@ impl ThemedPointer Result<(), PointerThemeError> { + let serial = match self + .pointer + .data::() + .and_then(|data| data.pointer_data().latest_enter_serial()) + { + Some(serial) => serial, + None => return Err(PointerThemeError::MissingEnterSerial), + }; + + if let Some(shape_device) = self.shape_device.as_ref() { + shape_device.set_shape(serial, cursor_icon_to_shape(icon)); + Ok(()) + } else { + self.set_cursor_legacy(conn, serial, icon) + } + } + + /// The legacy method of loading the cursor from the system cursor + /// theme instead of relying on compositor to set the cursor. + fn set_cursor_legacy( + &self, + conn: &Connection, + serial: u32, + icon: CursorIcon, + ) -> Result<(), PointerThemeError> { let mut themes = self.themes.lock().unwrap(); let scale = self.surface.data::().unwrap().surface_data().scale_factor(); @@ -436,18 +487,8 @@ impl ThemedPointer(); - if let Some(serial) = data.and_then(|data| data.pointer_data().latest_enter_serial()) { - self.pointer.set_cursor( - serial, - Some(&self.surface), - hx as i32 / scale, - hy as i32 / scale, - ); - Ok(()) - } else { - Err(PointerThemeError::MissingEnterSerial) - } + self.pointer.set_cursor(serial, Some(&self.surface), hx as i32 / scale, hy as i32 / scale); + Ok(()) } /// Hide the cursor by providing empty surface for it. @@ -476,6 +517,10 @@ impl ThemedPointer Drop for ThemedPointer { fn drop(&mut self) { + if let Some(shape_device) = self.shape_device.take() { + shape_device.destroy(); + } + if self.pointer.version() >= 3 { self.pointer.release(); }