use super::{make_shell_client_data, PopupConfigure, PopupKind, PopupState, PositionerState, ShellClient, ShellClientData, ShellImplementation, ToplevelConfigure, ToplevelKind, ToplevelState, XdgRequest, XdgSurfacePendingState, XdgSurfaceRole}; use std::sync::Mutex; use utils::Rectangle; use wayland::compositor::CompositorToken; use wayland::compositor::roles::*; use wayland_protocols::xdg_shell::server::{xdg_positioner, xdg_toplevel}; use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6, zxdg_positioner_v6, zxdg_shell_v6, zxdg_surface_v6, zxdg_toplevel_v6}; use wayland_server::{LoopToken, NewResource, Resource}; use wayland_server::commons::{downcast_impl, Implementation}; use wayland_server::protocol::wl_surface; pub(crate) fn implement_shell( shell: NewResource, implem: &ShellImplementation, ) -> Resource where U: 'static, R: Role + 'static, SD: Default + 'static, { let shell = shell.implement_nonsend( implem.clone(), Some(|shell, _| destroy_shell::(&shell)), &implem.loop_token, ); shell.set_user_data(Box::into_raw(Box::new(Mutex::new(make_shell_client_data::()))) as *mut _); let mut user_impl = implem.user_impl.borrow_mut(); user_impl.receive( XdgRequest::NewClient { client: make_shell_client(&shell), }, (), ); shell } /* * xdg_shell */ pub(crate) type ShellUserData = Mutex>; fn destroy_shell(shell: &Resource) { let ptr = shell.get_user_data(); shell.set_user_data(::std::ptr::null_mut()); let data = unsafe { Box::from_raw(ptr as *mut ShellUserData) }; // explicit call to drop to not forget what we're doing here ::std::mem::drop(data); } pub(crate) fn make_shell_client(resource: &Resource) -> ShellClient { ShellClient { kind: super::ShellClientKind::ZxdgV6(resource.clone()), _data: ::std::marker::PhantomData, } } impl Implementation, zxdg_shell_v6::Request> for ShellImplementation where U: 'static, R: Role + 'static, SD: 'static, { fn receive(&mut self, request: zxdg_shell_v6::Request, shell: Resource) { match request { zxdg_shell_v6::Request::Destroy => { // all is handled by destructor } zxdg_shell_v6::Request::CreatePositioner { id } => { implement_positioner(id, &self.loop_token); } 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 self.compositor_token .give_role_with(&surface, role_data) .is_err() { shell.post_error( zxdg_shell_v6::Error::Role as u32, "Surface already has a role.".into(), ); return; } let xdg_surface = id.implement_nonsend( self.clone(), Some(destroy_surface::), &self.loop_token, ); xdg_surface .set_user_data(Box::into_raw(Box::new((surface.clone(), shell.clone()))) as *mut _); } zxdg_shell_v6::Request::Pong { serial } => { let valid = { let mutex = unsafe { &*(shell.get_user_data() as *mut ShellUserData) }; let mut guard = mutex.lock().unwrap(); if guard.pending_ping == serial { guard.pending_ping = 0; true } else { false } }; if valid { let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive( XdgRequest::ClientPong { client: make_shell_client(&shell), }, (), ); } } } } } /* * xdg_positioner */ fn destroy_positioner(positioner: &Resource) { let ptr = positioner.get_user_data(); positioner.set_user_data(::std::ptr::null_mut()); // drop the PositionerState let surface = unsafe { Box::from_raw(ptr as *mut PositionerState) }; // explicit call to drop to not forget what we're doing here ::std::mem::drop(surface); } fn implement_positioner( positioner: NewResource, token: &LoopToken, ) -> Resource { let positioner = positioner.implement_nonsend( |request, positioner: Resource<_>| { let ptr = positioner.get_user_data(); let state = unsafe { &mut *(ptr as *mut PositionerState) }; match request { zxdg_positioner_v6::Request::Destroy => { // handled by destructor } zxdg_positioner_v6::Request::SetSize { width, height } => { if width < 1 || height < 1 { positioner.post_error( zxdg_positioner_v6::Error::InvalidInput as u32, "Invalid size for positioner.".into(), ); } else { state.rect_size = (width, height); } } zxdg_positioner_v6::Request::SetAnchorRect { x, y, width, height, } => { if width < 1 || height < 1 { positioner.post_error( zxdg_positioner_v6::Error::InvalidInput as u32, "Invalid size for positioner's anchor rectangle.".into(), ); } else { state.anchor_rect = Rectangle { x, y, width, height, }; } } zxdg_positioner_v6::Request::SetAnchor { anchor } => { if let Some(anchor) = zxdg_anchor_to_xdg(anchor) { state.anchor_edges = anchor; } else { positioner.post_error( zxdg_positioner_v6::Error::InvalidInput as u32, "Invalid anchor for positioner.".into(), ); } } zxdg_positioner_v6::Request::SetGravity { gravity } => { if let Some(gravity) = zxdg_gravity_to_xdg(gravity) { state.gravity = gravity; } else { positioner.post_error( zxdg_positioner_v6::Error::InvalidInput as u32, "Invalid gravity for positioner.".into(), ); } } zxdg_positioner_v6::Request::SetConstraintAdjustment { constraint_adjustment, } => { let constraint_adjustment = zxdg_positioner_v6::ConstraintAdjustment::from_bits_truncate(constraint_adjustment); state.constraint_adjustment = zxdg_constraints_adg_to_xdg(constraint_adjustment); } zxdg_positioner_v6::Request::SetOffset { x, y } => { state.offset = (x, y); } } }, Some(|positioner, _| destroy_positioner(&positioner)), token, ); let data = PositionerState::new(); positioner.set_user_data(Box::into_raw(Box::new(data)) as *mut _); positioner } /* * xdg_surface */ type XdgSurfaceUserData = ( Resource, Resource, ); fn destroy_surface( surface: Resource, implem: Box, zxdg_surface_v6::Request>>, ) where U: 'static, R: Role + 'static, SD: 'static, { let implem: ShellImplementation = *downcast_impl(implem).unwrap_or_else(|_| unreachable!()); let ptr = surface.get_user_data(); surface.set_user_data(::std::ptr::null_mut()); // take back ownership of the userdata let data = unsafe { Box::from_raw(ptr as *mut XdgSurfaceUserData) }; if !data.0.is_alive() { // the wl_surface is destroyed, this means the client is not // trying to change the role but it's a cleanup (possibly a // disconnecting client), ignore the protocol check. return; } implem .compositor_token .with_role_data::(&data.0, |rdata| { if let XdgSurfacePendingState::None = rdata.pending_state { // all is good } else { data.1.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?!"); } impl Implementation, zxdg_surface_v6::Request> for ShellImplementation where U: 'static, R: Role + 'static, SD: 'static, { fn receive( &mut self, request: zxdg_surface_v6::Request, xdg_surface: Resource, ) { let ptr = xdg_surface.get_user_data(); let &(ref surface, ref shell) = unsafe { &*(ptr as *mut XdgSurfaceUserData) }; match request { zxdg_surface_v6::Request::Destroy => { // all is handled by our destructor } zxdg_surface_v6::Request::GetToplevel { id } => { self.compositor_token .with_role_data::(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?!"); let toplevel = id.implement_nonsend( self.clone(), Some(destroy_toplevel::), &self.loop_token, ); toplevel.set_user_data(Box::into_raw(Box::new(( surface.clone(), shell.clone(), xdg_surface.clone(), ))) as *mut _); self.shell_state .lock() .unwrap() .known_toplevels .push(make_toplevel_handle(self.compositor_token, &toplevel)); let handle = make_toplevel_handle(self.compositor_token, &toplevel); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive(XdgRequest::NewToplevel { surface: handle }, ()); } zxdg_surface_v6::Request::GetPopup { id, parent, positioner, } => { let positioner_data = unsafe { &*(positioner.get_user_data() as *const PositionerState) }; let parent_ptr = parent.get_user_data(); let &(ref parent_surface, _) = unsafe { &*(parent_ptr as *mut XdgSurfaceUserData) }; self.compositor_token .with_role_data::(surface, |data| { data.pending_state = XdgSurfacePendingState::Popup(PopupState { parent: Some(parent_surface.clone()), positioner: positioner_data.clone(), }); }) .expect("xdg_surface exists but surface has not shell_surface role?!"); let popup = id.implement_nonsend( self.clone(), Some(destroy_popup::), &self.loop_token, ); popup.set_user_data(Box::into_raw(Box::new(( surface.clone(), shell.clone(), xdg_surface.clone(), ))) as *mut _); self.shell_state .lock() .unwrap() .known_popups .push(make_popup_handle(self.compositor_token, &popup)); let handle = make_popup_handle(self.compositor_token, &popup); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive(XdgRequest::NewPopup { surface: handle }, ()); } zxdg_surface_v6::Request::SetWindowGeometry { x, y, width, height, } => { self.compositor_token .with_role_data::(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 } => { self.compositor_token .with_role_data::(surface, |data| { let mut found = false; data.pending_configures.retain(|&s| { if s == serial { found = true; } s > serial }); if !found { // client responded to a non-existing configure shell.post_error( zxdg_shell_v6::Error::InvalidSurfaceState as u32, format!("Wrong configure serial: {}", serial), ); } data.configured = true; }) .expect("xdg_surface exists but surface has not shell_surface role?!"); } } } } /* * xdg_toplevel */ pub type ShellSurfaceUserData = ( Resource, Resource, Resource, ); // Utility functions allowing to factor out a lot of the upcoming logic fn with_surface_toplevel_data( implem: &ShellImplementation, toplevel: &Resource, f: F, ) where U: 'static, R: Role + 'static, SD: 'static, F: FnOnce(&mut ToplevelState), { let ptr = toplevel.get_user_data(); let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; implem .compositor_token .with_role_data::(surface, |data| match data.pending_state { XdgSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data), _ => unreachable!(), }) .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } pub fn send_toplevel_configure( token: CompositorToken, resource: &Resource, configure: ToplevelConfigure, ) where U: 'static, R: Role + 'static, { let &(ref surface, _, ref shell_surface) = unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; let (width, height) = configure.size.unwrap_or((0, 0)); // convert the Vec (which is really a Vec) into Vec let states = { let mut states = configure.states; let ptr = states.as_mut_ptr(); let len = states.len(); let cap = states.capacity(); ::std::mem::forget(states); unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } }; let serial = configure.serial; resource.send(zxdg_toplevel_v6::Event::Configure { width, height, states, }); shell_surface.send(zxdg_surface_v6::Event::Configure { serial }); // Add the configure as pending token .with_role_data::(surface, |data| data.pending_configures.push(serial)) .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } fn make_toplevel_handle( token: CompositorToken, resource: &Resource, ) -> super::ToplevelSurface { let ptr = resource.get_user_data(); let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; super::ToplevelSurface { wl_surface: wl_surface.clone(), shell_surface: ToplevelKind::ZxdgV6(resource.clone()), token: token, _shell_data: ::std::marker::PhantomData, } } impl Implementation, zxdg_toplevel_v6::Request> for ShellImplementation where U: 'static, R: Role + 'static, SD: 'static, { fn receive( &mut self, request: zxdg_toplevel_v6::Request, toplevel: Resource, ) { match request { zxdg_toplevel_v6::Request::Destroy => { // all it done by the destructor } zxdg_toplevel_v6::Request::SetParent { parent } => { with_surface_toplevel_data(self, &toplevel, |toplevel_data| { toplevel_data.parent = parent.map(|toplevel_surface_parent| { let parent_ptr = toplevel_surface_parent.get_user_data(); let &(ref parent_surface, _, _) = unsafe { &*(parent_ptr as *mut ShellSurfaceUserData) }; parent_surface.clone() }) }); } zxdg_toplevel_v6::Request::SetTitle { title } => { with_surface_toplevel_data(self, &toplevel, |toplevel_data| { toplevel_data.title = title; }); } zxdg_toplevel_v6::Request::SetAppId { app_id } => { with_surface_toplevel_data(self, &toplevel, |toplevel_data| { toplevel_data.app_id = app_id; }); } zxdg_toplevel_v6::Request::ShowWindowMenu { seat, serial, x, y } => { let handle = make_toplevel_handle(self.compositor_token, &toplevel); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive( XdgRequest::ShowWindowMenu { surface: handle, seat, serial, location: (x, y), }, (), ); } zxdg_toplevel_v6::Request::Move { seat, serial } => { let handle = make_toplevel_handle(self.compositor_token, &toplevel); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive( XdgRequest::Move { surface: handle, seat, serial, }, (), ); } zxdg_toplevel_v6::Request::Resize { seat, serial, edges, } => { let edges = zxdg_toplevel_v6::ResizeEdge::from_raw(edges) .unwrap_or(zxdg_toplevel_v6::ResizeEdge::None); let handle = make_toplevel_handle(self.compositor_token, &toplevel); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive( XdgRequest::Resize { surface: handle, seat, serial, edges: zxdg_edges_to_xdg(edges), }, (), ); } zxdg_toplevel_v6::Request::SetMaxSize { width, height } => { with_surface_toplevel_data(self, &toplevel, |toplevel_data| { toplevel_data.max_size = (width, height); }); } zxdg_toplevel_v6::Request::SetMinSize { width, height } => { with_surface_toplevel_data(self, &toplevel, |toplevel_data| { toplevel_data.max_size = (width, height); }); } zxdg_toplevel_v6::Request::SetMaximized => { let handle = make_toplevel_handle(self.compositor_token, &toplevel); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive(XdgRequest::Maximize { surface: handle }, ()); } zxdg_toplevel_v6::Request::UnsetMaximized => { let handle = make_toplevel_handle(self.compositor_token, &toplevel); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive(XdgRequest::UnMaximize { surface: handle }, ()); } zxdg_toplevel_v6::Request::SetFullscreen { output } => { let handle = make_toplevel_handle(self.compositor_token, &toplevel); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive( XdgRequest::Fullscreen { surface: handle, output, }, (), ); } zxdg_toplevel_v6::Request::UnsetFullscreen => { let handle = make_toplevel_handle(self.compositor_token, &toplevel); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive(XdgRequest::UnFullscreen { surface: handle }, ()); } zxdg_toplevel_v6::Request::SetMinimized => { let handle = make_toplevel_handle(self.compositor_token, &toplevel); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive(XdgRequest::Minimize { surface: handle }, ()); } } } } fn destroy_toplevel( toplevel: Resource, implem: Box, zxdg_toplevel_v6::Request>>, ) where U: 'static, R: Role + 'static, SD: 'static, { let implem: ShellImplementation = *downcast_impl(implem).unwrap_or_else(|_| unreachable!()); let ptr = toplevel.get_user_data(); toplevel.set_user_data(::std::ptr::null_mut()); // take back ownership of the userdata let data = *unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; if !data.0.is_alive() { // the wl_surface is destroyed, this means the client is not // trying to change the role but it's a cleanup (possibly a // disconnecting client), ignore the protocol check. return; } else { implem .compositor_token .with_role_data::(&data.0, |data| { data.pending_state = XdgSurfacePendingState::None; data.configured = false; }) .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } // remove this surface from the known ones (as well as any leftover dead surface) implem .shell_state .lock() .unwrap() .known_toplevels .retain(|other| other.alive()); } /* * xdg_popup */ pub(crate) fn send_popup_configure( token: CompositorToken, resource: &Resource, configure: PopupConfigure, ) where U: 'static, R: Role + 'static, { let &(ref surface, _, ref shell_surface) = unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) }; let (x, y) = configure.position; let (width, height) = configure.size; let serial = configure.serial; resource.send(zxdg_popup_v6::Event::Configure { x, y, width, height, }); shell_surface.send(zxdg_surface_v6::Event::Configure { serial }); // Add the configure as pending token .with_role_data::(surface, |data| data.pending_configures.push(serial)) .expect("xdg_toplevel exists but surface has not shell_surface role?!"); } fn make_popup_handle( token: CompositorToken, resource: &Resource, ) -> super::PopupSurface { let ptr = resource.get_user_data(); let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) }; super::PopupSurface { wl_surface: wl_surface.clone(), shell_surface: PopupKind::ZxdgV6(resource.clone()), token: token, _shell_data: ::std::marker::PhantomData, } } impl Implementation, zxdg_popup_v6::Request> for ShellImplementation where U: 'static, R: Role + 'static, SD: 'static, { fn receive(&mut self, request: zxdg_popup_v6::Request, popup: Resource) { match request { zxdg_popup_v6::Request::Destroy => { // all is handled by our destructor } zxdg_popup_v6::Request::Grab { seat, serial } => { let handle = make_popup_handle(self.compositor_token, &popup); let mut user_impl = self.user_impl.borrow_mut(); user_impl.receive( XdgRequest::Grab { surface: handle, seat, serial, }, (), ); } } } } fn destroy_popup( popup: Resource, implem: Box, zxdg_popup_v6::Request>>, ) where U: 'static, R: Role + 'static, SD: 'static, { let implem: ShellImplementation = *downcast_impl(implem).unwrap_or_else(|_| unreachable!()); let ptr = popup.get_user_data(); popup.set_user_data(::std::ptr::null_mut()); // take back ownership of the userdata let data = *unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) }; if !data.0.is_alive() { // the wl_surface is destroyed, this means the client is not // trying to change the role but it's a cleanup (possibly a // disconnecting client), ignore the protocol check. return; } else { implem .compositor_token .with_role_data::(&data.0, |data| { data.pending_state = XdgSurfacePendingState::None; data.configured = false; }) .expect("xdg_popup exists but surface has not shell_surface role?!"); } // remove this surface from the known ones (as well as any leftover dead surface) implem .shell_state .lock() .unwrap() .known_popups .retain(|other| other.alive()); } fn zxdg_edges_to_xdg(e: zxdg_toplevel_v6::ResizeEdge) -> xdg_toplevel::ResizeEdge { match e { zxdg_toplevel_v6::ResizeEdge::None => xdg_toplevel::ResizeEdge::None, zxdg_toplevel_v6::ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top, zxdg_toplevel_v6::ResizeEdge::Bottom => xdg_toplevel::ResizeEdge::Bottom, zxdg_toplevel_v6::ResizeEdge::Left => xdg_toplevel::ResizeEdge::Left, zxdg_toplevel_v6::ResizeEdge::Right => xdg_toplevel::ResizeEdge::Right, zxdg_toplevel_v6::ResizeEdge::TopLeft => xdg_toplevel::ResizeEdge::TopLeft, zxdg_toplevel_v6::ResizeEdge::TopRight => xdg_toplevel::ResizeEdge::TopRight, zxdg_toplevel_v6::ResizeEdge::BottomLeft => xdg_toplevel::ResizeEdge::BottomLeft, zxdg_toplevel_v6::ResizeEdge::BottomRight => xdg_toplevel::ResizeEdge::BottomRight, } } fn zxdg_constraints_adg_to_xdg( c: zxdg_positioner_v6::ConstraintAdjustment, ) -> xdg_positioner::ConstraintAdjustment { xdg_positioner::ConstraintAdjustment::from_bits_truncate(c.bits()) } fn zxdg_gravity_to_xdg(c: zxdg_positioner_v6::Gravity) -> Option { match c.bits() { 0b0000 => Some(xdg_positioner::Gravity::None), 0b0001 => Some(xdg_positioner::Gravity::Top), 0b0010 => Some(xdg_positioner::Gravity::Bottom), 0b0100 => Some(xdg_positioner::Gravity::Left), 0b0101 => Some(xdg_positioner::Gravity::TopLeft), 0b0110 => Some(xdg_positioner::Gravity::BottomLeft), 0b1000 => Some(xdg_positioner::Gravity::Right), 0b1001 => Some(xdg_positioner::Gravity::TopRight), 0b1010 => Some(xdg_positioner::Gravity::BottomRight), _ => None, } } fn zxdg_anchor_to_xdg(c: zxdg_positioner_v6::Anchor) -> Option { match c.bits() { 0b0000 => Some(xdg_positioner::Anchor::None), 0b0001 => Some(xdg_positioner::Anchor::Top), 0b0010 => Some(xdg_positioner::Anchor::Bottom), 0b0100 => Some(xdg_positioner::Anchor::Left), 0b0101 => Some(xdg_positioner::Anchor::TopLeft), 0b0110 => Some(xdg_positioner::Anchor::BottomLeft), 0b1000 => Some(xdg_positioner::Anchor::Right), 0b1001 => Some(xdg_positioner::Anchor::TopRight), 0b1010 => Some(xdg_positioner::Anchor::BottomRight), _ => None, } }