use std::ffi::c_char;
use crate::ClipboardError;
use super::dlopen::{XcbConnection, XcbFns, xcb_fns};
pub struct ScreenInfo {
pub root: u32,
pub root_visual: u32,
pub root_depth: u8,
#[allow(dead_code)]
pub width: u16,
#[allow(dead_code)]
pub height: u16,
pub max_request_len_bytes: u32,
}
#[derive(Clone, Copy)]
pub struct Atoms {
pub clipboard: u32,
pub primary: u32,
pub targets: u32,
pub utf8_string: u32,
pub string: u32,
pub text_plain_utf8: u32,
pub text_html: u32,
pub text_rtf: u32,
pub text_uri_list: u32,
pub image_png: u32,
pub incr: u32,
pub clipboard_manager: u32,
pub save_targets: u32,
pub multiple: u32,
pub hjkl_clipboard_get: u32,
}
const ATOM_NAMES: &[&str] = &[
"CLIPBOARD",
"PRIMARY",
"TARGETS",
"UTF8_STRING",
"STRING",
"text/plain;charset=utf-8",
"text/html",
"text/rtf",
"text/uri-list",
"image/png",
"INCR",
"CLIPBOARD_MANAGER",
"SAVE_TARGETS",
"MULTIPLE",
"HJKL_CLIPBOARD_GET",
];
pub struct X11Connection {
fns: &'static XcbFns,
conn: *mut XcbConnection,
pub screen: ScreenInfo,
pub atoms: Atoms,
}
unsafe impl Send for X11Connection {}
impl X11Connection {
pub(crate) fn open() -> Result<Self, ClipboardError> {
if std::env::var_os("DISPLAY").is_none() {
return Err(ClipboardError::NoDisplay);
}
let fns = xcb_fns()?;
let conn = unsafe { (fns.xcb_connect)(std::ptr::null(), std::ptr::null_mut()) };
if conn.is_null() {
return Err(ClipboardError::io_other("xcb_connect returned null"));
}
let err = unsafe { (fns.xcb_connection_has_error)(conn) };
if err != 0 {
unsafe { (fns.xcb_disconnect)(conn) };
return Err(ClipboardError::io(std::io::Error::other(format!(
"xcb_connection_has_error: {err}"
))));
}
let setup = unsafe { (fns.xcb_get_setup)(conn) };
if setup.is_null() {
unsafe { (fns.xcb_disconnect)(conn) };
return Err(ClipboardError::io_other("xcb_get_setup returned null"));
}
let iter = unsafe { (fns.xcb_setup_roots_iterator)(setup) };
if iter.data.is_null() {
unsafe { (fns.xcb_disconnect)(conn) };
return Err(ClipboardError::io_other("no screens in XCB setup"));
}
let screen_ptr = iter.data as *const u8;
let root = u32::from_ne_bytes(
unsafe { *screen_ptr.cast::<[u8; 4]>() },
);
let width = u16::from_ne_bytes(unsafe { *screen_ptr.add(20).cast::<[u8; 2]>() });
let height = u16::from_ne_bytes(unsafe { *screen_ptr.add(22).cast::<[u8; 2]>() });
let root_visual = u32::from_ne_bytes(unsafe { *screen_ptr.add(32).cast::<[u8; 4]>() });
let root_depth = unsafe { *screen_ptr.add(38) };
let setup_ptr = setup as *const u8;
let max_req_units = u16::from_ne_bytes(unsafe { *setup_ptr.add(26).cast::<[u8; 2]>() });
let max_request_len_bytes = if max_req_units == 0 {
256 * 1024 } else {
u32::from(max_req_units) * 4
};
let screen = ScreenInfo {
root,
root_visual,
root_depth,
width,
height,
max_request_len_bytes,
};
let atoms = intern_atoms(fns, conn).inspect_err(|_| {
unsafe { (fns.xcb_disconnect)(conn) };
})?;
Ok(Self {
fns,
conn,
screen,
atoms,
})
}
}
impl Drop for X11Connection {
fn drop(&mut self) {
unsafe { (self.fns.xcb_disconnect)(self.conn) };
}
}
impl X11Connection {
pub(crate) fn fns(&self) -> &'static XcbFns {
self.fns
}
pub(crate) fn raw(&self) -> *mut super::dlopen::XcbConnection {
self.conn
}
pub(crate) fn screen(&self) -> &ScreenInfo {
&self.screen
}
pub(crate) fn atoms(&self) -> &Atoms {
&self.atoms
}
}
fn intern_atoms(fns: &'static XcbFns, conn: *mut XcbConnection) -> Result<Atoms, ClipboardError> {
use super::dlopen::XcbInternAtomCookie;
let mut cookies: [XcbInternAtomCookie; 15] = [XcbInternAtomCookie { sequence: 0 }; 15];
for (i, name) in ATOM_NAMES.iter().enumerate() {
let len = name.len() as u16;
cookies[i] = unsafe { (fns.xcb_intern_atom)(conn, 0, len, name.as_ptr().cast::<c_char>()) };
}
let mut values = [0u32; 15];
for (i, cookie) in cookies.into_iter().enumerate() {
let reply = unsafe { (fns.xcb_intern_atom_reply)(conn, cookie, std::ptr::null_mut()) };
if reply.is_null() {
return Err(ClipboardError::io(std::io::Error::other(format!(
"xcb_intern_atom_reply null for atom {i} ({})",
ATOM_NAMES[i]
))));
}
let atom = unsafe { (*reply).atom };
unsafe { libc::free(reply.cast()) };
values[i] = atom;
}
Ok(Atoms {
clipboard: values[0],
primary: values[1],
targets: values[2],
utf8_string: values[3],
string: values[4],
text_plain_utf8: values[5],
text_html: values[6],
text_rtf: values[7],
text_uri_list: values[8],
image_png: values[9],
incr: values[10],
clipboard_manager: values[11],
save_targets: values[12],
multiple: values[13],
hjkl_clipboard_get: values[14],
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dlopen_smoke() {
match xcb_fns() {
Ok(_) => {}
Err(ClipboardError::LibNotFound) => {
eprintln!("SKIP dlopen_smoke: libxcb.so.1 not found");
}
Err(e) => panic!("unexpected error: {e}"),
}
}
}