Skip to content

Commit fdb57b9

Browse files
committed
seat/pointer: add support for cursor-shape-v1
When using `ThemedPointer` automatically use the `cursor-shape-v1` protocol to set cursor icon if the seat was made aware to use cursor_shape. We still accept the `WlSurface` and `WlShm` when building the `ThemedPointer` to handle the pointer hiding and custom cursor setting.
1 parent c9940f4 commit fdb57b9

File tree

6 files changed

+205
-20
lines changed

6 files changed

+205
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
- Make `DataDeviceManagerState`'s `create_{copy_paste,drag_and_drop}_source` accept `IntoIterator<Item = T: ToString>`.
2020
- Add support for `zwp_primary_selection_v1`.
21+
- `CursorShapeManager` providing handling for `cursor-shape-v1` protocol.
22+
- `SeatState::with_cursor_shape_state_manager` to automatically utilize `cursor-shape-v1` for `ThemedPointer`.
2123

2224
## 0.17.0 - 2023-03-28
2325

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ thiserror = "1.0.30"
2626
wayland-backend = "0.1.0"
2727
wayland-client = "0.30.1"
2828
wayland-cursor = "0.30.0"
29-
wayland-protocols = { version = "0.30.0", features = ["client", "unstable"] }
29+
wayland-protocols = { version = "0.30.1", features = ["client", "staging", "unstable"] }
3030
wayland-protocols-wlr = { version = "0.1.0", features = ["client"] }
3131
wayland-scanner = "0.30.0"
3232
wayland-csd-frame = "0.1.0"

