From 6c35e92603c1e5f6348208b77e914c7aa1885ead Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 19 Sep 2024 14:52:46 -0700 Subject: [PATCH] client-toolkit: Add suport for cosmic-toplevel-info-v1 version 2 This keeps the API compatible, adding fields to `ToplevelInfo` for the foreign handle, identifier, and geometry. While also supporting version 1 if that's all the compositor exposes. --- client-toolkit/Cargo.toml | 1 + client-toolkit/examples/toplevel-monitor.rs | 4 + client-toolkit/src/lib.rs | 1 + client-toolkit/src/toplevel_info.rs | 177 +++++++++++++++++++- 4 files changed, 174 insertions(+), 9 deletions(-) diff --git a/client-toolkit/Cargo.toml b/client-toolkit/Cargo.toml index 15b6f2f79c..f291bb38ba 100644 --- a/client-toolkit/Cargo.toml +++ b/client-toolkit/Cargo.toml @@ -9,6 +9,7 @@ sctk = { package = "smithay-client-toolkit", version = "0.19.1" } wayland-client = { version = "0.31.1" } smithay = { git = "https://github.com/Smithay/smithay", rev = "c35bc3e", default-features = false, features = ["backend_egl", "renderer_gl", "renderer_multi"], optional = true } libc = "0.2.153" +wayland-protocols = { version = "0.32.4", features = ["client", "staging"] } [build-dependencies] gl_generator = { version = "0.14.0", optional = true } diff --git a/client-toolkit/examples/toplevel-monitor.rs b/client-toolkit/examples/toplevel-monitor.rs index c93e3b0aa8..22be187d31 100644 --- a/client-toolkit/examples/toplevel-monitor.rs +++ b/client-toolkit/examples/toplevel-monitor.rs @@ -91,6 +91,10 @@ impl ToplevelInfoHandler for AppData { self.toplevel_info_state.info(toplevel).unwrap() ); } + + fn info_done(&mut self, conn: &Connection, qh: &QueueHandle) { + println!("Info done"); + } } fn main() { diff --git a/client-toolkit/src/lib.rs b/client-toolkit/src/lib.rs index 769953998b..b3cf51a528 100644 --- a/client-toolkit/src/lib.rs +++ b/client-toolkit/src/lib.rs @@ -1,6 +1,7 @@ pub use cosmic_protocols; pub use sctk; pub use wayland_client; +pub use wayland_protocols; #[cfg(feature = "gl")] pub mod egl; diff --git a/client-toolkit/src/toplevel_info.rs b/client-toolkit/src/toplevel_info.rs index 49082c3114..8337b9b448 100644 --- a/client-toolkit/src/toplevel_info.rs +++ b/client-toolkit/src/toplevel_info.rs @@ -1,19 +1,36 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use cosmic_protocols::{ toplevel_info::v1::client::{zcosmic_toplevel_handle_v1, zcosmic_toplevel_info_v1}, workspace::v1::client::zcosmic_workspace_handle_v1, }; use sctk::registry::RegistryState; -use wayland_client::{protocol::wl_output, Connection, Dispatch, QueueHandle}; +use wayland_client::{protocol::wl_output, Connection, Dispatch, Proxy, QueueHandle}; +use wayland_protocols::ext::foreign_toplevel_list::v1::client::{ + ext_foreign_toplevel_handle_v1, ext_foreign_toplevel_list_v1, +}; + +#[derive(Clone, Debug, Default)] +pub struct ToplevelGeometry { + pub x: i32, + pub y: i32, + pub width: i32, + pub height: i32, +} #[derive(Clone, Debug, Default)] pub struct ToplevelInfo { pub title: String, pub app_id: String, + /// Requires zcosmic_toplevel_info_v1 version 2 + pub identifier: Option, pub state: HashSet, pub output: HashSet, + /// Requires zcosmic_toplevel_info_v1 version 2 + pub geometry: HashMap, pub workspace: HashSet, + /// Requires zcosmic_toplevel_info_v1 version 2 + pub foreign_toplevel: Option, } #[derive(Debug, Default)] @@ -24,6 +41,7 @@ struct ToplevelData { #[derive(Debug)] pub struct ToplevelInfoState { + pub cosmic_toplevel_info: zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, toplevels: Vec<( zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, ToplevelData, @@ -33,13 +51,24 @@ pub struct ToplevelInfoState { impl ToplevelInfoState { pub fn new(registry: &RegistryState, qh: &QueueHandle) -> Self where - D: Dispatch + 'static, + D: Dispatch + + Dispatch + + 'static, { - registry - .bind_one::(qh, 1..=1, ()) + let cosmic_toplevel_info = registry + .bind_one::(qh, 1..=2, ()) .unwrap(); + if cosmic_toplevel_info.version() >= 2 { + let _ = registry + .bind_one::( + qh, + 1..=1, + (), + ); + } Self { + cosmic_toplevel_info, toplevels: Vec::new(), } } @@ -93,6 +122,11 @@ pub trait ToplevelInfoHandler: Sized { qh: &QueueHandle, toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, ); + + /// Only sent for zcosmic_toplevel_info_v1 version 2 + fn info_done(&mut self, conn: &Connection, qh: &QueueHandle) {} + + fn finished(&mut self, _conn: &Connection, _qh: &QueueHandle) {} } impl Dispatch for ToplevelInfoState @@ -104,11 +138,11 @@ where { fn event( state: &mut D, - _: &zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, + proxy: &zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, event: zcosmic_toplevel_info_v1::Event, _: &(), - _: &Connection, - _: &QueueHandle, + conn: &Connection, + qh: &QueueHandle, ) { match event { zcosmic_toplevel_info_v1::Event::Toplevel { toplevel } => { @@ -117,7 +151,12 @@ where .toplevels .push((toplevel, ToplevelData::default())); } - zcosmic_toplevel_info_v1::Event::Finished => {} + zcosmic_toplevel_info_v1::Event::Done => { + state.info_done(conn, qh); + } + zcosmic_toplevel_info_v1::Event::Finished => { + state.finished(conn, qh); + } _ => unreachable!(), } } @@ -160,6 +199,7 @@ where } zcosmic_toplevel_handle_v1::Event::OutputLeave { output } => { data.pending_info.output.remove(&output); + data.pending_info.geometry.remove(&output); } zcosmic_toplevel_handle_v1::Event::WorkspaceEnter { workspace } => { data.pending_info.workspace.insert(workspace); @@ -179,6 +219,23 @@ where } } } + zcosmic_toplevel_handle_v1::Event::Geometry { + output, + x, + y, + width, + height, + } => { + data.pending_info.geometry.insert( + output, + ToplevelGeometry { + x, + y, + width, + height, + }, + ); + } zcosmic_toplevel_handle_v1::Event::Done => { let is_new = data.current_info.is_none(); data.current_info = Some(data.pending_info.clone()); @@ -201,6 +258,102 @@ where } } +impl Dispatch + for ToplevelInfoState +where + D: Dispatch + + Dispatch + + Dispatch + + ToplevelInfoHandler + + 'static, +{ + fn event( + state: &mut D, + proxy: &ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, + event: ext_foreign_toplevel_list_v1::Event, + _: &(), + conn: &Connection, + qh: &QueueHandle, + ) { + match event { + ext_foreign_toplevel_list_v1::Event::Toplevel { toplevel } => { + let info_state = state.toplevel_info_state(); + let cosmic_toplevel = + info_state + .cosmic_toplevel_info + .get_cosmic_toplevel(&toplevel, qh, ()); + let mut toplevel_data = ToplevelData::default(); + toplevel_data.pending_info.foreign_toplevel = Some(toplevel); + info_state.toplevels.push((cosmic_toplevel, toplevel_data)); + } + ext_foreign_toplevel_list_v1::Event::Finished => { + state.finished(conn, qh); + proxy.destroy(); + } + _ => unreachable!(), + } + } + + wayland_client::event_created_child!(D, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, [ + ext_foreign_toplevel_list_v1::EVT_TOPLEVEL_OPCODE => (ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, Default::default()) + ]); +} + +impl Dispatch + for ToplevelInfoState +where + D: Dispatch + + ToplevelInfoHandler, +{ + fn event( + state: &mut D, + handle: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + event: ext_foreign_toplevel_handle_v1::Event, + data: &(), + conn: &Connection, + qh: &QueueHandle, + ) { + let (toplevel, data) = &mut state + .toplevel_info_state() + .toplevels + .iter_mut() + .find(|(_, data)| data.pending_info.foreign_toplevel.as_ref() == Some(handle)) + .expect("Received event for dead toplevel"); + match event { + ext_foreign_toplevel_handle_v1::Event::Closed => { + let toplevel = toplevel.clone(); + state.toplevel_closed(conn, qh, &toplevel); + + let toplevels = &mut state.toplevel_info_state().toplevels; + if let Some(idx) = toplevels.iter().position(|(handle, _)| handle == &toplevel) { + toplevels.remove(idx); + } + } + ext_foreign_toplevel_handle_v1::Event::Done => { + // XXX + let is_new = data.current_info.is_none(); + data.current_info = Some(data.pending_info.clone()); + let toplevel = toplevel.clone(); + if is_new { + state.new_toplevel(conn, qh, &toplevel); + } else { + state.update_toplevel(conn, qh, &toplevel); + } + } + ext_foreign_toplevel_handle_v1::Event::Title { title } => { + data.pending_info.title = title; + } + ext_foreign_toplevel_handle_v1::Event::AppId { app_id } => { + data.pending_info.app_id = app_id; + } + ext_foreign_toplevel_handle_v1::Event::Identifier { identifier } => { + data.pending_info.identifier = Some(identifier); + } + _ => unreachable!(), + } + } +} + #[macro_export] macro_rules! delegate_toplevel_info { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { @@ -210,5 +363,11 @@ macro_rules! delegate_toplevel_info { $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1: () ] => $crate::toplevel_info::ToplevelInfoState); + $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1: () + ] => $crate::toplevel_info::ToplevelInfoState); + $crate::wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1: () + ] => $crate::toplevel_info::ToplevelInfoState); }; }