diff --git a/Cargo.toml b/Cargo.toml index 30b0998..c7534c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,4 @@ bitflags = "1.3.2" derivative = "2.2.0" serde = { version = "1.0", features = ["derive"] } toml = "0.5" +num-traits = "0.2.14" diff --git a/src/backends/traits.rs b/src/backends/traits.rs index 603a3b8..1698c69 100644 --- a/src/backends/traits.rs +++ b/src/backends/traits.rs @@ -1,7 +1,5 @@ -use super::{ - window_event, - window_event::{KeyOrMouseBind, Point}, -}; +use super::window_event::{self, KeyOrMouseBind}; +use crate::util::{Point, Size}; pub trait WindowServerBackend { type Window; @@ -26,13 +24,13 @@ pub trait WindowServerBackend { fn configure_window( &self, window: Self::Window, - new_size: Option>, + new_size: Option>, new_pos: Option>, new_border: Option, ); - fn screen_size(&self) -> Point; - fn get_window_size(&self, window: Self::Window) -> Option>; + fn screen_size(&self) -> Size; + fn get_window_size(&self, window: Self::Window) -> Option>; fn grab_cursor(&self); fn ungrab_cursor(&self); @@ -43,7 +41,7 @@ pub trait WindowServerBackend { fn set_active_window_border_color(&mut self, color_name: &str); fn set_inactive_window_border_color(&mut self, color_name: &str); - fn resize_window(&self, window: Self::Window, new_size: Point) { + fn resize_window(&self, window: Self::Window, new_size: Size) { self.configure_window(window, Some(new_size), None, None); } diff --git a/src/backends/window_event.rs b/src/backends/window_event.rs index c99beba..44ddb1e 100644 --- a/src/backends/window_event.rs +++ b/src/backends/window_event.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] use super::keycodes::{KeyOrButton, MouseButton, VirtualKeyCode}; +use crate::util::{Point, Size}; use bitflags::bitflags; #[derive(Debug)] @@ -129,43 +130,6 @@ impl KeyEvent { } } -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] -pub struct Point -where - I: Copy + Clone + PartialEq + PartialOrd, -{ - pub x: I, - pub y: I, -} -impl From<(I, I)> for Point -where - I: Copy + Clone + PartialEq + PartialOrd, -{ - fn from(value: (I, I)) -> Self { - Self::from_tuple(value) - } -} - -impl Point -where - I: Copy + Clone + PartialEq + PartialOrd, -{ - pub fn new(x: I, y: I) -> Self { - Self { x, y } - } - - pub fn from_tuple(tuple: (I, I)) -> Self { - Self { - x: tuple.0, - y: tuple.1, - } - } - - pub fn as_tuple(&self) -> (I, I) { - (self.x, self.y) - } -} - #[derive(Debug)] pub struct ButtonEvent { pub window: Window, @@ -235,11 +199,11 @@ impl DestroyEvent { pub struct CreateEvent { pub window: Window, pub position: Point, - pub size: Point, + pub size: Size, } impl CreateEvent { - pub fn new(window: Window, position: Point, size: Point) -> Self { + pub fn new(window: Window, position: Point, size: Size) -> Self { Self { window, position, @@ -252,11 +216,11 @@ impl CreateEvent { pub struct ConfigureEvent { pub window: Window, pub position: Point, - pub size: Point, + pub size: Size, } impl ConfigureEvent { - pub fn new(window: Window, position: Point, size: Point) -> Self { + pub fn new(window: Window, position: Point, size: Size) -> Self { Self { window, position, @@ -265,18 +229,31 @@ impl ConfigureEvent { } } +#[derive(Debug)] +pub enum FullscreenState { + On, + Off, + Toggle, +} + +impl From for FullscreenState { + fn from(value: bool) -> Self { + match value { + true => Self::On, + false => Self::Off, + } + } +} + #[derive(Debug)] pub struct FullscreenEvent { - window: Window, - new_fullscreen: bool, + pub window: Window, + pub state: FullscreenState, } impl FullscreenEvent { - pub fn new(window: Window, new_fullscreen: bool) -> Self { - Self { - window, - new_fullscreen, - } + pub fn new(window: Window, state: FullscreenState) -> Self { + Self { window, state } } } diff --git a/src/backends/xlib/mod.rs b/src/backends/xlib/mod.rs index 87d4f85..4d222d1 100644 --- a/src/backends/xlib/mod.rs +++ b/src/backends/xlib/mod.rs @@ -1,5 +1,6 @@ use log::{error, warn}; -use std::{ffi::CString, rc::Rc}; +use num_traits::Zero; +use std::{ffi::CString, mem::MaybeUninit, rc::Rc}; use thiserror::Error; @@ -17,12 +18,13 @@ use self::keysym::{ use super::{ keycodes::VirtualKeyCode, window_event::{ - ButtonEvent, ConfigureEvent, DestroyEvent, EnterEvent, KeyEvent, - KeyOrMouseBind, KeyState, MapEvent, ModifierState, MotionEvent, Point, - UnmapEvent, WindowEvent, + ButtonEvent, ConfigureEvent, DestroyEvent, EnterEvent, FullscreenEvent, + FullscreenState, KeyEvent, KeyOrMouseBind, KeyState, MapEvent, + ModifierState, MotionEvent, UnmapEvent, WindowEvent, }, WindowServerBackend, }; +use crate::util::{Point, Size}; pub mod color; pub mod keysym; @@ -285,6 +287,53 @@ impl XLib { )) }) } + xlib::PropertyNotify => { + let ev = unsafe { &event.property }; + + (ev.atom == self.atoms.net_wm_window_type) + .then(|| { + (self.get_atom_property( + ev.window, + self.atoms.net_wm_state, + ) == Some(self.atoms.net_wm_state_fullscreen)) + .then(|| { + XLibWindowEvent::FullscreenEvent( + FullscreenEvent::new( + ev.window, + FullscreenState::On, + ), + ) + }) + }) + .flatten() + } + xlib::ClientMessage => { + let ev = unsafe { &event.client_message }; + + (ev.message_type == self.atoms.net_wm_state) + .then(|| { + let data = ev.data.as_longs(); + (data[1] as u64 == self.atoms.net_wm_state_fullscreen + || data[2] as u64 + == self.atoms.net_wm_state_fullscreen) + .then(|| { + XLibWindowEvent::FullscreenEvent( + FullscreenEvent::new( + ev.window, + match data[0] /* as u64 */ { + 0 => FullscreenState::Off, + 1 => FullscreenState::On, + 2 => FullscreenState::Toggle, + _ => { + unreachable!() + } + }, + ), + ) + }) + }) + .flatten() + } _ => None, } } @@ -308,6 +357,37 @@ impl XLib { } } + fn get_atom_property( + &self, + window: xlib::Window, + atom: xlib::Atom, + ) -> Option { + let mut di = 0; + let mut dl0 = 0; + let mut dl1 = 0; + let mut da = 0; + + let mut atom_out = MaybeUninit::::zeroed(); + + unsafe { + (xlib::XGetWindowProperty( + self.dpy(), + window, + atom, + 0, + std::mem::size_of::() as i64, + 0, + xlib::XA_ATOM, + &mut da, + &mut di, + &mut dl0, + &mut dl1, + atom_out.as_mut_ptr() as *mut _, + ) != 0) + .then(|| atom_out.assume_init()) + } + } + fn check_for_protocol( &self, window: xlib::Window, @@ -726,8 +806,8 @@ impl WindowServerBackend for XLib { } fn hide_window(&self, window: Self::Window) { - let screen_size = self.screen_size(); - self.move_window(window, screen_size); + let screen_size = self.screen_size() + Size::new(100, 100); + self.move_window(window, screen_size.into()); } fn kill_window(&self, window: Self::Window) { @@ -753,17 +833,17 @@ impl WindowServerBackend for XLib { fn configure_window( &self, window: Self::Window, - new_size: Option>, - new_pos: Option>, + new_size: Option>, + new_pos: Option>, new_border: Option, ) { - let position = new_pos.unwrap_or(Point::new(0, 0)); - let size = new_size.unwrap_or(Point::new(0, 0)); + let position = new_pos.unwrap_or(Point::zero()); + let size = new_size.unwrap_or(Size::zero()); let mut wc = xlib::XWindowChanges { x: position.x, y: position.y, - width: size.x, - height: size.y, + width: size.width, + height: size.height, border_width: new_border.unwrap_or(0), sibling: 0, stack_mode: 0, @@ -789,7 +869,7 @@ impl WindowServerBackend for XLib { } } - fn screen_size(&self) -> Point { + fn screen_size(&self) -> Size { unsafe { let mut wa = std::mem::MaybeUninit::::zeroed(); @@ -802,7 +882,7 @@ impl WindowServerBackend for XLib { } } - fn get_window_size(&self, window: Self::Window) -> Option> { + fn get_window_size(&self, window: Self::Window) -> Option> { self.get_window_attributes(window) .map(|wa| (wa.width, wa.height).into()) } diff --git a/src/clients.rs b/src/clients.rs index e641d8a..6676d57 100644 --- a/src/clients.rs +++ b/src/clients.rs @@ -1,25 +1,24 @@ -use std::num::NonZeroI32; use std::{ops::Rem, usize}; use indexmap::IndexMap; -use log::{error, info}; +use log::error; -use crate::backends::window_event::Point; use crate::util::BuildIdentityHasher; +use crate::util::{Point, Size}; mod client { use std::hash::{Hash, Hasher}; + use crate::util::{Point, Size}; use x11::xlib::Window; - use crate::backends::window_event::Point; - #[derive(Clone, Debug)] pub struct Client { pub(crate) window: Window, - pub(crate) size: Point, + pub(crate) size: Size, pub(crate) position: Point, pub(crate) transient_for: Option, + pub(crate) fullscreen: bool, } impl Default for Client { @@ -29,6 +28,7 @@ mod client { size: (100, 100).into(), position: (0, 0).into(), transient_for: None, + fullscreen: false, } } } @@ -37,20 +37,20 @@ mod client { #[allow(dead_code)] pub fn new( window: Window, - size: Point, + size: Size, position: Point, ) -> Self { Self { window, size, position, - transient_for: None, + ..Self::default() } } pub fn new_transient( window: Window, - size: Point, + size: Size, transient: Window, ) -> Self { Self { @@ -68,6 +68,27 @@ mod client { } } + /// toggles the clients fullscreen flag. + /// returns `true` if the client is now fullscreen. + pub fn toggle_fullscreen(&mut self) -> bool { + self.fullscreen = !self.fullscreen; + + self.is_fullscreen() + } + + pub fn set_fullscreen(&mut self, fullscreen: bool) -> bool { + if self.fullscreen == fullscreen { + false + } else { + self.fullscreen = fullscreen; + true + } + } + + pub fn is_fullscreen(&self) -> bool { + self.fullscreen + } + pub fn is_transient(&self) -> bool { self.transient_for.is_some() } @@ -143,7 +164,7 @@ pub struct ClientState { pub(self) virtual_screens: VirtualScreenStore, pub(self) gap: i32, - pub(self) screen_size: Point, + pub(self) screen_size: Size, pub(self) master_size: f32, border_size: i32, } @@ -192,7 +213,7 @@ impl ClientState { } } - pub fn with_screen_size(self, screen_size: Point) -> Self { + pub fn with_screen_size(self, screen_size: Size) -> Self { Self { screen_size, ..self @@ -226,9 +247,9 @@ impl ClientState { client.position = { ( transient.position.x - + (transient.size.x - client.size.x) / 2, + + (transient.size.width - client.size.width) / 2, transient.position.y - + (transient.size.y - client.size.y) / 2, + + (transient.size.height - client.size.height) / 2, ) .into() }; @@ -408,6 +429,43 @@ impl ClientState { self.arrange_virtual_screen(); } + pub fn set_fullscreen(&mut self, key: &K, fullscreen: bool) -> bool + where + K: ClientKey, + { + self.get(key) + .into_option() + .map(|client| client.is_fullscreen() != fullscreen) + .unwrap_or(false) + .then(|| self.toggle_fullscreen(key)) + .unwrap_or(false) + } + + pub fn toggle_fullscreen(&mut self, key: &K) -> bool + where + K: ClientKey, + { + if self.inner_toggle_fullscreen(key) { + self.arrange_virtual_screen(); + true + } else { + false + } + } + + fn inner_toggle_fullscreen(&mut self, key: &K) -> bool + where + K: ClientKey, + { + self.get_mut(key) + .into_option() + .map(|client| { + client.toggle_fullscreen(); + true + }) + .unwrap_or(false) + } + /** Sets a tiled client to floating and returns true, does nothing for a floating client and returns false. If this function returns `true` you have to call `arrange_clients` after. @@ -538,7 +596,7 @@ impl ClientState { let (new, old) = self.focus_client_inner(key); if !(new.is_vacant() && old.is_vacant()) { - info!("Swapping focus: new({:?}) old({:?})", new, old); + //info!("Swapping focus: new({:?}) old({:?})", new, old); } (new, old) @@ -635,49 +693,87 @@ impl ClientState { let vs = self.virtual_screens.get_mut_current(); // if aux is empty -> width : width / 2 - let (master_width, aux_width) = { - let effective_width = width - gap * 2; + let vs_width = width - gap * 2; - let master_size = if vs.aux.is_empty() { + let master_position = Point::new(0, 0); + let master_window_size = { + let factor = if vs.aux.is_empty() { 1.0 } else { self.master_size / 2.0 }; - let master_width = (effective_width as f32 * master_size) as i32; - let aux_width = effective_width - master_width; + let width = (vs_width as f32 * factor) as i32; - (master_width, aux_width) + // make sure we dont devide by 0 + // height is max height / number of clients in the stack + let height = match vs.master.len() as i32 { + 0 => 1, + n => (height - gap * 2) / n, + }; + + Size::new(width, height) }; - // make sure we dont devide by 0 - // height is max height / number of clients in the stack - let master_height = (height - gap * 2) - / match NonZeroI32::new(vs.master.len() as i32) { - Some(i) => i.get(), - None => 1, + let aux_position = Point::new(master_window_size.width, 0); + let aux_window_size = { + let width = vs_width - master_window_size.width; + + // make sure we dont devide by 0 + // height is max height / number of clients in the stack + let height = match vs.aux.len() as i32 { + 0 => 1, + n => (height - gap * 2) / n, }; - // height is max height / number of clients in the stack - let aux_height = (height - gap * 2) - / match NonZeroI32::new(vs.aux.len() as i32) { - Some(i) => i.get(), - None => 1, - }; + Size::new(width, height) + }; + + fn calculate_window_dimensions( + screen_size: Size, + stack_size: Size, + stack_position: Point, + fullscreen: bool, + nth: i32, + gap: i32, + border: i32, + ) -> (Size, Point) { + if fullscreen { + let size = Size::new( + screen_size.width - border * 2, + screen_size.height - border * 2, + ); + let pos = Point::new(0, 0); + (size, pos) + } else { + let size = Size::new( + stack_size.width - gap * 2 - border * 2, + stack_size.height - gap * 2 - border * 2, + ); + let pos = Point::new( + stack_position.x + gap * 2, + stack_position.y + stack_size.height * nth + gap * 2, + ); + (size, pos) + } + } // Master for (i, key) in vs.master.iter().enumerate() { - let size = ( - master_width - gap * 2 - self.border_size * 2, - master_height - gap * 2 - self.border_size * 2, - ); - - let position = (gap * 2, master_height * i as i32 + gap * 2); - if let Some(client) = self.clients.get_mut(key) { + let (size, position) = calculate_window_dimensions( + self.screen_size.into(), + master_window_size, + master_position, + client.is_fullscreen(), + i as i32, + gap, + self.border_size, + ); + *client = Client { size: size.into(), - position: position.into(), + position, ..*client }; } @@ -685,18 +781,20 @@ impl ClientState { // Aux for (i, key) in vs.aux.iter().enumerate() { - let size = ( - aux_width - gap * 2 - self.border_size * 2, - aux_height - gap * 2 - self.border_size * 2, - ); - - let position = - (master_width + gap * 2, aux_height * i as i32 + gap * 2); - if let Some(client) = self.clients.get_mut(key) { + let (size, position) = calculate_window_dimensions( + self.screen_size.into(), + aux_window_size, + aux_position, + client.is_fullscreen(), + i as i32, + gap, + self.border_size, + ); + *client = Client { size: size.into(), - position: position.into(), + position, ..*client }; } diff --git a/src/state.rs b/src/state.rs index 6e545f3..4eadd27 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,13 +4,14 @@ use log::{error, info}; use x11::xlib::{self, Window}; +use crate::backends::window_event::{FullscreenEvent, FullscreenState}; +use crate::util::{Point, Size}; use crate::{ backends::{ keycodes::{MouseButton, VirtualKeyCode}, window_event::{ ButtonEvent, ConfigureEvent, KeyBind, KeyEvent, KeyState, MapEvent, - ModifierKey, ModifierState, MotionEvent, MouseBind, Point, - WindowEvent, + ModifierKey, ModifierState, MotionEvent, MouseBind, WindowEvent, }, xlib::XLib, WindowServerBackend, @@ -105,7 +106,7 @@ struct MoveInfoInner { struct ResizeInfoInner { window: Window, starting_cursor_pos: Point, - starting_window_size: Point, + starting_window_size: Size, } use derivative::*; @@ -464,6 +465,24 @@ where // None => self.xlib.configure_window(event), // } } + 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) + } + } { + self.arrange_clients(); + } + } // i dont think i actually have to handle destroy notify events. // every window should be unmapped regardless @@ -652,6 +671,12 @@ where 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) { @@ -756,12 +781,7 @@ where let client = self.clients.get(&window).unwrap(); - let corner_pos = { - ( - client.position.x + client.size.x, - client.position.y + client.size.y, - ) - }; + let corner_pos = client.position + client.size.into(); self.backend.move_cursor(None, corner_pos.into()); self.backend.grab_cursor(); @@ -820,8 +840,10 @@ where { 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); + 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); } diff --git a/src/util.rs b/src/util.rs index 4389ed7..bfc5fc2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -22,3 +22,218 @@ impl Hasher for IdentityHasher { } pub type BuildIdentityHasher = BuildHasherDefault; + +pub use point::Point; +pub use size::Size; + +mod size { + #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] + pub struct Size + where + I: num_traits::PrimInt + num_traits::Zero, + { + pub width: I, + pub height: I, + } + + impl std::ops::Add for Size + where + I: num_traits::PrimInt + num_traits::Zero, + { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + width: self.width + rhs.width, + height: self.height + rhs.height, + } + } + } + + impl std::ops::Sub for Size + where + I: num_traits::PrimInt + num_traits::Zero, + { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + width: self.width - rhs.width, + height: self.height - rhs.height, + } + } + } + + impl num_traits::Zero for Size + where + I: num_traits::PrimInt + num_traits::Zero, + { + fn zero() -> Self { + Self::default() + } + + fn is_zero(&self) -> bool { + self.width == I::zero() && self.height == I::zero() + } + } + + impl Default for Size + where + I: num_traits::PrimInt + num_traits::Zero, + { + fn default() -> Self { + Self { + width: I::zero(), + height: I::zero(), + } + } + } + + impl From<(I, I)> for Size + where + I: num_traits::PrimInt + num_traits::Zero, + { + fn from(value: (I, I)) -> Self { + Self::from_tuple(value) + } + } + + impl From> for Size + where + I: num_traits::PrimInt + num_traits::Zero, + { + fn from(value: super::point::Point) -> Self { + Self::new(value.x, value.y) + } + } + + impl Size + where + I: num_traits::PrimInt + num_traits::Zero, + { + pub fn new(width: I, height: I) -> Self { + Self { width, height } + } + + pub fn from_tuple(tuple: (I, I)) -> Self { + Self::new(tuple.0, tuple.1) + } + + pub fn as_tuple(&self) -> (I, I) { + (self.width, self.height) + } + + pub fn map(self, f: F) -> Self + where + F: FnOnce(I, I) -> Self, + { + f(self.width, self.height) + } + } +} + +mod point { + #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] + pub struct Point + where + I: num_traits::PrimInt + num_traits::Zero, + { + pub x: I, + pub y: I, + } + + impl std::ops::Add for Point + where + I: num_traits::PrimInt + num_traits::Zero, + { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } + } + + impl std::ops::Sub for Point + where + I: num_traits::PrimInt + num_traits::Zero, + { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } + } + + impl num_traits::Zero for Point + where + I: num_traits::PrimInt + num_traits::Zero, + { + fn zero() -> Self { + Self::default() + } + + fn is_zero(&self) -> bool { + self.x == I::zero() && self.y == I::zero() + } + } + + impl Default for Point + where + I: num_traits::PrimInt + num_traits::Zero, + { + fn default() -> Self { + Self { + x: I::zero(), + y: I::zero(), + } + } + } + + impl From<(I, I)> for Point + where + I: num_traits::PrimInt + num_traits::Zero, + { + fn from(value: (I, I)) -> Self { + Self::from_tuple(value) + } + } + + impl From> for Point + where + I: num_traits::PrimInt + num_traits::Zero, + { + fn from(value: super::size::Size) -> Self { + Self::new(value.width, value.height) + } + } + + impl Point + where + I: num_traits::PrimInt + num_traits::Zero, + { + pub fn new(x: I, y: I) -> Self { + Self { x, y } + } + + pub fn from_tuple(tuple: (I, I)) -> Self { + Self::new(tuple.0, tuple.1) + } + + pub fn as_tuple(&self) -> (I, I) { + (self.x, self.y) + } + + pub fn map(self, f: F) -> T + where + F: FnOnce(I, I) -> T, + { + f(self.x, self.y) + } + } +}