shell: handle fullscreen windows on a dedicated layer

I hoped to split this up into multiple commits, but the api
changes to `shell/workspace.rs` were to invasive to feasibly do this.

Here is a rough list of changes:

- Fullscreen windows aren't mapped to other layers anymore
  - This they need their own logic for:
    - Sending frames
    - Dmabuf Feedback
    - Primary outputs
    - On commit handlers
    - cursor tests
  - They get their own unmap/remap logic
  - They get a new restore state similar to minimized windows
    - Refactored the minimized window state to reuse as much as possible
      here
  - They need to be part of focus stacks, which means adjusting them
    to a new type `FocusTarget` as they previously only handled
    `CosmicMapped`.
  - Various shell handlers (minimize, move, menu) now have dedicated
    logic for fullscreen surfaces
    - This was partially necessary due to relying on CosmicSurface now,
      partially because they should've had their own logic from the
      start. E.g. the context menu is now reflecting the fullscreen
      state
- Fullscreen windows may be rendered behind other windows now, when they
  loose focus.
  - This needed changes to input handling / rendering
This commit is contained in:
Victoria Brekenfeld 2025-06-25 17:54:27 +02:00 committed by Victoria Brekenfeld
parent 8ef6c161a0
commit adedb705e7
23 changed files with 2554 additions and 1796 deletions

View file

