2022-02-01 13:59:39 +01:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
|
2022-08-05 16:28:05 +02:00
|
|
|
use crate::utils::prelude::*;
|
2022-02-01 13:59:39 +01:00
|
|
|
use smithay::{
|
2022-09-28 12:01:29 +02:00
|
|
|
backend::renderer::{
|
|
|
|
|
element::{
|
|
|
|
|
surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement},
|
|
|
|
|
texture::{TextureBuffer, TextureRenderElement},
|
|
|
|
|
},
|
|
|
|
|
ImportAll, ImportMem, Renderer,
|
|
|
|
|
},
|
2022-08-31 13:01:23 +02:00
|
|
|
input::{
|
|
|
|
|
pointer::{CursorImageAttributes, CursorImageStatus},
|
|
|
|
|
Seat,
|
|
|
|
|
},
|
2022-02-01 13:59:39 +01:00
|
|
|
reexports::wayland_server::protocol::wl_surface,
|
2022-09-28 12:01:29 +02:00
|
|
|
render_elements,
|
|
|
|
|
utils::{IsAlive, Logical, Point, Scale, Transform},
|
2022-08-31 13:01:23 +02:00
|
|
|
wayland::compositor::{get_role, with_states},
|
2022-02-01 13:59:39 +01:00
|
|
|
};
|
2022-03-16 20:05:24 +01:00
|
|
|
use std::{
|
|
|
|
|
any::{Any, TypeId},
|
|
|
|
|
cell::RefCell,
|
|
|
|
|
collections::HashMap,
|
|
|
|
|
io::Read,
|
|
|
|
|
sync::Mutex,
|
|
|
|
|
};
|
2022-02-01 13:59:39 +01:00
|
|
|
use xcursor::{
|
|
|
|
|
parser::{parse_xcursor, Image},
|
|
|
|
|
CursorTheme,
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-04 21:04:17 +01:00
|
|
|
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/cursor.rgba");
|
2022-02-01 13:59:39 +01:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct Cursor {
|
|
|
|
|
icons: Vec<Image>,
|
|
|
|
|
size: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Cursor {
|
|
|
|
|
pub fn load() -> Cursor {
|
|
|
|
|
let name = std::env::var("XCURSOR_THEME")
|
|
|
|
|
.ok()
|
|
|
|
|
.unwrap_or_else(|| "default".into());
|
|
|
|
|
let size = std::env::var("XCURSOR_SIZE")
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|s| s.parse().ok())
|
|
|
|
|
.unwrap_or(24);
|
|
|
|
|
|
|
|
|
|
let theme = CursorTheme::load(&name);
|
|
|
|
|
let icons = load_icon(&theme)
|
2022-02-04 21:04:17 +01:00
|
|
|
.map_err(|err| {
|
|
|
|
|
slog_scope::warn!("Unable to load xcursor: {}, using fallback cursor", err)
|
|
|
|
|
})
|
2022-02-01 13:59:39 +01:00
|
|
|
.unwrap_or_else(|_| {
|
|
|
|
|
vec![Image {
|
|
|
|
|
size: 32,
|
|
|
|
|
width: 64,
|
|
|
|
|
height: 64,
|
|
|
|
|
xhot: 1,
|
|
|
|
|
yhot: 1,
|
|
|
|
|
delay: 1,
|
|
|
|
|
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
|
|
|
|
|
pixels_argb: vec![], //unused
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Cursor { icons, size }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_image(&self, scale: u32, millis: u32) -> Image {
|
|
|
|
|
let size = self.size * scale;
|
|
|
|
|
frame(millis, size, &self.icons)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Cursor {
|
2022-02-04 21:04:17 +01:00
|
|
|
fn default() -> Cursor {
|
|
|
|
|
Cursor::load()
|
|
|
|
|
}
|
2022-02-01 13:59:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
|
|
|
|
|
// Follow the nominal size of the cursor to choose the nearest
|
|
|
|
|
let nearest_image = images
|
|
|
|
|
.iter()
|
|
|
|
|
.min_by_key(|image| (size as i32 - image.size as i32).abs())
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2022-02-04 21:04:17 +01:00
|
|
|
images.iter().filter(move |image| {
|
|
|
|
|
image.width == nearest_image.width && image.height == nearest_image.height
|
|
|
|
|
})
|
2022-02-01 13:59:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
|
|
|
|
|
let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay);
|
|
|
|
|
millis %= total;
|
|
|
|
|
|
|
|
|
|
for img in nearest_images(size, images) {
|
|
|
|
|
if millis < img.delay {
|
|
|
|
|
return img.clone();
|
|
|
|
|
}
|
|
|
|
|
millis -= img.delay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unreachable!()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
|
enum Error {
|
|
|
|
|
#[error("Theme has no default cursor")]
|
|
|
|
|
NoDefaultCursor,
|
|
|
|
|
#[error("Error opening xcursor file: {0}")]
|
|
|
|
|
File(#[from] std::io::Error),
|
|
|
|
|
#[error("Failed to parse XCursor file")]
|
|
|
|
|
Parse,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load_icon(theme: &CursorTheme) -> Result<Vec<Image>, Error> {
|
|
|
|
|
let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?;
|
|
|
|
|
let mut cursor_file = std::fs::File::open(&icon_path)?;
|
|
|
|
|
let mut cursor_data = Vec::new();
|
|
|
|
|
cursor_file.read_to_end(&mut cursor_data)?;
|
|
|
|
|
parse_xcursor(&cursor_data).ok_or(Error::Parse)
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-28 12:01:29 +02:00
|
|
|
render_elements! {
|
|
|
|
|
pub CursorRenderElement<R> where R: ImportAll;
|
|
|
|
|
Static=TextureRenderElement<<R as Renderer>::TextureId>,
|
|
|
|
|
Surface=WaylandSurfaceRenderElement,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn draw_surface_cursor<R: Renderer + ImportAll>(
|
|
|
|
|
surface: &wl_surface::WlSurface,
|
2022-02-01 13:59:39 +01:00
|
|
|
location: impl Into<Point<i32, Logical>>,
|
2022-09-28 12:01:29 +02:00
|
|
|
scale: impl Into<Scale<f64>>,
|
|
|
|
|
) -> Vec<CursorRenderElement<R>>
|
2022-02-01 13:59:39 +01:00
|
|
|
where
|
|
|
|
|
{
|
|
|
|
|
let mut position = location.into();
|
2022-09-28 12:01:29 +02:00
|
|
|
let scale = scale.into();
|
2022-07-04 15:29:31 +02:00
|
|
|
let h = with_states(&surface, |states| {
|
|
|
|
|
states
|
|
|
|
|
.data_map
|
|
|
|
|
.get::<Mutex<CursorImageAttributes>>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.hotspot
|
|
|
|
|
});
|
|
|
|
|
position -= h;
|
2022-09-28 12:01:29 +02:00
|
|
|
|
|
|
|
|
render_elements_from_surface_tree(surface, position.to_physical_precise_round(scale), scale)
|
2022-02-01 13:59:39 +01:00
|
|
|
}
|
|
|
|
|
|
2022-09-28 12:01:29 +02:00
|
|
|
pub fn draw_dnd_icon<R: Renderer + ImportAll>(
|
|
|
|
|
surface: &wl_surface::WlSurface,
|
2022-02-01 13:59:39 +01:00
|
|
|
location: impl Into<Point<i32, Logical>>,
|
2022-09-28 12:01:29 +02:00
|
|
|
scale: impl Into<Scale<f64>>,
|
|
|
|
|
) -> Vec<CursorRenderElement<R>> {
|
2022-02-01 13:59:39 +01:00
|
|
|
if get_role(&surface) != Some("dnd_icon") {
|
2022-02-04 21:04:17 +01:00
|
|
|
slog_scope::warn!(
|
|
|
|
|
"Trying to display as a dnd icon a surface that does not have the DndIcon role."
|
|
|
|
|
);
|
2022-02-01 13:59:39 +01:00
|
|
|
}
|
2022-09-28 12:01:29 +02:00
|
|
|
let scale = scale.into();
|
|
|
|
|
render_elements_from_surface_tree(
|
2022-02-01 13:59:39 +01:00
|
|
|
surface,
|
2022-09-28 12:01:29 +02:00
|
|
|
location.into().to_physical_precise_round(scale),
|
|
|
|
|
scale,
|
|
|
|
|
)
|
2022-02-01 13:59:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct CursorState {
|
|
|
|
|
cursor: Cursor,
|
|
|
|
|
current_image: RefCell<Option<Image>>,
|
2022-03-16 20:05:24 +01:00
|
|
|
image_cache: RefCell<HashMap<(TypeId, usize), Vec<(Image, Box<dyn Any + 'static>)>>>,
|
2022-02-01 13:59:39 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-16 20:05:24 +01:00
|
|
|
impl Default for CursorState {
|
|
|
|
|
fn default() -> CursorState {
|
|
|
|
|
CursorState {
|
|
|
|
|
cursor: Cursor::default(),
|
|
|
|
|
current_image: RefCell::new(None),
|
|
|
|
|
image_cache: RefCell::new(HashMap::new()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-01 13:59:39 +01:00
|
|
|
|
2022-09-28 12:01:29 +02:00
|
|
|
pub fn draw_cursor<R>(
|
2022-03-16 20:05:24 +01:00
|
|
|
renderer: &mut R,
|
2022-07-04 15:29:31 +02:00
|
|
|
seat: &Seat<State>,
|
|
|
|
|
location: Point<f64, Logical>,
|
2022-09-28 12:01:29 +02:00
|
|
|
scale: Scale<f64>,
|
2022-02-01 13:59:39 +01:00
|
|
|
start_time: &std::time::Instant,
|
|
|
|
|
draw_default: bool,
|
2022-09-28 12:01:29 +02:00
|
|
|
) -> Vec<CursorRenderElement<R>>
|
2022-03-16 20:05:24 +01:00
|
|
|
where
|
|
|
|
|
R: Renderer + ImportAll + ImportMem,
|
|
|
|
|
<R as Renderer>::TextureId: Clone + 'static,
|
|
|
|
|
{
|
2022-02-01 13:59:39 +01:00
|
|
|
// draw the cursor as relevant
|
|
|
|
|
{
|
|
|
|
|
// reset the cursor if the surface is no longer alive
|
|
|
|
|
let cursor_status = seat
|
|
|
|
|
.user_data()
|
|
|
|
|
.get::<RefCell<CursorImageStatus>>()
|
|
|
|
|
.map(|cell| {
|
|
|
|
|
let mut cursor_status = cell.borrow_mut();
|
2022-08-31 13:01:23 +02:00
|
|
|
if let CursorImageStatus::Surface(ref surface) = *cursor_status {
|
2022-07-04 15:29:31 +02:00
|
|
|
if !surface.alive() {
|
2022-02-01 13:59:39 +01:00
|
|
|
*cursor_status = CursorImageStatus::Default;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cursor_status.clone()
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or(CursorImageStatus::Default);
|
|
|
|
|
|
2022-09-28 12:01:29 +02:00
|
|
|
if let CursorImageStatus::Surface(ref wl_surface) = cursor_status {
|
|
|
|
|
return draw_surface_cursor(wl_surface, location.to_i32_round(), scale);
|
2022-02-01 13:59:39 +01:00
|
|
|
} else if draw_default {
|
2022-09-28 12:01:29 +02:00
|
|
|
let integer_scale = scale.x.max(scale.y).ceil() as u32;
|
|
|
|
|
|
2022-02-01 13:59:39 +01:00
|
|
|
let seat_userdata = seat.user_data();
|
|
|
|
|
seat_userdata.insert_if_missing(CursorState::default);
|
|
|
|
|
let state = seat_userdata.get::<CursorState>().unwrap();
|
2022-02-04 21:04:17 +01:00
|
|
|
let frame = state
|
|
|
|
|
.cursor
|
2022-09-28 12:01:29 +02:00
|
|
|
.get_image(integer_scale, start_time.elapsed().as_millis() as u32);
|
2022-02-01 13:59:39 +01:00
|
|
|
|
2022-03-16 20:05:24 +01:00
|
|
|
let mut cache = state.image_cache.borrow_mut();
|
|
|
|
|
let pointer_images = cache
|
2022-09-28 12:01:29 +02:00
|
|
|
.entry((
|
|
|
|
|
TypeId::of::<TextureBuffer<<R as Renderer>::TextureId>>(),
|
|
|
|
|
renderer.id(),
|
|
|
|
|
))
|
2022-03-16 20:05:24 +01:00
|
|
|
.or_default();
|
2022-09-28 12:01:29 +02:00
|
|
|
|
|
|
|
|
let maybe_image = pointer_images
|
2022-02-01 13:59:39 +01:00
|
|
|
.iter()
|
|
|
|
|
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None })
|
2022-03-16 20:05:24 +01:00
|
|
|
.and_then(|texture| {
|
2022-09-28 12:01:29 +02:00
|
|
|
texture.downcast_ref::<TextureBuffer<<R as Renderer>::TextureId>>()
|
2022-02-01 13:59:39 +01:00
|
|
|
});
|
2022-09-28 12:01:29 +02:00
|
|
|
let pointer_image = match maybe_image {
|
|
|
|
|
Some(image) => image,
|
|
|
|
|
None => {
|
|
|
|
|
let texture = TextureBuffer::from_memory(
|
|
|
|
|
renderer,
|
|
|
|
|
&frame.pixels_rgba,
|
|
|
|
|
(frame.width as i32, frame.height as i32),
|
|
|
|
|
false,
|
|
|
|
|
integer_scale as i32,
|
|
|
|
|
Transform::Normal,
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
.expect("Failed to import cursor bitmap");
|
|
|
|
|
pointer_images.push((frame.clone(), Box::new(texture.clone())));
|
|
|
|
|
pointer_images
|
|
|
|
|
.last()
|
|
|
|
|
.and_then(|(_, i)| {
|
|
|
|
|
i.downcast_ref::<TextureBuffer<<R as Renderer>::TextureId>>()
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-04 16:00:29 +02:00
|
|
|
let hotspot =
|
|
|
|
|
Point::<i32, Logical>::from((frame.xhot as i32, frame.yhot as i32)).to_f64();
|
2022-02-01 13:59:39 +01:00
|
|
|
*state.current_image.borrow_mut() = Some(frame);
|
|
|
|
|
|
2022-09-28 12:01:29 +02:00
|
|
|
return vec![CursorRenderElement::Static(
|
|
|
|
|
TextureRenderElement::from_texture_buffer(
|
|
|
|
|
(location - hotspot).to_physical(scale),
|
|
|
|
|
pointer_image,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
),
|
|
|
|
|
)];
|
2022-02-01 13:59:39 +01:00
|
|
|
} else {
|
2022-09-28 12:01:29 +02:00
|
|
|
Vec::new()
|
2022-02-01 13:59:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|