examples/themed_window.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use smithay_client_toolkit::reexports::csd_frame::{
1111
};
1212
use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
1313
use smithay_client_toolkit::seat::keyboard::keysyms;
14+
use smithay_client_toolkit::seat::pointer::cursor_shape::CursorShapeManager;
1415
use smithay_client_toolkit::{
1516
compositor::{CompositorHandler, CompositorState},
1617
delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer, delegate_registry,
@@ -87,7 +88,8 @@ fn main() {
8788
let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
8889
let qh = event_queue.handle();
8990
let registry_state = RegistryState::new(&globals);
90-
let seat_state = SeatState::new(&globals, &qh);
91+
let csm = CursorShapeManager::bind(&globals, &qh).ok();
92+
let seat_state = SeatState::new(&globals, &qh).with_cursor_shape_manager(csm);
9193
let output_state = OutputState::new(&globals, &qh);
9294
let compositor_state =
9395
CompositorState::bind(&globals, &qh).expect("wl_compositor not available");

src/seat/mod.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ use wayland_client::{
2121

2222
use crate::{
2323
compositor::SurfaceDataExt,
24+
globals::GlobalData,
2425
registry::{ProvidesRegistryState, RegistryHandler},
2526
};
2627

27-
use self::{
28-
pointer::{PointerData, PointerDataExt, PointerHandler, ThemeSpec, ThemedPointer, Themes},
29-
touch::{TouchData, TouchDataExt, TouchHandler},
30-
};
28+
use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
29+
use pointer::cursor_shape::CursorShapeManager;
30+
use pointer::{PointerData, PointerDataExt, PointerHandler, ThemeSpec, ThemedPointer, Themes};
31+
use touch::{TouchData, TouchDataExt, TouchHandler};
3132

3233
#[non_exhaustive]
3334
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -64,6 +65,8 @@ pub enum SeatError {
6465
pub struct SeatState {
6566
// (name, seat)
6667
seats: Vec<SeatInner>,
68+
// Shape manager for the cursor icons.
69+
cursor_shape_manager: Option<CursorShapeManager>,
6770
}
6871

6972
impl SeatState {
@@ -82,7 +85,7 @@ impl SeatState {
8285
.expect("failed to bind global")
8386
});
8487

85-
let mut state = SeatState { seats: vec![] };
88+
let mut state = SeatState { seats: vec![], cursor_shape_manager: None };
8689
for seat in seats {
8790
let data = seat.data::<SeatData>().unwrap().clone();
8891

@@ -91,11 +94,26 @@ impl SeatState {
9194
state
9295
}
9396

97+
/// Provide the [`CursorShapeManager`] to automatically handle server side cursor
98+
/// when using [`Self::get_pointer_with_theme`] and [`Self::get_pointer_with_theme_and_data`].
99+
pub fn with_cursor_shape_manager(
100+
mut self,
101+
cursor_shape_manager: Option<CursorShapeManager>,
102+
) -> Self {
103+
self.cursor_shape_manager = cursor_shape_manager;
104+
self
105+
}
106+
94107
/// Returns an iterator over all the seats.
95108
pub fn seats(&self) -> impl Iterator<Item = wl_seat::WlSeat> {
96109
self.seats.iter().map(|inner| inner.seat.clone()).collect::<Vec<_>>().into_iter()
97110
}
98111

112+
/// Get the cursor shape manager passed in [`SeatState::with_cursor_shape_manager`].
113+
pub fn cursor_shape_manager(&self) -> Option<&CursorShapeManager> {
114+
self.cursor_shape_manager.as_ref()
115+
}
116+
99117
/// Returns information about a seat.
100118
///
101119
/// This will return [`None`] if the seat is dead.
@@ -144,6 +162,7 @@ impl SeatState {
144162
where
145163
D: Dispatch<wl_pointer::WlPointer, PointerData>
146164
+ Dispatch<wl_surface::WlSurface, S>
165+
+ Dispatch<WpCursorShapeDeviceV1, GlobalData>
147166
+ PointerHandler
148167
+ 'static,
149168
S: SurfaceDataExt + 'static,
@@ -200,6 +219,7 @@ impl SeatState {
200219
where
201220
D: Dispatch<wl_pointer::WlPointer, U>
202221
+ Dispatch<wl_surface::WlSurface, S>
222+
+ Dispatch<WpCursorShapeDeviceV1, GlobalData>
203223
+ PointerHandler
204224
+ 'static,
205225
S: SurfaceDataExt + 'static,
@@ -213,11 +233,13 @@ impl SeatState {
213233
}
214234

215235
let wl_ptr = seat.get_pointer(qh, pointer_data);
236+
let shape_device = self.cursor_shape_manager().map(|csm| csm.get_shape_device(&wl_ptr, qh));
216237
Ok(ThemedPointer {
217238
themes: Arc::new(Mutex::new(Themes::new(theme))),
218239
pointer: wl_ptr,
219240
shm: shm.clone(),
220241
surface,
242+
shape_device,
221243
_marker: std::marker::PhantomData,
222244
_surface_data: std::marker::PhantomData,
223245
})

src/seat/pointer/cursor_shape.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use cursor_icon::CursorIcon;
2+
3+
use crate::globals::GlobalData;
4+
use crate::reexports::client::globals::{BindError, GlobalList};
5+
use crate::reexports::client::protocol::wl_pointer::WlPointer;
6+
use crate::reexports::client::{Connection, Dispatch, Proxy, QueueHandle};
7+
use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
8+
use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
9+
use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1;
10+
11+
#[derive(Debug)]
12+
pub struct CursorShapeManager {
13+
cursor_shape_manager: WpCursorShapeManagerV1,
14+
}
15+
16+
impl CursorShapeManager {
17+
pub fn bind<State>(
18+
globals: &GlobalList,
19+
queue_handle: &QueueHandle<State>,
20+
) -> Result<Self, BindError>
21+
where
22+
State: Dispatch<WpCursorShapeManagerV1, GlobalData> + 'static,
23+
{
24+
let cursor_shape_manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
25+
Ok(Self { cursor_shape_manager })
26+
}
27+
28+
pub fn get_shape_device<State>(
29+
&self,
30+
pointer: &WlPointer,
31+
queue_handle: &QueueHandle<State>,
32+
) -> WpCursorShapeDeviceV1
33+
where
34+
State: Dispatch<WpCursorShapeDeviceV1, GlobalData> + 'static,
35+
{
36+
self.cursor_shape_manager.get_pointer(pointer, queue_handle, GlobalData)
37+
}
38+
39+
pub fn inner(&self) -> &WpCursorShapeManagerV1 {
40+
&self.cursor_shape_manager
41+
}
42+
}
43+
44+
impl<State> Dispatch<WpCursorShapeManagerV1, GlobalData, State> for CursorShapeManager
45+
where
46+
State: Dispatch<WpCursorShapeManagerV1, GlobalData>,
47+
{
48+
fn event(
49+
_: &mut State,
50+
_: &WpCursorShapeManagerV1,
51+
_: <WpCursorShapeManagerV1 as Proxy>::Event,
52+
_: &GlobalData,
53+
_: &Connection,
54+
_: &QueueHandle<State>,
55+
) {
56+
unreachable!("wl_cursor_shape_manager_v1 has no events")
57+
}
58+
}
59+
60+
impl<State> Dispatch<WpCursorShapeDeviceV1, GlobalData, State> for CursorShapeManager
61+
where
62+
State: Dispatch<WpCursorShapeDeviceV1, GlobalData>,
63+
{
64+
fn event(
65+
_: &mut State,
66+
_: &WpCursorShapeDeviceV1,
67+
_: <WpCursorShapeDeviceV1 as Proxy>::Event,
68+
_: &GlobalData,
69+
_: &Connection,
70+
_: &QueueHandle<State>,
71+
) {
72+
unreachable!("wl_cursor_shape_device_v1 has no events")
73+
}
74+
}
75+
76+
pub(crate) fn cursor_icon_to_shape(cursor_icon: CursorIcon) -> Shape {
77+
match cursor_icon {
78+
CursorIcon::Default => Shape::Default,
79+
CursorIcon::ContextMenu => Shape::ContextMenu,
80+
CursorIcon::Help => Shape::Help,
81+
CursorIcon::Pointer => Shape::Pointer,
82+
CursorIcon::Progress => Shape::Progress,
83+
CursorIcon::Wait => Shape::Wait,
84+
CursorIcon::Cell => Shape::Cell,
85+
CursorIcon::Crosshair => Shape::Crosshair,
86+
CursorIcon::Text => Shape::Text,
87+
CursorIcon::VerticalText => Shape::VerticalText,
88+
CursorIcon::Alias => Shape::Alias,
89+
CursorIcon::Copy => Shape::Copy,
90+
CursorIcon::Move => Shape::Move,
91+
CursorIcon::NoDrop => Shape::NoDrop,
92+
CursorIcon::NotAllowed => Shape::NotAllowed,
93+
CursorIcon::Grab => Shape::Grab,
94+
CursorIcon::Grabbing => Shape::Grabbing,
95+
CursorIcon::EResize => Shape::EResize,
96+
CursorIcon::NResize => Shape::NResize,
97+
CursorIcon::NeResize => Shape::NeResize,
98+
CursorIcon::NwResize => Shape::NwResize,
99+
CursorIcon::SResize => Shape::SResize,
100+
CursorIcon::SeResize => Shape::SeResize,
101+
CursorIcon::SwResize => Shape::SwResize,
102+
CursorIcon::WResize => Shape::WResize,
103+
CursorIcon::EwResize => Shape::EwResize,
104+
CursorIcon::NsResize => Shape::NsResize,
105+
CursorIcon::NeswResize => Shape::NeswResize,
106+
CursorIcon::NwseResize => Shape::NwseResize,
107+
CursorIcon::ColResize => Shape::ColResize,
108+
CursorIcon::RowResize => Shape::RowResize,
109+
CursorIcon::AllScroll => Shape::AllScroll,
110+
CursorIcon::ZoomIn => Shape::ZoomIn,
111+
CursorIcon::ZoomOut => Shape::ZoomOut,
112+
_ => Shape::Default,
113+
}
114+
}

src/seat/pointer/mod.rs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use wayland_client::{
1515
Connection, Dispatch, Proxy, QueueHandle, WEnum,
1616
};
1717
use wayland_cursor::{Cursor, CursorTheme};
18+
use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
1819

1920
use crate::{
2021
compositor::{SurfaceData, SurfaceDataExt},
@@ -26,6 +27,10 @@ use super::SeatState;
2627
#[doc(inline)]
2728
pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError};
2829

30+
pub mod cursor_shape;
31+
32+
use cursor_shape::cursor_icon_to_shape;
33+
2934
/* From linux/input-event-codes.h - the buttons usually used by mice */
3035
pub const BTN_LEFT: u32 = 0x110;
3136
pub const BTN_RIGHT: u32 = 0x111;
@@ -171,7 +176,17 @@ macro_rules! delegate_pointer {
171176
$crate::reexports::client::protocol::wl_pointer::WlPointer: $crate::seat::pointer::PointerData
172177
] => $crate::seat::SeatState
173178
);
174-
};
179+
$crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
180+
[
181+
$crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1: $crate::globals::GlobalData
182+
] => $crate::seat::pointer::cursor_shape::CursorShapeManager
183+
);
184+
$crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
185+
[
186+
$crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1: $crate::globals::GlobalData
187+
] => $crate::seat::pointer::cursor_shape::CursorShapeManager
188+
);
189+
};
175190
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, pointer: [$($pointer_data:ty),* $(,)?]) => {
176191
$crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
177192
[
@@ -180,6 +195,16 @@ macro_rules! delegate_pointer {
180195
)*
181196
] => $crate::seat::SeatState
182197
);
198+
$crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
199+
[
200+
$crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1: $crate::globals::GlobalData
201+
] => $crate::seat::pointer::cursor_shape::CursorShapeManager
202+
);
203+
$crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
204+
[
205+
$crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1: $crate::globals::GlobalData
206+
] => $crate::seat::pointer::cursor_shape::CursorShapeManager
207+
);
183208
};
184209
}
185210