@ -18,7 +18,7 @@ use crate::{
},
focus::{
target::{KeyboardFocusTarget, PointerFocusTarget, WindowGroup},
FocusStackMut,
FocusStackMut, FocusTarget,
},
grabs::ResizeEdge,
layout::Orientation,
@ -130,7 +130,7 @@ impl TreeQueue {
pub struct TilingLayout {
output: Output,
queue: TreeQueue,
placeholder_id: Id,
backdrop_id: Id,
swapping_stack_surface_id: Id,
last_overview_hover: Option<(Option<Instant>, TargetZone)>,
pub theme: cosmic::Theme,
@ -157,11 +157,18 @@ pub enum Data {
minimize_rect: Option<Rectangle<i32, Local>>,
},
Placeholder {
id: Id,
last_geometry: Rectangle<i32, Local>,
initial_placeholder: bool,
type_: PlaceholderType,
},
}
#[derive(Debug, Clone)]
pub enum PlaceholderType {
GrabbedWindow,
DropZone,
}
impl Data {
fn new_group(orientation: Orientation, geo: Rectangle<i32, Local>) -> Data {
Data::Group {
@ -331,8 +338,8 @@ enum FocusedNodeData {
Window(CosmicMapped),
}
#[derive(Debug)]
pub struct MinimizedTilingState {
#[derive(Debug, Clone)]
pub struct RestoreTilingState {
pub parent: Option<id_tree::NodeId>,
pub sibling: Option<id_tree::NodeId>,
pub orientation: Orientation,
@ -352,7 +359,7 @@ impl TilingLayout {
animation_start: None,
},
output: output.clone(),
placeholder_id: Id::new(),
backdrop_id: Id::new(),
swapping_stack_surface_id: Id::new(),
last_overview_hover: None,
theme,
@ -383,7 +390,7 @@ impl TilingLayout {
pub fn map<'a>(
&mut self,
window: CosmicMapped,
focus_stack: Option<impl Iterator<Item = &'a CosmicMapped> + 'a>,
focus_stack: Option<impl Iterator<Item = &'a FocusTarget> + 'a>,
direction: Option<Direction>,
) {
window.output_enter(&self.output, window.bbox());
@ -394,7 +401,7 @@ impl TilingLayout {
pub fn map_internal<'a>(
&mut self,
window: impl Into<CosmicMapped>,
focus_stack: Option<impl Iterator<Item = &'a CosmicMapped> + 'a>,
focus_stack: Option<impl Iterator<Item = &'a FocusTarget> + 'a>,
direction: Option<Direction>,
minimize_rect: Option<Rectangle<i32, Local>>,
) {
@ -422,18 +429,17 @@ impl TilingLayout {
self.queue.push_tree(tree, duration, blocker);
}
pub fn remap_minimized<'a>(
pub fn remap<'a>(
&mut self,
window: CosmicMapped,
from: Rectangle<i32, Local>,
tiling_state: Option<MinimizedTilingState>,
focus_stack: Option<impl Iterator<Item = &'a CosmicMapped> + 'a>,
from: Option<Rectangle<i32, Local>>,
tiling_state: Option<RestoreTilingState>,
focus_stack: Option<impl Iterator<Item = &'a FocusTarget> + 'a>,
) {
window.set_minimized(false);
let gaps = self.gaps();
let mut tree = self.queue.trees.back().unwrap().0.copy_clone();
if let Some(MinimizedTilingState {
if let Some(RestoreTilingState {
parent,
sibling,
orientation,
@ -475,7 +481,7 @@ impl TilingLayout {
let new_node = Node::new(Data::Mapped {
mapped: window.clone(),
last_geometry: Rectangle::from_size((100, 100).into()),
minimize_rect: Some(from),
minimize_rect: from,
});
let new_id = tree
.insert(new_node, InsertBehavior::UnderNode(&parent_id))
@ -498,7 +504,7 @@ impl TilingLayout {
let new_node = Node::new(Data::Mapped {
mapped: window.clone(),
last_geometry: Rectangle::from_size((100, 100).into()),
minimize_rect: Some(from),
minimize_rect: from,
});
let new_id = tree.insert(new_node, InsertBehavior::AsRoot).unwrap();
@ -530,7 +536,7 @@ impl TilingLayout {
}
// else add as new_window
self.map_internal(window, focus_stack, None, Some(from));
self.map_internal(window, focus_stack, None, from);
}
fn map_to_tree(
@ -637,7 +643,7 @@ impl TilingLayout {
other: &mut Self,
other_handle: &WorkspaceHandle,
seat: &Seat<State>,
focus_stack: impl Iterator<Item = &'a CosmicMapped> + 'a,
focus_stack: impl Iterator<Item = &'a FocusTarget> + 'a,
desc: NodeDesc,
direction: Option<Direction>,
) -> Option<KeyboardFocusTarget> {
@ -658,7 +664,7 @@ impl TilingLayout {
let this_stack = this_mapped.stack_ref()?;
this_stack.remove_window(&stack_surface);
if !this_stack.alive() {
this.unmap(&this_mapped);
let _ = this.unmap(&this_mapped, None);
}
let mapped: CosmicMapped =
@ -1210,7 +1216,7 @@ impl TilingLayout {
let this_was_active = &this_stack.active() == this_surface;
let other_was_active = &other_stack.active() == other_surface;
this_stack.add_window(other_surface.clone(), Some(this_idx), None);
this_stack.remove_window(&this_surface);
this_stack.remove_window(this_surface);
other_stack.add_window(this_surface.clone(), Some(other_idx), None);
if this.output != other_output {
@ -1226,12 +1232,12 @@ impl TilingLayout {
toplevel_enter_workspace(other_surface, &this_desc.handle);
}
other_stack.remove_window(&other_surface);
other_stack.remove_window(other_surface);
if this_was_active {
this_stack.set_active(&other_surface);
this_stack.set_active(other_surface);
}
if other_was_active {
other_stack.set_active(&this_surface);
other_stack.set_active(this_surface);
}
return other
@ -1289,45 +1295,17 @@ impl TilingLayout {
&self.queue.trees.back().unwrap().0
}
pub fn unmap(&mut self, window: &CosmicMapped) -> bool {
if self.unmap_window_internal(window, false) {
window.output_leave(&self.output);
window.set_tiled(false);
*window.tiling_node_id.lock().unwrap() = None;
true
} else {
false
}
}
pub fn unmap_as_placeholder(&mut self, window: &CosmicMapped) -> Option<NodeId> {
let node_id = window.tiling_node_id.lock().unwrap().take()?;
let data = self
.queue
.trees
.back_mut()
.unwrap()
.0
.get_mut(&node_id)
.unwrap()
.data_mut();
*data = Data::Placeholder {
last_geometry: data.geometry().clone(),
initial_placeholder: true,
};
window.output_leave(&self.output);
window.set_tiled(false);
Some(node_id)
}
pub fn unmap_minimize(
pub fn unmap(
&mut self,
window: &CosmicMapped,
to: Rectangle<i32, Local>,
) -> Option<MinimizedTilingState> {
let node_id = window.tiling_node_id.lock().unwrap().clone()?;
to: Option<Rectangle<i32, Local>>,
) -> Result<Option<RestoreTilingState>, NodeIdError> {
let node_id = window
.tiling_node_id
.lock()
.unwrap()
.clone()
.ok_or(NodeIdError::NodeIdNoLongerValid)?;
let state = {
let tree = &self.queue.trees.back().unwrap().0;
tree.get(&node_id).unwrap().parent().and_then(|parent_id| {
@ -1343,7 +1321,7 @@ impl TilingLayout {
{
if sizes.len() == 2 {
// this group will be flattened
Some(MinimizedTilingState {
Some(RestoreTilingState {
parent: None,
sibling: parent.children().iter().cloned().find(|id| id != &node_id),
orientation: *orientation,
@ -1351,7 +1329,7 @@ impl TilingLayout {
sizes: sizes.clone(),
})
} else {
Some(MinimizedTilingState {
Some(RestoreTilingState {
parent: Some(parent_id.clone()),
sibling: None,
orientation: *orientation,
@ -1365,24 +1343,58 @@ impl TilingLayout {
})
};
if self.unmap_window_internal(window, true) {
let tree = &mut self
.queue
.trees
.get_mut(self.queue.trees.len() - 2)
.unwrap()
.0;
if let Data::Mapped {
minimize_rect: minimize_to,
..
} = tree.get_mut(&node_id).unwrap().data_mut()
{
*minimize_to = Some(to);
if self.unmap_window_internal(window, to.is_some()) {
if let Some(to) = to {
let tree = &mut self
.queue
.trees
.get_mut(self.queue.trees.len() - 2)
.unwrap()
.0;
if let Data::Mapped {
minimize_rect: minimize_to,
..
} = tree.get_mut(&node_id).unwrap().data_mut()
{
*minimize_to = Some(to);
}
}
window.set_minimized(true);
window.output_leave(&self.output);
window.set_tiled(false);
*window.tiling_node_id.lock().unwrap() = None;
} else {
return Err(NodeIdError::InvalidNodeIdForTree);
}
state
Ok(state)
}
pub fn unmap_as_placeholder(
&mut self,
window: &CosmicMapped,
type_: PlaceholderType,
) -> Option<NodeId> {
let node_id = window.tiling_node_id.lock().unwrap().take()?;
let data = self
.queue
.trees
.back_mut()
.unwrap()
.0
.get_mut(&node_id)
.unwrap()
.data_mut();
*data = Data::Placeholder {
id: Id::new(),
last_geometry: data.geometry().clone(),
type_,
};
window.output_leave(&self.output);
window.set_tiled(false);
Some(node_id)
}
fn unmap_window_internal(&mut self, mapped: &CosmicMapped, minimizing: bool) -> bool {
@ -1810,7 +1822,7 @@ impl TilingLayout {
&self,
direction: FocusDirection,
seat: &Seat<State>,
focus_stack: impl Iterator<Item = &'a CosmicMapped> + 'a,
focus_stack: impl Iterator<Item = &'a FocusTarget> + 'a,
swap_desc: Option<NodeDesc>,
) -> FocusResult {
let tree = &self.queue.trees.back().unwrap().0;
@ -2127,7 +2139,7 @@ impl TilingLayout {
match tree.get_mut(&node_id).unwrap().data_mut() {
Data::Mapped { mapped, .. } => {
mapped.convert_to_stack((&self.output, mapped.bbox()), self.theme.clone());
focus_stack.append(&mapped);
focus_stack.append(mapped.clone());
KeyboardFocusTarget::Element(mapped.clone())
}
_ => unreachable!(),
@ -2198,7 +2210,7 @@ impl TilingLayout {
};
for elem in new_elements.iter().rev() {
focus_stack.append(elem);
focus_stack.append(elem.clone());
}
match tree.get(&node_id).unwrap().data() {
@ -2286,7 +2298,7 @@ impl TilingLayout {
let mapped = CosmicMapped::from(stack);
*mapped.last_geometry.lock().unwrap() = Some(geo);
*mapped.tiling_node_id.lock().unwrap() = Some(last_active);
focus_stack.append(&mapped);
focus_stack.append(mapped.clone());
*data = Data::Mapped {
mapped: mapped.clone(),
last_geometry: geo,
@ -2785,14 +2797,23 @@ impl TilingLayout {
fn last_active_window<'a>(
tree: &Tree<Data>,
mut focus_stack: impl Iterator<Item = &'a CosmicMapped>,
mut focus_stack: impl Iterator<Item = &'a FocusTarget>,
) -> Option<(NodeId, CosmicMapped)> {
focus_stack
.find_map(|mapped| tree.root_node_id()
.and_then(|root| tree.traverse_pre_order_ids(root).unwrap()
.find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Mapped { mapped: m, .. }) if m == mapped))
).map(|id| (id, mapped.clone()))
)
focus_stack.find_map(|target| {
tree.root_node_id().and_then(|root| {
tree.traverse_pre_order_ids(root).unwrap().find_map(|id| {
let Ok(Data::Mapped { mapped, .. }) = tree.get(&id).map(|n| n.data()) else {
return None;
};
if target == mapped {
Some((id, mapped.clone()))
} else {
None
}
})
})
})
}
fn currently_focused_node(
@ -3314,7 +3335,6 @@ impl TilingLayout {
) {
let gaps = self.gaps();
let last_overview_hover = &mut self.last_overview_hover;
let placeholder_id = &self.placeholder_id;
let tree = &self.queue.trees.back().unwrap().0;
let Some(root) = tree.root_node_id() else {
return;
@ -3346,7 +3366,7 @@ impl TilingLayout {
None,
1.0,
overview.alpha().unwrap(),
placeholder_id,
&self.backdrop_id,
Some(None),
None,
None,
@ -3372,7 +3392,7 @@ impl TilingLayout {
matches!(
child.data(),
Data::Placeholder {
initial_placeholder: false,
type_: PlaceholderType::DropZone,
..
}
)
@ -3407,7 +3427,7 @@ impl TilingLayout {
matches!(
child.data(),
Data::Placeholder {
initial_placeholder: false,
type_: PlaceholderType::DropZone,
..
}
)
@ -3620,7 +3640,7 @@ impl TilingLayout {
.unwrap()
.find(|id| match tree.get(id).unwrap().data() {
Data::Placeholder {
initial_placeholder: true,
type_: PlaceholderType::GrabbedWindow,
..
} => true,
_ => false,
@ -3679,7 +3699,7 @@ impl TilingLayout {
let matches = matches!(
tree.get(&id).unwrap().data(),
Data::Placeholder {
initial_placeholder: false,
type_: PlaceholderType::DropZone,
..
}
);
@ -3724,10 +3744,11 @@ impl TilingLayout {
let id = tree
.insert(
Node::new(Data::Placeholder {
id: Id::new(),
last_geometry: Rectangle::from_size(
(100, 100).into(),
),
initial_placeholder: false,
type_: PlaceholderType::DropZone,
}),
InsertBehavior::UnderNode(node_id),
)
@ -4001,7 +4022,7 @@ impl TilingLayout {
// but for that we have to associate focus with a tree (and animate focus changes properly)
1.0 - transition,
transition,
&self.placeholder_id,
&self.backdrop_id,
is_mouse_tiling,
swap_desc.clone(),
overview.1.as_ref().and_then(|(_, tree)| tree.clone()),
@ -4038,7 +4059,7 @@ impl TilingLayout {
seat,
transition,
transition,
&self.placeholder_id,
&self.backdrop_id,
is_mouse_tiling,
swap_desc.clone(),
overview.1.as_ref().and_then(|(_, tree)| tree.clone()),
@ -4076,7 +4097,7 @@ impl TilingLayout {
resize_indicator,
swap_desc.clone(),
&self.swapping_stack_surface_id,
&self.placeholder_id,
&self.backdrop_id,
theme,
));
@ -4150,7 +4171,7 @@ impl TilingLayout {
// but for that we have to associate focus with a tree (and animate focus changes properly)
1.0 - transition,
transition,
&self.placeholder_id,
&self.backdrop_id,
is_mouse_tiling,
swap_desc.clone(),
overview.1.as_ref().and_then(|(_, tree)| tree.clone()),
@ -4185,7 +4206,7 @@ impl TilingLayout {
seat,
transition,
transition,
&self.placeholder_id,
&self.backdrop_id,
is_mouse_tiling,
swap_desc.clone(),
overview.1.as_ref().and_then(|(_, tree)| tree.clone()),
@ -4259,7 +4280,7 @@ fn geometries_for_groupview<'a, R>(
seat: Option<&Seat<State>>,
alpha: f32,
transition: f32,
placeholder_id: &Id,
backdrop_id: &Id,
mouse_tiling: Option<Option<&TargetZone>>,
swap_desc: Option<NodeDesc>,
swap_tree: Option<&Tree<Data>>,
@ -4593,7 +4614,7 @@ where
elements.push(
BackdropShader::element(
*renderer,
placeholder_id.clone(),
backdrop_id.clone(),
pill_geo,
8.,
alpha * 0.4,
@ -4696,7 +4717,7 @@ where
elements.push(
BackdropShader::element(
*renderer,
placeholder_id.clone(),
backdrop_id.clone(),
Rectangle::new(
(geo.loc.x, geo.loc.y - 8).into(),
(geo.size.w, 16).into(),
@ -4738,7 +4759,7 @@ where
elements.push(
BackdropShader::element(
*renderer,
placeholder_id.clone(),
backdrop_id.clone(),
Rectangle::new(
(geo.loc.x - 8, geo.loc.y).into(),
(16, geo.size.h).into(),
@ -4855,7 +4876,7 @@ where
geometries.insert(node_id.clone(), geo);
}
Data::Placeholder { .. } => {
Data::Placeholder { id, .. } => {
geo.loc += (element_gap_left, element_gap_up).into();
geo.size -= (element_gap_left, element_gap_up).into();
geo.size -= (element_gap_right, element_gap_down).into();
@ -4866,7 +4887,7 @@ where
elements.push(
BackdropShader::element(
*renderer,
placeholder_id.clone(),
id.clone(),
geo,
8.,
alpha * 0.4,
@ -5165,7 +5186,7 @@ fn render_new_tree_windows<R>(
mut resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
swap_desc: Option<NodeDesc>,
swapping_stack_surface_id: &Id,
placeholder_id: &Id,
backdrop_id: &Id,
theme: &cosmic::theme::CosmicTheme,
) -> Vec<CosmicMappedRenderElement<R>>
where
@ -5230,7 +5251,7 @@ where
window_elements.push(
BackdropShader::element(
renderer,
placeholder_id.clone(),
backdrop_id.clone(),
focused_geo,
8.,
transition.unwrap_or(1.0) * 0.4,