tiny_xlib/
ffi.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0 OR Zlib
2
3// Copyright 2023 John Nunley
4//
5// Licensed under the Apache License, Version 2.0, the MIT License, and
6// the Zlib license. You may not use this software except in compliance
7// with at least one of these licenses. You should have received a copy
8// of these licenses with this software. You may also find them at:
9//
10//     http://www.apache.org/licenses/LICENSE-2.0
11//     https://opensource.org/licenses/MIT
12//     https://opensource.org/licenses/Zlib
13//
14// Unless required by applicable law or agreed to in writing, software
15// distributed under these licenses is distributed on an "AS IS" BASIS,
16// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17// See the licenses for the specific language governing permissions and
18// limitations under the licenses.
19
20//! FFI bindings to Xlib and XlibXCB.
21
22use as_raw_xcb_connection::xcb_connection_t;
23use std::os::raw::{c_char, c_int, c_uchar, c_ulong};
24
25// See build.rs for how this file is generated.
26#[cfg(feature = "dlopen")]
27include! {
28    concat!(env!("OUT_DIR"), "/libdir.rs")
29}
30
31/// Base type for the display pointer.
32pub(crate) enum Display {}
33
34/// The type of the error.
35#[repr(C)]
36#[derive(Clone, Copy)]
37pub(crate) struct XErrorEvent {
38    pub type_: c_int,
39    pub display: *mut Display,
40    pub resourceid: c_ulong,
41    pub serial: c_ulong,
42    pub error_code: c_uchar,
43    pub request_code: c_uchar,
44    pub minor_code: c_uchar,
45}
46
47// Function pointer types.
48type XOpenDisplay = unsafe extern "C" fn(display_name: *const c_char) -> *mut Display;
49type XCloseDisplay = unsafe extern "C" fn(display: *mut Display) -> c_int;
50type XGetXCBConnection = unsafe extern "C" fn(display: *mut Display) -> *mut xcb_connection_t;
51type XDefaultScreen = unsafe extern "C" fn(display: *mut Display) -> c_int;
52pub(crate) type XErrorHook =
53    Option<unsafe extern "C" fn(display: *mut Display, error_event: *mut XErrorEvent) -> c_int>;
54type XSetErrorHandler = unsafe extern "C" fn(handler: XErrorHook) -> XErrorHook;
55type XInitThreads = unsafe extern "C" fn() -> c_int;
56
57/// Catalogue of functions offered by Xlib.
58pub(crate) struct Xlib {
59    /// The currently loaded Xlib library.
60    #[cfg(feature = "dlopen")]
61    _xlib_library: libloading::Library,
62
63    /// The currently loaded XlibXcb library.
64    #[cfg(feature = "dlopen")]
65    _xlib_xcb_library: libloading::Library,
66
67    /// The XOpenDisplay function.
68    x_open_display: XOpenDisplay,
69
70    /// The XCloseDisplay function.
71    x_close_display: XCloseDisplay,
72
73    /// The XGetXCBConnection function.
74    x_get_xcb_connection: XGetXCBConnection,
75
76    /// The XDefaultScreen function.
77    x_default_screen: XDefaultScreen,
78
79    /// The XSetErrorHandler function.
80    x_set_error_handler: XSetErrorHandler,
81
82    /// The XInitThreads function.
83    x_init_threads: XInitThreads,
84}
85
86impl Xlib {
87    /// Open a new connection to the X server.
88    pub(crate) unsafe fn open_display(&self, display_name: *const c_char) -> *mut Display {
89        (self.x_open_display)(display_name)
90    }
91
92    /// Close a connection to the X server.
93    pub(crate) unsafe fn close_display(&self, display: *mut Display) -> c_int {
94        (self.x_close_display)(display)
95    }
96
97    /// Get the XCB connection from an Xlib display.
98    pub(crate) unsafe fn get_xcb_connection(&self, display: *mut Display) -> *mut xcb_connection_t {
99        (self.x_get_xcb_connection)(display)
100    }
101
102    /// Get the default screen index.
103    pub(crate) unsafe fn default_screen(&self, display: *mut Display) -> c_int {
104        (self.x_default_screen)(display)
105    }
106
107    /// Set the error handler.
108    pub(crate) unsafe fn set_error_handler(&self, handler: XErrorHook) -> XErrorHook {
109        (self.x_set_error_handler)(handler)
110    }
111
112    /// Initialize threads.
113    pub(crate) unsafe fn init_threads(&self) -> c_int {
114        (self.x_init_threads)()
115    }
116
117    /// Load the Xlib library at runtime.
118    #[cfg_attr(coverage, no_coverage)]
119    #[cfg(not(feature = "dlopen"))]
120    pub(crate) fn load() -> Result<Self, std::io::Error> {
121        #[link(name = "X11", kind = "dylib")]
122        extern "C" {
123            fn XOpenDisplay(display_name: *const c_char) -> *mut Display;
124            fn XCloseDisplay(display: *mut Display) -> c_int;
125            fn XDefaultScreen(display: *mut Display) -> c_int;
126            fn XSetErrorHandler(handler: XErrorHook) -> XErrorHook;
127            fn XInitThreads() -> c_int;
128        }
129
130        #[link(name = "X11-xcb", kind = "dylib")]
131        extern "C" {
132            fn XGetXCBConnection(display: *mut Display) -> *mut xcb_connection_t;
133        }
134
135        Ok(Self {
136            x_open_display: XOpenDisplay,
137            x_close_display: XCloseDisplay,
138            x_get_xcb_connection: XGetXCBConnection,
139            x_default_screen: XDefaultScreen,
140            x_set_error_handler: XSetErrorHandler,
141            x_init_threads: XInitThreads,
142        })
143    }
144
145    /// Load the Xlib library at runtime.
146    #[cfg_attr(coverage, no_coverage)]
147    #[cfg(feature = "dlopen")]
148    pub(crate) fn load() -> Result<Self, libloading::Error> {
149        let xlib_library = unsafe { load_library(XLIB_LIBDIR, &["libX11.so.6", "libX11.so"]) }?;
150        let xlib_xcb_library =
151            unsafe { load_library(XLIB_XCB_LIBDIR, &["libX11-xcb.so.1", "libX11-xcb.so"]) }?;
152
153        let x_open_display = unsafe { xlib_library.get::<XOpenDisplay>(b"XOpenDisplay\0")? };
154
155        let x_close_display = unsafe { xlib_library.get::<XCloseDisplay>(b"XCloseDisplay\0")? };
156
157        let x_set_error_handler =
158            unsafe { xlib_library.get::<XSetErrorHandler>(b"XSetErrorHandler\0")? };
159
160        let x_default_screen = unsafe { xlib_library.get::<XDefaultScreen>(b"XDefaultScreen\0")? };
161
162        let x_get_xcb_connection =
163            unsafe { xlib_xcb_library.get::<XGetXCBConnection>(b"XGetXCBConnection\0")? };
164
165        let x_init_threads = unsafe { xlib_library.get::<XInitThreads>(b"XInitThreads\0")? };
166
167        Ok(Self {
168            x_open_display: *x_open_display,
169            x_close_display: *x_close_display,
170            x_get_xcb_connection: *x_get_xcb_connection,
171            x_default_screen: *x_default_screen,
172            x_set_error_handler: *x_set_error_handler,
173            x_init_threads: *x_init_threads,
174            _xlib_library: xlib_library,
175            _xlib_xcb_library: xlib_xcb_library,
176        })
177    }
178}
179
180#[cfg(feature = "dlopen")]
181#[cfg_attr(coverage, no_coverage)]
182unsafe fn load_library(
183    prefix: Option<&str>,
184    names: &[&str],
185) -> Result<libloading::Library, libloading::Error> {
186    use std::path::{Path, PathBuf};
187
188    debug_assert!(!names.is_empty());
189    let mut last_error = None;
190
191    for name in names {
192        let realpath = match prefix {
193            Some(prefix) => Path::new(prefix).join(name),
194            None => PathBuf::from(name),
195        };
196        match libloading::Library::new(realpath) {
197            Ok(lib) => return Ok(lib),
198            Err(err) => {
199                last_error = Some(err);
200            }
201        }
202    }
203
204    if prefix.is_some() {
205        if let Ok(lib) = load_library(None, names) {
206            return Ok(lib);
207        }
208    }
209
210    Err(last_error.unwrap())
211}