diff --git a/Cargo.lock b/Cargo.lock index a1bfdd0..8ead548 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1120,7 +1120,7 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "workstyle" -version = "0.8.2" +version = "0.9.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index d75f3cb..3e5c047 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "workstyle" -version = "0.8.2" +version = "0.9.0" authors = ["Pierre Chevalier "] edition = "2021" license = "MIT" diff --git a/README.md b/README.md index f1fc018..ae1462f 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,15 @@ If you prefer not to have multiple copies of the same icon when there are multip deduplicate_icons = true ``` +If you prefer that empty workspaces be named with an icon, +instead of with a number, you can also specify: + +```toml +[other] +use_empty_icon = true +empty_icon = "" +``` + Note that the crate [`find_unicode`](https://github.com/pierrechevalier83/find_unicode/) can help find a unicode character directly from the command line. It now supports all of nerdfonts unicode space. Minimal waybar configuration so the workspace names are showed diff --git a/src/config.rs b/src/config.rs index bd93021..1aaa162 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,6 +22,7 @@ pub struct Other { pub fallback_icon: Option, pub separator: Option, pub deduplicate_icons: bool, + pub empty_icon: Option, } impl Config { @@ -70,6 +71,9 @@ impl Config { DEFAULT_SEPARATOR } } + pub fn empty_icon(&self) -> Option<&str> { + self.other.empty_icon.as_deref() + } pub fn path() -> Result { let mut user_path = dirs::config_dir().context("Could not find the configuration path")?; diff --git a/src/main.rs b/src/main.rs index f54bfb1..a9b14e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,13 @@ use window_manager::{Window, WindowManager, WM}; /// /// [other] /// deduplicate_icons = true +/// +/// If you prefer that empty workspaces be named with an icon, +/// instead of with a number, you can also specify: +/// +/// [other] +/// empty_icon = "" + #[derive(Parser, Debug)] #[clap(version, about, long_about)] struct Args { @@ -139,12 +146,19 @@ fn run() -> Result<()> { .next() .context("Unexpected workspace name")?; if new_name.is_empty() { - wm.rename_workspace(&name, num)?; + let empty_name = if let Some(icon) = config.empty_icon() { + icon + } else { + num + } + .to_string(); + + // Extra space matches other workspace icons. + wm.rename_workspace(&name, &format!("{num}{sep}{empty_name} "))?; } else { wm.rename_workspace(&name, &format!("{num}{sep}{new_name}"))?; } } - wm.wait_for_event()?; } } diff --git a/src/window_manager.rs b/src/window_manager.rs index eb2b686..a640fe1 100644 --- a/src/window_manager.rs +++ b/src/window_manager.rs @@ -1,4 +1,6 @@ use crate::EnforceWindowManager; +use log::{debug, info}; + use anyhow::{anyhow, bail, Context, Result}; use hyprland::data::{Clients, Version, Workspaces}; use hyprland::dispatch::{Dispatch, DispatchType}; @@ -8,7 +10,9 @@ use itertools::Itertools; use std::collections::BTreeMap; use std::sync::{mpsc, mpsc::Receiver}; use std::thread; -use swayipc::{Connection, EventStream, EventType, Node, NodeType}; +use swayipc::{ + Connection, Event, EventStream, EventType, Node, NodeType, WindowChange, WorkspaceChange, +}; trait NodeExt { fn is_workspace(&self) -> bool; @@ -287,13 +291,12 @@ impl WM for SwayOrI3 { connection: Connection::new().context("Couldn't connect to WM")?, events: Connection::new() .context("Couldn't connect to WM")? - .subscribe([EventType::Window]) - .context("Couldn't subscribe to events of type Window")?, + .subscribe([EventType::Window, EventType::Workspace]) + .context("Couldn't subscribe to events of type Window and Workspace")?, })), _ => bail!("Not connecting to Sway or i3 as we've explicitly been asked not to"), } } - fn get_windows_in_each_workspace(&mut self) -> Result>> { self.connection .get_tree() @@ -313,10 +316,54 @@ impl WM for SwayOrI3 { } fn wait_for_event(&mut self) -> Result<()> { - match self.events.next() { - Some(Err(e)) => Err(anyhow!(e).context("Failed to receive next event")), - None => bail!("Event stream ended"), - _ => Ok(()), + let next_relevant_event = self.events.find_map(move |event| { + if let Ok(event) = event { + if Self::event_is_relevant(&event) { + Some(Ok(())) + } else { + None + } + } else { + Some(Err(())) + } + }); + if next_relevant_event.is_none() { + bail!("Event stream ended") + } else { + Ok(()) + } + } +} + +impl SwayOrI3 { + fn event_is_relevant(event: &Event) -> bool { + match event { + Event::Workspace(boxed_workspace_event) => match (*boxed_workspace_event).change { + ch @ WorkspaceChange::Init => { + info!("Renaming on WorkspaceEvent: {:#?}", ch); + true + } + ch => { + debug!("Rejected Rename for WorkspaceEvent: {:#?}", ch); + false + } + }, + Event::Window(boxed_window_event) => match (*boxed_window_event).change { + ch @ (WindowChange::New + | WindowChange::Close + | WindowChange::Title + | WindowChange::Move + | WindowChange::Urgent + | WindowChange::Mark) => { + info!("Renaming on WindowEvent: {:#?}", ch); + true + } + ch => { + debug!("Rejected Rename for WindowEvent: {:#?}", ch); + false + } + }, + _ => false, } } }