x11rb/cursor/
mod.rs

1//! Utility functions for working with X11 cursors
2//!
3//! The code in this module is only available when the `cursor` feature of the library is enabled.
4
5use 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/// The level of cursor support of the X11 server
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20enum RenderSupport {
21    /// Render extension not available
22    None,
23
24    /// Static cursor support (CreateCursor added in RENDER 0.5)
25    StaticCursor,
26
27    /// Animated cursor support (CreateAnimCursor added in RENDER 0.8)
28    AnimatedCursor,
29}
30
31/// A cookie for creating a `Handle`
32#[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    /// Get the handle from the replies from the X11 server
45    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    /// Get the handle from the replies from the X11 server
63    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/// A handle necessary for loading cursors
124#[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    /// Create a new cursor handle for creating cursors on the given screen.
136    ///
137    /// The `resource_database` is used to look up settings like the current cursor theme and the
138    /// cursor size to use.
139    ///
140    /// This function returns a cookie that can be used to later get the actual handle.
141    ///
142    /// If you want this function not to block, you should prefetch the RENDER extension's data on
143    /// the connection.
144    #[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    /// Loads the specified cursor, either from the cursor theme or by falling back to the X11
170    /// "cursor" font.
171    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        // foreground color
206        0,
207        0,
208        0,
209        // background color
210        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    // Get a pixmap of the right size and a gc for it
226    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    // Sigh. We need the pixel data as a bunch of bytes.
244    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    // Find the right cursor, load it directly if it is a core cursor
281    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    // We have to load a file and use RENDER to create a cursor
290    if handle.render_support == RenderSupport::None {
291        return Ok(NONE);
292    }
293
294    // Load the cursor from the file
295    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    // No animated cursor support? Only use the first image
301    if handle.render_support == RenderSupport::StaticCursor {
302        images = &images[0..1];
303    }
304
305    // Now transfer the cursors to the X11 server
306    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}