diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 37e437b3425..d7e877a74d6 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -36,7 +36,13 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --all-features + args: --features all-extensions,xlib_xcb + + - name: Run cargo test (dl) + uses: actions-rs/cargo@v1 + with: + command: test + args: --features all-extensions,xlib_xcb_dl - name: Run cargo doc uses: actions-rs/cargo@v1 diff --git a/Cargo.toml b/Cargo.toml index 5afbe2b9b82..9f8dbb50758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "xcb" version = "1.6.0" -authors = [ "Remi Thebault " ] +authors = ["Remi Thebault "] description = "Rust safe bindings for XCB" repository = "https://github.com/rust-x-bindings/rust-xcb" documentation = "https://docs.rs/xcb/latest/xcb" @@ -9,7 +9,13 @@ readme = "README.md" keywords = ["xcb", "window", "xlib", "x11", "opengl"] license = "MIT" build = "build/main.rs" -exclude = [".github", "examples/todo/*", "gen", "xml/upstream", "xml/upstream_normalized"] +exclude = [ + ".github", + "examples/todo/*", + "gen", + "xml/upstream", + "xml/upstream_normalized", +] autoexamples = false edition = "2018" @@ -23,6 +29,7 @@ quick-xml = "0.30.0" libc = "0.2.102" bitflags = "1.3.2" as-raw-xcb-connection = { version = "1.0", optional = true } +libloading = { version = "0.9.0", optional = true } tiny-xlib = { version = "0.2.4", optional = true } [dependencies.x11] @@ -30,21 +37,59 @@ version = "2.19.0" optional = true features = ["xlib"] +[dependencies.x11-dl] +version = "2.19.0" +optional = true + [features] default = ["libxcb_v1_14"] debug_atom_names = [] -xlib_xcb = ["x11/xlib"] +dl = ["dep:libloading"] libxcb_v1_14 = [] - -composite = [ "xfixes" ] -damage = [ "xfixes" ] +xlib_xcb = ["x11/xlib"] +xlib_xcb_dl = ["dl", "dep:x11-dl"] + +all-extensions = [ + "composite", + "damage", + "dpms", + "dri2", + "dri3", + "ge", + "glx", + "present", + "randr", + "record", + "render", + "res", + "screensaver", + "shape", + "shm", + "sync", + "xevie", + "xf86dri", + "xf86vidmode", + "xfixes", + "xinerama", + "xinput", + "xkb", + "xprint", + "xselinux", + "xtest", + "xv", + "xvmc", +] + +# Extension features +composite = ["xfixes"] +damage = ["xfixes"] dpms = [] dri2 = [] dri3 = [] ge = [] glx = [] -present = [ "render", "xfixes", "sync" ] -randr = [ "render" ] +present = ["render", "xfixes", "sync"] +randr = ["render"] record = [] render = [] res = [] @@ -55,15 +100,16 @@ sync = [] xevie = [] xf86dri = [] xf86vidmode = [] -xfixes = [ "render", "shape" ] +xfixes = ["render", "shape"] xinerama = [] -xinput = [ "xfixes" ] +xinput = ["xfixes"] xkb = [] xprint = [] xselinux = [] xtest = [] -xv = [ "shm" ] -xvmc = [ "xv" ] +xv = ["shm"] +xvmc = ["xv"] + tiny-xlib = ["dep:tiny-xlib"] [dev-dependencies] @@ -100,6 +146,10 @@ name = "get_all_windows" name = "opengl_window" required-features = ["glx", "xlib_xcb", "dri2"] +[[example]] +name = "opengl_window_dl" +required-features = ["dri2", "glx", "xlib_xcb_dl"] + [[example]] name = "present_special_event" required-features = ["present", "randr"] @@ -146,4 +196,4 @@ required-features = ["xkb"] [[example]] name = "tiny_xlib" -required-features = ["tiny-xlib", "as-raw-xcb-connection"] \ No newline at end of file +required-features = ["tiny-xlib", "as-raw-xcb-connection"] diff --git a/build/cg/mod.rs b/build/cg/mod.rs index f742dc511b5..8859b3ae866 100644 --- a/build/cg/mod.rs +++ b/build/cg/mod.rs @@ -554,6 +554,11 @@ impl CodeGen { out, "pub fn prefetch_extension_data(conn: &base::Connection) {{" )?; + writeln!(out, " #[cfg(feature = \"dl\")]")?; + writeln!( + out, + " base::xcb_get_conn_funcs!(conn, xcb_prefetch_extension_data);" + )?; writeln!(out, " unsafe {{")?; writeln!( out, @@ -582,6 +587,11 @@ impl CodeGen { out, "pub fn get_extension_data(conn: &base::Connection) -> std::option::Option {{" )?; + writeln!(out, " #[cfg(feature = \"dl\")]")?; + writeln!( + out, + " base::xcb_get_conn_funcs!(conn, xcb_get_extension_data);" + )?; writeln!(out, " unsafe {{")?; writeln!( out, diff --git a/build/cg/request.rs b/build/cg/request.rs index 1a0119fc806..25de1d02578 100644 --- a/build/cg/request.rs +++ b/build/cg/request.rs @@ -885,6 +885,10 @@ impl CodeGen { } else { "xcb_send_request64" }; + + writeln!(out)?; + writeln!(out, "{}#[cfg(feature = \"dl\")]", cg::ind(2))?; + writeln!(out, "{}base::xcb_get_conn_funcs!(c, {});", cg::ind(2), func)?; writeln!(out)?; writeln!(out, "{}{}(", cg::ind(2), func)?; writeln!(out, "{} c.get_raw_conn(),", cg::ind(2))?; diff --git a/examples/opengl_window_dl.rs b/examples/opengl_window_dl.rs new file mode 100644 index 00000000000..3c2f23d6eb7 --- /dev/null +++ b/examples/opengl_window_dl.rs @@ -0,0 +1,350 @@ +use xcb::ffi::xcb_generic_event_t; +use xcb::{self, dri2, glx, x}; +use xcb::{Raw, Xid}; + +use x11_dl::glx::*; +use x11_dl::xlib::{self, Xlib}; + +use std::ffi::{CStr, CString}; +use std::os::raw::{c_int, c_void}; +use std::ptr; + +const GLX_CONTEXT_MAJOR_VERSION_ARB: u32 = 0x2091; +const GLX_CONTEXT_MINOR_VERSION_ARB: u32 = 0x2092; + +type GlXCreateContextAttribsARBProc = unsafe extern "C" fn( + dpy: *mut xlib::Display, + fbc: GLXFBConfig, + share_context: GLXContext, + direct: xlib::Bool, + attribs: *const c_int, +) -> GLXContext; + +unsafe fn load_gl_func(glxlib: &Glx, name: &str) -> *mut c_void { + let cname = CString::new(name).unwrap(); + let ptr: *mut c_void = + std::mem::transmute((glxlib.glXGetProcAddress)(cname.as_ptr() as *const u8)); + if ptr.is_null() { + panic!("could not load {}", name); + } + ptr +} + +fn check_glx_extension(glx_exts: &str, ext_name: &str) -> bool { + for glx_ext in glx_exts.split(" ") { + if glx_ext == ext_name { + return true; + } + } + false +} + +static mut CTX_ERROR_OCCURED: bool = false; +unsafe extern "C" fn ctx_error_handler( + _dpy: *mut xlib::Display, + _ev: *mut xlib::XErrorEvent, +) -> i32 { + CTX_ERROR_OCCURED = true; + 0 +} + +unsafe fn check_gl_error() { + let err = gl::GetError(); + if err != gl::NO_ERROR { + println!("got gl error {}", err); + } +} + +fn get_glxfbconfig( + xliblib: &Xlib, + glxlib: &Glx, + dpy: *mut xlib::Display, + screen_num: i32, + visual_attribs: &[i32], +) -> GLXFBConfig { + unsafe { + let mut fbcount: c_int = 0; + let fbcs = (glxlib.glXChooseFBConfig)( + dpy, + screen_num, + visual_attribs.as_ptr(), + &mut fbcount as *mut c_int, + ); + + if fbcount == 0 { + panic!("could not find compatible fb config"); + } + // we pick the first from the list + let fbc = *fbcs; + (xliblib.XFree)(fbcs as *mut c_void); + fbc + } +} + +fn main() -> xcb::Result<()> { + // We use unwrap here, but in a real application when using dynamic loading, + // you wouldn't do that. The main reason you'd want to use dynamic loading is + // to be able to fall back to other protocols or graphics apis like wayland or vulkan. + let xliblib = Xlib::open().unwrap(); + let glxlib = Glx::open().unwrap(); + + let (conn, screen_num) = + xcb::Connection::connect_with_xlib_display_and_extensions(&[], &[xcb::Extension::Dri2])?; + + conn.set_event_queue_owner(xcb::EventQueueOwner::Xcb); + + let glx_ver = conn.wait_for_reply(conn.send_request(&glx::QueryVersion { + major_version: 1, + minor_version: 3, + }))?; + assert!(glx_ver.major_version() >= 1 && glx_ver.minor_version() >= 3); + + let fbc = get_glxfbconfig( + &xliblib, + &glxlib, + conn.get_raw_dpy(), + screen_num, + &[ + GLX_X_RENDERABLE, + 1, + GLX_DRAWABLE_TYPE, + GLX_WINDOW_BIT, + GLX_RENDER_TYPE, + GLX_RGBA_BIT, + GLX_X_VISUAL_TYPE, + GLX_TRUE_COLOR, + GLX_RED_SIZE, + 8, + GLX_GREEN_SIZE, + 8, + GLX_BLUE_SIZE, + 8, + GLX_ALPHA_SIZE, + 8, + GLX_DEPTH_SIZE, + 24, + GLX_STENCIL_SIZE, + 8, + GLX_DOUBLEBUFFER, + 1, + 0, + ], + ); + + let vi_ptr: *mut xlib::XVisualInfo = + unsafe { (glxlib.glXGetVisualFromFBConfig)(conn.get_raw_dpy(), fbc) }; + let vi = unsafe { *vi_ptr }; + + // retrieving a few atoms + let (wm_protocols, wm_del_window) = { + let cookies = ( + conn.send_request(&x::InternAtom { + only_if_exists: false, + name: b"WM_PROTOCOLS", + }), + conn.send_request(&x::InternAtom { + only_if_exists: false, + name: b"WM_DELETE_WINDOW", + }), + ); + ( + conn.wait_for_reply(cookies.0)?.atom(), + conn.wait_for_reply(cookies.1)?.atom(), + ) + }; + + let setup = conn.get_setup(); + let screen = setup.roots().nth(vi.screen as usize).unwrap(); + + let cmap: x::Colormap = conn.generate_id(); + let win: x::Window = conn.generate_id(); + + conn.send_request(&x::CreateColormap { + alloc: x::ColormapAlloc::None, + mid: cmap, + window: screen.root(), + visual: vi.visualid as u32, + }); + + conn.send_request(&x::CreateWindow { + depth: x::COPY_FROM_PARENT as u8, + wid: win, + parent: screen.root(), + x: 0, + y: 0, + width: 640, + height: 480, + border_width: 0, + class: x::WindowClass::InputOutput, + visual: vi.visualid as u32, + value_list: &[ + x::Cw::BackPixel(screen.white_pixel()), + x::Cw::EventMask(x::EventMask::EXPOSURE | x::EventMask::KEY_PRESS), + x::Cw::Colormap(cmap), + ], + }); + + unsafe { + (xliblib.XFree)(vi_ptr as *mut c_void); + } + + let title = "XCB OpenGL"; + + conn.check_request(conn.send_request_checked(&x::ChangeProperty { + mode: x::PropMode::Replace, + window: win, + property: x::ATOM_WM_NAME, + r#type: x::ATOM_STRING, + data: title.as_bytes(), + }))?; + + conn.check_request(conn.send_request_checked(&x::ChangeProperty { + mode: x::PropMode::Replace, + window: win, + property: wm_protocols, + r#type: x::ATOM_ATOM, + data: &[wm_del_window], + }))?; + + conn.check_request(conn.send_request_checked(&x::MapWindow { window: win }))?; + + unsafe { + (xliblib.XSync)(conn.get_raw_dpy(), xlib::False); + } + + let glx_exts = unsafe { + CStr::from_ptr((glxlib.glXQueryExtensionsString)( + conn.get_raw_dpy(), + screen_num, + )) + } + .to_str() + .unwrap(); + + if !check_glx_extension(&glx_exts, "GLX_ARB_create_context") { + panic!("could not find GLX extension GLX_ARB_create_context"); + } + + // with glx, no need of a current context is needed to load symbols + // otherwise we would need to create a temporary legacy GL context + // for loading symbols (at least glXCreateContextAttribsARB) + let glx_create_context_attribs: GlXCreateContextAttribsARBProc = + unsafe { std::mem::transmute(load_gl_func(&glxlib, "glXCreateContextAttribsARB")) }; + + // loading all other symbols + unsafe { + gl::load_with(|n| load_gl_func(&glxlib, &n)); + } + + if !gl::GenVertexArrays::is_loaded() { + panic!("no GL3 support available!"); + } + + // installing an event handler to check if error is generated + unsafe { + CTX_ERROR_OCCURED = false; + } + + let old_handler = unsafe { (xliblib.XSetErrorHandler)(Some(ctx_error_handler)) }; + + let context_attribs: [c_int; 5] = [ + GLX_CONTEXT_MAJOR_VERSION_ARB as c_int, + 3, + GLX_CONTEXT_MINOR_VERSION_ARB as c_int, + 0, + 0, + ]; + let ctx = unsafe { + glx_create_context_attribs( + conn.get_raw_dpy(), + fbc, + ptr::null_mut(), + xlib::True, + &context_attribs[0] as *const c_int, + ) + }; + + conn.flush()?; + + unsafe { + (xliblib.XSync)(conn.get_raw_dpy(), xlib::False); + (xliblib.XSetErrorHandler)(std::mem::transmute(old_handler)); + } + + if ctx.is_null() || unsafe { CTX_ERROR_OCCURED } { + panic!("error when creating gl-3.0 context"); + } + + if unsafe { (glxlib.glXIsDirect)(conn.get_raw_dpy(), ctx) } == 0 { + panic!("obtained indirect rendering context") + } + + loop { + conn.flush()?; + + match conn.wait_for_event()? { + xcb::Event::X(x::Event::Expose(_)) => unsafe { + (glxlib.glXMakeCurrent)(conn.get_raw_dpy(), win.resource_id() as xlib::XID, ctx); + gl::ClearColor(0.5f32, 0.5f32, 1.0f32, 1.0f32); + gl::Clear(gl::COLOR_BUFFER_BIT); + gl::Flush(); + check_gl_error(); + (glxlib.glXSwapBuffers)(conn.get_raw_dpy(), win.resource_id() as xlib::XID); + (glxlib.glXMakeCurrent)(conn.get_raw_dpy(), 0, ptr::null_mut()); + }, + xcb::Event::X(x::Event::KeyPress(_ev)) => {} + xcb::Event::X(x::Event::ClientMessage(ev)) => { + if let x::ClientMessageData::Data32([atom, ..]) = ev.data() { + if atom == wm_del_window.resource_id() { + // window "x" button clicked by user, we gracefully exit + break; + } + } + } + + // Following stuff is not obvious at all. + // I've seen this as necessary in the past to handle GL when XCB owns the event queue. + // It doesn't seem necessary anymore (in fact DRI2 is not present + // on my system, so I won't even hit this code) but I leave it here + // in case it is useful to someone. + + // These are libgl dri2 event that need special handling + // see https://bugs.freedesktop.org/show_bug.cgi?id=35945#c4 + // and mailing thread starting here: + // http://lists.freedesktop.org/archives/xcb/2015-November/010556.html + xcb::Event::Dri2(dri2::Event::BufferSwapComplete(ev)) => unsafe { + rewire_event(&xliblib, &conn, ev.as_raw()) + }, + xcb::Event::Dri2(dri2::Event::InvalidateBuffers(ev)) => unsafe { + rewire_event(&xliblib, &conn, ev.as_raw()) + }, + _ => {} + } + } + + unsafe { + (glxlib.glXDestroyContext)(conn.get_raw_dpy(), ctx); + } + + conn.send_request(&x::UnmapWindow { window: win }); + conn.send_request(&x::DestroyWindow { window: win }); + conn.send_request(&x::FreeColormap { cmap }); + conn.flush()?; + + Ok(()) +} + +unsafe fn rewire_event(xliblib: &Xlib, conn: &xcb::Connection, raw_ev: *mut xcb_generic_event_t) { + let ev_type = ((*raw_ev).response_type & 0x7f) as i32; + + if let Some(proc) = (xliblib.XESetWireToEvent)(conn.get_raw_dpy(), ev_type, None) { + (xliblib.XESetWireToEvent)(conn.get_raw_dpy(), ev_type, Some(proc)); + (*raw_ev).sequence = (xliblib.XLastKnownRequestProcessed)(conn.get_raw_dpy()) as u16; + let mut dummy: xlib::XEvent = std::mem::zeroed(); + proc( + conn.get_raw_dpy(), + &mut dummy as *mut xlib::XEvent, + raw_ev as *mut xlib::xEvent, + ); + } +} diff --git a/src/base.rs b/src/base.rs index 4ad0c7a48ff..09943d31ab7 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,6 +1,14 @@ use crate::error::{self, ProtocolError}; use crate::event::{self, Event}; use crate::ext::{Extension, ExtensionData}; +#[cfg(feature = "dl")] +use crate::ffi::dl::xcb_get_funcs; +#[cfg(feature = "dl")] +use crate::ffi::dl::OpenError; +#[cfg(feature = "dl")] +use crate::ffi::XcbLib; +#[cfg(feature = "xlib_xcb_dl")] +use crate::ffi::XlibXcbLib; #[cfg(feature = "present")] use crate::present; use crate::x::{Atom, Keysym, Setup, Timestamp}; @@ -8,9 +16,12 @@ use crate::x::{Atom, Keysym, Setup, Timestamp}; use crate::xinput; use crate::{cache_extensions_data, ffi::*}; -#[cfg(feature = "xlib_xcb")] +#[cfg(all(feature = "xlib_xcb", not(feature = "xlib_xcb_dl")))] use x11::xlib; +#[cfg(feature = "xlib_xcb_dl")] +use x11_dl::xlib; + use bitflags::bitflags; use libc::{c_char, c_int}; @@ -23,6 +34,9 @@ use std::mem; use std::os::fd::{IntoRawFd, OwnedFd}; use std::os::unix::prelude::{AsRawFd, RawFd}; use std::ptr; +#[cfg(feature = "dl")] +#[cfg(feature = "dl")] +use std::rc::Rc; use std::result; use std::slice; @@ -434,7 +448,7 @@ pub trait RequestWithReply: Request { /// This item is behind the `xlib_xcb` cargo feature. /// /// See [`Connection::set_event_queue_owner`]. -#[cfg(feature = "xlib_xcb")] +#[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] #[derive(Debug)] pub enum EventQueueOwner { /// XCB owns the event queue @@ -499,6 +513,15 @@ pub fn parse_display(name: &str) -> Option { let mut screen = 0i32; let success = unsafe { + #[cfg(feature = "dl")] + let lib = crate::ffi::XcbLib::open(); + #[cfg(feature = "dl")] + let xcb_parse_display = match lib { + Ok(lib) => lib.xcb_parse_display, + Err(_) => { + return None; + } + }; xcb_parse_display( name.as_ptr(), &mut hostp as *mut _, @@ -527,6 +550,18 @@ pub fn parse_display(name: &str) -> Option { } } +/// Unloads any cached dynamic libraries loaded by this crate. +/// Doesn't prevent another open of the libraries, so should be used +/// after no more calls into this crate occurs, if needed. +#[cfg(feature = "dl")] +pub fn unload_libraries() -> result::Result<(), OpenError> { + #[cfg(feature = "dl")] + crate::ffi::XcbLib::unload()?; + #[cfg(feature = "xlib_xcb_dl")] + crate::ffi::XlibXcbLib::unload()?; + Ok(()) +} + /// A struct that serve as an identifier for internal special queue in XCB /// /// See [Connection::register_for_special_xge]. @@ -582,8 +617,11 @@ pub enum ConnError { /// Connection closed because some file descriptor passing operation failed. ClosedFdPassingFailed, /// XOpenDisplay returned NULL - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] XOpenDisplay, + /// Libraries not loaded. + #[cfg(feature = "dl")] + LibrariesNotLoaded, } impl ConnError { @@ -602,10 +640,12 @@ impl ConnError { ConnError::ClosedFdPassingFailed => { "Connection closed, some file descriptor passing operation failed" } - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] ConnError::XOpenDisplay => { "XOpenDisplay failed to open a display. Check the $DISPLAY env var" } + #[cfg(feature = "dl")] + ConnError::LibrariesNotLoaded => "Libraries are not loaded", } } } @@ -622,6 +662,13 @@ impl std::error::Error for ConnError { } } +#[cfg(feature = "dl")] +impl From for ConnError { + fn from(_: OpenError) -> Self { + ConnError::LibrariesNotLoaded + } +} + /// The result type associated with [ConnError]. pub type ConnResult = result::Result; @@ -684,7 +731,7 @@ pub type Result = result::Result; pub struct Connection { c: *mut xcb_connection_t, - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] dpy: *mut xlib::Display, ext_data: Vec, @@ -697,6 +744,9 @@ pub struct Connection { #[cfg(feature = "debug_atom_names")] dbg_atom_names: bool, + #[cfg(feature = "dl")] + pub(crate) lib: XcbLib, + // Whether to call xcb_disconnect() on drop. should_drop: bool, } @@ -704,6 +754,18 @@ pub struct Connection { unsafe impl Send for Connection {} unsafe impl Sync for Connection {} +#[cfg(feature = "dl")] +macro_rules! xcb_get_conn_funcs { + ($self:expr, $($name:ident),*) => { + $( + let $name = $self.lib.$name; + )* + }; +} + +#[cfg(feature = "dl")] +pub(crate) use xcb_get_conn_funcs; + impl Connection { /// Connects to the X server. /// @@ -752,6 +814,8 @@ impl Connection { mandatory: &[Extension], optional: &[Extension], ) -> ConnResult<(Connection, i32)> { + #[cfg(feature = "dl")] + xcb_get_funcs!(xcb_connect); let mut screen_num: c_int = 0; let displayname = display_name.map(|s| CString::new(s).unwrap()); unsafe { @@ -775,20 +839,35 @@ impl Connection { /// OpenGL. /// /// This function is behind the `xlib_xcb` cargo feature. - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] pub fn connect_with_xlib_display() -> ConnResult<(Connection, i32)> { + #[cfg(all(feature = "xlib_xcb", not(feature = "xlib_xcb_dl")))] + let (xopen_display, xdefault_screen, xget_xcbconnection) = + (xlib::XOpenDisplay, xlib::XDefaultScreen, XGetXCBConnection); + #[cfg(feature = "xlib_xcb_dl")] + let (xopen_display, xdefault_screen, _xlib_lib, xget_xcbconnection, _xlib_xcb_lib) = { + let xlib_lib = xlib::Xlib::open().map_err(|_| ConnError::LibrariesNotLoaded)?; + let xlib_xcb_lib = XlibXcbLib::open().map_err(|_| ConnError::LibrariesNotLoaded)?; + ( + xlib_lib.XOpenDisplay, + xlib_lib.XDefaultScreen, + xlib_lib, + xlib_xcb_lib.XGetXCBConnection, + xlib_xcb_lib, + ) + }; unsafe { - let dpy = xlib::XOpenDisplay(ptr::null()); + let dpy = xopen_display(ptr::null()); if dpy.is_null() { return Err(ConnError::XOpenDisplay); } - check_connection_error(XGetXCBConnection(dpy))?; + check_connection_error(xget_xcbconnection(dpy))?; let conn = Self::from_xlib_display(dpy); conn.has_error() - .map(|_| (conn, xlib::XDefaultScreen(dpy) as i32)) + .map(|_| (conn, xdefault_screen(dpy) as i32)) } } @@ -805,23 +884,38 @@ impl Connection { /// /// # Panics /// Panics if one of the mandatory extension is not present. - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] pub fn connect_with_xlib_display_and_extensions( mandatory: &[Extension], optional: &[Extension], ) -> ConnResult<(Connection, i32)> { + #[cfg(all(feature = "xlib_xcb", not(feature = "xlib_xcb_dl")))] + let (xopen_display, xdefault_screen, xget_xcbconnection) = + (xlib::XOpenDisplay, xlib::XDefaultScreen, XGetXCBConnection); + #[cfg(feature = "xlib_xcb_dl")] + let (xopen_display, xdefault_screen, _xlib_lib, xget_xcbconnection, _xlib_xcb_lib) = { + let xlib_lib = xlib::Xlib::open().map_err(|_| ConnError::LibrariesNotLoaded)?; + let xlib_xcb_lib = XlibXcbLib::open().map_err(|_| ConnError::LibrariesNotLoaded)?; + ( + xlib_lib.XOpenDisplay, + xlib_lib.XDefaultScreen, + xlib_lib, + xlib_xcb_lib.XGetXCBConnection, + xlib_xcb_lib, + ) + }; unsafe { - let dpy = xlib::XOpenDisplay(ptr::null()); + let dpy = xopen_display(ptr::null()); if dpy.is_null() { return Err(ConnError::XOpenDisplay); } - check_connection_error(XGetXCBConnection(dpy))?; + check_connection_error(xget_xcbconnection(dpy))?; let conn = Self::from_xlib_display_and_extensions(dpy, mandatory, optional); conn.has_error() - .map(|_| (conn, xlib::XDefaultScreen(dpy) as i32)) + .map(|_| (conn, xdefault_screen(dpy) as i32)) } } @@ -856,6 +950,8 @@ impl Connection { mandatory: &[Extension], optional: &[Extension], ) -> ConnResult { + #[cfg(feature = "dl")] + xcb_get_funcs!(xcb_connect_to_fd); let mut auth_info = auth_info.map(|auth_info| { let auth_name = CString::new(auth_info.name).unwrap(); let auth_data = CString::new(auth_info.data).unwrap(); @@ -912,6 +1008,8 @@ impl Connection { mandatory: &[Extension], optional: &[Extension], ) -> ConnResult { + #[cfg(feature = "dl")] + xcb_get_funcs!(xcb_connect_to_fd); let mut auth_info = auth_info.map(|auth_info| { let auth_name = CString::new(auth_info.name).unwrap(); let auth_data = CString::new(auth_info.data).unwrap(); @@ -973,6 +1071,8 @@ impl Connection { mandatory: &[Extension], optional: &[Extension], ) -> ConnResult<(Connection, i32)> { + #[cfg(feature = "dl")] + xcb_get_funcs!(xcb_connect_to_display_with_auth_info); let mut screen_num: c_int = 0; let display_name = display_name.map(|s| CString::new(s).unwrap()); @@ -1020,6 +1120,7 @@ impl Connection { /// the resolution of events and errors in these extensions. /// /// # Panics + /// Panics if feature dl is active and libraries were not loaded. /// Panics if the connection is null or in error state. /// Panics if one of the mandatory extension is not present. /// @@ -1045,39 +1146,15 @@ impl Connection { let ext_data = cache_extensions_data(conn, mandatory, optional); - #[cfg(not(feature = "xlib_xcb"))] - #[cfg(not(feature = "debug_atom_names"))] - return Connection { - c: conn, - ext_data, - should_drop: true, - }; - - #[cfg(not(feature = "xlib_xcb"))] - #[cfg(feature = "debug_atom_names")] - return Connection { - c: conn, - ext_data, - dbg_atom_names, - should_drop: true, - }; - - #[cfg(feature = "xlib_xcb")] - #[cfg(not(feature = "debug_atom_names"))] - return Connection { - c: conn, - dpy: ptr::null_mut(), - ext_data, - should_drop: true, - }; - - #[cfg(feature = "xlib_xcb")] - #[cfg(feature = "debug_atom_names")] return Connection { c: conn, + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] dpy: ptr::null_mut(), ext_data, + #[cfg(feature = "debug_atom_names")] dbg_atom_names, + #[cfg(feature = "dl")] + lib: XcbLib::open().expect("xcb library not loaded"), should_drop: true, }; } @@ -1122,11 +1199,13 @@ impl Connection { return Connection { c: conn, - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] dpy: ptr::null_mut(), ext_data, #[cfg(feature = "debug_atom_names")] dbg_atom_names, + #[cfg(feature = "dl")] + lib: XcbLib::open().expect("xcb library not loaded"), should_drop: false, }; } @@ -1140,7 +1219,7 @@ impl Connection { /// /// # Safety /// The `dpy` pointer must be a pointer to a valid `xlib::Display` - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] pub unsafe fn from_xlib_display(dpy: *mut xlib::Display) -> Connection { Self::from_xlib_display_and_extensions(dpy, &[], &[]) } @@ -1156,18 +1235,26 @@ impl Connection { /// This function is behind the `xlib_xcb` cargo feature. /// /// # Panics + /// Panics if features dl or xlib_xcb_dl are active and libraries were not loaded. /// Panics if the connection is null or in error state. /// /// # Safety /// The `dpy` pointer must be a pointer to a valid `xlib::Display`. - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] pub unsafe fn from_xlib_display_and_extensions( dpy: *mut xlib::Display, mandatory: &[Extension], optional: &[Extension], ) -> Connection { assert!(!dpy.is_null(), "attempt connect with null display"); - let c = XGetXCBConnection(dpy); + #[cfg(all(feature = "xlib_xcb", not(feature = "xlib_xcb_dl")))] + let xget_xcbconnection = XGetXCBConnection; + #[cfg(feature = "xlib_xcb_dl")] + let (xget_xcbconnection, _lib) = { + let lib = XlibXcbLib::open().expect("X11-xcb library not loaded"); + (lib.XGetXCBConnection, lib) + }; + let c = xget_xcbconnection(dpy); assert!(check_connection_error(c).is_ok()); @@ -1183,17 +1270,16 @@ impl Connection { let ext_data = cache_extensions_data(c, mandatory, optional); - #[cfg(feature = "debug_atom_names")] return Connection { c, dpy, ext_data, + #[cfg(feature = "debug_atom_names")] dbg_atom_names, + #[cfg(feature = "dl")] + lib: XcbLib::open().expect("xcb library not loaded"), should_drop: true, }; - - #[cfg(not(feature = "debug_atom_names"))] - return Connection { c, dpy, ext_data }; } /// Get the extensions activated for this connection. @@ -1239,7 +1325,7 @@ impl Connection { /// Returns the inner ffi `xlib::Display` pointer. /// /// This function is behind the `xlib_xcb` cargo feature. - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] pub fn get_raw_dpy(&self) -> *mut xlib::Display { self.dpy } @@ -1248,11 +1334,17 @@ impl Connection { /// with the Xlib interface. In that case, the default owner is Xlib. /// /// This function is behind the `xlib_xcb` cargo feature. - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] pub fn set_event_queue_owner(&self, owner: EventQueueOwner) { debug_assert!(!self.dpy.is_null()); unsafe { - XSetEventQueueOwner( + #[cfg(all(feature = "xlib_xcb", not(feature = "xlib_xcb_dl")))] + let xset_eventqueueowner = XSetEventQueueOwner; + #[cfg(feature = "xlib_xcb_dl")] + let xset_eventqueueowner = XlibXcbLib::open() + .expect("X11-xcb library not loaded") + .XSetEventQueueOwner; + xset_eventqueueowner( self.dpy, match owner { EventQueueOwner::Xcb => XCBOwnsEventQueue, @@ -1274,6 +1366,8 @@ impl Connection { /// theoretical maximum lengths roughly 256kB without BIG-REQUESTS and /// 16GB with. pub fn get_maximum_request_length(&self) -> u32 { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_get_maximum_request_length); unsafe { xcb_get_maximum_request_length(self.c) } } @@ -1290,6 +1384,8 @@ impl Connection { /// Note that in order for this function to be fully non-blocking, the /// application must previously have called [crate::bigreq::prefetch_extension_data]. pub fn prefetch_maximum_request_length(&self) { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_prefetch_maximum_request_length); unsafe { xcb_prefetch_maximum_request_length(self.c); } @@ -1309,6 +1405,8 @@ impl Connection { /// # } /// ``` pub fn generate_id(&self) -> T { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_generate_id); XidNew::new(unsafe { xcb_generate_id(self.c) }) } @@ -1326,6 +1424,8 @@ impl Connection { /// See also: [wait_for_event](Connection::wait_for_event), [check_request](Connection::check_request), /// [send_and_check_request](Connection::send_and_check_request). pub fn flush(&self) -> ConnResult<()> { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_flush); unsafe { let ret = xcb_flush(self.c); if ret > 0 { @@ -1392,6 +1492,8 @@ impl Connection { /// } /// ``` pub fn wait_for_event(&self) -> Result { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_wait_for_event); unsafe { let ev = xcb_wait_for_event(self.c); self.handle_wait_for_event(ev) @@ -1406,6 +1508,8 @@ impl Connection { /// attempting to read the next event, in which case the connection is /// shut down when this function returns. pub fn poll_for_event(&self) -> Result> { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_poll_for_event); unsafe { let ev = xcb_poll_for_event(self.c); self.handle_poll_for_event(ev) @@ -1423,6 +1527,8 @@ impl Connection { /// example, callers might use [Connection::wait_for_reply] and be interested /// only of events that preceded a specific reply. pub fn poll_for_queued_event(&self) -> ProtocolResult> { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_poll_for_queued_event); unsafe { let ev = xcb_poll_for_queued_event(self.c); if ev.is_null() { @@ -1445,6 +1551,8 @@ impl Connection { #[cfg(any(feature = "xinput", feature = "present"))] #[allow(deprecated)] pub fn register_for_special_xge(&self) -> SpecialEventId { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_register_for_special_xge); unsafe { let ext: *mut xcb_extension_t = match XGE::EXTENSION { #[cfg(feature = "xinput")] @@ -1467,6 +1575,8 @@ impl Connection { #[cfg(any(feature = "xinput", feature = "present"))] #[allow(deprecated)] pub fn unregister_for_special_xge(&self, se: SpecialEventId) { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_unregister_for_special_event); unsafe { xcb_unregister_for_special_event(self.c, se.raw); } @@ -1477,6 +1587,8 @@ impl Connection { #[cfg(any(feature = "xinput", feature = "present"))] #[allow(deprecated)] pub fn wait_for_special_event(&self, se: SpecialEventId) -> Result { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_wait_for_special_event); unsafe { let ev = xcb_wait_for_special_event(self.c, se.raw); self.handle_wait_for_event(ev) @@ -1488,6 +1600,8 @@ impl Connection { #[cfg(any(feature = "xinput", feature = "present"))] #[allow(deprecated)] pub fn poll_for_special_event(&self, se: SpecialEventId) -> Result> { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_poll_for_special_event); unsafe { let ev = xcb_poll_for_special_event(self.c, se.raw); self.handle_poll_for_event(ev) @@ -1506,6 +1620,8 @@ impl Connection { extension: Extension, eid: EID, ) -> SpecialEvent { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_register_for_special_xge); unsafe { let ext: *mut xcb_extension_t = match extension { #[cfg(feature = "xinput")] @@ -1524,6 +1640,8 @@ impl Connection { /// Stop listening to a special event #[cfg(any(feature = "xinput", feature = "present"))] pub fn unregister_for_special_event(&self, se: SpecialEvent) { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_unregister_for_special_event); unsafe { xcb_unregister_for_special_event(self.c, se.raw); } @@ -1532,6 +1650,8 @@ impl Connection { /// Returns the next event from a special queue, blocking until one arrives #[cfg(any(feature = "xinput", feature = "present"))] pub fn wait_for_special_event2(&self, se: &SpecialEvent) -> Result { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_wait_for_special_event); unsafe { let ev = xcb_wait_for_special_event(self.c, se.raw); self.handle_wait_for_event(ev) @@ -1541,6 +1661,8 @@ impl Connection { /// Returns the next event from a special queue #[cfg(any(feature = "xinput", feature = "present"))] pub fn poll_for_special_event2(&self, se: &SpecialEvent) -> Result> { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_poll_for_special_event); unsafe { let ev = xcb_poll_for_special_event(self.c, se.raw); self.handle_poll_for_event(ev) @@ -1555,6 +1677,8 @@ impl Connection { /// /// This function will not block even if the reply is not yet available. fn discard_reply(&self, cookie: C) { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_discard_reply64); unsafe { xcb_discard_reply64(self.c, cookie.sequence()); } @@ -1573,6 +1697,8 @@ impl Connection { /// /// See the X protocol specification for more details. pub fn get_setup(&self) -> &Setup { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_get_setup); unsafe { let ptr = xcb_get_setup(self.c); // let len = <&Setup as WiredIn>::compute_wire_len(ptr, ()); @@ -1720,6 +1846,8 @@ impl Connection { let cookie = xcb_void_cookie_t { seq: cookie.sequence() as u32, }; + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_request_check); let error = unsafe { xcb_request_check(self.c, cookie) }; if error.is_null() { Ok(()) @@ -1783,6 +1911,8 @@ impl Connection { where C: CookieWithReplyChecked, { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_wait_for_reply64); unsafe { let mut error: *mut xcb_generic_error_t = ptr::null_mut(); let reply = xcb_wait_for_reply64(self.c, cookie.sequence(), &mut error as *mut _); @@ -1818,6 +1948,8 @@ impl Connection { where C: CookieWithReplyUnchecked, { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_wait_for_reply64); unsafe { let reply = xcb_wait_for_reply64(self.c, cookie.sequence(), ptr::null_mut()); self.handle_reply_unchecked::(reply) @@ -1885,6 +2017,9 @@ impl Connection { where C: CookieWithReplyChecked, { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_poll_for_reply64); + unsafe { let mut error: *mut xcb_generic_error_t = ptr::null_mut(); let mut reply: *mut c_void = ptr::null_mut(); @@ -1976,6 +2111,9 @@ impl Connection { where C: CookieWithReplyUnchecked, { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_poll_for_reply64); + unsafe { let mut reply: *mut c_void = ptr::null_mut(); @@ -2004,6 +2142,8 @@ impl Connection { /// Since: libxcb 1.14 #[cfg(feature = "libxcb_v1_14")] pub fn total_read(&self) -> usize { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_total_read); unsafe { xcb_total_read(self.c) as usize } } @@ -2017,6 +2157,8 @@ impl Connection { /// Since: libxcb 1.14 #[cfg(feature = "libxcb_v1_14")] pub fn total_written(&self) -> usize { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_total_written); unsafe { xcb_total_written(self.c) as usize } } } @@ -2087,6 +2229,8 @@ impl AsRef for Connection { impl AsRawFd for Connection { fn as_raw_fd(&self) -> RawFd { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_get_file_descriptor); unsafe { xcb_get_file_descriptor(self.c) } } } @@ -2109,17 +2253,28 @@ impl Drop for Connection { } if self.should_drop { - #[cfg(not(feature = "xlib_xcb"))] + #[cfg(not(any(feature = "xlib_xcb", feature = "xlib_xcb_dl")))] unsafe { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_disconnect); xcb_disconnect(self.c); } - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] unsafe { if self.dpy.is_null() { + #[cfg(feature = "dl")] + xcb_get_conn_funcs!(self, xcb_disconnect); xcb_disconnect(self.c); } else { - xlib::XCloseDisplay(self.dpy); + #[cfg(all(feature = "xlib_xcb", not(feature = "xlib_xcb_dl")))] + let xclose_display = xlib::XCloseDisplay; + #[cfg(feature = "xlib_xcb_dl")] + let (xclose_display, _lib) = { + let lib = xlib::Xlib::open().expect("X11-xcb library not loaded"); + (lib.XCloseDisplay, lib) + }; + xclose_display(self.dpy); } } } @@ -2167,6 +2322,8 @@ mod dan { } unsafe fn check_connection_error(conn: *mut xcb_connection_t) -> ConnResult<()> { + #[cfg(feature = "dl")] + xcb_get_funcs!(xcb_connection_has_error); match xcb_connection_has_error(conn) { 0 => Ok(()), XCB_CONN_ERROR => Err(ConnError::Connection), diff --git a/src/ext.rs b/src/ext.rs index 646e392bebf..e7a2c2315ec 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -1,7 +1,9 @@ use crate::base::{Connection, Reply}; -use crate::ffi::{ - xcb_connection_t, xcb_extension_t, xcb_get_extension_data, xcb_prefetch_extension_data, -}; +#[cfg(feature = "dl")] +use crate::ffi::XcbLib; +use crate::ffi::{xcb_connection_t, xcb_extension_t}; +#[cfg(not(feature = "dl"))] +use crate::ffi::{xcb_get_extension_data, xcb_prefetch_extension_data}; use crate::x; use std::fmt; @@ -281,6 +283,9 @@ pub fn cache_extensions_data( mandatory: &[Extension], optional: &[Extension], ) -> Vec { + #[cfg(feature = "dl")] + crate::ffi::dl::xcb_get_funcs_expect!(xcb_prefetch_extension_data, xcb_get_extension_data); + unsafe { for ext in mandatory { let ext_id = get_extension_id(*ext); diff --git a/src/ffi/base.rs b/src/ffi/base.rs index 4e69785fa45..e6c391b6dee 100644 --- a/src/ffi/base.rs +++ b/src/ffi/base.rs @@ -1,7 +1,5 @@ -use crate::AuthInfo; - use super::ext::*; -use libc::{c_char, c_int, c_uint, c_void}; +use libc::{c_char, c_int, c_uint, c_void, iovec}; /// Current protocol version pub const X_PROTOCOL: u32 = 11; @@ -114,8 +112,18 @@ pub(crate) struct xcb_auth_info_t { pub data: *mut c_char, } -#[link(name = "xcb")] -extern "C" { +#[cfg(feature = "dl")] +pub(crate) use crate::ffi::dl::define_api_dynamic as define_api; + +#[cfg(not(feature = "dl"))] +pub(crate) use crate::ffi::dl::define_api_link as define_api; + +define_api! { + pub(crate) XcbLib XCBLIB_CACHE + libs: ["libxcb.so.1", "libxcb.so"] + link: "xcb" + + functions: pub(crate) fn xcb_flush(c: *mut xcb_connection_t) -> c_int; pub(crate) fn xcb_get_maximum_request_length(c: *mut xcb_connection_t) -> u32; @@ -206,4 +214,208 @@ extern "C" { pub(crate) fn xcb_total_read(c: *mut xcb_connection_t) -> u64; pub(crate) fn xcb_total_written(c: *mut xcb_connection_t) -> u64; + + // ext + pub(crate) fn xcb_send_request( + c: *mut xcb_connection_t, + flags: c_int, + vector: *mut iovec, + request: *const xcb_protocol_request_t, + ) -> c_uint; + + /** + * @brief Send a request to the server. + * @param c The connection to the X server. + * @param flags A combination of flags from the xcb_send_request_flags_t enumeration. + * @param vector Data to send; must have two iovecs before start for internal use. + * @param request Information about the request to be sent. + * @param num_fds Number of additional file descriptors to send to the server + * @param fds Additional file descriptors that should be send to the server. + * @return The request's sequence number on success, 0 otherwise. + * + * This function sends a new request to the X server. The data of the request is + * given as an array of @c iovecs in the @p vector argument. The length of that + * array and the necessary management information are given in the @p request + * argument. + * + * If @p num_fds is non-zero, @p fds points to an array of file descriptors that + * will be sent to the X server along with this request. After this function + * returns, all file descriptors sent are owned by xcb and will be closed + * eventually. + * + * When this function returns, the request might or might not be sent already. + * Use xcb_flush() to make sure that it really was sent. + * + * Please note that this function is not the preferred way for sending requests. + * + * Please note that xcb might use index -1 and -2 of the @p vector array internally, + * so they must be valid! + */ + pub(crate) fn xcb_send_request_with_fds( + c: *mut xcb_connection_t, + flags: c_int, + vector: *mut iovec, + request: *const xcb_protocol_request_t, + num_fds: c_uint, + fds: *mut c_int, + ) -> c_uint; + + pub(crate) fn xcb_send_request64( + c: *mut xcb_connection_t, + flags: c_int, + vector: *mut iovec, + request: *const xcb_protocol_request_t, + ) -> u64; + + /** + * @brief Send a request to the server, with 64-bit sequence number returned. + * @param c The connection to the X server. + * @param flags A combination of flags from the xcb_send_request_flags_t enumeration. + * @param vector Data to send; must have two iovecs before start for internal use. + * @param request Information about the request to be sent. + * @param num_fds Number of additional file descriptors to send to the server + * @param fds Additional file descriptors that should be send to the server. + * @return The request's sequence number on success, 0 otherwise. + * + * This function sends a new request to the X server. The data of the request is + * given as an array of @c iovecs in the @p vector argument. The length of that + * array and the necessary management information are given in the @p request + * argument. + * + * If @p num_fds is non-zero, @p fds points to an array of file descriptors that + * will be sent to the X server along with this request. After this function + * returns, all file descriptors sent are owned by xcb and will be closed + * eventually. + * + * When this function returns, the request might or might not be sent already. + * Use xcb_flush() to make sure that it really was sent. + * + * Please note that this function is not the preferred way for sending requests. + * It's better to use the generated wrapper functions. + * + * Please note that xcb might use index -1 and -2 of the @p vector array internally, + * so they must be valid! + */ + pub(crate) fn xcb_send_request_with_fds64( + c: *mut xcb_connection_t, + flags: c_int, + vector: *mut iovec, + request: *const xcb_protocol_request_t, + num_fds: c_uint, + fds: *mut c_int, + ) -> u64; + + /** + * @brief Send a file descriptor to the server in the next call to xcb_send_request. + * @param c The connection to the X server. + * @param fd The file descriptor to send. + * + * After this function returns, the file descriptor given is owned by xcb and + * will be closed eventually. + * + * @deprecated This function cannot be used in a thread-safe way. Two threads + * that run xcb_send_fd(); xcb_send_request(); could mix up their file + * descriptors. Instead, xcb_send_request_with_fds() should be used. + */ + pub(crate) fn xcb_send_fd(c: *mut xcb_connection_t, fd: c_int); + + /** no-run + * @brief Take over the write side of the socket + * @param c The connection to the X server. + * @param return_socket Callback function that will be called when xcb wants + * to use the socket again. + * @param closure Argument to the callback function. + * @param flags A combination of flags from the xcb_send_request_flags_t enumeration. + * @param sent Location to the sequence number of the last sequence request. + * Must not be NULL. + * @return 1 on success, else 0. + * + * xcb_take_socket allows external code to ask XCB for permission to + * take over the write side of the socket and send raw data with + * xcb_writev. xcb_take_socket provides the sequence number of the last + * request XCB sent. The caller of xcb_take_socket must supply a + * callback which XCB can call when it wants the write side of the + * socket back to make a request. This callback synchronizes with the + * external socket owner and flushes any output queues if appropriate. + * If you are sending requests which won't cause a reply, please note the + * comment for xcb_writev which explains some sequence number wrap issues. + * + * All replies that are generated while the socket is owned externally have + * @p flags applied to them. For example, use XCB_REQUEST_CHECK if you don't + * want errors to go to xcb's normal error handling, but instead having to be + * picked up via xcb_wait_for_reply(), xcb_poll_for_reply() or + * xcb_request_check(). + */ + pub(crate) fn xcb_take_socket( + c: *mut xcb_connection_t, + return_socket: extern "C" fn(closure: *mut c_void), + closure: *mut c_void, + flags: c_int, + sent: *mut u64, + ) -> c_int; + + /** + * @brief Send raw data to the X server. + * @param c The connection to the X server. + * @param vector Array of data to be sent. + * @param count Number of entries in @p vector. + * @param requests Number of requests that are being sent. + * @return 1 on success, else 0. + * + * You must own the write-side of the socket (you've called + * xcb_take_socket, and haven't returned from return_socket yet) to call + * xcb_writev. Also, the iovec must have at least 1 byte of data in it. + * You have to make sure that xcb can detect sequence number wraps correctly. + * This means that the first request you send after xcb_take_socket must cause a + * reply (e.g. just insert a GetInputFocus request). After every (1 << 16) - 1 + * requests without a reply, you have to insert a request which will cause a + * reply. You can again use GetInputFocus for this. You do not have to wait for + * any of the GetInputFocus replies, but can instead handle them via + * xcb_discard_reply(). + */ + pub(crate) fn xcb_writev( + c: *mut xcb_connection_t, + vector: *mut iovec, + count: c_int, + requests: u64, + ) -> c_int; + + pub(crate) fn xcb_wait_for_reply( + c: *mut xcb_connection_t, + request: c_uint, + e: *mut *mut xcb_generic_error_t, + ) -> *mut c_void; + + pub(crate) fn xcb_wait_for_reply64( + c: *mut xcb_connection_t, + request: u64, + e: *mut *mut xcb_generic_error_t, + ) -> *mut c_void; + + pub(crate) fn xcb_poll_for_reply( + c: *mut xcb_connection_t, + request: c_uint, + reply: *mut *mut c_void, + e: *mut *mut xcb_generic_error_t, + ) -> c_int; + + pub(crate) fn xcb_poll_for_reply64( + c: *mut xcb_connection_t, + request: u64, + reply: *mut *mut c_void, + e: *mut *mut xcb_generic_error_t, + ) -> c_int; + + /** + * @brief Don't use this, only needed by the generated code. + * @param c The connection to the X server. + * @param reply A reply that was received from the server + * @param replylen The size of the reply. + * @return Pointer to the location where received file descriptors are stored. + */ + pub(crate) fn xcb_get_reply_fds( + c: *mut xcb_connection_t, + reply: *mut c_void, + replylen: usize, + ) -> *mut c_int; } diff --git a/src/ffi/dl.rs b/src/ffi/dl.rs new file mode 100644 index 00000000000..567ef9c5791 --- /dev/null +++ b/src/ffi/dl.rs @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2013 James Miller + * Copyright (c) 2016 + * Remi Thebault + * Thomas Bracht Laumann Jespersen + * Copyright (c) 2025 Tolga Mizrak + * + * Permission is hereby granted, free of charge, to any + * person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the + * Software without restriction, including without + * limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software + * is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice + * shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF + * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT + * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#[cfg(feature = "dl")] +mod dl_impl { + use std::error::Error; + use std::fmt::{Display, Formatter}; + use std::sync::PoisonError; + + #[derive(Debug)] + pub enum OpenError { + Library(libloading::Error), + InternalError, + } + + impl Display for OpenError { + fn fmt(&self, f: &mut Formatter) -> Result<(), ::std::fmt::Error> { + let detail = match self { + OpenError::Library(detail) => { + f.write_str("Library")?; + detail.to_string() + } + OpenError::InternalError => { + f.write_str("InternalError")?; + String::new() + } + }; + if !detail.is_empty() { + f.write_fmt(format_args!(" ({})", detail))?; + } + Ok(()) + } + } + + impl Error for OpenError {} + + impl From for OpenError { + fn from(value: libloading::Error) -> Self { + OpenError::Library(value) + } + } + + impl From> for OpenError { + fn from(_: PoisonError) -> Self { + OpenError::InternalError + } + } + + pub unsafe fn load_library( + prefix: Option<&str>, + names: &[&str], + ) -> Result { + use std::path::{Path, PathBuf}; + + debug_assert!(!names.is_empty()); + let mut last_error = None; + + if prefix.is_some() { + match load_library(None, names) { + Ok(lib) => return Ok(lib), + Err(err) => last_error = Some(err), + } + } + + for name in names { + let realpath = match prefix { + Some(prefix) => Path::new(prefix).join(name), + None => PathBuf::from(name), + }; + match libloading::Library::new(realpath) { + Ok(lib) => return Ok(lib), + Err(err) => last_error = Some(err), + } + } + + Err(last_error.unwrap()) + } + + macro_rules! xcb_get_funcs { + ($($name:ident),*) => { + let lib = crate::ffi::XcbLib::open()?; + $( + let $name = lib.$name; + )* + }; + } + pub(crate) use xcb_get_funcs; +} + +#[cfg(feature = "dl")] +pub(crate) use dl_impl::*; + +/// This macro either defines extern functions to link to, or a wrapper around a dynamically loaded library, +/// depending on whether the crate is build with the "dl" flag or not. +/// The wrapper struct is shareable (through clone()) and will keep the function pointers alive, +/// as long as the struct lives. +#[cfg(feature = "dl")] +macro_rules! define_api_dynamic { + ( + $(#[$smeta:meta])* + $svis:vis $sname:ident $cache:ident + libs: [$($lname:literal),+] + link: $link_name:literal + functions: $($(#[$meta:meta])* $fvis:vis fn $fname:ident($($farg:ident : $ftype:ty),*$(,)?) $(-> $fret:ty)?);+; + ) => { + $(#[$smeta])* + #[derive(Clone)] + $svis struct $sname { + // By putting the library handle into an Arc we allow this struct to be shareable, + // but don't pay for the atomic load when just using the function pointers. + _lib: std::sync::Arc, + $( + $(#[$meta])* + $fvis $fname: unsafe extern "C" fn($($farg : $ftype),*) $(-> $fret)? + ),+, + } + + // Global cache so that if the library is already loaded, we just return a reference + // counted clone. + static $cache: std::sync::RwLock> = std::sync::RwLock::new(None); + + impl $sname { + $svis fn open() -> Result { + // Try to read at first, whether the library was already loaded. + { + let lock = $cache.read()?; + if let Some(lib) = lock.as_ref() { + return Ok(lib.clone()); + } + } + + let mut lock = $cache.write()?; + // Between the read and write lock, someone might have already loaded the library. + if let Some(lib) = lock.as_ref() { + return Ok(lib.clone()); + } + + let lib = unsafe { + let lib = std::sync::Arc::new(crate::ffi::dl::load_library(None, &[$($lname),+])?); + Self { + $( + $fname: *lib.get(concat!(stringify!($fname), "\0").as_bytes())? + ),+, + _lib: lib, + } + }; + *lock = Some(lib.clone()); + Ok(lib) + } + + /// Unloads the current cached instance of the library. + /// Doesn't prevent another open of the library and recreation of the cache, + /// if they are used again after unloading. + pub fn unload() -> Result<(), crate::ffi::dl::OpenError> { + let mut lock = $cache.write()?; + *lock = None; + Ok(()) + } + } + }; +} + +#[cfg(feature = "dl")] +pub(crate) use define_api_dynamic; + +#[cfg(feature = "dl")] +macro_rules! xcb_get_funcs_expect { + ($($name:ident),*) => { + let lib = crate::ffi::XcbLib::open().expect("xcb library not loaded"); + $( + let $name = lib.$name; + )* + }; +} +#[cfg(feature = "dl")] +pub(crate) use xcb_get_funcs_expect; + +#[cfg(not(feature = "dl"))] +macro_rules! define_api_link { + ( + $(#[$_:meta])* + $svis:vis $sname:ident $cache:ident + libs: [$($lname:literal),+] + link: $link_name:literal + functions: $($(#[$meta:meta])* $fvis:vis fn $fname:ident($($farg:ident : $ftype:ty),*$(,)?) $(-> $fret:ty)?);+; + ) => { + #[link(name = $link_name)] + extern "C" { + $( + $(#[$meta])* + $fvis fn $fname($($farg : $ftype),*) $(-> $fret)?; + )+ + } + }; +} + +#[cfg(not(feature = "dl"))] +pub(crate) use define_api_link; diff --git a/src/ffi/ext.rs b/src/ffi/ext.rs index 1e283f281bd..43513319cd3 100644 --- a/src/ffi/ext.rs +++ b/src/ffi/ext.rs @@ -30,7 +30,7 @@ */ use crate::ffi::base::*; -use libc::{c_char, c_int, c_uint, c_void, iovec}; +use libc::{c_char, c_int}; #[derive(Debug, Copy, Clone)] #[repr(C)] @@ -54,209 +54,3 @@ pub(crate) enum xcb_send_request_flags_t { XCB_REQUEST_DISCARD_REPLY = 1 << 2, XCB_REQUEST_REPLY_FDS = 1 << 3, } - -#[link(name = "xcb")] -extern "C" { - pub(crate) fn xcb_send_request( - c: *mut xcb_connection_t, - flags: c_int, - vector: *mut iovec, - request: *const xcb_protocol_request_t, - ) -> c_uint; - - /** - * @brief Send a request to the server. - * @param c The connection to the X server. - * @param flags A combination of flags from the xcb_send_request_flags_t enumeration. - * @param vector Data to send; must have two iovecs before start for internal use. - * @param request Information about the request to be sent. - * @param num_fds Number of additional file descriptors to send to the server - * @param fds Additional file descriptors that should be send to the server. - * @return The request's sequence number on success, 0 otherwise. - * - * This function sends a new request to the X server. The data of the request is - * given as an array of @c iovecs in the @p vector argument. The length of that - * array and the necessary management information are given in the @p request - * argument. - * - * If @p num_fds is non-zero, @p fds points to an array of file descriptors that - * will be sent to the X server along with this request. After this function - * returns, all file descriptors sent are owned by xcb and will be closed - * eventually. - * - * When this function returns, the request might or might not be sent already. - * Use xcb_flush() to make sure that it really was sent. - * - * Please note that this function is not the preferred way for sending requests. - * - * Please note that xcb might use index -1 and -2 of the @p vector array internally, - * so they must be valid! - */ - pub(crate) fn xcb_send_request_with_fds( - c: *mut xcb_connection_t, - flags: c_int, - vector: *mut iovec, - request: *const xcb_protocol_request_t, - num_fds: c_uint, - fds: *mut c_int, - ) -> c_uint; - - pub(crate) fn xcb_send_request64( - c: *mut xcb_connection_t, - flags: c_int, - vector: *mut iovec, - request: *const xcb_protocol_request_t, - ) -> u64; - - /** - * @brief Send a request to the server, with 64-bit sequence number returned. - * @param c The connection to the X server. - * @param flags A combination of flags from the xcb_send_request_flags_t enumeration. - * @param vector Data to send; must have two iovecs before start for internal use. - * @param request Information about the request to be sent. - * @param num_fds Number of additional file descriptors to send to the server - * @param fds Additional file descriptors that should be send to the server. - * @return The request's sequence number on success, 0 otherwise. - * - * This function sends a new request to the X server. The data of the request is - * given as an array of @c iovecs in the @p vector argument. The length of that - * array and the necessary management information are given in the @p request - * argument. - * - * If @p num_fds is non-zero, @p fds points to an array of file descriptors that - * will be sent to the X server along with this request. After this function - * returns, all file descriptors sent are owned by xcb and will be closed - * eventually. - * - * When this function returns, the request might or might not be sent already. - * Use xcb_flush() to make sure that it really was sent. - * - * Please note that this function is not the preferred way for sending requests. - * It's better to use the generated wrapper functions. - * - * Please note that xcb might use index -1 and -2 of the @p vector array internally, - * so they must be valid! - */ - pub(crate) fn xcb_send_request_with_fds64( - c: *mut xcb_connection_t, - flags: c_int, - vector: *mut iovec, - request: *const xcb_protocol_request_t, - num_fds: c_uint, - fds: *mut c_int, - ) -> u64; - - /** - * @brief Send a file descriptor to the server in the next call to xcb_send_request. - * @param c The connection to the X server. - * @param fd The file descriptor to send. - * - * After this function returns, the file descriptor given is owned by xcb and - * will be closed eventually. - * - * @deprecated This function cannot be used in a thread-safe way. Two threads - * that run xcb_send_fd(); xcb_send_request(); could mix up their file - * descriptors. Instead, xcb_send_request_with_fds() should be used. - */ - pub(crate) fn xcb_send_fd(c: *mut xcb_connection_t, fd: c_int); - - /** - * @brief Take over the write side of the socket - * @param c The connection to the X server. - * @param return_socket Callback function that will be called when xcb wants - * to use the socket again. - * @param closure Argument to the callback function. - * @param flags A combination of flags from the xcb_send_request_flags_t enumeration. - * @param sent Location to the sequence number of the last sequence request. - * Must not be NULL. - * @return 1 on success, else 0. - * - * xcb_take_socket allows external code to ask XCB for permission to - * take over the write side of the socket and send raw data with - * xcb_writev. xcb_take_socket provides the sequence number of the last - * request XCB sent. The caller of xcb_take_socket must supply a - * callback which XCB can call when it wants the write side of the - * socket back to make a request. This callback synchronizes with the - * external socket owner and flushes any output queues if appropriate. - * If you are sending requests which won't cause a reply, please note the - * comment for xcb_writev which explains some sequence number wrap issues. - * - * All replies that are generated while the socket is owned externally have - * @p flags applied to them. For example, use XCB_REQUEST_CHECK if you don't - * want errors to go to xcb's normal error handling, but instead having to be - * picked up via xcb_wait_for_reply(), xcb_poll_for_reply() or - * xcb_request_check(). - */ - pub(crate) fn xcb_take_socket( - c: *mut xcb_connection_t, - return_socket: fn(closure: *mut c_void), - closure: *mut c_void, - flags: c_int, - sent: *mut u64, - ) -> c_int; - - /** - * @brief Send raw data to the X server. - * @param c The connection to the X server. - * @param vector Array of data to be sent. - * @param count Number of entries in @p vector. - * @param requests Number of requests that are being sent. - * @return 1 on success, else 0. - * - * You must own the write-side of the socket (you've called - * xcb_take_socket, and haven't returned from return_socket yet) to call - * xcb_writev. Also, the iovec must have at least 1 byte of data in it. - * You have to make sure that xcb can detect sequence number wraps correctly. - * This means that the first request you send after xcb_take_socket must cause a - * reply (e.g. just insert a GetInputFocus request). After every (1 << 16) - 1 - * requests without a reply, you have to insert a request which will cause a - * reply. You can again use GetInputFocus for this. You do not have to wait for - * any of the GetInputFocus replies, but can instead handle them via - * xcb_discard_reply(). - */ - pub(crate) fn xcb_writev( - c: *mut xcb_connection_t, - vector: *mut iovec, - count: c_int, - requests: u64, - ) -> c_int; - - pub(crate) fn xcb_wait_for_reply( - c: *mut xcb_connection_t, - request: c_uint, - e: *mut *mut xcb_generic_error_t, - ) -> *mut c_void; - - pub(crate) fn xcb_wait_for_reply64( - c: *mut xcb_connection_t, - request: u64, - e: *mut *mut xcb_generic_error_t, - ) -> *mut c_void; - - pub(crate) fn xcb_poll_for_reply( - c: *mut xcb_connection_t, - request: c_uint, - reply: *mut *mut c_void, - e: *mut *mut xcb_generic_error_t, - ) -> c_int; - - pub(crate) fn xcb_poll_for_reply64( - c: *mut xcb_connection_t, - request: u64, - reply: *mut *mut c_void, - e: *mut *mut xcb_generic_error_t, - ) -> c_int; - - /** - * @brief Don't use this, only needed by the generated code. - * @param c The connection to the X server. - * @param reply A reply that was received from the server - * @param replylen The size of the reply. - * @return Pointer to the location where received file descriptors are stored. - */ - pub(crate) fn xcb_get_reply_fds( - c: *mut xcb_connection_t, - reply: *mut c_void, - replylen: usize, - ) -> *mut c_int; -} diff --git a/src/ffi/xlib_xcb.rs b/src/ffi/xlib_xcb.rs index 36b05b5532b..e31f7a4e20d 100644 --- a/src/ffi/xlib_xcb.rs +++ b/src/ffi/xlib_xcb.rs @@ -35,9 +35,13 @@ use crate::ffi::xcb_connection_t; use libc::c_uint; +#[cfg(all(feature = "xlib_xcb", not(feature = "xlib_xcb_dl")))] use x11::xlib; -/// Type for [XSetEventQueueOwner] owner parameter +#[cfg(feature = "xlib_xcb_dl")] +use x11_dl::xlib; + +/// Type for [XlibXcbLib::XSetEventQueueOwner] owner parameter /// /// This item is behind the `xlib_xcb` cargo feature. pub type XEventQueueOwner = c_uint; @@ -52,14 +56,26 @@ pub const XlibOwnsEventQueue: XEventQueueOwner = 0; /// This item is behind the `xlib_xcb` cargo feature. pub const XCBOwnsEventQueue: XEventQueueOwner = 1; -#[link(name = "X11-xcb")] -extern "C" { +#[cfg(feature = "xlib_xcb_dl")] +use super::dl::define_api_dynamic as define_api; + +#[cfg(not(feature = "xlib_xcb_dl"))] +use super::dl::define_api_link as define_api; + +define_api! { + /// Dynamically loaded X11-xcb library. + pub XlibXcbLib XLIBXCBLIB_CACHE + libs: ["libX11-xcb.so.1", "libX11-xcb.so"] + link: "X11-xcb" + + functions: + /// Get an XCB connection from the `xlib::Display`. /// - /// This function is behind the `xlib_xcb` cargo feature. + /// This function is behind the `xlib_xcb`/`xlib_xcb_dl` cargo features. pub fn XGetXCBConnection(dpy: *mut xlib::Display) -> *mut xcb_connection_t; /// Set the owner of the X client event queue. /// - /// This function is behind the `xlib_xcb` cargo feature. + /// This function is behind the `xlib_xcb`/`xlib_xcb_dl` cargo features. pub fn XSetEventQueueOwner(dpy: *mut xlib::Display, owner: XEventQueueOwner); } diff --git a/src/lib.rs b/src/lib.rs index 29d7c118e72..4b271ae60d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -239,12 +239,18 @@ //! //! The following Cargo features are available //! -//! ## `xlib_xcb` +//! ## `dl` +//! +//! When this feature is activated, libxcb will be loaded dynamically at runtime with dlopen, +//! instead of using dynamic linking. +//! +//! ## `xlib_xcb` or `xlib_xcb_dl` //! //! This feature activates the use of `xlib::Display` to connect to XCB, therefore making //! available both Xlib and XCB functions to the same connection. //! While XCB is sufficient to handle all communication with the X server, some things can //! still only be done by Xlib. E.g. hardware initialization for OpenGL is done by Xlib only. +//! Use `xlib_xcb_dl` instead to load the xlib library dynamically with dlopen. //! //! ## `debug_atom_names` //! @@ -328,13 +334,15 @@ pub mod ffi { pub(crate) mod base; pub(crate) mod ext; - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] pub(crate) mod xlib_xcb; + pub(crate) mod dl; + pub use base::*; pub use ext::*; - #[cfg(feature = "xlib_xcb")] + #[cfg(any(feature = "xlib_xcb", feature = "xlib_xcb_dl"))] pub use xlib_xcb::*; }