feat: support multiple fullscreen windows per workspace

This commit is contained in:
Hojjat Abdollahi 2026-06-02 09:48:33 -06:00 committed by GitHub
parent 9f28a764a7
commit 28ef6bdbc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 381 additions and 308 deletions

View file

@ -1024,14 +1024,18 @@ impl SurfaceThreadState {
let animations_going = shell.animations_going(); let animations_going = shell.animations_going();
let output = self.mirroring.as_ref().unwrap_or(&self.output); let output = self.mirroring.as_ref().unwrap_or(&self.output);
if let Some((_, workspace)) = shell.workspaces.active(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); const _30_FPS: Duration = Duration::from_nanos(1_000_000_000 / 30);
( (
true, true,
fullscreen_surface.wl_surface().is_some_and(|surface| { fullscreen_surface
recursive_frame_time_estimation(&self.clock, &surface) .surface
.is_some_and(|dur| dur <= _30_FPS) .wl_surface()
}), .is_some_and(|surface| {
recursive_frame_time_estimation(&self.clock, &surface)
.is_some_and(|dur| dur <= _30_FPS)
}),
animations_going, animations_going,
) )
} else { } else {
@ -1490,18 +1494,21 @@ fn render_node_for_output(
let Some(workspace) = shell.active_space(output) else { let Some(workspace) = shell.active_space(output) else {
return *target_node; return *target_node;
}; };
let nodes = workspace let fullscreens: Vec<_> = workspace
.get_fullscreen() .get_fullscreen_surfaces()
.map(|w| vec![w.clone()]) .map(|f| f.surface.clone())
.unwrap_or_else(|| { .collect();
workspace let nodes = if !fullscreens.is_empty() {
.mapped() fullscreens
.map(|mapped| mapped.active_window()) } else {
.collect::<Vec<_>>() workspace
}) .mapped()
.into_iter() .map(|mapped| mapped.active_window())
.flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s))) .collect::<Vec<_>>()
.collect::<Vec<_>>(); }
.into_iter()
.flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s)))
.collect::<Vec<_>>();
if nodes.contains(target_node) || nodes.is_empty() { if nodes.contains(target_node) || nodes.is_empty() {
*target_node *target_node

View file

@ -2498,16 +2498,14 @@ fn cursor_sessions_for_output<'a>(
.active_space(output) .active_space(output)
.into_iter() .into_iter()
.flat_map(|workspace| { .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 workspace
.cursor_sessions() .cursor_sessions()
.into_iter() .into_iter()
.chain( .chain(fullscreen_cursors)
maybe_fullscreen
.map(|w| w.cursor_sessions())
.into_iter()
.flatten(),
)
.chain(output.cursor_sessions()) .chain(output.cursor_sessions())
}) })
} }

View file

@ -288,22 +288,18 @@ impl Shell {
} }
let workspace = &mut set.workspaces[set.active]; let workspace = &mut set.workspaces[set.active];
if let Some(fullscreen) = workspace.get_fullscreen() { for fs in workspace.get_fullscreen_surfaces() {
if self.seats.iter().any(|seat| { let is_focused = self.seats.iter().any(|seat| {
if let Some(KeyboardFocusTarget::Fullscreen(s)) = if let Some(KeyboardFocusTarget::Fullscreen(s)) =
seat.get_keyboard().unwrap().current_focus() seat.get_keyboard().unwrap().current_focus()
{ {
&s == fullscreen s == fs.surface
} else { } else {
false false
} }
}) { });
fullscreen.set_activated(true); fs.surface.set_activated(is_focused);
fullscreen.send_configure(); fs.surface.send_configure();
} else {
fullscreen.set_activated(false);
fullscreen.send_configure();
}
} }
for focused in focused_windows.iter() { for focused in focused_windows.iter() {
raise_with_children(&mut workspace.floating_layer, focused); raise_with_children(&mut workspace.floating_layer, focused);
@ -661,7 +657,9 @@ fn focus_target_is_valid(
.has_node(&node), .has_node(&node),
KeyboardFocusTarget::Fullscreen(window) => { KeyboardFocusTarget::Fullscreen(window) => {
let workspace = shell.active_space(output).unwrap(); 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::Popup(_) => true,
KeyboardFocusTarget::LockSurface(_) => false, KeyboardFocusTarget::LockSurface(_) => false,
@ -714,9 +712,9 @@ fn update_focus_target(
.map(KeyboardFocusTarget::Element) .map(KeyboardFocusTarget::Element)
.or_else(|| { .or_else(|| {
workspace workspace
.get_fullscreen() .get_fullscreen(seat)
.cloned() .cloned()
.map(KeyboardFocusTarget::Fullscreen) .map(|fs| KeyboardFocusTarget::Fullscreen(fs.surface))
}) })
}) })
} }

View file

