//! A lightweight X11 window manager, inspired by dwm. use atoms::InternedAtoms; use clients::ClientState; use colours::Colours; use cursors::Cursors; use error::*; use keys::KeyboardInfo; use xcb::{ x::{self, ChangeWindowAttributes, PropertyNotifyEvent, Window}, Connection, Event, Extension, }; mod atoms; mod clients; mod colours; mod config; mod cursors; mod error; mod focus; mod keys; fn main() -> Result<()> { cleanup_process_children(); let (conn, screen_num) = Connection::connect_with_extensions(None, &[], &[Extension::Xinerama])?; let mut wm = WM::new(&conn, screen_num)?; wm.event_loop()?; Ok(()) } /// The window manager's state struct WM<'a> { /// The open connection to an X server conn: &'a Connection, /// The 'screen' number on the X server /// Note this isn't what you think it is on multi-monitor setups screen_num: i32, /// The root window root: Window, /// WM client state clients: ClientState, /// Cached colours, colours: Colours, /// Cached cursors cursors: Cursors, /// Cached atoms atoms: InternedAtoms, /// Cached keyboard layout information keyboard_state: KeyboardInfo, } impl WM<'_> { /// Prepare the window manager to run on the given connection and screen number. /// This will fail if another WM is running. fn new(conn: &'_ Connection, screen_num: i32) -> Result> { // Fetch root window let setup = conn.get_setup(); let screen = setup .roots() .nth(screen_num as usize) .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)?; Ok(WM { colours: Colours::new_with(conn, screen.default_colormap())?, atoms: InternedAtoms::new_with(conn)?, cursors: Cursors::new_with(conn)?, keyboard_state: KeyboardInfo::new_with(conn)?, clients: Default::default(), conn, screen_num, root: screen.root(), }) } /// Set the correct properties on the root window fn setup_root(&mut self) -> Result<()> { // TODO: Set EHWM properties on root window self.conn .check_request(self.conn.send_request_checked(&ChangeWindowAttributes { window: self.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(self.cursors.normal()), ], }))?; self.grab_keys()?; // TODO: reset focus self.unfocus(); Ok(()) } fn event_loop(&mut self) -> Result<()> { // Perform setup self.update_geometry()?; self.setup_root()?; loop { match self.conn.wait_for_event()? { // 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.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)?, _ => {} }; } } /// Handle a property notify event, by doing *todo* fn handle_property_notify(&self, _e: PropertyNotifyEvent) -> Result<()> { println!("TODO: handle_property_notify"); Ok(()) } } /// 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 } impl<'a> std::fmt::Debug for WM<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("WM") .field("screen_num", &self.screen_num) .field("root", &self.root) .field("clients", &self.clients) .field("atoms", &self.atoms) .finish() } }