1use crate::connection::Connection;
6use crate::cookie::Cookie as X11Cookie;
7use crate::errors::{ConnectionError, ReplyOrIdError};
8use crate::protocol::render::{self, Pictformat};
9use crate::protocol::xproto::{self, FontWrapper, Window};
10use crate::resource_manager::Database;
11use crate::NONE;
12use xcursor::parser::Image;
13
14mod find_cursor;
15mod parse_cursor;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19enum RenderSupport {
20 None,
22
23 StaticCursor,
25
26 AnimatedCursor,
28}
29
30#[derive(Debug)]
32pub struct Cookie<'a, 'b, C: Connection> {
33 screen: &'a xproto::Screen,
34 resource_database: &'b Database,
35 render_info: Option<(
36 X11Cookie<'a, C, render::QueryVersionReply>,
37 X11Cookie<'a, C, render::QueryPictFormatsReply>,
38 )>,
39}
40
41impl<C: Connection> Cookie<'_, '_, C> {
42 pub fn reply(self) -> Result<Handle, ReplyOrIdError> {
44 let mut render_version = (0, 0);
45 let mut picture_format = NONE;
46 if let Some((version, formats)) = self.render_info {
47 let version = version.reply()?;
48 render_version = (version.major_version, version.minor_version);
49 picture_format = find_format(&formats.reply()?);
50 }
51 Self::from_replies(
52 self.screen,
53 self.resource_database,
54 render_version,
55 picture_format,
56 )
57 }
58
59 pub fn reply_unchecked(self) -> Result<Option<Handle>, ReplyOrIdError> {
61 let mut render_version = (0, 0);
62 let mut picture_format = NONE;
63 if let Some((version, formats)) = self.render_info {
64 match (version.reply_unchecked()?, formats.reply_unchecked()?) {
65 (Some(version), Some(formats)) => {
66 render_version = (version.major_version, version.minor_version);
67 picture_format = find_format(&formats);
68 }
69 _ => return Ok(None),
70 }
71 }
72 Ok(Some(Self::from_replies(
73 self.screen,
74 self.resource_database,
75 render_version,
76 picture_format,
77 )?))
78 }
79
80 fn from_replies(
81 screen: &xproto::Screen,
82 resource_database: &Database,
83 render_version: (u32, u32),
84 picture_format: Pictformat,
85 ) -> Result<Handle, ReplyOrIdError> {
86 let render_support = if render_version.0 >= 1 || render_version.1 >= 8 {
87 RenderSupport::AnimatedCursor
88 } else if render_version.0 >= 1 || render_version.1 >= 5 {
89 RenderSupport::StaticCursor
90 } else {
91 RenderSupport::None
92 };
93 let theme = std::env::var("XCURSOR_THEME").ok().or_else(|| {
94 resource_database
95 .get_string("Xcursor.theme", "")
96 .map(|theme| theme.to_string())
97 });
98 let cursor_size = match resource_database.get_value("Xcursor.size", "") {
99 Ok(Some(value)) => value,
100 _ => 0,
101 };
102 let xft_dpi = match resource_database.get_value("Xft.dpi", "") {
103 Ok(Some(value)) => value,
104 _ => 0,
105 };
106 let cursor_size = get_cursor_size(cursor_size, xft_dpi, screen);
107 Ok(Handle {
108 root: screen.root,
109 picture_format,
110 render_support,
111 theme,
112 cursor_size,
113 })
114 }
115}
116
117#[derive(Debug)]
119pub struct Handle {
120 root: Window,
121 picture_format: Pictformat,
122 render_support: RenderSupport,
123 theme: Option<String>,
124 cursor_size: u32,
125}
126
127impl Handle {
128 #[allow(clippy::new_ret_no_self)]
138 pub fn new<'a, 'b, C: Connection>(
139 conn: &'a C,
140 screen: usize,
141 resource_database: &'b Database,
142 ) -> Result<Cookie<'a, 'b, C>, ConnectionError> {
143 let screen = &conn.setup().roots[screen];
144 let render_info = if conn
145 .extension_information(render::X11_EXTENSION_NAME)?
146 .is_some()
147 {
148 let render_version = render::query_version(conn, 0, 8)?;
149 let render_pict_format = render::query_pict_formats(conn)?;
150 Some((render_version, render_pict_format))
151 } else {
152 None
153 };
154 Ok(Cookie {
155 screen,
156 resource_database,
157 render_info,
158 })
159 }
160
161 pub fn load_cursor<C>(&self, conn: &C, name: &str) -> Result<xproto::Cursor, ReplyOrIdError>
164 where
165 C: Connection,
166 {
167 load_cursor(conn, self, name)
168 }
169}
170
171fn open_cursor(theme: &Option<String>, name: &str) -> Option<find_cursor::Cursor> {
172 theme
173 .as_ref()
174 .and_then(|theme| find_cursor::find_cursor(theme, name))
175 .or_else(|| find_cursor::find_cursor("default", name))
176}
177
178fn create_core_cursor<C: Connection>(
179 conn: &C,
180 cursor: u16,
181) -> Result<xproto::Cursor, ReplyOrIdError> {
182 let result = conn.generate_id()?;
183 let cursor_font = FontWrapper::open_font(conn, b"cursor")?;
184 let _ = xproto::create_glyph_cursor(
185 conn,
186 result,
187 cursor_font.font(),
188 cursor_font.font(),
189 cursor,
190 cursor + 1,
191 0,
193 0,
194 0,
195 u16::MAX,
197 u16::MAX,
198 u16::MAX,
199 )?;
200 Ok(result)
201}
202
203fn create_render_cursor<C: Connection>(
204 conn: &C,
205 handle: &Handle,
206 image: &Image,
207 storage: &mut Option<(xproto::Pixmap, xproto::Gcontext, u16, u16)>,
208) -> Result<render::Animcursorelt, ReplyOrIdError> {
209 let to_u16 = |input: u32| {
210 u16::try_from(input).expect(
211 "xcursor-rs has a 16 bit limit on cursor size, but some number does not fit into u16?!",
212 )
213 };
214
215 let (width, height) = (to_u16(image.width), to_u16(image.height));
216
217 let (pixmap, gc) = match *storage {
219 Some((pixmap, gc, w, h)) if (w, h) == (width, height) => (pixmap, gc),
220 _ => {
221 let (pixmap, gc) = if let Some((pixmap, gc, _, _)) = storage {
222 let _ = xproto::free_gc(conn, *gc)?;
223 let _ = xproto::free_pixmap(conn, *pixmap)?;
224 (*pixmap, *gc)
225 } else {
226 (conn.generate_id()?, conn.generate_id()?)
227 };
228 let _ = xproto::create_pixmap(conn, 32, pixmap, handle.root, width, height)?;
229 let _ = xproto::create_gc(conn, gc, pixmap, &Default::default())?;
230
231 *storage = Some((pixmap, gc, width, height));
232 (pixmap, gc)
233 }
234 };
235
236 let _ = xproto::put_image(
237 conn,
238 xproto::ImageFormat::Z_PIXMAP,
239 pixmap,
240 gc,
241 width,
242 height,
243 0,
244 0,
245 0,
246 32,
247 &image.pixels_rgba,
248 )?;
249
250 let picture = render::PictureWrapper::create_picture(
251 conn,
252 pixmap,
253 handle.picture_format,
254 &Default::default(),
255 )?;
256 let cursor = conn.generate_id()?;
257 let _ = render::create_cursor(
258 conn,
259 cursor,
260 picture.picture(),
261 to_u16(image.xhot),
262 to_u16(image.yhot),
263 )?;
264
265 Ok(render::Animcursorelt {
266 cursor,
267 delay: image.delay,
268 })
269}
270
271fn load_cursor<C: Connection>(
272 conn: &C,
273 handle: &Handle,
274 name: &str,
275) -> Result<xproto::Cursor, ReplyOrIdError> {
276 let cursor_file = match open_cursor(&handle.theme, name) {
278 None => return Ok(NONE),
279 Some(find_cursor::Cursor::CoreChar(c)) => return create_core_cursor(conn, c),
280 Some(find_cursor::Cursor::File(f)) => f,
281 };
282
283 if handle.render_support == RenderSupport::None {
285 return Ok(NONE);
286 }
287
288 use std::io::BufReader;
290 let images = parse_cursor::parse_cursor(&mut BufReader::new(cursor_file), handle.cursor_size)
291 .or(Err(crate::errors::ParseError::InvalidValue))?;
292 let mut images = &images[..];
293
294 if handle.render_support == RenderSupport::StaticCursor {
296 images = &images[0..1];
297 }
298
299 let mut storage = None;
301 let cursors = images
302 .iter()
303 .map(|image| create_render_cursor(conn, handle, image, &mut storage))
304 .collect::<Result<Vec<_>, _>>()?;
305 if let Some((pixmap, gc, _, _)) = storage {
306 let _ = xproto::free_gc(conn, gc)?;
307 let _ = xproto::free_pixmap(conn, pixmap)?;
308 }
309
310 if cursors.len() == 1 {
311 Ok(cursors[0].cursor)
312 } else {
313 let result = conn.generate_id()?;
314 let _ = render::create_anim_cursor(conn, result, &cursors)?;
315 for elem in cursors {
316 let _ = xproto::free_cursor(conn, elem.cursor)?;
317 }
318 Ok(result)
319 }
320}
321
322fn find_format(reply: &render::QueryPictFormatsReply) -> Pictformat {
323 reply
324 .formats
325 .iter()
326 .filter(|format| {
327 format.type_ == render::PictType::DIRECT
328 && format.depth == 32
329 && format.direct.red_shift == 16
330 && format.direct.red_mask == 0xff
331 && format.direct.green_shift == 8
332 && format.direct.green_mask == 0xff
333 && format.direct.blue_shift == 0
334 && format.direct.blue_mask == 0xff
335 && format.direct.alpha_shift == 24
336 && format.direct.alpha_mask == 0xff
337 })
338 .map(|format| format.id)
339 .next()
340 .expect("The X11 server is missing the RENDER ARGB_32 standard format!")
341}
342
343fn get_cursor_size(rm_cursor_size: u32, rm_xft_dpi: u32, screen: &xproto::Screen) -> u32 {
344 if let Some(size) = std::env::var("XCURSOR_SIZE")
345 .ok()
346 .and_then(|s| s.parse().ok())
347 {
348 return size;
349 }
350 if rm_cursor_size > 0 {
351 return rm_cursor_size;
352 }
353 if rm_xft_dpi > 0 {
354 return rm_xft_dpi * 16 / 72;
355 }
356 u32::from(screen.height_in_pixels.min(screen.width_in_pixels) / 48)
357}