summaryrefslogtreecommitdiff
path: root/src/conn_info/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/conn_info/mod.rs')
-rw-r--r--src/conn_info/mod.rs225
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!()
+ }
+}