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