//! A lightweight X11 window manager, inspired by dwm. //! //! # Structure //! //! The main thing a WM has to do is respond to events: this is done in the [`WM::event_loop`] function, which dispatches to the `handle_*` methods of that struct. //! //! [`conn_info`] wraps XCB's [`xcb::Connection`] type and caches common resources such as atoms, colours, cursors, and keyboard layout info. //! //! `focus.rs`, [`keys`], and [`clients`] all add some event handlers to [`WM`], but most of the important code is in [`clients`]. //! //! [`config`] holds all of the variables that a user might want to change. //! //! # XCB //! //! Unlike dwm, blow uses XCB rather than Xlib. This means requests are asynchronous by default. //! In most places, we avoid checking for errors unless we need to see the response to a request. //! Errors will be caught and logged in the event loop instead. See [`xcb`] documentation for more details. #![deny(clippy::all, clippy::pedantic, clippy::nursery)] #![allow(clippy::must_use_candidate, clippy::missing_errors_doc)] use clients::ClientState; use conn_info::Connection; pub use error::*; use xcb::{ x::{self, PropertyNotifyEvent}, Connection as RawConnection, Event, Extension, }; pub mod clients; pub mod config; pub mod conn_info; #[doc(hidden)] mod error; #[doc(hidden)] mod focus; pub mod keys; fn main() -> Result<()> { cleanup_process_children(); let (conn, screen_num) = RawConnection::connect_with_extensions(None, &[], &[Extension::Xinerama])?; #[allow(clippy::cast_sign_loss)] let mut wm = WM::new(&conn, screen_num as usize)?; wm.event_loop()?; Ok(()) } /// All of the state used by the window manager pub struct WM<'a> { conn: Connection<'a>, clients: ClientState, } impl<'a> WM<'a> { /// Prepare to start the window manager, using the given connection and scren number. pub fn new(conn: &'a RawConnection, screen_num: usize) -> Result { let mut this = Self { conn: Connection::new(conn, screen_num)?, clients: ClientState::default(), }; this.clients.update_geometry(&this.conn)?; Ok(this) } /// Run the main event loop until we encounter a non-recoverable error (usually connection). /// This will only ever return an error. pub fn event_loop(&mut self) -> Result<()> { loop { match self.conn.wait_for_event() { Ok(x) => match x { // See keys.rs Event::X(x::Event::KeyPress(e)) => self.handle_key_press(&e), Event::X(x::Event::MappingNotify(e)) => self.handle_mapping_notify(&e)?, // See clients/mod.rs Event::X(x::Event::ConfigureRequest(e)) => self.handle_configure_request(&e)?, Event::X(x::Event::ConfigureNotify(e)) => self.handle_configure_notify(&e)?, Event::X(x::Event::DestroyNotify(e)) => self.handle_destroy_notify(&e), Event::X(x::Event::MapRequest(e)) => self.handle_map_request(&e)?, Event::X(x::Event::UnmapNotify(e)) => self.handle_unmap_notify(&e), // // See focus.rs Event::X(x::Event::EnterNotify(e)) => self.handle_enter_notify(&e), Event::X(x::Event::FocusIn(e)) => self.handle_focus_in(&e), // // See below Event::X(x::Event::PropertyNotify(e)) => self.handle_property_notify(&e), _ => {} }, Err(Error::Xcb(xcb::Error::Protocol(e))) => { eprintln!("protocol error in event loop: {e:#?}\ncontinuing anyway"); } Err(e) => { eprintln!("unrecoverable error: {e:#?}\nexiting event loop"); return Err(e); } }; self.conn.flush()?; } } /// Update client properties when they change in X11. fn handle_property_notify(&mut self, e: &PropertyNotifyEvent) { if x::ATOM_WM_HINTS == e.atom() { let focused = self.clients.is_focused(e.window()); if let Some(c) = self.clients.find_client_mut(e.window()) { c.sync_properties(&self.conn, focused); } } } } /// Cleanup this process' children and set some flags. /// This is necessary when used with `startx`. fn cleanup_process_children() { // TODO: dont transform children into zombies when they terminate // TODO: cleanup zombies // todo!() }