use crate::Config; use crate::animations::{AnimationConfig, ScrollAnimation}; use crate::bar::Bar; use crate::client::{Client, TagMask}; use crate::errors::{ConfigError, WmError}; use crate::keyboard::{self, Arg, KeyAction, handlers}; use crate::layout::GapConfig; use crate::layout::tiling::TilingLayout; use crate::layout::{Layout, LayoutBox, LayoutType, layout_from_str, next_layout}; use crate::monitor::{Monitor, detect_monitors}; use crate::overlay::{ErrorOverlay, KeybindOverlay, Overlay}; use std::collections::{HashMap, HashSet}; use x11rb::cursor::Handle as CursorHandle; use x11rb::connection::Connection; use x11rb::protocol::Event; use x11rb::protocol::xproto::*; use x11rb::rust_connection::RustConnection; enum Control { Continue, Quit, } pub fn tag_mask(tag: usize) -> TagMask { 1 << tag } pub fn unmask_tag(mask: TagMask) -> usize { mask.trailing_zeros() as usize } struct AtomCache { net_supported: Atom, net_supporting_wm_check: Atom, net_current_desktop: Atom, net_client_info: Atom, wm_state: Atom, wm_protocols: Atom, wm_delete_window: Atom, net_wm_state: Atom, net_wm_state_fullscreen: Atom, net_wm_window_type: Atom, net_wm_window_type_dialog: Atom, wm_name: Atom, net_wm_name: Atom, utf8_string: Atom, net_active_window: Atom, wm_take_focus: Atom, net_client_list: Atom, } impl AtomCache { fn new(connection: &RustConnection) -> WmResult { let net_supported = connection .intern_atom(false, b"_NET_SUPPORTED")? .reply()? .atom; let net_supporting_wm_check = connection .intern_atom(false, b"_NET_SUPPORTING_WM_CHECK")? .reply()? .atom; let net_current_desktop = connection .intern_atom(false, b"_NET_CURRENT_DESKTOP")? .reply()? .atom; let net_client_info = connection .intern_atom(false, b"_NET_CLIENT_INFO")? .reply()? .atom; let wm_state = connection.intern_atom(false, b"WM_STATE")?.reply()?.atom; let wm_protocols = connection .intern_atom(false, b"WM_PROTOCOLS")? .reply()? .atom; let wm_delete_window = connection .intern_atom(false, b"WM_DELETE_WINDOW")? .reply()? .atom; let net_wm_state = connection .intern_atom(false, b"_NET_WM_STATE")? .reply()? .atom; let net_wm_state_fullscreen = connection .intern_atom(false, b"_NET_WM_STATE_FULLSCREEN")? .reply()? .atom; let net_wm_window_type = connection .intern_atom(false, b"_NET_WM_WINDOW_TYPE")? .reply()? .atom; let net_wm_window_type_dialog = connection .intern_atom(false, b"_NET_WM_WINDOW_TYPE_DIALOG")? .reply()? .atom; let wm_name = AtomEnum::WM_NAME.into(); let net_wm_name = connection .intern_atom(false, b"_NET_WM_NAME")? .reply()? .atom; let utf8_string = connection.intern_atom(false, b"UTF8_STRING")?.reply()?.atom; let net_active_window = connection .intern_atom(false, b"_NET_ACTIVE_WINDOW")? .reply()? .atom; let wm_take_focus = connection .intern_atom(false, b"WM_TAKE_FOCUS")? .reply()? .atom; let net_client_list = connection .intern_atom(false, b"_NET_CLIENT_LIST")? .reply()? .atom; Ok(Self { net_supported, net_supporting_wm_check, net_current_desktop, net_client_info, wm_state, wm_protocols, wm_delete_window, net_wm_state, net_wm_state_fullscreen, net_wm_window_type, net_wm_window_type_dialog, wm_name, net_wm_name, utf8_string, net_active_window, wm_take_focus, net_client_list, }) } } pub struct WindowManager { config: Config, connection: RustConnection, screen_number: usize, root: Window, _wm_check_window: Window, screen: Screen, windows: Vec, clients: HashMap, layout: LayoutBox, gaps_enabled: bool, floating_windows: HashSet, fullscreen_windows: HashSet, bars: Vec, tab_bars: Vec, show_bar: bool, monitors: Vec, selected_monitor: usize, atoms: AtomCache, previous_focused: Option, display: *mut x11::xlib::Display, font: crate::bar::font::Font, keychord_state: keyboard::handlers::KeychordState, current_key: usize, keyboard_mapping: Option, error_message: Option, overlay: ErrorOverlay, keybind_overlay: KeybindOverlay, scroll_animation: ScrollAnimation, animation_config: AnimationConfig, } type WmResult = Result; impl WindowManager { pub fn new(config: Config) -> WmResult { let (connection, screen_number) = x11rb::connect(None)?; let root = connection.setup().roots[screen_number].root; let screen = connection.setup().roots[screen_number].clone(); let normal_cursor = CursorHandle::new( &connection, screen_number, &x11rb::resource_manager::new_from_default(&connection)?, )? .reply()? .load_cursor(&connection, "left_ptr")?; connection .change_window_attributes( root, &ChangeWindowAttributesAux::new() .cursor(normal_cursor) .event_mask( EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY | EventMask::PROPERTY_CHANGE | EventMask::KEY_PRESS | EventMask::BUTTON_PRESS | EventMask::POINTER_MOTION, ), )? .check()?; let ignore_modifiers = [ 0, u16::from(ModMask::LOCK), u16::from(ModMask::M2), u16::from(ModMask::LOCK | ModMask::M2), ]; for &ignore_mask in &ignore_modifiers { let grab_mask = u16::from(config.modkey) | ignore_mask; connection.grab_button( false, root, EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE, GrabMode::SYNC, GrabMode::ASYNC, x11rb::NONE, x11rb::NONE, ButtonIndex::M1, grab_mask.into(), )?; connection.grab_button( false, root, EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE, GrabMode::SYNC, GrabMode::ASYNC, x11rb::NONE, x11rb::NONE, ButtonIndex::M3, grab_mask.into(), )?; } let mut monitors = detect_monitors(&connection, &screen, root)?; for monitor in monitors.iter_mut() { monitor.init_pertag(config.tags.len(), "tiling"); } let display = unsafe { x11::xlib::XOpenDisplay(std::ptr::null()) }; if display.is_null() { return Err(WmError::X11(crate::errors::X11Error::DisplayOpenFailed)); } let font = crate::bar::font::Font::new(display, screen_number as i32, &config.font)?; let mut bars = Vec::new(); for monitor in monitors.iter() { let bar = Bar::new( &connection, &screen, screen_number, &config, display, &font, monitor.screen_x as i16, monitor.screen_y as i16, monitor.screen_width as u16, )?; bars.push(bar); } let bar_height = font.height() as f32 * 1.4; let mut tab_bars = Vec::new(); for monitor in monitors.iter() { let tab_bar = crate::tab_bar::TabBar::new( &connection, &screen, screen_number, display, &font, (monitor.screen_x + config.gap_outer_horizontal as i32) as i16, (monitor.screen_y as f32 + bar_height + config.gap_outer_vertical as f32) as i16, monitor .screen_width .saturating_sub(2 * config.gap_outer_horizontal as i32) as u16, config.scheme_occupied, config.scheme_selected, )?; tab_bars.push(tab_bar); } let gaps_enabled = config.gaps_enabled; let atoms = AtomCache::new(&connection)?; let supported_atoms: Vec = vec![ atoms.net_supported, atoms.net_supporting_wm_check, atoms.net_wm_state, atoms.net_wm_state_fullscreen, atoms.net_wm_window_type, atoms.net_wm_window_type_dialog, atoms.net_active_window, atoms.net_wm_name, atoms.net_current_desktop, atoms.net_client_info, atoms.net_client_list, ]; let supported_bytes: Vec = supported_atoms .iter() .flat_map(|a| a.to_ne_bytes()) .collect(); connection.change_property( PropMode::REPLACE, root, atoms.net_supported, AtomEnum::ATOM, 32, supported_atoms.len() as u32, &supported_bytes, )?; let wm_check_window = connection.generate_id()?; connection.create_window( screen.root_depth, wm_check_window, root, 0, 0, 1, 1, 0, WindowClass::INPUT_OUTPUT, 0, &CreateWindowAux::new(), )?; connection.change_property( PropMode::REPLACE, wm_check_window, atoms.net_supporting_wm_check, AtomEnum::WINDOW, 32, 1, &wm_check_window.to_ne_bytes(), )?; connection.change_property( PropMode::REPLACE, wm_check_window, atoms.net_wm_name, atoms.utf8_string, 8, 4, b"oxwm", )?; connection.change_property( PropMode::REPLACE, root, atoms.net_supporting_wm_check, AtomEnum::WINDOW, 32, 1, &wm_check_window.to_ne_bytes(), )?; let overlay = ErrorOverlay::new( &connection, &screen, screen_number, display, &font, screen.width_in_pixels, )?; let keybind_overlay = KeybindOverlay::new(&connection, &screen, screen_number, display, config.modkey)?; let mut window_manager = Self { config, connection, screen_number, root, _wm_check_window: wm_check_window, screen, windows: Vec::new(), clients: HashMap::new(), layout: Box::new(TilingLayout), gaps_enabled, floating_windows: HashSet::new(), fullscreen_windows: HashSet::new(), bars, tab_bars, show_bar: true, monitors, selected_monitor: 0, atoms, previous_focused: None, display, font, keychord_state: keyboard::handlers::KeychordState::Idle, current_key: 0, keyboard_mapping: None, error_message: None, overlay, keybind_overlay, scroll_animation: ScrollAnimation::new(), animation_config: AnimationConfig::default(), }; for tab_bar in &window_manager.tab_bars { tab_bar.hide(&window_manager.connection)?; } window_manager.scan_existing_windows()?; window_manager.update_bar()?; window_manager.run_autostart_commands(); Ok(window_manager) } pub fn show_startup_config_error(&mut self, error: ConfigError) { let monitor = &self.monitors[self.selected_monitor]; let monitor_x = monitor.screen_x as i16; let monitor_y = monitor.screen_y as i16; let screen_width = monitor.screen_width as u16; let screen_height = monitor.screen_height as u16; if let Err(e) = self.overlay.show_error( &self.connection, &self.font, error, monitor_x, monitor_y, screen_width, screen_height, ) { eprintln!("Failed to show config error overlay: {:?}", e); } } fn try_reload_config(&mut self) -> Result<(), ConfigError> { let lua_path = self .config .path .as_ref() .ok_or(ConfigError::NoConfigPathSet)?; if !lua_path.exists() { return Err(ConfigError::NoConfigAtPath); } let config_str = std::fs::read_to_string(lua_path).map_err(|e| ConfigError::CouldNotReadConfig(e))?; let config_dir = lua_path.parent(); let new_config = crate::config::parse_lua_config(&config_str, config_dir)?; let lua_path = self.config.path.take(); self.config = new_config; self.config.path = lua_path; self.error_message = None; for bar in &mut self.bars { bar.update_from_config(&self.config); } Ok(()) } fn scan_existing_windows(&mut self) -> WmResult<()> { let tree = self.connection.query_tree(self.root)?.reply()?; let net_client_info = self.atoms.net_client_info; let wm_state_atom = self.atoms.wm_state; for &window in &tree.children { if self.bars.iter().any(|bar| bar.window() == window) { continue; } let Ok(attrs) = self.connection.get_window_attributes(window)?.reply() else { continue; }; if attrs.override_redirect { continue; } if attrs.map_state == MapState::VIEWABLE { let _tag = self.get_saved_tag(window, net_client_info)?; self.windows.push(window); continue; } if attrs.map_state == MapState::UNMAPPED { let has_wm_state = self .connection .get_property(false, window, wm_state_atom, AtomEnum::ANY, 0, 2)? .reply() .is_ok_and(|prop| !prop.value.is_empty()); if !has_wm_state { continue; } let has_wm_class = self .connection .get_property(false, window, AtomEnum::WM_CLASS, AtomEnum::STRING, 0, 1024)? .reply() .is_ok_and(|prop| !prop.value.is_empty()); if has_wm_class { let _tag = self.get_saved_tag(window, net_client_info)?; self.connection.map_window(window)?; self.windows.push(window); } } } if let Some(&first) = self.windows.first() { self.focus(Some(first))?; } self.apply_layout()?; Ok(()) } fn get_saved_tag(&self, window: Window, net_client_info: Atom) -> WmResult { match self .connection .get_property(false, window, net_client_info, AtomEnum::CARDINAL, 0, 2)? .reply() { Ok(prop) if prop.value.len() >= 4 => { let tags = u32::from_ne_bytes([ prop.value[0], prop.value[1], prop.value[2], prop.value[3], ]); if tags != 0 && tags < (1 << self.config.tags.len()) { return Ok(tags); } } Ok(_) => {} Err(e) => { eprintln!("No _NET_CLIENT_INFO property ({})", e); } } Ok(self .monitors .get(self.selected_monitor) .map(|m| m.tagset[m.selected_tags_index]) .unwrap_or(tag_mask(0))) } fn save_client_tag(&self, window: Window, tag: TagMask) -> WmResult<()> { let net_client_info = self.atoms.net_client_info; let bytes = tag.to_ne_bytes().to_vec(); self.connection.change_property( PropMode::REPLACE, window, net_client_info, AtomEnum::CARDINAL, 32, 1, &bytes, )?; self.connection.flush()?; Ok(()) } fn set_wm_state(&self, window: Window, state: u32) -> WmResult<()> { let wm_state_atom = self.atoms.wm_state; let data = [state, 0u32]; let bytes: Vec = data.iter().flat_map(|&v| v.to_ne_bytes()).collect(); self.connection.change_property( PropMode::REPLACE, window, wm_state_atom, wm_state_atom, 32, 2, &bytes, )?; self.connection.flush()?; Ok(()) } fn update_client_list(&self) -> WmResult<()> { let window_bytes: Vec = self .windows .iter() .flat_map(|window| window.to_ne_bytes()) .collect(); self.connection.change_property( PropMode::REPLACE, self.root, self.atoms.net_client_list, AtomEnum::WINDOW, 32, self.windows.len() as u32, &window_bytes, )?; Ok(()) } pub fn run(&mut self) -> WmResult<()> { println!("oxwm started on display {}", self.screen_number); self.grab_keys()?; self.update_bar()?; let mut last_bar_update = std::time::Instant::now(); const BAR_UPDATE_INTERVAL_MS: u64 = 100; loop { match self.connection.poll_for_event_with_sequence()? { Some((event, _sequence)) => { if matches!(self.handle_event(event)?, Control::Quit) { return Ok(()); } } None => { if last_bar_update.elapsed().as_millis() >= BAR_UPDATE_INTERVAL_MS as u128 { if let Some(bar) = self.bars.get_mut(self.selected_monitor) { bar.update_blocks(); } if self.bars.iter().any(|bar| bar.needs_redraw()) { self.update_bar()?; } last_bar_update = std::time::Instant::now(); } self.tick_animations()?; self.connection.flush()?; std::thread::sleep(std::time::Duration::from_millis(16)); } } } } fn toggle_floating(&mut self) -> WmResult<()> { let focused = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client); if focused.is_none() { return Ok(()); } let focused = focused.unwrap(); if let Some(client) = self.clients.get(&focused) && client.is_fullscreen { return Ok(()); } let (is_fixed, x, y, w, h) = if let Some(client) = self.clients.get(&focused) { ( client.is_fixed, client.x_position as i32, client.y_position as i32, client.width as u32, client.height as u32, ) } else { return Ok(()); }; let was_floating = self.floating_windows.contains(&focused); if was_floating { self.floating_windows.remove(&focused); if let Some(client) = self.clients.get_mut(&focused) { client.is_floating = false; } } else { self.floating_windows.insert(focused); if let Some(client) = self.clients.get_mut(&focused) { client.is_floating = is_fixed || !client.is_floating; } self.connection.configure_window( focused, &ConfigureWindowAux::new() .x(x) .y(y) .width(w) .height(h) .stack_mode(StackMode::ABOVE), )?; } self.apply_layout()?; Ok(()) } fn set_master_factor(&mut self, delta: f32) -> WmResult<()> { if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { let new_mfact = (monitor.master_factor + delta).clamp(0.05, 0.95); monitor.master_factor = new_mfact; if let Some(ref mut pertag) = monitor.pertag { pertag.master_factors[pertag.current_tag] = new_mfact; } self.apply_layout()?; } Ok(()) } fn inc_num_master(&mut self, delta: i32) -> WmResult<()> { if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { let new_nmaster = (monitor.num_master + delta).max(0); monitor.num_master = new_nmaster; if let Some(ref mut pertag) = monitor.pertag { pertag.num_masters[pertag.current_tag] = new_nmaster; } self.apply_layout()?; } Ok(()) } fn tick_animations(&mut self) -> WmResult<()> { if self.scroll_animation.is_active() { if let Some(new_offset) = self.scroll_animation.update() { if let Some(m) = self.monitors.get_mut(self.selected_monitor) { m.scroll_offset = new_offset; } self.apply_layout()?; self.update_bar()?; } } Ok(()) } fn scroll_layout(&mut self, direction: i32) -> WmResult<()> { if self.layout.name() != "scrolling" { return Ok(()); } let monitor_index = self.selected_monitor; let monitor = match self.monitors.get(monitor_index) { Some(m) => m.clone(), None => return Ok(()), }; let visible_count = if monitor.num_master > 0 { monitor.num_master as usize } else { 2 }; let mut tiled_count = 0; let mut current = self.next_tiled(monitor.clients_head, &monitor); while let Some(window) = current { tiled_count += 1; if let Some(client) = self.clients.get(&window) { current = self.next_tiled(client.next, &monitor); } else { break; } } if tiled_count <= visible_count { if let Some(m) = self.monitors.get_mut(monitor_index) { m.scroll_offset = 0; } return Ok(()); } let outer_gap = if self.gaps_enabled { self.config.gap_outer_vertical } else { 0 }; let inner_gap = if self.gaps_enabled { self.config.gap_inner_vertical } else { 0 }; let available_width = monitor.screen_width - 2 * outer_gap as i32; let total_inner_gaps = inner_gap as i32 * (visible_count - 1) as i32; let window_width = (available_width - total_inner_gaps) / visible_count as i32; let scroll_amount = window_width + inner_gap as i32; let total_width = tiled_count as i32 * window_width + (tiled_count - 1) as i32 * inner_gap as i32; let max_scroll = (total_width - available_width).max(0); let current_offset = monitor.scroll_offset; let target_offset = if self.scroll_animation.is_active() { self.scroll_animation.target() + direction * scroll_amount } else { current_offset + direction * scroll_amount }; let target_offset = target_offset.clamp(0, max_scroll); self.scroll_animation.start(current_offset, target_offset, &self.animation_config); Ok(()) } fn scroll_to_window(&mut self, target_window: Window, animate: bool) -> WmResult<()> { if self.layout.name() != "scrolling" { return Ok(()); } let monitor_index = self.selected_monitor; let monitor = match self.monitors.get(monitor_index) { Some(m) => m.clone(), None => return Ok(()), }; let visible_count = if monitor.num_master > 0 { monitor.num_master as usize } else { 2 }; let outer_gap = if self.gaps_enabled { self.config.gap_outer_vertical } else { 0 }; let inner_gap = if self.gaps_enabled { self.config.gap_inner_vertical } else { 0 }; let mut tiled_windows = Vec::new(); let mut current = self.next_tiled(monitor.clients_head, &monitor); while let Some(window) = current { tiled_windows.push(window); if let Some(client) = self.clients.get(&window) { current = self.next_tiled(client.next, &monitor); } else { break; } } let target_idx = tiled_windows.iter().position(|&w| w == target_window); let target_idx = match target_idx { Some(idx) => idx, None => return Ok(()), }; let tiled_count = tiled_windows.len(); if tiled_count <= visible_count { if animate && monitor.scroll_offset != 0 { self.scroll_animation.start(monitor.scroll_offset, 0, &self.animation_config); } else if let Some(m) = self.monitors.get_mut(monitor_index) { m.scroll_offset = 0; } return Ok(()); } let available_width = monitor.screen_width - 2 * outer_gap as i32; let total_inner_gaps = inner_gap as i32 * (visible_count - 1) as i32; let window_width = (available_width - total_inner_gaps) / visible_count as i32; let scroll_step = window_width + inner_gap as i32; let total_width = tiled_count as i32 * window_width + (tiled_count - 1) as i32 * inner_gap as i32; let max_scroll = (total_width - available_width).max(0); let target_scroll = (target_idx as i32) * scroll_step; let new_offset = target_scroll.clamp(0, max_scroll); let current_offset = monitor.scroll_offset; if current_offset != new_offset { if animate { self.scroll_animation.start(current_offset, new_offset, &self.animation_config); } else { if let Some(m) = self.monitors.get_mut(monitor_index) { m.scroll_offset = new_offset; } } } Ok(()) } fn toggle_bar(&mut self) -> WmResult<()> { if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { monitor.show_bar = !monitor.show_bar; self.show_bar = monitor.show_bar; if let Some(ref mut pertag) = monitor.pertag { pertag.show_bars[pertag.current_tag] = monitor.show_bar; } } self.apply_layout()?; self.update_bar()?; Ok(()) } fn get_layout_symbol(&self) -> String { let layout_name = self.layout.name(); if layout_name == "scrolling" { if let Some(monitor) = self.monitors.get(self.selected_monitor) { let visible_count = if monitor.num_master > 0 { monitor.num_master as usize } else { 2 }; let mut tiled_count = 0; let mut current = self.next_tiled(monitor.clients_head, monitor); while let Some(window) = current { tiled_count += 1; if let Some(client) = self.clients.get(&window) { current = self.next_tiled(client.next, monitor); } else { break; } } if tiled_count > 0 { let outer_gap = if self.gaps_enabled { self.config.gap_outer_vertical } else { 0 }; let inner_gap = if self.gaps_enabled { self.config.gap_inner_vertical } else { 0 }; let available_width = monitor.screen_width - 2 * outer_gap as i32; let total_inner_gaps = inner_gap as i32 * (visible_count.min(tiled_count) - 1) as i32; let window_width = if tiled_count <= visible_count { (available_width - total_inner_gaps) / tiled_count as i32 } else { (available_width - inner_gap as i32 * (visible_count - 1) as i32) / visible_count as i32 }; let scroll_step = window_width + inner_gap as i32; let first_visible = if scroll_step > 0 { (monitor.scroll_offset / scroll_step) + 1 } else { 1 }; let last_visible = (first_visible + visible_count as i32 - 1).min(tiled_count as i32); return format!("[{}-{}/{}]", first_visible, last_visible, tiled_count); } } } self.config .layout_symbols .iter() .find(|l| l.name == layout_name) .map(|l| l.symbol.clone()) .unwrap_or_else(|| self.layout.symbol().to_string()) } fn get_keychord_indicator(&self) -> Option { match &self.keychord_state { keyboard::handlers::KeychordState::Idle => None, keyboard::handlers::KeychordState::InProgress { candidates, keys_pressed, } => { if candidates.is_empty() { return None; } let binding = &self.config.keybindings[candidates[0]]; let mut indicator = String::new(); for (i, key_press) in binding.keys.iter().take(*keys_pressed).enumerate() { if i > 0 { indicator.push(' '); } for modifier in &key_press.modifiers { indicator.push_str(Self::format_modifier(*modifier)); indicator.push('+'); } indicator.push_str(&keyboard::keysyms::format_keysym(key_press.keysym)); } indicator.push('-'); Some(indicator) } } } fn format_modifier(modifier: KeyButMask) -> &'static str { match modifier { KeyButMask::MOD1 => "Alt", KeyButMask::MOD4 => "Super", KeyButMask::SHIFT => "Shift", KeyButMask::CONTROL => "Ctrl", _ => "Mod", } } fn update_bar(&mut self) -> WmResult<()> { let layout_symbol = self.get_layout_symbol(); let keychord_indicator = self.get_keychord_indicator(); for (monitor_index, monitor) in self.monitors.iter().enumerate() { if let Some(bar) = self.bars.get_mut(monitor_index) { let mut occupied_tags: TagMask = 0; let mut urgent_tags: TagMask = 0; for client in self.clients.values() { if client.monitor_index == monitor_index { occupied_tags |= client.tags; if client.is_urgent { urgent_tags |= client.tags; } } } let draw_blocks = monitor_index == self.selected_monitor; bar.invalidate(); bar.draw( &self.connection, &self.font, self.display, monitor.tagset[monitor.selected_tags_index], occupied_tags, urgent_tags, draw_blocks, &layout_symbol, keychord_indicator.as_deref(), )?; } } Ok(()) } fn update_tab_bars(&mut self) -> WmResult<()> { for (monitor_index, monitor) in self.monitors.iter().enumerate() { if let Some(tab_bar) = self.tab_bars.get_mut(monitor_index) { let visible_windows: Vec<(Window, String)> = self .windows .iter() .filter_map(|&window| { if let Some(client) = self.clients.get(&window) { if client.monitor_index != monitor_index || self.floating_windows.contains(&window) || self.fullscreen_windows.contains(&window) { return None; } if (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0 { return Some((window, client.name.clone())); } } None }) .collect(); let focused_window = monitor.selected_client; tab_bar.draw( &self.connection, &self.font, &visible_windows, focused_window, )?; } } Ok(()) } fn handle_key_action(&mut self, action: KeyAction, arg: &Arg) -> WmResult<()> { match action { KeyAction::Spawn => handlers::handle_spawn_action(action, arg, self.selected_monitor)?, KeyAction::SpawnTerminal => { crate::signal::spawn_detached(&self.config.terminal); } KeyAction::KillClient => { if let Some(focused) = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client) { self.kill_client(focused)?; } } KeyAction::ToggleFullScreen => { self.fullscreen()?; self.restack()?; } KeyAction::ChangeLayout => { if let Arg::Str(layout_name) = arg { match layout_from_str(layout_name) { Ok(layout) => { self.layout = layout; if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { if let Some(ref mut pertag) = monitor.pertag { pertag.layouts[pertag.current_tag] = layout_name.to_string(); } } if layout_name != "normie" && layout_name != "floating" { self.floating_windows.clear(); } self.apply_layout()?; self.update_bar()?; self.restack()?; } Err(e) => eprintln!("Failed to change layout: {}", e), } } } KeyAction::CycleLayout => { let current_name = self.layout.name(); let next_name = next_layout(current_name); match layout_from_str(next_name) { Ok(layout) => { self.layout = layout; if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { if let Some(ref mut pertag) = monitor.pertag { pertag.layouts[pertag.current_tag] = next_name.to_string(); } } if next_name != "normie" && next_name != "floating" { self.floating_windows.clear(); } self.apply_layout()?; self.update_bar()?; self.restack()?; } Err(e) => eprintln!("Failed to cycle layout: {}", e), } } KeyAction::ToggleFloating => { self.toggle_floating()?; self.restack()?; } KeyAction::FocusStack => { if let Arg::Int(direction) = arg { self.focusstack(*direction)?; self.restack()?; } } KeyAction::MoveStack => { if let Arg::Int(direction) = arg { self.move_stack(*direction)?; self.restack()?; } } KeyAction::Quit | KeyAction::Restart => {} KeyAction::ViewTag => { if let Arg::Int(tag_index) = arg { self.view_tag(*tag_index as usize)?; } } KeyAction::ViewNextTag => { let monitor = self.get_selected_monitor(); let current_tag_index = unmask_tag(monitor.get_selected_tag()) as i32; let len = self.config.tags.len() as i32; self.view_tag((current_tag_index + 1).rem_euclid(len) as usize)?; } KeyAction::ViewPreviousTag => { let monitor = self.get_selected_monitor(); let current_tag_index = unmask_tag(monitor.get_selected_tag()) as i32; let len = self.config.tags.len() as i32; self.view_tag((current_tag_index - 1).rem_euclid(len) as usize)?; } KeyAction::ViewNextNonEmptyTag => { let monitor = self.get_selected_monitor(); let current = unmask_tag(monitor.get_selected_tag()) as i32; let len = self.config.tags.len() as i32; let mon_num = monitor.monitor_number; for offset in 1..len { let next = (current + offset).rem_euclid(len) as usize; if self.has_windows_on_tag(mon_num, next) { self.view_tag(next)?; break; } } } KeyAction::ViewPreviousNonEmptyTag => { let monitor = self.get_selected_monitor(); let current = unmask_tag(monitor.get_selected_tag()) as i32; let len = self.config.tags.len() as i32; let mon_num = monitor.monitor_number; for offset in 1..len { let prev = (current - offset).rem_euclid(len) as usize; if self.has_windows_on_tag(mon_num, prev) { self.view_tag(prev)?; break; } } } KeyAction::ToggleView => { if let Arg::Int(tag_index) = arg { self.toggleview(*tag_index as usize)?; } } KeyAction::MoveToTag => { if let Arg::Int(tag_index) = arg { self.move_to_tag(*tag_index as usize)?; } } KeyAction::ToggleTag => { if let Arg::Int(tag_index) = arg { self.toggletag(*tag_index as usize)?; } } KeyAction::ToggleGaps => { self.gaps_enabled = !self.gaps_enabled; self.apply_layout()?; self.restack()?; } KeyAction::FocusMonitor => { if let Arg::Int(direction) = arg { self.focus_monitor(*direction)?; } } KeyAction::TagMonitor => { if let Arg::Int(direction) = arg { self.send_window_to_adjacent_monitor(*direction)?; } } KeyAction::ShowKeybindOverlay => { let monitor = &self.monitors[self.selected_monitor]; self.keybind_overlay.toggle( &self.connection, &self.font, &self.config.keybindings, monitor.screen_x as i16, monitor.screen_y as i16, monitor.screen_width as u16, monitor.screen_height as u16, )?; } KeyAction::SetMasterFactor => { if let Arg::Int(delta) = arg { self.set_master_factor(*delta as f32 / 100.0)?; } } KeyAction::IncNumMaster => { if let Arg::Int(delta) = arg { self.inc_num_master(*delta)?; } } KeyAction::ScrollLeft => { self.scroll_layout(-1)?; } KeyAction::ScrollRight => { self.scroll_layout(1)?; } KeyAction::None => {} } Ok(()) } fn is_window_visible(&self, window: Window) -> bool { if let Some(client) = self.clients.get(&window) { let monitor = self.monitors.get(client.monitor_index); let selected_tags = monitor .map(|m| m.tagset[m.selected_tags_index]) .unwrap_or(0); (client.tags & selected_tags) != 0 } else { false } } fn visible_windows(&self) -> Vec { let mut result = Vec::new(); for monitor in &self.monitors { let mut current = monitor.clients_head; while let Some(window) = current { if let Some(client) = self.clients.get(&window) { let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index]; if visible_tags != 0 { result.push(window); } current = client.next; } else { break; } } } result } fn visible_windows_on_monitor(&self, monitor_index: usize) -> Vec { let mut result = Vec::new(); if let Some(monitor) = self.monitors.get(monitor_index) { let mut current = monitor.clients_head; while let Some(window) = current { if let Some(client) = self.clients.get(&window) { let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index]; if visible_tags != 0 { result.push(window); } current = client.next; } else { break; } } } result } fn get_monitor_at_point(&self, x: i32, y: i32) -> Option { self.monitors .iter() .position(|mon| mon.contains_point(x, y)) } fn get_monitor_for_rect(&self, x: i32, y: i32, w: i32, h: i32) -> usize { let mut best_monitor = self.selected_monitor; let mut max_area = 0; for (idx, monitor) in self.monitors.iter().enumerate() { let intersect_width = 0.max( (x + w).min(monitor.window_area_x + monitor.window_area_width) - x.max(monitor.window_area_x), ); let intersect_height = 0.max( (y + h).min(monitor.window_area_y + monitor.window_area_height) - y.max(monitor.window_area_y), ); let area = intersect_width * intersect_height; if area > max_area { max_area = area; best_monitor = idx; } } best_monitor } fn move_window_to_monitor( &mut self, window: Window, target_monitor_index: usize, ) -> WmResult<()> { let current_monitor_index = self.clients.get(&window).map(|c| c.monitor_index); if let Some(current_idx) = current_monitor_index && current_idx == target_monitor_index { return Ok(()); } self.unfocus(window, false)?; self.detach(window); self.detach_stack(window); if let Some(client) = self.clients.get_mut(&window) { client.monitor_index = target_monitor_index; if let Some(target_monitor) = self.monitors.get(target_monitor_index) { client.tags = target_monitor.tagset[target_monitor.selected_tags_index]; } } self.attach_aside(window, target_monitor_index); self.attach_stack(window, target_monitor_index); self.focus(None)?; self.apply_layout()?; Ok(()) } fn get_adjacent_monitor(&self, direction: i32) -> Option { if self.monitors.len() <= 1 { return None; } if direction > 0 { if self.selected_monitor + 1 < self.monitors.len() { Some(self.selected_monitor + 1) } else { Some(0) } } else if self.selected_monitor == 0 { Some(self.monitors.len() - 1) } else { Some(self.selected_monitor - 1) } } fn is_visible(&self, window: Window) -> bool { let Some(client) = self.clients.get(&window) else { return false; }; let Some(monitor) = self.monitors.get(client.monitor_index) else { return false; }; (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0 } fn showhide(&mut self, window: Option) -> WmResult<()> { let Some(window) = window else { return Ok(()); }; let Some(client) = self.clients.get(&window).cloned() else { return Ok(()); }; let monitor = match self.monitors.get(client.monitor_index) { Some(m) => m, None => return Ok(()), }; let is_visible = (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0; if is_visible { self.connection.configure_window( window, &ConfigureWindowAux::new() .x(client.x_position as i32) .y(client.y_position as i32), )?; let is_floating = client.is_floating; let is_fullscreen = client.is_fullscreen; let has_no_layout = self.layout.name() == LayoutType::Normie.as_str(); if (has_no_layout || is_floating) && !is_fullscreen { let (x, y, w, h, changed) = self.apply_size_hints( window, client.x_position as i32, client.y_position as i32, client.width as i32, client.height as i32, ); if changed { if let Some(c) = self.clients.get_mut(&window) { c.old_x_position = c.x_position; c.old_y_position = c.y_position; c.old_width = c.width; c.old_height = c.height; c.x_position = x as i16; c.y_position = y as i16; c.width = w as u16; c.height = h as u16; } self.connection.configure_window( window, &ConfigureWindowAux::new() .x(x) .y(y) .width(w as u32) .height(h as u32) .border_width(self.config.border_width), )?; self.send_configure_notify(window)?; self.connection.flush()?; } } self.showhide(client.stack_next)?; } else { self.showhide(client.stack_next)?; let width = client.width_with_border() as i32; self.connection.configure_window( window, &ConfigureWindowAux::new() .x(width * -2) .y(client.y_position as i32), )?; } Ok(()) } pub fn view_tag(&mut self, tag_index: usize) -> WmResult<()> { if tag_index >= self.config.tags.len() { return Ok(()); } let new_tagset = tag_mask(tag_index); let mut layout_name: Option = None; let mut toggle_bar = false; if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { if new_tagset == monitor.tagset[monitor.selected_tags_index] { if !self.config.tag_back_and_forth { return Ok(()); } monitor.tagset.swap(0, 1); if let Some(ref mut pertag) = monitor.pertag { let tmp = pertag.previous_tag; pertag.previous_tag = pertag.current_tag; pertag.current_tag = tmp; } } else { monitor.selected_tags_index ^= 1; monitor.tagset[monitor.selected_tags_index] = new_tagset; if let Some(ref mut pertag) = monitor.pertag { pertag.previous_tag = pertag.current_tag; pertag.current_tag = tag_index + 1; } } if let Some(ref pertag) = monitor.pertag { monitor.num_master = pertag.num_masters[pertag.current_tag]; monitor.master_factor = pertag.master_factors[pertag.current_tag]; layout_name = Some(pertag.layouts[pertag.current_tag].clone()); if monitor.show_bar != pertag.show_bars[pertag.current_tag] { toggle_bar = true; } } } if let Some(name) = layout_name { if let Ok(layout) = layout_from_str(&name) { self.layout = layout; } } if toggle_bar { self.toggle_bar()?; } self.save_selected_tags()?; self.focus(None)?; self.apply_layout()?; self.update_bar()?; Ok(()) } pub fn toggleview(&mut self, tag_index: usize) -> WmResult<()> { if tag_index >= self.config.tags.len() { return Ok(()); } let num_tags = self.config.tags.len(); let all_tags_mask = (1u32 << num_tags) - 1; let mut layout_name: Option = None; let mut toggle_bar = false; if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { let mask = tag_mask(tag_index); let new_tagset = monitor.tagset[monitor.selected_tags_index] ^ mask; if new_tagset == 0 { return Ok(()); } monitor.tagset[monitor.selected_tags_index] = new_tagset; if let Some(ref mut pertag) = monitor.pertag { if new_tagset == all_tags_mask { pertag.previous_tag = pertag.current_tag; pertag.current_tag = 0; } if pertag.current_tag > 0 && (new_tagset & (1 << (pertag.current_tag - 1))) == 0 { pertag.previous_tag = pertag.current_tag; pertag.current_tag = (new_tagset.trailing_zeros() as usize) + 1; } monitor.num_master = pertag.num_masters[pertag.current_tag]; monitor.master_factor = pertag.master_factors[pertag.current_tag]; layout_name = Some(pertag.layouts[pertag.current_tag].clone()); if monitor.show_bar != pertag.show_bars[pertag.current_tag] { toggle_bar = true; } } } if let Some(name) = layout_name { if let Ok(layout) = layout_from_str(&name) { self.layout = layout; } } if toggle_bar { self.toggle_bar()?; } self.save_selected_tags()?; self.focus(None)?; self.apply_layout()?; self.update_bar()?; Ok(()) } fn save_selected_tags(&self) -> WmResult<()> { let net_current_desktop = self.atoms.net_current_desktop; let selected_tags = self .monitors .get(self.selected_monitor) .map(|m| m.tagset[m.selected_tags_index]) .unwrap_or(tag_mask(0)); let desktop = selected_tags.trailing_zeros(); let bytes = (desktop as u32).to_ne_bytes(); self.connection.change_property( PropMode::REPLACE, self.root, net_current_desktop, AtomEnum::CARDINAL, 32, 1, &bytes, )?; self.connection.flush()?; Ok(()) } pub fn move_to_tag(&mut self, tag_index: usize) -> WmResult<()> { if tag_index >= self.config.tags.len() { return Ok(()); } let focused = match self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client) { Some(win) => win, None => return Ok(()), }; let mask = tag_mask(tag_index); if let Some(client) = self.clients.get_mut(&focused) { client.tags = mask; } if let Err(error) = self.save_client_tag(focused, mask) { eprintln!("Failed to save client tag: {:?}", error); } self.focus(None)?; self.apply_layout()?; self.update_bar()?; Ok(()) } pub fn toggletag(&mut self, tag_index: usize) -> WmResult<()> { if tag_index >= self.config.tags.len() { return Ok(()); } let focused = match self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client) { Some(win) => win, None => return Ok(()), }; let mask = tag_mask(tag_index); let current_tags = self.clients.get(&focused).map(|c| c.tags).unwrap_or(0); let new_tags = current_tags ^ mask; if new_tags == 0 { return Ok(()); } if let Some(client) = self.clients.get_mut(&focused) { client.tags = new_tags; } if let Err(error) = self.save_client_tag(focused, new_tags) { eprintln!("Failed to save client tag: {:?}", error); } self.focus(None)?; self.apply_layout()?; self.update_bar()?; Ok(()) } pub fn cycle_focus(&mut self, direction: i32) -> WmResult<()> { let visible = self.visible_windows(); if visible.is_empty() { return Ok(()); } let current = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client); let next_window = if let Some(current) = current { if let Some(current_index) = visible.iter().position(|&w| w == current) { let next_index = if direction > 0 { (current_index + 1) % visible.len() } else { (current_index + visible.len() - 1) % visible.len() }; visible[next_index] } else { visible[0] } } else { visible[0] }; let is_tabbed = self.layout.name() == "tabbed"; if is_tabbed { self.connection.configure_window( next_window, &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE), )?; } self.focus(Some(next_window))?; if is_tabbed { self.update_tab_bars()?; } Ok(()) } fn grab_keys(&mut self) -> WmResult<()> { self.keyboard_mapping = Some(keyboard::grab_keys( &self.connection, self.root, &self.config.keybindings, self.current_key, )?); Ok(()) } fn kill_client(&self, window: Window) -> WmResult<()> { if self.send_event(window, self.atoms.wm_delete_window)? { self.connection.flush()?; } else { eprintln!( "Window {} doesn't support WM_DELETE_WINDOW, killing forcefully", window ); self.connection.kill_client(window)?; self.connection.flush()?; } Ok(()) } fn send_event(&self, window: Window, protocol: Atom) -> WmResult { let protocols_reply = self .connection .get_property( false, window, self.atoms.wm_protocols, AtomEnum::ATOM, 0, 100, )? .reply(); let protocols_reply = match protocols_reply { Ok(reply) => reply, Err(_) => return Ok(false), }; let protocols: Vec = protocols_reply .value .chunks_exact(4) .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) .collect(); if !protocols.contains(&protocol) { return Ok(false); } let event = x11rb::protocol::xproto::ClientMessageEvent { response_type: x11rb::protocol::xproto::CLIENT_MESSAGE_EVENT, format: 32, sequence: 0, window, type_: self.atoms.wm_protocols, data: x11rb::protocol::xproto::ClientMessageData::from([ protocol, x11rb::CURRENT_TIME, 0, 0, 0, ]), }; self.connection .send_event(false, window, EventMask::NO_EVENT, event)?; self.connection.flush()?; Ok(true) } fn set_urgent(&mut self, window: Window, urgent: bool) -> WmResult<()> { if let Some(client) = self.clients.get_mut(&window) { client.is_urgent = urgent; } let hints_reply = self .connection .get_property(false, window, AtomEnum::WM_HINTS, AtomEnum::WM_HINTS, 0, 9)? .reply(); if let Ok(hints) = hints_reply && hints.value.len() >= 4 { let mut flags = u32::from_ne_bytes([ hints.value[0], hints.value[1], hints.value[2], hints.value[3], ]); if urgent { flags |= 256; } else { flags &= !256; } let mut new_hints = hints.value.clone(); new_hints[0..4].copy_from_slice(&flags.to_ne_bytes()); self.connection.change_property( PropMode::REPLACE, window, AtomEnum::WM_HINTS, AtomEnum::WM_HINTS, 32, new_hints.len() as u32 / 4, &new_hints, )?; } Ok(()) } fn get_window_atom_property(&self, window: Window, property: Atom) -> WmResult> { let reply = self .connection .get_property(false, window, property, AtomEnum::ATOM, 0, 1)? .reply(); match reply { Ok(prop) if !prop.value.is_empty() && prop.value.len() >= 4 => { let atom = u32::from_ne_bytes([ prop.value[0], prop.value[1], prop.value[2], prop.value[3], ]); Ok(Some(atom)) } _ => Ok(None), } } fn get_window_atom_list_property(&self, window: Window, property: Atom) -> WmResult> { let reply = self .connection .get_property(false, window, property, AtomEnum::ATOM, 0, 32)? .reply(); match reply { Ok(prop) if !prop.value.is_empty() => { let atoms: Vec = prop .value .chunks_exact(4) .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) .collect(); Ok(atoms) } _ => Ok(Vec::new()), } } fn fullscreen(&mut self) -> WmResult<()> { let Some(focused_window) = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client) else { return Ok(()); }; let is_fullscreen = self.fullscreen_windows.contains(&focused_window); self.set_window_fullscreen(focused_window, !is_fullscreen)?; Ok(()) } fn set_window_fullscreen(&mut self, window: Window, fullscreen: bool) -> WmResult<()> { let monitor_idx = self .clients .get(&window) .map(|c| c.monitor_index) .unwrap_or(self.selected_monitor); let monitor = &self.monitors[monitor_idx]; if fullscreen && !self.fullscreen_windows.contains(&window) { let bytes = self.atoms.net_wm_state_fullscreen.to_ne_bytes().to_vec(); self.connection.change_property( PropMode::REPLACE, window, self.atoms.net_wm_state, AtomEnum::ATOM, 32, 1, &bytes, )?; if let Some(client) = self.clients.get_mut(&window) { client.is_fullscreen = true; client.old_state = client.is_floating; client.old_border_width = client.border_width; client.old_x_position = client.x_position; client.old_y_position = client.y_position; client.old_width = client.width; client.old_height = client.height; client.border_width = 0; client.is_floating = true; } self.fullscreen_windows.insert(window); self.floating_windows.insert(window); self.connection.configure_window( window, &x11rb::protocol::xproto::ConfigureWindowAux::new() .border_width(0) .x(monitor.screen_x) .y(monitor.screen_y) .width(monitor.screen_width as u32) .height(monitor.screen_height as u32) .stack_mode(x11rb::protocol::xproto::StackMode::ABOVE), )?; self.connection.flush()?; } else if !fullscreen && self.fullscreen_windows.contains(&window) { self.connection.change_property( PropMode::REPLACE, window, self.atoms.net_wm_state, AtomEnum::ATOM, 32, 0, &[], )?; self.fullscreen_windows.remove(&window); let (was_floating, restored_x, restored_y, restored_width, restored_height, restored_border) = self .clients .get(&window) .map(|client| { ( client.old_state, client.old_x_position, client.old_y_position, client.old_width, client.old_height, client.old_border_width, ) }) .unwrap_or((false, 0, 0, 100, 100, 0)); if !was_floating { self.floating_windows.remove(&window); } if let Some(client) = self.clients.get_mut(&window) { client.is_fullscreen = false; client.is_floating = client.old_state; client.border_width = client.old_border_width; client.x_position = client.old_x_position; client.y_position = client.old_y_position; client.width = client.old_width; client.height = client.old_height; } self.connection.configure_window( window, &ConfigureWindowAux::new() .x(restored_x as i32) .y(restored_y as i32) .width(restored_width as u32) .height(restored_height as u32) .border_width(restored_border as u32), )?; self.apply_layout()?; } Ok(()) } fn get_transient_parent(&self, window: Window) -> Option { self.connection .get_property( false, window, AtomEnum::WM_TRANSIENT_FOR, AtomEnum::WINDOW, 0, 1, ) .ok() .and_then(|cookie| cookie.reply().ok()) .filter(|reply| !reply.value.is_empty()) .and_then(|reply| { if reply.value.len() >= 4 { let parent_window = u32::from_ne_bytes([ reply.value[0], reply.value[1], reply.value[2], reply.value[3], ]); Some(parent_window) } else { None } }) } fn get_window_class_instance(&self, window: Window) -> (String, String) { let reply = self .connection .get_property(false, window, AtomEnum::WM_CLASS, AtomEnum::STRING, 0, 1024) .ok() .and_then(|cookie| cookie.reply().ok()); if let Some(reply) = reply && !reply.value.is_empty() && let Ok(text) = std::str::from_utf8(&reply.value) { let parts: Vec<&str> = text.split('\0').collect(); let instance = parts.first().unwrap_or(&"").to_string(); let class = parts.get(1).unwrap_or(&"").to_string(); return (instance, class); } (String::new(), String::new()) } fn apply_rules(&mut self, window: Window) -> WmResult<()> { let (instance, class) = self.get_window_class_instance(window); let title = self .clients .get(&window) .map(|c| c.name.clone()) .unwrap_or_default(); let mut rule_tags: Option = None; let mut rule_floating: Option = None; let mut rule_monitor: Option = None; let mut rule_focus = false; for rule in &self.config.window_rules { if rule.matches(&class, &instance, &title) { if rule.tags.is_some() { rule_tags = rule.tags; } if rule.is_floating.is_some() { rule_floating = rule.is_floating; } if rule.monitor.is_some() { rule_monitor = rule.monitor; } rule_focus = rule.focus.unwrap_or(false); } } if let Some(client) = self.clients.get_mut(&window) { if let Some(is_floating) = rule_floating { client.is_floating = is_floating; if is_floating { self.floating_windows.insert(window); } else { self.floating_windows.remove(&window); } } if let Some(monitor_index) = rule_monitor && monitor_index < self.monitors.len() { client.monitor_index = monitor_index; } if let Some(tags) = rule_tags { client.tags = tags; if rule_focus { let tag_index = unmask_tag(tags); let monitor_tagset = self .monitors .get(client.monitor_index) .map(|monitor| monitor.get_selected_tag()) .unwrap_or(tag_mask(0)); let is_tag_focused = monitor_tagset & tags == tags; if !is_tag_focused { self.view_tag(tag_index)?; } } } } Ok(()) } fn manage_window(&mut self, window: Window) -> WmResult<()> { let geometry = self.connection.get_geometry(window)?.reply()?; let border_width = self.config.border_width; let transient_parent = self.get_transient_parent(window); let is_transient = transient_parent.is_some(); let (monitor_index, tags) = if let Some(parent) = transient_parent { if let Some(parent_client) = self.clients.get(&parent) { (parent_client.monitor_index, parent_client.tags) } else { let tags = self .monitors .get(self.selected_monitor) .map(|monitor| monitor.tagset[monitor.selected_tags_index]) .unwrap_or(tag_mask(0)); (self.selected_monitor, tags) } } else { let tags = self .monitors .get(self.selected_monitor) .map(|monitor| monitor.tagset[monitor.selected_tags_index]) .unwrap_or(tag_mask(0)); (self.selected_monitor, tags) }; let mut client = Client::new(window, monitor_index, tags); client.x_position = geometry.x; client.y_position = geometry.y; client.width = geometry.width; client.height = geometry.height; client.old_x_position = geometry.x; client.old_y_position = geometry.y; client.old_width = geometry.width; client.old_height = geometry.height; client.old_border_width = geometry.border_width; client.border_width = border_width as u16; self.clients.insert(window, client); self.update_window_title(window)?; if !is_transient { self.apply_rules(window)?; } let client_monitor = self .clients .get(&window) .map(|c| c.monitor_index) .unwrap_or(monitor_index); let monitor = &self.monitors[client_monitor]; let mut x = self .clients .get(&window) .map(|c| c.x_position as i32) .unwrap_or(0); let mut y = self .clients .get(&window) .map(|c| c.y_position as i32) .unwrap_or(0); let w = self .clients .get(&window) .map(|c| c.width as i32) .unwrap_or(1); let h = self .clients .get(&window) .map(|c| c.height as i32) .unwrap_or(1); let bw = border_width as i32; if x + w + 2 * bw > monitor.window_area_x + monitor.window_area_width { x = monitor.window_area_x + monitor.window_area_width - w - 2 * bw; } if y + h + 2 * bw > monitor.window_area_y + monitor.window_area_height { y = monitor.window_area_y + monitor.window_area_height - h - 2 * bw; } x = x.max(monitor.window_area_x); y = y.max(monitor.window_area_y); if let Some(c) = self.clients.get_mut(&window) { c.x_position = x as i16; c.y_position = y as i16; } self.connection.configure_window( window, &ConfigureWindowAux::new().border_width(border_width), )?; self.connection.change_window_attributes( window, &ChangeWindowAttributesAux::new().border_pixel(self.config.border_unfocused), )?; self.send_configure_notify(window)?; self.update_window_type(window)?; self.update_size_hints(window)?; self.update_window_hints(window)?; self.connection.change_window_attributes( window, &ChangeWindowAttributesAux::new().event_mask( EventMask::ENTER_WINDOW | EventMask::FOCUS_CHANGE | EventMask::PROPERTY_CHANGE | EventMask::STRUCTURE_NOTIFY, ), )?; let is_fixed = self .clients .get(&window) .map(|c| c.is_fixed) .unwrap_or(false); if let Some(c) = self.clients.get_mut(&window) && !c.is_floating { c.is_floating = is_transient || is_fixed; c.old_state = c.is_floating; } if self .clients .get(&window) .map(|c| c.is_floating) .unwrap_or(false) { self.floating_windows.insert(window); self.connection.configure_window( window, &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE), )?; } if self.layout.name() == "scrolling" { if let Some(selected) = self.monitors.get(client_monitor).and_then(|m| m.selected_client) { self.attach_after(window, selected, client_monitor); } else { self.attach_aside(window, client_monitor); } } else { self.attach_aside(window, client_monitor); } self.attach_stack(window, client_monitor); self.windows.push(window); let off_screen_x = x + 2 * self.screen.width_in_pixels as i32; self.connection.configure_window( window, &ConfigureWindowAux::new() .x(off_screen_x) .y(y) .width(w as u32) .height(h as u32), )?; self.set_wm_state(window, 1)?; self.update_client_list()?; let final_tags = self.clients.get(&window).map(|c| c.tags).unwrap_or(tags); let _ = self.save_client_tag(window, final_tags); if client_monitor == self.selected_monitor && let Some(old_sel) = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client) { self.unfocus(old_sel, false)?; } if let Some(m) = self.monitors.get_mut(client_monitor) { m.selected_client = Some(window); } if self.layout.name() == "scrolling" { self.scroll_to_window(window, true)?; } self.apply_layout()?; self.connection.map_window(window)?; self.focus(None)?; self.update_bar()?; if self.layout.name() == "tabbed" { self.update_tab_bars()?; } Ok(()) } pub fn set_focus(&mut self, window: Window) -> WmResult<()> { let never_focus = self .clients .get(&window) .map(|c| c.never_focus) .unwrap_or(false); if !never_focus { self.connection .set_input_focus(InputFocus::POINTER_ROOT, window, x11rb::CURRENT_TIME)?; self.connection.change_property( PropMode::REPLACE, self.root, self.atoms.net_active_window, AtomEnum::WINDOW, 32, 1, &window.to_ne_bytes(), )?; } let _ = self.send_event(window, self.atoms.wm_take_focus); self.connection.flush()?; Ok(()) } fn grabbuttons(&self, window: Window, focused: bool) -> WmResult<()> { self.connection.ungrab_button(ButtonIndex::ANY, window, ModMask::ANY)?; if !focused { self.connection.grab_button( false, window, EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE, GrabMode::SYNC, GrabMode::SYNC, x11rb::NONE, x11rb::NONE, ButtonIndex::ANY, ModMask::ANY, )?; } let ignore_modifiers = [ 0u16, u16::from(ModMask::LOCK), u16::from(ModMask::M2), u16::from(ModMask::LOCK | ModMask::M2), ]; for &ignore_mask in &ignore_modifiers { let grab_mask = u16::from(self.config.modkey) | ignore_mask; self.connection.grab_button( false, window, EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE, GrabMode::ASYNC, GrabMode::SYNC, x11rb::NONE, x11rb::NONE, ButtonIndex::M1, grab_mask.into(), )?; self.connection.grab_button( false, window, EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE, GrabMode::ASYNC, GrabMode::SYNC, x11rb::NONE, x11rb::NONE, ButtonIndex::M3, grab_mask.into(), )?; } Ok(()) } fn unfocus(&self, window: Window, reset_input_focus: bool) -> WmResult<()> { if !self.windows.contains(&window) { return Ok(()); } self.grabbuttons(window, false)?; self.connection.change_window_attributes( window, &ChangeWindowAttributesAux::new().border_pixel(self.config.border_unfocused), )?; if reset_input_focus { self.connection.set_input_focus( InputFocus::POINTER_ROOT, self.root, x11rb::CURRENT_TIME, )?; self.connection.delete_property(self.root, self.atoms.net_active_window)?; } Ok(()) } fn focus(&mut self, window: Option) -> WmResult<()> { let old_selected = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client); let mut focus_client = window; if focus_client.is_none() || focus_client.is_some_and(|w| !self.is_visible(w)) { let mut current = self .monitors .get(self.selected_monitor) .and_then(|m| m.stack_head); focus_client = None; while let Some(w) = current { if self.is_visible(w) { focus_client = Some(w); break; } current = self.clients.get(&w).and_then(|c| c.stack_next); } } if old_selected != focus_client { if let Some(old_win) = old_selected { self.unfocus(old_win, false)?; } } if let Some(win) = focus_client { let monitor_idx = self .clients .get(&win) .map(|c| c.monitor_index) .unwrap_or(self.selected_monitor); if monitor_idx != self.selected_monitor { self.selected_monitor = monitor_idx; } if self.clients.get(&win).is_some_and(|c| c.is_urgent) { self.set_urgent(win, false)?; } self.detach_stack(win); self.attach_stack(win, monitor_idx); self.grabbuttons(win, true)?; self.connection.change_window_attributes( win, &ChangeWindowAttributesAux::new().border_pixel(self.config.border_focused), )?; let never_focus = self .clients .get(&win) .map(|client| client.never_focus) .unwrap_or(false); if !never_focus { self.connection .set_input_focus(InputFocus::POINTER_ROOT, win, x11rb::CURRENT_TIME)?; self.connection.change_property( PropMode::REPLACE, self.root, self.atoms.net_active_window, AtomEnum::WINDOW, 32, 1, &win.to_ne_bytes(), )?; } let _ = self.send_event(win, self.atoms.wm_take_focus); if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { monitor.selected_client = Some(win); } self.previous_focused = Some(win); } else { self.connection.set_input_focus( InputFocus::POINTER_ROOT, self.root, x11rb::CURRENT_TIME, )?; self.connection .delete_property(self.root, self.atoms.net_active_window)?; if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { monitor.selected_client = None; } } self.connection.flush()?; Ok(()) } fn restack(&mut self) -> WmResult<()> { let monitor = match self.monitors.get(self.selected_monitor) { Some(m) => m, None => return Ok(()), }; let mut windows_to_restack: Vec = Vec::new(); if let Some(selected) = monitor.selected_client && self.floating_windows.contains(&selected) { windows_to_restack.push(selected); } let mut current = monitor.stack_head; while let Some(win) = current { if self.windows.contains(&win) && self.floating_windows.contains(&win) && Some(win) != monitor.selected_client { windows_to_restack.push(win); } current = self.clients.get(&win).and_then(|c| c.stack_next); } current = monitor.stack_head; while let Some(win) = current { if self.windows.contains(&win) && !self.floating_windows.contains(&win) { windows_to_restack.push(win); } current = self.clients.get(&win).and_then(|c| c.stack_next); } for (i, &win) in windows_to_restack.iter().enumerate() { if i == 0 { self.connection.configure_window( win, &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE), )?; } else { self.connection.configure_window( win, &ConfigureWindowAux::new() .sibling(windows_to_restack[i - 1]) .stack_mode(StackMode::BELOW), )?; } } Ok(()) } fn focusstack(&mut self, direction: i32) -> WmResult<()> { let monitor = match self.monitors.get(self.selected_monitor) { Some(monitor) => monitor, None => return Ok(()), }; let selected_window = match monitor.selected_client { Some(window) => window, None => return Ok(()), }; let selected_is_fullscreen = self .clients .get(&selected_window) .map(|client| client.is_fullscreen) .unwrap_or(false); if selected_is_fullscreen { return Ok(()); } let selected_tags = monitor.tagset[monitor.selected_tags_index]; let mut stack_windows: Vec = Vec::new(); let mut current_window = monitor.clients_head; while let Some(window) = current_window { if let Some(client) = self.clients.get(&window) { if client.tags & selected_tags != 0 && !client.is_floating { stack_windows.push(window); } current_window = client.next; } else { break; } } if stack_windows.is_empty() { return Ok(()); } let current_index = stack_windows .iter() .position(|&window| window == selected_window); let next_window = if let Some(index) = current_index { if direction > 0 { if index + 1 < stack_windows.len() { stack_windows[index + 1] } else { stack_windows[0] } } else if index > 0 { stack_windows[index - 1] } else { stack_windows[stack_windows.len() - 1] } } else { return Ok(()); }; self.focus(Some(next_window))?; if self.layout.name() == "scrolling" { self.scroll_to_window(next_window, true)?; } self.restack()?; self.update_tab_bars()?; Ok(()) } pub fn move_stack(&mut self, direction: i32) -> WmResult<()> { let monitor_index = self.selected_monitor; let monitor = match self.monitors.get(monitor_index) { Some(m) => m.clone(), None => return Ok(()), }; let selected = match monitor.selected_client { Some(win) => win, None => return Ok(()), }; let selected_client = match self.clients.get(&selected) { Some(c) => c, None => return Ok(()), }; let target = if direction > 0 { let next = self.next_tiled(selected_client.next, &monitor); if next.is_some() { next } else { self.next_tiled(monitor.clients_head, &monitor) } } else { let mut previous = None; let mut current = monitor.clients_head; while let Some(window) = current { if window == selected { break; } if let Some(client) = self.clients.get(&window) { let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index]; if visible_tags != 0 && !client.is_floating { previous = Some(window); } current = client.next; } else { break; } } if previous.is_none() { let mut last = None; let mut current = monitor.clients_head; while let Some(window) = current { if let Some(client) = self.clients.get(&window) { let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index]; if visible_tags != 0 && !client.is_floating { last = Some(window); } current = client.next; } else { break; } } last } else { previous } }; let target = match target { Some(t) if t != selected => t, _ => return Ok(()), }; let mut prev_selected = None; let mut prev_target = None; let mut current = monitor.clients_head; while let Some(window) = current { if let Some(client) = self.clients.get(&window) { if client.next == Some(selected) { prev_selected = Some(window); } if client.next == Some(target) { prev_target = Some(window); } current = client.next; } else { break; } } let selected_next = self.clients.get(&selected).and_then(|c| c.next); let target_next = self.clients.get(&target).and_then(|c| c.next); let temp = if selected_next == Some(target) { Some(selected) } else { selected_next }; if let Some(client) = self.clients.get_mut(&selected) { client.next = if target_next == Some(selected) { Some(target) } else { target_next }; } if let Some(client) = self.clients.get_mut(&target) { client.next = temp; } if let Some(prev) = prev_selected && prev != target && let Some(client) = self.clients.get_mut(&prev) { client.next = Some(target); } if let Some(prev) = prev_target && prev != selected && let Some(client) = self.clients.get_mut(&prev) { client.next = Some(selected); } if let Some(monitor) = self.monitors.get_mut(monitor_index) { if monitor.clients_head == Some(selected) { monitor.clients_head = Some(target); } else if monitor.clients_head == Some(target) { monitor.clients_head = Some(selected); } } self.apply_layout()?; Ok(()) } pub fn focus_monitor(&mut self, direction: i32) -> WmResult<()> { if self.monitors.len() <= 1 { return Ok(()); } let target_monitor = match self.get_adjacent_monitor(direction) { Some(idx) if idx != self.selected_monitor => idx, _ => return Ok(()), }; let old_selected = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client); if let Some(win) = old_selected { self.unfocus(win, true)?; } self.selected_monitor = target_monitor; self.focus(None)?; Ok(()) } pub fn send_window_to_adjacent_monitor(&mut self, direction: i32) -> WmResult<()> { if self.monitors.len() <= 1 { return Ok(()); } let selected_window = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client); let window = match selected_window { Some(win) => win, None => return Ok(()), }; let target_monitor = match self.get_adjacent_monitor(direction) { Some(idx) => idx, None => return Ok(()), }; self.move_window_to_monitor(window, target_monitor)?; Ok(()) } fn drag_window(&mut self, window: Window) -> WmResult<()> { let is_fullscreen = self .clients .get(&window) .map(|c| c.is_fullscreen) .unwrap_or(false); if is_fullscreen { return Ok(()); } let client_info = self.clients.get(&window).map(|c| { ( c.x_position, c.y_position, c.width, c.height, c.is_floating, c.monitor_index, ) }); let Some((orig_x, orig_y, width, height, was_floating, monitor_idx)) = client_info else { return Ok(()); }; let monitor = self.monitors.get(monitor_idx).cloned(); let Some(monitor) = monitor else { return Ok(()); }; let snap = 32; let is_normie = self.layout.name() == "normie"; if !was_floating && !is_normie { self.toggle_floating()?; } self.connection .grab_pointer( false, self.root, EventMask::POINTER_MOTION | EventMask::BUTTON_RELEASE | EventMask::BUTTON_PRESS, GrabMode::ASYNC, GrabMode::ASYNC, x11rb::NONE, x11rb::NONE, x11rb::CURRENT_TIME, )? .reply()?; let pointer = self.connection.query_pointer(self.root)?.reply()?; let (start_x, start_y) = (pointer.root_x as i32, pointer.root_y as i32); let mut last_time = 0u32; loop { let event = self.connection.wait_for_event()?; match event { Event::ConfigureRequest(_) | Event::MapRequest(_) | Event::Expose(_) => {} Event::MotionNotify(e) => { if e.time.wrapping_sub(last_time) <= 16 { continue; } last_time = e.time; let mut new_x = orig_x as i32 + (e.root_x as i32 - start_x); let mut new_y = orig_y as i32 + (e.root_y as i32 - start_y); if (monitor.window_area_x - new_x).abs() < snap { new_x = monitor.window_area_x; } else if ((monitor.window_area_x + monitor.window_area_width) - (new_x + width as i32)) .abs() < snap { new_x = monitor.window_area_x + monitor.window_area_width - width as i32; } if (monitor.window_area_y - new_y).abs() < snap { new_y = monitor.window_area_y; } else if ((monitor.window_area_y + monitor.window_area_height) - (new_y + height as i32)) .abs() < snap { new_y = monitor.window_area_y + monitor.window_area_height - height as i32; } let should_resize = is_normie || self .clients .get(&window) .map(|c| c.is_floating) .unwrap_or(false); if should_resize { if let Some(client) = self.clients.get_mut(&window) { client.x_position = new_x as i16; client.y_position = new_y as i16; } self.connection.configure_window( window, &ConfigureWindowAux::new().x(new_x).y(new_y), )?; self.connection.flush()?; } } Event::ButtonRelease(_) => break, _ => {} } } self.connection .ungrab_pointer(x11rb::CURRENT_TIME)? .check()?; let final_client = self .clients .get(&window) .map(|c| (c.x_position, c.y_position, c.width, c.height)); if let Some((x, y, w, h)) = final_client { let new_monitor = self.get_monitor_for_rect(x as i32, y as i32, w as i32, h as i32); if new_monitor != monitor_idx { self.move_window_to_monitor(window, new_monitor)?; self.selected_monitor = new_monitor; self.focus(None)?; } } if self.config.auto_tile && !was_floating && !is_normie { let drop_monitor_idx = self .clients .get(&window) .map(|c| c.monitor_index) .unwrap_or(monitor_idx); if let Some((x, y, w, h)) = final_client { let center = (x as i32 + w as i32 / 2, y as i32 + h as i32 / 2); if let Some(target) = self.tiled_window_at(window, drop_monitor_idx, center) { self.detach(window); self.insert_before(window, target, drop_monitor_idx); } } self.floating_windows.remove(&window); if let Some(client) = self.clients.get_mut(&window) { client.is_floating = false; } self.apply_layout()?; } Ok(()) } fn tiled_window_at( &self, exclude: Window, monitor_idx: usize, (px, py): (i32, i32), ) -> Option { let monitor = self.monitors.get(monitor_idx)?; let tags = monitor.tagset[monitor.selected_tags_index]; let mut current = monitor.clients_head; while let Some(win) = current { let c = self.clients.get(&win)?; current = c.next; if win == exclude || c.is_floating || (c.tags & tags) == 0 { continue; } let (x, y) = (c.x_position as i32, c.y_position as i32); let (w, h) = ( c.width as i32 + c.border_width as i32 * 2, c.height as i32 + c.border_width as i32 * 2, ); if px >= x && px < x + w && py >= y && py < y + h { return Some(win); } } None } fn insert_before(&mut self, window: Window, target: Window, monitor_idx: usize) { let Some(monitor) = self.monitors.get_mut(monitor_idx) else { return; }; if monitor.clients_head == Some(target) { if let Some(c) = self.clients.get_mut(&window) { c.next = Some(target); } monitor.clients_head = Some(window); return; } let mut current = monitor.clients_head; while let Some(w) = current { let Some(c) = self.clients.get(&w) else { break }; if c.next != Some(target) { current = c.next; continue; } if let Some(prev) = self.clients.get_mut(&w) { prev.next = Some(window); } if let Some(inserted) = self.clients.get_mut(&window) { inserted.next = Some(target); } break; } } fn resize_window_with_mouse(&mut self, window: Window) -> WmResult<()> { let is_fullscreen = self .clients .get(&window) .map(|c| c.is_fullscreen) .unwrap_or(false); if is_fullscreen { return Ok(()); } let client_info = self.clients.get(&window).map(|c| { ( c.x_position, c.y_position, c.width, c.height, c.border_width, c.is_floating, c.monitor_index, ) }); let Some(( orig_x, orig_y, orig_width, orig_height, border_width, was_floating, monitor_idx, )) = client_info else { return Ok(()); }; let monitor = match self.monitors.get(monitor_idx) { Some(m) => m, None => return Ok(()), }; let is_normie = self.layout.name() == "normie"; if self.config.auto_tile && !was_floating && !is_normie { let mut tiled_count = 0; let mut current = monitor.clients_head; while let Some(w) = current { if let Some(c) = self.clients.get(&w) { let visible = (c.tags & monitor.tagset[monitor.selected_tags_index]) != 0; if visible && !c.is_floating { tiled_count += 1; } current = c.next; } else { break; } } if tiled_count <= 1 { return Ok(()); } } if !was_floating && !is_normie { self.toggle_floating()?; } self.connection.warp_pointer( x11rb::NONE, window, 0, 0, 0, 0, (orig_width + border_width - 1) as i16, (orig_height + border_width - 1) as i16, )?; self.connection .grab_pointer( false, self.root, EventMask::POINTER_MOTION | EventMask::BUTTON_RELEASE | EventMask::BUTTON_PRESS, GrabMode::ASYNC, GrabMode::ASYNC, x11rb::NONE, x11rb::NONE, x11rb::CURRENT_TIME, )? .reply()?; let mut last_time = 0u32; loop { let event = self.connection.wait_for_event()?; match event { Event::ConfigureRequest(_) | Event::MapRequest(_) | Event::Expose(_) => {} Event::MotionNotify(e) => { if e.time.wrapping_sub(last_time) <= 16 { continue; } last_time = e.time; let new_width = ((e.root_x as i32 - orig_x as i32 - 2 * border_width as i32 + 1) .max(1)) as u32; let new_height = ((e.root_y as i32 - orig_y as i32 - 2 * border_width as i32 + 1) .max(1)) as u32; let should_resize = is_normie || self .clients .get(&window) .map(|c| c.is_floating) .unwrap_or(false); if should_resize && let Some(client) = self.clients.get(&window).cloned() { let (_, _, hint_width, hint_height, _) = self.apply_size_hints( window, client.x_position as i32, client.y_position as i32, new_width as i32, new_height as i32, ); if let Some(client_mut) = self.clients.get_mut(&window) { client_mut.width = hint_width as u16; client_mut.height = hint_height as u16; } self.connection.configure_window( window, &ConfigureWindowAux::new() .width(hint_width as u32) .height(hint_height as u32), )?; self.connection.flush()?; } } Event::ButtonRelease(_) => break, _ => {} } } let final_client = self.clients.get(&window).map(|c| (c.width, c.border_width)); if let Some((w, bw)) = final_client { self.connection.warp_pointer( x11rb::NONE, window, 0, 0, 0, 0, (w + bw - 1) as i16, (w + bw - 1) as i16, )?; } self.connection .ungrab_pointer(x11rb::CURRENT_TIME)? .check()?; let final_client_pos = self .clients .get(&window) .map(|c| (c.x_position, c.y_position, c.width, c.height)); if let Some((x, y, w, h)) = final_client_pos { let new_monitor = self.get_monitor_for_rect(x as i32, y as i32, w as i32, h as i32); if new_monitor != monitor_idx { self.move_window_to_monitor(window, new_monitor)?; self.selected_monitor = new_monitor; self.focus(None)?; } } if self.config.auto_tile && !was_floating && !is_normie { self.floating_windows.remove(&window); if let Some(client) = self.clients.get_mut(&window) { client.is_floating = false; } self.apply_layout()?; } Ok(()) } fn handle_event(&mut self, event: Event) -> WmResult { match event { Event::KeyPress(ref key_event) if key_event.event == self.overlay.window() => { if self.overlay.is_visible() && let Err(error) = self.overlay.hide(&self.connection) { eprintln!("Failed to hide overlay: {:?}", error); } return Ok(Control::Continue); } Event::ButtonPress(ref button_event) if button_event.event == self.overlay.window() => { if self.overlay.is_visible() && let Err(error) = self.overlay.hide(&self.connection) { eprintln!("Failed to hide overlay: {:?}", error); } return Ok(Control::Continue); } Event::Expose(ref expose_event) if expose_event.window == self.overlay.window() => { if self.overlay.is_visible() && let Err(error) = self.overlay.draw(&self.connection, &self.font) { eprintln!("Failed to draw overlay: {:?}", error); } return Ok(Control::Continue); } Event::KeyPress(ref e) if e.event == self.keybind_overlay.window() => { if self.keybind_overlay.is_visible() && !self.keybind_overlay.should_suppress_input() { use crate::keyboard::keysyms; if let Some(mapping) = &self.keyboard_mapping { let keysym = mapping.keycode_to_keysym(e.detail); let is_escape = keysym == keysyms::XK_ESCAPE; let is_q = keysym == keysyms::XK_Q || keysym == 0x0051; if (is_escape || is_q) && let Err(error) = self.keybind_overlay.hide(&self.connection) { eprintln!("Failed to hide keybind overlay: {:?}", error); } } } return Ok(Control::Continue); } Event::ButtonPress(ref e) if e.event == self.keybind_overlay.window() => { self.connection .allow_events(Allow::REPLAY_POINTER, e.time)?; return Ok(Control::Continue); } Event::Expose(ref expose_event) if expose_event.window == self.keybind_overlay.window() => { if self.keybind_overlay.is_visible() && let Err(error) = self.keybind_overlay.draw(&self.connection, &self.font) { eprintln!("Failed to draw keybind overlay: {:?}", error); } return Ok(Control::Continue); } Event::MapRequest(event) => { let attrs = match self.connection.get_window_attributes(event.window)?.reply() { Ok(attrs) => attrs, Err(_) => return Ok(Control::Continue), }; if attrs.override_redirect { return Ok(Control::Continue); } if !self.windows.contains(&event.window) { self.manage_window(event.window)?; } } Event::UnmapNotify(event) => { if self.windows.contains(&event.window) && self.is_window_visible(event.window) { self.remove_window(event.window, false)?; } } Event::DestroyNotify(event) => { if self.windows.contains(&event.window) { self.remove_window(event.window, true)?; } } Event::PropertyNotify(event) => { if event.state == Property::DELETE { return Ok(Control::Continue); } if !self.clients.contains_key(&event.window) { return Ok(Control::Continue); } if event.atom == AtomEnum::WM_TRANSIENT_FOR.into() { let is_floating = self .clients .get(&event.window) .map(|c| c.is_floating) .unwrap_or(false); if !is_floating && let Some(parent) = self.get_transient_parent(event.window) && self.clients.contains_key(&parent) { if let Some(c) = self.clients.get_mut(&event.window) { c.is_floating = true; } self.floating_windows.insert(event.window); self.apply_layout()?; } } else if event.atom == AtomEnum::WM_NORMAL_HINTS.into() { if let Some(c) = self.clients.get_mut(&event.window) { c.hints_valid = false; } } else if event.atom == AtomEnum::WM_HINTS.into() { self.update_window_hints(event.window)?; self.update_bar()?; } if event.atom == self.atoms.wm_name || event.atom == self.atoms.net_wm_name { let _ = self.update_window_title(event.window); if self.layout.name() == "tabbed" { self.update_tab_bars()?; } } if event.atom == self.atoms.net_wm_window_type { self.update_window_type(event.window)?; } } Event::EnterNotify(event) => { if event.mode != x11rb::protocol::xproto::NotifyMode::NORMAL || event.detail == x11rb::protocol::xproto::NotifyDetail::INFERIOR { return Ok(Control::Continue); } if self.windows.contains(&event.event) { if let Some(client) = self.clients.get(&event.event) && client.monitor_index != self.selected_monitor { if let Some(old_selected) = self .monitors .get(self.selected_monitor) .and_then(|monitor| monitor.selected_client) { self.unfocus(old_selected, false)?; } self.selected_monitor = client.monitor_index; self.update_bar()?; } self.focus(Some(event.event))?; self.update_tab_bars()?; } } Event::MotionNotify(event) => { if event.event != self.root { return Ok(Control::Continue); } if let Some(monitor_index) = self.get_monitor_at_point(event.root_x as i32, event.root_y as i32) && monitor_index != self.selected_monitor { if let Some(old_selected) = self .monitors .get(self.selected_monitor) .and_then(|monitor| monitor.selected_client) { self.unfocus(old_selected, true)?; } self.selected_monitor = monitor_index; self.focus(None)?; self.update_bar()?; self.update_tab_bars()?; } } Event::KeyPress(event) => { let Some(mapping) = &self.keyboard_mapping else { return Ok(Control::Continue); }; let result = keyboard::handle_key_press( event, &self.config.keybindings, &self.keychord_state, mapping, ); match result { keyboard::handlers::KeychordResult::Completed(action, arg) => { self.keychord_state = keyboard::handlers::KeychordState::Idle; self.current_key = 0; self.grab_keys()?; self.update_bar()?; match action { KeyAction::Quit => return Ok(Control::Quit), KeyAction::Restart => match self.try_reload_config() { Ok(()) => { self.gaps_enabled = self.config.gaps_enabled; self.error_message = None; if let Err(error) = self.overlay.hide(&self.connection) { eprintln!( "Failed to hide overlay after config reload: {:?}", error ); } self.apply_layout()?; self.update_bar()?; } Err(err) => { eprintln!("Config reload error: {}", err); self.error_message = Some(err.to_string()); let monitor = &self.monitors[self.selected_monitor]; let monitor_x = monitor.screen_x as i16; let monitor_y = monitor.screen_y as i16; let screen_width = monitor.screen_width as u16; let screen_height = monitor.screen_height as u16; match self.overlay.show_error( &self.connection, &self.font, err, monitor_x, monitor_y, screen_width, screen_height, ) { Ok(()) => eprintln!("Error modal displayed"), Err(e) => eprintln!("Failed to show error modal: {:?}", e), } } }, _ => self.handle_key_action(action, &arg)?, } } keyboard::handlers::KeychordResult::InProgress(candidates) => { self.current_key += 1; self.keychord_state = keyboard::handlers::KeychordState::InProgress { candidates: candidates.clone(), keys_pressed: self.current_key, }; self.grab_keys()?; self.update_bar()?; } keyboard::handlers::KeychordResult::Cancelled | keyboard::handlers::KeychordResult::None => { self.keychord_state = keyboard::handlers::KeychordState::Idle; self.current_key = 0; self.grab_keys()?; self.update_bar()?; } } } Event::ButtonPress(event) => { if self.keybind_overlay.is_visible() && event.event != self.keybind_overlay.window() && let Err(error) = self.keybind_overlay.hide(&self.connection) { eprintln!("Failed to hide keybind overlay: {:?}", error); } let is_bar_click = self .bars .iter() .enumerate() .find(|(_, bar)| bar.window() == event.event); if let Some((monitor_index, bar)) = is_bar_click { if let Some(tag_index) = bar.handle_click(event.event_x) { if monitor_index != self.selected_monitor { self.selected_monitor = monitor_index; } self.view_tag(tag_index)?; } } else { let is_tab_bar_click = self .tab_bars .iter() .enumerate() .find(|(_, tab_bar)| tab_bar.window() == event.event); if let Some((monitor_index, tab_bar)) = is_tab_bar_click { if monitor_index != self.selected_monitor { self.selected_monitor = monitor_index; } let visible_windows: Vec<(Window, String)> = self .windows .iter() .filter_map(|&window| { if let Some(client) = self.clients.get(&window) { if client.monitor_index != monitor_index || self.floating_windows.contains(&window) || self.fullscreen_windows.contains(&window) { return None; } let monitor_tags = self .monitors .get(monitor_index) .map(|m| m.tagset[m.selected_tags_index]) .unwrap_or(0); if (client.tags & monitor_tags) != 0 { return Some((window, client.name.clone())); } } None }) .collect(); if let Some(clicked_window) = tab_bar.get_clicked_window(&visible_windows, event.event_x) { self.connection.configure_window( clicked_window, &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE), )?; self.focus(Some(clicked_window))?; self.update_tab_bars()?; } } else if event.child != x11rb::NONE { self.focus(Some(event.child))?; self.restack()?; self.update_tab_bars()?; let state_clean = u16::from(event.state) & !(u16::from(ModMask::LOCK) | u16::from(ModMask::M2)); let modkey_held = state_clean & u16::from(self.config.modkey) != 0; if modkey_held && event.detail == ButtonIndex::M1.into() { if self.clients.contains_key(&event.child) { self.drag_window(event.child)?; } self.connection .allow_events(Allow::REPLAY_POINTER, event.time)?; } else if modkey_held && event.detail == ButtonIndex::M3.into() { if self.clients.contains_key(&event.child) { self.resize_window_with_mouse(event.child)?; } self.connection .allow_events(Allow::REPLAY_POINTER, event.time)?; } else { self.connection .allow_events(Allow::REPLAY_POINTER, event.time)?; } } else if self.windows.contains(&event.event) { self.focus(Some(event.event))?; self.restack()?; self.update_tab_bars()?; let state_clean = u16::from(event.state) & !(u16::from(ModMask::LOCK) | u16::from(ModMask::M2)); let modkey_held = state_clean & u16::from(self.config.modkey) != 0; if modkey_held && event.detail == ButtonIndex::M1.into() { self.drag_window(event.event)?; self.connection .allow_events(Allow::REPLAY_POINTER, event.time)?; } else if modkey_held && event.detail == ButtonIndex::M3.into() { self.resize_window_with_mouse(event.event)?; self.connection .allow_events(Allow::REPLAY_POINTER, event.time)?; } else { self.connection .allow_events(Allow::REPLAY_POINTER, event.time)?; } } else { self.connection .allow_events(Allow::REPLAY_POINTER, event.time)?; } } } Event::Expose(event) => { for bar in &mut self.bars { if event.window == bar.window() { bar.invalidate(); self.update_bar()?; break; } } for _tab_bar in &self.tab_bars { if event.window == _tab_bar.window() { self.update_tab_bars()?; break; } } } Event::ConfigureRequest(event) => { if let Some(client) = self.clients.get(&event.window) { let monitor = &self.monitors[client.monitor_index]; let is_floating = client.is_floating; let is_fullscreen = client.is_fullscreen; let has_layout = self.layout.name() != "normie"; if event.value_mask.contains(ConfigWindow::BORDER_WIDTH) { if let Some(c) = self.clients.get_mut(&event.window) { c.border_width = event.border_width; } } else if is_fullscreen { self.send_configure_notify(event.window)?; } else if is_floating || !has_layout { let mut x = client.x_position as i32; let mut y = client.y_position as i32; let mut w = client.width as i32; let mut h = client.height as i32; if event.value_mask.contains(ConfigWindow::X) { if let Some(c) = self.clients.get_mut(&event.window) { c.old_x_position = c.x_position; } x = monitor.screen_x + event.x as i32; } if event.value_mask.contains(ConfigWindow::Y) { if let Some(c) = self.clients.get_mut(&event.window) { c.old_y_position = c.y_position; } y = monitor.screen_y + event.y as i32; } if event.value_mask.contains(ConfigWindow::WIDTH) { if let Some(c) = self.clients.get_mut(&event.window) { c.old_width = c.width; } w = event.width as i32; } if event.value_mask.contains(ConfigWindow::HEIGHT) { if let Some(c) = self.clients.get_mut(&event.window) { c.old_height = c.height; } h = event.height as i32; } let bw = self.config.border_width as i32; let width_with_border = w + 2 * bw; let height_with_border = h + 2 * bw; if (x + w) > monitor.screen_x + monitor.screen_width && is_floating { x = monitor.screen_x + (monitor.screen_width / 2 - width_with_border / 2); } if (y + h) > monitor.screen_y + monitor.screen_height && is_floating { y = monitor.screen_y + (monitor.screen_height / 2 - height_with_border / 2); } if let Some(c) = self.clients.get_mut(&event.window) { c.x_position = x as i16; c.y_position = y as i16; c.width = w as u16; c.height = h as u16; } let only_position_change = event.value_mask.contains(ConfigWindow::X) || event.value_mask.contains(ConfigWindow::Y); let no_size_change = !event.value_mask.contains(ConfigWindow::WIDTH) && !event.value_mask.contains(ConfigWindow::HEIGHT); if only_position_change && no_size_change { self.send_configure_notify(event.window)?; } if self.is_visible(event.window) { self.connection.configure_window( event.window, &ConfigureWindowAux::new() .x(x) .y(y) .width(w as u32) .height(h as u32), )?; } } else { self.send_configure_notify(event.window)?; } } else { let mut aux = ConfigureWindowAux::new(); if event.value_mask.contains(ConfigWindow::X) { aux = aux.x(event.x as i32); } if event.value_mask.contains(ConfigWindow::Y) { aux = aux.y(event.y as i32); } if event.value_mask.contains(ConfigWindow::WIDTH) { aux = aux.width(event.width as u32); } if event.value_mask.contains(ConfigWindow::HEIGHT) { aux = aux.height(event.height as u32); } if event.value_mask.contains(ConfigWindow::BORDER_WIDTH) { aux = aux.border_width(event.border_width as u32); } if event.value_mask.contains(ConfigWindow::SIBLING) { aux = aux.sibling(event.sibling); } if event.value_mask.contains(ConfigWindow::STACK_MODE) { aux = aux.stack_mode(event.stack_mode); } self.connection.configure_window(event.window, &aux)?; } self.connection.flush()?; } Event::ClientMessage(event) => { if !self.clients.contains_key(&event.window) { return Ok(Control::Continue); } if event.type_ == self.atoms.net_wm_state { let data = event.data.as_data32(); let atom1 = data.get(1).copied().unwrap_or(0); let atom2 = data.get(2).copied().unwrap_or(0); if atom1 == self.atoms.net_wm_state_fullscreen || atom2 == self.atoms.net_wm_state_fullscreen { let action = data[0]; let fullscreen = match action { 1 => true, 0 => false, 2 => !self.fullscreen_windows.contains(&event.window), _ => return Ok(Control::Continue), }; self.set_window_fullscreen(event.window, fullscreen)?; self.restack()?; } } else if event.type_ == self.atoms.net_active_window { let selected_window = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client); let is_urgent = self .clients .get(&event.window) .map(|c| c.is_urgent) .unwrap_or(false); if Some(event.window) != selected_window && !is_urgent { self.set_urgent(event.window, true)?; } } } Event::FocusIn(event) => { if !self.windows.contains(&event.event) { return Ok(Control::Continue); } let event_window_visible = self.is_visible(event.event); if !event_window_visible { self.focus(None)?; } else { let selected_window = self .monitors .get(self.selected_monitor) .and_then(|monitor| monitor.selected_client); if let Some(focused_window) = selected_window && event.event != focused_window { self.set_focus(focused_window)?; } } } Event::MappingNotify(event) => { if event.request == x11rb::protocol::xproto::Mapping::KEYBOARD { self.grab_keys()?; } } Event::ConfigureNotify(event) => { if event.window == self.root { let old_width = self.screen.width_in_pixels; let old_height = self.screen.height_in_pixels; if event.width != old_width || event.height != old_height { self.screen = self.connection.setup().roots[self.screen_number].clone(); for monitor_index in 0..self.monitors.len() { let monitor = &self.monitors[monitor_index]; let monitor_x = monitor.screen_x; let monitor_y = monitor.screen_y; let monitor_width = monitor.screen_width as u32; let monitor_height = monitor.screen_height as u32; let fullscreen_on_monitor: Vec = self .fullscreen_windows .iter() .filter(|&&window| { self.clients .get(&window) .map(|client| client.monitor_index == monitor_index) .unwrap_or(false) }) .copied() .collect(); for window in fullscreen_on_monitor { self.connection.configure_window( window, &ConfigureWindowAux::new() .x(monitor_x) .y(monitor_y) .width(monitor_width) .height(monitor_height), )?; } } self.apply_layout()?; } } } _ => {} } Ok(Control::Continue) } fn apply_layout(&mut self) -> WmResult<()> { for monitor_index in 0..self.monitors.len() { let stack_head = self.monitors.get(monitor_index).and_then(|m| m.stack_head); self.showhide(stack_head)?; } let is_normie = self.layout.name() == LayoutType::Normie.as_str(); if !is_normie { let monitor_count = self.monitors.len(); for monitor_index in 0..monitor_count { let monitor = &self.monitors[monitor_index]; let border_width = self.config.border_width; let gaps = if self.gaps_enabled { GapConfig { inner_horizontal: self.config.gap_inner_horizontal, inner_vertical: self.config.gap_inner_vertical, outer_horizontal: self.config.gap_outer_horizontal, outer_vertical: self.config.gap_outer_vertical, } } else { GapConfig { inner_horizontal: 0, inner_vertical: 0, outer_horizontal: 0, outer_vertical: 0, } }; let monitor_x = monitor.screen_x; let monitor_y = monitor.screen_y; let monitor_width = monitor.screen_width; let monitor_height = monitor.screen_height; let scroll_offset = monitor.scroll_offset; let mut visible: Vec = Vec::new(); let mut current = self.next_tiled(monitor.clients_head, monitor); while let Some(window) = current { visible.push(window); if let Some(client) = self.clients.get(&window) { current = self.next_tiled(client.next, monitor); } else { break; } } let bar_height = if self.show_bar { self.bars .get(monitor_index) .map(|bar| bar.height() as u32) .unwrap_or(0) } else { 0 }; let usable_height = monitor_height.saturating_sub(bar_height as i32); let master_factor = monitor.master_factor; let num_master = monitor.num_master; let smartgaps_enabled = self.config.smartgaps_enabled; let geometries = self.layout.arrange( &visible, monitor_width as u32, usable_height as u32, &gaps, master_factor, num_master, smartgaps_enabled, ); for (window, geometry) in visible.iter().zip(geometries.iter()) { let mut adjusted_width = geometry.width.saturating_sub(2 * border_width); let mut adjusted_height = geometry.height.saturating_sub(2 * border_width); if let Some(client) = self.clients.get(window).cloned() && !client.is_floating { let (_, _, hint_width, hint_height, _) = self.apply_size_hints( *window, geometry.x_coordinate, geometry.y_coordinate, adjusted_width as i32, adjusted_height as i32, ); adjusted_width = hint_width as u32; adjusted_height = hint_height as u32; } let is_scrolling = self.layout.name() == "scrolling"; let adjusted_x = if is_scrolling { geometry.x_coordinate + monitor_x - scroll_offset } else { geometry.x_coordinate + monitor_x }; let adjusted_y = geometry.y_coordinate + monitor_y + bar_height as i32; if let Some(client) = self.clients.get_mut(window) { client.x_position = adjusted_x as i16; client.y_position = adjusted_y as i16; client.width = adjusted_width as u16; client.height = adjusted_height as u16; } self.connection.configure_window( *window, &ConfigureWindowAux::new() .x(adjusted_x) .y(adjusted_y) .width(adjusted_width) .height(adjusted_height) .border_width(border_width), )?; if let Some(c) = self.clients.get_mut(window) { c.x_position = adjusted_x as i16; c.y_position = adjusted_y as i16; c.width = adjusted_width as u16; c.height = adjusted_height as u16; c.border_width = border_width as u16; } } } } for monitor_index in 0..self.monitors.len() { let stack_head = self.monitors[monitor_index].stack_head; self.showhide(stack_head)?; } for monitor_index in 0..self.monitors.len() { let monitor = &self.monitors[monitor_index]; let tags = monitor.tagset[monitor.selected_tags_index]; let has_visible_fullscreen = self.fullscreen_windows.iter().any(|&w| { self.clients.get(&w).map_or(false, |c| { c.monitor_index == monitor_index && (c.tags & tags) != 0 }) }); if has_visible_fullscreen { if let Some(bar) = self.bars.get(monitor_index) { self.connection.unmap_window(bar.window())?; } for &window in &self.fullscreen_windows { if let Some(client) = self.clients.get(&window) { if client.monitor_index == monitor_index && (client.tags & tags) != 0 { self.connection.configure_window( window, &ConfigureWindowAux::new() .border_width(0) .x(monitor.screen_x) .y(monitor.screen_y) .width(monitor.screen_width as u32) .height(monitor.screen_height as u32) .stack_mode(StackMode::ABOVE), )?; } } } } else if self.show_bar { if let Some(bar) = self.bars.get(monitor_index) { self.connection.map_window(bar.window())?; } } } self.connection.flush()?; let is_tabbed = self.layout.name() == LayoutType::Tabbed.as_str(); if is_tabbed { let outer_horizontal = if self.gaps_enabled { self.config.gap_outer_horizontal } else { 0 }; let outer_vertical = if self.gaps_enabled { self.config.gap_outer_vertical } else { 0 }; for monitor_index in 0..self.tab_bars.len() { if let Some(monitor) = self.monitors.get(monitor_index) { let bar_height = if self.show_bar { self.bars .get(monitor_index) .map(|bar| bar.height() as f32) .unwrap_or(0.0) } else { 0.0 }; let tab_bar_x = (monitor.screen_x + outer_horizontal as i32) as i16; let tab_bar_y = (monitor.screen_y as f32 + bar_height + outer_vertical as f32) as i16; let tab_bar_width = monitor .screen_width .saturating_sub(2 * outer_horizontal as i32) as u16; if let Err(e) = self.tab_bars[monitor_index].reposition( &self.connection, tab_bar_x, tab_bar_y, tab_bar_width, ) { eprintln!("Failed to reposition tab bar: {:?}", e); } } } } for monitor_index in 0..self.tab_bars.len() { let has_visible_windows = self.windows.iter().any(|&window| { if let Some(client) = self.clients.get(&window) { if client.monitor_index != monitor_index || self.floating_windows.contains(&window) || self.fullscreen_windows.contains(&window) { return false; } if let Some(monitor) = self.monitors.get(monitor_index) { return (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0; } } false }); if is_tabbed && has_visible_windows { if let Err(e) = self.tab_bars[monitor_index].show(&self.connection) { eprintln!("Failed to show tab bar: {:?}", e); } } else if let Err(e) = self.tab_bars[monitor_index].hide(&self.connection) { eprintln!("Failed to hide tab bar: {:?}", e); } } if is_tabbed { self.update_tab_bars()?; } Ok(()) } pub fn change_layout(&mut self, new_layout: L) -> WmResult<()> { self.layout = Box::new(new_layout); self.apply_layout()?; Ok(()) } fn send_configure_notify(&self, window: Window) -> WmResult<()> { let client = self.clients.get(&window); let (x, y, w, h, bw) = if let Some(c) = client { ( c.x_position, c.y_position, c.width, c.height, c.border_width, ) } else { let geom = self.connection.get_geometry(window)?.reply()?; (geom.x, geom.y, geom.width, geom.height, geom.border_width) }; let event = ConfigureNotifyEvent { response_type: CONFIGURE_NOTIFY_EVENT, sequence: 0, event: window, window, above_sibling: x11rb::NONE, x, y, width: w, height: h, border_width: bw, override_redirect: false, }; self.connection .send_event(false, window, EventMask::STRUCTURE_NOTIFY, event)?; Ok(()) } fn update_size_hints(&mut self, window: Window) -> WmResult<()> { let size_hints = self .connection .get_property( false, window, x11rb::protocol::xproto::AtomEnum::WM_NORMAL_HINTS, x11rb::protocol::xproto::AtomEnum::WM_SIZE_HINTS, 0, 18, )? .reply()?; if size_hints.value.is_empty() { if let Some(client) = self.clients.get_mut(&window) { client.hints_valid = false; } return Ok(()); } if size_hints.value.len() < 18 * 4 { if let Some(client) = self.clients.get_mut(&window) { client.hints_valid = false; } return Ok(()); } use crate::size_hints::{flags::*, offset::*}; let read_u32 = |offset: usize| -> u32 { let bytes = &size_hints.value[offset * 4..(offset + 1) * 4]; u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) }; let flags = read_u32(FLAGS); if let Some(client) = self.clients.get_mut(&window) { if flags & P_BASE_SIZE != 0 { client.base_width = read_u32(BASE_WIDTH) as i32; client.base_height = read_u32(BASE_HEIGHT) as i32; } else if flags & P_MIN_SIZE != 0 { client.base_width = read_u32(MIN_WIDTH) as i32; client.base_height = read_u32(MIN_HEIGHT) as i32; } else { client.base_width = 0; client.base_height = 0; } if flags & P_RESIZE_INC != 0 { client.increment_width = read_u32(WIDTH_INC) as i32; client.increment_height = read_u32(HEIGHT_INC) as i32; } else { client.increment_width = 0; client.increment_height = 0; } if flags & P_MAX_SIZE != 0 { client.max_width = read_u32(MAX_WIDTH) as i32; client.max_height = read_u32(MAX_HEIGHT) as i32; } else { client.max_width = 0; client.max_height = 0; } if flags & P_MIN_SIZE != 0 { client.min_width = read_u32(MIN_WIDTH) as i32; client.min_height = read_u32(MIN_HEIGHT) as i32; } else if flags & P_BASE_SIZE != 0 { client.min_width = read_u32(BASE_WIDTH) as i32; client.min_height = read_u32(BASE_HEIGHT) as i32; } else { client.min_width = 0; client.min_height = 0; } if flags & P_ASPECT != 0 { client.min_aspect = (read_u32(MIN_ASPECT_Y) as f32) / (read_u32(MIN_ASPECT_X) as f32).max(1.0); client.max_aspect = (read_u32(MAX_ASPECT_X) as f32) / (read_u32(MAX_ASPECT_Y) as f32).max(1.0); } else { client.min_aspect = 0.0; client.max_aspect = 0.0; } client.is_fixed = client.max_width > 0 && client.max_height > 0 && client.max_width == client.min_width && client.max_height == client.min_height; client.hints_valid = true; } Ok(()) } fn update_window_title(&mut self, window: Window) -> WmResult<()> { let net_name = self .connection .get_property( false, window, self.atoms.net_wm_name, self.atoms.utf8_string, 0, 256, ) .ok() .and_then(|cookie| cookie.reply().ok()); if let Some(name) = net_name && !name.value.is_empty() && let Ok(title) = String::from_utf8(name.value.clone()) && let Some(client) = self.clients.get_mut(&window) { client.name = title; return Ok(()); } let wm_name = self .connection .get_property( false, window, self.atoms.wm_name, x11rb::protocol::xproto::AtomEnum::STRING, 0, 256, )? .reply()?; if !wm_name.value.is_empty() && let Ok(title) = String::from_utf8(wm_name.value.clone()) && let Some(client) = self.clients.get_mut(&window) { client.name = title; } Ok(()) } fn update_window_hints(&mut self, window: Window) -> WmResult<()> { let hints_reply = self .connection .get_property(false, window, AtomEnum::WM_HINTS, AtomEnum::WM_HINTS, 0, 9)? .reply(); if let Ok(hints) = hints_reply && hints.value.len() >= 4 { let flags = u32::from_ne_bytes([ hints.value[0], hints.value[1], hints.value[2], hints.value[3], ]); let selected_window = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client); if Some(window) == selected_window && (flags & 256) != 0 { let new_flags = flags & !256; let mut new_hints = hints.value.clone(); new_hints[0..4].copy_from_slice(&new_flags.to_ne_bytes()); self.connection.change_property( x11rb::protocol::xproto::PropMode::REPLACE, window, AtomEnum::WM_HINTS, AtomEnum::WM_HINTS, 32, 9, &new_hints, )?; } else if let Some(client) = self.clients.get_mut(&window) { client.is_urgent = (flags & 256) != 0; } if hints.value.len() >= 8 && (flags & 1) != 0 { let input = i32::from_ne_bytes([ hints.value[4], hints.value[5], hints.value[6], hints.value[7], ]); if let Some(client) = self.clients.get_mut(&window) { client.never_focus = input == 0; } } else if let Some(client) = self.clients.get_mut(&window) { client.never_focus = false; } } Ok(()) } fn update_window_type(&mut self, window: Window) -> WmResult<()> { if let Ok(state_atoms) = self.get_window_atom_list_property(window, self.atoms.net_wm_state) { if state_atoms.contains(&self.atoms.net_wm_state_fullscreen) { self.set_window_fullscreen(window, true)?; } } if let Ok(Some(type_atom)) = self.get_window_atom_property(window, self.atoms.net_wm_window_type) && type_atom == self.atoms.net_wm_window_type_dialog { if let Some(client) = self.clients.get_mut(&window) { client.is_floating = true; } self.floating_windows.insert(window); } Ok(()) } fn apply_size_hints( &mut self, window: Window, mut x: i32, mut y: i32, mut w: i32, mut h: i32, ) -> (i32, i32, i32, i32, bool) { let bh = 20; let ( client_x, client_y, client_w, client_h, bw, monitor_index, is_floating, mut hints_valid, ) = { let client = match self.clients.get(&window) { Some(c) => c, None => return (x, y, w, h, false), }; ( client.x_position as i32, client.y_position as i32, client.width as i32, client.height as i32, client.border_width as i32, client.monitor_index, client.is_floating, client.hints_valid, ) }; let monitor = &self.monitors[monitor_index]; let client_width = client_w + 2 * bw; let client_height = client_h + 2 * bw; w = w.max(1); h = h.max(1); if x >= monitor.window_area_x + monitor.window_area_width { x = monitor.window_area_x + monitor.window_area_width - client_width; } if y >= monitor.window_area_y + monitor.window_area_height { y = monitor.window_area_y + monitor.window_area_height - client_height; } if x + w + 2 * bw <= monitor.window_area_x { x = monitor.window_area_x; } if y + h + 2 * bw <= monitor.window_area_y { y = monitor.window_area_y; } if h < bh { h = bh; } if w < bh { w = bh; } if is_floating || self.layout.name() == "normie" { if !hints_valid { let _ = self.update_size_hints(window); hints_valid = self .clients .get(&window) .map(|c| c.hints_valid) .unwrap_or(false); } if hints_valid { let ( base_width, base_height, min_width, min_height, max_width, max_height, inc_width, inc_height, min_aspect, max_aspect, ) = { let client = self.clients.get(&window).unwrap(); ( client.base_width, client.base_height, client.min_width, client.min_height, client.max_width, client.max_height, client.increment_width, client.increment_height, client.min_aspect, client.max_aspect, ) }; let base_is_min = base_width == min_width && base_height == min_height; if !base_is_min { w -= base_width; h -= base_height; } if min_aspect > 0.0 && max_aspect > 0.0 { if max_aspect < (w as f32 / h as f32) { w = (h as f32 * max_aspect + 0.5) as i32; } else if min_aspect < (h as f32 / w as f32) { h = (w as f32 * min_aspect + 0.5) as i32; } } if base_is_min { w -= base_width; h -= base_height; } if inc_width > 0 { w -= w % inc_width; } if inc_height > 0 { h -= h % inc_height; } w = (w + base_width).max(min_width); h = (h + base_height).max(min_height); if max_width > 0 { w = w.min(max_width); } if max_height > 0 { h = h.min(max_height); } } } let changed = x != client_x || y != client_y || w != client_w || h != client_h; (x, y, w, h, changed) } fn next_tiled(&self, start: Option, monitor: &Monitor) -> Option { let mut current = start; while let Some(window) = current { if let Some(client) = self.clients.get(&window) { let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index]; if visible_tags != 0 && !client.is_floating { return Some(window); } current = client.next; } else { break; } } None } fn next_tagged(&self, start: Option, tags: u32) -> Option { let mut current = start; while let Some(window) = current { if let Some(client) = self.clients.get(&window) { let visible_on_tags = (client.tags & tags) != 0; if !client.is_floating && visible_on_tags { return Some(window); } current = client.next; } else { break; } } None } fn attach(&mut self, window: Window, monitor_index: usize) { if let Some(monitor) = self.monitors.get_mut(monitor_index) && let Some(client) = self.clients.get_mut(&window) { client.next = monitor.clients_head; monitor.clients_head = Some(window); } } fn attach_after(&mut self, window: Window, after_window: Window, monitor_index: usize) { if let Some(after_client) = self.clients.get(&after_window) { let old_next = after_client.next; if let Some(new_client) = self.clients.get_mut(&window) { new_client.next = old_next; } if let Some(after_client_mut) = self.clients.get_mut(&after_window) { after_client_mut.next = Some(window); } } else { self.attach(window, monitor_index); } } fn attach_aside(&mut self, window: Window, monitor_index: usize) { let monitor = match self.monitors.get(monitor_index) { Some(m) => m, None => return, }; let new_window_tags = self.clients.get(&window).map(|c| c.tags).unwrap_or(0); let first_tagged = self.next_tagged(monitor.clients_head, new_window_tags); if first_tagged.is_none() { self.attach(window, monitor_index); return; } if let Some(insert_after_window) = first_tagged && let Some(after_client) = self.clients.get(&insert_after_window) { let old_next = after_client.next; if let Some(new_client) = self.clients.get_mut(&window) { new_client.next = old_next; } if let Some(after_client_mut) = self.clients.get_mut(&insert_after_window) { after_client_mut.next = Some(window); } } } fn detach(&mut self, window: Window) { let monitor_index = self.clients.get(&window).map(|c| c.monitor_index); if let Some(monitor_index) = monitor_index && let Some(monitor) = self.monitors.get_mut(monitor_index) { if monitor.clients_head == Some(window) { if let Some(client) = self.clients.get(&window) { monitor.clients_head = client.next; } } else { let mut current = monitor.clients_head; while let Some(current_window) = current { if let Some(current_client) = self.clients.get(¤t_window) { if current_client.next == Some(window) { let new_next = self.clients.get(&window).and_then(|c| c.next); if let Some(current_client_mut) = self.clients.get_mut(¤t_window) { current_client_mut.next = new_next; } break; } current = current_client.next; } else { break; } } } } } fn attach_stack(&mut self, window: Window, monitor_index: usize) { if let Some(monitor) = self.monitors.get_mut(monitor_index) && let Some(client) = self.clients.get_mut(&window) { client.stack_next = monitor.stack_head; monitor.stack_head = Some(window); } } fn detach_stack(&mut self, window: Window) { let monitor_index = self.clients.get(&window).map(|c| c.monitor_index); if let Some(monitor_index) = monitor_index && let Some(monitor) = self.monitors.get_mut(monitor_index) { if monitor.stack_head == Some(window) { if let Some(client) = self.clients.get(&window) { monitor.stack_head = client.stack_next; } let should_update_selected = monitor.selected_client == Some(window); let mut new_selected: Option = None; if should_update_selected { let mut stack_current = monitor.stack_head; while let Some(stack_window) = stack_current { if let Some(stack_client) = self.clients.get(&stack_window) { if self.is_window_visible(stack_window) { new_selected = Some(stack_window); break; } stack_current = stack_client.stack_next; } else { break; } } } if should_update_selected && let Some(monitor) = self.monitors.get_mut(monitor_index) { monitor.selected_client = new_selected; } } else { let mut current = monitor.stack_head; while let Some(current_window) = current { if let Some(current_client) = self.clients.get(¤t_window) { if current_client.stack_next == Some(window) { let new_stack_next = self.clients.get(&window).and_then(|c| c.stack_next); if let Some(current_client_mut) = self.clients.get_mut(¤t_window) { current_client_mut.stack_next = new_stack_next; } break; } current = current_client.stack_next; } else { break; } } } } } fn remove_window(&mut self, window: Window, destroyed: bool) -> WmResult<()> { let initial_count = self.windows.len(); let focused = self .monitors .get(self.selected_monitor) .and_then(|m| m.selected_client); if !destroyed { if let Some(client) = self.clients.get(&window) { let old_border_width = client.old_border_width; self.connection.configure_window( window, &ConfigureWindowAux::new().border_width(old_border_width as u32), )?; } self.set_wm_state(window, 0)?; } if self.clients.contains_key(&window) { self.detach(window); self.detach_stack(window); self.clients.remove(&window); } self.windows.retain(|&w| w != window); self.floating_windows.remove(&window); self.update_client_list()?; if self.windows.len() < initial_count { if focused == Some(window) { let visible = self.visible_windows_on_monitor(self.selected_monitor); if let Some(&new_win) = visible.last() { self.focus(Some(new_win))?; if self.layout.name() == "scrolling" { self.scroll_to_window(new_win, true)?; } } else if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) { monitor.selected_client = None; } } self.apply_layout()?; self.update_bar()?; } Ok(()) } fn get_selected_monitor(&self) -> &Monitor { &self.monitors[self.selected_monitor] } fn has_windows_on_tag(&self, monitor_number: usize, tag_index: usize) -> bool { let Some(monitor) = self.monitors.get(monitor_number) else { return false; }; let mut current = monitor.clients_head; while let Some(window) = current { let Some(client) = self.clients.get(&window) else { break; }; if unmask_tag(client.tags) == tag_index { return true; } current = client.next; } false } fn run_autostart_commands(&self) { for command in &self.config.autostart { crate::signal::spawn_detached(command); eprintln!("[autostart] Spawned: {}", command); } } }