diff --git a/Cargo.toml b/Cargo.toml index 8f397425..15fc5ff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,15 +23,15 @@ exclude = [".github"] [features] default = [] -opengl = ["uuid", "x11/glx"] +opengl = ["uuid"] [dependencies] keyboard-types = { version = "0.6.1", default-features = false } raw-window-handle = "0.5" [target.'cfg(target_os="linux")'.dependencies] -x11rb = { version = "0.13.0", features = ["cursor", "resource_manager", "allow-unsafe-code"] } -x11 = { version = "2.21", features = ["xlib", "xlib_xcb"] } +x11rb = { version = "0.13.2", features = ["cursor", "resource_manager", "allow-unsafe-code", "dl-libxcb"] } +x11-dl = { version = "2.21" } nix = "0.22.0" [target.'cfg(target_os="windows")'.dependencies] diff --git a/src/gl/x11.rs b/src/gl/x11.rs index 4a4de735..706f0280 100644 --- a/src/gl/x11.rs +++ b/src/gl/x11.rs @@ -1,10 +1,12 @@ +use super::{GlConfig, GlError, Profile}; +use crate::x11::XcbConnection; use std::ffi::{c_void, CString}; use std::os::raw::{c_int, c_ulong}; - -use x11::glx; -use x11::xlib; - -use super::{GlConfig, GlError, Profile}; +use std::rc::Rc; +use x11_dl::error::OpenError; +use x11_dl::glx; +use x11_dl::glx::Glx; +use x11_dl::xlib; mod errors; @@ -16,6 +18,7 @@ pub enum CreationFailedError { MakeCurrentFailed, ContextCreationFailed, X11Error(errors::XLibError), + OpenError(OpenError), } impl From for GlError { @@ -24,6 +27,12 @@ impl From for GlError { } } +impl From for GlError { + fn from(e: OpenError) -> Self { + GlError::CreationFailed(CreationFailedError::OpenError(e)) + } +} + // See https://www.khronos.org/registry/OpenGL/extensions/ARB/GLX_ARB_create_context.txt type GlXCreateContextAttribsARB = unsafe extern "C" fn( @@ -43,14 +52,15 @@ type GlXSwapIntervalEXT = const GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20B2; -fn get_proc_address(symbol: &str) -> *const c_void { +fn get_proc_address(glx: &Glx, symbol: &str) -> *const c_void { let symbol = CString::new(symbol).unwrap(); - unsafe { glx::glXGetProcAddress(symbol.as_ptr() as *const u8).unwrap() as *const c_void } + unsafe { (glx.glXGetProcAddress)(symbol.as_ptr() as *const u8).unwrap() as *const c_void } } pub struct GlContext { + glx: Glx, window: c_ulong, - display: *mut xlib::_XDisplay, + connection: Rc, context: glx::GLXContext, } @@ -78,16 +88,14 @@ impl GlContext { /// /// Use [Self::get_fb_config_and_visual] to create both of these things. pub unsafe fn create( - window: c_ulong, display: *mut xlib::_XDisplay, config: FbConfig, + window: c_ulong, connection: Rc, config: FbConfig, ) -> Result { - if display.is_null() { - return Err(GlError::InvalidWindowHandle); - } + let glx = Glx::open()?; - errors::XErrorHandler::handle(display, |error_handler| { + let context = errors::XErrorHandler::handle(&connection, |error_handler| { #[allow(non_snake_case)] let glXCreateContextAttribsARB: GlXCreateContextAttribsARB = { - let addr = get_proc_address("glXCreateContextAttribsARB"); + let addr = get_proc_address(&glx, "glXCreateContextAttribsARB"); if addr.is_null() { return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed)); } else { @@ -98,7 +106,7 @@ impl GlContext { #[allow(non_snake_case)] let glXSwapIntervalEXT: GlXSwapIntervalEXT = { - let addr = get_proc_address("glXSwapIntervalEXT"); + let addr = get_proc_address(&glx, "glXSwapIntervalEXT"); if addr.is_null() { return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed)); } else { @@ -123,7 +131,7 @@ impl GlContext { ]; let context = glXCreateContextAttribsARB( - display, + connection.dpy, config.fb_config, std::ptr::null_mut(), 1, @@ -136,32 +144,36 @@ impl GlContext { return Err(GlError::CreationFailed(CreationFailedError::ContextCreationFailed)); } - let res = glx::glXMakeCurrent(display, window, context); + let res = (glx.glXMakeCurrent)(connection.dpy, window, context); error_handler.check()?; if res == 0 { return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed)); } - glXSwapIntervalEXT(display, window, config.gl_config.vsync as i32); + glXSwapIntervalEXT(connection.dpy, window, config.gl_config.vsync as i32); error_handler.check()?; - if glx::glXMakeCurrent(display, 0, std::ptr::null_mut()) == 0 { + if (glx.glXMakeCurrent)(connection.dpy, 0, std::ptr::null_mut()) == 0 { error_handler.check()?; return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed)); } - Ok(GlContext { window, display, context }) - }) + Ok(context) + })?; + + Ok(GlContext { glx, window, connection, context }) } /// Find a matching framebuffer config and window visual for the given OpenGL configuration. /// This needs to be passed to [Self::create] along with a handle to a window that was created /// using the visual also returned from this function. - pub unsafe fn get_fb_config_and_visual( - display: *mut xlib::_XDisplay, config: GlConfig, + pub fn get_fb_config_and_visual( + conn: &XcbConnection, config: GlConfig, ) -> Result<(FbConfig, WindowConfig), GlError> { - errors::XErrorHandler::handle(display, |error_handler| { - let screen = xlib::XDefaultScreen(display); + let glx = Glx::open()?; + + errors::XErrorHandler::handle(conn, |error_handler| { + let screen = conn.screen; #[rustfmt::skip] let fb_attribs = [ @@ -183,8 +195,9 @@ impl GlContext { ]; let mut n_configs = 0; - let fb_config = - glx::glXChooseFBConfig(display, screen, fb_attribs.as_ptr(), &mut n_configs); + let fb_config = unsafe { + (glx.glXChooseFBConfig)(conn.dpy, screen, fb_attribs.as_ptr(), &mut n_configs) + }; error_handler.check()?; if n_configs <= 0 || fb_config.is_null() { @@ -193,22 +206,23 @@ impl GlContext { // Now that we have a matching framebuffer config, we need to know which visual matches // thsi config so the window is compatible with the OpenGL context we're about to create - let fb_config = *fb_config; - let visual = glx::glXGetVisualFromFBConfig(display, fb_config); + let fb_config = unsafe { fb_config.read() }; + let visual = unsafe { (glx.glXGetVisualFromFBConfig)(conn.dpy, fb_config) }; if visual.is_null() { return Err(GlError::CreationFailed(CreationFailedError::NoVisual)); } + let visual = unsafe { visual.read() }; Ok(( FbConfig { fb_config, gl_config: config }, - WindowConfig { depth: (*visual).depth as u8, visual: (*visual).visualid as u32 }, + WindowConfig { depth: visual.depth as u8, visual: visual.visualid as u32 }, )) }) } pub unsafe fn make_current(&self) { - errors::XErrorHandler::handle(self.display, |error_handler| { - let res = glx::glXMakeCurrent(self.display, self.window, self.context); + errors::XErrorHandler::handle(&self.connection, |error_handler| { + let res = (self.glx.glXMakeCurrent)(self.connection.dpy, self.window, self.context); error_handler.check().unwrap(); if res == 0 { panic!("make_current failed") @@ -217,8 +231,8 @@ impl GlContext { } pub unsafe fn make_not_current(&self) { - errors::XErrorHandler::handle(self.display, |error_handler| { - let res = glx::glXMakeCurrent(self.display, 0, std::ptr::null_mut()); + errors::XErrorHandler::handle(&self.connection, |error_handler| { + let res = (self.glx.glXMakeCurrent)(self.connection.dpy, 0, std::ptr::null_mut()); error_handler.check().unwrap(); if res == 0 { panic!("make_not_current failed") @@ -227,13 +241,13 @@ impl GlContext { } pub fn get_proc_address(&self, symbol: &str) -> *const c_void { - get_proc_address(symbol) + get_proc_address(&self.glx, symbol) } pub fn swap_buffers(&self) { unsafe { - errors::XErrorHandler::handle(self.display, |error_handler| { - glx::glXSwapBuffers(self.display, self.window); + errors::XErrorHandler::handle(&self.connection, |error_handler| { + (self.glx.glXSwapBuffers)(self.connection.dpy, self.window); error_handler.check().unwrap(); }) } @@ -243,7 +257,7 @@ impl GlContext { impl Drop for GlContext { fn drop(&mut self) { unsafe { - glx::glXDestroyContext(self.display, self.context); + (self.glx.glXDestroyContext)(self.connection.dpy, self.context); } } } diff --git a/src/gl/x11/errors.rs b/src/gl/x11/errors.rs index 184a625f..7c63d715 100644 --- a/src/gl/x11/errors.rs +++ b/src/gl/x11/errors.rs @@ -1,7 +1,8 @@ use std::ffi::CStr; use std::fmt::{Debug, Display, Formatter}; -use x11::xlib; +use x11_dl::xlib; +use crate::x11::XcbConnection; use std::cell::RefCell; use std::error::Error; use std::os::raw::{c_int, c_uchar, c_ulong}; @@ -11,40 +12,32 @@ thread_local! { /// Used as part of [`XErrorHandler::handle()`]. When an X11 error occurs during this function, /// the error gets copied to this RefCell after which the program is allowed to resume. The /// error can then be converted to a regular Rust Result value afterward. - static CURRENT_X11_ERROR: RefCell> = const { RefCell::new(None) }; + static CURRENT_X11_ERROR: RefCell> = const { RefCell::new(None) }; } /// A helper struct for safe X11 error handling pub struct XErrorHandler<'a> { - display: *mut xlib::Display, - error: &'a RefCell>, + conn: &'a XcbConnection, + error: &'a RefCell>, } impl<'a> XErrorHandler<'a> { /// Syncs and checks if any previous X11 calls from the given display returned an error pub fn check(&mut self) -> Result<(), XLibError> { // Flush all possible previous errors - unsafe { - xlib::XSync(self.display, 0); - } + unsafe { (self.conn.xlib.XSync)(self.conn.dpy, 0) }; + let error = self.error.borrow_mut().take(); match error { None => Ok(()), - Some(inner) => Err(inner), + Some(inner) => Err(XLibError::from_inner(inner, self.conn)), } } /// Sets up a temporary X11 error handler for the duration of the given closure, and allows /// that closure to check on the latest X11 error at any time. - /// - /// # Safety - /// - /// The given display pointer *must* be and remain valid for the duration of this function, as - /// well as for the duration of the given `handler` closure. - pub unsafe fn handle T>( - display: *mut xlib::Display, handler: F, - ) -> T { + pub fn handle T>(conn: &XcbConnection, handler: F) -> T { /// # Safety /// The given display and error pointers *must* be valid for the duration of this function. unsafe extern "C" fn error_handler( @@ -60,7 +53,7 @@ impl<'a> XErrorHandler<'a> { // cause of the other errors Some(_) => 1, None => { - *error = Some(XLibError::from_event(err)); + *error = Some(CaughtXLibError::from_event(err)); 0 } } @@ -68,9 +61,7 @@ impl<'a> XErrorHandler<'a> { } // Flush all possible previous errors - unsafe { - xlib::XSync(display, 0); - } + unsafe { (conn.xlib.XSync)(conn.dpy, 0) }; CURRENT_X11_ERROR.with(|error| { // Make sure to clear any errors from the last call to this function @@ -78,13 +69,13 @@ impl<'a> XErrorHandler<'a> { *error.borrow_mut() = None; } - let old_handler = unsafe { xlib::XSetErrorHandler(Some(error_handler)) }; + let old_handler = unsafe { (conn.xlib.XSetErrorHandler)(Some(error_handler)) }; let panic_result = std::panic::catch_unwind(AssertUnwindSafe(|| { - let mut h = XErrorHandler { display, error }; + let mut h = XErrorHandler { conn, error }; handler(&mut h) })); // Whatever happened, restore old error handler - unsafe { xlib::XSetErrorHandler(old_handler) }; + unsafe { (conn.xlib.XSetErrorHandler)(old_handler) }; match panic_result { Ok(v) => v, @@ -94,21 +85,17 @@ impl<'a> XErrorHandler<'a> { } } -pub struct XLibError { +struct CaughtXLibError { type_: c_int, resourceid: xlib::XID, serial: c_ulong, error_code: c_uchar, request_code: c_uchar, minor_code: c_uchar, - - display_name: Box, } -impl XLibError { - /// # Safety - /// The display pointer inside error must be valid for the duration of this call - unsafe fn from_event(error: &xlib::XErrorEvent) -> Self { +impl CaughtXLibError { + fn from_event(error: &xlib::XErrorEvent) -> CaughtXLibError { Self { type_: error.type_, resourceid: error.resourceid, @@ -117,23 +104,30 @@ impl XLibError { error_code: error.error_code, request_code: error.request_code, minor_code: error.minor_code, - - display_name: Self::get_display_name(error), } } +} + +pub struct XLibError { + inner: CaughtXLibError, + display_name: Box, +} - /// # Safety - /// The display pointer inside error must be valid for the duration of this call - unsafe fn get_display_name(error: &xlib::XErrorEvent) -> Box { +impl XLibError { + fn from_inner(inner: CaughtXLibError, conn: &XcbConnection) -> Self { + Self { display_name: Self::get_display_name(&inner, conn), inner } + } + + fn get_display_name(error: &CaughtXLibError, conn: &XcbConnection) -> Box { let mut buf = [0; 255]; unsafe { - xlib::XGetErrorText( - error.display, + (conn.xlib.XGetErrorText)( + conn.dpy, error.error_code.into(), buf.as_mut_ptr().cast(), (buf.len() - 1) as i32, - ); - } + ) + }; *buf.last_mut().unwrap() = 0; // SAFETY: whatever XGetErrorText did or not, we guaranteed there is a nul byte at the end of the buffer @@ -146,20 +140,20 @@ impl XLibError { impl Debug for XLibError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("XLibError") - .field("error_code", &self.error_code) + .field("error_code", &self.inner.error_code) .field("error_message", &self.display_name) - .field("minor_code", &self.minor_code) - .field("request_code", &self.request_code) - .field("type", &self.type_) - .field("resource_id", &self.resourceid) - .field("serial", &self.serial) + .field("minor_code", &self.inner.minor_code) + .field("request_code", &self.inner.request_code) + .field("type", &self.inner.type_) + .field("resource_id", &self.inner.resourceid) + .field("serial", &self.inner.serial) .finish() } } impl Display for XLibError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "XLib error: {} (error code {})", &self.display_name, self.error_code) + write!(f, "XLib error: {} (error code {})", &self.display_name, self.inner.error_code) } } diff --git a/src/lib.rs b/src/lib.rs index 623d7b98..32b249c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ mod macos; #[cfg(target_os = "windows")] mod win; #[cfg(target_os = "linux")] -mod x11; +pub(crate) mod x11; mod clipboard; mod event; diff --git a/src/x11/mod.rs b/src/x11/mod.rs index 149df0be..f1c14892 100644 --- a/src/x11/mod.rs +++ b/src/x11/mod.rs @@ -1,5 +1,5 @@ mod xcb_connection; -use xcb_connection::XcbConnection; +pub(crate) use xcb_connection::XcbConnection; mod window; pub use window::*; diff --git a/src/x11/visual_info.rs b/src/x11/visual_info.rs index 3f1be380..1b355a85 100644 --- a/src/x11/visual_info.rs +++ b/src/x11/visual_info.rs @@ -23,11 +23,9 @@ impl WindowVisualConfig { ) -> Result> { let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) }; - // SAFETY: TODO - let (fb_config, window_config) = unsafe { - crate::gl::platform::GlContext::get_fb_config_and_visual(connection.dpy, gl_config) - } - .expect("Could not fetch framebuffer config"); + let (fb_config, window_config) = + crate::gl::platform::GlContext::get_fb_config_and_visual(connection, gl_config) + .expect("Could not fetch framebuffer config"); Ok(Self { fb_config: Some(fb_config), diff --git a/src/x11/window.rs b/src/x11/window.rs index e556fe20..7ab44cae 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -1,6 +1,7 @@ use std::cell::Cell; use std::error::Error; use std::ffi::c_void; +use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc; use std::sync::Arc; @@ -96,7 +97,7 @@ pub(crate) struct WindowInner { #[cfg(feature = "opengl")] gl_context: Option, - pub(crate) xcb_connection: XcbConnection, + pub(crate) xcb_connection: Rc, window_id: XWindow, pub(crate) window_info: WindowInfo, visual_id: Visualid, @@ -251,6 +252,7 @@ impl<'a> Window<'a> { )?; xcb_connection.conn.flush()?; + let xcb_connection = Rc::new(xcb_connection); // TODO: These APIs could use a couple tweaks now that everything is internal and there is // no error handling anymore at this point. Everything is more or less unchanged @@ -260,11 +262,12 @@ impl<'a> Window<'a> { use std::ffi::c_ulong; let window = window_id as c_ulong; - let display = xcb_connection.dpy; // Because of the visual negotation we had to take some extra steps to create this context - let context = unsafe { platform::GlContext::create(window, display, fb_config) } - .expect("Could not create OpenGL context"); + let context = unsafe { + platform::GlContext::create(window, Rc::clone(&xcb_connection), fb_config) + } + .expect("Could not create OpenGL context"); GlContext::new(context) }); @@ -365,7 +368,7 @@ unsafe impl<'a> HasRawDisplayHandle for Window<'a> { let mut handle = XlibDisplayHandle::empty(); handle.display = display as *mut c_void; - handle.screen = unsafe { x11::xlib::XDefaultScreen(display) }; + handle.screen = self.inner.xcb_connection.screen; RawDisplayHandle::Xlib(handle) } diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index a5ea06d9..c8725917 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -1,9 +1,11 @@ use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; use std::error::Error; - -use x11::{xlib, xlib::Display, xlib_xcb}; - +use std::ffi::c_int; +use std::sync::Arc; +use x11_dl::xlib::Xlib; +use x11_dl::xlib_xcb::Xlib_xcb; +use x11_dl::{xlib::Display, xlib_xcb}; use x11rb::connection::Connection; use x11rb::cursor::Handle as CursorHandle; use x11rb::protocol::xproto::{Cursor, Screen}; @@ -25,9 +27,10 @@ x11rb::atom_manager! { /// /// Keeps track of the xcb connection itself and the xlib display ID that was used to connect. pub struct XcbConnection { + pub(crate) xlib: Arc, pub(crate) dpy: *mut Display, pub(crate) conn: XCBConnection, - pub(crate) screen: usize, + pub(crate) screen: c_int, pub(crate) atoms: Atoms, pub(crate) resources: resource_manager::Database, pub(crate) cursor_handle: CursorHandle, @@ -36,21 +39,24 @@ pub struct XcbConnection { impl XcbConnection { pub fn new() -> Result> { - let dpy = unsafe { xlib::XOpenDisplay(std::ptr::null()) }; + let xlib = Xlib::open()?; + let dpy = unsafe { (xlib.XOpenDisplay)(std::ptr::null()) }; assert!(!dpy.is_null()); - let xcb_connection = unsafe { xlib_xcb::XGetXCBConnection(dpy) }; + let xlib_xcb = Xlib_xcb::open()?; + let xcb_connection = unsafe { (xlib_xcb.XGetXCBConnection)(dpy) }; assert!(!xcb_connection.is_null()); - let screen = unsafe { xlib::XDefaultScreen(dpy) } as usize; + let screen = unsafe { (xlib.XDefaultScreen)(dpy) }; let conn = unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection, false)? }; unsafe { - xlib_xcb::XSetEventQueueOwner(dpy, xlib_xcb::XEventQueueOwner::XCBOwnsEventQueue) + (xlib_xcb.XSetEventQueueOwner)(dpy, xlib_xcb::XEventQueueOwner::XCBOwnsEventQueue) }; let atoms = Atoms::new(&conn)?.reply()?; let resources = resource_manager::new_from_default(&conn)?; - let cursor_handle = CursorHandle::new(&conn, screen, &resources)?.reply()?; + let cursor_handle = CursorHandle::new(&conn, screen as usize, &resources)?.reply()?; Ok(Self { + xlib: Arc::new(xlib), dpy, conn, screen, @@ -110,8 +116,12 @@ impl XcbConnection { match cursor_cache.entry(cursor) { Entry::Occupied(entry) => Ok(*entry.get()), Entry::Vacant(entry) => { - let cursor = - cursor::get_xcursor(&self.conn, self.screen, &self.cursor_handle, cursor)?; + let cursor = cursor::get_xcursor( + &self.conn, + self.screen as usize, + &self.cursor_handle, + cursor, + )?; entry.insert(cursor); Ok(cursor) } @@ -119,14 +129,14 @@ impl XcbConnection { } pub fn screen(&self) -> &Screen { - &self.conn.setup().roots[self.screen] + &self.conn.setup().roots[self.screen as usize] } } impl Drop for XcbConnection { fn drop(&mut self) { unsafe { - xlib::XCloseDisplay(self.dpy); + (self.xlib.XCloseDisplay)(self.dpy); } } }