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/input/mod.rs | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 14 deletions(-) (limited to 'src/input') 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(); -- cgit