//! A lightweight X11 window manager, inspired by dwm. use atoms::Atoms; use clients::ClientState; use colours::Colours; use cursors::Cursors; use error::*; use keys::KeyboardInfo; use xcb::{ x::{ self, ChangeProperty, ChangeWindowAttributes, CreateWindow, DeleteProperty, PropertyNotifyEvent, Window, WindowClass, }, 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, /// 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, /// WM client state clients: ClientState, /// Cached colours, colours: Colours, /// Cached cursors cursors: Cursors, /// Cached atoms atoms: Atoms, /// 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: Atoms::intern_all(conn)?, cursors: Cursors::new_with(conn)?, keyboard_state: KeyboardInfo::new_with(conn)?, clients: Default::default(), check_window: conn.generate_id(), conn, screen_num, root: screen.root(), }) } /// Set the correct properties on the root window fn setup_root(&mut self) -> Result<()> { // Check window self.conn.send_request(&CreateWindow { wid: self.check_window, parent: self.root, depth: 0, x: 0, y: 0, width: 0, height: 0, border_width: 0, class: WindowClass::InputOutput, visual: 0, value_list: &[], }); self.conn.send_request(&ChangeProperty { mode: x::PropMode::Replace, window: self.root, property: self.atoms.net_wm_check, r#type: x::ATOM_WINDOW, data: &[self.check_window], }); self.conn.send_request(&ChangeProperty { mode: x::PropMode::Replace, window: self.check_window, property: self.atoms.net_wm_check, r#type: x::ATOM_WINDOW, data: &[self.check_window], }); self.conn.send_request(&ChangeProperty { mode: x::PropMode::Replace, window: self.check_window, property: self.atoms.net_wm_name, r#type: x::ATOM_STRING, data: b"blow", }); // Supported flag self.conn.send_request(&ChangeProperty { mode: x::PropMode::Replace, window: self.root, property: self.atoms.net_supported, r#type: x::ATOM_ATOM, data: &[ self.atoms.net_active_window, self.atoms.net_wm_name, self.atoms.net_wm_state, self.atoms.net_wm_check, self.atoms.net_wm_fullscreen, self.atoms.net_wm_window_type, self.atoms.net_wm_window_type_dialog, self.atoms.net_client_list, ], }); // Cleanup state self.conn.send_request(&DeleteProperty { window: self.root, property: self.atoms.net_client_list, }); // Get the right events self.conn.send_request(&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()?; self.refocus(usize::MAX, usize::MAX); self.conn.flush()?; 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() } }