968 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			968 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::{cell::RefCell, rc::Rc};
 | |
| 
 | |
| use log::{error, info};
 | |
| 
 | |
| use x11::xlib::{self, Window};
 | |
| 
 | |
| use crate::backends::structs::WindowType;
 | |
| use crate::backends::window_event::{
 | |
|     FullscreenEvent, FullscreenState, WindowNameEvent, WindowTypeChangedEvent,
 | |
| };
 | |
| use crate::util::{Point, Size};
 | |
| use crate::{
 | |
|     backends::{
 | |
|         keycodes::{MouseButton, VirtualKeyCode},
 | |
|         window_event::{
 | |
|             ButtonEvent, ConfigureEvent, KeyBind, KeyEvent, KeyState, MapEvent,
 | |
|             ModifierKey, ModifierState, MotionEvent, MouseBind, WindowEvent,
 | |
|         },
 | |
|         xlib::XLib,
 | |
|         WindowServerBackend,
 | |
|     },
 | |
|     clients::{Client, ClientEntry, ClientKey, ClientState},
 | |
| };
 | |
| 
 | |
| use serde::Deserialize;
 | |
| 
 | |
| /**
 | |
| Contains static config data for the window manager, the sort of stuff you might want to
 | |
| be able to configure in a config file.
 | |
|  */
 | |
| #[derive(Debug, Deserialize)]
 | |
| pub struct WMConfig {
 | |
|     num_virtualscreens: usize,
 | |
|     mod_key: ModifierKey,
 | |
|     gap: Option<i32>,
 | |
|     kill_clients_on_exit: bool,
 | |
|     #[serde(default = "WMConfig::default_active_window_border_color")]
 | |
|     active_window_border_color: String,
 | |
|     #[serde(default = "WMConfig::default_inactive_window_border_color")]
 | |
|     inactive_window_border_color: String,
 | |
|     #[serde(default = "WMConfig::default_terminal")]
 | |
|     terminal_command: (String, Vec<String>),
 | |
|     border_width: Option<i32>,
 | |
| }
 | |
| 
 | |
