use log::{debug, error, warn}; use num_traits::Zero; use std::{convert::TryFrom, ptr::NonNull, rc::Rc}; use thiserror::Error; use x11::xlib::{self, Atom, Success, XEvent, XKeyEvent}; use crate::backends::{ keycodes::KeyOrButton, xlib::keysym::mouse_button_to_xbutton, }; use self::{ connection::XLibConnection, ewmh::{EWMHAtom, EWMHAtoms}, keysym::{ keysym_to_virtual_keycode, virtual_keycode_to_keysym, xev_to_mouse_button, XKeySym, }, wmh::{ICCCMAtom, ICCCMAtoms}, }; use super::{ keycodes::VirtualKeyCode, structs::WindowType, window_event::{ ButtonEvent, ConfigureEvent, DestroyEvent, EnterEvent, FullscreenEvent, FullscreenState, KeyEvent, KeyOrMouseBind, KeyState, MapEvent, ModifierState, MotionEvent, UnmapEvent, WindowEvent, WindowNameEvent, }, WindowServerBackend, }; use crate::util::{Point, Size}; pub mod color; pub mod keysym; pub type XLibWindowEvent = WindowEvent; #[derive(Clone)] pub struct Display(Rc>); #[derive(Debug, Error)] pub enum XlibError { #[error("BadAccess")] BadAccess, #[error("BadAlloc")] BadAlloc, #[error("BadAtom")] BadAtom, #[error("BadColor")] BadColor, #[error("BadCursor")] BadCursor, #[error("BadDrawable")] BadDrawable, #[error("BadFont")] BadFont, #[error("BadGC")] BadGC, #[error("BadIDChoice")] BadIDChoice, #[error("BadImplementation")] BadImplementation, #[error("BadLength")] BadLength, #[error("BadMatch")] BadMatch, #[error("BadName")] BadName, #[error("BadPixmap")] BadPixmap, #[error("BadRequest")] BadRequest, #[error("BadValue")] BadValue, #[error("BadWindow")] BadWindow, #[error("Invalid XError: {0}")] InvalidError(u8), } impl From for XlibError { fn from(value: u8) -> Self { match value { xlib::BadAccess => XlibError::BadAccess, xlib::BadAlloc => XlibError::BadAlloc, xlib::BadAtom => XlibError::BadAtom, xlib::BadColor => XlibError::BadColor, xlib::BadCursor => XlibError::BadCursor, xlib::BadDrawable => XlibError::BadDrawable, xlib::BadFont => XlibError::BadFont, xlib::BadGC => XlibError::BadGC, xlib::BadIDChoice => XlibError::BadIDChoice, xlib::BadImplementation => XlibError::BadImplementation, xlib::BadLength => XlibError::BadLength, xlib::BadMatch => XlibError::BadMatch, xlib::BadName => XlibError::BadName, xlib::BadPixmap => XlibError::BadPixmap, xlib::BadRequest => XlibError::BadRequest, xlib::BadValue => XlibError::BadValue, xlib::BadWindow => XlibError::BadWindow, any => XlibError::InvalidError(any), } } } pub mod wmh { use std::{borrow::Borrow, ffi::CString, ops::Index}; use strum::{EnumCount, EnumIter}; use x11::xlib::Atom; use super::{connection::XLibConnection, Display}; #[derive(Debug, PartialEq, Eq, EnumIter, EnumCount, Clone, Copy)] pub enum ICCCMAtom { WmName, WmProtocols, WmDeleteWindow, WmActiveWindow, WmTakeFocus, WmState, WmTransientFor, } #[derive(Debug, Clone)] pub struct ICCCMAtoms { inner: Vec, } impl Index for ICCCMAtoms { type Output = Atom; fn index(&self, index: ICCCMAtom) -> &Self::Output { &self.inner[usize::from(index)] } } impl ICCCMAtoms { pub fn from_connection>( con: C, ) -> Option { ICCCMAtom::try_get_atoms(con.borrow().display()) .map(|atoms| Self { inner: atoms }) } } impl ICCCMAtom { pub fn try_get_atoms(display: Display) -> Option> { use strum::IntoEnumIterator; Self::iter() .map(|atom| atom.try_into_x_atom(&display)) .collect::>>() } fn try_into_x_atom(self, display: &Display) -> Option { let name = CString::new::<&str>(self.into()).ok()?; match unsafe { x11::xlib::XInternAtom( display.get(), name.as_c_str().as_ptr(), 0, ) } { 0 => None, atom => Some(atom), } } } impl From for usize { fn from(atom: ICCCMAtom) -> Self { atom as usize } } impl From for &str { fn from(atom: ICCCMAtom) -> Self { match atom { ICCCMAtom::WmName => "WM_NAME", ICCCMAtom::WmProtocols => "WM_PROTOCOLS", ICCCMAtom::WmDeleteWindow => "WM_DELETE_WINDOW", ICCCMAtom::WmActiveWindow => "WM_ACTIVE_WINDOW", ICCCMAtom::WmTakeFocus => "WM_TAKE_FOCUS", ICCCMAtom::WmState => "WM_STATE", ICCCMAtom::WmTransientFor => "WM_TRANSIENT_FOR", } } } } pub mod ewmh { use std::{borrow::Borrow, ffi::CString, ops::Index, os::raw::c_long}; use strum::{EnumCount, EnumIter, FromRepr}; use x11::xlib::{Atom, XA_ATOM}; use super::{ connection::{PropMode, XLibConnection}, Display, }; #[derive( Debug, PartialEq, Eq, EnumIter, EnumCount, Clone, Copy, FromRepr, )] pub enum EWMHAtom { NetSupported, NetClientList, NetNumberOfDesktops, NetDesktopGeometry, NetDesktopViewport, NetCurrentDesktop, NetDesktopNames, NetActiveWindow, NetWorkarea, NetSupportingWmCheck, NetVirtualRoots, NetDesktopLayout, NetShowingDesktop, NetCloseWindow, NetMoveresizeWindow, NetWmMoveresize, NetRestackWindow, NetRequestFrameExtents, NetWmName, NetWmVisibleName, NetWmIconName, NetWmVisibleIconName, NetWmDesktop, NetWmWindowType, NetWmState, NetWmAllowedActions, NetWmStrut, NetWmStrutPartial, NetWmIconGeometry, NetWmIcon, NetWmPid, NetWmHandledIcons, NetWmUserTime, NetFrameExtents, NetWmPing, NetWmSyncRequest, // idk if these are atoms? NetWmWindowTypeDesktop, NetWmWindowTypeDock, NetWmWindowTypeToolbar, NetWmWindowTypeMenu, NetWmWindowTypeUtility, NetWmWindowTypeSplash, NetWmWindowTypeDialog, NetWmWindowTypeNormal, NetWmStateModal, NetWmStateSticky, NetWmStateMaximizedVert, NetWmStateMaximizedHorz, NetWmStateShaded, NetWmStateSkipTaskbar, NetWmStateSkipPager, NetWmStateHidden, NetWmStateFullscreen, NetWmStateAbove, NetWmStateBelow, NetWmStateDemandsAttention, NetWmActionMove, NetWmActionResize, NetWmActionMinimize, NetWmActionShade, NetWmActionStick, NetWmActionMaximizeHorz, NetWmActionMaximizeVert, NetWmActionFullscreen, NetWmActionChangeDesktop, NetWmActionClose, } #[derive(Debug, Clone)] pub struct EWMHAtoms { inner: Vec, } impl Index for EWMHAtoms { type Output = Atom; fn index(&self, index: EWMHAtom) -> &Self::Output { &self.inner[usize::from(index)] } } impl EWMHAtoms { pub fn from_connection>( con: C, ) -> Option { EWMHAtom::try_get_atoms(con.borrow().display()) .map(|atoms| Self { inner: atoms }) } pub fn reverse_lookup(&self, atom: Atom) -> Option { self.inner .iter() .position(|a| *a == atom) .map(|position| EWMHAtom::from_repr(position)) .flatten() } pub fn set_supported_atoms>(&self, con: C) { let supported_atoms = [ self[EWMHAtom::NetActiveWindow], self[EWMHAtom::NetWmWindowType], self[EWMHAtom::NetWmWindowTypeDialog], self[EWMHAtom::NetWmState], self[EWMHAtom::NetWmName], self[EWMHAtom::NetClientList], self[EWMHAtom::NetWmStateFullscreen], ] .to_vec(); con.borrow().change_root_property_long( self[EWMHAtom::NetSupported], XA_ATOM, PropMode::Replace, supported_atoms .into_iter() .map(|atom| atom as c_long) .collect::>(), ); } } impl EWMHAtom { pub fn try_get_atoms(display: Display) -> Option> { use strum::IntoEnumIterator; Self::iter() .map(|atom| atom.try_into_x_atom(&display)) .collect::>>() } fn try_into_x_atom(self, display: &Display) -> Option { let name = CString::new::<&str>(self.into()).ok()?; match unsafe { x11::xlib::XInternAtom( display.get(), name.as_c_str().as_ptr(), 0, ) } { 0 => None, atom => Some(atom), } } } impl From for u8 { fn from(atom: EWMHAtom) -> Self { atom as u8 } } impl From for usize { fn from(atom: EWMHAtom) -> Self { atom as usize } } impl From for &str { fn from(atom: EWMHAtom) -> Self { match atom { EWMHAtom::NetSupported => "_NET_SUPPORTED", EWMHAtom::NetClientList => "_NET_CLIENT_LIST", EWMHAtom::NetNumberOfDesktops => "_NET_NUMBER_OF_DESKTOPS", EWMHAtom::NetDesktopGeometry => "_NET_DESKTOP_GEOMETRY", EWMHAtom::NetDesktopViewport => "_NET_DESKTOP_VIEWPORT", EWMHAtom::NetCurrentDesktop => "_NET_CURRENT_DESKTOP", EWMHAtom::NetDesktopNames => "_NET_DESKTOP_NAMES", EWMHAtom::NetActiveWindow => "_NET_ACTIVE_WINDOW", EWMHAtom::NetWorkarea => "_NET_WORKAREA", EWMHAtom::NetSupportingWmCheck => "_NET_SUPPORTING_WM_CHECK", EWMHAtom::NetVirtualRoots => "_NET_VIRTUAL_ROOTS", EWMHAtom::NetDesktopLayout => "_NET_DESKTOP_LAYOUT", EWMHAtom::NetShowingDesktop => "_NET_SHOWING_DESKTOP", EWMHAtom::NetCloseWindow => "_NET_CLOSE_WINDOW", EWMHAtom::NetMoveresizeWindow => "_NET_MOVERESIZE_WINDOW", EWMHAtom::NetWmMoveresize => "_NET_WM_MOVERESIZE", EWMHAtom::NetRestackWindow => "_NET_RESTACK_WINDOW", EWMHAtom::NetRequestFrameExtents => { "_NET_REQUEST_FRAME_EXTENTS" } EWMHAtom::NetWmName => "_NET_WM_NAME", EWMHAtom::NetWmVisibleName => "_NET_WM_VISIBLE_NAME", EWMHAtom::NetWmIconName => "_NET_WM_ICON_NAME", EWMHAtom::NetWmVisibleIconName => "_NET_WM_VISIBLE_ICON_NAME", EWMHAtom::NetWmDesktop => "_NET_WM_DESKTOP", EWMHAtom::NetWmWindowType => "_NET_WM_WINDOW_TYPE", EWMHAtom::NetWmState => "_NET_WM_STATE", EWMHAtom::NetWmAllowedActions => "_NET_WM_ALLOWED_ACTIONS", EWMHAtom::NetWmStrut => "_NET_WM_STRUT", EWMHAtom::NetWmStrutPartial => "_NET_WM_STRUT_PARTIAL", EWMHAtom::NetWmIconGeometry => "_NET_WM_ICON_GEOMETRY", EWMHAtom::NetWmIcon => "_NET_WM_ICON", EWMHAtom::NetWmPid => "_NET_WM_PID", EWMHAtom::NetWmHandledIcons => "_NET_WM_HANDLED_ICONS", EWMHAtom::NetWmUserTime => "_NET_WM_USER_TIME", EWMHAtom::NetFrameExtents => "_NET_FRAME_EXTENTS", EWMHAtom::NetWmPing => "_NET_WM_PING", EWMHAtom::NetWmSyncRequest => "_NET_WM_SYNC_REQUEST", EWMHAtom::NetWmWindowTypeDesktop => { "_NET_WM_WINDOW_TYPE_DESKTOP" } EWMHAtom::NetWmWindowTypeDock => "_NET_WM_WINDOW_TYPE_DOCK", EWMHAtom::NetWmWindowTypeToolbar => { "_NET_WM_WINDOW_TYPE_TOOLBAR" } EWMHAtom::NetWmWindowTypeMenu => "_NET_WM_WINDOW_TYPE_MENU", EWMHAtom::NetWmWindowTypeUtility => { "_NET_WM_WINDOW_TYPE_UTILITY" } EWMHAtom::NetWmWindowTypeSplash => "_NET_WM_WINDOW_TYPE_SPLASH", EWMHAtom::NetWmWindowTypeDialog => "_NET_WM_WINDOW_TYPE_DIALOG", EWMHAtom::NetWmWindowTypeNormal => "_NET_WM_WINDOW_TYPE_NORMAL", EWMHAtom::NetWmStateModal => "_NET_WM_STATE_MODAL", EWMHAtom::NetWmStateSticky => "_NET_WM_STATE_STICKY", EWMHAtom::NetWmStateMaximizedVert => { "_NET_WM_STATE_MAXIMIZED_VERT" } EWMHAtom::NetWmStateMaximizedHorz => { "_NET_WM_STATE_MAXIMIZED_HORZ" } EWMHAtom::NetWmStateShaded => "_NET_WM_STATE_SHADED", EWMHAtom::NetWmStateSkipTaskbar => "_NET_WM_STATE_SKIP_TASKBAR", EWMHAtom::NetWmStateSkipPager => "_NET_WM_STATE_SKIP_PAGER", EWMHAtom::NetWmStateHidden => "_NET_WM_STATE_HIDDEN", EWMHAtom::NetWmStateFullscreen => "_NET_WM_STATE_FULLSCREEN", EWMHAtom::NetWmStateAbove => "_NET_WM_STATE_ABOVE", EWMHAtom::NetWmStateBelow => "_NET_WM_STATE_BELOW", EWMHAtom::NetWmStateDemandsAttention => { "_NET_WM_STATE_DEMANDS_ATTENTION" } EWMHAtom::NetWmActionMove => "_NET_WM_ACTION_MOVE", EWMHAtom::NetWmActionResize => "_NET_WM_ACTION_RESIZE", EWMHAtom::NetWmActionMinimize => "_NET_WM_ACTION_MINIMIZE", EWMHAtom::NetWmActionShade => "_NET_WM_ACTION_SHADE", EWMHAtom::NetWmActionStick => "_NET_WM_ACTION_STICK", EWMHAtom::NetWmActionMaximizeHorz => { "_NET_WM_ACTION_MAXIMIZE_HORZ" } EWMHAtom::NetWmActionMaximizeVert => { "_NET_WM_ACTION_MAXIMIZE_VERT" } EWMHAtom::NetWmActionFullscreen => "_NET_WM_ACTION_FULLSCREEN", EWMHAtom::NetWmActionChangeDesktop => { "_NET_WM_ACTION_CHANGE_DESKTOP" } EWMHAtom::NetWmActionClose => "_NET_WM_ACTION_CLOSE", } } } #[cfg(test)] mod tests { use super::*; #[test] fn get_atoms() { let display = Display::open().unwrap(); let atoms = EWMHAtom::try_get_atoms(display).expect("atoms"); println!("{:?}", atoms); } } } pub mod connection { use std::{ ffi::CString, os::raw::{c_char, c_long}, }; use x11::xlib::{self, Atom, Window}; use super::Display; pub struct XLibConnection { display: Display, root: Window, screen: i32, } impl Drop for XLibConnection { fn drop(&mut self) { unsafe { xlib::XCloseDisplay(self.display.get()) }; } } #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum PropMode { Replace, Append, Prepend, } impl From for i32 { fn from(mode: PropMode) -> Self { match mode { PropMode::Replace => xlib::PropModeReplace, PropMode::Append => xlib::PropModeAppend, PropMode::Prepend => xlib::PropModePrepend, } } } impl XLibConnection { pub fn new() -> Option { if let Some(display) = Display::open() { let screen = unsafe { xlib::XDefaultScreen(display.get()) }; let root = unsafe { xlib::XRootWindow(display.get(), screen) }; Some(Self { display, root, screen, }) } else { None } } pub fn dpy(&self) -> *mut xlib::Display { self.display.get() } pub fn display(&self) -> Display { self.display.clone() } pub fn root(&self) -> Window { self.root } pub fn screen(&self) -> i32 { self.screen } pub fn get_text_property( &self, window: Window, atom: Atom, ) -> Option { unsafe { let mut text_prop = std::mem::MaybeUninit::::zeroed() .assume_init(); if xlib::XGetTextProperty( self.dpy(), window, &mut text_prop, atom, ) == 0 { return None; } CString::from_raw(text_prop.value.cast::()) .into_string() .ok() } } pub fn change_root_property_byte>( &self, atom: Atom, atom_type: Atom, mode: PropMode, data: T, ) { unsafe { xlib::XChangeProperty( self.dpy(), self.root, atom, atom_type, 8, mode.into(), data.as_ref().as_ptr().cast::(), data.as_ref().len() as i32, ); } } pub fn change_root_property_long>( &self, atom: Atom, atom_type: Atom, mode: PropMode, data: T, ) { unsafe { xlib::XChangeProperty( self.dpy(), self.root, atom, atom_type, 32, mode.into(), data.as_ref().as_ptr().cast::(), data.as_ref().len() as i32, ); } } } } impl Display { pub fn new(display: *mut x11::xlib::Display) -> Option { NonNull::new(display).map(|ptr| Self(Rc::new(ptr))) } // TODO: error communication pub fn open() -> Option { Self::new(unsafe { xlib::XOpenDisplay(std::ptr::null()) }) } /// this should definitely be unsafe lmao pub fn get(&self) -> *mut x11::xlib::Display { self.0.as_ptr() } } pub struct XLib { connection: Rc, //atoms: XLibAtoms, atoms: ICCCMAtoms, ewmh_atoms: EWMHAtoms, keybinds: Vec, active_border_color: Option, inactive_border_color: Option, } impl XLib { fn new() -> Self { let con = Rc::new(XLibConnection::new().expect("failed to open x display")); Self { connection: con.clone(), atoms: ICCCMAtoms::from_connection(con.clone()).expect("atoms"), ewmh_atoms: EWMHAtoms::from_connection(con.clone()) .expect("ewmh atoms"), keybinds: Vec::new(), active_border_color: None, inactive_border_color: None, } } unsafe fn init_as_wm(&self) { let mut window_attributes = std::mem::MaybeUninit::::zeroed() .assume_init(); window_attributes.event_mask = xlib::SubstructureRedirectMask | xlib::StructureNotifyMask | xlib::SubstructureNotifyMask | xlib::EnterWindowMask | xlib::PointerMotionMask | xlib::ButtonPressMask; xlib::XChangeWindowAttributes( self.connection.dpy(), self.connection.root(), xlib::CWEventMask, &mut window_attributes, ); xlib::XSelectInput( self.dpy(), self.connection.root(), window_attributes.event_mask, ); xlib::XSetErrorHandler(Some(xlib_error_handler)); xlib::XSync(self.dpy(), 0); self.ewmh_atoms.set_supported_atoms(self.connection.clone()); } //#[deprecated = "use `self.connection.dpy()` instead"] fn dpy(&self) -> *mut xlib::Display { self.connection.dpy() } fn next_xevent(&mut self) -> XEvent { let event = unsafe { let mut event = std::mem::MaybeUninit::::zeroed(); xlib::XNextEvent(self.dpy(), event.as_mut_ptr()); event.assume_init() }; // match event.get_type() { // xlib::KeyPress | xlib::KeyRelease => { // self.update_modifier_state(AsRef::::as_ref( // &event, // )); // } // _ => {} // } event } fn xevent_to_window_event(&self, event: XEvent) -> Option { match event.get_type() { xlib::MapRequest => { let ev = unsafe { &event.map_request }; Some(XLibWindowEvent::MapRequestEvent(MapEvent { window: ev.window, })) } xlib::UnmapNotify => { let ev = unsafe { &event.unmap }; Some(XLibWindowEvent::UnmapEvent(UnmapEvent { window: ev.window, })) } xlib::ConfigureRequest => { let ev = unsafe { &event.configure_request }; Some(XLibWindowEvent::ConfigureEvent(ConfigureEvent { window: ev.window, position: (ev.x, ev.y).into(), size: (ev.width, ev.height).into(), })) } xlib::EnterNotify => { let ev = unsafe { &event.crossing }; Some(XLibWindowEvent::EnterEvent(EnterEvent { window: ev.window, })) } xlib::DestroyNotify => { let ev = unsafe { &event.destroy_window }; Some(XLibWindowEvent::DestroyEvent(DestroyEvent { window: ev.window, })) } xlib::MotionNotify => { let ev = unsafe { &event.motion }; Some(XLibWindowEvent::MotionEvent(MotionEvent { position: (ev.x, ev.y).into(), window: ev.window, })) } // both ButtonPress and ButtonRelease use the XButtonEvent structure, aliased as either // XButtonReleasedEvent or XButtonPressedEvent xlib::ButtonPress | xlib::ButtonRelease => { let ev = unsafe { &event.button }; let keycode = xev_to_mouse_button(ev).unwrap(); let state = if ev.type_ == xlib::ButtonPress { KeyState::Pressed } else { KeyState::Released }; Some(XLibWindowEvent::ButtonEvent(ButtonEvent::new( ev.subwindow, state, keycode, (ev.x, ev.y).into(), ModifierState::from_modmask(ev.state), ))) } xlib::KeyPress | xlib::KeyRelease => { let ev = unsafe { &event.key }; let keycode = keysym_to_virtual_keycode(self.keyev_to_keysym(ev).get()); let state = if ev.type_ == xlib::KeyPress { KeyState::Pressed } else { KeyState::Released }; keycode.map(|keycode| { XLibWindowEvent::KeyEvent(KeyEvent::new( ev.subwindow, state, keycode, ModifierState::from_modmask(ev.state), )) }) } xlib::PropertyNotify => { let ev = unsafe { &event.property }; match ev.atom { atom if atom == self.ewmh_atoms[EWMHAtom::NetWmName] || atom == self.atoms[ICCCMAtom::WmName] => { self.get_window_name(ev.window).map(|name| { XLibWindowEvent::WindowNameEvent( WindowNameEvent::new(ev.window, name), ) }) } atom if atom == self.ewmh_atoms[EWMHAtom::NetWmWindowType] => { if self .get_atom_property( ev.window, self.ewmh_atoms[EWMHAtom::NetWmState], ) .map(|atom| { *atom == self.ewmh_atoms [EWMHAtom::NetWmStateFullscreen] }) .unwrap_or(false) { debug!("fullscreen event"); Some(XLibWindowEvent::FullscreenEvent( FullscreenEvent::new( ev.window, FullscreenState::On, ), )) } else { None } } _ => None, } } xlib::ClientMessage => { let ev = unsafe { &event.client_message }; match ev.message_type { message_type if message_type == self.ewmh_atoms[EWMHAtom::NetWmState] => { let data = ev.data.as_longs(); if data[1] as u64 == self.ewmh_atoms[EWMHAtom::NetWmStateFullscreen] || data[2] as u64 == self.ewmh_atoms [EWMHAtom::NetWmStateFullscreen] { debug!("fullscreen event"); Some(XLibWindowEvent::FullscreenEvent( FullscreenEvent::new( ev.window, match data[0] /* as u64 */ { 0 => FullscreenState::Off, 1 => FullscreenState::On, 2 => FullscreenState::Toggle, _ => { unreachable!() } }, ), )) } else { None } } _ => None, } } _ => None, } } #[allow(dead_code)] fn get_window_attributes( &self, window: xlib::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 } } fn get_atom_property( &self, window: xlib::Window, atom: xlib::Atom, ) -> Option> { let mut di = 0; let mut dl0 = 0; let mut dl1 = 0; let mut da = 0; let (atom_out, success) = xpointer::XPointer::::build_with_result(|ptr| unsafe { xlib::XGetWindowProperty( self.dpy(), window, atom, 0, std::mem::size_of::() as i64, 0, xlib::XA_ATOM, &mut da, &mut di, &mut dl0, &mut dl1, ptr as *mut _ as *mut _, ) == i32::from(Success) }); success.then(|| atom_out).flatten() } fn check_for_protocol( &self, window: xlib::Window, proto: xlib::Atom, ) -> bool { let mut protos: *mut xlib::Atom = std::ptr::null_mut(); let mut num_protos: i32 = 0; unsafe { if xlib::XGetWMProtocols( self.dpy(), 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, window: xlib::Window, proto: Atom) -> bool { if self.check_for_protocol(window, 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, format: 32, message_type: self.atoms[ICCCMAtom::WmProtocols], data, }, }; unsafe { xlib::XSendEvent( self.dpy(), window, 0, xlib::NoEventMask, &mut event, ); } true } else { false } } // #[allow(non_upper_case_globals)] // fn update_modifier_state(&mut self, keyevent: &XKeyEvent) { // //keyevent.keycode // let keysym = self.keyev_to_keysym(keyevent); // use x11::keysym::*; // let modifier = match keysym.get() { // XK_Shift_L | XK_Shift_R => Some(ModifierKey::Shift), // XK_Control_L | XK_Control_R => Some(ModifierKey::Control), // XK_Alt_L | XK_Alt_R => Some(ModifierKey::Alt), // XK_ISO_Level3_Shift => Some(ModifierKey::AltGr), // XK_Caps_Lock => Some(ModifierKey::ShiftLock), // XK_Num_Lock => Some(ModifierKey::NumLock), // XK_Win_L | XK_Win_R => Some(ModifierKey::Super), // XK_Super_L | XK_Super_R => Some(ModifierKey::Super), // _ => None, // }; // if let Some(modifier) = modifier { // match keyevent.type_ { // KeyPress => self.modifier_state.insert_mod(modifier), // KeyRelease => self.modifier_state.unset_mod(modifier), // _ => unreachable!("keyyevent != (KeyPress | KeyRelease)"), // } // } // } fn get_numlock_mask(&self) -> Option { 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 Some(1 << i); } } } } None } fn grab_key_or_button( &self, binding: &KeyOrMouseBind, window: xlib::Window, ) { let modmask = binding.modifiers.as_modmask(self); let numlock_mask = self .get_numlock_mask() .expect("failed to query numlock mask."); let modifiers = vec![ 0, xlib::LockMask, numlock_mask, xlib::LockMask | numlock_mask, ]; let keycode = match binding.key { KeyOrButton::Key(key) => self.vk_to_keycode(key), KeyOrButton::Button(button) => mouse_button_to_xbutton(button), }; for modifier in modifiers.iter() { match binding.key { KeyOrButton::Key(_) => unsafe { xlib::XGrabKey( self.dpy(), keycode, modmask | modifier, window, 1, xlib::GrabModeAsync, xlib::GrabModeAsync, ); }, KeyOrButton::Button(_) => unsafe { xlib::XGrabButton( self.dpy(), keycode as u32, modmask | modifier, window, 1, (xlib::ButtonPressMask | xlib::ButtonReleaseMask | xlib::PointerMotionMask) as u32, xlib::GrabModeAsync, xlib::GrabModeAsync, 0, 0, ); }, } } } #[allow(dead_code)] fn ungrab_key_or_button( &self, binding: &KeyOrMouseBind, window: xlib::Window, ) { let modmask = binding.modifiers.as_modmask(self); let numlock_mask = self .get_numlock_mask() .expect("failed to query numlock mask."); let modifiers = vec![ 0, xlib::LockMask, numlock_mask, xlib::LockMask | numlock_mask, ]; let keycode = match binding.key { KeyOrButton::Key(key) => self.vk_to_keycode(key), KeyOrButton::Button(button) => mouse_button_to_xbutton(button), }; for modifier in modifiers.iter() { match binding.key { KeyOrButton::Key(_) => unsafe { xlib::XUngrabKey( self.dpy(), keycode, modmask | modifier, window, ); }, KeyOrButton::Button(_) => unsafe { xlib::XUngrabButton( self.dpy(), keycode as u32, modmask | modifier, window, ); }, } } } fn grab_global_keybinds(&self, window: xlib::Window) { for binding in self.keybinds.iter() { self.grab_key_or_button(binding, window); } } fn vk_to_keycode(&self, vk: VirtualKeyCode) -> i32 { unsafe { xlib::XKeysymToKeycode( self.dpy(), virtual_keycode_to_keysym(vk).unwrap() as u64, ) as i32 } } fn keyev_to_keysym(&self, ev: &XKeyEvent) -> XKeySym { let keysym = unsafe { xlib::XLookupKeysym(ev as *const _ as *mut _, 0) }; XKeySym::new(keysym as u32) } } trait ModifierStateExt { fn as_modmask(&self, xlib: &XLib) -> u32; fn from_modmask(modmask: u32) -> Self; } impl ModifierStateExt for ModifierState { fn as_modmask(&self, xlib: &XLib) -> u32 { let mut mask = 0; let _numlock_mask = xlib .get_numlock_mask() .expect("failed to query numlock mask"); mask |= xlib::ShiftMask * u32::from(self.contains(Self::SHIFT)); //mask |= xlib::LockMask * u32::from(self.contains(Self::SHIFT_LOCK)); mask |= xlib::ControlMask * u32::from(self.contains(Self::CONTROL)); mask |= xlib::Mod1Mask * u32::from(self.contains(Self::ALT)); //mask |= xlib::Mod2Mask * u32::from(self.contains(Self::NUM_LOCK)); //mask |= xlib::Mod3Mask * u32::from(self.contains(Self::ALT_GR)); mask |= xlib::Mod4Mask * u32::from(self.contains(Self::SUPER)); //mask |= numlock_mask * u32::from(self.contains(Self::NUM_LOCK)); mask } fn from_modmask(modmask: u32) -> Self { let mut state = Self::empty(); state.set(Self::SHIFT, (modmask & xlib::ShiftMask) != 0); //state.set(Self::SHIFT_LOCK, (modmask & xlib::LockMask) != 0); state.set(Self::CONTROL, (modmask & xlib::ControlMask) != 0); state.set(Self::ALT, (modmask & xlib::Mod1Mask) != 0); //state.set(Self::NUM_LOCK, (modmask & xlib::Mod2Mask) != 0); state.set(Self::ALT_GR, (modmask & xlib::Mod3Mask) != 0); state.set(Self::SUPER, (modmask & xlib::Mod4Mask) != 0); state } } impl WindowServerBackend for XLib { type Window = xlib::Window; fn build() -> Self { let xlib = Self::new(); unsafe { xlib.init_as_wm() }; xlib } fn next_event(&mut self) -> super::window_event::WindowEvent { loop { let ev = self.next_xevent(); let ev = self.xevent_to_window_event(ev); if let Some(ev) = ev { return ev; } } } fn handle_event( &mut self, event: super::window_event::WindowEvent, ) { match event { WindowEvent::MapRequestEvent(event) => { unsafe { xlib::XMapWindow(self.dpy(), event.window); xlib::XSelectInput( self.dpy(), event.window, xlib::EnterWindowMask | xlib::FocusChangeMask | xlib::PropertyChangeMask | xlib::StructureNotifyMask, ); } self.grab_global_keybinds(event.window); } WindowEvent::ConfigureEvent(event) => { self.configure_window( event.window, Some(event.size), Some(event.position), None, ); } _ => {} } } fn add_keybind(&mut self, keybind: super::window_event::KeyOrMouseBind) { self.grab_key_or_button(&keybind, self.connection.root()); self.keybinds.push(keybind); } fn remove_keybind( &mut self, keybind: &super::window_event::KeyOrMouseBind, ) { self.keybinds.retain(|kb| kb != keybind); } fn focus_window(&self, window: Self::Window) { unsafe { xlib::XSetInputFocus( self.dpy(), window, xlib::RevertToPointerRoot, xlib::CurrentTime, ); let border_color = self .active_border_color .as_ref() .map(|color| color.pixel()) .unwrap_or_else(|| { xlib::XDefaultScreenOfDisplay(self.dpy()) .as_ref() .unwrap() .white_pixel }); xlib::XSetWindowBorder(self.dpy(), window, border_color); xlib::XChangeProperty( self.dpy(), self.connection.root(), self.atoms[ICCCMAtom::WmActiveWindow], xlib::XA_WINDOW, 32, xlib::PropModeReplace, &window as *const u64 as *const _, 1, ); } self.send_protocol(window, self.atoms[ICCCMAtom::WmTakeFocus]); } fn unfocus_window(&self, window: Self::Window) { unsafe { xlib::XSetInputFocus( self.dpy(), self.connection.root(), xlib::RevertToPointerRoot, xlib::CurrentTime, ); // TODO: make painting the window border a seperate function, and configurable let border_color = self .inactive_border_color .as_ref() .map(|color| color.pixel()) .unwrap_or_else(|| { xlib::XDefaultScreenOfDisplay(self.dpy()) .as_ref() .unwrap() .black_pixel }); xlib::XSetWindowBorder(self.dpy(), window, border_color); xlib::XDeleteProperty( self.dpy(), self.connection.root(), self.atoms[ICCCMAtom::WmActiveWindow], ); } } fn raise_window(&self, window: Self::Window) { unsafe { xlib::XRaiseWindow(self.dpy(), window); } } fn hide_window(&self, window: Self::Window) { let screen_size = self.screen_size() + Size::new(100, 100); self.move_window(window, screen_size.into()); } fn kill_window(&self, window: Self::Window) { if !self.send_protocol(window, self.atoms[ICCCMAtom::WmDeleteWindow]) { unsafe { xlib::XKillClient(self.dpy(), window); } } } fn get_parent_window(&self, window: Self::Window) -> Option { let mut parent_window: Self::Window = 0; if unsafe { xlib::XGetTransientForHint(self.dpy(), window, &mut parent_window) != 0 } { Some(parent_window) } else { None } } fn configure_window( &self, window: Self::Window, new_size: Option>, new_pos: Option>, new_border: Option, ) { let position = new_pos.unwrap_or(Point::zero()); let size = new_size.unwrap_or(Size::zero()); let mut wc = xlib::XWindowChanges { x: position.x, y: position.y, width: size.width, height: size.height, border_width: new_border.unwrap_or(0), sibling: 0, stack_mode: 0, }; let mask = { let mut mask = 0; if new_pos.is_some() { mask |= xlib::CWX | xlib::CWY; } if new_size.is_some() && wc.width > 1 && wc.height > 1 { mask |= xlib::CWWidth | xlib::CWHeight; } if new_border.is_some() { mask |= xlib::CWBorderWidth; } u32::from(mask) }; unsafe { xlib::XConfigureWindow(self.dpy(), window, mask, &mut wc); } } fn screen_size(&self) -> Size { unsafe { let mut wa = std::mem::MaybeUninit::::zeroed(); xlib::XGetWindowAttributes( self.dpy(), self.connection.root(), wa.as_mut_ptr(), ); let wa = wa.assume_init(); (wa.width, wa.height).into() } } fn get_window_size(&self, window: Self::Window) -> Option> { self.get_window_attributes(window) .map(|wa| (wa.width, wa.height).into()) } fn grab_cursor(&self) { unsafe { xlib::XGrabPointer( self.dpy(), self.connection.root(), 0, (xlib::ButtonPressMask | xlib::ButtonReleaseMask | xlib::PointerMotionMask) as u32, xlib::GrabModeAsync, xlib::GrabModeAsync, 0, 0, xlib::CurrentTime, ); } } fn ungrab_cursor(&self) { unsafe { xlib::XUngrabPointer(self.dpy(), xlib::CurrentTime); } } fn move_cursor(&self, window: Option, position: Point) { unsafe { xlib::XWarpPointer( self.dpy(), 0, window.unwrap_or(self.connection.root()), 0, 0, 0, 0, position.x, position.y, ); } } fn all_windows(&self) -> Option> { let mut parent = 0; let mut root = 0; let mut children = std::ptr::null_mut(); let mut num_children = 0; unsafe { xlib::XQueryTree( self.dpy(), self.connection.root(), &mut root, &mut parent, &mut children, &mut num_children, ) != 0 } .then(|| { let windows = unsafe { std::slice::from_raw_parts(children, num_children as usize) .to_vec() }; unsafe { xlib::XFree(children as *mut _) }; windows }) } fn set_active_window_border_color(&mut self, color_name: &str) { self.active_border_color = color::XftColor::new( self.connection.display(), self.connection.screen(), color_name.to_owned(), ) .ok(); } fn set_inactive_window_border_color(&mut self, color_name: &str) { self.inactive_border_color = color::XftColor::new( self.connection.display(), self.connection.screen(), color_name.to_owned(), ) .ok(); } fn get_window_name(&self, window: Self::Window) -> Option { self.connection .get_text_property(window, self.ewmh_atoms[EWMHAtom::NetWmName]) .or_else(|| { self.connection .get_text_property(window, self.atoms[ICCCMAtom::WmName]) }) } fn get_window_type( &self, window: Self::Window, ) -> super::structs::WindowType { match self .get_atom_property( window, self.ewmh_atoms[EWMHAtom::NetWmWindowType], ) .and_then(|atom| self.ewmh_atoms.reverse_lookup(*atom)) .and_then(|atom| WindowType::try_from(atom).ok()) { Some(window_type) => window_type, None => match self.get_parent_window(window) { Some(_) => WindowType::Dialog, None => WindowType::Normal, }, } } } impl TryFrom for WindowType { type Error = (); fn try_from(value: EWMHAtom) -> Result { match value { EWMHAtom::NetWmWindowTypeDesktop => Ok(Self::Desktop), EWMHAtom::NetWmWindowTypeDock => Ok(Self::Dock), EWMHAtom::NetWmWindowTypeUtility => Ok(Self::Utility), EWMHAtom::NetWmWindowTypeMenu => Ok(Self::Menu), EWMHAtom::NetWmWindowTypeToolbar => Ok(Self::Toolbar), EWMHAtom::NetWmWindowTypeSplash => Ok(Self::Splash), EWMHAtom::NetWmWindowTypeDialog => Ok(Self::Dialog), EWMHAtom::NetWmWindowTypeNormal => Ok(Self::Normal), _ => Err(()), } } } #[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_event = ee.as_ref().unwrap(); let err = XlibError::from(err_event.error_code); match err { err @ XlibError::BadAccess | err @ XlibError::BadMatch | err @ XlibError::BadWindow | err @ XlibError::BadDrawable => { warn!("{:?}", err); 0 } _ => { error!( "wm: fatal error:\nrequest_code: {}\nerror_code: {}", err_event.request_code, err_event.error_code ); std::process::exit(1) } } } pub mod xpointer { use std::{ ops::{Deref, DerefMut}, ptr::{null, NonNull}, }; use x11::xlib::XFree; #[repr(C)] #[derive(Debug)] pub struct XPointer(NonNull); impl XPointer { pub fn build_with(cb: F) -> Option where F: FnOnce(&mut *const ()), { let mut ptr = null() as *const (); cb(&mut ptr); NonNull::new(ptr as *mut T).map(|ptr| Self(ptr)) } pub fn build_with_result(cb: F) -> (Option, R) where F: FnOnce(&mut *const ()) -> R, { let mut ptr = null() as *const (); let result = cb(&mut ptr); (NonNull::new(ptr as *mut T).map(|ptr| Self(ptr)), result) } } impl AsRef for XPointer { fn as_ref(&self) -> &T { &**self } } impl AsMut for XPointer { fn as_mut(&mut self) -> &mut T { &mut **self } } impl PartialEq for XPointer where T: PartialEq, { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl Eq for XPointer where T: Eq {} impl Deref for XPointer { type Target = T; fn deref(&self) -> &Self::Target { unsafe { self.0.as_ref() } } } impl DerefMut for XPointer { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { self.0.as_mut() } } } impl Drop for XPointer { fn drop(&mut self) { unsafe { XFree(self.0.as_ptr() as _) }; } } }