1use 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#[derive(Clone)]
64pub struct FrameBuffers {
65 pub extents: Extents,
66 pub parts: Vec<(Rect, MemoryRenderBuffer)>,
67}
68
69pub struct Overlay {
71 pub rect: Rect,
72 pub buffer: MemoryRenderBuffer,
73}
74
75pub struct Ghost {
78 pub texture: GhostTexture,
79 pub id: Id,
81 pub surface: Rect,
83 pub geometry: Rect,
86 pub frame: Option<FrameBuffers>,
88 pub taken: Instant,
89}
90
91pub enum GhostTexture {
96 Memory(MemoryRenderBuffer),
97 Context(Box<dyn Any>),
99}
100
101const GHOST_UNCLAIMED: Duration = Duration::from_millis(250);
103const GHOST_LIFETIME: Duration = Duration::from_secs(2);
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113pub struct PendingMove {
114 pub rect: Rect,
116 pub holds_right: bool,
117 pub holds_bottom: bool,
118}
119
120impl PendingMove {
121 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 fn holds_edge(&self) -> bool {
139 self.holds_right || self.holds_bottom
140 }
141}
142
143pub 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 pub ghosts: HashMap<WindowId, Ghost>,
151 pub hidden: HashMap<WindowId, Window>,
154 pub rects: HashMap<WindowId, Rect>,
156 pub states: HashMap<WindowId, States>,
158 pub decorations: HashMap<WindowId, Decoration>,
160 pub known: HashMap<WindowId, Size>,
162 pub pending_moves: HashMap<WindowId, PendingMove>,
166 pub acked: HashMap<WindowId, Option<Size>>,
169 pub wake_at: Option<Instant>,
171 pub cursor: Option<CursorIcon>,
173 pub pointer_taken: bool,
175 pub taken_at: Option<Point<f64, Logical>>,
177 pub outputs: Vec<OutputInfo>,
179 pub focus: Option<WindowId>,
181 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
211pub fn active(state: &AppState) -> bool {
213 state.wm.as_ref().is_some_and(|wm| wm.policy.is_some())
214}
215
216pub 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
246pub 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
278pub 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
299fn 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
349pub 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
361fn 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
379pub 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
425pub 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 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
516pub 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
531pub 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
548pub 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
570pub 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
616pub 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
637pub 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 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
666pub 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
675pub 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
687pub 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
697pub 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
732pub 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
739pub fn show_desktop(state: &mut AppState, on: bool) {
741 if active(state) {
742 dispatch(state, Event::ShowDesktop(on));
743 }
744}
745
746pub fn activate_desktop(state: &mut AppState, index: u32) {
748 if active(state) {
749 dispatch(state, Event::ActivateDesktop(index));
750 }
751}
752
753fn 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
769pub 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
784pub 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 None | Some((FocusTarget::Subsurface(..), _)) => Under::default(),
815 }
816}
817
818pub 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
892pub 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
913pub 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
929fn 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
940pub 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
959pub 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
968pub fn next_wake(state: &AppState) -> Option<Instant> {
970 state.wm.as_ref().and_then(|wm| wm.wake_at)
971}
972
973pub 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
986fn 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
1001pub 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
1064fn 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 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
1105pub 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
1123pub 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
1219pub 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
1264pub 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
1276pub 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
1285pub 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
1303fn 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
1323fn 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 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 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
1473fn 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
1506fn 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
1604fn 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
1652pub fn rect_of(state: &AppState, id: WindowId) -> Option<Rect> {
1654 state.wm.as_ref().and_then(|wm| wm.rects.get(&id).copied())
1655}
1656
1657pub fn states_of(state: &AppState, id: WindowId) -> Option<States> {
1659 state.wm.as_ref().and_then(|wm| wm.states.get(&id).copied())
1660}
1661
1662pub 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
1670pub 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
1677pub 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
1717pub 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
1761pub 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}