| impl WMConfig {
 | |
|     fn default_active_window_border_color() -> String {
 | |
|         "#ffffff".to_string()
 | |
|     }
 | |
| 
 | |
|     fn default_inactive_window_border_color() -> String {
 | |
|         "#444444".to_string()
 | |
|     }
 | |
| 
 | |
|     fn default_terminal() -> (String, Vec<String>) {
 | |
|         ("xterm".to_string(), vec![])
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Default for WMConfig {
 | |
|     fn default() -> Self {
 | |
|         Self {
 | |
|             num_virtualscreens: 10,
 | |
|             mod_key: ModifierKey::Super,
 | |
|             gap: Some(2),
 | |
|             kill_clients_on_exit: false,
 | |
|             active_window_border_color:
 | |
|                 Self::default_active_window_border_color(),
 | |
|             inactive_window_border_color:
 | |
|                 Self::default_inactive_window_border_color(),
 | |
|             terminal_command: Self::default_terminal(),
 | |
|             border_width: Some(1),
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub struct WindowManager<B = XLib>
 | |
| where
 | |
|     B: WindowServerBackend,
 | |
| {
 | |
|     clients: ClientState,
 | |
|     move_resize_window: MoveResizeInfo,
 | |
|     keybinds: Rc<RefCell<Vec<KeyBinding<B>>>>,
 | |
|     backend: B,
 | |
| 
 | |
|     config: WMConfig,
 | |
| }
 | |
| 
 | |
| #[derive(Debug, Clone, Copy)]
 | |
| pub enum Direction {
 | |
|     West(usize),
 | |
|     East(usize),
 | |
|     North(usize),
 | |
|     South(usize),
 | |
| }
 | |
| 
 | |
| enum MoveResizeInfo {
 | |
|     Move(MoveInfoInner),
 | |
|     Resize(ResizeInfoInner),
 | |
|     None,
 | |
| }
 | |
| 
 | |
| #[derive(Debug)]
 | |
| struct MoveInfoInner {
 | |
|     window: Window,
 | |
|     starting_cursor_pos: Point<i32>,
 | |
|     starting_window_pos: Point<i32>,
 | |
| }
 | |
| 
 | |
| #[derive(Debug)]
 | |
| struct ResizeInfoInner {
 | |
|     window: Window,
 | |
|     starting_cursor_pos: Point<i32>,
 | |
|     starting_window_size: Size<i32>,
 | |
| }
 | |
| 
 | |
| use derivative::*;
 | |
| 
 | |
| #[derive(Derivative)]
 | |
| #[derivative(Clone(bound = ""))]
 | |
| struct KeyBinding<B: WindowServerBackend> {
 | |
|     key: KeyBind,
 | |
|     closure: Rc<dyn Fn(&mut WindowManager<B>, &KeyEvent<B::Window>)>,
 | |
| }
 | |
| 
 | |
| impl<B: WindowServerBackend> KeyBinding<B> {
 | |
|     pub fn new<F>(key: KeyBind, cb: F) -> Self
 | |
|     where
 | |
|         F: Fn(&mut WindowManager<B>, &KeyEvent<B::Window>),
 | |
|         F: 'static,
 | |
|     {
 | |
|         Self {
 | |
|             key,
 | |
|             closure: Rc::new(cb),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn call(&self, wm: &mut WindowManager<B>, ev: &KeyEvent<B::Window>) {
 | |
|         (self.closure)(wm, ev);
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl<B> WindowManager<B>
 | |
| where
 | |
|     B: WindowServerBackend<Window = xlib::Window>,
 | |
| {
 | |
|     pub fn new(config: WMConfig) -> Self {
 | |
|         let backend = B::build();
 | |
| 
 | |
|         let clients = ClientState::new()
 | |
|             .with_virtualscreens(config.num_virtualscreens)
 | |
|             .with_gap(config.gap.unwrap_or(1))
 | |
|             .with_border(config.border_width.unwrap_or(1))
 | |
|             .with_screen_size(backend.screen_size());
 | |
| 
 | |
|         Self {
 | |
|             clients,
 | |
|             move_resize_window: MoveResizeInfo::None,
 | |
|             keybinds: Rc::new(RefCell::new(Vec::new())),
 | |
|             backend,
 | |
|             config,
 | |
|         }
 | |
|         .init()
 | |
|     }
 | |
| 
 | |
|     fn init(mut self) -> Self {
 | |
|         self.backend.add_keybind(
 | |
|             MouseBind::new(MouseButton::Left)
 | |
|                 .with_mod(self.config.mod_key)
 | |
|                 .into(),
 | |
|         );
 | |
|         self.backend.add_keybind(
 | |
|             MouseBind::new(MouseButton::Middle)
 | |
|                 .with_mod(self.config.mod_key)
 | |
|                 .into(),
 | |
|         );
 | |
|         self.backend.add_keybind(
 | |
|             MouseBind::new(MouseButton::Right)
 | |
|                 .with_mod(self.config.mod_key)
 | |
|                 .into(),
 | |
|         );
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::P).with_mod(self.config.mod_key),
 | |
|             |wm, _| {
 | |
|                 wm.spawn(
 | |
|                     &"dmenu_run",
 | |
|                     &[
 | |
|                         "-m",
 | |
|                         "0",
 | |
|                         "-fn",
 | |
|                         "'New York:size=13'",
 | |
|                         "-nb",
 | |
|                         "#222222",
 | |
|                         "-nf",
 | |
|                         "#bbbbbb",
 | |
|                         "-sb",
 | |
|                         "#dddddd",
 | |
|                         "-sf",
 | |
|                         "#eeeeee",
 | |
|                     ],
 | |
|                 )
 | |
|             },
 | |
|         ));
 | |
| 
 | |
|         // self.add_keybind(KeyBinding::new(
 | |
|         //     KeyBind::new(VirtualKeyCode::Print),
 | |
|         //     |wm, _| wm.spawn("screenshot.sh", &[]),
 | |
|         // ));
 | |
| 
 | |
|         // self.add_keybind(KeyBinding::new(
 | |
|         //     KeyBind::new(VirtualKeyCode::Print).with_mod(ModifierKey::Shift),
 | |
|         //     |wm, _| wm.spawn("screenshot.sh", &["-edit"]),
 | |
|         // ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::M).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.handle_switch_stack(),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::F).with_mod(self.config.mod_key),
 | |
|             |wm, _| {
 | |
|                 wm.clients
 | |
|                     .get_focused()
 | |
|                     .into_option()
 | |
|                     .map(|c| c.key())
 | |
|                     .and_then(|k| Some(wm.clients.toggle_floating(&k)));
 | |
| 
 | |
|                 wm.arrange_clients();
 | |
|             },
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Q).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.kill_client(),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Q)
 | |
|                 .with_mod(self.config.mod_key)
 | |
|                 .with_mod(ModifierKey::Shift),
 | |
|             |wm, _| wm.quit(),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Return)
 | |
|                 .with_mod(self.config.mod_key)
 | |
|                 .with_mod(ModifierKey::Shift),
 | |
|             |wm, _| {
 | |
|                 wm.spawn(
 | |
|                     &wm.config.terminal_command.0,
 | |
|                     &wm.config.terminal_command.1,
 | |
|                 )
 | |
|             },
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::J).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.move_focus(Direction::south()),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::K).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.move_focus(Direction::north()),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::H).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.move_focus(Direction::west()),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::L).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.move_focus(Direction::east()),
 | |
|         ));
 | |
