diff --git a/Cargo.lock b/Cargo.lock index c395cf8..7bc9a2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -187,6 +187,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "async-task" version = "4.7.1" @@ -296,6 +318,12 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "errno" version = "0.3.8" @@ -418,6 +446,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + [[package]] name = "gimli" version = "0.28.1" @@ -444,29 +478,33 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hyprland" -version = "0.4.0-alpha.3" +version = "0.4.0-beta.1" dependencies = [ "ahash", "async-net", "async-std", + "async-stream", "derive_more", + "either", "futures-lite 2.3.0", "hyprland-macros", "num-traits", "once_cell", "parking_lot", "paste", - "regex", + "phf", "serde", "serde_json", "serde_repr", "tokio", + "tokio-stream", ] [[package]] name = "hyprland-macros" -version = "0.4.0-alpha.3" +version = "0.4.0-beta.1" dependencies = [ + "proc-macro2", "quote", "syn", ] @@ -641,6 +679,48 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -714,43 +794,29 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.5.1" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "bitflags 2.5.0", + "rand_core", ] [[package]] -name = "regex" -version = "1.10.6" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] -name = "regex-automata" -version = "0.4.6" +name = "redox_syscall" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "bitflags 2.5.0", ] -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -839,6 +905,12 @@ dependencies = [ "syn", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -913,6 +985,31 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tracing" version = "0.1.40" diff --git a/Cargo.toml b/Cargo.toml index 51b6ac3..81a23dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ members = [ ] [workspace.package] -version = "0.4.0-alpha.3" +version = "0.4.0-beta.1" license = "GPL-3.0-or-later" repository = "https://github.com/hyprland-community/hyprland-rs" keywords = ["hyprland", "ipc", "hypr", "wayland", "linux"] @@ -35,7 +35,7 @@ categories = ["api-bindings"] authors = ["yavko "] [dependencies] -hyprland-macros = { path = "hyprland-macros", version = "0.4.0-alpha.1" } +hyprland-macros = { path = "hyprland-macros", version = "0.4.0-beta.1" } serde = { version = "1", features = ["derive"] } serde_json = "1" serde_repr = "0.1" @@ -44,16 +44,13 @@ tokio = { version = "1", features = [ "io-util", "macros", "net", + "sync", "rt" ], optional = true } +tokio-stream = { version = "0.1", features = ["sync"], optional = true } async-net = { version = "2.0", optional = true } async-std = { version = "1.12", optional = true } -futures-lite = { version = "2.3", optional = true, default-features = false } -regex = { version = "1.10", default-features = false, features = [ - "std", - "perf", - "unicode", -] } +futures-lite = { version = "2.3", default-features = false } num-traits = "0.2.19" paste = "1.0.14" derive_more = { version = "0.99", default-features = false, features = [ @@ -67,6 +64,9 @@ ahash = { version = "0.8", features = [ "no-rng", "serde", ], optional = true, default-features = false } +phf = {version="0.11.2", features = ["macros"] } +either = "1.13.0" +async-stream = "0.3.5" [features] default = [ @@ -79,8 +79,8 @@ default = [ "tokio", "ahash", ] -async-lite = ["dep:async-net", "dep:futures-lite"] -async-std = ["dep:async-std", "dep:futures-lite"] +async-lite = ["dep:async-net"] +async-std = ["dep:async-std"] tokio = ["dep:tokio"] dispatch = [] data = [] @@ -88,7 +88,6 @@ ctl = [] keyword = [] config = ["dispatch", "keyword"] listener = ["data", "dispatch"] -silent = [] parking_lot = ["dep:parking_lot", "once_cell/parking_lot", "tokio?/parking_lot"] ahash = ["dep:ahash"] unsafe-impl = [] diff --git a/README.md b/README.md index a457cc0..2b063c6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ An unofficial rust wrapper for Hyprland's IPC ## Help Wanted! We need help with developing the next version of hyprland-rs `0.4`, -if you know how to do the things in https://github.com/hyprland-community/hyprland-rs/milestone/4 +if you know how to do the things in contributions in those areas would be greatly appreciated! ## Disclaimer @@ -28,7 +28,7 @@ Let's get started with Hyprland-rs! Add the code below to the dependencies section of your Cargo.toml file! ```toml -hyprland = "0.3.13" +hyprland = "0.4.0-beta.1" ``` ### Reading the docs diff --git a/hyprland-macros/Cargo.toml b/hyprland-macros/Cargo.toml index 11d7067..4683069 100644 --- a/hyprland-macros/Cargo.toml +++ b/hyprland-macros/Cargo.toml @@ -17,3 +17,4 @@ proc-macro = true [dependencies] syn = { version = "2", features = ["full", "parsing"]} quote = "1" +proc-macro2 = "1" diff --git a/hyprland-macros/src/lib.rs b/hyprland-macros/src/lib.rs index 7bb974b..f0cfcdc 100644 --- a/hyprland-macros/src/lib.rs +++ b/hyprland-macros/src/lib.rs @@ -2,8 +2,12 @@ #![deny(missing_docs)] use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, ExprClosure}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, Block, ExprClosure, Result, Token, Type, +}; /// Creates a async closure #[proc_macro] @@ -18,3 +22,96 @@ pub fn async_closure(input: TokenStream) -> TokenStream { }}; expanded.into() } + +struct If { + type_to_match: Type, + input_type: Type, + true_branch: T, + false_branch: T, +} + +impl Parse for If { + fn parse(input: ParseStream) -> Result { + let type_to_match: Type = input.parse()?; + input.parse::()?; + let input_type: Type = input.parse()?; + input.parse::()?; + let true_branch: T = input.parse()?; + input.parse::()?; + let false_branch: T = input.parse()?; + Ok(If { + type_to_match, + input_type, + true_branch, + false_branch, + }) + } +} + +/// Creates a compile time if statement +/// that takes checks if 2 types are the same +/// and if returns one of the branches +#[proc_macro] +pub fn block_if(input: TokenStream) -> TokenStream { + let If { + type_to_match, + input_type, + true_branch, + false_branch, + } = parse_macro_input!(input as If); + let used_branch = if type_to_match.to_token_stream().to_string() + == input_type.to_token_stream().to_string() + { + true_branch + } else { + false_branch + } + .stmts; + let mut strm = TokenStream2::new(); + for stmt in used_branch { + stmt.to_tokens(&mut strm) + } + strm.into() +} + +/// Creates a compile time if statement +/// that takes checks if 2 types are the same +/// and if returns one of the branches +#[proc_macro] +pub fn type_if(input: TokenStream) -> TokenStream { + let If { + type_to_match, + input_type, + true_branch, + false_branch, + } = parse_macro_input!(input as If); + let used_branch = if type_to_match.to_token_stream().to_string() + == input_type.to_token_stream().to_string() + { + true_branch + } else { + false_branch + }; + used_branch.into_token_stream().into() +} + +/// Creates a compile time if statement +/// that takes checks if 2 types are the same +/// and if returns one of the branches +#[proc_macro] +pub fn expr_if(input: TokenStream) -> TokenStream { + let If { + type_to_match, + input_type, + true_branch, + false_branch, + } = parse_macro_input!(input as If); + let used_branch = if type_to_match.to_token_stream().to_string() + == input_type.to_token_stream().to_string() + { + true_branch + } else { + false_branch + }; + used_branch.into_token_stream().into() +} diff --git a/src/ctl.rs b/src/ctl.rs index 977aece..5162007 100644 --- a/src/ctl.rs +++ b/src/ctl.rs @@ -200,6 +200,49 @@ pub mod notify { Ok(()) } } +/// Dismisses all or up to a specified amount of notifications with Hyprland +pub mod dismissnotify { + use std::num::NonZeroU8; + + use super::*; + /// Dismisses notifications with Hyprland + /// + /// If `amount` is [None] then will dismiss ALL notifications + pub fn call(amount: Option) -> crate::Result<()> { + write_to_socket_sync( + SocketType::Command, + command!( + Empty, + "dismissnotify {}", + if let Some(amount) = amount { + amount.to_string() + } else { + (-1).to_string() + } + ), + )?; + Ok(()) + } + /// Dismisses notifications with Hyprland (async) + /// + /// If `amount` is [None] then will dismiss ALL notifications + pub async fn call_async(amount: Option) -> crate::Result<()> { + write_to_socket( + SocketType::Command, + command!( + Empty, + "dismissnotify {}", + if let Some(amount) = amount { + amount.to_string() + } else { + (-1).to_string() + } + ), + ) + .await?; + Ok(()) + } +} /// A 8-bit color with a alpha channel #[derive(Debug, Copy, Clone, MDisplay, Constructor, PartialEq, Eq)] diff --git a/src/dispatch.rs b/src/dispatch.rs index 51a2ea9..2cb8323 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -229,7 +229,7 @@ impl std::fmt::Display for WorkspaceIdentifier<'_> { } } -/// This enum is the params to MoveWindow dispatcher +/// This enum is the params to [DispatchType::MoveWindow] dispatcher #[derive(Debug, Clone)] pub enum WindowMove<'a> { /// Moves the window to a specified monitor @@ -299,13 +299,13 @@ pub enum DispatchType<'a> { MoveWindow(WindowMove<'a>), /// This dispatcher centers the active window CenterWindow, - /// This dispatcher resizes the active window using a [`Position`][Position] enum + /// This dispatcher resizes the active window using a [Position] enum ResizeActive(Position), - /// This dispatcher moves the active window using a [`Position`][Position] enum + /// This dispatcher moves the active window using a [Position] enum MoveActive(Position), - /// This dispatcher resizes the specified window using a [`Position`][Position] enum + /// This dispatcher resizes the specified window using a [Position] enum ResizeWindowPixel(Position, WindowIdentifier<'a>), - /// This dispatcher moves the specified window using a [`Position`][Position] enum + /// This dispatcher moves the specified window using a [Position] enum MoveWindowPixel(Position, WindowIdentifier<'a>), /// This dispatcher cycles windows using a specified direction CycleWindow(CycleDirection), @@ -414,7 +414,7 @@ pub enum LockType { ToggleLock, } -/// Param for [SwapWithMaster] dispatcher +/// Param for [DispatchType::SwapWithMaster] dispatcher #[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] pub enum SwapWithMasterParam { /// New focus is the new master window @@ -428,7 +428,7 @@ pub enum SwapWithMasterParam { Auto, } -/// Param for [FocusMaster] dispatcher +/// Param for [DispatchType::FocusMaster] dispatcher #[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] pub enum FocusMasterParam { /// Focus stays at master, (even if it was selected before) diff --git a/src/event_listener/async_im.rs b/src/event_listener/async_im.rs index 0be2577..fe16b6b 100644 --- a/src/event_listener/async_im.rs +++ b/src/event_listener/async_im.rs @@ -1,6 +1,4 @@ -use crate::shared::*; - -use crate::event_listener::shared::*; +use super::*; /// This struct is used for adding event handlers and executing them on events /// # The Event Listener @@ -27,46 +25,6 @@ impl Default for AsyncEventListener { } } -impl HasAsyncExecutor for AsyncEventListener { - async fn event_executor_async(&mut self, event: Event) -> crate::Result<()> { - match event { - Event::WorkspaceChanged(id) => arm_async!(id, workspace_changed_events, self), - Event::WorkspaceAdded(id) => arm_async!(id, workspace_added_events, self), - Event::WorkspaceDeleted(data) => { - arm_async!(data, workspace_destroyed_events, self) - } - Event::WorkspaceMoved(evend) => arm_async!(evend, workspace_moved_events, self), - Event::WorkspaceRename(even) => arm_async!(even, workspace_rename_events, self), - Event::ActiveMonitorChanged(evend) => { - arm_async!(evend, active_monitor_changed_events, self) - } - Event::ActiveWindowChangedMerged(event) => { - arm_async!(event, active_window_changed_events, self) - } - Event::ActiveWindowChangedV1(_) => (), - Event::ActiveWindowChangedV2(_) => (), - Event::FullscreenStateChanged(bool) => { - arm_async!(bool, fullscreen_state_changed_events, self) - } - Event::MonitorAdded(monitor) => arm_async!(monitor, monitor_added_events, self), - Event::MonitorRemoved(monitor) => arm_async!(monitor, monitor_removed_events, self), - Event::WindowClosed(addr) => arm_async!(addr, window_close_events, self), - Event::WindowMoved(even) => arm_async!(even, window_moved_events, self), - Event::WindowOpened(even) => arm_async!(even, window_open_events, self), - Event::LayoutChanged(even) => arm_async!(even, keyboard_layout_change_events, self), - Event::SubMapChanged(map) => arm_async!(map, sub_map_changed_events, self), - Event::LayerOpened(namespace) => arm_async!(namespace, layer_open_events, self), - Event::LayerClosed(namespace) => arm_async!(namespace, layer_closed_events, self), - Event::FloatStateChanged(even) => arm_async!(even, float_state_events, self), - Event::UrgentStateChanged(even) => arm_async!(even, urgent_state_events, self), - Event::Minimize(data) => arm_async!(data, minimize_events, self), - Event::WindowTitleChanged(addr) => arm_async!(addr, window_title_changed_events, self), - Event::Screencast(data) => arm_async!(data, screencast_events, self), - } - Ok(()) - } -} - impl AsyncEventListener { /// This method creates a new EventListener instance /// @@ -76,7 +34,7 @@ impl AsyncEventListener { /// ``` pub fn new() -> Self { Self { - events: init_events!(AsyncEvents), + events: create_events_async(), } } @@ -111,7 +69,8 @@ impl AsyncEventListener { let parsed: Vec = event_parser(string)?; for event in parsed { - self.event_primer_async(event, &mut active_windows).await?; + self.event_primer_exec_async(event, &mut active_windows) + .await?; } } diff --git a/src/event_listener/immutable.rs b/src/event_listener/immutable.rs index 59311c5..073fe45 100644 --- a/src/event_listener/immutable.rs +++ b/src/event_listener/immutable.rs @@ -25,39 +25,6 @@ impl Default for EventListener { } } -impl HasExecutor for EventListener { - fn event_executor(&mut self, event: Event) -> crate::Result<()> { - use Event::*; - match event { - WorkspaceChanged(id) => arm!(id, workspace_changed_events, self), - WorkspaceAdded(id) => arm!(id, workspace_added_events, self), - WorkspaceDeleted(data) => arm!(data, workspace_destroyed_events, self), - WorkspaceMoved(evend) => arm!(evend, workspace_moved_events, self), - WorkspaceRename(even) => arm!(even, workspace_rename_events, self), - ActiveMonitorChanged(evend) => arm!(evend, active_monitor_changed_events, self), - ActiveWindowChangedMerged(opt) => arm!(opt, active_window_changed_events, self), - ActiveWindowChangedV1(_) => (), - ActiveWindowChangedV2(_) => (), - FullscreenStateChanged(bool) => arm!(bool, fullscreen_state_changed_events, self), - MonitorAdded(monitor) => arm!(monitor, monitor_added_events, self), - MonitorRemoved(monitor) => arm!(monitor, monitor_removed_events, self), - WindowClosed(addr) => arm!(addr, window_close_events, self), - WindowMoved(even) => arm!(even, window_moved_events, self), - WindowOpened(even) => arm!(even, window_open_events, self), - LayoutChanged(even) => arm!(even, keyboard_layout_change_events, self), - SubMapChanged(map) => arm!(map, sub_map_changed_events, self), - LayerOpened(namespace) => arm!(namespace, layer_open_events, self), - LayerClosed(namespace) => arm!(namespace, layer_closed_events, self), - FloatStateChanged(even) => arm!(even, float_state_events, self), - UrgentStateChanged(even) => arm!(even, urgent_state_events, self), - Minimize(data) => arm!(data, minimize_events, self), - WindowTitleChanged(addr) => arm!(addr, window_title_changed_events, self), - Screencast(data) => arm!(data, screencast_events, self), - } - Ok(()) - } -} - impl EventListener { /// This method creates a new EventListener instance /// @@ -67,7 +34,7 @@ impl EventListener { /// ``` pub fn new() -> EventListener { EventListener { - events: init_events!(Events), + events: create_events(), } } diff --git a/src/event_listener/macros.rs b/src/event_listener/macros.rs index 3601351..1ef20db 100644 --- a/src/event_listener/macros.rs +++ b/src/event_listener/macros.rs @@ -1,92 +1,143 @@ -macro_rules! add_listener { - ($name:ident $end:ident,$f:ty,$c:literal,$c2:literal => $id:ident) => { - add_listener_reg!($name $end,$f,$c,$c2 => $id); - add_async_listener!($name $end,$f,$c,$c2 => $id); +macro_rules! events { + ($($name:ty => $f:ty,$c:literal,$c2:literal => $id:ident);*) => { + paste! { + pub(crate) struct Events { + $( + pub(crate) [<$name:snake _events>]: type_if! {(),$f,Vec, Closures<$f>} + ),* + } + #[allow(clippy::type_complexity)] + pub(crate) struct AsyncEvents { + $( + pub(crate) [<$name:snake _events>]: type_if! {(),$f,Vec, AsyncClosures<$f>} + ),* + } + pub(crate) fn create_events() -> Events { + Events { + $([<$name:snake _events>]: vec![]),* + } + } + pub(crate) fn create_events_async() -> AsyncEvents { + AsyncEvents { + $([<$name:snake _events>]: vec![]),* + } + } + + impl HasAsyncExecutor for AsyncEventListener { + async fn event_executor_async(&mut self, event: Event) -> crate::Result<()> { + use Event::*; + match event { + $( + expr_if! {(),$f, $name, $name($id)} => expr_if! { + (), + $f, + arm_async!([<$name:snake _events>], self), + arm_async!($id, [<$name:snake _events>], self) + }, + )* + _ => () + } + Ok(()) + } + } + impl HasExecutor for EventListener { + fn event_executor(&mut self, event: Event) -> crate::Result<()> { + use Event::*; + match event { + $( + expr_if! {(),$f, $name, $name($id)} => expr_if! { + (), + $f, + arm!([<$name:snake _events>], self), + arm!($id, [<$name:snake _events>], self) + }, + )* + _ => () + } + Ok(()) + } + } + } + $( + paste!{ + block_if!{ + (), + $f, + { + add_listener!{[<$name:snake>],$c,$c2 => $id} + }, + { + add_listener!{[<$name:snake>],$f,$c,$c2 => $id} + } + } + } + )* }; +} + +macro_rules! add_listener { ($name:ident,$f:ty,$c:literal,$c2:literal => $id:ident) => { add_listener_reg!($name,$f,$c,$c2 => $id); add_async_listener!($name,$f,$c,$c2 => $id); }; + ($name:ident,$c:literal,$c2:literal => $id:ident) => { + add_listener_reg!($name,$c,$c2 => $id); + add_async_listener!($name,$c,$c2 => $id); + }; } -macro_rules! add_listener_reg { - ($name:ident $end:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { - paste! { - impl EventListener { - #[doc = concat!("This methods adds a event which ", stringify!($c), r#" -```rust, no_run -use hyprland::event_listener::EventListener; -let mut listener = EventListener::new(); -listener.add_"#, stringify!($name), r#"_handler(|"#, stringify!($id), r#"| println!(""#, $c2, ": {", stringify!($id), r#":#?}")); -listener.start_listener();"#)] - pub fn [](&mut self, f: impl Fn($f) + 'static) { - self.events.[<$name $end _events>].push(Box::new(f)); - } - } - } +macro_rules! add_async_listener { + ($name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { + add_async_listener_raw!($name,$name,impl Fn($f) -> VoidFuture + Send + Sync + 'static,$c,$c2 => $id); + }; + ($name:ident,$c:literal,$c2:expr => $id:ident) => { + add_async_listener_raw!($name,$name,impl Fn() -> VoidFuture + Send + Sync + 'static,$c,$c2 => $id); }; +} +macro_rules! add_listener_reg { ($name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { - paste! { - impl EventListener { - #[doc = concat!("This methods adds a event which executes when ", $c, r#" -```rust, no_run -use hyprland::event_listener::EventListener; -let mut listener = EventListener::new(); -listener.add_"#, stringify!($name), r#"_handler(|"#, stringify!($id), r#"| println!(""#, $c2, ": {", stringify!($id), r#":#?}")); -listener.start_listener();"#)] - pub fn [](&mut self, f: impl Fn($f) + 'static) { - self.events.[<$name _events>].push(Box::new(f)); - } - } - } + add_listener_reg_raw!($name,$name,impl Fn($f) + 'static,$c,$c2 => $id); + }; + ($name:ident,$c:literal,$c2:expr => $id:ident) => { + add_listener_reg_raw!($name,$name,impl Fn() + 'static,$c,$c2 => $id); }; } -macro_rules! add_async_listener { - ($name:ident $end:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { +macro_rules! add_listener_reg_raw { + ($name:ident,$list_name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { paste! { - impl AsyncEventListener { - #[doc = concat!("This methods adds a event which ", $c, r#" + impl EventListener { + #[doc = concat!("This method adds an event which executes when", stringify!($c), r#" ```rust, no_run use hyprland::event_listener::EventListener; let mut listener = EventListener::new(); listener.add_"#, stringify!($name), r#"_handler(|"#, stringify!($id), r#"| println!(""#, $c2, ": {", stringify!($id), r#":#?}")); listener.start_listener();"#)] - pub fn [](&mut self, f: impl Fn($f) -> VoidFuture + Send + Sync + 'static) { - self.events.[<$name $end _events>].push(Box::pin(f)); + pub fn [](&mut self, f: $f) { + self.events.[<$list_name _events>].push(Box::new(f)); } } } }; - ($name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { +} +macro_rules! add_async_listener_raw { + ($name:ident,$list_name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { paste! { impl AsyncEventListener { - #[doc = concat!("This methods adds a event which executes when ", $c, r#" + #[doc = concat!("This method adds an event which executes when ", $c, r#" ```rust, no_run use hyprland::event_listener::EventListener; let mut listener = EventListener::new(); listener.add_"#, stringify!($name), r#"_handler(|"#, stringify!($id), r#"| println!(""#, $c2, ": {", stringify!($id), r#":#?}")); listener.start_listener();"#)] - pub fn [](&mut self, f: impl Fn($f) -> VoidFuture + Send + Sync + 'static) { - self.events.[<$name _events>].push(Box::pin(f)); + pub fn [](&mut self, f: $f) { + self.events.[<$list_name _events>].push(Box::pin(f)); } } } }; } -#[allow(unused_macros)] -macro_rules! arm_alpha { - ($sync:ident; $val:expr,$nam:ident,$se:ident) => {{ - paste! { - let events = &$se.events.$nam; - for item in events.iter() { - [](item, $val); - } - } - }}; -} - macro_rules! arm { ($val:expr,$nam:ident,$se:ident) => {{ let events = &$se.events.$nam; @@ -94,6 +145,12 @@ macro_rules! arm { execute_closure(item, $val.clone()); } }}; + ($nam:ident,$se:ident) => {{ + let events = &$se.events.$nam; + for item in events.iter() { + execute_empty_closure(item); + } + }}; } macro_rules! arm_async { @@ -103,33 +160,10 @@ macro_rules! arm_async { execute_closure_async(item, $val.clone()).await; } }}; -} - -macro_rules! init_events { - ($name:ident) => { - $name { - workspace_changed_events: vec![], - workspace_added_events: vec![], - workspace_destroyed_events: vec![], - workspace_moved_events: vec![], - workspace_rename_events: vec![], - active_monitor_changed_events: vec![], - active_window_changed_events: vec![], - fullscreen_state_changed_events: vec![], - monitor_removed_events: vec![], - monitor_added_events: vec![], - window_open_events: vec![], - window_close_events: vec![], - window_moved_events: vec![], - keyboard_layout_change_events: vec![], - sub_map_changed_events: vec![], - layer_open_events: vec![], - layer_closed_events: vec![], - float_state_events: vec![], - urgent_state_events: vec![], - minimize_events: vec![], - window_title_changed_events: vec![], - screencast_events: vec![], + ($nam:ident,$se:ident) => {{ + let events = &$se.events.$nam; + for item in events.iter() { + execute_empty_closure_async(item).await; } - }; + }}; } diff --git a/src/event_listener/mod.rs b/src/event_listener/mod.rs index 57545d8..a0564b1 100644 --- a/src/event_listener/mod.rs +++ b/src/event_listener/mod.rs @@ -1,3 +1,9 @@ +//! # Event Listener Module +//! for documentation go to: +//! * [crate::event_listener::EventStream] for the event listener implementation based on the [futures_lite::Stream] api +//! * [crate::event_listener::EventListener] for the normal [Fn] based event listener +//! * [crate::event_listener::AsyncEventListener] for the [Fn] based event listener which uses closures that return [std::future::Future]s + #[macro_use] mod macros; @@ -12,25 +18,40 @@ pub use crate::event_listener::immutable::EventListener; mod async_im; pub use crate::event_listener::async_im::AsyncEventListener; -add_listener!(workspace_change d, WorkspaceType, "on workspace change", "changed workspace to" => id); -add_listener!(workspace_added, WorkspaceType, "a workspace is created", "workspace was added" => id); -add_listener!(workspace_destroy ed, WorkspaceDestroyedEventData, "a workspace is destroyed", "a workspace was destroyed" => data); -add_listener!(workspace_moved, MonitorEventData, "a workspace is moved", "workspace was moved" => id); -add_listener!(workspace_rename, WorkspaceRenameEventData, "a workspace is renamed", "workspace was renamed" => id); -add_listener!(active_monitor_change d, MonitorEventData, "the active monitor is changed", "Active monitor changed to" => data); -add_listener!(active_window_change d, Option, "the active window is changed", "Active window changed" => data); -add_listener!(fullscreen_state_change d, bool, "the active monitor is changed", "Fullscreen is on" => state); -add_listener!(monitor_added, String, "a new monitor is added", "Monitor added" => data); -add_listener!(monitor_removed, String, "a monitor is removed", "Monitor removed" => data); -add_listener!(window_open, WindowOpenEvent, "a window is opened", "Window opened" => data); -add_listener!(window_close, Address, "a window is closed", "Window closed" => data); -add_listener!(window_moved, WindowMoveEvent, "a window is moved", "Window moved" => data); -add_listener!(keyboard_layout_change, LayoutEvent, "the keyboard layout is changed", "Layout changed" => data); -add_listener!(sub_map_change d, String, "the sub map is changed", "Submap changed" => data); -add_listener!(layer_open, String, "a new layer is opened", "Layer opened" => data); -add_listener!(layer_closed, String, "a layer is closed", "Layer closed" => data); -add_listener!(float_state, WindowFloatEventData, "the float state of a window is changed", "Float state changed" => data); -add_listener!(urgent_state, Address, "the urgent state of a window is changed", "urgent state changed" => data); -add_listener!(minimize, MinimizeEventData, "the minimize state of a window is changed", "minimize state changed" => data); -add_listener!(window_title_change d, Address, "a window title is changed", "A window title changed" => data); -add_listener!(screencast, ScreencastEventData, "the screencast state of a window is changed", "screencast state changed" => data); +mod stream; +pub use crate::event_listener::stream::EventStream; + +// generates code for the closure based event listeners +events! { + WorkspaceChanged => WorkspaceEventData, "on workspace change", "changed workspace to" => id; + WorkspaceAdded => WorkspaceEventData, "a workspace is created", "workspace was added" => id; + WorkspaceDeleted => WorkspaceEventData, "a workspace is destroyed", "a workspace was destroyed" => data; + WorkspaceMoved => WorkspaceMovedEventData, "a workspace is moved", "workspace was moved" => id; + WorkspaceRenamed => NonSpecialWorkspaceEventData, "a workspace is renamed", "workspace was renamed" => id; + ActiveMonitorChanged => MonitorEventData, "the active monitor is changed", "Active monitor changed to" => data; + ActiveWindowChanged => Option, "the active window is changed", "Active window changed" => data; + FullscreenStateChanged => bool, "the fullscreen state is changed", "Fullscreen is on" => state; + MonitorAdded => MonitorAddedEventData, "a new monitor is added", "Monitor added" => data; + MonitorRemoved => String, "a monitor is removed", "Monitor removed" => data; + WindowOpened => WindowOpenEvent, "a window is opened", "Window opened" => data; + WindowClosed => Address, "a window is closed", "Window closed" => data; + WindowMoved => WindowMoveEvent, "a window is moved", "Window moved" => data; + SpecialRemoved => String, "a monitor's special workspace is removed", "Special Workspace removed" => monitor; + ChangedSpecial => ChangedSpecialEventData, "a monitor's special workspace is changed", "Special Workspace changed" => data; + LayoutChanged => LayoutEvent, "the keyboard layout is changed", "Layout changed" => data; + SubMapChanged => String, "the submap is changed", "Submap changed" => data; + LayerOpened => String, "a new layer is opened", "Layer opened" => data; + LayerClosed => String, "a layer is closed", "Layer closed" => data; + FloatStateChanged => WindowFloatEventData, "the float state of a window is changed", "Float state changed" => data; + UrgentStateChanged => Address, "the urgent state of a window is changed", "urgent state changed" => data; + WindowTitleChanged => WindowTitleEventData, "a window title is changed", "A window title changed" => data; + Screencast => ScreencastEventData, "the screencast state of a window is changed", "screencast state changed" => data; + ConfigReloaded => (), "the configuration of Hyprland is reloaded", "config reloaded" => _empty; + IgnoreGroupLockStateChanged => bool, "the state of ignore group lock is toggled", "ignore group lock toggled to" => data; + LockGroupsStateChanged => bool, "the state of lock groups is toggled", "lock group state toggled to" => data; + WindowPinned => WindowPinEventData, "the pinned state of a window is changed", "window pin was set to" => state; + GroupToggled => GroupToggledEventData, "a group was toggled", "the group toggle state was set to" => data; + WindowMovedIntoGroup => Address, "a window was moved into a group", "a window was moved into a group with the address of" => addr; + WindowMovedOutOfGroup => Address, "a window was moved out of a group", "a window was moved out of a group with the address of" => addr; + Unknown => UnknownEventData, "the state of some unknown event changed", "unknown state changed to" => value +} diff --git a/src/event_listener/shared.rs b/src/event_listener/shared.rs index 84feaa5..6d5d125 100644 --- a/src/event_listener/shared.rs +++ b/src/event_listener/shared.rs @@ -1,20 +1,6 @@ use crate::shared::*; -use once_cell::sync::Lazy; -use regex::{Error as RegexError, Regex}; use std::{fmt::Debug, pin::Pin}; -/// This trait provides shared behaviour for listener types -pub(crate) trait Listener: HasExecutor { - /// This method starts the event listener - fn start_listener() -> crate::Result<()>; -} - -/// This trait provides shared behaviour for listener types -pub(crate) trait AsyncListener: HasAsyncExecutor { - /// This method starts the event listener (async) - async fn start_listener_async() -> crate::Result<()>; -} - #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum ActiveWindowValue { Queued(T), // aka Some(T) @@ -86,10 +72,59 @@ pub(crate) trait HasExecutor { } } +pub(crate) fn event_primer_noexec<'a>( + event: Event, + abuf: &mut Vec, +) -> crate::Result> { + if abuf.is_empty() { + abuf.push(ActiveWindowState::new()); + } + let mut events: Vec = vec![]; + if let Event::ActiveWindowChangedV1(data) = event { + let mut to_remove = vec![]; + let data = into(data); + for (index, awin) in abuf.iter_mut().enumerate() { + if awin.title.is_empty() && awin.class.is_empty() { + (awin.class, awin.title) = data.clone(); + } + if awin.ready() { + if let Some(event) = awin.get_event() { + events.push(event); + }; + to_remove.push(index); + break; + } + } + for index in to_remove.into_iter().rev() { + abuf.swap_remove(index); + } + } else if let Event::ActiveWindowChangedV2(data) = event { + let mut to_remove = vec![]; + for (index, awin) in abuf.iter_mut().enumerate() { + if awin.addr.is_empty() { + awin.addr = data.clone().into(); + } + if awin.ready() { + if let Some(event) = awin.get_event() { + events.push(event); + }; + to_remove.push(index); + break; + } + } + for index in to_remove.into_iter().rev() { + abuf.swap_remove(index); + } + } else { + events.push(event); + } + Ok(events) +} + pub(crate) trait HasAsyncExecutor { async fn event_executor_async(&mut self, event: Event) -> crate::Result<()>; - async fn event_primer_async( + async fn event_primer_exec_async( &mut self, event: Event, abuf: &mut Vec, @@ -97,42 +132,8 @@ pub(crate) trait HasAsyncExecutor { where Self: std::marker::Sized, { - if abuf.is_empty() { - abuf.push(ActiveWindowState::new()); - } - if let Event::ActiveWindowChangedV1(data) = event { - let mut to_remove = vec![]; - let data = into(data); - for (index, awin) in abuf.iter_mut().enumerate() { - if awin.title.is_empty() && awin.class.is_empty() { - (awin.class, awin.title) = data.clone(); - } - if awin.ready() { - awin.execute_async(self).await?; - to_remove.push(index); - break; - } - } - for index in to_remove.into_iter().rev() { - abuf.swap_remove(index); - } - } else if let Event::ActiveWindowChangedV2(data) = event { - let mut to_remove = vec![]; - for (index, awin) in abuf.iter_mut().enumerate() { - if awin.addr.is_empty() { - awin.addr = data.clone().into(); - } - if awin.ready() { - awin.execute_async(self).await?; - to_remove.push(index); - break; - } - } - for index in to_remove.into_iter().rev() { - abuf.swap_remove(index); - } - } else { - self.event_executor_async(event).await?; + for x in event_primer_noexec(event, abuf)? { + self.event_executor_async(x).await?; } Ok(()) } @@ -143,38 +144,32 @@ impl ActiveWindowState { use ActiveWindowValue::{None, Queued}; let data = (&self.title, &self.class, &self.addr); if let (Queued(ref title), Queued(ref class), Queued(ref addr)) = data { - listener.event_executor(Event::ActiveWindowChangedMerged(Some(WindowEventData { - window_class: class.to_string(), - window_title: title.to_string(), - window_address: addr.clone(), + listener.event_executor(Event::ActiveWindowChanged(Some(WindowEventData { + class: class.to_string(), + title: title.to_string(), + address: addr.clone(), })))?; self.reset(); } else if let (None, None, None) = data { - listener.event_executor(Event::ActiveWindowChangedMerged(Option::None))?; + listener.event_executor(Event::ActiveWindowChanged(Option::None))?; } Ok(()) } - pub async fn execute_async( - &mut self, - listener: &mut T, - ) -> crate::Result<()> { + pub fn get_event(&mut self) -> Option { use ActiveWindowValue::{None, Queued}; let data = (&self.title, &self.class, &self.addr); + let mut event = Option::None; if let (Queued(ref title), Queued(ref class), Queued(ref addr)) = data { - listener - .event_executor_async(Event::ActiveWindowChangedMerged(Some(WindowEventData { - window_class: class.to_string(), - window_title: title.to_string(), - window_address: addr.clone(), - }))) - .await?; + event = Some(Event::ActiveWindowChanged(Some(WindowEventData { + class: class.to_string(), + title: title.to_string(), + address: addr.clone(), + }))); self.reset(); } else if let (None, None, None) = data { - listener - .event_executor_async(Event::ActiveWindowChangedMerged(Option::None)) - .await?; + event = Some(Event::ActiveWindowChanged(Option::None)); } - Ok(()) + event } pub fn ready(&self) -> bool { @@ -219,96 +214,20 @@ pub(crate) type AsyncEventType = Pin>; pub(crate) type VoidFuture = std::pin::Pin + Send>>; +pub(crate) type EmptyClosure = EventType; pub(crate) type Closure = EventType; pub(crate) type AsyncClosure = AsyncEventType VoidFuture>; +pub(crate) type EmptyAsyncClosure = AsyncEventType VoidFuture>; pub(crate) type Closures = Vec>; pub(crate) type AsyncClosures = Vec>; -pub(crate) struct Events { - pub(crate) workspace_changed_events: Closures, - pub(crate) workspace_added_events: Closures, - pub(crate) workspace_destroyed_events: Closures, - pub(crate) workspace_moved_events: Closures, - pub(crate) workspace_rename_events: Closures, - pub(crate) active_monitor_changed_events: Closures, - pub(crate) active_window_changed_events: Closures>, - pub(crate) fullscreen_state_changed_events: Closures, - pub(crate) monitor_removed_events: Closures, - pub(crate) monitor_added_events: Closures, - pub(crate) keyboard_layout_change_events: Closures, - pub(crate) sub_map_changed_events: Closures, - pub(crate) window_open_events: Closures, - pub(crate) window_close_events: Closures
, - pub(crate) window_moved_events: Closures, - pub(crate) layer_open_events: Closures, - pub(crate) layer_closed_events: Closures, - pub(crate) float_state_events: Closures, - pub(crate) urgent_state_events: Closures
, - pub(crate) minimize_events: Closures, - pub(crate) window_title_changed_events: Closures
, - pub(crate) screencast_events: Closures, -} - -#[allow(clippy::type_complexity)] -pub(crate) struct AsyncEvents { - pub(crate) workspace_changed_events: AsyncClosures, - pub(crate) workspace_added_events: AsyncClosures, - pub(crate) workspace_destroyed_events: AsyncClosures, - pub(crate) workspace_moved_events: AsyncClosures, - pub(crate) workspace_rename_events: AsyncClosures, - pub(crate) active_monitor_changed_events: AsyncClosures, - pub(crate) active_window_changed_events: AsyncClosures>, - pub(crate) fullscreen_state_changed_events: AsyncClosures, - pub(crate) monitor_removed_events: AsyncClosures, - pub(crate) monitor_added_events: AsyncClosures, - pub(crate) keyboard_layout_change_events: AsyncClosures, - pub(crate) sub_map_changed_events: AsyncClosures, - pub(crate) window_open_events: AsyncClosures, - pub(crate) window_close_events: AsyncClosures
, - pub(crate) window_moved_events: AsyncClosures, - pub(crate) layer_open_events: AsyncClosures, - pub(crate) layer_closed_events: AsyncClosures, - pub(crate) float_state_events: AsyncClosures, - pub(crate) urgent_state_events: AsyncClosures
, - pub(crate) minimize_events: AsyncClosures, - pub(crate) window_title_changed_events: AsyncClosures
, - pub(crate) screencast_events: AsyncClosures, -} - -/// Event data for destroyworkspacev2 event -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct WorkspaceDestroyedEventData { - /// Workspace Id - pub workspace_id: WorkspaceId, - /// Workspace name - pub workspace_name: String, -} - -/// Event data for renameworkspace event -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct WorkspaceRenameEventData { - /// Workspace id - pub workspace_id: WorkspaceId, - /// Workspace name content - pub workspace_name: String, -} - -/// Event data for a minimize event -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MinimizeEventData { - /// Window address - pub window_address: Address, - /// whether it's minimized or not - pub is_minimized: bool, -} - /// Event data for screencast event #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ScreencastEventData { /// State/Is it turning on? - pub is_turning_on: bool, + pub turning_on: bool, /// Owner type, is it a monitor? - pub is_monitor: bool, + pub monitor: bool, } /// The data for the event executed when moving a window to a new workspace @@ -316,12 +235,14 @@ pub struct ScreencastEventData { pub struct WindowMoveEvent { /// Window address pub window_address: Address, + /// the workspace id + pub workspace_id: WorkspaceId, /// The workspace name - pub workspace_name: String, + pub workspace_name: WorkspaceType, } /// The data for the event executed when opening a new window -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct WindowOpenEvent { /// Window address pub window_address: Address, @@ -419,70 +340,249 @@ impl State { } } +pub(crate) fn execute_empty_closure(f: &EmptyClosure) { + f(); +} + pub(crate) fn execute_closure(f: &Closure, val: T) { f(val); } +pub(crate) async fn execute_empty_closure_async(f: &EmptyAsyncClosure) { + f().await; +} pub(crate) async fn execute_closure_async(f: &AsyncClosure, val: T) { f(val).await; } -/// This tuple struct holds window event data +/// This struct holds workspace event data +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WorkspaceEventData { + /// The workspace name + pub name: WorkspaceType, + /// The window id + pub id: WorkspaceId, +} + +/// This struct holds workspace event data +/// when the workspace cannot be special +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NonSpecialWorkspaceEventData { + /// The workspace name + pub name: String, + /// The window id + pub id: WorkspaceId, +} + +/// This struct holds workspace moved event data +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WorkspaceMovedEventData { + /// The workspace name + pub name: WorkspaceType, + /// The window id + pub id: WorkspaceId, + /// The monitor name + pub monitor: String, +} + +/// This struct holds window event data #[derive(Debug, Clone, PartialEq, Eq)] pub struct WindowEventData { /// The window class - pub window_class: String, + pub class: String, /// The window title - pub window_title: String, + pub title: String, /// The window address - pub window_address: Address, + pub address: Address, } -/// This tuple struct holds monitor event data +/// This struct holds monitor event data #[derive(Debug, Clone, PartialEq, Eq)] pub struct MonitorEventData { /// The monitor name pub monitor_name: String, - /// The workspace - pub workspace: WorkspaceType, + /// The workspace name + pub workspace_name: Option, +} + +/// This struct holds changed special event data +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ChangedSpecialEventData { + /// The monitor name + pub monitor_name: String, + /// The workspace name + pub workspace_name: String, } -/// This tuple struct holds monitor event data +/// This struct holds monitor event data +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MonitorAddedEventData { + /// The monitor's id + pub id: u8, + /// The monitor's name + pub name: String, + /// the monitor's description + pub description: String, +} + +/// This struct holds window float event data #[derive(Debug, Clone, PartialEq, Eq)] pub struct WindowFloatEventData { /// The window address - pub window_address: Address, + pub address: Address, /// The float state - pub is_floating: bool, + pub floating: bool, +} + +/// This struct holds window pin event data +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WindowPinEventData { + /// The window address + pub address: Address, + /// The pin state + pub pinned: bool, +} + +/// This struct holds the event data for the windowtitle changed event +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WindowTitleEventData { + /// The window address + pub address: Address, + /// The window title + pub title: String, +} + +/// This struct represents an unknown event to hyprland-rs +/// this allows you to use events that haven't been implemented in hyprland-rs. +/// To use this use the [UnknownEventData::parse_args] method to properly get the args +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UnknownEventData { + /// The event's name + pub name: String, + /// The args as a string + pub args: String, +} + +impl UnknownEventData { + /// Takes the amount of args, and splits the string correctly + pub fn parse_args(self, count: usize) -> Vec { + self.args + .splitn(count, ",") + .map(|x| x.to_string()) + .collect() + } +} +/// This struct holds the data for the [Event::GroupToggled] event +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GroupToggledEventData { + /// The toggle status, `false` means the group was destroyed + pub toggled: bool, + /// The window addresses associated with the group + pub window_addresses: Vec
, } /// This enum holds every event type #[derive(Debug, Clone)] -pub(crate) enum Event { - WorkspaceChanged(WorkspaceType), - WorkspaceDeleted(WorkspaceDestroyedEventData), - WorkspaceAdded(WorkspaceType), - WorkspaceMoved(MonitorEventData), - WorkspaceRename(WorkspaceRenameEventData), - ActiveWindowChangedV1(Option<(String, String)>), - ActiveWindowChangedV2(Option
), - ActiveWindowChangedMerged(Option), +pub enum Event { + /// An unknown event + Unknown(UnknownEventData), + /// An event that emits when the current workspace is changed, + /// it is the equivelant of the `workspacev2` event + WorkspaceChanged(WorkspaceEventData), + /// An event that emits when a workspace is deleted, + /// it is the equivelant of the `destroyworkspacev2` event + WorkspaceDeleted(WorkspaceEventData), + /// An event that emits when a workspace is created, + /// it is the equivelant of the `createworkspacev2` event + WorkspaceAdded(WorkspaceEventData), + /// An event that emits when a workspace is moved to another monitor, + /// it is the equivelant of the `moveworkspacev2` event + WorkspaceMoved(WorkspaceMovedEventData), + /// An event that emits when a workspace is renamed, + /// it is the equivelant of the `renameworkspace` event + WorkspaceRenamed(NonSpecialWorkspaceEventData), + #[doc(hidden)] + ActiveWindowChangedV1(Option<(String, String)>), // internal intermediary event + #[doc(hidden)] + ActiveWindowChangedV2(Option
), // internal intermediary event + /// An event that emits when the active window is changed + /// Unlike the other events, this is a combination of 2 events + /// Those being `activewindow` and `activewindowv2`, + /// it waits for both, and then sends one unified event :) + ActiveWindowChanged(Option), + /// An event that emits when the active monitor is changed, + /// it is the equivelant of the `focusedmon` event ActiveMonitorChanged(MonitorEventData), + /// An event that emits when the current fullscreen state is changed, + /// it is the equivelant of the `fullscreen` event FullscreenStateChanged(bool), - MonitorAdded(String), + /// An event that emits when a new monitor is added/connected, + /// it is the equivelant of the `monitoraddedv2` event + MonitorAdded(MonitorAddedEventData), + /// An event that emits when a monitor is removed/disconnected, + /// it is the equivelant of the `monitorremoved` event MonitorRemoved(String), + /// An event that emits when a window is opened, + /// it is the equivelant of the `openwindow` event WindowOpened(WindowOpenEvent), + /// An event that emits when a window is closed, + /// it is the equivelant of the `closewindow` event WindowClosed(Address), + /// An event that emits when a window is moved to a different workspace, + /// it is the equivelant of the `movewindowv2` event WindowMoved(WindowMoveEvent), + /// An event that emits when a special workspace is closed on the current monitor, + /// it is the equivelant of the `activespecial` event + SpecialRemoved(String), + /// An event that emits when the current special workspace is changed on a monitor, + /// it is the equivelant of the `activespecial` event + ChangedSpecial(ChangedSpecialEventData), + /// An event that emits when the layout of a keyboard changes, + /// it is the equivelant of the `activelayout` event LayoutChanged(LayoutEvent), + /// An event that emits when the current keybind submap changes, + /// it is the equivelant of the `submap` event SubMapChanged(String), + /// An event that emits when a layer shell surface is opened/mapped, + /// it is the equivelant of the `openlayer` event LayerOpened(String), + /// An event that emits when a layer shell surface is closed/unmapped, + /// it is the equivelant of the `closelayer` event LayerClosed(String), + /// An event that emits when the floating state of a window changes, + /// it is the equivelant of the `changefloatingmode` event FloatStateChanged(WindowFloatEventData), + /// An event that emits when the a window requests the urgent state, + /// it is the equivelant of the `urgent` event UrgentStateChanged(Address), - Minimize(MinimizeEventData), - WindowTitleChanged(Address), + /// An event that emits when the title of a window changes, + /// it is the equivelant of the `windowtitlev2` event + WindowTitleChanged(WindowTitleEventData), + /// An event that emits when the screencopy state of a client changes + /// AKA, a process wants to capture/record your screen, + /// it is the equivelant of the `screencast` event Screencast(ScreencastEventData), + /// An event that emits when hyprland is reloaded, + /// it is the equivelant of the `configreloaded` event + ConfigReloaded, + /// An event that emits when `ignoregrouplock` is toggled, + /// it is the equivelant of the `ignoregrouplock` event + IgnoreGroupLockStateChanged(bool), + /// An event that emits when `lockgroups` is toggled, + /// it is the equivelant of the `lockgroups` event + LockGroupsStateChanged(bool), + /// An event that emits when a window is pinned or unpinned, + /// it is the equivelant of the `pin` event + WindowPinned(WindowPinEventData), + /// And event that emits when a group is toggled, + /// it is the equivelant of the `togglegroup` + GroupToggled(GroupToggledEventData), + /// And event that emits when a window is moved into a group, + /// it is the equivelant of the `moveintogroup` + WindowMovedIntoGroup(Address), + /// And event that emits when a window is moved out of a group, + /// it is the equivelant of the `moveoutofgroup` + WindowMovedOutOfGroup(Address), } fn parse_string_as_work(str: String) -> WorkspaceType { @@ -502,254 +602,189 @@ fn parse_string_as_work(str: String) -> WorkspaceType { } } -macro_rules! report_unknown { - ($event:expr) => { - #[cfg(not(feature = "silent"))] - eprintln!( - "An unknown event was passed into Hyprland-rs - PLEASE MAKE AN ISSUE!! - The event was: {event}", - event = $event - ); - }; -} - -#[cfg(feature = "ahash")] -use ahash::{HashSet, HashSetExt}; -#[cfg(not(feature = "ahash"))] -use std::collections::HashSet; - -#[cfg(feature = "parking_lot")] -use parking_lot::Mutex; -#[cfg(not(feature = "parking_lot"))] -use std::sync::Mutex; - -static CHECK_TABLE: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); - #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] -enum ParsedEventType { - WorkspaceChanged, +pub(crate) enum ParsedEventType { + WorkspaceChangedV2, WorkspaceDeletedV2, - WorkspaceAdded, - WorkspaceMoved, + WorkspaceAddedV2, + WorkspaceMovedV2, WorkspaceRename, ActiveWindowChangedV1, ActiveWindowChangedV2, ActiveMonitorChanged, FullscreenStateChanged, - MonitorAdded, + MonitorAddedV2, MonitorRemoved, WindowOpened, WindowClosed, - WindowMoved, + WindowMovedV2, + ActiveSpecial, LayoutChanged, SubMapChanged, LayerOpened, LayerClosed, FloatStateChanged, UrgentStateChanged, - Minimize, - WindowTitleChanged, + WindowTitleChangedV2, Screencast, - Unknown, + ConfigReloaded, + IgnoreGroupLock, + LockGroups, + Pin, + ToggleGroup, + MoveIntoGroup, + MoveOutOfGroup, } -/// All the recognized events -static EVENT_SET: Lazy> = Lazy::new(|| { - [ - ( - ParsedEventType::WorkspaceChanged, - r"\bworkspace>>(?P.*)", - ), - ( - ParsedEventType::WorkspaceDeletedV2, - r"destroyworkspacev2>>(?P.*),(?P.*)", - ), - ( - ParsedEventType::WorkspaceAdded, - r"createworkspace>>(?P.*)", - ), - ( - ParsedEventType::WorkspaceMoved, - r"moveworkspace>>(?P.*),(?P.*)", - ), - ( - ParsedEventType::WorkspaceRename, - r"renameworkspace>>(?P.*),(?P.*)", - ), - ( - ParsedEventType::ActiveMonitorChanged, - r"focusedmon>>(?P.*),(?P.*)", - ), - ( - ParsedEventType::ActiveWindowChangedV1, - r"activewindow>>(?P.*?),(?P.*)", - ), - ( - ParsedEventType::ActiveWindowChangedV2, - r"activewindowv2>>(?P<address>.*)", - ), - ( - ParsedEventType::FullscreenStateChanged, - r"fullscreen>>(?P<state>0|1)", - ), - ( - ParsedEventType::MonitorRemoved, - r"monitorremoved>>(?P<monitor>.*)", - ), - ( - ParsedEventType::MonitorAdded, - r"monitoradded>>(?P<monitor>.*)", - ), - ( - ParsedEventType::WindowOpened, - r"openwindow>>(?P<address>.*),(?P<workspace>.*),(?P<class>.*),(?P<title>.*)", - ), - ( - ParsedEventType::WindowClosed, - r"closewindow>>(?P<address>.*)", - ), - ( - ParsedEventType::WindowMoved, - r"movewindow>>(?P<address>.*),(?P<workspace>.*)", - ), - ( - ParsedEventType::LayoutChanged, - r"activelayout>>(?P<keyboard>.*)(?P<layout>.*)", - ), - (ParsedEventType::SubMapChanged, r"submap>>(?P<submap>.*)"), - ( - ParsedEventType::LayerOpened, - r"openlayer>>(?P<namespace>.*)", - ), - ( - ParsedEventType::LayerClosed, - r"closelayer>>(?P<namespace>.*)", - ), - ( - ParsedEventType::FloatStateChanged, - r"changefloatingmode>>(?P<address>.*),(?P<floatstate>[0-1])", - ), - ( - ParsedEventType::Minimize, - r"minimize>>(?P<address>.*),(?P<state>[0-1])", - ), - ( - ParsedEventType::Screencast, - r"screencast>>(?P<state>[0-1]),(?P<owner>[0-1])", - ), - ( - ParsedEventType::UrgentStateChanged, - r"urgent>>(?P<address>.*)", - ), - ( - ParsedEventType::WindowTitleChanged, - r"windowtitle>>(?P<address>.*)", - ), - (ParsedEventType::Unknown, r"(?P<Event>^[^>]*)"), - ].into_iter() - .map(|(e, r)| ( - e, - match Regex::new(r) { - Ok(value) => value, - Err(e) => { - // I believe that panics here are fine because the chances of the library user finding them are extremely high - // This check does occur at runtime though... - eprintln!("An internal error occured in hyprland-rs while parsing regex! Please open an issue!"); - match e { - RegexError::Syntax(str) => panic!("Regex syntax error: {str}"), - RegexError::CompiledTooBig(size) => { - panic!("The compiled regex size is too big! ({size})") - } - _ => panic!("Error compiling regex: {e}"), - } +/// All Hyprland events's arg count and enum variant. +/// The first item of the tuple is a usize of the argument count +/// This allows for easy parsing because the last arg in a Hyprland event +/// has the ability to have extra `,`s +pub(crate) static EVENTS: phf::Map<&'static str, (usize, ParsedEventType)> = phf::phf_map! { + "workspacev2" => ((2),ParsedEventType::WorkspaceChangedV2), + "destroyworkspacev2" => ((2),ParsedEventType::WorkspaceDeletedV2), + "createworkspacev2" => ((2),ParsedEventType::WorkspaceAddedV2), + "moveworkspacev2" => ((3),ParsedEventType::WorkspaceMovedV2), + "renameworkspace" => ((2),ParsedEventType::WorkspaceRename), + "focusedmon" => ((2),ParsedEventType::ActiveMonitorChanged), + "activewindow" => ((2),ParsedEventType::ActiveWindowChangedV1), + "activewindowv2" => ((1),ParsedEventType::ActiveWindowChangedV2), + "fullscreen" => ((1),ParsedEventType::FullscreenStateChanged), + "monitorremoved" => ((1),ParsedEventType::MonitorRemoved), + "monitoraddedv2" => ((3),ParsedEventType::MonitorAddedV2), + "openwindow" => ((4),ParsedEventType::WindowOpened), + "closewindow" => ((1),ParsedEventType::WindowClosed), + "movewindowv2" => ((3),ParsedEventType::WindowMovedV2), + "activelayout" => ((2),ParsedEventType::LayoutChanged), + "activespecial" => ((2),ParsedEventType::ActiveSpecial), + "submap" => ((1), ParsedEventType::SubMapChanged), + "openlayer" => ((1),ParsedEventType::LayerOpened), + "closelayer" => ((1),ParsedEventType::LayerClosed), + "changefloatingmode" => ((2),ParsedEventType::FloatStateChanged), + "screencast" => ((2),ParsedEventType::Screencast), + "urgent" => ((1),ParsedEventType::UrgentStateChanged), + "windowtitlev2" => ((2),ParsedEventType::WindowTitleChangedV2), + "configreloaded" => ((0),ParsedEventType::ConfigReloaded), + "ignoregrouplock" => ((1),ParsedEventType::IgnoreGroupLock), + "lockgroups" => ((1),ParsedEventType::LockGroups), + "pin" => ((2),ParsedEventType::Pin), + "togglegroup" => (2,ParsedEventType::ToggleGroup), + "moveintogroup" => (1,ParsedEventType::MoveIntoGroup), + "moveoutofgroup" => (1,ParsedEventType::MoveOutOfGroup) +}; + +use either::Either; + +fn new_event_parser( + input: &str, +) -> crate::Result<Either<(ParsedEventType, Vec<String>), (String, String)>> { + input + .to_string() + .split_once(">>") + .ok_or(HyprError::Other( + "could not get event name from Hyprland IPC data (not hyprland-rs)".to_string(), + )) + .map(|(name, x)| { + if let Some(event) = EVENTS.get(name) { + Either::Left(( + event.1, + x.splitn(event.0 as usize, ",") + .map(|y| y.to_string()) + .collect(), + )) + } else { + Either::Right((name.to_string(), x.to_string())) } }) - ).collect() -}); +} + +macro_rules! parse_int { + ($int:expr, event: $event:literal) => { + parse_int!($int, event: $event => WorkspaceId) + }; + ($int:expr, event: $event:literal => $int_type:ty) => { + ($int + .parse::<$int_type>() + .map_err(|e| + HyprError::Internal(format!(concat!($event, ": invalid integer error: {}"), e)) + )? + ) + }; + +} + +macro_rules! get { + ($args:expr ; $id:literal) => { + get![ref $args;$id].clone() + }; + (ref $args:expr ; $id:literal) => { + $args + .get($id) + .ok_or(HyprError::Internal( + concat!("could not get the event arg of index ", stringify!($id)).to_string(), + ))? + }; +} /// This internal function parses event strings pub(crate) fn event_parser(event: String) -> crate::Result<Vec<Event>> { // TODO: Optimize nested looped regex capturing. Maybe pull in rayon if possible. - let event_iter = event - .trim() - .lines() - .map(|event_line| { - let type_matches = EVENT_SET - .iter() - .filter_map(|(event_type, regex)| Some((event_type, regex.captures(event_line)?))) - .collect::<Vec<_>>(); - - (event_line, type_matches) - }) - .filter(|(_, b)| !b.is_empty()); - - let mut temp_event_holder = Vec::new(); - - for (event_str, matches) in event_iter { - match matches.len() { - 0 => hypr_err!( - "A Hyprland event that has no regex matches was passed! Please file a bug report!" - ), - 1 => { - report_unknown!((event_str.split('>').next().unwrap_or("unknown"))); - continue; - } - 2 => { - let (event_type, captures) = match matches - .into_iter() - .find(|(e, _)| **e != ParsedEventType::Unknown) { - Some(t) => t, - None => hypr_err!("The only events captured were unknown Hyprland events! Please file a bug report!"), - }; - - temp_event_holder.push((event_str, event_type, captures)); - } - _ => { - hypr_err!("Event matched more than one regex (not an unknown event issue!)"); - } + let event_iter = event.trim().lines().filter_map(|event_line| { + if event_line.is_empty() { + None + } else { + Some(new_event_parser(event_line)) } - } - - let parsed_events = temp_event_holder - .into_iter() - .map(|(event_str, event_type, captures)| match event_type { - ParsedEventType::WorkspaceChanged => { - let captured = &captures["workspace"]; - let workspace = if !captured.is_empty() { - parse_string_as_work(captured.to_string()) - } else { - WorkspaceType::Regular("1".to_string()) - }; - Ok(Event::WorkspaceChanged(workspace)) + }); + + let parsed_events = event_iter.map(|event| match event { + Err(x) => Err(x), + Ok(Either::Right((name, args))) => Ok(Event::Unknown(UnknownEventData { name, args })), + Ok(Either::Left((event_type, args))) => match event_type { + ParsedEventType::WorkspaceChangedV2 => { + Ok(Event::WorkspaceChanged(WorkspaceEventData { + id: parse_int!(get![ref args;0], event: "WorkspaceChangedV2"), + name: parse_string_as_work(get![args;1]), + })) + } + ParsedEventType::WorkspaceDeletedV2 => { + Ok(Event::WorkspaceDeleted(WorkspaceEventData { + id: parse_int!(get![ref args;0], event: "WorkspaceDeletedV2"), + name: parse_string_as_work(get![args;1]), + })) } - ParsedEventType::WorkspaceDeletedV2 => Ok(Event::WorkspaceDeleted(WorkspaceDestroyedEventData { workspace_id: captures["id"].parse::<WorkspaceId>().map_err(|e| HyprError::Internal(format!("Workspace delete v2: invalid integer error: {e}")))?, workspace_name: captures["name"].to_string() })), - ParsedEventType::WorkspaceAdded => Ok(Event::WorkspaceAdded(parse_string_as_work( - captures["workspace"].to_string(), - ))), - ParsedEventType::WorkspaceMoved => Ok(Event::WorkspaceMoved(MonitorEventData { - monitor_name: captures["monitor"].to_string(), - workspace: parse_string_as_work(captures["workspace"].to_string()), + ParsedEventType::WorkspaceAddedV2 => Ok(Event::WorkspaceAdded(WorkspaceEventData { + id: parse_int!(get![ref args;0], event: "WorkspaceAddedV2"), + name: parse_string_as_work(get![args;1]), })), + ParsedEventType::WorkspaceMovedV2 => { + Ok(Event::WorkspaceMoved(WorkspaceMovedEventData { + id: parse_int!(get![ref args;0], event: "WorkspaceMovedV2"), + name: parse_string_as_work(get![args;1]), + monitor: get![args;2], + })) + } ParsedEventType::WorkspaceRename => { - Ok(Event::WorkspaceRename(WorkspaceRenameEventData { - workspace_id: captures["id"] - .parse::<WorkspaceId>() - .map_err(|e| HyprError::Internal(format!("Workspace rename: invalid integer error: {e}")))?, - workspace_name: captures["name"].to_string(), + Ok(Event::WorkspaceRenamed(NonSpecialWorkspaceEventData { + id: parse_int!(get![args;0], event: "WorkspaceRenamed"), + name: get![args;1], })) } ParsedEventType::ActiveMonitorChanged => { Ok(Event::ActiveMonitorChanged(MonitorEventData { - monitor_name: captures["monitor"].to_string(), - workspace: WorkspaceType::Regular(captures["workspace"].to_string()), + monitor_name: get![args;0], + workspace_name: if get![args;1] == "?" { + None + } else { + Some(parse_string_as_work(get![args;1])) + }, })) } ParsedEventType::ActiveWindowChangedV1 => { - let class = &captures["class"]; - let title = &captures["title"]; + let class = get![args;0]; + let title = get![args;1]; let event = if !class.is_empty() && !title.is_empty() { - Event::ActiveWindowChangedV1(Some((class.to_string(), title.to_string()))) + Event::ActiveWindowChangedV1(Some((class, title))) } else { Event::ActiveWindowChangedV1(None) }; @@ -757,99 +792,104 @@ pub(crate) fn event_parser(event: String) -> crate::Result<Vec<Event>> { Ok(event) } ParsedEventType::ActiveWindowChangedV2 => { - let addr = &captures["address"]; + let addr = get![ref args;0]; let event = if addr != "," { - Event::ActiveWindowChangedV2(Some(Address::fmt_new(addr))) + Event::ActiveWindowChangedV2(Some(Address::new(addr))) } else { Event::ActiveWindowChangedV2(None) }; Ok(event) } ParsedEventType::FullscreenStateChanged => { - let state = &captures["state"] != "0"; - Ok(Event::FullscreenStateChanged(state)) - } - ParsedEventType::MonitorRemoved => { - Ok(Event::MonitorRemoved(captures["monitor"].to_string())) - } - ParsedEventType::MonitorAdded => { - Ok(Event::MonitorAdded(captures["monitor"].to_string())) + Ok(Event::FullscreenStateChanged(get![ref args;0] != "0")) } + ParsedEventType::MonitorRemoved => Ok(Event::MonitorRemoved(get![args;0])), + ParsedEventType::MonitorAddedV2 => Ok(Event::MonitorAdded(MonitorAddedEventData { + id: parse_int!(get![ref args;0], event: "MonitorAddedV2" => u8), + name: get![args;1], + description: get![args;2], + })), ParsedEventType::WindowOpened => Ok(Event::WindowOpened(WindowOpenEvent { - window_address: Address::fmt_new(&captures["address"]), - workspace_name: captures["workspace"].to_string(), - window_class: captures["class"].to_string(), - window_title: captures["title"].to_string(), + window_address: Address::new(get![ref args;0]), + workspace_name: get![args;1], + window_class: get![args;2], + window_title: get![args;3], })), - ParsedEventType::WindowClosed => Ok(Event::WindowClosed(Address::fmt_new(&captures["address"]))), - ParsedEventType::WindowMoved => Ok(Event::WindowMoved(WindowMoveEvent { - window_address: Address::fmt_new(&captures["address"]), - workspace_name: captures["workspace"].to_string(), + ParsedEventType::WindowClosed => Ok(Event::WindowClosed(Address::new(get![args;0]))), + ParsedEventType::WindowMovedV2 => Ok(Event::WindowMoved(WindowMoveEvent { + window_address: Address::fmt_new(get![ref args;0]), + workspace_id: parse_int!(get![ref args;1], event: "WindowMoved"), + workspace_name: parse_string_as_work(get![args;2]), })), + ParsedEventType::ActiveSpecial => { + let workspace_name = get![args;0]; + let monitor_name = get![args;1]; + if workspace_name.is_empty() { + Ok(Event::SpecialRemoved(monitor_name)) + } else { + Ok(Event::ChangedSpecial(ChangedSpecialEventData { + monitor_name, + workspace_name, + })) + } + } ParsedEventType::LayoutChanged => Ok(Event::LayoutChanged(LayoutEvent { - keyboard_name: captures["keyboard"].to_string(), - layout_name: captures["layout"].to_string(), + keyboard_name: get![args;0], + layout_name: get![args;1], })), - ParsedEventType::SubMapChanged => { - Ok(Event::SubMapChanged(captures["submap"].to_string())) - } - ParsedEventType::LayerOpened => { - Ok(Event::LayerOpened(captures["namespace"].to_string())) - } - ParsedEventType::LayerClosed => { - Ok(Event::LayerClosed(captures["namespace"].to_string())) - } + ParsedEventType::SubMapChanged => Ok(Event::SubMapChanged(get![args;0])), + ParsedEventType::LayerOpened => Ok(Event::LayerOpened(get![args;0])), + ParsedEventType::LayerClosed => Ok(Event::LayerClosed(get![args;0])), ParsedEventType::FloatStateChanged => { - let state = &captures["floatstate"] == "0"; // FIXME: does 0 mean it's floating? + let state = get![ref args;1] == "0"; // FIXME: does 0 mean it's floating? Ok(Event::FloatStateChanged(WindowFloatEventData { - window_address: Address::fmt_new(&captures["address"]), - is_floating: state, - })) - } - ParsedEventType::Minimize => { - let state = &captures["state"] == "1"; - Ok(Event::Minimize(MinimizeEventData { - window_address: Address::fmt_new(&captures["address"]), - is_minimized: state, + address: Address::new(get![ref args;0]), + floating: state, })) } ParsedEventType::Screencast => { - let state = &captures["state"] == "1"; - let owner = &captures["owner"] == "1"; + let state = get![ref args;0] == "1"; + let owner = get![ref args;1] == "1"; Ok(Event::Screencast(ScreencastEventData { - is_turning_on: state, - is_monitor: owner, + turning_on: state, + monitor: owner, })) } - ParsedEventType::UrgentStateChanged => Ok(Event::UrgentStateChanged(Address::fmt_new(&captures["address"]))), - ParsedEventType::WindowTitleChanged => Ok(Event::WindowTitleChanged(Address::fmt_new(&captures["address"]))), - ParsedEventType::Unknown => { - #[cfg(not(feature = "silent"))] - { - let table = CHECK_TABLE.lock(); - // The std mutex returns a Result, the parking_lot mutex does not. This is a hack that allows us to - // keep the table code how it is, without duplicating or `return`ing. - #[cfg(feature = "parking_lot")] - let table = Ok::<_, std::convert::Infallible>(table); - - if let Ok(mut tbl) = table { - let (event_string, print_str) = - match captures.name("event").map(|s| s.as_str()) { - Some(s) => (s.to_string(), s), - None => ("Unknown".to_owned(), event_str), - }; - - let should_run = tbl.insert(event_string); - if should_run { - eprintln!( - "An unknown event was passed into Hyprland-rs\nPLEASE MAKE AN ISSUE!!\nThe event was: {print_str}" - ); - } - } - } - hypr_err!("Unknown event: {event_str}"); + ParsedEventType::UrgentStateChanged => { + Ok(Event::UrgentStateChanged(Address::new(get![ref args;0]))) + } + ParsedEventType::WindowTitleChangedV2 => { + Ok(Event::WindowTitleChanged(WindowTitleEventData { + address: Address::new(get![ref args;0]), + title: get![args;1], + })) + } + ParsedEventType::ConfigReloaded => Ok(Event::ConfigReloaded), + ParsedEventType::IgnoreGroupLock => { + Ok(Event::IgnoreGroupLockStateChanged(get![ref args;0] == "1")) } - }); + ParsedEventType::LockGroups => { + Ok(Event::LockGroupsStateChanged(get![ref args;0] == "1")) + } + ParsedEventType::Pin => Ok(Event::WindowPinned(WindowPinEventData { + address: Address::new(get![ref args;0]), + pinned: get![ref args;1] == "1", + })), + ParsedEventType::ToggleGroup => Ok(Event::GroupToggled(GroupToggledEventData { + toggled: get![ref args;0] == "1", + window_addresses: get![ref args;1] + .split(",") + .map(|x| Address::new(x)) + .collect(), + })), + ParsedEventType::MoveIntoGroup => { + Ok(Event::WindowMovedIntoGroup(Address::new(get![ref args;0]))) + } + ParsedEventType::MoveOutOfGroup => { + Ok(Event::WindowMovedOutOfGroup(Address::new(get![ref args;0]))) + } + }, + }); let mut events: Vec<Event> = Vec::new(); @@ -857,9 +897,5 @@ pub(crate) fn event_parser(event: String) -> crate::Result<Vec<Event>> { events.push(event?); } - // if events.is_empty() { - // hypr_err!("No events!"); - // } - Ok(events) } diff --git a/src/event_listener/stream.rs b/src/event_listener/stream.rs new file mode 100644 index 0000000..7baa192 --- /dev/null +++ b/src/event_listener/stream.rs @@ -0,0 +1,73 @@ +use super::*; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use async_stream::try_stream; +use futures_lite::{Stream, StreamExt}; + +/// Event listener, but [Stream] +/// This is the new prefered way of listening for events +/// as its more idiomatic, and allows for more efficient memory management +/// +/// # Examples +/// ```rust +/// use hyprland::prelude::*; +/// use hyprland::event_listener::EventStream; +/// use hyprland::Result as HResult; +/// use futures_lite::StreamExt; +/// +/// #[tokio::main] +/// async fn main() -> HResult<()> { +/// let mut stream = EventStream::new(); +/// while let Some(Ok(event)) = stream.next().await { +/// println!("{event:?}"); +/// } +/// } +/// ``` +#[must_use = "streams nothing unless polled"] +pub struct EventStream { + stream: Pin<Box<dyn Stream<Item = crate::Result<Event>>>>, +} +impl EventStream { + /// Creates a new [EventListener] + pub fn new() -> Self { + use crate::unix_async::*; + let stream = try_stream! { + + let socket_path = get_socket_path(SocketType::Listener)?; + let mut stream = UnixStream::connect(socket_path).await?; + + let mut active_windows = vec![]; + loop { + let mut buf = [0; 4096]; + + let num_read = stream.read(&mut buf).await?; + if num_read == 0 { + break; + } + let buf = &buf[..num_read]; + let string = String::from_utf8(buf.to_vec())?; + let parsed: Vec<Event> = event_parser(string)?; + + for event in parsed { + for primed_event in event_primer_noexec(event, &mut active_windows)? { + yield primed_event; + } + } + } + }; + Self { + stream: Box::pin(stream), + } + } +} + +impl Stream for EventStream { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { + self.as_mut().stream.poll_next(cx) + } + + type Item = crate::Result<Event>; +} diff --git a/src/lib.rs b/src/lib.rs index 6e10dda..0f13a67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,10 @@ #[macro_use] extern crate paste; -pub use hyprland_macros::*; +#[macro_use] +extern crate hyprland_macros; + +pub use hyprland_macros::async_closure; /// This module provides several impls that are unsafe, for FFI purposes. Only use if you know what you are doing. #[cfg(feature = "unsafe-impl")] diff --git a/src/shared.rs b/src/shared.rs index f9ec13e..7242d7e 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -91,10 +91,6 @@ macro_rules! hypr_err { pub(crate) use hypr_err; -/// This type provides the result type used everywhere in Hyprland-rs -#[deprecated(since = "0.3.1", note = "New location: hyprland::Result")] -pub type HResult<T> = Result<T, HyprError>; - /// The address struct holds a address as a tuple with a single value /// and has methods to reveal the address in different data formats #[derive( @@ -109,7 +105,12 @@ impl Address { } /// This creates a new address from a value that implements [std::string::ToString] pub fn new<T: ToString>(string: T) -> Self { - Self(string.to_string()) + let str = string.to_string(); + if str.starts_with("0x") { + Self(str) + } else { + Self("0x".to_owned() + str.as_str()) + } } }