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

@ -3,8 +3,9 @@
use crate::{
config::{Action, PrivateAction},
shell::{
focus::target::KeyboardFocusTarget, layout::tiling::SwapWindowGrab, FocusResult,
InvalidWorkspaceIndex, MoveResult, SeatExt, Trigger, WorkspaceDelta,
focus::{target::KeyboardFocusTarget, FocusTarget},
layout::tiling::SwapWindowGrab,
FocusResult, InvalidWorkspaceIndex, MoveResult, SeatExt, Trigger, WorkspaceDelta,
},
utils::prelude::*,
wayland::{
@ -288,13 +289,13 @@ impl State {
Action::MoveToWorkspace(x) | Action::SendToWorkspace(x) => x - 1,
_ => unreachable!(),
};
let res = self.common.shell.write().move_current_window(
let res = self.common.shell.write().move_current(
seat,
&focused_output,
(&focused_output, Some(workspace as usize)),
follow,
None,
&mut self.common.workspace_state.update(),
&self.common.event_loop_handle,
);
if let Ok(Some((target, _point))) = res {
Shell::set_focus(
@ -313,13 +314,13 @@ impl State {
};
let mut shell = self.common.shell.write();
let workspace = shell.workspaces.len(&focused_output).saturating_sub(1);
let res = shell.move_current_window(
let res = shell.move_current(
seat,
&focused_output,
(&focused_output, Some(workspace)),
matches!(x, Action::MoveToLastWorkspace),
None,
&mut self.common.workspace_state.update(),
&self.common.event_loop_handle,
);
// If the active workspace changed, the cursor_follows_focus should probably be checked
if let Ok(Some((target, _point))) = res {
@ -359,13 +360,13 @@ impl State {
.checked_add(1)
.ok_or(InvalidWorkspaceIndex)
.and_then(|workspace| {
shell.move_current_window(
shell.move_current(
seat,
&focused_output,
(&focused_output, Some(workspace)),
matches!(x, Action::MoveToNextWorkspace),
direction,
&mut self.common.workspace_state.update(),
&self.common.event_loop_handle,
)
})
};
@ -443,13 +444,13 @@ impl State {
.checked_sub(1)
.ok_or(InvalidWorkspaceIndex)
.and_then(|workspace| {
shell.move_current_window(
shell.move_current(
seat,
&focused_output,
(&focused_output, Some(workspace)),
matches!(x, Action::MoveToPreviousWorkspace),
direction,
&mut self.common.workspace_state.update(),
&self.common.event_loop_handle,
)
})
};
@ -539,16 +540,13 @@ impl State {
};
if let Ok(Some(new_pos)) = res {
let new_target = shell
.workspaces
.active(&next_output)
.unwrap()
.1
let workspace = shell.workspaces.active(&next_output).unwrap().1;
let new_target = workspace
.focus_stack
.get(&seat)
.last()
.cloned()
.map(KeyboardFocusTarget::from);
.map(Into::<KeyboardFocusTarget>::into);
std::mem::drop(shell);
let move_cursor = if let Some(under) = new_target {
@ -604,13 +602,13 @@ impl State {
if let Some(next_output) = next_output {
let res = {
let mut workspace_guard = self.common.workspace_state.update();
let res = shell.move_current_window(
let res = shell.move_current(
seat,
&focused_output,
(&next_output, None),
is_move_action,
Some(direction),
&mut workspace_guard,
&self.common.event_loop_handle,
);
if is_move_action && propagate {
@ -808,8 +806,10 @@ impl State {
let current_output = seat.active_output();
let mut shell = self.common.shell.write();
let workspace = shell.active_space(&current_output).unwrap();
if let Some(focused_window) = workspace.focus_stack.get(seat).last() {
if workspace.is_tiled(focused_window) {
if let Some(FocusTarget::Window(focused_window)) =
workspace.focus_stack.get(seat).last()
{
if workspace.is_tiled(&focused_window.active_window()) {
shell.set_overview_mode(
Some(Trigger::KeyboardMove(pattern.modifiers)),
self.common.event_loop_handle.clone(),
@ -827,11 +827,8 @@ impl State {
let mut shell = self.common.shell.write();
let workspace = shell.active_space_mut(&focused_output).unwrap();
if workspace.get_fullscreen().is_some() {
return; // TODO, is this what we want? Maybe disengage fullscreen instead?
}
let keyboard_handle = seat.get_keyboard().unwrap();
if let Some(focus) = keyboard_handle.current_focus() {
if let Some(descriptor) = workspace.node_desc(focus) {
let grab = SwapWindowGrab::new(seat.clone(), descriptor.clone());
@ -853,9 +850,8 @@ impl State {
let mut shell = self.common.shell.write();
let workspace = shell.active_space_mut(&focused_output).unwrap();
let focus_stack = workspace.focus_stack.get(seat);
let focused_window = focus_stack.last().cloned();
if let Some(window) = focused_window {
shell.minimize_request(&window);
if let Some(surface) = focus_stack.last().and_then(FocusTarget::wl_surface) {
shell.minimize_request(&surface);
}
}
@ -867,8 +863,8 @@ impl State {
let workspace = shell.active_space(&focused_output).unwrap();
let focus_stack = workspace.focus_stack.get(seat);
let focused_window = focus_stack.last().cloned();
if let Some(window) = focused_window {
shell.maximize_toggle(&window, seat);
if let Some(FocusTarget::Window(window)) = focused_window {
shell.maximize_toggle(&window, seat, &self.common.event_loop_handle);
}
}
@ -896,7 +892,11 @@ impl State {
}
Action::ToggleStacking => {
let res = self.common.shell.write().toggle_stacking_focused(seat);
let res = self
.common
.shell
.write()
.toggle_stacking_focused(seat, &self.common.event_loop_handle);
if let Some(new_focus) = res {
Shell::set_focus(self, Some(&new_focus), seat, Some(serial), false);
}

View file

@ -391,9 +391,14 @@ impl State {
//If the pointer isn't grabbed, we should check if the focused element should be updated
} else if self.common.config.cosmic_conf.focus_follows_cursor {
let shell = self.common.shell.read();
let old_keyboard_target =
State::element_under(original_position, &current_output, &*shell);
let new_keyboard_target = State::element_under(position, &output, &*shell);
let old_keyboard_target = State::element_under(
original_position,
&current_output,
&*shell,
&seat,
);
let new_keyboard_target =
State::element_under(position, &output, &*shell, &seat);
if old_keyboard_target != new_keyboard_target
&& new_keyboard_target.is_some()
@ -705,7 +710,7 @@ impl State {
seat.get_pointer().unwrap().current_location().as_global();
let under = {
let shell = self.common.shell.read();
State::element_under(global_position, &output, &shell)
State::element_under(global_position, &output, &shell, &seat)
};
if let Some(target) = under {
if let Some(surface) = target.toplevel().map(Cow::into_owned) {
@ -1878,7 +1883,7 @@ impl State {
for elem in old_descriptor.focus_stack.iter().flat_map(|node_id| {
old_workspace.tiling_layer.element_for_node(node_id)
}) {
stack.append(elem);
stack.append(elem.clone());
}
}
{
@ -1886,7 +1891,7 @@ impl State {
for elem in new_descriptor.focus_stack.iter().flat_map(|node_id| {
new_workspace.tiling_layer.element_for_node(node_id)
}) {
stack.append(elem);
stack.append(elem.clone());
}
}
if let Some(focus) = TilingLayout::swap_trees(
@ -1938,7 +1943,7 @@ impl State {
for elem in old_descriptor.focus_stack.iter().flat_map(|node_id| {
old_workspace.tiling_layer.element_for_node(node_id)
}) {
stack.append(elem);
stack.append(elem.clone());
}
}
if let Some(focus) = TilingLayout::move_tree(
@ -1968,6 +1973,7 @@ impl State {
global_pos: Point<f64, Global>,
output: &Output,
shell: &Shell,
seat: &Seat<State>,
) -> Option<KeyboardFocusTarget> {
let (previous_workspace, workspace) = shell.workspaces.active(output)?;
let (previous_idx, idx) = shell.workspaces.active_num(output);
@ -2062,7 +2068,7 @@ impl State {
geometry.contains(global_pos.to_local(output).to_i32_round())
})
{
if let Some(element) = workspace.popup_element_under(location) {
if let Some(element) = workspace.popup_element_under(location, seat) {
return ControlFlow::Break(Ok(Some(element)));
}
}
@ -2077,7 +2083,8 @@ impl State {
geometry.contains(global_pos.to_local(output).to_i32_round())
})
{
if let Some(element) = workspace.toplevel_element_under(location) {
if let Some(element) = workspace.toplevel_element_under(location, seat)
{
return ControlFlow::Break(Ok(Some(element)));
}
}
@ -2112,6 +2119,7 @@ impl State {
let relative_pos = global_pos.to_local(output);
let output_geo = output.geometry();
let overview = shell.overview_mode().0;
let seat = shell.seats.last_active();
render_input_order(
shell,
@ -2224,7 +2232,7 @@ impl State {
Stage::WorkspacePopups { workspace, offset } => {
let global_pos = global_pos + offset.to_f64().as_global();
if let Some(under) =
workspace.popup_surface_under(global_pos, overview.clone())
workspace.popup_surface_under(global_pos, overview.clone(), seat)
{
return ControlFlow::Break(Ok(Some(under)));
}
@ -2232,7 +2240,7 @@ impl State {
Stage::Workspace { workspace, offset } => {
let global_pos = global_pos + offset.to_f64().as_global();
if let Some(under) =
workspace.toplevel_surface_under(global_pos, overview.clone())
workspace.toplevel_surface_under(global_pos, overview.clone(), seat)
{
return ControlFlow::Break(Ok(Some(under)));
}