use log::info; use std::ptr::{null, null_mut}; use std::{ffi::CString, rc::Rc}; use x11::xlib::{ self, AnyButton, AnyKey, AnyModifier, Atom, ButtonPressMask, ButtonReleaseMask, CWEventMask, ControlMask, CurrentTime, EnterWindowMask, FocusChangeMask, LockMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, PointerMotionMask, PropertyChangeMask, ShiftMask, StructureNotifyMask, SubstructureNotifyMask, SubstructureRedirectMask, Window, XCloseDisplay, XConfigureRequestEvent, XDefaultScreen, XEvent, XGetTransientForHint, XGrabPointer, XInternAtom, XKillClient, XMapWindow, XOpenDisplay, XRaiseWindow, XRootWindow, XSetErrorHandler, XSync, XUngrabButton, XUngrabKey, XUngrabPointer, XWarpPointer, XWindowAttributes, }; use xlib::GrabModeAsync; use log::error; use crate::clients::Client; pub struct XLib { display: Display, root: Window, _screen: i32, atoms: Atoms, global_keybinds: Vec, } struct Atoms { protocols: Atom, delete_window: Atom, active_window: Atom, take_focus: Atom, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum KeyOrButton { Key(i32, u32), Button(u32, u32, u64), } impl KeyOrButton { #[allow(dead_code)] pub fn key(keycode: i32, modmask: u32) -> Self { Self::Key(keycode, modmask) } pub fn button(button: u32, modmask: u32, buttonmask: i64) -> Self { Self::Button(button, modmask, buttonmask as u64) } } #[derive(Clone)] pub struct Display(Rc<*mut xlib::Display>); impl Drop for XLib { fn drop(&mut self) { self.close_dpy(); } } impl XLib { pub fn new() -> Self { let (display, _screen, root) = unsafe { let display = XOpenDisplay(null()); assert_ne!(display, null_mut()); let display = Display::new(display); let screen = XDefaultScreen(display.get()); let root = XRootWindow(display.get(), screen); (display, screen, root) }; Self { atoms: Atoms::init(display.clone()), global_keybinds: Vec::new(), root, _screen, display, } } pub fn init(&mut self) { unsafe { let mut window_attributes = std::mem::MaybeUninit::::zeroed() .assume_init(); window_attributes.event_mask = SubstructureRedirectMask | StructureNotifyMask | SubstructureNotifyMask | EnterWindowMask | PointerMotionMask | ButtonPressMask; xlib::XChangeWindowAttributes( self.dpy(), self.root, CWEventMask, &mut window_attributes, ); xlib::XSelectInput( self.dpy(), self.root, window_attributes.event_mask, ); XSetErrorHandler(Some(xlib_error_handler)); XSync(self.dpy(), 0); } self.grab_global_keybinds(self.root); } pub fn add_global_keybind(&mut self, key: KeyOrButton) { self.global_keybinds.push(key); } #[allow(dead_code)] fn ungrab_global_keybings(&self, window: Window) { unsafe { XUngrabButton(self.dpy(), AnyButton as u32, AnyModifier, window); XUngrabKey(self.dpy(), AnyKey, AnyModifier, window); } } fn grab_global_keybinds(&self, window: Window) { for kb in self.global_keybinds.iter() { self.grab_key_or_button(window, kb); } } #[allow(dead_code)] pub fn remove_global_keybind(&mut self, key: &KeyOrButton) { if self.global_keybinds.contains(key) { self.global_keybinds.retain(|kb| kb != key); } } fn dpy(&self) -> *mut xlib::Display { self.display.get() } #[allow(dead_code)] pub fn squash_event(&self, event_type: i32) -> XEvent { unsafe { let mut event = std::mem::MaybeUninit::::zeroed().assume_init(); while xlib::XCheckTypedEvent(self.dpy(), event_type, &mut event) != 0 {} event } } pub fn next_event(&self) -> XEvent { unsafe { let mut event = std::mem::MaybeUninit::::zeroed().assume_init(); xlib::XNextEvent(self.dpy(), &mut event); event } } pub fn grab_key_or_button(&self, window: Window, key: &KeyOrButton) { let numlock_mask = self.get_numlock_mask(); let modifiers = vec![0, LockMask, numlock_mask, LockMask | numlock_mask]; for modifier in modifiers.iter() { match key { &KeyOrButton::Key(keycode, modmask) => { unsafe { xlib::XGrabKey( self.dpy(), keycode, modmask | modifier, window, 1, /* true */ GrabModeAsync, GrabModeAsync, ); } } &KeyOrButton::Button(button, modmask, buttonmask) => { unsafe { xlib::XGrabButton( self.dpy(), button, modmask | modifier, window, 1, /*true */ buttonmask as u32, GrabModeAsync, GrabModeAsync, 0, 0, ); } } } } } pub fn focus_client(&self, client: &Client) { unsafe { xlib::XSetInputFocus( self.dpy(), client.window, xlib::RevertToPointerRoot, xlib::CurrentTime, ); let screen = xlib::XDefaultScreenOfDisplay(self.dpy()).as_ref(); if let Some(screen) = screen { xlib::XSetWindowBorder( self.dpy(), client.window, screen.white_pixel, ); } xlib::XChangeProperty( self.dpy(), self.root, self.atoms.active_window, xlib::XA_WINDOW, 32, xlib::PropModeReplace, &client.window as *const u64 as *const _, 1, ); } self.send_protocol(client, self.atoms.take_focus); } pub fn unfocus_client(&self, client: &Client) { //info!("unfocusing client: {:?}", client); unsafe { xlib::XSetInputFocus( self.dpy(), self.root, xlib::RevertToPointerRoot, xlib::CurrentTime, ); let screen = xlib::XDefaultScreenOfDisplay(self.dpy()).as_ref(); if let Some(screen) = screen { xlib::XSetWindowBorder( self.dpy(), client.window, screen.black_pixel, ); } // xlib::XDeleteProperty( // self.dpy(), // self.root, // self.atoms.active_window, // ); } } pub fn move_resize_client(&self, client: &Client) { let mut windowchanges = xlib::XWindowChanges { x: client.position.x, y: client.position.y, width: client.size.x, height: client.size.y, border_width: 0, sibling: 0, stack_mode: 0, }; if client.size.x < 1 || client.size.y < 1 { error!("client {:?} size is less than 1 pixel!", client); } else { unsafe { xlib::XConfigureWindow( self.dpy(), client.window, (xlib::CWY | xlib::CWX | xlib::CWHeight | xlib::CWWidth) as u32, &mut windowchanges, ); // I don't think I have to call this ~ //self.configure_client(client); } } } pub fn move_client(&self, client: &Client) { let mut wc = xlib::XWindowChanges { x: client.position.x, y: client.position.y, width: client.size.x, height: client.size.y, border_width: 0, sibling: 0, stack_mode: 0, }; if client.size.x < 1 || client.size.y < 1 { error!("client {:?} size is less than 1 pixel!", client); } else { unsafe { xlib::XConfigureWindow( self.dpy(), client.window, (xlib::CWX | xlib::CWY) as u32, &mut wc, ); } } } pub fn resize_client(&self, client: &Client) { let mut wc = xlib::XWindowChanges { x: client.position.x, y: client.position.y, width: client.size.x, height: client.size.y, border_width: 0, sibling: 0, stack_mode: 0, }; if client.size.x < 1 || client.size.y < 1 { error!("client {:?} size is less than 1 pixel!", client); } else { unsafe { xlib::XConfigureWindow( self.dpy(), client.window, (xlib::CWWidth | xlib::CWHeight) as u32, &mut wc, ); } } } pub fn hide_client(&self, client: &Client) { let mut wc = xlib::XWindowChanges { x: client.size.x * -2, y: client.position.y, width: client.size.x, height: client.size.y, border_width: 0, sibling: 0, stack_mode: 0, }; if client.size.x < 1 || client.size.y < 1 { error!("client {:?} size is less than 1 pixel!", client); } else { unsafe { xlib::XConfigureWindow( self.dpy(), client.window, (xlib::CWX | xlib::CWY) as u32, &mut wc, ); } } } pub fn raise_client(&self, client: &Client) { unsafe { XRaiseWindow(self.dpy(), client.window); } } pub fn get_window_size(&self, window: Window) -> Option<(i32, i32)> { let mut wa = unsafe { std::mem::MaybeUninit::::zeroed() .assume_init() }; if unsafe { xlib::XGetWindowAttributes(self.dpy(), window, &mut wa) != 0 } { Some((wa.width, wa.height)) } else { None } } #[allow(dead_code)] fn get_window_attributes( &self, window: Window, ) -> Option { let mut wa = unsafe { std::mem::MaybeUninit::::zeroed() .assume_init() }; if unsafe { xlib::XGetWindowAttributes(self.dpy(), window, &mut wa) != 0 } { Some(wa) } else { None } } pub fn get_transient_for_window(&self, window: Window) -> Option { let mut transient_for: Window = 0; if unsafe { XGetTransientForHint(self.dpy(), window, &mut transient_for) != 0 } { Some(transient_for) } else { None } } #[allow(dead_code)] pub fn expose_client(&self, client: &Client) { self.expose_window(client.window); } #[allow(dead_code)] fn expose_window(&self, window: Window) { if let Some(wa) = self.get_window_attributes(window) { unsafe { xlib::XClearArea( self.dpy(), window, 0, 0, wa.width as u32, wa.height as u32, 1, ); } } } pub fn configure_window(&self, event: &XConfigureRequestEvent) { let mut wc = xlib::XWindowChanges { x: event.x, y: event.y, width: event.width, height: event.height, border_width: event.border_width, sibling: event.above, stack_mode: event.detail, }; unsafe { xlib::XConfigureWindow( self.dpy(), event.window, event.value_mask as u32, &mut wc, ); } } pub fn configure_client(&self, client: &Client, border: i32) { let mut event = xlib::XConfigureEvent { type_: xlib::ConfigureNotify, display: self.dpy(), event: client.window, window: client.window, x: client.position.x, y: client.position.y, width: client.size.x, height: client.size.y, border_width: border, override_redirect: 0, send_event: 0, serial: 0, above: 0, }; unsafe { xlib::XSetWindowBorderWidth( self.dpy(), event.window, event.border_width as u32, ); xlib::XSendEvent( self.dpy(), event.window, 0, StructureNotifyMask, &mut event as *mut _ as *mut XEvent, ); } } pub fn map_window(&self, window: Window) { unsafe { XMapWindow(self.dpy(), window); xlib::XSelectInput( self.dpy(), window, EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask, ); } self.grab_global_keybinds(window); } pub fn dimensions(&self) -> (i32, i32) { unsafe { let mut wa = std::mem::MaybeUninit::::zeroed() .assume_init(); xlib::XGetWindowAttributes(self.dpy(), self.root, &mut wa); info!("Root window dimensions: {}, {}", wa.width, wa.height); (wa.width, wa.height) } } fn close_dpy(&self) { unsafe { XCloseDisplay(self.dpy()); } } pub fn kill_client(&self, client: &Client) { if !self.send_protocol(client, self.atoms.delete_window) { unsafe { XKillClient(self.dpy(), client.window); } } } pub fn grab_cursor(&self) { unsafe { XGrabPointer( self.dpy(), self.root, 0, (ButtonPressMask | ButtonReleaseMask | PointerMotionMask) as u32, GrabModeAsync, GrabModeAsync, 0, 0, CurrentTime, ); } } pub fn release_cursor(&self) { unsafe { XUngrabPointer(self.dpy(), CurrentTime); } } pub fn move_cursor(&self, window: Option, position: (i32, i32)) { unsafe { XWarpPointer( self.dpy(), 0, window.unwrap_or(self.root), 0, 0, 0, 0, position.0, position.1, ); } } fn check_for_protocol(&self, client: &Client, proto: xlib::Atom) -> bool { let mut protos: *mut xlib::Atom = null_mut(); let mut num_protos: i32 = 0; unsafe { if xlib::XGetWMProtocols( self.dpy(), client.window, &mut protos, &mut num_protos, ) != 0 { for i in 0..num_protos { if *protos.offset(i as isize) == proto { return true; } } } } return false; } fn send_protocol(&self, client: &Client, proto: xlib::Atom) -> bool { if self.check_for_protocol(client, proto) { let mut data = xlib::ClientMessageData::default(); data.set_long(0, proto as i64); let mut event = XEvent { client_message: xlib::XClientMessageEvent { type_: xlib::ClientMessage, serial: 0, display: self.dpy(), send_event: 0, window: client.window, format: 32, message_type: self.atoms.protocols, data, }, }; unsafe { xlib::XSendEvent( self.dpy(), client.window, 0, xlib::NoEventMask, &mut event, ); } true } else { false } } pub fn make_key(&self, key: S, modmask: u32) -> KeyOrButton where S: AsRef, { let key = self.keycode(key); KeyOrButton::Key(key, modmask) } fn keycode(&self, string: S) -> i32 where S: AsRef, { let c_string = CString::new(string.as_ref()).unwrap(); unsafe { let keysym = xlib::XStringToKeysym(c_string.as_ptr()); xlib::XKeysymToKeycode(self.dpy(), keysym) as i32 } } fn get_numlock_mask(&self) -> u32 { unsafe { let modmap = xlib::XGetModifierMapping(self.dpy()); let max_keypermod = (*modmap).max_keypermod; for i in 0..8 { for j in 0..max_keypermod { if *(*modmap) .modifiermap .offset((i * max_keypermod + j) as isize) == xlib::XKeysymToKeycode( self.dpy(), x11::keysym::XK_Num_Lock as u64, ) { return 1 << i; } } } } 0 } pub fn get_clean_mask(&self) -> u32 { !(self.get_numlock_mask() | LockMask) & (ShiftMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) } #[allow(dead_code)] pub fn clean_mask(&self, mask: u32) -> u32 { mask & self.get_clean_mask() } pub fn are_masks_equal(&self, rhs: u32, lhs: u32) -> bool { let clean = self.get_clean_mask(); rhs & clean == lhs & clean } } impl Display { pub fn new(display: *mut x11::xlib::Display) -> Self { Self { 0: Rc::new(display), } } pub fn get(&self) -> *mut x11::xlib::Display { *self.0 } } impl Atoms { fn init(display: Display) -> Self { unsafe { Self { protocols: { let name = CString::new("WM_PROTOCOLS").unwrap(); XInternAtom(display.get(), name.as_c_str().as_ptr(), 0) }, delete_window: { let name = CString::new("WM_DELETE_WINDOW").unwrap(); XInternAtom(display.get(), name.as_c_str().as_ptr(), 0) }, active_window: { let name = CString::new("WM_ACTIVE_WINDOW").unwrap(); XInternAtom(display.get(), name.as_c_str().as_ptr(), 0) }, take_focus: { let name = CString::new("WM_TAKE_FOCUS").unwrap(); XInternAtom(display.get(), name.as_c_str().as_ptr(), 0) }, } } } } #[allow(dead_code)] unsafe extern "C" fn xlib_error_handler( _dpy: *mut x11::xlib::Display, ee: *mut x11::xlib::XErrorEvent, ) -> std::os::raw::c_int { let err = ee.as_ref().unwrap(); if err.error_code == x11::xlib::BadWindow || err.error_code == x11::xlib::BadDrawable || err.error_code == x11::xlib::BadAccess || err.error_code == x11::xlib::BadMatch { 0 } else { error!( "wm: fatal error:\nrequest_code: {}\nerror_code: {}", err.request_code, err.error_code ); std::process::exit(1); } }