1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
use super::*;
impl CompState {
/// Re-broadcasts any dock/panel-facing protocol state that changed by a
/// path with no direct hook back into this module - specifically a
/// compositor keybinding via `crates/config`'s `WindowAction`/
/// `srd.workspace.*` API, which only ever touches `WindowManager` and
/// has no way to reach `CompState`. See `foreign_toplevel::
/// broadcast_dirty_state` and `workspace::broadcast_dirty_active`'s own
/// doc comments for the full story; called once per frame alongside
/// `tick_animations`, from both backends' poll loops.
pub(crate) fn tick_dirty_broadcasts(&mut self) {
foreign_toplevel::broadcast_dirty_state(self);
workspace::broadcast_dirty_active(self);
output_management::broadcast_dirty_outputs(self);
}
/// Advances every in-flight `WindowAnim` by one frame; called once per
/// redraw from both backends' poll loops. A finished tween is dropped
/// *before* its final `sync_geometry` call, so that call lands exactly
/// on `Window.geometry` (the authoritative target) rather than on
/// whatever the eased curve's last sub-pixel step happened to be.
pub(crate) fn tick_animations(&mut self) {
if self.window_anims.is_empty() {
return;
}
let ids: Vec<WindowId> = self.window_anims.keys().copied().collect();
for id in ids {
if self.window_anims.get(&id).is_some_and(WindowAnim::is_done) {
self.window_anims.remove(&id);
}
self.sync_geometry(id);
}
}
/// Forces a fresh `redraw_decoration_buffer` call every frame while the
/// titlebar-button glyph-reveal-on-hover animation is still in
/// progress; called once per redraw from both backends' poll loops,
/// alongside `tick_animations`. Needed for the same reason that one
/// is: `redraw_decoration_buffer`'s own signature-based cache only
/// rebuilds when *called*, and nothing else calls it once a pointer
/// stops moving over an already-hovered button - without this, the
/// glyph would jump straight from invisible to full opacity on the
/// one motion event that started the hover, then never update again
/// for the rest of the animation's own duration, since no further
/// motion event arrives to drive it. Does nothing in `theme.
/// button_glyph_always` mode or once the animation has actually
/// finished (`HOVER_GLYPH_DURATION` elapsed) - both are already a
/// stable, cached final state with nothing left to advance.
pub(crate) fn tick_hover_glyph_animation(&mut self) {
let Some((id, _, start)) = self.hovered_titlebar_button else { return };
if self.wm.borrow().theme.button_glyph_always || start.elapsed() >= decoration::HOVER_GLYPH_DURATION {
return;
}
self.redraw_decoration_buffer(id);
}
/// Re-applies `WindowManager`'s own stacking order to `Space`, bottom
/// to top.
///
/// `Space::map_element` (smithay 0.7.0) always re-stacks its target to
/// the top of `Space`'s internal order as an unconditional side effect
/// of updating its tracked position - true regardless of the
/// `activate` argument, and there is no "move without restacking" in
/// this smithay version. `sync_geometry` calls `map_element` for
/// reasons that have nothing to do with raising a window at all (a
/// title/app_id changing, an ordinary resize frame, a workspace
/// switch), so every one of those silently raised the window it
/// touched to the top of `Space`'s order regardless of which window
/// `WindowManager`/the user actually considered focused or on top.
/// Real-world effect, confirmed live: two windows created moments
/// apart, each independently going through their own startup
/// title/app_id negotiation, would each trigger a handful of
/// `sync_geometry` calls purely from that startup sequence - so
/// whichever one happened to settle *last* silently won `Space`'s
/// notion of "on top", a race with no relationship to which window was
/// actually focused. Reported live as a background window's content
/// and decoration randomly painting in front of the actually-focused
/// window on top of it. Calling this right after any `map_element`
/// restores `Space`'s order to exactly match `WindowManager.order`
/// (which `restack_pinned` already keeps pinned windows at the tail
/// of), so the two can never drift apart again.
pub(crate) fn resync_stacking_order(&mut self) {
let order: Vec<WindowId> = self.wm.borrow().stacking_order().map(|w| w.id).collect();
for id in order {
if let Some(w) = self.id_to_window.get(&id).cloned() {
self.space.raise_element(&w, false);
}
}
}
}
|