diff --git a/README.md b/README.md index 9f78352..9a18a00 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,10 @@ With `oniri` started in "first window only" mode in your niri configuration file +With `oniri` started in "tiling layout" mode in your niri configuration file (`spawn-sh-at-startup "oniri --tiling-layout"` in `~/.config/niri/config.kdl`), the first window gets unmaximized when a second one is opened, mimicking the behavior of a tiling compositor: + + + **Note:** Due to current limitations of the niri IPC, "buggy" behaviors (e.g. window not being correctly maximized) *may* be expected in specific setups / edgy cases. Those limitations should *hopefully* be addressed on the niri IPC side at some point, allowing to fix those eventual "buggy" behaviors once and for all (see for more details). In the mean time, things *should* still work just fine for most "classic" setups though! diff --git a/doc/man/oniri.1.scd b/doc/man/oniri.1.scd index 10a2c8e..f95693a 100644 --- a/doc/man/oniri.1.scd +++ b/doc/man/oniri.1.scd @@ -17,6 +17,9 @@ Oniri is a tool that automatically maximizes the *on*ly window of a *niri* works *-F, --first-only* Only maximize the first window opened, do no act on the last remaining one. +*-T, --tiling-layout* + Unmaximize the first window when opening a second one, like in a tiling compositor. + *-H, --height-tolerance* Set the height size tolerance (in pixels) when comparing the window size to the output size to determine if the window is maximized or not (https://github.com/Antiz96/oniri/issues/3). diff --git a/res/completions/oniri.bash b/res/completions/oniri.bash index 6957872..9f28c03 100644 --- a/res/completions/oniri.bash +++ b/res/completions/oniri.bash @@ -2,6 +2,7 @@ _oniri() { local arg="${2}" local -a opts opts=('-F --first-only + -T --tiling-layout -H --height-tolerance -W --width-tolerance -h --help diff --git a/res/completions/oniri.fish b/res/completions/oniri.fish index 098c048..d5b0ffd 100644 --- a/res/completions/oniri.fish +++ b/res/completions/oniri.fish @@ -1,6 +1,7 @@ complete -c oniri -f complete -c oniri -s F -l first-only -d 'Only maximize the first window opened, do no act on the last remaining one' +complete -c oniri -s T -l tiling-layout -d 'Unmaximize the first window when opening a second one, like in a tiling compositor' complete -c oniri -s H -l height-tolerance -d 'Set the height size tolerance (in pixels) when comparing the window size to the output size to determine if the window is maximized or not' complete -c oniri -s W -l width-tolerance -d 'Set the width size tolerance (in pixels) when comparing the window size to the output size to determine if the window is maximized or not' complete -c oniri -s h -l help -d 'Display the help message' diff --git a/res/completions/oniri.zsh b/res/completions/oniri.zsh index 5fee730..c65166c 100644 --- a/res/completions/oniri.zsh +++ b/res/completions/oniri.zsh @@ -3,6 +3,7 @@ local -a opts opts=( {-F,--first-only}'[Only maximize the first window opened, do no act on the last remaining one]' + {-T,--tiling-layout}'[Unmaximize the first window when opening a second one, like in a tiling compositor]' {-H,--height-tolerance}'[Set the height size tolerance (in pixels) when comparing the window size to the output size to determine if the window is maximized or not]' {-W,--width-tolerance}'[Set the width size tolerance (in pixels) when comparing the window size to the output size to determine if the window is maximized or not]' {-h,--help}'[Display the help message]' diff --git a/src/help.rs b/src/help.rs index 30aa679..57d999a 100644 --- a/src/help.rs +++ b/src/help.rs @@ -12,6 +12,9 @@ pub fn show_help() { println!( " -F, --first-only Only maximize the first opened window, do not act on the last remaining one" ); + println!( + " -T, --tiling-layout Unmaximize the first window when opening a second one, like in a tiling compositor" + ); println!( " -H, --height-tolerance Set the height size tolerance (in pixels) when comparing the window size to the output size to determine if the window is maximized or not" // https://github.com/Antiz96/oniri/issues/3 ); diff --git a/src/main.rs b/src/main.rs index e8e9718..5d1f05c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,8 @@ use log::{debug, info}; use niri_ipc::{Event, state::EventStreamState, state::EventStreamStatePart}; use std::env; +use crate::{maximize_window::maximize_window, size_compare::is_maximized}; + // Import internal modules mod help; mod maximize_window; @@ -43,6 +45,14 @@ fn main() -> anyhow::Result<()> { info!("Running in first-only mode: only acting on the first window"); } + // Run in "tiling-layout" mode if the -T / --tiling-layout arg is passed + let tiling_layout = has_arg("-T") || has_arg("--tiling-layout"); + if tiling_layout { + info!( + "Running in tiling-layout mode: Opening a second window will collapse the first window" + ); + } + // Set pixel tolerances for window/output size comparison // This can be dropped once https://github.com/Antiz96/oniri/issues/3 is resolved let (tol_h, tol_w) = size_compare::set_tolerances(); @@ -99,34 +109,42 @@ fn main() -> anyhow::Result<()> { for windows in workspace_windows.values_mut() { windows.retain(|&wid| wid != id); } - workspace_windows.entry(ws).or_default().push(id); + + let windows = workspace_windows.entry(ws).or_default(); + windows.push(id); // Check if there's only one window in the workspace/window(s) map & maximize it if so - maximize_window::maximize_window_if_alone( - ws, - &workspace_windows, - &state, // https://github.com/Antiz96/oniri/issues/3 - &outputs, // https://github.com/Antiz96/oniri/issues/3 - tol_h, // https://github.com/Antiz96/oniri/issues/3 - tol_w, // https://github.com/Antiz96/oniri/issues/3 - &mut action_socket, - )?; + match windows.len() { + 1 => { + let first_window = windows[0]; + if !is_maximized(&state, &outputs, first_window, tol_h, tol_w) { + maximize_window(&mut action_socket, &state, first_window)?; + } + } + + // If running in tiling layout mode, un-maximize the first window when a second one is opened + 2 if tiling_layout => { + let first_window = windows[0]; + if is_maximized(&state, &outputs, first_window, tol_h, tol_w) { + maximize_window(&mut action_socket, &state, first_window)?; + } + } + _ => {} + } } // Window being closed Event::WindowClosed { id } => { debug!("Trigger Event: Window Closed"); - let Some(ws) = workspace_windows - .iter() - .find_map(|(&ws, windows)| windows.contains(&id).then_some(ws)) + let Some((_, windows)) = workspace_windows + .iter_mut() + .find(|(_, windows)| windows.contains(&id)) else { continue; }; - // Update the workspace/window(s) map - if let Some(windows) = workspace_windows.get_mut(&ws) { - windows.retain(|&wid| wid != id); - } + // Update the workspace vector + windows.retain(|&wid| wid != id); // Skip if the -F / --first-only arg is passed if first_only { @@ -134,15 +152,14 @@ fn main() -> anyhow::Result<()> { } // Check if there's only one window in the workspace/window(s) map & maximize it if so - maximize_window::maximize_window_if_alone( - ws, - &workspace_windows, - &state, // https://github.com/Antiz96/oniri/issues/3 - &outputs, // https://github.com/Antiz96/oniri/issues/3 - tol_h, // https://github.com/Antiz96/oniri/issues/3 - tol_w, // https://github.com/Antiz96/oniri/issues/3 - &mut action_socket, - )?; + if windows.len() != 1 { + continue; + } + + let id = windows[0]; + if !is_maximized(&state, &outputs, id, tol_h, tol_w) { + maximize_window(&mut action_socket, &state, id)?; + } } // Ignore other events _ => {} diff --git a/src/maximize_window.rs b/src/maximize_window.rs index 496dbe7..0d19021 100644 --- a/src/maximize_window.rs +++ b/src/maximize_window.rs @@ -1,38 +1,32 @@ -//! Check if there's only one window in the workspace/window(s) map & maximize it if so, -//! unless it's maximized already (https://github.com/Antiz96/oniri/issues/3) +//! Helper for maximizing a window, since before maximizing the window must be focused. // Import external modules use log::info; -use niri_ipc::{Output, Request, socket::Socket}; -use std::collections::HashMap; +use niri_ipc::state::EventStreamState; +use niri_ipc::{Request, socket::Socket}; -// Import internal modules -use crate::size_compare::is_maximized; // https://github.com/Antiz96/oniri/issues/3 - -pub fn maximize_window_if_alone( - workspace_id: u64, - workspace_windows: &HashMap>, - state: &niri_ipc::state::EventStreamState, // https://github.com/Antiz96/oniri/issues/3 - outputs: &HashMap, // https://github.com/Antiz96/oniri/issues/3 - tol_h: i32, // https://github.com/Antiz96/oniri/issues/3 - tol_w: i32, // https://github.com/Antiz96/oniri/issues/3 - action_socket: &mut Socket, +pub fn maximize_window( + socket: &mut Socket, + state: &EventStreamState, + window_id: u64, ) -> anyhow::Result<()> { - let Some(windows) = workspace_windows.get(&workspace_id) else { + // We need this information to restore focus state after maximizing @window_id + let Some(focused_id) = state + .windows + .windows + .values() + .find_map(|window| window.is_focused.then_some(window.id)) + else { return Ok(()); }; - if windows.len() != 1 { - return Ok(()); - } - - let id = windows[0]; - // https://github.com/Antiz96/oniri/issues/3 - if !is_maximized(state, outputs, id, tol_h, tol_w) { - let _ = action_socket.send(Request::Action(niri_ipc::Action::FocusWindow { id })); - let _ = action_socket.send(Request::Action(niri_ipc::Action::MaximizeColumn {})); - info!("Maximized window {}", id); - } - + let _ = socket.send(Request::Action(niri_ipc::Action::FocusWindow { + id: window_id, + })); + let _ = socket.send(Request::Action(niri_ipc::Action::MaximizeColumn {})); + let _ = socket.send(Request::Action(niri_ipc::Action::FocusWindow { + id: focused_id, + })); + info!("Maximized window {}", window_id); Ok(()) }