rework xdg_shell (#286)

* rework xdg_shell
use distinct surface roles for xdg_toplevel and
xdg_popup using a xdg_role! macro

* fix clippy warnings in shell

* added a generic DeadResource error and...
...added a result to xdg with_pending_state
Renamed the ToplevelState to ToplevelStateSet
This commit is contained in:
cmeissl 2021-06-15 23:32:02 +02:00 committed by GitHub
parent 41c7b22cc4
commit e9aef7caad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1690 additions and 531 deletions

View File

@ -28,8 +28,8 @@ use smithay::{
wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind, ShellSurfaceRole, wl_shell_init, ShellRequest, ShellState as WlShellState, ShellSurfaceKind, ShellSurfaceRole,
}, },
xdg::{ xdg::{
xdg_shell_init, PopupConfigure, ShellState as XdgShellState, ToplevelConfigure, XdgRequest, xdg_shell_init, Configure, ShellState as XdgShellState, XdgPopupSurfaceRole, XdgRequest,
XdgSurfacePendingState, XdgSurfaceRole, XdgToplevelSurfaceRole,
}, },
}, },
Serial, Serial,
@ -38,24 +38,17 @@ use smithay::{
use crate::{ use crate::{
state::AnvilState, state::AnvilState,
window_map::{Kind as SurfaceKind, WindowMap}, window_map::{Kind as SurfaceKind, PopupKind, WindowMap},
}; };
#[cfg(feature = "xwayland")] #[cfg(feature = "xwayland")]
use crate::xwayland::X11SurfaceRole; use crate::xwayland::X11SurfaceRole;
// The xwayland feature only adds a X11Surface role, but the macro does not support #[cfg]
#[cfg(not(feature = "xwayland"))]
define_roles!(Roles => define_roles!(Roles =>
[ XdgSurface, XdgSurfaceRole ] [ XdgToplevelSurface, XdgToplevelSurfaceRole ]
[ ShellSurface, ShellSurfaceRole] [ XdgPopupSurface, XdgPopupSurfaceRole ]
[ DnDIcon, DnDIconRole ]
[ CursorImage, CursorImageRole ]
);
#[cfg(feature = "xwayland")]
define_roles!(Roles =>
[ XdgSurface, XdgSurfaceRole ]
[ ShellSurface, ShellSurfaceRole] [ ShellSurface, ShellSurfaceRole]
#[cfg(feature = "xwayland")]
[ X11Surface, X11SurfaceRole ] [ X11Surface, X11SurfaceRole ]
[ DnDIcon, DnDIconRole ] [ DnDIcon, DnDIconRole ]
[ CursorImage, CursorImageRole ] [ CursorImage, CursorImageRole ]
@ -172,7 +165,7 @@ impl PointerGrab for ResizeSurfaceGrab {
_handle: &mut PointerInnerHandle<'_>, _handle: &mut PointerInnerHandle<'_>,
location: (f64, f64), location: (f64, f64),
_focus: Option<(wl_surface::WlSurface, (f64, f64))>, _focus: Option<(wl_surface::WlSurface, (f64, f64))>,
serial: Serial, _serial: Serial,
_time: u32, _time: u32,
) { ) {
let mut dx = location.0 - self.start_data.location.0; let mut dx = location.0 - self.start_data.location.0;
@ -226,11 +219,17 @@ impl PointerGrab for ResizeSurfaceGrab {
self.last_window_size = (new_window_width, new_window_height); self.last_window_size = (new_window_width, new_window_height);
match &self.toplevel { match &self.toplevel {
SurfaceKind::Xdg(xdg) => xdg.send_configure(ToplevelConfigure { SurfaceKind::Xdg(xdg) => {
size: Some(self.last_window_size), if xdg
states: vec![xdg_toplevel::State::Resizing], .with_pending_state(|state| {
serial, state.states.set(xdg_toplevel::State::Resizing);
}), state.size = Some(self.last_window_size);
})
.is_ok()
{
xdg.send_configure();
}
}
SurfaceKind::Wl(wl) => wl.send_configure( SurfaceKind::Wl(wl) => wl.send_configure(
(self.last_window_size.0 as u32, self.last_window_size.1 as u32), (self.last_window_size.0 as u32, self.last_window_size.1 as u32),
self.edges.into(), self.edges.into(),
@ -256,12 +255,15 @@ impl PointerGrab for ResizeSurfaceGrab {
handle.unset_grab(serial, time); handle.unset_grab(serial, time);
if let SurfaceKind::Xdg(xdg) = &self.toplevel { if let SurfaceKind::Xdg(xdg) = &self.toplevel {
// Send the final configure without the resizing state. if xdg
xdg.send_configure(ToplevelConfigure { .with_pending_state(|state| {
size: Some(self.last_window_size), state.states.unset(xdg_toplevel::State::Resizing);
states: vec![], state.size = Some(self.last_window_size);
serial, })
}); .is_ok()
{
xdg.send_configure();
}
self.ctoken self.ctoken
.with_surface_data(self.toplevel.get_surface().unwrap(), |attrs| { .with_surface_data(self.toplevel.get_surface().unwrap(), |attrs| {
@ -348,20 +350,19 @@ pub fn init_shell<Backend: 'static>(display: &mut Display, log: ::slog::Logger)
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let x = range.sample(&mut rng); let x = range.sample(&mut rng);
let y = range.sample(&mut rng); let y = range.sample(&mut rng);
surface.send_configure(ToplevelConfigure { // Do not send a configure here, the initial configure
size: None, // of a xdg_surface has to be sent during the commit if
states: vec![], // the surface is not already configured
serial: Serial::from(42),
});
xdg_window_map xdg_window_map
.borrow_mut() .borrow_mut()
.insert(SurfaceKind::Xdg(surface), (x, y)); .insert(SurfaceKind::Xdg(surface), (x, y));
} }
XdgRequest::NewPopup { surface } => surface.send_configure(PopupConfigure { XdgRequest::NewPopup { surface } => {
size: (10, 10), // Do not send a configure here, the initial configure
position: (10, 10), // of a xdg_surface has to be sent during the commit if
serial: Serial::from(42), // the surface is not already configured
}), xdg_window_map.borrow_mut().insert_popup(PopupKind::Xdg(surface));
}
XdgRequest::Move { XdgRequest::Move {
surface, surface,
seat, seat,
@ -462,38 +463,95 @@ pub fn init_shell<Backend: 'static>(display: &mut Display, log: ::slog::Logger)
pointer.set_grab(grab, serial); pointer.set_grab(grab, serial);
} }
XdgRequest::AckConfigure { surface, .. } => { XdgRequest::AckConfigure {
let waiting_for_serial = compositor_token.with_surface_data(&surface, |attrs| { surface, configure, ..
if let Some(data) = attrs.user_data.get::<RefCell<SurfaceData>>() { } => {
if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state { if let Configure::Toplevel(configure) = configure {
return Some(serial); let waiting_for_serial = compositor_token.with_surface_data(&surface, |attrs| {
if let Some(data) = attrs.user_data.get::<RefCell<SurfaceData>>() {
if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state {
return Some(serial);
}
}
None
});
if let Some(serial) = waiting_for_serial {
if configure.serial > serial {
// TODO: huh, we have missed the serial somehow.
// this should not happen, but it may be better to handle
// this case anyway
}
if serial == configure.serial {
if configure.state.states.contains(xdg_toplevel::State::Resizing) {
compositor_token.with_surface_data(&surface, |attrs| {
let mut data = attrs
.user_data
.get::<RefCell<SurfaceData>>()
.unwrap()
.borrow_mut();
if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state
{
data.resize_state = ResizeState::WaitingForCommit(resize_data);
} else {
unreachable!()
}
})
}
} }
} }
}
None }
}); XdgRequest::Fullscreen { surface, output, .. } => {
if surface
if let Some(serial) = waiting_for_serial { .with_pending_state(|state| {
let acked = compositor_token // TODO: Use size of current output the window is on and set position to (0,0)
.with_role_data(&surface, |role: &mut XdgSurfaceRole| { state.states.set(xdg_toplevel::State::Fullscreen);
!role.pending_configures.contains(&serial.into()) state.size = Some((800, 600));
}) // TODO: If the provided output is None, use the output where
.unwrap(); // the toplevel is currently shown
state.fullscreen_output = output;
if acked { })
compositor_token.with_surface_data(&surface, |attrs| { .is_ok()
let mut data = attrs {
.user_data surface.send_configure();
.get::<RefCell<SurfaceData>>() }
.unwrap() }
.borrow_mut(); XdgRequest::UnFullscreen { surface } => {
if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state { if surface
data.resize_state = ResizeState::WaitingForCommit(resize_data); .with_pending_state(|state| {
} else { state.states.unset(xdg_toplevel::State::Fullscreen);
unreachable!() state.size = None;
} state.fullscreen_output = None;
}) })
} .is_ok()
{
surface.send_configure();
}
}
XdgRequest::Maximize { surface } => {
if surface
.with_pending_state(|state| {
// TODO: Use size of current output the window is on and set position to (0,0)
state.states.set(xdg_toplevel::State::Maximized);
state.size = Some((800, 600));
})
.is_ok()
{
surface.send_configure();
}
}
XdgRequest::UnMaximize { surface } => {
if surface
.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Maximized);
state.size = None;
})
.is_ok()
{
surface.send_configure();
} }
} }
_ => (), _ => (),
@ -790,14 +848,36 @@ fn surface_commit(
let mut geometry = None; let mut geometry = None;
let mut min_size = (0, 0); let mut min_size = (0, 0);
let mut max_size = (0, 0); let mut max_size = (0, 0);
let _ = token.with_role_data(surface, |role: &mut XdgSurfaceRole| {
if let XdgSurfacePendingState::Toplevel(ref state) = role.pending_state { if token.has_role::<XdgToplevelSurfaceRole>(surface) {
min_size = state.min_size; if let Some(SurfaceKind::Xdg(xdg)) = window_map.borrow().find(surface) {
max_size = state.max_size; xdg.commit();
} }
geometry = role.window_geometry; token
}); .with_role_data(surface, |role: &mut XdgToplevelSurfaceRole| {
if let XdgToplevelSurfaceRole::Some(attributes) = role {
geometry = attributes.window_geometry;
min_size = attributes.min_size;
max_size = attributes.max_size;
}
})
.unwrap();
}
if token.has_role::<XdgPopupSurfaceRole>(surface) {
if let Some(PopupKind::Xdg(xdg)) = window_map.borrow().find_popup(&surface) {
xdg.commit();
}
token
.with_role_data(surface, |role: &mut XdgPopupSurfaceRole| {
if let XdgPopupSurfaceRole::Some(attributes) = role {
geometry = attributes.window_geometry;
}
})
.unwrap();
}
let sub_data = token let sub_data = token
.with_role_data(surface, |&mut role: &mut SubsurfaceRole| role) .with_role_data(surface, |&mut role: &mut SubsurfaceRole| role)

View File

@ -7,7 +7,7 @@ use smithay::{
compositor::{roles::Role, CompositorToken, SubsurfaceRole, TraversalAction}, compositor::{roles::Role, CompositorToken, SubsurfaceRole, TraversalAction},
shell::{ shell::{
legacy::{ShellSurface, ShellSurfaceRole}, legacy::{ShellSurface, ShellSurfaceRole},
xdg::{ToplevelSurface, XdgSurfaceRole}, xdg::{PopupSurface, ToplevelSurface, XdgPopupSurfaceRole, XdgToplevelSurfaceRole},
}, },
}, },
}; };
@ -37,7 +37,7 @@ impl<R> Clone for Kind<R> {
impl<R> Kind<R> impl<R> Kind<R>
where where
R: Role<SubsurfaceRole> + Role<XdgSurfaceRole> + Role<ShellSurfaceRole> + 'static, R: Role<SubsurfaceRole> + Role<XdgToplevelSurfaceRole> + Role<ShellSurfaceRole> + 'static,
{ {
pub fn alive(&self) -> bool { pub fn alive(&self) -> bool {
match *self { match *self {
@ -68,6 +68,35 @@ where
} }
} }
pub enum PopupKind<R> {
Xdg(PopupSurface<R>),
}
// We implement Clone manually because #[derive(..)] would require R: Clone.
impl<R> Clone for PopupKind<R> {
fn clone(&self) -> Self {
match self {
PopupKind::Xdg(xdg) => PopupKind::Xdg(xdg.clone()),
}
}
}
impl<R> PopupKind<R>
where
R: Role<XdgPopupSurfaceRole> + 'static,
{
pub fn alive(&self) -> bool {
match *self {
PopupKind::Xdg(ref t) => t.alive(),
}
}
pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> {
match *self {
PopupKind::Xdg(ref t) => t.get_surface(),
}
}
}
struct Window<R> { struct Window<R> {
location: (i32, i32), location: (i32, i32),
/// A bounding box over this window and its children. /// A bounding box over this window and its children.
@ -80,7 +109,7 @@ struct Window<R> {
impl<R> Window<R> impl<R> Window<R>
where where
R: Role<SubsurfaceRole> + Role<XdgSurfaceRole> + Role<ShellSurfaceRole> + 'static, R: Role<SubsurfaceRole> + Role<XdgToplevelSurfaceRole> + Role<ShellSurfaceRole> + 'static,
{ {
/// Finds the topmost surface under this point if any and returns it together with the location of this /// Finds the topmost surface under this point if any and returns it together with the location of this
/// surface. /// surface.
@ -203,19 +232,29 @@ where
} }
} }
pub struct Popup<R> {
popup: PopupKind<R>,
}
pub struct WindowMap<R> { pub struct WindowMap<R> {
ctoken: CompositorToken<R>, ctoken: CompositorToken<R>,
windows: Vec<Window<R>>, windows: Vec<Window<R>>,
popups: Vec<Popup<R>>,
} }
impl<R> WindowMap<R> impl<R> WindowMap<R>
where where
R: Role<SubsurfaceRole> + Role<XdgSurfaceRole> + Role<ShellSurfaceRole> + 'static, R: Role<SubsurfaceRole>
+ Role<XdgToplevelSurfaceRole>
+ Role<XdgPopupSurfaceRole>
+ Role<ShellSurfaceRole>
+ 'static,
{ {
pub fn new(ctoken: CompositorToken<R>) -> Self { pub fn new(ctoken: CompositorToken<R>) -> Self {
WindowMap { WindowMap {
ctoken, ctoken,
windows: Vec::new(), windows: Vec::new(),
popups: Vec::new(),
} }
} }
@ -229,6 +268,11 @@ where
self.windows.insert(0, window); self.windows.insert(0, window);
} }
pub fn insert_popup(&mut self, popup: PopupKind<R>) {
let popup = Popup { popup };
self.popups.push(popup);
}
pub fn get_surface_under(&self, point: (f64, f64)) -> Option<(wl_surface::WlSurface, (f64, f64))> { pub fn get_surface_under(&self, point: (f64, f64)) -> Option<(wl_surface::WlSurface, (f64, f64))> {
for w in &self.windows { for w in &self.windows {
if let Some(surface) = w.matching(point, self.ctoken) { if let Some(surface) = w.matching(point, self.ctoken) {
@ -269,6 +313,7 @@ where
pub fn refresh(&mut self) { pub fn refresh(&mut self) {
self.windows.retain(|w| w.toplevel.alive()); self.windows.retain(|w| w.toplevel.alive());
self.popups.retain(|p| p.popup.alive());
for w in &mut self.windows { for w in &mut self.windows {
w.self_update(self.ctoken); w.self_update(self.ctoken);
} }
@ -300,6 +345,20 @@ where
}) })
} }
pub fn find_popup(&self, surface: &wl_surface::WlSurface) -> Option<PopupKind<R>> {
self.popups.iter().find_map(|p| {
if p.popup
.get_surface()
.map(|s| s.as_ref().equals(surface.as_ref()))
.unwrap_or(false)
{
Some(p.popup.clone())
} else {
None
}
})
}
/// Returns the location of the toplevel, if it exists. /// Returns the location of the toplevel, if it exists.
pub fn location(&self, toplevel: &Kind<R>) -> Option<(i32, i32)> { pub fn location(&self, toplevel: &Kind<R>) -> Option<(i32, i32)> {
self.windows self.windows

View File

@ -15,3 +15,15 @@ impl std::fmt::Display for UnmanagedResource {
} }
impl std::error::Error for UnmanagedResource {} impl std::error::Error for UnmanagedResource {}
/// This resource has been destroyed and can no longer be used.
#[derive(Debug)]
pub struct DeadResource;
impl std::fmt::Display for DeadResource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("This resource has been destroyed and can no longer be used.")
}
}
impl std::error::Error for DeadResource {}

View File

@ -1,5 +1,5 @@
/// A rectangle defined by its top-left corner and dimensions /// A rectangle defined by its top-left corner and dimensions
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Rectangle { pub struct Rectangle {
/// horizontal position of the top-left corner of the rectangle, in surface coordinates /// horizontal position of the top-left corner of the rectangle, in surface coordinates
pub x: i32, pub x: i32,

View File

@ -186,17 +186,17 @@ macro_rules! define_roles(
($enum_name: ident) => { ($enum_name: ident) => {
define_roles!($enum_name =>); define_roles!($enum_name =>);
}; };
($enum_name:ident => $([ $role_name: ident, $role_data: ty])*) => { ($enum_name:ident => $($(#[$role_attr:meta])* [$role_name: ident, $role_data: ty])*) => {
define_roles!(__impl $enum_name => define_roles!(__impl $enum_name =>
// add in subsurface role // add in subsurface role
[Subsurface, $crate::wayland::compositor::SubsurfaceRole] [Subsurface, $crate::wayland::compositor::SubsurfaceRole]
$([$role_name, $role_data])* $($(#[$role_attr])* [$role_name, $role_data])*
); );
}; };
(__impl $enum_name:ident => $([ $role_name: ident, $role_data: ty])*) => { (__impl $enum_name:ident => $($(#[$role_attr:meta])* [$role_name: ident, $role_data: ty])*) => {
pub enum $enum_name { pub enum $enum_name {
NoRole, NoRole,
$($role_name($role_data)),* $($(#[$role_attr])* $role_name($role_data)),*
} }
impl Default for $enum_name { impl Default for $enum_name {
@ -216,6 +216,7 @@ macro_rules! define_roles(
} }
$( $(
$(#[$role_attr])*
impl $crate::wayland::compositor::roles::Role<$role_data> for $enum_name { impl $crate::wayland::compositor::roles::Role<$role_data> for $enum_name {
fn set_with(&mut self, data: $role_data) -> ::std::result::Result<(), $role_data> { fn set_with(&mut self, data: $role_data) -> ::std::result::Result<(), $role_data> {
if let $enum_name::NoRole = *self { if let $enum_name::NoRole = *self {

View File

@ -74,6 +74,8 @@ use wayland_server::{
Display, Filter, Global, Display, Filter, Global,
}; };
use super::PingError;
mod wl_handlers; mod wl_handlers;
/// Metadata associated with the `wl_surface` role /// Metadata associated with the `wl_surface` role
@ -83,7 +85,7 @@ pub struct ShellSurfaceRole {
pub title: String, pub title: String,
/// Class of the surface /// Class of the surface
pub class: String, pub class: String,
pending_ping: Serial, pending_ping: Option<Serial>,
} }
/// A handle to a shell surface /// A handle to a shell surface
@ -139,24 +141,21 @@ where
/// down to 0 before a pong is received, mark the client as unresponsive. /// down to 0 before a pong is received, mark the client as unresponsive.
/// ///
/// Fails if this shell client already has a pending ping or is already dead. /// Fails if this shell client already has a pending ping or is already dead.
pub fn send_ping(&self, serial: Serial) -> Result<(), ()> { pub fn send_ping(&self, serial: Serial) -> Result<(), PingError> {
if !self.alive() { if !self.alive() {
return Err(()); return Err(PingError::DeadSurface);
}
let ret = self.token.with_role_data(&self.wl_surface, |data| {
if data.pending_ping == Serial::from(0) {
data.pending_ping = serial;
true
} else {
false
}
});
if let Ok(true) = ret {
self.shell_surface.ping(serial.into());
Ok(())
} else {
Err(())
} }
self.token
.with_role_data(&self.wl_surface, |data| {
if let Some(pending_ping) = data.pending_ping {
return Err(PingError::PingAlreadyPending(pending_ping));
}
data.pending_ping = Some(serial);
Ok(())
})
.unwrap()?;
self.shell_surface.ping(serial.into());
Ok(())
} }
/// Send a configure event to this toplevel surface to suggest it a new configuration /// Send a configure event to this toplevel surface to suggest it a new configuration

View File

@ -32,7 +32,7 @@ pub(crate) fn implement_shell<R, Impl>(
let role_data = ShellSurfaceRole { let role_data = ShellSurfaceRole {
title: "".into(), title: "".into(),
class: "".into(), class: "".into(),
pending_ping: Serial::from(0), pending_ping: None,
}; };
if ctoken.give_role_with(&surface, role_data).is_err() { if ctoken.give_role_with(&surface, role_data).is_err() {
shell shell
@ -102,8 +102,8 @@ where
let serial = Serial::from(serial); let serial = Serial::from(serial);
let valid = ctoken let valid = ctoken
.with_role_data(&data.surface, |data| { .with_role_data(&data.surface, |data| {
if data.pending_ping == serial { if data.pending_ping == Some(serial) {
data.pending_ping = Serial::from(0); data.pending_ping = None;
true true
} else { } else {
false false

View File

@ -14,5 +14,20 @@
//! - The [`legacy`](legacy/index.html) module provides handlers for the `wl_shell` protocol, which //! - The [`legacy`](legacy/index.html) module provides handlers for the `wl_shell` protocol, which
//! is now deprecated. You only need it if you want to support apps predating `xdg_shell`. //! is now deprecated. You only need it if you want to support apps predating `xdg_shell`.
use super::Serial;
use thiserror::Error;
pub mod legacy; pub mod legacy;
pub mod xdg; pub mod xdg;
/// Represents the possible errors returned from
/// a surface ping
#[derive(Debug, Error)]
pub enum PingError {
/// The operation failed because the underlying surface has been destroyed
#[error("the ping failed cause the underlying surface has been destroyed")]
DeadSurface,
/// There is already a pending ping
#[error("there is already a ping pending `{0:?}`")]
PingAlreadyPending(Serial),
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
use std::{cell::RefCell, ops::Deref as _, sync::Mutex}; use std::{cell::RefCell, ops::Deref as _, sync::Mutex};
use crate::wayland::compositor::{roles::*, CompositorToken}; use crate::wayland::compositor::{roles::*, CompositorToken};
use crate::wayland::shell::xdg::{ConfigureError, PopupState};
use crate::wayland::Serial; use crate::wayland::Serial;
use wayland_protocols::xdg_shell::server::{ use wayland_protocols::xdg_shell::server::{
xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base, xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
@ -10,9 +11,9 @@ use wayland_server::{protocol::wl_surface, Filter, Main};
use crate::utils::Rectangle; use crate::utils::Rectangle;
use super::{ use super::{
make_shell_client_data, PopupConfigure, PopupKind, PopupState, PositionerState, ShellClient, make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData,
ShellClientData, ShellData, ToplevelConfigure, ToplevelKind, ToplevelState, XdgRequest, ShellData, ToplevelClientPending, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRole,
XdgSurfacePendingState, XdgSurfaceRole, XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes,
}; };
pub(crate) fn implement_wm_base<R>( pub(crate) fn implement_wm_base<R>(
@ -20,7 +21,7 @@ pub(crate) fn implement_wm_base<R>(
shell_data: &ShellData<R>, shell_data: &ShellData<R>,
) -> xdg_wm_base::XdgWmBase ) -> xdg_wm_base::XdgWmBase
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{ {
shell.quick_assign(|shell, req, _data| wm_implementation::<R>(req, shell.deref().clone())); shell.quick_assign(|shell, req, _data| wm_implementation::<R>(req, shell.deref().clone()));
shell.as_ref().user_data().set(|| ShellUserData { shell.as_ref().user_data().set(|| ShellUserData {
@ -55,7 +56,7 @@ pub(crate) fn make_shell_client<R>(
fn wm_implementation<R>(request: xdg_wm_base::Request, shell: xdg_wm_base::XdgWmBase) fn wm_implementation<R>(request: xdg_wm_base::Request, shell: xdg_wm_base::XdgWmBase)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = shell.as_ref().user_data().get::<ShellUserData<R>>().unwrap(); let data = shell.as_ref().user_data().get::<ShellUserData<R>>().unwrap();
match request { match request {
@ -66,24 +67,9 @@ where
implement_positioner(id); implement_positioner(id);
} }
xdg_wm_base::Request::GetXdgSurface { id, surface } => { xdg_wm_base::Request::GetXdgSurface { id, surface } => {
let role_data = XdgSurfaceRole { // Do not assign a role to the surface here
pending_state: XdgSurfacePendingState::None, // xdg_surface is not role, only xdg_toplevel and
window_geometry: None, // xdg_popup are defined as roles
pending_configures: Vec::new(),
configured: false,
};
if data
.shell_data
.compositor_token
.give_role_with(&surface, role_data)
.is_err()
{
shell.as_ref().post_error(
xdg_wm_base::Error::Role as u32,
"Surface already has a role.".into(),
);
return;
}
id.quick_assign(|surface, req, _data| { id.quick_assign(|surface, req, _data| {
xdg_surface_implementation::<R>(req, surface.deref().clone()) xdg_surface_implementation::<R>(req, surface.deref().clone())
}); });
@ -98,8 +84,8 @@ where
let serial = Serial::from(serial); let serial = Serial::from(serial);
let valid = { let valid = {
let mut guard = data.client_data.lock().unwrap(); let mut guard = data.client_data.lock().unwrap();
if guard.pending_ping == serial { if guard.pending_ping == Some(serial) {
guard.pending_ping = Serial::from(0); guard.pending_ping = None;
true true
} else { } else {
false false
@ -191,7 +177,7 @@ struct XdgSurfaceUserData<R> {
fn destroy_surface<R>(surface: xdg_surface::XdgSurface) fn destroy_surface<R>(surface: xdg_surface::XdgSurface)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = surface let data = surface
.as_ref() .as_ref()
@ -204,24 +190,36 @@ where
// disconnecting client), ignore the protocol check. // disconnecting client), ignore the protocol check.
return; return;
} }
data.shell_data
if !data.shell_data.compositor_token.has_a_role(&data.wl_surface) {
// No role assigned to the surface, we can exit early.
return;
}
let has_active_xdg_role = data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |rdata| { .with_role_data(&data.wl_surface, |role: &mut XdgToplevelSurfaceRole| {
if let XdgSurfacePendingState::None = rdata.pending_state { role.is_some()
// all is good
} else {
data.wm_base.as_ref().post_error(
xdg_wm_base::Error::Role as u32,
"xdg_surface was destroyed before its role object".into(),
);
}
}) })
.expect("xdg_surface exists but surface has not shell_surface role?!"); .unwrap_or(false)
|| data
.shell_data
.compositor_token
.with_role_data(&data.wl_surface, |role: &mut XdgPopupSurfaceRole| role.is_some())
.unwrap_or(false);
if has_active_xdg_role {
data.wm_base.as_ref().post_error(
xdg_wm_base::Error::Role as u32,
"xdg_surface was destroyed before its role object".into(),
);
}
} }
fn xdg_surface_implementation<R>(request: xdg_surface::Request, xdg_surface: xdg_surface::XdgSurface) fn xdg_surface_implementation<R>(request: xdg_surface::Request, xdg_surface: xdg_surface::XdgSurface)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = xdg_surface let data = xdg_surface
.as_ref() .as_ref()
@ -233,18 +231,25 @@ where
// all is handled by our destructor // all is handled by our destructor
} }
xdg_surface::Request::GetToplevel { id } => { xdg_surface::Request::GetToplevel { id } => {
data.shell_data // We now can assign a role to the surface
let surface = &data.wl_surface;
let shell = &data.wm_base;
let role_data = XdgToplevelSurfaceRole::Some(Default::default());
if data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .give_role_with(&surface, role_data)
data.pending_state = XdgSurfacePendingState::Toplevel(ToplevelState { .is_err()
parent: None, {
title: String::new(), shell.as_ref().post_error(
app_id: String::new(), xdg_wm_base::Error::Role as u32,
min_size: (0, 0), "Surface already has a role.".into(),
max_size: (0, 0), );
}); return;
}) }
.expect("xdg_surface exists but surface has not shell_surface role?!");
id.quick_assign(|toplevel, req, _data| { id.quick_assign(|toplevel, req, _data| {
toplevel_implementation::<R>(req, toplevel.deref().clone()) toplevel_implementation::<R>(req, toplevel.deref().clone())
}); });
@ -276,7 +281,9 @@ where
.as_ref() .as_ref()
.user_data() .user_data()
.get::<RefCell<PositionerState>>() .get::<RefCell<PositionerState>>()
.unwrap(); .unwrap()
.borrow()
.clone();
let parent_surface = parent.map(|parent| { let parent_surface = parent.map(|parent| {
let parent_data = parent let parent_data = parent
@ -286,16 +293,34 @@ where
.unwrap(); .unwrap();
parent_data.wl_surface.clone() parent_data.wl_surface.clone()
}); });
data.shell_data
// We now can assign a role to the surface
let surface = &data.wl_surface;
let shell = &data.wm_base;
let role_data = XdgPopupSurfaceRole::Some(XdgPopupSurfaceRoleAttributes {
parent: parent_surface,
server_pending: Some(PopupState {
// Set the positioner data as the popup geometry
geometry: positioner_data.get_geometry(),
}),
..Default::default()
});
if data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .give_role_with(&surface, role_data)
data.pending_state = XdgSurfacePendingState::Popup(PopupState { .is_err()
parent: parent_surface, {
positioner: positioner_data.borrow().clone(), shell.as_ref().post_error(
}); xdg_wm_base::Error::Role as u32,
}) "Surface already has a role.".into(),
.expect("xdg_surface exists but surface has not shell_surface role?!"); );
id.quick_assign(|popup, req, _data| xg_popup_implementation::<R>(req, popup.deref().clone())); return;
}
id.quick_assign(|popup, req, _data| xdg_popup_implementation::<R>(req, popup.deref().clone()));
id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::<R>(popup))); id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::<R>(popup)));
id.as_ref().user_data().set(|| ShellSurfaceUserData { id.as_ref().user_data().set(|| ShellSurfaceUserData {
shell_data: data.shell_data.clone(), shell_data: data.shell_data.clone(),
@ -316,40 +341,93 @@ where
(&mut *user_impl)(XdgRequest::NewPopup { surface: handle }); (&mut *user_impl)(XdgRequest::NewPopup { surface: handle });
} }
xdg_surface::Request::SetWindowGeometry { x, y, width, height } => { xdg_surface::Request::SetWindowGeometry { x, y, width, height } => {
data.shell_data // Check the role of the surface, this can be either xdg_toplevel
// or xdg_popup. If none of the role matches the xdg_surface has no role set
// which is a protocol error.
let surface = &data.wl_surface;
if !data.shell_data.compositor_token.has_a_role(surface) {
data.wm_base.as_ref().post_error(
xdg_surface::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(),
);
return;
}
// Set the next window geometry here, the geometry will be moved from
// next to the current geometry on a commit. This has to be done currently
// in anvil as the whole commit logic is implemented there until a proper
// abstraction has been found to handle commits within roles. This also
// ensures that a commit for a xdg_surface follows the rules for subsurfaces.
let has_wrong_role = data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .with_xdg_role(surface, |role| {
data.window_geometry = Some(Rectangle { x, y, width, height }); role.set_window_geometry(Rectangle { x, y, width, height })
}) })
.expect("xdg_surface exists but surface has not shell_surface role?!"); .is_err();
if has_wrong_role {
data.wm_base.as_ref().post_error(
xdg_wm_base::Error::Role as u32,
"xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(),
);
}
} }
xdg_surface::Request::AckConfigure { serial } => { xdg_surface::Request::AckConfigure { serial } => {
data.shell_data let serial = Serial::from(serial);
let surface = &data.wl_surface;
// Check the role of the surface, this can be either xdg_toplevel
// or xdg_popup. If none of the role matches the xdg_surface has no role set
// which is a protocol error.
if !data.shell_data.compositor_token.has_a_role(surface) {
data.wm_base.as_ref().post_error(
xdg_surface::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(),
);
return;
}
// Find the correct configure state for the provided serial
// discard all configure states that are older than the provided
// serial.
// If no matching serial can be found raise a protocol error
//
// Invoke the user impl with the found configuration
// This has to include the serial and the role specific data.
// - For xdg_popup there is no data.
// - For xdg_toplevel send the state data including
// width, height, min/max size, maximized, fullscreen, resizing, activated
//
// This can be used to integrate custom protocol extensions
//
let configure = match data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |role_data| { .with_xdg_role(surface, |role| role.ack_configure(serial))
let mut found = false; {
role_data.pending_configures.retain(|&s| { Ok(Ok(configure)) => configure,
if s == serial { Ok(Err(ConfigureError::SerialNotFound(serial))) => {
found = true; data.wm_base.as_ref().post_error(
} xdg_wm_base::Error::InvalidSurfaceState as u32,
s > serial format!("wrong configure serial: {}", <u32>::from(serial)),
}); );
if !found { return;
// client responded to a non-existing configure }
data.wm_base.as_ref().post_error( Err(_) => {
xdg_wm_base::Error::InvalidSurfaceState as u32, data.wm_base.as_ref().post_error(
format!("Wrong configure serial: {}", serial), xdg_wm_base::Error::Role as u32,
); "xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(),
} );
role_data.configured = true; return;
}) }
.expect("xdg_surface exists but surface has not shell_surface role?!"); };
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
let serial = Serial::from(serial);
(&mut *user_impl)(XdgRequest::AckConfigure { (&mut *user_impl)(XdgRequest::AckConfigure {
surface: data.wl_surface.clone(), surface: surface.clone(),
serial, configure,
}); });
} }
_ => unreachable!(), _ => unreachable!(),
@ -368,38 +446,62 @@ pub(crate) struct ShellSurfaceUserData<R> {
} }
// Utility functions allowing to factor out a lot of the upcoming logic // Utility functions allowing to factor out a lot of the upcoming logic
fn with_surface_toplevel_data<R, F>(shell_data: &ShellData<R>, toplevel: &xdg_toplevel::XdgToplevel, f: F) fn with_surface_toplevel_role_data<R, F, T>(
shell_data: &ShellData<R>,
toplevel: &xdg_toplevel::XdgToplevel,
f: F,
) -> T
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut ToplevelState), F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
{ {
let toplevel_data = toplevel let data = toplevel
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData<R>>()
.unwrap(); .unwrap();
shell_data shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&toplevel_data.wl_surface, |data| match data.pending_state { .with_role_data::<XdgToplevelSurfaceRole, _, _>(&data.wl_surface, |role| {
XdgSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data), let attributes = role
_ => unreachable!(), .as_mut()
.expect("xdg_toplevel exists but role has been destroyed?!");
f(attributes)
}) })
.expect("xdg_toplevel exists but surface has not shell_surface role?!"); .expect("xdg_toplevel exists but surface has not shell_surface role?!")
}
fn with_surface_toplevel_client_pending<R, F, T>(
shell_data: &ShellData<R>,
toplevel: &xdg_toplevel::XdgToplevel,
f: F,
) -> T
where
R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut ToplevelClientPending) -> T,
{
with_surface_toplevel_role_data(shell_data, toplevel, |data| {
if data.client_pending.is_none() {
data.client_pending = Some(Default::default());
}
f(&mut data.client_pending.as_mut().unwrap())
})
} }
pub fn send_toplevel_configure<R>(resource: &xdg_toplevel::XdgToplevel, configure: ToplevelConfigure) pub fn send_toplevel_configure<R>(resource: &xdg_toplevel::XdgToplevel, configure: ToplevelConfigure)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + 'static,
{ {
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData<R>>()
.unwrap(); .unwrap();
let (width, height) = configure.size.unwrap_or((0, 0));
let (width, height) = configure.state.size.unwrap_or((0, 0));
// convert the Vec<State> (which is really a Vec<u32>) into Vec<u8> // convert the Vec<State> (which is really a Vec<u32>) into Vec<u8>
let states = { let states = {
let mut states = configure.states; let mut states: Vec<xdg_toplevel::State> = configure.state.states.into();
let ptr = states.as_mut_ptr(); let ptr = states.as_mut_ptr();
let len = states.len(); let len = states.len();
let cap = states.capacity(); let cap = states.capacity();
@ -407,15 +509,13 @@ where
unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) }
}; };
let serial = configure.serial; let serial = configure.serial;
// Send the toplevel configure
resource.configure(width, height, states); resource.configure(width, height, states);
// Send the base xdg_surface configure event to mark
// The configure as finished
data.xdg_surface.configure(serial.into()); data.xdg_surface.configure(serial.into());
// Add the configure as pending
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_configures.push(serial.into())
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
} }
fn make_toplevel_handle<R: 'static>(resource: &xdg_toplevel::XdgToplevel) -> super::ToplevelSurface<R> { fn make_toplevel_handle<R: 'static>(resource: &xdg_toplevel::XdgToplevel) -> super::ToplevelSurface<R> {
@ -433,7 +533,7 @@ fn make_toplevel_handle<R: 'static>(resource: &xdg_toplevel::XdgToplevel) -> sup
fn toplevel_implementation<R>(request: xdg_toplevel::Request, toplevel: xdg_toplevel::XdgToplevel) fn toplevel_implementation<R>(request: xdg_toplevel::Request, toplevel: xdg_toplevel::XdgToplevel)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + 'static,
{ {
let data = toplevel let data = toplevel
.as_ref() .as_ref()
@ -445,8 +545,9 @@ where
// all it done by the destructor // all it done by the destructor
} }
xdg_toplevel::Request::SetParent { parent } => { xdg_toplevel::Request::SetParent { parent } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| { // Parent is not double buffered, we can set it directly
toplevel_data.parent = parent.map(|toplevel_surface_parent| { with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| {
data.parent = parent.map(|toplevel_surface_parent| {
toplevel_surface_parent toplevel_surface_parent
.as_ref() .as_ref()
.user_data() .user_data()
@ -458,16 +559,19 @@ where
}); });
} }
xdg_toplevel::Request::SetTitle { title } => { xdg_toplevel::Request::SetTitle { title } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| { // Title is not double buffered, we can set it directly
toplevel_data.title = title; with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| {
data.title = Some(title);
}); });
} }
xdg_toplevel::Request::SetAppId { app_id } => { xdg_toplevel::Request::SetAppId { app_id } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| { // AppId is not double buffered, we can set it directly
toplevel_data.app_id = app_id; with_surface_toplevel_role_data(&data.shell_data, &toplevel, |role| {
role.app_id = Some(app_id);
}); });
} }
xdg_toplevel::Request::ShowWindowMenu { seat, serial, x, y } => { xdg_toplevel::Request::ShowWindowMenu { seat, serial, x, y } => {
// This has to be handled by the compositor
let handle = make_toplevel_handle(&toplevel); let handle = make_toplevel_handle(&toplevel);
let serial = Serial::from(serial); let serial = Serial::from(serial);
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
@ -479,6 +583,7 @@ where
}); });
} }
xdg_toplevel::Request::Move { seat, serial } => { xdg_toplevel::Request::Move { seat, serial } => {
// This has to be handled by the compositor
let handle = make_toplevel_handle(&toplevel); let handle = make_toplevel_handle(&toplevel);
let serial = Serial::from(serial); let serial = Serial::from(serial);
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
@ -489,6 +594,7 @@ where
}); });
} }
xdg_toplevel::Request::Resize { seat, serial, edges } => { xdg_toplevel::Request::Resize { seat, serial, edges } => {
// This has to be handled by the compositor
let handle = make_toplevel_handle(&toplevel); let handle = make_toplevel_handle(&toplevel);
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
let serial = Serial::from(serial); let serial = Serial::from(serial);
@ -500,13 +606,13 @@ where
}); });
} }
xdg_toplevel::Request::SetMaxSize { width, height } => { xdg_toplevel::Request::SetMaxSize { width, height } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| { with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.max_size = (width, height); toplevel_data.max_size = Some((width, height));
}); });
} }
xdg_toplevel::Request::SetMinSize { width, height } => { xdg_toplevel::Request::SetMinSize { width, height } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| { with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.min_size = (width, height); toplevel_data.min_size = Some((width, height));
}); });
} }
xdg_toplevel::Request::SetMaximized => { xdg_toplevel::Request::SetMaximized => {
@ -533,6 +639,8 @@ where
(&mut *user_impl)(XdgRequest::UnFullscreen { surface: handle }); (&mut *user_impl)(XdgRequest::UnFullscreen { surface: handle });
} }
xdg_toplevel::Request::SetMinimized => { xdg_toplevel::Request::SetMinimized => {
// This has to be handled by the compositor, may not be
// supported and just ignored
let handle = make_toplevel_handle(&toplevel); let handle = make_toplevel_handle(&toplevel);
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::Minimize { surface: handle }); (&mut *user_impl)(XdgRequest::Minimize { surface: handle });
@ -543,7 +651,7 @@ where
fn destroy_toplevel<R>(toplevel: xdg_toplevel::XdgToplevel) fn destroy_toplevel<R>(toplevel: xdg_toplevel::XdgToplevel)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + 'static,
{ {
let data = toplevel let data = toplevel
.as_ref() .as_ref()
@ -557,9 +665,8 @@ where
} else { } else {
data.shell_data data.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .with_role_data(&data.wl_surface, |role_data| {
data.pending_state = XdgSurfacePendingState::None; *role_data = XdgToplevelSurfaceRole::None;
data.configured = false;
}) })
.expect("xdg_toplevel exists but surface has not shell_surface role?!"); .expect("xdg_toplevel exists but surface has not shell_surface role?!");
} }
@ -578,25 +685,23 @@ where
pub(crate) fn send_popup_configure<R>(resource: &xdg_popup::XdgPopup, configure: PopupConfigure) pub(crate) fn send_popup_configure<R>(resource: &xdg_popup::XdgPopup, configure: PopupConfigure)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData<R>>()
.unwrap(); .unwrap();
let (x, y) = configure.position;
let (width, height) = configure.size;
let serial = configure.serial; let serial = configure.serial;
resource.configure(x, y, width, height); let geometry = configure.state.geometry;
// Send the popup configure
resource.configure(geometry.x, geometry.y, geometry.width, geometry.height);
// Send the base xdg_surface configure event to mark
// the configure as finished
data.xdg_surface.configure(serial.into()); data.xdg_surface.configure(serial.into());
// Add the configure as pending
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_configures.push(serial.into())
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
} }
fn make_popup_handle<R: 'static>(resource: &xdg_popup::XdgPopup) -> super::PopupSurface<R> { fn make_popup_handle<R: 'static>(resource: &xdg_popup::XdgPopup) -> super::PopupSurface<R> {
@ -612,9 +717,9 @@ fn make_popup_handle<R: 'static>(resource: &xdg_popup::XdgPopup) -> super::Popup
} }
} }
fn xg_popup_implementation<R>(request: xdg_popup::Request, popup: xdg_popup::XdgPopup) fn xdg_popup_implementation<R>(request: xdg_popup::Request, popup: xdg_popup::XdgPopup)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = popup let data = popup
.as_ref() .as_ref()
@ -641,7 +746,7 @@ where
fn destroy_popup<R>(popup: xdg_popup::XdgPopup) fn destroy_popup<R>(popup: xdg_popup::XdgPopup)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = popup let data = popup
.as_ref() .as_ref()
@ -655,9 +760,8 @@ where
} else { } else {
data.shell_data data.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .with_role_data(&data.wl_surface, |role_data| {
data.pending_state = XdgSurfacePendingState::None; *role_data = XdgPopupSurfaceRole::None;
data.configured = false;
}) })
.expect("xdg_popup exists but surface has not shell_surface role?!"); .expect("xdg_popup exists but surface has not shell_surface role?!");
} }

View File

@ -1,6 +1,7 @@
use std::{cell::RefCell, ops::Deref as _, sync::Mutex}; use std::{cell::RefCell, ops::Deref as _, sync::Mutex};
use crate::wayland::compositor::{roles::*, CompositorToken}; use crate::wayland::compositor::{roles::*, CompositorToken};
use crate::wayland::shell::xdg::{ConfigureError, PopupState};
use crate::wayland::Serial; use crate::wayland::Serial;
use wayland_protocols::{ use wayland_protocols::{
unstable::xdg_shell::v6::server::{ unstable::xdg_shell::v6::server::{
@ -13,9 +14,9 @@ use wayland_server::{protocol::wl_surface, Filter, Main};
use crate::utils::Rectangle; use crate::utils::Rectangle;
use super::{ use super::{
make_shell_client_data, PopupConfigure, PopupKind, PopupState, PositionerState, ShellClient, make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData,
ShellClientData, ShellData, ToplevelConfigure, ToplevelKind, ToplevelState, XdgRequest, ShellData, ToplevelClientPending, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRole,
XdgSurfacePendingState, XdgSurfaceRole, XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes,
}; };
pub(crate) fn implement_shell<R>( pub(crate) fn implement_shell<R>(
@ -23,7 +24,7 @@ pub(crate) fn implement_shell<R>(
shell_data: &ShellData<R>, shell_data: &ShellData<R>,
) -> zxdg_shell_v6::ZxdgShellV6 ) -> zxdg_shell_v6::ZxdgShellV6
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{ {
shell.quick_assign(|shell, req, _data| shell_implementation::<R>(req, shell.deref().clone())); shell.quick_assign(|shell, req, _data| shell_implementation::<R>(req, shell.deref().clone()));
shell.as_ref().user_data().set(|| ShellUserData { shell.as_ref().user_data().set(|| ShellUserData {
@ -58,7 +59,7 @@ pub(crate) fn make_shell_client<R>(
fn shell_implementation<R>(request: zxdg_shell_v6::Request, shell: zxdg_shell_v6::ZxdgShellV6) fn shell_implementation<R>(request: zxdg_shell_v6::Request, shell: zxdg_shell_v6::ZxdgShellV6)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = shell.as_ref().user_data().get::<ShellUserData<R>>().unwrap(); let data = shell.as_ref().user_data().get::<ShellUserData<R>>().unwrap();
match request { match request {
@ -69,24 +70,6 @@ where
implement_positioner(id); implement_positioner(id);
} }
zxdg_shell_v6::Request::GetXdgSurface { id, surface } => { zxdg_shell_v6::Request::GetXdgSurface { id, surface } => {
let role_data = XdgSurfaceRole {
pending_state: XdgSurfacePendingState::None,
window_geometry: None,
pending_configures: Vec::new(),
configured: false,
};
if data
.shell_data
.compositor_token
.give_role_with(&surface, role_data)
.is_err()
{
shell.as_ref().post_error(
zxdg_shell_v6::Error::Role as u32,
"Surface already has a role.".into(),
);
return;
}
id.quick_assign(|surface, req, _data| { id.quick_assign(|surface, req, _data| {
xdg_surface_implementation::<R>(req, surface.deref().clone()) xdg_surface_implementation::<R>(req, surface.deref().clone())
}); });
@ -98,10 +81,11 @@ where
}); });
} }
zxdg_shell_v6::Request::Pong { serial } => { zxdg_shell_v6::Request::Pong { serial } => {
let serial = Serial::from(serial);
let valid = { let valid = {
let mut guard = data.client_data.lock().unwrap(); let mut guard = data.client_data.lock().unwrap();
if guard.pending_ping == Serial::from(serial) { if guard.pending_ping == Some(serial) {
guard.pending_ping = Serial::from(0); guard.pending_ping = None;
true true
} else { } else {
false false
@ -209,7 +193,7 @@ struct XdgSurfaceUserData<R> {
fn destroy_surface<R>(surface: zxdg_surface_v6::ZxdgSurfaceV6) fn destroy_surface<R>(surface: zxdg_surface_v6::ZxdgSurfaceV6)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = surface let data = surface
.as_ref() .as_ref()
@ -222,26 +206,38 @@ where
// disconnecting client), ignore the protocol check. // disconnecting client), ignore the protocol check.
return; return;
} }
data.shell_data
if !data.shell_data.compositor_token.has_a_role(&data.wl_surface) {
// No role assigned to the surface, we can exit early.
return;
}
let has_active_xdg_role = data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |rdata| { .with_role_data(&data.wl_surface, |role: &mut XdgToplevelSurfaceRole| {
if let XdgSurfacePendingState::None = rdata.pending_state { role.is_some()
// all is good
} else {
data.shell.as_ref().post_error(
zxdg_shell_v6::Error::Role as u32,
"xdg_surface was destroyed before its role object".into(),
);
}
}) })
.expect("xdg_surface exists but surface has not shell_surface role?!"); .unwrap_or(false)
|| data
.shell_data
.compositor_token
.with_role_data(&data.wl_surface, |role: &mut XdgPopupSurfaceRole| role.is_some())
.unwrap_or(false);
if has_active_xdg_role {
data.shell.as_ref().post_error(
zxdg_shell_v6::Error::Role as u32,
"xdg_surface was destroyed before its role object".into(),
);
}
} }
fn xdg_surface_implementation<R>( fn xdg_surface_implementation<R>(
request: zxdg_surface_v6::Request, request: zxdg_surface_v6::Request,
xdg_surface: zxdg_surface_v6::ZxdgSurfaceV6, xdg_surface: zxdg_surface_v6::ZxdgSurfaceV6,
) where ) where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = xdg_surface let data = xdg_surface
.as_ref() .as_ref()
@ -253,18 +249,25 @@ fn xdg_surface_implementation<R>(
// all is handled by our destructor // all is handled by our destructor
} }
zxdg_surface_v6::Request::GetToplevel { id } => { zxdg_surface_v6::Request::GetToplevel { id } => {
data.shell_data // We now can assign a role to the surface
let surface = &data.wl_surface;
let shell = &data.shell;
let role_data = XdgToplevelSurfaceRole::Some(Default::default());
if data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .give_role_with(&surface, role_data)
data.pending_state = XdgSurfacePendingState::Toplevel(ToplevelState { .is_err()
parent: None, {
title: String::new(), shell.as_ref().post_error(
app_id: String::new(), zxdg_shell_v6::Error::Role as u32,
min_size: (0, 0), "Surface already has a role.".into(),
max_size: (0, 0), );
}); return;
}) }
.expect("xdg_surface exists but surface has not shell_surface role?!");
id.quick_assign(|toplevel, req, _data| { id.quick_assign(|toplevel, req, _data| {
toplevel_implementation::<R>(req, toplevel.deref().clone()) toplevel_implementation::<R>(req, toplevel.deref().clone())
}); });
@ -296,22 +299,45 @@ fn xdg_surface_implementation<R>(
.as_ref() .as_ref()
.user_data() .user_data()
.get::<RefCell<PositionerState>>() .get::<RefCell<PositionerState>>()
.unwrap(); .unwrap()
.borrow()
.clone();
let parent_data = parent let parent_surface = {
.as_ref() let parent_data = parent
.user_data() .as_ref()
.get::<XdgSurfaceUserData<R>>() .user_data()
.unwrap(); .get::<XdgSurfaceUserData<R>>()
data.shell_data .unwrap();
parent_data.wl_surface.clone()
};
// We now can assign a role to the surface
let surface = &data.wl_surface;
let shell = &data.shell;
let role_data = XdgPopupSurfaceRole::Some(XdgPopupSurfaceRoleAttributes {
parent: Some(parent_surface),
server_pending: Some(PopupState {
// Set the positioner data as the popup geometry
geometry: positioner_data.get_geometry(),
}),
..Default::default()
});
if data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .give_role_with(&surface, role_data)
data.pending_state = XdgSurfacePendingState::Popup(PopupState { .is_err()
parent: Some(parent_data.wl_surface.clone()), {
positioner: positioner_data.borrow().clone(), shell.as_ref().post_error(
}); zxdg_shell_v6::Error::Role as u32,
}) "Surface already has a role.".into(),
.expect("xdg_surface exists but surface has not shell_surface role?!"); );
return;
}
id.quick_assign(|popup, req, _data| popup_implementation::<R>(req, popup.deref().clone())); id.quick_assign(|popup, req, _data| popup_implementation::<R>(req, popup.deref().clone()));
id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::<R>(popup))); id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::<R>(popup)));
id.as_ref().user_data().set(|| ShellSurfaceUserData { id.as_ref().user_data().set(|| ShellSurfaceUserData {
@ -333,40 +359,93 @@ fn xdg_surface_implementation<R>(
(&mut *user_impl)(XdgRequest::NewPopup { surface: handle }); (&mut *user_impl)(XdgRequest::NewPopup { surface: handle });
} }
zxdg_surface_v6::Request::SetWindowGeometry { x, y, width, height } => { zxdg_surface_v6::Request::SetWindowGeometry { x, y, width, height } => {
data.shell_data // Check the role of the surface, this can be either xdg_toplevel
// or xdg_popup. If none of the role matches the xdg_surface has no role set
// which is a protocol error.
let surface = &data.wl_surface;
if !data.shell_data.compositor_token.has_a_role(surface) {
data.shell.as_ref().post_error(
zxdg_surface_v6::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(),
);
return;
}
// Set the next window geometry here, the geometry will be moved from
// next to the current geometry on a commit. This has to be done currently
// in anvil as the whole commit logic is implemented there until a proper
// abstraction has been found to handle commits within roles. This also
// ensures that a commit for a xdg_surface follows the rules for subsurfaces.
let has_wrong_role = data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .with_xdg_role(surface, |role| {
data.window_geometry = Some(Rectangle { x, y, width, height }); role.set_window_geometry(Rectangle { x, y, width, height })
}) })
.expect("xdg_surface exists but surface has not shell_surface role?!"); .is_err();
if has_wrong_role {
data.shell.as_ref().post_error(
zxdg_shell_v6::Error::Role as u32,
"xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(),
);
}
} }
zxdg_surface_v6::Request::AckConfigure { serial } => { zxdg_surface_v6::Request::AckConfigure { serial } => {
data.shell_data let serial = Serial::from(serial);
let surface = &data.wl_surface;
// Check the role of the surface, this can be either xdg_toplevel
// or xdg_popup. If none of the role matches the xdg_surface has no role set
// which is a protocol error.
if !data.shell_data.compositor_token.has_a_role(surface) {
data.shell.as_ref().post_error(
zxdg_surface_v6::Error::NotConstructed as u32,
"xdg_surface must have a role.".into(),
);
return;
}
// Find the correct configure state for the provided serial
// discard all configure states that are older than the provided
// serial.
// If no matching serial can be found raise a protocol error
//
// Invoke the user impl with the found configuration
// This has to include the serial and the role specific data.
// - For xdg_popup there is no data.
// - For xdg_toplevel send the state data including
// width, height, min/max size, maximized, fullscreen, resizing, activated
//
// This can be used to integrate custom protocol extensions
//
let configure = match data
.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |role_data| { .with_xdg_role(surface, |role| role.ack_configure(serial))
let mut found = false; {
role_data.pending_configures.retain(|&s| { Ok(Ok(configure)) => configure,
if s == serial { Ok(Err(ConfigureError::SerialNotFound(serial))) => {
found = true; data.shell.as_ref().post_error(
} zxdg_shell_v6::Error::InvalidSurfaceState as u32,
s > serial format!("wrong configure serial: {}", <u32>::from(serial)),
}); );
if !found { return;
// client responded to a non-existing configure }
data.shell.as_ref().post_error( Err(_) => {
zxdg_shell_v6::Error::InvalidSurfaceState as u32, data.shell.as_ref().post_error(
format!("Wrong configure serial: {}", serial), zxdg_shell_v6::Error::Role as u32,
); "xdg_surface must have a role of xdg_toplevel or xdg_popup.".into(),
} );
role_data.configured = true; return;
}) }
.expect("xdg_surface exists but surface has not shell_surface role?!"); };
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
let serial = Serial::from(serial);
(&mut *user_impl)(XdgRequest::AckConfigure { (&mut *user_impl)(XdgRequest::AckConfigure {
surface: data.wl_surface.clone(), surface: surface.clone(),
serial, configure,
}); });
} }
_ => unreachable!(), _ => unreachable!(),
@ -385,38 +464,62 @@ pub struct ShellSurfaceUserData<R> {
} }
// Utility functions allowing to factor out a lot of the upcoming logic // Utility functions allowing to factor out a lot of the upcoming logic
fn with_surface_toplevel_data<R, F>(toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F) fn with_surface_toplevel_role_data<R, F, T>(
shell_data: &ShellData<R>,
toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6,
f: F,
) -> T
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut ToplevelState), F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
{ {
let data = toplevel let data = toplevel
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData<R>>()
.unwrap(); .unwrap();
data.shell_data shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| match data.pending_state { .with_role_data::<XdgToplevelSurfaceRole, _, _>(&data.wl_surface, |role| {
XdgSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data), let attributes = role
_ => unreachable!(), .as_mut()
.expect("xdg_toplevel exists but role has been destroyed?!");
f(attributes)
}) })
.expect("xdg_toplevel exists but surface has not shell_surface role?!"); .expect("xdg_toplevel exists but surface has not shell_surface role?!")
}
fn with_surface_toplevel_client_pending<R, F, T>(
shell_data: &ShellData<R>,
toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6,
f: F,
) -> T
where
R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut ToplevelClientPending) -> T,
{
with_surface_toplevel_role_data(shell_data, toplevel, |data| {
if data.client_pending.is_none() {
data.client_pending = Some(Default::default());
}
f(&mut data.client_pending.as_mut().unwrap())
})
} }
pub fn send_toplevel_configure<R>(resource: &zxdg_toplevel_v6::ZxdgToplevelV6, configure: ToplevelConfigure) pub fn send_toplevel_configure<R>(resource: &zxdg_toplevel_v6::ZxdgToplevelV6, configure: ToplevelConfigure)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + 'static,
{ {
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData<R>>()
.unwrap(); .unwrap();
let (width, height) = configure.size.unwrap_or((0, 0));
let (width, height) = configure.state.size.unwrap_or((0, 0));
// convert the Vec<State> (which is really a Vec<u32>) into Vec<u8> // convert the Vec<State> (which is really a Vec<u32>) into Vec<u8>
let states = { let states = {
let mut states = configure.states; let mut states: Vec<xdg_toplevel::State> = configure.state.states.into();
let ptr = states.as_mut_ptr(); let ptr = states.as_mut_ptr();
let len = states.len(); let len = states.len();
let cap = states.capacity(); let cap = states.capacity();
@ -424,15 +527,13 @@ where
unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) }
}; };
let serial = configure.serial; let serial = configure.serial;
// Send the toplevel configure
resource.configure(width, height, states); resource.configure(width, height, states);
// Send the base xdg_surface configure event to mark
// The configure as finished
data.xdg_surface.configure(serial.into()); data.xdg_surface.configure(serial.into());
// Add the configure as pending
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_configures.push(serial.into())
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
} }
fn make_toplevel_handle<R: 'static>( fn make_toplevel_handle<R: 'static>(
@ -452,7 +553,7 @@ fn make_toplevel_handle<R: 'static>(
fn toplevel_implementation<R>(request: zxdg_toplevel_v6::Request, toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) fn toplevel_implementation<R>(request: zxdg_toplevel_v6::Request, toplevel: zxdg_toplevel_v6::ZxdgToplevelV6)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + 'static,
{ {
let data = toplevel let data = toplevel
.as_ref() .as_ref()
@ -464,8 +565,8 @@ where
// all it done by the destructor // all it done by the destructor
} }
zxdg_toplevel_v6::Request::SetParent { parent } => { zxdg_toplevel_v6::Request::SetParent { parent } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| { with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| {
toplevel_data.parent = parent.map(|toplevel_surface_parent| { data.parent = parent.map(|toplevel_surface_parent| {
let parent_data = toplevel_surface_parent let parent_data = toplevel_surface_parent
.as_ref() .as_ref()
.user_data() .user_data()
@ -476,13 +577,15 @@ where
}); });
} }
zxdg_toplevel_v6::Request::SetTitle { title } => { zxdg_toplevel_v6::Request::SetTitle { title } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| { // Title is not double buffered, we can set it directly
toplevel_data.title = title; with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| {
data.title = Some(title);
}); });
} }
zxdg_toplevel_v6::Request::SetAppId { app_id } => { zxdg_toplevel_v6::Request::SetAppId { app_id } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| { // AppId is not double buffered, we can set it directly
toplevel_data.app_id = app_id; with_surface_toplevel_role_data(&data.shell_data, &toplevel, |role| {
role.app_id = Some(app_id);
}); });
} }
zxdg_toplevel_v6::Request::ShowWindowMenu { seat, serial, x, y } => { zxdg_toplevel_v6::Request::ShowWindowMenu { seat, serial, x, y } => {
@ -520,13 +623,13 @@ where
}); });
} }
zxdg_toplevel_v6::Request::SetMaxSize { width, height } => { zxdg_toplevel_v6::Request::SetMaxSize { width, height } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| { with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.max_size = (width, height); toplevel_data.max_size = Some((width, height));
}); });
} }
zxdg_toplevel_v6::Request::SetMinSize { width, height } => { zxdg_toplevel_v6::Request::SetMinSize { width, height } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| { with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.min_size = (width, height); toplevel_data.min_size = Some((width, height));
}); });
} }
zxdg_toplevel_v6::Request::SetMaximized => { zxdg_toplevel_v6::Request::SetMaximized => {
@ -553,6 +656,8 @@ where
(&mut *user_impl)(XdgRequest::UnFullscreen { surface: handle }); (&mut *user_impl)(XdgRequest::UnFullscreen { surface: handle });
} }
zxdg_toplevel_v6::Request::SetMinimized => { zxdg_toplevel_v6::Request::SetMinimized => {
// This has to be handled by the compositor, may not be
// supported and just ignored
let handle = make_toplevel_handle(&toplevel); let handle = make_toplevel_handle(&toplevel);
let mut user_impl = data.shell_data.user_impl.borrow_mut(); let mut user_impl = data.shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::Minimize { surface: handle }); (&mut *user_impl)(XdgRequest::Minimize { surface: handle });
@ -563,7 +668,7 @@ where
fn destroy_toplevel<R>(toplevel: zxdg_toplevel_v6::ZxdgToplevelV6) fn destroy_toplevel<R>(toplevel: zxdg_toplevel_v6::ZxdgToplevelV6)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgToplevelSurfaceRole> + 'static,
{ {
let data = toplevel let data = toplevel
.as_ref() .as_ref()
@ -577,9 +682,8 @@ where
} else { } else {
data.shell_data data.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .with_role_data(&data.wl_surface, |role_data| {
data.pending_state = XdgSurfacePendingState::None; *role_data = XdgToplevelSurfaceRole::None;
data.configured = false;
}) })
.expect("xdg_toplevel exists but surface has not shell_surface role?!"); .expect("xdg_toplevel exists but surface has not shell_surface role?!");
} }
@ -598,25 +702,23 @@ where
pub(crate) fn send_popup_configure<R>(resource: &zxdg_popup_v6::ZxdgPopupV6, configure: PopupConfigure) pub(crate) fn send_popup_configure<R>(resource: &zxdg_popup_v6::ZxdgPopupV6, configure: PopupConfigure)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = resource let data = resource
.as_ref() .as_ref()
.user_data() .user_data()
.get::<ShellSurfaceUserData<R>>() .get::<ShellSurfaceUserData<R>>()
.unwrap(); .unwrap();
let (x, y) = configure.position;
let (width, height) = configure.size;
let serial = configure.serial; let serial = configure.serial;
resource.configure(x, y, width, height); let geometry = configure.state.geometry;
// Send the popup configure
resource.configure(geometry.x, geometry.y, geometry.width, geometry.height);
// Send the base xdg_surface configure event to mark
// the configure as finished
data.xdg_surface.configure(serial.into()); data.xdg_surface.configure(serial.into());
// Add the configure as pending
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_configures.push(serial.into())
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
} }
fn make_popup_handle<R: 'static>(resource: &zxdg_popup_v6::ZxdgPopupV6) -> super::PopupSurface<R> { fn make_popup_handle<R: 'static>(resource: &zxdg_popup_v6::ZxdgPopupV6) -> super::PopupSurface<R> {
@ -634,7 +736,7 @@ fn make_popup_handle<R: 'static>(resource: &zxdg_popup_v6::ZxdgPopupV6) -> super
fn popup_implementation<R>(request: zxdg_popup_v6::Request, popup: zxdg_popup_v6::ZxdgPopupV6) fn popup_implementation<R>(request: zxdg_popup_v6::Request, popup: zxdg_popup_v6::ZxdgPopupV6)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = popup let data = popup
.as_ref() .as_ref()
@ -661,7 +763,7 @@ where
fn destroy_popup<R>(popup: zxdg_popup_v6::ZxdgPopupV6) fn destroy_popup<R>(popup: zxdg_popup_v6::ZxdgPopupV6)
where where
R: Role<XdgSurfaceRole> + 'static, R: Role<XdgPopupSurfaceRole> + 'static,
{ {
let data = popup let data = popup
.as_ref() .as_ref()
@ -675,9 +777,8 @@ where
} else { } else {
data.shell_data data.shell_data
.compositor_token .compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| { .with_role_data(&data.wl_surface, |role_data| {
data.pending_state = XdgSurfacePendingState::None; *role_data = XdgPopupSurfaceRole::None;
data.configured = false;
}) })
.expect("xdg_popup exists but surface has not shell_surface role?!"); .expect("xdg_popup exists but surface has not shell_surface role?!");
} }