use crate::client::Client;
use crate::data_types::{
Change, ColorScheme, Config, Direction, KeyBindings, KeyCode, Point, Region, Ring, WinId,
};
use crate::hooks;
use crate::screen::Screen;
use crate::workspace::Workspace;
use crate::xconnection::{XConn, XEvent};
use nix::sys::signal::{signal, SigHandler, Signal};
use std::collections::HashMap;
use std::process::exit;
pub struct WindowManager<'a> {
conn: &'a dyn XConn,
screens: Ring<Screen>,
workspaces: Ring<Workspace>,
client_map: HashMap<WinId, Client>,
previous_workspace: usize,
floating_classes: &'static [&'static str],
color_scheme: ColorScheme,
border_px: u32,
gap_px: u32,
main_ratio_step: f32,
show_bar: bool,
new_client_hooks: Vec<hooks::NewClientHook>,
layout_hooks: Vec<hooks::LayoutHook>,
workspace_change_hooks: Vec<hooks::WorkspaceChangeHook>,
screen_change_hooks: Vec<hooks::ScreenChangeHook>,
focus_hooks: Vec<hooks::FocusHook>,
}
impl<'a> WindowManager<'a> {
pub fn init(config: Config, conn: &'a dyn XConn) -> WindowManager<'a> {
let mut screens = conn.current_outputs();
info!("connected to X server: {} screens detected", screens.len());
for (i, s) in screens.iter().enumerate() {
info!("screen ({}) :: {:?}", i, s);
}
screens
.iter_mut()
.for_each(|s| s.update_effective_region(config.bar_height, config.top_bar));
let workspaces: Vec<Workspace> = config
.workspaces
.iter()
.map(|name| Workspace::new(name, config.layouts.clone().to_vec()))
.collect();
conn.set_wm_properties(config.workspaces);
WindowManager {
conn: conn,
screens: Ring::new(screens),
workspaces: Ring::new(workspaces),
client_map: HashMap::new(),
previous_workspace: 0,
floating_classes: config.floating_classes,
color_scheme: config.color_scheme,
border_px: config.border_px,
gap_px: config.gap_px,
main_ratio_step: config.main_ratio_step,
show_bar: config.show_bar,
new_client_hooks: config.new_client_hooks,
layout_hooks: config.layout_hooks,
workspace_change_hooks: config.workspace_change_hooks,
screen_change_hooks: config.screen_change_hooks,
focus_hooks: config.focus_hooks,
}
}
fn apply_layout(&mut self, wix: usize) {
let lc = self.workspaces[wix].layout_conf();
if lc.floating {
return;
}
let (i, s) = {
self.screens
.iter()
.enumerate()
.find(|(_, s)| s.wix == wix)
.unwrap()
};
let r = s.region(self.show_bar);
self.layout_hooks
.clone()
.iter()
.for_each(|h| (h)(self, wix, i));
let ws = &self.workspaces[wix];
let gpx = if lc.gapless { 0 } else { self.gap_px };
let padding = 2 * (self.border_px + gpx);
for (id, region) in ws.arrange(r, &self.client_map) {
debug!("configuring {} with {:?}", id, region);
let (x, y, w, h) = region.values();
let r = Region::new(x + gpx, y + gpx, w - padding, h - padding);
self.conn.position_window(id, r, self.border_px);
}
}
fn remove_client(&mut self, win_id: WinId) {
match self.client_map.get(&win_id) {
Some(client) => {
self.workspaces[client.workspace()].remove_client(win_id);
self.client_map.remove(&win_id).map(|c| {
debug!("removing ref to client {} ({})", c.id(), c.class());
});
}
None => warn!("attempt to remove unknown client {}", win_id),
}
}
fn set_screen_from_cursor(&mut self, cursor: Point) -> Option<&Screen> {
let s = self.screens.focus_by(|s| s.contains(cursor));
debug!("FOCUSED_SCREEN :: {:?}", s);
s
}
fn workspace_index_for_client(&mut self, id: WinId) -> Option<usize> {
self.client_map.get(&id).map(|c| c.workspace())
}
fn active_ws_index(&self) -> usize {
self.screens.focused().unwrap().wix
}
fn focused_client(&self) -> Option<&Client> {
self.workspaces[self.active_ws_index()]
.focused_client()
.and_then(|id| self.client_map.get(&id))
}
fn client_gained_focus(&mut self, id: WinId) {
self.focus_hooks.clone().iter().for_each(|h| (h)(self, id));
let color_focus = self.color_scheme.highlight;
let color_normal = self.color_scheme.fg_1;
self.focused_client()
.map(|c| self.conn.set_client_border_color(c.id(), color_normal));
self.conn.focus_client(id);
self.conn.set_client_border_color(id, color_focus);
if let Some(wix) = self.workspace_index_for_client(id) {
let ws = &mut self.workspaces[wix];
ws.focus_client(id);
if ws.layout_conf().follow_focus {
self.apply_layout(wix);
}
}
}
fn client_lost_focus(&mut self, id: WinId) {
let color = self.color_scheme.fg_1;
self.conn.set_client_border_color(id, color);
}
pub fn grab_keys_and_run(&mut self, bindings: KeyBindings) {
unsafe { signal(Signal::SIGCHLD, SigHandler::SigIgn) }.unwrap();
self.conn.grab_keys(&bindings);
self.focus_workspace(0);
loop {
if let Some(event) = self.conn.wait_for_event() {
match event {
XEvent::KeyPress { code } => self.handle_key_press(code, &bindings),
XEvent::Map { id, ignore } => self.handle_map_notify(id, ignore),
XEvent::Enter { id, rpt, wpt } => self.handle_enter_notify(id, rpt, wpt),
XEvent::Leave { id, rpt, wpt } => self.handle_leave_notify(id, rpt, wpt),
XEvent::Destroy { id } => self.handle_destroy_notify(id),
XEvent::ScreenChange => self.handle_screen_change(),
_ => (),
}
}
self.conn.flush();
}
}
fn handle_key_press(&mut self, key_code: KeyCode, bindings: &KeyBindings) {
if let Some(action) = bindings.get(&key_code) {
debug!("handling key code: {:?}", key_code);
action(self);
}
}
fn handle_map_notify(&mut self, win_id: WinId, override_redirect: bool) {
if override_redirect || self.client_map.contains_key(&win_id) {
return;
}
let wm_class = match self.conn.str_prop(win_id, "WM_CLASS") {
Ok(s) => s.split("\0").collect::<Vec<&str>>()[0].into(),
Err(_) => String::new(),
};
let floating = self.floating_classes.contains(&wm_class.as_ref());
let wix = self.active_ws_index();
let mut client = Client::new(win_id, wm_class, wix, floating);
debug!("mapping client: {:?}", client);
self.new_client_hooks
.clone()
.iter()
.for_each(|h| (h)(self, &mut client));
self.client_map.insert(win_id, client);
if !floating {
self.workspaces[wix].add_client(win_id);
}
self.conn.focus_client(win_id);
self.conn.mark_new_window(win_id);
let color = self.color_scheme.highlight;
self.conn.set_client_border_color(win_id, color);
self.conn.set_client_workspace(win_id, wix);
self.apply_layout(self.active_ws_index());
}
fn handle_enter_notify(&mut self, id: WinId, rpt: Point, _wpt: Point) {
self.client_gained_focus(id);
self.set_screen_from_cursor(rpt);
self.focus_hooks.clone().iter().for_each(|h| (h)(self, id));
}
fn handle_leave_notify(&mut self, id: WinId, rpt: Point, _wpt: Point) {
self.client_lost_focus(id);
self.set_screen_from_cursor(rpt);
}
fn handle_screen_change(&mut self) {
self.set_screen_from_cursor(self.conn.cursor_position());
self.workspaces
.focus_nth(self.screens.focused().unwrap().wix);
}
fn handle_destroy_notify(&mut self, win_id: WinId) {
debug!("DESTROY_NOTIFY for {}", win_id);
self.remove_client(win_id);
self.apply_layout(self.active_ws_index());
}
pub fn log(&self, msg: &str) {
info!("{}", msg);
}
pub fn cycle_screen(&mut self, direction: Direction) {
if !self.screens.would_wrap(direction) {
self.screens.cycle_focus(direction);
self.workspaces
.focus_nth(self.screens.focused().unwrap().wix);
self.conn.warp_cursor(None, self.screens.focused().unwrap());
let wix = self.workspaces.focused_index();
self.conn.set_current_workspace(wix);
let i = self.screens.focused_index();
self.screen_change_hooks
.clone()
.iter()
.for_each(|h| (h)(self, i));
}
}
pub fn cycle_workspace(&mut self, direction: Direction) {
self.workspaces.cycle_focus(direction);
let i = self.workspaces.focused_index();
self.focus_workspace(i);
}
pub fn drag_workspace(&mut self, direction: Direction) {
let wix = self.active_ws_index();
self.cycle_screen(direction);
self.focus_workspace(wix);
}
pub fn cycle_client(&mut self, direction: Direction) {
let wix = self.active_ws_index();
let cycled = self.workspaces[wix].cycle_client(direction);
if let Some((prev, new)) = cycled {
self.client_lost_focus(prev);
self.client_gained_focus(new);
self.conn
.warp_cursor(Some(new), self.screens.focused().unwrap());
}
}
pub fn drag_client(&mut self, direction: Direction) {
if let Some(id) = self.focused_client().and_then(|c| Some(c.id())) {
let wix = self.active_ws_index();
self.workspaces[wix].drag_client(direction);
self.apply_layout(wix);
self.client_gained_focus(id);
self.conn
.warp_cursor(Some(id), self.screens.focused().unwrap());
}
}
pub fn cycle_layout(&mut self, direction: Direction) {
let wix = self.active_ws_index();
self.workspaces[wix].cycle_layout(direction);
self.apply_layout(wix);
info!("ACTIVE_LAYOUT {}", self.workspaces[wix].layout_symbol());
}
pub fn update_max_main(&mut self, change: Change) {
let wix = self.active_ws_index();
self.workspaces[wix].update_max_main(change);
self.apply_layout(wix);
}
pub fn update_main_ratio(&mut self, change: Change) {
let step = self.main_ratio_step;
let wix = self.active_ws_index();
self.workspaces[wix].update_main_ratio(change, step);
self.apply_layout(wix);
}
pub fn exit(&mut self) {
self.conn.cleanup();
self.conn.flush();
exit(0);
}
pub fn current_layout_symbol(&self) -> &str {
self.workspaces[self.active_ws_index()].layout_symbol()
}
pub fn set_root_window_name(&self, s: &str) {
self.conn.set_root_window_name(s);
}
pub fn focus_workspace(&mut self, index: usize) {
info!("ACTIVE_LAYOUT {}", self.workspaces[index].layout_symbol());
let active = self.active_ws_index();
if active == index {
return;
}
self.previous_workspace = active;
self.workspace_change_hooks
.clone()
.iter()
.for_each(|h| (h)(self, active, index));
for i in 0..self.screens.len() {
if self.screens[i].wix == index {
self.screens[i].wix = self.screens.focused().unwrap().wix;
self.screens.focused_mut().unwrap().wix = index;
self.apply_layout(active);
self.apply_layout(index);
return;
}
}
self.workspaces[active]
.iter()
.for_each(|c| self.conn.unmap_window(*c));
self.workspaces[index]
.iter()
.for_each(|c| self.conn.map_window(*c));
self.screens.focused_mut().unwrap().wix = index;
self.apply_layout(index);
self.conn.set_current_workspace(index);
}
pub fn toggle_workspace(&mut self) {
self.focus_workspace(self.previous_workspace);
}
pub fn client_to_workspace(&mut self, index: usize) {
if index == self.screens.focused().unwrap().wix {
return;
}
let wix = self.active_ws_index();
let ws = &mut self.workspaces[wix];
ws.remove_focused_client().map(|id| {
self.conn.unmap_window(id);
self.workspaces[index].add_client(id);
self.client_map.get_mut(&id).map(|c| c.set_workspace(index));
self.conn.set_client_workspace(id, index);
self.apply_layout(self.active_ws_index());
});
}
pub fn kill_client(&mut self) {
if let Some(client) = self.focused_client() {
let id = client.id();
debug!("KILL_CLIENT for {}", id);
self.conn.send_client_event(id, "WM_DELETE_WINDOW");
self.conn.flush();
self.remove_client(id);
self.apply_layout(self.active_ws_index());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data_types::{Direction::*, *};
use crate::layout::*;
use crate::screen::*;
use crate::xconnection::*;
fn wm_with_mock_conn<'a>(layouts: Vec<Layout>, conn: &'a MockXConn) -> WindowManager<'a> {
let mut conf = Config::default();
conf.layouts = layouts;
WindowManager::init(conf, conn)
}
fn test_layouts() -> Vec<Layout> {
vec![Layout::new("t", LayoutConf::default(), mock_layout, 1, 0.6)]
}
fn test_screens() -> Vec<Screen> {
let r = Region::new(0, 0, 1366, 768);
vec![Screen {
true_region: r,
effective_region: r,
wix: 0,
}]
}
fn add_n_clients(wm: &mut WindowManager, n: usize, offset: usize) {
for i in 0..n {
wm.handle_map_notify(10 * (i + offset + 1) as u32, false);
}
}
#[test]
fn worspace_switching_with_active_clients() {
let conn = MockXConn::new(test_screens());
let mut wm = wm_with_mock_conn(test_layouts(), &conn);
add_n_clients(&mut wm, 3, 0);
assert_eq!(wm.workspaces[0].len(), 3);
assert_eq!(wm.workspaces[0].focused_client(), Some(30));
wm.focus_workspace(1);
add_n_clients(&mut wm, 2, 3);
assert_eq!(wm.workspaces[1].len(), 2);
assert_eq!(wm.workspaces[1].focused_client(), Some(50));
wm.focus_workspace(0);
assert_eq!(wm.workspaces[0].len(), 3);
assert_eq!(wm.workspaces[0].focused_client(), Some(30));
}
#[test]
fn killing_a_client_removes_it_from_the_workspace() {
let conn = MockXConn::new(test_screens());
let mut wm = wm_with_mock_conn(test_layouts(), &conn);
add_n_clients(&mut wm, 1, 0);
wm.kill_client();
assert_eq!(wm.workspaces[0].len(), 0);
}
#[test]
fn kill_client_kills_focused_not_first() {
let conn = MockXConn::new(test_screens());
let mut wm = wm_with_mock_conn(test_layouts(), &conn);
add_n_clients(&mut wm, 5, 0);
assert_eq!(wm.active_ws_index(), 0);
wm.cycle_client(Forward);
assert_eq!(wm.workspaces[0].focused_client(), Some(40));
wm.kill_client();
let ids: Vec<WinId> = wm.workspaces[0].iter().map(|c| *c).collect();
assert_eq!(ids, vec![50, 30, 20, 10]);
assert_eq!(wm.workspaces[0].focused_client(), Some(30));
}
#[test]
fn moving_then_deleting_clients() {
let conn = MockXConn::new(test_screens());
let mut wm = wm_with_mock_conn(test_layouts(), &conn);
add_n_clients(&mut wm, 2, 0);
wm.client_to_workspace(1);
wm.client_to_workspace(1);
wm.focus_workspace(1);
wm.kill_client();
assert_eq!(wm.workspaces[1].iter().collect::<Vec<&WinId>>(), vec![&20]);
}
#[test]
fn sending_a_client_inserts_at_head() {
let conn = MockXConn::new(test_screens());
let mut wm = wm_with_mock_conn(test_layouts(), &conn);
add_n_clients(&mut wm, 2, 0);
wm.client_to_workspace(1);
wm.client_to_workspace(1);
wm.focus_workspace(1);
assert_eq!(
wm.workspaces[1].iter().collect::<Vec<&WinId>>(),
vec![&10, &20]
);
}
#[test]
fn sending_a_client_sets_focus() {
let conn = MockXConn::new(test_screens());
let mut wm = wm_with_mock_conn(test_layouts(), &conn);
add_n_clients(&mut wm, 2, 0);
wm.client_to_workspace(1);
wm.client_to_workspace(1);
wm.focus_workspace(1);
assert_eq!(wm.workspaces[1].focused_client(), Some(10));
}
#[test]
fn x_focus_events_set_workspace_focus() {
let conn = MockXConn::new(test_screens());
let mut wm = wm_with_mock_conn(test_layouts(), &conn);
add_n_clients(&mut wm, 5, 0);
wm.client_gained_focus(10);
assert_eq!(wm.workspaces[0].focused_client(), Some(10));
}
#[test]
fn dragging_clients_forward_from_index_0() {
let conn = MockXConn::new(test_screens());
let mut wm = wm_with_mock_conn(test_layouts(), &conn);
add_n_clients(&mut wm, 5, 0);
let clients = |w: &mut WindowManager| {
w.workspaces[w.screens[0].wix]
.iter()
.cloned()
.collect::<Vec<_>>()
};
wm.drag_client(Forward);
assert_eq!(wm.focused_client().unwrap().id(), 50);
assert_eq!(clients(&mut wm), vec![40, 50, 30, 20, 10]);
wm.drag_client(Forward);
assert_eq!(wm.focused_client().unwrap().id(), 50);
assert_eq!(clients(&mut wm), vec![40, 30, 50, 20, 10]);
wm.client_gained_focus(20);
wm.drag_client(Forward);
assert_eq!(wm.focused_client().unwrap().id(), 20);
assert_eq!(clients(&mut wm), vec![40, 30, 50, 10, 20]);
}
}