xdg-foreign

This is very much incomplete, but it implements the import and export of surfaces. However functions related to setting and imported surface the parent of another surface is not done.

importer destructor destroys all imports made from the same client 


Implement SetParentOf, no undoing yet


Add inner import type


Destroy child surface relationships when importing


A note


move init function upwards


use 0.29.0 crate


Move role string constants into the public


Use toplevel_like for surface checks in foreign


add methods to get the parent surface


Make sure ZXDG roles are public api


Consistent ordering of parameters on xdg_foreign impl methods


Let's update the changelog


Document how to use xdg_foreign


wl_shell_surface is likely invalid, so it cannot be imported or exported


Changelog suggestions


remove redundant inner ref cell


Remove all uses of the refcell like behavior


Allow compositor to export surfaces on server



Appease new clippy lints


then


Use string references and iterate in reverse to simplify removals


heed request for order
This commit is contained in:
i509VCB 2021-07-30 22:43:59 -05:00 committed by Victor Berger
parent 963f742e74
commit 23a8af399d
10 changed files with 622 additions and 30 deletions

View File

@ -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.

View File

@ -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 }

View File

@ -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.
///

View File

@ -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

View File

@ -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};

View File

@ -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)
)
}

View File

@ -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<wl_surface::WlSurface> {
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<wl_surface::WlSurface>) -> 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)]

View File

@ -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<xdg_wm_base::XdgWmBase>,
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::<ShellSurfaceUserData>()
.unwrap()
.wl_surface
.clone()
})
let parent_surface = parent.map(|toplevel_surface_parent| {
toplevel_surface_parent
.as_ref()
.user_data()
.get::<ShellSurfaceUserData>()
.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<wl_surface::WlSurface> {
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<wl_surface::WlSurface>) {
with_surface_toplevel_role_data(toplevel, |data| {
data.parent = parent;
});
}

View File

@ -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<zxdg_shell_v6::ZxdgShellV6>,
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::<ShellSurfaceUserData>()
.unwrap();
parent_data.wl_surface.clone()
})
let parent_surface = parent.map(|toplevel_surface_parent| {
toplevel_surface_parent
.as_ref()
.user_data()
.get::<ShellSurfaceUserData>()
.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<xdg_positioner::A
_ => None,
}
}
pub(crate) fn get_parent(toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6) -> Option<wl_surface::WlSurface> {
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<wl_surface::WlSurface>) {
with_surface_toplevel_role_data(toplevel, |data| {
data.parent = parent;
});
}

View File

@ -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<Mutex<ShellState>>,
exports: Vec<Export>,
}
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<WlSurface> {
self.exports
.iter()
.find(|export| export.handle == handle)
.map(|export| export.surface.clone())
}
}
/// Creates new `xdg-foreign` globals.
pub fn xdg_foreign_init<L>(
display: &mut Display,
xdg_shell_state: Arc<Mutex<ShellState>>,
logger: L,
) -> (
Arc<Mutex<XdgForeignState>>,
Global<zxdg_exporter_v2::ZxdgExporterV2>,
Global<zxdg_importer_v2::ZxdgImporterV2>,
)
where
L: Into<Option<::slog::Logger>>,
{
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<Import>,
/// 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<WlSurface>,
}
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<zxdg_exporter_v2::ZxdgExporterV2>,
state: Arc<Mutex<XdgForeignState>>,
shell: Arc<Mutex<ShellState>>,
) -> 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<zxdg_exported_v2::ZxdgExportedV2>,
state: Arc<Mutex<XdgForeignState>>,
shell: Arc<Mutex<ShellState>>,
) {
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<Mutex<XdgForeignState>>,
shell: Arc<Mutex<ShellState>>,
) {
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<zxdg_importer_v2::ZxdgImporterV2>,
state: Arc<Mutex<XdgForeignState>>,
shell_state: Arc<Mutex<ShellState>>,
) -> 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<Mutex<XdgForeignState>>,
shell_state: Arc<Mutex<ShellState>>,
) {
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<Mutex<XdgForeignState>>,
shell: Arc<Mutex<ShellState>>,
) {
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!(),
}
}