// xfwl4 -- Wayland compositor for the Xfce Desktop Environment
//
// Copyright (C) 2026 Brian Tarricone <brian@tarricone.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use std::collections::VecDeque;

use smithay::{
    desktop::{WindowSurface, WindowSurfaceType, find_popup_root_surface, layer_map_for_output, space::SpaceElement},
    input::Seat,
    output::Output,
    reexports::{wayland_protocols::xdg::shell::server::xdg_toplevel, wayland_server::Resource},
    utils::{Logical, Point, Rectangle, SERIAL_COUNTER, Serial},
};

use crate::{
    backend::Backend,
    core::{
        config::{ActivateAction, OutputAndRect, adjacent_monitor_in_direction},
        cycle::CyclingPhase,
        focus::KeyboardFocusTarget,
        placement::FillMode,
        shell::{
            TileMode, WindowCapabilities, WindowElement, WindowFlags, WindowLayout, WorkspaceLocation, output_and_geom_for_anchored_layout,
            remove_all_layout_states, remove_tiled_states, ssd::DecorationInput, xdg::send_unfulfilled_configure,
        },
        state::Xfwl4State,
        util::{Direction, OutputExt},
    },
    protocols::foreign_toplevel_management::ToplevelChangedInput,
};

mod manager;
mod workspace;

pub use manager::{WindowOutputChangeEvent, WindowStackingLayer, WorkspaceManager};
pub use workspace::Workspace;

impl WindowElement {
    fn set_minimized_state(&self, is_minimized: bool) {
        self.props().is_minimized = is_minimized;

        match self.0.underlying_surface() {
            WindowSurface::Wayland(surface) => {
                surface.with_pending_state(|state| {
                    if is_minimized {
                        state.states.set(xdg_toplevel::State::Suspended);
                    } else {
                        state.states.unset(xdg_toplevel::State::Suspended);
                    }
                });

                if surface.is_initial_configure_sent() {
                    surface.send_pending_configure();
                }
            }
            #[cfg(feature = "xwayland")]
            WindowSurface::X11(x11_surface) => {
                if x11_surface.is_hidden() != is_minimized {
                    let _ = x11_surface.set_hidden(is_minimized);
                }
            }
        }
    }

    pub(in crate::core) fn clear_tiled_metadata(&self) {
        let mut props = self.props();
        if props.tile_mode.is_some() {
            props.tile_mode = None;
            props.anchored_output = None;
            props.saved_geom = None;
            drop(props);

            if let WindowSurface::Wayland(surface) = self.0.underlying_surface() {
                surface.with_pending_state(remove_tiled_states);
            }
        }
    }
}

