From dcb29efce58ce0e122806b7f352a9f682e2fcbd8 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Fri, 6 Sep 2024 15:10:01 +0300 Subject: Implement by-id window addressing in IPC and CLI, fix move-column-to-workspace This is a JSON-breaking change for the IPC actions that changed from unit variants to struct variants. Unfortunately, I couldn't find a way with serde to both preserve a single variant, and make it serialize to the old value when the new field is None. I don't think anyone is using these actions from JSON at the moment, so this breaking change is fine. --- src/handlers/xdg_shell.rs | 4 +- src/input/mod.rs | 158 ++++++++++++++++++++--- src/layout/mod.rs | 312 ++++++++++++++++++++++++++++++++++++++-------- src/layout/monitor.rs | 75 ++++++----- src/layout/workspace.rs | 38 +++++- src/niri.rs | 11 +- 6 files changed, 483 insertions(+), 115 deletions(-) (limited to 'src') diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 48c798aa..80dd2262 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -119,10 +119,8 @@ impl XdgShellHandler for State { self.niri.layout.toggle_full_width(); } if intersection.intersects(ResizeEdge::TOP_BOTTOM) { - // FIXME: don't activate once we can pass specific windows to actions. - self.niri.layout.activate_window(&window); self.niri.layer_shell_on_demand_focus = None; - self.niri.layout.reset_window_height(); + self.niri.layout.reset_window_height(Some(&window)); } // FIXME: granular. self.niri.queue_redraw_all(); diff --git a/src/input/mod.rs b/src/input/mod.rs index 91581642..e5820495 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -525,11 +525,29 @@ impl State { }); } } + Action::ScreenshotWindowById(id) => { + let mut windows = self.niri.layout.windows(); + let window = windows.find(|(_, m)| m.id().get() == id); + if let Some((Some(monitor), mapped)) = window { + let output = &monitor.output; + self.backend.with_primary_renderer(|renderer| { + if let Err(err) = self.niri.screenshot_window(renderer, output, mapped) { + warn!("error taking screenshot: {err:?}"); + } + }); + } + } Action::CloseWindow => { if let Some(mapped) = self.niri.layout.focus() { mapped.toplevel().send_close(); } } + Action::CloseWindowById(id) => { + let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); + if let Some((_, mapped)) = window { + mapped.toplevel().send_close(); + } + } Action::FullscreenWindow => { let focus = self.niri.layout.focus().map(|m| m.window.clone()); if let Some(window) = focus { @@ -538,6 +556,37 @@ impl State { self.niri.queue_redraw_all(); } } + Action::FullscreenWindowById(id) => { + let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); + let window = window.map(|(_, m)| m.window.clone()); + if let Some(window) = window { + self.niri.layout.toggle_fullscreen(&window); + // FIXME: granular + self.niri.queue_redraw_all(); + } + } + Action::FocusWindow(id) => { + let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); + let window = window.map(|(_, m)| m.window.clone()); + if let Some(window) = window { + let active_output = self.niri.layout.active_output().cloned(); + + self.niri.layout.activate_window(&window); + + let new_active = self.niri.layout.active_output().cloned(); + #[allow(clippy::collapsible_if)] + if new_active != active_output { + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&new_active.unwrap()); + } + } else { + self.maybe_warp_cursor_to_focus(); + } + + // FIXME: granular + self.niri.queue_redraw_all(); + } + } Action::SwitchLayout(action) => { let keyboard = &self.niri.seat.get_keyboard().unwrap(); keyboard.with_xkb_state(self, |mut state| match action { @@ -804,15 +853,25 @@ impl State { self.niri.queue_redraw_all(); } Action::MoveWindowToWorkspace(reference) => { - if let Some((output, index)) = self.niri.find_output_and_workspace_index(reference) + if let Some((mut output, index)) = + self.niri.find_output_and_workspace_index(reference) { + // The source output is always the active output, so if the target output is + // also the active output, we don't need to use move_to_output(). + if let Some(active) = self.niri.layout.active_output() { + if output.as_ref() == Some(active) { + output = None; + } + } + if let Some(output) = output { - self.niri.layout.move_to_workspace_on_output(&output, index); + self.niri.layout.move_to_output(None, &output, Some(index)); + if !self.maybe_warp_cursor_to_focus_centered() { self.move_cursor_to_output(&output); } } else { - self.niri.layout.move_to_workspace(index); + self.niri.layout.move_to_workspace(None, index); self.maybe_warp_cursor_to_focus(); } @@ -820,6 +879,51 @@ impl State { self.niri.queue_redraw_all(); } } + Action::MoveWindowToWorkspaceById { + window_id: id, + reference, + } => { + let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); + let window = window.map(|(_, m)| m.window.clone()); + if let Some(window) = window { + if let Some((output, index)) = + self.niri.find_output_and_workspace_index(reference) + { + let target_was_active = self + .niri + .layout + .active_output() + .map_or(false, |active| output.as_ref() == Some(active)); + + if let Some(output) = output { + self.niri + .layout + .move_to_output(Some(&window), &output, Some(index)); + + // If the active output changed (window was moved and focused). + #[allow(clippy::collapsible_if)] + if !target_was_active + && self.niri.layout.active_output() == Some(&output) + { + if !self.maybe_warp_cursor_to_focus_centered() { + self.move_cursor_to_output(&output); + } + } + } else { + self.niri.layout.move_to_workspace(Some(&window), index); + + // If we focused the target window. + let new_active_win = self.niri.layout.active_window(); + if new_active_win.map_or(false, |(win, _)| win.window == window) { + self.maybe_warp_cursor_to_focus(); + } + } + + // FIXME: granular + self.niri.queue_redraw_all(); + } + } + } Action::MoveColumnToWorkspaceDown => { self.niri.layout.move_column_to_workspace_down(); self.maybe_warp_cursor_to_focus(); @@ -833,8 +937,15 @@ impl State { self.niri.queue_redraw_all(); } Action::MoveColumnToWorkspace(reference) => { - if let Some((output, index)) = self.niri.find_output_and_workspace_index(reference) + if let Some((mut output, index)) = + self.niri.find_output_and_workspace_index(reference) { + if let Some(active) = self.niri.layout.active_output() { + if output.as_ref() == Some(active) { + output = None; + } + } + if let Some(output) = output { self.niri .layout @@ -864,8 +975,15 @@ impl State { self.niri.queue_redraw_all(); } Action::FocusWorkspace(reference) => { - if let Some((output, index)) = self.niri.find_output_and_workspace_index(reference) + if let Some((mut output, index)) = + self.niri.find_output_and_workspace_index(reference) { + if let Some(active) = self.niri.layout.active_output() { + if output.as_ref() == Some(active) { + output = None; + } + } + if let Some(output) = output { self.niri.layout.focus_output(&output); self.niri.layout.switch_workspace(index); @@ -959,7 +1077,7 @@ impl State { } Action::MoveWindowToMonitorLeft => { if let Some(output) = self.niri.output_left() { - self.niri.layout.move_to_output(&output); + self.niri.layout.move_to_output(None, &output, None); self.niri.layout.focus_output(&output); if !self.maybe_warp_cursor_to_focus_centered() { self.move_cursor_to_output(&output); @@ -968,7 +1086,7 @@ impl State { } Action::MoveWindowToMonitorRight => { if let Some(output) = self.niri.output_right() { - self.niri.layout.move_to_output(&output); + self.niri.layout.move_to_output(None, &output, None); self.niri.layout.focus_output(&output); if !self.maybe_warp_cursor_to_focus_centered() { self.move_cursor_to_output(&output); @@ -977,7 +1095,7 @@ impl State { } Action::MoveWindowToMonitorDown => { if let Some(output) = self.niri.output_down() { - self.niri.layout.move_to_output(&output); + self.niri.layout.move_to_output(None, &output, None); self.niri.layout.focus_output(&output); if !self.maybe_warp_cursor_to_focus_centered() { self.move_cursor_to_output(&output); @@ -986,7 +1104,7 @@ impl State { } Action::MoveWindowToMonitorUp => { if let Some(output) = self.niri.output_up() { - self.niri.layout.move_to_output(&output); + self.niri.layout.move_to_output(None, &output, None); self.niri.layout.focus_output(&output); if !self.maybe_warp_cursor_to_focus_centered() { self.move_cursor_to_output(&output); @@ -1033,10 +1151,24 @@ impl State { self.niri.layout.set_column_width(change); } Action::SetWindowHeight(change) => { - self.niri.layout.set_window_height(change); + self.niri.layout.set_window_height(None, change); + } + Action::SetWindowHeightById { id, change } => { + let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); + let window = window.map(|(_, m)| m.window.clone()); + if let Some(window) = window { + self.niri.layout.set_window_height(Some(&window), change); + } } Action::ResetWindowHeight => { - self.niri.layout.reset_window_height(); + self.niri.layout.reset_window_height(None); + } + Action::ResetWindowHeightById(id) => { + let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id); + let window = window.map(|(_, m)| m.window.clone()); + if let Some(window) = window { + self.niri.layout.reset_window_height(Some(&window)); + } } Action::ShowHotkeyOverlay => { if self.niri.hotkey_overlay.show() { @@ -1366,10 +1498,8 @@ impl State { self.niri.layout.toggle_full_width(); } if intersection.intersects(ResizeEdge::TOP_BOTTOM) { - // FIXME: don't activate once we can pass specific windows - // to actions. self.niri.layout.activate_window(&window); - self.niri.layout.reset_window_height(); + self.niri.layout.reset_window_height(Some(&window)); } // FIXME: granular. self.niri.queue_redraw_all(); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index fd1c5f7e..35f4a2c7 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1092,6 +1092,20 @@ impl Layout { Some(&mon.workspaces[mon.active_workspace_idx]) } + pub fn active_workspace_mut(&mut self) -> Option<&mut Workspace> { + let MonitorSet::Normal { + monitors, + active_monitor_idx, + .. + } = &mut self.monitor_set + else { + return None; + }; + + let mon = &mut monitors[*active_monitor_idx]; + Some(&mut mon.workspaces[mon.active_workspace_idx]) + } + pub fn active_window(&self) -> Option<(&W, &Output)> { let MonitorSet::Normal { monitors, @@ -1502,17 +1516,11 @@ impl Layout { monitor.move_to_workspace_down(); } - pub fn move_to_workspace(&mut self, idx: usize) { + pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) { let Some(monitor) = self.active_monitor() else { return; }; - monitor.move_to_workspace(idx); - } - - pub fn move_to_workspace_on_output(&mut self, output: &Output, idx: usize) { - self.move_to_output(output); - self.focus_output(output); - self.move_to_workspace(idx); + monitor.move_to_workspace(window, idx); } pub fn move_column_to_workspace_up(&mut self) { @@ -1537,7 +1545,7 @@ impl Layout { } pub fn move_column_to_workspace_on_output(&mut self, output: &Output, idx: usize) { - self.move_to_output(output); + self.move_column_to_output(output); self.focus_output(output); self.move_column_to_workspace(idx); } @@ -1943,18 +1951,38 @@ impl Layout { monitor.set_column_width(change); } - pub fn set_window_height(&mut self, change: SizeChange) { - let Some(monitor) = self.active_monitor() else { + pub fn set_window_height(&mut self, window: Option<&W::Id>, change: SizeChange) { + let workspace = if let Some(window) = window { + Some( + self.workspaces_mut() + .find(|ws| ws.has_window(window)) + .unwrap(), + ) + } else { + self.active_workspace_mut() + }; + + let Some(workspace) = workspace else { return; }; - monitor.set_window_height(change); + workspace.set_window_height(window, change); } - pub fn reset_window_height(&mut self) { - let Some(monitor) = self.active_monitor() else { + pub fn reset_window_height(&mut self, window: Option<&W::Id>) { + let workspace = if let Some(window) = window { + Some( + self.workspaces_mut() + .find(|ws| ws.has_window(window)) + .unwrap(), + ) + } else { + self.active_workspace_mut() + }; + + let Some(workspace) = workspace else { return; }; - monitor.reset_window_height(); + workspace.reset_window_height(window); } pub fn focus_output(&mut self, output: &Output) { @@ -1973,7 +2001,12 @@ impl Layout { } } - pub fn move_to_output(&mut self, output: &Output) { + pub fn move_to_output( + &mut self, + window: Option<&W::Id>, + output: &Output, + target_ws_idx: Option, + ) { if let MonitorSet::Normal { monitors, active_monitor_idx, @@ -1985,25 +2018,71 @@ impl Layout { .position(|mon| &mon.output == output) .unwrap(); - let current = &mut monitors[*active_monitor_idx]; - let ws = current.active_workspace(); - if !ws.has_windows() { + let (mon_idx, ws_idx, col_idx, tile_idx) = if let Some(window) = window { + monitors + .iter() + .enumerate() + .find_map(|(mon_idx, mon)| { + mon.workspaces.iter().enumerate().find_map(|(ws_idx, ws)| { + ws.columns.iter().enumerate().find_map(|(col_idx, col)| { + col.tiles + .iter() + .position(|tile| tile.window().id() == window) + .map(|tile_idx| (mon_idx, ws_idx, col_idx, tile_idx)) + }) + }) + }) + .unwrap() + } else { + let mon_idx = *active_monitor_idx; + let mon = &monitors[mon_idx]; + let ws_idx = mon.active_workspace_idx; + let ws = &mon.workspaces[ws_idx]; + + if ws.columns.is_empty() { + return; + } + + let col_idx = ws.active_column_idx; + let tile_idx = ws.columns[col_idx].active_tile_idx; + (mon_idx, ws_idx, col_idx, tile_idx) + }; + + let workspace_idx = target_ws_idx.unwrap_or(monitors[new_idx].active_workspace_idx); + if mon_idx == new_idx && ws_idx == workspace_idx { return; } - let column = &ws.columns[ws.active_column_idx]; + + let mon = &mut monitors[mon_idx]; + let ws = &mut mon.workspaces[ws_idx]; + let column = &ws.columns[col_idx]; let width = column.width; let is_full_width = column.is_full_width; + let activate = mon_idx == *active_monitor_idx + && ws_idx == mon.active_workspace_idx + && col_idx == ws.active_column_idx + && tile_idx == column.active_tile_idx; + let window = ws - .remove_tile_by_idx( - ws.active_column_idx, - column.active_tile_idx, - Transaction::new(), - None, - ) + .remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None) .into_window(); - let workspace_idx = monitors[new_idx].active_workspace_idx; - self.add_window_by_idx(new_idx, workspace_idx, window, true, width, is_full_width); + self.add_window_by_idx( + new_idx, + workspace_idx, + window, + activate, + width, + is_full_width, + ); + + let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else { + unreachable!() + }; + let mon = &mut monitors[mon_idx]; + if mon.workspace_switch.is_none() { + monitors[mon_idx].clean_up_workspaces(); + } } } @@ -2543,6 +2622,41 @@ impl Layout { let iter_no_outputs = iter_no_outputs.into_iter().flatten(); iter_normal.chain(iter_no_outputs) } + + pub fn workspaces_mut(&mut self) -> impl Iterator> + '_ { + let iter_normal; + let iter_no_outputs; + + match &mut self.monitor_set { + MonitorSet::Normal { monitors, .. } => { + let it = monitors + .iter_mut() + .flat_map(|mon| mon.workspaces.iter_mut()); + + iter_normal = Some(it); + iter_no_outputs = None; + } + MonitorSet::NoOutputs { workspaces } => { + let it = workspaces.iter_mut(); + + iter_normal = None; + iter_no_outputs = Some(it); + } + } + + let iter_normal = iter_normal.into_iter().flatten(); + let iter_no_outputs = iter_no_outputs.into_iter().flatten(); + iter_normal.chain(iter_no_outputs) + } + + pub fn windows(&self) -> impl Iterator>, &W)> { + self.workspaces() + .flat_map(|(mon, _, ws)| ws.windows().map(move |win| (mon, win))) + } + + pub fn has_window(&self, window: &W::Id) -> bool { + self.windows().any(|(_, win)| win.id() == window) + } } impl Default for MonitorSet { @@ -2892,19 +3006,39 @@ mod tests { FocusWorkspacePrevious, MoveWindowToWorkspaceDown, MoveWindowToWorkspaceUp, - MoveWindowToWorkspace(#[proptest(strategy = "0..=4usize")] usize), + MoveWindowToWorkspace { + #[proptest(strategy = "proptest::option::of(1..=5usize)")] + window_id: Option, + #[proptest(strategy = "0..=4usize")] + workspace_idx: usize, + }, MoveColumnToWorkspaceDown, MoveColumnToWorkspaceUp, MoveColumnToWorkspace(#[proptest(strategy = "0..=4usize")] usize), MoveWorkspaceDown, MoveWorkspaceUp, - MoveWindowToOutput(#[proptest(strategy = "1..=5u8")] u8), + MoveWindowToOutput { + #[proptest(strategy = "proptest::option::of(1..=5usize)")] + window_id: Option, + #[proptest(strategy = "1..=5u8")] + output_id: u8, + #[proptest(strategy = "proptest::option::of(0..=4usize)")] + target_ws_idx: Option, + }, MoveColumnToOutput(#[proptest(strategy = "1..=5u8")] u8), SwitchPresetColumnWidth, MaximizeColumn, SetColumnWidth(#[proptest(strategy = "arbitrary_size_change()")] SizeChange), - SetWindowHeight(#[proptest(strategy = "arbitrary_size_change()")] SizeChange), - ResetWindowHeight, + SetWindowHeight { + #[proptest(strategy = "proptest::option::of(1..=5usize)")] + id: Option, + #[proptest(strategy = "arbitrary_size_change()")] + change: SizeChange, + }, + ResetWindowHeight { + #[proptest(strategy = "proptest::option::of(1..=5usize)")] + id: Option, + }, Communicate(#[proptest(strategy = "1..=5usize")] usize), MoveWorkspaceToOutput(#[proptest(strategy = "1..=5u8")] u8), ViewOffsetGestureBegin { @@ -3279,17 +3413,34 @@ mod tests { Op::FocusWorkspacePrevious => layout.switch_workspace_previous(), Op::MoveWindowToWorkspaceDown => layout.move_to_workspace_down(), Op::MoveWindowToWorkspaceUp => layout.move_to_workspace_up(), - Op::MoveWindowToWorkspace(idx) => layout.move_to_workspace(idx), + Op::MoveWindowToWorkspace { + window_id, + workspace_idx, + } => { + let window_id = window_id.filter(|id| { + layout + .active_monitor() + .map_or(false, |mon| mon.has_window(id)) + }); + layout.move_to_workspace(window_id.as_ref(), workspace_idx); + } Op::MoveColumnToWorkspaceDown => layout.move_column_to_workspace_down(), Op::MoveColumnToWorkspaceUp => layout.move_column_to_workspace_up(), Op::MoveColumnToWorkspace(idx) => layout.move_column_to_workspace(idx), - Op::MoveWindowToOutput(id) => { + Op::MoveWindowToOutput { + window_id, + output_id: id, + target_ws_idx, + } => { let name = format!("output{id}"); let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { return; }; + let mon = layout.monitor_for_output(&output).unwrap(); - layout.move_to_output(&output); + let window_id = window_id.filter(|id| layout.has_window(id)); + let target_ws_idx = target_ws_idx.filter(|idx| mon.workspaces.len() > *idx); + layout.move_to_output(window_id.as_ref(), &output, target_ws_idx); } Op::MoveColumnToOutput(id) => { let name = format!("output{id}"); @@ -3304,8 +3455,14 @@ mod tests { Op::SwitchPresetColumnWidth => layout.toggle_width(), Op::MaximizeColumn => layout.toggle_full_width(), Op::SetColumnWidth(change) => layout.set_column_width(change), - Op::SetWindowHeight(change) => layout.set_window_height(change), - Op::ResetWindowHeight => layout.reset_window_height(), + Op::SetWindowHeight { id, change } => { + let id = id.filter(|id| layout.has_window(id)); + layout.set_window_height(id.as_ref(), change); + } + Op::ResetWindowHeight { id } => { + let id = id.filter(|id| layout.has_window(id)); + layout.reset_window_height(id.as_ref()); + } Op::Communicate(id) => { let mut update = false; match &mut layout.monitor_set { @@ -3502,8 +3659,14 @@ mod tests { Op::FocusWorkspace(2), Op::MoveWindowToWorkspaceDown, Op::MoveWindowToWorkspaceUp, - Op::MoveWindowToWorkspace(1), - Op::MoveWindowToWorkspace(2), + Op::MoveWindowToWorkspace { + window_id: None, + workspace_idx: 1, + }, + Op::MoveWindowToWorkspace { + window_id: None, + workspace_idx: 2, + }, Op::MoveColumnToWorkspaceDown, Op::MoveColumnToWorkspaceUp, Op::MoveColumnToWorkspace(1), @@ -3575,7 +3738,11 @@ mod tests { bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)), min_max_size: Default::default(), }, - Op::MoveWindowToOutput(2), + Op::MoveWindowToOutput { + window_id: None, + output_id: 2, + target_ws_idx: None, + }, Op::FocusOutput(1), Op::Communicate(1), Op::Communicate(2), @@ -3684,9 +3851,18 @@ mod tests { Op::FocusWorkspace(3), Op::MoveWindowToWorkspaceDown, Op::MoveWindowToWorkspaceUp, - Op::MoveWindowToWorkspace(1), - Op::MoveWindowToWorkspace(2), - Op::MoveWindowToWorkspace(3), + Op::MoveWindowToWorkspace { + window_id: None, + workspace_idx: 1, + }, + Op::MoveWindowToWorkspace { + window_id: None, + workspace_idx: 2, + }, + Op::MoveWindowToWorkspace { + window_id: None, + workspace_idx: 3, + }, Op::MoveColumnToWorkspaceDown, Op::MoveColumnToWorkspaceUp, Op::MoveColumnToWorkspace(1), @@ -3798,7 +3974,18 @@ mod tests { bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)), min_max_size: Default::default(), }, - Op::MoveWindowToWorkspace(2), + Op::AddOutput(2), + Op::FocusOutput(2), + Op::AddWindow { + id: 1, + bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)), + min_max_size: Default::default(), + }, + Op::RemoveOutput(1), + Op::MoveWindowToWorkspace { + window_id: Some(0), + workspace_idx: 2, + }, ]; let mut layout = Layout::default(); @@ -3810,7 +3997,7 @@ mod tests { unreachable!() }; - assert!(monitors[0].workspaces[0].has_windows()); + assert!(monitors[0].workspaces[1].has_windows()); } #[test] @@ -3847,7 +4034,10 @@ mod tests { bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)), min_max_size: Default::default(), }, - Op::SetWindowHeight(SizeChange::AdjustProportion(-1e129)), + Op::SetWindowHeight { + id: None, + change: SizeChange::AdjustProportion(-1e129), + }, ]; let mut options = Options::default(); @@ -4311,9 +4501,15 @@ mod tests { min_max_size: Default::default(), }, Op::ConsumeOrExpelWindowLeft, - Op::SetWindowHeight(SizeChange::SetFixed(100)), + Op::SetWindowHeight { + id: None, + change: SizeChange::SetFixed(100), + }, Op::FocusWindowUp, - Op::SetWindowHeight(SizeChange::SetFixed(200)), + Op::SetWindowHeight { + id: None, + change: SizeChange::SetFixed(200), + }, ]; check_ops(&ops); @@ -4340,10 +4536,16 @@ mod tests { min_max_size: Default::default(), }, Op::ConsumeOrExpelWindowLeft, - Op::SetWindowHeight(SizeChange::SetFixed(100)), + Op::SetWindowHeight { + id: None, + change: SizeChange::SetFixed(100), + }, Op::Communicate(2), Op::FocusWindowUp, - Op::SetWindowHeight(SizeChange::SetFixed(200)), + Op::SetWindowHeight { + id: None, + change: SizeChange::SetFixed(200), + }, Op::Communicate(1), Op::CloseWindow(0), Op::CloseWindow(1), @@ -4373,10 +4575,16 @@ mod tests { min_max_size: Default::default(), }, Op::ConsumeOrExpelWindowLeft, - Op::SetWindowHeight(SizeChange::SetFixed(100)), + Op::SetWindowHeight { + id: None, + change: SizeChange::SetFixed(100), + }, Op::Communicate(2), Op::FocusWindowUp, - Op::SetWindowHeight(SizeChange::SetFixed(200)), + Op::SetWindowHeight { + id: None, + change: SizeChange::SetFixed(200), + }, Op::Communicate(1), Op::CloseWindow(0), Op::FullscreenWindow(1), diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index 6635de9b..3227c9c4 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -133,6 +133,14 @@ impl Monitor { &mut self.workspaces[self.active_workspace_idx] } + pub fn windows(&self) -> impl Iterator { + self.workspaces.iter().flat_map(|ws| ws.windows()) + } + + pub fn has_window(&self, window: &W::Id) -> bool { + self.windows().any(|win| win.id() == window) + } + fn activate_workspace(&mut self, idx: usize) { if self.active_workspace_idx == idx { return; @@ -489,8 +497,32 @@ impl Monitor { self.add_window(new_idx, window, true, width, is_full_width); } - pub fn move_to_workspace(&mut self, idx: usize) { - let source_workspace_idx = self.active_workspace_idx; + pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) { + let (source_workspace_idx, col_idx, tile_idx) = if let Some(window) = window { + self.workspaces + .iter() + .enumerate() + .find_map(|(ws_idx, ws)| { + ws.columns.iter().enumerate().find_map(|(col_idx, col)| { + col.tiles + .iter() + .position(|tile| tile.window().id() == window) + .map(|tile_idx| (ws_idx, col_idx, tile_idx)) + }) + }) + .unwrap() + } else { + let ws_idx = self.active_workspace_idx; + + let ws = &self.workspaces[ws_idx]; + if ws.columns.is_empty() { + return; + } + + let col_idx = ws.active_column_idx; + let tile_idx = ws.columns[col_idx].active_tile_idx; + (ws_idx, col_idx, tile_idx) + }; let new_idx = min(idx, self.workspaces.len() - 1); if new_idx == source_workspace_idx { @@ -498,28 +530,22 @@ impl Monitor { } let workspace = &mut self.workspaces[source_workspace_idx]; - if workspace.columns.is_empty() { - return; - } - - let column = &workspace.columns[workspace.active_column_idx]; + let column = &workspace.columns[col_idx]; let width = column.width; let is_full_width = column.is_full_width; + let activate = source_workspace_idx == self.active_workspace_idx + && col_idx == workspace.active_column_idx + && tile_idx == column.active_tile_idx; + let window = workspace - .remove_tile_by_idx( - workspace.active_column_idx, - column.active_tile_idx, - Transaction::new(), - None, - ) + .remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None) .into_window(); - self.add_window(new_idx, window, true, width, is_full_width); - - // Don't animate this action. - self.workspace_switch = None; + self.add_window(new_idx, window, activate, width, is_full_width); - self.clean_up_workspaces(); + if self.workspace_switch.is_none() { + self.clean_up_workspaces(); + } } pub fn move_column_to_workspace_up(&mut self) { @@ -571,11 +597,6 @@ impl Monitor { let column = workspace.remove_column_by_idx(workspace.active_column_idx); self.add_column(new_idx, column, true); - - // Don't animate this action. - self.workspace_switch = None; - - self.clean_up_workspaces(); } pub fn switch_workspace_up(&mut self) { @@ -727,14 +748,6 @@ impl Monitor { self.active_workspace().set_column_width(change); } - pub fn set_window_height(&mut self, change: SizeChange) { - self.active_workspace().set_window_height(change); - } - - pub fn reset_window_height(&mut self) { - self.active_workspace().reset_window_height(); - } - pub fn move_workspace_down(&mut self) { let new_idx = min(self.active_workspace_idx + 1, self.workspaces.len() - 1); if new_idx == self.active_workspace_idx { diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index b80cb95c..0064b8e3 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -2303,24 +2303,50 @@ impl Workspace { cancel_resize_for_column(&mut self.interactive_resize, col); } - pub fn set_window_height(&mut self, change: SizeChange) { + pub fn set_window_height(&mut self, window: Option<&W::Id>, change: SizeChange) { if self.columns.is_empty() { return; } - let col = &mut self.columns[self.active_column_idx]; - col.set_window_height(change, None, true); + let (col, tile_idx) = if let Some(window) = window { + self.columns + .iter_mut() + .find_map(|col| { + col.tiles + .iter() + .position(|tile| tile.window().id() == window) + .map(|tile_idx| (col, Some(tile_idx))) + }) + .unwrap() + } else { + (&mut self.columns[self.active_column_idx], None) + }; + + col.set_window_height(change, tile_idx, true); cancel_resize_for_column(&mut self.interactive_resize, col); } - pub fn reset_window_height(&mut self) { + pub fn reset_window_height(&mut self, window: Option<&W::Id>) { if self.columns.is_empty() { return; } - let col = &mut self.columns[self.active_column_idx]; - col.reset_window_height(None, true); + let (col, tile_idx) = if let Some(window) = window { + self.columns + .iter_mut() + .find_map(|col| { + col.tiles + .iter() + .position(|tile| tile.window().id() == window) + .map(|tile_idx| (col, Some(tile_idx))) + }) + .unwrap() + } else { + (&mut self.columns[self.active_column_idx], None) + }; + + col.reset_window_height(tile_idx, true); cancel_resize_for_column(&mut self.interactive_resize, col); } diff --git a/src/niri.rs b/src/niri.rs index f7963742..a5cedef4 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -2447,15 +2447,8 @@ impl Niri { } }; - // FIXME: when we do fixes for no connected outputs, this will need fixing too. - let active_workspace = self.layout.active_workspace()?; - - if target_workspace.current_output() == active_workspace.current_output() { - return Some((None, target_workspace_index)); - } - let target_output = target_workspace.current_output()?; - - Some((Some(target_output.clone()), target_workspace_index)) + let target_output = target_workspace.current_output(); + Some((target_output.cloned(), target_workspace_index)) } pub fn output_down(&self) -> Option { -- cgit