mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
feat: add little tool to assign per workspace layouts in i3
This commit is contained in:
parent
4fba5b5255
commit
8f1773fdd0
5 changed files with 1130 additions and 0 deletions
2
users/myuser/graphical/i3-per-workspace-layout/.gitignore
vendored
Normal file
2
users/myuser/graphical/i3-per-workspace-layout/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
.cache
|
1003
users/myuser/graphical/i3-per-workspace-layout/Cargo.lock
generated
Normal file
1003
users/myuser/graphical/i3-per-workspace-layout/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
users/myuser/graphical/i3-per-workspace-layout/Cargo.toml
Normal file
18
users/myuser/graphical/i3-per-workspace-layout/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "i3-per-workspace-layout"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["oddlama <oddlama@oddlama.org>"]
|
||||||
|
description = "A helper utility to allow assigning a layout to each workspace in i3"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.75"
|
||||||
|
toml = "0.8.1"
|
||||||
|
serde = { version = "1.0.171", features = ["derive"] }
|
||||||
|
tokio = { version = "1.32.0", features = ["full"] }
|
||||||
|
tokio-i3ipc = "0.16.0"
|
||||||
|
clap = { version = "4.4.6", features = ["derive"] }
|
||||||
|
tokio-stream = "0.1.14"
|
||||||
|
flexi_logger = "0.27.2"
|
||||||
|
log = "0.4.20"
|
|
@ -0,0 +1,2 @@
|
||||||
|
max_width = 120
|
||||||
|
single_line_let_else_max_width = 120
|
105
users/myuser/graphical/i3-per-workspace-layout/src/main.rs
Normal file
105
users/myuser/graphical/i3-per-workspace-layout/src/main.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// Inspired by https://github.com/chmln/i3-auto-layout (MIT licensed)
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{debug, info};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tokio::{sync::mpsc, task::JoinHandle};
|
||||||
|
use tokio_i3ipc::{
|
||||||
|
event::{Event, Subscribe, WorkspaceChange},
|
||||||
|
msg::Msg,
|
||||||
|
I3,
|
||||||
|
};
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
/// Specifies the confiuguration file that maps workspace names to their desired layout.
|
||||||
|
/// Should be a simple toml file containing a category [layouts] that contains the mapping.
|
||||||
|
#[arg(short, long, value_name = "FILE")]
|
||||||
|
config: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// [layouts]
|
||||||
|
/// "1" = "stacked"
|
||||||
|
/// "2" = "tabbed"
|
||||||
|
/// # ...
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct Config {
|
||||||
|
/// Whether to force setting the layout even on filled workspaces
|
||||||
|
#[serde(default)] // false
|
||||||
|
force: bool,
|
||||||
|
/// The workspace -> layout associations
|
||||||
|
layouts: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
flexi_logger::Logger::try_with_env()?.start()?;
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
// Load the layout configuration from the TOML file.
|
||||||
|
let config_file = std::fs::read_to_string(cli.config)?;
|
||||||
|
let config: Config = toml::from_str(&config_file)?;
|
||||||
|
|
||||||
|
debug!("Connecting to i3 to send and receive events...");
|
||||||
|
let (send, mut recv) = mpsc::channel::<String>(10);
|
||||||
|
let s_handle: JoinHandle<Result<()>> = tokio::spawn(async move {
|
||||||
|
let mut event_listener = {
|
||||||
|
let mut i3 = I3::connect().await?;
|
||||||
|
i3.subscribe([Subscribe::Workspace]).await?;
|
||||||
|
i3.listen()
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Waiting for workspace events...");
|
||||||
|
loop {
|
||||||
|
let Some(Ok(Event::Workspace(workspace_event))) = event_listener.next().await else { continue };
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Got workspace event: name={:?}, change={:?}",
|
||||||
|
workspace_event.current.clone().and_then(|x| x.name),
|
||||||
|
workspace_event.change
|
||||||
|
);
|
||||||
|
|
||||||
|
if WorkspaceChange::Focus == workspace_event.change {
|
||||||
|
let workspace = workspace_event
|
||||||
|
.current
|
||||||
|
.ok_or_else(|| anyhow!("Missing current field on workspace event. Is your i3 up-to-date?"))?;
|
||||||
|
|
||||||
|
// Only change the layout if the workspace is empty or force == true
|
||||||
|
if !workspace.nodes.is_empty() && !config.force {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(name) = workspace.name else { continue };
|
||||||
|
let Some(desired_layout) = config.layouts.get(&name) else { continue };
|
||||||
|
|
||||||
|
send.send(format!("[con_id={}] layout {}", workspace.id, desired_layout))
|
||||||
|
.await
|
||||||
|
.context("Failed to queue command for sending")?;
|
||||||
|
|
||||||
|
debug!("Changed layout of workspace {:?} to {}", &name, desired_layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let r_handle: JoinHandle<Result<()>> = tokio::spawn(async move {
|
||||||
|
let mut i3 = I3::connect().await?;
|
||||||
|
loop {
|
||||||
|
let Some(cmd) = recv.recv().await else { continue };
|
||||||
|
i3.send_msg_body(Msg::RunCommand, cmd).await?;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let (send, recv) = tokio::try_join!(s_handle, r_handle)?;
|
||||||
|
send.and(recv)?;
|
||||||
|
debug!("Shutting down...");
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue