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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
use super::*;
impl CompState {
/// Re-raises always-on-top windows in the `Space`.
///
/// `WindowManager` keeps pinned windows last in its own stacking order,
/// but the `Space` has an order of its own that decides what actually
/// draws on top - so pinning is only real once it is pushed here.
/// Called after anything that raises a window.
pub(crate) fn raise_pinned(&mut self) {
let pinned: Vec<WindowId> = self.wm.borrow().stacking_order().filter(|w| w.always_on_top).map(|w| w.id).collect();
for id in pinned {
if let Some(w) = self.id_to_window.get(&id).cloned() {
self.space.raise_element(&w, false);
}
}
}
pub(crate) fn sync_geometry(&mut self, id: WindowId) {
// A pending `anim_from` (set by `toggle_maximize`/`toggle_fullscreen`,
// or by `new_managed_window` for the open-slide) means the target
// geometry below is where this window is *headed*, not where it
// should appear right now - register (or replace) a tween and use
// `WindowAnim::current_rect` in its place for this call and every
// `tick_animations` call afterward, until it completes. `take()`
// both reads and clears it, so a later, non-animated `sync_geometry`
// call for the same window (an ordinary drag/resize frame) goes
// straight back to applying `geometry` immediately, as before.
let anim_from = self.wm.borrow_mut().window_mut(id).and_then(|w| w.anim_from.take());
let Some((target, decorated)) = self.wm.borrow().window(id).map(|w| (w.geometry, w.decorated)) else { return };
if let Some(from) = anim_from {
let duration_ms = self.wm.borrow().animation_duration_ms;
if from != target && duration_ms > 0 {
self.window_anims
.insert(id, WindowAnim { from, to: target, start: Instant::now(), duration: Duration::from_millis(duration_ms as u64) });
}
}
let geom = self.window_anims.get(&id).map(WindowAnim::current_rect).unwrap_or(target);
// The titlebar band is only actually reserved when there is one --
// an undecorated window (client-side decoration, see
// `set_decorated_from_mode`) gets the whole of `geom` as content,
// not `geom` minus a band that's no longer being drawn. Without
// this, a window that negotiated client-side decoration kept the
// same 30px gap at its top anyway: our titlebar wasn't drawn there
// (correctly), but the content was still offset down and told it
// was 30px shorter than the window actually is, leaving a blank
// strip and the frame sitting visibly wrong relative to what's
// inside it.
let band = if decorated { TITLEBAR_HEIGHT as i32 } else { 0 };
// Position always moves with the pointer; only a size change needs a
// client configure or a titlebar re-render (see `last_synced_size`'s
// doc comment).
let size = (geom.width as i32, geom.height as i32 - band);
let size_changed = self.last_synced_size.insert(id, size) != Some(size);
let mut moved = false;
if let Some(w) = self.id_to_window.get(&id) {
self.space.map_element(w.clone(), (geom.x, geom.y + band), false);
moved = true;
if let Some(top) = w.toplevel() {
// xdg-shell position is a purely compositor-side concept --
// the client is never told it - so only a size change
// needs a configure here.
if size_changed {
top.with_pending_state(|state| {
state.size = Some(size.into());
// No configure from this compositor, ever, set any
// `xdg_toplevel` state bit at all before this --
// confirmed by grepping the whole crate for
// `xdg_toplevel::State`, zero hits. GTK4 (Firefox
// concretely) reads the tiled bits to decide whether
// to reserve its own invisible client-side shadow
// margin around its actual content, independent of
// whether decoration is server- or client-side --
// with none ever sent, it always assumed "floating,
// might need a shadow" and kept reserving one. That
// margin sits inside the committed buffer but is
// functionally invisible, so this compositor's own
// border - drawn at the *full* geometry, margin
// included, since nothing here knew the margin
// existed - ended up visibly offset from where the
// client's real chrome began. Reported live as
// Firefox's border "not with the window," and more
// generally never feeling like part of it. Setting
// all four unconditionally (the same technique
// river/dwl use) tells every window it's flush
// against something and should skip its own shadow,
// regardless of whether it's actually in a tiled
// layout - which is the outcome actually wanted:
// this compositor draws the frame, so nothing else
// should also be reserving room for one.
state.states.set(xdg_toplevel::State::TiledLeft);
state.states.set(xdg_toplevel::State::TiledRight);
state.states.set(xdg_toplevel::State::TiledTop);
state.states.set(xdg_toplevel::State::TiledBottom);
});
top.send_configure();
}
} else if let Some(x11) = w.x11_surface() {
// Unlike xdg-shell, an X11 client's real on-screen position
// is part of its own window state - it has to be told on
// every move, not just every resize, the same way a real
// X11 window manager sends continuous `ConfigureNotify`
// during an interactive drag. Without this branch at all,
// `sync_geometry` never reconfigured an XWayland window a
// second time past its initial map: `space.map_element`
// above still moved smithay's own tracked position (see
// `resync_stacking_order`'s doc comment for the real
// z-order side effect that has, since fixed below) and the
// border/titlebar still redrew at the new `Window.geometry`
// (both read it fresh every frame), but the real X11
// client window was never told to move or resize - any
// drag, resize, maximize, edge-snap, or tiling re-layout of
// an XWayland-backed app left its actual content frozen at
// its original position/size forever while srdwm's own
// decoration moved freely around it.
let _ = x11.configure(Rectangle::new((geom.x, geom.y + band).into(), size.into()));
}
}
if size_changed && self.decorations.contains_key(&id) {
self.redraw_decoration_buffer(id);
}
// See `resync_stacking_order`'s doc comment: `map_element` above
// always re-stacks its target to the top of `Space`'s own order as
// a side effect of updating position, `activate` or not - and
// `sync_geometry` runs for reasons with nothing to do with raising
// a window (a title changing, an ordinary resize frame), so left
// uncorrected this silently, non-deterministically desynced
// `Space`'s notion of "on top" from `WindowManager`'s.
if moved {
self.resync_stacking_order();
}
}
}
|