@@ -401,6 +426,7 @@ pub struct ThemedPointer<U = PointerData, S = SurfaceData> {
401426
pub(super) shm: WlShm,
402427
/// The surface owned by the cursor to present the icon.
403428
pub(super) surface: WlSurface,
429+
pub(super) shape_device: Option<WpCursorShapeDeviceV1>,
404430
pub(super) _marker: std::marker::PhantomData<U>,
405431
pub(super) _surface_data: std::marker::PhantomData<S>,
406432
}
@@ -410,6 +436,31 @@ impl<U: PointerDataExt + 'static, S: SurfaceDataExt + 'static> ThemedPointer<U,
410436
///
411437
/// The cursor icon should be reloaded on every [`PointerEventKind::Enter`] event.
412438
pub fn set_cursor(&self, conn: &Connection, icon: CursorIcon) -> Result<(), PointerThemeError> {
439+
let serial = match self
440+
.pointer
441+
.data::<U>()
442+
.and_then(|data| data.pointer_data().latest_enter_serial())
443+
{
444+
Some(serial) => serial,
445+
None => return Err(PointerThemeError::MissingEnterSerial),
446+
};
447+
448+
if let Some(shape_device) = self.shape_device.as_ref() {
449+
shape_device.set_shape(serial, cursor_icon_to_shape(icon));
450+
Ok(())
451+
} else {
452+
self.set_cursor_legacy(conn, serial, icon)
453+
}
454+
}
455+
456+
/// The legacy method of loading the cursor from the system cursor
457+
/// theme instead of relying on compositor to set the cursor.
458+
fn set_cursor_legacy(
459+
&self,
460+
conn: &Connection,
461+
serial: u32,
462+
icon: CursorIcon,
463+
) -> Result<(), PointerThemeError> {
413464
let mut themes = self.themes.lock().unwrap();
414465

415466
let scale = self.surface.data::<S>().unwrap().surface_data().scale_factor();
@@ -436,18 +487,8 @@ impl<U: PointerDataExt + 'static, S: SurfaceDataExt + 'static> ThemedPointer<U,
436487
self.surface.commit();
437488

438489
// Set the pointer surface to change the pointer.
439-
let data = self.pointer.data::<U>();
440-
if let Some(serial) = data.and_then(|data| data.pointer_data().latest_enter_serial()) {
441-
self.pointer.set_cursor(
442-
serial,
443-
Some(&self.surface),
444-
hx as i32 / scale,
445-
hy as i32 / scale,
446-
);
447-
Ok(())
448-
} else {
449-
Err(PointerThemeError::MissingEnterSerial)
450-
}
490+
self.pointer.set_cursor(serial, Some(&self.surface), hx as i32 / scale, hy as i32 / scale);
491+
Ok(())
451492
}
452493

453494
/// Hide the cursor by providing empty surface for it.
@@ -476,6 +517,10 @@ impl<U: PointerDataExt + 'static, S: SurfaceDataExt + 'static> ThemedPointer<U,
476517

477518
impl<U, S> Drop for ThemedPointer<U, S> {
478519
fn drop(&mut self) {
520+
if let Some(shape_device) = self.shape_device.take() {
521+
shape_device.destroy();
522+
}
523+
479524
if self.pointer.version() >= 3 {
480525
self.pointer.release();
481526
}

0 commit comments

Comments
 (0)