diff --git a/Cargo.lock b/Cargo.lock index 689338b..2159d0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1079,15 +1079,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - [[package]] name = "downcast-rs" version = "1.2.1" @@ -1961,16 +1952,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "libloading" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - [[package]] name = "libredox" version = "0.1.3" @@ -2621,9 +2602,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" dependencies = [ "memchr", ] @@ -4095,24 +4076,25 @@ dependencies = [ "tempfile", "tokio", "toml", + "wayland-backend", "wayland-client", "wayland-protocols", "wayland-protocols-plasma", "wayland-protocols-wlr", + "wayland-scanner", "x11rb", "zbus", ] [[package]] name = "wayland-backend" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" dependencies = [ "cc", "downcast-rs", "rustix 0.38.42", - "scoped-tls", "smallvec", "wayland-sys", ] @@ -4169,9 +4151,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", "quick-xml", @@ -4180,12 +4162,10 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" dependencies = [ - "dlib", - "log", "pkg-config", ] diff --git a/watchers/Cargo.toml b/watchers/Cargo.toml index aa5dc57..6fd87a8 100644 --- a/watchers/Cargo.toml +++ b/watchers/Cargo.toml @@ -16,6 +16,8 @@ tempfile = "3.13.0" [dependencies] aw-client-rust = { git = "https://github.com/ActivityWatch/aw-server-rust", rev = "656f3c9" } wayland-client = "0.31.7" +wayland-scanner = "0.31.6" +wayland-backend = "0.3.8" wayland-protocols = { version = "0.32.5", features = ["staging", "client" ]} wayland-protocols-plasma = { version = "0.3.5", features = ["client"] } wayland-protocols-wlr = { version = "0.3.5", features = ["client"] } diff --git a/watchers/src/watchers.rs b/watchers/src/watchers.rs index f2c211e..0e12035 100644 --- a/watchers/src/watchers.rs +++ b/watchers/src/watchers.rs @@ -8,6 +8,8 @@ pub mod idle; #[cfg(feature = "kwin_window")] mod kwin_window; mod wl_connection; +mod wl_bindings; +mod wl_dwl_ipc; mod wl_ext_idle_notify; mod wl_foreign_toplevel_management; mod wl_kwin_idle; @@ -98,6 +100,10 @@ async fn filter_first_supported( )); } WatcherType::ActiveWindow => { + watch!(create_watcher::( + client, + "Wayland window (dwl-ipc-unstable-v2)" + )); watch!(create_watcher::< wl_foreign_toplevel_management::WindowWatcher, >( diff --git a/watchers/src/watchers/wl-protocols/dwl_ipc_unstable_v2.xml b/watchers/src/watchers/wl-protocols/dwl_ipc_unstable_v2.xml new file mode 100644 index 0000000..6a80f43 --- /dev/null +++ b/watchers/src/watchers/wl-protocols/dwl_ipc_unstable_v2.xml @@ -0,0 +1,177 @@ + + + + This protocol allows clients to update and get updates from dwl. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible + changes may be added together with the corresponding interface + version bump. + Backward incompatible changes are done by bumping the version + number in the protocol and interface names and resetting the + interface version. Once the protocol is to be declared stable, + the 'z' prefix and the version number in the protocol and + interface names are removed and the interface version number is + reset. + + + + + This interface is exposed as a global in wl_registry. + + Clients can use this interface to get a dwl_ipc_output. + After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events. + The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client. + + + + + Indicates that the client will not the dwl_ipc_manager object anymore. + Objects created through this instance are not affected. + + + + + + Get a dwl_ipc_outout for the specified wl_output. + + + + + + + + This event is sent after binding. + A roundtrip after binding guarantees the client recieved all tags. + + + + + + + This event is sent after binding. + A roundtrip after binding guarantees the client recieved all layouts. + + + + + + + + Observe and control a dwl output. + + Events are double-buffered: + Clients should cache events and redraw when a dwl_ipc_output.frame event is sent. + + Request are not double-buffered: + The compositor will update immediately upon request. + + + + + + + + + + + Indicates to that the client no longer needs this dwl_ipc_output. + + + + + + Indicates the client should hide or show themselves. + If the client is visible then hide, if hidden then show. + + + + + + Indicates if the output is active. Zero is invalid, nonzero is valid. + + + + + + + Indicates that a tag has been updated. + + + + + + + + + + Indicates a new layout is selected. + + + + + + + Indicates the title has changed. + + + + + + + Indicates the appid has changed. + + + + + + + Indicates the layout has changed. Since layout symbols are dynamic. + As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying. + You can ignore the zdwl_ipc_output.layout event. + + + + + + + Indicates that a sequence of status updates have finished and the client should redraw. + + + + + + + + + + + + The tags are updated as follows: + new_tags = (current_tags AND and_tags) XOR xor_tags + + + + + + + + + + + + + + Indicates if the selected client on this output is fullscreen. + + + + + + + Indicates if the selected client on this output is floating. + + + + + diff --git a/watchers/src/watchers/wl_bindings.rs b/watchers/src/watchers/wl_bindings.rs new file mode 100644 index 0000000..803ab9a --- /dev/null +++ b/watchers/src/watchers/wl_bindings.rs @@ -0,0 +1,12 @@ +pub mod zdwl_ipc { + use wayland_client; + use wayland_client::protocol::*; + + pub mod __interfaces { + use wayland_client::protocol::__interfaces::*; + wayland_scanner::generate_interfaces!("src/watchers/wl-protocols/dwl_ipc_unstable_v2.xml"); + } + use self::__interfaces::*; + + wayland_scanner::generate_client_code!("src/watchers/wl-protocols/dwl_ipc_unstable_v2.xml"); +} diff --git a/watchers/src/watchers/wl_connection.rs b/watchers/src/watchers/wl_connection.rs index 6654e32..d7a4e5c 100644 --- a/watchers/src/watchers/wl_connection.rs +++ b/watchers/src/watchers/wl_connection.rs @@ -13,6 +13,8 @@ use wayland_protocols_plasma::idle::client::{ }; use wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1; +use super::wl_bindings::zdwl_ipc::zdwl_ipc_manager_v2::ZdwlIpcManagerV2; + macro_rules! subscribe_state { ($struct_name:ty, $data_name:ty, $state:ty) => { impl Dispatch<$struct_name, $data_name> for $state { @@ -72,6 +74,19 @@ where .map_err(std::convert::Into::into) } + pub fn get_dwl_ipc_manager(&self) -> anyhow::Result + where + T: Dispatch, + { + self.globals + .bind::( + &self.queue_handle, + 1..=ZdwlIpcManagerV2::interface().version, + (), + ) + .map_err(std::convert::Into::into) + } + pub fn get_kwin_idle(&self) -> anyhow::Result where T: Dispatch, diff --git a/watchers/src/watchers/wl_dwl_ipc.rs b/watchers/src/watchers/wl_dwl_ipc.rs new file mode 100644 index 0000000..b3ece90 --- /dev/null +++ b/watchers/src/watchers/wl_dwl_ipc.rs @@ -0,0 +1,182 @@ +use super::wl_bindings::zdwl_ipc::{ + zdwl_ipc_manager_v2::ZdwlIpcManagerV2, + zdwl_ipc_output_v2::{Event as ZdwlIpcOutputEvent, ZdwlIpcOutputV2}, +}; +use crate::watchers::wl_connection::WlEventConnection; +use crate::watchers::Watcher; +use crate::ReportClient; +use async_trait::async_trait; +use std::sync::Arc; +use wayland_client::globals::GlobalListContents; +use wayland_client::protocol::{wl_output::WlOutput, wl_registry, wl_registry::WlRegistry}; +use wayland_client::{Connection, Dispatch, QueueHandle}; + +struct WindowData { + app_id: String, + title: String, +} + +struct DwlState { + current_window: Option, + active: bool, +} + +impl DwlState { + fn new() -> Self { + Self { + current_window: None, + active: false, + } + } +} + +impl Dispatch for DwlState { + fn event( + _state: &mut Self, + registry: &WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_registry::Event::Global { + name, + interface, + version, + } = event + { + match interface.as_str() { + "zdwl_ipc_manager_v2" => { + registry.bind::(name, version, qh, ()); + } + "wl_output" => { + registry.bind::(name, version, qh, ()); + } + _ => {} + } + } + } +} + +impl Dispatch for DwlState { + fn event( + _: &mut Self, + _: &ZdwlIpcManagerV2, + _: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for DwlState { + fn event( + state: &mut Self, + _: &ZdwlIpcOutputV2, + event: ZdwlIpcOutputEvent, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + ZdwlIpcOutputEvent::Active { active } => { + state.active = active != 0; + } + ZdwlIpcOutputEvent::Title { title } => { + state + .current_window + .get_or_insert(WindowData { + app_id: String::new(), + title: String::new(), + }) + .title = title; + } + ZdwlIpcOutputEvent::Appid { appid } => { + state + .current_window + .get_or_insert(WindowData { + app_id: String::new(), + title: String::new(), + }) + .app_id = appid; + } + ZdwlIpcOutputEvent::Frame => { /* Optional handling */ } + _ => {} + } + } +} + +impl Dispatch for DwlState { + fn event( + _: &mut Self, + _: &WlOutput, + _: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for DwlState { + fn event( + _: &mut Self, + _: &WlRegistry, + _: wl_registry::Event, + _: &GlobalListContents, + _: &Connection, + _: &QueueHandle, + ) { + } +} + +pub struct WindowWatcher { + connection: WlEventConnection, + toplevel_state: DwlState, +} + +impl WindowWatcher { + async fn send_active_window(&self, client: &Arc) -> anyhow::Result<()> { + if self.toplevel_state.active { + if let Some(window) = &self.toplevel_state.current_window { + client + .send_active_window(&window.app_id, &window.title) + .await?; + } + } + Ok(()) + } +} + +#[async_trait] +impl Watcher for WindowWatcher { + async fn new(_: &Arc) -> anyhow::Result { + let mut connection: WlEventConnection = WlEventConnection::connect()?; + let qh: QueueHandle = connection.queue_handle.clone(); + let ipc_manager: ZdwlIpcManagerV2 = connection.get_dwl_ipc_manager()?; + + connection.event_queue.roundtrip(&mut DwlState::new())?; + + let wl_output: WlOutput = + connection + .globals + .bind::(&qh, 1..=4, ())?; + let _output: ZdwlIpcOutputV2 = ipc_manager.get_output(&wl_output, &qh, ()); + + Ok(WindowWatcher { + connection, + toplevel_state: DwlState { + current_window: None, + active: false, + }, + }) + } + + async fn run_iteration(&mut self, client: &Arc) -> anyhow::Result<()> { + self.connection + .event_queue + .roundtrip(&mut self.toplevel_state)?; + self.send_active_window(client).await + } +}