use std::ffi::{CStr, c_char, c_int, c_uint, c_void};
use std::sync::OnceLock;
use crate::ClipboardError;
pub enum XcbConnection {}
pub enum XcbSetup {}
pub enum XcbScreen {}
pub enum XcbGenericError {}
pub enum XcbGenericEvent {}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XcbInternAtomCookie {
pub sequence: c_uint,
}
#[repr(C)]
pub struct XcbInternAtomReply {
pub response_type: u8,
pub pad0: u8,
pub sequence: u16,
pub length: u32,
pub atom: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XcbScreenIterator {
pub data: *mut XcbScreen,
pub rem: c_int,
pub index: c_int,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XcbGetSelectionOwnerCookie {
pub sequence: c_uint,
}
#[repr(C)]
pub struct XcbGetSelectionOwnerReply {
pub response_type: u8,
pub pad0: u8,
pub sequence: u16,
pub length: u32,
pub owner: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XcbGetPropertyCookie {
pub sequence: c_uint,
}
#[repr(C)]
pub struct XcbGetPropertyReply {
pub response_type: u8,
pub format: u8,
pub sequence: u16,
pub length: u32,
pub r#type: u32,
pub bytes_after: u32,
pub value_len: u32,
pub pad0: [u8; 12],
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XcbVoidCookie {
pub sequence: c_uint,
}
pub struct XcbFns {
pub xcb_connect:
unsafe extern "C" fn(display: *const c_char, screen_p: *mut c_int) -> *mut XcbConnection,
pub xcb_disconnect: unsafe extern "C" fn(c: *mut XcbConnection),
pub xcb_connection_has_error: unsafe extern "C" fn(c: *mut XcbConnection) -> c_int,
pub xcb_get_setup: unsafe extern "C" fn(c: *mut XcbConnection) -> *const XcbSetup,
pub xcb_setup_roots_iterator: unsafe extern "C" fn(r: *const XcbSetup) -> XcbScreenIterator,
pub xcb_intern_atom: unsafe extern "C" fn(
c: *mut XcbConnection,
only_if_exists: u8,
name_len: u16,
name: *const c_char,
) -> XcbInternAtomCookie,
pub xcb_intern_atom_reply: unsafe extern "C" fn(
c: *mut XcbConnection,
cookie: XcbInternAtomCookie,
e: *mut *mut XcbGenericError,
) -> *mut XcbInternAtomReply,
pub xcb_flush: unsafe extern "C" fn(c: *mut XcbConnection) -> c_int,
pub xcb_generate_id: unsafe extern "C" fn(c: *mut XcbConnection) -> u32,
pub xcb_create_window: unsafe extern "C" fn(
c: *mut XcbConnection,
depth: u8,
wid: u32,
parent: u32,
x: i16,
y: i16,
width: u16,
height: u16,
border_width: u16,
class: u16,
visual: u32,
value_mask: u32,
value_list: *const c_void,
) -> XcbVoidCookie,
pub xcb_set_selection_owner: unsafe extern "C" fn(
c: *mut XcbConnection,
owner: u32,
selection: u32,
time: u32,
) -> XcbVoidCookie,
pub xcb_get_selection_owner:
unsafe extern "C" fn(c: *mut XcbConnection, selection: u32) -> XcbGetSelectionOwnerCookie,
pub xcb_get_selection_owner_reply: unsafe extern "C" fn(
c: *mut XcbConnection,
cookie: XcbGetSelectionOwnerCookie,
e: *mut *mut XcbGenericError,
) -> *mut XcbGetSelectionOwnerReply,
pub xcb_change_property: unsafe extern "C" fn(
c: *mut XcbConnection,
mode: u8,
window: u32,
property: u32,
r#type: u32,
format: u8,
data_len: u32,
data: *const c_void,
) -> XcbVoidCookie,
pub xcb_send_event: unsafe extern "C" fn(
c: *mut XcbConnection,
propagate: u8,
destination: u32,
event_mask: u32,
event: *const c_char,
) -> XcbVoidCookie,
#[allow(dead_code)]
pub xcb_wait_for_event: unsafe extern "C" fn(c: *mut XcbConnection) -> *mut XcbGenericEvent,
pub xcb_poll_for_event: unsafe extern "C" fn(c: *mut XcbConnection) -> *mut XcbGenericEvent,
#[allow(dead_code)]
pub xcb_request_check:
unsafe extern "C" fn(c: *mut XcbConnection, cookie: XcbVoidCookie) -> *mut XcbGenericError,
pub xcb_convert_selection: unsafe extern "C" fn(
c: *mut XcbConnection,
requestor: u32,
selection: u32,
target: u32,
property: u32,
time: u32,
) -> XcbVoidCookie,
pub xcb_get_property: unsafe extern "C" fn(
c: *mut XcbConnection,
delete: u8,
window: u32,
property: u32,
r#type: u32,
long_offset: u32,
long_length: u32,
) -> XcbGetPropertyCookie,
pub xcb_get_property_reply: unsafe extern "C" fn(
c: *mut XcbConnection,
cookie: XcbGetPropertyCookie,
e: *mut *mut XcbGenericError,
) -> *mut XcbGetPropertyReply,
pub xcb_get_property_value: unsafe extern "C" fn(r: *const XcbGetPropertyReply) -> *mut c_void,
pub xcb_get_property_value_length: unsafe extern "C" fn(r: *const XcbGetPropertyReply) -> c_int,
pub xcb_delete_property:
unsafe extern "C" fn(c: *mut XcbConnection, window: u32, property: u32) -> XcbVoidCookie,
pub xcb_change_window_attributes: unsafe extern "C" fn(
c: *mut XcbConnection,
window: u32,
value_mask: u32,
value_list: *const c_void,
) -> XcbVoidCookie,
}
unsafe impl Send for XcbFns {}
unsafe impl Sync for XcbFns {}
const LIBXCB: &CStr = c"libxcb.so.1";
unsafe fn load_sym<T: Copy>(handle: *mut c_void, name: &CStr) -> Option<T> {
let ptr = unsafe { libc::dlsym(handle, name.as_ptr()) };
if ptr.is_null() {
return None;
}
Some(unsafe { std::mem::transmute_copy(&ptr) })
}
macro_rules! sym {
($handle:expr, $name:literal) => {{
let cstr = unsafe { CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes()) };
match unsafe { load_sym($handle, cstr) } {
Some(f) => f,
None => return None,
}
}};
}
fn try_load_xcb() -> Option<XcbFns> {
let handle = unsafe { libc::dlopen(LIBXCB.as_ptr(), libc::RTLD_LAZY | libc::RTLD_GLOBAL) };
if handle.is_null() {
return None;
}
Some(XcbFns {
xcb_connect: sym!(handle, "xcb_connect"),
xcb_disconnect: sym!(handle, "xcb_disconnect"),
xcb_connection_has_error: sym!(handle, "xcb_connection_has_error"),
xcb_get_setup: sym!(handle, "xcb_get_setup"),
xcb_setup_roots_iterator: sym!(handle, "xcb_setup_roots_iterator"),
xcb_intern_atom: sym!(handle, "xcb_intern_atom"),
xcb_intern_atom_reply: sym!(handle, "xcb_intern_atom_reply"),
xcb_flush: sym!(handle, "xcb_flush"),
xcb_generate_id: sym!(handle, "xcb_generate_id"),
xcb_create_window: sym!(handle, "xcb_create_window"),
xcb_set_selection_owner: sym!(handle, "xcb_set_selection_owner"),
xcb_get_selection_owner: sym!(handle, "xcb_get_selection_owner"),
xcb_get_selection_owner_reply: sym!(handle, "xcb_get_selection_owner_reply"),
xcb_change_property: sym!(handle, "xcb_change_property"),
xcb_send_event: sym!(handle, "xcb_send_event"),
xcb_wait_for_event: sym!(handle, "xcb_wait_for_event"),
xcb_poll_for_event: sym!(handle, "xcb_poll_for_event"),
xcb_request_check: sym!(handle, "xcb_request_check"),
xcb_convert_selection: sym!(handle, "xcb_convert_selection"),
xcb_get_property: sym!(handle, "xcb_get_property"),
xcb_get_property_reply: sym!(handle, "xcb_get_property_reply"),
xcb_get_property_value: sym!(handle, "xcb_get_property_value"),
xcb_get_property_value_length: sym!(handle, "xcb_get_property_value_length"),
xcb_delete_property: sym!(handle, "xcb_delete_property"),
xcb_change_window_attributes: sym!(handle, "xcb_change_window_attributes"),
})
}
static XCB_FNS: OnceLock<Option<XcbFns>> = OnceLock::new();
pub fn xcb_fns() -> Result<&'static XcbFns, ClipboardError> {
XCB_FNS
.get_or_init(try_load_xcb)
.as_ref()
.ok_or(ClipboardError::LibNotFound)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xcb_dlopen_smoke() {
match xcb_fns() {
Ok(_) => {
}
Err(ClipboardError::LibNotFound) => {
eprintln!("SKIP xcb_dlopen_smoke: libxcb.so.1 not found");
}
Err(e) => panic!("unexpected error: {e}"),
}
}
}