nirgendwm/src/state.rs

910 lines
27 KiB
Rust

use std::{cell::RefCell, rc::Rc};
use log::{error, info};
use x11::xlib::{self, Window, XEvent};
use xlib::{
XConfigureRequestEvent, XCrossingEvent, XDestroyWindowEvent,
XMapRequestEvent, XUnmapEvent,
};
use crate::{
backends::{
keycodes::{MouseButton, VirtualKeyCode},
window_event::{
ButtonEvent, ConfigureEvent, KeyBind, KeyEvent, KeyState, MapEvent,
ModifierKey, ModifierState, MotionEvent, MouseBind, Point,
WindowEvent,
},
xlib::XLib,
WindowServerBackend,
},
clients::{Client, ClientEntry, ClientKey, ClientState},
};
/**
Contains static config data for the window manager, the sort of stuff you might want to
be able to configure in a config file.
*/
pub struct WMConfig {
num_virtualscreens: usize,
mod_key: ModifierKey,
gap: Option<i32>,
kill_clients_on_exit: bool,
}
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: Point<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(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("alacritty", &[]),
));
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();
// add all already existing windows to the WM
if let Some(windows) = self.backend.all_windows() {
windows
.into_iter()
.for_each(|window| self.new_client(window));
}
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);
}
self.backend.handle_event(event);
}
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, ..
}) => {
if !self.clients.contains(&window) {
self.backend.handle_event(event);
}
// TODO
// match self.clients.get(&event.window).into_option() {
// Some(client) => self
// .xlib
// .configure_client(client, self.clients.get_border()),
// None => self.xlib.configure_window(event),
// }
}
// 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, client)| 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!("rotateing 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));
}
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) {
info!("new client: {:?}", window);
let client = if let Some(transient_window) =
self.backend.get_parent_window(window)
{
Client::new_transient(
window,
self.backend
.get_window_size(window)
.unwrap_or((100, 100).into()),
transient_window,
)
} else {
Client::new_default(window)
};
self.backend.configure_window(
window,
None,
None,
Some(self.clients.get_border()),
);
self.clients.insert(client).unwrap();
self.arrange_clients();
self.focus_client(&window, true);
}
fn map_request(&mut self, event: &XEvent) {
let event: &XMapRequestEvent = event.as_ref();
if !self.clients.contains(&event.window) {
self.new_client(event.window);
}
}
fn unmap_notify(&mut self, event: &XEvent) {
let event: &XUnmapEvent = event.as_ref();
self.clients.remove(&event.window);
self.arrange_clients();
}
fn destroy_notify(&mut self, event: &XEvent) {
let event: &XDestroyWindowEvent = event.as_ref();
self.clients.remove(&event.window);
self.arrange_clients();
}
fn configure_request(&mut self, event: &XEvent) {
let event: &XConfigureRequestEvent = event.as_ref();
// TODO
// match self.clients.get(&event.window).into_option() {
// Some(client) => self
// .xlib
// .configure_client(client, self.clients.get_border()),
// None => self.xlib.configure_window(event),
// }
}
fn enter_notify(&mut self, event: &XEvent) {
let event: &XCrossingEvent = event.as_ref();
self.focus_client(&event.window, false);
}
/// 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
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.x + client.size.x,
client.position.y + client.size.y,
)
};
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) => {
info!("do_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) => {
info!("do_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.x = std::cmp::max(1, info.starting_window_size.x + x);
size.y = std::cmp::max(1, info.starting_window_size.y + 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(&self, command: &str, args: &[&str]) {
info!("spawn: {:?} {:?}", command, args.join(" "));
match std::process::Command::new(command).args(args).spawn() {
Ok(_) => {}
Err(err) => {
error!("Failed to spawn {:?}: {:?}", command, err);
}
}
}
}
impl Default for WMConfig {
fn default() -> Self {
Self {
num_virtualscreens: 10,
mod_key: ModifierKey::Super,
gap: Some(2),
kill_clients_on_exit: false,
}
}
}
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),
}
}
}