use crate::{
cbox::CBox,
extension_manager::ExtensionManager,
sync::{call_once, mtx_lock, Mutex, OnceCell},
xcb_ffi::{
errors, flags, xcb, AuthInfo, Connection, GenericError, GenericEvent, Iovec,
ProtocolRequest, VoidCookie, XcbFfi,
},
};
use alloc::{sync::Arc, vec::Vec};
use breadx::{
display::{Display, DisplayBase, DisplayFunctionsExt, RawReply, RawRequest},
protocol::{xproto::Setup, Event, ReplyFdKind},
x11_utils::TryParse,
Error, Result,
};
use core::{
alloc::Layout,
mem::{self, MaybeUninit},
ptr::{null, null_mut, slice_from_raw_parts_mut, NonNull},
slice,
};
use cstr_core::CStr;
use libc::{c_int, c_void};
#[cfg(all(unix, feature = "to_socket"))]
use std::os::unix::io::{AsRawFd, RawFd};
pub struct XcbDisplay {
connection: NonNull<Connection>,
disconnect: bool,
setup: OnceCell<Arc<Setup>>,
extension_manager: ExtensionManager,
has_fds: Mutex<HashSet<u64>>,
screen: usize,
}
unsafe impl Send for XcbDisplay {}
unsafe impl Sync for XcbDisplay {}
impl XcbDisplay {
pub fn connect(display: Option<&CStr>) -> Result<XcbDisplay> {
let mut screen = MaybeUninit::uninit();
let display = display.map_or(null(), |display| display.as_ptr());
let connection = unsafe { xcb().xcb_connect(display, screen.as_mut_ptr()) };
Ok(unsafe { XcbDisplay::connected(connection, screen.assume_init() as usize)? })
}
pub fn connect_with_auth_info(
display: Option<&CStr>,
auth_name: &[u8],
auth_data: &[u8],
) -> Result<XcbDisplay> {
let mut screen = MaybeUninit::uninit();
let mut auth_info = auth_info(auth_name, auth_data);
let display = display.map_or(null(), |display| display.as_ptr());
let connection = unsafe {
xcb().xcb_connect_to_display_with_auth_info(
display,
&mut auth_info,
screen.as_mut_ptr(),
)
};
Ok(unsafe { XcbDisplay::connected(connection, screen.assume_init() as usize)? })
}
pub unsafe fn connect_to_fd(
fd: c_int,
auth_name: &[u8],
auth_data: &[u8],
screen: usize,
) -> Result<XcbDisplay> {
let mut auth_info = auth_info(auth_name, auth_data);
let connection = unsafe { xcb().xcb_connect_to_fd(fd, &mut auth_info) };
unsafe { XcbDisplay::connected(connection, screen) }
}
unsafe fn connected(ptr: *mut Connection, screen: usize) -> Result<Self> {
assert!(!ptr.is_null());
let this = Self::from_ptr(ptr.cast(), true, screen as usize);
if let Some(err) = this.take_error() {
Err(err)
} else {
Ok(this)
}
}
pub unsafe fn from_ptr(ptr: *mut c_void, disconnect: bool, screen: usize) -> XcbDisplay {
let conn = NonNull::new_unchecked(ptr.cast());
XcbDisplay {
connection: conn,
disconnect,
setup: OnceCell::new(),
extension_manager: ExtensionManager::new(),
has_fds: Mutex::new(HashSet::with_hasher(Default::default())),
screen,
}
}
fn as_ptr(&self) -> *mut Connection {
self.connection.as_ptr()
}
pub fn as_raw_connection(&self) -> *mut c_void {
self.as_ptr().cast()
}
pub fn get_fd(&self) -> c_int {
unsafe { xcb().xcb_get_file_descriptor(self.as_ptr()) }
}
unsafe fn ptr_take_error(ptr: *mut Connection) -> Option<Error> {
let error = unsafe { xcb().xcb_connection_has_error(ptr) };
match error {
0 => None,
errors::XCB_CONN_ERROR => {
cfg_if::cfg_if! {
if #[cfg(feature = "std")] {
let io = std::io::Error::last_os_error();
Some(io.into())
} else {
Some(Error::make_msg(
"an unknown I/O error occurred"
))
}
}
}
errors::XCB_CONN_CLOSED_EXT_NOTSUPPORTED => {
Some(Error::make_missing_extension("<unknown>"))
}
errors::XCB_CONN_CLOSED_MEM_INSUFFICIENT => {
let layout = Layout::from_size_align_unchecked(32, 4);
alloc::alloc::handle_alloc_error(layout)
}
errors::XCB_CONN_CLOSED_REQ_LEN_EXCEED => {
Some(Error::make_msg("request length exceeded"))
}
errors::XCB_CONN_CLOSED_PARSE_ERR => {
Some(Error::make_msg("failed to parse server reply"))
}
errors::XCB_CONN_CLOSED_INVALID_SCREEN => Some(Error::make_msg("invalid screen")),
errors::XCB_CONN_CLOSED_FDPASSING_FAILED => Some(Error::make_msg("failed to pass FD")),
_ => Some(Error::make_msg("unknown error")),
}
}
pub fn take_error(&self) -> Option<Error> {
unsafe { Self::ptr_take_error(self.as_ptr()) }
}
pub fn take_maybe_error(&self) -> Error {
match self.take_error() {
Some(err) => err,
None => Error::make_msg("no error"),
}
}
pub fn get_setup(&self) -> &Arc<Setup> {
call_once(&self.setup, || {
let setup_ptr = unsafe { xcb().xcb_get_setup(self.as_ptr()) } as *mut u8 as *const u8;
let header = unsafe { slice::from_raw_parts(setup_ptr, 8) };
let xlen = u16::from_ne_bytes([header[6], header[7]]);
let length = ((xlen as usize) * 4) + 8;
let setup_slice = unsafe { slice::from_raw_parts(setup_ptr, length) };
Setup::try_parse(setup_slice)
.expect("xcb had invalid setup struct")
.0
.into()
})
}
fn generate_xid_impl(&self) -> Result<u32> {
let xid = unsafe { xcb().xcb_generate_id(self.as_ptr()) };
if xid == -1i32 as u32 {
Err(self.take_maybe_error())
} else {
Ok(xid)
}
}
fn maximum_request_length_impl(&self) -> u32 {
unsafe { xcb().xcb_get_maximum_request_length(self.as_ptr()) }
}
fn synchronize_impl(&self) -> Result<()> {
let mut this = self;
let cookie = this.no_operation()?;
let seq = cookie.sequence();
self.check_for_error_impl(seq)
}
fn flush_impl(&self) -> Result<()> {
let res = unsafe { xcb().xcb_flush(self.as_ptr()) };
if res <= 0 {
Err(self.take_maybe_error())
} else {
Ok(())
}
}
unsafe fn parse_event(&self, event: *mut GenericEvent) -> Result<Event> {
let header = event as *const GenericEvent as *const [u8; 32];
let evbytes = event as *mut u8;
let header = &*header;
let mut length = 32;
if header[0] & 0x7F == breadx::protocol::xproto::GE_GENERIC_EVENT {
let xlen = u32::from_ne_bytes([header[4], header[5], header[6], header[7]]);
let xlen = xlen as usize * 4;
length += xlen;
core::ptr::copy(evbytes.add(36), evbytes.add(32), xlen);
}
let event = slice_from_raw_parts_mut(evbytes, length);
let event = unsafe { CBox::new(event) };
Event::parse(&event, &self.extension_manager).map_err(Error::make_parse_error)
}
fn wait_for_event_impl(&self) -> Result<Event> {
let event = unsafe { xcb().xcb_wait_for_event(self.as_ptr()) };
let event = if event.is_null() {
return Err(self.take_maybe_error());
} else {
event
};
unsafe { self.parse_event(event) }
}
fn poll_for_event_impl(&self) -> Result<Option<Event>> {
let event = unsafe { xcb().xcb_poll_for_event(self.as_ptr()) };
let event = if event.is_null() {
if let Some(err) = self.take_error() {
return Err(err);
} else {
return Ok(None);
}
} else {
event
};
unsafe { self.parse_event(event) }.map(Some)
}
fn send_request_impl(&self, mut request: RawRequest) -> Result<u64> {
let ext_opcode = request
.extension()
.map(|ext| {
let mut this = self;
match self.extension_manager.extension_code(&mut this, ext)? {
Some(code) => Ok(code),
None => Err(Error::make_missing_extension(ext)),
}
})
.transpose()?;
request.format(ext_opcode, self.maximum_request_length_impl() as usize)?;
let variant = request.variant();
let reply_has_fds = matches!(variant, ReplyFdKind::ReplyWithFDs);
let check_reply = request.discard_mode().is_none();
let (buf, fds) = request.mut_parts();
let iov = (&mut buf[1..]).as_mut_ptr() as *mut Iovec;
let proto_request = ProtocolRequest {
count: buf.len() - 1,
extension: null_mut(),
opcode: 0,
isvoid: matches!(variant, ReplyFdKind::NoReply) as u8,
};
let mut sr_flags = flags::RAW;
if check_reply {
sr_flags |= flags::CHECKED;
}
if reply_has_fds {
sr_flags |= flags::REPLY_HAS_FDS;
}
let seq = if fds.is_empty() {
unsafe { xcb().xcb_send_request64(self.as_ptr(), sr_flags, iov, &proto_request) }
} else {
let mut fds = mem::take(fds)
.into_iter()
.map(|fd| {
cfg_if::cfg_if! {
if #[cfg(all(unix, feature = "std"))] {
fd.into_raw_fd()
} else {
let _ = fd;
unreachable!()
}
}
})
.collect::<Vec<_>>();
unsafe {
xcb().xcb_send_request_with_fds64(
self.as_ptr(),
sr_flags,
iov,
&proto_request,
fds.len() as i32,
fds.as_mut_ptr(),
)
}
};
if seq == 0 {
return Err(self.take_maybe_error());
}
if reply_has_fds {
mtx_lock(&self.has_fds).insert(seq);
}
Ok(seq)
}
#[cfg(unix)]
unsafe fn extract_fds(&self, reply: &[u8], seq: u64) -> Vec<c_int> {
if !mtx_lock(&self.has_fds).remove(&seq) {
return Vec::new();
}
let nfds = reply[1];
let fd_ptr = (reply.as_ptr() as *const c_int).add(reply.len());
let fd_slice = slice::from_raw_parts(fd_ptr, nfds as usize);
fd_slice.into()
}
#[cfg(not(unix))]
unsafe fn extract_fds(&self, _reply: &[u8], _seq: u64) -> Vec<c_int> {
Vec::new()
}
unsafe fn wrap_error(&self, error: *mut GenericError) -> Error {
use breadx::protocol::X11Error;
let error_ptr = error as *mut [u8; 32];
let error_boxed = unsafe { CBox::new(error_ptr) };
X11Error::try_parse(&*error_boxed, &self.extension_manager)
.map_or_else(Error::make_parse_error, Error::from)
}
fn poll_for_reply_impl(&self, seq: u64) -> Result<Option<XcbReply>> {
let mut reply = null_mut();
let mut error = null_mut();
let found =
unsafe { xcb().xcb_poll_for_reply64(self.as_ptr(), seq, &mut reply, &mut error) };
if found == 0 {
return Ok(None);
}
let reply = match (reply.is_null(), error.is_null()) {
(true, true) => return Ok(None),
(false, true) => {
unsafe { wrap_reply(reply) }
}
(true, false) => {
return Err(unsafe { self.wrap_error(error) });
}
(false, false) => panic!("reply and error are both non-null"),
};
let fds = unsafe { self.extract_fds(reply.as_ref(), seq) };
Ok(Some(XcbReply { reply, fds }))
}
fn wait_for_reply_impl(&self, seq: u64) -> Result<XcbReply> {
let mut error = null_mut();
let reply = unsafe { xcb().xcb_wait_for_reply64(self.as_ptr(), seq, &mut error) };
match (reply.is_null(), error.is_null()) {
(true, true) => {
Err(self.take_maybe_error())
}
(false, true) => {
let reply = unsafe { wrap_reply(reply) };
let fds = unsafe { self.extract_fds(reply.as_ref(), seq) };
Ok(XcbReply { reply, fds })
}
(true, false) => {
Err(unsafe { self.wrap_error(error) })
}
(false, false) => {
panic!("reply and error are both non-null")
}
}
}
fn check_for_error_impl(&self, seq: u64) -> Result<()> {
let seq = VoidCookie { sequence: seq as _ };
let err = unsafe { xcb().xcb_request_check(self.as_ptr(), seq) };
if err.is_null() {
return Ok(());
}
let err = unsafe { self.wrap_error(err) };
Err(err)
}
}
#[cfg(all(unix, feature = "to_socket"))]
impl XcbDisplay {
pub unsafe fn connect_to_socket(
socket: impl AsRawFd,
auth_name: &[u8],
auth_data: &[u8],
screen: usize,
) -> Result<Self> {
unsafe { Self::connect_to_fd(socket.as_raw_fd(), auth_name, auth_data, screen) }
}
}
#[cfg(all(unix, feature = "to_socket"))]
impl AsRawFd for XcbDisplay {
fn as_raw_fd(&self) -> RawFd {
self.as_ptr() as RawFd
}
}
impl DisplayBase for XcbDisplay {
fn setup(&self) -> &Arc<Setup> {
self.get_setup()
}
fn default_screen_index(&self) -> usize {
self.screen
}
fn poll_for_event(&mut self) -> Result<Option<Event>> {
self.poll_for_event_impl()
}
fn poll_for_reply_raw(&mut self, seq: u64) -> Result<Option<RawReply>> {
self.poll_for_reply_impl(seq).map(|o| o.map(Into::into))
}
}
impl DisplayBase for &XcbDisplay {
fn setup(&self) -> &Arc<Setup> {
self.get_setup()
}
fn default_screen_index(&self) -> usize {
self.screen
}
fn poll_for_event(&mut self) -> Result<Option<Event>> {
self.poll_for_event_impl()
}
fn poll_for_reply_raw(&mut self, seq: u64) -> Result<Option<RawReply>> {
self.poll_for_reply_impl(seq).map(|o| o.map(Into::into))
}
}
impl Display for XcbDisplay {
fn send_request_raw(&mut self, req: RawRequest<'_, '_>) -> Result<u64> {
self.send_request_impl(req)
}
fn flush(&mut self) -> Result<()> {
self.flush_impl()
}
fn generate_xid(&mut self) -> Result<u32> {
self.generate_xid_impl()
}
fn maximum_request_length(&mut self) -> Result<usize> {
Ok(self.maximum_request_length_impl() as usize)
}
fn synchronize(&mut self) -> Result<()> {
self.synchronize_impl()
}
fn wait_for_event(&mut self) -> Result<Event> {
self.wait_for_event_impl()
}
fn wait_for_reply_raw(&mut self, seq: u64) -> Result<RawReply> {
self.wait_for_reply_impl(seq).map(Into::into)
}
fn check_for_error(&mut self, seq: u64) -> Result<()> {
self.check_for_error_impl(seq)
}
}
impl Display for &XcbDisplay {
fn flush(&mut self) -> Result<()> {
self.flush_impl()
}
fn generate_xid(&mut self) -> Result<u32> {
self.generate_xid_impl()
}
fn maximum_request_length(&mut self) -> Result<usize> {
Ok(self.maximum_request_length_impl() as usize)
}
fn send_request_raw(&mut self, req: RawRequest<'_, '_>) -> Result<u64> {
self.send_request_impl(req)
}
fn synchronize(&mut self) -> Result<()> {
self.synchronize_impl()
}
fn wait_for_event(&mut self) -> Result<Event> {
self.wait_for_event_impl()
}
fn wait_for_reply_raw(&mut self, seq: u64) -> Result<RawReply> {
self.wait_for_reply_impl(seq).map(Into::into)
}
fn check_for_error(&mut self, seq: u64) -> Result<()> {
self.check_for_error_impl(seq)
}
}
impl Drop for XcbDisplay {
fn drop(&mut self) {
if self.disconnect {
unsafe {
xcb().xcb_disconnect(self.as_ptr());
}
}
}
}
unsafe fn wrap_reply(reply: *mut c_void) -> CBox<[u8]> {
let header = unsafe { slice::from_raw_parts(reply as *mut u8 as *const u8, 32) };
let length = u32::from_ne_bytes([header[4], header[5], header[6], header[7]]);
let length = 32usize + (4 * (length as usize));
let reply = slice_from_raw_parts_mut(reply as *mut u8, length);
unsafe { CBox::new(reply) }
}
pub struct XcbReply {
reply: CBox<[u8]>,
fds: Vec<c_int>,
}
impl From<XcbReply> for RawReply {
fn from(xcr: XcbReply) -> Self {
let XcbReply { reply, fds } = xcr;
let data = reply.clone_slice().into_boxed_slice();
let fds = fds
.into_iter()
.map(|fd| {
cfg_if::cfg_if! {
if #[cfg(all(unix, feature = "std"))] {
breadx::Fd::new(fd)
} else {
let _ = fd;
unreachable!()
}
}
})
.collect::<Vec<breadx::Fd>>();
RawReply::new(data, fds)
}
}
fn auth_info(auth_name: &[u8], auth_data: &[u8]) -> AuthInfo {
AuthInfo {
namelen: auth_name.len() as _,
name: auth_name.as_ptr() as *const _ as *mut _,
datalen: auth_data.len() as _,
data: auth_data.as_ptr() as *const _ as *mut _,
}
}
type HashSet<T> = hashbrown::HashSet<T, core::hash::BuildHasherDefault<rustc_hash::FxHasher>>;