From ae678c4d310fecfd42b2a5e1d4225a06999c84c7 Mon Sep 17 00:00:00 2001 From: Antikyth <104020300+Antikyth@users.noreply.github.com> Date: Tue, 21 Nov 2023 23:19:54 +1300 Subject: [PATCH] layouts: add support for window gaps --- src/display_server/wayland/state.rs | 4 +- src/display_server/x11.rs | 78 ++++++++++++++++++++++ src/layout.rs | 19 ++++++ src/layout/implementations/node_changes.rs | 41 ++++++++---- src/state.rs | 42 ++++++++---- 5 files changed, 157 insertions(+), 27 deletions(-) diff --git a/src/display_server/wayland/state.rs b/src/display_server/wayland/state.rs index 1829b12b..3f790a12 100644 --- a/src/display_server/wayland/state.rs +++ b/src/display_server/wayland/state.rs @@ -49,7 +49,7 @@ use smithay::{ }; use super::grabs::{move_grab::MoveSurfaceGrab, resize_grab::ResizeSurfaceGrab}; -use crate::state::MapState; +use crate::{layout::LayoutSettings, state::MapState}; type Point = smithay::utils::Point; @@ -319,7 +319,7 @@ impl WaylandState { space: Space::default(), loop_signal: event_loop.get_signal(), - aquariwm_state: crate::state::AquariWm::new(), + aquariwm_state: crate::state::AquariWm::new(LayoutSettings::default()), // A whole bunch of Smithay-related state. compositor_state: CompositorState::new::(&display_handle), diff --git a/src/display_server/x11.rs b/src/display_server/x11.rs index dbed64d3..c15cae49 100644 --- a/src/display_server/x11.rs +++ b/src/display_server/x11.rs @@ -17,7 +17,10 @@ use x11rb_async::{ ConnectionExt, CreateNotifyEvent as CreateNotify, DestroyNotifyEvent as DestroyNotify, + EnterNotifyEvent as EnterNotify, EventMask, + InputFocus, + KeyPressEvent as KeyPress, MapRequestEvent as MapRequest, UnmapNotifyEvent as UnmapNotify, }, @@ -29,6 +32,7 @@ use x11rb_async::{ use crate::{ display_server::{AsyncDisplayServer, DisplayServer}, layout, + layout::LayoutSettings, state, }; @@ -124,12 +128,46 @@ impl DisplayServer for X11 { }, } + const ENTER: u8 = 0x0d; + let exit_window_grab = async { + wm.conn + .grab_key( + false, + root, + x11::ModMask::M4 | x11::ModMask::SHIFT, + b'I', + x11::GrabMode::ASYNC, + x11::GrabMode::ASYNC, + ) + .await? + .ignore_error(); + + >::Ok(()) + }; + let spawn_terminal_grab = async { + wm.conn + .grab_key( + false, + root, + x11::ModMask::M4 | x11::ModMask::SHIFT, + ENTER, + x11::GrabMode::ASYNC, + x11::GrabMode::ASYNC, + ) + .await? + .ignore_error(); + + Ok(()) + }; + try_join!(exit_window_grab, spawn_terminal_grab)?; + let mut state = state::AquariWm::with_tiling_layout_and_windows::>( 0, 0, width as u32, height as u32, wm.query_windows().await?, + LayoutSettings::default(), ); if testing { @@ -204,6 +242,46 @@ impl DisplayServer for X11 { wm.circulate_window(&state, request.window, request.place).await?; }, + // Focus a window when the cursor enters it. + // TODO: move floating windows above (avoid flickering bug). + // TODO: implement focus behavior setting + Event::EnterNotify(EnterNotify { event, .. }) => { + const CURRENT_TIME: u32 = 0; + + wm.conn + .set_input_focus(InputFocus::PARENT, event, CURRENT_TIME) + .await? + .ignore_error(); + }, + + Event::KeyPress(KeyPress { + event, state, detail, .. + }) => { + event!( + Level::INFO, + "Key pressed, {event}, {state:?}, {detail}", + event = event, + state = state, + detail = detail, + ); + + if state == x11::KeyButMask::MOD4 | x11::KeyButMask::SHIFT { + match detail { + ENTER => { + if let Err(error) = crate::launch_terminal() { + event!(Level::WARN, "Failed to launch terminal: {error}"); + } + }, + + b'I' => { + wm.conn.destroy_window(event).await?.ignore_error(); + }, + + _ => (), + } + } + }, + _ => (), } } diff --git a/src/layout.rs b/src/layout.rs index 1e221a77..1aee614a 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -4,6 +4,8 @@ use std::{collections::VecDeque, fmt::Debug}; +use derive_extras::builder; + /// Contains `impl` blocks for types defined in [layout]. /// /// This is a separate module to keep the [layout] module file more readable. @@ -16,6 +18,23 @@ mod implementations; /// [layout managers]: TilingLayoutManager pub mod managers; +// This is a false positive: `derive_extras::Default` is not the same as `Default`. +#[allow(unused_qualifications)] +/// Controls settings used when [applying] a [tiling layout]. +/// +/// [applying]: GroupNode::apply_changes +/// [tiling layout]: TilingLayout +#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_extras::Default, builder)] +#[new] +pub struct LayoutSettings { + /// The gap between [nodes] in the [tiling layout]. + /// + /// [nodes]: Node + /// [tiling layout]: TilingLayout + #[default = 10] + pub window_gap: u32, +} + /// Whether a window is [`Tiled`] or [`Floating`]. /// /// [`Tiled`]: Mode::Tiled diff --git a/src/layout/implementations/node_changes.rs b/src/layout/implementations/node_changes.rs index 16bb2065..65565683 100644 --- a/src/layout/implementations/node_changes.rs +++ b/src/layout/implementations/node_changes.rs @@ -525,12 +525,14 @@ impl GroupNode { pub(crate) fn apply_changes( &mut self, reconfigure_window: &mut impl FnMut(&Window, i32, i32, u32, u32) -> Result<(), Error>, + settings: &LayoutSettings, ) -> Result<(), Error> { + // FIXME: need to also determine whether changes have been made to the layout settings. // If no changes have been made to this group, apply all the child groups' changes and return. if !self.changes_made() { for node in self { match node { - Node::Group(group) => group.apply_changes(reconfigure_window)?, + Node::Group(group) => group.apply_changes(reconfigure_window, settings)?, Node::Window(WindowNode { window, @@ -622,7 +624,7 @@ impl GroupNode { node.set_secondary_dimension(group_secondary, new_axis); match node { - Node::Group(group) => group.apply_changes(reconfigure_window), + Node::Group(group) => group.apply_changes(reconfigure_window, settings), Node::Window(WindowNode { window, @@ -638,29 +640,38 @@ impl GroupNode { } }; - let new_nodes_len = (self.children.len() + self.additions.len()) as u32; + let current_nodes_len = self.children.len(); + let new_nodes_len = (current_nodes_len + self.additions.len()) as u32; + let total_gap = if new_nodes_len == 0 { + 0 + } else { + (new_nodes_len - 1) * settings.window_gap + }; // The size of new additions. let new_primary = if new_nodes_len == 0 { group_primary } else { - group_primary / new_nodes_len + (group_primary - total_gap) / new_nodes_len }; let mut new_total_node_primary = 0; // The new total size for the existing nodes to be resized to fit within. // // `u64` is used because we will be multiplying two 'u32' values, and `u64::MAX` is // `u32::MAX * u32::MAX`. - let rescaling_primary = (group_primary - (new_primary * (additions.len() as u32))) as u64; + let rescaling_primary = (group_primary - (new_primary * (additions.len() as u32)) - total_gap) as u64; let mut additions = additions.into_iter(); let mut next_addition = additions.next(); // Resize all the nodes appropriately. for (index, node) in self.children.iter_mut().enumerate() { + let gap = (settings.window_gap as i32) * (index as i32); + let coord = (new_total_node_primary as i32) + gap; + // If `node` is an addition, resize it with the new size. if let Some(addition) = next_addition { if index == addition { - configure_node(node, new_total_node_primary as i32, new_primary)?; + configure_node(node, coord, new_primary)?; next_addition = additions.next(); @@ -681,7 +692,7 @@ impl GroupNode { // large - monitors don't tend to be millions of pixels in width or height. let rescaled_primary: u32 = ((old_primary * rescaling_primary) / old_total_node_primary).shrink(); - configure_node(node, new_total_node_primary as i32, rescaled_primary)?; + configure_node(node, coord, rescaled_primary)?; new_total_node_primary += rescaled_primary; } @@ -722,7 +733,9 @@ mod tests { assert_eq!(group.orientation(), NEW_ORIENTATION); // Apply the change in orientation. - group.apply_changes(&mut resize_window).unwrap(); + group + .apply_changes(&mut resize_window, &LayoutSettings::default()) + .unwrap(); assert_eq!(group.orientation, NEW_ORIENTATION); assert_eq!(group.new_orientation, None); @@ -760,6 +773,8 @@ mod tests { const GROUP_WIDTH: u32 = 3000; const GROUP_HEIGHT: u32 = 1000; + let settings = LayoutSettings::new().window_gap(0); + // The width of each node after three nodes have been added. const THREE_NODES_WIDTH: u32 = GROUP_WIDTH / 3; // The width of each node after two nodes have been added. @@ -787,7 +802,7 @@ mod tests { ); // Resize the added window. - group.apply_changes(&mut resize_window).unwrap(); + group.apply_changes(&mut resize_window, &settings).unwrap(); assert!( matches!( @@ -805,7 +820,7 @@ mod tests { group.push_windows_back([2, 3]); // Resize the existing window and two added windows. - group.apply_changes(&mut resize_window).unwrap(); + group.apply_changes(&mut resize_window, &settings).unwrap(); for node in &group { assert!( @@ -826,7 +841,7 @@ mod tests { group.remove(0); // Resize the two remaining windows. - group.apply_changes(&mut resize_window).unwrap(); + group.apply_changes(&mut resize_window, &settings).unwrap(); for node in &group { assert!( @@ -849,13 +864,13 @@ mod tests { clone.push_window_front(1); clone.remove(0); // Apply the changes (of which there should be none). - clone.apply_changes(&mut resize_window).unwrap(); + clone.apply_changes(&mut resize_window, &settings).unwrap(); assert_eq!(group, clone); group.set_orientation(Orientation::TopToBottom); // Apply the orientation change. - group.apply_changes(&mut resize_window).unwrap(); + group.apply_changes(&mut resize_window, &settings).unwrap(); for node in &group { assert!( diff --git a/src/state.rs b/src/state.rs index a84a1fef..a32c36de 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,7 +7,7 @@ use std::{collections::HashMap, hash::Hash}; #[cfg(feature = "async")] use {futures::future, std::future::Future}; -use crate::layout::{self, CurrentLayout}; +use crate::layout::{self, CurrentLayout, LayoutSettings}; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum MapState { @@ -59,6 +59,7 @@ impl WindowState { pub struct AquariWm { /// The current window layout. pub layout: CurrentLayout, + pub settings: LayoutSettings, /// A [`HashMap`] of windows and their current [`WindowState`s]. /// @@ -69,35 +70,46 @@ pub struct AquariWm { impl Default for AquariWm { #[inline] fn default() -> Self { - Self::new() + Self { + layout: Default::default(), + settings: Default::default(), + windows: Default::default(), + } } } impl AquariWm { /// Creates a new AquariWM state struct with the default [`CurrentLayout`] and no windows. #[inline] - pub fn new() -> Self { - Self { ..Default::default() } + pub fn new(settings: LayoutSettings) -> Self { + Self { + settings, + + ..Default::default() + } } /// Creates a new AquariWM state struct with the given `layout` and no windows. #[inline] - pub fn with_tiling_layout(x: i32, y: i32, width: u32, height: u32) -> Self + pub fn with_tiling_layout(x: i32, y: i32, width: u32, height: u32, settings: LayoutSettings) -> Self where Manager: layout::TilingLayoutManager, { Self { layout: CurrentLayout::new_tiled::(x, y, width, height), + settings, + windows: HashMap::new(), } } /// Creates a new AquariWM state struct with the default [`CurrentLayout`] and the given /// `windows`. - #[inline] - pub fn with_windows(windows: impl IntoIterator) -> Self { + pub fn with_windows(windows: impl IntoIterator, settings: LayoutSettings) -> Self { let mut aquariwm = Self { layout: CurrentLayout::default(), + settings, + windows: HashMap::new(), }; @@ -113,12 +125,15 @@ impl AquariWm { width: u32, height: u32, windows: impl IntoIterator, + settings: LayoutSettings, ) -> Self where Manager: layout::TilingLayoutManager, { let mut aquariwm = Self { layout: CurrentLayout::new_tiled::(x, y, width, height), + settings, + windows: HashMap::new(), }; @@ -235,7 +250,9 @@ impl AquariWm { mut reconfigure_window: impl FnMut(&Window, i32, i32, u32, u32) -> Result<(), Error>, ) -> Result<(), Error> { if let CurrentLayout::Tiled(manager) = &mut self.layout { - manager.layout_mut().apply_changes(&mut reconfigure_window)?; + manager + .layout_mut() + .apply_changes(&mut reconfigure_window, &self.settings)?; } Ok(()) @@ -264,13 +281,14 @@ impl AquariWm { // Add all the `resize_window` futures to this list... let mut futures = Vec::new(); - manager - .layout_mut() - .apply_changes(&mut |window, x, y, width, height| -> Result<(), Error> { + manager.layout_mut().apply_changes( + &mut |window, x, y, width, height| -> Result<(), Error> { futures.push(reconfigure_window(window, x, y, width, height)); Ok(()) - })?; + }, + &self.settings, + )?; // Await all the `resize_window` futures. future::try_join_all(futures).await?;