@ -112,8 +112,8 @@ fn render_input_order_internal<R: 'static>(
let output_size = output.geometry().size; let output_size = output.geometry().size;
// this is more hacky than I would like.. // 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 seat = shell.seats.last_active();
let fullscreen = workspace.get_fullscreen(seat);
let is_active_workspace = seat.focused_output().is_some_and(|output| { let is_active_workspace = seat.focused_output().is_some_and(|output| {
shell shell
.active_space(&output) .active_space(&output)
@ -144,7 +144,7 @@ fn render_input_order_internal<R: 'static>(
let Some(workspace) = shell.workspaces.space_for_handle(previous) else { let Some(workspace) = shell.workspaces.space_for_handle(previous) else {
return ControlFlow::Break(Err(OutputNoMode)); 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 { let (forward, percentage) = match start {
WorkspaceDelta::Shortcut(st) => ( WorkspaceDelta::Shortcut(st) => (

View file

@ -763,21 +763,19 @@ impl WorkspaceSet {
.any(|(w, _)| w.wl_surface().as_deref() == Some(&root)) .any(|(w, _)| w.wl_surface().as_deref() == Some(&root))
}) })
.and_then(|w| { .and_then(|w| {
w.surface_offset(surface).and_then(|offset| { self.sticky_layer
self.sticky_layer .element_geometry(w)
.element_geometry(w) .zip(w.surface_offset(surface))
.map(|geom| (geom, offset))
})
}) })
.or_else(|| { .or_else(|| {
self.workspaces.iter().find_map(|workspace| { self.workspaces.iter().find_map(|workspace| {
workspace workspace
.get_fullscreen() .get_fullscreen_surfaces()
.and_then(|fullscreen| { .find_map(|fs| {
(fullscreen.wl_surface().as_deref() == Some(&root)) (fs.surface.wl_surface().as_deref() == Some(&root))
.then(|| { .then(|| {
fullscreen.surface_offset(surface).and_then(|offset| { fs.surface.surface_offset(surface).map(|offset| {
workspace.fullscreen_geometry().map(|geom| (geom, offset)) (workspace.fullscreen_geometry_for(fs), offset)
}) })
}) })
.flatten() .flatten()
@ -787,9 +785,7 @@ impl WorkspaceSet {
w.windows() w.windows()
.any(|(w, _)| w.wl_surface().as_deref() == Some(&root)) .any(|(w, _)| w.wl_surface().as_deref() == Some(&root))
.then(|| { .then(|| {
w.surface_offset(surface).and_then(|offset| { workspace.element_geometry(w).zip(w.surface_offset(surface))
workspace.element_geometry(w).map(|geom| (geom, offset))
})
}) })
.flatten() .flatten()
}) })
@ -1641,12 +1637,13 @@ impl Common {
if let Some(mapped) = shell.element_for_surface(surface) { if let Some(mapped) = shell.element_for_surface(surface) {
mapped.on_commit(surface); mapped.on_commit(surface);
} }
if let Some(surface) = shell if let Some(fs) = shell
.workspaces .workspaces
.spaces() .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); self.popups.commit(surface);
@ -1907,7 +1904,9 @@ impl Shell {
.outputs() .outputs()
.find(|output| { .find(|output| {
let workspace = self.active_space(output).unwrap(); let workspace = self.active_space(output).unwrap();
workspace.get_fullscreen() == Some(&elem) workspace
.get_fullscreen_surfaces()
.any(|f| f.surface == elem)
}) })
.cloned(), .cloned(),
KeyboardFocusTarget::Group(WindowGroup { node, .. }) => self KeyboardFocusTarget::Group(WindowGroup { node, .. }) => self
@ -2040,8 +2039,8 @@ impl Shell {
let workspace = self.active_space(o).unwrap(); let workspace = self.active_space(o).unwrap();
workspace workspace
.get_fullscreen() .get_fullscreen_surfaces()
.is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL)) .any(|f| f.surface.has_surface(surface, WindowSurfaceType::ALL))
|| workspace || workspace
.mapped() .mapped()
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) .any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
@ -2096,8 +2095,8 @@ impl Shell {
.workspaces .workspaces
.spaces() .spaces()
.find(|w| { .find(|w| {
w.get_fullscreen() w.get_fullscreen_surfaces()
.is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL)) .any(|f| f.surface.has_surface(surface, WindowSurfaceType::ALL))
|| w.mapped() || w.mapped()
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) .any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
|| w.minimized_windows.iter().any(|m| { || w.minimized_windows.iter().any(|m| {
@ -2144,7 +2143,7 @@ impl Shell {
.mapped() .mapped()
.any(|m| m.windows().any(|(s, _)| &s == surface)) .any(|m| m.windows().any(|(s, _)| &s == surface))
|| set.workspaces.iter().any(|w| { || 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 || w.minimized_windows
.iter() .iter()
.any(|m| m.windows().any(|s| &s == surface)) .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); let floating_exception = layout::has_floating_exception(&self.tiling_exceptions, &window);
if should_be_fullscreen { if should_be_fullscreen {
if let Some((surface, state, _)) = workspace.map_fullscreen(&window, &seat, None, None) 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);
}
if was_activated { if was_activated {
workspace_state.add_workspace_state(&workspace_handle, WState::Urgent); 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 from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above
let is_minimized = window.is_minimized(); 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 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( WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map(
|(previous_state, previous_geometry)| FullscreenRestoreData { |(previous_state, previous_geometry)| FullscreenRestoreData {
previous_state, previous_state,
@ -3367,14 +3364,12 @@ impl Shell {
); );
mapped.into() mapped.into()
} else if let WorkspaceRestoreData::Fullscreen(previous) = window_state { } else if let WorkspaceRestoreData::Fullscreen(previous) = window_state {
if let Some((old_surface, previous_state, _)) = to_workspace.map_fullscreen( to_workspace.map_fullscreen(
window, window,
None, None,
previous.clone().map(|p| p.previous_state), previous.clone().map(|p| p.previous_state),
previous.map(|p| p.previous_geometry), previous.map(|p| p.previous_geometry),
) { );
self.remap_unfullscreened_window(old_surface, previous_state, evlh);
}
window.clone().into() window.clone().into()
} else { } else {
unreachable!() // TODO: sticky unreachable!() // TODO: sticky
@ -3592,8 +3587,12 @@ impl Shell {
} else if let Some((workspace, output)) = self.workspace_for_surface(surface) { } else if let Some((workspace, output)) = self.workspace_for_surface(surface) {
let workspace = self.workspaces.space_for_handle(&workspace).unwrap(); let workspace = self.workspaces.space_for_handle(&workspace).unwrap();
if let Some(window) = workspace.get_fullscreen().filter(|s| *s == surface) { if let Some(fs) = workspace
let global_position = (workspace.fullscreen_geometry().unwrap().loc .get_fullscreen_surfaces()
.find(|f| &f.surface == surface)
{
let window = &fs.surface;
let global_position = (workspace.fullscreen_geometry_for(fs).loc
+ location.as_local()) + location.as_local())
.to_global(&output); .to_global(&output);
@ -3686,10 +3685,14 @@ impl Shell {
let maybe_fullscreen_workspace = self let maybe_fullscreen_workspace = self
.workspaces .workspaces
.spaces_mut() .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 { if let Some(workspace) = maybe_fullscreen_workspace {
element_geo = Some(workspace.fullscreen_geometry().unwrap()); let fs = workspace
let (surface, state, _) = workspace.remove_fullscreen().unwrap(); .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); self.remap_unfullscreened_window(surface, state, evlh);
}; };
@ -3902,19 +3905,15 @@ impl Shell {
/// Get the window geometry of a keyboard focus target /// Get the window geometry of a keyboard focus target
pub fn focused_geometry(&self, target: &KeyboardFocusTarget) -> Option<Rectangle<i32, Global>> { pub fn focused_geometry(&self, target: &KeyboardFocusTarget) -> Option<Rectangle<i32, Global>> {
match target { match target {
KeyboardFocusTarget::Fullscreen(surface) => { KeyboardFocusTarget::Fullscreen(surface) => surface
if let Some(workspace) = surface .wl_surface()
.wl_surface() .and_then(|s| self.workspace_for_surface(&s))
.and_then(|s| self.workspace_for_surface(&s)) .and_then(|(handle, _)| self.workspaces.space_for_handle(&handle))
.and_then(|(handle, _)| self.workspaces.space_for_handle(&handle)) .map(|workspace| {
{
workspace workspace
.fullscreen_geometry() .fullscreen_geometry_for_surface(surface)
.map(|f| f.to_global(workspace.output())) .to_global(workspace.output())
} else { }),
None
}
}
_ => { _ => {
if let Some(element) = self.focused_element(target) { if let Some(element) = self.focused_element(target) {
self.element_geometry(&element) self.element_geometry(&element)
@ -4266,9 +4265,8 @@ impl Shell {
self.workspaces.sets.values_mut().find_map(|set| { self.workspaces.sets.values_mut().find_map(|set| {
set.workspaces.iter_mut().find_map(|workspace| { set.workspaces.iter_mut().find_map(|workspace| {
let window = workspace let window = workspace
.get_fullscreen() .get_fullscreen_surfaces()
.cloned() .map(|f| f.surface.clone())
.into_iter()
.chain(workspace.mapped().map(|m| m.active_window())) .chain(workspace.mapped().map(|m| m.active_window()))
.find(|s| s == surface); .find(|s| s == surface);
window.map(|s| (workspace, s)) window.map(|s| (workspace, s))
@ -4768,15 +4766,16 @@ impl Shell {
&mut self, &mut self,
surface: &S, surface: &S,
output: Output, output: Output,
loop_handle: &LoopHandle<'static, State>, _loop_handle: &LoopHandle<'static, State>,
) -> Option<KeyboardFocusTarget> ) -> Option<KeyboardFocusTarget>
where where
CosmicSurface: PartialEq<S>, CosmicSurface: PartialEq<S>,
{ {
let mapped = self.element_for_surface(surface).cloned()?; let mapped = self.element_for_surface(surface).cloned()?;
let seat = self.seats.last_active().clone();
let window; let window;
let old_fullscreen = if let Some((old_output, set)) = self if let Some((old_output, set)) = self
.workspaces .workspaces
.sets .sets
.iter_mut() .iter_mut()
@ -4823,7 +4822,7 @@ impl Shell {
let workspace = self.active_space_mut(&output).unwrap(); let workspace = self.active_space_mut(&output).unwrap();
workspace.map_fullscreen( workspace.map_fullscreen(
&window, &window,
None, &seat,
Some(FullscreenRestoreState::Sticky { Some(FullscreenRestoreState::Sticky {
output: old_output, output: old_output,
state: FloatingRestoreData { state: FloatingRestoreData {
@ -4834,7 +4833,7 @@ impl Shell {
}, },
}), }),
Some(from), Some(from),
) );
} else if let Some(workspace) = self.space_for_mut(&mapped) { } else if let Some(workspace) = self.space_for_mut(&mapped) {
if mapped.is_minimized() { if mapped.is_minimized() {
// TODO: Rewrite the `MinimizedWindow` to restore to fullscreen // TODO: Rewrite the `MinimizedWindow` to restore to fullscreen
@ -4860,7 +4859,7 @@ impl Shell {
workspace.map_fullscreen( workspace.map_fullscreen(
&window, &window,
None, &seat,
match state { match state {
WorkspaceRestoreData::Floating(floating_state) => { WorkspaceRestoreData::Floating(floating_state) => {
floating_state.map(|state| FullscreenRestoreState::Floating { floating_state.map(|state| FullscreenRestoreState::Floating {
@ -4877,15 +4876,11 @@ impl Shell {
WorkspaceRestoreData::Fullscreen(_) => unreachable!(), WorkspaceRestoreData::Fullscreen(_) => unreachable!(),
}, },
Some(from), Some(from),
) );
} else { } else {
return None; return None;
}; };
if let Some((old_fullscreen, restore, _)) = old_fullscreen {
self.remap_unfullscreened_window(old_fullscreen, restore, loop_handle);
}
Some(KeyboardFocusTarget::Fullscreen(window)) Some(KeyboardFocusTarget::Fullscreen(window))
} }
@ -4900,11 +4895,12 @@ impl Shell {
let maybe_workspace = self.workspaces.iter_mut().find_map(|(_, s)| { let maybe_workspace = self.workspaces.iter_mut().find_map(|(_, s)| {
s.workspaces s.workspaces
.iter_mut() .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 { 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_output(&old_fullscreen, &workspace.output);
toplevel_leave_workspace(&old_fullscreen, &workspace.handle); toplevel_leave_workspace(&old_fullscreen, &workspace.handle);

View file

@ -108,7 +108,7 @@ pub struct Workspace {
pub floating_layer: FloatingLayout, pub floating_layer: FloatingLayout,
pub minimized_windows: Vec<MinimizedWindow>, pub minimized_windows: Vec<MinimizedWindow>,
pub tiling_enabled: bool, pub tiling_enabled: bool,
pub fullscreen: Option<FullscreenSurface>, pub fullscreen_surfaces: Vec<FullscreenSurface>,
pub pinned: bool, pub pinned: bool,
pub id: Option<String>, pub id: Option<String>,
@ -383,7 +383,7 @@ impl Workspace {
floating_layer, floating_layer,
tiling_enabled, tiling_enabled,
minimized_windows: Vec::new(), minimized_windows: Vec::new(),
fullscreen: None, fullscreen_surfaces: Vec::new(),
pinned: false, pinned: false,
id: None, id: None,
handle, handle,
@ -416,7 +416,7 @@ impl Workspace {
floating_layer, floating_layer,
tiling_enabled: pinned.tiling_enabled, tiling_enabled: pinned.tiling_enabled,
minimized_windows: Vec::new(), minimized_windows: Vec::new(),
fullscreen: None, fullscreen_surfaces: Vec::new(),
pinned: true, pinned: true,
id: pinned.id.clone(), id: pinned.id.clone(),
handle, handle,
@ -454,7 +454,10 @@ impl Workspace {
#[profiling::function] #[profiling::function]
pub fn refresh(&mut self) { 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.floating_layer.refresh();
self.tiling_layer.refresh(); self.tiling_layer.refresh();
} }
@ -478,14 +481,15 @@ impl Workspace {
self.is_empty() && !self.has_activation_token(xdg_activation_state) && !self.pinned 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) { pub fn refresh_focus_stack(&mut self) {
for (seat, stack) in self.focus_stack.0.iter_mut() { for (seat, stack) in self.focus_stack.0.iter_mut() {
let fullscreen = self let fullscreen_surfaces: Vec<&CosmicSurface> = self
.fullscreen .fullscreen_surfaces
.as_ref() .iter()
.filter(|f| f.alive()) .filter(|f| f.alive() && f.ended_at.is_none())
.filter(|f| f.ended_at.is_none()) .map(|f| &f.surface)
.map(|f| &f.surface); .collect();
// Move grab is treated as focused, so don't change focus to a // Move grab is treated as focused, so don't change focus to a
// window while grab exists. // window while grab exists.
@ -506,7 +510,7 @@ impl Workspace {
.chain(move_mapped.iter()) .chain(move_mapped.iter())
}; };
stack.retain(|w| match w { 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), FocusTarget::Window(w) => mapped().any(|m| w == m),
}); });
} }
@ -515,15 +519,12 @@ impl Workspace {
pub fn animations_going(&self) -> bool { pub fn animations_going(&self) -> bool {
self.tiling_layer.animations_going() self.tiling_layer.animations_going()
|| self.floating_layer.animations_going() || self.floating_layer.animations_going()
|| self || self.fullscreen_surfaces.iter().any(|f| f.is_animating())
.fullscreen
.as_ref()
.is_some_and(|f| f.start_at.is_some() || f.ended_at.is_some())
|| self.dirty.swap(false, Ordering::SeqCst) || self.dirty.swap(false, Ordering::SeqCst)
} }
pub fn update_animations(&mut self) -> HashMap<ClientId, Client> { pub fn update_animations(&mut self) -> HashMap<ClientId, Client> {
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() { if let Some(start) = f.start_at.as_ref() {
let duration_since = Instant::now().duration_since(*start); let duration_since = Instant::now().duration_since(*start);
if duration_since > FULLSCREEN_ANIMATION_DURATION { if duration_since > FULLSCREEN_ANIMATION_DURATION {
@ -531,16 +532,18 @@ impl Workspace {
self.dirty.store(true, Ordering::SeqCst); 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(); let clients = self.tiling_layer.update_animation_state();
self.floating_layer.update_animation_state(); self.floating_layer.update_animation_state();
clients clients
@ -574,7 +577,11 @@ impl Workspace {
toplevel_enter_output(&surface, output); 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_leave_output(&f.surface, &self.output);
toplevel_enter_output(&f.surface, output); toplevel_enter_output(&f.surface, output);
} }
@ -665,12 +672,13 @@ impl Workspace {
where where
CosmicSurface: PartialEq<S>, CosmicSurface: PartialEq<S>,
{ {
if self if let Some(idx) = self
.fullscreen .fullscreen_surfaces
.as_ref() .iter()
.is_some_and(|f| f.ended_at.is_none() && &f.surface == surface) .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(( return Some((
surface, surface,
WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map( WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map(
@ -723,24 +731,28 @@ impl Workspace {
Some((mapped.active_window(), layer)) Some((mapped.active_window(), layer))
} }
pub fn fullscreen_geometry(&self) -> Option<Rectangle<i32, Local>> { pub fn fullscreen_geometry_for_surface(
self.fullscreen.as_ref().map(|fullscreen| { &self,
let bbox = fullscreen.surface.bbox().as_local(); surface: &CosmicSurface,
) -> Rectangle<i32, Local> {
let bbox = surface.bbox().as_local();
let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local()); let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local());
if bbox != full_geo { if bbox != full_geo {
if bbox.size.w < full_geo.size.w { if bbox.size.w < full_geo.size.w {
full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2; full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2;
full_geo.size.w = bbox.size.w; 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;
}
} }
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<i32, Local> {
self.fullscreen_geometry_for_surface(&fullscreen.surface)
} }
pub fn element_for_surface<S>(&self, surface: &S) -> Option<&CosmicMapped> pub fn element_for_surface<S>(&self, surface: &S) -> Option<&CosmicMapped>
@ -780,13 +792,12 @@ impl Workspace {
let stack = self.focus_stack.get(seat); let stack = self.focus_stack.get(seat);
let last_focused = stack.last(); let last_focused = stack.last();
if let Some(fullscreen) = self.fullscreen.as_ref() if let Some(fullscreen) = self.fullscreen_surfaces.iter().find(|f| {
&& last_focused.is_some_and( !f.is_animating()
|t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface), && last_focused
) .is_some_and(|t| matches!(t, FocusTarget::Fullscreen(s) if s == &f.surface))
&& !fullscreen.is_animating() }) {
{ let geometry = self.fullscreen_geometry_for(fullscreen);
let geometry = self.fullscreen_geometry().unwrap();
return fullscreen_element_under(fullscreen, geometry); return fullscreen_element_under(fullscreen, geometry);
} }
@ -795,9 +806,9 @@ impl Workspace {
.or_else(|| self.tiling_layer.popup_element_under(location, seat)) .or_else(|| self.tiling_layer.popup_element_under(location, seat))
.or_else(|| { .or_else(|| {
if last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_))) 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); return fullscreen_element_under(fullscreen, geometry);
} }
None None
@ -830,13 +841,12 @@ impl Workspace {
let stack = self.focus_stack.get(seat); let stack = self.focus_stack.get(seat);
let last_focused = stack.last(); let last_focused = stack.last();
if let Some(fullscreen) = self.fullscreen.as_ref() if let Some(fullscreen) = self.fullscreen_surfaces.iter().find(|fs| {
&& last_focused.is_some_and( !fs.is_animating()
|t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface), && last_focused
) .is_some_and(|t| matches!(t, FocusTarget::Fullscreen(f) if f == &fs.surface))
&& !fullscreen.is_animating() }) {
{ let geometry = self.fullscreen_geometry_for(fullscreen);
let geometry = self.fullscreen_geometry().unwrap();
return fullscreen_element_under(fullscreen, geometry); return fullscreen_element_under(fullscreen, geometry);
} }
@ -845,9 +855,9 @@ impl Workspace {
.or_else(|| self.tiling_layer.toplevel_element_under(location, seat)) .or_else(|| self.tiling_layer.toplevel_element_under(location, seat))
.or_else(|| { .or_else(|| {
if last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_))) 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); return fullscreen_element_under(fullscreen, geometry);
} }
None None
@ -867,7 +877,7 @@ impl Workspace {
let check_fullscreen = |fullscreen: &FullscreenSurface| { let check_fullscreen = |fullscreen: &FullscreenSurface| {
if !fullscreen.is_animating() { if !fullscreen.is_animating() {
let geometry = self.fullscreen_geometry().unwrap(); let geometry = self.fullscreen_geometry_for(fullscreen);
return fullscreen return fullscreen
.surface .surface
.0 .0
@ -891,21 +901,16 @@ impl Workspace {
let stack = self.focus_stack.get(seat); let stack = self.focus_stack.get(seat);
let last_focused = stack.last(); let last_focused = stack.last();
self.fullscreen self.fullscreen_surfaces
.as_ref() .iter()
.filter(|f| last_focused.is_some_and(|t| t == &f.surface)) .find(|f| last_focused.is_some_and(|t| t == &f.surface))
.and_then(check_fullscreen) .and_then(check_fullscreen)
.or_else(|| self.floating_layer.popup_surface_under(location, seat)) .or_else(|| self.floating_layer.popup_surface_under(location, seat))
.or_else(|| { .or_else(|| {
self.tiling_layer self.tiling_layer
.popup_surface_under(location, overview, seat) .popup_surface_under(location, overview, seat)
}) })
.or_else(|| { .or_else(|| self.get_fullscreen(seat).and_then(check_fullscreen))
self.fullscreen
.as_ref()
.filter(|f| last_focused.is_none_or(|t| t != &f.surface))
.and_then(check_fullscreen)
})
.map(|(m, p)| (m, p.to_global(&self.output))) .map(|(m, p)| (m, p.to_global(&self.output)))
} }
@ -922,7 +927,7 @@ impl Workspace {
let check_fullscreen = |fullscreen: &FullscreenSurface| { let check_fullscreen = |fullscreen: &FullscreenSurface| {
if !fullscreen.is_animating() { if !fullscreen.is_animating() {
let geometry = self.fullscreen_geometry().unwrap(); let geometry = self.fullscreen_geometry_for(fullscreen);
return fullscreen return fullscreen
.surface .surface
.focus_under( .focus_under(
@ -940,21 +945,16 @@ impl Workspace {
let stack = self.focus_stack.get(seat); let stack = self.focus_stack.get(seat);
let last_focused = stack.last(); let last_focused = stack.last();
self.fullscreen self.fullscreen_surfaces
.as_ref() .iter()
.filter(|f| last_focused.is_some_and(|t| t == &f.surface)) .find(|f| last_focused.is_some_and(|t| t == &f.surface))
.and_then(check_fullscreen) .and_then(check_fullscreen)
.or_else(|| self.floating_layer.toplevel_surface_under(location, seat)) .or_else(|| self.floating_layer.toplevel_surface_under(location, seat))
.or_else(|| { .or_else(|| {
self.tiling_layer self.tiling_layer
.toplevel_surface_under(location, overview, seat) .toplevel_surface_under(location, overview, seat)
}) })
.or_else(|| { .or_else(|| self.get_fullscreen(seat).and_then(check_fullscreen))
self.fullscreen
.as_ref()
.filter(|f| last_focused.is_none_or(|t| t != &f.surface))
.and_then(check_fullscreen)
})
.map(|(m, p)| (m, p.to_global(&self.output))) .map(|(m, p)| (m, p.to_global(&self.output)))
} }
@ -1026,10 +1026,14 @@ impl Workspace {
where where
CosmicSurface: PartialEq<S>, CosmicSurface: PartialEq<S>,
{ {
if self.get_fullscreen().is_some_and(|s| s == surface) { if let Some(idx) = self
let fullscreen_state = self.fullscreen.clone().unwrap(); .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.previous_geometry = Some(to);
f.ended_at = Some( f.ended_at = Some(
Instant::now() Instant::now()
@ -1133,16 +1137,20 @@ impl Workspace {
)> { )> {
match window { match window {
MinimizedWindow::Fullscreen { previous, surface } => { MinimizedWindow::Fullscreen { previous, surface } => {
let old_fullscreen = self.remove_fullscreen();
surface.set_minimized(false); 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, surface,
previous_state: previous.clone().map(|p| p.previous_state), previous_state: previous.clone().map(|p| p.previous_state),
previous_geometry: previous.map(|p| p.previous_geometry), previous_geometry: previous.map(|p| p.previous_geometry),
start_at: None, start_at: None,
ended_at: None, ended_at: None,
}); });
old_fullscreen self.dirty.store(true, Ordering::SeqCst);
None
} }
MinimizedWindow::Floating { window, previous } => { MinimizedWindow::Floating { window, previous } => {
let current_output_size = self.output.geometry().size.as_logical(); let current_output_size = self.output.geometry().size.as_logical();
@ -1230,13 +1238,7 @@ impl Workspace {
seat: impl Into<Option<&'a Seat<State>>>, seat: impl Into<Option<&'a Seat<State>>>,
restore: Option<FullscreenRestoreState>, restore: Option<FullscreenRestoreState>,
previous_geometry: Option<Rectangle<i32, Local>>, previous_geometry: Option<Rectangle<i32, Local>>,
) -> Option<( ) {
CosmicSurface,
Option<FullscreenRestoreState>,
Option<Rectangle<i32, Local>>,
)> {
let res = self.remove_fullscreen();
window.set_fullscreen(true); window.set_fullscreen(true);
window.set_geometry(self.output.geometry(), 0); window.set_geometry(self.output.geometry(), 0);
window.send_configure(); window.send_configure();
@ -1249,96 +1251,137 @@ impl Workspace {
self.focus_stack.get_mut(seat).append(window.clone()); 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(), surface: window.clone(),
previous_state: restore, previous_state: restore,
previous_geometry, previous_geometry,
start_at: Some(Instant::now()), start_at: Some(Instant::now()),
ended_at: None, ended_at: None,
}); });
res
} }
#[must_use] #[must_use]
pub fn take_fullscreen( pub fn take_fullscreen<S>(
&mut self, &mut self,
surface: &S,
) -> Option<(
CosmicSurface,
Option<FullscreenRestoreState>,
Option<Rectangle<i32, Local>>,
)>
where
CosmicSurface: PartialEq<S>,
{
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<( ) -> Option<(
CosmicSurface, CosmicSurface,
Option<FullscreenRestoreState>, Option<FullscreenRestoreState>,
Option<Rectangle<i32, Local>>, Option<Rectangle<i32, Local>>,
)> { )> {
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() { for focus_stack in self.focus_stack.0.values_mut() {
focus_stack.retain(|t| t != &surface.surface); 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(( Some((
surface.surface, surface.surface.clone(),
surface.previous_state, surface.previous_state.clone(),
surface.previous_geometry, surface.previous_geometry,
)) ))
} }
#[must_use] #[must_use]
pub fn remove_fullscreen( pub fn remove_fullscreen_surface<S>(
&mut self, &mut self,
surface: &S,
) -> Option<( ) -> Option<(
CosmicSurface, CosmicSurface,
Option<FullscreenRestoreState>, Option<FullscreenRestoreState>,
Option<Rectangle<i32, Local>>, Option<Rectangle<i32, Local>>,
)> { )>
if let Some(surface) = self.fullscreen.as_mut() { where
if surface.ended_at.is_some() { CosmicSurface: PartialEq<S>,
return None; {
} let idx = self
.fullscreen_surfaces
if surface.surface.alive() { .iter()
surface.surface.output_leave(&self.output); .position(|f| f.ended_at.is_none() && &f.surface == surface)?;
surface.surface.set_fullscreen(false); self.remove_fullscreen_at(idx)
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
}
} }
pub fn get_fullscreen(&self) -> Option<&CosmicSurface> { pub fn get_fullscreen(&self, seat: &Seat<State>) -> Option<&FullscreenSurface> {
self.fullscreen let stack = self.focus_stack.get(seat);
.as_ref() stack
.filter(|f| f.alive()) .iter()
.filter(|f| f.ended_at.is_none()) .find_map(|t| {
.map(|f| &f.surface) 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<Item = &FullscreenSurface> {
self.fullscreen_surfaces
.iter()
.filter(|f| f.alive() && f.ended_at.is_none())
} }
pub fn resize( pub fn resize(
@ -1465,14 +1508,18 @@ impl Workspace {
self.floating_layer.mapped().count() self.floating_layer.mapped().count()
+ self.tiling_layer.mapped().count() + self.tiling_layer.mapped().count()
+ self.minimized_windows.len() + 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 { pub fn is_empty(&self) -> bool {
self.floating_layer.mapped().next().is_none() self.floating_layer.mapped().next().is_none()
&& self.tiling_layer.mapped().next().is_none() && self.tiling_layer.mapped().next().is_none()
&& self.minimized_windows.is_empty() && self.minimized_windows.is_empty()
&& self.fullscreen.is_none() && self.fullscreen_surfaces.is_empty()
} }
pub fn is_floating<S>(&self, surface: &S) -> bool pub fn is_floating<S>(&self, surface: &S) -> bool
@ -1579,8 +1626,11 @@ impl Workspace {
}; };
let focused = self.focus_stack.get(last_active_seat).last().cloned(); let focused = self.focus_stack.get(last_active_seat).last().cloned();
let mut fullscreen_elements = if let Some(fullscreen) = self.fullscreen.as_ref() { let render_fullscreen = |fullscreen: &FullscreenSurface,
let fullscreen_geo = self.fullscreen_geometry().unwrap(); renderer: &mut R,
output_scale: f64|
-> Vec<WorkspaceRenderElement<R>> {
let fullscreen_geo = self.fullscreen_geometry_for(fullscreen);
let previous_geo = fullscreen let previous_geo = fullscreen
.previous_geometry .previous_geometry
.as_ref() .as_ref()
@ -1650,20 +1700,35 @@ impl Workspace {
.into_iter() .into_iter()
.map(animation_rescale) .map(animation_rescale)
.collect::<Vec<_>>() .collect::<Vec<_>>()
} else {
Vec::new()
}; };
let top_fullscreen = self.get_fullscreen(last_active_seat);
let mut fullscreen_elements: Vec<WorkspaceRenderElement<R>> = 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(_))) { if matches!(focused, Some(FocusTarget::Fullscreen(_))) {
elements.append(&mut fullscreen_elements); 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(_))) if !matches!(focused, Some(FocusTarget::Fullscreen(_)))
|| any_fullscreen_animating
|| self || self
.fullscreen .fullscreen_surfaces
.as_ref() .iter()
.map(|f| f.start_at.is_some() || f.ended_at.is_some()) .all(|f| !f.alive() || f.ended_at.is_some())
.unwrap_or(true)
{ {
// floating surfaces // floating surfaces
let alpha = match &overview.0 { let alpha = match &overview.0 {
@ -1780,8 +1845,12 @@ impl Workspace {
layer_map.non_exclusive_zone().as_local() layer_map.non_exclusive_zone().as_local()
}; };
if let Some(fullscreen) = self.fullscreen.as_ref() { // Render popups for the top (most recently focused) fullscreen
let fullscreen_geo = self.fullscreen_geometry().unwrap(); 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 let previous_geo = fullscreen
.previous_geometry .previous_geometry
.as_ref() .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(_))) if !matches!(focus_stack.last(), Some(FocusTarget::Fullscreen(_)))
|| any_fullscreen_animating
|| self || self
.fullscreen .fullscreen_surfaces
.as_ref() .iter()
.map(|f| f.start_at.is_some() || f.ended_at.is_some()) .all(|f| !f.alive() || f.ended_at.is_some())
.unwrap_or(true)
{ {
// floating surfaces // floating surfaces
let alpha = match &overview.0 { let alpha = match &overview.0 {

View file

@ -996,8 +996,8 @@ impl Common {
// normal windows // normal windows
for space in shell.workspaces.spaces() { for space in shell.workspaces.spaces() {
if let Some(window) = space.get_fullscreen() { if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) {
window.with_surfaces(processor); fs.surface.with_surfaces(processor);
} }
space.mapped().for_each(|mapped| { space.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() { for (window, _) in mapped.windows() {
@ -1162,15 +1162,16 @@ impl Common {
}); });
if let Some(active) = shell.active_space(output) { if let Some(active) = shell.active_space(output) {
if let Some(window) = active.get_fullscreen() if let Some(fs) = active.get_fullscreen(shell.seats.last_active())
&& let Some(feedback) = window && let Some(feedback) = fs
.surface
.wl_surface() .wl_surface()
.and_then(|wl_surface| { .and_then(|wl_surface| {
advertised_node_for_surface(&wl_surface, &self.display_handle) advertised_node_for_surface(&wl_surface, &self.display_handle)
}) })
.and_then(&mut dmabuf_feedback) .and_then(&mut dmabuf_feedback)
{ {
window.send_dmabuf_feedback( fs.surface.send_dmabuf_feedback(
output, output,
&feedback, &feedback,
render_element_states, render_element_states,
@ -1356,8 +1357,9 @@ impl Common {
}); });
if let Some(active) = shell.active_space(output) { if let Some(active) = shell.active_space(output) {
if let Some(window) = active.get_fullscreen() { if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) {
window.send_frame(output, time, throttle(window), should_send); fs.surface
.send_frame(output, time, throttle(&fs.surface), should_send);
} }
active.mapped().for_each(|mapped| { active.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() { for (window, _) in mapped.windows() {
@ -1377,9 +1379,9 @@ impl Common {
.spaces_for_output(output) .spaces_for_output(output)
.filter(|w| w.handle != active.handle) .filter(|w| w.handle != active.handle)
{ {
if let Some(window) = space.get_fullscreen() { if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) {
let throttle = min(throttle(space), throttle(window)); let throttle = min(throttle(space), throttle(&fs.surface));
window.send_frame(output, time, throttle, |_, _| None); fs.surface.send_frame(output, time, throttle, |_, _| None);
} }
space.mapped().for_each(|mapped| { space.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() { for (window, _) in mapped.windows() {

View file

@ -51,9 +51,11 @@ impl DmabufHandler for State {
let is_fullscreen = shell let is_fullscreen = shell
.workspaces .workspaces
.space_for_handle(&handle)? .space_for_handle(&handle)?
.fullscreen .fullscreen_surfaces
.as_ref() .iter()
.is_some_and(|f| f.surface.has_surface(surface, WindowSurfaceType::all())); .any(|f| {
f.ended_at.is_none() && f.surface.has_surface(surface, WindowSurfaceType::all())
});
let node = kms let node = kms
.drm_devices .drm_devices

View file

@ -42,7 +42,7 @@ impl ToplevelManagementHandler for State {
.spaces_for_output(output) .spaces_for_output(output)
.enumerate() .enumerate()
.find(|(_, w)| { .find(|(_, w)| {
w.get_fullscreen().is_some_and(|f| f == window) w.get_fullscreen_surfaces().any(|f| &f.surface == window)
|| w.mapped() || w.mapped()
.flat_map(|m| m.windows().map(|(s, _)| s)) .flat_map(|m| m.windows().map(|(s, _)| s))
.any(|w| &w == window) .any(|w| &w == window)

View file

@ -219,8 +219,8 @@ impl State {
.workspaces .workspaces
.space_for_handle(&workspace) .space_for_handle(&workspace)
.unwrap() .unwrap()
.get_fullscreen() .get_fullscreen(&seat)
.cloned() .map(|f| f.surface.clone())
.map(KeyboardFocusTarget::Fullscreen) .map(KeyboardFocusTarget::Fullscreen)
else { else {
return; return;
@ -232,8 +232,8 @@ impl State {
if let Some(surface) = shell if let Some(surface) = shell
.workspaces .workspaces
.space_for_handle(&workspace) .space_for_handle(&workspace)
.and_then(|w| w.get_fullscreen()) .and_then(|w| w.get_fullscreen(&seat))
.cloned() .map(|f| f.surface.clone())
{ {
shell.append_focus_stack(surface, &seat) shell.append_focus_stack(surface, &seat)
} }

View file

@ -68,7 +68,7 @@ impl Shell {
unconstrain_xdg_popup(surface, window_loc, output.geometry()); unconstrain_xdg_popup(surface, window_loc, output.geometry());
} }
} else if let Some(output) = self.workspaces.spaces().find_map(|w| { } 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()) (f.surface.wl_surface().as_deref() == Some(&parent)).then_some(w.output())
}) })
}) { }) {

View file

@ -575,16 +575,15 @@ impl Common {
.filter(|(i, _)| *i != set.active), .filter(|(i, _)| *i != set.active),
) )
.flat_map(|(_, workspace)| { .flat_map(|(_, workspace)| {
let focus_last =
workspace.focus_stack.get(seat).last().cloned();
workspace workspace
.get_fullscreen() .get_fullscreen_surfaces()
.filter(|f| { .filter(|f| {
workspace focus_last.as_ref().is_some_and(|t| t == &f.surface)
.focus_stack
.get(seat)
.last()
.is_some_and(|t| &t == f)
}) })
.cloned() .map(|f| f.surface.clone())
.collect::<Vec<_>>()
.into_iter() .into_iter()
.chain(workspace.mapped().flat_map(|mapped| { .chain(workspace.mapped().flat_map(|mapped| {
let active = mapped.active_window(); let active = mapped.active_window();
@ -603,15 +602,14 @@ impl Common {
})) }))
.chain( .chain(
workspace workspace
.get_fullscreen() .get_fullscreen_surfaces()
.filter(|f| { .filter(|f| {
workspace focus_last
.focus_stack .as_ref()
.get(seat) .is_none_or(|t| t != &f.surface)
.last()
.is_none_or(|t| &t != f)
}) })
.cloned() .map(|f| f.surface.clone())
.collect::<Vec<_>>()
.into_iter(), .into_iter(),
) )
.chain( .chain(