use std::sync::atomic::Ordering; use crate::{ utils::Serial, wayland::{ compositor, shell::{is_valid_parent, xdg::XdgToplevelSurfaceData}, }, }; use wayland_protocols::xdg::shell::server::xdg_toplevel::{self, XdgToplevel}; use wayland_server::{ backend::ClientId, protocol::wl_surface, DataInit, Dispatch, DisplayHandle, Resource, WEnum, }; use super::{ SurfaceCachedState, ToplevelConfigure, XdgShellHandler, XdgShellState, XdgShellSurfaceUserData, XdgSurfaceUserData, XdgToplevelSurfaceRoleAttributes, }; impl Dispatch for XdgShellState where D: Dispatch, D: XdgShellHandler, D: 'static, { fn request( state: &mut D, _client: &wayland_server::Client, toplevel: &XdgToplevel, request: xdg_toplevel::Request, data: &XdgShellSurfaceUserData, _dh: &DisplayHandle, _data_init: &mut DataInit<'_, D>, ) { match request { xdg_toplevel::Request::Destroy => { if let Some(surface_data) = data.xdg_surface.data::() { surface_data.has_active_role.store(false, Ordering::Release); } } xdg_toplevel::Request::SetParent { parent } => { let parent_surface = parent.map(|toplevel_surface_parent| { toplevel_surface_parent .data::() .unwrap() .wl_surface .clone() }); let old_parent = get_parent(toplevel); let changed = old_parent != parent_surface; // Parent is not double buffered, we can set it directly if !set_parent(toplevel, parent_surface) { toplevel.post_error(xdg_toplevel::Error::InvalidParent, "invalid parent toplevel"); } if changed { let handle = make_toplevel_handle(toplevel); XdgShellHandler::parent_changed(state, handle); } } xdg_toplevel::Request::SetTitle { title } => { // Title is not double buffered, we can set it directly let changed = with_surface_toplevel_role_data(toplevel, |role| { if role.title.as_ref() != Some(&title) { role.title = Some(title); true } else { false } }); if changed { let handle = make_toplevel_handle(toplevel); XdgShellHandler::title_changed(state, handle); } } xdg_toplevel::Request::SetAppId { app_id } => { // AppId is not double buffered, we can set it directly let changed = with_surface_toplevel_role_data(toplevel, |role| { if role.app_id.as_ref() != Some(&app_id) { role.app_id = Some(app_id); true } else { false } }); if changed { let handle = make_toplevel_handle(toplevel); XdgShellHandler::app_id_changed(state, handle); } } 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); XdgShellHandler::show_window_menu(state, handle, seat, serial, (x, y).into()); } 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); XdgShellHandler::move_request(state, handle, seat, serial); } xdg_toplevel::Request::Resize { seat, serial, edges } => { if let WEnum::Value(edges) = edges { // This has to be handled by the compositor let handle = make_toplevel_handle(toplevel); let serial = Serial::from(serial); XdgShellHandler::resize_request(state, handle, seat, serial, edges); } } xdg_toplevel::Request::SetMaxSize { width, height } => { with_toplevel_pending_state(data, |toplevel_data| { toplevel_data.max_size = (width, height).into(); }); } xdg_toplevel::Request::SetMinSize { width, height } => { with_toplevel_pending_state(data, |toplevel_data| { toplevel_data.min_size = (width, height).into(); }); } xdg_toplevel::Request::SetMaximized => { let handle = make_toplevel_handle(toplevel); XdgShellHandler::maximize_request(state, handle); } xdg_toplevel::Request::UnsetMaximized => { let handle = make_toplevel_handle(toplevel); XdgShellHandler::unmaximize_request(state, handle); } xdg_toplevel::Request::SetFullscreen { output } => { let handle = make_toplevel_handle(toplevel); XdgShellHandler::fullscreen_request(state, handle, output); } xdg_toplevel::Request::UnsetFullscreen => { let handle = make_toplevel_handle(toplevel); XdgShellHandler::unfullscreen_request(state, 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); XdgShellHandler::minimize_request(state, handle); } _ => unreachable!(), } } fn destroyed( state: &mut D, _client_id: ClientId, xdg_toplevel: &XdgToplevel, data: &XdgShellSurfaceUserData, ) { data.alive_tracker.destroy_notify(); data.decoration.lock().unwrap().take(); if let Some(index) = state .xdg_shell_state() .known_toplevels .iter() .position(|top| top.shell_surface.id() == xdg_toplevel.id()) { let toplevel = state.xdg_shell_state().known_toplevels.remove(index); let surface = toplevel.wl_surface().clone(); XdgShellHandler::toplevel_destroyed(state, toplevel); compositor::with_states(&surface, |states| { *states .data_map .get::() .unwrap() .lock() .unwrap() = Default::default(); let mut guard = states.cached_state.get::(); *guard.pending() = Default::default(); *guard.current() = Default::default(); }) } } } // Utility functions allowing to factor out a lot of the upcoming logic fn with_surface_toplevel_role_data(toplevel: &xdg_toplevel::XdgToplevel, f: F) -> T where F: FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T, { let data = toplevel.data::().unwrap(); compositor::with_states(&data.wl_surface, |states| { f(&mut states .data_map .get::() .unwrap() .lock() .unwrap()) }) } pub(super) fn make_toplevel_handle( resource: &xdg_toplevel::XdgToplevel, ) -> crate::wayland::shell::xdg::ToplevelSurface { let data = resource.data::().unwrap(); crate::wayland::shell::xdg::ToplevelSurface { wl_surface: data.wl_surface.clone(), shell_surface: resource.clone(), } } pub fn get_parent(toplevel: &xdg_toplevel::XdgToplevel) -> Option { with_surface_toplevel_role_data(toplevel, |data| data.parent.clone()) } /// Sets the parent of the specified toplevel surface and returns whether the parent was successfully set. /// /// The parent must be a toplevel surface. /// /// The parent of a surface is not double buffered and therefore may be set directly. /// /// If the parent is `None`, the parent-child relationship is removed. pub fn set_parent(toplevel: &xdg_toplevel::XdgToplevel, parent: Option) -> bool { if let Some(parent) = &parent { let data = toplevel.data::().unwrap(); if !is_valid_parent(&data.wl_surface, parent) { return false; } } with_surface_toplevel_role_data(toplevel, |data| { data.parent = parent; }); true } fn with_toplevel_pending_state(data: &XdgShellSurfaceUserData, f: F) -> T where F: FnOnce(&mut SurfaceCachedState) -> T, { compositor::with_states(&data.wl_surface, |states| { f(states.cached_state.get::().pending()) }) } pub fn send_toplevel_configure( resource: &xdg_toplevel::XdgToplevel, configure: ToplevelConfigure, send_bounds: bool, send_capabilities: bool, ) { let data = resource.data::().unwrap(); let (width, height) = configure.state.size.unwrap_or_default().into(); // convert the Vec (which is really a Vec) into Vec let states = { let mut states: Vec = configure.state.states.into_filtered_states(resource.version()); 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; // send bounds if requested if send_bounds && resource.version() >= xdg_toplevel::EVT_CONFIGURE_BOUNDS_SINCE { let bounds = configure.state.bounds.unwrap_or_default(); resource.configure_bounds(bounds.w, bounds.h); } // send the capabilities if requested if send_capabilities && resource.version() >= xdg_toplevel::EVT_WM_CAPABILITIES_SINCE { let mut capabilities = configure .state .capabilities .capabilities() .copied() .collect::>(); let capabilities = { let ptr = capabilities.as_mut_ptr(); let len = capabilities.len(); let cap = capabilities.capacity(); ::std::mem::forget(capabilities); unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } }; resource.wm_capabilities(capabilities); } // 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()); }