diff --git a/Cargo.toml b/Cargo.toml index 22c5ebef39..993f904ec7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -266,6 +266,7 @@ wayland-protocols-plasma = { version = "0.2.0", features = [ x11-dl = { version = "2.19.1", optional = true } x11rb = { version = "0.13.0", default-features = false, features = [ "allow-unsafe-code", + "cursor", "dl-libxcb", "randr", "resource_manager", diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 83af3409a4..cbc72b2f91 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -44,6 +44,7 @@ changelog entry. - On Web, let events wake up event loop immediately when using `ControlFlow::Poll`. - Bump MSRV from `1.70` to `1.73`. +- On X11, remove our dependency on libXcursor. (#3749) ### Removed diff --git a/src/platform_impl/linux/x11/ffi.rs b/src/platform_impl/linux/x11/ffi.rs index 57bd78e95d..6895f19238 100644 --- a/src/platform_impl/linux/x11/ffi.rs +++ b/src/platform_impl/linux/x11/ffi.rs @@ -1,5 +1,4 @@ pub use x11_dl::error::OpenError; -pub use x11_dl::xcursor::*; pub use x11_dl::xinput2::*; pub use x11_dl::xlib::*; pub use x11_dl::xlib_xcb::*; diff --git a/src/platform_impl/linux/x11/util/cursor.rs b/src/platform_impl/linux/x11/util/cursor.rs index 169748d14d..7ffc2bb298 100644 --- a/src/platform_impl/linux/x11/util/cursor.rs +++ b/src/platform_impl/linux/x11/util/cursor.rs @@ -1,9 +1,10 @@ -use std::ffi::CString; use std::hash::{Hash, Hasher}; +use std::iter; use std::sync::Arc; -use std::{iter, slice}; use x11rb::connection::Connection; +use x11rb::protocol::render::{self, ConnectionExt as _}; +use x11rb::protocol::xproto; use crate::platform_impl::PlatformCustomCursorSource; use crate::window::CursorIcon; @@ -18,7 +19,7 @@ impl XConnection { .lock() .unwrap() .entry(cursor) - .or_insert_with(|| self.get_cursor(cursor)); + .or_insert_with(|| self.get_cursor(cursor).expect("failed to create cursor")); self.update_cursor(window, cursor).expect("Failed to set cursor"); } @@ -27,66 +28,119 @@ impl XConnection { self.update_cursor(window, cursor.inner.cursor).expect("Failed to set cursor"); } - fn create_empty_cursor(&self) -> ffi::Cursor { - let data = 0; - let pixmap = unsafe { - let screen = (self.xlib.XDefaultScreen)(self.display); - let window = (self.xlib.XRootWindow)(self.display, screen); - (self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1) - }; + /// Create a cursor from an image. + fn create_cursor_from_image( + &self, + width: u16, + height: u16, + depth: u8, + hotspot_x: u16, + hotspot_y: u16, + image: &[u8], + ) -> Result { + // Create a pixap for the default root window. + let root = self.default_root().root; + let pixmap = self.xcb_connection().generate_id()?; + self.xcb_connection().create_pixmap(depth, pixmap, root, width, height)?.ignore_error(); + let pixmap_guard = CallOnDrop(|| { + self.xcb_connection().free_pixmap(pixmap).map(|r| r.ignore_error()).ok(); + }); + + // Create a GC to draw with. + let gc = self.xcb_connection().generate_id()?; + self.xcb_connection().create_gc(gc, pixmap, &Default::default())?.ignore_error(); + let gc_guard = CallOnDrop(|| { + self.xcb_connection().free_gc(gc).map(|r| r.ignore_error()).ok(); + }); + + // Draw the data into it. + let format = + if depth == 1 { xproto::ImageFormat::XY_PIXMAP } else { xproto::ImageFormat::Z_PIXMAP }; + self.xcb_connection() + .put_image(format, pixmap, gc, width, height, 0, 0, 0, depth, image)? + .ignore_error(); + drop(gc_guard); - if pixmap == 0 { - panic!("failed to allocate pixmap for cursor"); - } + // Create the XRender picture. + let picture = self.xcb_connection().generate_id()?; + self.xcb_connection() + .render_create_picture(picture, pixmap, self.find_argb32_format(), &Default::default())? + .check()?; + let _picture_guard = CallOnDrop(|| { + self.xcb_connection().render_free_picture(picture).map(|r| r.ignore_error()).ok(); + }); + drop(pixmap_guard); + + // Create the cursor. + let cursor = self.xcb_connection().generate_id()?; + self.xcb_connection() + .render_create_cursor(cursor, picture, hotspot_x, hotspot_y)? + .check()?; - unsafe { - // We don't care about this color, since it only fills bytes - // in the pixmap which are not 0 in the mask. - let mut dummy_color = MaybeUninit::uninit(); - let cursor = (self.xlib.XCreatePixmapCursor)( - self.display, - pixmap, - pixmap, - dummy_color.as_mut_ptr(), - dummy_color.as_mut_ptr(), - 0, - 0, - ); - (self.xlib.XFreePixmap)(self.display, pixmap); - - cursor + Ok(cursor) + } + + /// Find the render format that corresponds to ARGB32. + fn find_argb32_format(&self) -> render::Pictformat { + macro_rules! direct { + ($format:expr, $shift_name:ident, $mask_name:ident, $shift:expr) => {{ + ($format).direct.$shift_name == $shift && ($format).direct.$mask_name == 0xff + }}; } + + self.render_formats() + .formats + .iter() + .find(|format| { + format.type_ == render::PictType::DIRECT + && format.depth == 32 + && direct!(format, red_shift, red_mask, 16) + && direct!(format, green_shift, green_mask, 8) + && direct!(format, blue_shift, blue_mask, 0) + && direct!(format, alpha_shift, alpha_mask, 24) + }) + .expect("unable to find ARGB32 xrender format") + .id + } + + fn create_empty_cursor(&self) -> Result { + self.create_cursor_from_image(1, 1, 32, 0, 0, &[0, 0, 0, 0]) } - fn get_cursor(&self, cursor: Option) -> ffi::Cursor { + fn get_cursor(&self, cursor: Option) -> Result { let cursor = match cursor { Some(cursor) => cursor, None => return self.create_empty_cursor(), }; - let mut xcursor = 0; + let database = self.database(); + let handle = x11rb::cursor::Handle::new( + self.xcb_connection(), + self.default_screen_index(), + &database, + )? + .reply()?; + + let mut last_error = None; for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) { - let name = CString::new(name).unwrap(); - xcursor = unsafe { - (self.xcursor.XcursorLibraryLoadCursor)( - self.display, - name.as_ptr() as *const c_char, - ) - }; - - if xcursor != 0 { - break; + match handle.load_cursor(self.xcb_connection(), name) { + Ok(cursor) => return Ok(cursor), + Err(err) => last_error = Some(err.into()), } } - xcursor + Err(last_error.unwrap()) } - fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> { + fn update_cursor( + &self, + window: xproto::Window, + cursor: xproto::Cursor, + ) -> Result<(), X11Error> { self.xcb_connection() .change_window_attributes( window, - &xproto::ChangeWindowAttributesAux::new().cursor(cursor as xproto::Cursor), + &xproto::ChangeWindowAttributesAux::new().cursor(cursor), )? .ignore_error(); @@ -123,47 +177,38 @@ impl Eq for CustomCursor {} impl CustomCursor { pub(crate) fn new( event_loop: &ActiveEventLoop, - cursor: PlatformCustomCursorSource, + mut cursor: PlatformCustomCursorSource, ) -> CustomCursor { - unsafe { - let ximage = (event_loop.xconn.xcursor.XcursorImageCreate)( - cursor.0.width as i32, - cursor.0.height as i32, - ); - if ximage.is_null() { - panic!("failed to allocate cursor image"); - } - (*ximage).xhot = cursor.0.hotspot_x as u32; - (*ximage).yhot = cursor.0.hotspot_y as u32; - (*ximage).delay = 0; - - let dst = slice::from_raw_parts_mut((*ximage).pixels, cursor.0.rgba.len() / 4); - for (dst, chunk) in dst.iter_mut().zip(cursor.0.rgba.chunks_exact(4)) { - *dst = (chunk[0] as u32) << 16 - | (chunk[1] as u32) << 8 - | (chunk[2] as u32) - | (chunk[3] as u32) << 24; - } - - let cursor = - (event_loop.xconn.xcursor.XcursorImageLoadCursor)(event_loop.xconn.display, ximage); - (event_loop.xconn.xcursor.XcursorImageDestroy)(ximage); - Self { inner: Arc::new(CustomCursorInner { xconn: event_loop.xconn.clone(), cursor }) } - } + // Reverse RGBA order to BGRA. + cursor.0.rgba.chunks_mut(4).for_each(|chunk| { + chunk[0..3].reverse(); + }); + + let cursor = event_loop + .xconn + .create_cursor_from_image( + cursor.0.width, + cursor.0.height, + 32, + cursor.0.hotspot_x, + cursor.0.hotspot_y, + &cursor.0.rgba, + ) + .expect("failed to create a custom cursor"); + + Self { inner: Arc::new(CustomCursorInner { xconn: event_loop.xconn.clone(), cursor }) } } } #[derive(Debug)] struct CustomCursorInner { xconn: Arc, - cursor: ffi::Cursor, + cursor: xproto::Cursor, } impl Drop for CustomCursorInner { fn drop(&mut self) { - unsafe { - (self.xconn.xlib.XFreeCursor)(self.xconn.display, self.cursor); - } + self.xconn.xcb_connection().free_cursor(self.cursor).map(|r| r.ignore_error()).ok(); } } @@ -172,3 +217,10 @@ impl Default for SelectedCursor { SelectedCursor::Named(Default::default()) } } + +struct CallOnDrop(F); +impl Drop for CallOnDrop { + fn drop(&mut self) { + (self.0)(); + } +} diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index 0a56f290a4..bf1b4cbb78 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -11,6 +11,7 @@ use super::ffi; use super::monitor::MonitorHandle; use x11rb::connection::Connection; use x11rb::protocol::randr::ConnectionExt as _; +use x11rb::protocol::render; use x11rb::protocol::xproto::{self, ConnectionExt}; use x11rb::resource_manager; use x11rb::xcb_ffi::XCBConnection; @@ -18,7 +19,6 @@ use x11rb::xcb_ffi::XCBConnection; /// A connection to an X server. pub struct XConnection { pub xlib: ffi::Xlib, - pub xcursor: ffi::Xcursor, // TODO(notgull): I'd like to remove this, but apparently Xlib and Xinput2 are tied together // for some reason. @@ -55,8 +55,11 @@ pub struct XConnection { /// Atom for the XSettings screen. xsettings_screen: Option, + /// XRender format information. + render_formats: render::QueryPictFormatsReply, + pub latest_error: Mutex>, - pub cursor_cache: Mutex, ffi::Cursor>>, + pub cursor_cache: Mutex, xproto::Cursor>>, } unsafe impl Send for XConnection {} @@ -69,7 +72,6 @@ impl XConnection { pub fn new(error_handler: XErrorHandler) -> Result { // opening the libraries let xlib = ffi::Xlib::open()?; - let xcursor = ffi::Xcursor::open()?; let xlib_xcb = ffi::Xlib_xcb::open()?; let xinput2 = ffi::XInput2::open()?; @@ -118,15 +120,22 @@ impl XConnection { tracing::warn!("error setting XSETTINGS; Xft options won't reload automatically") } + // Start getting the XRender formats. + let formats_cookie = render::query_pict_formats(&xcb) + .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; + // Fetch atoms. let atoms = Atoms::new(&xcb) .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))? .reply() .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; + // Finish getting everything else. + let formats = + formats_cookie.reply().map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; + Ok(XConnection { xlib, - xcursor, xinput2, display, xcb: Some(xcb), @@ -138,6 +147,7 @@ impl XConnection { database: RwLock::new(database), cursor_cache: Default::default(), randr_version: (randr_version.major_version, randr_version.minor_version), + render_formats: formats, xsettings_screen, }) } @@ -257,6 +267,12 @@ impl XConnection { pub fn xsettings_screen(&self) -> Option { self.xsettings_screen } + + /// Get the data containing our rendering formats. + #[inline] + pub fn render_formats(&self) -> &render::QueryPictFormatsReply { + &self.render_formats + } } impl fmt::Debug for XConnection {