mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
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.
This commit is contained in:
@@ -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();
|
||||
|
||||
+144
-14
@@ -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();
|
||||
|
||||
+260
-52
@@ -1092,6 +1092,20 @@ impl<W: LayoutElement> Layout<W> {
|
||||
Some(&mon.workspaces[mon.active_workspace_idx])
|
||||
}
|
||||
|
||||
pub fn active_workspace_mut(&mut self) -> Option<&mut Workspace<W>> {
|
||||
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<W: LayoutElement> Layout<W> {
|
||||
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<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
|
||||
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<W: LayoutElement> Layout<W> {
|
||||
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<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
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<usize>,
|
||||
) {
|
||||
if let MonitorSet::Normal {
|
||||
monitors,
|
||||
active_monitor_idx,
|
||||
@@ -1985,25 +2018,71 @@ impl<W: LayoutElement> Layout<W> {
|
||||
.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<W: LayoutElement> Layout<W> {
|
||||
let iter_no_outputs = iter_no_outputs.into_iter().flatten();
|
||||
iter_normal.chain(iter_no_outputs)
|
||||
}
|
||||
|
||||
pub fn workspaces_mut(&mut self) -> impl Iterator<Item = &mut Workspace<W>> + '_ {
|
||||
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<Item = (Option<&Monitor<W>>, &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<W: LayoutElement> Default for MonitorSet<W> {
|
||||
@@ -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<usize>,
|
||||
#[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<usize>,
|
||||
#[proptest(strategy = "1..=5u8")]
|
||||
output_id: u8,
|
||||
#[proptest(strategy = "proptest::option::of(0..=4usize)")]
|
||||
target_ws_idx: Option<usize>,
|
||||
},
|
||||
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<usize>,
|
||||
#[proptest(strategy = "arbitrary_size_change()")]
|
||||
change: SizeChange,
|
||||
},
|
||||
ResetWindowHeight {
|
||||
#[proptest(strategy = "proptest::option::of(1..=5usize)")]
|
||||
id: Option<usize>,
|
||||
},
|
||||
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),
|
||||
|
||||
+44
-31
@@ -133,6 +133,14 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
&mut self.workspaces[self.active_workspace_idx]
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> impl Iterator<Item = &W> {
|
||||
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<W: LayoutElement> Monitor<W> {
|
||||
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<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
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);
|
||||
self.add_window(new_idx, window, activate, width, is_full_width);
|
||||
|
||||
// Don't animate this action.
|
||||
self.workspace_switch = None;
|
||||
|
||||
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<W: LayoutElement> Monitor<W> {
|
||||
|
||||
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<W: LayoutElement> Monitor<W> {
|
||||
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 {
|
||||
|
||||
+32
-6
@@ -2303,24 +2303,50 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
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);
|
||||
}
|
||||
|
||||
+2
-9
@@ -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<Output> {
|
||||
|
||||
Reference in New Issue
Block a user