use crate::CursorIcon;
use super::protocol::cursor_shape::wp_cursor_shape_device_v1::{self, shape};
use super::protocol::cursor_shape::wp_cursor_shape_manager_v1;
use super::{ObjectKind, WaylandShell};
pub(super) const CURSOR_SHAPE_VERSION: u32 = 1;
pub(super) const fn shape_of(icon: CursorIcon) -> u32 {
match icon {
CursorIcon::Default => shape::DEFAULT,
CursorIcon::Pointer => shape::POINTER,
CursorIcon::Text => shape::TEXT,
CursorIcon::Crosshair => shape::CROSSHAIR,
CursorIcon::Wait => shape::WAIT,
CursorIcon::Progress => shape::PROGRESS,
CursorIcon::NotAllowed => shape::NOT_ALLOWED,
CursorIcon::Grab => shape::GRAB,
CursorIcon::Grabbing => shape::GRABBING,
CursorIcon::Move => shape::MOVE,
CursorIcon::ResizeNorthSouth => shape::NS_RESIZE,
CursorIcon::ResizeEastWest => shape::EW_RESIZE,
CursorIcon::ResizeNorthEastSouthWest => shape::NESW_RESIZE,
CursorIcon::ResizeNorthWestSouthEast => shape::NWSE_RESIZE,
CursorIcon::ResizeColumn => shape::COL_RESIZE,
CursorIcon::ResizeRow => shape::ROW_RESIZE,
}
}
impl WaylandShell {
pub(super) fn add_cursor_shape(&mut self, index: usize) {
let manager = self.conn.cursor_shape_manager;
let seat = &self.seats[index];
if manager.is_null() || seat.pointer.is_null() || !seat.cursor_shape.is_null() {
return;
}
let pointer = seat.pointer;
let device = unsafe { wp_cursor_shape_manager_v1::get_pointer(manager, pointer) };
if device.is_null() {
return;
}
self.conn.watch(device, ObjectKind::Ignored);
self.seats[index].cursor_shape = device;
if let Some(window) = self.seats[index].pointer_focus {
self.apply_cursor(index, window);
}
}
pub(super) fn ensure_cursor_shapes(&mut self) {
if self.conn.cursor_shape_manager.is_null() {
return;
}
for index in 0..self.seats.len() {
self.add_cursor_shape(index);
}
self.conn.flush();
}
pub(super) fn remove_cursor_shape(&mut self, index: usize) {
let device = core::mem::replace(&mut self.seats[index].cursor_shape, core::ptr::null_mut());
unsafe {
self.conn
.release(device, wp_cursor_shape_device_v1::destroy)
};
}
}
#[cfg(test)]
mod tests {
use super::*;
const XML: &str = include_str!("../../wayland-protocols/cursor-shape-v1.xml");
fn entry(name: &str) -> Option<(u32, u32)> {
let start = format!("<entry name=\"{name}\" ");
let line = XML
.lines()
.map(str::trim_start)
.find(|line| line.starts_with(&start))?;
let attribute = |key: &str| {
line.split(&format!(" {key}=\""))
.nth(1)
.and_then(|rest| rest.split('"').next())
.map(|value| value.parse::<u32>().expect("a decimal attribute"))
};
Some((attribute("value")?, attribute("since").unwrap_or(1)))
}
#[test]
fn every_icon_is_the_entry_its_css_keyword_names_at_the_bound_version() {
for &icon in CursorIcon::ALL {
let name = icon.as_css_name().replace('-', "_");
let (value, since) =
entry(&name).unwrap_or_else(|| panic!("{icon:?}: no `{name}` entry in the XML"));
assert_eq!(shape_of(icon), value, "{icon:?} is `{name}`");
assert!(
since <= CURSOR_SHAPE_VERSION,
"{icon:?} is `{name}`, since version {since}, which a manager bound at \
{CURSOR_SHAPE_VERSION} refuses with invalid_shape"
);
}
assert_eq!(entry("dnd_ask"), Some((shape::DND_ASK, 2)));
assert_eq!(entry("no_such_shape"), None);
}
}