use x11::{xinput2, xlib};
use std::ffi::CString;
use std::os::raw::{c_int, c_uchar};
use std::{mem, ptr};
use crate::widgets::Size;
pub struct Display {
pub display: *mut xlib::_XDisplay,
pub screen: i32,
}
impl Display {
pub fn create() -> Result<Display, failure::Error> {
let display = unsafe { xlib::XOpenDisplay(ptr::null()) };
if display.is_null() {
bail!("Unable to open X11 display");
}
let screen = unsafe { xlib::XDefaultScreen(display) };
Ok(Display { display, screen })
}
pub fn get_width(&mut self) -> i32 {
unsafe {
let s = xlib::XScreenOfDisplay(self.display, self.screen);
xlib::XWidthOfScreen(s)
}
}
pub fn get_widths(&mut self) -> Result<Vec<(i32, i32)>, failure::Error> {
if unsafe { x11::xinerama::XineramaIsActive(self.display) != 0 } {
let mut screens = 0;
let screen_info =
unsafe { x11::xinerama::XineramaQueryScreens(self.display, &mut screens) };
let mut widths = Vec::new();
for i in 0..screens {
unsafe {
let si = screen_info
.offset(i as isize)
.as_ref()
.ok_or_else(|| format_err!("bad pointer"))?;
widths.push((si.x_org as i32, si.width as i32));
}
}
Ok(widths)
} else {
Ok(vec![(0, self.get_width())])
}
}
}
impl Drop for Display {
fn drop(&mut self) {
unsafe {
xlib::XCloseDisplay(self.display);
}
}
}
/// All the state needed to keep around to run this sort of
/// application!
pub struct Window<'t> {
pub display: &'t Display,
pub screen: i32,
pub window: u64,
// these two are interned strings kept around because we want to
// check against them a _lot_, to find out if an event is a quit
// event
pub wm_protocols: u64,
pub wm_delete_window: u64,
// The width and height of the window
pub width: i32,
pub height: i32,
}
impl<'t> Window<'t> {
/// Create a new Window from a given Display and with the desire
/// width and height
pub fn create(
display: &'t Display,
Size {
wd: width,
ht: height,
xo,
yo,
}: Size,
) -> Result<Window<'t>, failure::Error> {
unsafe {
let screen = display.screen;
let window = xlib::XCreateSimpleWindow(
display.display,
xlib::XRootWindow(display.display, screen),
xo as i32,
yo as i32,
width as u32,
height as u32,
1,
xlib::XBlackPixel(display.display, screen),
xlib::XWhitePixel(display.display, screen),
);
let wm_protocols = {
let cstr = CString::new("WM_PROTOCOLS")?;
xlib::XInternAtom(display.display, cstr.as_ptr(), 0)
};
let wm_delete_window = {
let cstr = CString::new("WM_DELETE_WINDOW")?;
xlib::XInternAtom(display.display, cstr.as_ptr(), 0)
};
Ok(Window {
display,
screen,
window,
wm_protocols,
wm_delete_window,
width,
height,
})
}
}
/// for this application, we might eventually care about the
/// mouse, so make sure we notify x11 that we care about those
pub fn set_input_masks(&mut self) -> Result<(), failure::Error> {
let mut opcode = 0;
let mut event = 0;
let mut error = 0;
let xinput_str = CString::new("XInputExtension")?;
unsafe {
xlib::XQueryExtension(
self.display.display,
xinput_str.as_ptr(),
&mut opcode,
&mut event,
&mut error,
);
}
let mut mask: [c_uchar; 1] = [0];
let mut input_event_mask = xinput2::XIEventMask {
deviceid: xinput2::XIAllMasterDevices,
mask_len: mask.len() as i32,
mask: mask.as_mut_ptr(),
};
let events = &[xinput2::XI_ButtonPress, xinput2::XI_ButtonRelease];
for &event in events {
xinput2::XISetMask(&mut mask, event);
}
match unsafe {
xinput2::XISelectEvents(self.display.display, self.window, &mut input_event_mask, 1)
} {
status if status as u8 == xlib::Success => (),
err => bail!("Failed to select events {:?}", err),
}
Ok(())
}
pub fn set_protocols(&mut self) -> Result<(), failure::Error> {
let mut protocols = [self.intern("WM_DELETE_WINDOW")?];
unsafe {
xlib::XSetWMProtocols(
self.display.display,
self.window,
protocols.as_mut_ptr(),
protocols.len() as c_int,
);
}
Ok(())
}
/// Set the name of the window to the desired string
pub fn set_title(&mut self, name: &str) -> Result<(), failure::Error> {
unsafe {
xlib::XStoreName(
self.display.display,
self.window,
CString::new(name)?.as_ptr(),
);
}
Ok(())
}
/// Map the window to the screen
pub fn map(&mut self) {
unsafe {
xlib::XMapWindow(self.display.display, self.window);
}
}
/// Intern a string in the x server
pub fn intern(&mut self, s: &str) -> Result<u64, failure::Error> {
unsafe {
let cstr = CString::new(s)?;
Ok(xlib::XInternAtom(self.display.display, cstr.as_ptr(), 0))
}
}
/// Modify the supplied property to the noted value.
pub fn change_property<T: XProperty>(
&mut self,
prop: &str,
val: &[T],
) -> Result<(), failure::Error> {
let prop = self.intern(prop)?;
unsafe {
let len = val.len();
T::with_ptr(val, self, |w, typ, ptr| {
xlib::XChangeProperty(
w.display.display,
w.window,
prop,
typ,
32,
xlib::PropModeReplace,
ptr,
len as c_int,
);
})?;
}
Ok(())
}
/// Get the Cairo drawing surface corresponding to the whole
/// window
pub fn get_cairo_surface(&mut self) -> cairo::Surface {
unsafe {
let s = cairo_sys::cairo_xlib_surface_create(
self.display.display,
self.window,
xlib::XDefaultVisual(self.display.display, self.screen),
self.width,
self.height,
);
cairo::Surface::from_raw_none(s)
}
}
/// handle a single event, wrapping it as an 'Event'. This is
/// pretty useless right now, but the plan is to make it easier to
/// handle things like keyboard input and mouse input later. This
/// will also only return values for events we care about
pub fn handle(&mut self) -> Option<Event> {
let mut e = mem::MaybeUninit::uninit();
unsafe {
xlib::XNextEvent(self.display.display, e.as_mut_ptr());
e.assume_init();
}
match unsafe { *e.as_ptr() }.get_type() {
// Is it a quit event? We gotta do some tedious string
// comparison to find out
xlib::ClientMessage => {
let xclient: xlib::XClientMessageEvent = unsafe { From::from(*e.as_ptr()) };
if xclient.message_type == self.wm_protocols && xclient.format == 32 {
let protocol = xclient.data.get_long(0) as xlib::Atom;
if protocol == self.wm_delete_window {
return Some(Event::QuitEvent);
}
}
}
// Is it a show event?
xlib::Expose => return Some(Event::ShowEvent),
// otherwise, it might be a mouse press event
xlib::GenericEvent => {
let mut cookie: xlib::XGenericEventCookie = unsafe { From::from(*e.as_ptr()) };
unsafe { xlib::XGetEventData(self.display.display, &mut cookie) };
if let xinput2::XI_ButtonPress = cookie.evtype {
let data: &xinput2::XIDeviceEvent =
unsafe { &*(cookie.data as *const xinput2::XIDeviceEvent) };
return Some(Event::MouseEvent {
x: data.event_x,
y: data.event_y,
});
}
}
_ => (),
}
None
}
/// True if there are any pending events.
pub fn has_events(&mut self) -> bool {
unsafe { xlib::XPending(self.display.display) != 0 }
}
/// Did you know that X11 uses a file descriptor underneath the
/// surface to wait on events? This lets us use select on it!
pub fn get_fd(&mut self) -> i32 {
unsafe { xlib::XConnectionNumber(self.display.display) }
}
pub fn size(&self) -> Size {
Size {
wd: self.width,
ht: self.height,
xo: 0,
yo: 0,
}
}
}
/// A trait for abstracting over different values which are allowed
/// for xlib properties
pub trait XProperty: Sized {
fn with_ptr(
xs: &[Self],
w: &mut Window,
f: impl FnOnce(&mut Window, u64, *const u8),
) -> Result<(), failure::Error>;
}
impl XProperty for i64 {
fn with_ptr(
xs: &[Self],
w: &mut Window,
f: impl FnOnce(&mut Window, u64, *const u8),
) -> Result<(), failure::Error> {
f(w, xlib::XA_CARDINAL, xs.as_ptr() as *const u8);
Ok(())
}
}
impl XProperty for &str {
fn with_ptr(
xs: &[Self],
w: &mut Window,
f: impl FnOnce(&mut Window, u64, *const u8),
) -> Result<(), failure::Error> {
let xs: Result<Vec<u64>, failure::Error> = xs.iter().map(|s| w.intern(s)).collect();
f(w, xlib::XA_ATOM, xs?.as_ptr() as *const u8);
Ok(())
}
}
/// An ADT of only the events we care about, wrapped in a high-level
/// way
#[derive(Debug)]
pub enum Event {
MouseEvent { x: f64, y: f64 },
ShowEvent,
QuitEvent,
}