Skip to main content

pixelflux/wayland/
wm.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 */
6
7//! The window policy's seat in the compositor: what the frontend tells an installed
8//! [`WindowPolicy`] and how its commands become Smithay state.
9//!
10//! With no policy installed every call here is a no-op and the frontend keeps its own behavior.
11//! Once one is (`Compositor::install_window_policy`), the frontend asks it at each decision
12//! point through the functions below instead of forcing a window fullscreen, and this module
13//! carries the answers out: a configure becomes an xdg configure and a Space mapping, a
14//! stacking order a sequence of raises, a frame a set of memory render buffers drawn with the
15//! window, an overlay one drawn over every output, an effect a transform the render tick
16//! applies, a hidden window a Space unmapping that keeps the window for its return.
17//!
18//! The policy runs inside the compositor's handlers, so a panic in it would end the stream;
19//! [`dispatch`] catches it, drops the policy, and puts every window back the way the frontend
20//! shows them without one, logging the panic once, so a window-manager bug degrades the
21//! desktop rather than ending the stream.
22//!
23//! Window ids are the frontend's `WindowMeta` ids, the ones the window list reports, so the
24//! policy and the consumer name the same window the same way.
25
26use std::any::Any;
27use std::collections::{BTreeMap, HashMap};
28use std::panic::{AssertUnwindSafe, catch_unwind};
29use std::time::{Duration, Instant};
30
31use smithay::backend::allocator::Fourcc;
32use smithay::backend::renderer::element::Id;
33use smithay::backend::renderer::element::Kind;
34use smithay::backend::renderer::element::memory::{
35    MemoryRenderBuffer, MemoryRenderBufferRenderElement,
36};
37use smithay::backend::renderer::element::texture::TextureRenderElement;
38use smithay::backend::renderer::utils::with_renderer_surface_state;
39use smithay::backend::renderer::{ImportMem, Renderer, Texture};
40use smithay::desktop::{Window, layer_map_for_output};
41use smithay::input::keyboard::ModifiersState;
42use smithay::input::pointer::{CursorIcon, CursorImageStatus};
43use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState;
44use smithay::reexports::wayland_server::Resource;
45use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Transform as OutputTransform};
46use smithay::wayland::compositor::with_states;
47use smithay::wayland::keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat;
48use smithay::wayland::seat::WaylandFocus;
49use smithay::wayland::shell::wlr_layer::Layer;
50use smithay::wayland::shell::xdg::SurfaceCachedState;
51use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
52
53use crate::wayland::frontend::{
54    AppState, FocusTarget, next_serial, wayland_time, window_meta, window_output_id,
55};
56use crate::wm::{
57    Command, Decoration, DesktopView, Event, Extents, Frame, Image, KeyEvent, Modifiers,
58    OutputInfo, PointerEvent, PointerKind, Rect, Request, Size, States, Transform, Under, Verdict,
59    WindowId, WindowInfo, WindowKind, WindowPolicy, WindowType,
60};
61
62/// A frame's parts as render buffers, placed relative to the client area.
63#[derive(Clone)]
64pub struct FrameBuffers {
65    pub extents: Extents,
66    pub parts: Vec<(Rect, MemoryRenderBuffer)>,
67}
68
69/// An overlay as a render buffer at a layout rectangle.
70pub struct Overlay {
71    pub rect: Rect,
72    pub buffer: MemoryRenderBuffer,
73}
74
75/// The last picture of a window that is going away, kept so the policy's close effect has
76/// something to shrink and fade once the client's buffer is gone.
77pub struct Ghost {
78    pub texture: GhostTexture,
79    /// The element id the ghost is drawn under, one for its life so damage tracking follows it.
80    pub id: Id,
81    /// Where the window's main surface was drawn, in layout coordinates.
82    pub surface: Rect,
83    /// The client area the window stood at, in layout coordinates: what an effect's rectangle
84    /// scales from.
85    pub geometry: Rect,
86    /// The frame the window wore, drawn around the ghost at the same scale.
87    pub frame: Option<FrameBuffers>,
88    pub taken: Instant,
89}
90
91/// What a ghost is drawn from: the renderer's own texture of the surface where the renderer
92/// keeps one that outlives the client's buffer (a GL texture, uploaded or bound to the
93/// dmabuf), else a copy of the shared-memory buffer taken while it was still attached, since a
94/// pixman texture reads the client's memory and is unusable once the buffer is destroyed.
95pub enum GhostTexture {
96    Memory(MemoryRenderBuffer),
97    /// The renderer's texture, downcast at render time to the renderer's texture type.
98    Context(Box<dyn Any>),
99}
100
101/// How long a ghost is kept waiting for the policy's effect before it is dropped unseen.
102const GHOST_UNCLAIMED: Duration = Duration::from_millis(250);
103/// How long a ghost may live however the policy drives it.
104const GHOST_LIFETIME: Duration = Duration::from_secs(2);
105
106/// A move held until the toplevel commits a size, see [`WmState::pending_moves`].
107///
108/// A configure holding an edge places every commit against that edge until the client reaches
109/// the size asked for: a client answering a drag commits sizes it was asked for a few
110/// configures back, and each lands with the held edge where the pointer put it rather than
111/// where the newest size would.
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113pub struct PendingMove {
114    /// The rectangle configured.
115    pub rect: Rect,
116    pub holds_right: bool,
117    pub holds_bottom: bool,
118}
119
120impl PendingMove {
121    /// Where the window's corner goes for a committed size.
122    fn location(&self, size: Size) -> (i32, i32) {
123        (
124            if self.holds_right {
125                self.rect.x + self.rect.w - size.w
126            } else {
127                self.rect.x
128            },
129            if self.holds_bottom {
130                self.rect.y + self.rect.h - size.h
131            } else {
132                self.rect.y
133            },
134        )
135    }
136
137    /// Whether the move waits for the size asked for, rather than for the first commit.
138    fn holds_edge(&self) -> bool {
139        self.holds_right || self.holds_bottom
140    }
141}
142
143/// Everything the compositor keeps for an installed policy.
144pub struct WmState {
145    policy: Option<Box<dyn WindowPolicy>>,
146    pub frames: HashMap<WindowId, FrameBuffers>,
147    pub overlays: BTreeMap<u32, Overlay>,
148    pub effects: HashMap<WindowId, Transform>,
149    /// The last pictures of windows that went away, by the id the policy's close effect names.
150    pub ghosts: HashMap<WindowId, Ghost>,
151    /// Windows the policy hid (minimized, or on another desktop): unmapped from the Space,
152    /// kept here for their return.
153    pub hidden: HashMap<WindowId, Window>,
154    /// The client rectangle the policy last configured each window to.
155    pub rects: HashMap<WindowId, Rect>,
156    /// The states the policy last configured each window to.
157    pub states: HashMap<WindowId, States>,
158    /// What each client said about decorations.
159    pub decorations: HashMap<WindowId, Decoration>,
160    /// The windows the policy was told about, with the size it last saw them at.
161    pub known: HashMap<WindowId, Size>,
162    /// Where a toplevel goes once it commits the size it was configured with along with the
163    /// move: a resize from a window's top or left edge, or a maximize, changes both, and moving
164    /// the old buffer first would show its far edge jump and come back.
165    pub pending_moves: HashMap<WindowId, PendingMove>,
166    /// The size of the configure each toplevel last acknowledged, as of its last commit, so a
167    /// commit that answers a configure is told from one that was already on its way.
168    pub acked: HashMap<WindowId, Option<Size>>,
169    /// When the policy asked to be ticked.
170    pub wake_at: Option<Instant>,
171    /// The cursor the policy shows while it holds the pointer; `None` leaves the client's.
172    pub cursor: Option<CursorIcon>,
173    /// Whether the policy took the last pointer event, so the clients saw the pointer leave.
174    pub pointer_taken: bool,
175    /// Where the policy took the last pointer motion, while it keeps taking pointer events.
176    pub taken_at: Option<Point<f64, Logical>>,
177    /// The outputs the policy was last told about.
178    pub outputs: Vec<OutputInfo>,
179    /// The focus the policy last asked for.
180    pub focus: Option<WindowId>,
181    /// Something the policy presents changed since the last render.
182    pub dirty: bool,
183}
184
185impl WmState {
186    fn new(policy: Box<dyn WindowPolicy>) -> WmState {
187        WmState {
188            policy: Some(policy),
189            frames: HashMap::new(),
190            overlays: BTreeMap::new(),
191            effects: HashMap::new(),
192            ghosts: HashMap::new(),
193            hidden: HashMap::new(),
194            rects: HashMap::new(),
195            states: HashMap::new(),
196            decorations: HashMap::new(),
197            known: HashMap::new(),
198            pending_moves: HashMap::new(),
199            acked: HashMap::new(),
200            wake_at: None,
201            cursor: None,
202            pointer_taken: false,
203            taken_at: None,
204            outputs: Vec::new(),
205            focus: None,
206            dirty: false,
207        }
208    }
209}
210
211/// Whether a policy is installed and alive.
212pub fn active(state: &AppState) -> bool {
213    state.wm.as_ref().is_some_and(|wm| wm.policy.is_some())
214}
215
216/// Install a policy: it is told the outputs and every window already shown, so a window
217/// configured fullscreen before it came is placed the way it would have been.
218pub fn install(state: &mut AppState, policy: Box<dyn WindowPolicy>) {
219    if let Some(old) = state.wm.take() {
220        drop(old);
221    }
222    state.wm = Some(WmState::new(policy));
223    println!("[Wayland] Window policy installed: the compositor manages its windows.");
224    outputs_changed(state);
225    let windows: Vec<Window> = state.space.elements().cloned().collect();
226    for window in windows {
227        let Some(meta) = window_meta(&window) else {
228            continue;
229        };
230        if meta.parked.load(std::sync::atomic::Ordering::Relaxed) {
231            continue;
232        }
233        window_added(state, &window);
234        let size = window.geometry().size;
235        window_mapped(
236            state,
237            &window,
238            Size {
239                w: size.w,
240                h: size.h,
241            },
242        );
243    }
244}
245
246/// The policy's decision about an event; `Pass` with no policy.
247pub fn dispatch(state: &mut AppState, event: Event) -> Verdict {
248    let Some(wm) = state.wm.as_mut() else {
249        return Verdict::Pass;
250    };
251    let Some(policy) = wm.policy.as_mut() else {
252        return Verdict::Pass;
253    };
254    let mut out = Vec::new();
255    let result = catch_unwind(AssertUnwindSafe(|| policy.handle(event, &mut out)));
256    match result {
257        Ok(verdict) => {
258            for command in out {
259                apply(state, command);
260            }
261            verdict
262        }
263        Err(payload) => {
264            let what = payload
265                .downcast_ref::<String>()
266                .cloned()
267                .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
268                .unwrap_or_else(|| "no message".to_string());
269            eprintln!(
270                "[Wayland] The window policy panicked ({what}); windows are shown without it from here on."
271            );
272            uninstall(state);
273            Verdict::Pass
274        }
275    }
276}
277
278/// The desktop as the installed policy holds it, each window's origin moved to its surface's
279/// corner; `None` with no policy, and after a panic in this call, which drops the policy as one
280/// in [`dispatch`] does.
281pub fn view(state: &mut AppState) -> Option<DesktopView> {
282    let policy = state.wm.as_ref()?.policy.as_ref()?;
283    let Ok(mut view) = catch_unwind(AssertUnwindSafe(|| policy.view())) else {
284        eprintln!(
285            "[Wayland] The window policy panicked describing the desktop; windows are shown without it from here on."
286        );
287        uninstall(state);
288        return None;
289    };
290    for window in &mut view.windows {
291        if let Some(found) = find_window(state, window.info.id) {
292            let offset = found.geometry().loc;
293            window.origin = (window.rect.x - offset.x, window.rect.y - offset.y);
294        }
295    }
296    Some(view)
297}
298
299/// Drop the policy and show every window the way the frontend does without one.
300///
301/// Beside the windows, everything the policy set outside its own state goes back to the
302/// frontend's: the client's cursor in place of one the policy showed, a single virtual desktop
303/// for the pagers, since every window is now shown, and the keyboard on the topmost window where
304/// the policy left it on nothing or on a window no longer shown.
305fn uninstall(state: &mut AppState) {
306    let Some(mut wm) = state.wm.take() else {
307        return;
308    };
309    wm.policy = None;
310    let hidden: Vec<Window> = wm.hidden.drain().map(|(_, w)| w).collect();
311    for window in hidden {
312        let output = window_output_id(&window);
313        state.place_window_on_output(&window, output);
314    }
315    let windows: Vec<Window> = state.space.elements().cloned().collect();
316    for window in windows {
317        let output = window_output_id(&window);
318        state.place_window_on_output(&window, output);
319    }
320    for node in &mut state.output_nodes {
321        if let Some(cap) = node.capture.as_mut() {
322            cap.needs_full_render = true;
323            cap.change_pending = true;
324        }
325    }
326    state.wm = None;
327    if wm.cursor.is_some() {
328        let image = state
329            .current_cursor_icon
330            .clone()
331            .unwrap_or(CursorImageStatus::Named(CursorIcon::Default));
332        state.send_cursor_image(&image);
333    }
334    crate::wayland::workspace::set_desktops(state, 1, 0);
335    if let Some(keyboard) = state.seat.get_keyboard() {
336        let kept = match keyboard.current_focus() {
337            Some(FocusTarget::Window(window)) => state.space.elements().any(|w| *w == window),
338            Some(_) => true,
339            None => false,
340        };
341        if !kept {
342            let top = state.space.elements().last().cloned();
343            keyboard.set_focus(state, top.map(FocusTarget::Window), next_serial());
344        }
345    }
346    state.refocus_pointer();
347}
348
349/// Every window the compositor has by id: shown, pending, or hidden.
350pub fn find_window(state: &AppState, id: WindowId) -> Option<Window> {
351    state
352        .space
353        .elements()
354        .chain(state.pending_windows.iter())
355        .find(|w| window_meta(w).is_some_and(|m| m.id == id))
356        .cloned()
357        .or_else(|| state.wm.as_ref().and_then(|wm| wm.hidden.get(&id).cloned()))
358        .or_else(|| state.xwm.windows.get(&id).cloned())
359}
360
361/// The layer surface on the top or overlay layer that asks for the keyboard exclusively (a shell's
362/// open menu or launcher), if one does.
363fn exclusive_layer(state: &AppState) -> Option<smithay::desktop::LayerSurface> {
364    use smithay::utils::IsAlive;
365    use smithay::wayland::shell::wlr_layer::KeyboardInteractivity;
366    state.output_nodes.iter().find_map(|node| {
367        layer_map_for_output(&node.output)
368            .layers()
369            .find(|layer| {
370                layer.alive()
371                    && matches!(layer.layer(), Layer::Top | Layer::Overlay)
372                    && layer.cached_state().keyboard_interactivity
373                        == KeyboardInteractivity::Exclusive
374            })
375            .cloned()
376    })
377}
378
379/// Give the keyboard to a layer surface that asks for it exclusively, or, once none does and a
380/// layer surface still holds it, back to the window the policy focused (the topmost window
381/// without a policy). Run when a layer surface commits or goes away, since that is when its
382/// keyboard interactivity changes; without it a shell's menu or launcher on Wayland would never
383/// see a key.
384pub fn sync_layer_keyboard(state: &mut AppState) {
385    let Some(keyboard) = state.seat.get_keyboard() else {
386        return;
387    };
388    let current = keyboard.current_focus();
389    match exclusive_layer(state) {
390        Some(layer) => {
391            let target = FocusTarget::LayerSurface(layer);
392            if current.as_ref() != Some(&target) {
393                keyboard.set_focus(state, Some(target), next_serial());
394            }
395        }
396        None => {
397            if matches!(current, Some(FocusTarget::LayerSurface(_))) {
398                let window = match state.wm.as_ref() {
399                    Some(wm) => wm.focus.and_then(|id| find_window(state, id)),
400                    None => state.space.elements().last().cloned(),
401                };
402                keyboard.set_focus(state, window.map(FocusTarget::Window), next_serial());
403            }
404        }
405    }
406}
407
408fn window_id(window: &Window) -> Option<WindowId> {
409    window_meta(window).map(|m| m.id)
410}
411
412fn window_id_of_surface(
413    state: &AppState,
414    surface: &smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
415) -> Option<WindowId> {
416    state
417        .space
418        .elements()
419        .chain(state.pending_windows.iter())
420        .chain(state.wm.iter().flat_map(|wm| wm.hidden.values()))
421        .find(|w| w.wl_surface().as_deref() == Some(surface))
422        .and_then(window_id)
423}
424
425/// What the policy is told about a window.
426pub fn window_info(state: &AppState, window: &Window) -> WindowInfo {
427    let id = window_id(window).unwrap_or(0);
428    let output = window_output_id(window);
429    // An X11 client without Motif hints leaves decorations to the manager; an xdg toplevel
430    // that never created a decoration object (GTK 3) draws its own, as labwc treats it.
431    let decoration = state
432        .wm
433        .as_ref()
434        .and_then(|wm| wm.decorations.get(&id).copied())
435        .unwrap_or(if window.x11_surface().is_some() {
436            Decoration::Unspecified
437        } else {
438            Decoration::Client
439        });
440    if let Some(surface) = window.x11_surface() {
441        return crate::wayland::xwm::window_info(state, id, surface, output, decoration);
442    }
443    let Some(toplevel) = window.toplevel() else {
444        return WindowInfo {
445            id,
446            output,
447            ..WindowInfo::default()
448        };
449    };
450    let (title, app_id, min, max, parent) = with_states(toplevel.wl_surface(), |states| {
451        let (min, max) = {
452            let mut cached = states.cached_state.get::<SurfaceCachedState>();
453            let current = cached.current();
454            (current.min_size, current.max_size)
455        };
456        let attributes = states
457            .data_map
458            .get::<XdgToplevelSurfaceData>()
459            .map(|d| d.lock().unwrap());
460        match attributes {
461            Some(a) => (
462                a.title.clone().unwrap_or_default(),
463                a.app_id.clone().unwrap_or_default(),
464                min,
465                max,
466                a.parent.clone(),
467            ),
468            None => (String::new(), String::new(), min, max, None),
469        }
470    });
471    let parent = parent.and_then(|p| window_id_of_surface(state, &p));
472    let (wants_fullscreen, wants_maximized) =
473        toplevel.with_committed_state(|committed| match committed {
474            Some(c) => (
475                c.states.contains(XdgState::Fullscreen),
476                c.states.contains(XdgState::Maximized),
477            ),
478            None => (false, false),
479        });
480    WindowInfo {
481        id,
482        kind: Some(WindowKind::Xdg),
483        window_type: if parent.is_some() {
484            WindowType::Dialog
485        } else {
486            WindowType::Normal
487        },
488        title,
489        app_id,
490        instance: String::new(),
491        parent,
492        modal: false,
493        hints: crate::wm::SizeHints {
494            min: (min.w > 0 || min.h > 0).then_some(Size { w: min.w, h: min.h }),
495            max: (max.w > 0 || max.h > 0).then_some(Size { w: max.w, h: max.h }),
496            increment: None,
497            base: None,
498        },
499        requested: None,
500        positioned: false,
501        decoration,
502        output,
503        pid: toplevel
504            .wl_surface()
505            .client()
506            .and_then(|c| c.get_credentials(&state.dh).ok())
507            .map(|c| c.pid as u32),
508        wants_fullscreen,
509        wants_maximized,
510        wants_minimized: false,
511        skip_taskbar: false,
512        accepts_focus: true,
513    }
514}
515
516/// Tell the policy about a window waiting for its first configure.
517pub fn window_added(state: &mut AppState, window: &Window) {
518    let Some(id) = window_id(window) else {
519        return;
520    };
521    if !active(state) {
522        return;
523    }
524    let info = window_info(state, window);
525    if let Some(wm) = state.wm.as_mut() {
526        wm.known.insert(id, Size::default());
527    }
528    dispatch(state, Event::Added(info));
529}
530
531/// Tell the policy a window's details changed.
532pub fn window_changed(state: &mut AppState, window: &Window) {
533    let Some(id) = window_id(window) else {
534        return;
535    };
536    if !active(state)
537        || !state
538            .wm
539            .as_ref()
540            .is_some_and(|wm| wm.known.contains_key(&id))
541    {
542        return;
543    }
544    let info = window_info(state, window);
545    dispatch(state, Event::Changed(info));
546}
547
548/// The window drew its first buffer: it is on screen at this size.
549pub fn window_mapped(state: &mut AppState, window: &Window, size: Size) {
550    let Some(id) = window_id(window) else {
551        return;
552    };
553    if !active(state) {
554        return;
555    }
556    if !state
557        .wm
558        .as_ref()
559        .is_some_and(|wm| wm.known.contains_key(&id))
560    {
561        window_added(state, window);
562    }
563    if let Some(wm) = state.wm.as_mut() {
564        wm.known.insert(id, size);
565    }
566    dispatch(state, Event::Mapped { id, size });
567    mark_dirty(state);
568}
569
570/// A mapped window committed a size. The policy is told when it differs from the last, and
571/// also when the commit answers a configure with a size other than the one asked for, which
572/// a client snapping to its cell size does without changing size at all; a move held for the
573/// configure is carried out on that answer, with whatever size the client chose.
574pub fn window_resized(state: &mut AppState, window: &Window, size: Size) {
575    let Some(id) = window_id(window) else {
576        return;
577    };
578    let acked = window
579        .toplevel()
580        .and_then(|toplevel| {
581            toplevel.with_committed_state(|committed| committed.and_then(|c| c.size))
582        })
583        .map(|s| Size { w: s.w, h: s.h });
584    let Some(wm) = state.wm.as_mut() else {
585        return;
586    };
587    let Some(last) = wm.known.get_mut(&id) else {
588        return;
589    };
590    let changed = *last != size;
591    *last = size;
592    let acked_now = acked.is_some() && wm.acked.insert(id, acked) != Some(acked);
593    let requested = wm.rects.get(&id).map(|r| Size { w: r.w, h: r.h });
594    let answered = acked_now && requested.is_some_and(|r| r != size);
595    let pending = if changed || acked_now {
596        match wm.pending_moves.get(&id).copied() {
597            Some(held) if held.holds_edge() && requested != Some(size) => Some(held),
598            _ => wm.pending_moves.remove(&id),
599        }
600    } else {
601        None
602    };
603    if let Some(pending) = pending {
604        let location = pending.location(size);
605        relocate(state, window, location);
606        let rect = Rect::new(location.0, location.1, size.w, size.h);
607        move_to_output_under(state, window, rect);
608        state.refocus_pointer();
609        mark_dirty(state);
610    }
611    if (changed || answered) && active(state) {
612        dispatch(state, Event::Resized { id, size });
613    }
614}
615
616/// The client withdrew its window.
617pub fn window_unmapped(state: &mut AppState, window: &Window) {
618    let Some(id) = window_id(window) else {
619        return;
620    };
621    if let Some(wm) = state.wm.as_mut() {
622        wm.frames.remove(&id);
623        wm.effects.remove(&id);
624        wm.hidden.remove(&id);
625    }
626    if active(state)
627        && state
628            .wm
629            .as_ref()
630            .is_some_and(|wm| wm.known.contains_key(&id))
631    {
632        dispatch(state, Event::Unmapped { id });
633        mark_dirty(state);
634    }
635}
636
637/// The window is gone.
638pub fn window_removed(state: &mut AppState, id: WindowId) {
639    let known = state
640        .wm
641        .as_ref()
642        .is_some_and(|wm| wm.known.contains_key(&id));
643    if let Some(wm) = state.wm.as_mut() {
644        wm.frames.remove(&id);
645        // A ghost's effect outlives the window: the policy clears it when the fade ends.
646        if !wm.ghosts.contains_key(&id) {
647            wm.effects.remove(&id);
648        }
649        wm.hidden.remove(&id);
650        wm.rects.remove(&id);
651        wm.states.remove(&id);
652        wm.decorations.remove(&id);
653        wm.known.remove(&id);
654        wm.pending_moves.remove(&id);
655        wm.acked.remove(&id);
656        if wm.focus == Some(id) {
657            wm.focus = None;
658        }
659    }
660    if known && active(state) {
661        dispatch(state, Event::Removed { id });
662        mark_dirty(state);
663    }
664}
665
666/// A protocol request about a window.
667pub fn request(state: &mut AppState, id: WindowId, request: Request) -> bool {
668    if !active(state) {
669        return false;
670    }
671    dispatch(state, Event::Request { id, request });
672    true
673}
674
675/// Record what a client said about who decorates it, telling the policy where it knows the
676/// window.
677pub fn set_decoration(state: &mut AppState, window: &Window, decoration: Decoration) {
678    let Some(id) = window_id(window) else {
679        return;
680    };
681    if let Some(wm) = state.wm.as_mut() {
682        wm.decorations.insert(id, decoration);
683    }
684    window_changed(state, window);
685}
686
687/// The policy's decoration choice for a window: whether it draws a frame, so the client is
688/// told the matching xdg-decoration mode.
689pub fn decorates(state: &AppState, id: WindowId) -> Option<bool> {
690    state
691        .wm
692        .as_ref()
693        .filter(|wm| wm.policy.is_some())
694        .map(|wm| wm.frames.contains_key(&id))
695}
696
697/// Tell the policy the layout: every output with its workarea, when it differs from the last.
698pub fn outputs_changed(state: &mut AppState) {
699    if !active(state) {
700        return;
701    }
702    let mut outputs = Vec::new();
703    for node in state.output_nodes.iter().filter(|n| n.owner.is_none()) {
704        let Some(geo) = node.logical_geometry() else {
705            continue;
706        };
707        let zone = layer_map_for_output(&node.output).non_exclusive_zone();
708        outputs.push(OutputInfo {
709            id: node.id,
710            rect: Rect::new(geo.loc.x, geo.loc.y, geo.size.w, geo.size.h),
711            workarea: Rect::new(
712                geo.loc.x + zone.loc.x,
713                geo.loc.y + zone.loc.y,
714                zone.size.w,
715                zone.size.h,
716            ),
717            scale: node.output.current_scale().fractional_scale(),
718            primary: node.id == 0,
719        });
720    }
721    let same = state.wm.as_ref().is_some_and(|wm| wm.outputs == outputs);
722    if same {
723        return;
724    }
725    if let Some(wm) = state.wm.as_mut() {
726        wm.outputs.clone_from(&outputs);
727    }
728    dispatch(state, Event::Outputs(outputs));
729    mark_dirty(state);
730}
731
732/// A taskbar said where it shows a window.
733pub fn taskbar_rect(state: &mut AppState, id: WindowId, rect: Option<Rect>) {
734    if active(state) {
735        dispatch(state, Event::TaskbarRect { id, rect });
736    }
737}
738
739/// A shell asked to show or restore the desktop.
740pub fn show_desktop(state: &mut AppState, on: bool) {
741    if active(state) {
742        dispatch(state, Event::ShowDesktop(on));
743    }
744}
745
746/// A pager asked to switch to a virtual desktop, counted from zero.
747pub fn activate_desktop(state: &mut AppState, index: u32) {
748    if active(state) {
749        dispatch(state, Event::ActivateDesktop(index));
750    }
751}
752
753/// The modifiers the seat's keyboard holds.
754fn modifiers(state: &AppState) -> Modifiers {
755    let mods: Option<ModifiersState> = state.seat.get_keyboard().map(|k| k.modifier_state());
756    match mods {
757        Some(m) => Modifiers {
758            shift: m.shift,
759            ctrl: m.ctrl,
760            alt: m.alt,
761            logo: m.logo,
762            caps_lock: m.caps_lock,
763            num_lock: m.num_lock,
764        },
765        None => Modifiers::default(),
766    }
767}
768
769/// Where a button or a scroll is judged, and where a relative delta adds to: where the policy
770/// took the last motion while it keeps taking pointer events, else the seat's pointer. The seat's
771/// pointer follows a motion the policy takes only while no client holds a pointer grab, so with
772/// a client's popup grab held a click on the policy's menu would otherwise land where the pointer
773/// stood before the menu took it, and every delta of a frame drag, whose press grabs for the
774/// client, would start from where the drag began.
775pub fn judged_location(state: &AppState, seat: Point<f64, Logical>) -> Point<f64, Logical> {
776    state
777        .wm
778        .as_ref()
779        .filter(|wm| wm.pointer_taken)
780        .and_then(|wm| wm.taken_at)
781        .unwrap_or(seat)
782}
783
784/// What the compositor's hit test found, for the policy. A popup, a Wayland one or an X11
785/// override-redirect window (a menu, a tooltip), is reported as the top layer: the policy never
786/// hears of an X11 one, so its id would match no window and the press would read as the root's.
787pub fn under_of(_state: &AppState, target: &Option<(FocusTarget, Point<f64, Logical>)>) -> Under {
788    match target.as_ref().map(|(target, at)| (target.owner(), at)) {
789        Some((FocusTarget::Window(window), _))
790            if crate::wayland::frontend::is_override_redirect(window) =>
791        {
792            Under {
793                window: None,
794                top_layer: true,
795                desktop: false,
796            }
797        }
798        Some((FocusTarget::Window(window), _)) => Under {
799            window: window_id(window),
800            top_layer: false,
801            desktop: false,
802        },
803        Some((FocusTarget::LayerSurface(layer), _)) => Under {
804            window: None,
805            top_layer: matches!(layer.layer(), Layer::Top | Layer::Overlay),
806            desktop: matches!(layer.layer(), Layer::Background | Layer::Bottom),
807        },
808        Some((FocusTarget::Popup(_), _)) => Under {
809            window: None,
810            top_layer: true,
811            desktop: false,
812        },
813        // `owner` is never a subsurface.
814        None | Some((FocusTarget::Subsurface(..), _)) => Under::default(),
815    }
816}
817
818/// The policy's verdict on a pointer event at a logical layout point. A taken event makes
819/// the clients see the pointer leave, so a frame drag never scrolls or clicks what lies under
820/// the frame, and still moves the seat's pointer, since a button is judged at the seat's
821/// location: a menu whose motion the policy took would otherwise see every click at the spot
822/// it opened on. Under a client's grab the seat's pointer is not moved, so the first event the
823/// policy passes after taking some brings it to where the policy last took the pointer: the
824/// release that ends a frame drag reaches the client there, and the next relative delta adds to
825/// the drag's end rather than its start.
826pub fn pointer(
827    state: &mut AppState,
828    kind: PointerKind,
829    p: Point<f64, Logical>,
830    under: Under,
831) -> Verdict {
832    if !active(state) {
833        return Verdict::Pass;
834    }
835    let event = PointerEvent {
836        x: p.x,
837        y: p.y,
838        kind,
839        modifiers: modifiers(state),
840        time: wayland_time().millis(),
841    };
842    let behind = state
843        .wm
844        .as_ref()
845        .filter(|wm| wm.pointer_taken)
846        .and_then(|wm| wm.taken_at);
847    let verdict = dispatch(state, Event::Pointer(event, under));
848    let taken = verdict == Verdict::Consumed;
849    if !taken
850        && let Some(at) = behind
851        && let Some(pointer) = state.seat.get_pointer()
852        && pointer.current_location() != at
853    {
854        let focus = state.pointer_target_under(at);
855        pointer.motion(
856            state,
857            focus,
858            &smithay::input::pointer::MotionEvent {
859                location: at,
860                serial: next_serial(),
861                time: wayland_time(),
862            },
863        );
864        pointer.frame(state);
865    }
866    if let Some(wm) = state.wm.as_mut() {
867        wm.pointer_taken = taken;
868        if !taken {
869            wm.taken_at = None;
870        } else if kind == PointerKind::Motion {
871            wm.taken_at = Some(p);
872        }
873    }
874    if taken
875        && let Some(pointer) = state.seat.get_pointer()
876        && !pointer.is_grabbed()
877    {
878        pointer.motion(
879            state,
880            None,
881            &smithay::input::pointer::MotionEvent {
882                location: p,
883                serial: next_serial(),
884                time: wayland_time(),
885            },
886        );
887        pointer.frame(state);
888    }
889    verdict
890}
891
892/// The policy's verdict on a key, with the keysyms the seat's keymap resolved.
893pub fn key(
894    state: &mut AppState,
895    keysym: u32,
896    base_keysym: u32,
897    keycode: u32,
898    pressed: bool,
899) -> Verdict {
900    if !active(state) {
901        return Verdict::Pass;
902    }
903    let event = KeyEvent {
904        keysym,
905        base_keysym,
906        keycode,
907        pressed,
908        modifiers: modifiers(state),
909    };
910    dispatch(state, Event::Key(event))
911}
912
913/// The surface of the window the policy gave the keyboard to, if it is on the layout.
914pub fn focused_surface(
915    state: &AppState,
916) -> Option<smithay::reexports::wayland_server::protocol::wl_surface::WlSurface> {
917    let focus = state.wm.as_ref()?.focus?;
918    let window = state
919        .space
920        .elements()
921        .find(|w| window_id(w) == Some(focus))?;
922    match (window.toplevel(), window.x11_surface()) {
923        (Some(toplevel), _) => Some(toplevel.wl_surface().clone()),
924        (None, Some(x11)) => x11.wl_surface(),
925        (None, None) => None,
926    }
927}
928
929/// Whether the focused window holds an active shortcuts inhibitor, in which case every key is
930/// the client's and the policy's binds stand aside.
931fn shortcuts_inhibited(state: &AppState) -> bool {
932    focused_surface(state).is_some_and(|surface| {
933        state
934            .seat
935            .keyboard_shortcuts_inhibitor_for_surface(&surface)
936            .is_some_and(|inhibitor| inhibitor.is_active())
937    })
938}
939
940/// The keyboard filter the seat's `input` takes: the policy's verdict as an intercept, with the
941/// keysyms the seat's keymap resolved for the key.
942pub fn key_filter(
943    state: &mut AppState,
944    handle: smithay::input::keyboard::KeysymHandle<'_>,
945    pressed: bool,
946) -> smithay::input::keyboard::FilterResult<()> {
947    if !active(state) || shortcuts_inhibited(state) {
948        return smithay::input::keyboard::FilterResult::Forward;
949    }
950    let keysym = handle.modified_sym().raw();
951    let base_keysym = handle.raw_syms().first().map(|k| k.raw()).unwrap_or(keysym);
952    let keycode = handle.raw_code().raw();
953    match key(state, keysym, base_keysym, keycode, pressed) {
954        Verdict::Consumed => smithay::input::keyboard::FilterResult::Intercept(()),
955        Verdict::Pass => smithay::input::keyboard::FilterResult::Forward,
956    }
957}
958
959/// Whether the policy asked for a tick by `now`.
960pub fn tick_due(state: &AppState, now: Instant) -> bool {
961    state
962        .wm
963        .as_ref()
964        .and_then(|wm| wm.wake_at)
965        .is_some_and(|at| at <= now)
966}
967
968/// When the policy next wants a tick.
969pub fn next_wake(state: &AppState) -> Option<Instant> {
970    state.wm.as_ref().and_then(|wm| wm.wake_at)
971}
972
973/// Tick the policy; whether anything it presents changed.
974pub fn tick(state: &mut AppState, now: Instant) -> bool {
975    if !active(state) {
976        return false;
977    }
978    if let Some(wm) = state.wm.as_mut() {
979        wm.wake_at = None;
980    }
981    dispatch(state, Event::Tick(now));
982    sweep_ghosts(state, now);
983    take_dirty(state)
984}
985
986/// Drop the ghosts the policy never animated and the ones past their lifetime.
987fn sweep_ghosts(state: &mut AppState, now: Instant) {
988    let Some(wm) = state.wm.as_mut() else {
989        return;
990    };
991    let before = wm.ghosts.len();
992    wm.ghosts.retain(|id, ghost| {
993        let age = now.saturating_duration_since(ghost.taken);
994        age < GHOST_LIFETIME && (wm.effects.contains_key(id) || age < GHOST_UNCLAIMED)
995    });
996    if wm.ghosts.len() != before {
997        wm.dirty = true;
998    }
999}
1000
1001/// Keep the last picture of a mapped window about to lose its buffer, with the frame it
1002/// wears, so the policy's close effect can shrink and fade it. Taken once per window: a client
1003/// that attaches a null buffer and then destroys its toplevel is snapshotted on the first.
1004pub fn snapshot(state: &mut AppState, window: &Window) {
1005    let Some(id) = window_id(window) else {
1006        crate::log::debug!("[Wayland] No ghost kept: the window has no id");
1007        return;
1008    };
1009    let known = state
1010        .wm
1011        .as_ref()
1012        .is_some_and(|wm| wm.known.contains_key(&id) && !wm.ghosts.contains_key(&id));
1013    if !known || !active(state) {
1014        crate::log::debug!(
1015            "[Wayland] Window {id}: no ghost kept, not known to the policy or one kept already"
1016        );
1017        return;
1018    }
1019    let surface = match (window.toplevel(), window.x11_surface()) {
1020        (Some(toplevel), _) => toplevel.wl_surface().clone(),
1021        (None, Some(x11)) => match x11.wl_surface() {
1022            Some(surface) => surface,
1023            None => {
1024                crate::log::debug!("[Wayland] Window {id}: no ghost kept, no surface");
1025                return;
1026            }
1027        },
1028        (None, None) => return,
1029    };
1030    let Some(location) = state.space.element_location(window) else {
1031        return;
1032    };
1033    let geometry = window.geometry();
1034    let Some((view, texture)) = with_renderer_surface_state(&surface, |s| {
1035        let view = s.view()?;
1036        let texture = if let Some(gles) = state.gles_renderer.as_ref() {
1037            s.texture(gles.context_id())
1038                .cloned()
1039                .map(|t| GhostTexture::Context(Box::new(t)))
1040        } else {
1041            s.buffer()
1042                .and_then(|buffer| copy_shm(buffer).map(GhostTexture::Memory))
1043        }?;
1044        Some((view, texture))
1045    })
1046    .flatten() else {
1047        return;
1048    };
1049    let surface_loc = location - geometry.loc + view.offset;
1050    let ghost = Ghost {
1051        texture,
1052        id: Id::new(),
1053        surface: Rect::new(surface_loc.x, surface_loc.y, view.dst.w, view.dst.h),
1054        geometry: Rect::new(location.x, location.y, geometry.size.w, geometry.size.h),
1055        frame: state.wm.as_ref().and_then(|wm| wm.frames.get(&id).cloned()),
1056        taken: Instant::now(),
1057    };
1058    if let Some(wm) = state.wm.as_mut() {
1059        wm.ghosts.insert(id, ghost);
1060    }
1061    crate::log::debug!("[Wayland] Window {id}: ghost kept for its close effect");
1062}
1063
1064/// A copy of a shared-memory buffer's pixels as a render buffer, `None` for a buffer that is
1065/// not shared memory or whose format the renderers do not take.
1066fn copy_shm(
1067    buffer: &smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer,
1068) -> Option<MemoryRenderBuffer> {
1069    use smithay::wayland::shm::{shm_format_to_fourcc, with_buffer_contents};
1070    with_buffer_contents(buffer, |ptr, len, data| {
1071        let format = shm_format_to_fourcc(data.format)?;
1072        let bpp = match format {
1073            Fourcc::Argb8888 | Fourcc::Xrgb8888 | Fourcc::Abgr8888 | Fourcc::Xbgr8888 => 4,
1074            _ => return None,
1075        };
1076        let (width, height, stride, offset) = (
1077            data.width.max(0) as usize,
1078            data.height.max(0) as usize,
1079            data.stride.max(0) as usize,
1080            data.offset.max(0) as usize,
1081        );
1082        if width == 0 || height == 0 || offset + stride * height > len || width * bpp > stride {
1083            return None;
1084        }
1085        // SAFETY: the pool is mapped for `len` bytes and the rows read stay within it, as
1086        // checked above; with_buffer_contents holds the pool for the duration.
1087        let bytes = unsafe { std::slice::from_raw_parts(ptr.add(offset), stride * height) };
1088        let mut tight = Vec::with_capacity(width * bpp * height);
1089        for row in bytes.chunks_exact(stride) {
1090            tight.extend_from_slice(&row[..width * bpp]);
1091        }
1092        Some(MemoryRenderBuffer::from_slice(
1093            &tight,
1094            format,
1095            (width as i32, height as i32),
1096            1,
1097            OutputTransform::Normal,
1098            None,
1099        ))
1100    })
1101    .ok()
1102    .flatten()
1103}
1104
1105/// A ghost's render elements: its picture as the renderer's texture or as the copied buffer,
1106/// and its frame parts.
1107pub struct GhostElements<R: Renderer> {
1108    pub texture: Option<TextureRenderElement<R::TextureId>>,
1109    pub memory: Option<MemoryRenderBufferRenderElement<R>>,
1110    pub frame: Vec<MemoryRenderBufferRenderElement<R>>,
1111}
1112
1113impl<R: Renderer> Default for GhostElements<R> {
1114    fn default() -> Self {
1115        GhostElements {
1116            texture: None,
1117            memory: None,
1118            frame: Vec::new(),
1119        }
1120    }
1121}
1122
1123/// The render elements of a ghost through its effect: the picture scaled from the client area
1124/// it stood at into the effect's rectangle and blended at its alpha, with the frame parts
1125/// around it at the same scale. Nothing for a ghost without an effect.
1126pub fn ghost_elements<R>(
1127    wm: Option<&WmState>,
1128    renderer: &mut R,
1129    id: WindowId,
1130    origin: Point<i32, Logical>,
1131    scale: f64,
1132) -> GhostElements<R>
1133where
1134    R: Renderer + ImportMem,
1135    R::TextureId: Texture + Clone + Send + 'static,
1136{
1137    let mut out = GhostElements::default();
1138    let Some(wm) = wm else {
1139        return out;
1140    };
1141    let (Some(ghost), Some(effect)) = (wm.ghosts.get(&id), wm.effects.get(&id)) else {
1142        return out;
1143    };
1144    if effect.alpha <= 0.0 || effect.rect.w <= 0 || effect.rect.h <= 0 {
1145        return out;
1146    }
1147    let sx = f64::from(effect.rect.w) / f64::from(ghost.geometry.w.max(1));
1148    let sy = f64::from(effect.rect.h) / f64::from(ghost.geometry.h.max(1));
1149    let place = |rect: Rect| -> (Point<f64, Physical>, smithay::utils::Size<i32, Logical>) {
1150        let x = f64::from(effect.rect.x) + f64::from(rect.x - ghost.geometry.x) * sx
1151            - f64::from(origin.x);
1152        let y = f64::from(effect.rect.y) + f64::from(rect.y - ghost.geometry.y) * sy
1153            - f64::from(origin.y);
1154        (
1155            Point::<f64, Logical>::from((x, y)).to_physical(Scale::from(scale)),
1156            (
1157                (f64::from(rect.w) * sx).round().max(1.0) as i32,
1158                (f64::from(rect.h) * sy).round().max(1.0) as i32,
1159            )
1160                .into(),
1161        )
1162    };
1163    let (location, size) = place(ghost.surface);
1164    match &ghost.texture {
1165        GhostTexture::Context(boxed) => {
1166            if let Some(t) = boxed.downcast_ref::<R::TextureId>() {
1167                out.texture = Some(TextureRenderElement::from_static_texture(
1168                    ghost.id.clone(),
1169                    renderer.context_id(),
1170                    location,
1171                    t.clone(),
1172                    1,
1173                    OutputTransform::Normal,
1174                    Some(effect.alpha),
1175                    None,
1176                    Some(size),
1177                    None,
1178                    Kind::Unspecified,
1179                ));
1180            }
1181        }
1182        GhostTexture::Memory(buffer) => {
1183            out.memory = MemoryRenderBufferRenderElement::from_buffer(
1184                renderer,
1185                location,
1186                buffer,
1187                Some(effect.alpha),
1188                None,
1189                Some(size),
1190                Kind::Unspecified,
1191            )
1192            .ok();
1193        }
1194    }
1195    if let Some(frame) = ghost.frame.as_ref() {
1196        for (rect, buffer) in &frame.parts {
1197            let (location, size) = place(Rect::new(
1198                ghost.geometry.x + rect.x,
1199                ghost.geometry.y + rect.y,
1200                rect.w,
1201                rect.h,
1202            ));
1203            if let Ok(element) = MemoryRenderBufferRenderElement::from_buffer(
1204                renderer,
1205                location,
1206                buffer,
1207                Some(effect.alpha),
1208                None,
1209                Some(size),
1210                Kind::Unspecified,
1211            ) {
1212                out.frame.push(element);
1213            }
1214        }
1215    }
1216    out
1217}
1218
1219/// The frame parts of a window through its effect: each placed and sized as the effect's
1220/// rectangle scales the client area, blended at its alpha.
1221pub fn frame_elements_through<R>(
1222    wm: Option<&WmState>,
1223    renderer: &mut R,
1224    id: WindowId,
1225    client: Rect,
1226    effect: &Transform,
1227    origin: Point<i32, Logical>,
1228    scale: f64,
1229) -> Vec<MemoryRenderBufferRenderElement<R>>
1230where
1231    R: Renderer + ImportMem,
1232    R::TextureId: Texture + Clone + Send + 'static,
1233{
1234    let mut elements = Vec::new();
1235    let Some(frame) = wm.and_then(|wm| wm.frames.get(&id)) else {
1236        return elements;
1237    };
1238    let sx = f64::from(effect.rect.w) / f64::from(client.w.max(1));
1239    let sy = f64::from(effect.rect.h) / f64::from(client.h.max(1));
1240    for (rect, buffer) in &frame.parts {
1241        let x = f64::from(effect.rect.x) + f64::from(rect.x) * sx - f64::from(origin.x);
1242        let y = f64::from(effect.rect.y) + f64::from(rect.y) * sy - f64::from(origin.y);
1243        let location = Point::<f64, Logical>::from((x, y)).to_physical(Scale::from(scale));
1244        let size: smithay::utils::Size<i32, Logical> = (
1245            (f64::from(rect.w) * sx).round().max(1.0) as i32,
1246            (f64::from(rect.h) * sy).round().max(1.0) as i32,
1247        )
1248            .into();
1249        if let Ok(element) = MemoryRenderBufferRenderElement::from_buffer(
1250            renderer,
1251            location,
1252            buffer,
1253            Some(effect.alpha),
1254            None,
1255            Some(size),
1256            Kind::Unspecified,
1257        ) {
1258            elements.push(element);
1259        }
1260    }
1261    elements
1262}
1263
1264/// Note that something the policy presents changed, so the captures render a frame.
1265pub fn mark_dirty(state: &mut AppState) {
1266    if let Some(wm) = state.wm.as_mut() {
1267        wm.dirty = true;
1268    }
1269    for node in &mut state.output_nodes {
1270        if let Some(cap) = node.capture.as_mut() {
1271            cap.change_pending = true;
1272        }
1273    }
1274}
1275
1276/// Whether the policy's presentation changed since the last call.
1277pub fn take_dirty(state: &mut AppState) -> bool {
1278    state
1279        .wm
1280        .as_mut()
1281        .map(|wm| std::mem::take(&mut wm.dirty))
1282        .unwrap_or(false)
1283}
1284
1285/// Tell the policy an X server is up for its X11 clients.
1286pub fn x11_display(state: &mut AppState, display: u32) {
1287    if active(state) {
1288        dispatch(state, Event::X11Display(display));
1289    }
1290}
1291
1292fn buffer_of(image: &Image) -> MemoryRenderBuffer {
1293    MemoryRenderBuffer::from_slice(
1294        &image.rgba,
1295        Fourcc::Abgr8888,
1296        (image.width as i32, image.height as i32),
1297        image.scale.max(1),
1298        OutputTransform::Normal,
1299        None,
1300    )
1301}
1302
1303/// Moves a mapped window to a location and keeps its place in the stacking: Smithay's
1304/// `Space::map_element` raises what it maps, so the windows that stood above this one are
1305/// raised over it again in their order. A configure or a held move applied after the policy
1306/// restacked (a cascade laid out over tiles, the tiles' moves landing as their clients answer)
1307/// would otherwise lift each moved window over the ones the policy stacked above it. A window
1308/// mapped for the first time goes on top.
1309fn relocate(state: &mut AppState, window: &Window, location: (i32, i32)) {
1310    let above: Vec<Window> = state
1311        .space
1312        .elements()
1313        .skip_while(|w| *w != window)
1314        .skip(1)
1315        .cloned()
1316        .collect();
1317    state.space.map_element(window.clone(), location, false);
1318    for other in above {
1319        state.space.raise_element(&other, false);
1320    }
1321}
1322
1323/// Carry one command out.
1324fn apply(state: &mut AppState, command: Command) {
1325    match command {
1326        Command::Configure { id, rect, states } => configure(state, id, rect, states),
1327        Command::Stack(order) => {
1328            for id in order {
1329                if let Some(window) = find_window(state, id)
1330                    && state.space.elements().any(|w| *w == window)
1331                {
1332                    state.space.raise_element(&window, false);
1333                }
1334            }
1335            // The override-redirect X11 windows (a menu, a browser's permission bubble, a
1336            // tooltip) stay above the managed ones, in their own order, so they keep taking
1337            // the clicks aimed at them.
1338            let popups: Vec<Window> = state
1339                .space
1340                .elements()
1341                .filter(|w| w.x11_surface().is_some_and(|s| s.is_override_redirect()))
1342                .cloned()
1343                .collect();
1344            for window in popups {
1345                state.space.raise_element(&window, false);
1346            }
1347            state.refocus_pointer();
1348            mark_dirty(state);
1349        }
1350        Command::Focus(id) => {
1351            let target = id.and_then(|id| find_window(state, id));
1352            if let Some(wm) = state.wm.as_mut() {
1353                wm.focus = id;
1354            }
1355            // A shell surface holding the keyboard exclusively keeps it; the window the policy
1356            // focused takes the keyboard back when that surface lets it go.
1357            if exclusive_layer(state).is_none()
1358                && let Some(keyboard) = state.seat.get_keyboard()
1359            {
1360                let serial = next_serial();
1361                keyboard.set_focus(state, target.map(FocusTarget::Window), serial);
1362            }
1363        }
1364        Command::Close(id) => {
1365            if let Some(window) = find_window(state, id) {
1366                if let Some(toplevel) = window.toplevel() {
1367                    toplevel.send_close();
1368                }
1369                if let Some(surface) = window.x11_surface() {
1370                    let _ = surface.close();
1371                }
1372            }
1373        }
1374        Command::Frame { id, frame } => {
1375            set_frame(state, id, frame);
1376            mark_dirty(state);
1377        }
1378        Command::Overlay { key, rect, image } => {
1379            let buffer = buffer_of(&image);
1380            if let Some(wm) = state.wm.as_mut() {
1381                wm.overlays.insert(key, Overlay { rect, buffer });
1382            }
1383            mark_dirty(state);
1384        }
1385        Command::ClearOverlay(key) => {
1386            if let Some(wm) = state.wm.as_mut()
1387                && wm.overlays.remove(&key).is_some()
1388            {
1389                mark_dirty(state);
1390            }
1391        }
1392        Command::Cursor(name) => {
1393            let icon = if name == "default" {
1394                None
1395            } else {
1396                name.parse::<CursorIcon>().ok()
1397            };
1398            if let Some(wm) = state.wm.as_mut() {
1399                wm.cursor = icon;
1400            }
1401            let image = match icon {
1402                Some(icon) => CursorImageStatus::Named(icon),
1403                None => state
1404                    .current_cursor_icon
1405                    .clone()
1406                    .unwrap_or(CursorImageStatus::Named(CursorIcon::Default)),
1407            };
1408            state.send_cursor_image(&image);
1409            mark_dirty(state);
1410        }
1411        Command::Effect { id, transform } => {
1412            if let Some(wm) = state.wm.as_mut() {
1413                match transform {
1414                    Some(t) => {
1415                        wm.effects.insert(id, t);
1416                    }
1417                    None => {
1418                        wm.effects.remove(&id);
1419                        wm.ghosts.remove(&id);
1420                    }
1421                }
1422            }
1423            mark_dirty(state);
1424        }
1425        Command::WakeAt(at) => {
1426            if let Some(wm) = state.wm.as_mut() {
1427                wm.wake_at = Some(match wm.wake_at {
1428                    Some(current) => current.min(at),
1429                    None => at,
1430                });
1431            }
1432        }
1433        Command::Desktops { count, current } => {
1434            crate::wayland::workspace::set_desktops(state, count, current);
1435        }
1436        Command::Redraw => {
1437            for node in &mut state.output_nodes {
1438                if let Some(cap) = node.capture.as_mut() {
1439                    cap.needs_full_render = true;
1440                }
1441            }
1442            mark_dirty(state);
1443        }
1444    }
1445}
1446
1447fn set_frame(state: &mut AppState, id: WindowId, frame: Option<Frame>) {
1448    let Some(wm) = state.wm.as_mut() else {
1449        return;
1450    };
1451    match frame {
1452        Some(frame) => {
1453            let parts = frame
1454                .parts
1455                .iter()
1456                .map(|part| (part.rect, buffer_of(&part.image)))
1457                .collect();
1458            wm.frames.insert(
1459                id,
1460                FrameBuffers {
1461                    extents: frame.extents,
1462                    parts,
1463                },
1464            );
1465        }
1466        None => {
1467            wm.frames.remove(&id);
1468        }
1469    }
1470    sync_decoration_mode(state, id);
1471}
1472
1473/// Tell an xdg client the decoration mode the policy settled on: server-side where it draws
1474/// a frame, client-side where the client wanted its own and none is drawn.
1475fn sync_decoration_mode(state: &mut AppState, id: WindowId) {
1476    use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
1477    let Some(window) = find_window(state, id) else {
1478        return;
1479    };
1480    let Some(toplevel) = window.toplevel() else {
1481        return;
1482    };
1483    let framed = state
1484        .wm
1485        .as_ref()
1486        .is_some_and(|wm| wm.frames.contains_key(&id));
1487    let preference = state
1488        .wm
1489        .as_ref()
1490        .and_then(|wm| wm.decorations.get(&id).copied())
1491        .unwrap_or_default();
1492    let mode = if framed || preference != Decoration::Client {
1493        Mode::ServerSide
1494    } else {
1495        Mode::ClientSide
1496    };
1497    let current = toplevel.with_committed_state(|c| c.and_then(|c| c.decoration_mode));
1498    if current != Some(mode) {
1499        toplevel.with_pending_state(|s| {
1500            s.decoration_mode = Some(mode);
1501        });
1502        toplevel.send_pending_configure();
1503    }
1504}
1505
1506/// Place and size a window as the policy asked: an xdg configure for the size and states, a
1507/// Space mapping for the position, a Space unmapping for a minimized one or one on another
1508/// desktop, and the output's enter and leave as the window moves between outputs.
1509fn configure(state: &mut AppState, id: WindowId, rect: Rect, states: States) {
1510    if let Some(wm) = state.wm.as_mut() {
1511        wm.rects.insert(id, rect);
1512        wm.states.insert(id, states);
1513    }
1514    let Some(window) = find_window(state, id) else {
1515        return;
1516    };
1517    let in_space = state.space.elements().any(|w| *w == window);
1518    let hidden = state
1519        .wm
1520        .as_ref()
1521        .is_some_and(|wm| wm.hidden.contains_key(&id));
1522    if let Some(toplevel) = window.toplevel() {
1523        toplevel.with_pending_state(|s| {
1524            s.size = (rect.w > 0 && rect.h > 0).then(|| (rect.w, rect.h).into());
1525            set_state(&mut s.states, XdgState::Maximized, states.maximized);
1526            set_state(&mut s.states, XdgState::Fullscreen, states.fullscreen);
1527            set_state(&mut s.states, XdgState::Activated, states.activated);
1528            set_state(&mut s.states, XdgState::Resizing, states.resizing);
1529            if states.maximized || states.fullscreen {
1530                set_state(&mut s.states, XdgState::TiledTop, true);
1531                set_state(&mut s.states, XdgState::TiledBottom, true);
1532                set_state(&mut s.states, XdgState::TiledLeft, true);
1533                set_state(&mut s.states, XdgState::TiledRight, true);
1534            } else {
1535                set_state(&mut s.states, XdgState::TiledTop, false);
1536                set_state(&mut s.states, XdgState::TiledBottom, false);
1537                set_state(&mut s.states, XdgState::TiledLeft, false);
1538                set_state(&mut s.states, XdgState::TiledRight, false);
1539            }
1540        });
1541        toplevel.send_pending_configure();
1542    }
1543    if let Some(surface) = window.x11_surface() {
1544        crate::wayland::xwm::configure(surface, rect, states);
1545    }
1546    if states.minimized || states.hidden {
1547        if in_space {
1548            state.space.unmap_elem(&window);
1549            if let Some(wm) = state.wm.as_mut() {
1550                wm.hidden.insert(id, window.clone());
1551            }
1552            state.refocus_pointer();
1553        }
1554    } else if in_space || hidden {
1555        let location = (rect.x, rect.y);
1556        let holds_edge = states.holds_right || states.holds_bottom;
1557        let awaits_size = in_space
1558            && window.toplevel().is_some()
1559            && (holds_edge || state.space.element_location(&window) != Some(location.into()))
1560            && state
1561                .wm
1562                .as_ref()
1563                .and_then(|wm| wm.known.get(&id))
1564                .is_some_and(|known| {
1565                    known.w > 0 && known.h > 0 && (known.w != rect.w || known.h != rect.h)
1566                });
1567        if let Some(wm) = state.wm.as_mut() {
1568            wm.hidden.remove(&id);
1569            if awaits_size {
1570                wm.pending_moves.insert(
1571                    id,
1572                    PendingMove {
1573                        rect,
1574                        holds_right: states.holds_right,
1575                        holds_bottom: states.holds_bottom,
1576                    },
1577                );
1578            } else {
1579                wm.pending_moves.remove(&id);
1580            }
1581        }
1582        if !awaits_size {
1583            relocate(state, &window, location);
1584            move_to_output_under(state, &window, rect);
1585            state.refocus_pointer();
1586        }
1587    }
1588    crate::wayland::foreign_toplevel::update(state, id);
1589    mark_dirty(state);
1590}
1591
1592fn set_state(
1593    states: &mut smithay::wayland::shell::xdg::ToplevelStateSet,
1594    state: XdgState,
1595    on: bool,
1596) {
1597    if on {
1598        states.set(state);
1599    } else {
1600        states.unset(state);
1601    }
1602}
1603
1604/// Keep the window's output tag and its surface's output enter and leave with the output the
1605/// client rectangle mostly lies on.
1606fn move_to_output_under(state: &mut AppState, window: &Window, rect: Rect) {
1607    let center = Point::<f64, Logical>::from((
1608        f64::from(rect.x) + f64::from(rect.w) / 2.0,
1609        f64::from(rect.y) + f64::from(rect.h) / 2.0,
1610    ));
1611    let Some(idx) = state.node_idx_under(center) else {
1612        return;
1613    };
1614    let node_id = state.output_nodes[idx]
1615        .owner
1616        .unwrap_or(state.output_nodes[idx].id);
1617    let Some(idx) = state.node_idx_for_id(node_id) else {
1618        return;
1619    };
1620    let old_id = window_output_id(window);
1621    if old_id == node_id {
1622        return;
1623    }
1624    let new_output = state.output_nodes[idx].output.clone();
1625    let old_output = state
1626        .node_idx_for_id(old_id)
1627        .map(|i| state.output_nodes[i].output.clone());
1628    if let Some(meta) = window_meta(window) {
1629        meta.output
1630            .store(node_id, std::sync::atomic::Ordering::Relaxed);
1631    }
1632    if let Some(surface) = window.wl_surface() {
1633        if let Some(old) = old_output {
1634            old.leave(&surface);
1635        }
1636        new_output.enter(&surface);
1637        let scale = new_output.current_scale().fractional_scale();
1638        with_states(&surface, |states| {
1639            smithay::wayland::compositor::send_surface_state(
1640                &surface,
1641                states,
1642                scale.ceil() as i32,
1643                OutputTransform::Normal,
1644            );
1645            smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
1646                fs.set_preferred_scale(scale);
1647            });
1648        });
1649    }
1650}
1651
1652/// The client rectangle the policy configured a window to.
1653pub fn rect_of(state: &AppState, id: WindowId) -> Option<Rect> {
1654    state.wm.as_ref().and_then(|wm| wm.rects.get(&id).copied())
1655}
1656
1657/// The states the policy configured a window to.
1658pub fn states_of(state: &AppState, id: WindowId) -> Option<States> {
1659    state.wm.as_ref().and_then(|wm| wm.states.get(&id).copied())
1660}
1661
1662/// The effect a window is presented through, if one runs.
1663pub fn effect_of(state: &AppState, id: WindowId) -> Option<Transform> {
1664    state
1665        .wm
1666        .as_ref()
1667        .and_then(|wm| wm.effects.get(&id).copied())
1668}
1669
1670/// Whether a window is hidden from the compositor's hit test: shaded or faded out.
1671pub fn hit_hidden(state: &AppState, window: &Window) -> bool {
1672    window_id(window)
1673        .and_then(|id| effect_of(state, id))
1674        .is_some_and(|t| t.alpha <= 0.0 || t.rect.h <= 0)
1675}
1676
1677/// The render elements of a window's frame around a client area at `client_loc` (layout
1678/// coordinates), for an output at `origin` and `scale`.
1679pub fn frame_elements<R>(
1680    wm: Option<&WmState>,
1681    renderer: &mut R,
1682    id: WindowId,
1683    client_loc: Point<i32, Logical>,
1684    origin: Point<i32, Logical>,
1685    scale: f64,
1686    alpha: f32,
1687) -> Vec<MemoryRenderBufferRenderElement<R>>
1688where
1689    R: Renderer + ImportMem,
1690    R::TextureId: Texture + Clone + Send + 'static,
1691{
1692    let mut elements = Vec::new();
1693    let Some(frame) = wm.and_then(|wm| wm.frames.get(&id)) else {
1694        return elements;
1695    };
1696    for (rect, buffer) in &frame.parts {
1697        let loc = Point::<i32, Logical>::from((
1698            client_loc.x + rect.x - origin.x,
1699            client_loc.y + rect.y - origin.y,
1700        ));
1701        let location: Point<f64, Physical> = loc.to_f64().to_physical(Scale::from(scale));
1702        if let Ok(element) = MemoryRenderBufferRenderElement::from_buffer(
1703            renderer,
1704            location,
1705            buffer,
1706            Some(alpha),
1707            None,
1708            Some((rect.w, rect.h).into()),
1709            Kind::Unspecified,
1710        ) {
1711            elements.push(element);
1712        }
1713    }
1714    elements
1715}
1716
1717/// The render elements of every overlay that touches an output at `origin` of `size`
1718/// (logical), topmost first.
1719pub fn overlay_elements<R>(
1720    wm: Option<&WmState>,
1721    renderer: &mut R,
1722    origin: Point<i32, Logical>,
1723    size: smithay::utils::Size<i32, Logical>,
1724    scale: f64,
1725) -> Vec<MemoryRenderBufferRenderElement<R>>
1726where
1727    R: Renderer + ImportMem,
1728    R::TextureId: Texture + Clone + Send + 'static,
1729{
1730    let mut elements = Vec::new();
1731    let Some(wm) = wm else {
1732        return elements;
1733    };
1734    let output = Rectangle::<i32, Logical>::new(origin, size);
1735    for overlay in wm.overlays.values().rev() {
1736        let rect = Rectangle::<i32, Logical>::new(
1737            (overlay.rect.x, overlay.rect.y).into(),
1738            (overlay.rect.w, overlay.rect.h).into(),
1739        );
1740        if !rect.overlaps(output) {
1741            continue;
1742        }
1743        let loc =
1744            Point::<i32, Logical>::from((overlay.rect.x - origin.x, overlay.rect.y - origin.y));
1745        let location: Point<f64, Physical> = loc.to_f64().to_physical(Scale::from(scale));
1746        if let Ok(element) = MemoryRenderBufferRenderElement::from_buffer(
1747            renderer,
1748            location,
1749            &overlay.buffer,
1750            Some(1.0),
1751            None,
1752            Some((overlay.rect.w, overlay.rect.h).into()),
1753            Kind::Unspecified,
1754        ) {
1755            elements.push(element);
1756        }
1757    }
1758    elements
1759}
1760
1761/// The frame's extents around a window, zero without one.
1762pub fn frame_extents(state: &AppState, id: WindowId) -> Extents {
1763    state
1764        .wm
1765        .as_ref()
1766        .and_then(|wm| wm.frames.get(&id))
1767        .map(|f| f.extents)
1768        .unwrap_or_default()
1769}