use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_short, c_uchar, c_uint, c_ushort};
use std::ptr::null;
use x11::{xlib, xrecord};
use crate::{GLOBAL_CALLBACK, ListenError, MouseMoveEvent};
#[repr(C)]
#[derive(Debug)]
struct XRecordDatum {
xtype: c_uint, time: c_uint,
root: c_uint,
event: c_uint,
child: c_uint,
root_x: c_short,
root_y: c_short,
event_x: c_short,
event_y: c_short,
state: c_ushort,
same_screen: c_uchar,
pad1: c_uchar,
}
unsafe extern "C" fn raw_callback(
_closure: *mut c_char,
recorded_data: *mut xrecord::XRecordInterceptData,
) {
let data = match unsafe { recorded_data.as_ref() } {
Some(d) => d,
None => return,
};
if data.category != xrecord::XRecordFromServer {
unsafe { xrecord::XRecordFreeData(recorded_data) };
return;
}
debug_assert!(data.data_len * 4 >= std::mem::size_of::<XRecordDatum>() as u64);
let xdatum = unsafe { &*(data.data as *const XRecordDatum) };
if xdatum.xtype as c_int == xlib::MotionNotify {
let event = MouseMoveEvent {
x: xdatum.root_x as f64,
y: xdatum.root_y as f64,
};
GLOBAL_CALLBACK.with(|cell| {
if let Some(callback) = cell.borrow_mut().as_mut() {
callback(event);
}
});
}
unsafe { xrecord::XRecordFreeData(recorded_data) };
}
pub fn listen<T>(callback: T) -> Result<(), ListenError>
where
T: FnMut(MouseMoveEvent) + 'static,
{
GLOBAL_CALLBACK.with(|cell| {
*cell.borrow_mut() = Some(Box::new(callback));
});
unsafe {
let dpy = xlib::XOpenDisplay(null());
if dpy.is_null() {
return Err(ListenError::XOpenDisplay);
}
let ext_name = CString::new("RECORD").unwrap();
if xlib::XInitExtension(dpy, ext_name.as_ptr()).is_null() {
return Err(ListenError::XRecordExtensionMissing);
}
let range = xrecord::XRecordAllocRange();
if range.is_null() {
return Err(ListenError::XRecordAllocRange);
}
(*range).device_events.first = xlib::MotionNotify as c_uchar;
(*range).device_events.last = xlib::MotionNotify as c_uchar;
let mut all_clients = xrecord::XRecordAllClients;
let mut range_ptr: *mut xrecord::XRecordRange = range;
let context = xrecord::XRecordCreateContext(dpy, 0, &mut all_clients, 1, &mut range_ptr, 1);
if context == 0 {
return Err(ListenError::XRecordCreateContext);
}
xlib::XSync(dpy, 0);
let ok = xrecord::XRecordEnableContext(dpy, context, Some(raw_callback), &mut 0);
if ok == 0 {
return Err(ListenError::XRecordEnableContext);
}
}
Ok(())
}