From 1b6aa104235df5f93ef165b816b63b2d827099a2 Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Fri, 16 May 2025 22:15:32 +0100 Subject: [PATCH] refactor: update hyprland-rs, remove `expect`s from hyprland client code Fixes #960 --- Cargo.lock | 118 +++++++++++++++- Cargo.toml | 6 +- src/clients/compositor/hyprland.rs | 211 ++++++++++++++++------------- 3 files changed, 233 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65786df..1e4f1af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,6 +168,28 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote 1.0.39", + "syn 2.0.99", +] + [[package]] name = "async-trait" version = "0.1.87" @@ -655,13 +677,23 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.19" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote 1.0.39", "syn 2.0.99", + "unicode-xid 0.2.6", ] [[package]] @@ -1542,17 +1574,20 @@ dependencies = [ [[package]] name = "hyprland" -version = "0.4.0-alpha.3" +version = "0.4.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de3f836e02af5a12f374d3a986867c1dc487a63a6d19477d66c7de50f715895" +checksum = "dc9c1413b6f0fd10b2e4463479490e30b2497ae4449f044da16053f5f2cb03b8" dependencies = [ "ahash", + "async-stream", "derive_more", + "either", + "futures-lite", "hyprland-macros", "num-traits", "once_cell", "paste", - "regex", + "phf", "serde", "serde_json", "serde_repr", @@ -2457,6 +2492,48 @@ dependencies = [ "sha2", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote 1.0.39", + "syn 2.0.99", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -2607,6 +2684,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "redox_syscall" version = "0.5.10" @@ -3038,6 +3130,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -3160,7 +3258,7 @@ checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" dependencies = [ "quote 0.3.15", "synom", - "unicode-xid", + "unicode-xid 0.0.4", ] [[package]] @@ -3200,7 +3298,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" dependencies = [ - "unicode-xid", + "unicode-xid 0.0.4", ] [[package]] @@ -3656,6 +3754,12 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-config" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 2564b47..10df583 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["gtk", "bar", "wayland", "wlroots", "gtk-layer-shell"] [features] default = [ + "bindmode+all", "cli", "cairo", "clipboard", @@ -19,7 +20,6 @@ default = [ "focused", "http", "ipc", - "bindmode+all", "keyboard+all", "launcher", "label", @@ -182,7 +182,7 @@ libpulse-binding = { version = "2.30.1", optional = true } futures-lite = { version = "2.6.0", optional = true } # network_manager, upower, workspaces, keyboard zbus = { version = "5.6.0", default-features = false, features = ["tokio"], optional = true } # network_manager, notifications, upower swayipc-async = { version = "2.0.4", optional = true } # workspaces, keyboard -hyprland = { version = "0.4.0-alpha.3", features = ["silent"], optional = true } # workspaces, keyboard +hyprland = { version = "0.4.0-beta.2", optional = true } # workspaces, keyboard rustix = { version = "1.0.7", default-features = false, features = ["std", "fs", "pipe", "event"], optional = true } # clipboard, input serde_json = { version = "1.0.140", optional = true } # ipc, niri @@ -193,4 +193,4 @@ schemars = { version = "0.8.22", optional = true } clap = { version = "4.5.38", features = ["derive"] } clap_complete = "4.5.50" serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" +serde_json = "1.0.140" \ No newline at end of file diff --git a/src/clients/compositor/hyprland.rs b/src/clients/compositor/hyprland.rs index ede71b8..d17baf6 100644 --- a/src/clients/compositor/hyprland.rs +++ b/src/clients/compositor/hyprland.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "bindmode+hyprland")] +use super::{BindModeClient, BindModeUpdate}; +#[cfg(feature = "keyboard+hyprland")] +use super::{KeyboardLayoutClient, KeyboardLayoutUpdate}; use super::{Visibility, Workspace, WorkspaceUpdate}; use crate::{arc_mut, lock, send, spawn_blocking}; use color_eyre::Result; @@ -8,12 +12,7 @@ use hyprland::event_listener::EventListener; use hyprland::prelude::*; use hyprland::shared::{HyprDataVec, WorkspaceType}; use tokio::sync::broadcast::{Receiver, Sender, channel}; -use tracing::{debug, error, info}; - -#[cfg(feature = "bindmode+hyprland")] -use super::{BindModeClient, BindModeUpdate}; -#[cfg(feature = "keyboard+hyprland")] -use super::{KeyboardLayoutClient, KeyboardLayoutUpdate}; +use tracing::{debug, error, info, warn}; #[derive(Debug)] struct TxRx { @@ -74,45 +73,55 @@ impl Client { // cache the active workspace since Hyprland doesn't give us the prev active #[cfg(feature = "workspaces+hyprland")] - Self::listen_workspace_events(workspace_tx, &mut event_listener, &lock); + Self::listen_workspace_events(&workspace_tx, &mut event_listener, &lock); #[cfg(feature = "keyboard+hyprland")] - Self::listen_keyboard_events(keyboard_layout_tx, &mut event_listener, &lock); + Self::listen_keyboard_events(&keyboard_layout_tx, &mut event_listener, &lock); #[cfg(feature = "bindmode+hyprland")] - Self::listen_bindmode_events(bindmode_tx, &mut event_listener, &lock); + Self::listen_bindmode_events(&bindmode_tx, &mut event_listener, &lock); - event_listener - .start_listener() - .expect("Failed to start listener"); + if let Err(err) = event_listener.start_listener() { + error!("Failed to start listener: {err:#}"); + } }); } #[cfg(feature = "workspaces+hyprland")] fn listen_workspace_events( - tx: Sender, + tx: &Sender, event_listener: &mut EventListener, lock: &std::sync::Arc>, ) { - let active = Self::get_active_workspace().expect("Failed to get active workspace"); - let active = arc_mut!(Some(active)); + let active = Self::get_active_workspace().map_or_else( + |err| { + error!("Failed to get active workspace: {err:#?}"); + None + }, + Some, + ); + let active = arc_mut!(active); { let tx = tx.clone(); let lock = lock.clone(); let active = active.clone(); - event_listener.add_workspace_added_handler(move |workspace_type| { + event_listener.add_workspace_added_handler(move |event| { let _lock = lock!(lock); - debug!("Added workspace: {workspace_type:?}"); + debug!("Added workspace: {event:?}"); - let workspace_name = get_workspace_name(workspace_type); + let workspace_name = get_workspace_name(event.name); let prev_workspace = lock!(active); let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref()); - if let Some(workspace) = workspace { - send!(tx, WorkspaceUpdate::Add(workspace)); + match workspace { + Ok(Some(workspace)) => { + send!(tx, WorkspaceUpdate::Add(workspace)); + } + Err(e) => error!("Failed to get workspace: {e:#}"), + _ => {} } }); } @@ -122,30 +131,29 @@ impl Client { let lock = lock.clone(); let active = active.clone(); - event_listener.add_workspace_change_handler(move |workspace_type| { + event_listener.add_workspace_changed_handler(move |event| { let _lock = lock!(lock); let mut prev_workspace = lock!(active); debug!( - "Received workspace change: {:?} -> {workspace_type:?}", + "Received workspace change: {:?} -> {event:?}", prev_workspace.as_ref().map(|w| &w.id) ); - let workspace_name = get_workspace_name(workspace_type); + let workspace_name = get_workspace_name(event.name); let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref()); - workspace.map_or_else( - || { + match workspace { + Ok(Some(workspace)) if !workspace.visibility.is_focused() => { + Self::send_focus_change(&mut prev_workspace, workspace, &tx); + } + Ok(None) => { error!("Unable to locate workspace"); - }, - |workspace| { - // there may be another type of update so dispatch that regardless of focus change - if !workspace.visibility.is_focused() { - Self::send_focus_change(&mut prev_workspace, workspace, &tx); - } - }, - ); + } + Err(e) => error!("Failed to get workspace: {e:#}"), + _ => {} + } }); } @@ -154,9 +162,12 @@ impl Client { let lock = lock.clone(); let active = active.clone(); - event_listener.add_active_monitor_change_handler(move |event_data| { + event_listener.add_active_monitor_changed_handler(move |event_data| { let _lock = lock!(lock); - let workspace_type = event_data.workspace; + let Some(workspace_type) = event_data.workspace_name else { + warn!("Received active monitor change with no workspace name"); + return; + }; let mut prev_workspace = lock!(active); @@ -168,11 +179,15 @@ impl Client { let workspace_name = get_workspace_name(workspace_type); let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref()); - if let Some((false, workspace)) = workspace.map(|w| (w.visibility.is_focused(), w)) - { - Self::send_focus_change(&mut prev_workspace, workspace, &tx); - } else { - error!("unable to locate workspace: {workspace_name}"); + match workspace { + Ok(Some(workspace)) if !workspace.visibility.is_focused() => { + Self::send_focus_change(&mut prev_workspace, workspace, &tx); + } + Ok(None) => { + error!("Unable to locate workspace"); + } + Err(e) => error!("Failed to get workspace: {e:#}"), + _ => {} } }); } @@ -183,7 +198,7 @@ impl Client { event_listener.add_workspace_moved_handler(move |event_data| { let _lock = lock!(lock); - let workspace_type = event_data.workspace; + let workspace_type = event_data.name; debug!("Received workspace move: {workspace_type:?}"); let mut prev_workspace = lock!(active); @@ -191,12 +206,15 @@ impl Client { let workspace_name = get_workspace_name(workspace_type); let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref()); - if let Some(workspace) = workspace { - send!(tx, WorkspaceUpdate::Move(workspace.clone())); - - if !workspace.visibility.is_focused() { + match workspace { + Ok(Some(workspace)) if !workspace.visibility.is_focused() => { Self::send_focus_change(&mut prev_workspace, workspace, &tx); } + Ok(None) => { + error!("Unable to locate workspace"); + } + Err(e) => error!("Failed to get workspace: {e:#}"), + _ => {} } }); } @@ -205,15 +223,15 @@ impl Client { let tx = tx.clone(); let lock = lock.clone(); - event_listener.add_workspace_rename_handler(move |data| { + event_listener.add_workspace_renamed_handler(move |data| { let _lock = lock!(lock); debug!("Received workspace rename: {data:?}"); send!( tx, WorkspaceUpdate::Rename { - id: data.workspace_id as i64, - name: data.workspace_name + id: data.id as i64, + name: data.name } ); }); @@ -223,10 +241,10 @@ impl Client { let tx = tx.clone(); let lock = lock.clone(); - event_listener.add_workspace_destroy_handler(move |data| { + event_listener.add_workspace_deleted_handler(move |data| { let _lock = lock!(lock); debug!("Received workspace destroy: {data:?}"); - send!(tx, WorkspaceUpdate::Remove(data.workspace_id as i64)); + send!(tx, WorkspaceUpdate::Remove(data.id as i64)); }); } @@ -234,7 +252,7 @@ impl Client { let tx = tx.clone(); let lock = lock.clone(); - event_listener.add_urgent_state_handler(move |address| { + event_listener.add_urgent_state_changed_handler(move |address| { let _lock = lock!(lock); debug!("Received urgent state: {address:?}"); @@ -265,14 +283,14 @@ impl Client { #[cfg(feature = "keyboard+hyprland")] fn listen_keyboard_events( - keyboard_layout_tx: Sender, + keyboard_layout_tx: &Sender, event_listener: &mut EventListener, lock: &std::sync::Arc>, ) { let tx = keyboard_layout_tx.clone(); let lock = lock.clone(); - event_listener.add_keyboard_layout_change_handler(move |layout_event| { + event_listener.add_layout_changed_handler(move |layout_event| { let _lock = lock!(lock); let layout = if layout_event.layout_name.is_empty() { @@ -319,14 +337,14 @@ impl Client { #[cfg(feature = "bindmode+hyprland")] fn listen_bindmode_events( - bindmode_tx: Sender, + bindmode_tx: &Sender, event_listener: &mut EventListener, lock: &std::sync::Arc>, ) { let tx = bindmode_tx.clone(); let lock = lock.clone(); - event_listener.add_sub_map_change_handler(move |bind_mode| { + event_listener.add_sub_map_changed_handler(move |bind_mode| { let _lock = lock!(lock); debug!("Received bind mode: {bind_mode:?}"); @@ -369,21 +387,20 @@ impl Client { /// Gets a workspace by name from the server, given the active workspace if known. #[cfg(feature = "workspaces+hyprland")] - fn get_workspace(name: &str, active: Option<&Workspace>) -> Option { - Workspaces::get() - .expect("Failed to get workspaces") - .into_iter() - .find_map(|w| { - if w.name == name { - let vis = Visibility::from((&w, active.map(|w| w.name.as_ref()), &|w| { - create_is_visible()(w) - })); + fn get_workspace(name: &str, active: Option<&Workspace>) -> Result> { + let workspace = Workspaces::get()?.into_iter().find_map(|w| { + if w.name == name { + let vis = Visibility::from((&w, active.map(|w| w.name.as_ref()), &|w| { + create_is_visible()(w) + })); - Some(Workspace::from((vis, w))) - } else { - None - } - }) + Some(Workspace::from((vis, w))) + } else { + None + } + }); + + Ok(workspace) } /// Gets the active workspace from the server. @@ -409,17 +426,22 @@ impl super::WorkspaceClient for Client { let active_id = HWorkspace::get_active().ok().map(|active| active.name); let is_visible = create_is_visible(); - let workspaces = Workspaces::get() - .expect("Failed to get workspaces") - .into_iter() - .map(|w| { - let vis = Visibility::from((&w, active_id.as_deref(), &is_visible)); + match Workspaces::get() { + Ok(workspaces) => { + let workspaces = workspaces + .into_iter() + .map(|w| { + let vis = Visibility::from((&w, active_id.as_deref(), &is_visible)); + Workspace::from((vis, w)) + }) + .collect(); - Workspace::from((vis, w)) - }) - .collect(); - - send!(self.workspace.tx, WorkspaceUpdate::Init(workspaces)); + send!(self.workspace.tx, WorkspaceUpdate::Init(workspaces)); + } + Err(e) => { + error!("Failed to get workspaces: {e:#}"); + } + } rx } @@ -428,8 +450,12 @@ impl super::WorkspaceClient for Client { #[cfg(feature = "keyboard+hyprland")] impl KeyboardLayoutClient for Client { fn set_next_active(&self) { - let device = Devices::get() - .expect("Failed to get devices") + let Ok(devices) = Devices::get() else { + error!("Failed to get devices"); + return; + }; + + let device = devices .keyboards .iter() .find(|k| k.main) @@ -449,17 +475,18 @@ impl KeyboardLayoutClient for Client { fn subscribe(&self) -> Receiver { let rx = self.keyboard_layout.tx.subscribe(); - let layout = Devices::get() - .expect("Failed to get devices") - .keyboards - .iter() - .find(|k| k.main) - .map(|k| k.active_keymap.clone()); - - if let Some(layout) = layout { - send!(self.keyboard_layout.tx, KeyboardLayoutUpdate(layout)); - } else { - error!("Failed to get current keyboard layout hyprland"); + match Devices::get().map(|devices| { + devices + .keyboards + .iter() + .find(|k| k.main) + .map(|k| k.active_keymap.clone()) + }) { + Ok(Some(layout)) => { + send!(self.keyboard_layout.tx, KeyboardLayoutUpdate(layout)); + } + Ok(None) => error!("Failed to get current keyboard layout hyprland"), + Err(err) => error!("Failed to get devices: {err:#?}"), } rx