impl<BackendData: Backend + 'static> Xfwl4State<BackendData> {
    pub(in crate::core) fn set_active_workspace(&mut self, workspace_number: u32) {
        let previously_active = self.active_window();

        // Annoying, need to do this first before 'self' gets borrowed mutably below.
        let window_under_pointer = self
            .surface_under_for_workspace(self.core.pointer.current_location(), workspace_number)
            .and_then(|(target, _)| self.window_for_pointer_focus_target(&target));

        let changed = if let Some((_, new_workspace)) = self.core.workspace_manager.set_active_workspace(workspace_number) {
            let new_active_window = if self.core.config.click_to_focus() {
                new_workspace.active_window().cloned()
            } else {
                window_under_pointer.clone()
            }
            .or_else(|| new_workspace.topmost_focusable_window().cloned());

            if let Some(active_window) = new_active_window {
                self.activate_window(&active_window, true, self.core.config.activate_action(), None);
            } else {
                self.clear_window_focus(SERIAL_COUNTER.next_serial());
            }

            self.core.set_pointer_window(window_under_pointer);
            self.core.set_pointer_focus_dirty();
            true
        } else {
            false
        };

        if changed {
            #[cfg(feature = "xwayland")]
            {
                self.core.xwayland_state.update_active_workspace(workspace_number);
                self.x11_update_window_stacking_order();
            }
            self.core.cancel_focus_follows_mouse_timers();
        }

        self.notify_active_window_change(previously_active);
    }

    pub(in crate::core) fn toggle_active_workspace(&mut self, workspace_number: u32) {
        if workspace_number == self.core.workspace_manager.active_workspace_index() && self.core.config.toggle_workspaces() {
            let prev_ws_num = self.core.workspace_manager.previous_active_workspace_index();
            self.set_active_workspace(prev_ws_num);
        } else {
            self.set_active_workspace(workspace_number);
        }
    }

    pub(in crate::core) fn scrolled_for_workspace_switch(&mut self, amount: f64) {
        let steps = self.core.workspace_manager.scrolled_for_switch(amount);
        if steps != 0 {
            let wrap = self.core.config.wrap_cycle();
            let new_index = self.core.workspace_manager.sequential_workspace_index(steps, wrap);
            if let Some(index) = new_index {
                self.set_active_workspace(index);
            }
        }
    }

    pub(in crate::core) fn append_workspace(&mut self) {
        self.core.workspace_manager.add_workspace();

        #[cfg(feature = "xwayland")]
        {
            self.core
                .xwayland_state
                .update_workspace_count(self.core.workspace_manager.workspaces().len() as u32);
            self.core
                .xwayland_state
                .update_workspace_names(self.core.workspace_manager.workspace_names());
            self.core
                .xwayland_state
                .update_workspace_layout(self.core.workspace_manager.geometry());
            self.x11_update_workarea();
            self.x11_update_window_stacking_order();
        }
    }

    pub(in crate::core) fn insert_workspace(&mut self, at_index: u32) {
        self.core.workspace_manager.insert_workspace(at_index);

        #[cfg(feature = "xwayland")]
        {
            self.core
                .xwayland_state
                .update_workspace_count(self.core.workspace_manager.workspaces().len() as u32);
            self.core
                .xwayland_state
                .update_workspace_names(self.core.workspace_manager.workspace_names());
            self.core
                .xwayland_state
                .update_workspace_layout(self.core.workspace_manager.geometry());
            self.core
                .xwayland_state
                .update_active_workspace(self.core.workspace_manager.active_workspace_index());
            self.x11_update_workarea();
            self.x11_update_window_stacking_order();
        }
    }

    pub(in crate::core) fn remove_workspace(&mut self, index: u32) {
        if let Some(new_ws_num) = self.core.workspace_manager.remove_workspace(index) {
            self.set_active_workspace(new_ws_num);
        }

        #[cfg(feature = "xwayland")]
        {
            self.core
                .xwayland_state
                .update_workspace_count(self.core.workspace_manager.workspaces().len() as u32);
            self.core
                .xwayland_state
                .update_workspace_names(self.core.workspace_manager.workspace_names());
            self.core
                .xwayland_state
                .update_workspace_layout(self.core.workspace_manager.geometry());
            self.x11_update_workarea();
            self.x11_update_window_stacking_order();
        }
    }

    pub(in crate::core) fn new_window<P: Into<Point<i32, Logical>>>(
        &mut self,
        window: WindowElement,
        location: P,
        allow_activate: bool,
        workspace_number: Option<u32>,
    ) {
        let previously_active = self.active_window();

        if !window.props().flags.contains(WindowFlags::NO_CYCLE) {
            self.core.cycling_state.add_window(window.clone());
        }

        let workspace_number = workspace_number.unwrap_or_else(|| self.core.workspace_manager.active_workspace_index());
        window.props().workspace_loc = WorkspaceLocation::Single(workspace_number);

        let give_focus = allow_activate
            && self.core.config.focus_new()
            && workspace_number == self.core.workspace_manager.active_workspace_index()
            && self.core.cycling_state.cycling_phase() == CyclingPhase::None;
        let parent = window.parent();

        self.core
            .workspace_manager
            .new_window(window.clone(), location, give_focus, Some(workspace_number), parent.as_ref());

        #[cfg(feature = "xwayland")]
        self.core.xwayland_state.update_window_workspace_location(&window);

        if give_focus {
            self.focus_window(&window, SERIAL_COUNTER.next_serial(), None);
        }

        if self.core.cycling_state.cycling_phase() == CyclingPhase::Active {
            self.add_window_to_tabwin(&window);
        }

        #[cfg(feature = "xwayland")]
        self.x11_update_window_stacking_order();

        self.core.set_pointer_focus_dirty();

        self.notify_active_window_change(previously_active);
    }

    pub(in crate::core) fn focus_window(&mut self, window: &WindowElement, serial: Serial, seat: Option<Seat<Self>>) {
        let window = window.modal_blocker().unwrap_or_else(|| window.clone());
        self.core.cycling_state.move_window_to_front(&window);
        self.focus_target(window, serial, seat);
    }

    pub(in crate::core) fn focus_target<F: Into<KeyboardFocusTarget>>(&mut self, focus: F, serial: Serial, seat: Option<Seat<Self>>) {
        if let Some(keyboard) = seat.as_ref().unwrap_or(&self.core.seat).get_keyboard() {
            let focus = focus.into();

            if let KeyboardFocusTarget::Window(window) = &focus
                && let Some(window) = self.core.workspace_manager.active_workspace().find_window(|elem| elem.0 == *window)
            {
                self.set_window_urgent_state(&window, false);
            }

            keyboard.set_focus(self, Some(focus), serial);
        }
    }

    // A window's `active()` flag tracks the workspace's designated active window, which is not the
    // same thing as holding the keyboard focus: layer-shell surfaces (the panel, the desktop,
    // notifications) take focus without disturbing it, and popups take it from their own parent.
    pub(in crate::core) fn window_has_keyboard_focus(&self, window: &WindowElement, seat: Option<&Seat<Self>>) -> bool {
        seat.unwrap_or(&self.core.seat)
            .get_keyboard()
            .and_then(|keyboard| keyboard.current_focus())
            .is_some_and(|focus| match focus {
                KeyboardFocusTarget::Window(focused) => focused == window.0,
                KeyboardFocusTarget::Popup(popup) => {
                    find_popup_root_surface(&popup).is_ok_and(|root| self.window_for_surface(&root).as_ref() == Some(window))
                }
                KeyboardFocusTarget::LayerSurface(_) => false,
                KeyboardFocusTarget::LockSurface(_) => false,
            })
    }

    fn focus_is_layer_shell(&self, focus: &KeyboardFocusTarget) -> bool {
        match focus {
            KeyboardFocusTarget::LayerSurface(_) => true,
            KeyboardFocusTarget::Popup(popup) => find_popup_root_surface(popup).is_ok_and(|root| {
                self.core.workspace_manager.active_workspace().outputs().any(|output| {
                    layer_map_for_output(output)
                        .layer_for_surface(&root, WindowSurfaceType::ALL)
                        .is_some()
                })
            }),
            KeyboardFocusTarget::Window(_) => false,
            KeyboardFocusTarget::LockSurface(_) => false,
        }
    }

    // Layer surfaces keep keyboard focus independently of the window stack, so don't take
    // focus away from one just because no window is left to hand it to.
    fn clear_window_focus(&mut self, serial: Serial) {
        if let Some(keyboard) = self.core.seat.get_keyboard()
            && !keyboard.current_focus().is_some_and(|focus| self.focus_is_layer_shell(&focus))
        {
            keyboard.set_focus(self, None, serial);
        }
    }

    pub(in crate::core) fn remove_window(&mut self, window: &WindowElement) {
        // Only losing the focused window should move focus; removing one that never had it must
        // leave focus where it is, or closing a background window would steal it.
        let was_focused = window.active() || self.window_has_keyboard_focus(window, None);

        self.core.cycling_state.remove_window(window);
        self.core.workspace_manager.remove_window(window);
        self.core.compositor_ui_state.tabwin_remove_window(window.window_id());
        self.core.close_dialog_for_window(window);

        // Forgetting it as the pointer window also cancels any focus timer still aimed at it, and
        // lets focus-follows-mouse re-evaluate against whatever is underneath rather than waiting
        // for the pointer to cross a window boundary.
        if self.core.input_state.is_pointer_window(window) {
            self.core.set_pointer_window(None);
        }
        self.core.set_pointer_focus_dirty();

        if was_focused && self.core.cycling_state.cycling_phase() == CyclingPhase::None {
            if let Some(window) = { self.core.workspace_manager.active_workspace().topmost_focusable_window().cloned() } {
                self.activate_window(&window, true, self.core.config.activate_action(), None);
            } else {
                self.clear_window_focus(SERIAL_COUNTER.next_serial());
            }
        }

        #[cfg(feature = "xwayland")]
        self.x11_update_window_stacking_order();
    }

    fn active_window(&self) -> Option<WindowElement> {
        self.core.workspace_manager.active_workspace().active_window().cloned()
    }

    fn notify_toplevel_state(&mut self, window: &WindowElement) {
        self.core.toplevel_changed(
            window,
            ToplevelChangedInput {
                state: Some(window.state()),
                ..Default::default()
            },
        );
    }

    // The active window changes through several paths (activation, raising,
    // lowering, workspace switches); each captures the previously-active window
    // and calls this afterward, so foreign-toplevel clients are always notified,
    // deactivated window before activated window.
    fn notify_active_window_change(&mut self, previously_active: Option<WindowElement>) {
        let now_active = self.active_window();
        if previously_active != now_active {
            if let Some(window) = &previously_active {
                self.notify_toplevel_state(window);
            }
            if let Some(window) = &now_active {
                self.notify_toplevel_state(window);
            }
        }
    }

    pub(in crate::core) fn activate_window(
        &mut self,
        window: &WindowElement,
        raise: bool,
        action: ActivateAction,
        seat: Option<Seat<Self>>,
    ) {
        // The modal takes the activation as well as the focus, or its parent would keep the active
        // titlebar while keystrokes went to the modal.
        let window = &window.modal_blocker().unwrap_or_else(|| window.clone());

        let previously_active = self.active_window();
        let active_workspace_index = self.core.workspace_manager.active_workspace_index();
        let window_workspace_index = match window.props().workspace_loc {
            WorkspaceLocation::Single(num) => num,
            WorkspaceLocation::All => active_workspace_index,
        };

        if active_workspace_index == window_workspace_index || action != ActivateAction::None {
            let window_workspace_index = if active_workspace_index != window_workspace_index {
                match action {
                    ActivateAction::Bring => {
                        self.core
                            .workspace_manager
                            .move_window_by_index(window, window_workspace_index, active_workspace_index);
                        active_workspace_index
                    }
                    ActivateAction::Switch => {
                        self.set_active_workspace(window_workspace_index);
                        window_workspace_index
                    }
                    ActivateAction::None => window_workspace_index,
                }
            } else {
                window_workspace_index
            };

            let old_active_window = self
                .core
                .workspace_manager
                .active_workspace()
                .visible_windows()
                .find(|active_window| active_window.active())
                .cloned();

            if window.minimized() {
                self.set_window_unminimized(window, SERIAL_COUNTER.next_serial(), false);
            }

            if raise {
                self.raise_window(window, SERIAL_COUNTER.next_serial(), true);
            } else if let Some(workspace) = self
                .core
                .workspace_manager
                .workspaces_mut()
                .get_mut(window_workspace_index as usize)
            {
                workspace.set_active_window(Some(window));
            }

            if old_active_window.as_ref() != Some(window) || !self.window_has_keyboard_focus(window, seat.as_ref()) {
                let serial = SERIAL_COUNTER.next_serial();
                self.focus_window(window, serial, seat);
            }
        }

        self.notify_active_window_change(previously_active);
    }

    fn set_window_minimized_internal(&mut self, window: &WindowElement) {
        if self.core.workspace_manager.set_window_minimized(window) {
            if !self.core.config.cycle_minimized() {
                self.core.cycling_state.move_window_to_back(window);
            }
            window.set_minimized_state(true);
            window.set_activate(false);

            self.update_window_capabilities(window);
            self.core.set_pointer_focus_dirty();

            self.core.toplevel_changed(
                window,
                ToplevelChangedInput {
                    state: Some(window.state()),
                    ..Default::default()
                },
            );
        }
    }

    pub(in crate::core) fn set_window_minimized(&mut self, window: &WindowElement) {
        if window.capabilities().contains(WindowCapabilities::MINIMIZE) {
            // Here we do a breadth-first traversal, but upside down.
            let mut windows = Vec::new();
            let mut queue = VecDeque::new();
            queue.push_back(window.clone());

            while let Some(window) = queue.pop_front() {
                for child in window.children() {
                    queue.push_back(child);
                }
                windows.push(window);
            }

            let was_active = windows.into_iter().rev().fold(false, |was_active_accum, window| {
                let was_active = window.active();
                self.set_window_minimized_internal(&window);
                was_active_accum | was_active
            });

            if was_active {
                if let Some(window) = { self.core.workspace_manager.active_workspace().topmost_focusable_window().cloned() } {
                    self.activate_window(&window, true, self.core.config.activate_action(), None);
                } else {
                    self.clear_window_focus(SERIAL_COUNTER.next_serial());
                }
            }
        }
    }

    fn set_window_unminimized_internal(&mut self, window: &WindowElement, serial: Serial, activate: bool) {
        if self.core.workspace_manager.set_window_unminimized(window, activate) {
            // Clearing the minimized flag first, so that the window has its SHADE capability back
            // by the time we ask to unshade it.
            window.set_minimized_state(false);
            self.set_window_shaded(window, false);

            if activate {
                self.focus_window(window, serial, None);
            }

            self.update_window_capabilities(window);
            self.core.set_pointer_focus_dirty();

            #[cfg(feature = "xwayland")]
            self.x11_update_window_stacking_order();

            self.core.toplevel_changed(
                window,
                ToplevelChangedInput {
                    state: Some(window.state()),
                    ..Default::default()
                },
            );
        }
    }

    pub(in crate::core) fn set_window_unminimized(&mut self, window: &WindowElement, serial: Serial, activate: bool) {
        let previously_active = self.active_window();

        self.maybe_clear_show_desktop_for(window);

        // Minimizing a window minimizes its descendants too, so restore the whole tree,
        // walking from the root down so parents are remapped before their children.
        let mut queue = VecDeque::new();
        queue.push_back(window.root_ancestor());
        while let Some(w) = queue.pop_front() {
            for child in w.children() {
                queue.push_back(child);
            }
            self.set_window_unminimized_internal(&w, serial, activate && &w == window);
        }

        self.notify_active_window_change(previously_active);
    }

    pub(in crate::core) fn toggle_show_desktop(&mut self) {
        if self.core.workspace_manager.showing_desktop() {
            self.deactivate_show_desktop();
        } else {
            self.activate_show_desktop();
        }
    }

    pub(in crate::core) fn activate_show_desktop(&mut self) {
        if !self.core.workspace_manager.showing_desktop() {
            let windows: Vec<WindowElement> = self
                .core
                .workspace_manager
                .workspaces()
                .iter()
                .flat_map(|ws| ws.visible_windows().cloned())
                .filter(is_show_desktop_eligible)
                .collect();

            for window in &windows {
                window.props().was_shown_before_show_desktop = true;
                self.set_window_minimized(window);
            }

            self.core.workspace_manager.set_showing_desktop(true);
            #[cfg(feature = "xwayland")]
            self.core.xwayland_state.set_showing_desktop(true);
        }
    }

    pub(in crate::core) fn deactivate_show_desktop(&mut self) {
        if self.core.workspace_manager.showing_desktop() {
            let to_restore: Vec<WindowElement> = self
                .core
                .workspace_manager
                .workspaces()
                .iter()
                .flat_map(|ws| ws.minimized_windows().cloned())
                .filter(|w| w.props().was_shown_before_show_desktop)
                .collect();

            self.core.workspace_manager.set_showing_desktop(false);
            for w in &to_restore {
                w.props().was_shown_before_show_desktop = false;
            }
            #[cfg(feature = "xwayland")]
            self.core.xwayland_state.set_showing_desktop(false);

            let serial = SERIAL_COUNTER.next_serial();
            for window in &to_restore {
                self.set_window_unminimized(window, serial, false);
            }

            let focus_target = to_restore
                .iter()
                // Prefer focusing a fullscreen window from the restore set.
                .find(|w| w.fullscreened())
                .or_else(|| to_restore.last())
                .cloned();
            if let Some(target) = focus_target {
                self.activate_window(&target, true, self.core.config.activate_action(), None);
            }
        }
    }

    fn maybe_clear_show_desktop_for(&mut self, window: &WindowElement) {
        if self.core.workspace_manager.showing_desktop() && window.props().was_shown_before_show_desktop {
            self.core.workspace_manager.set_showing_desktop(false);
            for ws in self.core.workspace_manager.workspaces() {
                for w in ws.visible_windows().chain(ws.minimized_windows()) {
                    w.props().was_shown_before_show_desktop = false;
                }
            }
            #[cfg(feature = "xwayland")]
            self.core.xwayland_state.set_showing_desktop(false);
        }
    }

    pub(in crate::core) fn set_window_maximized(
        &mut self,
        window: &WindowElement,
        fill_mode: FillMode,
        anchor: Option<Point<f64, Logical>>,
    ) {
        if window.capabilities().contains(WindowCapabilities::MAXIMIZE) {
            self.set_window_untiled(window, None);
            self.set_window_shaded(window, false);

            if let Some((output, output_geom)) = output_and_geom_for_anchored_layout(&self.core.workspace_manager, window, anchor) {
                let old_geom = self.core.workspace_manager.window_geometry(window);
                let mut props = window.props();
                if props.saved_geom.is_none() {
                    props.saved_geom = old_geom;
                }
                props.maximized_mode = Some(fill_mode);
                drop(props);

                if let Some(window_decorations) = window.decoration_state_mut().window_decorations_mut() {
                    window_decorations.update(DecorationInput::Maximized(Some(fill_mode)));
                }
                #[cfg(feature = "xwayland")]
                self.core.xwayland_state.update_window_frame_extents(window);
                self.update_window_capabilities(window);

                self.apply_anchored_layout(window, WindowLayout::Maximized(fill_mode), &output, output_geom);

                self.core.toplevel_changed(
                    window,
                    ToplevelChangedInput {
                        state: Some(window.state()),
                        ..Default::default()
                    },
                );
            }
        } else if let Some(surface) = window.0.toplevel() {
            send_unfulfilled_configure(surface);
        }
    }

    pub(in crate::core) fn set_window_unmaximized(&mut self, window: &WindowElement, new_location: Option<Point<i32, Logical>>) {
        if window.maximized() {
            self.set_window_shaded(window, false);

            let old_geom = self.clear_window_maximized_state(window, false);
            let new_location = new_location.or_else(|| old_geom.map(|geom| geom.loc));

            match window.0.underlying_surface() {
                WindowSurface::Wayland(surface) => {
                    surface.with_pending_state(|state| {
                        state.size = None;
                    });

                    send_unfulfilled_configure(surface);
                }

                #[cfg(feature = "xwayland")]
                WindowSurface::X11(surface) => {
                    if let Some(old_geom) = old_geom {
                        let _ = surface.configure(window.grow_rect_by_gtk_frame_extents(old_geom));
                    }
                }
            }

            if let Some(new_location) = new_location {
                self.core.workspace_manager.relocate_window(window, new_location);
            }
        } else if let Some(surface) = window.0.toplevel() {
            send_unfulfilled_configure(surface);
        }
    }

    /// Clears the maximized state, dropping stored_geom.  Does not configure or restore the old
    /// size.
    pub(in crate::core) fn clear_window_maximized_state(
        &mut self,
        window: &WindowElement,
        xdg_send_configure: bool,
    ) -> Option<Rectangle<i32, Logical>> {
        if window.maximized() {
            if let Some(window_decorations) = window.decoration_state_mut().window_decorations_mut() {
                window_decorations.update(DecorationInput::Maximized(None));
            }
            #[cfg(feature = "xwayland")]
            self.core.xwayland_state.update_window_frame_extents(window);
            self.update_window_capabilities(window);

            let mut props = window.props();
            let old_geom = props.saved_geom.take();
            props.anchored_output = None;
            props.maximized_mode = None;
            drop(props);

            match window.0.underlying_surface() {
                WindowSurface::Wayland(surface) => {
                    surface.with_pending_state(|state| {
                        state.states.unset(xdg_toplevel::State::Maximized);
                    });

                    if xdg_send_configure && surface.is_initial_configure_sent() {
                        surface.send_configure();
                    }
                }

                #[cfg(feature = "xwayland")]
                WindowSurface::X11(surface) => {
                    let _ = surface.set_maximized(false);
                }
            }

            self.core.toplevel_changed(
                window,
                ToplevelChangedInput {
                    state: Some(window.state()),
                    ..Default::default()
                },
            );

            old_geom
        } else {
            None
        }
    }

    pub(in crate::core) fn set_window_tiled(&mut self, window: &WindowElement, mode: TileMode, anchor: Option<Point<f64, Logical>>) {
        if window.can_tile() {
            self.set_window_unmaximized(window, None);

            if let Some((output, output_geom)) = output_and_geom_for_anchored_layout(&self.core.workspace_manager, window, anchor) {
                let old_geom = self.core.workspace_manager.window_geometry(window);
                let mut props = window.props();
                props.tile_mode = Some(mode);
                let saved_geom_was_empty = props.saved_geom.is_none();
                if saved_geom_was_empty {
                    props.saved_geom = old_geom;
                }
                drop(props);

                if self
                    .apply_anchored_layout(window, WindowLayout::Tiled(mode), &output, output_geom)
                    .is_none()
                {
                    let mut props = window.props();
                    props.tile_mode = None;
                    if saved_geom_was_empty {
                        props.saved_geom = None;
                    }
                }

                self.update_window_capabilities(window);
            }
        }
    }

    pub(in crate::core) fn reapply_anchored_layouts_on_output(&mut self, output: &Output) {
        let affected: Vec<WindowElement> = self
            .core
            .workspace_manager
            .workspaces()
            .iter()
            .enumerate()
            .flat_map(|(workspace_num, workspace)| {
                workspace
                    .visible_windows()
                    .filter(move |window| {
                        (!window.sticky() || workspace_num == 0)
                            && window.current_layout() != WindowLayout::Normal
                            && window.props().anchored_output.as_ref().and_then(|w| w.upgrade()).as_ref() == Some(output)
                    })
                    .cloned()
                    .collect::<Vec<_>>()
            })
            .collect();

        if let Some(output_geom) = self.core.workspace_manager.output_geometry(output) {
            let mut untile_windows = Vec::new();
            for window in &affected {
                let layout = window.current_layout();
                if self.apply_anchored_layout(window, layout, output, output_geom).is_none() {
                    untile_windows.push(window.clone());
                }
            }
            for window in untile_windows {
                self.set_window_untiled(&window, None);
            }
        }
    }

    pub(in crate::core) fn apply_anchored_layout(
        &mut self,
        window: &WindowElement,
        layout: WindowLayout,
        output: &Output,
        output_geom: Rectangle<i32, Logical>,
    ) -> Option<Vec<Output>> {
        let zone = layer_map_for_output(output).non_exclusive_zone();
        let zone = Rectangle::new(output_geom.loc + zone.loc, zone.size);

        window.clear_resize_state();

        if let Some(mut geometry) = layout.geometry_in_zone(zone) {
            match layout {
                WindowLayout::Maximized(FillMode::Vertical) => {
                    if let Some(saved_geom) = { window.props().saved_geom } {
                        geometry.loc.x = saved_geom.loc.x;
                        geometry.size.w = saved_geom.size.w;
                    }
                }
                WindowLayout::Maximized(FillMode::Horizontal) => {
                    if let Some(saved_geom) = { window.props().saved_geom } {
                        geometry.loc.y = saved_geom.loc.y;
                        geometry.size.h = saved_geom.size.h;
                    }
                }
                _ => (),
            }

            if let Some(window_decorations) = window.decoration_state_mut().window_decorations_mut() {
                window_decorations.refresh_layout();
                let e = window_decorations.decorations_extents();
                geometry.size.w -= e.left + e.right;
                geometry.size.h -= e.top + e.bottom;
            }
            #[cfg(feature = "xwayland")]
            self.core.xwayland_state.update_window_frame_extents(window);

            let fits_hints = if matches!(layout, WindowLayout::Tiled(_)) {
                let (min, max) = window.min_max_sizes();
                geometry.size.w >= min.w
                    && geometry.size.h >= min.h
                    && (max.w == 0 || geometry.size.w <= max.w)
                    && (max.h == 0 || geometry.size.h <= max.h)
            } else {
                true
            };

            if fits_hints {
                window.props().anchored_output = Some(output.downgrade());

                let xdg_states = layout.as_xdg_toplevel_states();

                match window.0.underlying_surface() {
                    WindowSurface::Wayland(surface) => {
                        surface.with_pending_state(|state| {
                            remove_all_layout_states(state);
                            for s in xdg_states {
                                state.states.set(*s);
                            }
                            state.size = Some(geometry.size);
                            state.bounds = Some(geometry.size);
                        });
                        if surface.is_initial_configure_sent() {
                            surface.send_pending_configure();
                        }
                    }

                    #[cfg(feature = "xwayland")]
                    WindowSurface::X11(surface) => {
                        let _ = surface.set_maximized(matches!(layout, WindowLayout::Maximized(_)));
                        let _ = surface.configure(window.grow_rect_by_gtk_frame_extents(geometry));
                    }
                }

                if !window.minimized() {
                    self.core.workspace_manager.relocate_window(window, geometry.loc);
                }

                Some(self.core.workspace_manager.output_under(geometry.loc.to_f64()).cloned().collect())
            } else {
                None
            }
        } else {
            None
        }
    }

    pub(in crate::core) fn toggle_window_tiled(&mut self, window: &WindowElement, mode: TileMode) {
        if window.tile_mode().is_some_and(|tile_mode| tile_mode == mode) {
            self.set_window_untiled(window, None);
        } else {
            self.set_window_tiled(window, mode, None);
        }
    }

    pub(in crate::core) fn set_window_untiled(&mut self, window: &WindowElement, new_location: Option<Point<i32, Logical>>) {
        let mut props = window.props();
        if props.tile_mode.is_some() {
            props.tile_mode = None;
            props.anchored_output = None;
            let saved_geom = props.saved_geom.take();
            drop(props);

            match window.0.underlying_surface() {
                WindowSurface::Wayland(surface) => {
                    surface.with_pending_state(|state| {
                        remove_tiled_states(state);
                        state.size = None;
                    });
                    if surface.is_initial_configure_sent() {
                        surface.send_configure();
                    }
                }

                #[cfg(feature = "xwayland")]
                WindowSurface::X11(surface) => {
                    if let Some(saved_geom) = saved_geom {
                        let _ = surface.configure(window.grow_rect_by_gtk_frame_extents(saved_geom));
                    }
                }
            }

            if let Some(new_location) = new_location.or_else(|| saved_geom.map(|geom| geom.loc)) {
                self.core.workspace_manager.relocate_window(window, new_location);
            }

            self.update_window_capabilities(window);
        }
    }

    pub(in crate::core) fn set_window_shaded(&mut self, window: &WindowElement, is_shaded: bool) {
        if window.capabilities().contains(WindowCapabilities::SHADE) || !is_shaded {
            let mut props = window.props();
            let changed = if props.is_shaded != is_shaded {
                props.is_shaded = is_shaded;
                if let Some(decorations) = window.decoration_state_mut().window_decorations_mut() {
                    decorations.update(DecorationInput::Shaded(is_shaded));
                }
                #[cfg(feature = "xwayland")]
                self.core.xwayland_state.update_window_frame_extents(window);

                true
            } else {
                false
            };
            drop(props);

            if changed {
                #[cfg(feature = "xwayland")]
                if let WindowSurface::X11(x11_surface) = window.0.underlying_surface() {
                    let _ = x11_surface.set_shaded(is_shaded);
                }

                self.core.toplevel_changed(
                    window,
                    ToplevelChangedInput {
                        state: Some(window.state()),
                        ..Default::default()
                    },
                );
            }
        }
    }

    fn set_window_sticky_internal(&mut self, window: &WindowElement, is_sticky: bool) {
        let cur_is_sticky = window.props().workspace_loc == WorkspaceLocation::All;
        if cur_is_sticky != is_sticky {
            let (new_ws_loc, new_ws_id) = if is_sticky {
                (WorkspaceLocation::All, None)
            } else {
                let idx = self.core.workspace_manager.active_workspace_index();
                let id = self.core.workspace_manager.active_workspace().id().to_owned();
                (WorkspaceLocation::Single(idx), Some(id))
            };

            self.core.workspace_manager.set_window_workspace_num(window, new_ws_loc);
            self.core.set_pointer_focus_dirty();

            if let Some(window_decorations) = window.decoration_state_mut().window_decorations_mut() {
                window_decorations.update(DecorationInput::Sticky(is_sticky));
            }

            #[cfg(feature = "xwayland")]
            if let WindowSurface::X11(x11_surface) = window.0.underlying_surface() {
                let _ = x11_surface.set_sticky(is_sticky);
                self.core.xwayland_state.update_window_workspace_location(window);
            }

            self.core.toplevel_changed(
                window,
                ToplevelChangedInput {
                    state: Some(window.state()),
                    workspace_id: Some(new_ws_id),
                    ..Default::default()
                },
            );

            #[cfg(feature = "xwayland")]
            self.x11_update_window_stacking_order();
        }
    }

    pub(in crate::core) fn set_window_sticky(&mut self, window: &WindowElement, is_sticky: bool) {
        // A whole tree lives on the same workspace, so it is the root that decides whether it may
        // be stuck: a child that could not be stuck on its own still has to follow its parent.
        let root = window.root_ancestor();

        if root.capabilities().contains(WindowCapabilities::STICK) || !is_sticky {
            // Do a breadth-first traversal, (un)sticking each window as we go down the tree.
            let mut queue = VecDeque::new();
            queue.push_back(root);
            while let Some(child) = queue.pop_front() {
                self.set_window_sticky_internal(&child, is_sticky);
                for child in child.children() {
                    queue.push_back(child);
                }
            }
        }
    }

    fn set_window_stacking_layer_internal(&mut self, window: &WindowElement, layer: WindowStackingLayer) {
        let old_layer = window.stacking_layer();

        if layer != old_layer {
            self.core.workspace_manager.set_window_stacking_layer(window, layer);
            self.core.set_pointer_focus_dirty();

            #[cfg(feature = "xwayland")]
            if let WindowSurface::X11(surface) = window.0.underlying_surface() {
                let _ = surface.set_below(matches!(
                    layer,
                    WindowStackingLayer::AlwaysOnBottom | WindowStackingLayer::Background
                ));
                let _ = surface.set_above(matches!(
                    layer,
                    WindowStackingLayer::AlwaysOnTop | WindowStackingLayer::Overlay | WindowStackingLayer::System
                ));
                self.x11_update_window_stacking_order();
            }

            if matches!(layer, WindowStackingLayer::AlwaysOnTop | WindowStackingLayer::AlwaysOnBottom)
                || (layer == WindowStackingLayer::Normal
                    && matches!(old_layer, WindowStackingLayer::AlwaysOnTop | WindowStackingLayer::AlwaysOnBottom))
            {
                self.core.toplevel_changed(
                    window,
                    ToplevelChangedInput {
                        state: Some(window.state()),
                        ..Default::default()
                    },
                );
            }
        }
    }

    // A raise normalizes the whole tree onto its root's layer, so the layer belongs to the tree
    // rather than to one window: setting it on a child alone would be undone by the next raise.
    pub(in crate::core) fn set_window_stacking_layer(&mut self, window: &WindowElement, layer: WindowStackingLayer) {
        // Since the layer belongs to the tree, it is the root that decides whether it may change:
        // a child that could not be raised on its own still has to follow its parent.
        let root = window.root_ancestor();

        let capabilities = root.capabilities();
        if (layer == WindowStackingLayer::AlwaysOnTop && capabilities.contains(WindowCapabilities::ABOVE))
            || (layer == WindowStackingLayer::AlwaysOnBottom && capabilities.contains(WindowCapabilities::BELOW))
            || !matches!(layer, WindowStackingLayer::AlwaysOnTop | WindowStackingLayer::AlwaysOnBottom)
        {
            let mut queue = VecDeque::new();
            queue.push_back(root.clone());
            while let Some(descendant) = queue.pop_front() {
                self.set_window_stacking_layer_internal(&descendant, layer);
                queue.extend(descendant.children());
            }

            // Moving a window to another layer raises it within that layer, so the tree has to be
            // re-ordered afterwards or it ends up stacked in the order it happened to be walked in.
            self.raise_window_tree(&root, SERIAL_COUNTER.next_serial(), None);
        }
    }

    pub(in crate::core) fn set_window_always_on_top(&mut self, window: &WindowElement) {
        self.set_window_stacking_layer(window, WindowStackingLayer::AlwaysOnTop);
    }

    pub(in crate::core) fn set_window_always_on_bottom(&mut self, window: &WindowElement) {
        self.set_window_stacking_layer(window, WindowStackingLayer::AlwaysOnBottom);
    }

    pub(in crate::core) fn set_window_normal_stacking(&mut self, window: &WindowElement) {
        self.set_window_stacking_layer(window, WindowStackingLayer::Normal);
    }

    pub(in crate::core) fn set_window_fullscreen(&mut self, window: &WindowElement, output: Option<Output>) {
        if window.capabilities().contains(WindowCapabilities::FULLSCREEN) {
            self.set_window_shaded(window, false);

            let workspace = self.core.workspace_manager.active_workspace_mut();
            let output_and_geometry = output
                .or_else(|| workspace.outputs_for_window(window).into_iter().next())
                .or_else(|| self.core.workspace_manager.outputs().next().cloned())
                .and_then(|output| self.core.workspace_manager.output_geometry(&output).map(|geom| (output, geom)));

            if let Some((output, geometry)) = output_and_geometry {
                // NOTE: This is only one part of the solution. We can set the
                // location and configure size here, but the surface should be rendered fullscreen
                // independently from its buffer size

                let (fullscreened, old_fullscreen_windows) = match window.0.underlying_surface() {
                    WindowSurface::Wayland(surface) => {
                        let (fullscreened, old_fullscreen_window) =
                            if let Ok(client) = self.core.display_handle.get_client(surface.wl_surface().id()) {
                                let wl_output = output.client_outputs(&client).last();

                                self.disable_decorations_for_window(window);
                                surface.with_pending_state(|state| {
                                    state.states.set(xdg_toplevel::State::Fullscreen);
                                    state.size = Some(geometry.size);
                                    state.fullscreen_output = wl_output;
                                });
                                tracing::trace!("Fullscreening: {:?}", window);
                                (true, self.core.workspace_manager.set_window_fullscreen(window, &output))
                            } else {
                                (false, vec![])
                            };

                        // The protocol demands us to always reply with a configure,
                        // regardless of we fulfilled the request or not
                        if surface.is_initial_configure_sent() {
                            surface.send_configure();
                        }

                        (fullscreened, old_fullscreen_window)
                    }

                    #[cfg(feature = "xwayland")]
                    WindowSurface::X11(surface) => {
                        self.disable_decorations_for_window(window);
                        let _ = surface.set_fullscreen(true);
                        let _ = surface.configure(window.grow_rect_by_gtk_frame_extents(geometry));
                        tracing::trace!("Fullscreening: {:?}", window);
                        (true, self.core.workspace_manager.set_window_fullscreen(window, &output))
                    }
                };

                self.backend.reset_buffers(&output);

                for old_fullscreen_window in old_fullscreen_windows {
                    self.set_window_unfullscreen(&old_fullscreen_window);
                }

                if fullscreened {
                    window.props().is_fullscreened = true;
                    self.update_window_capabilities(window);
                    self.core.toplevel_changed(
                        window,
                        ToplevelChangedInput {
                            state: Some(window.state()),
                            ..Default::default()
                        },
                    );
                }
            }
        } else if let Some(surface) = window.0.toplevel() {
            send_unfulfilled_configure(surface);
        }
    }

    pub(in crate::core) fn set_window_unfullscreen(&mut self, window: &WindowElement) {
        match window.0.underlying_surface() {
            WindowSurface::Wayland(surface) => {
                surface.with_pending_state(|state| {
                    state.states.unset(xdg_toplevel::State::Fullscreen);
                    state.size = None;
                    state.fullscreen_output = None;
                });

                send_unfulfilled_configure(surface);
            }

            #[cfg(feature = "xwayland")]
            WindowSurface::X11(surface) => {
                let _ = surface.set_fullscreen(false);
                if let Some(workspace) = self.core.workspace_manager.workspace_for_window_mut(window) {
                    let _ = surface.configure(workspace.window_bbox(window));
                }
                if window.wants_decorations() {
                    self.enable_decorations_for_window(window);
                } else {
                    self.disable_decorations_for_window(window);
                }
            }
        }

        for output in self.core.workspace_manager.set_window_unfullscreen(window) {
            self.backend.reset_buffers(&output);
        }

        window.props().is_fullscreened = false;
        self.update_window_capabilities(window);
        self.core.toplevel_changed(
            window,
            ToplevelChangedInput {
                state: Some(window.state()),
                ..Default::default()
            },
        );
    }

    fn raise_window_internal(&mut self, window: &WindowElement, root_stacking: WindowStackingLayer, serial: Serial, activate: bool) {
        self.set_window_stacking_layer_internal(window, root_stacking);

        let active_ws_num = self.core.workspace_manager.active_workspace_index();
        let workspace_and_index = if !window.sticky() {
            self.core.workspace_manager.workspace_for_window_with_index_mut(window)
        } else {
            Some((active_ws_num, self.core.workspace_manager.active_workspace_mut()))
        };

        if let Some((ws_num, workspace)) = workspace_and_index {
            workspace.raise_window(window, activate);
            if ws_num == active_ws_num && activate {
                self.focus_window(window, serial, None);
            }
        }

        #[cfg(feature = "xwayland")]
        self.x11_update_window_stacking_order();
    }

    fn group_transients_for(&mut self, window: &WindowElement) -> Vec<WindowElement> {
        self.core
            .workspace_manager
            .workspace_for_window(window)
            .map(|workspace| {
                workspace
                    .visible_windows()
                    .filter(|other| *other != window && other.is_group_transient() && other.same_application_as(window))
                    .cloned()
                    .collect()
            })
            .unwrap_or_default()
    }

    // Depth-first from `root`, so each window lands above its parent and a subtree stays contiguous
    // instead of interleaving with its siblings'.  The branch holding `activate_target` is visited
    // last at every level, which puts it above its siblings rather than leaving whichever was
    // created most recently on top.
    fn raise_window_tree(&mut self, root: &WindowElement, serial: Serial, activate_target: Option<&WindowElement>) {
        let root_stacking = root.stacking_layer();
        let ancestry = activate_target
            .map(|window| window.ancestry().collect::<Vec<_>>())
            .unwrap_or_default();

        let mut stack = vec![root.clone()];
        while let Some(child) = stack.pop() {
            self.raise_window_internal(&child, root_stacking, serial, activate_target == Some(&child));

            let mut children = child.children();
            if let Some(index) = children.iter().position(|child| ancestry.contains(child)) {
                let on_path = children.remove(index);
                children.push(on_path);
            }
            // A modal belongs on top of its siblings no matter which of them is being raised.
            children.sort_by_key(|child| child.modal());
            stack.extend(children.into_iter().rev());
        }
    }

    pub(in crate::core) fn raise_window(&mut self, window: &WindowElement, serial: Serial, activate: bool) {
        // Raising still covers the whole tree, but the modal is what ends up activated and on top
        // of its siblings.
        let window = &window.modal_blocker().unwrap_or_else(|| window.clone());

        let previously_active = self.active_window();

        self.raise_window_tree(&window.root_ancestor(), serial, activate.then_some(window));

        // A group transient sits above the whole application, so raising any of the application's
        // other windows has to carry them along or they would end up buried.
        if !window.is_group_transient() {
            for transient in self.group_transients_for(window) {
                self.raise_window_tree(&transient, serial, None);
            }
        }

        self.core.set_pointer_focus_dirty();

        self.notify_active_window_change(previously_active);
    }

    fn lower_window_internal(&mut self, window: &WindowElement, below: Option<&WindowElement>) {
        if let Some(workspace) = if !window.sticky() {
            self.core.workspace_manager.workspace_for_window_mut(window)
        } else {
            Some(self.core.workspace_manager.active_workspace_mut())
        } {
            if let Some(below) = below {
                workspace.lower_window_below(window, below);
            } else {
                workspace.lower_window(window);
            }
        }
    }

    pub(in crate::core) fn lower_window(&mut self, window: &WindowElement, serial: Serial, below: Option<WindowElement>) {
        let previously_active = self.active_window();

        let mut windows = Vec::new();
        let mut queue = VecDeque::new();
        queue.push_back(window.root_ancestor());
        while let Some(child) = queue.pop_front() {
            for grandchild in child.children() {
                queue.push_back(grandchild);
            }
            windows.push(child);
        }

        // With no reference window each one is dropped to the bottom of the stack, so whichever
        // moves last ends up lowest and the tree has to be walked leaves-first to leave the root
        // underneath its children.  A reference window instead slots each one directly beneath it,
        // so whichever moves last ends up highest, and the same ordering needs the opposite walk.
        if below.is_none() {
            windows.reverse();
        }

        let was_active = windows.into_iter().fold(false, |was_active_accum, child| {
            let was_active = child.active();
            self.lower_window_internal(&child, below.as_ref());
            was_active_accum | was_active
        });

        let active_ws_num = self.core.workspace_manager.active_workspace_index();
        let workspace_and_index = if !window.sticky() {
            self.core.workspace_manager.workspace_for_window_with_index_mut(window)
        } else {
            Some((active_ws_num, self.core.workspace_manager.active_workspace_mut()))
        };

        if let Some((ws_num, workspace)) = workspace_and_index
            && was_active
        {
            // Next activate and give focus to the now-top window in the stack.
            if let Some(new_focus) = workspace.topmost_focusable_window().cloned() {
                workspace.raise_window(&new_focus, true);
                if ws_num == active_ws_num {
                    self.focus_window(&new_focus, serial, None);
                }
            }
        }

        #[cfg(feature = "xwayland")]
        self.x11_update_window_stacking_order();

        self.core.set_pointer_focus_dirty();

        self.notify_active_window_change(previously_active);
    }

    fn notify_workspace_changed(&mut self, window: &WindowElement, new_ws_num: Option<u32>) {
        if let Some(new_ws_num) = new_ws_num
            && let Some(workspace_id) = self
                .core
                .workspace_manager
                .workspaces()
                .get(new_ws_num as usize)
                .map(|workspace| workspace.id().to_owned())
        {
            self.core.set_pointer_focus_dirty();
            self.core.toplevel_changed(
                window,
                ToplevelChangedInput {
                    state: Some(window.state()),
                    workspace_id: Some(Some(workspace_id)),
                    ..Default::default()
                },
            );
        }
    }

    pub(in crate::core) fn move_window_to_workspace_in_direction(&mut self, window: &WindowElement, direction: Direction) -> Option<u32> {
        let new_ws_num = self
            .core
            .workspace_manager
            .move_window_by_direction(window, direction, self.core.config.wrap_layout());
        self.notify_workspace_changed(window, new_ws_num);

        #[cfg(feature = "xwayland")]
        if new_ws_num.is_some() {
            self.core.xwayland_state.update_window_workspace_location(window);
            self.x11_update_window_stacking_order();
        }

        new_ws_num
    }

    pub(in crate::core) fn move_window_to_workspace_index(&mut self, window: &WindowElement, new_index: u32) -> bool {
        let updated = self.core.workspace_manager.move_window_to(window, new_index);

        if updated {
            self.notify_workspace_changed(window, Some(new_index));

            #[cfg(feature = "xwayland")]
            {
                self.core.xwayland_state.update_window_workspace_location(window);
                self.x11_update_window_stacking_order();
            }
        }

        updated
    }

    pub(in crate::core) fn move_window_to_workspace_old_new_index(
        &mut self,
        window: &WindowElement,
        old_index: u32,
        new_index: u32,
    ) -> bool {
        let updated = self.core.workspace_manager.move_window_by_index(window, old_index, new_index);

        if updated {
            self.notify_workspace_changed(window, Some(new_index));

            #[cfg(feature = "xwayland")]
            {
                self.core.xwayland_state.update_window_workspace_location(window);
                self.x11_update_window_stacking_order();
            }
        }

        updated
    }

    pub(in crate::core) fn move_window_to_previous_workspace(&mut self, window: &WindowElement) -> Option<u32> {
        let new_ws_num = self
            .core
            .workspace_manager
            .move_window_previous(window, self.core.config.wrap_layout());
        self.notify_workspace_changed(window, new_ws_num);

        #[cfg(feature = "xwayland")]
        if new_ws_num.is_some() {
            self.core.xwayland_state.update_window_workspace_location(window);
            self.x11_update_window_stacking_order();
        }

        new_ws_num
    }

    pub(in crate::core) fn move_window_to_next_workspace(&mut self, window: &WindowElement) -> Option<u32> {
        let new_ws_num = self.core.workspace_manager.move_window_next(window, self.core.config.wrap_layout());
        self.notify_workspace_changed(window, new_ws_num);

        #[cfg(feature = "xwayland")]
        if new_ws_num.is_some() {
            self.core.xwayland_state.update_window_workspace_location(window);
            self.x11_update_window_stacking_order();
        }

        new_ws_num
    }

    pub(in crate::core) fn move_window_to_output(&mut self, window: &WindowElement, new_output: Output) {
        if let Some(current_output_and_rect) = self.output_and_rect_for_window(window)
            && let Some(new_output_rect) = new_output.geometry()
        {
            let OutputAndRect {
                output: current_output,
                rect: current_output_rect,
            } = current_output_and_rect;
            let current_zone_rect = {
                let mut zone_rect = layer_map_for_output(&current_output).non_exclusive_zone();
                zone_rect.loc += current_output_rect.loc;
                zone_rect
            };

            let new_zone_rect = {
                let mut zone_rect = layer_map_for_output(&new_output).non_exclusive_zone();
                zone_rect.loc += new_output_rect.loc;
                zone_rect
            };

            let moved = if window.minimized() {
                self.core
                    .workspace_manager
                    .translate_minimized_window(window, new_zone_rect.loc - current_zone_rect.loc);
                true
            } else if let Some(current_window_loc) = self.core.workspace_manager.window_location(window) {
                let offset_in_current_output = current_window_loc - current_zone_rect.loc;
                let new_location = new_zone_rect.loc + offset_in_current_output;
                self.core.workspace_manager.relocate_window(window, new_location);

                let layout = window.current_layout();
                if layout != WindowLayout::Normal {
                    self.apply_anchored_layout(window, layout, &new_output, new_output_rect);
                }
                true
            } else {
                false
            };

            if moved {
                self.core.set_pointer_focus_dirty();
                self.core.toplevel_changed(
                    window,
                    ToplevelChangedInput {
                        state: Some(window.state()),
                        outputs_added: vec![new_output],
                        outputs_removed: vec![current_output],
                        ..Default::default()
                    },
                );
            }
        }
    }

    pub(in crate::core) fn move_window_to_output_in_direction(&mut self, window: &WindowElement, direction: Direction) {
        if let Some(current_output_and_rect) = self.output_and_rect_for_window(window)
            && let outputs_and_rects = self.outputs_and_rects()
            && let Some(OutputAndRect { output: new_output, .. }) =
                adjacent_monitor_in_direction(&outputs_and_rects, &current_output_and_rect, direction)
        {
            self.move_window_to_output(window, new_output);
        }
    }

    pub(in crate::core) fn set_window_parent(&mut self, window: &WindowElement, parent: Option<WindowElement>) {
        let previously_active = self.active_window();
        let old_parent = window.parent();
        if window.set_parent(parent.clone()) {
            if let Some(old_parent) = old_parent {
                old_parent.remove_child(window);
            }

            if let Some(parent) = &parent {
                parent.add_child(window.clone());
            }
        }

        if let Some(parent) = parent {
            // Windows are only stacked relative to each other within a layer, so the new child has
            // to join its parent's before it can be raised above it.
            self.set_window_stacking_layer(window, parent.root_ancestor().stacking_layer());

            let workspace_loc = parent.props().workspace_loc;
            match workspace_loc {
                WorkspaceLocation::Single(num) => {
                    self.set_window_sticky(window, false);

                    if let Some(workspace) = self.core.workspace_manager.workspaces_mut().get_mut(num as usize)
                        && workspace.window_location(window).is_some()
                    {
                        let activate = workspace.active_window().is_some_and(|active| active == &parent);
                        workspace.raise_window_above(window, &parent, activate);
                    }
                }

                WorkspaceLocation::All => {
                    self.set_window_sticky(window, true);

                    for workspace in self.core.workspace_manager.workspaces_mut() {
                        if workspace.window_location(window).is_some() {
                            let activate = workspace.active_window().is_some_and(|active| active == &parent);
                            workspace.raise_window_above(window, &parent, activate);
                        }
                    }
                }
            }
        }

        self.notify_active_window_change(previously_active);
        self.update_window_capabilities(window);

        let parent = Some(window.parent().and_then(|parent| self.core.toplevel_id_for_window(&parent)));
        self.core.toplevel_changed(
            window,
            ToplevelChangedInput {
                state: Some(window.state()),
                parent,
                ..Default::default()
            },
        );

        #[cfg(feature = "xwayland")]
        self.x11_update_window_stacking_order();
    }
}

fn is_show_desktop_eligible(window: &WindowElement) -> bool {
    if window.props().flags.contains(WindowFlags::NO_CYCLE) {
        return false;
    }
    match window.0.underlying_surface() {
        WindowSurface::Wayland(_) => true,
        #[cfg(feature = "xwayland")]
        WindowSurface::X11(surface) => {
            use smithay::xwayland::xwm::WmWindowType;

            !surface.is_override_redirect()
                && !surface.is_skip_taskbar()
                && surface.window_type().is_none_or(|wmtype| {
                    !matches!(
                        wmtype,
                        WmWindowType::Combo
                            | WmWindowType::Desktop
                            | WmWindowType::Dnd
                            | WmWindowType::Dock
                            | WmWindowType::DropdownMenu
                            | WmWindowType::Menu
                            | WmWindowType::Notification
                            | WmWindowType::PopupMenu
                            | WmWindowType::Splash
                            | WmWindowType::Toolbar
                            | WmWindowType::Tooltip
                            | WmWindowType::Utility
                    )
                })
        }
    }
}