| 
 | |
|         // resize master stack
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::K)
 | |
|                 .with_mod(self.config.mod_key)
 | |
|                 .with_mod(ModifierKey::Shift),
 | |
|             |wm, _| {
 | |
|                 wm.clients.change_master_size(0.1);
 | |
|                 wm.arrange_clients();
 | |
|             },
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::J)
 | |
|                 .with_mod(self.config.mod_key)
 | |
|                 .with_mod(ModifierKey::Shift),
 | |
|             |wm, _| {
 | |
|                 wm.clients.change_master_size(-0.1);
 | |
|                 wm.arrange_clients();
 | |
|             },
 | |
|         ));
 | |
| 
 | |
|         self.add_vs_switch_keybinds();
 | |
| 
 | |
|         self.backend.set_active_window_border_color(
 | |
|             &self.config.active_window_border_color,
 | |
|         );
 | |
|         self.backend.set_inactive_window_border_color(
 | |
|             &self.config.inactive_window_border_color,
 | |
|         );
 | |
| 
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     fn add_keybind(&mut self, keybind: KeyBinding<B>) {
 | |
|         self.backend.add_keybind((&keybind.key).into());
 | |
|         self.keybinds.borrow_mut().push(keybind);
 | |
|     }
 | |
| 
 | |
|     fn add_vs_switch_keybinds(&mut self) {
 | |
|         // Old keybinds
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Left).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.rotate_virtual_screen(Direction::West(1)),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::H)
 | |
|                 .with_mod(self.config.mod_key)
 | |
|                 .with_mod(ModifierKey::Shift),
 | |
|             |wm, _| wm.rotate_virtual_screen(Direction::West(1)),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Right).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.rotate_virtual_screen(Direction::East(1)),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::L)
 | |
|                 .with_mod(self.config.mod_key)
 | |
|                 .with_mod(ModifierKey::Shift),
 | |
|             |wm, _| wm.rotate_virtual_screen(Direction::East(1)),
 | |
|         ));
 | |
| 
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Tab).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.rotate_virtual_screen_back(),
 | |
|         ));
 | |
| 
 | |
|         // Mod + Num
 | |
| 
 | |
|         // Press Mod + `1` to move go to the `1`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::One).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(1),
 | |
|         ));
 | |
| 
 | |
|         // Press Mod + `2` to move go to the `2`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Two).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(2),
 | |
|         ));
 | |
| 
 | |
|         // Press Mod + `3` to move go to the `3`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Three).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(3),
 | |
|         ));
 | |
| 
 | |
|         // Press Mod + `4` to move go to the `4`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Four).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(4),
 | |
|         ));
 | |
| 
 | |
|         // Press Mod + `5` to move go to the `5`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Five).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(5),
 | |
|         ));
 | |
| 
 | |
|         // Press Mod + `6` to move go to the `6`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Six).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(6),
 | |
|         ));
 | |
| 
 | |
|         // Press Mod + `7` to move go to the `7`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Seven).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(7),
 | |
|         ));
 | |
| 
 | |
|         // Press Mod + `8` to move go to the `8`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Eight).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(8),
 | |
|         ));
 | |
