From e57d355db226efca4235e57b04dde0841b292686 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Fri, 25 Sep 2026 00:44:43 +0100 Subject: [PATCH] feat: support xdg foreign parenting --- compositor/src/handlers/mod.rs | 8 ++++++ compositor/src/handlers/xdg_shell.rs | 25 ++++++++++++++++++ compositor/src/state.rs | 39 ++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/compositor/src/handlers/mod.rs b/compositor/src/handlers/mod.rs index c16bd8b..c92c366 100644 --- a/compositor/src/handlers/mod.rs +++ b/compositor/src/handlers/mod.rs @@ -27,6 +27,7 @@ use smithay::{ PrimarySelectionHandler, PrimarySelectionState, set_primary_focus, }, }, + xdg_foreign::{XdgForeignHandler, XdgForeignState}, }, }; @@ -150,6 +151,12 @@ impl PrimarySelectionHandler for Ferese { } } +impl XdgForeignHandler for Ferese { + fn xdg_foreign_state(&mut self) -> &mut XdgForeignState { + &mut self.xdg_foreign_state + } +} + smithay::delegate_compositor!(Ferese); smithay::delegate_data_device!(Ferese); smithay::delegate_fractional_scale!(Ferese); @@ -165,4 +172,5 @@ smithay::delegate_single_pixel_buffer!(Ferese); smithay::delegate_viewporter!(Ferese); smithay::delegate_xdg_activation!(Ferese); smithay::delegate_xdg_decoration!(Ferese); +smithay::delegate_xdg_foreign!(Ferese); smithay::delegate_xdg_shell!(Ferese); diff --git a/compositor/src/handlers/xdg_shell.rs b/compositor/src/handlers/xdg_shell.rs index 384cf4d..10b0e5e 100644 --- a/compositor/src/handlers/xdg_shell.rs +++ b/compositor/src/handlers/xdg_shell.rs @@ -51,6 +51,31 @@ impl XdgShellHandler for Ferese { } } + 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 diff --git a/compositor/src/state.rs b/compositor/src/state.rs index b077d4c..46ed2bc 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -45,6 +45,7 @@ use smithay::{ socket::ListeningSocketSource, viewporter::ViewporterState, xdg_activation::XdgActivationState, + xdg_foreign::XdgForeignState, }, }; @@ -80,6 +81,7 @@ pub struct Ferese { pub viewporter_state: ViewporterState, pub layer_shell_state: WlrLayerShellState, pub xdg_activation_state: XdgActivationState, + pub xdg_foreign_state: XdgForeignState, pub xdg_shell_state: XdgShellState, } @@ -105,6 +107,7 @@ impl Ferese { let viewporter_state = ViewporterState::new::(&display_handle); let layer_shell_state = WlrLayerShellState::new::(&display_handle); let xdg_activation_state = XdgActivationState::new::(&display_handle); + let xdg_foreign_state = XdgForeignState::new::(&display_handle); let mut seat_state = SeatState::new(); let mut seat = seat_state.new_wl_seat(&display_handle, "ferese-winit"); seat.add_keyboard(Default::default(), 200, 25)?; @@ -145,6 +148,7 @@ impl Ferese { viewporter_state, layer_shell_state, xdg_activation_state, + xdg_foreign_state, xdg_shell_state, }) } @@ -347,6 +351,41 @@ impl Ferese { self.relayout(); } + pub fn make_window_transient(&mut self, window: &Window, parent: WindowId) { + let Some(id) = self.window_ids.get(window).copied() else { + return; + }; + if self.workspaces.workspace_for_window(id) != self.workspaces.workspace_for_window(parent) + { + tracing::warn!( + ?id, + ?parent, + "ignored transient parent on another workspace" + ); + return; + } + let Some(bounds) = self.output_bounds() else { + return; + }; + let parent_rect = self.logical_window_rect(parent, bounds).unwrap_or(bounds); + let rect = centered_transient_rect(parent_rect); + let result = match self.workspaces.placement(id) { + Some(WindowPlacement::Tiled) => self + .workspaces + .toggle_floating(id, rect, Axis::Horizontal, 0.5) + .map(|_| ()), + Some(WindowPlacement::Floating { .. }) => self.workspaces.set_floating_rect(id, rect), + None => return, + }; + + if let Err(error) = result { + tracing::warn!(%error, ?id, ?parent, "failed to apply transient placement"); + return; + } + + self.relayout(); + } + pub fn remove_tiled_window(&mut self, window: &Window) { let Some(id) = self.window_ids.remove(window) else { return;