forked from mirrors_public/oddlama_nix-config
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