From 64143e75e70aef9c164c3821c63fe8a26786a148 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 11 Oct 2023 22:00:20 +0200 Subject: [PATCH] shell: Properly handle fullscreen outputs --- src/shell/layout/tiling/mod.rs | 8 +- src/shell/mod.rs | 93 +++++++++++++++++++---- src/shell/workspace.rs | 32 ++++++-- src/wayland/handlers/compositor.rs | 7 +- src/wayland/handlers/xdg_shell/mod.rs | 104 ++++++++++++++++++++++---- src/xwayland.rs | 17 +++-- 6 files changed, 211 insertions(+), 50 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 415e7b86..7fb4399e 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -363,15 +363,15 @@ impl TilingLayout { pub fn map<'a>( &mut self, window: CosmicMapped, - focus_stack: impl Iterator + 'a, + focus_stack: Option + 'a>, direction: Option, ) { window.output_enter(&self.output, window.bbox()); window.set_bounds(self.output.geometry().size.as_logical()); - self.map_internal(window, Some(focus_stack), direction); + self.map_internal(window, focus_stack, direction); } - fn map_internal<'a>( + pub fn map_internal<'a>( &mut self, window: impl Into, focus_stack: Option + 'a>, @@ -528,7 +528,7 @@ impl TilingLayout { } mapped.set_tiled(true); - other.map(mapped.clone(), focus_stack, None); + other.map(mapped.clone(), Some(focus_stack), None); return Some(KeyboardFocusTarget::Element(mapped)); } None => { diff --git a/src/shell/mod.rs b/src/shell/mod.rs index d981559d..1b57f0c9 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -152,7 +152,7 @@ pub struct Shell { pub popups: PopupManager, pub maximize_mode: MaximizeMode, - pub pending_windows: Vec<(CosmicSurface, Seat)>, + pub pending_windows: Vec<(CosmicSurface, Seat, Option)>, pub pending_layers: Vec<(LayerSurface, Output, Seat)>, pub override_redirect_windows: Vec, @@ -382,7 +382,8 @@ impl WorkspaceSet { for mut workspace in overflow { if last_space.fullscreen.is_some() { - workspace.remove_fullscreen(); + // Don't handle the returned original workspace, for this nieche case. + let _ = workspace.remove_fullscreen(); } for element in workspace.mapped() { @@ -1250,18 +1251,78 @@ impl Shell { .refresh(Some(&self.workspace_state)); } - pub fn map_window(state: &mut State, window: &CosmicSurface, output: &Output) { + pub fn remap_unfullscreened_window( + &mut self, + mapped: CosmicMapped, + current_workspace: &WorkspaceHandle, + previous_workspace: &WorkspaceHandle, + target_layer: ManagedLayer, + ) { + if self.space_for_handle(previous_workspace).is_none() { + return; + } + + { + let Some(workspace) = self.space_for_handle_mut(¤t_workspace) else { return }; + let _ = workspace.unmap(&mapped); + } + + let new_workspace_output = self + .space_for_handle(&previous_workspace) + .unwrap() + .output() + .clone(); + for (window, _) in mapped.windows() { + self.toplevel_info_state + .toplevel_enter_output(&window, &new_workspace_output); + self.toplevel_info_state + .toplevel_enter_workspace(&window, &previous_workspace); + } + + let new_workspace = self.space_for_handle_mut(&previous_workspace).unwrap(); + match target_layer { + ManagedLayer::Floating => new_workspace.floating_layer.map(mapped, None), + ManagedLayer::Tiling => { + new_workspace + .tiling_layer + .map(mapped, Option::>::None, None) + } + }; + } + + pub fn map_window(state: &mut State, window: &CosmicSurface) { let pos = state .common .shell .pending_windows .iter() - .position(|(w, _)| w == window) + .position(|(w, _, _)| w == window) .unwrap(); - let (window, seat) = state.common.shell.pending_windows.remove(pos); + let (window, seat, output) = state.common.shell.pending_windows.remove(pos); - let workspace = state.common.shell.workspaces.active_mut(output); - workspace.remove_fullscreen(); + let should_be_fullscreen = output.is_some(); + let output = output.unwrap_or_else(|| seat.active_output()); + + let workspace = state.common.shell.workspaces.active_mut(&output); + if let Some((mapped, layer, previous_workspace)) = workspace.remove_fullscreen() { + let old_handle = workspace.handle.clone(); + let new_workspace_handle = state + .common + .shell + .space_for_handle(&previous_workspace) + .is_some() + .then_some(previous_workspace) + .unwrap_or(old_handle); + + state.common.shell.remap_unfullscreened_window( + mapped, + &old_handle, + &new_workspace_handle, + layer, + ); + }; + + let workspace = state.common.shell.workspaces.active_mut(&output); state.common.shell.toplevel_info_state.new_toplevel(&window); state .common @@ -1297,17 +1358,21 @@ impl Shell { let focus_stack = workspace.focus_stack.get(&seat); workspace .tiling_layer - .map(mapped.clone(), focus_stack.iter(), None); + .map(mapped.clone(), Some(focus_stack.iter()), None); } - if let CosmicSurface::X11(surface) = window { + if should_be_fullscreen { + workspace.fullscreen_request(&mapped.active_window(), None); + } + + if let CosmicSurface::X11(ref surface) = window { if let Some(xwm) = state .common .xwayland_state .as_mut() .and_then(|state| state.xwm.as_mut()) { - if let Err(err) = xwm.raise_window(&surface) { + if let Err(err) = xwm.raise_window(surface) { warn!(?err, "Failed to update Xwayland stacking order."); } } @@ -1315,7 +1380,7 @@ impl Shell { Shell::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); - let active_space = state.common.shell.active_space(output); + let active_space = state.common.shell.active_space(&output); for mapped in active_space.mapped() { state.common.shell.update_reactive_popups(mapped); } @@ -1440,10 +1505,10 @@ impl Shell { } else { to_workspace .tiling_layer - .map(mapped.clone(), focus_stack.iter(), direction); + .map(mapped.clone(), Some(focus_stack.iter()), direction); } - let focus_target = if window_state.was_fullscreen.is_some() { - to_workspace.fullscreen_request(&mapped.active_window()); + let focus_target = if let Some(f) = window_state.was_fullscreen { + to_workspace.fullscreen_request(&mapped.active_window(), f.previously); KeyboardFocusTarget::from(to_workspace.get_fullscreen().unwrap().clone()) } else { KeyboardFocusTarget::from(mapped.clone()) diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 444708ab..9bec3d88 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -93,6 +93,7 @@ pub struct Workspace { #[derive(Debug, Clone)] pub struct FullscreenSurface { pub surface: CosmicSurface, + pub previously: Option<(ManagedLayer, WorkspaceHandle)>, original_geometry: Rectangle, start_at: Option, ended_at: Option, @@ -472,7 +473,11 @@ impl Workspace { None } - pub fn fullscreen_request(&mut self, window: &CosmicSurface) { + pub fn fullscreen_request( + &mut self, + window: &CosmicSurface, + previously: Option<(ManagedLayer, WorkspaceHandle)>, + ) { if self.fullscreen.is_some() { return; } @@ -497,6 +502,7 @@ impl Workspace { self.fullscreen = Some(FullscreenSurface { surface: window.clone(), + previously, original_geometry, start_at: Some(Instant::now()), ended_at: None, @@ -504,7 +510,11 @@ impl Workspace { }); } - pub fn unfullscreen_request(&mut self, window: &CosmicSurface) -> Option> { + #[must_use] + pub fn unfullscreen_request( + &mut self, + window: &CosmicSurface, + ) -> Option<(ManagedLayer, WorkspaceHandle)> { if let Some(f) = self.fullscreen.as_mut().filter(|f| &f.surface == window) { window.set_fullscreen(false); window.set_geometry(f.original_geometry); @@ -545,15 +555,19 @@ impl Workspace { } } - Some(f.original_geometry.size.as_logical()) + f.previously } else { None } } - pub fn remove_fullscreen(&mut self) { + #[must_use] + pub fn remove_fullscreen(&mut self) -> Option<(CosmicMapped, ManagedLayer, WorkspaceHandle)> { if let Some(surface) = self.fullscreen.as_ref().map(|f| f.surface.clone()) { - self.unfullscreen_request(&surface); + self.unfullscreen_request(&surface) + .map(|(l, h)| (self.element_for_surface(&surface).unwrap().clone(), l, h)) + } else { + None } } @@ -633,7 +647,7 @@ impl Workspace { .as_ref() .is_some_and(|f| &f.surface == window) { - self.remove_fullscreen(); + let _ = self.remove_fullscreen(); // We are moving this window, we don't need to send it back to it's original workspace } let mapped = self.element_for_surface(&window)?.clone(); @@ -692,7 +706,8 @@ impl Workspace { .into_iter() { self.floating_layer.unmap(&window); - self.tiling_layer.map(window, focus_stack.iter(), None) + self.tiling_layer + .map(window, Some(focus_stack.iter()), None) } self.tiling_enabled = true; } @@ -707,7 +722,8 @@ impl Workspace { } else if self.floating_layer.mapped().any(|w| w == &window) { let focus_stack = self.focus_stack.get(seat); self.floating_layer.unmap(&window); - self.tiling_layer.map(window, focus_stack.iter(), None) + self.tiling_layer + .map(window, Some(focus_stack.iter()), None) } } } diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index 0380e8e0..e725c1fe 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -171,12 +171,12 @@ impl CompositorHandler for State { on_commit_buffer_handler::(surface); // then handle initial configure events and map windows if necessary - if let Some((window, seat)) = self + if let Some((window, _, _)) = self .common .shell .pending_windows .iter() - .find(|(window, _)| window.wl_surface().as_ref() == Some(surface)) + .find(|(window, _, _)| window.wl_surface().as_ref() == Some(surface)) .cloned() { match window { @@ -185,9 +185,8 @@ impl CompositorHandler for State { if self.toplevel_ensure_initial_configure(&toplevel) && with_renderer_surface_state(&surface, |state| state.buffer().is_some()) { - let output = seat.active_output(); window.on_commit(); - Shell::map_window(self, &window, &output); + Shell::map_window(self, &window); } else { return; } diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 84c4d4a2..88e10fc3 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{shell::CosmicSurface, utils::prelude::*, wayland::protocols::screencopy::SessionType}; +use crate::{ + shell::{element::CosmicWindow, CosmicMapped, CosmicSurface, ManagedLayer}, + utils::prelude::*, + wayland::protocols::screencopy::SessionType, +}; use smithay::{ delegate_xdg_shell, desktop::{ @@ -38,7 +42,7 @@ impl XdgShellHandler for State { fn new_toplevel(&mut self, surface: ToplevelSurface) { let seat = self.common.last_active_seat().clone(); let window = CosmicSurface::Wayland(Window::new(surface)); - self.common.shell.pending_windows.push((window, seat)); + self.common.shell.pending_windows.push((window, seat, None)); // We will position the window after the first commit, when we know its size hints } @@ -184,14 +188,14 @@ impl XdgShellHandler for State { } fn fullscreen_request(&mut self, surface: ToplevelSurface, output: Option) { + let active_output = { + let seat = self.common.last_active_seat(); + seat.active_output() + }; let output = output .as_ref() .and_then(Output::from_resource) - .unwrap_or_else(|| { - let seat = self.common.last_active_seat(); - seat.active_output() - }); - // TODO: If this is not the output? Do we move it? + .unwrap_or_else(|| active_output.clone()); if let Some(mapped) = self .common @@ -200,11 +204,69 @@ impl XdgShellHandler for State { .cloned() { if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { - let (window, _) = mapped - .windows() - .find(|(w, _)| w.wl_surface().as_ref() == Some(surface.wl_surface())) - .unwrap(); - workspace.fullscreen_request(&window) + if workspace.output != output { + let (mapped, layer) = if mapped + .stack_ref() + .map(|stack| stack.len() > 1) + .unwrap_or(false) + { + let stack = mapped.stack_ref().unwrap(); + let surface = stack + .surfaces() + .find(|s| s.wl_surface().as_ref() == Some(surface.wl_surface())) + .unwrap(); + stack.remove_window(&surface); + ( + CosmicMapped::from(CosmicWindow::new( + surface, + self.common.event_loop_handle.clone(), + )), + if workspace.is_tiled(&mapped) { + ManagedLayer::Tiling + } else { + ManagedLayer::Floating + }, + ) + } else { + let layer = workspace.unmap(&mapped).unwrap().layer; + (mapped, layer) + }; + let handle = workspace.handle.clone(); + std::mem::drop(workspace); + + let workspace_handle = self.common.shell.active_space(&output).handle.clone(); + for (window, _) in mapped.windows() { + self.common + .shell + .toplevel_info_state + .toplevel_enter_output(&window, &output); + self.common + .shell + .toplevel_info_state + .toplevel_enter_workspace(&window, &workspace_handle); + } + + let workspace = self.common.shell.active_space_mut(&output); + workspace.floating_layer.map(mapped.clone(), None); + workspace.fullscreen_request(&mapped.active_window(), Some((layer, handle))); + } else { + let (window, _) = mapped + .windows() + .find(|(w, _)| w.wl_surface().as_ref() == Some(surface.wl_surface())) + .unwrap(); + workspace.fullscreen_request(&window, None) + } + } + } else { + if let Some(o) = self + .common + .shell + .pending_windows + .iter_mut() + .find(|(s, _, _)| s.wl_surface().as_ref() == Some(surface.wl_surface())) + .map(|(_, _, o)| o) + { + *o = Some(output); } } } @@ -221,7 +283,23 @@ impl XdgShellHandler for State { .windows() .find(|(w, _)| w.wl_surface().as_ref() == Some(surface.wl_surface())) .unwrap(); - workspace.unfullscreen_request(&window); + if let Some((layer, previous_workspace)) = workspace.unfullscreen_request(&window) { + let old_handle = workspace.handle.clone(); + let new_workspace_handle = self + .common + .shell + .space_for_handle(&previous_workspace) + .is_some() + .then_some(previous_workspace) + .unwrap_or(old_handle); // if the workspace doesn't exist anymore, we can still remap on the right layer + + self.common.shell.remap_unfullscreened_window( + mapped, + &old_handle, + &new_workspace_handle, + layer, + ); + } } } } diff --git a/src/xwayland.rs b/src/xwayland.rs index 0d5cdc33..a344178a 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -151,16 +151,19 @@ impl XwmHandler for State { } let seat = self.common.last_active_seat().clone(); - self.common.shell.pending_windows.push((surface, seat)); + self.common + .shell + .pending_windows + .push((surface, seat, None)); } fn map_window_notify(&mut self, _xwm: XwmId, surface: X11Surface) { - if let Some((window, seat)) = self + if let Some((window, _, _)) = self .common .shell .pending_windows .iter() - .find(|(window, _)| { + .find(|(window, _, _)| { if let CosmicSurface::X11(window) = window { window == &surface } else { @@ -169,8 +172,7 @@ impl XwmHandler for State { }) .cloned() { - let output = seat.active_output(); - Shell::map_window(self, &window, &output); + Shell::map_window(self, &window); } } @@ -386,7 +388,7 @@ impl XwmHandler for State { let surface = CosmicSurface::X11(window); if let Some(mapped) = self.common.shell.element_for_surface(&surface).cloned() { if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { - workspace.fullscreen_request(&surface) + workspace.fullscreen_request(&surface, None) } } } @@ -396,7 +398,8 @@ impl XwmHandler for State { if let Some(mapped) = self.common.shell.element_for_surface(&surface).cloned() { if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { let (window, _) = mapped.windows().find(|(w, _)| w == &surface).unwrap(); - workspace.unfullscreen_request(&window); + let previous = workspace.unfullscreen_request(&window); + assert!(previous.is_none()); } } }