| 
 | |
|         // Press Mod + `9` to move go to the `9`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Nine).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(9),
 | |
|         ));
 | |
| 
 | |
|         // Press Mod + `0` to move go to the `0`th virtual screen
 | |
|         self.add_keybind(KeyBinding::new(
 | |
|             KeyBind::new(VirtualKeyCode::Zero).with_mod(self.config.mod_key),
 | |
|             |wm, _| wm.go_to_nth_virtual_screen(10),
 | |
|         ));
 | |
|     }
 | |
| 
 | |
|     #[allow(unused_mut)]
 | |
|     pub fn run(mut self) -> ! {
 | |
|         loop {
 | |
|             let event = self.backend.next_event();
 | |
| 
 | |
|             match event {
 | |
|                 WindowEvent::KeyEvent(event) => {
 | |
|                     if event.state == KeyState::Pressed {
 | |
|                         self.handle_keybinds(&event);
 | |
|                     }
 | |
|                 }
 | |
|                 WindowEvent::ButtonEvent(event) => {
 | |
|                     self.button_event(&event);
 | |
|                 }
 | |
|                 WindowEvent::MapRequestEvent(MapEvent { window }) => {
 | |
|                     if !self.clients.contains(&window) {
 | |
|                         self.new_client(window);
 | |
|                     }
 | |
|                 }
 | |
|                 WindowEvent::UnmapEvent(event) => {
 | |
|                     self.clients.remove(&event.window);
 | |
|                     self.arrange_clients();
 | |
|                 }
 | |
|                 WindowEvent::EnterEvent(event) => {
 | |
|                     self.focus_client(&event.window, false);
 | |
|                 }
 | |
|                 WindowEvent::MotionEvent(event) => {
 | |
|                     self.do_move_resize_window(&event);
 | |
|                 }
 | |
|                 WindowEvent::ConfigureEvent(ConfigureEvent {
 | |
|                     window,
 | |
|                     size,
 | |
|                     position,
 | |
|                     ..
 | |
|                 }) => match self.clients.get(&window) {
 | |
|                     ClientEntry::Tiled(client)
 | |
|                     | ClientEntry::Floating(client) => {
 | |
|                         self.backend.configure_window(
 | |
|                             window,
 | |
|                             Some(client.size),
 | |
|                             Some(client.position),
 | |
|                             None,
 | |
|                         )
 | |
|                     }
 | |
|                     ClientEntry::Vacant => self.backend.configure_window(
 | |
|                         window,
 | |
|                         Some(size),
 | |
|                         Some(position),
 | |
|                         None,
 | |
|                     ),
 | |
|                 },
 | |
|                 WindowEvent::FullscreenEvent(FullscreenEvent {
 | |
|                     window,
 | |
|                     state,
 | |
|                 }) => {
 | |
|                     if match state {
 | |
|                         FullscreenState::On => {
 | |
|                             self.clients.set_fullscreen(&window, true)
 | |
|                         }
 | |
|                         FullscreenState::Off => {
 | |
|                             self.clients.set_fullscreen(&window, false)
 | |
|                         }
 | |
|                         FullscreenState::Toggle => {
 | |
|                             self.clients.toggle_fullscreen(&window)
 | |
|                         }
 | |
|                     } {
 | |
|                         if let Some(client) =
 | |
|                             self.clients.get(&window).into_option()
 | |
|                         {
 | |
|                             self.backend.configure_window(
 | |
|                                 window,
 | |
|                                 None,
 | |
|                                 None,
 | |
|                                 if client.is_fullscreen() {
 | |
|                                     Some(0)
 | |
|                                 } else {
 | |
|                                     Some(self.clients.get_border())
 | |
|                                 },
 | |
|                             );
 | |
|                         };
 | |
| 
 | |
|                         self.arrange_clients();
 | |
|                     }
 | |
|                 }
 | |
|                 WindowEvent::WindowNameEvent(WindowNameEvent { .. }) => {
 | |
|                     info!("{:#?}", event);
 | |
|                 }
 | |
|                 WindowEvent::WindowTypeChangedEvent(
 | |
|                     WindowTypeChangedEvent {
 | |
|                         window,
 | |
|                         window_type,
 | |
|                     },
 | |
|                 ) => {
 | |
|                     self.clients.update_window_type(&window, window_type);
 | |
|                 }
 | |
| 
 | |
|                 // i dont think i actually have to handle destroy notify events.
 | |
|                 // every window should be unmapped regardless
 | |
|                 // xlib::DestroyNotify => self.destroy_notify(&event),
 | |
|                 _ => {}
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn quit(&self) -> ! {
 | |
|         // TODO: should the window manager kill all clients on exit? probably
 | |
|         if self.config.kill_clients_on_exit {
 | |
|             self.clients
 | |
|                 .iter_all_clients()
 | |
|                 .for_each(|(&window, _)| self.backend.kill_window(window));
 | |
|         }
 | |
| 
 | |
|         info!("Goodbye.");
 | |
| 
 | |
|         std::process::exit(0);
 | |
|     }
 | |
| 
 | |
|     fn kill_client(&mut self) {
 | |
|         if let Some(client) = self.clients.get_focused().into_option() {
 | |
|             self.backend.kill_window(client.window);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // TODO: change this somehow cuz I'm not a big fan of this "hardcoded" keybind stuff
 | |
|     fn handle_keybinds(&mut self, event: &KeyEvent<B::Window>) {
 | |
|         // I'm not sure if this has to be a Rc<RefCell>> or if it would be better as a Cell<>
 | |
|         let keybinds = self.keybinds.clone();
 | |
| 
 | |
|         for kb in keybinds.borrow().iter() {
 | |
|             if kb.key.key == event.keycode
 | |
|                 && kb.key.modifiers == event.modifierstate
 | |
|             {
 | |
|                 kb.call(self, event);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn handle_switch_stack(&mut self) {
 | |
|         if let Some(client) =
 | |
|             self.clients.get_focused().into_option().map(|c| c.key())
 | |
|         {
 | |
|             info!("Switching stack for window {:?}", client);
 | |
|             self.clients.switch_stack_for_client(&client);
 | |
|         }
 | |
| 
 | |
|         self.arrange_clients();
 | |
|     }
 | |
| 
 | |
|     fn rotate_virtual_screen_back(&mut self) {
 | |
|         self.clients.rotate_back();
 | |
| 
 | |
|         self.arrange_clients();
 | |
|     }
 | |
| 
 | |
|     fn go_to_nth_virtual_screen(&mut self, n: usize) {
 | |
|         self.clients.go_to_nth_virtualscreen(n - 1);
 | |
|         self.arrange_clients();
 | |
|     }
 | |
| 
 | |
|     fn rotate_virtual_screen(&mut self, dir: Direction) {
 | |
|         info!("rotating VS: {:?}", dir);
 | |
| 
 | |
|         match dir {
 | |
|             Direction::West(n) => self.clients.rotate_left(n),
 | |
|             Direction::East(n) => self.clients.rotate_right(n),
 | |
|             _ => {}
 | |
|         }
 | |
| 
 | |
|         self.arrange_clients();
 | |
|     }
 | |
| 
 | |
|     fn focus_any(&mut self) {
 | |
|         // focus first client in all visible clients
 | |
|         let to_focus =
 | |
|             self.clients.iter_visible().next().map(|(k, _)| k).cloned();
 | |
| 
 | |
|         if let Some(key) = to_focus {
 | |
|             self.focus_client(&key, false);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn focus_master_stack(&mut self) {
 | |
|         let focused = self.clients.get_focused().into_option().map(|c| c.key());
 | |
| 
 | |
|         let k = self
 | |
|             .clients
 | |
|             .iter_floating_visible()
 | |
|             .chain(self.clients.iter_master_stack())
 | |
|             .map(|(k, _)| k)
 | |
|             // get the first client on the stack thats not already focused
 | |
|             .filter(|&&k| focused.map(|f| f != k).unwrap_or(true))
 | |
|             .next()
 | |
|             .cloned();
 | |
| 
 | |
|         if let Some(k) = k {
 | |
|             self.focus_client(&k, false);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn focus_aux_stack(&mut self) {
 | |
|         let focused = self.clients.get_focused().into_option().map(|c| c.key());
 | |
| 
 | |
|         let k = self
 | |
|             .clients
 | |
|             .iter_floating_visible()
 | |
|             .chain(self.clients.iter_aux_stack())
 | |
|             .map(|(k, _)| k)
 | |
|             // get the first client on the stack thats not already focused
 | |
|             .filter(|&&k| focused.map(|f| f != k).unwrap_or(true))
 | |
|             .next()
 | |
|             .cloned();
 | |
| 
 | |
|         if let Some(k) = k {
 | |
|             self.focus_client(&k, false);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn focus_up(&mut self) {
 | |
|         let focused = self.clients.get_focused().into_option().map(|c| c.key());
 | |
| 
 | |
|         let k = focused.and_then(|focused| {
 | |
|             self.clients
 | |
|                 .get_stack_for_client(&focused)
 | |
|                 .and_then(|stack| {
 | |
|                     stack
 | |
|                         .iter()
 | |
|                         .rev()
 | |
|                         .skip_while(|&&k| k != focused)
 | |
|                         .skip(1)
 | |
|                         .next()
 | |
|                         .cloned()
 | |
|                 })
 | |
|         });
 | |
| 
 | |
|         if let Some(k) = k {
 | |
|             self.focus_client(&k, false);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn focus_down(&mut self) {
 | |
|         let focused = self.clients.get_focused().into_option().map(|c| c.key());
 | |
| 
 | |
|         let k = focused.and_then(|focused| {
 | |
|             self.clients
 | |
|                 .get_stack_for_client(&focused)
 | |
|                 .and_then(|stack| {
 | |
|                     stack
 | |
|                         .iter()
 | |
|                         .skip_while(|&&k| k != focused)
 | |
|                         .skip(1)
 | |
|                         .next()
 | |
|                         .cloned()
 | |
|                 })
 | |
|         });
 | |
| 
 | |
|         if let Some(k) = k {
 | |
|             self.focus_client(&k, false);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn move_focus(&mut self, dir: Direction) {
 | |
|         match dir {
 | |
|             Direction::East(_) => self.focus_aux_stack(),
 | |
|             Direction::West(_) => self.focus_master_stack(),
 | |
|             Direction::North(_) => self.focus_up(),
 | |
|             Direction::South(_) => self.focus_down(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn hide_hidden_clients(&self) {
 | |
|         self.clients
 | |
|             .iter_hidden()
 | |
|             .for_each(|(_, c)| self.backend.hide_window(c.window));
 | |
|     }
 | |
| 
 | |
|     fn raise_floating_clients(&self) {
 | |
|         self.clients
 | |
|             .iter_floating()
 | |
|             .for_each(|(_, c)| self.backend.raise_window(c.window));
 | |
| 
 | |
|         self.clients
 | |
|             .iter_transient()
 | |
|             .for_each(|(_, c)| self.backend.raise_window(c.window));
 | |
| 
 | |
|         //raise fullscreen windows
 | |
|         self.clients
 | |
|             .iter_current_screen()
 | |
|             .filter(|(_, c)| c.is_fullscreen())
 | |
|             .for_each(|(_, c)| self.backend.raise_window(c.window));
 | |
|     }
 | |
| 
 | |
|     fn arrange_clients(&mut self) {
 | |
|         self.clients.iter_visible().for_each(|(_, c)| {
 | |
|             self.backend.move_window(c.window, c.position);
 | |
|             self.backend.resize_window(c.window, c.size);
 | |
|             //self.xlib.expose_client(c);
 | |
|         });
 | |
| 
 | |
|         self.hide_hidden_clients();
 | |
| 
 | |
|         self.raise_floating_clients();
 | |
| 
 | |
|         // if no visible client is focused, focus any.
 | |
|         if !self
 | |
|             .clients
 | |
|             .iter_visible()
 | |
|             .any(|(k, _)| self.clients.is_focused(k))
 | |
|         {
 | |
|             self.focus_any();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn focus_client<K>(&mut self, key: &K, try_raise: bool)
 | |
|     where
 | |
|         K: ClientKey,
 | |
|     {
 | |
|         let (new, old) = self.clients.focus_client(key);
 | |
| 
 | |
|         if let Some(old) = old.into_option() {
 | |
|             self.backend.unfocus_window(old.window);
 | |
|         }
 | |
| 
 | |
|         match new {
 | |
|             ClientEntry::Floating(new) => {
 | |
|                 self.backend.focus_window(new.window);
 | |
| 
 | |
|                 if try_raise {
 | |
|                     self.backend.raise_window(new.window);
 | |
|                 }
 | |
|             }
 | |
|             ClientEntry::Tiled(new) => {
 | |
|                 self.backend.focus_window(new.window);
 | |
|             }
 | |
|             _ => {}
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn new_client(&mut self, window: Window) {
 | |
|         let client = match self.backend.get_window_type(window) {
 | |
|             WindowType::Normal => Client::new_default(window),
 | |
|             window_type @ _ => Client::new_default(window)
 | |
|                 .with_window_type(window_type)
 | |
|                 .with_size(
 | |
|                     self.backend
 | |
|                         .get_window_size(window)
 | |
|                         .unwrap_or((100, 100).into()),
 | |
|                 )
 | |
|                 .with_parent_window(self.backend.get_parent_window(window)),
 | |
|         };
 | |
| 
 | |
|         self.backend.configure_window(
 | |
|             window,
 | |
|             None,
 | |
|             None,
 | |
|             Some(self.clients.get_border()),
 | |
|         );
 | |
| 
 | |
|         info!("new client: {:#?}", client);
 | |
| 
 | |
|         self.clients.insert(client).unwrap();
 | |
|         self.arrange_clients();
 | |
| 
 | |
|         self.focus_client(&window, true);
 | |
|     }
 | |
| 
 | |
|     /// ensure event.subwindow refers to a valid client.
 | |
|     fn start_move_resize_window(&mut self, event: &ButtonEvent<B::Window>) {
 | |
|         let window = event.window; // xev.subwindow
 | |
| 
 | |
|         if !self.clients.get(&window).is_fullscreen() {
 | |
|             match event.keycode {
 | |
|                 MouseButton::Left => {
 | |
|                     if self.clients.set_floating(&window) {
 | |
|                         self.arrange_clients();
 | |
|                     }
 | |
| 
 | |
|                     self.move_resize_window =
 | |
|                         MoveResizeInfo::Move(MoveInfoInner {
 | |
|                             window,
 | |
|                             starting_cursor_pos: event.cursor_position,
 | |
|                             starting_window_pos: self
 | |
|                                 .clients
 | |
|                                 .get(&window)
 | |
|                                 .unwrap()
 | |
|                                 .position,
 | |
|                         });
 | |
|                 }
 | |
|                 MouseButton::Right => {
 | |
|                     if self.clients.set_floating(&window) {
 | |
|                         self.arrange_clients();
 | |
|                     }
 | |
| 
 | |
|                     let client = self.clients.get(&window).unwrap();
 | |
| 
 | |
|                     let corner_pos = client.position + client.size.into();
 | |
| 
 | |
|                     self.backend.move_cursor(None, corner_pos.into());
 | |
|                     self.backend.grab_cursor();
 | |
| 
 | |
|                     self.move_resize_window =
 | |
|                         MoveResizeInfo::Resize(ResizeInfoInner {
 | |
|                             window,
 | |
|                             starting_cursor_pos: corner_pos.into(),
 | |
|                             starting_window_size: client.size,
 | |
|                         });
 | |
|                 }
 | |
|                 _ => {}
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn end_move_resize_window(&mut self, event: &ButtonEvent<B::Window>) {
 | |
|         match event.keycode {
 | |
|             MouseButton::Left => {
 | |
|                 self.move_resize_window = MoveResizeInfo::None;
 | |
|             }
 | |
|             MouseButton::Right => {
 | |
|                 self.move_resize_window = MoveResizeInfo::None;
 | |
|                 self.backend.ungrab_cursor();
 | |
|             }
 | |
|             _ => {}
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn do_move_resize_window(&mut self, event: &MotionEvent<B::Window>) {
 | |
|         match &self.move_resize_window {
 | |
|             MoveResizeInfo::Move(info) => {
 | |
|                 let (x, y) = (
 | |
|                     event.position.x - info.starting_cursor_pos.x,
 | |
|                     event.position.y - info.starting_cursor_pos.y,
 | |
|                 );
 | |
| 
 | |
|                 if let Some(client) =
 | |
|                     self.clients.get_mut(&info.window).into_option()
 | |
|                 {
 | |
|                     let position = &mut client.position;
 | |
| 
 | |
|                     position.x = info.starting_window_pos.x + x;
 | |
|                     position.y = info.starting_window_pos.y + y;
 | |
| 
 | |
|                     self.backend.move_window(client.window, client.position);
 | |
|                 }
 | |
|             }
 | |
|             MoveResizeInfo::Resize(info) => {
 | |
|                 let (x, y) = (
 | |
|                     event.position.x - info.starting_cursor_pos.x,
 | |
|                     event.position.y - info.starting_cursor_pos.y,
 | |
|                 );
 | |
| 
 | |
|                 if let Some(client) =
 | |
|                     self.clients.get_mut(&info.window).into_option()
 | |
|                 {
 | |
|                     let size = &mut client.size;
 | |
| 
 | |
|                     size.width =
 | |
|                         std::cmp::max(1, info.starting_window_size.width + x);
 | |
|                     size.height =
 | |
|                         std::cmp::max(1, info.starting_window_size.height + y);
 | |
| 
 | |
|                     self.backend.resize_window(client.window, client.size);
 | |
|                 }
 | |
|             }
 | |
|             _ => {}
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn button_event(&mut self, event: &ButtonEvent<B::Window>) {
 | |
|         match event.state {
 | |
|             KeyState::Pressed => {
 | |
|                 self.focus_client(&event.window, true);
 | |
| 
 | |
|                 match event.keycode {
 | |
|                     MouseButton::Left | MouseButton::Right => {
 | |
|                         match self.move_resize_window {
 | |
|                             MoveResizeInfo::None
 | |
|                                 if ModifierState::from([self
 | |
|                                     .config
 | |
|                                     .mod_key])
 | |
|                                 .eq(&event.modifierstate)
 | |
|                                     && self.clients.contains(&event.window) =>
 | |
|                             {
 | |
|                                 self.start_move_resize_window(event)
 | |
|                             }
 | |
|                             _ => {}
 | |
|                         }
 | |
|                     }
 | |
|                     MouseButton::Middle => {
 | |
|                         self.clients.toggle_floating(&event.window);
 | |
|                         self.arrange_clients();
 | |
|                     }
 | |
|                     _ => {}
 | |
|                 }
 | |
|             }
 | |
|             KeyState::Released => match self.move_resize_window {
 | |
|                 MoveResizeInfo::None => {}
 | |
|                 _ => {
 | |
|                     self.end_move_resize_window(event);
 | |
|                 }
 | |
|             },
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn spawn<'a, S, I>(&self, command: S, args: I)
 | |
|     where
 | |
|         S: AsRef<str> + AsRef<std::ffi::OsStr>,
 | |
|         I: IntoIterator<Item = S> + std::fmt::Debug,
 | |
|     {
 | |
|         info!("spawn: {:?} {:?}", AsRef::<str>::as_ref(&command), args);
 | |
|         match std::process::Command::new(AsRef::<std::ffi::OsStr>::as_ref(
 | |
|             &command,
 | |
|         ))
 | |
|         .args(args)
 | |
|         .spawn()
 | |
|         {
 | |
|             Ok(_) => {}
 | |
|             Err(err) => {
 | |
|                 error!(
 | |
|                     "Failed to spawn {:?}: {:?}",
 | |
|                     AsRef::<str>::as_ref(&command),
 | |
|                     err
 | |
|                 );
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Direction {
 | |
|     fn west() -> Self {
 | |
|         Direction::West(1)
 | |
|     }
 | |
| 
 | |
|     fn east() -> Self {
 | |
|         Direction::East(1)
 | |
|     }
 | |
| 
 | |
|     fn north() -> Self {
 | |
|         Direction::North(1)
 | |
|     }
 | |
| 
 | |
|     fn south() -> Self {
 | |
|         Direction::South(1)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl std::ops::Not for Direction {
 | |
|     type Output = Self;
 | |
| 
 | |
|     fn not(self) -> Self::Output {
 | |
|         match self {
 | |
|             Direction::West(n) => Direction::East(n),
 | |
|             Direction::East(n) => Direction::West(n),
 | |
|             Direction::North(n) => Direction::North(n),
 | |
|             Direction::South(n) => Direction::South(n),
 | |
|         }
 | |
|     }
 | |
| }
 |