//! Global resources and utilities use std::fmt::Debug; use xcb::{ x::{ self, Atom, ChangeProperty, ChangeWindowAttributes, ClientMessageData, ClientMessageEvent, CreateWindow, DeleteProperty, DestroyWindow, EventMask, GetProperty, SendEvent, SendEventDest, Window, WindowClass, }, Connection as RawConnection, VoidCookieChecked, Xid, }; #[doc(hidden)] mod atoms; #[doc(hidden)] mod colours; #[doc(hidden)] mod cursors; #[doc(hidden)] mod keys; pub use self::{ atoms::Atoms, colours::{Colour, Colours}, cursors::Cursors, keys::KeyboardInfo, }; use crate::{ debug_req, error::{Error, Result}, }; /// The connection, along with some cached resources required for WM operations. pub struct Connection<'a> { /// The open connection to an X server conn: &'a RawConnection, /// The 'screen' number on the X server. Note this isn’t what you think it is on multi-monitor setups screen_num: usize, /// The root window root: Window, /// A window used to prove we're actually EWMH compliant. /// See [the EWMH spec](https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html#idm46187912047344) check_window: Window, /// Cached colours, pub colours: Colours, /// Cached cursors pub cursors: Cursors, /// Cached atoms pub atoms: Atoms, /// Cached keyboard layout information pub keyboard_state: KeyboardInfo, } impl<'a> Connection<'a> { /// Prepare the window manager to run on the given connection and screen number. /// This will fail if another WM is running. pub fn new(conn: &'a RawConnection, screen_num: usize) -> Result { // Fetch root window let setup = conn.get_setup(); let screen = setup.roots().nth(screen_num).ok_or(Error::NoSuchScreen)?; // Check no other WM is running conn.check_request(conn.send_request_checked(&ChangeWindowAttributes { window: screen.root(), value_list: &[ x::Cw::BackPixel(screen.white_pixel()), x::Cw::EventMask(x::EventMask::SUBSTRUCTURE_REDIRECT), ], })) .map_err(|_| Error::OtherWMRunning)?; // Create check window let check_window = conn.generate_id(); let root = screen.root(); let atoms = Atoms::intern_all(conn)?; conn.send_request(&CreateWindow { wid: check_window, parent: root, depth: 0, x: 0, y: 0, width: 1, height: 1, border_width: 0, class: WindowClass::InputOnly, visual: 0, value_list: &[], }); conn.send_request(&ChangeProperty { mode: x::PropMode::Replace, window: root, property: atoms.net_wm_check, r#type: x::ATOM_WINDOW, data: &[check_window], }); conn.send_request(&ChangeProperty { mode: x::PropMode::Replace, window: check_window, property: atoms.net_wm_check, r#type: x::ATOM_WINDOW, data: &[check_window], }); conn.send_request(&ChangeProperty { mode: x::PropMode::Replace, window: check_window, property: atoms.net_wm_name, r#type: x::ATOM_STRING, data: b"blow", }); // Supported flag conn.send_request(&ChangeProperty { mode: x::PropMode::Replace, window: root, property: atoms.net_supported, r#type: x::ATOM_ATOM, data: &[ atoms.net_active_window, atoms.net_wm_name, atoms.net_wm_state, atoms.net_wm_check, atoms.net_wm_fullscreen, atoms.net_wm_window_type, atoms.net_wm_window_type_dialog, atoms.net_client_list, ], }); // Cleanup state conn.send_request(&DeleteProperty { window: root, property: atoms.net_client_list, }); // Get the right events let cursors = Cursors::new_with(conn)?; conn.send_request(&ChangeWindowAttributes { window: root, value_list: &[ x::Cw::EventMask( x::EventMask::SUBSTRUCTURE_REDIRECT | x::EventMask::SUBSTRUCTURE_NOTIFY | x::EventMask::BUTTON_PRESS | x::EventMask::ENTER_WINDOW | x::EventMask::FOCUS_CHANGE | x::EventMask::PROPERTY_CHANGE, ), x::Cw::Cursor(cursors.normal()), ], }); conn.flush()?; Ok(Self { colours: Colours::new_with(conn, screen.default_colormap())?, atoms, cursors, keyboard_state: KeyboardInfo::new_with(conn)?, check_window, conn, screen_num, root: screen.root(), }) } /// Refresh cached info about keyboard layout pub fn refresh_keyboard_info(&mut self) -> Result<()> { self.keyboard_state = KeyboardInfo::new_with(self.conn)?; Ok(()) } /// Send event to window `w`, if the event is supported. pub fn send_event(&self, window: Window, event: Atom) { let Ok(protocols) = self.wait_for_reply(self.send_request(&GetProperty { delete: false, window, property: self.atoms.wm_protocols, r#type: x::ATOM_ATOM, long_offset: 0, long_length: 100_000, })) else { return; }; if protocols.r#type() != x::ATOM_ATOM || protocols.format() != 32 { return; } let supported = protocols .value::() .iter() .any(|a| *a == event.resource_id()); if supported { self.send_request(&SendEvent { propagate: false, destination: SendEventDest::Window(window), event_mask: EventMask::NO_EVENT, event: &ClientMessageEvent::new( window, self.atoms.wm_protocols, ClientMessageData::Data32([event.resource_id(), x::CURRENT_TIME, 0, 0, 0]), ), }); } } /// Delegate for [`RawConnection::send_request`] pub fn send_request(&self, req: &R) -> R::Cookie where R: xcb::Request + Debug, { let cookie = self.conn.send_request(req); debug_req!(req, cookie); cookie } /// Delegate for [`RawConnection::send_request_checked`] #[must_use] pub fn send_request_checked(&self, req: &R) -> VoidCookieChecked where R: xcb::RequestWithoutReply + Debug, { let cookie = self.conn.send_request_checked(req); debug_req!(req, cookie); cookie } /// Delegate for [`RawConnection::send_and_check_request`] pub fn send_and_check_request(&self, req: &R) -> Result<()> where R: xcb::RequestWithoutReply + Debug, { debug_req!(req); self.conn.send_and_check_request(req).map_err(Into::into) } /// Delegate for [`RawConnection::check_request`] pub fn check_request(&self, c: VoidCookieChecked) -> Result<()> { self.conn.check_request(c).map_err(Into::into) } /// Delegate for [`RawConnection::flush`] pub fn flush(&self) -> Result<()> { self.conn.flush().map_err(Into::into) } /// Delegate for [`RawConnection::wait_for_event`] pub fn wait_for_event(&self) -> Result { self.conn.wait_for_event().map_err(Into::into) } /// Delegate for [`RawConnection::active_extensions`] pub fn active_extensions(&self) -> impl Iterator + '_ { self.conn.active_extensions() } /// Delegate for [`RawConnection::wait_for_reply`] pub fn wait_for_reply(&self, cookie: C) -> xcb::Result where C: xcb::CookieWithReplyChecked, { self.conn.wait_for_reply(cookie) } /// Delegate for [`RawConnection::get_setup`] pub fn get_setup(&self) -> &x::Setup { self.conn.get_setup() } /// The root window pub const fn root(&self) -> Window { self.root } /// The 'screen' number on the X server. Note this isn’t what you think it is on multi-monitor setups pub const fn screen_num(&self) -> usize { self.screen_num } } impl Drop for Connection<'_> { fn drop(&mut self) { self.conn.send_request(&DestroyWindow { window: self.check_window, }); // Unset attributes of root window self.conn.send_request(&DeleteProperty { window: self.root, property: self.atoms.net_wm_check, }); self.conn.send_request(&DeleteProperty { window: self.root, property: self.atoms.net_client_list, }); self.conn.send_request(&DeleteProperty { window: self.root, property: self.atoms.net_supported, }); self.conn.send_request(&ChangeWindowAttributes { window: self.root, value_list: &[x::Cw::EventMask(x::EventMask::NO_EVENT)], }); // Safety: These methods only require that the object is not used again, which will be true since we're in the destructor. unsafe { self.cursors.free(self.conn); self.colours.free(self.conn); } } }