1 | // x11-rs: Rust bindings for X11 libraries |
2 | // The X11 libraries are available under the MIT license. |
3 | // These bindings are public domain. |
4 | |
5 | use std::ffi::{CStr, CString}; |
6 | use std::os::raw::{c_char, c_void}; |
7 | use std::path::Path; |
8 | |
9 | use super::error::{OpenError, OpenErrorKind}; |
10 | |
11 | include!(concat!(env!("OUT_DIR" ), "/config.rs" )); |
12 | |
13 | // |
14 | // x11_link! |
15 | // |
16 | |
17 | macro_rules! x11_link { |
18 | { $struct_name:ident, $pkg_name:ident, [$($lib_name:expr),*], $nsyms:expr, |
19 | $(pub fn $fn_name:ident ($($param_name:ident : $param_type:ty),*) -> $ret_type:ty,)* |
20 | variadic: |
21 | $(pub fn $vfn_name:ident ($($vparam_name: ident : $vparam_type:ty),+) -> $vret_type:ty,)* |
22 | globals: |
23 | $(pub static $var_name:ident : $var_type:ty,)* |
24 | } => { |
25 | #[allow(clippy::manual_non_exhaustive)] |
26 | pub struct $struct_name { |
27 | _private: (), |
28 | $(pub $fn_name: unsafe extern "C" fn ($($param_type),*) -> $ret_type,)* |
29 | $(pub $vfn_name: unsafe extern "C" fn ($($vparam_type),+, ...) -> $vret_type,)* |
30 | $(pub $var_name: *mut $var_type,)* |
31 | } |
32 | |
33 | unsafe impl Send for $struct_name {} |
34 | unsafe impl Sync for $struct_name {} |
35 | |
36 | impl $struct_name { |
37 | pub fn open () -> Result<$struct_name, $crate::error::OpenError> { |
38 | /// Cached function pointers and global variables for X11 libraries. |
39 | static CACHED: once_cell::sync::OnceCell<($crate::link::DynamicLibrary, $struct_name)> = once_cell::sync::OnceCell::new(); |
40 | |
41 | // Use the cached library or open a new one. |
42 | let (_, funcs) = CACHED.get_or_try_init(|| { |
43 | unsafe { |
44 | let libdir = $crate::link::config::libdir::$pkg_name; |
45 | let lib = $crate::link::DynamicLibrary::open_multi(libdir, &[$($lib_name),*])?; |
46 | |
47 | // Load every function pointer. |
48 | let funcs = $struct_name { |
49 | _private: (), |
50 | $($fn_name: ::std::mem::transmute(lib.symbol(stringify!($fn_name))?),)* |
51 | $($vfn_name: ::std::mem::transmute(lib.symbol(stringify!($vfn_name))?),)* |
52 | $($var_name: ::std::mem::transmute(lib.symbol(stringify!($var_name))?),)* |
53 | }; |
54 | |
55 | Ok((lib, funcs)) |
56 | } |
57 | })?; |
58 | |
59 | Ok($struct_name { |
60 | _private: (), |
61 | $($fn_name: funcs.$fn_name,)* |
62 | $($vfn_name: funcs.$vfn_name,)* |
63 | $($var_name: funcs.$var_name,)* |
64 | }) |
65 | } |
66 | } |
67 | }; |
68 | } |
69 | |
70 | // |
71 | // DynamicLibrary |
72 | // |
73 | |
74 | pub struct DynamicLibrary { |
75 | handle: *mut c_void, |
76 | } |
77 | |
78 | impl DynamicLibrary { |
79 | pub fn open(name: &str) -> Result<DynamicLibrary, OpenError> { |
80 | unsafe { |
81 | let cname = match CString::new(name) { |
82 | Ok(cname) => cname, |
83 | Err(_) => { |
84 | return Err(OpenError::new( |
85 | OpenErrorKind::Library, |
86 | String::from("library name contains NUL byte(s)" ), |
87 | )); |
88 | } |
89 | }; |
90 | |
91 | let handle = libc::dlopen(cname.as_ptr(), libc::RTLD_LAZY); |
92 | |
93 | if handle.is_null() { |
94 | let msg = libc::dlerror(); |
95 | |
96 | if msg.is_null() { |
97 | return Err(OpenError::new(OpenErrorKind::Library, String::new())); |
98 | } |
99 | |
100 | let cmsg = CStr::from_ptr(msg as *const c_char); |
101 | let detail = cmsg.to_string_lossy().into_owned(); |
102 | return Err(OpenError::new(OpenErrorKind::Library, detail)); |
103 | } |
104 | |
105 | Ok(DynamicLibrary { |
106 | handle: handle as *mut c_void, |
107 | }) |
108 | } |
109 | } |
110 | |
111 | pub fn open_multi( |
112 | libdir: Option<&'static str>, |
113 | names: &[&str], |
114 | ) -> Result<DynamicLibrary, OpenError> { |
115 | assert!(!names.is_empty()); |
116 | |
117 | let paths = libdir.map_or(Vec::new(), |dir| { |
118 | let path = Path::new(dir); |
119 | names |
120 | .iter() |
121 | .map(|name| path.join(name).to_str().unwrap().to_string()) |
122 | .collect::<Vec<_>>() |
123 | }); |
124 | |
125 | let mut msgs = Vec::new(); |
126 | |
127 | for name in names.iter().copied().chain(paths.iter().map(|x| &**x)) { |
128 | match DynamicLibrary::open(name) { |
129 | Ok(lib) => { |
130 | return Ok(lib); |
131 | } |
132 | Err(err) => { |
133 | msgs.push(format!(" {}" , err)); |
134 | } |
135 | } |
136 | } |
137 | |
138 | let mut detail = String::new(); |
139 | |
140 | for (i, msg) in msgs.iter().enumerate() { |
141 | if i != 0 { |
142 | detail.push_str("; " ); |
143 | } |
144 | detail.push_str(msg.as_ref()); |
145 | } |
146 | |
147 | Err(OpenError::new(OpenErrorKind::Library, detail)) |
148 | } |
149 | |
150 | pub fn symbol(&self, name: &str) -> Result<*mut c_void, OpenError> { |
151 | unsafe { |
152 | let cname = match CString::new(name) { |
153 | Ok(cname) => cname, |
154 | Err(_) => { |
155 | return Err(OpenError::new( |
156 | OpenErrorKind::Symbol, |
157 | String::from("symbol name contains NUL byte(s)" ), |
158 | )); |
159 | } |
160 | }; |
161 | |
162 | let sym = libc::dlsym(self.handle as *mut _, cname.as_ptr()); |
163 | |
164 | if sym.is_null() { |
165 | let msg = libc::dlerror(); |
166 | |
167 | if msg.is_null() { |
168 | return Err(OpenError::new(OpenErrorKind::Symbol, String::from(name))); |
169 | } |
170 | |
171 | let cmsg = CStr::from_ptr(msg as *const c_char); |
172 | let detail = format!(" {} - {}" , name, cmsg.to_string_lossy().into_owned()); |
173 | return Err(OpenError::new(OpenErrorKind::Symbol, detail)); |
174 | } |
175 | |
176 | Ok(sym as *mut c_void) |
177 | } |
178 | } |
179 | } |
180 | |
181 | impl Drop for DynamicLibrary { |
182 | fn drop (&mut self) { |
183 | unsafe { |
184 | libc::dlclose(self.handle as *mut _); |
185 | } |
186 | } |
187 | } |
188 | |
189 | unsafe impl Send for DynamicLibrary {} |
190 | unsafe impl Sync for DynamicLibrary {} |
191 | |