diff options
Diffstat (limited to 'src/conn_info/mod.rs')
-rw-r--r-- | src/conn_info/mod.rs | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/src/conn_info/mod.rs b/src/conn_info/mod.rs new file mode 100644 index 0000000..623e2a5 --- /dev/null +++ b/src/conn_info/mod.rs @@ -0,0 +1,225 @@ +use xcb::{ + x::{ + self, ChangeProperty, ChangeWindowAttributes, CreateWindow, DeleteProperty, DestroyWindow, + Window, WindowClass, + }, + Connection as RawConnection, +}; + +mod atoms; +mod colours; +mod cursors; +mod keys; + +pub use self::{atoms::Atoms, colours::Colours, cursors::Cursors, keys::KeyboardInfo}; +use crate::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) + // TODO: Destroy this properly + 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<Self> { + // 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: 0, + height: 0, + border_width: 0, + class: WindowClass::InputOutput, + 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::STRUCTURE_NOTIFY + | x::EventMask::PROPERTY_CHANGE, + ), + x::Cw::Cursor(cursors.normal()), + ], + }); + + Ok(Self { + colours: Colours::new_with(conn, screen.default_colormap())?, + atoms: Atoms::intern_all(conn)?, + cursors: Cursors::new_with(conn)?, + keyboard_state: KeyboardInfo::new_with(conn)?, + check_window: conn.generate_id(), + conn, + screen_num, + root: screen.root(), + }) + } + + /// Get the root window our WM is using + pub const fn root(&self) -> Window { + self.root + } + + /// Refresh cached info about keyboard layout + pub fn refresh_keyboard_info(&mut self) -> Result<()> { + self.keyboard_state = KeyboardInfo::new_with(self.conn)?; + Ok(()) + } + + /// Delegate for [`RawConnection::send_request`] + pub fn send_request<R>(&self, req: &R) -> R::Cookie + where + R: xcb::Request, + { + self.conn.send_request(req) + } + + /// Delegate for [`RawConnection::send_and_check_request`] + pub fn send_and_check_request<R>(&self, req: &R) -> Result<()> + where + R: xcb::RequestWithoutReply, + { + self.conn.send_and_check_request(req).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<xcb::Event> { + self.conn.wait_for_event().map_err(Into::into) + } + + pub fn active_extensions(&self) -> impl Iterator<Item = xcb::Extension> + '_ { + self.conn.active_extensions() + } + + pub fn wait_for_reply<C>(&self, cookie: C) -> xcb::Result<C::Reply> + where + C: xcb::CookieWithReplyChecked, + { + self.conn.wait_for_reply(cookie) + } + + pub fn get_setup(&self) -> &x::Setup { + self.conn.get_setup() + } + + pub const fn screen_num(&self) -> usize { + self.screen_num + } +} + +impl Drop for Connection<'_> { + fn drop(&mut self) { + self.send_request(&DestroyWindow { + window: self.check_window, + }); + // TODO: Unset attributes of root window + todo!() + } +} |