Skip to main content

pixelflux/
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 interface: what a window manager decides for the compositor.
8//!
9//! The capture compositor (`wayland::frontend`) owns the protocol state of every window and
10//! renders what is on each output; it decides nothing about where a window goes, which one has
11//! focus, or what a key chord means. Those decisions belong to a [`WindowPolicy`] installed
12//! through [`crate::Compositor::install_window_policy`]: the compositor hands it an [`Event`] at
13//! each decision point and applies the [`Command`]s it answers with. Without a policy the
14//! compositor keeps its own behavior, every window fullscreen on its output and no frame drawn,
15//! so a session that nests another compositor or runs one application is exactly what it was.
16//!
17//! The interface is deliberately free of Smithay types: a window is a [`WindowId`], geometry
18//! an integer [`Rect`] in the compositor's logical layout coordinates, and a frame or a menu a
19//! raster [`Image`] the policy drew and the compositor presents, so the same policy drives an
20//! X11 server through another backend with the same model. Images are premultiplied RGBA in
21//! physical pixels at the output's scale, the format both tiny-skia produces and the renderers
22//! import without conversion.
23//!
24//! The policy runs on the compositor thread, inside the handlers that call it, so it must never
25//! block; a long task (a pipe menu's command) is run on a thread of its own and its result
26//! applied on a later event. A panic in the policy is caught at the boundary
27//! (`frontend::AppState::policy`): the compositor drops the policy, logs the panic, and carries
28//! on with its own behavior, so a window-manager bug degrades the desktop rather than ending
29//! the stream.
30
31use std::fmt;
32use std::sync::Arc;
33use std::time::{Duration, Instant};
34
35/// The compositor's stable numeric id of a window, the one its window list reports.
36pub type WindowId = u32;
37
38/// The display id of an output, the key the consumer addresses displays by.
39pub type OutputId = u32;
40
41/// An integer rectangle in logical layout coordinates.
42#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
43pub struct Rect {
44    pub x: i32,
45    pub y: i32,
46    pub w: i32,
47    pub h: i32,
48}
49
50impl Rect {
51    /// A rectangle from its corner and size; a negative size is clamped to zero.
52    pub const fn new(x: i32, y: i32, w: i32, h: i32) -> Rect {
53        Rect {
54            x,
55            y,
56            w: if w < 0 { 0 } else { w },
57            h: if h < 0 { 0 } else { h },
58        }
59    }
60
61    /// The right edge, exclusive.
62    pub const fn right(&self) -> i32 {
63        self.x + self.w
64    }
65
66    /// The bottom edge, exclusive.
67    pub const fn bottom(&self) -> i32 {
68        self.y + self.h
69    }
70
71    /// Whether the point lies inside.
72    pub fn contains(&self, x: i32, y: i32) -> bool {
73        x >= self.x && y >= self.y && x < self.right() && y < self.bottom()
74    }
75
76    /// Whether the point, in continuous coordinates, lies inside.
77    pub fn contains_f64(&self, x: f64, y: f64) -> bool {
78        x >= f64::from(self.x)
79            && y >= f64::from(self.y)
80            && x < f64::from(self.right())
81            && y < f64::from(self.bottom())
82    }
83
84    /// The overlap with another rectangle, empty when they are apart.
85    pub fn intersection(&self, other: &Rect) -> Rect {
86        let x = self.x.max(other.x);
87        let y = self.y.max(other.y);
88        let right = self.right().min(other.right());
89        let bottom = self.bottom().min(other.bottom());
90        if right <= x || bottom <= y {
91            return Rect::default();
92        }
93        Rect::new(x, y, right - x, bottom - y)
94    }
95
96    /// Whether the rectangle holds no pixel.
97    pub const fn is_empty(&self) -> bool {
98        self.w <= 0 || self.h <= 0
99    }
100
101    /// The pixels inside, zero when empty.
102    pub const fn area(&self) -> i64 {
103        if self.is_empty() {
104            0
105        } else {
106            self.w as i64 * self.h as i64
107        }
108    }
109
110    /// The same size at another corner.
111    pub const fn at(&self, x: i32, y: i32) -> Rect {
112        Rect::new(x, y, self.w, self.h)
113    }
114
115    /// The rectangle grown by the extents on each side.
116    pub fn grown(&self, extents: Extents) -> Rect {
117        Rect::new(
118            self.x - extents.left,
119            self.y - extents.top,
120            self.w + extents.left + extents.right,
121            self.h + extents.top + extents.bottom,
122        )
123    }
124}
125
126/// A size in logical pixels.
127#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
128pub struct Size {
129    pub w: i32,
130    pub h: i32,
131}
132
133/// The thickness of a frame around a client area, in logical pixels.
134#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
135pub struct Extents {
136    pub top: i32,
137    pub left: i32,
138    pub right: i32,
139    pub bottom: i32,
140}
141
142/// The protocol a window arrived through.
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144pub enum WindowKind {
145    /// An xdg-shell toplevel.
146    Xdg,
147    /// An X11 window, through XWayland or on an X server: `override_redirect` is one the
148    /// server told the manager to leave alone (a menu, a tooltip, a drag icon).
149    X11 { override_redirect: bool },
150}
151
152/// What a window is for, from `_NET_WM_WINDOW_TYPE` or the xdg role.
153#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
154pub enum WindowType {
155    #[default]
156    Normal,
157    Dialog,
158    Utility,
159    Toolbar,
160    Menu,
161    Splash,
162    Dock,
163    Desktop,
164    Notification,
165    DropdownMenu,
166    PopupMenu,
167    Tooltip,
168    Combo,
169    Dnd,
170}
171
172/// What a client said about who decorates it.
173#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
174pub enum Decoration {
175    /// The client said nothing: it bound no decoration protocol and set no hints, so the
176    /// configuration's default decides.
177    #[default]
178    Unspecified,
179    /// The client asked for server-side decorations.
180    Server,
181    /// The client draws its own frame.
182    Client,
183    /// Motif hints asked for no frame at all.
184    None,
185}
186
187/// Size constraints a client published (`WM_NORMAL_HINTS`, xdg min and max size).
188#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
189pub struct SizeHints {
190    pub min: Option<Size>,
191    pub max: Option<Size>,
192    /// The step a resize moves in, for terminals.
193    pub increment: Option<Size>,
194    /// The size the increments count from.
195    pub base: Option<Size>,
196}
197
198/// Everything the policy knows about a window, sent when it appears and again when any of it
199/// changes.
200#[derive(Clone, Debug, Default, PartialEq)]
201pub struct WindowInfo {
202    pub id: WindowId,
203    pub kind: Option<WindowKind>,
204    pub window_type: WindowType,
205    pub title: String,
206    /// The xdg app id, or the X11 class.
207    pub app_id: String,
208    /// The X11 instance name; empty for a Wayland client.
209    pub instance: String,
210    /// The window this one is transient for.
211    pub parent: Option<WindowId>,
212    pub modal: bool,
213    pub hints: SizeHints,
214    /// The geometry an X11 client asked for, in layout coordinates, where it named one.
215    pub requested: Option<Rect>,
216    /// Whether the client asked for `requested`'s position (X11 `USPosition` or `PPosition`
217    /// hints with a position other than the origin, which toolkits set without meaning it, or a
218    /// type that places itself), so the policy keeps it instead of choosing a place.
219    pub positioned: bool,
220    pub decoration: Decoration,
221    /// The output the window was opened on: the one under the pointer, or the one a request
222    /// named.
223    pub output: OutputId,
224    pub pid: Option<u32>,
225    /// States the client asked for before it mapped.
226    pub wants_fullscreen: bool,
227    pub wants_maximized: bool,
228    pub wants_minimized: bool,
229    pub skip_taskbar: bool,
230    /// Whether the client accepts keyboard focus (X11 `WM_HINTS` input, `WM_TAKE_FOCUS`).
231    pub accepts_focus: bool,
232}
233
234/// One output of the layout.
235#[derive(Clone, Copy, Debug, Default, PartialEq)]
236pub struct OutputInfo {
237    pub id: OutputId,
238    /// The output's rectangle in logical layout coordinates.
239    pub rect: Rect,
240    /// The part of it not reserved by panels (layer-shell exclusive zones, X11 struts).
241    pub workarea: Rect,
242    pub scale: f64,
243    pub primary: bool,
244}
245
246/// The modifiers held with a key or a button.
247#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
248pub struct Modifiers {
249    pub shift: bool,
250    pub ctrl: bool,
251    pub alt: bool,
252    pub logo: bool,
253    pub caps_lock: bool,
254    pub num_lock: bool,
255}
256
257/// One edge or corner a resize pulls, in xdg-shell's numbering.
258#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
259pub enum ResizeEdge {
260    #[default]
261    None,
262    Top,
263    Bottom,
264    Left,
265    TopLeft,
266    BottomLeft,
267    Right,
268    TopRight,
269    BottomRight,
270}
271
272impl ResizeEdge {
273    /// The edge xdg-shell and `_NET_WM_MOVERESIZE` number `raw`, `None` for the rest.
274    pub fn from_xdg(raw: u32) -> ResizeEdge {
275        match raw {
276            1 => ResizeEdge::Top,
277            2 => ResizeEdge::Bottom,
278            4 => ResizeEdge::Left,
279            5 => ResizeEdge::TopLeft,
280            6 => ResizeEdge::BottomLeft,
281            8 => ResizeEdge::Right,
282            9 => ResizeEdge::TopRight,
283            10 => ResizeEdge::BottomRight,
284            _ => ResizeEdge::None,
285        }
286    }
287
288    /// Whether the edge moves the top side.
289    pub fn top(&self) -> bool {
290        matches!(
291            self,
292            ResizeEdge::Top | ResizeEdge::TopLeft | ResizeEdge::TopRight
293        )
294    }
295
296    /// Whether the edge moves the bottom side.
297    pub fn bottom(&self) -> bool {
298        matches!(
299            self,
300            ResizeEdge::Bottom | ResizeEdge::BottomLeft | ResizeEdge::BottomRight
301        )
302    }
303
304    /// Whether the edge moves the left side.
305    pub fn left(&self) -> bool {
306        matches!(
307            self,
308            ResizeEdge::Left | ResizeEdge::TopLeft | ResizeEdge::BottomLeft
309        )
310    }
311
312    /// Whether the edge moves the right side.
313    pub fn right(&self) -> bool {
314        matches!(
315            self,
316            ResizeEdge::Right | ResizeEdge::TopRight | ResizeEdge::BottomRight
317        )
318    }
319
320    /// The CSS cursor name for pulling this edge.
321    pub fn cursor(&self) -> &'static str {
322        match self {
323            ResizeEdge::None => "default",
324            ResizeEdge::Top => "n-resize",
325            ResizeEdge::Bottom => "s-resize",
326            ResizeEdge::Left => "w-resize",
327            ResizeEdge::Right => "e-resize",
328            ResizeEdge::TopLeft => "nw-resize",
329            ResizeEdge::TopRight => "ne-resize",
330            ResizeEdge::BottomLeft => "sw-resize",
331            ResizeEdge::BottomRight => "se-resize",
332        }
333    }
334}
335
336/// A request a client or a shell made of its window through a protocol.
337#[derive(Clone, Copy, Debug, PartialEq)]
338pub enum Request {
339    Maximize,
340    Unmaximize,
341    /// Fullscreen, on the output named where one was.
342    Fullscreen(Option<OutputId>),
343    Unfullscreen,
344    Minimize,
345    Unminimize,
346    /// An interactive move the client started from a button press it holds.
347    Move,
348    /// An interactive resize the client started from a button press it holds.
349    Resize(ResizeEdge),
350    /// Focus and raise: xdg-activation, `_NET_ACTIVE_WINDOW`, or a taskbar.
351    Activate,
352    /// A taskbar or a shell asks the window to close.
353    Close,
354    /// The client asks for its window menu at a point in its own coordinates.
355    ShowMenu {
356        x: i32,
357        y: i32,
358    },
359    /// An X11 client asks to be placed or sized (a `ConfigureRequest`).
360    Geometry(Rect),
361    /// An X11 client asks to be raised.
362    Raise,
363    /// An X11 client asks to be lowered.
364    Lower,
365    /// The window wants attention (`_NET_WM_STATE_DEMANDS_ATTENTION`).
366    Attention(bool),
367    /// Always on top, or no longer.
368    Above(bool),
369    /// Always below, or no longer.
370    Below(bool),
371    /// Shaded to its title bar, or unshaded.
372    Shade(bool),
373}
374
375/// One pointer event in logical layout coordinates.
376#[derive(Clone, Copy, Debug, PartialEq)]
377pub struct PointerEvent {
378    pub x: f64,
379    pub y: f64,
380    pub kind: PointerKind,
381    pub modifiers: Modifiers,
382    /// Milliseconds, the input time stamp.
383    pub time: u32,
384}
385
386/// What the pointer did.
387#[derive(Clone, Copy, Debug, PartialEq)]
388pub enum PointerKind {
389    Motion,
390    /// A button, in the Linux event codes (`0x110` is the left button).
391    Button {
392        button: u32,
393        pressed: bool,
394    },
395    Axis {
396        horizontal: f64,
397        vertical: f64,
398    },
399}
400
401/// What the compositor found under the pointer, for the policy's own hit test to combine
402/// with: a layer surface on the top or overlay layers takes the pointer ahead of every window
403/// and its frame, a window's client area stands below the policy's frames and menus.
404#[derive(Clone, Copy, Debug, Default, PartialEq)]
405pub struct Under {
406    /// The window whose client area, or whose popup, lies under the point.
407    pub window: Option<WindowId>,
408    /// Whether a panel or an overlay surface lies under the point.
409    pub top_layer: bool,
410    /// Whether a desktop's wallpaper surface lies under the point: a background or bottom
411    /// layer surface, the Wayland shape of an X11 desktop window, so a click there is the
412    /// desktop's and never the bare root's.
413    pub desktop: bool,
414}
415
416/// One key event, already resolved through the seat's keymap.
417#[derive(Clone, Copy, Debug, PartialEq, Eq)]
418pub struct KeyEvent {
419    /// The X keysym the keycode produced under the held modifiers.
420    pub keysym: u32,
421    /// The keysym the keycode produces with no modifier held, what a bind names.
422    pub base_keysym: u32,
423    /// The evdev keycode, offset by eight as xkb numbers it.
424    pub keycode: u32,
425    pub pressed: bool,
426    pub modifiers: Modifiers,
427}
428
429/// What a policy decides about an input event.
430#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
431pub enum Verdict {
432    /// The compositor delivers the event to the client under the pointer or in focus.
433    #[default]
434    Pass,
435    /// The policy took the event (a frame click, a grab in progress, a bind); the client sees
436    /// nothing of it.
437    Consumed,
438}
439
440/// A decision point the compositor hands the policy.
441#[derive(Clone, Debug, PartialEq)]
442pub enum Event {
443    /// The layout changed: every output, in display id order.
444    Outputs(Vec<OutputInfo>),
445    /// A window appeared and waits for its first configure. The policy answers with
446    /// [`Command::Configure`] at least; the compositor maps the window once the client has drawn.
447    Added(WindowInfo),
448    /// What the policy knew about a window changed (its title, parent, hints, or type).
449    Changed(WindowInfo),
450    /// The client drew its first buffer: the window is on screen at this size.
451    Mapped { id: WindowId, size: Size },
452    /// A mapped window committed a new size.
453    Resized { id: WindowId, size: Size },
454    /// The client withdrew its window (a null buffer, an unmap); it may map again.
455    Unmapped { id: WindowId },
456    /// The window is gone.
457    Removed { id: WindowId },
458    /// A protocol request about a window.
459    Request { id: WindowId, request: Request },
460    /// A taskbar told where it shows the window, in layout coordinates, or that it no longer
461    /// does; where a minimize animation heads.
462    TaskbarRect { id: WindowId, rect: Option<Rect> },
463    /// A shell asked to show or to restore the desktop.
464    ShowDesktop(bool),
465    /// The pointer moved, pressed, or scrolled; answered with a [`Verdict`].
466    Pointer(PointerEvent, Under),
467    /// A key went down or up; answered with a [`Verdict`].
468    Key(KeyEvent),
469    /// The time the policy asked for ([`Command::WakeAt`]) came, or a frame is about to render
470    /// while an effect runs.
471    Tick(Instant),
472    /// An X server the compositor runs for its X11 clients is up on this display number, so
473    /// the policy can hand `DISPLAY` to what it starts.
474    X11Display(u32),
475    /// A client (a pager) asked, through the workspace protocol, to switch to this virtual
476    /// desktop, counted from zero.
477    ActivateDesktop(u32),
478}
479
480/// The xdg states a configure carries, which the compositor also publishes to taskbars.
481#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
482pub struct States {
483    pub activated: bool,
484    pub maximized: bool,
485    pub fullscreen: bool,
486    pub resizing: bool,
487    /// Hidden by a minimize: the client keeps its surface and nothing of it is composited.
488    pub minimized: bool,
489    /// On another virtual desktop: kept and composited like a minimized window, but told to
490    /// taskbars as a window on no output rather than a minimized one.
491    pub hidden: bool,
492    /// The window's right edge stays where the rectangle puts it whatever width the client
493    /// commits, as a resize from the left edge holds it.
494    pub holds_right: bool,
495    /// The window's bottom edge stays where the rectangle puts it whatever height the client
496    /// commits, as a resize from the top edge holds it.
497    pub holds_bottom: bool,
498}
499
500/// A raster the policy drew: premultiplied RGBA, `width * 4` bytes a row, in physical pixels
501/// at `scale` logical pixels to one.
502#[derive(Clone, PartialEq, Eq)]
503pub struct Image {
504    pub width: u32,
505    pub height: u32,
506    pub scale: i32,
507    pub rgba: Arc<[u8]>,
508}
509
510impl fmt::Debug for Image {
511    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512        write!(f, "Image({}x{} @{})", self.width, self.height, self.scale)
513    }
514}
515
516/// One drawn part of a window's frame, placed relative to the client area's top-left corner.
517#[derive(Clone, Debug, PartialEq)]
518pub struct FramePart {
519    /// Where the part sits, relative to the client area, in logical pixels.
520    pub rect: Rect,
521    pub image: Image,
522}
523
524/// A window's server-side frame: the border it adds around the client area and the parts
525/// that paint it. The compositor hit-tests nothing here; the policy keeps its own map of what
526/// a point on the frame means.
527#[derive(Clone, Debug, Default, PartialEq)]
528pub struct Frame {
529    pub extents: Extents,
530    pub parts: Vec<FramePart>,
531}
532
533/// What an effect is for, so a backend that cannot scale a live window (an X server) still
534/// knows what to show: a minimize or a restore moves the empty frame, a map, a resize, or a
535/// close shows the window as it stands, a shade hides the client under its title bar.
536#[derive(Clone, Copy, Debug, PartialEq, Eq)]
537pub enum EffectKind {
538    Minimize,
539    Restore,
540    Map,
541    Shade,
542    /// A maximize, a restore from it, or a quick tile: the window glides from the geometry it
543    /// had to the one it was configured to.
544    Resize,
545    /// The window is gone: its last picture, kept by the compositor as a ghost, shrinks and
546    /// fades where it stood.
547    Close,
548    /// A virtual desktop switch: the window slides across its output, in from the side on the
549    /// desktop shown or out to the other side on the one left.
550    Slide,
551}
552
553/// A per-frame transform an effect applies to a window as it is presented: the client area is
554/// drawn at `rect` instead of its configured geometry, which scales it, blended at `alpha`, and
555/// its frame parts follow it at the same scale. A `Close` transform names a window the
556/// compositor no longer has: it applies to the ghost the compositor kept of it, and the ghost
557/// is dropped when the transform is cleared.
558#[derive(Clone, Copy, Debug, PartialEq)]
559pub struct Transform {
560    pub kind: EffectKind,
561    /// Where the window's client area (frame excluded) is drawn, in layout coordinates; its
562    /// size relative to the configured one is the scale.
563    pub rect: Rect,
564    pub alpha: f32,
565}
566
567/// What the policy tells the compositor to do.
568#[derive(Clone, Debug, PartialEq)]
569pub enum Command {
570    /// Place and size the client area and set its states. Sent for a window the client has
571    /// not mapped yet, it is the initial configure.
572    Configure {
573        id: WindowId,
574        rect: Rect,
575        states: States,
576    },
577    /// Where every window stands, bottom first. A mapped window left out keeps its place
578    /// below the listed ones.
579    Stack(Vec<WindowId>),
580    /// Give the keyboard to this window, or to none.
581    Focus(Option<WindowId>),
582    /// Ask the client to close the window.
583    Close(WindowId),
584    /// The frame around a window, or none: the compositor presents the parts with the window
585    /// and keeps its layout (`Rect::grown` by the extents) for the window list's geometry.
586    Frame { id: WindowId, frame: Option<Frame> },
587    /// A raster over everything on the layout (a menu, an on-screen display), replacing the
588    /// one of the same key.
589    Overlay { key: u32, rect: Rect, image: Image },
590    /// Take an overlay down.
591    ClearOverlay(u32),
592    /// The pointer image while the policy holds the pointer, by CSS cursor name; `default`
593    /// hands the choice back to the client under the pointer.
594    Cursor(&'static str),
595    /// Present a window through a transform (an effect frame), or plainly again.
596    Effect {
597        id: WindowId,
598        transform: Option<Transform>,
599    },
600    /// Call the policy back with [`Event::Tick`] no later than this.
601    WakeAt(Instant),
602    /// Everything on the layout changed from the policy's side (a theme), so every frame is
603    /// to be presented again.
604    Redraw,
605    /// The virtual desktops: how many there are and which is current, counted from zero, for
606    /// the workspace protocol the pagers read.
607    Desktops { count: u32, current: u32 },
608}
609
610/// One window as the policy holds it, for a consumer that describes the desktop.
611#[derive(Clone, Debug, Default, PartialEq)]
612pub struct WindowView {
613    pub info: WindowInfo,
614    /// The client area in logical layout coordinates.
615    pub rect: Rect,
616    /// The frame the policy draws around the client area.
617    pub frame: Extents,
618    /// Where the origin of the client's own coordinates lies in logical layout coordinates.
619    /// A Wayland client counts from its surface's corner, which lies outside `rect` by the
620    /// shadow it draws itself (the xdg window geometry's offset); an X11 client from its
621    /// window's corner, which is `rect`'s. The policy answers `rect`'s corner and the backend
622    /// that knows the surface corrects it.
623    pub origin: (i32, i32),
624    pub states: States,
625    pub shaded: bool,
626    /// Whether the client has drawn the window.
627    pub mapped: bool,
628    /// The virtual desktop the window is on, counted from zero; `None` for every desktop.
629    pub desktop: Option<u32>,
630    /// Whether a taskbar and the window switcher list the window.
631    pub listed: bool,
632}
633
634/// The desktop as the policy holds it at one moment: what a consumer needs to say where every
635/// window is and which one a click at a point reaches.
636#[derive(Clone, Debug, Default, PartialEq)]
637pub struct DesktopView {
638    pub outputs: Vec<OutputInfo>,
639    /// Every window the policy manages; the shown ones in stacking order, bottom first, after
640    /// the ones that are not.
641    pub windows: Vec<WindowView>,
642    pub focus: Option<WindowId>,
643    pub desktop_count: u32,
644    pub current_desktop: u32,
645}
646
647/// A window manager's decisions for the compositor; see the module documentation.
648pub trait WindowPolicy: Send {
649    /// Handle one event, pushing the commands it leads to onto `out`, and say whether an input
650    /// event was taken. The verdict is read for [`Event::Pointer`] and [`Event::Key`] alone.
651    fn handle(&mut self, event: Event, out: &mut Vec<Command>) -> Verdict;
652
653    /// The desktop as the policy holds it now. Asked from the thread that calls
654    /// [`WindowPolicy::handle`], between events, so it must not block either.
655    fn view(&self) -> DesktopView {
656        DesktopView::default()
657    }
658}
659
660/// The interval between effect frames when no capture paces them: a frame at 60 Hz.
661pub const EFFECT_FRAME: Duration = Duration::from_micros(16_667);
662
663#[cfg(test)]
664mod tests {
665    use super::*;
666
667    /// A rectangle grows by its extents on every side and reports its corners exclusively.
668    #[test]
669    fn a_rectangle_grows_by_its_extents() {
670        let client = Rect::new(100, 50, 640, 480);
671        let frame = client.grown(Extents {
672            top: 30,
673            left: 2,
674            right: 2,
675            bottom: 2,
676        });
677        assert_eq!(frame, Rect::new(98, 20, 644, 512));
678        assert!(frame.contains(98, 20));
679        assert!(!frame.contains(742, 532));
680        assert_eq!(
681            frame.intersection(&Rect::new(700, 500, 100, 100)),
682            Rect::new(700, 500, 42, 32)
683        );
684        assert!(
685            Rect::new(0, 0, 10, 10)
686                .intersection(&Rect::new(20, 20, 5, 5))
687                .is_empty()
688        );
689    }
690
691    /// The xdg edge numbers map onto the edges they name and the sides they move.
692    #[test]
693    fn resize_edges_follow_the_xdg_numbering() {
694        assert_eq!(ResizeEdge::from_xdg(10), ResizeEdge::BottomRight);
695        assert!(ResizeEdge::from_xdg(10).bottom() && ResizeEdge::from_xdg(10).right());
696        assert!(ResizeEdge::from_xdg(5).top() && ResizeEdge::from_xdg(5).left());
697        assert_eq!(ResizeEdge::from_xdg(3), ResizeEdge::None);
698        assert_eq!(ResizeEdge::Left.cursor(), "w-resize");
699    }
700}