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,
},
xdg::{
xdg_shell_init, PopupConfigure, ShellState as XdgShellState, ToplevelConfigure, XdgRequest,
XdgSurfacePendingState, XdgSurfaceRole,
xdg_shell_init, Configure, ShellState as XdgShellState, XdgPopupSurfaceRole, XdgRequest,
XdgToplevelSurfaceRole,
},
},
Serial,
@ -38,24 +38,17 @@ use smithay::{
use crate::{
state::AnvilState,
window_map::{Kind as SurfaceKind, WindowMap},
window_map::{Kind as SurfaceKind, PopupKind, WindowMap},
};
#[cfg(feature = "xwayland")]
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 =>
[ XdgSurface, XdgSurfaceRole ]
[ ShellSurface, ShellSurfaceRole]
[ DnDIcon, DnDIconRole ]
[ CursorImage, CursorImageRole ]
);
#[cfg(feature = "xwayland")]
define_roles!(Roles =>
[ XdgSurface, XdgSurfaceRole ]
[ XdgToplevelSurface, XdgToplevelSurfaceRole ]
[ XdgPopupSurface, XdgPopupSurfaceRole ]
[ ShellSurface, ShellSurfaceRole]
#[cfg(feature = "xwayland")]
[ X11Surface, X11SurfaceRole ]
[ DnDIcon, DnDIconRole ]
[ CursorImage, CursorImageRole ]
@ -172,7 +165,7 @@ impl PointerGrab for ResizeSurfaceGrab {
_handle: &mut PointerInnerHandle<'_>,
location: (f64, f64),
_focus: Option<(wl_surface::WlSurface, (f64, f64))>,
serial: Serial,
_serial: Serial,
_time: u32,
) {
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);
match &self.toplevel {
SurfaceKind::Xdg(xdg) => xdg.send_configure(ToplevelConfigure {
size: Some(self.last_window_size),
states: vec![xdg_toplevel::State::Resizing],
serial,
}),
SurfaceKind::Xdg(xdg) => {
if xdg
.with_pending_state(|state| {
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(
(self.last_window_size.0 as u32, self.last_window_size.1 as u32),
self.edges.into(),
@ -256,12 +255,15 @@ impl PointerGrab for ResizeSurfaceGrab {
handle.unset_grab(serial, time);
if let SurfaceKind::Xdg(xdg) = &self.toplevel {
// Send the final configure without the resizing state.
xdg.send_configure(ToplevelConfigure {
size: Some(self.last_window_size),
states: vec![],
serial,
});
if xdg
.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Resizing);
state.size = Some(self.last_window_size);
})
.is_ok()
{
xdg.send_configure();
}
self.ctoken
.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 x = range.sample(&mut rng);
let y = range.sample(&mut rng);
surface.send_configure(ToplevelConfigure {
size: None,
states: vec![],
serial: Serial::from(42),
});
// Do not send a configure here, the initial configure
// of a xdg_surface has to be sent during the commit if
// the surface is not already configured
xdg_window_map
.borrow_mut()
.insert(SurfaceKind::Xdg(surface), (x, y));
}
XdgRequest::NewPopup { surface } => surface.send_configure(PopupConfigure {
size: (10, 10),
position: (10, 10),
serial: Serial::from(42),
}),
XdgRequest::NewPopup { surface } => {
// Do not send a configure here, the initial configure
// of a xdg_surface has to be sent during the commit if
// the surface is not already configured
xdg_window_map.borrow_mut().insert_popup(PopupKind::Xdg(surface));
}
XdgRequest::Move {
surface,
seat,
@ -462,7 +463,10 @@ pub fn init_shell<Backend: 'static>(display: &mut Display, log: ::slog::Logger)
pointer.set_grab(grab, serial);
}
XdgRequest::AckConfigure { surface, .. } => {
XdgRequest::AckConfigure {
surface, configure, ..
} => {
if let Configure::Toplevel(configure) = configure {
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 {
@ -474,20 +478,22 @@ pub fn init_shell<Backend: 'static>(display: &mut Display, log: ::slog::Logger)
});
if let Some(serial) = waiting_for_serial {
let acked = compositor_token
.with_role_data(&surface, |role: &mut XdgSurfaceRole| {
!role.pending_configures.contains(&serial.into())
})
.unwrap();
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 acked {
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 {
if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state
{
data.resize_state = ResizeState::WaitingForCommit(resize_data);
} else {
unreachable!()
@ -496,6 +502,58 @@ pub fn init_shell<Backend: 'static>(display: &mut Display, log: ::slog::Logger)
}
}
}
}
}
XdgRequest::Fullscreen { surface, output, .. } => {
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::Fullscreen);
state.size = Some((800, 600));
// TODO: If the provided output is None, use the output where
// the toplevel is currently shown
state.fullscreen_output = output;
})
.is_ok()
{
surface.send_configure();
}
}
XdgRequest::UnFullscreen { surface } => {
if surface
.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Fullscreen);
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();
}
}
_ => (),
},
log.clone(),
@ -790,14 +848,36 @@ fn surface_commit(
let mut geometry = None;
let mut min_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 {
min_size = state.min_size;
max_size = state.max_size;
if token.has_role::<XdgToplevelSurfaceRole>(surface) {
if let Some(SurfaceKind::Xdg(xdg)) = window_map.borrow().find(surface) {
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
.with_role_data(surface, |&mut role: &mut SubsurfaceRole| role)

View File

@ -7,7 +7,7 @@ use smithay::{
compositor::{roles::Role, CompositorToken, SubsurfaceRole, TraversalAction},
shell::{
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>
where
R: Role<SubsurfaceRole> + Role<XdgSurfaceRole> + Role<ShellSurfaceRole> + 'static,
R: Role<SubsurfaceRole> + Role<XdgToplevelSurfaceRole> + Role<ShellSurfaceRole> + 'static,
{
pub fn alive(&self) -> bool {
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> {
location: (i32, i32),
/// A bounding box over this window and its children.
@ -80,7 +109,7 @@ struct Window<R> {
impl<R> Window<R>
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
/// surface.
@ -203,19 +232,29 @@ where
}
}
pub struct Popup<R> {
popup: PopupKind<R>,
}
pub struct WindowMap<R> {
ctoken: CompositorToken<R>,
windows: Vec<Window<R>>,
popups: Vec<Popup<R>>,
}
impl<R> WindowMap<R>
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 {
WindowMap {
ctoken,
windows: Vec::new(),
popups: Vec::new(),
}
}
@ -229,6 +268,11 @@ where
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))> {
for w in &self.windows {
if let Some(surface) = w.matching(point, self.ctoken) {
@ -269,6 +313,7 @@ where
pub fn refresh(&mut self) {
self.windows.retain(|w| w.toplevel.alive());
self.popups.retain(|p| p.popup.alive());
for w in &mut self.windows {
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.
pub fn location(&self, toplevel: &Kind<R>) -> Option<(i32, i32)> {
self.windows

View File

@ -15,3 +15,15 @@ impl std::fmt::Display 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
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Rectangle {
/// horizontal position of the top-left corner of the rectangle, in surface coordinates
pub x: i32,

View File

@ -186,17 +186,17 @@ macro_rules! define_roles(
($enum_name: ident) => {
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 =>
// add in subsurface role
[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 {
NoRole,
$($role_name($role_data)),*
$($(#[$role_attr])* $role_name($role_data)),*
}
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 {
fn set_with(&mut self, data: $role_data) -> ::std::result::Result<(), $role_data> {
if let $enum_name::NoRole = *self {

View File

@ -74,6 +74,8 @@ use wayland_server::{
Display, Filter, Global,
};
use super::PingError;
mod wl_handlers;
/// Metadata associated with the `wl_surface` role
@ -83,7 +85,7 @@ pub struct ShellSurfaceRole {
pub title: String,
/// Class of the surface
pub class: String,
pending_ping: Serial,
pending_ping: Option<Serial>,
}
/// A handle to a shell surface
@ -139,24 +141,21 @@ where
/// 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.
pub fn send_ping(&self, serial: Serial) -> Result<(), ()> {
pub fn send_ping(&self, serial: Serial) -> Result<(), PingError> {
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
self.token
.with_role_data(&self.wl_surface, |data| {
if let Some(pending_ping) = data.pending_ping {
return Err(PingError::PingAlreadyPending(pending_ping));
}
});
if let Ok(true) = ret {
data.pending_ping = Some(serial);
Ok(())
})
.unwrap()?;
self.shell_surface.ping(serial.into());
Ok(())
} else {
Err(())
}
}
/// 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 {
title: "".into(),
class: "".into(),
pending_ping: Serial::from(0),
pending_ping: None,
};
if ctoken.give_role_with(&surface, role_data).is_err() {
shell
@ -102,8 +102,8 @@ where
let serial = Serial::from(serial);
let valid = ctoken
.with_role_data(&data.surface, |data| {
if data.pending_ping == serial {
data.pending_ping = Serial::from(0);
if data.pending_ping == Some(serial) {
data.pending_ping = None;
true
} else {
false

View File

@ -14,5 +14,20 @@
//! - 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`.
use super::Serial;
use thiserror::Error;
pub mod legacy;
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 crate::wayland::compositor::{roles::*, CompositorToken};
use crate::wayland::shell::xdg::{ConfigureError, PopupState};
use crate::wayland::Serial;
use wayland_protocols::xdg_shell::server::{
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 super::{
make_shell_client_data, PopupConfigure, PopupKind, PopupState, PositionerState, ShellClient,
ShellClientData, ShellData, ToplevelConfigure, ToplevelKind, ToplevelState, XdgRequest,
XdgSurfacePendingState, XdgSurfaceRole,
make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData,
ShellData, ToplevelClientPending, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRole,
XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes,
};
pub(crate) fn implement_wm_base<R>(
@ -20,7 +21,7 @@ pub(crate) fn implement_wm_base<R>(
shell_data: &ShellData<R>,
) -> xdg_wm_base::XdgWmBase
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.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)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = shell.as_ref().user_data().get::<ShellUserData<R>>().unwrap();
match request {
@ -66,24 +67,9 @@ where
implement_positioner(id);
}
xdg_wm_base::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(
xdg_wm_base::Error::Role as u32,
"Surface already has a role.".into(),
);
return;
}
// Do not assign a role to the surface here
// xdg_surface is not role, only xdg_toplevel and
// xdg_popup are defined as roles
id.quick_assign(|surface, req, _data| {
xdg_surface_implementation::<R>(req, surface.deref().clone())
});
@ -98,8 +84,8 @@ where
let serial = Serial::from(serial);
let valid = {
let mut guard = data.client_data.lock().unwrap();
if guard.pending_ping == serial {
guard.pending_ping = Serial::from(0);
if guard.pending_ping == Some(serial) {
guard.pending_ping = None;
true
} else {
false
@ -191,7 +177,7 @@ struct XdgSurfaceUserData<R> {
fn destroy_surface<R>(surface: xdg_surface::XdgSurface)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = surface
.as_ref()
@ -204,24 +190,36 @@ where
// disconnecting client), ignore the protocol check.
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
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |rdata| {
if let XdgSurfacePendingState::None = rdata.pending_state {
// all is good
} else {
.with_role_data(&data.wl_surface, |role: &mut XdgToplevelSurfaceRole| {
role.is_some()
})
.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(),
);
}
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
fn xdg_surface_implementation<R>(request: xdg_surface::Request, xdg_surface: xdg_surface::XdgSurface)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = xdg_surface
.as_ref()
@ -233,18 +231,25 @@ where
// all is handled by our destructor
}
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
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::Toplevel(ToplevelState {
parent: None,
title: String::new(),
app_id: String::new(),
min_size: (0, 0),
max_size: (0, 0),
});
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
.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(|toplevel, req, _data| {
toplevel_implementation::<R>(req, toplevel.deref().clone())
});
@ -276,7 +281,9 @@ where
.as_ref()
.user_data()
.get::<RefCell<PositionerState>>()
.unwrap();
.unwrap()
.borrow()
.clone();
let parent_surface = parent.map(|parent| {
let parent_data = parent
@ -286,16 +293,34 @@ where
.unwrap();
parent_data.wl_surface.clone()
});
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::Popup(PopupState {
// 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,
positioner: positioner_data.borrow().clone(),
server_pending: Some(PopupState {
// Set the positioner data as the popup geometry
geometry: positioner_data.get_geometry(),
}),
..Default::default()
});
})
.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()));
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(|popup, req, _data| xdg_popup_implementation::<R>(req, popup.deref().clone()));
id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::<R>(popup)));
id.as_ref().user_data().set(|| ShellSurfaceUserData {
shell_data: data.shell_data.clone(),
@ -316,40 +341,93 @@ where
(&mut *user_impl)(XdgRequest::NewPopup { surface: handle });
}
xdg_surface::Request::SetWindowGeometry { x, y, width, height } => {
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.window_geometry = Some(Rectangle { x, y, width, height });
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
xdg_surface::Request::AckConfigure { serial } => {
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |role_data| {
let mut found = false;
role_data.pending_configures.retain(|&s| {
if s == serial {
found = true;
}
s > serial
});
if !found {
// client responded to a non-existing configure
// 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_wm_base::Error::InvalidSurfaceState as u32,
format!("Wrong configure serial: {}", serial),
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
.with_xdg_role(surface, |role| {
role.set_window_geometry(Rectangle { x, y, width, height })
})
.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(),
);
}
role_data.configured = true;
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
xdg_surface::Request::AckConfigure { serial } => {
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
.with_xdg_role(surface, |role| role.ack_configure(serial))
{
Ok(Ok(configure)) => configure,
Ok(Err(ConfigureError::SerialNotFound(serial))) => {
data.wm_base.as_ref().post_error(
xdg_wm_base::Error::InvalidSurfaceState as u32,
format!("wrong configure serial: {}", <u32>::from(serial)),
);
return;
}
Err(_) => {
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(),
);
return;
}
};
let mut user_impl = data.shell_data.user_impl.borrow_mut();
let serial = Serial::from(serial);
(&mut *user_impl)(XdgRequest::AckConfigure {
surface: data.wl_surface.clone(),
serial,
surface: surface.clone(),
configure,
});
}
_ => unreachable!(),
@ -368,38 +446,62 @@ pub(crate) struct ShellSurfaceUserData<R> {
}
// 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
R: Role<XdgSurfaceRole> + 'static,
F: FnOnce(&mut ToplevelState),
R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
{
let toplevel_data = toplevel
let data = toplevel
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.unwrap();
shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&toplevel_data.wl_surface, |data| match data.pending_state {
XdgSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data),
_ => unreachable!(),
.with_role_data::<XdgToplevelSurfaceRole, _, _>(&data.wl_surface, |role| {
let attributes = role
.as_mut()
.expect("xdg_toplevel exists but role has been destroyed?!");
f(attributes)
})
.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())
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
}
pub fn send_toplevel_configure<R>(resource: &xdg_toplevel::XdgToplevel, configure: ToplevelConfigure)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = resource
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.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>
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 len = states.len();
let cap = states.capacity();
@ -407,15 +509,13 @@ where
unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) }
};
let serial = configure.serial;
// Send the toplevel configure
resource.configure(width, height, states);
// Send the base xdg_surface configure event to mark
// The configure as finished
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> {
@ -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)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = toplevel
.as_ref()
@ -445,8 +545,9 @@ where
// all it done by the destructor
}
xdg_toplevel::Request::SetParent { parent } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.parent = parent.map(|toplevel_surface_parent| {
// Parent is not double buffered, we can set it directly
with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| {
data.parent = parent.map(|toplevel_surface_parent| {
toplevel_surface_parent
.as_ref()
.user_data()
@ -458,16 +559,19 @@ where
});
}
xdg_toplevel::Request::SetTitle { title } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.title = title;
// Title is not double buffered, we can set it directly
with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| {
data.title = Some(title);
});
}
xdg_toplevel::Request::SetAppId { app_id } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.app_id = app_id;
// AppId is not double buffered, we can set it directly
with_surface_toplevel_role_data(&data.shell_data, &toplevel, |role| {
role.app_id = Some(app_id);
});
}
xdg_toplevel::Request::ShowWindowMenu { seat, serial, x, y } => {
// This has to be handled by the compositor
let handle = make_toplevel_handle(&toplevel);
let serial = Serial::from(serial);
let mut user_impl = data.shell_data.user_impl.borrow_mut();
@ -479,6 +583,7 @@ where
});
}
xdg_toplevel::Request::Move { seat, serial } => {
// This has to be handled by the compositor
let handle = make_toplevel_handle(&toplevel);
let serial = Serial::from(serial);
let mut user_impl = data.shell_data.user_impl.borrow_mut();
@ -489,6 +594,7 @@ where
});
}
xdg_toplevel::Request::Resize { seat, serial, edges } => {
// This has to be handled by the compositor
let handle = make_toplevel_handle(&toplevel);
let mut user_impl = data.shell_data.user_impl.borrow_mut();
let serial = Serial::from(serial);
@ -500,13 +606,13 @@ where
});
}
xdg_toplevel::Request::SetMaxSize { width, height } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.max_size = (width, height);
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.max_size = Some((width, height));
});
}
xdg_toplevel::Request::SetMinSize { width, height } => {
with_surface_toplevel_data(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.min_size = (width, height);
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.min_size = Some((width, height));
});
}
xdg_toplevel::Request::SetMaximized => {
@ -533,6 +639,8 @@ where
(&mut *user_impl)(XdgRequest::UnFullscreen { surface: handle });
}
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 mut user_impl = data.shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::Minimize { surface: handle });
@ -543,7 +651,7 @@ where
fn destroy_toplevel<R>(toplevel: xdg_toplevel::XdgToplevel)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = toplevel
.as_ref()
@ -557,9 +665,8 @@ where
} else {
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::None;
data.configured = false;
.with_role_data(&data.wl_surface, |role_data| {
*role_data = XdgToplevelSurfaceRole::None;
})
.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)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = resource
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.unwrap();
let (x, y) = configure.position;
let (width, height) = configure.size;
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());
// 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> {
@ -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
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = popup
.as_ref()
@ -641,7 +746,7 @@ where
fn destroy_popup<R>(popup: xdg_popup::XdgPopup)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = popup
.as_ref()
@ -655,9 +760,8 @@ where
} else {
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::None;
data.configured = false;
.with_role_data(&data.wl_surface, |role_data| {
*role_data = XdgPopupSurfaceRole::None;
})
.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 crate::wayland::compositor::{roles::*, CompositorToken};
use crate::wayland::shell::xdg::{ConfigureError, PopupState};
use crate::wayland::Serial;
use wayland_protocols::{
unstable::xdg_shell::v6::server::{
@ -13,9 +14,9 @@ use wayland_server::{protocol::wl_surface, Filter, Main};
use crate::utils::Rectangle;
use super::{
make_shell_client_data, PopupConfigure, PopupKind, PopupState, PositionerState, ShellClient,
ShellClientData, ShellData, ToplevelConfigure, ToplevelKind, ToplevelState, XdgRequest,
XdgSurfacePendingState, XdgSurfaceRole,
make_shell_client_data, PopupConfigure, PopupKind, PositionerState, ShellClient, ShellClientData,
ShellData, ToplevelClientPending, ToplevelConfigure, ToplevelKind, XdgPopupSurfaceRole,
XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRole, XdgToplevelSurfaceRoleAttributes,
};
pub(crate) fn implement_shell<R>(
@ -23,7 +24,7 @@ pub(crate) fn implement_shell<R>(
shell_data: &ShellData<R>,
) -> zxdg_shell_v6::ZxdgShellV6
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.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)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = shell.as_ref().user_data().get::<ShellUserData<R>>().unwrap();
match request {
@ -69,24 +70,6 @@ where
implement_positioner(id);
}
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| {
xdg_surface_implementation::<R>(req, surface.deref().clone())
});
@ -98,10 +81,11 @@ where
});
}
zxdg_shell_v6::Request::Pong { serial } => {
let serial = Serial::from(serial);
let valid = {
let mut guard = data.client_data.lock().unwrap();
if guard.pending_ping == Serial::from(serial) {
guard.pending_ping = Serial::from(0);
if guard.pending_ping == Some(serial) {
guard.pending_ping = None;
true
} else {
false
@ -209,7 +193,7 @@ struct XdgSurfaceUserData<R> {
fn destroy_surface<R>(surface: zxdg_surface_v6::ZxdgSurfaceV6)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = surface
.as_ref()
@ -222,26 +206,38 @@ where
// disconnecting client), ignore the protocol check.
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
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |rdata| {
if let XdgSurfacePendingState::None = rdata.pending_state {
// all is good
} else {
.with_role_data(&data.wl_surface, |role: &mut XdgToplevelSurfaceRole| {
role.is_some()
})
.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(),
);
}
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
fn xdg_surface_implementation<R>(
request: zxdg_surface_v6::Request,
xdg_surface: zxdg_surface_v6::ZxdgSurfaceV6,
) where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + Role<XdgPopupSurfaceRole> + 'static,
{
let data = xdg_surface
.as_ref()
@ -253,18 +249,25 @@ fn xdg_surface_implementation<R>(
// all is handled by our destructor
}
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
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::Toplevel(ToplevelState {
parent: None,
title: String::new(),
app_id: String::new(),
min_size: (0, 0),
max_size: (0, 0),
});
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
.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(|toplevel, req, _data| {
toplevel_implementation::<R>(req, toplevel.deref().clone())
});
@ -296,22 +299,45 @@ fn xdg_surface_implementation<R>(
.as_ref()
.user_data()
.get::<RefCell<PositionerState>>()
.unwrap();
.unwrap()
.borrow()
.clone();
let parent_surface = {
let parent_data = parent
.as_ref()
.user_data()
.get::<XdgSurfaceUserData<R>>()
.unwrap();
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::Popup(PopupState {
parent: Some(parent_data.wl_surface.clone()),
positioner: positioner_data.borrow().clone(),
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()
});
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
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(|popup, req, _data| popup_implementation::<R>(req, popup.deref().clone()));
id.assign_destructor(Filter::new(|popup, _, _data| destroy_popup::<R>(popup)));
id.as_ref().user_data().set(|| ShellSurfaceUserData {
@ -333,40 +359,93 @@ fn xdg_surface_implementation<R>(
(&mut *user_impl)(XdgRequest::NewPopup { surface: handle });
}
zxdg_surface_v6::Request::SetWindowGeometry { x, y, width, height } => {
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.window_geometry = Some(Rectangle { x, y, width, height });
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
zxdg_surface_v6::Request::AckConfigure { serial } => {
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |role_data| {
let mut found = false;
role_data.pending_configures.retain(|&s| {
if s == serial {
found = true;
}
s > serial
});
if !found {
// client responded to a non-existing configure
// 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_shell_v6::Error::InvalidSurfaceState as u32,
format!("Wrong configure serial: {}", serial),
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
.with_xdg_role(surface, |role| {
role.set_window_geometry(Rectangle { x, y, width, height })
})
.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(),
);
}
role_data.configured = true;
})
.expect("xdg_surface exists but surface has not shell_surface role?!");
}
zxdg_surface_v6::Request::AckConfigure { serial } => {
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
.with_xdg_role(surface, |role| role.ack_configure(serial))
{
Ok(Ok(configure)) => configure,
Ok(Err(ConfigureError::SerialNotFound(serial))) => {
data.shell.as_ref().post_error(
zxdg_shell_v6::Error::InvalidSurfaceState as u32,
format!("wrong configure serial: {}", <u32>::from(serial)),
);
return;
}
Err(_) => {
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(),
);
return;
}
};
let mut user_impl = data.shell_data.user_impl.borrow_mut();
let serial = Serial::from(serial);
(&mut *user_impl)(XdgRequest::AckConfigure {
surface: data.wl_surface.clone(),
serial,
surface: surface.clone(),
configure,
});
}
_ => unreachable!(),
@ -385,38 +464,62 @@ pub struct ShellSurfaceUserData<R> {
}
// 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
R: Role<XdgSurfaceRole> + 'static,
F: FnOnce(&mut ToplevelState),
R: Role<XdgToplevelSurfaceRole> + 'static,
F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
{
let data = toplevel
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.unwrap();
data.shell_data
shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| match data.pending_state {
XdgSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data),
_ => unreachable!(),
.with_role_data::<XdgToplevelSurfaceRole, _, _>(&data.wl_surface, |role| {
let attributes = role
.as_mut()
.expect("xdg_toplevel exists but role has been destroyed?!");
f(attributes)
})
.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())
})
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
}
pub fn send_toplevel_configure<R>(resource: &zxdg_toplevel_v6::ZxdgToplevelV6, configure: ToplevelConfigure)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = resource
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.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>
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 len = states.len();
let cap = states.capacity();
@ -424,15 +527,13 @@ where
unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) }
};
let serial = configure.serial;
// Send the toplevel configure
resource.configure(width, height, states);
// Send the base xdg_surface configure event to mark
// The configure as finished
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>(
@ -452,7 +553,7 @@ fn make_toplevel_handle<R: 'static>(
fn toplevel_implementation<R>(request: zxdg_toplevel_v6::Request, toplevel: zxdg_toplevel_v6::ZxdgToplevelV6)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = toplevel
.as_ref()
@ -464,8 +565,8 @@ where
// all it done by the destructor
}
zxdg_toplevel_v6::Request::SetParent { parent } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| {
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| {
let parent_data = toplevel_surface_parent
.as_ref()
.user_data()
@ -476,13 +577,15 @@ where
});
}
zxdg_toplevel_v6::Request::SetTitle { title } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| {
toplevel_data.title = title;
// Title is not double buffered, we can set it directly
with_surface_toplevel_role_data(&data.shell_data, &toplevel, |data| {
data.title = Some(title);
});
}
zxdg_toplevel_v6::Request::SetAppId { app_id } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| {
toplevel_data.app_id = app_id;
// AppId is not double buffered, we can set it directly
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 } => {
@ -520,13 +623,13 @@ where
});
}
zxdg_toplevel_v6::Request::SetMaxSize { width, height } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| {
toplevel_data.max_size = (width, height);
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.max_size = Some((width, height));
});
}
zxdg_toplevel_v6::Request::SetMinSize { width, height } => {
with_surface_toplevel_data::<R, _>(&toplevel, |toplevel_data| {
toplevel_data.min_size = (width, height);
with_surface_toplevel_client_pending(&data.shell_data, &toplevel, |toplevel_data| {
toplevel_data.min_size = Some((width, height));
});
}
zxdg_toplevel_v6::Request::SetMaximized => {
@ -553,6 +656,8 @@ where
(&mut *user_impl)(XdgRequest::UnFullscreen { surface: handle });
}
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 mut user_impl = data.shell_data.user_impl.borrow_mut();
(&mut *user_impl)(XdgRequest::Minimize { surface: handle });
@ -563,7 +668,7 @@ where
fn destroy_toplevel<R>(toplevel: zxdg_toplevel_v6::ZxdgToplevelV6)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgToplevelSurfaceRole> + 'static,
{
let data = toplevel
.as_ref()
@ -577,9 +682,8 @@ where
} else {
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::None;
data.configured = false;
.with_role_data(&data.wl_surface, |role_data| {
*role_data = XdgToplevelSurfaceRole::None;
})
.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)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = resource
.as_ref()
.user_data()
.get::<ShellSurfaceUserData<R>>()
.unwrap();
let (x, y) = configure.position;
let (width, height) = configure.size;
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());
// 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> {
@ -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)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = popup
.as_ref()
@ -661,7 +763,7 @@ where
fn destroy_popup<R>(popup: zxdg_popup_v6::ZxdgPopupV6)
where
R: Role<XdgSurfaceRole> + 'static,
R: Role<XdgPopupSurfaceRole> + 'static,
{
let data = popup
.as_ref()
@ -675,9 +777,8 @@ where
} else {
data.shell_data
.compositor_token
.with_role_data::<XdgSurfaceRole, _, _>(&data.wl_surface, |data| {
data.pending_state = XdgSurfacePendingState::None;
data.configured = false;
.with_role_data(&data.wl_surface, |role_data| {
*role_data = XdgPopupSurfaceRole::None;
})
.expect("xdg_popup exists but surface has not shell_surface role?!");
}