Add per-monitor workspace back-and-forth
This commit is contained in:
1 parent
cb6fe73e44
commit
17b8482433
12 files changed
+405
-20
No files matched your search
@@ -27,6 +27,8 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
layout: LayoutConfig,
|
||||
#[serde(default)]
|
||||
workspaces: WorkspacesConfig,
|
||||
#[serde(default)]
|
||||
input: InputConfig,
|
||||
#[serde(default)]
|
||||
theme: ThemeConfig,
|
||||
@@ -315,6 +317,7 @@ impl Config {
|
||||
lock_idle: self.lock_screen.validate()?,
|
||||
autostart: self.autostart.clone(),
|
||||
layout_mode: self.layout_mode(),
|
||||
workspace_auto_back_and_forth: self.workspaces.auto_back_and_forth,
|
||||
gap_config: self.gap_config()?,
|
||||
input_settings,
|
||||
bindings,
|
||||
@@ -500,6 +503,7 @@ pub enum BindingAction {
|
||||
Resize(ferese_layout::Direction),
|
||||
SwitchWorkspace(u8),
|
||||
SwitchRelativeWorkspace(bool),
|
||||
WorkspaceBackAndForth,
|
||||
MoveToWorkspace(u8),
|
||||
ToggleFullscreen,
|
||||
ToggleMaximized,
|
||||
@@ -598,6 +602,7 @@ impl Binding {
|
||||
BindingAction::Move(direction) => format!("Move window {direction:?}"),
|
||||
BindingAction::Resize(direction) => format!("Resize window {direction:?}"),
|
||||
BindingAction::SwitchWorkspace(index) => format!("Go to workspace {index}"),
|
||||
BindingAction::WorkspaceBackAndForth => "Return to last workspace on this monitor".into(),
|
||||
BindingAction::MoveToWorkspace(index) => format!("Move window to workspace {index}"),
|
||||
BindingAction::SwitchRelativeWorkspace(next) => {
|
||||
if *next { "Next workspace" } else { "Previous workspace" }.into()
|
||||
@@ -818,6 +823,12 @@ impl Default for SpringSettings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct WorkspacesConfig {
|
||||
#[serde(default)]
|
||||
auto_back_and_forth: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LayoutConfig {
|
||||
mode: Option<LayoutModeValue>,
|
||||
@@ -1495,6 +1506,10 @@ fn parse_action(
|
||||
no_argument()?;
|
||||
Ok(BindingAction::SwitchRelativeWorkspace(action == "workspace-next"))
|
||||
}
|
||||
"workspace-back-and-forth" => {
|
||||
no_argument()?;
|
||||
Ok(BindingAction::WorkspaceBackAndForth)
|
||||
}
|
||||
"spawn" => {
|
||||
let command = required_argument()?;
|
||||
let argv = commands
|
||||
@@ -1591,6 +1606,7 @@ fn default_bindings() -> Vec<BindingConfig> {
|
||||
binding("Super+]", "expel", None),
|
||||
binding("Super+Shift+Space", "toggle-floating", None),
|
||||
binding("Super+Tab", "toggle-overview", None),
|
||||
binding("Super+Escape", "workspace-back-and-forth", None),
|
||||
binding("Super+Shift+E", "exit", None),
|
||||
];
|
||||
|
||||
@@ -1872,6 +1888,34 @@ pub(crate) fn config_path() -> Option<PathBuf> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn workspace_back_and_forth_config_is_optional_and_bindable() {
|
||||
let defaults = Config::default().runtime_config().unwrap();
|
||||
assert!(!defaults.workspace_auto_back_and_forth);
|
||||
assert!(
|
||||
defaults
|
||||
.bindings
|
||||
.iter()
|
||||
.any(|binding| binding.action == BindingAction::WorkspaceBackAndForth)
|
||||
);
|
||||
let configured = parse(
|
||||
"workspaces {\n auto-back-and-forth #true\n}\nbinding \"Super+BackSpace\" \"workspace-back-and-forth\"\n",
|
||||
)
|
||||
.runtime_config()
|
||||
.unwrap();
|
||||
assert!(configured.workspace_auto_back_and_forth);
|
||||
assert_eq!(
|
||||
configured
|
||||
.bindings
|
||||
.iter()
|
||||
.filter(|binding| binding.action == BindingAction::WorkspaceBackAndForth)
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
assert!(parse_action("workspace-back-and-forth", Some("2"), &HashMap::new()).is_err());
|
||||
assert!(Config::parse_source("workspaces {\n auto-back-and-forth \"yes\"\n}\n").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packaged_and_custom_kdl_pass_runtime_validation() {
|
||||
Config::parse_source(include_str!("../../packaging/config.kdl"))
|
||||
@@ -2328,7 +2372,7 @@ mod tests {
|
||||
let input = config.input_settings().unwrap();
|
||||
let bindings = config.bindings(&input).unwrap();
|
||||
|
||||
assert_eq!(bindings.len(), 48);
|
||||
assert_eq!(bindings.len(), 49);
|
||||
for (shift, action) in [
|
||||
(false, BindingAction::ToggleMaximized),
|
||||
(true, BindingAction::ToggleFullscreen),
|
||||
@@ -2371,14 +2415,14 @@ mod tests {
|
||||
|
||||
let input = replaced.input_settings().unwrap();
|
||||
let bindings = replaced.bindings(&input).unwrap();
|
||||
assert_eq!(bindings.len(), 48);
|
||||
assert_eq!(bindings.len(), 49);
|
||||
assert!(bindings.iter().any(|binding| {
|
||||
binding.action == BindingAction::Spawn(vec!["foot".to_owned(), "--app-id".to_owned(), "work".to_owned()])
|
||||
}));
|
||||
|
||||
let input = unbound.input_settings().unwrap();
|
||||
let bindings = unbound.bindings(&input).unwrap();
|
||||
assert_eq!(bindings.len(), 47);
|
||||
assert_eq!(bindings.len(), 48);
|
||||
assert!(!bindings.iter().any(|binding| binding.action == BindingAction::Close));
|
||||
}
|
||||
|
||||
|
||||
@@ -987,8 +987,9 @@ impl Ferese {
|
||||
BindingAction::Move(direction) => self.move_direction(direction),
|
||||
BindingAction::Resize(direction) => self.resize_direction(direction),
|
||||
BindingAction::SwitchRelativeWorkspace(next) => self.switch_relative_workspace(next, None),
|
||||
BindingAction::WorkspaceBackAndForth => self.workspace_back_and_forth(),
|
||||
BindingAction::SwitchWorkspace(workspace) => {
|
||||
self.switch_workspace(u32::from(workspace));
|
||||
self.switch_workspace_from_binding(u32::from(workspace));
|
||||
}
|
||||
BindingAction::MoveToWorkspace(workspace) => {
|
||||
self.move_focused_to_workspace(u32::from(workspace));
|
||||
|
||||
@@ -650,6 +650,7 @@ impl Ferese {
|
||||
"move" => self.move_direction(direction_arg(args)?),
|
||||
"resize" => self.resize_direction(direction_arg(args)?),
|
||||
"workspace" => self.switch_workspace(workspace_arg(args)?),
|
||||
"workspace-back-and-forth" => self.workspace_back_and_forth(),
|
||||
"move-to-workspace" => self.move_focused_to_workspace(workspace_arg(args)?),
|
||||
"toggle-floating" => self.toggle_focused_floating(),
|
||||
"toggle-fullscreen" => self.toggle_focused_fullscreen(),
|
||||
|
||||
+36
-5
@@ -276,6 +276,7 @@ pub struct Ferese {
|
||||
gap_config: GapConfig,
|
||||
pub(crate) input_settings: InputSettings,
|
||||
pub(crate) bindings: Vec<Binding>,
|
||||
workspace_auto_back_and_forth: bool,
|
||||
window_rules: Vec<WindowRule>,
|
||||
window_rules_applied: HashSet<WindowId>,
|
||||
pub(crate) theme_settings: ThemeSettings,
|
||||
@@ -354,6 +355,7 @@ pub struct RuntimeConfig {
|
||||
pub gap_config: GapConfig,
|
||||
pub input_settings: InputSettings,
|
||||
pub bindings: Vec<Binding>,
|
||||
pub workspace_auto_back_and_forth: bool,
|
||||
pub window_rules: Vec<WindowRule>,
|
||||
pub theme_settings: ThemeSettings,
|
||||
pub default_column_width: ColumnWidth,
|
||||
@@ -495,6 +497,7 @@ impl Ferese {
|
||||
gap_config: config.gap_config,
|
||||
input_settings: config.input_settings,
|
||||
bindings: config.bindings,
|
||||
workspace_auto_back_and_forth: config.workspace_auto_back_and_forth,
|
||||
window_rules: config.window_rules,
|
||||
window_rules_applied: HashSet::new(),
|
||||
theme_settings: config.theme_settings,
|
||||
@@ -631,6 +634,7 @@ impl Ferese {
|
||||
self.gap_config = config.gap_config;
|
||||
self.input_settings = config.input_settings;
|
||||
self.bindings = config.bindings;
|
||||
self.workspace_auto_back_and_forth = config.workspace_auto_back_and_forth;
|
||||
self.portal_shortcuts.reconcile(&self.bindings, &self.input_settings);
|
||||
let old_rules = std::mem::replace(&mut self.window_rules, config.window_rules);
|
||||
self.theme_settings = config.theme_settings;
|
||||
@@ -1505,7 +1509,9 @@ impl Ferese {
|
||||
}
|
||||
|
||||
fn prune_empty_workspaces(&mut self, visible: &HashSet<WorkspaceId>) {
|
||||
for workspace in self.workspaces.prune_empty(visible) {
|
||||
let mut protected = visible.clone();
|
||||
protected.extend(self.output_workspaces.history_workspaces());
|
||||
for workspace in self.workspaces.prune_empty(&protected) {
|
||||
self.output_workspaces.forget_workspace(workspace);
|
||||
self.viewport_animations.remove(&workspace);
|
||||
}
|
||||
@@ -2768,14 +2774,32 @@ impl Ferese {
|
||||
}
|
||||
|
||||
pub fn switch_workspace(&mut self, index: u32) {
|
||||
self.switch_workspace_internal(index, None);
|
||||
self.switch_workspace_internal(index, None, false);
|
||||
}
|
||||
|
||||
pub(crate) fn switch_workspace_from_binding(&mut self, index: u32) {
|
||||
self.switch_workspace_internal(index, None, self.workspace_auto_back_and_forth);
|
||||
}
|
||||
|
||||
pub(crate) fn workspace_back_and_forth(&mut self) {
|
||||
let Some(output) = self.output_workspaces.focused_output() else {
|
||||
return;
|
||||
};
|
||||
if let Some(workspace) = self.output_workspaces.previous_workspace(output) {
|
||||
self.activate_managed_workspace(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn switch_workspace_from_swipe(&mut self, index: u32, direction: SwipeDirection) {
|
||||
self.switch_workspace_internal(index, Some(direction));
|
||||
self.switch_workspace_internal(index, Some(direction), false);
|
||||
}
|
||||
|
||||
fn switch_workspace_internal(&mut self, index: u32, slide_direction: Option<SwipeDirection>) {
|
||||
fn switch_workspace_internal(
|
||||
&mut self,
|
||||
index: u32,
|
||||
slide_direction: Option<SwipeDirection>,
|
||||
auto_back_and_forth: bool,
|
||||
) {
|
||||
let workspace = match self.workspaces.ensure_numeric(index) {
|
||||
Ok(workspace) => workspace,
|
||||
Err(error) => {
|
||||
@@ -2787,7 +2811,10 @@ impl Ferese {
|
||||
return;
|
||||
};
|
||||
let previous = self.output_workspaces.active_workspace(output);
|
||||
let owner = match self.output_workspaces.switch_workspace(output, workspace) {
|
||||
let owner = match self
|
||||
.output_workspaces
|
||||
.select_workspace(output, workspace, auto_back_and_forth)
|
||||
{
|
||||
Ok(ferese_core::WorkspaceSwitch::Activated(output))
|
||||
| Ok(ferese_core::WorkspaceSwitch::FocusedExisting(output)) => output,
|
||||
Err(error) => {
|
||||
@@ -2795,6 +2822,10 @@ impl Ferese {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let workspace = self
|
||||
.output_workspaces
|
||||
.active_workspace(owner)
|
||||
.expect("workspace owner is connected");
|
||||
self.update_workspace_slide(output, owner, previous, workspace, slide_direction);
|
||||
self.activate_output_workspace(owner, workspace);
|
||||
|
||||
|
||||
Reference in new issue
Block a user