feat(tazjin/niri-reap): add a workspace compacting tool
I don't use workspaces and don't have them bound to anything in my Niri configuration. However, when an external screen is unplugged, its workspace (and windows) move to one of the remaining outputs. This adds a tool that makes the windows available again by "reaping" them from the other workspaces and moving them to the current one. For starters I'll bind this to a key and see how it works in practice. Change-Id: I18b2d60e93c8397dd637cdc426b4e46af5725558 Reviewed-on: https://cl.tvl.fyi/c/depot/+/12451 Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
This commit is contained in:
parent
8206f68aea
commit
158ba0d607
5 changed files with 201 additions and 0 deletions
1
users/tazjin/niri-reap/.gitignore
vendored
Normal file
1
users/tazjin/niri-reap/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
target/
|
104
users/tazjin/niri-reap/Cargo.lock
generated
Normal file
104
users/tazjin/niri-reap/Cargo.lock
generated
Normal file
|
@ -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.8"
|
||||
source = "git+https://github.com/YaLTeR/niri.git#370fd4e172ec3daf9dc9c75dc0555fe91182f731"
|
||||
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.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
7
users/tazjin/niri-reap/Cargo.toml
Normal file
7
users/tazjin/niri-reap/Cargo.toml
Normal file
|
@ -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.8" }
|
13
users/tazjin/niri-reap/default.nix
Normal file
13
users/tazjin/niri-reap/default.nix
Normal file
|
@ -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.8" = "sha256:0wyl0mpk9hg67bvj7q120wanrdqn3ls9zv9vjv9yxp11kan5pi1q";
|
||||
};
|
||||
};
|
||||
}
|
76
users/tazjin/niri-reap/src/main.rs
Normal file
76
users/tazjin/niri-reap/src/main.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
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 main() {
|
||||
let workspaces = list_workspaces();
|
||||
|
||||
let active_workspace = workspaces
|
||||
.iter()
|
||||
.filter(|w| w.is_focused)
|
||||
.next()
|
||||
.expect("expected an active workspace");
|
||||
|
||||
let orphan_workspaces = workspaces
|
||||
.iter()
|
||||
.filter(|w| w.output == active_workspace.output)
|
||||
// Only select workspaces that are further down, to avoid issues with
|
||||
// indices changing during the operation.
|
||||
.filter(|w| w.idx > active_workspace.idx)
|
||||
.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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue