use std::{
collections::{hash_map::Entry, HashMap},
env, iter, mem,
sync::{Arc, Mutex},
};
use wayland_backend::{client::InvalidId, smallvec::SmallVec};
use wayland_client::{
protocol::{
wl_pointer::{self, WlPointer},
wl_seat::WlSeat,
wl_shm::WlShm,
wl_surface::WlSurface,
},
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},
error::GlobalError,
};
use super::SeatState;
#[doc(inline)]
pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError};
pub mod cursor_shape;
use cursor_shape::cursor_icon_to_shape;
pub const BTN_LEFT: u32 = 0x110;
pub const BTN_RIGHT: u32 = 0x111;
pub const BTN_MIDDLE: u32 = 0x112;
pub const BTN_SIDE: u32 = 0x113;
pub const BTN_EXTRA: u32 = 0x114;
pub const BTN_FORWARD: u32 = 0x115;
pub const BTN_BACK: u32 = 0x116;
pub const BTN_TASK: u32 = 0x117;
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct AxisScroll {
pub absolute: f64,
pub discrete: i32,
pub stop: bool,
}
impl AxisScroll {
pub fn is_none(&self) -> bool {
*self == Self::default()
}
fn merge(&mut self, other: &Self) {
self.absolute += other.absolute;
self.discrete += other.discrete;
self.stop |= other.stop;
}
}
#[derive(Debug, Clone)]
pub struct PointerEvent {
pub surface: WlSurface,
pub position: (f64, f64),
pub kind: PointerEventKind,
}
#[derive(Debug, Clone)]
pub enum PointerEventKind {
Enter {
serial: u32,
},
Leave {
serial: u32,
},
Motion {
time: u32,
},
Press {
time: u32,
button: u32,
serial: u32,
},
Release {
time: u32,
button: u32,
serial: u32,
},
Axis {
time: u32,
horizontal: AxisScroll,
vertical: AxisScroll,
source: Option<wl_pointer::AxisSource>,
},
}
pub trait PointerHandler: Sized {
fn pointer_frame(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
pointer: &WlPointer,
events: &[PointerEvent],
);
}
#[derive(Debug)]
pub struct PointerData {
seat: WlSeat,
pub(crate) inner: Mutex<PointerDataInner>,
}
impl PointerData {
pub fn new(seat: WlSeat) -> Self {
Self { seat, inner: Default::default() }
}
pub fn seat(&self) -> &WlSeat {
&self.seat
}
pub fn latest_enter_serial(&self) -> Option<u32> {
self.inner.lock().unwrap().latest_enter
}
pub fn latest_button_serial(&self) -> Option<u32> {
self.inner.lock().unwrap().latest_btn
}
}
pub trait PointerDataExt: Send + Sync {
fn pointer_data(&self) -> &PointerData;
}
impl PointerDataExt for PointerData {
fn pointer_data(&self) -> &PointerData {
self
}
}
#[macro_export]
macro_rules! delegate_pointer {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
$crate::delegate_pointer!(@{ $(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty }; pointer: []);
$crate::delegate_pointer!(@{ $(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty }; pointer-only: $crate::seat::pointer::PointerData);
};
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, pointer: [$($pointer_data:ty),* $(,)?]) => {
$crate::delegate_pointer!(@{ $(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty }; pointer: [ $($pointer_data),* ]);
};
(@{$($ty:tt)*}; pointer: []) => {
$crate::reexports::client::delegate_dispatch!($($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!($($ty)*:
[
$crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1: $crate::globals::GlobalData
] => $crate::seat::pointer::cursor_shape::CursorShapeManager
);
};
(@{$($ty:tt)*}; pointer-only: $pointer_data:ty) => {
$crate::reexports::client::delegate_dispatch!($($ty)*:
[
$crate::reexports::client::protocol::wl_pointer::WlPointer: $pointer_data
] => $crate::seat::SeatState
);
};
(@$ty:tt; pointer: [$($pointer:ty),*]) => {
$crate::delegate_pointer!(@$ty; pointer: []);
$( $crate::delegate_pointer!(@$ty; pointer-only: $pointer); )*
}
}
#[derive(Debug, Default)]
pub(crate) struct PointerDataInner {
pub(crate) surface: Option<WlSurface>,
pub(crate) position: (f64, f64),
pub(crate) pending: SmallVec<[PointerEvent; 3]>,
pub(crate) latest_enter: Option<u32>,
pub(crate) latest_btn: Option<u32>,
}
impl<D, U> Dispatch<WlPointer, U, D> for SeatState
where
D: Dispatch<WlPointer, U> + PointerHandler,
U: PointerDataExt,
{
fn event(
data: &mut D,
pointer: &WlPointer,
event: wl_pointer::Event,
udata: &U,
conn: &Connection,
qh: &QueueHandle<D>,
) {
let udata = udata.pointer_data();
let mut guard = udata.inner.lock().unwrap();
let mut leave_surface = None;
let kind = match event {
wl_pointer::Event::Enter { surface, surface_x, surface_y, serial } => {
guard.surface = Some(surface);
guard.position = (surface_x, surface_y);
guard.latest_enter.replace(serial);
PointerEventKind::Enter { serial }
}
wl_pointer::Event::Leave { surface, serial } => {
if guard.surface.as_ref() == Some(&surface) {
guard.surface = None;
}
leave_surface = Some(surface);
PointerEventKind::Leave { serial }
}
wl_pointer::Event::Motion { time, surface_x, surface_y } => {
guard.position = (surface_x, surface_y);
PointerEventKind::Motion { time }
}
wl_pointer::Event::Button { time, button, state, serial } => {
guard.latest_btn.replace(serial);
match state {
WEnum::Value(wl_pointer::ButtonState::Pressed) => {
PointerEventKind::Press { time, button, serial }
}
WEnum::Value(wl_pointer::ButtonState::Released) => {
PointerEventKind::Release { time, button, serial }
}
WEnum::Unknown(unknown) => {
log::warn!(target: "sctk", "{}: invalid pointer button state: {:x}", pointer.id(), unknown);
return;
}
_ => unreachable!(),
}
}
wl_pointer::Event::Axis { time, axis, value } => match axis {
WEnum::Value(axis) => {
let (mut horizontal, mut vertical) = <(AxisScroll, AxisScroll)>::default();
match axis {
wl_pointer::Axis::VerticalScroll => {
vertical.absolute = value;
}
wl_pointer::Axis::HorizontalScroll => {
horizontal.absolute = value;
}
_ => unreachable!(),
};
PointerEventKind::Axis { time, horizontal, vertical, source: None }
}
WEnum::Unknown(unknown) => {
log::warn!(target: "sctk", "{}: invalid pointer axis: {:x}", pointer.id(), unknown);
return;
}
},
wl_pointer::Event::AxisSource { axis_source } => match axis_source {
WEnum::Value(source) => PointerEventKind::Axis {
horizontal: AxisScroll::default(),
vertical: AxisScroll::default(),
source: Some(source),
time: 0,
},
WEnum::Unknown(unknown) => {
log::warn!(target: "sctk", "unknown pointer axis source: {:x}", unknown);
return;
}
},
wl_pointer::Event::AxisStop { time, axis } => match axis {
WEnum::Value(axis) => {
let (mut horizontal, mut vertical) = <(AxisScroll, AxisScroll)>::default();
match axis {
wl_pointer::Axis::VerticalScroll => vertical.stop = true,
wl_pointer::Axis::HorizontalScroll => horizontal.stop = true,
_ => unreachable!(),
}
PointerEventKind::Axis { time, horizontal, vertical, source: None }
}
WEnum::Unknown(unknown) => {
log::warn!(target: "sctk", "{}: invalid pointer axis: {:x}", pointer.id(), unknown);
return;
}
},
wl_pointer::Event::AxisDiscrete { axis, discrete } => match axis {
WEnum::Value(axis) => {
let (mut horizontal, mut vertical) = <(AxisScroll, AxisScroll)>::default();
match axis {
wl_pointer::Axis::VerticalScroll => {
vertical.discrete = discrete;
}
wl_pointer::Axis::HorizontalScroll => {
horizontal.discrete = discrete;
}
_ => unreachable!(),
};
PointerEventKind::Axis { time: 0, horizontal, vertical, source: None }
}
WEnum::Unknown(unknown) => {
log::warn!(target: "sctk", "{}: invalid pointer axis: {:x}", pointer.id(), unknown);
return;
}
},
wl_pointer::Event::Frame => {
let pending = mem::take(&mut guard.pending);
drop(guard);
if !pending.is_empty() {
data.pointer_frame(conn, qh, pointer, &pending);
}
return;
}
_ => unreachable!(),
};
let surface = match (leave_surface, &guard.surface) {
(Some(surface), _) => surface,
(None, Some(surface)) => surface.clone(),
(None, None) => {
log::warn!(target: "sctk", "{}: got pointer event {:?} without an entered surface", pointer.id(), kind);
return;
}
};
let event = PointerEvent { surface, position: guard.position, kind };
if pointer.version() < 5 {
drop(guard);
data.pointer_frame(conn, qh, pointer, &[event]);
} else {
if let (
Some(PointerEvent {
kind:
PointerEventKind::Axis { time: ot, horizontal: oh, vertical: ov, source: os },
..
}),
PointerEvent {
kind:
PointerEventKind::Axis { time: nt, horizontal: nh, vertical: nv, source: ns },
..
},
) = (guard.pending.last_mut(), &event)
{
if *ot == 0 {
*ot = *nt;
}
oh.merge(nh);
ov.merge(nv);
*os = os.or(*ns);
return;
}
guard.pending.push(event);
}
}
}
#[derive(Debug)]
pub struct ThemedPointer<U = PointerData, S = SurfaceData> {
pub(super) themes: Arc<Mutex<Themes>>,
pub(super) pointer: WlPointer,
pub(super) shm: WlShm,
pub(super) surface: WlSurface,
pub(super) shape_device: Option<WpCursorShapeDeviceV1>,
pub(super) _marker: std::marker::PhantomData<U>,
pub(super) _surface_data: std::marker::PhantomData<S>,
}
impl<U: PointerDataExt + 'static, S: SurfaceDataExt + 'static> ThemedPointer<U, S> {
pub fn set_cursor(&self, conn: &Connection, icon: CursorIcon) -> Result<(), PointerThemeError> {
let serial = match self
.pointer
.data::<U>()
.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)
}
}
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::<S>().unwrap().surface_data().scale_factor();
for cursor_icon_name in iter::once(&icon.name()).chain(icon.alt_names().iter()) {
if let Some(cursor) = themes
.get_cursor(conn, cursor_icon_name, scale as u32, &self.shm)
.map_err(PointerThemeError::InvalidId)?
{
let image = &cursor[0];
let (w, h) = image.dimensions();
let (hx, hy) = image.hotspot();
self.surface.set_buffer_scale(scale);
self.surface.attach(Some(image), 0, 0);
if self.surface.version() >= 4 {
self.surface.damage_buffer(0, 0, w as i32, h as i32);
} else {
self.surface.damage(0, 0, w as i32 / scale, h as i32 / scale);
}
self.surface.commit();
self.pointer.set_cursor(
serial,
Some(&self.surface),
hx as i32 / scale,
hy as i32 / scale,
);
return Ok(());
}
}
Err(PointerThemeError::CursorNotFound)
}
pub fn hide_cursor(&self) -> Result<(), PointerThemeError> {
let data = self.pointer.data::<U>();
if let Some(serial) = data.and_then(|data| data.pointer_data().latest_enter_serial()) {
self.pointer.set_cursor(serial, None, 0, 0);
Ok(())
} else {
Err(PointerThemeError::MissingEnterSerial)
}
}
pub fn pointer(&self) -> &WlPointer {
&self.pointer
}
pub fn surface(&self) -> &WlSurface {
&self.surface
}
}
impl<U, S> Drop for ThemedPointer<U, S> {
fn drop(&mut self) {
if let Some(shape_device) = self.shape_device.take() {
shape_device.destroy();
}
if self.pointer.version() >= 3 {
self.pointer.release();
}
self.surface.destroy();
}
}
#[derive(Debug)]
pub enum ThemeSpec<'a> {
Named {
name: &'a str,
size: u32,
},
System,
}
impl<'a> Default for ThemeSpec<'a> {
fn default() -> Self {
Self::System
}
}
#[derive(Debug, thiserror::Error)]
pub enum PointerThemeError {
#[error("Invalid ObjectId")]
InvalidId(InvalidId),
#[error("A Global Error occured")]
GlobalError(GlobalError),
#[error("Cursor not found")]
CursorNotFound,
#[error("Missing enter event serial")]
MissingEnterSerial,
}
#[derive(Debug)]
pub(crate) struct Themes {
name: String,
size: u32,
themes: HashMap<u32, CursorTheme>,
}
impl Default for Themes {
fn default() -> Self {
Themes::new(ThemeSpec::default())
}
}
impl Themes {
pub(crate) fn new(spec: ThemeSpec) -> Themes {
let (name, size) = match spec {
ThemeSpec::Named { name, size } => (name.into(), size),
ThemeSpec::System => {
let name = env::var("XCURSOR_THEME").ok().unwrap_or_else(|| "default".into());
let size = env::var("XCURSOR_SIZE").ok().and_then(|s| s.parse().ok()).unwrap_or(24);
(name, size)
}
};
Themes { name, size, themes: HashMap::new() }
}
fn get_cursor(
&mut self,
conn: &Connection,
name: &str,
scale: u32,
shm: &WlShm,
) -> Result<Option<&Cursor>, InvalidId> {
if let Entry::Vacant(e) = self.themes.entry(scale) {
let theme = CursorTheme::load_from_name(
conn,
shm.clone(), &self.name,
self.size * scale,
)?;
e.insert(theme);
}
let theme = self.themes.get_mut(&scale).unwrap();
Ok(theme.get_cursor(name))
}
}