diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 6b0d42b4..62ba7c1f 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -1024,14 +1024,18 @@ impl SurfaceThreadState { let animations_going = shell.animations_going(); let output = self.mirroring.as_ref().unwrap_or(&self.output); if let Some((_, workspace)) = shell.workspaces.active(output) { - if let Some(fullscreen_surface) = workspace.get_fullscreen() { + let seat = shell.seats.last_active(); + if let Some(fullscreen_surface) = workspace.get_fullscreen(seat) { const _30_FPS: Duration = Duration::from_nanos(1_000_000_000 / 30); ( true, - fullscreen_surface.wl_surface().is_some_and(|surface| { - recursive_frame_time_estimation(&self.clock, &surface) - .is_some_and(|dur| dur <= _30_FPS) - }), + fullscreen_surface + .surface + .wl_surface() + .is_some_and(|surface| { + recursive_frame_time_estimation(&self.clock, &surface) + .is_some_and(|dur| dur <= _30_FPS) + }), animations_going, ) } else { @@ -1490,18 +1494,21 @@ fn render_node_for_output( let Some(workspace) = shell.active_space(output) else { return *target_node; }; - let nodes = workspace - .get_fullscreen() - .map(|w| vec![w.clone()]) - .unwrap_or_else(|| { - workspace - .mapped() - .map(|mapped| mapped.active_window()) - .collect::>() - }) - .into_iter() - .flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s))) - .collect::>(); + let fullscreens: Vec<_> = workspace + .get_fullscreen_surfaces() + .map(|f| f.surface.clone()) + .collect(); + let nodes = if !fullscreens.is_empty() { + fullscreens + } else { + workspace + .mapped() + .map(|mapped| mapped.active_window()) + .collect::>() + } + .into_iter() + .flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s))) + .collect::>(); if nodes.contains(target_node) || nodes.is_empty() { *target_node diff --git a/src/input/mod.rs b/src/input/mod.rs index 1a26dc62..c4f422c3 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2498,16 +2498,14 @@ fn cursor_sessions_for_output<'a>( .active_space(output) .into_iter() .flat_map(|workspace| { - let maybe_fullscreen = workspace.get_fullscreen(); + let fullscreen_cursors: Vec<_> = workspace + .get_fullscreen_surfaces() + .flat_map(|f| f.surface.cursor_sessions()) + .collect(); workspace .cursor_sessions() .into_iter() - .chain( - maybe_fullscreen - .map(|w| w.cursor_sessions()) - .into_iter() - .flatten(), - ) + .chain(fullscreen_cursors) .chain(output.cursor_sessions()) }) } diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index e109519e..9374230a 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -288,22 +288,18 @@ impl Shell { } let workspace = &mut set.workspaces[set.active]; - if let Some(fullscreen) = workspace.get_fullscreen() { - if self.seats.iter().any(|seat| { + for fs in workspace.get_fullscreen_surfaces() { + let is_focused = self.seats.iter().any(|seat| { if let Some(KeyboardFocusTarget::Fullscreen(s)) = seat.get_keyboard().unwrap().current_focus() { - &s == fullscreen + s == fs.surface } else { false } - }) { - fullscreen.set_activated(true); - fullscreen.send_configure(); - } else { - fullscreen.set_activated(false); - fullscreen.send_configure(); - } + }); + fs.surface.set_activated(is_focused); + fs.surface.send_configure(); } for focused in focused_windows.iter() { raise_with_children(&mut workspace.floating_layer, focused); @@ -661,7 +657,9 @@ fn focus_target_is_valid( .has_node(&node), KeyboardFocusTarget::Fullscreen(window) => { let workspace = shell.active_space(output).unwrap(); - workspace.get_fullscreen().is_some_and(|w| w == &window) + workspace + .get_fullscreen_surfaces() + .any(|f| f.surface == window) } KeyboardFocusTarget::Popup(_) => true, KeyboardFocusTarget::LockSurface(_) => false, @@ -714,9 +712,9 @@ fn update_focus_target( .map(KeyboardFocusTarget::Element) .or_else(|| { workspace - .get_fullscreen() + .get_fullscreen(seat) .cloned() - .map(KeyboardFocusTarget::Fullscreen) + .map(|fs| KeyboardFocusTarget::Fullscreen(fs.surface)) }) }) } diff --git a/src/shell/focus/order.rs b/src/shell/focus/order.rs index 8c6dae0b..6d021e6c 100644 --- a/src/shell/focus/order.rs +++ b/src/shell/focus/order.rs @@ -112,8 +112,8 @@ fn render_input_order_internal( let output_size = output.geometry().size; // this is more hacky than I would like.. - let fullscreen = workspace.fullscreen.as_ref().filter(|f| !f.is_animating()); let seat = shell.seats.last_active(); + let fullscreen = workspace.get_fullscreen(seat); let is_active_workspace = seat.focused_output().is_some_and(|output| { shell .active_space(&output) @@ -144,7 +144,7 @@ fn render_input_order_internal( let Some(workspace) = shell.workspaces.space_for_handle(previous) else { return ControlFlow::Break(Err(OutputNoMode)); }; - let has_fullscreen = workspace.fullscreen.is_some(); + let has_fullscreen = workspace.get_fullscreen(seat).is_some(); let (forward, percentage) = match start { WorkspaceDelta::Shortcut(st) => ( diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 6ea3b5e2..da168360 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -763,21 +763,19 @@ impl WorkspaceSet { .any(|(w, _)| w.wl_surface().as_deref() == Some(&root)) }) .and_then(|w| { - w.surface_offset(surface).and_then(|offset| { - self.sticky_layer - .element_geometry(w) - .map(|geom| (geom, offset)) - }) + self.sticky_layer + .element_geometry(w) + .zip(w.surface_offset(surface)) }) .or_else(|| { self.workspaces.iter().find_map(|workspace| { workspace - .get_fullscreen() - .and_then(|fullscreen| { - (fullscreen.wl_surface().as_deref() == Some(&root)) + .get_fullscreen_surfaces() + .find_map(|fs| { + (fs.surface.wl_surface().as_deref() == Some(&root)) .then(|| { - fullscreen.surface_offset(surface).and_then(|offset| { - workspace.fullscreen_geometry().map(|geom| (geom, offset)) + fs.surface.surface_offset(surface).map(|offset| { + (workspace.fullscreen_geometry_for(fs), offset) }) }) .flatten() @@ -787,9 +785,7 @@ impl WorkspaceSet { w.windows() .any(|(w, _)| w.wl_surface().as_deref() == Some(&root)) .then(|| { - w.surface_offset(surface).and_then(|offset| { - workspace.element_geometry(w).map(|geom| (geom, offset)) - }) + workspace.element_geometry(w).zip(w.surface_offset(surface)) }) .flatten() }) @@ -1641,12 +1637,13 @@ impl Common { if let Some(mapped) = shell.element_for_surface(surface) { mapped.on_commit(surface); } - if let Some(surface) = shell + if let Some(fs) = shell .workspaces .spaces() - .find_map(|w| w.get_fullscreen().filter(|s| *s == surface)) + .flat_map(|w| w.get_fullscreen_surfaces()) + .find(|f| f.surface == *surface) { - surface.on_commit() + fs.surface.on_commit() }; } self.popups.commit(surface); @@ -1907,7 +1904,9 @@ impl Shell { .outputs() .find(|output| { let workspace = self.active_space(output).unwrap(); - workspace.get_fullscreen() == Some(&elem) + workspace + .get_fullscreen_surfaces() + .any(|f| f.surface == elem) }) .cloned(), KeyboardFocusTarget::Group(WindowGroup { node, .. }) => self @@ -2040,8 +2039,8 @@ impl Shell { let workspace = self.active_space(o).unwrap(); workspace - .get_fullscreen() - .is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL)) + .get_fullscreen_surfaces() + .any(|f| f.surface.has_surface(surface, WindowSurfaceType::ALL)) || workspace .mapped() .any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) @@ -2096,8 +2095,8 @@ impl Shell { .workspaces .spaces() .find(|w| { - w.get_fullscreen() - .is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL)) + w.get_fullscreen_surfaces() + .any(|f| f.surface.has_surface(surface, WindowSurfaceType::ALL)) || w.mapped() .any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) || w.minimized_windows.iter().any(|m| { @@ -2144,7 +2143,7 @@ impl Shell { .mapped() .any(|m| m.windows().any(|(s, _)| &s == surface)) || set.workspaces.iter().any(|w| { - w.get_fullscreen().is_some_and(|s| s == surface) + w.get_fullscreen_surfaces().any(|f| &f.surface == surface) || w.minimized_windows .iter() .any(|m| m.windows().any(|s| &s == surface)) @@ -2833,12 +2832,7 @@ impl Shell { let floating_exception = layout::has_floating_exception(&self.tiling_exceptions, &window); if should_be_fullscreen { - if let Some((surface, state, _)) = workspace.map_fullscreen(&window, &seat, None, None) - { - toplevel_leave_output(&surface, &workspace.output); - toplevel_leave_workspace(&surface, &workspace.handle); - self.remap_unfullscreened_window(surface, state, loop_handle); - } + workspace.map_fullscreen(&window, &seat, None, None); if was_activated { workspace_state.add_workspace_state(&workspace_handle, WState::Urgent); } @@ -3209,9 +3203,12 @@ impl Shell { let from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above let is_minimized = window.is_minimized(); - let is_fullscreen = from_workspace.get_fullscreen().is_some_and(|f| f == window); + let is_fullscreen = from_workspace + .get_fullscreen_surfaces() + .any(|f| &f.surface == window); let mut window_state = if is_fullscreen { - let (_, previous_state, previous_geometry) = from_workspace.take_fullscreen().unwrap(); + let (_, previous_state, previous_geometry) = + from_workspace.take_fullscreen(window).unwrap(); WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map( |(previous_state, previous_geometry)| FullscreenRestoreData { previous_state, @@ -3367,14 +3364,12 @@ impl Shell { ); mapped.into() } else if let WorkspaceRestoreData::Fullscreen(previous) = window_state { - if let Some((old_surface, previous_state, _)) = to_workspace.map_fullscreen( + to_workspace.map_fullscreen( window, None, previous.clone().map(|p| p.previous_state), previous.map(|p| p.previous_geometry), - ) { - self.remap_unfullscreened_window(old_surface, previous_state, evlh); - } + ); window.clone().into() } else { unreachable!() // TODO: sticky @@ -3592,8 +3587,12 @@ impl Shell { } else if let Some((workspace, output)) = self.workspace_for_surface(surface) { let workspace = self.workspaces.space_for_handle(&workspace).unwrap(); - if let Some(window) = workspace.get_fullscreen().filter(|s| *s == surface) { - let global_position = (workspace.fullscreen_geometry().unwrap().loc + if let Some(fs) = workspace + .get_fullscreen_surfaces() + .find(|f| &f.surface == surface) + { + let window = &fs.surface; + let global_position = (workspace.fullscreen_geometry_for(fs).loc + location.as_local()) .to_global(&output); @@ -3686,10 +3685,14 @@ impl Shell { let maybe_fullscreen_workspace = self .workspaces .spaces_mut() - .find(|w| w.get_fullscreen().is_some_and(|s| s == surface)); + .find(|w| w.get_fullscreen_surfaces().any(|f| &f.surface == surface)); if let Some(workspace) = maybe_fullscreen_workspace { - element_geo = Some(workspace.fullscreen_geometry().unwrap()); - let (surface, state, _) = workspace.remove_fullscreen().unwrap(); + let fs = workspace + .get_fullscreen_surfaces() + .find(|f| &f.surface == surface) + .unwrap(); + element_geo = Some(workspace.fullscreen_geometry_for(fs)); + let (surface, state, _) = workspace.remove_fullscreen_surface(surface).unwrap(); self.remap_unfullscreened_window(surface, state, evlh); }; @@ -3902,19 +3905,15 @@ impl Shell { /// Get the window geometry of a keyboard focus target pub fn focused_geometry(&self, target: &KeyboardFocusTarget) -> Option> { match target { - KeyboardFocusTarget::Fullscreen(surface) => { - if let Some(workspace) = surface - .wl_surface() - .and_then(|s| self.workspace_for_surface(&s)) - .and_then(|(handle, _)| self.workspaces.space_for_handle(&handle)) - { + KeyboardFocusTarget::Fullscreen(surface) => surface + .wl_surface() + .and_then(|s| self.workspace_for_surface(&s)) + .and_then(|(handle, _)| self.workspaces.space_for_handle(&handle)) + .map(|workspace| { workspace - .fullscreen_geometry() - .map(|f| f.to_global(workspace.output())) - } else { - None - } - } + .fullscreen_geometry_for_surface(surface) + .to_global(workspace.output()) + }), _ => { if let Some(element) = self.focused_element(target) { self.element_geometry(&element) @@ -4266,9 +4265,8 @@ impl Shell { self.workspaces.sets.values_mut().find_map(|set| { set.workspaces.iter_mut().find_map(|workspace| { let window = workspace - .get_fullscreen() - .cloned() - .into_iter() + .get_fullscreen_surfaces() + .map(|f| f.surface.clone()) .chain(workspace.mapped().map(|m| m.active_window())) .find(|s| s == surface); window.map(|s| (workspace, s)) @@ -4768,15 +4766,16 @@ impl Shell { &mut self, surface: &S, output: Output, - loop_handle: &LoopHandle<'static, State>, + _loop_handle: &LoopHandle<'static, State>, ) -> Option where CosmicSurface: PartialEq, { let mapped = self.element_for_surface(surface).cloned()?; + let seat = self.seats.last_active().clone(); let window; - let old_fullscreen = if let Some((old_output, set)) = self + if let Some((old_output, set)) = self .workspaces .sets .iter_mut() @@ -4823,7 +4822,7 @@ impl Shell { let workspace = self.active_space_mut(&output).unwrap(); workspace.map_fullscreen( &window, - None, + &seat, Some(FullscreenRestoreState::Sticky { output: old_output, state: FloatingRestoreData { @@ -4834,7 +4833,7 @@ impl Shell { }, }), Some(from), - ) + ); } else if let Some(workspace) = self.space_for_mut(&mapped) { if mapped.is_minimized() { // TODO: Rewrite the `MinimizedWindow` to restore to fullscreen @@ -4860,7 +4859,7 @@ impl Shell { workspace.map_fullscreen( &window, - None, + &seat, match state { WorkspaceRestoreData::Floating(floating_state) => { floating_state.map(|state| FullscreenRestoreState::Floating { @@ -4877,15 +4876,11 @@ impl Shell { WorkspaceRestoreData::Fullscreen(_) => unreachable!(), }, Some(from), - ) + ); } else { return None; }; - if let Some((old_fullscreen, restore, _)) = old_fullscreen { - self.remap_unfullscreened_window(old_fullscreen, restore, loop_handle); - } - Some(KeyboardFocusTarget::Fullscreen(window)) } @@ -4900,11 +4895,12 @@ impl Shell { let maybe_workspace = self.workspaces.iter_mut().find_map(|(_, s)| { s.workspaces .iter_mut() - .find(|w| w.get_fullscreen().is_some_and(|f| f == surface)) + .find(|w| w.get_fullscreen_surfaces().any(|f| &f.surface == surface)) }); if let Some(workspace) = maybe_workspace { - let (old_fullscreen, restore, _) = workspace.remove_fullscreen().unwrap(); + let (old_fullscreen, restore, _) = + workspace.remove_fullscreen_surface(surface).unwrap(); toplevel_leave_output(&old_fullscreen, &workspace.output); toplevel_leave_workspace(&old_fullscreen, &workspace.handle); diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index ccf5e4ab..ce5bf624 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -108,7 +108,7 @@ pub struct Workspace { pub floating_layer: FloatingLayout, pub minimized_windows: Vec, pub tiling_enabled: bool, - pub fullscreen: Option, + pub fullscreen_surfaces: Vec, pub pinned: bool, pub id: Option, @@ -383,7 +383,7 @@ impl Workspace { floating_layer, tiling_enabled, minimized_windows: Vec::new(), - fullscreen: None, + fullscreen_surfaces: Vec::new(), pinned: false, id: None, handle, @@ -416,7 +416,7 @@ impl Workspace { floating_layer, tiling_enabled: pinned.tiling_enabled, minimized_windows: Vec::new(), - fullscreen: None, + fullscreen_surfaces: Vec::new(), pinned: true, id: pinned.id.clone(), handle, @@ -454,7 +454,10 @@ impl Workspace { #[profiling::function] pub fn refresh(&mut self) { - self.fullscreen.take_if(|w| !w.alive()); + // seems it removes dead windows + // self.fullscreen.take_if(|w| !w.alive()); + self.fullscreen_surfaces.retain(|w| w.alive()); + self.floating_layer.refresh(); self.tiling_layer.refresh(); } @@ -478,14 +481,15 @@ impl Workspace { self.is_empty() && !self.has_activation_token(xdg_activation_state) && !self.pinned } + /// cleans up any window that is not alive anymore pub fn refresh_focus_stack(&mut self) { for (seat, stack) in self.focus_stack.0.iter_mut() { - let fullscreen = self - .fullscreen - .as_ref() - .filter(|f| f.alive()) - .filter(|f| f.ended_at.is_none()) - .map(|f| &f.surface); + let fullscreen_surfaces: Vec<&CosmicSurface> = self + .fullscreen_surfaces + .iter() + .filter(|f| f.alive() && f.ended_at.is_none()) + .map(|f| &f.surface) + .collect(); // Move grab is treated as focused, so don't change focus to a // window while grab exists. @@ -506,7 +510,7 @@ impl Workspace { .chain(move_mapped.iter()) }; stack.retain(|w| match w { - FocusTarget::Fullscreen(s) => fullscreen.is_some_and(|f| f == s), + FocusTarget::Fullscreen(s) => fullscreen_surfaces.contains(&s), FocusTarget::Window(w) => mapped().any(|m| w == m), }); } @@ -515,15 +519,12 @@ impl Workspace { pub fn animations_going(&self) -> bool { self.tiling_layer.animations_going() || self.floating_layer.animations_going() - || self - .fullscreen - .as_ref() - .is_some_and(|f| f.start_at.is_some() || f.ended_at.is_some()) + || self.fullscreen_surfaces.iter().any(|f| f.is_animating()) || self.dirty.swap(false, Ordering::SeqCst) } pub fn update_animations(&mut self) -> HashMap { - if let Some(f) = self.fullscreen.as_mut() { + for f in self.fullscreen_surfaces.iter_mut() { if let Some(start) = f.start_at.as_ref() { let duration_since = Instant::now().duration_since(*start); if duration_since > FULLSCREEN_ANIMATION_DURATION { @@ -531,16 +532,18 @@ impl Workspace { self.dirty.store(true, Ordering::SeqCst); } } - - if let Some(end) = f.ended_at { - let duration_since = Instant::now().duration_since(end); - if duration_since >= FULLSCREEN_ANIMATION_DURATION { - let _ = self.fullscreen.take(); - self.dirty.store(true, Ordering::SeqCst); - } - } } + self.fullscreen_surfaces.retain(|f| { + if let Some(end) = f.ended_at + && Instant::now().duration_since(end) >= FULLSCREEN_ANIMATION_DURATION + { + self.dirty.store(true, Ordering::SeqCst); + return false; + } + true + }); + let clients = self.tiling_layer.update_animation_state(); self.floating_layer.update_animation_state(); clients @@ -574,7 +577,11 @@ impl Workspace { toplevel_enter_output(&surface, output); } } - if let Some(f) = self.fullscreen.as_ref().filter(|f| f.ended_at.is_none()) { + for f in self + .fullscreen_surfaces + .iter() + .filter(|f| f.ended_at.is_none()) + { toplevel_leave_output(&f.surface, &self.output); toplevel_enter_output(&f.surface, output); } @@ -665,12 +672,13 @@ impl Workspace { where CosmicSurface: PartialEq, { - if self - .fullscreen - .as_ref() - .is_some_and(|f| f.ended_at.is_none() && &f.surface == surface) + if let Some(idx) = self + .fullscreen_surfaces + .iter() + .position(|f| f.ended_at.is_none() && &f.surface == surface) { - let (surface, previous_state, previous_geometry) = self.remove_fullscreen().unwrap(); + let (surface, previous_state, previous_geometry) = + self.remove_fullscreen_at(idx).unwrap(); return Some(( surface, WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map( @@ -723,24 +731,28 @@ impl Workspace { Some((mapped.active_window(), layer)) } - pub fn fullscreen_geometry(&self) -> Option> { - self.fullscreen.as_ref().map(|fullscreen| { - let bbox = fullscreen.surface.bbox().as_local(); + pub fn fullscreen_geometry_for_surface( + &self, + surface: &CosmicSurface, + ) -> Rectangle { + let bbox = surface.bbox().as_local(); - let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local()); - if bbox != full_geo { - if bbox.size.w < full_geo.size.w { - full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2; - full_geo.size.w = bbox.size.w; - } - if bbox.size.h < full_geo.size.h { - full_geo.loc.y += (full_geo.size.h - bbox.size.h) / 2; - full_geo.size.h = bbox.size.h; - } + let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local()); + if bbox != full_geo { + if bbox.size.w < full_geo.size.w { + full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2; + full_geo.size.w = bbox.size.w; } + if bbox.size.h < full_geo.size.h { + full_geo.loc.y += (full_geo.size.h - bbox.size.h) / 2; + full_geo.size.h = bbox.size.h; + } + } - full_geo - }) + full_geo + } + pub fn fullscreen_geometry_for(&self, fullscreen: &FullscreenSurface) -> Rectangle { + self.fullscreen_geometry_for_surface(&fullscreen.surface) } pub fn element_for_surface(&self, surface: &S) -> Option<&CosmicMapped> @@ -780,13 +792,12 @@ impl Workspace { let stack = self.focus_stack.get(seat); let last_focused = stack.last(); - if let Some(fullscreen) = self.fullscreen.as_ref() - && last_focused.is_some_and( - |t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface), - ) - && !fullscreen.is_animating() - { - let geometry = self.fullscreen_geometry().unwrap(); + if let Some(fullscreen) = self.fullscreen_surfaces.iter().find(|f| { + !f.is_animating() + && last_focused + .is_some_and(|t| matches!(t, FocusTarget::Fullscreen(s) if s == &f.surface)) + }) { + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen_element_under(fullscreen, geometry); } @@ -795,9 +806,9 @@ impl Workspace { .or_else(|| self.tiling_layer.popup_element_under(location, seat)) .or_else(|| { if last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_))) - && let Some(fullscreen) = self.fullscreen.as_ref() + && let Some(fullscreen) = self.get_fullscreen(seat) { - let geometry = self.fullscreen_geometry().unwrap(); + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen_element_under(fullscreen, geometry); } None @@ -830,13 +841,12 @@ impl Workspace { let stack = self.focus_stack.get(seat); let last_focused = stack.last(); - if let Some(fullscreen) = self.fullscreen.as_ref() - && last_focused.is_some_and( - |t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface), - ) - && !fullscreen.is_animating() - { - let geometry = self.fullscreen_geometry().unwrap(); + if let Some(fullscreen) = self.fullscreen_surfaces.iter().find(|fs| { + !fs.is_animating() + && last_focused + .is_some_and(|t| matches!(t, FocusTarget::Fullscreen(f) if f == &fs.surface)) + }) { + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen_element_under(fullscreen, geometry); } @@ -845,9 +855,9 @@ impl Workspace { .or_else(|| self.tiling_layer.toplevel_element_under(location, seat)) .or_else(|| { if last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_))) - && let Some(fullscreen) = self.fullscreen.as_ref() + && let Some(fullscreen) = self.get_fullscreen(seat) { - let geometry = self.fullscreen_geometry().unwrap(); + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen_element_under(fullscreen, geometry); } None @@ -867,7 +877,7 @@ impl Workspace { let check_fullscreen = |fullscreen: &FullscreenSurface| { if !fullscreen.is_animating() { - let geometry = self.fullscreen_geometry().unwrap(); + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen .surface .0 @@ -891,21 +901,16 @@ impl Workspace { let stack = self.focus_stack.get(seat); let last_focused = stack.last(); - self.fullscreen - .as_ref() - .filter(|f| last_focused.is_some_and(|t| t == &f.surface)) + self.fullscreen_surfaces + .iter() + .find(|f| last_focused.is_some_and(|t| t == &f.surface)) .and_then(check_fullscreen) .or_else(|| self.floating_layer.popup_surface_under(location, seat)) .or_else(|| { self.tiling_layer .popup_surface_under(location, overview, seat) }) - .or_else(|| { - self.fullscreen - .as_ref() - .filter(|f| last_focused.is_none_or(|t| t != &f.surface)) - .and_then(check_fullscreen) - }) + .or_else(|| self.get_fullscreen(seat).and_then(check_fullscreen)) .map(|(m, p)| (m, p.to_global(&self.output))) } @@ -922,7 +927,7 @@ impl Workspace { let check_fullscreen = |fullscreen: &FullscreenSurface| { if !fullscreen.is_animating() { - let geometry = self.fullscreen_geometry().unwrap(); + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen .surface .focus_under( @@ -940,21 +945,16 @@ impl Workspace { let stack = self.focus_stack.get(seat); let last_focused = stack.last(); - self.fullscreen - .as_ref() - .filter(|f| last_focused.is_some_and(|t| t == &f.surface)) + self.fullscreen_surfaces + .iter() + .find(|f| last_focused.is_some_and(|t| t == &f.surface)) .and_then(check_fullscreen) .or_else(|| self.floating_layer.toplevel_surface_under(location, seat)) .or_else(|| { self.tiling_layer .toplevel_surface_under(location, overview, seat) }) - .or_else(|| { - self.fullscreen - .as_ref() - .filter(|f| last_focused.is_none_or(|t| t != &f.surface)) - .and_then(check_fullscreen) - }) + .or_else(|| self.get_fullscreen(seat).and_then(check_fullscreen)) .map(|(m, p)| (m, p.to_global(&self.output))) } @@ -1026,10 +1026,14 @@ impl Workspace { where CosmicSurface: PartialEq, { - if self.get_fullscreen().is_some_and(|s| s == surface) { - let fullscreen_state = self.fullscreen.clone().unwrap(); + if let Some(idx) = self + .fullscreen_surfaces + .iter() + .position(|f| f.ended_at.is_none() && &f.surface == surface) + { + let fullscreen_state = self.fullscreen_surfaces.get(idx)?.clone(); { - let f = self.fullscreen.as_mut().unwrap(); + let f = self.fullscreen_surfaces.get_mut(idx)?; f.previous_geometry = Some(to); f.ended_at = Some( Instant::now() @@ -1133,16 +1137,20 @@ impl Workspace { )> { match window { MinimizedWindow::Fullscreen { previous, surface } => { - let old_fullscreen = self.remove_fullscreen(); surface.set_minimized(false); - self.fullscreen = Some(FullscreenSurface { + // focus it so it's the top fullscreen window + self.focus_stack + .get_mut(seat) + .append(FocusTarget::Fullscreen(surface.clone())); + self.fullscreen_surfaces.push(FullscreenSurface { surface, previous_state: previous.clone().map(|p| p.previous_state), previous_geometry: previous.map(|p| p.previous_geometry), start_at: None, ended_at: None, }); - old_fullscreen + self.dirty.store(true, Ordering::SeqCst); + None } MinimizedWindow::Floating { window, previous } => { let current_output_size = self.output.geometry().size.as_logical(); @@ -1230,13 +1238,7 @@ impl Workspace { seat: impl Into>>, restore: Option, previous_geometry: Option>, - ) -> Option<( - CosmicSurface, - Option, - Option>, - )> { - let res = self.remove_fullscreen(); - + ) { window.set_fullscreen(true); window.set_geometry(self.output.geometry(), 0); window.send_configure(); @@ -1249,96 +1251,137 @@ impl Workspace { self.focus_stack.get_mut(seat).append(window.clone()); } - self.fullscreen = Some(FullscreenSurface { + self.dirty.store(true, Ordering::SeqCst); + self.fullscreen_surfaces.push(FullscreenSurface { surface: window.clone(), previous_state: restore, previous_geometry, start_at: Some(Instant::now()), ended_at: None, }); - - res } #[must_use] - pub fn take_fullscreen( + pub fn take_fullscreen( &mut self, + surface: &S, + ) -> Option<( + CosmicSurface, + Option, + Option>, + )> + where + CosmicSurface: PartialEq, + { + let idx = self + .fullscreen_surfaces + .iter() + .position(|f| f.ended_at.is_none() && &f.surface == surface)?; + let fs = self.fullscreen_surfaces.remove(idx); + + for focus_stack in self.focus_stack.0.values_mut() { + focus_stack.retain(|t| t != &fs.surface); + } + + Some((fs.surface, fs.previous_state, fs.previous_geometry)) + } + + #[must_use] + pub fn remove_fullscreen_at( + &mut self, + idx: usize, ) -> Option<( CosmicSurface, Option, Option>, )> { - let surface = self.fullscreen.take_if(|s| s.ended_at.is_none())?; + // if it doesn't exist we move on. + let surface = self.fullscreen_surfaces.get_mut(idx)?; + // if already being removed, do nothing + if surface.ended_at.is_some() { + return None; + } + + if surface.surface.alive() { + surface.surface.output_leave(&self.output); + surface.surface.set_fullscreen(false); + if let Some(previous_geometry) = surface.previous_geometry.as_ref() { + surface + .surface + .set_geometry(previous_geometry.to_global(&self.output), 0); + } + surface.surface.send_configure(); + } for focus_stack in self.focus_stack.0.values_mut() { focus_stack.retain(|t| t != &surface.surface); } + surface.ended_at = Some( + Instant::now() + - (FULLSCREEN_ANIMATION_DURATION + - surface + .start_at + .take() + .map(|earlier| { + Instant::now() + .duration_since(earlier) + .min(FULLSCREEN_ANIMATION_DURATION) + }) + .unwrap_or(FULLSCREEN_ANIMATION_DURATION)), + ); + Some(( - surface.surface, - surface.previous_state, + surface.surface.clone(), + surface.previous_state.clone(), surface.previous_geometry, )) } #[must_use] - pub fn remove_fullscreen( + pub fn remove_fullscreen_surface( &mut self, + surface: &S, ) -> Option<( CosmicSurface, Option, Option>, - )> { - if let Some(surface) = self.fullscreen.as_mut() { - if surface.ended_at.is_some() { - return None; - } - - if surface.surface.alive() { - surface.surface.output_leave(&self.output); - surface.surface.set_fullscreen(false); - if let Some(previous_geometry) = surface.previous_geometry.as_ref() { - surface - .surface - .set_geometry(previous_geometry.to_global(&self.output), 0); - } - surface.surface.send_configure(); - } - - for focus_stack in self.focus_stack.0.values_mut() { - focus_stack.retain(|t| t != &surface.surface); - } - - surface.ended_at = Some( - Instant::now() - - (FULLSCREEN_ANIMATION_DURATION - - surface - .start_at - .take() - .map(|earlier| { - Instant::now() - .duration_since(earlier) - .min(FULLSCREEN_ANIMATION_DURATION) - }) - .unwrap_or(FULLSCREEN_ANIMATION_DURATION)), - ); - - Some(( - surface.surface.clone(), - surface.previous_state.clone(), - surface.previous_geometry, - )) - } else { - None - } + )> + where + CosmicSurface: PartialEq, + { + let idx = self + .fullscreen_surfaces + .iter() + .position(|f| f.ended_at.is_none() && &f.surface == surface)?; + self.remove_fullscreen_at(idx) } - pub fn get_fullscreen(&self) -> Option<&CosmicSurface> { - self.fullscreen - .as_ref() - .filter(|f| f.alive()) - .filter(|f| f.ended_at.is_none()) - .map(|f| &f.surface) + pub fn get_fullscreen(&self, seat: &Seat) -> Option<&FullscreenSurface> { + let stack = self.focus_stack.get(seat); + stack + .iter() + .find_map(|t| { + if let FocusTarget::Fullscreen(s) = t { + self.fullscreen_surfaces + .iter() + .find(|f| f.alive() && f.ended_at.is_none() && &f.surface == s) + } else { + None + } + }) + .or_else(|| { + self.fullscreen_surfaces + .iter() + .rev() + .find(|f| f.alive() && f.ended_at.is_none()) + }) + } + + pub fn get_fullscreen_surfaces(&self) -> impl Iterator { + self.fullscreen_surfaces + .iter() + .filter(|f| f.alive() && f.ended_at.is_none()) } pub fn resize( @@ -1465,14 +1508,18 @@ impl Workspace { self.floating_layer.mapped().count() + self.tiling_layer.mapped().count() + self.minimized_windows.len() - + if self.fullscreen.is_some() { 1 } else { 0 } + + self + .fullscreen_surfaces + .iter() + .filter(|f| f.ended_at.is_none()) + .count() } pub fn is_empty(&self) -> bool { self.floating_layer.mapped().next().is_none() && self.tiling_layer.mapped().next().is_none() && self.minimized_windows.is_empty() - && self.fullscreen.is_none() + && self.fullscreen_surfaces.is_empty() } pub fn is_floating(&self, surface: &S) -> bool @@ -1579,8 +1626,11 @@ impl Workspace { }; let focused = self.focus_stack.get(last_active_seat).last().cloned(); - let mut fullscreen_elements = if let Some(fullscreen) = self.fullscreen.as_ref() { - let fullscreen_geo = self.fullscreen_geometry().unwrap(); + let render_fullscreen = |fullscreen: &FullscreenSurface, + renderer: &mut R, + output_scale: f64| + -> Vec> { + let fullscreen_geo = self.fullscreen_geometry_for(fullscreen); let previous_geo = fullscreen .previous_geometry .as_ref() @@ -1650,20 +1700,35 @@ impl Workspace { .into_iter() .map(animation_rescale) .collect::>() - } else { - Vec::new() }; + let top_fullscreen = self.get_fullscreen(last_active_seat); + + let mut fullscreen_elements: Vec> = Vec::new(); + if let Some(fs) = top_fullscreen { + fullscreen_elements.extend(render_fullscreen(fs, renderer, output_scale)); + } + // Also render any animating (entering/exiting) fullscreens + for fs in self.fullscreen_surfaces.iter().filter(|f| f.is_animating()) { + if top_fullscreen.is_none_or(|top| top.surface != fs.surface) { + fullscreen_elements.extend(render_fullscreen(fs, renderer, output_scale)); + }; + } + if matches!(focused, Some(FocusTarget::Fullscreen(_))) { elements.append(&mut fullscreen_elements); } + let any_fullscreen_animating = self + .fullscreen_surfaces + .iter() + .any(|f| f.start_at.is_some() || f.ended_at.is_some()); if !matches!(focused, Some(FocusTarget::Fullscreen(_))) + || any_fullscreen_animating || self - .fullscreen - .as_ref() - .map(|f| f.start_at.is_some() || f.ended_at.is_some()) - .unwrap_or(true) + .fullscreen_surfaces + .iter() + .all(|f| !f.alive() || f.ended_at.is_some()) { // floating surfaces let alpha = match &overview.0 { @@ -1780,8 +1845,12 @@ impl Workspace { layer_map.non_exclusive_zone().as_local() }; - if let Some(fullscreen) = self.fullscreen.as_ref() { - let fullscreen_geo = self.fullscreen_geometry().unwrap(); + // Render popups for the top (most recently focused) fullscreen + let focus_stack = self.focus_stack.get(last_active_seat); + let top_fullscreen = self.get_fullscreen(last_active_seat); + + if let Some(fullscreen) = top_fullscreen { + let fullscreen_geo = self.fullscreen_geometry_for(fullscreen); let previous_geo = fullscreen .previous_geometry .as_ref() @@ -1838,13 +1907,16 @@ impl Workspace { ); } - let focus_stack = self.focus_stack.get(last_active_seat); + let any_fullscreen_animating = self + .fullscreen_surfaces + .iter() + .any(|f| f.start_at.is_some() || f.ended_at.is_some()); if !matches!(focus_stack.last(), Some(FocusTarget::Fullscreen(_))) + || any_fullscreen_animating || self - .fullscreen - .as_ref() - .map(|f| f.start_at.is_some() || f.ended_at.is_some()) - .unwrap_or(true) + .fullscreen_surfaces + .iter() + .all(|f| !f.alive() || f.ended_at.is_some()) { // floating surfaces let alpha = match &overview.0 { diff --git a/src/state.rs b/src/state.rs index 88298a94..935261ac 100644 --- a/src/state.rs +++ b/src/state.rs @@ -996,8 +996,8 @@ impl Common { // normal windows for space in shell.workspaces.spaces() { - if let Some(window) = space.get_fullscreen() { - window.with_surfaces(processor); + if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) { + fs.surface.with_surfaces(processor); } space.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { @@ -1162,15 +1162,16 @@ impl Common { }); if let Some(active) = shell.active_space(output) { - if let Some(window) = active.get_fullscreen() - && let Some(feedback) = window + if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) + && let Some(feedback) = fs + .surface .wl_surface() .and_then(|wl_surface| { advertised_node_for_surface(&wl_surface, &self.display_handle) }) .and_then(&mut dmabuf_feedback) { - window.send_dmabuf_feedback( + fs.surface.send_dmabuf_feedback( output, &feedback, render_element_states, @@ -1356,8 +1357,9 @@ impl Common { }); if let Some(active) = shell.active_space(output) { - if let Some(window) = active.get_fullscreen() { - window.send_frame(output, time, throttle(window), should_send); + if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) { + fs.surface + .send_frame(output, time, throttle(&fs.surface), should_send); } active.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { @@ -1377,9 +1379,9 @@ impl Common { .spaces_for_output(output) .filter(|w| w.handle != active.handle) { - if let Some(window) = space.get_fullscreen() { - let throttle = min(throttle(space), throttle(window)); - window.send_frame(output, time, throttle, |_, _| None); + if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) { + let throttle = min(throttle(space), throttle(&fs.surface)); + fs.surface.send_frame(output, time, throttle, |_, _| None); } space.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { diff --git a/src/wayland/handlers/dmabuf.rs b/src/wayland/handlers/dmabuf.rs index 093f88da..4d9732bb 100644 --- a/src/wayland/handlers/dmabuf.rs +++ b/src/wayland/handlers/dmabuf.rs @@ -51,9 +51,11 @@ impl DmabufHandler for State { let is_fullscreen = shell .workspaces .space_for_handle(&handle)? - .fullscreen - .as_ref() - .is_some_and(|f| f.surface.has_surface(surface, WindowSurfaceType::all())); + .fullscreen_surfaces + .iter() + .any(|f| { + f.ended_at.is_none() && f.surface.has_surface(surface, WindowSurfaceType::all()) + }); let node = kms .drm_devices diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs index 01410bb5..3da39952 100644 --- a/src/wayland/handlers/toplevel_management.rs +++ b/src/wayland/handlers/toplevel_management.rs @@ -42,7 +42,7 @@ impl ToplevelManagementHandler for State { .spaces_for_output(output) .enumerate() .find(|(_, w)| { - w.get_fullscreen().is_some_and(|f| f == window) + w.get_fullscreen_surfaces().any(|f| &f.surface == window) || w.mapped() .flat_map(|m| m.windows().map(|(s, _)| s)) .any(|w| &w == window) diff --git a/src/wayland/handlers/xdg_activation.rs b/src/wayland/handlers/xdg_activation.rs index b9674e38..359f654d 100644 --- a/src/wayland/handlers/xdg_activation.rs +++ b/src/wayland/handlers/xdg_activation.rs @@ -219,8 +219,8 @@ impl State { .workspaces .space_for_handle(&workspace) .unwrap() - .get_fullscreen() - .cloned() + .get_fullscreen(&seat) + .map(|f| f.surface.clone()) .map(KeyboardFocusTarget::Fullscreen) else { return; @@ -232,8 +232,8 @@ impl State { if let Some(surface) = shell .workspaces .space_for_handle(&workspace) - .and_then(|w| w.get_fullscreen()) - .cloned() + .and_then(|w| w.get_fullscreen(&seat)) + .map(|f| f.surface.clone()) { shell.append_focus_stack(surface, &seat) } diff --git a/src/wayland/handlers/xdg_shell/popup.rs b/src/wayland/handlers/xdg_shell/popup.rs index 965e8908..a3ee2e31 100644 --- a/src/wayland/handlers/xdg_shell/popup.rs +++ b/src/wayland/handlers/xdg_shell/popup.rs @@ -68,7 +68,7 @@ impl Shell { unconstrain_xdg_popup(surface, window_loc, output.geometry()); } } else if let Some(output) = self.workspaces.spaces().find_map(|w| { - w.fullscreen.as_ref().and_then(|f| { + w.fullscreen_surfaces.iter().find_map(|f| { (f.surface.wl_surface().as_deref() == Some(&parent)).then_some(w.output()) }) }) { diff --git a/src/xwayland.rs b/src/xwayland.rs index 4c2f3b55..eb3e1570 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -575,16 +575,15 @@ impl Common { .filter(|(i, _)| *i != set.active), ) .flat_map(|(_, workspace)| { + let focus_last = + workspace.focus_stack.get(seat).last().cloned(); workspace - .get_fullscreen() + .get_fullscreen_surfaces() .filter(|f| { - workspace - .focus_stack - .get(seat) - .last() - .is_some_and(|t| &t == f) + focus_last.as_ref().is_some_and(|t| t == &f.surface) }) - .cloned() + .map(|f| f.surface.clone()) + .collect::>() .into_iter() .chain(workspace.mapped().flat_map(|mapped| { let active = mapped.active_window(); @@ -603,15 +602,14 @@ impl Common { })) .chain( workspace - .get_fullscreen() + .get_fullscreen_surfaces() .filter(|f| { - workspace - .focus_stack - .get(seat) - .last() - .is_none_or(|t| &t != f) + focus_last + .as_ref() + .is_none_or(|t| t != &f.surface) }) - .cloned() + .map(|f| f.surface.clone()) + .collect::>() .into_iter(), ) .chain(