1//! The code in this module allows to call libxcb's C functions.
2//!
3//! At its heart, this module only contains an `extern "C"` block that defines the C functions so
4//! that they can be called from Rust.
5//!
6//! However, this module also contains the implementation of the `dl-libxcb` features. When this
7//! future is enabled, we do not link against libxcb, but instead use `libloading` to load
8//! `libxcb.so` at runtime. Most of the code is actually responsible for this later feature.
9
10use super::{
11 c_char, c_int, c_uint, c_void, iovec, xcb_connection_t, xcb_generic_error_t,
12 xcb_generic_event_t, xcb_protocol_request_t, xcb_setup_t, xcb_void_cookie_t,
13};
14
15#[cfg(feature = "dl-libxcb")]
16pub(crate) mod libxcb_library {
17 use super::LibxcbFuncs;
18 use crate::errors::LibxcbLoadError;
19
20 pub(super) struct LibxcbLibrary {
21 // Needed to keep the library loaded
22 _library: libloading::Library,
23 pub(super) funcs: LibxcbFuncs,
24 }
25
26 impl LibxcbLibrary {
27 /// # Safety
28 ///
29 /// The functions pointers in `funcs` do not have lifetime,
30 /// but they must not outlive the returned result.
31 #[cold]
32 #[inline(never)]
33 unsafe fn load() -> Result<Self, LibxcbLoadError> {
34 // TODO: Names for non-unix platforms
35 #[cfg(not(unix))]
36 compile_error!("dl-libxcb feature is not supported on non-unix");
37
38 #[cfg(all(unix, target_os = "linux"))]
39 const LIB_NAME: &str = "libxcb.so.1";
40
41 // libtool turns -version-info differently into SONAMES on NetBSD.
42 // Also, the library is apparently not in the default search path, hence use a full path.
43 #[cfg(all(unix, target_os = "netbsd"))]
44 const LIB_NAME: &str = "/usr/X11R7/lib/libxcb.so.2";
45
46 // If we do not know anything, just assume libxcb.so and hope for the best.
47 // This is actually the right thing to do on OpenBSD since the dynamic linker then does
48 // some magic to find the right SONAME.
49 #[cfg(all(unix, not(any(target_os = "linux", target_os = "netbsd"))))]
50 const LIB_NAME: &str = "libxcb.so";
51
52 let library = libloading::Library::new(LIB_NAME)
53 .map_err(|e| LibxcbLoadError::OpenLibError(LIB_NAME.into(), e.to_string()))?;
54 let funcs = LibxcbFuncs::new(&library).map_err(|(symbol, e)| {
55 LibxcbLoadError::GetSymbolError(symbol.into(), e.to_string())
56 })?;
57 Ok(Self {
58 _library: library,
59 funcs,
60 })
61 }
62 }
63
64 use once_cell::sync::Lazy;
65
66 static LIBXCB_LIBRARY: Lazy<Result<LibxcbLibrary, LibxcbLoadError>> =
67 Lazy::new(|| unsafe { LibxcbLibrary::load() });
68
69 pub(super) fn get_libxcb() -> &'static LibxcbLibrary {
70 #[cold]
71 #[inline(never)]
72 fn failed(e: &LibxcbLoadError) -> ! {
73 panic!("failed to load libxcb: {}", e);
74 }
75 match *LIBXCB_LIBRARY {
76 Ok(ref library) => library,
77 Err(ref e) => failed(e),
78 }
79 }
80
81 /// Tries to dynamically load libxcb, returning an error on failure.
82 ///
83 /// It is not required to call this function, as libxcb will be lazily loaded.
84 /// However, if a lazy load fails, a panic will be raised, missing the chance
85 /// to (nicely) handle the error.
86 ///
87 /// It is safe to call this function more than once from the same or different
88 /// threads. Only the first call will try to load libxcb, subsequent calls will
89 /// always return the same result.
90 pub fn load_libxcb() -> Result<(), LibxcbLoadError> {
91 match Lazy::force(&LIBXCB_LIBRARY) {
92 Ok(_) => Ok(()),
93 Err(e) => Err(e.clone()),
94 }
95 }
96}
97
98macro_rules! make_ffi_fn_defs {
99 {
100 $(
101 $(#[$fn_attr:meta])*
102 fn $fn_name:ident($($fn_arg_name:ident: $fn_arg_type:ty),*) $(-> $fn_ret_ty:ty)?;
103 )*
104 } => {
105 #[cfg(not(feature = "dl-libxcb"))]
106 #[link(name = "xcb")]
107 extern "C" {
108 $(
109 $(#[$fn_attr])*
110 pub(crate) fn $fn_name($($fn_arg_name: $fn_arg_type),*) $(-> $fn_ret_ty)?;
111 )*
112 }
113
114 #[cfg(feature = "dl-libxcb")]
115 struct LibxcbFuncs {
116 $(
117 $(#[$fn_attr])*
118 $fn_name: unsafe extern "C" fn($($fn_arg_name: $fn_arg_type),*) $(-> $fn_ret_ty)?,
119 )*
120 }
121
122 #[cfg(feature = "dl-libxcb")]
123 impl LibxcbFuncs {
124 unsafe fn new(library: &libloading::Library) -> Result<Self, (&'static [u8], libloading::Error)> {
125 Ok(Self {
126 $($fn_name: {
127 let symbol_name = concat!(stringify!($fn_name), "\0").as_bytes();
128 *library.get(symbol_name).map_err(|e| (stringify!($fn_name).as_bytes(), e))?
129 },)*
130 })
131 }
132 }
133
134 $(
135 #[cfg(feature = "dl-libxcb")]
136 $(#[$fn_attr])*
137 pub(crate) unsafe fn $fn_name($($fn_arg_name: $fn_arg_type),*) $(-> $fn_ret_ty)? {
138 (libxcb_library::get_libxcb().funcs.$fn_name)($($fn_arg_name),*)
139 }
140 )*
141 };
142}
143
144make_ffi_fn_defs! {
145 // From xcb.h
146 fn xcb_flush(c: *mut xcb_connection_t) -> c_int;
147 fn xcb_get_maximum_request_length(c: *mut xcb_connection_t) -> u32;
148 fn xcb_prefetch_maximum_request_length(c: *mut xcb_connection_t);
149 fn xcb_wait_for_event(c: *mut xcb_connection_t) -> *mut xcb_generic_event_t;
150 fn xcb_poll_for_event(c: *mut xcb_connection_t) -> *mut xcb_generic_event_t;
151 fn xcb_request_check(
152 c: *mut xcb_connection_t,
153 void_cookie: xcb_void_cookie_t
154 ) -> *mut xcb_generic_error_t;
155 fn xcb_discard_reply64(c: *mut xcb_connection_t, sequence: u64);
156 fn xcb_get_setup(c: *mut xcb_connection_t) -> *const xcb_setup_t;
157 #[cfg(unix)]
158 fn xcb_get_file_descriptor(c: *mut xcb_connection_t) -> c_int;
159 fn xcb_connection_has_error(c: *mut xcb_connection_t) -> c_int;
160 fn xcb_disconnect(c: *mut xcb_connection_t);
161 fn xcb_connect(
162 displayname: *const c_char,
163 screenp: *mut c_int
164 ) -> *mut xcb_connection_t;
165 fn xcb_generate_id(c: *mut xcb_connection_t) -> u32;
166
167 // From xcbext.h
168 fn xcb_send_request64(
169 c: *mut xcb_connection_t,
170 flags: c_int,
171 vector: *mut iovec,
172 request: *const xcb_protocol_request_t
173 ) -> u64;
174 #[cfg(unix)]
175 fn xcb_send_request_with_fds64(
176 c: *mut xcb_connection_t,
177 flags: c_int,
178 vector: *mut iovec,
179 request: *const xcb_protocol_request_t,
180 num_fds: c_uint,
181 fds: *mut c_int
182 ) -> u64;
183 fn xcb_wait_for_reply64(
184 c: *mut xcb_connection_t,
185 request: u64,
186 e: *mut *mut xcb_generic_error_t
187 ) -> *mut c_void;
188 fn xcb_poll_for_reply64(
189 c: *mut xcb_connection_t,
190 request: u64,
191 reply: *mut *mut c_void,
192 error: *mut *mut xcb_generic_error_t
193 ) -> c_int;
194}
195