use smithay::{ desktop::{ PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, Space, Window, WindowSurfaceType, find_popup_root_surface, get_popup_toplevel_coords, }, input::{ Seat, pointer::{Focus, GrabStartData}, }, reexports::wayland_protocols::xdg::{ decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode, shell::server::xdg_toplevel::State as ToplevelState, }, reexports::wayland_server::{ Resource, protocol::{wl_seat, wl_surface::WlSurface}, }, utils::Serial, wayland::{ compositor::with_states, shell::xdg::decoration::XdgDecorationHandler, shell::xdg::{ PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, XdgToplevelSurfaceData, }, }, }; use crate::{ Ferese, grabs::{MoveSurfaceGrab, ResizeEdge, ResizeSurfaceGrab, handle_resize_commit}, }; impl XdgShellHandler for Ferese { fn xdg_shell_state(&mut self) -> &mut XdgShellState { &mut self.xdg_shell_state } fn new_toplevel(&mut self, surface: ToplevelSurface) { let window = Window::new_wayland_window(surface); // app_id, title and parent arrive after get_toplevel. Keep the surface // discoverable, but do not mutate/focus the layout until its initial // commit supplies the metadata needed to resolve placement rules. self.space.map_element(window, (0, 0), false); } fn parent_changed(&mut self, surface: ToplevelSurface) { let Some(parent) = surface.parent() else { return; }; let parent = self.window_ids.iter().find_map(|(window, id)| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == &parent) .then_some(*id) }); let child = self .space .elements() .find(|window| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) }) .cloned(); if let (Some(parent), Some(child)) = (parent, child) { self.make_window_transient(&child, parent); } } fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { let Some(window) = self .space .elements() .find(|window| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) }) .cloned() else { return; }; self.remove_tiled_window(&window); } fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) { self.unconstrain_popup(&surface); if let Err(error) = self.popups.track_popup(PopupKind::Xdg(surface)) { tracing::warn!(?error, "failed to track xdg popup"); } } fn reposition_request( &mut self, surface: PopupSurface, positioner: PositionerState, token: u32, ) { surface.with_pending_state(|state| { state.geometry = positioner.get_geometry(); state.positioner = positioner; }); self.unconstrain_popup(&surface); surface.send_repositioned(token); } fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) { if self.session_lock.active { return; } let Some(seat) = Seat::from_resource(&seat) else { return; }; let Some(start_data) = check_grab(&seat, surface.wl_surface(), serial) else { return; }; let Some(pointer) = seat.get_pointer() else { return; }; let Some(window) = self .space .elements() .find(|window| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) }) .cloned() else { return; }; let Some(initial_rect) = self.visual_rect_for_window(&window) else { return; }; if !self.is_floating_window(&window) { return; } pointer.set_grab( self, MoveSurfaceGrab { start_data, window, initial_location: initial_rect.loc, initial_size: initial_rect.size, finished: false, }, serial, Focus::Clear, ); } fn resize_request( &mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial, edges: smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, ) { if self.session_lock.active { return; } let Some(seat) = Seat::from_resource(&seat) else { return; }; let Some(start_data) = check_grab(&seat, surface.wl_surface(), serial) else { return; }; let Some(pointer) = seat.get_pointer() else { return; }; let Some(window) = self .space .elements() .find(|window| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) }) .cloned() else { return; }; let Some(rect) = self.visual_rect_for_window(&window) else { return; }; if !self.is_floating_window(&window) { return; } pointer.set_grab( self, ResizeSurfaceGrab::new(start_data, window, ResizeEdge::from(edges), rect), serial, Focus::Clear, ); } fn maximize_request(&mut self, surface: ToplevelSurface) { if let Some(id) = self.window_ids.iter().find_map(|(window, id)| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) .then_some(*id) }) { self.set_window_maximized(id, true); } else { surface.with_pending_state(|state| state.states.set(ToplevelState::Maximized)); } } fn unmaximize_request(&mut self, surface: ToplevelSurface) { if let Some(id) = self.window_ids.iter().find_map(|(window, id)| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) .then_some(*id) }) { self.set_window_maximized(id, false); } else { surface.with_pending_state(|state| state.states.unset(ToplevelState::Maximized)); } } fn fullscreen_request( &mut self, surface: ToplevelSurface, _output: Option, ) { if let Some(window) = self.window_ids.iter().find_map(|(window, id)| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) .then_some(*id) }) { self.set_window_fullscreen(window, true); } else { surface.with_pending_state(|state| state.states.set(ToplevelState::Fullscreen)); } } fn unfullscreen_request(&mut self, surface: ToplevelSurface) { if let Some(window) = self.window_ids.iter().find_map(|(window, id)| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) .then_some(*id) }) { self.set_window_fullscreen(window, false); } else { surface.with_pending_state(|state| state.states.unset(ToplevelState::Fullscreen)); } } fn grab(&mut self, surface: PopupSurface, seat: wl_seat::WlSeat, serial: Serial) { if self.session_lock.active { return; } let Some(seat) = Seat::from_resource(&seat) else { return; }; let popup = PopupKind::Xdg(surface); let Ok(root) = find_popup_root_surface(&popup) else { return; }; let popup_grab = match self.popups.grab_popup(root, popup, &seat, serial) { Ok(grab) => grab, Err(error) => { tracing::debug!(?error, "rejected xdg popup grab"); return; } }; if let Some(pointer) = seat.get_pointer() { pointer.set_grab( self, PopupPointerGrab::new(&popup_grab), serial, Focus::Keep, ); } if let Some(keyboard) = seat.get_keyboard() { keyboard.set_grab(self, PopupKeyboardGrab::new(&popup_grab), serial); // Focus the popup immediately, rather than waiting for its first // key event. Shell clients use focus to initialize popup effects, // and Escape must be delivered to the popup from the outset. keyboard.set_focus(self, popup_grab.current_grab(), serial); } } } fn check_grab( seat: &Seat, surface: &WlSurface, serial: Serial, ) -> Option> { let pointer = seat.get_pointer()?; if !pointer.has_grab(serial) { return None; } let start_data = pointer.grab_start_data()?; let (focus, _) = start_data.focus.as_ref()?; focus .id() .same_client_as(&surface.id()) .then_some(start_data) } impl XdgDecorationHandler for Ferese { fn new_decoration(&mut self, toplevel: ToplevelSurface) { let mode = self.decoration_mode_for(&toplevel); toplevel.with_pending_state(|state| state.decoration_mode = Some(mode)); // This must be unconditional: the initial xdg-toplevel configure may // already contain the same mode before the decoration object exists. // `send_pending_configure` would then see no state change and omit the // decoration configure, leaving clients to fall back to CSD. toplevel.send_configure(); } fn request_mode(&mut self, toplevel: ToplevelSurface, _mode: Mode) { let mode = self.decoration_mode_for(&toplevel); set_decoration_mode(&toplevel, mode); } fn unset_mode(&mut self, toplevel: ToplevelSurface) { let mode = self.decoration_mode_for(&toplevel); set_decoration_mode(&toplevel, mode); } } impl Ferese { fn decoration_mode_for(&self, toplevel: &ToplevelSurface) -> Mode { self.space .elements() .find(|window| { window .toplevel() .is_some_and(|candidate| candidate.wl_surface() == toplevel.wl_surface()) }) .filter(|window| self.is_floating_window(window)) .map_or(Mode::ServerSide, |_| Mode::ClientSide) } } fn set_decoration_mode(toplevel: &ToplevelSurface, mode: Mode) { toplevel.with_pending_state(|state| state.decoration_mode = Some(mode)); toplevel.send_pending_configure(); } pub fn handle_commit(popups: &mut PopupManager, space: &mut Space, surface: &WlSurface) { handle_resize_commit(surface); if let Some(window) = space .elements() .find(|window| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == surface) }) .cloned() { let configured = with_states(surface, |states| { states .data_map .get::() .expect("xdg toplevel state exists") .lock() .expect("xdg toplevel state is not poisoned") .initial_configure_sent }); if !configured { window .toplevel() .expect("mapped window has a toplevel") .send_configure(); } } popups.commit(surface); if let Some(PopupKind::Xdg(popup)) = popups.find_popup(surface) && !popup.is_initial_configure_sent() { popup .send_configure() .expect("initial popup configure is valid"); } } pub fn apply_initial_window_rules(state: &mut Ferese, window: &Window) { let Some(toplevel) = window.toplevel() else { return; }; let (app_id, title, transient) = with_states(toplevel.wl_surface(), |states| { let attributes = states .data_map .get::() .expect("xdg toplevel state exists") .lock() .expect("xdg toplevel state is not poisoned"); ( attributes.app_id.clone(), attributes.title.clone(), attributes.parent.is_some(), ) }); let initial = !state.window_ids.contains_key(window); let requested = initial.then(|| { toplevel.with_pending_state(|pending| { ( pending.states.contains(ToplevelState::Maximized), pending.states.contains(ToplevelState::Fullscreen), ) }) }); if initial { let parent = toplevel.parent().and_then(|parent| { state.window_ids.iter().find_map(|(candidate, id)| { candidate .toplevel() .is_some_and(|surface| surface.wl_surface() == &parent) .then_some(*id) }) }); state.add_rule_placed_window(window.clone(), app_id.as_deref(), title.as_deref(), parent); } state.apply_initial_window_rules(window, app_id.as_deref(), title.as_deref(), transient); if let Some((maximized, fullscreen)) = requested && let Some(id) = state.window_ids.get(window).copied() { if maximized { state.set_window_maximized(id, true); } if fullscreen { state.set_window_fullscreen(id, true); } } } impl Ferese { pub(crate) fn unconstrain_popup(&self, popup: &PopupSurface) { let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else { return; }; let window = self.space.elements().find(|window| { window .toplevel() .is_some_and(|toplevel| toplevel.wl_surface() == &root) }); let geometry = (|| { if let Some(window) = window { let output = self.space.outputs().next()?; let output_geometry = self.space.output_geometry(output)?; let window_geometry = self.space.element_geometry(window)?; Some((output_geometry, window_geometry.loc)) } else { let layer = self .space .layer_for_surface(&root, WindowSurfaceType::TOPLEVEL)?; self.space.outputs().find_map(|output| { let output_geometry = self.space.output_geometry(output)?; let layer_geometry = smithay::desktop::layer_map_for_output(output).layer_geometry(&layer)?; Some((output_geometry, output_geometry.loc + layer_geometry.loc)) }) } })(); let Some((mut target, root_location)) = geometry else { return; }; target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone())); target.loc -= root_location; popup.with_pending_state(|state| { state.geometry = state.positioner.get_unconstrained_geometry(target); }); } }