#![allow(unused_unsafe)]
#![cfg_attr(coverage, feature(coverage_attribute))]
mod ffi;
use std::cell::Cell;
use std::ffi::CStr;
use std::fmt;
use std::io;
use std::marker::PhantomData;
use std::mem::{self, ManuallyDrop};
use std::os::raw::{c_int, c_void};
use std::ptr::{self, NonNull};
use std::sync::{Mutex, MutexGuard, Once, PoisonError};
macro_rules! lock {
($e:expr) => {{
#[cfg_attr(coverage, coverage(off))]
fn unwrapper<T>(guard: PoisonError<MutexGuard<'_, T>>) -> MutexGuard<'_, T> {
guard.into_inner()
}
($e).lock().unwrap_or_else(unwrapper)
}};
}
ctor::declarative::ctor! {
#[ctor(unsafe)]
static XLIB: io::Result<ffi::Xlib> = {
#[cfg_attr(coverage, coverage(off))]
unsafe fn load_xlib_with_error_hook() -> io::Result<ffi::Xlib> {
#[cfg_attr(coverage, coverage(off))]
fn error(e: impl std::error::Error) -> io::Error {
io::Error::new(io::ErrorKind::Other, format!("failed to load Xlib: {}", e))
}
let xlib = ffi::Xlib::load().map_err(error)?;
#[cfg_attr(coverage, coverage(off))]
unsafe extern "C" fn dummy(
_display: *mut ffi::Display,
_error: *mut ffi::XErrorEvent,
) -> std::os::raw::c_int {
0
}
let default_hook = xlib.set_error_handler(Some(dummy));
DEFAULT_ERROR_HOOK.set(default_hook);
xlib.set_error_handler(default_hook);
Ok(xlib)
}
unsafe { load_xlib_with_error_hook() }
};
}
#[inline]
fn get_xlib(sym: &io::Result<ffi::Xlib>) -> io::Result<&ffi::Xlib> {
#[cfg_attr(coverage, coverage(off))]
fn error(e: &io::Error) -> io::Error {
io::Error::new(e.kind(), e.to_string())
}
sym.as_ref().map_err(error)
}
static DEFAULT_ERROR_HOOK: ErrorHookSlot = ErrorHookSlot::new();
type ErrorHook = Box<dyn FnMut(&Display, &ErrorEvent) -> bool + Send + Sync + 'static>;
static ERROR_HANDLERS: Mutex<HandlerList> = Mutex::new(HandlerList::new());
unsafe extern "C" fn error_handler(
display: *mut ffi::Display,
error: *mut ffi::XErrorEvent,
) -> c_int {
struct AbortOnPanic;
impl Drop for AbortOnPanic {
#[cfg_attr(coverage, coverage(off))]
#[cold]
#[inline(never)]
fn drop(&mut self) {
std::process::abort();
}
}
let bomb = AbortOnPanic;
let mut handlers = lock!(ERROR_HANDLERS);
let prev = handlers.prev;
if let Some(prev) = prev {
drop(handlers);
unsafe {
prev(display, error);
}
handlers = lock!(ERROR_HANDLERS);
}
let display_ptr = unsafe { Display::from_ptr(display.cast()) };
let event = ErrorEvent(ptr::read(error));
#[cfg(feature = "tracing")]
tracing::error!(
display = ?&*display_ptr,
error = ?event,
"got Xlib error",
);
handlers.iter_mut().any(|(_i, handler)| {
#[cfg(feature = "tracing")]
tracing::trace!(key = _i, "invoking error handler");
let stop_going = (handler)(&display_ptr, &event);
#[cfg(feature = "tracing")]
{
if stop_going {
tracing::trace!("error handler returned true, stopping");
} else {
tracing::trace!("error handler returned false, continuing");
}
}
stop_going
});
mem::forget(bomb);
0
}
fn setup_error_handler(xlib: &ffi::Xlib) {
static REGISTERED: Once = Once::new();
REGISTERED.call_once(move || {
unsafe {
xlib.init_threads();
}
let prev = unsafe { xlib.set_error_handler(Some(error_handler)) };
let default_hook = unsafe { DEFAULT_ERROR_HOOK.get() };
#[allow(unpredictable_function_pointer_comparisons)]
if prev != default_hook.flatten() && prev != Some(error_handler) {
lock!(ERROR_HANDLERS).prev = prev;
}
});
}
#[derive(Debug, Copy, Clone)]
pub struct HandlerKey(usize);
#[derive(Clone)]
pub struct ErrorEvent(ffi::XErrorEvent);
unsafe impl Send for ErrorEvent {}
unsafe impl Sync for ErrorEvent {}
impl ErrorEvent {
#[allow(clippy::unnecessary_cast)]
pub fn serial(&self) -> u64 {
self.0.serial as u64
}
pub fn error_code(&self) -> u8 {
self.0.error_code
}
pub fn request_code(&self) -> u8 {
self.0.request_code
}
pub fn minor_code(&self) -> u8 {
self.0.minor_code
}
pub fn resource_id(&self) -> usize {
self.0.resourceid as usize
}
}
impl fmt::Debug for ErrorEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ErrorEvent")
.field("serial", &self.serial())
.field("error_code", &self.error_code())
.field("request_code", &self.request_code())
.field("minor_code", &self.minor_code())
.field("resource_id", &self.resource_id())
.finish_non_exhaustive()
}
}
pub struct Display {
ptr: NonNull<ffi::Display>,
_marker: PhantomData<Box<ffi::Display>>,
}
unsafe impl Send for Display {}
unsafe impl Sync for Display {}
impl fmt::Debug for Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Display").field(&self.ptr.as_ptr()).finish()
}
}
impl Display {
pub fn new(name: Option<&CStr>) -> io::Result<Self> {
let xlib = get_xlib(&XLIB)?;
setup_error_handler(xlib);
let name = name.map_or(std::ptr::null(), |n| n.as_ptr());
let pointer = unsafe { xlib.open_display(name) };
NonNull::new(pointer)
.map(|ptr| Self {
ptr,
_marker: PhantomData,
})
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "failed to open display"))
}
pub unsafe fn from_ptr(ptr: *mut c_void) -> ManuallyDrop<Self> {
ManuallyDrop::new(Self {
ptr: NonNull::new_unchecked(ptr.cast()),
_marker: PhantomData,
})
}
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr().cast()
}
pub fn screen_index(&self) -> usize {
let xlib = get_xlib(&XLIB).expect("failed to load Xlib");
let index = unsafe { xlib.default_screen(self.ptr.as_ptr()) };
index.try_into().unwrap_or_else(|_| {
#[cfg(feature = "tracing")]
tracing::error!(
"XDefaultScreen returned a value out of usize range (how?!), returning zero"
);
0
})
}
}
unsafe impl as_raw_xcb_connection::AsRawXcbConnection for Display {
fn as_raw_xcb_connection(&self) -> *mut as_raw_xcb_connection::xcb_connection_t {
let xlib = get_xlib(&XLIB).expect("failed to load Xlib");
unsafe { xlib.get_xcb_connection(self.ptr.as_ptr()) }
}
}
impl Drop for Display {
fn drop(&mut self) {
if let Ok(xlib) = get_xlib(&XLIB) {
unsafe {
xlib.close_display(self.ptr.as_ptr());
}
}
}
}
pub fn register_error_handler(handler: ErrorHook) -> io::Result<HandlerKey> {
setup_error_handler(get_xlib(&XLIB)?);
let mut handlers = lock!(ERROR_HANDLERS);
let key = handlers.insert(handler);
Ok(HandlerKey(key))
}
pub fn unregister_error_handler(key: HandlerKey) {
let mut handlers = lock!(ERROR_HANDLERS);
handlers.remove(key.0);
}
struct HandlerList {
slots: Vec<Slot>,
filled: usize,
unfilled: usize,
prev: ffi::XErrorHook,
}
enum Slot {
Filled(ErrorHook),
Unfilled(usize),
}
impl HandlerList {
#[cfg_attr(coverage, coverage(off))]
const fn new() -> Self {
Self {
slots: vec![],
filled: 0,
unfilled: 0,
prev: None,
}
}
fn insert(&mut self, handler: ErrorHook) -> usize {
#[cfg_attr(coverage, coverage(off))]
#[inline(always)]
fn unwrapper(slot: &Slot) -> usize {
match slot {
Slot::Filled(_) => unreachable!(),
Slot::Unfilled(next) => *next,
}
}
let index = self.filled;
if self.unfilled == self.slots.len() {
self.slots.push(Slot::Filled(handler));
self.unfilled += 1;
} else {
let unfilled = self.unfilled;
self.unfilled = unwrapper(&self.slots[unfilled]);
self.slots[unfilled] = Slot::Filled(handler);
}
self.filled += 1;
index
}
fn remove(&mut self, index: usize) {
let slot = &mut self.slots[index];
if let Slot::Filled(_) = slot {
*slot = Slot::Unfilled(self.unfilled);
self.unfilled = index;
self.filled -= 1;
}
}
fn iter_mut(&mut self) -> impl Iterator<Item = (usize, &mut ErrorHook)> {
self.slots
.iter_mut()
.enumerate()
.filter_map(|(i, slot)| match slot {
Slot::Filled(handler) => Some((i, handler)),
_ => None,
})
}
}
struct ErrorHookSlot(Cell<Option<ffi::XErrorHook>>);
unsafe impl Sync for ErrorHookSlot {}
impl ErrorHookSlot {
#[cfg_attr(coverage, coverage(off))]
const fn new() -> Self {
Self(Cell::new(None))
}
unsafe fn get(&self) -> Option<ffi::XErrorHook> {
self.0.get()
}
#[cfg_attr(coverage, coverage(off))]
unsafe fn set(&self, hook: ffi::XErrorHook) {
self.0.set(Some(hook));
}
}