From 6e5d0c1e8c0b5d7e330608fc835e1e9733f156de Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Fri, 27 Jan 2023 20:08:14 +0000 Subject: [PATCH] feat(workspaces): hyprland support Resolves #18. The bar will now automatically detect whether running under Sway or Hyprland and use the correct IPC client depending. --- Cargo.lock | 33 ++++ Cargo.toml | 1 + src/clients/compositor/hyprland.rs | 250 +++++++++++++++++++++++++++++ src/clients/compositor/mod.rs | 89 ++++++++++ src/clients/compositor/sway.rs | 148 +++++++++++++++++ src/clients/mod.rs | 2 +- src/clients/sway.rs | 74 --------- src/modules/workspaces.rs | 165 ++++++++----------- 8 files changed, 585 insertions(+), 177 deletions(-) create mode 100644 src/clients/compositor/hyprland.rs create mode 100644 src/clients/compositor/mod.rs create mode 100644 src/clients/compositor/sway.rs delete mode 100644 src/clients/sway.rs diff --git a/Cargo.lock b/Cargo.lock index 1625420..fb8b49b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -585,6 +585,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "downcast-rs" version = "1.2.0" @@ -1084,6 +1090,25 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hyprland" +version = "0.3.0-alpha.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a04a666b11a405dd7d74dbfb915e7d6f09bc0a52b0715204bf2e54d5572ab935" +dependencies = [ + "async-trait", + "doc-comment", + "hex", + "lazy_static", + "num-traits", + "paste", + "regex", + "serde", + "serde_json", + "serde_repr", + "tokio", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1171,6 +1196,7 @@ dependencies = [ "glib", "gtk", "gtk-layer-shell", + "hyprland", "indexmap", "lazy_static", "libcorn", @@ -1615,6 +1641,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "paste" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" + [[package]] name = "pest" version = "2.5.1" @@ -2300,6 +2332,7 @@ dependencies = [ "memchr", "mio", "num_cpus", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", diff --git a/Cargo.toml b/Cargo.toml index 8be0b1e..4ee119f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ notify = { version = "5.0.0", default-features = false } mpd_client = "1.0.0" mpris = "2.0.0" swayipc-async = { version = "2.0.1" } +hyprland = "0.3.0-alpha.0" sysinfo = "0.27.0" wayland-client = "0.29.5" wayland-protocols = { version = "0.29.5", features = ["unstable_protocols", "client"] } diff --git a/src/clients/compositor/hyprland.rs b/src/clients/compositor/hyprland.rs new file mode 100644 index 0000000..021604b --- /dev/null +++ b/src/clients/compositor/hyprland.rs @@ -0,0 +1,250 @@ +use super::{Workspace, WorkspaceClient, WorkspaceUpdate}; +use crate::error::{ERR_CHANNEL_SEND, ERR_MUTEX_LOCK}; +use hyprland::data::{Workspace as HWorkspace, Workspaces}; +use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial}; +use hyprland::event_listener::EventListenerMutable as EventListener; +use hyprland::prelude::*; +use hyprland::shared::WorkspaceType; +use lazy_static::lazy_static; +use std::sync::{Arc, Mutex}; +use tokio::sync::broadcast::{channel, Receiver, Sender}; +use tokio::task::spawn_blocking; +use tracing::{error, info}; + +pub struct EventClient { + workspaces: Arc>>, + workspace_tx: Sender, + _workspace_rx: Receiver, +} + +impl EventClient { + fn new() -> Self { + let (workspace_tx, workspace_rx) = channel(16); + + let workspaces = Arc::new(Mutex::new(vec![])); + // load initial list + Self::refresh_workspaces(&workspaces); + + Self { + workspaces, + workspace_tx, + _workspace_rx: workspace_rx, + } + } + + fn listen_workspace_events(&self) { + info!("Starting Hyprland event listener"); + + let workspaces = self.workspaces.clone(); + let tx = self.workspace_tx.clone(); + + spawn_blocking(move || { + let mut event_listener = EventListener::new(); + + { + let workspaces = workspaces.clone(); + let tx = tx.clone(); + + event_listener.add_workspace_added_handler(move |workspace_type, _state| { + Self::refresh_workspaces(&workspaces); + + let workspace = Self::get_workspace(&workspaces, workspace_type); + workspace.map_or_else( + || error!("Unable to locate workspace"), + |workspace| { + tx.send(WorkspaceUpdate::Add(workspace)) + .expect(ERR_CHANNEL_SEND); + }, + ); + }); + } + + { + let workspaces = workspaces.clone(); + let tx = tx.clone(); + + event_listener.add_workspace_change_handler(move |workspace_type, _state| { + let prev_workspace = Self::get_focused_workspace(&workspaces); + + Self::refresh_workspaces(&workspaces); + + let workspace = Self::get_workspace(&workspaces, workspace_type); + + if let (Some(prev_workspace), Some(workspace)) = (prev_workspace, workspace) { + if prev_workspace.id != workspace.id { + tx.send(WorkspaceUpdate::Focus { + old: prev_workspace, + new: workspace.clone(), + }) + .expect(ERR_CHANNEL_SEND); + } + + // there may be another type of update so dispatch that regardless of focus change + tx.send(WorkspaceUpdate::Update(workspace)) + .expect(ERR_CHANNEL_SEND); + } else { + error!("Unable to locate workspace"); + } + }); + } + + { + let workspaces = workspaces.clone(); + let tx = tx.clone(); + + event_listener.add_workspace_destroy_handler(move |workspace_type, _state| { + let workspace = Self::get_workspace(&workspaces, workspace_type); + workspace.map_or_else( + || error!("Unable to locate workspace"), + |workspace| { + tx.send(WorkspaceUpdate::Remove(workspace)) + .expect(ERR_CHANNEL_SEND); + }, + ); + + Self::refresh_workspaces(&workspaces); + }); + } + + { + let workspaces = workspaces.clone(); + let tx = tx.clone(); + + event_listener.add_workspace_moved_handler(move |event_data, _state| { + let workspace_type = event_data.1; + + Self::refresh_workspaces(&workspaces); + + let workspace = Self::get_workspace(&workspaces, workspace_type); + workspace.map_or_else( + || error!("Unable to locate workspace"), + |workspace| { + tx.send(WorkspaceUpdate::Move(workspace)) + .expect(ERR_CHANNEL_SEND); + }, + ); + }); + } + + { + let workspaces = workspaces.clone(); + + event_listener.add_active_monitor_change_handler(move |event_data, _state| { + let workspace_type = event_data.1; + + let prev_workspace = Self::get_focused_workspace(&workspaces); + + Self::refresh_workspaces(&workspaces); + + let workspace = Self::get_workspace(&workspaces, workspace_type); + + if let (Some(prev_workspace), Some(workspace)) = (prev_workspace, workspace) { + if prev_workspace.id != workspace.id { + tx.send(WorkspaceUpdate::Focus { + old: prev_workspace, + new: workspace.clone(), + }) + .expect(ERR_CHANNEL_SEND); + } + } else { + error!("Unable to locate workspace"); + } + }); + } + + event_listener + .start_listener() + .expect("Failed to start listener"); + }); + } + + fn refresh_workspaces(workspaces: &Mutex>) { + let mut workspaces = workspaces.lock().expect(ERR_MUTEX_LOCK); + + let active = HWorkspace::get_active().expect("Failed to get active workspace"); + let new_workspaces = Workspaces::get() + .expect("Failed to get workspaces") + .collect() + .into_iter() + .map(|workspace| Workspace::from((workspace.id == active.id, workspace))); + + workspaces.clear(); + workspaces.extend(new_workspaces); + } + + fn get_workspace(workspaces: &Mutex>, id: WorkspaceType) -> Option { + let id_string = id_to_string(id); + + let workspaces = workspaces.lock().expect(ERR_MUTEX_LOCK); + workspaces + .iter() + .find(|workspace| workspace.id == id_string) + .cloned() + } + + fn get_focused_workspace(workspaces: &Mutex>) -> Option { + let workspaces = workspaces.lock().expect(ERR_MUTEX_LOCK); + workspaces + .iter() + .find(|workspace| workspace.focused) + .cloned() + } +} + +impl WorkspaceClient for EventClient { + fn focus(&self, id: String) -> color_eyre::Result<()> { + Dispatch::call(DispatchType::Workspace( + WorkspaceIdentifierWithSpecial::Name(&id), + ))?; + Ok(()) + } + + fn subscribe_workspace_change(&self) -> Receiver { + let rx = self.workspace_tx.subscribe(); + + { + let tx = self.workspace_tx.clone(); + + let workspaces = self.workspaces.clone(); + Self::refresh_workspaces(&workspaces); + + let workspaces = workspaces.lock().expect(ERR_MUTEX_LOCK); + + tx.send(WorkspaceUpdate::Init(workspaces.clone())) + .expect(ERR_CHANNEL_SEND); + } + + rx + } +} + +lazy_static! { + static ref CLIENT: EventClient = { + let client = EventClient::new(); + client.listen_workspace_events(); + client + }; +} + +pub fn get_client() -> &'static EventClient { + &CLIENT +} + +fn id_to_string(id: WorkspaceType) -> String { + match id { + WorkspaceType::Unnamed(id) => id.to_string(), + WorkspaceType::Named(name) => name, + WorkspaceType::Special(name) => name.unwrap_or_default(), + } +} + +impl From<(bool, hyprland::data::Workspace)> for Workspace { + fn from((focused, workspace): (bool, hyprland::data::Workspace)) -> Self { + Self { + id: id_to_string(workspace.id), + name: workspace.name, + monitor: workspace.monitor, + focused, + } + } +} diff --git a/src/clients/compositor/mod.rs b/src/clients/compositor/mod.rs new file mode 100644 index 0000000..48a2dc2 --- /dev/null +++ b/src/clients/compositor/mod.rs @@ -0,0 +1,89 @@ +use color_eyre::{Help, Report, Result}; +use std::fmt::{Display, Formatter}; +use tokio::sync::broadcast; +use tracing::debug; + +pub mod hyprland; +pub mod sway; + +pub enum Compositor { + Sway, + Hyprland, + Unsupported, +} + +impl Display for Compositor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Compositor::Sway => "Sway", + Compositor::Hyprland => "Hyprland", + Compositor::Unsupported => "Unsupported", + } + ) + } +} + +impl Compositor { + /// Attempts to get the current compositor. + /// This is done by checking system env vars. + fn get_current() -> Self { + if std::env::var("SWAYSOCK").is_ok() { + Self::Sway + } else if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() { + Self::Hyprland + } else { + Self::Unsupported + } + } + + /// Gets the workspace client for the current compositor + pub fn get_workspace_client() -> Result<&'static (dyn WorkspaceClient + Send)> { + let current = Self::get_current(); + debug!("Getting workspace client for: {current}"); + match current { + Compositor::Sway => Ok(sway::get_sub_client()), + Compositor::Hyprland => Ok(hyprland::get_client()), + Compositor::Unsupported => Err(Report::msg("Unsupported compositor") + .note("Currently workspaces are only supported by Sway and Hyprland")), + } + } +} + +#[derive(Debug, Clone)] +pub struct Workspace { + /// Unique identifier + pub id: String, + /// Workspace friendly name + pub name: String, + /// Name of the monitor (output) the workspace is located on + pub monitor: String, + /// Whether the workspace is in focus + pub focused: bool, +} + +#[derive(Debug, Clone)] +pub enum WorkspaceUpdate { + /// Provides an initial list of workspaces. + /// This is re-sent to all subscribers when a new subscription is created. + Init(Vec), + Add(Workspace), + Remove(Workspace), + Update(Workspace), + Move(Workspace), + /// Declares focus moved from the old workspace to the new. + Focus { + old: Workspace, + new: Workspace, + }, +} + +pub trait WorkspaceClient { + /// Requests the workspace with this name is focused. + fn focus(&self, name: String) -> Result<()>; + + /// Creates a new to workspace event receiver. + fn subscribe_workspace_change(&self) -> broadcast::Receiver; +} diff --git a/src/clients/compositor/sway.rs b/src/clients/compositor/sway.rs new file mode 100644 index 0000000..7770119 --- /dev/null +++ b/src/clients/compositor/sway.rs @@ -0,0 +1,148 @@ +use super::{Workspace, WorkspaceClient, WorkspaceUpdate}; +use crate::await_sync; +use crate::error::ERR_CHANNEL_SEND; +use async_once::AsyncOnce; +use color_eyre::Report; +use futures_util::StreamExt; +use lazy_static::lazy_static; +use std::sync::Arc; +use swayipc_async::{Connection, Event, EventType, Node, WorkspaceChange, WorkspaceEvent}; +use tokio::spawn; +use tokio::sync::broadcast::{channel, Receiver, Sender}; +use tokio::sync::Mutex; +use tracing::{info, trace}; + +pub struct SwayEventClient { + workspace_tx: Sender, + _workspace_rx: Receiver, +} + +impl SwayEventClient { + fn new() -> Self { + let (workspace_tx, workspace_rx) = channel(16); + + { + let workspace_tx = workspace_tx.clone(); + spawn(async move { + let client = Connection::new().await?; + info!("Sway IPC subscription client connected"); + + let event_types = [EventType::Workspace]; + + let mut events = client.subscribe(event_types).await?; + + while let Some(event) = events.next().await { + trace!("event: {:?}", event); + if let Event::Workspace(ev) = event? { + workspace_tx.send(WorkspaceUpdate::from(*ev))?; + }; + } + + Ok::<(), Report>(()) + }); + } + + Self { + workspace_tx, + _workspace_rx: workspace_rx, + } + } +} + +impl WorkspaceClient for SwayEventClient { + fn focus(&self, id: String) -> color_eyre::Result<()> { + await_sync(async move { + let client = get_client().await; + let mut client = client.lock().await; + client.run_command(format!("workspace {id}")).await + })?; + Ok(()) + } + + fn subscribe_workspace_change(&self) -> Receiver { + let rx = self.workspace_tx.subscribe(); + + { + let tx = self.workspace_tx.clone(); + await_sync(async { + let client = get_client().await; + let mut client = client.lock().await; + + let workspaces = client + .get_workspaces() + .await + .expect("Failed to get workspaces"); + let event = + WorkspaceUpdate::Init(workspaces.into_iter().map(Workspace::from).collect()); + + tx.send(event).expect(ERR_CHANNEL_SEND); + }); + } + + rx + } +} + +lazy_static! { + static ref CLIENT: AsyncOnce>> = AsyncOnce::new(async { + let client = Connection::new() + .await + .expect("Failed to connect to Sway socket"); + Arc::new(Mutex::new(client)) + }); + static ref SUB_CLIENT: SwayEventClient = SwayEventClient::new(); +} + +/// Gets the sway IPC client +async fn get_client() -> Arc> { + let client = CLIENT.get().await; + Arc::clone(client) +} + +/// Gets the sway IPC event subscription client +pub fn get_sub_client() -> &'static SwayEventClient { + &SUB_CLIENT +} + +impl From for Workspace { + fn from(node: Node) -> Self { + Self { + id: node.id.to_string(), + name: node.name.unwrap_or_default(), + monitor: node.output.unwrap_or_default(), + focused: node.focused, + } + } +} + +impl From for Workspace { + fn from(workspace: swayipc_async::Workspace) -> Self { + Self { + id: workspace.id.to_string(), + name: workspace.name, + monitor: workspace.output, + focused: workspace.focused, + } + } +} + +impl From for WorkspaceUpdate { + fn from(event: WorkspaceEvent) -> Self { + match event.change { + WorkspaceChange::Init => { + Self::Add(event.current.expect("Missing current workspace").into()) + } + WorkspaceChange::Empty => { + Self::Remove(event.current.expect("Missing current workspace").into()) + } + WorkspaceChange::Focus => Self::Focus { + old: event.old.expect("Missing old workspace").into(), + new: event.current.expect("Missing current workspace").into(), + }, + WorkspaceChange::Move => { + Self::Move(event.current.expect("Missing current workspace").into()) + } + _ => Self::Update(event.current.expect("Missing current workspace").into()), + } + } +} diff --git a/src/clients/mod.rs b/src/clients/mod.rs index 3fbfac8..544ed60 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -1,4 +1,4 @@ +pub mod compositor; pub mod music; -pub mod sway; pub mod system_tray; pub mod wayland; diff --git a/src/clients/sway.rs b/src/clients/sway.rs deleted file mode 100644 index 073ba47..0000000 --- a/src/clients/sway.rs +++ /dev/null @@ -1,74 +0,0 @@ -use async_once::AsyncOnce; -use color_eyre::Report; -use futures_util::StreamExt; -use lazy_static::lazy_static; -use std::sync::Arc; -use swayipc_async::{Connection, Event, EventType, WorkspaceEvent}; -use tokio::spawn; -use tokio::sync::broadcast::{channel, Receiver, Sender}; -use tokio::sync::Mutex; -use tracing::{info, trace}; - -pub struct SwayEventClient { - workspace_tx: Sender>, - _workspace_rx: Receiver>, -} - -impl SwayEventClient { - fn new() -> Self { - let (workspace_tx, workspace_rx) = channel(16); - - let workspace_tx2 = workspace_tx.clone(); - - spawn(async move { - let workspace_tx = workspace_tx2; - - let client = Connection::new().await?; - info!("Sway IPC subscription client connected"); - - let event_types = [EventType::Workspace]; - - let mut events = client.subscribe(event_types).await?; - - while let Some(event) = events.next().await { - trace!("event: {:?}", event); - if let Event::Workspace(ev) = event? { - workspace_tx.send(ev)?; - }; - } - - Ok::<(), Report>(()) - }); - - Self { - workspace_tx, - _workspace_rx: workspace_rx, - } - } - - /// Gets an event receiver for workspace events - pub fn subscribe_workspace(&self) -> Receiver> { - self.workspace_tx.subscribe() - } -} - -lazy_static! { - static ref CLIENT: AsyncOnce>> = AsyncOnce::new(async { - let client = Connection::new() - .await - .expect("Failed to connect to Sway socket"); - Arc::new(Mutex::new(client)) - }); - static ref SUB_CLIENT: SwayEventClient = SwayEventClient::new(); -} - -/// Gets the sway IPC client -pub async fn get_client() -> Arc> { - let client = CLIENT.get().await; - Arc::clone(client) -} - -/// Gets the sway IPC event subscription client -pub fn get_sub_client() -> &'static SwayEventClient { - &SUB_CLIENT -} diff --git a/src/modules/workspaces.rs b/src/modules/workspaces.rs index d8ac598..e99abd2 100644 --- a/src/modules/workspaces.rs +++ b/src/modules/workspaces.rs @@ -1,13 +1,12 @@ -use crate::clients::sway::{get_client, get_sub_client}; +use crate::clients::compositor::{Compositor, WorkspaceUpdate}; use crate::config::CommonConfig; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; -use crate::{await_sync, send_async, try_send}; +use crate::{send_async, try_send}; use color_eyre::{Report, Result}; use gtk::prelude::*; use gtk::Button; use serde::Deserialize; use std::collections::HashMap; -use swayipc_async::{Workspace, WorkspaceChange, WorkspaceEvent}; use tokio::spawn; use tokio::sync::mpsc::{Receiver, Sender}; use tracing::trace; @@ -25,12 +24,6 @@ pub struct WorkspacesModule { pub common: Option, } -#[derive(Clone, Debug)] -pub enum WorkspaceUpdate { - Init(Vec), - Update(Box), -} - /// Creates a button from a workspace fn create_button( name: &str, @@ -70,58 +63,33 @@ impl Module for WorkspacesModule { fn spawn_controller( &self, - info: &ModuleInfo, + _info: &ModuleInfo, tx: Sender>, mut rx: Receiver, ) -> Result<()> { - let workspaces = { - trace!("Getting current workspaces"); - let workspaces = await_sync(async { - let sway = get_client().await; - let mut sway = sway.lock().await; - sway.get_workspaces().await - })?; - - if self.all_monitors { - workspaces - } else { - trace!("Filtering workspaces to current monitor only"); - workspaces - .into_iter() - .filter(|workspace| workspace.output == info.output_name) - .collect() - } - }; - - try_send!( - tx, - ModuleUpdateEvent::Update(WorkspaceUpdate::Init(workspaces)) - ); - // Subscribe & send events spawn(async move { let mut srx = { - let sway = get_sub_client(); - sway.subscribe_workspace() + let client = + Compositor::get_workspace_client().expect("Failed to get workspace client"); + client.subscribe_workspace_change() }; trace!("Set up Sway workspace subscription"); while let Ok(payload) = srx.recv().await { - send_async!( - tx, - ModuleUpdateEvent::Update(WorkspaceUpdate::Update(payload)) - ); + send_async!(tx, ModuleUpdateEvent::Update(payload)); } }); // Change workspace focus spawn(async move { trace!("Setting up UI event handler"); - let sway = get_client().await; + while let Some(name) = rx.recv().await { - let mut sway = sway.lock().await; - sway.run_command(format!("workspace {}", name)).await?; + let client = + Compositor::get_workspace_client().expect("Failed to get workspace client"); + client.focus(name)?; } Ok::<(), Report>(()) @@ -145,45 +113,64 @@ impl Module for WorkspacesModule { let container = container.clone(); let output_name = info.output_name.to_string(); + // keep track of whether init event has fired previously + // since it fires for every workspace subscriber + let mut has_initialized = false; + context.widget_rx.attach(None, move |event| { match event { WorkspaceUpdate::Init(workspaces) => { - trace!("Creating workspace buttons"); - for workspace in workspaces { - let item = create_button( - &workspace.name, - workspace.focused, - &name_map, - &context.controller_tx, - ); - container.add(&item); - button_map.insert(workspace.name, item); + if !has_initialized { + trace!("Creating workspace buttons"); + for workspace in workspaces { + if self.all_monitors || workspace.monitor == output_name { + let item = create_button( + &workspace.name, + workspace.focused, + &name_map, + &context.controller_tx, + ); + container.add(&item); + button_map.insert(workspace.name, item); + } + } + container.show_all(); + has_initialized = true; } - container.show_all(); } - WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Focus => { - let old = event - .old - .and_then(|old| old.name) - .and_then(|name| button_map.get(&name)); + WorkspaceUpdate::Focus { old, new } => { + let old = button_map.get(&old.name); if let Some(old) = old { old.style_context().remove_class("focused"); } - let new = event - .current - .and_then(|old| old.name) - .and_then(|new| button_map.get(&new)); + let new = button_map.get(&new.name); if let Some(new) = new { new.style_context().add_class("focused"); } } - WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Init => { - if let Some(workspace) = event.current { - if self.all_monitors - || workspace.output.unwrap_or_default() == output_name - { - let name = workspace.name.unwrap_or_default(); + WorkspaceUpdate::Add(workspace) => { + if self.all_monitors || workspace.monitor == output_name { + let name = workspace.name; + let item = create_button( + &name, + workspace.focused, + &name_map, + &context.controller_tx, + ); + + item.show(); + container.add(&item); + + if !name.is_empty() { + button_map.insert(name, item); + } + } + } + WorkspaceUpdate::Move(workspace) => { + if !self.all_monitors { + if workspace.monitor == output_name { + let name = workspace.name; let item = create_button( &name, workspace.focused, @@ -197,43 +184,17 @@ impl Module for WorkspacesModule { if !name.is_empty() { button_map.insert(name, item); } - } - } - } - WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Move => { - if let Some(workspace) = event.current { - if !self.all_monitors { - if workspace.output.unwrap_or_default() == output_name { - let name = workspace.name.unwrap_or_default(); - let item = create_button( - &name, - workspace.focused, - &name_map, - &context.controller_tx, - ); - - item.show(); - container.add(&item); - - if !name.is_empty() { - button_map.insert(name, item); - } - } else if let Some(item) = - button_map.get(&workspace.name.unwrap_or_default()) - { - container.remove(item); - } - } - } - } - WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Empty => { - if let Some(workspace) = event.current { - if let Some(item) = button_map.get(&workspace.name.unwrap_or_default()) - { + } else if let Some(item) = button_map.get(&workspace.name) { container.remove(item); } } } + WorkspaceUpdate::Remove(workspace) => { + let button = button_map.get(&workspace.name); + if let Some(item) = button { + container.remove(item); + } + } WorkspaceUpdate::Update(_) => {} };