Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ With `oniri` started in "first window only" mode in your niri configuration file

<https://github.com/user-attachments/assets/d97f1416-a5f0-452c-b2d4-16b6af12631f>

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:

<https://github.com/user-attachments/assets/d8ebfab0-3d88-44f9-9613-96b734b572ee>

**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 <https://github.com/Antiz96/oniri/issues/3> for more details).
In the mean time, things *should* still work just fine for most "classic" setups though!

Expand Down
3 changes: 3 additions & 0 deletions doc/man/oniri.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -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* <number>
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).

Expand Down
1 change: 1 addition & 0 deletions res/completions/oniri.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions res/completions/oniri.fish
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
1 change: 1 addition & 0 deletions res/completions/oniri.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -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]'
Expand Down
3 changes: 3 additions & 0 deletions src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand Down
69 changes: 43 additions & 26 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -99,50 +109,57 @@ 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 {
continue;
}

// 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
_ => {}
Expand Down
50 changes: 22 additions & 28 deletions src/maximize_window.rs
Original file line number Diff line number Diff line change
@@ -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<u64, Vec<u64>>,
state: &niri_ipc::state::EventStreamState, // https://github.com/Antiz96/oniri/issues/3
outputs: &HashMap<String, Output>, // 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(())
}