From be19856ae19c892cb480e176797d597d06cf5701 Mon Sep 17 00:00:00 2001 From: Darksome Date: Sun, 18 Aug 2024 19:27:02 +0000 Subject: [PATCH 1/2] shell: configurable empty workspace removal behaivior --- cosmic-comp-config/src/workspace.rs | 14 +++ flake.lock | 64 +++---------- flake.nix | 10 +- src/shell/mod.rs | 143 ++++++++++++++++++---------- 4 files changed, 125 insertions(+), 106 deletions(-) diff --git a/cosmic-comp-config/src/workspace.rs b/cosmic-comp-config/src/workspace.rs index 2d4bf2dc..764265b4 100644 --- a/cosmic-comp-config/src/workspace.rs +++ b/cosmic-comp-config/src/workspace.rs @@ -11,6 +11,19 @@ pub struct WorkspaceConfig { pub workspace_mode: WorkspaceMode, #[serde(default = "default_workspace_layout")] pub workspace_layout: WorkspaceLayout, + #[serde(default)] + pub remove_empty: RemoveEmpty, +} + +/// Setting of which empty workspaces the compositor should automatically remove. +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] +pub enum RemoveEmpty { + /// Remove all empty workspaces, except the last one. + #[default] + All, + + /// Remove only trailing empty workspaces, after the last one. + Trailing, } impl Default for WorkspaceConfig { @@ -18,6 +31,7 @@ impl Default for WorkspaceConfig { Self { workspace_mode: WorkspaceMode::OutputBound, workspace_layout: WorkspaceLayout::Vertical, + remove_empty: RemoveEmpty::default(), } } } diff --git a/flake.lock b/flake.lock index 3cf3d910..54545900 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1699548976, - "narHash": "sha256-xnpxms0koM8mQpxIup9JnT0F7GrKdvv0QvtxvRuOYR4=", + "lastModified": 1722960479, + "narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=", "owner": "ipetkov", "repo": "crane", - "rev": "6849911446e18e520970cc6b7a691e64ee90d649", + "rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4", "type": "github" }, "original": { @@ -20,31 +20,13 @@ "type": "github" } }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nix-filter": { "locked": { - "lastModified": 1687178632, - "narHash": "sha256-HS7YR5erss0JCaUijPeyg2XrisEb959FIct3n2TMGbE=", + "lastModified": 1710156097, + "narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=", "owner": "numtide", "repo": "nix-filter", - "rev": "d90c75e8319d0dd9be67d933d8eb9d0894ec9174", + "rev": "3342559a24e85fc164b295c3444e8a139924675b", "type": "github" }, "original": { @@ -55,11 +37,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1685591878, - "narHash": "sha256-Ib3apaLqIFkZb94q6Q214DXrz0FnJq5C7usywTv63og=", + "lastModified": 1723891200, + "narHash": "sha256-uljX21+D/DZgb9uEFFG2dkkQbPZN+ig4Z6+UCLWFVAk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8d4d822bc0efa9de6eddc79cb0d82897a9baa750", + "rev": "a0d6390cb3e82062a35d0288979c45756e481f60", "type": "github" }, "original": { @@ -76,11 +58,11 @@ ] }, "locked": { - "lastModified": 1688466019, - "narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", + "lastModified": 1722555600, + "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", + "rev": "8471fe90ad337a8074e957b69ca4d0089218391d", "type": "github" }, "original": { @@ -100,17 +82,16 @@ }, "rust": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1688438033, - "narHash": "sha256-wOmpZis06pVKTR+5meGwhrW10/buf98lnA26uQLaqek=", + "lastModified": 1723947704, + "narHash": "sha256-TcVf66N2NgGhxORFytzgqWcg0XJ+kk8uNLNsTRI5sYM=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "c3e43223dece545cfe06ddd92fd782adc73d56c3", + "rev": "456e78a55feade2c3bc6d7bc0bf5e710c9d86120", "type": "github" }, "original": { @@ -118,21 +99,6 @@ "repo": "rust-overlay", "type": "github" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 5cf1d10a..818d537a 100644 --- a/flake.nix +++ b/flake.nix @@ -48,14 +48,12 @@ ]; buildInputs = with pkgs; [ - wayland - systemd # For libudev - seatd # For libseat libxkbcommon libinput - mesa # For libgbm - fontconfig - stdenv.cc.cc.lib + mesa + pixman + seatd + udev ]; runtimeDependencies = with pkgs; [ diff --git a/src/shell/mod.rs b/src/shell/mod.rs index f594891c..00f99ec7 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -9,7 +9,7 @@ use std::{ use wayland_backend::server::ClientId; use cosmic_comp_config::{ - workspace::{WorkspaceLayout, WorkspaceMode}, + workspace::{RemoveEmpty, WorkspaceConfig, WorkspaceLayout, WorkspaceMode}, TileBehavior, }; use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::{ @@ -555,8 +555,8 @@ impl WorkspaceSet { self.workspaces.push(workspace); } + /// Ensures that the last workspace is empty by creating a new one if it's not. fn ensure_last_empty<'a>(&mut self, state: &mut WorkspaceUpdateGuard) { - // add empty at the end, if necessary if self .workspaces .last() @@ -565,30 +565,40 @@ impl WorkspaceSet { { self.add_empty_workspace(state); } + } - // remove empty workspaces in between, if they are not active + /// Removes empty non-active workspaces according to the provided [`RemoveEmpty`] setting. + /// + /// Leaves the last empty workspace intact. + fn remove_empty(&mut self, which: RemoveEmpty, state: &mut WorkspaceUpdateGuard) { let len = self.workspaces.len(); - let mut keep = vec![true; len]; - for (i, workspace) in self.workspaces.iter().enumerate() { - if workspace.is_empty() && i != self.active && i != len - 1 { - state.remove_workspace(workspace.handle); - keep[i] = false; + + for i in (0..len).rev() { + let is_empty = self.workspaces[i].is_empty() && i != self.active; + let is_last = i == len - 1; + + match which { + _ if is_empty && is_last => continue, + RemoveEmpty::All if !is_empty => continue, + RemoveEmpty::Trailing if !is_empty => break, + _ => {} } - } - let mut iter = keep.iter(); - self.workspaces.retain(|_| *iter.next().unwrap()); - self.active -= keep - .iter() - .take(self.active + 1) - .filter(|keep| !**keep) - .count(); + let workspace = self.workspaces.remove(i); + state.remove_workspace(workspace.handle); - if keep.iter().any(|val| *val == false) { - for (i, workspace) in self.workspaces.iter().enumerate() { - workspace_set_idx(state, i as u8 + 1, self.idx, &workspace.handle); + if i < self.active { + self.active -= 1; } } + + if self.workspaces.len() == len { + return; + } + + for (i, workspace) in self.workspaces.iter().enumerate() { + workspace_set_idx(state, i as u8 + 1, self.idx, &workspace.handle); + } } fn update_idx(&mut self, state: &mut WorkspaceUpdateGuard<'_, State>, idx: usize) { @@ -608,6 +618,8 @@ pub struct Workspaces { autotile: bool, autotile_behavior: TileBehavior, theme: cosmic::Theme, + + config: WorkspaceConfig, } impl Workspaces { @@ -620,6 +632,7 @@ impl Workspaces { autotile: config.cosmic_conf.autotile, autotile_behavior: config.cosmic_conf.autotile_behavior, theme, + config: config.cosmic_conf.workspaces.clone(), } } @@ -790,6 +803,7 @@ impl Workspaces { let old_mode = self.mode; self.mode = config.cosmic_conf.workspaces.workspace_mode; self.layout = config.cosmic_conf.workspaces.workspace_layout; + self.config = config.cosmic_conf.workspaces.clone(); if self.sets.len() <= 1 { return; @@ -880,8 +894,22 @@ impl Workspaces { set.add_empty_workspace(workspace_state) } } + } + WorkspaceMode::OutputBound => {} + } + + self.ensure_last_empty(workspace_state); + self.remove_empty(workspace_state); + + for set in self.sets.values_mut() { + set.refresh(xdg_activation_state) + } + } - // add empty at the end, if necessary + /// Ensures that the last workspace is empty by creating a new one if it's not. + fn ensure_last_empty(&mut self, workspace_state: &mut WorkspaceUpdateGuard<'_, State>) { + match self.mode { + WorkspaceMode::Global => { if self .sets .values() @@ -892,54 +920,67 @@ impl Workspaces { set.add_empty_workspace(workspace_state); } } + } + WorkspaceMode::OutputBound => { + for set in self.sets.values_mut() { + set.ensure_last_empty(workspace_state); + } + } + } + } + + /// Removes empty non-active workspaces according to the [`RemoveEmpty`] config setting. + /// + /// Leaves the last empty workspace intact. + fn remove_empty(&mut self, workspace_state: &mut WorkspaceUpdateGuard<'_, State>) { + match self.mode { + WorkspaceMode::Global => { + if self.sets.is_empty() { + return; + } - // remove empty workspaces in between, if they are not active let len = self.sets[0].workspaces.len(); let mut active = self.sets[0].active; - let mut keep = vec![true; len]; - for i in 0..len { + + for i in (0..len).rev() { let has_windows = self.sets.values().any(|s| !s.workspaces[i].is_empty()); + let is_empty = !has_windows && i != active; + let is_last = i == len - 1; + + match self.config.remove_empty { + _ if is_empty && is_last => continue, + RemoveEmpty::All if !is_empty => continue, + RemoveEmpty::Trailing if !is_empty => break, + _ => {} + } - if !has_windows && i != active && i != len - 1 { - for workspace in self.sets.values().map(|s| &s.workspaces[i]) { - workspace_state.remove_workspace(workspace.handle); - } - keep[i] = false; + for workspace in self.sets.values_mut().map(|s| s.workspaces.remove(i)) { + workspace_state.remove_workspace(workspace.handle); + } + + if i < active { + active -= 1; } } - self.sets.values_mut().for_each(|s| { - let mut iter = keep.iter(); - s.workspaces.retain(|_| *iter.next().unwrap()); - }); - active -= keep.iter().take(active + 1).filter(|keep| !**keep).count(); - self.sets.values_mut().for_each(|s| { - s.active = active; - }); + if self.sets[0].workspaces.len() == len { + return; + } - if keep.iter().any(|val| *val == false) { - for set in self.sets.values_mut() { - for (i, workspace) in set.workspaces.iter().enumerate() { - workspace_set_idx( - workspace_state, - i as u8 + 1, - set.idx, - &workspace.handle, - ); - } + for set in self.sets.values_mut() { + set.active = active; + + for (i, workspace) in set.workspaces.iter().enumerate() { + workspace_set_idx(workspace_state, i as u8 + 1, set.idx, &workspace.handle); } } } WorkspaceMode::OutputBound => { for set in self.sets.values_mut() { - set.ensure_last_empty(workspace_state); + set.remove_empty(self.config.remove_empty, workspace_state); } } } - - for set in self.sets.values_mut() { - set.refresh(xdg_activation_state) - } } pub fn get(&self, num: usize, output: &Output) -> Option<&Workspace> { From a8ea17d4148868753e161d013008f6c7aeee8005 Mon Sep 17 00:00:00 2001 From: Darksome Date: Sun, 18 Aug 2024 20:43:44 +0000 Subject: [PATCH 2/2] fix crash --- src/shell/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 00f99ec7..ef6198c1 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -999,7 +999,7 @@ impl Workspaces { let set = self.sets.get(output).or(self.backup_set.as_ref()).unwrap(); ( set.previously_active - .map(|(idx, start)| (&set.workspaces[idx], start)), + .and_then(|(idx, start)| set.workspaces.get(idx).map(|w| (w, start))), &set.workspaces[set.active], ) }