1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::Read;
use anyhow::{anyhow, Context};
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::texture::TextureBuffer;
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
use smithay::utils::{Physical, Point, Transform};
use xcursor::parser::{parse_xcursor, Image};
use xcursor::CursorTheme;
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
pub struct Cursor {
images: Vec<Image>,
size: i32,
cache: HashMap<i32, (TextureBuffer<GlesTexture>, Point<i32, Physical>)>,
}
impl Cursor {
/// Load the said theme as well as set the `XCURSOR_THEME` and `XCURSOR_SIZE`
/// env variables.
pub fn load(theme: &str, size: u8) -> Self {
env::set_var("XCURSOR_THEME", theme);
env::set_var("XCURSOR_SIZE", size.to_string());
let images = match load_xcursor(theme) {
Ok(images) => images,
Err(err) => {
warn!("error loading xcursor default cursor: {err:?}");
vec![Image {
size: 32,
width: 64,
height: 64,
xhot: 1,
yhot: 1,
delay: 1,
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
pixels_argb: vec![],
}]
}
};
Self {
images,
size: size as i32,
cache: HashMap::new(),
}
}
pub fn get(
&mut self,
renderer: &mut GlesRenderer,
scale: i32,
) -> (TextureBuffer<GlesTexture>, Point<i32, Physical>) {
self.cache
.entry(scale)
.or_insert_with_key(|scale| {
let _span = tracy_client::span!("create cursor texture");
let size = self.size * scale;
let nearest_image = self
.images
.iter()
.min_by_key(|image| (size - image.size as i32).abs())
.unwrap();
let frame = self
.images
.iter()
.find(move |image| {
image.width == nearest_image.width && image.height == nearest_image.height
})
.unwrap();
let texture = TextureBuffer::from_memory(
renderer,
&frame.pixels_rgba,
Fourcc::Abgr8888,
(frame.width as i32, frame.height as i32),
false,
*scale,
Transform::Normal,
None,
)
.unwrap();
(texture, (frame.xhot as i32, frame.yhot as i32).into())
})
.clone()
}
pub fn get_cached_hotspot(&self, scale: i32) -> Option<Point<i32, Physical>> {
self.cache.get(&scale).map(|(_, hotspot)| *hotspot)
}
}
fn load_xcursor(theme: &str) -> anyhow::Result<Vec<Image>> {
let _span = tracy_client::span!();
let theme = CursorTheme::load(theme);
let path = theme
.load_icon("default")
.ok_or_else(|| anyhow!("no default icon"))?;
let mut file = File::open(path).context("error opening cursor icon file")?;
let mut buf = vec![];
file.read_to_end(&mut buf)
.context("error reading cursor icon file")?;
let images = parse_xcursor(&buf).context("error parsing cursor icon file")?;
Ok(images)
}
|