From 614b00766500471c66fe816de01af7e180e97a8b Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 28 Aug 2024 13:04:24 -0700 Subject: [PATCH] Add `ext-foreign-toplevel-list-v1` protocol Provides a handler API similar to the output API. --- examples/foreign-toplevel-monitor.rs | 81 +++++++++++ src/foreign_toplevel_list.rs | 193 +++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 275 insertions(+) create mode 100644 examples/foreign-toplevel-monitor.rs create mode 100644 src/foreign_toplevel_list.rs diff --git a/examples/foreign-toplevel-monitor.rs b/examples/foreign-toplevel-monitor.rs new file mode 100644 index 000000000..5e634d679 --- /dev/null +++ b/examples/foreign-toplevel-monitor.rs @@ -0,0 +1,81 @@ +use std::error::Error; + +use smithay_client_toolkit::{ + delegate_foreign_toplevel_list, delegate_registry, + foreign_toplevel_list::{ForeignToplevelList, ForeignToplevelListHandler}, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, +}; +use wayland_client::{globals::registry_queue_init, Connection, QueueHandle}; +use wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1; + +struct State { + registry_state: RegistryState, + foreign_toplevel_list: ForeignToplevelList, +} + +fn main() -> Result<(), Box> { + let conn = Connection::connect_to_env()?; + let (globals, mut event_queue) = registry_queue_init(&conn)?; + let qh = event_queue.handle(); + let registry_state = RegistryState::new(&globals); + let foreign_toplevel_list = ForeignToplevelList::new(&globals, &qh); + + let mut state = State { registry_state, foreign_toplevel_list }; + loop { + event_queue.blocking_dispatch(&mut state)?; + } +} + +impl ProvidesRegistryState for State { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + + registry_handlers! {} +} + +impl ForeignToplevelListHandler for State { + fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelList { + &mut self.foreign_toplevel_list + } + + fn new_toplevel( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + toplevel_handle: ExtForeignToplevelHandleV1, + ) { + let Some(info) = self.foreign_toplevel_list.info(&toplevel_handle) else { + return; + }; + println!("New toplevel: {:?}", info); + } + + fn update_toplevel( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + toplevel_handle: ExtForeignToplevelHandleV1, + ) { + let Some(info) = self.foreign_toplevel_list.info(&toplevel_handle) else { + return; + }; + println!("Update toplevel: {:?}", info); + } + + fn toplevel_closed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + toplevel_handle: ExtForeignToplevelHandleV1, + ) { + let Some(info) = self.foreign_toplevel_list.info(&toplevel_handle) else { + return; + }; + println!("Close toplevel: {:?}", info); + } +} + +delegate_foreign_toplevel_list!(State); +delegate_registry!(State); diff --git a/src/foreign_toplevel_list.rs b/src/foreign_toplevel_list.rs new file mode 100644 index 000000000..5670af4e7 --- /dev/null +++ b/src/foreign_toplevel_list.rs @@ -0,0 +1,193 @@ +use crate::{globals::GlobalData, registry::GlobalProxy}; +use std::sync::{Arc, Mutex}; +use wayland_client::{globals::GlobalList, Connection, Dispatch, Proxy, QueueHandle}; +use wayland_protocols::ext::foreign_toplevel_list::v1::client::{ + ext_foreign_toplevel_handle_v1, ext_foreign_toplevel_list_v1, +}; + +/// Information about a toplevel. +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct ForeignToplevelInfo { + /// Title + pub title: String, + /// App id + pub app_id: String, + /// Identifier to check if two toplevel handles refer to same toplevel + pub identifier: String, +} + +#[derive(Debug, Default)] +struct ForeignToplevelInner { + current_info: Option, + pending_info: ForeignToplevelInfo, +} + +#[doc(hidden)] +#[derive(Debug, Default, Clone)] +pub struct ForeignToplevelData(Arc>); + +#[derive(Debug)] +pub struct ForeignToplevelList { + foreign_toplevel_list: GlobalProxy, + toplevels: Vec, +} + +impl ForeignToplevelList { + pub fn new(globals: &GlobalList, qh: &QueueHandle) -> Self + where + D: Dispatch + 'static, + { + let foreign_toplevel_list = GlobalProxy::from(globals.bind(qh, 1..=1, GlobalData)); + Self { foreign_toplevel_list, toplevels: Vec::new() } + } + + /// Returns list of toplevels. + pub fn toplevels(&self) -> &[ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1] { + &self.toplevels + } + + /// Returns information about a toplevel. + /// + /// This may be none if the toplevel has been destroyed or the compositor has not sent + /// information about the toplevel yet. + pub fn info( + &self, + toplevel: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + ) -> Option { + toplevel.data::()?.0.lock().unwrap().current_info.clone() + } + + pub fn stop(&self) { + if let Ok(toplevel_list) = self.foreign_toplevel_list.get() { + toplevel_list.stop(); + } + } +} + +/// Handler trait for foreign toplevel list protocol. +pub trait ForeignToplevelListHandler: Sized { + fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelList; + + /// A new toplevel has been opened. + fn new_toplevel( + &mut self, + conn: &Connection, + qh: &QueueHandle, + toplevel_handle: ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + ); + + /// An existing toplevel has changed. + fn update_toplevel( + &mut self, + conn: &Connection, + qh: &QueueHandle, + toplevel_handle: ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + ); + + /// A toplevel has closed. + fn toplevel_closed( + &mut self, + conn: &Connection, + qh: &QueueHandle, + toplevel_handle: ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + ); + + fn finished(&mut self, _conn: &Connection, _qh: &QueueHandle) {} +} + +impl Dispatch + for ForeignToplevelList +where + D: Dispatch + + Dispatch + + ForeignToplevelListHandler + + 'static, +{ + fn event( + state: &mut D, + proxy: &ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, + event: ext_foreign_toplevel_list_v1::Event, + _: &GlobalData, + conn: &Connection, + qh: &QueueHandle, + ) { + match event { + ext_foreign_toplevel_list_v1::Event::Toplevel { toplevel: _ } => {} + 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 ForeignToplevelList +where + D: Dispatch + + ForeignToplevelListHandler, +{ + fn event( + state: &mut D, + handle: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + event: ext_foreign_toplevel_handle_v1::Event, + data: &ForeignToplevelData, + conn: &Connection, + qh: &QueueHandle, + ) { + match event { + ext_foreign_toplevel_handle_v1::Event::Closed => { + state.toplevel_closed(conn, qh, handle.clone()); + let toplevels = &mut state.foreign_toplevel_list_state().toplevels; + if let Some(idx) = toplevels.iter().position(|x| x == handle) { + toplevels.remove(idx); + } + handle.destroy(); + } + ext_foreign_toplevel_handle_v1::Event::Done => { + let mut inner = data.0.lock().unwrap(); + let just_created = inner.current_info.is_none(); + inner.current_info = Some(inner.pending_info.clone()); + drop(inner); + if just_created { + state.foreign_toplevel_list_state().toplevels.push(handle.clone()); + state.new_toplevel(conn, qh, handle.clone()); + } else { + state.update_toplevel(conn, qh, handle.clone()); + } + } + ext_foreign_toplevel_handle_v1::Event::Title { title } => { + data.0.lock().unwrap().pending_info.title = title; + } + ext_foreign_toplevel_handle_v1::Event::AppId { app_id } => { + data.0.lock().unwrap().pending_info.app_id = app_id; + } + ext_foreign_toplevel_handle_v1::Event::Identifier { identifier } => { + data.0.lock().unwrap().pending_info.identifier = identifier; + } + _ => unreachable!(), + } + } +} + +#[macro_export] +macro_rules! delegate_foreign_toplevel_list { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1: $crate::globals::GlobalData + ] => $crate::foreign_toplevel_list::ForeignToplevelList + ); + $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + $crate::reexports::protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1: $crate::foreign_toplevel_list::ForeignToplevelData + ] => $crate::foreign_toplevel_list::ForeignToplevelList + ); + }; +} diff --git a/src/lib.rs b/src/lib.rs index 2e02d0904..b0a1435ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ pub mod compositor; pub mod data_device_manager; pub mod dmabuf; pub mod error; +pub mod foreign_toplevel_list; pub mod globals; pub mod output; pub mod primary_selection;