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}