From 4f2add81cc0cb780cb37054dfc4fc5ffc64313aa Mon Sep 17 00:00:00 2001 From: Exidex <16986685+exidex@users.noreply.github.com> Date: Tue, 31 Dec 2024 21:04:05 +0100 Subject: [PATCH] Add cosmic support for window tracking --- Cargo.lock | 37 ++++ rust/plugin_runtime/Cargo.toml | 1 + .../src/plugins/applications.rs | 4 +- .../applications/linux/wayland/cosmic.rs | 177 ++++++++++++++++++ .../plugins/applications/linux/wayland/mod.rs | 11 +- .../plugins/applications/linux/wayland/wlr.rs | 20 +- 6 files changed, 230 insertions(+), 20 deletions(-) create mode 100644 rust/plugin_runtime/src/plugins/applications/linux/wayland/cosmic.rs diff --git a/Cargo.lock b/Cargo.lock index fddcf1cd..b6d5ada0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1643,6 +1643,20 @@ dependencies = [ "libc", ] +[[package]] +name = "cosmic-protocols" +version = "0.1.0" +source = "git+https://github.com/pop-os/cosmic-protocols.git#d218c76b58c7a3b20dd5e7943f93fc306a1b81b8" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.5", + "wayland-protocols-wlr 0.3.5", + "wayland-scanner", + "wayland-server", +] + [[package]] name = "cosmic-text" version = "0.12.1" @@ -4104,6 +4118,7 @@ dependencies = [ "bincode 2.0.0-rc.3", "bytes", "cacao", + "cosmic-protocols", "deno_core", "deno_runtime", "encoding", @@ -5706,6 +5721,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + [[package]] name = "ipconfig" version = "0.3.2" @@ -11817,6 +11838,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-scanner", + "wayland-server", ] [[package]] @@ -11869,6 +11891,7 @@ dependencies = [ "wayland-client", "wayland-protocols 0.32.5", "wayland-scanner", + "wayland-server", ] [[package]] @@ -11882,6 +11905,20 @@ dependencies = [ "quote", ] +[[package]] +name = "wayland-server" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89532cc712a2adb119eb4d09694b402576052254d0bb284f82ac1c47fb786ad" +dependencies = [ + "bitflags 2.6.0", + "downcast-rs", + "io-lifetimes", + "rustix", + "wayland-backend", + "wayland-scanner", +] + [[package]] name = "wayland-sys" version = "0.31.5" diff --git a/rust/plugin_runtime/Cargo.toml b/rust/plugin_runtime/Cargo.toml index a927ae4e..834f0c09 100644 --- a/rust/plugin_runtime/Cargo.toml +++ b/rust/plugin_runtime/Cargo.toml @@ -43,6 +43,7 @@ encoding = "0.2" freedesktop_entry_parser = "1.3" freedesktop-icons = "0.2" wayland-protocols-wlr = { version = "0.3.5", features = ["client"] } +cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols.git" } wayland-client = "0.31.7" smithay-client-toolkit = "0.19.2" diff --git a/rust/plugin_runtime/src/plugins/applications.rs b/rust/plugin_runtime/src/plugins/applications.rs index 849c4d4c..30521525 100644 --- a/rust/plugin_runtime/src/plugins/applications.rs +++ b/rust/plugin_runtime/src/plugins/applications.rs @@ -101,16 +101,18 @@ pub enum DesktopEnvironment { impl DesktopEnvironment { fn new() -> anyhow::Result { + #[cfg(target_os = "linux")] let result = Ok(Self::Linux(linux::LinuxDesktopEnvironment::new()?)); #[cfg(not(target_os = "linux"))] - let result = None; + let result = Ok(Self::None); result } fn is_wayland(&self) -> bool { match self { + #[cfg(target_os = "linux")] DesktopEnvironment::Linux(linux) => linux.is_wayland(), DesktopEnvironment::None => false } diff --git a/rust/plugin_runtime/src/plugins/applications/linux/wayland/cosmic.rs b/rust/plugin_runtime/src/plugins/applications/linux/wayland/cosmic.rs new file mode 100644 index 00000000..20b885a3 --- /dev/null +++ b/rust/plugin_runtime/src/plugins/applications/linux/wayland/cosmic.rs @@ -0,0 +1,177 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use anyhow::anyhow; +use smithay_client_toolkit::reexports::calloop::channel::Sender; +use smithay_client_toolkit::seat::SeatState; +use tokio::runtime::Handle; +use crate::plugins::applications::linux::wayland::{send_event, JsWaylandApplicationEvent, WaylandState, WaylandStateInner}; +use wayland_client::globals::GlobalList; +use wayland_client::{event_created_child, Connection, Dispatch, Proxy, QueueHandle}; +use wayland_client::backend::ObjectId; +use wayland_client::protocol::wl_seat::WlSeat; +use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1; +use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_info_v1; +use cosmic_protocols::toplevel_management::v1::client::zcosmic_toplevel_manager_v1; + +pub struct CosmicWaylandState { + uuid_to_obj_id: HashMap, + obj_id_to_uuid: HashMap, + toplevels: HashMap, + management: zcosmic_toplevel_manager_v1::ZcosmicToplevelManagerV1, +} + +impl CosmicWaylandState { + pub fn new(globals: &GlobalList, queue_handle: &QueueHandle) -> anyhow::Result { + let management = globals + .bind::( + &queue_handle, + 3..=3, + (), + )?; + + Ok(Self { + management, + uuid_to_obj_id: HashMap::new(), + obj_id_to_uuid: HashMap::new(), + toplevels: HashMap::new(), + }) + } + + pub fn focus_window(&self, window_uuid: String, seat_state: &SeatState) -> anyhow::Result<()> { + let obj_id = self.uuid_to_obj_id + .get(&window_uuid) + .ok_or(anyhow!("Unable to find object id for window uuid: {}", window_uuid))?; + + let toplevel = self.toplevels + .get(&obj_id) + .ok_or(anyhow!("Unable to find object id for window uuid: {}", window_uuid))?; + + match seat_state.seats().next() { + Some(seat) => self.management.activate(&toplevel, &seat), + None => Err(anyhow!("no wayland seats found"))? + }; + + Ok(()) + } +} + +impl Dispatch for WaylandState { + fn event( + _state: &mut Self, + _proxy: &zcosmic_toplevel_manager_v1::ZcosmicToplevelManagerV1, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle + ) { + } +} + +impl Dispatch for WaylandState { + fn event( + state: &mut Self, + _proxy: &zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + match event { + zcosmic_toplevel_info_v1::Event::Toplevel { toplevel } => { + match &mut state.inner { + WaylandStateInner::Cosmic(inner) => { + let window_id = uuid::Uuid::new_v4().to_string(); + + inner.uuid_to_obj_id.insert(window_id.clone(), toplevel.id()); + inner.obj_id_to_uuid.insert(toplevel.id(), window_id.clone()); + inner.toplevels.insert(toplevel.id(), toplevel); + + send_event(&state.tokio_handle, &state.sender, JsWaylandApplicationEvent::WindowOpened { + window_id, + }); + } + _ => {} + } + } + _ => {} + } + } + + event_created_child!(WaylandState, zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, [ + zcosmic_toplevel_info_v1::EVT_TOPLEVEL_OPCODE => (zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, ()), + ]); +} + +impl Dispatch for WaylandState { + fn event( + state: &mut Self, + proxy: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + match event { + zcosmic_toplevel_handle_v1::Event::Title { title } => { + match &state.inner { + WaylandStateInner::Cosmic(inner) => { + match inner.obj_id_to_uuid.get(&proxy.id()) { + Some(window_id) => { + send_event(&state.tokio_handle, &state.sender, JsWaylandApplicationEvent::WindowTitleChanged { + window_id: window_id.clone(), + title, + }); + } + None => { + tracing::warn!("Received event for cosmic wayland toplevel that doesn't exist in state"); + } + } + } + _ => {} + } + } + zcosmic_toplevel_handle_v1::Event::AppId { app_id } => { + match &state.inner { + WaylandStateInner::Cosmic(inner) => { + match inner.obj_id_to_uuid.get(&proxy.id()) { + Some(window_id) => { + send_event(&state.tokio_handle, &state.sender, JsWaylandApplicationEvent::WindowAppIdChanged { + window_id: window_id.clone(), + app_id, + }); + } + None => { + tracing::warn!("Received event for cosmic wayland toplevel that doesn't exist in state"); + } + } + } + _ => {} + } + } + zcosmic_toplevel_handle_v1::Event::Closed => { + match &mut state.inner { + WaylandStateInner::Cosmic(inner) => { + + inner.toplevels.remove(&proxy.id()); + match inner.obj_id_to_uuid.remove(&proxy.id()) { + Some(window_id) => { + inner.uuid_to_obj_id.remove(&window_id); + + send_event(&state.tokio_handle, &state.sender, JsWaylandApplicationEvent::WindowClosed { + window_id: window_id.clone(), + }); + } + None => { + tracing::warn!("Received event for cosmic wayland toplevel that doesn't exist in state"); + } + } + } + _ => {} + } + } + _ => {} + } + } +} \ No newline at end of file diff --git a/rust/plugin_runtime/src/plugins/applications/linux/wayland/mod.rs b/rust/plugin_runtime/src/plugins/applications/linux/wayland/mod.rs index 552f3450..1288ea92 100644 --- a/rust/plugin_runtime/src/plugins/applications/linux/wayland/mod.rs +++ b/rust/plugin_runtime/src/plugins/applications/linux/wayland/mod.rs @@ -16,7 +16,8 @@ use wayland_client::protocol::wl_registry; use wayland_client::protocol::wl_seat::WlSeat; use crate::plugins::applications::{linux, ApplicationContext, DesktopEnvironment}; -pub mod wlr; +mod wlr; +mod cosmic; pub struct WaylandDesktopEnvironment { activate_sender: calloop::channel::Sender, @@ -101,7 +102,7 @@ impl WaylandState { pub enum WaylandStateInner { Wlr(wlr::WlrWaylandState), - Cosmic, + Cosmic(cosmic::CosmicWaylandState), None } @@ -183,7 +184,11 @@ fn activation_handler(event: Event, _metadata: &mut (), state: &mut Wayl tracing::error!("Unable to focus wayland window: {:?}", err); }; } - WaylandStateInner::Cosmic => {} + WaylandStateInner::Cosmic(cosmic) => { + if let Err(err) = cosmic.focus_window(window_uuid, &state.seat_state) { + tracing::error!("Unable to focus wayland window: {:?}", err); + }; + } WaylandStateInner::None => { tracing::error!("Calling focus window when there is no supported wayland protocols available"); } diff --git a/rust/plugin_runtime/src/plugins/applications/linux/wayland/wlr.rs b/rust/plugin_runtime/src/plugins/applications/linux/wayland/wlr.rs index 287cd707..6ad93493 100644 --- a/rust/plugin_runtime/src/plugins/applications/linux/wayland/wlr.rs +++ b/rust/plugin_runtime/src/plugins/applications/linux/wayland/wlr.rs @@ -76,10 +76,7 @@ impl Dispatch { - todo!() - } - WaylandStateInner::None => {} + _ => {} } } _ => {} @@ -116,10 +113,7 @@ impl Dispatch } } } - WaylandStateInner::Cosmic => { - todo!() - } - WaylandStateInner::None => {} + _ => {} } } zwlr_foreign_toplevel_handle_v1::Event::AppId { app_id } => { @@ -137,10 +131,7 @@ impl Dispatch } } } - WaylandStateInner::Cosmic => { - todo!() - } - WaylandStateInner::None => {} + _ => {} } } zwlr_foreign_toplevel_handle_v1::Event::Closed => { @@ -161,10 +152,7 @@ impl Dispatch } } } - WaylandStateInner::Cosmic => { - todo!() - } - WaylandStateInner::None => {} + _ => {} } } _ => {}