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

@ -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<Rectangle<i32, Global>> {
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<KeyboardFocusTarget>
where
CosmicSurface: PartialEq<S>,
{
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);