diff options
Diffstat (limited to 'users/tazjin/niri-reap')
-rw-r--r-- | users/tazjin/niri-reap/.gitignore | 1 | ||||
-rw-r--r-- | users/tazjin/niri-reap/Cargo.lock | 104 | ||||
-rw-r--r-- | users/tazjin/niri-reap/Cargo.toml | 7 | ||||
-rw-r--r-- | users/tazjin/niri-reap/README.md | 20 | ||||
-rw-r--r-- | users/tazjin/niri-reap/default.nix | 13 | ||||
-rw-r--r-- | users/tazjin/niri-reap/src/main.rs | 93 |
6 files changed, 238 insertions, 0 deletions
diff --git a/users/tazjin/niri-reap/.gitignore b/users/tazjin/niri-reap/.gitignore new file mode 100644 index 000000000000..2f7896d1d136 --- /dev/null +++ b/users/tazjin/niri-reap/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/users/tazjin/niri-reap/Cargo.lock b/users/tazjin/niri-reap/Cargo.lock new file mode 100644 index 000000000000..e7916c5b3acd --- /dev/null +++ b/users/tazjin/niri-reap/Cargo.lock @@ -0,0 +1,104 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "niri-ipc" +version = "0.1.9" +source = "git+https://github.com/YaLTeR/niri.git#6a48728ffb1e638839b07f9ab2f06b2adb41dc61" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "niri-reap" +version = "0.1.0" +dependencies = [ + "niri-ipc", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" diff --git a/users/tazjin/niri-reap/Cargo.toml b/users/tazjin/niri-reap/Cargo.toml new file mode 100644 index 000000000000..5f6677196333 --- /dev/null +++ b/users/tazjin/niri-reap/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "niri-reap" +version = "0.1.0" +edition = "2021" + +[dependencies] +niri-ipc = { git = "https://github.com/YaLTeR/niri.git", version = "0.1.9" } diff --git a/users/tazjin/niri-reap/README.md b/users/tazjin/niri-reap/README.md new file mode 100644 index 000000000000..207a087657fa --- /dev/null +++ b/users/tazjin/niri-reap/README.md @@ -0,0 +1,20 @@ +niri-reap +========= + +Tiny, MIT-licensed companion program for [niri](https://github.com/YaLTeR/niri). + +I don't use workspaces in my workflow, but when disconnecting an external +screen, the workspaces that it was displaying are moved to the remaining screen. + +This program "reaps" all windows on workspaces except the currently active one, +and moves them all to the current workspace. + +## Usage + +If you have the full TVL monorepo, just `mg run //users/tazjin/niri-reap`. There +is no configuration, and there are no flags. + +If you don't have the TVL monorepo and just want `niri-reap`, do this: + +1. Get the code: `git clone https://code.tvl.fyi/depot.git:/users/tazjin/niri-reap.git` +2. Run the code: `cargo run` diff --git a/users/tazjin/niri-reap/default.nix b/users/tazjin/niri-reap/default.nix new file mode 100644 index 000000000000..80c82f475a2f --- /dev/null +++ b/users/tazjin/niri-reap/default.nix @@ -0,0 +1,13 @@ +{ depot, pkgs, ... }: + +pkgs.rustPlatform.buildRustPackage { + name = "niri-reap"; + src = depot.third_party.gitignoreSource ./.; + + cargoLock = { + lockFile = ./Cargo.lock; + outputHashes = { + "niri-ipc-0.1.9" = "sha256:1s294bw62mmckq9xyfzgw4p2nvkzday4k276j60m668prhlfp071"; + }; + }; +} diff --git a/users/tazjin/niri-reap/src/main.rs b/users/tazjin/niri-reap/src/main.rs new file mode 100644 index 000000000000..315a5015d413 --- /dev/null +++ b/users/tazjin/niri-reap/src/main.rs @@ -0,0 +1,93 @@ +use niri_ipc::socket::Socket; +use niri_ipc::{Action, Reply, Request, Response, Window, Workspace}; + +fn sock() -> Socket { + Socket::connect().expect("could not connect to Niri socket") +} + +fn list_workspaces() -> Vec<Workspace> { + let (reply, _) = sock() + .send(Request::Workspaces) + .expect("failed to send workspace request"); + + match reply { + Reply::Err(err) => panic!("failed to list workspaces: {}", err), + Reply::Ok(Response::Workspaces(w)) => w, + Reply::Ok(other) => panic!("unexpected reply from Niri: {:#?}", other), + } +} + +fn list_windows() -> Vec<Window> { + let (reply, _) = sock() + .send(Request::Windows) + .expect("failed to send window request"); + + match reply { + Reply::Err(err) => panic!("failed to list windows: {}", err), + Reply::Ok(Response::Windows(w)) => w, + Reply::Ok(other) => panic!("unexpected reply from Niri: {:#?}", other), + } +} + +fn reap_window(window: u64, workspace: u64) { + let (reply, _) = sock() + .send(Request::Action(Action::MoveWindowToWorkspace { + window_id: Some(window), + reference: niri_ipc::WorkspaceReferenceArg::Id(workspace), + })) + .expect("failed to send window move request"); + + reply.expect("failed to move window to workspace"); +} + +fn get_active_workspace(workspaces: &[Workspace]) -> &Workspace { + workspaces + .iter() + .filter(|w| w.is_focused) + .next() + .expect("expected an active workspace") +} + +fn move_workspace_up() { + let (result, _) = sock() + .send(Request::Action(Action::MoveWorkspaceUp {})) + .expect("failed to send workspace move command"); + + result.expect("failed to move workspace up"); +} + +fn main() { + let mut workspaces = list_workspaces(); + let mut active_workspace = get_active_workspace(&workspaces); + + // Ensure that the current workspace is the first one, to avoid issues with + // indices changing during the window moves. + while active_workspace.idx > 1 { + move_workspace_up(); + workspaces = list_workspaces(); + active_workspace = get_active_workspace(&workspaces); + } + + let orphan_workspaces = workspaces + .iter() + .filter(|w| w.output == active_workspace.output) + .filter(|w| w.idx > 1) + .map(|w| w.id) + .collect::<Vec<_>>(); + + if orphan_workspaces.is_empty() { + return; + } + + let reapable = list_windows() + .into_iter() + .filter(|w| match w.workspace_id { + Some(id) => orphan_workspaces.contains(&id), + None => true, + }) + .collect::<Vec<_>>(); + + for window in reapable.iter().rev() { + reap_window(window.id, active_workspace.id); + } +} |