winit/platform_impl/linux/x11/util/
cursor.rs

1use std::collections::hash_map::Entry;
2use std::hash::{Hash, Hasher};
3use std::iter;
4use std::sync::Arc;
5
6use x11rb::connection::Connection;
7use x11rb::protocol::render::{self, ConnectionExt as _};
8use x11rb::protocol::xproto;
9
10use super::super::ActiveEventLoop;
11use super::*;
12use crate::error::RequestError;
13use crate::platform_impl::PlatformCustomCursorSource;
14use crate::window::CursorIcon;
15
16impl XConnection {
17    pub fn set_cursor_icon(
18        &self,
19        window: xproto::Window,
20        cursor: Option<CursorIcon>,
21    ) -> Result<(), X11Error> {
22        let cursor = {
23            let mut cache = self.cursor_cache.lock().unwrap_or_else(|e| e.into_inner());
24
25            match cache.entry(cursor) {
26                Entry::Occupied(o) => *o.get(),
27                Entry::Vacant(v) => *v.insert(self.get_cursor(cursor)?),
28            }
29        };
30
31        self.update_cursor(window, cursor)
32    }
33
34    pub(crate) fn set_custom_cursor(
35        &self,
36        window: xproto::Window,
37        cursor: &CustomCursor,
38    ) -> Result<(), X11Error> {
39        self.update_cursor(window, cursor.inner.cursor)
40    }
41
42    /// Create a cursor from an image.
43    fn create_cursor_from_image(
44        &self,
45        width: u16,
46        height: u16,
47        hotspot_x: u16,
48        hotspot_y: u16,
49        image: &[u8],
50    ) -> Result<xproto::Cursor, X11Error> {
51        // Create a pixmap for the default root window.
52        let root = self.default_root().root;
53        let pixmap =
54            xproto::PixmapWrapper::create_pixmap(self.xcb_connection(), 32, root, width, height)?;
55
56        // Create a GC to draw with.
57        let gc = xproto::GcontextWrapper::create_gc(
58            self.xcb_connection(),
59            pixmap.pixmap(),
60            &Default::default(),
61        )?;
62
63        // Draw the data into it.
64        self.xcb_connection()
65            .put_image(
66                xproto::ImageFormat::Z_PIXMAP,
67                pixmap.pixmap(),
68                gc.gcontext(),
69                width,
70                height,
71                0,
72                0,
73                0,
74                32,
75                image,
76            )?
77            .ignore_error();
78        drop(gc);
79
80        // Create the XRender picture.
81        let picture = render::PictureWrapper::create_picture(
82            self.xcb_connection(),
83            pixmap.pixmap(),
84            self.find_argb32_format()?,
85            &Default::default(),
86        )?;
87        drop(pixmap);
88
89        // Create the cursor.
90        let cursor = self.xcb_connection().generate_id()?;
91        self.xcb_connection()
92            .render_create_cursor(cursor, picture.picture(), hotspot_x, hotspot_y)?
93            .check()?;
94
95        Ok(cursor)
96    }
97
98    /// Find the render format that corresponds to ARGB32.
99    fn find_argb32_format(&self) -> Result<render::Pictformat, X11Error> {
100        macro_rules! direct {
101            ($format:expr, $shift_name:ident, $mask_name:ident, $shift:expr) => {{
102                ($format).direct.$shift_name == $shift && ($format).direct.$mask_name == 0xff
103            }};
104        }
105
106        self.render_formats()
107            .formats
108            .iter()
109            .find(|format| {
110                format.type_ == render::PictType::DIRECT
111                    && format.depth == 32
112                    && direct!(format, red_shift, red_mask, 16)
113                    && direct!(format, green_shift, green_mask, 8)
114                    && direct!(format, blue_shift, blue_mask, 0)
115                    && direct!(format, alpha_shift, alpha_mask, 24)
116            })
117            .ok_or(X11Error::NoArgb32Format)
118            .map(|format| format.id)
119    }
120
121    fn create_empty_cursor(&self) -> Result<xproto::Cursor, X11Error> {
122        self.create_cursor_from_image(1, 1, 0, 0, &[0, 0, 0, 0])
123    }
124
125    fn get_cursor(&self, cursor: Option<CursorIcon>) -> Result<xproto::Cursor, X11Error> {
126        let cursor = match cursor {
127            Some(cursor) => cursor,
128            None => return self.create_empty_cursor(),
129        };
130
131        let database = self.database();
132        let handle = x11rb::cursor::Handle::new(
133            self.xcb_connection(),
134            self.default_screen_index(),
135            &database,
136        )?
137        .reply()?;
138
139        let mut last_error = None;
140        for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) {
141            match handle.load_cursor(self.xcb_connection(), name) {
142                Ok(cursor) => return Ok(cursor),
143                Err(err) => last_error = Some(err.into()),
144            }
145        }
146
147        Err(last_error.unwrap())
148    }
149
150    fn update_cursor(
151        &self,
152        window: xproto::Window,
153        cursor: xproto::Cursor,
154    ) -> Result<(), X11Error> {
155        self.xcb_connection()
156            .change_window_attributes(
157                window,
158                &xproto::ChangeWindowAttributesAux::new().cursor(cursor),
159            )?
160            .ignore_error();
161
162        self.xcb_connection().flush()?;
163        Ok(())
164    }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub enum SelectedCursor {
169    Custom(CustomCursor),
170    Named(CursorIcon),
171}
172
173#[derive(Debug, Clone)]
174pub struct CustomCursor {
175    inner: Arc<CustomCursorInner>,
176}
177
178impl Hash for CustomCursor {
179    fn hash<H: Hasher>(&self, state: &mut H) {
180        Arc::as_ptr(&self.inner).hash(state);
181    }
182}
183
184impl PartialEq for CustomCursor {
185    fn eq(&self, other: &Self) -> bool {
186        Arc::ptr_eq(&self.inner, &other.inner)
187    }
188}
189
190impl Eq for CustomCursor {}
191
192impl CustomCursor {
193    pub(crate) fn new(
194        event_loop: &ActiveEventLoop,
195        mut cursor: PlatformCustomCursorSource,
196    ) -> Result<CustomCursor, RequestError> {
197        // Reverse RGBA order to BGRA.
198        cursor.0.rgba.chunks_mut(4).for_each(|chunk| {
199            let chunk: &mut [u8; 4] = chunk.try_into().unwrap();
200            chunk[0..3].reverse();
201
202            // Byteswap if we need to.
203            if event_loop.xconn.needs_endian_swap() {
204                let value = u32::from_ne_bytes(*chunk).swap_bytes();
205                *chunk = value.to_ne_bytes();
206            }
207        });
208
209        let cursor = event_loop
210            .xconn
211            .create_cursor_from_image(
212                cursor.0.width,
213                cursor.0.height,
214                cursor.0.hotspot_x,
215                cursor.0.hotspot_y,
216                &cursor.0.rgba,
217            )
218            .map_err(|err| os_error!(err))?;
219
220        Ok(Self { inner: Arc::new(CustomCursorInner { xconn: event_loop.xconn.clone(), cursor }) })
221    }
222}
223
224#[derive(Debug)]
225struct CustomCursorInner {
226    xconn: Arc<XConnection>,
227    cursor: xproto::Cursor,
228}
229
230impl Drop for CustomCursorInner {
231    fn drop(&mut self) {
232        self.xconn.xcb_connection().free_cursor(self.cursor).map(|r| r.ignore_error()).ok();
233    }
234}
235
236impl Default for SelectedCursor {
237    fn default() -> Self {
238        SelectedCursor::Named(Default::default())
239    }
240}