Commit cadf3a07a5dc
tarball9 files changed+102-17
@@ -294,10 +294,9 @@ impl Layout {
294294
if win.scratchpad.is_some() || self.is_viewed(o, tag) {295295
return;296296
}297
- let alone = !self298
- .windows299
- .iter()300
- .any(|(x, v)| x != w && v.output == Some(o) && v.tag == tag && v.scratchpad.is_none());297
+ let alone = !self.windows.iter().any(|(x, v)| {298
+ x != w && v.output == Some(o) && v.tag == tag && v.scratchpad.is_none() && !v.hidden299
+ });301300
if alone {302301
self.toggle_view(o, tag);303302
}@@ -40,7 +40,11 @@ impl Layout {
4040
let o = self.active?;4141
let sp = &mut self.scratchpads[s];4242
let mut next = sp.shown.map_or(0, |i| i + 1);43
- while sp.windows.get(next).is_some_and(|&w| self.windows[w].hidden) {43
+ while sp44
+ .windows45
+ .get(next)46
+ .is_some_and(|&w| self.windows[w].hidden)47
+ {4448
next += 1;4549
}4650
if next >= sp.windows.len() {@@ -89,6 +89,7 @@ impl Animator {
8989
.ghosts9090
.iter()9191
.filter(|g| g.output == o && g.fixed.is_none())92
+ .filter(|g| !targets.iter().any(|t| t.0 == g.id))9293
.map(|g| (g.id, g.seed));9394
let targets: Vec<(WindowId, Rect)> = ghosts.chain(targets.iter().copied()).collect();9495
let ids: Vec<WindowId> = targets.iter().map(|(id, _)| *id).collect();@@ -27,6 +27,7 @@ impl CompositorHandler for State {
2727
fn new_surface(&mut self, surface: &WlSurface) {2828
add_pre_commit_hook::<Self, _>(surface, |state, _dh, surface| {2929
crate::timing::watch(state, surface);30
+ state.keep_frame_for_hide(surface);3031
hold_until_readable(state, surface);3132
});3233
}@@ -97,6 +98,15 @@ impl CompositorHandler for State {
9798
t.send_configure();9899
}99100
}101
+ if let Some(id) = self102
+ .window_id(surface)103
+ .filter(|&id| self.wm.layout.window(id).is_some_and(|w| w.hidden))104
+ {105
+ self.show_window(id);106
+ if let Some(t) = self.wm.window(id).and_then(|w| w.toplevel()) {107
+ t.send_configure();108
+ }109
+ }100110
self.on_buffer(surface);101111
self.wm.popups.commit(surface);102112
if let Some(PopupKind::Xdg(popup)) = self.wm.popups.find_popup(surface)@@ -177,6 +177,7 @@ fn snapshot(state: &State) -> monolith_ipc::State {
177177
.collect();178178
let mut windows: Vec<monolith_ipc::Window> = l179179
.windows()180
+ .filter(|(_, w)| !w.hidden)180181
.map(|(id, w)| {181182
let (app_id, title) = state182183
.wm@@ -70,8 +70,12 @@ impl ForeignToplevelHandler for State {
7070
impl State {7171
/// Brings both window lists in line with the windows.7272
pub fn publish_toplevels(&mut self) {73
+ let hidden: Vec<_> = (self.wm.layout.windows())74
+ .filter(|(_, w)| w.hidden)75
+ .map(|(id, _)| id)76
+ .collect();7377
let list = &mut self.protocols.toplevel_list;74
- for (_, w) in self.wm.iter_mut() {78
+ for (_, w) in self.wm.iter_mut().filter(|(id, _)| !hidden.contains(id)) {7579
match &w.list_handle {7680
Some(h) => {7781
if h.title() != w.title || h.app_id() != w.app_id {@@ -92,6 +96,7 @@ impl State {
9296
let toplevels = self9397
.wm9498
.iter()99
+ .filter(|(id, _)| !hidden.contains(id))95100
.map(|(id, w)| {96101
let (app_id, title) = (w.app_id.clone(), w.title.clone());97102
let w = self.wm.layout.window(id);@@ -12,7 +12,7 @@ use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::Sta
1212
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;1313
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;1414
use smithay::utils::{Logical, Point, Rectangle, Serial};15
-use smithay::wayland::compositor::with_states;15
+use smithay::wayland::compositor::{BufferAssignment, SurfaceAttributes, with_states};1616
use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;1717
use std::time::Duration;1818
@@ -157,12 +157,67 @@ impl State {
157157
}158158
}159159
160
- /// Takes a toplevel that dropped its buffer, as clients do to hide to a161
- /// tray, out of the layout. Smithay has reset its role, so its next commit162
- /// maps it afresh with a new initial configure.160
+ /// Hides a toplevel that dropped its buffer, as clients do to hide to a161
+ /// tray. The layout keeps its entry, so its next commit, which smithay162
+ /// treats as a fresh map with a new initial configure, restores it.163163
fn hide_window(&mut self, id: WindowId) {164
- if let Some(window) = self.remove_window(id) {165
- self.wm.unmapped.push(window);164
+ let Some(s) = self.wm.get_mut(id) else {165
+ return;166
+ };167
+ let window = s.window.clone();168
+ let saved = s.hide_frame.take();169
+ let opacity = s.rule.opacity.unwrap_or(1.0);170
+ if let Some(h) = s.list_handle.take() {171
+ h.send_closed();172
+ }173
+ self.leave_ghost(id, &window, saved, opacity);174
+ self.wm.layout.set_hidden(id, true);175
+ self.wm.anim.forget(id);176
+ self.apply_layout();177
+ }178
+179
+ /// Brings back a hidden toplevel on its first commit since, where it was180
+ /// and as it was, to be held on its seed until its buffer lands.181
+ pub(crate) fn show_window(&mut self, id: WindowId) {182
+ let Some(s) = self.wm.get_mut(id) else {183
+ return;184
+ };185
+ s.awaiting_buffer = true;186
+ s.drawn = false;187
+ s.hide_frame = None;188
+ let window = s.window.clone();189
+ let focus = s.rule.focus != Some(false);190
+ let parent = self.parent_of(&window);191
+ self.wm.layout.set_hidden(id, false);192
+ self.wm.layout.set_parent(id, parent);193
+ self.wm.layout.set_modal(id, is_modal(&window));194
+ self.refresh_identity(id);195
+ self.view_if_first(id);196
+ self.await_first_buffer(id);197
+ if focus {198
+ self.wm.layout.focus_unless_covered(id);199
+ }200
+ self.apply_layout();201
+ }202
+203
+ /// Called before a commit applies. A null-buffer commit makes smithay204
+ /// drop the surface's textures before `on_buffer` runs, so the frame a205
+ /// hiding window leaves behind has to be taken here.206
+ pub fn keep_frame_for_hide(&mut self, surface: &WlSurface) {207
+ let Some(id) = self.window_id(surface) else {208
+ return;209
+ };210
+ let removes = with_states(surface, |s| {211
+ let mut attrs = s.cached_state.get::<SurfaceAttributes>();212
+ matches!(attrs.pending().buffer, Some(BufferAssignment::Removed))213
+ });214
+ let Some(s) = self.wm.get(id).filter(|s| removes && s.drawn) else {215
+ return;216
+ };217
+ let window = s.window.clone();218
+ let frame = self.last_frame(&window);219
+ if let Some(s) = self.wm.get_mut(id) {220
+ s.hide_frame = frame;166221
}167222
}168223
@@ -247,7 +302,7 @@ impl State {
247302
.wm248303
.layout249304
.windows()250
- .filter(|(_, w)| w.floating && w.float_default && w.scratchpad.is_none())305
+ .filter(|(_, w)| w.floating && w.float_default && w.scratchpad.is_none() && !w.hidden)251306
.map(|(id, _)| id)252307
.collect();253308
for id in defaults {@@ -435,7 +490,7 @@ impl State {
435490
if let Some(state) = &state {436491
self.wm.space.unmap_elem(&state.window);437492
let opacity = state.rule.opacity.unwrap_or(1.0);438
- self.leave_ghost(id, &state.window, opacity);493
+ self.leave_ghost(id, &state.window, None, opacity);439494
}440495
self.wm.layout.remove_window(id);441496
self.apply_layout();@@ -444,7 +499,13 @@ impl State {
444499
445500
/// Keeps a visible tiled window's last frame shrinking to its seed while446501
/// its neighbours close over it.447
- fn leave_ghost(&mut self, id: WindowId, window: &Window, opacity: f32) {502
+ fn leave_ghost(503
+ &mut self,504
+ id: WindowId,505
+ window: &Window,506
+ saved: Option<LastFrame>,507
+ opacity: f32,508
+ ) {448509
let Some(o) = self.wm.layout.window(id).and_then(|w| w.output) else {449510
return;450511
};@@ -466,7 +527,7 @@ impl State {
466527
else {467528
return;468529
};469
- if let Some(frame) = self.last_frame(window) {530
+ if let Some(frame) = saved.or_else(|| self.last_frame(window)) {470531
self.wm.last_frames.insert(id, frame);471532
self.wm.anim.add_ghost(Ghost {472533
id,@@ -81,6 +81,9 @@ pub struct WindowState {
8181
pub awaiting_buffer: bool,8282
/// Has shown a buffer, so one committed without is an unmap.8383
pub drawn: bool,84
+ /// What it showed when its client committed a null buffer, taken before85
+ /// smithay drops the textures so hiding can leave a ghost.86
+ pub hide_frame: Option<LastFrame>,8487
/// Configured without a size until its first buffer shows the one it8588
/// picked.8689
pub natural_size: bool,@@ -159,6 +162,7 @@ impl Wm {
159162
placed: None,160163
awaiting_buffer: true,161164
drawn: false,165
+ hide_frame: None,162166
natural_size: false,163167
natural_after: None,164168
reclassify: false,@@ -56,7 +56,7 @@ impl State {
5656
let l = &self.wm.layout;5757
let occupied: HashSet<(OutputId, monolith_layout::TagId)> = l5858
.windows()59
- .filter(|(_, w)| w.scratchpad.is_none())59
+ .filter(|(_, w)| w.scratchpad.is_none() && !w.hidden)6060
.filter_map(|(_, w)| Some((w.output?, w.tag)))6161
.collect();6262
let groups = l