From be918152d5e20c01a8037536339ec2a6922d24c2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 25 May 2023 00:10:24 +0200 Subject: [PATCH] shell: Implement Workspace Layout --- config.ron | 23 +--- src/backend/render/mod.rs | 2 +- src/config/mod.rs | 139 +++++++++++++++++++- src/input/mod.rs | 116 ++++++++++------ src/shell/mod.rs | 57 +++++--- src/wayland/handlers/toplevel_management.rs | 2 +- src/wayland/handlers/workspace.rs | 2 +- 7 files changed, 261 insertions(+), 80 deletions(-) diff --git a/config.ron b/config.ron index 19fd729f..8b8acdf6 100644 --- a/config.ron +++ b/config.ron @@ -25,24 +25,10 @@ (modifiers: [Super, Shift], key: "9"): MoveToWorkspace(9), (modifiers: [Super, Shift], key: "0"): MoveToLastWorkspace, - // TODO: Depends on workspace orientation - (modifiers: [Super, Ctrl], key: "Right"): NextWorkspace, - (modifiers: [Super, Ctrl], key: "Left"): PreviousWorkspace, - (modifiers: [Super, Ctrl, Shift], key: "Right"): MoveToNextWorkspace, - (modifiers: [Super, Ctrl, Shift], key: "Left"): MoveToPreviousWorkspace, - (modifiers: [Super, Ctrl], key: "l"): NextWorkspace, - (modifiers: [Super, Ctrl], key: "h"): PreviousWorkspace, - (modifiers: [Super, Ctrl, Shift], key: "l"): MoveToNextWorkspace, - (modifiers: [Super, Ctrl, Shift], key: "h"): MoveToPreviousWorkspace, - - (modifiers: [Super, Ctrl], key: "Down"): NextOutput, - (modifiers: [Super, Ctrl], key: "Up"): PreviousOutput, - (modifiers: [Super, Ctrl, Alt], key: "Down"): NextOutput, - (modifiers: [Super, Ctrl, Alt], key: "Up"): PreviousOutput, - (modifiers: [Super, Ctrl], key: "j"): NextOutput, - (modifiers: [Super, Ctrl], key: "k"): PreviousOutput, - (modifiers: [Super, Ctrl, Alt], key: "j"): NextOutput, - (modifiers: [Super, Ctrl, Alt], key: "k"): PreviousOutput, + (modifiers: [Super, Ctrl, Alt], key: "Down"): MoveToNextOutput, + (modifiers: [Super, Ctrl, Alt], key: "Up"): MoveToPreviousOutput, + (modifiers: [Super, Ctrl, Alt], key: "j"): MoveToNextOutput, + (modifiers: [Super, Ctrl, Alt], key: "k"): MoveToPreviousOutput, (modifiers: [Super], key: "Period"): NextOutput, (modifiers: [Super], key: "Comma"): PreviousOutput, @@ -93,5 +79,6 @@ }, workspace_mode: OutputBound, workspace_amount: Dynamic, + workspace_layout: Vertical, tiling_enabled: false, ) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index ec765b6d..88b34d09 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -396,7 +396,7 @@ where let offset = match previous.as_ref() { Some((previous, previous_idx, start)) => { - let layout = WorkspaceLayout::Vertical; + let layout = state.config.static_conf.workspace_layout; let workspace = state .shell diff --git a/src/config/mod.rs b/src/config/mod.rs index 19657c55..45143d5d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -36,6 +36,8 @@ pub struct StaticConfig { pub key_bindings: HashMap, pub workspace_mode: WorkspaceMode, pub workspace_amount: WorkspaceAmount, + #[serde(default = "default_workspace_layout")] + pub workspace_layout: WorkspaceLayout, pub tiling_enabled: bool, #[serde(default = "default_active_hint")] pub active_hint: u8, @@ -95,6 +97,10 @@ fn default_gaps() -> (u8, u8) { (0, 4) } +fn default_workspace_layout() -> WorkspaceLayout { + WorkspaceLayout::Vertical +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct OutputConfig { pub mode: ((i32, i32), Option), @@ -232,8 +238,132 @@ impl Config { debug!("Trying config location: {}", path.display()); if path.exists() { info!("Using config at {}", path.display()); - return ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) - .expect("Malformed config file"); + let mut config: StaticConfig = + ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) + .expect("Malformed config file"); + + let (workspace_previous, workspace_next, output_previous, output_next) = + match config.workspace_layout { + WorkspaceLayout::Horizontal => ( + [KeySyms::KEY_Left, KeySyms::KEY_h], + [KeySyms::KEY_Right, KeySyms::KEY_j], + [KeySyms::KEY_Up, KeySyms::KEY_k], + [KeySyms::KEY_Down, KeySyms::KEY_j], + ), + WorkspaceLayout::Vertical => ( + [KeySyms::KEY_Up, KeySyms::KEY_k], + [KeySyms::KEY_Down, KeySyms::KEY_j], + [KeySyms::KEY_Left, KeySyms::KEY_h], + [KeySyms::KEY_Right, KeySyms::KEY_j], + ), + }; + + fn insert_binding( + key_bindings: &mut HashMap, + modifiers: KeyModifiers, + keys: impl Iterator, + action: Action, + ) { + if !key_bindings.values().any(|a| a == &action) { + for key in keys { + let pattern = KeyPattern { + modifiers: modifiers.clone(), + key, + }; + if !key_bindings.contains_key(&pattern) { + key_bindings.insert(pattern, action.clone()); + } + } + } + } + + insert_binding( + &mut config.key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + ..Default::default() + }, + workspace_previous.iter().copied(), + Action::PreviousWorkspace, + ); + insert_binding( + &mut config.key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + ..Default::default() + }, + workspace_next.iter().copied(), + Action::NextWorkspace, + ); + insert_binding( + &mut config.key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + shift: true, + ..Default::default() + }, + workspace_previous.iter().copied(), + Action::MoveToPreviousWorkspace, + ); + insert_binding( + &mut config.key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + shift: true, + ..Default::default() + }, + workspace_next.iter().copied(), + Action::MoveToNextWorkspace, + ); + + insert_binding( + &mut config.key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + ..Default::default() + }, + output_previous.iter().copied(), + Action::PreviousOutput, + ); + insert_binding( + &mut config.key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + ..Default::default() + }, + output_next.iter().copied(), + Action::NextOutput, + ); + insert_binding( + &mut config.key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + shift: true, + ..Default::default() + }, + output_previous.iter().copied(), + Action::MoveToPreviousOutput, + ); + insert_binding( + &mut config.key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + shift: true, + ..Default::default() + }, + output_next.iter().copied(), + Action::MoveToNextOutput, + ); + + return config; } } @@ -241,6 +371,7 @@ impl Config { key_bindings: HashMap::new(), workspace_mode: WorkspaceMode::Global, workspace_amount: WorkspaceAmount::Dynamic, + workspace_layout: WorkspaceLayout::Vertical, tiling_enabled: false, active_hint: default_active_hint(), gaps: default_gaps(), @@ -776,7 +907,7 @@ pub enum KeyModifier { NumLock, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] pub struct KeyModifiers { pub ctrl: bool, pub alt: bool, @@ -880,6 +1011,8 @@ pub enum Action { PreviousOutput, MoveToNextOutput, MoveToPreviousOutput, + SendToNextOutput, + SendToPreviousOutput, Focus(FocusDirection), Move(Direction), diff --git a/src/input/mod.rs b/src/input/mod.rs index 8640fe78..05bb3b54 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - config::{Action, Config, KeyModifiers}, + config::{Action, Config, KeyModifiers, WorkspaceLayout}, shell::{ focus::{target::PointerFocusTarget, FocusDirection}, layout::{ @@ -710,8 +710,14 @@ impl State { .active_num(¤t_output) .1 .saturating_add(1); - // TODO: Possibly move to next output, if idx to large - let _ = self.common.shell.activate(¤t_output, workspace); + if self + .common + .shell + .activate(¤t_output, workspace) + .is_err() + { + self.handle_action(Action::NextOutput, seat, serial, time, mods); + } } Action::PreviousWorkspace => { let current_output = seat.active_output(); @@ -722,8 +728,14 @@ impl State { .active_num(¤t_output) .1 .saturating_sub(1); - // TODO: Possibly move to prev output, if idx < 0 - let _ = self.common.shell.activate(¤t_output, workspace); + if self + .common + .shell + .activate(¤t_output, workspace) + .is_err() + { + self.handle_action(Action::PreviousOutput, seat, serial, time, mods); + } } Action::LastWorkspace => { let current_output = seat.active_output(); @@ -743,7 +755,7 @@ impl State { Action::MoveToWorkspace(x) | Action::SendToWorkspace(x) => x - 1, _ => unreachable!(), }; - Shell::move_current_window( + let _ = Shell::move_current_window( self, seat, ¤t_output, @@ -760,14 +772,27 @@ impl State { .active_num(¤t_output) .1 .saturating_add(1); - // TODO: Possibly move to next output, if idx too large - Shell::move_current_window( + if Shell::move_current_window( self, seat, ¤t_output, (¤t_output, Some(workspace as usize)), matches!(x, Action::MoveToNextWorkspace), - ); + ) + .is_err() + { + self.handle_action( + if matches!(x, Action::MoveToNextWorkspace) { + Action::MoveToNextOutput + } else { + Action::SendToNextOutput + }, + seat, + serial, + time, + mods, + ) + } } x @ Action::MoveToPreviousWorkspace | x @ Action::SendToPreviousWorkspace => { let current_output = seat.active_output(); @@ -779,13 +804,27 @@ impl State { .1 .saturating_sub(1); // TODO: Possibly move to prev output, if idx < 0 - Shell::move_current_window( + if Shell::move_current_window( self, seat, ¤t_output, (¤t_output, Some(workspace as usize)), matches!(x, Action::MoveToPreviousWorkspace), - ); + ) + .is_err() + { + self.handle_action( + if matches!(x, Action::MoveToNextWorkspace) { + Action::MoveToPreviousOutput + } else { + Action::SendToPreviousOutput + }, + seat, + serial, + time, + mods, + ) + } } x @ Action::MoveToLastWorkspace | x @ Action::SendToLastWorkspace => { let current_output = seat.active_output(); @@ -795,7 +834,7 @@ impl State { .workspaces .len(¤t_output) .saturating_sub(1); - Shell::move_current_window( + let _ = Shell::move_current_window( self, seat, ¤t_output, @@ -816,7 +855,7 @@ impl State { .cloned() { let idx = self.common.shell.workspaces.active_num(&next_output).1; - if let Some(new_pos) = self.common.shell.activate(&next_output, idx) { + if let Ok(Some(new_pos)) = self.common.shell.activate(&next_output, idx) { seat.set_active_output(&next_output); if let Some(ptr) = seat.get_pointer() { ptr.motion( @@ -846,7 +885,7 @@ impl State { .cloned() { let idx = self.common.shell.workspaces.active_num(&prev_output).1; - if let Some(new_pos) = self.common.shell.activate(&prev_output, idx) { + if let Ok(Some(new_pos)) = self.common.shell.activate(&prev_output, idx) { seat.set_active_output(&prev_output); if let Some(ptr) = seat.get_pointer() { ptr.motion( @@ -862,7 +901,7 @@ impl State { } } } - Action::MoveToNextOutput => { + x @ Action::MoveToNextOutput | x @ Action::SendToNextOutput => { let current_output = seat.active_output(); if let Some(next_output) = self .common @@ -874,12 +913,12 @@ impl State { .next() .cloned() { - if let Some(new_pos) = Shell::move_current_window( + if let Ok(Some(new_pos)) = Shell::move_current_window( self, seat, ¤t_output, (&next_output, None), - true, + matches!(x, Action::MoveToNextOutput), ) { if let Some(ptr) = seat.get_pointer() { ptr.motion( @@ -895,7 +934,7 @@ impl State { } } } - Action::MoveToPreviousOutput => { + x @ Action::MoveToPreviousOutput | x @ Action::SendToPreviousOutput => { let current_output = seat.active_output(); if let Some(prev_output) = self .common @@ -908,12 +947,12 @@ impl State { .next() .cloned() { - if let Some(new_pos) = Shell::move_current_window( + if let Ok(Some(new_pos)) = Shell::move_current_window( self, seat, ¤t_output, (&prev_output, None), - true, + matches!(x, Action::MoveToPreviousOutput), ) { if let Some(ptr) = seat.get_pointer() { ptr.motion( @@ -942,22 +981,20 @@ impl State { match result { FocusResult::None => { - // TODO: Handle Workspace orientation - match focus { - FocusDirection::Left => self.handle_action( - Action::PreviousWorkspace, - seat, - serial, - time, - mods, - ), - FocusDirection::Right => { + match (focus, self.common.config.static_conf.workspace_layout) { + (FocusDirection::Left, WorkspaceLayout::Horizontal) + | (FocusDirection::Up, WorkspaceLayout::Vertical) => self + .handle_action(Action::PreviousWorkspace, seat, serial, time, mods), + (FocusDirection::Right, WorkspaceLayout::Horizontal) + | (FocusDirection::Down, WorkspaceLayout::Vertical) => { self.handle_action(Action::NextWorkspace, seat, serial, time, mods) } - FocusDirection::Up => { + (FocusDirection::Left, WorkspaceLayout::Vertical) + | (FocusDirection::Up, WorkspaceLayout::Horizontal) => { self.handle_action(Action::PreviousOutput, seat, serial, time, mods) } - FocusDirection::Down => { + (FocusDirection::Right, WorkspaceLayout::Vertical) + | (FocusDirection::Down, WorkspaceLayout::Horizontal) => { self.handle_action(Action::NextOutput, seat, serial, time, mods) } _ => {} @@ -980,31 +1017,34 @@ impl State { if let Some(_move_further) = workspace.tiling_layer.move_current_window(direction, seat) { - // TODO: Handle Workspace orientation // TODO: Being able to move Groups (move_further should be KeyboardFocusTarget instead) - match direction { - Direction::Left => self.handle_action( + match (direction, self.common.config.static_conf.workspace_layout) { + (Direction::Left, WorkspaceLayout::Horizontal) + | (Direction::Up, WorkspaceLayout::Vertical) => self.handle_action( Action::MoveToPreviousWorkspace, seat, serial, time, mods, ), - Direction::Right => self.handle_action( + (Direction::Right, WorkspaceLayout::Horizontal) + | (Direction::Down, WorkspaceLayout::Vertical) => self.handle_action( Action::MoveToNextWorkspace, seat, serial, time, mods, ), - Direction::Up => self.handle_action( + (Direction::Left, WorkspaceLayout::Vertical) + | (Direction::Up, WorkspaceLayout::Horizontal) => self.handle_action( Action::MoveToPreviousOutput, seat, serial, time, mods, ), - Direction::Down => { + (Direction::Right, WorkspaceLayout::Vertical) + | (Direction::Down, WorkspaceLayout::Horizontal) => { self.handle_action(Action::MoveToNextOutput, seat, serial, time, mods) } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 961e01bf..bc24d642 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -167,13 +167,24 @@ impl WorkspaceSet { } } - fn activate(&mut self, idx: usize, state: &mut WorkspaceUpdateGuard<'_, State>) { - if idx < self.workspaces.len() && self.active != idx { + fn activate( + &mut self, + idx: usize, + state: &mut WorkspaceUpdateGuard<'_, State>, + ) -> Result { + if idx >= self.workspaces.len() { + return Err(InvalidWorkspaceIndex); + } + + if self.active != idx { let old_active = self.active; state.remove_workspace_state(&self.workspaces[old_active].handle, WState::Active); state.add_workspace_state(&self.workspaces[idx].handle, WState::Active); self.previously_active = Some((old_active, Instant::now())); self.active = idx; + Ok(true) + } else { + Ok(false) } } @@ -476,6 +487,8 @@ impl WorkspaceMode { } } +pub struct InvalidWorkspaceIndex; + impl Shell { pub fn new(config: &Config, dh: &DisplayHandle) -> Self { // TODO: Privileged protocols @@ -893,20 +906,28 @@ impl Shell { self.refresh(); // get rid of empty workspaces and enforce potential maximum } - pub fn activate(&mut self, output: &Output, idx: usize) -> Option> { - match &mut self.workspaces { + pub fn activate( + &mut self, + output: &Output, + idx: usize, + ) -> Result>, InvalidWorkspaceIndex> { + if match &mut self.workspaces { WorkspaceMode::OutputBound(sets, _) => { if let Some(set) = sets.get_mut(output) { - set.activate(idx, &mut self.workspace_state.update()); + set.activate(idx, &mut self.workspace_state.update())? + } else { + false } } - WorkspaceMode::Global(set) => { - set.activate(idx, &mut self.workspace_state.update()); - } + WorkspaceMode::Global(set) => set.activate(idx, &mut self.workspace_state.update())?, + } { + let output_geo = output.geometry(); + Ok(Some( + output_geo.loc + Point::from((output_geo.size.w / 2, output_geo.size.h / 2)), + )) + } else { + Ok(None) } - - let output_geo = output.geometry(); - Some(output_geo.loc + Point::from((output_geo.size.w / 2, output_geo.size.h / 2))) } pub fn active_space(&self, output: &Output) -> &Workspace { @@ -1254,7 +1275,7 @@ impl Shell { from_output: &Output, to: (&Output, Option), follow: bool, - ) -> Option> { + ) -> Result>, InvalidWorkspaceIndex> { let (to_output, to_idx) = to; let to_idx = to_idx.unwrap_or(state.common.shell.workspaces.active_num(to_output).1); if state @@ -1264,20 +1285,20 @@ impl Shell { .get(to_idx, to_output) .is_none() { - return None; + return Err(InvalidWorkspaceIndex); } if from_output == to_output && to_idx == state.common.shell.workspaces.active_num(from_output).1 { - return None; + return Ok(None); } let from_workspace = state.common.shell.workspaces.active_mut(from_output); let maybe_window = from_workspace.focus_stack.get(seat).last().cloned(); - let Some(mapped) = maybe_window else { return None; }; - let Some(window_state) = from_workspace.unmap(&mapped) else { return None; }; + let Some(mapped) = maybe_window else { return Ok(None); }; + let Some(window_state) = from_workspace.unmap(&mapped) else { return Ok(None); }; for (toplevel, _) in mapped.windows() { state @@ -1300,7 +1321,7 @@ impl Shell { } let new_pos = if follow { seat.set_active_output(&to_output); - state.common.shell.activate(to_output, to_idx) + state.common.shell.activate(to_output, to_idx)? } else { None }; @@ -1345,7 +1366,7 @@ impl Shell { if follow { Common::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); } - new_pos + Ok(new_pos) } pub fn update_reactive_popups(&self, mapped: &CosmicMapped) { diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs index 786be8bd..d4440908 100644 --- a/src/wayland/handlers/toplevel_management.rs +++ b/src/wayland/handlers/toplevel_management.rs @@ -48,7 +48,7 @@ impl ToplevelManagementHandler for State { .unwrap() .clone(); - self.common.shell.activate(&output, idx as usize); + let _ = self.common.shell.activate(&output, idx as usize); // TODO: Move pointer? mapped.focus_window(window); Common::set_focus(self, Some(&mapped.clone().into()), &seat, None); return; diff --git a/src/wayland/handlers/workspace.rs b/src/wayland/handlers/workspace.rs index eecc24c6..56b268b7 100644 --- a/src/wayland/handlers/workspace.rs +++ b/src/wayland/handlers/workspace.rs @@ -45,7 +45,7 @@ impl WorkspaceHandler for State { }; if let Some((output, idx)) = maybe { - self.common.shell.activate(&output, idx); + let _ = self.common.shell.activate(&output, idx); // TODO: move cursor? } } _ => {}