diff --git a/CHANGELOG.md b/CHANGELOG.md index 25fe625..aa5d823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +### Additions + +#### Clients & Protocols + +- Added public api constants for the roles of `wl_shell_surface`, `zxdg_toplevel` and `xdg_toplevel`. See the + `shell::legacy` and `shell::xdg` modules for these constants. +- Whether a surface is toplevel equivalent can be determined with the new function `shell::is_toplevel_equivalent`. +- Setting the parent of a toplevel surface is now possible with the `xdg::ToplevelSurface::set_parent` function. +- Add support for the zxdg-foreign-v2 protocol. + ### Bugfixes - EGLBufferReader now checks if buffers are alive before using them. diff --git a/Cargo.toml b/Cargo.toml index 499acf9..43af617 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ libc = "0.2.70" libseat= { version = "0.1.1", optional = true } libloading = "0.7.0" nix = "0.22" +rand = "0.8.4" slog = "2" slog-stdlog = { version = "4", optional = true } tempfile = { version = "3.0", optional = true } diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 2c75485..f14c7a2 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -63,6 +63,7 @@ pub mod seat; pub mod shell; pub mod shm; pub mod tablet_manager; +pub mod xdg_foreign; /// A global [`SerialCounter`] for use in your compositor. /// diff --git a/src/wayland/shell/legacy/mod.rs b/src/wayland/shell/legacy/mod.rs index 2c83340..91bc56c 100644 --- a/src/wayland/shell/legacy/mod.rs +++ b/src/wayland/shell/legacy/mod.rs @@ -61,6 +61,9 @@ use wayland_server::{ use super::PingError; +/// The role of a wl_shell_surface. +pub const WL_SHELL_SURFACE_ROLE: &str = "wl_shell_surface"; + mod wl_handlers; /// Metadata associated with the `wl_surface` role diff --git a/src/wayland/shell/legacy/wl_handlers.rs b/src/wayland/shell/legacy/wl_handlers.rs index 0d96226..90d3463 100644 --- a/src/wayland/shell/legacy/wl_handlers.rs +++ b/src/wayland/shell/legacy/wl_handlers.rs @@ -10,9 +10,8 @@ use wayland_server::{ DispatchData, Filter, Main, }; -static WL_SHELL_SURFACE_ROLE: &str = "wl_shell_surface"; - use crate::wayland::compositor; +use crate::wayland::shell::legacy::WL_SHELL_SURFACE_ROLE; use crate::wayland::Serial; use super::{ShellRequest, ShellState, ShellSurface, ShellSurfaceAttributes, ShellSurfaceKind}; diff --git a/src/wayland/shell/mod.rs b/src/wayland/shell/mod.rs index 4db509d..470bf67 100644 --- a/src/wayland/shell/mod.rs +++ b/src/wayland/shell/mod.rs @@ -15,7 +15,9 @@ //! is now deprecated. You only need it if you want to support apps predating `xdg_shell`. use super::Serial; +use crate::wayland::compositor; use thiserror::Error; +use wayland_server::protocol::wl_surface::WlSurface; pub mod legacy; pub mod xdg; @@ -31,3 +33,17 @@ pub enum PingError { #[error("there is already a ping pending `{0:?}`")] PingAlreadyPending(Serial), } + +/// Returns true if the surface is toplevel equivalent. +/// +/// This is method checks if the surface roles is one of `wl_shell_surface`, `xdg_toplevel` +/// or `zxdg_toplevel`. +pub fn is_toplevel_equivalent(surface: &WlSurface) -> bool { + // (z)xdg_toplevel and wl_shell_surface are toplevel like, so verify if the roles match. + let role = compositor::get_role(surface); + + matches!( + role, + Some(xdg::XDG_TOPLEVEL_ROLE) | Some(xdg::ZXDG_TOPLEVEL_ROLE) | Some(legacy::WL_SHELL_SURFACE_ROLE) + ) +} diff --git a/src/wayland/shell/xdg/mod.rs b/src/wayland/shell/xdg/mod.rs index 8e52c6b..b5e2bd0 100644 --- a/src/wayland/shell/xdg/mod.rs +++ b/src/wayland/shell/xdg/mod.rs @@ -65,6 +65,7 @@ use crate::utils::DeadResource; use crate::utils::{Logical, Point, Rectangle, Size}; use crate::wayland::compositor; use crate::wayland::compositor::Cacheable; +use crate::wayland::shell::is_toplevel_equivalent; use crate::wayland::{Serial, SERIAL_COUNTER}; use std::fmt::Debug; use std::{ @@ -72,6 +73,7 @@ use std::{ rc::Rc, sync::{Arc, Mutex}, }; + use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_surface_v6; use wayland_protocols::xdg_shell::server::xdg_surface; use wayland_protocols::{ @@ -91,6 +93,38 @@ mod xdg_handlers; // compatibility handlers for the zxdg_shell_v6 protocol, its earlier version mod zxdgv6_handlers; +/// The role of an XDG toplevel surface. +/// +/// If you are checking if the surface role is an xdg_toplevel, you should also check if the surface +/// is an [zxdg_toplevel] since the zxdg toplevel role is equivalent. +/// +/// [zxdg_toplevel]: self::ZXDG_TOPLEVEL_ROLE +pub const XDG_TOPLEVEL_ROLE: &str = "xdg_toplevel"; + +/// The role of an XDG popup surface. +/// +/// If you are checking if the surface role is an xdg_popup, you should also check if the surface +/// is a [zxdg_popup] since the zxdg popup role is equivalent. +/// +/// [zxdg_popup]: self::ZXDG_POPUP_ROLE +pub const XDG_POPUP_ROLE: &str = "xdg_popup"; + +/// The role of an ZXDG toplevel surface. +/// +/// If you are checking if the surface role is an zxdg_toplevel, you should also check if the surface +/// is an [xdg_toplevel] since the xdg toplevel role is equivalent. +/// +/// [xdg_toplevel]: self::XDG_TOPLEVEL_ROLE +pub const ZXDG_TOPLEVEL_ROLE: &str = "zxdg_toplevel"; + +/// The role of an ZXDG popup surface. +/// +/// If you are checking if the surface role is an zxdg_popup, you should also check if the surface +/// is a [xdg_popup] since the xdg popup role is equivalent. +/// +/// [xdg_popup]: self::XDG_POPUP_ROLE +pub const ZXDG_POPUP_ROLE: &str = "zxdg_popup"; + macro_rules! xdg_role { ($configure:ty, $(#[$attr:meta])* $element:ident {$($(#[$field_attr:meta])* $vis:vis$field:ident:$type:ty),*}, @@ -698,6 +732,13 @@ impl ShellState { &self.known_toplevels[..] } + /// Returns a reference to the toplevel surface mapped to the provided wl_surface. + pub fn toplevel_surface(&self, surface: &wl_surface::WlSurface) -> Option<&ToplevelSurface> { + self.known_toplevels + .iter() + .find(|toplevel| toplevel.wl_surface == *surface) + } + /// Access all the popup surfaces known by this handler pub fn popup_surfaces(&self) -> &[PopupSurface] { &self.known_popups[..] @@ -1115,6 +1156,35 @@ impl ToplevelSurface { .unwrap(), ) } + + /// Returns the parent of this toplevel surface. + pub fn parent(&self) -> Option { + match &self.shell_surface { + ToplevelKind::Xdg(toplevel) => xdg_handlers::get_parent(toplevel), + ToplevelKind::ZxdgV6(toplevel) => zxdgv6_handlers::get_parent(toplevel), + } + } + + /// Sets the parent of this toplevel surface and returns whether the parent was successfully set. + /// + /// The parent must be another toplevel equivalent surface. + /// + /// If the parent is `None`, the parent-child relationship is removed. + pub fn set_parent(&self, parent: Option) -> bool { + if let Some(parent) = parent { + if !is_toplevel_equivalent(&parent) { + return false; + } + } + + // Unset the parent + match &self.shell_surface { + ToplevelKind::Xdg(toplevel) => xdg_handlers::set_parent(toplevel, None), + ToplevelKind::ZxdgV6(toplevel) => zxdgv6_handlers::set_parent(toplevel, None), + } + + true + } } #[derive(Debug, Clone)] diff --git a/src/wayland/shell/xdg/xdg_handlers.rs b/src/wayland/shell/xdg/xdg_handlers.rs index 661fdb2..3733132 100644 --- a/src/wayland/shell/xdg/xdg_handlers.rs +++ b/src/wayland/shell/xdg/xdg_handlers.rs @@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::{cell::RefCell, ops::Deref as _, sync::Mutex}; use crate::wayland::compositor; -use crate::wayland::shell::xdg::PopupState; +use crate::wayland::shell::xdg::{PopupState, XDG_POPUP_ROLE, XDG_TOPLEVEL_ROLE}; use crate::wayland::Serial; use wayland_protocols::xdg_shell::server::{ xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base, @@ -18,9 +18,6 @@ use super::{ XdgRequest, XdgToplevelSurfaceRoleAttributes, }; -static XDG_TOPLEVEL_ROLE: &str = "xdg_toplevel"; -static XDG_POPUP_ROLE: &str = "xdg_popup"; - pub(crate) fn implement_wm_base( shell: Main, shell_data: &ShellData, @@ -548,18 +545,18 @@ fn toplevel_implementation( // all it done by the destructor } xdg_toplevel::Request::SetParent { parent } => { - // Parent is not double buffered, we can set it directly - with_surface_toplevel_role_data(&toplevel, |data| { - data.parent = parent.map(|toplevel_surface_parent| { - toplevel_surface_parent - .as_ref() - .user_data() - .get::() - .unwrap() - .wl_surface - .clone() - }) + let parent_surface = parent.map(|toplevel_surface_parent| { + toplevel_surface_parent + .as_ref() + .user_data() + .get::() + .unwrap() + .wl_surface + .clone() }); + + // Parent is not double buffered, we can set it directly + set_parent(&toplevel, parent_surface); } xdg_toplevel::Request::SetTitle { title } => { // Title is not double buffered, we can set it directly @@ -756,3 +753,20 @@ fn destroy_popup(popup: xdg_popup::XdgPopup) { .known_popups .retain(|other| other.alive()); } + +pub(crate) fn get_parent(toplevel: &xdg_toplevel::XdgToplevel) -> Option { + with_surface_toplevel_role_data(toplevel, |data| data.parent.clone()) +} + +/// Sets the parent of the specified toplevel surface. +/// +/// The parent must be a toplevel surface. +/// +/// The parent of a surface is not double buffered and therefore may be set directly. +/// +/// If the parent is `None`, the parent-child relationship is removed. +pub(crate) fn set_parent(toplevel: &xdg_toplevel::XdgToplevel, parent: Option) { + with_surface_toplevel_role_data(toplevel, |data| { + data.parent = parent; + }); +} diff --git a/src/wayland/shell/xdg/zxdgv6_handlers.rs b/src/wayland/shell/xdg/zxdgv6_handlers.rs index c718dc7..bf10ff0 100644 --- a/src/wayland/shell/xdg/zxdgv6_handlers.rs +++ b/src/wayland/shell/xdg/zxdgv6_handlers.rs @@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::{cell::RefCell, ops::Deref as _, sync::Mutex}; use crate::wayland::compositor; -use crate::wayland::shell::xdg::PopupState; +use crate::wayland::shell::xdg::{PopupState, ZXDG_POPUP_ROLE, ZXDG_TOPLEVEL_ROLE}; use crate::wayland::Serial; use wayland_protocols::{ unstable::xdg_shell::v6::server::{ @@ -21,9 +21,6 @@ use super::{ XdgRequest, XdgToplevelSurfaceRoleAttributes, }; -static ZXDG_TOPLEVEL_ROLE: &str = "zxdg_toplevel"; -static ZXDG_POPUP_ROLE: &str = "zxdg_popup"; - pub(crate) fn implement_shell( shell: Main, shell_data: &ShellData, @@ -563,16 +560,18 @@ fn toplevel_implementation( // all it done by the destructor } zxdg_toplevel_v6::Request::SetParent { parent } => { - with_surface_toplevel_role_data(&toplevel, |data| { - data.parent = parent.map(|toplevel_surface_parent| { - let parent_data = toplevel_surface_parent - .as_ref() - .user_data() - .get::() - .unwrap(); - parent_data.wl_surface.clone() - }) + let parent_surface = parent.map(|toplevel_surface_parent| { + toplevel_surface_parent + .as_ref() + .user_data() + .get::() + .unwrap() + .wl_surface + .clone() }); + + // Parent is not double buffered, we can set it directly + set_parent(&toplevel, parent_surface); } zxdg_toplevel_v6::Request::SetTitle { title } => { // Title is not double buffered, we can set it directly @@ -819,3 +818,20 @@ fn zxdg_anchor_to_xdg(c: zxdg_positioner_v6::Anchor) -> Option None, } } + +pub(crate) fn get_parent(toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6) -> Option { + with_surface_toplevel_role_data(toplevel, |data| data.parent.clone()) +} + +/// Sets the parent of the specified toplevel surface. +/// +/// The parent must be a toplevel surface. +/// +/// The parent of a surface is not double buffered and therefore may be set directly. +/// +/// If the parent is `None`, the parent-child relationship is removed. +pub(crate) fn set_parent(toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, parent: Option) { + with_surface_toplevel_role_data(toplevel, |data| { + data.parent = parent; + }); +} diff --git a/src/wayland/xdg_foreign/mod.rs b/src/wayland/xdg_foreign/mod.rs new file mode 100644 index 0000000..63d18b6 --- /dev/null +++ b/src/wayland/xdg_foreign/mod.rs @@ -0,0 +1,462 @@ +//! Utilities to allow clients to export and import handles to a surface. +//! +//! This module provides automatic management of exporting surfaces from one client and allowing +//! another client to import the surface using a handle. Management of the lifetimes of exports, +//! imports and allowing imported surfaces to be set as the parent of a surface are handled by the +//! module. +//! +//! # How to use it +//! +//! Having a functional xdg_shell global setup is required. +//! +//! With a valid xdg_shell global, simply initialize the xdg_foreign global using the +//! `xdg_foreign_init` function. After that just ensure the `XdgForeignState` returned by the +//! function is kept alive. +//! +//! ```no_run +//! # extern crate wayland_server; +//! # use smithay::wayland::shell::xdg::{xdg_shell_init, XdgRequest}; +//! # use smithay::wayland::xdg_foreign::xdg_foreign_init; +//! +//! # let mut display = wayland_server::Display::new(); +//! // XDG Shell init +//! let (shell_state, _, _) = xdg_shell_init( +//! &mut display, +//! |event: XdgRequest, dispatch_data| { /* handle the shell requests here */ }, +//! None +//! ); +//! +//! let (foreign_state, _, _) = xdg_foreign_init(&mut display, shell_state.clone(), None); +//! +//! // Good to go! +//! ``` + +use crate::wayland::compositor; +use crate::wayland::shell::is_toplevel_equivalent; +use crate::wayland::shell::legacy::WL_SHELL_SURFACE_ROLE; +use crate::wayland::shell::xdg::ShellState; +use rand::distributions::{Alphanumeric, DistString}; +use std::ops::Deref; +use std::sync::{Arc, Mutex}; +use wayland_protocols::unstable::xdg_foreign::v2::server::{ + zxdg_exported_v2, zxdg_exporter_v2, zxdg_imported_v2, zxdg_importer_v2, +}; +use wayland_server::protocol::wl_surface::WlSurface; +use wayland_server::{Display, Filter, Global, Main}; + +/// Manages all exported and imported surfaces. +#[derive(Debug)] +pub struct XdgForeignState { + log: ::slog::Logger, + shell: Arc>, + exports: Vec, +} + +impl XdgForeignState { + /// Returns true if an export with the given handle is still valid. + pub fn is_export_valid(&self, handle: &str) -> bool { + self.exports.iter().any(|export| export.handle == handle) + } + + /// Returns the surface that an exported handle refers to. + /// + /// Returns `None` if no export exists for the handle. + pub fn get_surface(&self, handle: &str) -> Option { + self.exports + .iter() + .find(|export| export.handle == handle) + .map(|export| export.surface.clone()) + } +} + +/// Creates new `xdg-foreign` globals. +pub fn xdg_foreign_init( + display: &mut Display, + xdg_shell_state: Arc>, + logger: L, +) -> ( + Arc>, + Global, + Global, +) +where + L: Into>, +{ + let log = crate::slog_or_fallback(logger); + + let state = Arc::new(Mutex::new(XdgForeignState { + log: log.new(slog::o!("smithay_module" => "xdg_foreign_handler")), + exports: vec![], + shell: xdg_shell_state.clone(), + })); + + // Borrow checking does not like us cloning the state inside the filter's closure so we clone + // now and pass the clones into the closure. + let export_state = state.clone(); + let import_state = state.clone(); + let export_shell = xdg_shell_state.clone(); + + let zxdg_exporter_v2_global = display.create_global( + 1, + Filter::new(move |(exporter, _version), _, _| { + implement_exporter(exporter, export_state.clone(), export_shell.clone()); + }), + ); + + let zxdg_importer_v2_global = display.create_global( + 1, + Filter::new(move |(importer, _version), _, _| { + implement_importer(importer, import_state.clone(), xdg_shell_state.clone()); + }), + ); + + (state, zxdg_exporter_v2_global, zxdg_importer_v2_global) +} + +/// An exported surface. +#[derive(Debug)] +struct Export { + surface: WlSurface, + handle: String, + inner: zxdg_exported_v2::ZxdgExportedV2, + /// All imports made from this export. + imports: Vec, + /// Whether this export was created by the compositor or a client. + compositor_created: bool, +} + +impl Export { + /// Destroys all imports created from this export. + /// + /// This does not destroy any relationships that may have been set by the surface. + fn destroy_imports(&mut self) { + self.imports.drain(..).for_each(|import| import.inner.destroyed()); + } +} + +impl PartialEq for Export { + fn eq(&self, other: &Self) -> bool { + // From the documentation of export_toplevel: + // + // A surface may be exported multiple times, and each exported handle may be used to create + // an xdg_imported multiple times. + // + // The interpretation used here is that each export is it's own handle to a toplevel + // surface and multiple different handles may refer to one surface. Therefore equality + // semantics should compare the surface and the handle. + self.surface == other.surface && self.handle == other.handle + } +} + +#[derive(Debug)] +struct Import { + inner: zxdg_imported_v2::ZxdgImportedV2, + surface: WlSurface, + handle: String, + /// Child surfaces which have imported this surface as a parent. + children: Vec, +} + +impl Import { + fn remove_children(&self, shell: &ShellState) { + for child in &self.children { + if let Some(child) = shell.toplevel_surface(child) { + // Make sure we are still the parent of the child surface? + if let Some(parent) = child.parent() { + if parent == self.surface { + // Make the imported surface no longer the parent of this surface. + child.set_parent(None); + } + } + } + } + } +} + +fn implement_exporter( + exporter: Main, + state: Arc>, + shell: Arc>, +) -> zxdg_exporter_v2::ZxdgExporterV2 { + let destructor_state = state.clone(); + let destructor_shell = shell.clone(); + + exporter.quick_assign(move |_, request, _| { + exporter_implementation(request, state.clone(), shell.clone()); + }); + + exporter.assign_destructor(Filter::new( + move |exporter: zxdg_exporter_v2::ZxdgExporterV2, _, _| { + let state = &mut *destructor_state.lock().unwrap(); + let exports = &mut state.exports; + + // Iterate in reverse to remove elements so we do not need to shift a cursor upon every removal. + // This would be a whole lot nicer if there were a retain_mut. + for index in (0..exports.len()).rev() { + let export = &mut exports[index]; + + // If the export is from the client this exporter is destroyed from, then remove it. + if export.inner.as_ref().same_client_as(exporter.as_ref()) { + // Destroy all imports created from this export's handle. + export.destroy_imports(); + + for import in &export.imports { + import.remove_children(&*destructor_shell.lock().unwrap()) + } + + exports.remove(index); + } + } + }, + )); + + exporter.deref().clone() +} + +fn exported_implementation( + exported: Main, + state: Arc>, + shell: Arc>, +) { + exported.assign_destructor(Filter::new( + move |exported: zxdg_exported_v2::ZxdgExportedV2, _, _| { + let state = &mut *state.lock().unwrap(); + + let exports = &mut state.exports; + let export = exports + .iter_mut() + .find(|export| export.inner == exported) + .unwrap(); + + export.destroy_imports(); + // Remove the export since the client has destroyed it. + exports.retain(|export| { + let destroy = export.inner != exported; + + if destroy { + let shell = shell.lock().unwrap(); + + // Destroy surface relationships. + for import in &export.imports { + import.remove_children(&*shell); + } + } + + destroy + }) + }, + )); +} + +fn exporter_implementation( + request: zxdg_exporter_v2::Request, + state: Arc>, + shell: Arc>, +) { + match request { + zxdg_exporter_v2::Request::Destroy => { + // all is handled by destructor. + } + + zxdg_exporter_v2::Request::ExportToplevel { id, surface } => { + // toplevel like would generally be okay, however we cannot rely on wl_shell_surface as + // a toplevel surface has no tracking for parents, only transient does. That becomes an + // issue when a wl_shell_surface attempts to import an (z)xdg_toplevel surface and set it + // as it's parent. + // + // Also the presence of a protocol error noting that the surface must be an xdg_toplevel + // probably means wl_shell_surface was not accounted for in the design. So we throw a + // protocol error if either surface in the relationship is not an (z)xdg_toplevel. + if !is_toplevel_equivalent(&surface) + && compositor::get_role(&surface) != Some(WL_SHELL_SURFACE_ROLE) + { + // Protocol error if not a toplevel like + surface.as_ref().post_error( + zxdg_exporter_v2::Error::InvalidSurface as u32, + "Surface must be a toplevel equivalent surface".into(), + ); + + return; + } + + let handle = { + let state = &mut *state.lock().unwrap(); + // Generate a randomized handle. Only use alphanumerics because some languages do + // not have the same string capabilities as rust and vice versa. + let handle = Alphanumeric.sample_string(&mut rand::thread_rng(), 32); + let exports = &mut state.exports; + + exports.push(Export { + surface, + handle: handle.clone(), + inner: id.deref().clone(), + imports: vec![], + compositor_created: false, + }); + + handle + }; + + exported_implementation(id.clone(), state, shell); + + id.deref().handle(handle); + } + + _ => unreachable!(), + } +} + +fn implement_importer( + importer: Main, + state: Arc>, + shell_state: Arc>, +) -> zxdg_importer_v2::ZxdgImporterV2 { + let destructor_state = state.clone(); + let destructor_shell = shell_state.clone(); + + importer.quick_assign(move |_, request, _| { + importer_implementation(request, state.clone(), shell_state.clone()); + }); + + importer.assign_destructor(Filter::new( + move |importer: zxdg_importer_v2::ZxdgImporterV2, _, _| { + let state = &mut *destructor_state.lock().unwrap(); + + state.exports.iter_mut().for_each(|export| { + export + .imports + // Remove imports from the same client as the importer + .retain(|import| { + let same_client = import.inner.as_ref().same_client_as(importer.as_ref()); + + if same_client { + import.inner.destroyed(); + import.remove_children(&*destructor_shell.lock().unwrap()); + + false + } else { + true + } + }); + }); + }, + )); + + importer.deref().clone() +} + +fn importer_implementation( + request: zxdg_importer_v2::Request, + state: Arc>, + shell_state: Arc>, +) { + let destructor_state = state.clone(); + let destructor_shell = shell_state.clone(); + + match request { + zxdg_importer_v2::Request::Destroy => { + // all is handled by destructor. + } + + zxdg_importer_v2::Request::ImportToplevel { id, handle } => { + { + let foreign_state = &mut state.lock().unwrap(); + let exports = &mut foreign_state.exports; + + match exports.iter_mut().find(|export| export.handle == handle) { + Some(export) => { + let inner = id.deref().clone(); + + export.imports.push(Import { + inner, + surface: export.surface.clone(), + handle: export.handle.clone(), + children: vec![], + }); + } + + // No matching handle was exported, give the client a dead import so the client + // knows the import handle is bad + None => id.deref().destroyed(), + } + } + + id.quick_assign(move |_, request, _| { + imported_implementation(request, handle.clone(), state.clone(), shell_state.clone()); + }); + + id.assign_destructor(Filter::new( + move |imported: zxdg_imported_v2::ZxdgImportedV2, _, _| { + let state = &mut *destructor_state.lock().unwrap(); + let exports = &mut state.exports; + + // Remove this import from the list of imports. + exports.iter_mut().for_each(|export| { + export.imports.retain(|import| { + let destroy = import.inner != imported; + + if destroy { + let shell = destructor_shell.lock().unwrap(); + import.remove_children(&*shell); + } + + destroy + }) + }); + }, + )); + } + + _ => unreachable!(), + } +} + +fn imported_implementation( + request: zxdg_imported_v2::Request, + handle: String, + state: Arc>, + shell: Arc>, +) { + match request { + zxdg_imported_v2::Request::Destroy => { + // all is handled by destructor. + } + + zxdg_imported_v2::Request::SetParentOf { surface } => { + // toplevel like would generally be okay, however we cannot rely on wl_shell_surface as + // a toplevel surface has no tracking for parents, only transient does. That becomes an + // issue when a wl_shell_surface attempts to import an (z)xdg_toplevel surface and set it + // as it's parent. + // + // Also the presence of a protocol error noting that the surface must be an xdg_toplevel + // probably means wl_shell_surface was not accounted for in the design. So we throw a + // protocol error if either surface in the relationship is not an (z)xdg_toplevel. + if !is_toplevel_equivalent(&surface) + && compositor::get_role(&surface) != Some(WL_SHELL_SURFACE_ROLE) + { + // Protocol error if not a toplevel like surface + surface.as_ref().post_error( + zxdg_imported_v2::Error::InvalidSurface as u32, + "Surface must be an xdg_toplevel surface".into(), + ); + + return; + } + + let shell_state = shell.lock().unwrap(); + let foreign_state = &mut *state.lock().unwrap(); + let toplevel_surface = shell_state.toplevel_surface(&surface).unwrap(); + // Our import is valid, so we can assert the imported surface is a toplevel. + let imported_parent = foreign_state + .exports + .iter() + .find(|export| export.handle == handle) + .map(|export| export.surface.clone()) + .unwrap(); + + toplevel_surface.set_parent(Some(imported_parent)); + } + + _ => unreachable!(), + } +}