From adedb705e7fc1ac4e6f57702edbdaccb6e9a9090 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 25 Jun 2025 17:54:27 +0200 Subject: [PATCH] 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 --- resources/i18n/en/cosmic_comp.ftl | 1 + src/backend/render/mod.rs | 15 +- src/input/actions.rs | 60 +- src/input/mod.rs | 30 +- src/shell/element/mod.rs | 49 +- src/shell/element/stack.rs | 5 +- src/shell/element/surface.rs | 50 +- src/shell/element/window.rs | 6 +- src/shell/focus/mod.rs | 211 ++- src/shell/focus/order.rs | 50 +- src/shell/focus/target.rs | 9 + src/shell/grabs/menu/default.rs | 291 +++- src/shell/grabs/moving.rs | 15 +- src/shell/layout/floating/mod.rs | 88 +- src/shell/layout/tiling/mod.rs | 237 ++-- src/shell/mod.rs | 1335 ++++++++++++------- src/shell/workspace.rs | 1181 ++++++++-------- src/state.rs | 32 +- src/wayland/handlers/toplevel_management.rs | 157 +-- src/wayland/handlers/xdg_activation.rs | 193 +-- src/wayland/handlers/xdg_shell/mod.rs | 173 +-- src/wayland/handlers/xdg_shell/popup.rs | 2 +- src/xwayland.rs | 160 ++- 23 files changed, 2554 insertions(+), 1796 deletions(-) diff --git a/resources/i18n/en/cosmic_comp.ftl b/resources/i18n/en/cosmic_comp.ftl index f11267dc..86d98619 100644 --- a/resources/i18n/en/cosmic_comp.ftl +++ b/resources/i18n/en/cosmic_comp.ftl @@ -9,6 +9,7 @@ stack-windows = Stack Windows unknown-keybinding = window-menu-minimize = Minimize window-menu-maximize = Maximize +window-menu-fullscreen = Fullscreen window-menu-tiled = Float window window-menu-screenshot = Take screenshot window-menu-move = Move diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 081d2ad0..bf2fbfac 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -16,7 +16,7 @@ use crate::{ config::ScreenFilter, shell::{ element::CosmicMappedKey, - focus::{render_input_order, target::WindowGroup, Stage}, + focus::{render_input_order, target::WindowGroup, FocusTarget, Stage}, grabs::{SeatMenuGrabState, SeatMoveGrabState}, layout::tiling::ANIMATION_DURATION, zoom::ZoomState, @@ -897,7 +897,12 @@ where layout .render( renderer, - current_focus.as_ref().and_then(|stack| stack.last()), + current_focus.as_ref().and_then(|stack| { + stack.last().and_then(|t| match t { + FocusTarget::Window(w) => Some(w), + _ => None, + }) + }), resize_indicator.clone(), active_hint, alpha, @@ -913,7 +918,8 @@ where elements.extend( match workspace.render_popups( renderer, - (!move_active && is_active_space).then_some(last_active_seat), + last_active_seat, + !move_active && is_active_space, overview.clone(), &theme.cosmic(), ) { @@ -941,7 +947,8 @@ where elements.extend( match workspace.render( renderer, - (!move_active && is_active_space).then_some(last_active_seat), + last_active_seat, + !move_active && is_active_space, overview.clone(), resize_indicator.clone(), active_hint, diff --git a/src/input/actions.rs b/src/input/actions.rs index 268c1153..e83d83d8 100644 --- a/src/input/actions.rs +++ b/src/input/actions.rs @@ -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::::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(¤t_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); } diff --git a/src/input/mod.rs b/src/input/mod.rs index 6e62fe92..9299a498 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -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, ¤t_output, &*shell); - let new_keyboard_target = State::element_under(position, &output, &*shell); + let old_keyboard_target = State::element_under( + original_position, + ¤t_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, output: &Output, shell: &Shell, + seat: &Seat, ) -> Option { 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))); } diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 69112ae3..05181a08 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -20,7 +20,7 @@ use smithay::{ ImportAll, ImportMem, Renderer, }, }, - desktop::{space::SpaceElement, PopupManager, WindowSurfaceType}, + desktop::{space::SpaceElement, WindowSurfaceType}, input::{ keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, Seat, @@ -31,10 +31,7 @@ use smithay::{ utils::{ Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, }, - wayland::{ - compositor::{with_surface_tree_downward, TraversalAction}, - seat::WaylandFocus, - }, + wayland::seat::WaylandFocus, xwayland::{xwm::X11Relatable, X11Surface}, }; use stack::CosmicStackInternal; @@ -245,7 +242,10 @@ impl CosmicMapped { .cloned() } - pub fn set_active(&self, window: &CosmicSurface) { + pub fn set_active(&self, window: &S) + where + CosmicSurface: PartialEq, + { if let CosmicMappedInternal::Stack(stack) = &self.element { stack.set_active(window); } @@ -259,41 +259,8 @@ impl CosmicMapped { } pub fn has_surface(&self, surface: &WlSurface, surface_type: WindowSurfaceType) -> bool { - self.windows().any(|(w, _)| { - let Some(toplevel) = w.wl_surface() else { - return false; - }; - - if surface_type.contains(WindowSurfaceType::TOPLEVEL) { - if *toplevel == *surface { - return true; - } - } - - if surface_type.contains(WindowSurfaceType::SUBSURFACE) { - use std::sync::atomic::Ordering; - - let found = AtomicBool::new(false); - with_surface_tree_downward( - &toplevel, - surface, - |_, _, search| TraversalAction::DoChildren(search), - |s, _, search| { - found.fetch_or(s == *search, Ordering::SeqCst); - }, - |_, _, _| !found.load(Ordering::SeqCst), - ); - if found.load(Ordering::SeqCst) { - return true; - } - } - - if surface_type.contains(WindowSurfaceType::POPUP) { - PopupManager::popups_for_surface(&toplevel).any(|(p, _)| p.wl_surface() == surface) - } else { - false - } - }) + self.windows() + .any(|(w, _)| w.has_surface(surface, surface_type)) } /// Give the pointer target under a relative offset into this element. diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 3011ac46..bc3d5165 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -455,7 +455,10 @@ impl CosmicStack { .with_program(|p| p.group_focused.load(Ordering::SeqCst)) } - pub fn set_active(&self, window: &CosmicSurface) { + pub fn set_active(&self, window: &S) + where + CosmicSurface: PartialEq, + { self.0.with_program(|p| { if let Some(val) = p.windows.lock().unwrap().iter().position(|w| w == window) { let old = p.active.swap(val, Ordering::SeqCst); diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 6b0bed11..3cb345a8 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -18,7 +18,8 @@ use smithay::{ ImportAll, Renderer, }, desktop::{ - space::SpaceElement, utils::OutputPresentationFeedback, PopupManager, Window, WindowSurface, + space::SpaceElement, utils::OutputPresentationFeedback, PopupManager, Window, + WindowSurface, WindowSurfaceType, }, input::{ keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, @@ -40,7 +41,7 @@ use smithay::{ user_data::UserDataMap, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, }, wayland::{ - compositor::{with_states, SurfaceData}, + compositor::{with_states, with_surface_tree_downward, SurfaceData, TraversalAction}, seat::WaylandFocus, shell::xdg::{SurfaceCachedState, ToplevelSurface, XdgToplevelSurfaceData}, }, @@ -54,7 +55,7 @@ use crate::{ wayland::handlers::decoration::{KdeDecorationData, PreferredDecorationMode}, }; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash, Eq)] pub struct CosmicSurface(pub Window); impl From for CosmicSurface { @@ -81,6 +82,13 @@ impl PartialEq for CosmicSurface { } } +impl PartialEq for CosmicSurface { + fn eq(&self, other: &ToplevelSurface) -> bool { + self.wl_surface() + .map_or(false, |s| &*s == other.wl_surface()) + } +} + impl PartialEq for CosmicSurface { fn eq(&self, other: &X11Surface) -> bool { self.x11_surface().map_or(false, |s| s == other) @@ -602,6 +610,42 @@ impl CosmicSurface { } } + pub fn has_surface(&self, surface: &WlSurface, surface_type: WindowSurfaceType) -> bool { + let Some(toplevel) = self.wl_surface() else { + return false; + }; + + if surface_type.contains(WindowSurfaceType::TOPLEVEL) { + if *toplevel == *surface { + return true; + } + } + + if surface_type.contains(WindowSurfaceType::SUBSURFACE) { + use std::sync::atomic::Ordering; + + let found = AtomicBool::new(false); + with_surface_tree_downward( + &toplevel, + surface, + |_, _, search| TraversalAction::DoChildren(search), + |s, _, search| { + found.fetch_or(s == *search, Ordering::SeqCst); + }, + |_, _, _| !found.load(Ordering::SeqCst), + ); + if found.load(Ordering::SeqCst) { + return true; + } + } + + if surface_type.contains(WindowSurfaceType::POPUP) { + PopupManager::popups_for_surface(&toplevel).any(|(p, _)| p.wl_surface() == surface) + } else { + false + } + } + pub fn on_commit(&self) { self.0.on_commit(); } diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 788c5d08..ab195e47 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -468,9 +468,7 @@ impl Program for CosmicWindowInternal { if let Some(surface) = self.window.wl_surface().map(Cow::into_owned) { loop_handle.insert_idle(move |state| { let mut shell = state.common.shell.write(); - if let Some(mapped) = shell.element_for_surface(&surface).cloned() { - shell.minimize_request(&mapped) - } + shell.minimize_request(&surface) }); } } @@ -480,7 +478,7 @@ impl Program for CosmicWindowInternal { let mut shell = state.common.shell.write(); if let Some(mapped) = shell.element_for_surface(&surface).cloned() { let seat = shell.seats.last_active().clone(); - shell.maximize_toggle(&mapped, &seat) + shell.maximize_toggle(&mapped, &seat, &state.common.event_loop_handle) } }); } diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index b4e6d311..73469044 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -1,5 +1,5 @@ use crate::{ - shell::{element::CosmicMapped, Shell}, + shell::{element::CosmicMapped, CosmicSurface, MinimizedWindow, Shell}, state::Common, utils::prelude::*, wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData}, @@ -9,7 +9,7 @@ use smithay::{ desktop::{layer_map_for_output, PopupUngrabStrategy}, input::{pointer::MotionEvent, Seat}, output::Output, - reexports::wayland_server::Resource, + reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource}, utils::{IsAlive, Point, Serial, SERIAL_COUNTER}, wayland::{ seat::WaylandFocus, @@ -29,18 +29,80 @@ use super::{grabs::SeatMoveGrabState, layout::floating::FloatingLayout, SeatExt} mod order; pub mod target; -pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet>); -pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet); +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum FocusTarget { + Window(CosmicMapped), + Fullscreen(CosmicSurface), +} + +impl PartialEq for FocusTarget { + fn eq(&self, other: &CosmicMapped) -> bool { + matches!(self, FocusTarget::Window(mapped) if mapped == other) + } +} + +impl PartialEq for FocusTarget { + fn eq(&self, other: &CosmicSurface) -> bool { + matches!(self, FocusTarget::Fullscreen(surface) if surface == other) + } +} + +impl From for FocusTarget { + fn from(value: CosmicMapped) -> Self { + Self::Window(value) + } +} + +impl From for FocusTarget { + fn from(value: CosmicSurface) -> Self { + Self::Fullscreen(value) + } +} + +impl Into for FocusTarget { + fn into(self) -> KeyboardFocusTarget { + match self { + FocusTarget::Window(mapped) => KeyboardFocusTarget::Element(mapped), + FocusTarget::Fullscreen(surface) => KeyboardFocusTarget::Fullscreen(surface), + } + } +} + +impl FocusTarget { + pub fn alive(&self) -> bool { + match self { + FocusTarget::Window(mapped) => mapped.alive(), + FocusTarget::Fullscreen(surface) => surface.alive(), + } + } + + fn is_minimized(&self) -> bool { + match self { + FocusTarget::Window(mapped) => mapped.is_minimized(), + FocusTarget::Fullscreen(surface) => surface.is_minimized(), + } + } + + pub fn wl_surface(&self) -> Option { + match self { + FocusTarget::Window(mapped) => mapped.active_window().wl_surface().map(Cow::into_owned), + FocusTarget::Fullscreen(surface) => surface.wl_surface().map(Cow::into_owned), + } + } +} + +pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet>); +pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet); impl<'a> FocusStack<'a> { /// returns the last unminimized window in the focus stack that is still alive - pub fn last(&self) -> Option<&CosmicMapped> { + pub fn last(&self) -> Option<&FocusTarget> { self.0 .as_ref() .and_then(|set| set.iter().rev().find(|w| w.alive() && !w.is_minimized())) } - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0 .iter() .flat_map(|set| set.iter().rev().filter(|w| w.alive() && !w.is_minimized())) @@ -48,21 +110,25 @@ impl<'a> FocusStack<'a> { } impl<'a> FocusStackMut<'a> { - pub fn append(&mut self, window: &CosmicMapped) { + pub fn append(&mut self, target: impl Into) { + let target = target.into(); self.0.retain(|w| w.alive()); - self.0.shift_remove(window); - self.0.insert(window.clone()); + self.0.shift_remove(&target); + self.0.insert(target); } - pub fn remove(&mut self, window: &CosmicMapped) { - self.0.retain(|w| w != window); + pub fn remove(&mut self, target: &T) + where + FocusTarget: PartialEq, + { + self.0.retain(|w| w != target); } - pub fn last(&self) -> Option<&CosmicMapped> { + pub fn last(&self) -> Option<&FocusTarget> { self.0.iter().rev().find(|w| w.alive() && !w.is_minimized()) } - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0 .iter() .rev() @@ -107,23 +173,16 @@ impl Shell { serial: Option, update_cursor: bool, ) { - let element = match target { - Some(KeyboardFocusTarget::Element(mapped)) => Some(mapped.clone()), - Some(KeyboardFocusTarget::Fullscreen(window)) => state - .common - .shell - .read() - .element_for_surface(window) - .cloned(), + let focus_target = match target { + Some(KeyboardFocusTarget::Element(mapped)) => Some(FocusTarget::Window(mapped.clone())), + Some(KeyboardFocusTarget::Fullscreen(surface)) => { + Some(FocusTarget::Fullscreen(surface.clone())) + } _ => None, }; - if let Some(mapped) = element { - if mapped.is_minimized() { - return; - } - - state.common.shell.write().append_focus_stack(&mapped, seat); + if let Some(target) = focus_target { + state.common.shell.write().append_focus_stack(target, seat); } update_focus_state(seat, target, state, serial, update_cursor); @@ -131,25 +190,30 @@ impl Shell { state.common.shell.write().update_active(); } - pub fn append_focus_stack(&mut self, mapped: &CosmicMapped, seat: &Seat) { - if mapped.is_minimized() { + pub fn append_focus_stack(&mut self, target: impl Into, seat: &Seat) { + let target = target.into(); + if target.is_minimized() { return; } // update FocusStack and notify layouts about new focus (if any window) - let workspace = self.space_for_mut(&mapped); + let workspace = target + .wl_surface() + .and_then(|surface| self.workspace_for_surface(&surface)); let workspace = if workspace.is_none() { //should this be the active output or the focused output? self.active_space_mut(&seat.focused_or_active_output()) .unwrap() } else { - workspace.unwrap() + self.workspaces + .space_for_handle_mut(&workspace.unwrap().0) + .unwrap() }; let mut focus_stack = workspace.focus_stack.get_mut(seat); - if Some(mapped) != focus_stack.last() { - trace!(?mapped, "Focusing window."); - focus_stack.append(&mapped); + if Some(&target) != focus_stack.last() { + trace!(?target, "Focusing window."); + focus_stack.append(target); // also remove popup grabs, if we are switching focus if let Some(mut popup_grab) = seat .user_data() @@ -171,7 +235,7 @@ impl Shell { .map(|seat| { if matches!( seat.get_keyboard().unwrap().current_focus(), - Some(KeyboardFocusTarget::Group(_)) + Some(KeyboardFocusTarget::Group(_)) | Some(KeyboardFocusTarget::LockSurface(_)) ) { return None; } @@ -179,7 +243,10 @@ impl Shell { let output = seat.focused_or_active_output(); let space = self.active_space(&output).unwrap(); let stack = space.focus_stack.get(seat); - stack.last().cloned() + stack.last().and_then(|target| match target { + FocusTarget::Window(window) => Some(window.clone()), + FocusTarget::Fullscreen(_) => None, + }) }) .flatten() .collect::>(); @@ -193,12 +260,33 @@ impl Shell { window.set_activated(focused_windows.contains(&window)); window.configure(); } - for m in set.minimized_windows.iter() { - m.window.set_activated(false); - m.window.configure(); + for window in set + .minimized_windows + .iter() + .flat_map(MinimizedWindow::mapped) + { + window.set_activated(false); + window.configure(); } let workspace = &mut set.workspaces[set.active]; + if let Some(fullscreen) = workspace.get_fullscreen() { + if self.seats.iter().any(|seat| { + if let Some(KeyboardFocusTarget::Fullscreen(s)) = + seat.get_keyboard().unwrap().current_focus() + { + &s == fullscreen + } else { + false + } + }) { + fullscreen.set_activated(true); + fullscreen.send_configure(); + } else { + fullscreen.set_activated(false); + fullscreen.send_configure(); + } + } for focused in focused_windows.iter() { raise_with_children(&mut workspace.floating_layer, focused); } @@ -207,8 +295,15 @@ impl Shell { window.configure(); } for m in workspace.minimized_windows.iter() { - m.window.set_activated(false); - m.window.configure(); + if let Some(window) = m.mapped() { + window.set_activated(false); + window.configure(); + } else { + for surface in m.windows() { + surface.set_activated(false); + surface.send_configure(); + } + } } for (i, workspace) in set.workspaces.iter().enumerate() { @@ -515,13 +610,11 @@ fn focus_target_is_valid( let workspace = shell.active_space(&output).unwrap(); let focus_stack = workspace.focus_stack.get(&seat); let is_in_focus_stack = focus_stack.last().map(|m| m == &mapped).unwrap_or(false); - let has_fullscreen = workspace.get_fullscreen().is_some(); - if is_sticky && !is_in_focus_stack { - shell.append_focus_stack(&mapped, seat); + shell.append_focus_stack(mapped, seat); } - (is_sticky || is_in_focus_stack) && !has_fullscreen + is_sticky || is_in_focus_stack } KeyboardFocusTarget::LayerSurface(layer) => { layer_map_for_output(&output).layers().any(|l| l == &layer) @@ -535,13 +628,7 @@ fn focus_target_is_valid( .has_node(&node), KeyboardFocusTarget::Fullscreen(window) => { let workspace = shell.active_space(&output).unwrap(); - let focus_stack = workspace.focus_stack.get(&seat); - - focus_stack - .last() - .map(|m| m.has_active_window(&window)) - .unwrap_or(false) - && workspace.get_fullscreen().is_some() + workspace.get_fullscreen().is_some_and(|w| w == &window) } KeyboardFocusTarget::Popup(_) => true, KeyboardFocusTarget::LockSurface(_) => false, @@ -579,8 +666,6 @@ fn update_focus_target( }) .cloned() .map(KeyboardFocusTarget::from) - } else if let Some(surface) = shell.active_space(&output).unwrap().get_fullscreen() { - Some(KeyboardFocusTarget::Fullscreen(surface.clone())) } else { shell .active_space(&output) @@ -588,9 +673,23 @@ fn update_focus_target( .focus_stack .get(&seat) .last() - .or_else(|| shell.active_space(&output).unwrap().mapped().next()) .cloned() - .map(KeyboardFocusTarget::from) + .map(Into::::into) + .or_else(|| { + let workspace = shell.active_space(&output).unwrap(); + + workspace + .mapped() + .next() + .cloned() + .map(KeyboardFocusTarget::Element) + .or_else(|| { + workspace + .get_fullscreen() + .cloned() + .map(KeyboardFocusTarget::Fullscreen) + }) + }) } } diff --git a/src/shell/focus/order.rs b/src/shell/focus/order.rs index ed2114de..e9b5be3c 100644 --- a/src/shell/focus/order.rs +++ b/src/shell/focus/order.rs @@ -13,10 +13,15 @@ use smithay::{ use crate::{ backend::render::ElementFilter, shell::{ + focus::target::KeyboardFocusTarget, layout::{floating::FloatingLayout, tiling::ANIMATION_DURATION}, - Shell, Workspace, WorkspaceDelta, + SeatExt, Shell, Workspace, WorkspaceDelta, + }, + utils::{ + geometry::*, + prelude::OutputExt, + quirks::{workspace_overview_is_open, WORKSPACE_OVERVIEW_NAMESPACE}, }, - utils::{geometry::*, prelude::OutputExt, quirks::WORKSPACE_OVERVIEW_NAMESPACE}, wayland::protocols::workspace::WorkspaceHandle, }; @@ -105,11 +110,32 @@ fn render_input_order_internal( return ControlFlow::Break(Err(OutputNoMode)); }; let output_size = output.geometry().size; - let has_fullscreen = workspace - .fullscreen - .as_ref() - .filter(|f| !f.is_animating()) - .is_some(); + + // 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 is_active_workspace = seat.focused_output().is_some_and(|output| { + shell + .active_space(&output) + .is_some_and(|w| w.handle == workspace.handle) + }); + let focus_stack_is_valid_fullscreen = workspace + .focus_stack + .get(seat) + .last() + .zip(fullscreen) + .is_some_and(|(target, fullscreen)| target == &fullscreen.surface); + let overview_is_open = workspace_overview_is_open(&output); + let has_focused_fullscreen = if is_active_workspace { + let current_focus = seat.get_keyboard().unwrap().current_focus(); + matches!(current_focus, Some(KeyboardFocusTarget::Fullscreen(_))) + || (current_focus.is_none() + && focus_stack_is_valid_fullscreen + && !workspace_overview_is_open(&output)) + } else { + focus_stack_is_valid_fullscreen && !overview_is_open + }; + let has_fullscreen = fullscreen.is_some() && !overview_is_open; let (previous, current_offset) = match previous.as_ref() { Some((previous, previous_idx, start)) => { @@ -163,7 +189,7 @@ fn render_input_order_internal( }; // Top-level layer shell popups - if !has_fullscreen { + if !has_focused_fullscreen { for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) { callback(Stage::LayerPopup { layer, @@ -193,7 +219,7 @@ fn render_input_order_internal( } // sticky window popups - if !has_fullscreen { + if !has_focused_fullscreen { callback(Stage::StickyPopups(&set.sticky_layer))?; } } @@ -222,7 +248,7 @@ fn render_input_order_internal( })?; } - if !has_fullscreen { + if !has_focused_fullscreen { // bottom layer popups for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) { callback(Stage::LayerPopup { @@ -271,7 +297,7 @@ fn render_input_order_internal( } } - if !has_fullscreen { + if !has_focused_fullscreen { // top-layer shell for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) { callback(Stage::LayerSurface { layer, location })?; @@ -302,7 +328,7 @@ fn render_input_order_internal( } } - if !has_fullscreen { + if !has_focused_fullscreen { // bottom layer for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { location += current_offset.as_global(); diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index 604eba16..8ffff748 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -181,6 +181,15 @@ impl KeyboardFocusTarget { } } + pub fn windows(&self) -> impl Iterator + '_ { + match self { + KeyboardFocusTarget::Element(mapped) => Box::new(mapped.windows().map(|(s, _)| s)) + as Box>, + KeyboardFocusTarget::Fullscreen(surface) => Box::new(std::iter::once(surface.clone())), + _ => Box::new(std::iter::empty()), + } + } + pub fn is_xwm(&self, xwm: XwmId) -> bool { match self { KeyboardFocusTarget::Element(mapped) => { diff --git a/src/shell/grabs/menu/default.rs b/src/shell/grabs/menu/default.rs index 8c1079a9..94c8c4da 100644 --- a/src/shell/grabs/menu/default.rs +++ b/src/shell/grabs/menu/default.rs @@ -1,5 +1,8 @@ use cosmic_settings_config::shortcuts::Action; -use smithay::{input::pointer::MotionEvent, utils::SERIAL_COUNTER, wayland::seat::WaylandFocus}; +use smithay::{ + input::pointer::MotionEvent, reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::SERIAL_COUNTER, wayland::seat::WaylandFocus, +}; use crate::{ config::Config, @@ -11,6 +14,7 @@ use crate::{ }, state::State, utils::{prelude::SeatExt, screenshot::screenshot_window}, + wayland::protocols::workspace::WorkspaceHandle, }; use super::{Item, ResizeEdge}; @@ -24,69 +28,136 @@ fn toggle_stacking(state: &mut State, mapped: &CosmicMapped) { } } -fn move_prev_workspace(state: &mut State, mapped: &CosmicMapped) { - let mut shell = state.common.shell.write(); - let seat = shell.seats.last_active().clone(); - let (current_handle, output) = { - let Some(ws) = shell.space_for(mapped) else { - return; - }; - (ws.handle, ws.output.clone()) - }; - let maybe_handle = shell +fn prev_workspace( + shell: &Shell, + surface: &WlSurface, +) -> Option<(WorkspaceHandle, WorkspaceHandle)> { + let (current_handle, output) = shell.workspace_for_surface(surface)?; + shell .workspaces .spaces_for_output(&output) .enumerate() .find_map(|(i, space)| (space.handle == current_handle).then_some(i)) .and_then(|i| i.checked_sub(1)) - .and_then(|i| shell.workspaces.get(i, &output).map(|s| s.handle)); - if let Some(prev_handle) = maybe_handle { - let res = shell.move_window( - Some(&seat), - mapped, - ¤t_handle, - &prev_handle, - true, - None, - &mut state.common.workspace_state.update(), - ); - if let Some((target, _)) = res { - std::mem::drop(shell); - Shell::set_focus(state, Some(&target), &seat, None, true); - } - } + .and_then(|i| shell.workspaces.get(i, &output)) + .map(|space| (current_handle, space.handle)) } -fn move_next_workspace(state: &mut State, mapped: &CosmicMapped) { - let mut shell = state.common.shell.write(); - let seat = shell.seats.last_active().clone(); - let (current_handle, output) = { - let Some(ws) = shell.space_for(mapped) else { - return; - }; - (ws.handle, ws.output.clone()) - }; - let maybe_handle = shell +fn next_workspace( + shell: &Shell, + surface: &WlSurface, +) -> Option<(WorkspaceHandle, WorkspaceHandle)> { + let (current_handle, output) = shell.workspace_for_surface(surface)?; + shell .workspaces .spaces_for_output(&output) .skip_while(|space| space.handle != current_handle) .skip(1) .next() - .map(|space| space.handle); - if let Some(next_handle) = maybe_handle { - let res = shell.move_window( - Some(&seat), - mapped, - ¤t_handle, - &next_handle, - true, - None, - &mut state.common.workspace_state.update(), - ); - if let Some((target, _point)) = res { - std::mem::drop(shell); - Shell::set_focus(state, Some(&target), &seat, None, true) - } + .map(|space| (current_handle, space.handle)) +} + +fn move_fullscreen_prev_workspace(state: &mut State, surface: &CosmicSurface) { + let mut shell = state.common.shell.write(); + let Some(wl_surface) = surface.wl_surface() else { + return; + }; + let Some((from, to)) = prev_workspace(&shell, &*wl_surface) else { + return; + }; + + let seat = shell.seats.last_active().clone(); + let res = shell.move_window( + Some(&seat), + surface, + &from, + &to, + true, + None, + &mut state.common.workspace_state.update(), + &state.common.event_loop_handle, + ); + if let Some((target, _)) = res { + std::mem::drop(shell); + Shell::set_focus(state, Some(&target), &seat, None, true); + } +} + +fn move_fullscreen_next_workspace(state: &mut State, surface: &CosmicSurface) { + let mut shell = state.common.shell.write(); + let Some(wl_surface) = surface.wl_surface() else { + return; + }; + let Some((from, to)) = next_workspace(&shell, &*wl_surface) else { + return; + }; + + let seat = shell.seats.last_active().clone(); + let res = shell.move_window( + Some(&seat), + surface, + &from, + &to, + true, + None, + &mut state.common.workspace_state.update(), + &state.common.event_loop_handle, + ); + if let Some((target, _)) = res { + std::mem::drop(shell); + Shell::set_focus(state, Some(&target), &seat, None, true); + } +} + +fn move_element_prev_workspace(state: &mut State, mapped: &CosmicMapped) { + let mut shell = state.common.shell.write(); + let window = mapped.active_window(); + let Some(wl_surface) = window.wl_surface() else { + return; + }; + let Some((from, to)) = prev_workspace(&shell, &*wl_surface) else { + return; + }; + + let seat = shell.seats.last_active().clone(); + let res = shell.move_element( + Some(&seat), + mapped, + &from, + &to, + true, + None, + &mut state.common.workspace_state.update(), + ); + if let Some((target, _)) = res { + std::mem::drop(shell); + Shell::set_focus(state, Some(&target), &seat, None, true); + } +} + +fn move_element_next_workspace(state: &mut State, mapped: &CosmicMapped) { + let mut shell = state.common.shell.write(); + let window = mapped.active_window(); + let Some(wl_surface) = window.wl_surface() else { + return; + }; + let Some((from, to)) = next_workspace(&shell, &*wl_surface) else { + return; + }; + + let seat = shell.seats.last_active().clone(); + let res = shell.move_element( + Some(&seat), + mapped, + &from, + &to, + true, + None, + &mut state.common.workspace_state.update(), + ); + if let Some((target, _point)) = res { + std::mem::drop(shell); + Shell::set_focus(state, Some(&target), &seat, None, true) } } @@ -198,7 +269,11 @@ pub fn window_items( Item::new(fl!("window-menu-minimize"), move |handle| { let mapped = minimize_clone.clone(); let _ = handle.insert_idle(move |state| { - state.common.shell.write().minimize_request(&mapped); + state + .common + .shell + .write() + .minimize_request(&mapped.active_window()); }); }) .shortcut(config.shortcut_for_action(&Action::Minimize)), @@ -209,7 +284,7 @@ pub fn window_items( let _ = handle.insert_idle(move |state| { let mut shell = state.common.shell.write(); let seat = shell.seats.last_active().clone(); - shell.maximize_toggle(&mapped, &seat); + shell.maximize_toggle(&mapped, &seat, &state.common.event_loop_handle); }); }) .shortcut(config.shortcut_for_action(&Action::Maximize)) @@ -422,7 +497,8 @@ pub fn window_items( Some( Item::new(fl!("window-menu-move-prev-workspace"), move |handle| { let mapped = move_prev_clone.clone(); - let _ = handle.insert_idle(move |state| move_prev_workspace(state, &mapped)); + let _ = + handle.insert_idle(move |state| move_element_prev_workspace(state, &mapped)); }) .shortcut(config.shortcut_for_action(&Action::MoveToPreviousWorkspace)) .disabled(is_sticky), @@ -430,7 +506,8 @@ pub fn window_items( Some( Item::new(fl!("window-menu-move-next-workspace"), move |handle| { let mapped = move_next_clone.clone(); - let _ = handle.insert_idle(move |state| move_next_workspace(state, &mapped)); + let _ = + handle.insert_idle(move |state| move_element_next_workspace(state, &mapped)); }) .shortcut(config.shortcut_for_action(&Action::MoveToNextWorkspace)) .disabled(is_sticky), @@ -466,3 +543,105 @@ pub fn window_items( .into_iter() .flatten() } + +pub fn fullscreen_items(window: &CosmicSurface, config: &Config) -> impl Iterator { + let minimize_clone = window.clone(); + let fullscreen_clone = window.clone(); + let move_prev_clone = window.clone(); + let move_next_clone = window.clone(); + let move_clone = window.clone(); + let screenshot_clone = window.clone(); + let close_clone = window.clone(); + + vec![ + Some( + Item::new(fl!("window-menu-minimize"), move |handle| { + let window = minimize_clone.clone(); + let _ = handle.insert_idle(move |state| { + state.common.shell.write().minimize_request(&window); + }); + }) + .shortcut(config.shortcut_for_action(&Action::Minimize)), + ), + Some( + Item::new(fl!("window-menu-fullscreen"), move |handle| { + let window = fullscreen_clone.clone(); + let _ = handle.insert_idle(move |state| { + let mut shell = state.common.shell.write(); + shell.unfullscreen_request(&window, &state.common.event_loop_handle); + }); + }) + //.shortcut(config.shortcut_for_action(&Action::Fullscreen)) + .toggled(true), + ), + Some(Item::Separator), + // TODO: Where to save? + Some(Item::new(fl!("window-menu-screenshot"), move |handle| { + let window = screenshot_clone.clone(); + let _ = handle.insert_idle(move |state| screenshot_window(state, &window)); + })), + Some(Item::Separator), + Some(Item::new(fl!("window-menu-move"), move |handle| { + let move_clone = move_clone.clone(); + let _ = handle.insert_idle(move |state| { + if let Some(surface) = move_clone.wl_surface() { + let mut shell = state.common.shell.write(); + let seat = shell.seats.last_active().clone(); + let res = shell.move_request( + &surface, + &seat, + None, + ReleaseMode::Click, + false, + &state.common.config, + &state.common.event_loop_handle, + false, + ); + + std::mem::drop(shell); + if let Some((grab, focus)) = res { + if grab.is_touch_grab() { + seat.get_touch().unwrap().set_grab( + state, + grab, + SERIAL_COUNTER.next_serial(), + ) + } else { + seat.get_pointer().unwrap().set_grab( + state, + grab, + SERIAL_COUNTER.next_serial(), + focus, + ); + } + } + } + }); + })), + Some( + Item::new(fl!("window-menu-move-prev-workspace"), move |handle| { + let window = move_prev_clone.clone(); + let _ = + handle.insert_idle(move |state| move_fullscreen_prev_workspace(state, &window)); + }) + .shortcut(config.shortcut_for_action(&Action::MoveToPreviousWorkspace)), + ), + Some( + Item::new(fl!("window-menu-move-next-workspace"), move |handle| { + let window = move_next_clone.clone(); + let _ = + handle.insert_idle(move |state| move_fullscreen_next_workspace(state, &window)); + }) + .shortcut(config.shortcut_for_action(&Action::MoveToNextWorkspace)), + ), + Some(Item::Separator), + Some( + Item::new(fl!("window-menu-close"), move |_handle| { + close_clone.close(); + }) + .shortcut(config.shortcut_for_action(&Action::Close)), + ), + ] + .into_iter() + .flatten() +} diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 9e97f450..8a7d0f9c 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -432,7 +432,8 @@ impl MoveGrab { } } - let indicator_location = shell.stacking_indicator(¤t_output, self.previous); + let indicator_location = + shell.stacking_indicator(¤t_output, self.previous.clone()); if indicator_location.is_some() != grab_state.stacking_indicator.is_some() { grab_state.stacking_indicator = indicator_location.map(|geo| { let element = stack_hover( @@ -731,7 +732,7 @@ impl MoveGrab { start: Instant::now(), stacking_indicator: None, snapping_zone: None, - previous: previous_layer, + previous: previous_layer.clone(), location: start_data.location(), cursor_output: cursor_output.clone(), }; @@ -779,7 +780,7 @@ impl Drop for MoveGrab { let output = self.cursor_output.clone(); let seat = self.seat.clone(); let window_outputs = self.window_outputs.drain().collect::>(); - let previous = self.previous; + let previous = self.previous.clone(); let window = self.window.clone(); let is_touch_grab = matches!(self.start_data, GrabStartData::Touch(_)); @@ -841,10 +842,14 @@ impl Drop for MoveGrab { window_location.to_local(&workspace.output), ); - if previous == ManagedLayer::Floating { + if matches!(previous, ManagedLayer::Floating) { if let Some(sz) = grab_state.snapping_zone { if sz == SnappingZone::Maximize { - shell.maximize_toggle(&window, &seat); + shell.maximize_toggle( + &window, + &seat, + &state.common.event_loop_handle, + ); } else { let directions = match sz { SnappingZone::Maximize => vec![], diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 7bf628d3..3d3a4bce 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -627,78 +627,46 @@ impl FloatingLayout { ); } - pub fn unmap(&mut self, window: &CosmicMapped) -> Option> { - let mut new_size = None; + pub fn unmap( + &mut self, + window: &CosmicMapped, + to: Option>, + ) -> Option> { + let _ = self.animations.remove(window); + let Some(mut mapped_geometry) = self.space.element_geometry(window).map(RectExt::as_local) + else { + return None; + }; if let Some(_) = window.floating_tiled.lock().unwrap().take() { if let Some(last_size) = window.last_geometry.lock().unwrap().map(|geo| geo.size) { - if let Some(location) = self.space.element_location(window) { - window.set_tiled(false); - window.set_geometry( - Rectangle::new(location, last_size.as_logical()) - .as_local() - .to_global(self.space.outputs().next().unwrap()), - ); - window.configure(); - new_size = Some(last_size.as_logical()); - } - } - } else if !window.is_maximized(true) && !window.is_fullscreen(true) { - if let Some(location) = self.space.element_location(window) { - *window.last_geometry.lock().unwrap() = Some( - Rectangle::new( - location, - window - .pending_size() - .unwrap_or_else(|| window.geometry().size), - ) - .as_local(), - ) + let geometry = Rectangle::new(mapped_geometry.loc, last_size); + window.set_tiled(false); + window.set_geometry(geometry.to_global(self.space.outputs().next().unwrap())); + window.configure(); + mapped_geometry.size = last_size; } + } else if !window.is_maximized(true) { + *window.last_geometry.lock().unwrap() = Some(mapped_geometry); } - let _ = self.animations.remove(window); - - let was_unmaped = self.space.elements().any(|e| e == window); - self.space.unmap_elem(&window); - - if was_unmaped { - if let Some(pos) = self.spawn_order.iter().position(|w| w == window) { - self.spawn_order.truncate(pos); - } - window.moved_since_mapped.store(true, Ordering::SeqCst); - Some(new_size.unwrap_or_else(|| window.geometry().size)) - } else { - None - } - } - - pub fn unmap_minimize( - &mut self, - window: &CosmicMapped, - to: Rectangle, - ) -> Option<(CosmicMapped, Point)> { - let previous_geometry = self.space.element_geometry(window); - self.space.unmap_elem(&window); - if let Some(previous_geometry) = previous_geometry { - if let Some(pos) = self.spawn_order.iter().position(|w| w == window) { - self.spawn_order.truncate(pos); - } - window.moved_since_mapped.store(true, Ordering::SeqCst); + if let Some(to) = to { self.animations.insert( window.clone(), Animation::Minimize { start: Instant::now(), - previous_geometry: previous_geometry.as_local(), + previous_geometry: mapped_geometry, target_geometry: to, }, ); - - window.set_minimized(true); - Some((window.clone(), previous_geometry.loc.as_local())) - } else { - None } + + self.space.unmap_elem(&window); + if let Some(pos) = self.spawn_order.iter().position(|w| w == window) { + self.spawn_order.truncate(pos); + } + window.moved_since_mapped.store(true, Ordering::SeqCst); + Some(mapped_geometry) } pub fn drop_window( @@ -1027,7 +995,7 @@ impl FloatingLayout { Some(geo.size), None, ); - focus_stack.append(&mapped); + focus_stack.append(mapped.clone()); Some(KeyboardFocusTarget::Element(mapped)) } else { // if we have a stack @@ -1068,7 +1036,7 @@ impl FloatingLayout { self.space.refresh(); for elem in new_elements.into_iter().rev() { - focus_stack.append(&elem); + focus_stack.append(elem); } Some(KeyboardFocusTarget::Element(mapped)) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 78b73bc7..6ead5a0b 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -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, TargetZone)>, pub theme: cosmic::Theme, @@ -157,11 +157,18 @@ pub enum Data { minimize_rect: Option>, }, Placeholder { + id: Id, last_geometry: Rectangle, - initial_placeholder: bool, + type_: PlaceholderType, }, } +#[derive(Debug, Clone)] +pub enum PlaceholderType { + GrabbedWindow, + DropZone, +} + impl Data { fn new_group(orientation: Orientation, geo: Rectangle) -> 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, pub sibling: Option, 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 + 'a>, + focus_stack: Option + 'a>, direction: Option, ) { window.output_enter(&self.output, window.bbox()); @@ -394,7 +401,7 @@ impl TilingLayout { pub fn map_internal<'a>( &mut self, window: impl Into, - focus_stack: Option + 'a>, + focus_stack: Option + 'a>, direction: Option, minimize_rect: Option>, ) { @@ -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, - tiling_state: Option, - focus_stack: Option + 'a>, + from: Option>, + tiling_state: Option, + focus_stack: Option + '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, - focus_stack: impl Iterator + 'a, + focus_stack: impl Iterator + 'a, desc: NodeDesc, direction: Option, ) -> Option { @@ -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 { - 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, - ) -> Option { - let node_id = window.tiling_node_id.lock().unwrap().clone()?; + to: Option>, + ) -> Result, 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 { + 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, - focus_stack: impl Iterator + 'a, + focus_stack: impl Iterator + 'a, swap_desc: Option, ) -> 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, - mut focus_stack: impl Iterator, + mut focus_stack: impl Iterator, ) -> 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>, alpha: f32, transition: f32, - placeholder_id: &Id, + backdrop_id: &Id, mouse_tiling: Option>, swap_desc: Option, swap_tree: Option<&Tree>, @@ -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( mut resize_indicator: Option<(ResizeMode, ResizeIndicator)>, swap_desc: Option, swapping_stack_surface_id: &Id, - placeholder_id: &Id, + backdrop_id: &Id, theme: &cosmic::theme::CosmicTheme, ) -> Vec> 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, diff --git a/src/shell/mod.rs b/src/shell/mod.rs index cd36f5b2..cb0259c0 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -11,9 +11,12 @@ use std::{ }; use wayland_backend::server::ClientId; -use crate::wayland::{ - handlers::data_device::{self, get_dnd_icon}, - protocols::workspace::{State as WState, WorkspaceCapabilities}, +use crate::{ + shell::{focus::FocusTarget, grabs::fullscreen_items, layout::tiling::PlaceholderType}, + wayland::{ + handlers::data_device::{self, get_dnd_icon}, + protocols::workspace::{State as WState, WorkspaceCapabilities}, + }, }; use cosmic_comp_config::{ workspace::{PinnedWorkspace, WorkspaceLayout, WorkspaceMode}, @@ -853,7 +856,7 @@ impl Workspaces { } new_set.sticky_layer.merge(set.sticky_layer); for window in set.minimized_windows.iter() { - for (surface, _) in window.window.windows() { + for surface in window.windows() { toplevel_leave_output(&surface, output); toplevel_enter_output(&surface, &new_output); } @@ -1178,6 +1181,10 @@ impl Workspaces { self.sets.iter() } + pub fn iter_mut(&mut self) -> impl Iterator { + self.sets.iter_mut() + } + pub fn spaces(&self) -> impl Iterator { self.sets.values().flat_map(|set| set.workspaces.iter()) } @@ -1454,6 +1461,13 @@ impl Common { if let Some(mapped) = shell.element_for_surface(surface) { mapped.on_commit(surface); } + if let Some(surface) = shell + .workspaces + .spaces() + .find_map(|w| w.get_fullscreen().filter(|s| *s == surface)) + { + surface.on_commit() + }; } self.popups.commit(surface); } @@ -1755,7 +1769,6 @@ impl Shell { /// actions, such as closing a window pub fn focused_element(&self, focus_target: &KeyboardFocusTarget) -> Option { match focus_target { - KeyboardFocusTarget::Fullscreen(surface) => self.element_for_surface(surface).cloned(), KeyboardFocusTarget::Element(window) => Some(window).cloned(), KeyboardFocusTarget::Popup(PopupKind::Xdg(popup)) => { if let Some(parent) = popup.get_parent_surface() { @@ -1777,12 +1790,18 @@ impl Shell { /// Close the focused keyboard focus target pub fn close_focused(&self, focus_target: &KeyboardFocusTarget) { - if let KeyboardFocusTarget::Group(_group) = focus_target { - //TODO: decide if we want close actions to apply to groups - return; - } else { - if let Some(mapped) = self.focused_element(focus_target) { - mapped.send_close(); + match focus_target { + KeyboardFocusTarget::Group(_group) => { + //TODO: decide if we want close actions to apply to groups + return; + } + KeyboardFocusTarget::Fullscreen(surface) => { + surface.close(); + } + x => { + if let Some(mapped) = self.focused_element(x) { + mapped.send_close(); + } } } } @@ -1848,10 +1867,14 @@ impl Shell { // normal window? .or_else(|| { self.outputs().find(|o| { - self.active_space(o) - .unwrap() - .mapped() - .any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) + let workspace = self.active_space(o).unwrap(); + + workspace + .get_fullscreen() + .is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL)) + || workspace + .mapped() + .any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) }) }) // cursor and drag surfaces @@ -1903,11 +1926,14 @@ impl Shell { .workspaces .spaces() .find(|w| { - w.mapped() - .any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) - || w.minimized_windows - .iter() - .any(|m| m.window.has_surface(surface, WindowSurfaceType::ALL)) + w.get_fullscreen() + .is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL)) + || w.mapped() + .any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) + || w.minimized_windows.iter().any(|m| { + m.mapped() + .is_some_and(|m| m.has_surface(surface, WindowSurfaceType::ALL)) + }) }) .map(|w| (w.handle.clone(), w.output().clone())), } @@ -1920,9 +1946,13 @@ impl Shell { self.workspaces.sets.values().find_map(|set| { set.minimized_windows .iter() - .map(|w| &w.window) - .chain(set.sticky_layer.mapped()) - .find(|w| w.windows().any(|(s, _)| &s == surface)) + .find(|w| w.windows().any(|s| &s == surface)) + .and_then(|w| w.mapped()) + .or_else(|| { + set.sticky_layer + .mapped() + .find(|w| w.windows().any(|(s, _)| &s == surface)) + }) .or_else(|| { set.workspaces .iter() @@ -1937,7 +1967,7 @@ impl Shell { || workspace .minimized_windows .iter() - .any(|m| &m.window == mapped) + .any(|m| m.mapped() == Some(mapped)) }) } @@ -1947,7 +1977,7 @@ impl Shell { || workspace .minimized_windows .iter() - .any(|m| &m.window == mapped) + .any(|m| m.mapped() == Some(mapped)) }) } @@ -2162,6 +2192,7 @@ impl Shell { .floating_layer .stacking_indicator(), ManagedLayer::Tiling => self.active_space(output)?.tiling_layer.stacking_indicator(), + ManagedLayer::Fullscreen => None, } } @@ -2359,58 +2390,100 @@ impl Shell { pub fn remap_unfullscreened_window( &mut self, - mapped: CosmicMapped, - current_workspace: &WorkspaceHandle, - previous_workspace: &WorkspaceHandle, - target_layer: ManagedLayer, + surface: CosmicSurface, + state: Option, + original_geometry: Option>, + loop_handle: &LoopHandle<'static, State>, ) { - if self - .workspaces - .space_for_handle(previous_workspace) - .is_none() - { + let window = CosmicMapped::from(CosmicWindow::new( + surface, + loop_handle.clone(), + self.theme.clone(), + )); + + if let Some(FullscreenRestoreState::Sticky { output, .. }) = &state { + let output = output + .upgrade() + .unwrap_or_else(|| self.seats.last_active().active_output()); + toplevel_enter_output(&window.active_window(), &output); + let set = self + .workspaces + .sets + .get_mut(&output) + .or_else(|| self.workspaces.backup_set.as_mut()) + .unwrap(); + set.sticky_layer.map_internal( + window, + original_geometry.map(|rect| rect.loc), + original_geometry.map(|rect| rect.size.as_logical()), + None, + ); return; } - { - let Some(workspace) = self.workspaces.space_for_handle_mut(¤t_workspace) else { - return; - }; - let _ = workspace.unmap(&mapped); - } + let seat = self.seats.last_active(); + let workspace = match &state { + Some(FullscreenRestoreState::Floating { workspace, .. }) + | Some(FullscreenRestoreState::Tiling { workspace, .. }) => { + let workspace = self.workspaces.space_for_handle_mut(&workspace); + let workspace = match workspace { + Some(workspace) => workspace, + None => self.workspaces.active_mut(&seat.active_output()).unwrap(), + }; + toplevel_enter_output(&window.active_window(), &workspace.output); + toplevel_enter_workspace(&window.active_window(), &workspace.handle); - let new_workspace_output = self - .workspaces - .space_for_handle(&previous_workspace) - .unwrap() - .output() - .clone(); - for (window, _) in mapped.windows() { - toplevel_enter_output(&window, &new_workspace_output); - toplevel_enter_workspace(&window, &previous_workspace); - } - - let new_workspace = self - .workspaces - .space_for_handle_mut(&previous_workspace) - .unwrap(); - match target_layer { - ManagedLayer::Sticky => { - let output = new_workspace.output().clone(); - self.workspaces - .sets - .get_mut(&output) - .unwrap() - .sticky_layer - .map(mapped, None) + workspace } - ManagedLayer::Tiling if new_workspace.tiling_enabled => { - new_workspace - .tiling_layer - .map(mapped, Option::>::None, None) - } - _ => new_workspace.floating_layer.map(mapped, None), + None => self.workspaces.active_mut(&seat.active_output()).unwrap(), + Some(FullscreenRestoreState::Sticky { .. }) => unreachable!(), }; + + match state { + None => { + toplevel_enter_output(&window.active_window(), &workspace.output); + toplevel_enter_workspace(&window.active_window(), &workspace.handle); + + if workspace.tiling_enabled { + workspace.tiling_layer.map( + window, + Some(workspace.focus_stack.get(seat).iter()), + None, + ); + } else { + workspace.floating_layer.map(window, None); + } + } + Some(FullscreenRestoreState::Floating { .. }) => { + workspace.floating_layer.map_internal( + window, + original_geometry.map(|geo| geo.loc), + original_geometry.map(|geo| geo.size.as_logical()), + None, + ); + } + Some(FullscreenRestoreState::Tiling { + state: + TilingRestoreData { + state, + was_maximized, + }, + .. + }) => { + let focus_stack = workspace.focus_stack.get(seat); + workspace + .tiling_layer + .remap(window.clone(), None, state, Some(focus_stack.iter())); + if was_maximized { + let previous_geometry = + workspace.tiling_layer.element_geometry(&window).unwrap(); + workspace + .floating_layer + .map_maximized(window, previous_geometry, true); + } + } + Some(FullscreenRestoreState::Sticky { .. }) => unreachable!(), + } } #[must_use] @@ -2419,7 +2492,7 @@ impl Shell { window: &CosmicSurface, toplevel_info: &mut ToplevelInfoState, workspace_state: &mut WorkspaceState, - evlh: &LoopHandle<'static, State>, + loop_handle: &LoopHandle<'static, State>, ) -> Option { let pos = self .pending_windows @@ -2476,18 +2549,6 @@ impl Shell { output = workspace.output.clone(); } - if let Some((mapped, layer, previous_workspace)) = workspace.remove_fullscreen() { - let old_handle = workspace.handle.clone(); - let new_workspace_handle = self - .workspaces - .space_for_handle(&previous_workspace) - .is_some() - .then_some(previous_workspace) - .unwrap_or(old_handle); - - self.remap_unfullscreened_window(mapped, &old_handle, &new_workspace_handle, layer); - }; - let active_handle = self.active_space(&output).unwrap().handle; let workspace = if let Some(handle) = workspace_handle.filter(|handle| { self.workspaces @@ -2515,22 +2576,40 @@ impl Shell { let is_dialog = layout::is_dialog(&window); let floating_exception = layout::has_floating_exception(&self.tiling_exceptions, &window); + if should_be_fullscreen { + if let Some((surface, state, geometry)) = + 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, geometry, loop_handle); + } + if was_activated { + workspace_state.add_workspace_state(&workspace_handle, WState::Urgent); + } + + return (workspace_output == seat.active_output() && active_handle == workspace_handle) + .then_some(KeyboardFocusTarget::Fullscreen(window)); + } + let maybe_focused = workspace.focus_stack.get(&seat).iter().next().cloned(); - if let Some(focused) = maybe_focused { - if (focused.is_stack() && !is_dialog && !should_be_fullscreen && !should_be_maximized) - && !(workspace.is_tiled(&focused) && floating_exception) + if let Some(FocusTarget::Window(focused)) = maybe_focused { + if (focused.is_stack() && !is_dialog && !should_be_maximized) + && !(workspace.is_tiled(&focused.active_window()) && floating_exception) { focused.stack_ref().unwrap().add_window(window, None, None); if was_activated { workspace_state.add_workspace_state(&workspace_handle, WState::Urgent); } - return None; + return (workspace_output == seat.active_output() + && active_handle == workspace_handle) + .then_some(KeyboardFocusTarget::Element(focused)); } } let mapped = CosmicMapped::from(CosmicWindow::new( window.clone(), - evlh.clone(), + loop_handle.clone(), self.theme.clone(), )); #[cfg(feature = "debug")] @@ -2557,18 +2636,12 @@ impl Shell { .map(mapped.clone(), Some(focus_stack.iter()), None); } - if !parent_is_sticky && should_be_fullscreen { - let from = minimize_rectangle(&output, &mapped.active_window()); - - workspace.fullscreen_request(&mapped.active_window(), None, from, &seat); - } - if parent_is_sticky { self.toggle_sticky(&seat, &mapped); } - if !should_be_fullscreen && should_be_maximized { - self.maximize_request(&mapped, &seat, false); + if should_be_maximized { + self.maximize_request(&mapped, &seat, false, loop_handle); } let new_target = if (workspace_output == seat.active_output() @@ -2578,8 +2651,8 @@ impl Shell { // TODO: enforce focus stealing prevention by also checking the same rules as for the else case. Some(KeyboardFocusTarget::from(mapped.clone())) } else { - if workspace_empty || was_activated || should_be_fullscreen { - self.append_focus_stack(&mapped, &seat); + if workspace_empty || was_activated { + self.append_focus_stack(mapped, &seat); workspace_state.add_workspace_state(&workspace_handle, WState::Urgent); } None @@ -2653,36 +2726,45 @@ impl Shell { if mapped.is_stack() { mapped.stack_ref().unwrap().remove_idx(idx) } else { - set.sticky_layer.unmap(&mapped); + set.sticky_layer.unmap(&mapped, None); Some(mapped.active_window()) } } else if let Some(idx) = set .minimized_windows .iter() - .map(|w| &w.window) - .position(|w| w.windows().any(|(s, _)| &s == surface)) + .position(|w| w.windows().any(|s| &s == surface)) { - if set.minimized_windows.get(idx).unwrap().window.is_stack() { - let window = &mut set.minimized_windows.get_mut(idx).unwrap().window; + if set + .minimized_windows + .get(idx) + .unwrap() + .mapped() + .is_some_and(CosmicMapped::is_stack) + { + let window = set + .minimized_windows + .get_mut(idx) + .unwrap() + .mapped_mut() + .unwrap(); let stack = window.stack_ref().unwrap(); let idx = stack.surfaces().position(|s| &s == surface); idx.and_then(|idx| stack.remove_idx(idx)) } else { - Some(set.minimized_windows.remove(idx).window.active_window()) - } - } else if let Some((workspace, elem)) = set.workspaces.iter_mut().find_map(|w| { - w.element_for_surface(&surface) - .cloned() - .map(|elem| (w, elem)) - }) { - if elem.is_stack() { - let stack = elem.stack_ref().unwrap(); - let idx = stack.surfaces().position(|s| &s == surface); - idx.and_then(|idx| stack.remove_idx(idx)) - } else { - workspace.unmap(&elem); - Some(elem.active_window()) + Some( + set.minimized_windows + .remove(idx) + .mapped() + .unwrap() + .active_window(), + ) } + } else if let Some((surface, _)) = set + .workspaces + .iter_mut() + .find_map(|w| w.unmap_surface(surface)) + { + Some(surface) } else { None }; @@ -2701,151 +2783,31 @@ impl Shell { } #[must_use] - pub fn move_window( - &mut self, - seat: Option<&Seat>, - mapped: &CosmicMapped, - from: &WorkspaceHandle, - to: &WorkspaceHandle, - follow: bool, - direction: Option, - workspace_state: &mut WorkspaceUpdateGuard<'_, State>, - ) -> Option<(KeyboardFocusTarget, Point)> { - let from_output = self.workspaces.space_for_handle(from)?.output.clone(); - let to_output = self.workspaces.space_for_handle(to)?.output.clone(); - - let from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above - let window_state = from_workspace.unmap(mapped)?; - let elements = from_workspace.mapped().cloned().collect::>(); - - for (toplevel, _) in mapped.windows() { - toplevel_leave_workspace(&toplevel, from); - if from_output != to_output { - toplevel_leave_output(&toplevel, &from_output); - } - } - for mapped in elements.into_iter() { - self.update_reactive_popups(&mapped); - } - let new_pos = if follow { - if let Some(seat) = seat { - seat.set_active_output(&to_output); - } - self.workspaces - .idx_for_handle(&to_output, to) - .and_then(|to_idx| { - self.activate( - &to_output, - to_idx, - WorkspaceDelta::new_shortcut(), - workspace_state, - ) - .unwrap() - }) - } else { - None - }; - - let any_seat = seat.unwrap_or(self.seats.last_active()).clone(); - let mut to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above - if window_state.layer == ManagedLayer::Floating || !to_workspace.tiling_enabled { - to_workspace.floating_layer.map(mapped.clone(), None); - } else { - for mapped in to_workspace - .mapped() - .filter(|m| m.maximized_state.lock().unwrap().is_some()) - .cloned() - .collect::>() - .into_iter() - { - to_workspace.unmaximize_request(&mapped); - } - let focus_stack = seat.map(|seat| to_workspace.focus_stack.get(&seat)); - to_workspace.tiling_layer.map( - mapped.clone(), - focus_stack.as_ref().map(|x| x.iter()), - direction, - ); - } - - let focus_target = if let Some(f) = window_state.was_fullscreen { - if to_workspace.fullscreen.is_some() { - if let Some((mapped, layer, previous_workspace)) = to_workspace.remove_fullscreen() - { - let old_handle = to.clone(); - let new_workspace_handle = self - .workspaces - .space_for_handle(&previous_workspace) - .is_some() - .then_some(previous_workspace) - .unwrap_or(old_handle); - - self.remap_unfullscreened_window( - mapped, - &old_handle, - &new_workspace_handle, - layer, - ); - to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); - // checked above - } - } - - let from = minimize_rectangle(&to_output, &mapped.active_window()); - - to_workspace.fullscreen_request(&mapped.active_window(), f.previously, from, &any_seat); - to_workspace - .fullscreen - .as_ref() - .map(|f| KeyboardFocusTarget::from(f.surface.clone())) - .unwrap_or_else(|| KeyboardFocusTarget::from(mapped.clone())) - } else { - KeyboardFocusTarget::from(mapped.clone()) - }; - - for mapped in to_workspace - .mapped() - .cloned() - .collect::>() - .into_iter() - { - self.update_reactive_popups(&mapped); - } - for (toplevel, _) in mapped.windows() { - if from_output != to_output { - toplevel_enter_output(&toplevel, &to_output); - } - toplevel_enter_workspace(&toplevel, to); - } - - new_pos.map(|pos| (focus_target, pos)) - } - - #[must_use] - pub fn move_current_window( + pub fn move_current( &mut self, seat: &Seat, - from_output: &Output, to: (&Output, Option), follow: bool, direction: Option, workspace_state: &mut WorkspaceUpdateGuard<'_, State>, + evlh: &LoopHandle<'static, State>, ) -> Result)>, InvalidWorkspaceIndex> { let (to_output, to_idx) = to; let to_idx = to_idx.unwrap_or(self.workspaces.active_num(to_output).1); - let from_idx = self.workspaces.active_num(from_output).1; + let from_output = seat.focused_or_active_output(); + let from_idx = self.workspaces.active_num(&from_output).1; - if from_output == to_output && to_idx == self.workspaces.active_num(from_output).1 { + if &from_output == to_output && to_idx == self.workspaces.active_num(&from_output).1 { return Ok(None); } - if from_output == to_output + if &from_output == to_output && to_idx.checked_sub(1).is_some_and(|idx| idx == from_idx) && to_idx == self.workspaces.len(to_output) - 1 && self .workspaces - .get(from_idx, from_output) - .is_some_and(|w| w.mapped().count() == 1) + .get(from_idx, &from_output) + .is_some_and(|w| w.len() == 1) && self .workspaces .get(to_idx, to_output) @@ -2862,9 +2824,8 @@ impl Shell { let from_workspace = self .workspaces - .active_mut(from_output) + .active_mut(&from_output) .ok_or(InvalidWorkspaceIndex)?; - let last_focused_window = from_workspace.focus_stack.get(seat).last().cloned(); let from = from_workspace.handle; match seat.get_keyboard().unwrap().current_focus() { @@ -2897,7 +2858,7 @@ impl Shell { for elem in focus_stack.iter().flat_map(|node_id| { from_workspace.tiling_layer.element_for_node(node_id) }) { - stack.append(elem); + stack.append(elem.clone()); } } @@ -2950,22 +2911,266 @@ impl Shell { Ok(None) } - _ => { - if let Some(mapped) = last_focused_window { - Ok(self.move_window( - Some(seat), - &mapped, - &from, - &to, - follow, - direction, - workspace_state, - )) - } else { - Ok(None) + Some(KeyboardFocusTarget::Fullscreen(surface)) => Ok(self.move_window( + Some(seat), + &surface, + &from, + &to, + follow, + direction, + workspace_state, + evlh, + )), + Some(KeyboardFocusTarget::Element(mapped)) => Ok(self.move_element( + Some(seat), + &mapped, + &from, + &to, + follow, + direction, + workspace_state, + )), + _ => Ok(None), + } + } + + #[must_use] + pub fn move_window( + &mut self, + seat: Option<&Seat>, + window: &CosmicSurface, + from: &WorkspaceHandle, + to: &WorkspaceHandle, + follow: bool, + direction: Option, + workspace_state: &mut WorkspaceUpdateGuard<'_, State>, + evlh: &LoopHandle<'static, State>, + ) -> Option<(KeyboardFocusTarget, Point)> { + let from_output = self.workspaces.space_for_handle(from)?.output.clone(); + let to_output = self.workspaces.space_for_handle(to)?.output.clone(); + + let from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above + let mut window_state = from_workspace.unmap_surface(window)?.1; + + toplevel_leave_workspace(window, from); + if from_output != to_output { + toplevel_leave_output(window, &from_output); + toplevel_enter_output(window, &to_output); + } + toplevel_enter_workspace(window, to); + + // we can't restore to a given position + if let WorkspaceRestoreData::Tiling(state) = &mut window_state { + state.take(); + } + // update fullscreen state to restore to the new workspace + if let WorkspaceRestoreData::Fullscreen(Some(FullscreenRestoreData { + previous_state, + .. + })) = &mut window_state + { + match previous_state { + FullscreenRestoreState::Tiling { workspace, .. } + | FullscreenRestoreState::Floating { workspace, .. } => { + *workspace = *to; } + _ => {} } } + + for mapped in from_workspace + .mapped() + .cloned() + .collect::>() + .into_iter() + { + self.update_reactive_popups(&mapped); + } + + let new_pos = if follow { + if let Some(seat) = seat { + seat.set_active_output(&to_output); + } + self.workspaces + .idx_for_handle(&to_output, to) + .and_then(|to_idx| { + self.activate( + &to_output, + to_idx, + WorkspaceDelta::new_shortcut(), + workspace_state, + ) + .unwrap() + }) + } else { + None + }; + + let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above + let to_mapped = to_workspace.mapped().cloned().collect::>(); + + let focus_target: KeyboardFocusTarget = + if matches!(window_state, WorkspaceRestoreData::Floating(_)) + || (matches!(window_state, WorkspaceRestoreData::Tiling(_)) + && !to_workspace.tiling_enabled) + { + let mapped = CosmicMapped::from(CosmicWindow::new( + window.clone(), + evlh.clone(), + self.theme.clone(), + )); + let position = match window_state { + WorkspaceRestoreData::Floating(Some(data)) => Some( + data.position_relative(to_workspace.output.geometry().size.as_logical()), + ), + _ => None, + }; + to_workspace.floating_layer.map(mapped.clone(), position); + mapped.into() + } else if matches!(window_state, WorkspaceRestoreData::Tiling(_)) { + let mapped = CosmicMapped::from(CosmicWindow::new( + window.clone(), + evlh.clone(), + self.theme.clone(), + )); + for mapped in to_workspace + .mapped() + .filter(|m| m.maximized_state.lock().unwrap().is_some()) + .cloned() + .collect::>() + .into_iter() + { + to_workspace.unmaximize_request(&mapped); + } + let focus_stack = seat.map(|seat| to_workspace.focus_stack.get(&seat)); + to_workspace.tiling_layer.map( + mapped.clone(), + focus_stack.as_ref().map(|x| x.iter()), + direction, + ); + mapped.into() + } else if let WorkspaceRestoreData::Fullscreen(previous) = window_state { + if let Some((old_surface, previous_state, previous_geometry)) = 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, + previous_geometry, + evlh, + ); + } + window.clone().into() + } else { + unreachable!() // TODO: sticky + }; + + for mapped in to_mapped.into_iter() { + self.update_reactive_popups(&mapped); + } + + new_pos.map(|pos| (focus_target, pos)) + } + + #[must_use] + pub fn move_element( + &mut self, + seat: Option<&Seat>, + mapped: &CosmicMapped, + from: &WorkspaceHandle, + to: &WorkspaceHandle, + follow: bool, + direction: Option, + workspace_state: &mut WorkspaceUpdateGuard<'_, State>, + ) -> Option<(KeyboardFocusTarget, Point)> { + let from_output = self.workspaces.space_for_handle(from)?.output.clone(); + let to_output = self.workspaces.space_for_handle(to)?.output.clone(); + + let from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above + let window_state = from_workspace.unmap_element(mapped)?; + let elements = from_workspace.mapped().cloned().collect::>(); + + for (toplevel, _) in mapped.windows() { + toplevel_leave_workspace(&toplevel, from); + if from_output != to_output { + toplevel_leave_output(&toplevel, &from_output); + } + } + for mapped in elements.into_iter() { + self.update_reactive_popups(&mapped); + } + let new_pos = if follow { + if let Some(seat) = seat { + seat.set_active_output(&to_output); + } + self.workspaces + .idx_for_handle(&to_output, to) + .and_then(|to_idx| { + self.activate( + &to_output, + to_idx, + WorkspaceDelta::new_shortcut(), + workspace_state, + ) + .unwrap() + }) + } else { + None + }; + + let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above + if matches!(window_state, WorkspaceRestoreData::Floating(_)) || !to_workspace.tiling_enabled + { + let position = match window_state { + WorkspaceRestoreData::Floating(Some(data)) => { + Some(data.position_relative(to_workspace.output.geometry().size.as_logical())) + } + _ => None, + }; + to_workspace.floating_layer.map(mapped.clone(), position); + } else if matches!(window_state, WorkspaceRestoreData::Tiling(_)) { + for mapped in to_workspace + .mapped() + .filter(|m| m.maximized_state.lock().unwrap().is_some()) + .cloned() + .collect::>() + .into_iter() + { + to_workspace.unmaximize_request(&mapped); + } + let focus_stack = seat.map(|seat| to_workspace.focus_stack.get(&seat)); + to_workspace.tiling_layer.map( + mapped.clone(), + focus_stack.as_ref().map(|x| x.iter()), + direction, + ); + } else { + unreachable!() // TODO: sticky + } + + let focus_target = KeyboardFocusTarget::from(mapped.clone()); + + for mapped in to_workspace + .mapped() + .cloned() + .collect::>() + .into_iter() + { + self.update_reactive_popups(&mapped); + } + for (toplevel, _) in mapped.windows() { + if from_output != to_output { + toplevel_enter_output(&toplevel, &to_output); + } + toplevel_enter_workspace(&toplevel, to); + } + + new_pos.map(|pos| (focus_target, pos)) } pub fn update_reactive_popups(&self, mapped: &CosmicMapped) { @@ -3005,69 +3210,13 @@ impl Shell { return None; // TODO: an application can send a menu request for a touch event }; - let mapped = self.element_for_surface(surface).cloned()?; - let (_, relative_loc) = mapped - .windows() - .find(|(w, _)| w.wl_surface().as_deref() == Some(surface)) - .unwrap(); + let items_for_element = |mapped: &CosmicMapped, + is_tiled: bool, + is_sticky: bool, + tiling_enabled: bool, + edge: ResizeEdge| { + let is_stacked = mapped.is_stack(); - let (global_position, edge, is_tiled, is_stacked, is_sticky, tiling_enabled) = - if let Some(set) = self - .workspaces - .sets - .values() - .find(|set| set.sticky_layer.mapped().any(|m| m == &mapped)) - { - let output = set.output.clone(); - let global_position = (set.sticky_layer.element_geometry(&mapped).unwrap().loc - + relative_loc.as_local() - + location.as_local()) - .to_global(&output); - ( - global_position, - ResizeEdge::all(), - false, - mapped.is_stack(), - true, - false, - ) - } else if let Some(workspace) = self.space_for(&mapped) { - let output = seat.active_output(); - - let elem_geo = workspace.element_geometry(&mapped)?; - let global_position = - (elem_geo.loc + relative_loc.as_local() + location.as_local()) - .to_global(&output); - let is_tiled = workspace.is_tiled(&mapped); - let edge = if is_tiled { - mapped - .tiling_node_id - .lock() - .unwrap() - .clone() - .map(|node_id| { - TilingLayout::possible_resizes(workspace.tiling_layer.tree(), node_id) - }) - .unwrap_or(ResizeEdge::empty()) - } else { - ResizeEdge::all() - }; - - ( - global_position, - edge, - is_tiled, - mapped.is_stack(), - false, - workspace.tiling_enabled, - ) - } else { - return None; - }; - - let grab = MenuGrab::new( - GrabStartData::Pointer(start_data), - seat, if target_stack || !is_stacked { Box::new(window_items( &mapped, @@ -3085,7 +3234,76 @@ impl Shell { .unwrap(); Box::new(tab_items(&mapped, &tab, is_tiled, config)) as Box> - }, + } + }; + + let (global_position, menu_items) = if let Some((set, mapped, relative_loc)) = + self.workspaces.sets.values().find_map(|set| { + set.sticky_layer + .mapped() + .find_map(|m| { + m.windows() + .find(|(ref w, _)| w == surface) + .map(|(_, loc)| (m, loc)) + }) + .map(|(mapped, relative_loc)| (set, mapped, relative_loc)) + }) { + let output = set.output.clone(); + let global_position = (set.sticky_layer.element_geometry(&mapped).unwrap().loc + + relative_loc.as_local() + + location.as_local()) + .to_global(&output); + ( + global_position, + items_for_element(&mapped, false, true, false, ResizeEdge::all()), + ) + } 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 + + location.as_local()) + .to_global(&output); + + ( + global_position, + Box::new(fullscreen_items(window, config)) as Box>, + ) + } else { + let mapped = workspace.element_for_surface(surface)?; + let elem_geo = workspace.element_geometry(mapped)?; + let relative_loc = mapped.active_window_geometry().loc; + let global_position = + (elem_geo.loc + relative_loc.as_local() + location.as_local()) + .to_global(&output); + let is_tiled = workspace.is_tiled(&mapped.active_window()); + let edge = if is_tiled { + mapped + .tiling_node_id + .lock() + .unwrap() + .clone() + .map(|node_id| { + TilingLayout::possible_resizes(workspace.tiling_layer.tree(), node_id) + }) + .unwrap_or(ResizeEdge::empty()) + } else { + ResizeEdge::all() + }; + + ( + global_position, + items_for_element(&mapped, is_tiled, false, workspace.tiling_enabled, edge), + ) + } + } else { + return None; + }; + + let grab = MenuGrab::new( + GrabStartData::Pointer(start_data), + seat, + menu_items, global_position, MenuAlignment::CORNER, None, @@ -3112,9 +3330,21 @@ impl Shell { } let serial = serial.into(); + let mut element_geo = None; let mut start_data = check_grab_preconditions(&seat, serial, client_initiated.then_some(surface))?; + + let maybe_fullscreen_workspace = self + .workspaces + .spaces_mut() + .find(|w| w.get_fullscreen().is_some_and(|s| s == surface)); + if let Some(workspace) = maybe_fullscreen_workspace { + element_geo = Some(workspace.fullscreen_geometry().unwrap()); + let (surface, state, original_geometry) = workspace.remove_fullscreen().unwrap(); + self.remap_unfullscreened_window(surface, state, original_geometry, evlh); + }; + let old_mapped = self.element_for_surface(surface).cloned()?; if old_mapped.is_minimized() { return None; @@ -3174,15 +3404,7 @@ impl Shell { let (initial_window_location, layer, workspace_handle) = if let Some(workspace) = self.space_for_mut(&old_mapped) { - if workspace - .fullscreen - .as_ref() - .is_some_and(|f| f.surface == window) - { - let _ = workspace.remove_fullscreen(); // We are moving this window, we don't need to send it back to it's original workspace - } - - let elem_geo = workspace.element_geometry(&old_mapped)?; + let elem_geo = element_geo.or_else(|| workspace.element_geometry(&old_mapped))?; let mut initial_window_location = elem_geo.loc.to_global(workspace.output()); let mut new_size = if mapped.maximized_state.lock().unwrap().is_some() { @@ -3193,11 +3415,13 @@ impl Shell { }; let layer = if mapped == old_mapped { - let was_floating = workspace.floating_layer.unmap(&mapped); - let was_tiled = workspace.tiling_layer.unmap_as_placeholder(&mapped); + let was_floating = workspace.floating_layer.unmap(&mapped, None); + let was_tiled = workspace + .tiling_layer + .unmap_as_placeholder(&mapped, PlaceholderType::GrabbedWindow); assert!(was_floating.is_some() != was_tiled.is_some()); - if was_floating.is_some_and(|size| size != elem_geo.size.as_logical()) { - new_size = was_floating; + if was_floating.is_some_and(|geo| geo.size != elem_geo.size) { + new_size = was_floating.map(|geo| geo.size.as_logical()); } was_tiled.is_some() } else { @@ -3249,9 +3473,9 @@ impl Shell { }; if mapped == old_mapped { - if let Some(size) = sticky_layer.unmap(&mapped) { - if size != elem_geo.size.as_logical() { - new_size = Some(size); + if let Some(geo) = sticky_layer.unmap(&mapped, None) { + if geo.size != elem_geo.size { + new_size = Some(geo.size.as_logical()); } } } @@ -3312,10 +3536,27 @@ impl Shell { // Just to avoid a longer lived shell reference /// Get the window geometry of a keyboard focus target pub fn focused_geometry(&self, target: &KeyboardFocusTarget) -> Option> { - if let Some(element) = self.focused_element(target) { - self.element_geometry(&element) - } else { - None + 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)) + { + workspace + .fullscreen_geometry() + .map(|f| f.to_global(workspace.output())) + } else { + None + } + } + _ => { + if let Some(element) = self.focused_element(target) { + self.element_geometry(&element) + } else { + None + } + } } } @@ -3350,11 +3591,6 @@ impl Shell { return FocusResult::None; }; let output = seat.active_output(); - let workspace = self.active_space(&output).unwrap(); - - if workspace.fullscreen.is_some() { - return FocusResult::None; - } if matches!(target, KeyboardFocusTarget::Fullscreen(_)) { return FocusResult::None; @@ -3407,7 +3643,7 @@ impl Shell { return FocusResult::Handled; } - if workspace.is_tiled(&focused) { + if workspace.is_tiled(&focused.active_window()) { if focused.is_maximized(false) { return FocusResult::None; } @@ -3507,42 +3743,43 @@ impl Shell { }; let workspace = self.active_space(&output).unwrap(); let focus_stack = workspace.focus_stack.get(seat); - let Some(last) = focus_stack.last().cloned() else { - return MoveResult::None; - }; - let fullscreen = workspace.fullscreen.as_ref().map(|f| f.surface.clone()); + match focus_stack.last().cloned() { + Some(FocusTarget::Fullscreen(surface)) => { + MoveResult::MoveFurther(KeyboardFocusTarget::Fullscreen(surface)) + } + Some(FocusTarget::Window(mapped)) => { + if let Some(set) = self + .workspaces + .sets + .values_mut() + .find(|set| set.sticky_layer.mapped().any(|m| &mapped == m)) + { + set.sticky_layer.move_current_element( + direction, + seat, + ManagedLayer::Sticky, + self.theme.clone(), + ) + } else { + let theme = self.theme.clone(); + if mapped + .maximized_state + .lock() + .unwrap() + .as_ref() + .is_some_and(|state| state.original_layer == ManagedLayer::Tiling) + { + self.unmaximize_request(&mapped); + } - if last - .maximized_state - .lock() - .unwrap() - .as_ref() - .is_some_and(|state| state.original_layer == ManagedLayer::Tiling) - { - self.unmaximize_request(&last); - } - - if let Some(surface) = fullscreen { - MoveResult::MoveFurther(KeyboardFocusTarget::Fullscreen(surface)) - } else if let Some(set) = self - .workspaces - .sets - .values_mut() - .find(|set| set.sticky_layer.mapped().any(|m| m == &last)) - { - set.sticky_layer.move_current_element( - direction, - seat, - ManagedLayer::Sticky, - self.theme.clone(), - ) - } else { - let theme = self.theme.clone(); - let workspace = self.active_space_mut(&output).unwrap(); - workspace - .floating_layer - .move_current_element(direction, seat, ManagedLayer::Floating, theme) - .or_else(|| workspace.tiling_layer.move_current_node(direction, seat)) + let workspace = self.active_space_mut(&output).unwrap(); + workspace + .floating_layer + .move_current_element(direction, seat, ManagedLayer::Floating, theme) + .or_else(|| workspace.tiling_layer.move_current_node(direction, seat)) + } + } + _ => MoveResult::None, } } @@ -3641,86 +3878,123 @@ impl Shell { Some(((focus, new_loc), (grab, Focus::Keep))) } - pub fn maximize_toggle(&mut self, window: &CosmicMapped, seat: &Seat) { + pub fn maximize_toggle( + &mut self, + window: &CosmicMapped, + seat: &Seat, + loop_handle: &LoopHandle<'static, State>, + ) { if window.is_maximized(true) { self.unmaximize_request(window); } else { if window.is_fullscreen(true) { return; } - self.maximize_request(window, seat, true); + self.maximize_request(window, seat, true, loop_handle); } } - pub fn minimize_request(&mut self, mapped: &CosmicMapped) { - if let Some(set) = self - .workspaces - .sets - .values_mut() - .find(|set| set.sticky_layer.mapped().any(|m| m == mapped)) - { - let to = minimize_rectangle(&set.output, &mapped.active_window()); - let (window, position) = set.sticky_layer.unmap_minimize(mapped, to).unwrap(); - set.minimized_windows.push(MinimizedWindow { - window, - previous_state: MinimizedState::Sticky { position }, - output_geo: set.output.geometry(), - fullscreen: None, - }); - } else if let Some(workspace) = self.workspaces.sets.values_mut().find_map(|set| { - set.workspaces - .iter_mut() - .find(|workspace| workspace.mapped().any(|m| m == mapped)) + pub fn minimize_request(&mut self, surface: &S) + where + CosmicSurface: PartialEq, + { + if let Some((set, mapped)) = self.workspaces.sets.values_mut().find_map(|set| { + let mapped = set + .sticky_layer + .mapped() + .find(|m| &m.active_window() == surface) + .cloned(); + mapped.map(|m| (set, m)) }) { - let to = minimize_rectangle(workspace.output(), &mapped.active_window()); - if let Some(minimized) = workspace.minimize(&mapped, to) { - workspace.minimized_windows.push(minimized); + let to = minimize_rectangle(&set.output, &mapped.active_window()); + let geo = set.sticky_layer.unmap(&mapped, Some(to)).unwrap(); + set.minimized_windows.push(MinimizedWindow::Floating { + window: mapped.clone(), + previous: FloatingRestoreData { + geometry: geo, + output_size: set.output.geometry().size.as_logical(), + }, + }); + } else { + if let Some((workspace, window)) = self.workspaces.sets.values_mut().find_map(|set| { + set.workspaces.iter_mut().find_map(|workspace| { + let window = workspace + .get_fullscreen() + .cloned() + .into_iter() + .chain(workspace.mapped().map(|m| m.active_window())) + .find(|s| s == surface); + window.map(|s| (workspace, s)) + }) + }) { + let to = minimize_rectangle(workspace.output(), &window); + if let Some(minimized) = workspace.minimize(&surface, to) { + workspace.minimized_windows.push(minimized); + } } } } - pub fn unminimize_request(&mut self, mapped: &CosmicMapped, seat: &Seat) { + pub fn unminimize_request( + &mut self, + surface: &S, + seat: &Seat, + loop_handle: &LoopHandle<'static, State>, + ) where + CosmicSurface: PartialEq, + { if let Some((set, window)) = self.workspaces.sets.values_mut().find_map(|set| { set.minimized_windows .iter() - .position(|m| &m.window == mapped) + .position(|m| m.windows().any(|s| &s == surface)) .map(|i| set.minimized_windows.swap_remove(i)) .map(|window| (set, window)) }) { - let from = minimize_rectangle(&set.output, &mapped.active_window()); - - if let MinimizedState::Sticky { mut position } = window.previous_state { - let current_output_size = set.output.geometry().size.as_logical(); - if current_output_size != window.output_geo.size.as_logical() { - position = Point::from(( - (position.x as f64 / window.output_geo.size.w as f64 - * current_output_size.w as f64) - .floor() as i32, - (position.y as f64 / window.output_geo.size.h as f64 - * current_output_size.h as f64) - .floor() as i32, - )) - }; - set.sticky_layer - .remap_minimized(window.window, from, position); - } else { + let MinimizedWindow::Floating { window, previous } = window else { unreachable!("None sticky window in WorkspaceSet minimized_windows"); - } - } else if let Some((workspace, window)) = self.workspaces.spaces_mut().find_map(|w| { - w.minimized_windows - .iter() - .position(|m| &m.window == mapped) - .map(|i| w.minimized_windows.swap_remove(i)) - .map(|window| (w, window)) - }) { - let from = minimize_rectangle(workspace.output(), &mapped.active_window()); + }; - workspace.unminimize(window, from, seat); + let from = minimize_rectangle(&set.output, &window.active_window()); + let previous_position = + previous.position_relative(set.output.geometry().size.as_logical()); + if window.is_stack() { + window.set_active(surface); + } + set.sticky_layer + .remap_minimized(window, from, previous_position); + } else { + let Some((workspace, window)) = self.workspaces.spaces_mut().find_map(|w| { + w.minimized_windows + .iter() + .position(|m| m.windows().any(|s| &s == surface)) + .map(|i| w.minimized_windows.swap_remove(i)) + .map(|window| (w, window)) + }) else { + return; + }; + + if window.mapped().is_some_and(|m| m.is_stack()) { + window.mapped().unwrap().set_active(surface); + } + let from = minimize_rectangle(workspace.output(), &window.active_window()); + if let Some((surface, restore, previous_geo)) = workspace.unminimize(window, from, seat) + { + toplevel_leave_output(&surface, &workspace.output); + toplevel_leave_workspace(&surface, &workspace.handle); + self.remap_unfullscreened_window(surface, restore, previous_geo, loop_handle); + } } } - pub fn maximize_request(&mut self, mapped: &CosmicMapped, seat: &Seat, animate: bool) { - self.unminimize_request(mapped, seat); + pub fn maximize_request( + &mut self, + mapped: &CosmicMapped, + seat: &Seat, + animate: bool, + loop_handle: &LoopHandle<'static, State>, + ) { + self.unminimize_request(&mapped.active_window(), seat, loop_handle); + let (original_layer, floating_layer, original_geometry) = if let Some(set) = self .workspaces .sets @@ -3730,7 +4004,7 @@ impl Shell { let geometry = set.sticky_layer.element_geometry(mapped).unwrap(); (ManagedLayer::Sticky, &mut set.sticky_layer, geometry) } else if let Some(workspace) = self.space_for_mut(&mapped) { - let layer = if workspace.is_floating(&mapped) { + let layer = if workspace.is_floating(&mapped.active_window()) { ManagedLayer::Floating } else { ManagedLayer::Tiling @@ -3755,7 +4029,10 @@ impl Shell { pub fn unmaximize_request(&mut self, mapped: &CosmicMapped) -> Option> { if let Some(set) = self.workspaces.sets.values_mut().find(|set| { set.sticky_layer.mapped().any(|m| m == mapped) - || set.minimized_windows.iter().any(|m| &m.window == mapped) + || set + .minimized_windows + .iter() + .any(|m| m.mapped().is_some_and(|m| m == mapped)) }) { let mut state = mapped.maximized_state.lock().unwrap(); if let Some(state) = state.take() { @@ -3764,7 +4041,7 @@ impl Shell { if let Some(minimized) = set .minimized_windows .iter_mut() - .find(|m| &m.window == mapped) + .find(|m| m.mapped().is_some_and(|m| m == mapped)) { minimized.unmaximize(state.original_geometry); } else { @@ -3800,7 +4077,7 @@ impl Shell { let start_data = check_grab_preconditions(&seat, serial, client_initiated.then_some(surface))?; let mapped = self.element_for_surface(surface).cloned()?; - if mapped.is_fullscreen(true) || mapped.is_maximized(true) { + if mapped.is_maximized(true) { return None; } @@ -3950,14 +4227,26 @@ impl Shell { } #[must_use] - pub fn toggle_stacking_focused(&mut self, seat: &Seat) -> Option { + pub fn toggle_stacking_focused( + &mut self, + seat: &Seat, + loop_handle: &LoopHandle<'static, State>, + ) -> Option { let Some(focused_output) = seat.focused_output() else { return None; }; let set = self.workspaces.sets.get_mut(&focused_output).unwrap(); let workspace = &mut set.workspaces[set.active]; + + if matches!( + seat.get_keyboard().unwrap().current_focus(), + Some(KeyboardFocusTarget::Fullscreen(_)) + ) { + return None; + } + let maybe_window = workspace.focus_stack.get(seat).iter().next().cloned(); - if let Some(window) = maybe_window { + if let Some(FocusTarget::Window(window)) = maybe_window { let was_maximized = window.is_maximized(false); if was_maximized { workspace.unmaximize_request(&window); @@ -3980,7 +4269,7 @@ impl Shell { if was_maximized { if let Some(KeyboardFocusTarget::Element(mapped)) = res.as_ref() { - self.maximize_request(mapped, seat, false); + self.maximize_request(mapped, seat, false, loop_handle); } } @@ -4000,11 +4289,7 @@ impl Shell { } if let Some(workspace) = self.space_for_mut(mapped) { - if workspace.is_fullscreen(mapped) { - // we are making it sticky, we don't need to move it to it's previous workspace - let _ = workspace.remove_fullscreen(); - } - let previous_layer = if workspace.is_tiled(mapped) { + let previous_layer = if workspace.is_tiled(&mapped.active_window()) { workspace.toggle_floating_window(seat, mapped); ManagedLayer::Tiling } else { @@ -4013,7 +4298,7 @@ impl Shell { let Some(geometry) = workspace.element_geometry(mapped) else { return; }; - workspace.unmap(mapped); + workspace.unmap_element(mapped); *mapped.previous_layer.lock().unwrap() = Some(previous_layer); let output = workspace.output().clone(); @@ -4050,8 +4335,7 @@ impl Shell { .values_mut() .find(|set| set.sticky_layer.mapped().any(|m| m == mapped)) { - let geometry = set.sticky_layer.element_geometry(mapped).unwrap(); - set.sticky_layer.unmap(mapped); + let geometry = set.sticky_layer.unmap(mapped, None).unwrap(); let workspace = &mut set.workspaces[set.active]; for (window, _) in mapped.windows() { @@ -4093,18 +4377,151 @@ impl Shell { } } - self.append_focus_stack(&mapped, seat); + self.append_focus_stack(mapped.clone(), seat); } pub fn toggle_sticky_current(&mut self, seat: &Seat) { + if matches!( + seat.get_keyboard().unwrap().current_focus(), + Some(KeyboardFocusTarget::Fullscreen(_)) + ) { + return; + } let set = self.workspaces.sets.get_mut(&seat.active_output()).unwrap(); let workspace = &mut set.workspaces[set.active]; + let maybe_window = workspace.focus_stack.get(seat).iter().next().cloned(); - if let Some(mapped) = maybe_window { + if let Some(FocusTarget::Window(mapped)) = maybe_window { self.toggle_sticky(seat, &mapped); } } + #[must_use] + pub fn fullscreen_request( + &mut self, + surface: &S, + output: Output, + loop_handle: &LoopHandle<'static, State>, + ) -> Option + where + CosmicSurface: PartialEq, + { + let Some(mapped) = self.element_for_surface(surface).cloned() else { + return None; + }; + let window; + + let old_fullscreen = if let Some((old_output, set)) = self + .workspaces + .sets + .iter_mut() + .find(|(_, set)| set.sticky_layer.mapped().any(|m| m == &mapped)) + { + let from = set.sticky_layer.element_geometry(&mapped).unwrap(); + window = 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 == surface).unwrap(); + stack.remove_window(&surface); + surface + } else { + set.sticky_layer.unmap(&mapped, None); + mapped.active_window() + }; + + toplevel_leave_output(&window, &old_output); + let old_output = old_output.downgrade(); + let workspace_handle = self.active_space(&output).unwrap().handle.clone(); + toplevel_enter_output(&window, &output); + toplevel_enter_workspace(&window, &workspace_handle); + + let workspace = self.active_space_mut(&output).unwrap(); + workspace.map_fullscreen( + &window, + None, + Some(FullscreenRestoreState::Sticky { + output: old_output, + state: FloatingRestoreData { + geometry: from, + output_size: workspace.output.geometry().size.as_logical(), + }, + }), + Some(from), + ) + } else if let Some(workspace) = self.space_for_mut(&mapped) { + let from = workspace.element_geometry(&mapped).unwrap(); + let (surface, state) = workspace.unmap_surface(surface).unwrap(); + window = surface; + let handle = workspace.handle.clone(); + + toplevel_leave_output(&window, &workspace.output); + toplevel_leave_workspace(&window, &workspace.handle); + + let workspace = self.active_space_mut(&output).unwrap(); + toplevel_enter_output(&window, &output); + toplevel_enter_workspace(&window, &workspace.handle); + + workspace.map_fullscreen( + &window, + None, + match state { + WorkspaceRestoreData::Floating(floating_state) => { + floating_state.map(|state| FullscreenRestoreState::Floating { + workspace: handle, + state, + }) + } + WorkspaceRestoreData::Tiling(tiling_state) => { + tiling_state.map(|state| FullscreenRestoreState::Tiling { + workspace: handle, + state, + }) + } + WorkspaceRestoreData::Fullscreen(_) => unreachable!(), + }, + Some(from), + ) + } else { + return None; + }; + + if let Some((old_fullscreen, restore, previous_geo)) = old_fullscreen { + self.remap_unfullscreened_window(old_fullscreen, restore, previous_geo, &loop_handle); + } + + Some(KeyboardFocusTarget::Fullscreen(window)) + } + + pub fn unfullscreen_request( + &mut self, + surface: &S, + loop_handle: &LoopHandle<'static, State>, + ) -> bool + where + CosmicSurface: PartialEq, + { + 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)) + }); + + if let Some(workspace) = maybe_workspace { + let (old_fullscreen, restore, previous_geo) = workspace.remove_fullscreen().unwrap(); + toplevel_leave_output(&old_fullscreen, &workspace.output); + toplevel_leave_workspace(&old_fullscreen, &workspace.handle); + + self.remap_unfullscreened_window(old_fullscreen, restore, previous_geo, loop_handle); + + true + } else { + false + } + } + pub fn update_toolkit( &mut self, toolkit: cosmic::config::CosmicTk, @@ -4202,10 +4619,10 @@ impl Shell { self.workspaces.iter().flat_map(|(_, set)| { set.sticky_layer .mapped() - .chain(set.minimized_windows.iter().map(|m| &m.window)) + .chain(set.minimized_windows.iter().flat_map(|m| m.mapped())) .chain(set.workspaces.iter().flat_map(|w| { w.mapped() - .chain(w.minimized_windows.iter().map(|m| &m.window)) + .chain(w.minimized_windows.iter().flat_map(|m| m.mapped())) })) }) } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 96526d0c..1e027e31 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,3 +1,5 @@ +use crate::shell::focus::FocusTarget; +use crate::shell::layout::tiling::RestoreTilingState; use crate::wayland::handlers::xdg_activation::ActivationContext; use crate::{ backend::render::{ @@ -25,6 +27,7 @@ use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::Tiling use id_tree::Tree; use indexmap::IndexSet; use keyframe::{ease, functions::EaseInOutCubic}; +use smithay::output::WeakOutput; use smithay::{ backend::renderer::{ element::{ @@ -39,20 +42,13 @@ use smithay::{ desktop::{layer_map_for_output, space::SpaceElement, WindowSurfaceType}, input::Seat, output::Output, - reexports::wayland_server::{Client, Resource}, + reexports::wayland_server::Client, utils::{Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Size}, - wayland::{ - compositor::{add_blocker, Blocker, BlockerState}, - seat::WaylandFocus, - xdg_activation::XdgActivationState, - }, + wayland::xdg_activation::XdgActivationState, }; use std::{ collections::{HashMap, VecDeque}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::atomic::{AtomicBool, Ordering}, time::{Duration, Instant}, }; use wayland_backend::server::ClientId; @@ -68,7 +64,7 @@ use super::{ FocusStack, FocusStackMut, }, grabs::ResizeEdge, - layout::tiling::{Data, MinimizedTilingState, NodeDesc}, + layout::tiling::{Data, NodeDesc}, CosmicMappedRenderElement, CosmicSurface, ResizeDirection, ResizeMode, }; @@ -120,58 +116,95 @@ pub struct Workspace { } #[derive(Debug)] -pub struct MinimizedWindow { - pub window: CosmicMapped, - pub previous_state: MinimizedState, - pub fullscreen: Option, - pub output_geo: Rectangle, -} - -#[derive(Debug)] -pub enum MinimizedState { - Sticky { - position: Point, +pub enum MinimizedWindow { + Fullscreen { + surface: CosmicSurface, + previous: Option, }, Floating { - position: Point, + window: CosmicMapped, + previous: FloatingRestoreData, }, Tiling { - tiling_state: Option, - was_maximized: bool, + window: CosmicMapped, + previous: TilingRestoreData, }, } +impl PartialEq for MinimizedWindow { + fn eq(&self, other: &CosmicMapped) -> bool { + self.mapped().is_some_and(|m| m == other) + } +} + impl MinimizedWindow { - pub(super) fn unmaximize(&mut self, original_geometry: Rectangle) { - self.window.set_maximized(false); - self.window.configure(); - - match &mut self.previous_state { - MinimizedState::Sticky { position } | MinimizedState::Floating { position } => { - *position = original_geometry.loc; + pub fn mapped(&self) -> Option<&CosmicMapped> { + match self { + MinimizedWindow::Floating { window, .. } | MinimizedWindow::Tiling { window, .. } => { + Some(window) } - MinimizedState::Tiling { was_maximized, .. } => { - *was_maximized = false; + _ => None, + } + } + + pub fn mapped_mut(&mut self) -> Option<&mut CosmicMapped> { + match self { + MinimizedWindow::Floating { window, .. } | MinimizedWindow::Tiling { window, .. } => { + Some(window) + } + _ => None, + } + } + + pub fn active_window(&self) -> CosmicSurface { + match self { + MinimizedWindow::Floating { window, .. } | MinimizedWindow::Tiling { window, .. } => { + window.active_window() + } + MinimizedWindow::Fullscreen { surface, .. } => surface.clone(), + } + } + + pub fn windows(&self) -> impl Iterator + '_ { + match self { + MinimizedWindow::Floating { window, .. } | MinimizedWindow::Tiling { window, .. } => { + Box::new(window.windows().map(|(s, _)| s)) + as Box> + } + MinimizedWindow::Fullscreen { surface, .. } => { + Box::new(std::iter::once(surface.clone())) as _ } } } - fn unfullscreen(&mut self) -> Option<(ManagedLayer, WorkspaceHandle)> { - let fullscreen = self.fullscreen.take()?; - self.window.set_fullscreen(false); - self.window.set_geometry(fullscreen.original_geometry); - fullscreen.previously + pub fn unmaximize(&mut self, original_geometry: Rectangle) { + match self { + MinimizedWindow::Fullscreen { .. } => {} + MinimizedWindow::Tiling { + window, previous, .. + } => { + previous.was_maximized = false; + window.set_maximized(false); + window.configure(); + } + MinimizedWindow::Floating { + window, previous, .. + } => { + previous.geometry = original_geometry; + window.set_maximized(false); + window.configure(); + } + } } } #[derive(Debug, Clone)] pub struct FullscreenSurface { pub surface: CosmicSurface, - pub previously: Option<(ManagedLayer, WorkspaceHandle)>, - original_geometry: Rectangle, + pub previous_state: Option, + pub previous_geometry: Option>, start_at: Option, - ended_at: Option, - animation_signal: Option>, + pub ended_at: Option, } impl PartialEq for FullscreenSurface { @@ -180,20 +213,6 @@ impl PartialEq for FullscreenSurface { } } -struct FullscreenBlocker { - signal: Arc, -} - -impl Blocker for FullscreenBlocker { - fn state(&self) -> BlockerState { - if self.signal.load(Ordering::SeqCst) { - BlockerState::Released - } else { - BlockerState::Pending - } - } -} - impl FullscreenSurface { pub fn is_animating(&self) -> bool { self.start_at.is_some() || self.ended_at.is_some() @@ -208,20 +227,82 @@ impl IsAlive for FullscreenSurface { /// LIFO stack of focus targets #[derive(Debug, Default)] -pub struct FocusStacks(HashMap, IndexSet>); +pub struct FocusStacks(HashMap, IndexSet>); -#[derive(Debug, Clone, PartialEq)] -pub struct ManagedState { - pub layer: ManagedLayer, - pub was_fullscreen: Option, -} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ManagedLayer { + Fullscreen, Tiling, Floating, Sticky, } +#[derive(Debug, Clone)] +pub enum FullscreenRestoreState { + Tiling { + workspace: WorkspaceHandle, + state: TilingRestoreData, + }, + Floating { + workspace: WorkspaceHandle, + state: FloatingRestoreData, + }, + Sticky { + output: WeakOutput, + state: FloatingRestoreData, + }, +} + +#[derive(Debug, Clone)] +pub enum WorkspaceRestoreData { + Fullscreen(Option), + Tiling(Option), + Floating(Option), +} + +impl From for WorkspaceRestoreData { + fn from(value: ManagedLayer) -> Self { + match value { + ManagedLayer::Floating | ManagedLayer::Sticky => WorkspaceRestoreData::Floating(None), + ManagedLayer::Tiling => WorkspaceRestoreData::Tiling(None), + ManagedLayer::Fullscreen => WorkspaceRestoreData::Fullscreen(None), + } + } +} + +#[derive(Debug, Clone)] +pub struct FloatingRestoreData { + pub geometry: Rectangle, + pub output_size: Size, +} + +impl FloatingRestoreData { + pub fn position_relative(&self, output_size: Size) -> Point { + if self.output_size != output_size { + Point::from(( + (self.geometry.loc.x as f64 / self.output_size.w as f64 * output_size.w as f64) + .floor() as i32, + (self.geometry.loc.y as f64 / self.output_size.h as f64 * output_size.h as f64) + .floor() as i32, + )) + } else { + self.geometry.loc + } + } +} + +#[derive(Debug, Clone)] +pub struct TilingRestoreData { + pub state: Option, + pub was_maximized: bool, +} + +#[derive(Debug, Clone)] +pub struct FullscreenRestoreData { + pub previous_state: FullscreenRestoreState, + pub previous_geometry: Rectangle, +} + #[derive(Debug, Clone, PartialEq)] pub enum FocusResult { None, @@ -344,11 +425,7 @@ impl Workspace { #[profiling::function] pub fn refresh(&mut self) { - // TODO: `Option::take_if` once stabilitized - if self.fullscreen.as_ref().is_some_and(|w| !w.alive()) { - let _ = self.fullscreen.take(); - }; - + self.fullscreen.take_if(|w| !w.alive()); self.floating_layer.refresh(); self.tiling_layer.refresh(); } @@ -373,9 +450,22 @@ impl Workspace { } pub fn refresh_focus_stack(&mut self) { - let windows: Vec = self.mapped().cloned().collect(); for stack in self.focus_stack.0.values_mut() { - stack.retain(|w| windows.contains(w)); + let fullscreen = self + .fullscreen + .as_ref() + .filter(|f| f.alive()) + .filter(|f| f.ended_at.is_none()) + .map(|f| &f.surface); + let mapped = || { + self.floating_layer + .mapped() + .chain(self.tiling_layer.mapped().map(|(w, _)| w)) + }; + stack.retain(|w| match w { + FocusTarget::Fullscreen(s) => fullscreen.is_some_and(|f| f == s), + FocusTarget::Window(w) => mapped().any(|m| w == m), + }); } } @@ -390,8 +480,6 @@ impl Workspace { } pub fn update_animations(&mut self) -> HashMap { - let mut clients = HashMap::new(); - if let Some(f) = self.fullscreen.as_mut() { if let Some(start) = f.start_at.as_ref() { let duration_since = Instant::now().duration_since(*start); @@ -399,31 +487,10 @@ impl Workspace { f.start_at.take(); self.dirty.store(true, Ordering::SeqCst); } - if duration_since * 2 > FULLSCREEN_ANIMATION_DURATION { - if let Some(signal) = f.animation_signal.take() { - signal.store(true, Ordering::SeqCst); - if let Some(client) = - f.surface.wl_surface().as_deref().and_then(Resource::client) - { - clients.insert(client.id(), client); - } - } - } } if let Some(end) = f.ended_at { let duration_since = Instant::now().duration_since(end); - if duration_since * 2 > FULLSCREEN_ANIMATION_DURATION { - if let Some(signal) = f.animation_signal.take() { - signal.store(true, Ordering::SeqCst); - if let Some(client) = - f.surface.wl_surface().as_deref().and_then(Resource::client) - { - clients.insert(client.id(), client); - } - } - } - if duration_since >= FULLSCREEN_ANIMATION_DURATION { let _ = self.fullscreen.take(); self.dirty.store(true, Ordering::SeqCst); @@ -431,7 +498,7 @@ impl Workspace { } } - clients.extend(self.tiling_layer.update_animation_state()); + let clients = self.tiling_layer.update_animation_state(); self.floating_layer.update_animation_state(); clients } @@ -459,11 +526,15 @@ impl Workspace { } } for window in self.minimized_windows.iter() { - for (surface, _) in window.window.windows() { + for surface in window.windows() { toplevel_leave_output(&surface, &self.output); toplevel_enter_output(&surface, output); } } + if let Some(f) = self.fullscreen.as_ref().filter(|f| f.ended_at.is_none()) { + toplevel_leave_output(&f.surface, &self.output); + toplevel_enter_output(&f.surface, output); + } if explicit { self.output_stack.clear(); } @@ -499,63 +570,116 @@ impl Workspace { .any(|i| output_matches(i, output, disambiguate)) } - pub fn unmap(&mut self, mapped: &CosmicMapped) -> Option { - let mut was_fullscreen = self - .fullscreen - .as_ref() - .filter(|f| f.ended_at.is_none()) - .map(|f| mapped.windows().any(|(w, _)| w == f.surface)) - .unwrap_or(false) - .then(|| self.fullscreen.take().unwrap()); - + pub fn unmap_element(&mut self, mapped: &CosmicMapped) -> Option { if mapped.maximized_state.lock().unwrap().is_some() { // If surface is maximized then unmaximize it, so it is assigned to only one layer - let _ = self.unmaximize_request(mapped); - } - - let mut was_floating = self.floating_layer.unmap(&mapped).is_some(); - let mut was_tiling = self.tiling_layer.unmap(&mapped); - if was_floating || was_tiling { - assert!(was_floating != was_tiling); - } - if let Some(pos) = self - .minimized_windows - .iter() - .position(|m| &m.window == mapped) - { - let state = self.minimized_windows.remove(pos); - state.window.set_minimized(false); - match state.previous_state { - MinimizedState::Sticky { .. } | MinimizedState::Floating { .. } => { - was_floating = true; - } - MinimizedState::Tiling { .. } => { - was_tiling = true; - } - } - was_fullscreen = state.fullscreen; + let _ = self.unmaximize_request(&mapped); } self.focus_stack .0 .values_mut() .for_each(|set| set.retain(|m| m != mapped)); - if was_floating { - Some(ManagedState { - layer: ManagedLayer::Floating, - was_fullscreen, - }) - } else if was_tiling { - Some(ManagedState { - layer: ManagedLayer::Tiling, - was_fullscreen, - }) - } else { - None + + if let Some(pos) = self.minimized_windows.iter().position(|m| m == mapped) { + let state = self.minimized_windows.remove(pos); + return Some(match state { + MinimizedWindow::Floating { previous, .. } => { + WorkspaceRestoreData::Floating(Some(previous)) + } + MinimizedWindow::Tiling { previous, .. } => { + WorkspaceRestoreData::Tiling(Some(previous)) + } + MinimizedWindow::Fullscreen { .. } => unreachable!(), + }); } + + let was_maximized = + if let Some(floating_geometry) = self.floating_layer.unmap(&mapped, None) { + if mapped.maximized_state.lock().unwrap().is_none() { + return Some(WorkspaceRestoreData::Floating(Some(FloatingRestoreData { + geometry: floating_geometry, + output_size: self.output.geometry().size.as_logical(), + }))); + } + true + } else { + false + }; + if let Ok(state) = self.tiling_layer.unmap(&mapped, None) { + return Some(WorkspaceRestoreData::Tiling(Some(TilingRestoreData { + state, + was_maximized, + }))); + } + + None } - fn fullscreen_geometry(&self) -> Option> { + pub fn unmap_surface(&mut self, surface: &S) -> Option<(CosmicSurface, WorkspaceRestoreData)> + where + CosmicSurface: PartialEq, + { + if self + .fullscreen + .as_ref() + .is_some_and(|f| f.ended_at.is_none() && &f.surface == surface) + { + let (surface, previous_state, previous_geometry) = self.remove_fullscreen().unwrap(); + return Some(( + surface, + WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map( + |(previous_state, previous_geometry)| FullscreenRestoreData { + previous_state, + previous_geometry, + }, + )), + )); + } + + if let Some(pos) = self.minimized_windows.iter().position(|m| { + if let MinimizedWindow::Fullscreen { surface: s, .. } = m { + s == surface + } else { + false + } + }) { + let MinimizedWindow::Fullscreen { surface, previous } = + self.minimized_windows.remove(pos) + else { + unreachable!() + }; + + return Some((surface, WorkspaceRestoreData::Fullscreen(previous))); + } + + let Some(mapped) = self.element_for_surface(surface) else { + return None; + }; + + let maybe_stack = mapped.stack_ref().filter(|s| s.len() > 1); + if let Some(stack) = maybe_stack { + if stack.len() > 1 { + let idx = stack.surfaces().position(|s| &s == surface); + let layer = self + .is_tiled(surface) + .then_some(ManagedLayer::Tiling) + .unwrap_or(ManagedLayer::Floating); + return idx + .and_then(|idx| stack.remove_idx(idx)) + .map(|s| (s, layer.into())); + } + } + + // we know mapped is no stack with more than one element now, + // so we can treat mapped as containing only our surface. + + let mapped = mapped.clone(); + let layer = self.unmap_element(&mapped)?; + Some((mapped.active_window(), layer)) + } + + pub fn fullscreen_geometry(&self) -> Option> { self.fullscreen.as_ref().map(|fullscreen| { let bbox = fullscreen.surface.bbox().as_local(); @@ -582,83 +706,129 @@ impl Workspace { self.floating_layer .mapped() .chain(self.tiling_layer.mapped().map(|(w, _)| w)) - .chain(self.minimized_windows.iter().map(|w| &w.window)) + .chain(self.minimized_windows.iter().flat_map(|w| w.mapped())) .find(|e| e.windows().any(|(w, _)| &w == surface)) } - pub fn popup_element_under(&self, location: Point) -> Option { - if !self.output.geometry().contains(location.to_i32_round()) { - return None; - } - let location = location.to_local(&self.output); - - if let Some(fullscreen) = self.fullscreen.as_ref() { - if !fullscreen.is_animating() { - let geometry = self.fullscreen_geometry().unwrap(); - return fullscreen - .surface - .0 - .surface_under( - (location + geometry.loc.to_f64()).as_logical(), - WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, - ) - .is_some() - .then(|| KeyboardFocusTarget::Fullscreen(fullscreen.surface.clone())); - } - } - - self.floating_layer - .popup_element_under(location) - .or_else(|| self.tiling_layer.popup_element_under(location)) - } - - pub fn toplevel_element_under( + pub fn popup_element_under( &self, location: Point, + seat: &Seat, ) -> Option { if !self.output.geometry().contains(location.to_i32_round()) { return None; } let location = location.to_local(&self.output); - if let Some(fullscreen) = self.fullscreen.as_ref() { - if !fullscreen.is_animating() { - let geometry = self.fullscreen_geometry().unwrap(); - return fullscreen + let fullscreen_element_under = + |fullscreen: &FullscreenSurface, geometry: Rectangle| { + fullscreen .surface .0 .surface_under( - (location + geometry.loc.to_f64()).as_logical(), + (location - geometry.loc.to_f64()).as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + .then(|| KeyboardFocusTarget::Fullscreen(fullscreen.surface.clone())) + }; + + let stack = self.focus_stack.get(seat); + let last_focused = stack.last(); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if last_focused.is_some_and( + |t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface), + ) && !fullscreen.is_animating() + { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen_element_under(fullscreen, geometry); + } + } + + self.floating_layer + .popup_element_under(location) + .or_else(|| self.tiling_layer.popup_element_under(location)) + .or_else(|| { + if last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_))) { + if let Some(fullscreen) = self.fullscreen.as_ref() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen_element_under(fullscreen, geometry); + } + } + None + }) + } + + pub fn toplevel_element_under( + &self, + location: Point, + seat: &Seat, + ) -> Option { + if !self.output.geometry().contains(location.to_i32_round()) { + return None; + } + let location = location.to_local(&self.output); + + let fullscreen_element_under = + |fullscreen: &FullscreenSurface, geometry: Rectangle| { + fullscreen + .surface + .0 + .surface_under( + (location - geometry.loc.to_f64()).as_logical(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, ) .is_some() - .then(|| KeyboardFocusTarget::Fullscreen(fullscreen.surface.clone())); + .then(|| KeyboardFocusTarget::Fullscreen(fullscreen.surface.clone())) + }; + + let stack = self.focus_stack.get(seat); + let last_focused = stack.last(); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if last_focused.is_some_and( + |t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface), + ) && !fullscreen.is_animating() + { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen_element_under(fullscreen, geometry); } } self.floating_layer .toplevel_element_under(location) .or_else(|| self.tiling_layer.toplevel_element_under(location)) + .or_else(|| { + if !last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_))) { + if let Some(fullscreen) = self.fullscreen.as_ref() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen_element_under(fullscreen, geometry); + } + } + None + }) } pub fn popup_surface_under( &self, location: Point, overview: OverviewMode, + seat: &Seat, ) -> Option<(PointerFocusTarget, Point)> { if !self.output.geometry().contains(location.to_i32_round()) { return None; } let location = location.to_local(&self.output); - if let Some(fullscreen) = self.fullscreen.as_ref() { + let check_fullscreen = |fullscreen: &FullscreenSurface| { if !fullscreen.is_animating() { let geometry = self.fullscreen_geometry().unwrap(); return fullscreen .surface .0 .surface_under( - (location + geometry.loc.to_f64()).as_logical(), + (location - geometry.loc.to_f64()).as_logical(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, ) .map(|(surface, surface_offset)| { @@ -667,17 +837,28 @@ impl Workspace { surface, toplevel: Some(fullscreen.surface.clone().into()), }, - (geometry.loc + surface_offset.as_local()) - .to_global(&self.output) - .to_f64(), + (geometry.loc + surface_offset.as_local()).to_f64(), ) }); } - } + None + }; - self.floating_layer - .popup_surface_under(location) + let stack = self.focus_stack.get(seat); + let last_focused = stack.last(); + + self.fullscreen + .as_ref() + .filter(|f| last_focused.is_some_and(|t| t == &f.surface)) + .and_then(|f| check_fullscreen(f)) + .or_else(|| self.floating_layer.popup_surface_under(location)) .or_else(|| self.tiling_layer.popup_surface_under(location, overview)) + .or_else(|| { + self.fullscreen + .as_ref() + .filter(|f| last_focused.is_none_or(|t| t != &f.surface)) + .and_then(|f| check_fullscreen(f)) + }) .map(|(m, p)| (m, p.to_global(&self.output))) } @@ -685,20 +866,21 @@ impl Workspace { &self, location: Point, overview: OverviewMode, + seat: &Seat, ) -> Option<(PointerFocusTarget, Point)> { if !self.output.geometry().contains(location.to_i32_round()) { return None; } let location = location.to_local(&self.output); - if let Some(fullscreen) = self.fullscreen.as_ref() { + let check_fullscreen = |fullscreen: &FullscreenSurface| { if !fullscreen.is_animating() { let geometry = self.fullscreen_geometry().unwrap(); return fullscreen .surface .0 .surface_under( - (location + geometry.loc.to_f64()).as_logical(), + (location - geometry.loc.to_f64()).as_logical(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, ) .map(|(surface, surface_offset)| { @@ -707,17 +889,29 @@ impl Workspace { surface, toplevel: Some(fullscreen.surface.clone().into()), }, - (geometry.loc + surface_offset.as_local()) - .to_global(&self.output) - .to_f64(), + (geometry.loc + surface_offset.as_local()).to_f64(), ) }); } - } - self.floating_layer - .toplevel_surface_under(location) + None + }; + + let stack = self.focus_stack.get(seat); + let last_focused = stack.last(); + + self.fullscreen + .as_ref() + .filter(|f| last_focused.is_some_and(|t| t == &f.surface)) + .and_then(|f| check_fullscreen(f)) + .or_else(|| self.floating_layer.toplevel_surface_under(location)) .or_else(|| self.tiling_layer.toplevel_surface_under(location, overview)) + .or_else(|| { + self.fullscreen + .as_ref() + .filter(|f| last_focused.is_none_or(|t| t != &f.surface)) + .and_then(|f| check_fullscreen(f)) + }) .map(|(m, p)| (m, p.to_global(&self.output))) } @@ -745,18 +939,14 @@ impl Workspace { pub fn unmaximize_request(&mut self, elem: &CosmicMapped) -> Option> { let mut state = elem.maximized_state.lock().unwrap(); if let Some(state) = state.take() { - if let Some(minimized) = self - .minimized_windows - .iter_mut() - .find(|m| &m.window == elem) - { + if let Some(minimized) = self.minimized_windows.iter_mut().find(|m| *m == elem) { minimized.unmaximize(state.original_geometry); Some(state.original_geometry.size.as_logical()) } else { match state.original_layer { ManagedLayer::Tiling if self.tiling_enabled => { // should still be mapped in tiling - self.floating_layer.unmap(&elem); + self.floating_layer.unmap(&elem, None); elem.output_enter(&self.output, elem.bbox()); elem.set_maximized(false); elem.set_geometry(state.original_geometry.to_global(&self.output)); @@ -784,18 +974,15 @@ impl Workspace { } } - pub fn minimize( - &mut self, - elem: &CosmicMapped, - to: Rectangle, - ) -> Option { - let fullscreen = if self - .get_fullscreen() - .is_some_and(|s| elem.windows().any(|(w, _)| *s == w)) - { + pub fn minimize(&mut self, surface: &S, to: Rectangle) -> Option + where + CosmicSurface: PartialEq, + { + if self.get_fullscreen().is_some_and(|s| s == surface) { let fullscreen_state = self.fullscreen.clone().unwrap(); { let f = self.fullscreen.as_mut().unwrap(); + f.previous_geometry = Some(to); f.ended_at = Some( Instant::now() - (FULLSCREEN_ANIMATION_DURATION @@ -809,33 +996,54 @@ impl Workspace { .unwrap_or(FULLSCREEN_ANIMATION_DURATION)), ); } - Some(fullscreen_state) - } else { - None - }; + return Some(MinimizedWindow::Fullscreen { + surface: fullscreen_state.surface, + previous: fullscreen_state + .previous_state + .zip(fullscreen_state.previous_geometry) + .map( + |(previous_state, previous_geometry)| FullscreenRestoreData { + previous_state, + previous_geometry, + }, + ), + }); + } - if self.tiling_layer.mapped().any(|(m, _)| m == elem) { - let was_maximized = self.floating_layer.unmap(&elem).is_some(); - let tiling_state = self.tiling_layer.unmap_minimize(elem, to); - Some(MinimizedWindow { - window: elem.clone(), - previous_state: MinimizedState::Tiling { - tiling_state, + let maybe_elem = self + .tiling_layer + .mapped() + .find(|(m, _)| m.windows().any(|(ref s, _)| s == surface)) + .map(|(s, _)| s.clone()); + if let Some(elem) = maybe_elem { + let was_maximized = self.floating_layer.unmap(&elem, None).is_some(); + let previous_state = self.tiling_layer.unmap(&elem, Some(to)).unwrap(); + return Some(MinimizedWindow::Tiling { + window: elem, + previous: TilingRestoreData { + state: previous_state, was_maximized, }, - output_geo: self.output.geometry(), - fullscreen, - }) - } else { - self.floating_layer - .unmap_minimize(elem, to) - .map(|(window, position)| MinimizedWindow { - window, - previous_state: MinimizedState::Floating { position }, - output_geo: self.output.geometry(), - fullscreen, - }) + }); } + + let maybe_elem = self + .floating_layer + .mapped() + .find(|m| m.windows().any(|(ref s, _)| s == surface)) + .cloned(); + if let Some(elem) = maybe_elem { + let geometry = self.floating_layer.unmap(&elem, Some(to)).unwrap(); + return Some(MinimizedWindow::Floating { + window: elem, + previous: FloatingRestoreData { + geometry, + output_size: self.output.geometry().size.as_logical(), + }, + }); + } + + None } pub fn unminimize( @@ -843,185 +1051,125 @@ impl Workspace { window: MinimizedWindow, from: Rectangle, seat: &Seat, - ) -> Option<(CosmicMapped, ManagedLayer, WorkspaceHandle)> { - match window.previous_state { - MinimizedState::Floating { mut position } => { - let current_output_size = self.output.geometry().size.as_logical(); - if current_output_size != window.output_geo.size.as_logical() { - position = Point::from(( - (position.x as f64 / window.output_geo.size.w as f64 - * current_output_size.w as f64) - .floor() as i32, - (position.y as f64 / window.output_geo.size.h as f64 - * current_output_size.h as f64) - .floor() as i32, - )) - }; - self.floating_layer - .remap_minimized(window.window, from, position); + ) -> Option<( + CosmicSurface, + Option, + Option>, + )> { + match window { + MinimizedWindow::Fullscreen { previous, surface } => { + let old_fullscreen = self.remove_fullscreen(); + self.fullscreen = Some(FullscreenSurface { + surface, + previous_state: previous.clone().map(|p| p.previous_state), + previous_geometry: previous.map(|p| p.previous_geometry), + start_at: None, + ended_at: None, + }); + old_fullscreen } - MinimizedState::Sticky { .. } => unreachable!(), - MinimizedState::Tiling { - tiling_state, - was_maximized, + MinimizedWindow::Floating { window, previous } => { + let current_output_size = self.output.geometry().size.as_logical(); + let previous_position = previous.position_relative(current_output_size); + + self.floating_layer + .remap_minimized(window, from, previous_position); + None + } + MinimizedWindow::Tiling { + window, + previous: + TilingRestoreData { + state, + was_maximized, + }, } => { if self.tiling_enabled { let focus_stack = self.focus_stack.get(seat); - self.tiling_layer.remap_minimized( - window.window.clone(), - from, - tiling_state, - Some(focus_stack.iter()), - ); + self.tiling_layer + .remap(window.clone(), None, state, Some(focus_stack.iter())); if was_maximized { let previous_geometry = - self.tiling_layer.element_geometry(&window.window).unwrap(); + self.tiling_layer.element_geometry(&window).unwrap(); self.floating_layer - .map_maximized(window.window, previous_geometry, true); + .map_maximized(window, previous_geometry, true); } } else { if was_maximized { - self.floating_layer.map_maximized(window.window, from, true); + self.floating_layer.map_maximized(window, from, true); } else { - self.floating_layer.map(window.window.clone(), None); + self.floating_layer.map(window.clone(), None); // get the right animation - let geometry = self - .floating_layer - .element_geometry(&window.window) - .unwrap(); - self.floating_layer.remap_minimized( - window.window.clone(), - from, - geometry.loc, - ); + let geometry = self.floating_layer.element_geometry(&window).unwrap(); + self.floating_layer + .remap_minimized(window.clone(), from, geometry.loc); } } + None } } - - if let Some(mut fullscreen) = window.fullscreen { - let old_fullscreen = self.remove_fullscreen(); - fullscreen.start_at = Some(Instant::now()); - let geo = self.output.geometry(); - if geo != window.output_geo { - fullscreen.animation_signal = if let Some(surface) = fullscreen.surface.wl_surface() - { - let signal = Arc::new(AtomicBool::new(false)); - add_blocker( - &surface, - FullscreenBlocker { - signal: signal.clone(), - }, - ); - Some(signal) - } else { - None - }; - fullscreen.surface.set_geometry(geo, 0); - fullscreen.surface.send_configure(); - } - - self.fullscreen = Some(fullscreen); - old_fullscreen - } else { - None - } } - pub fn fullscreen_request( + pub fn map_fullscreen<'a>( &mut self, window: &CosmicSurface, - previously: Option<(ManagedLayer, WorkspaceHandle)>, - from: Rectangle, - seat: &Seat, - ) { - if self - .fullscreen - .as_ref() - .filter(|f| f.ended_at.is_none()) - .is_some() - { - return; - } - - if let Some(pos) = self - .minimized_windows - .iter() - .position(|m| m.window.windows().any(|(w, _)| &w == window)) - { - let minimized = self.minimized_windows.remove(pos); - let _ = self.unminimize(minimized, from, seat); - } + seat: impl Into>>, + restore: Option, + previous_geometry: Option>, + ) -> Option<( + CosmicSurface, + Option, + Option>, + )> { + let res = self.remove_fullscreen(); window.set_fullscreen(true); - let geo = self.output.geometry(); - let original_geometry = window.global_geometry().unwrap_or_default(); - let signal = if let Some(surface) = window.wl_surface() { - let signal = Arc::new(AtomicBool::new(false)); - add_blocker( - &surface, - FullscreenBlocker { - signal: signal.clone(), - }, - ); - Some(signal) - } else { - None - }; - window.set_geometry(geo, 0); + window.set_geometry(self.output.geometry(), 0); window.send_configure(); + if let Some(seat) = seat.into() { + self.focus_stack.get_mut(seat).append(window.clone()); + } + self.fullscreen = Some(FullscreenSurface { surface: window.clone(), - previously, - original_geometry, + previous_state: restore, + previous_geometry, start_at: Some(Instant::now()), ended_at: None, - animation_signal: signal, }); + + res } #[must_use] - pub fn unfullscreen_request( + pub fn remove_fullscreen( &mut self, - window: &CosmicSurface, - ) -> Option<(ManagedLayer, WorkspaceHandle)> { - if let Some(minimized) = self - .minimized_windows - .iter_mut() - .find(|m| m.fullscreen.as_ref().is_some_and(|f| &f.surface == window)) - { - minimized.unfullscreen() - } else if let Some(f) = self - .fullscreen - .as_mut() - .filter(|f| &f.surface == window && f.ended_at.is_none()) - { - window.set_fullscreen(false); - window.set_geometry(f.original_geometry, 0); + ) -> Option<( + CosmicSurface, + Option, + Option>, + )> { + if let Some(surface) = self.fullscreen.as_mut() { + if surface.surface.alive() { + 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(); + } - self.floating_layer.refresh(); - self.tiling_layer.recalculate(); - self.tiling_layer.refresh(); + for focus_stack in self.focus_stack.0.values_mut() { + focus_stack.retain(|t| t != &surface.surface); + } - let signal = if let Some(surface) = window.wl_surface() { - let signal = Arc::new(AtomicBool::new(false)); - add_blocker( - &surface, - FullscreenBlocker { - signal: signal.clone(), - }, - ); - Some(signal) - } else { - None - }; - window.send_configure(); - - f.ended_at = Some( + surface.ended_at = Some( Instant::now() - (FULLSCREEN_ANIMATION_DURATION - - f.start_at + - surface + .start_at .take() .map(|earlier| { Instant::now() @@ -1030,30 +1178,17 @@ impl Workspace { }) .unwrap_or(FULLSCREEN_ANIMATION_DURATION)), ); - if let Some(new_signal) = signal { - if let Some(old_signal) = f.animation_signal.replace(new_signal) { - old_signal.store(true, Ordering::SeqCst); - } - } - f.previously + Some(( + surface.surface.clone(), + surface.previous_state.clone(), + surface.previous_geometry.clone(), + )) } else { None } } - #[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) - .map(|(l, h)| (self.element_for_surface(&surface).unwrap().clone(), l, h)) - } else { - None - } - } - - /// Returns the content of the current display if it is alive and - /// not in the process of rendering an animation pub fn get_fullscreen(&self) -> Option<&CosmicSurface> { self.fullscreen .as_ref() @@ -1069,12 +1204,8 @@ impl Workspace { edge: ResizeEdge, amount: i32, ) -> bool { - if let Some(toplevel) = focused.toplevel() { - if self.fullscreen.as_ref().is_some_and(|f| { - f.ended_at.is_none() && f.surface.wl_surface().as_deref() == Some(&toplevel) - }) { - return false; - } + if matches!(focused, KeyboardFocusTarget::Fullscreen(_)) { + return false; } if !self.floating_layer.resize(focused, direction, edge, amount) { @@ -1113,7 +1244,7 @@ impl Workspace { let focus_stack = self.focus_stack.get(seat); for window in floating_windows.into_iter() { - self.floating_layer.unmap(&window); + self.floating_layer.unmap(&window, None); self.tiling_layer .map(window, Some(focus_stack.iter()), None) } @@ -1139,7 +1270,7 @@ impl Workspace { original_geometry, )); } - self.tiling_layer.unmap(&window); + let _ = self.tiling_layer.unmap(&window, None); self.floating_layer.map(window, None); } workspace_state.set_workspace_tiling_state(&self.handle, TilingState::FloatingOnly); @@ -1164,11 +1295,11 @@ impl Workspace { self.unmaximize_request(window); } if self.tiling_layer.mapped().any(|(m, _)| m == window) { - self.tiling_layer.unmap(window); + let _ = self.tiling_layer.unmap(window, None); self.floating_layer.map(window.clone(), None); } else if self.floating_layer.mapped().any(|w| w == window) { let focus_stack = self.focus_stack.get(seat); - self.floating_layer.unmap(&window); + self.floating_layer.unmap(&window, None); self.tiling_layer .map(window.clone(), Some(focus_stack.iter()), None) } @@ -1176,8 +1307,14 @@ impl Workspace { } pub fn toggle_floating_window_focused(&mut self, seat: &Seat) { + if matches!( + seat.get_keyboard().unwrap().current_focus(), + Some(KeyboardFocusTarget::Fullscreen(_)) + ) { + return; + } let maybe_window = self.focus_stack.get(seat).iter().next().cloned(); - if let Some(window) = maybe_window { + if let Some(FocusTarget::Window(window)) = maybe_window { self.toggle_floating_window(seat, &window); } } @@ -1188,40 +1325,50 @@ impl Workspace { .chain(self.tiling_layer.mapped().map(|(w, _)| w)) } + pub fn len(&self) -> usize { + self.floating_layer.mapped().count() + + self.tiling_layer.mapped().count() + + self.minimized_windows.len() + + if self.fullscreen.is_some() { 1 } else { 0 } + } + pub fn is_empty(&self) -> bool { self.floating_layer.mapped().next().is_none() && self.tiling_layer.mapped().next().is_none() && self.minimized_windows.is_empty() + && self.fullscreen.is_none() } - pub fn is_fullscreen(&self, mapped: &CosmicMapped) -> bool { - self.fullscreen - .as_ref() - .is_some_and(|f| f.ended_at.is_none() && f.surface == mapped.active_window()) - || self - .minimized_windows - .iter() - .any(|m| &m.window == mapped && m.fullscreen.is_some()) + pub fn is_floating(&self, surface: &S) -> bool + where + CosmicSurface: PartialEq, + { + self.floating_layer + .mapped() + .any(|m| m.windows().any(|(ref s, _)| s == surface)) + || self.minimized_windows.iter().any(|m| { + if let MinimizedWindow::Floating { window, .. } = m { + window.windows().any(|(ref s, _)| s == surface) + } else { + false + } + }) } - pub fn is_floating(&self, mapped: &CosmicMapped) -> bool { - !self.is_fullscreen(mapped) - && (self.floating_layer.mapped().any(|m| m == mapped) - || self.minimized_windows.iter().any(|m| { - &m.window == mapped - && matches!( - m.previous_state, - MinimizedState::Floating { .. } | MinimizedState::Sticky { .. } - ) - })) - } - - pub fn is_tiled(&self, mapped: &CosmicMapped) -> bool { - !self.is_fullscreen(mapped) - && (self.tiling_layer.mapped().any(|(m, _)| m == mapped) - || self.minimized_windows.iter().any(|m| { - &m.window == mapped && matches!(m.previous_state, MinimizedState::Tiling { .. }) - })) + pub fn is_tiled(&self, surface: &S) -> bool + where + CosmicSurface: PartialEq, + { + self.tiling_layer + .mapped() + .any(|(m, _)| m.windows().any(|(ref s, _)| s == surface)) + || self.minimized_windows.iter().any(|m| { + if let MinimizedWindow::Tiling { window, .. } = m { + window.windows().any(|(ref s, _)| s == surface) + } else { + false + } + }) } pub fn node_desc(&self, focus: KeyboardFocusTarget) -> Option { @@ -1272,7 +1419,8 @@ impl Workspace { pub fn render<'a, R>( &self, renderer: &mut R, - draw_focus_indicator: Option<&Seat>, + last_active_seat: &Seat, + render_focus: bool, overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, @@ -1293,39 +1441,15 @@ impl Workspace { let layer_map = layer_map_for_output(&self.output); layer_map.non_exclusive_zone().as_local() }; + let focused = self.focus_stack.get(last_active_seat).last().cloned(); - if let Some(fullscreen) = self.fullscreen.as_ref() { - // fullscreen window - let bbox = fullscreen.surface.bbox().as_local(); - let element_geo = Rectangle::new( - self.element_for_surface(&fullscreen.surface) - .and_then(|elem| { - self.floating_layer - .element_geometry(elem) - .or_else(|| self.tiling_layer.element_geometry(elem)) - .map(|mut geo| { - geo.loc -= elem.geometry().loc.as_local(); - geo - }) - }) - .unwrap_or(bbox) - .loc, - fullscreen.original_geometry.size.as_local(), - ); - - let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local()); - if fullscreen.start_at.is_none() { - if bbox != full_geo { - if bbox.size.w < full_geo.size.w { - full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2; - 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; - } - } - } + let mut fullscreen_elements = if let Some(fullscreen) = self.fullscreen.as_ref() { + let bbox = fullscreen.surface.bbox(); + let fullscreen_geo = self.fullscreen_geometry().unwrap(); + let previous_geo = fullscreen + .previous_geometry + .as_ref() + .unwrap_or(&fullscreen_geo); let (target_geo, alpha) = match (fullscreen.start_at, fullscreen.ended_at) { (Some(started), _) => { @@ -1334,8 +1458,8 @@ impl Workspace { ( ease( EaseInOutCubic, - EaseRectangle(element_geo), - EaseRectangle(full_geo), + EaseRectangle(*previous_geo), + EaseRectangle(fullscreen_geo), duration, ) .0, @@ -1348,15 +1472,15 @@ impl Workspace { ( ease( EaseInOutCubic, - EaseRectangle(full_geo), - EaseRectangle(element_geo), + EaseRectangle(fullscreen_geo), + EaseRectangle(*previous_geo), duration, ) .0, ease(EaseInOutCubic, 1.0, 0.0, duration), ) } - (None, None) => (full_geo, 1.0), + (None, None) => (fullscreen_geo, 1.0), }; let render_loc = target_geo @@ -1368,31 +1492,33 @@ impl Workspace { y: target_geo.size.h as f64 / bbox.size.h as f64, }; - elements.extend( - fullscreen - .surface - .render_elements::>( - renderer, - render_loc, - output_scale.into(), - alpha, - ) - .into_iter() - .map(|elem| RescaleRenderElement::from_element(elem, render_loc, scale)) - .map(Into::into), - ); + fullscreen + .surface + .render_elements::>( + renderer, + render_loc, + output_scale.into(), + alpha, + ) + .into_iter() + .map(|elem| RescaleRenderElement::from_element(elem, render_loc, scale)) + .map(Into::into) + .collect::>() + } else { + Vec::new() + }; + + if matches!(focused, Some(FocusTarget::Fullscreen(_))) { + elements.extend(fullscreen_elements.drain(..)); } - if self - .fullscreen - .as_ref() - .map(|f| f.start_at.is_some() || f.ended_at.is_some()) - .unwrap_or(true) + if !matches!(focused, Some(FocusTarget::Fullscreen(_))) + || self + .fullscreen + .as_ref() + .map(|f| f.start_at.is_some() || f.ended_at.is_some()) + .unwrap_or(true) { - let focused = draw_focus_indicator - .filter(|_| !self.fullscreen.is_some()) - .and_then(|seat| self.focus_stack.get(seat).last().cloned()); - // floating surfaces let alpha = match &overview.0 { OverviewMode::Started(_, started) => { @@ -1416,7 +1542,13 @@ impl Workspace { self.floating_layer .render::( renderer, - focused.as_ref(), + focused.as_ref().and_then(|target| { + if let FocusTarget::Window(mapped) = target { + Some(mapped) + } else { + None + } + }), resize_indicator.clone(), indicator_thickness, alpha, @@ -1444,7 +1576,7 @@ impl Workspace { self.tiling_layer .render::( renderer, - draw_focus_indicator, + render_focus.then_some(last_active_seat), zone, overview, resize_indicator, @@ -1470,6 +1602,10 @@ impl Workspace { } } + if !matches!(focused, Some(FocusTarget::Fullscreen(_))) { + elements.extend(fullscreen_elements.into_iter()); + } + Ok(elements) } @@ -1477,7 +1613,8 @@ impl Workspace { pub fn render_popups<'a, R>( &self, renderer: &mut R, - draw_focus_indicator: Option<&Seat>, + last_active_seat: &Seat, + render_focus: bool, overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), theme: &CosmicTheme, ) -> Result>, OutputNotMapped> @@ -1498,37 +1635,11 @@ impl Workspace { }; if let Some(fullscreen) = self.fullscreen.as_ref() { - // fullscreen window - let bbox = fullscreen.surface.bbox().as_local(); - let element_geo = Rectangle::new( - self.element_for_surface(&fullscreen.surface) - .and_then(|elem| { - self.floating_layer - .element_geometry(elem) - .or_else(|| self.tiling_layer.element_geometry(elem)) - .map(|mut geo| { - geo.loc -= elem.geometry().loc.as_local(); - geo - }) - }) - .unwrap_or(bbox) - .loc, - fullscreen.original_geometry.size.as_local(), - ); - - let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local()); - if fullscreen.start_at.is_none() { - if bbox != full_geo { - if bbox.size.w < full_geo.size.w { - full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2; - 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; - } - } - } + let fullscreen_geo = self.fullscreen_geometry().unwrap(); + let previous_geo = fullscreen + .previous_geometry + .as_ref() + .unwrap_or(&fullscreen_geo); let (target_geo, alpha) = match (fullscreen.start_at, fullscreen.ended_at) { (Some(started), _) => { @@ -1537,8 +1648,8 @@ impl Workspace { ( ease( EaseInOutCubic, - EaseRectangle(element_geo), - EaseRectangle(full_geo), + EaseRectangle(*previous_geo), + EaseRectangle(fullscreen_geo), duration, ) .0, @@ -1551,15 +1662,15 @@ impl Workspace { ( ease( EaseInOutCubic, - EaseRectangle(full_geo), - EaseRectangle(element_geo), + EaseRectangle(fullscreen_geo), + EaseRectangle(*previous_geo), duration, ) .0, ease(EaseInOutCubic, 1.0, 0.0, duration), ) } - (None, None) => (full_geo, 1.0), + (None, None) => (fullscreen_geo, 1.0), }; let render_loc = target_geo @@ -1581,11 +1692,13 @@ impl Workspace { ); } - if self - .fullscreen - .as_ref() - .map(|f| f.start_at.is_some() || f.ended_at.is_some()) - .unwrap_or(true) + let focus_stack = self.focus_stack.get(last_active_seat); + if !matches!(focus_stack.last(), Some(FocusTarget::Fullscreen(_))) + || self + .fullscreen + .as_ref() + .map(|f| f.start_at.is_some() || f.ended_at.is_some()) + .unwrap_or(true) { // floating surfaces let alpha = match &overview.0 { @@ -1616,7 +1729,13 @@ impl Workspace { //tiling surfaces elements.extend( self.tiling_layer - .render_popups::(renderer, draw_focus_indicator, zone, overview, theme)? + .render_popups::( + renderer, + render_focus.then_some(last_active_seat), + zone, + overview, + theme, + )? .into_iter() .map(WorkspaceRenderElement::from), ); diff --git a/src/state.rs b/src/state.rs index 547034de..9451a17f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -761,13 +761,16 @@ impl Common { // normal windows for space in shell.workspaces.spaces() { + if let Some(window) = space.get_fullscreen() { + window.with_surfaces(processor); + } space.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { window.with_surfaces(processor); } }); space.minimized_windows.iter().for_each(|m| { - for (window, _) in m.window.windows() { + for window in m.windows() { window.with_surfaces(processor); } }) @@ -919,6 +922,22 @@ impl Common { }); if let Some(active) = shell.active_space(output) { + if let Some(window) = active.get_fullscreen() { + if let Some(feedback) = window + .wl_surface() + .and_then(|wl_surface| { + advertised_node_for_surface(&wl_surface, &self.display_handle) + }) + .and_then(|source| dmabuf_feedback(source)) + { + window.send_dmabuf_feedback( + output, + &feedback, + render_element_states, + surface_primary_scanout_output, + ); + } + } active.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { if let Some(feedback) = window @@ -1098,6 +1117,9 @@ impl Common { }); if let Some(active) = shell.active_space(output) { + if let Some(window) = active.get_fullscreen() { + window.send_frame(output, time, throttle(window), should_send); + } active.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { window.send_frame(output, time, throttle(&window), should_send); @@ -1106,7 +1128,7 @@ impl Common { // other (throttled) windows active.minimized_windows.iter().for_each(|m| { - for (window, _) in m.window.windows() { + for window in m.windows() { window.send_frame(output, time, throttle(&window), |_, _| None); } }); @@ -1116,6 +1138,10 @@ impl Common { .spaces_for_output(output) .filter(|w| w.handle != active.handle) { + if let Some(window) = space.get_fullscreen() { + let throttle = min(throttle(space), throttle(window)); + window.send_frame(output, time, throttle, |_, _| None); + } space.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { let throttle = min(throttle(space), throttle(&window)); @@ -1123,7 +1149,7 @@ impl Common { } }); space.minimized_windows.iter().for_each(|m| { - for (window, _) in m.window.windows() { + for window in m.windows() { window.send_frame(output, time, throttle(&window), |_, _| None); } }) diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs index b411913b..b329a5c6 100644 --- a/src/wayland/handlers/toplevel_management.rs +++ b/src/wayland/handlers/toplevel_management.rs @@ -6,10 +6,11 @@ use smithay::{ output::Output, reexports::wayland_server::DisplayHandle, utils::{Point, Rectangle, Size, SERIAL_COUNTER}, + wayland::seat::WaylandFocus, }; use crate::{ - shell::{element::CosmicWindow, CosmicSurface, Shell, WorkspaceDelta}, + shell::{focus::target::KeyboardFocusTarget, CosmicSurface, Shell, WorkspaceDelta}, utils::prelude::*, wayland::protocols::{ toplevel_info::ToplevelInfoHandler, @@ -41,17 +42,13 @@ impl ToplevelManagementHandler for State { .spaces_for_output(output) .enumerate() .find(|(_, w)| { - w.mapped() - .flat_map(|m| m.windows().map(|(s, _)| s)) - .any(|w| &w == window) + w.get_fullscreen().is_some_and(|f| f == window) + || w.mapped() + .flat_map(|m| m.windows().map(|(s, _)| s)) + .any(|w| &w == window) }); if let Some((idx, workspace)) = maybe { let seat = seat.unwrap_or(shell.seats.last_active().clone()); - let mapped = workspace - .mapped() - .find(|m| m.windows().any(|(w, _)| &w == window)) - .unwrap() - .clone(); let handle = workspace.handle; let res = shell.activate( @@ -62,8 +59,12 @@ impl ToplevelManagementHandler for State { ); let workspace = shell.workspaces.space_for_handle_mut(&handle).unwrap(); - if seat.get_keyboard().unwrap().current_focus() != Some(mapped.clone().into()) - && workspace.is_tiled(&mapped) + if seat + .get_keyboard() + .unwrap() + .current_focus() + .is_some_and(|focus| !focus.windows().any(|w| w == *window)) + && workspace.is_tiled(window) { for mapped in workspace .mapped() @@ -75,6 +76,13 @@ impl ToplevelManagementHandler for State { workspace.unmaximize_request(&mapped); } } + + let target = if let Some(mapped) = workspace.element_for_surface(window) { + mapped.focus_window(window); + KeyboardFocusTarget::Element(mapped.clone()) + } else { + KeyboardFocusTarget::Fullscreen(window.clone()) + }; std::mem::drop(shell); if seat.active_output() != *output { @@ -102,8 +110,7 @@ impl ToplevelManagementHandler for State { } } - mapped.focus_window(window); - Shell::set_focus(self, Some(&mapped.clone().into()), &seat, None, false); + Shell::set_focus(self, Some(&target), &seat, None, false); return; } } @@ -121,44 +128,27 @@ impl ToplevelManagementHandler for State { _output: Output, ) { let mut shell = self.common.shell.write(); - if let Some(mut mapped) = shell.element_for_surface(window).cloned() { - if let Some(from_workspace) = shell.space_for_mut(&mapped) { - // If window is part of a stack, remove it and map it outside the stack - if let Some(stack) = mapped.stack_ref() { - stack.remove_window(&window); - mapped = CosmicWindow::new( - window.clone(), - self.common.event_loop_handle.clone(), - self.common.theme.clone(), - ) - .into(); - if from_workspace.tiling_enabled { - from_workspace.tiling_layer.map( - mapped.clone(), - None::>, - None, - ); - } else { - from_workspace.floating_layer.map(mapped.clone(), None); - } - } + let seat = shell.seats.last_active().clone(); + let Some(surface) = window.wl_surface() else { + return; + }; + let Some((from_workspace, _)) = shell.workspace_for_surface(&*surface) else { + return; + }; - let from_handle = from_workspace.handle; - let seat = shell.seats.last_active().clone(); - let res = shell.move_window( - Some(&seat), - &mapped, - &from_handle, - &to_handle, - false, - None, - &mut self.common.workspace_state.update(), - ); - if let Some((target, _)) = res { - std::mem::drop(shell); - Shell::set_focus(self, Some(&target), &seat, None, true); - } - } + let res = shell.move_window( + Some(&seat), + window, + &from_workspace, + &to_handle, + false, + None, + &mut self.common.workspace_state.update(), + &self.common.event_loop_handle, + ); + if let Some((target, _)) = res { + std::mem::drop(shell); + Shell::set_focus(self, Some(&target), &seat, None, true); } } @@ -170,23 +160,18 @@ impl ToplevelManagementHandler for State { ) { let mut shell = self.common.shell.write(); let seat = shell.seats.last_active().clone(); - if let Some(mapped) = shell.element_for_surface(window).cloned() { - if let Some((output, workspace)) = - output.and_then(|output| shell.workspaces.active_mut(&output).map(|w| (output, w))) - { - let from = minimize_rectangle(&output, window); - workspace.fullscreen_request(window, None, from, &seat); - } else if let Some((output, handle)) = shell - .space_for(&mapped) - .map(|workspace| (workspace.output.clone(), workspace.handle.clone())) - { - let from = minimize_rectangle(&output, window); - shell - .workspaces - .space_for_handle_mut(&handle) - .unwrap() - .fullscreen_request(window, None, from, &seat); - } + let output = output + .or_else(|| { + window + .wl_surface() + .and_then(|surface| shell.visible_output_for_surface(&*surface).cloned()) + }) + .unwrap_or_else(|| seat.focused_or_active_output()); + if let Some(target) = + shell.fullscreen_request(window, output, &self.common.event_loop_handle) + { + std::mem::drop(shell); + Shell::set_focus(self, Some(&target), &seat, None, true); } } @@ -196,33 +181,14 @@ impl ToplevelManagementHandler for State { window: &::Window, ) { let mut shell = self.common.shell.write(); - if let Some(mapped) = shell.element_for_surface(window).cloned() { - if let Some(workspace) = shell.space_for_mut(&mapped) { - if let Some((layer, previous_workspace)) = workspace.unfullscreen_request(window) { - let old_handle = workspace.handle.clone(); - let new_workspace_handle = shell - .workspaces - .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 - - shell.remap_unfullscreened_window( - mapped, - &old_handle, - &new_workspace_handle, - layer, - ); - } - } - } + shell.unfullscreen_request(window, &self.common.event_loop_handle); } fn maximize(&mut self, _dh: &DisplayHandle, window: &::Window) { let mut shell = self.common.shell.write(); if let Some(mapped) = shell.element_for_surface(window).cloned() { let seat = shell.seats.last_active().clone(); - shell.maximize_request(&mapped, &seat, true); + shell.maximize_request(&mapped, &seat, true, &self.common.event_loop_handle); } } @@ -235,22 +201,13 @@ impl ToplevelManagementHandler for State { fn minimize(&mut self, _dh: &DisplayHandle, window: &::Window) { let mut shell = self.common.shell.write(); - if let Some(mapped) = shell.element_for_surface(window).cloned() { - if !mapped.is_stack() || &mapped.active_window() == window { - shell.minimize_request(&mapped); - } - } + shell.minimize_request(window); } fn unminimize(&mut self, _dh: &DisplayHandle, window: &::Window) { let mut shell = self.common.shell.write(); - if let Some(mapped) = shell.element_for_surface(window).cloned() { - let seat = shell.seats.last_active().clone(); - shell.unminimize_request(&mapped, &seat); - if mapped.is_stack() { - mapped.stack_ref().unwrap().set_active(window); - } - } + let seat = shell.seats.last_active().clone(); + shell.unminimize_request(window, &seat, &self.common.event_loop_handle); } fn set_sticky(&mut self, _dh: &DisplayHandle, window: &::Window) { diff --git a/src/wayland/handlers/xdg_activation.rs b/src/wayland/handlers/xdg_activation.rs index 77d8dc42..54fa91fc 100644 --- a/src/wayland/handlers/xdg_activation.rs +++ b/src/wayland/handlers/xdg_activation.rs @@ -1,3 +1,4 @@ +use crate::shell::focus::target::KeyboardFocusTarget; use crate::{shell::ActivationKey, state::ClientState, utils::prelude::*}; use crate::{ state::State, @@ -93,8 +94,6 @@ impl XdgActivationHandler for State { .insert_if_missing(move || ActivationContext::Workspace(handle)); debug!(?token, "created workspace token"); - } else { - debug!(?token, "created urgent-only token for invalid serial"); } valid @@ -106,88 +105,118 @@ impl XdgActivationHandler for State { token_data: XdgActivationTokenData, surface: WlSurface, ) { - if let Some(context) = token_data.user_data.get::() { - let mut shell = self.common.shell.write(); - if let Some(element) = shell.element_for_surface(&surface).cloned() { - match context { - ActivationContext::UrgentOnly => { - if let Some((workspace, _output)) = shell.workspace_for_surface(&surface) { - let mut workspace_guard = self.common.workspace_state.update(); - workspace_guard.add_workspace_state(&workspace, WState::Urgent); - } - } - ActivationContext::Workspace(_) => { - let seat = shell.seats.last_active().clone(); - let current_output = seat.active_output(); + let Some(context) = token_data.user_data.get::() else { + return; + }; + let mut shell = self.common.shell.write(); - if element.is_minimized() { - shell.unminimize_request(&element, &seat); - } - - let element_workspace = shell.space_for(&element).map(|w| w.handle.clone()); - let current_workspace = shell.active_space_mut(¤t_output).unwrap(); - - let in_current_workspace = element_workspace - .as_ref() - .map(|w| *w == current_workspace.handle) - .unwrap_or(false); - - if in_current_workspace { - current_workspace - .floating_layer - .space - .raise_element(&element, true); - } - - if element.is_stack() { - if let Some((window, _)) = element.windows().find(|(window, _)| { - let mut found = false; - window.with_surfaces(|wl_surface, _| { - if wl_surface == &surface { - found = true; - } - }); - found - }) { - element.set_active(&window); - } - } - - if in_current_workspace { - if seat.get_keyboard().unwrap().current_focus() - != Some(element.clone().into()) - && current_workspace.is_tiled(&element) - { - for mapped in current_workspace - .mapped() - .filter(|m| m.maximized_state.lock().unwrap().is_some()) - .cloned() - .collect::>() - .into_iter() - { - current_workspace.unmaximize_request(&mapped); - } - } - - std::mem::drop(shell); - Shell::set_focus( - self, - Some(&element.clone().into()), - &seat, - None, - false, - ); - } else if let Some(w) = element_workspace { - shell.append_focus_stack(&element, &seat); - let mut workspace_guard = self.common.workspace_state.update(); - workspace_guard.add_workspace_state(&w, WState::Urgent); - } - } + match context { + ActivationContext::UrgentOnly => { + if let Some((workspace, _output)) = shell.workspace_for_surface(&surface) { + let mut workspace_guard = self.common.workspace_state.update(); + workspace_guard.add_workspace_state(&workspace, WState::Urgent); } - } else { - shell - .pending_activations - .insert(ActivationKey::Wayland(surface), context.clone()); + } + ActivationContext::Workspace(_) => { + let seat = shell.seats.last_active().clone(); + let current_output = seat.active_output(); + + if let Some(element) = shell.element_for_surface(&surface).cloned() { + if element.is_minimized() { + shell.unminimize_request(&surface, &seat, &self.common.event_loop_handle); + } + + let element_workspace = shell.space_for(&element).map(|w| w.handle.clone()); + let current_workspace = shell.active_space_mut(¤t_output).unwrap(); + + let in_current_workspace = element_workspace + .as_ref() + .map(|w| *w == current_workspace.handle) + .unwrap_or(false); + + if in_current_workspace { + current_workspace + .floating_layer + .space + .raise_element(&element, true); + } + + if element.is_stack() { + if let Some((window, _)) = element.windows().find(|(window, _)| { + let mut found = false; + window.with_surfaces(|wl_surface, _| { + if wl_surface == &surface { + found = true; + } + }); + found + }) { + element.set_active(&window); + } + } + + if in_current_workspace { + if seat.get_keyboard().unwrap().current_focus() + != Some(element.clone().into()) + && current_workspace.is_tiled(&surface) + { + for mapped in current_workspace + .mapped() + .filter(|m| m.maximized_state.lock().unwrap().is_some()) + .cloned() + .collect::>() + .into_iter() + { + current_workspace.unmaximize_request(&mapped); + } + } + + std::mem::drop(shell); + Shell::set_focus( + self, + Some(&KeyboardFocusTarget::Element(element.clone())), + &seat, + None, + false, + ); + } else if let Some(w) = element_workspace { + shell.append_focus_stack(element, &seat); + let mut workspace_guard = self.common.workspace_state.update(); + workspace_guard.add_workspace_state(&w, WState::Urgent); + } + } else if let Some((workspace, _)) = shell.workspace_for_surface(&surface) { + let current_workspace = shell.active_space(¤t_output).unwrap(); + if workspace == current_workspace.handle { + let Some(target) = shell + .workspaces + .space_for_handle(&workspace) + .unwrap() + .get_fullscreen() + .cloned() + .map(KeyboardFocusTarget::Fullscreen) + else { + return; + }; + + std::mem::drop(shell); + Shell::set_focus(self, Some(&target), &seat, None, false); + } else { + if let Some(surface) = shell + .workspaces + .space_for_handle(&workspace) + .and_then(|w| w.get_fullscreen()) + .cloned() + { + shell.append_focus_stack(surface, &seat) + } + let mut workspace_guard = self.common.workspace_state.update(); + workspace_guard.add_workspace_state(&workspace, WState::Urgent); + } + } else { + shell + .pending_activations + .insert(ActivationKey::Wayland(surface), context.clone()); + }; } } } diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 1df2adaa..5f640c39 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -1,12 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - shell::{ - element::CosmicWindow, grabs::ReleaseMode, CosmicMapped, CosmicSurface, ManagedLayer, - PendingWindow, - }, + shell::{grabs::ReleaseMode, CosmicSurface, PendingWindow}, utils::prelude::*, - wayland::protocols::toplevel_info::{toplevel_enter_output, toplevel_enter_workspace}, }; use smithay::{ delegate_xdg_shell, @@ -33,7 +29,7 @@ use smithay::{ use std::cell::Cell; use tracing::warn; -use super::{compositor::client_compositor_state, toplevel_management::minimize_rectangle}; +use super::compositor::client_compositor_state; pub mod popup; @@ -204,20 +200,14 @@ impl XdgShellHandler for State { fn minimize_request(&mut self, surface: ToplevelSurface) { let mut shell = self.common.shell.write(); - if let Some(mapped) = shell.element_for_surface(surface.wl_surface()).cloned() { - if !mapped.is_stack() - || mapped.active_window().wl_surface().as_deref() == Some(surface.wl_surface()) - { - shell.minimize_request(&mapped) - } - } + shell.minimize_request(surface.wl_surface()) } fn maximize_request(&mut self, surface: ToplevelSurface) { let mut shell = self.common.shell.write(); if let Some(mapped) = shell.element_for_surface(surface.wl_surface()).cloned() { let seat = shell.seats.last_active().clone(); - shell.maximize_request(&mapped, &seat, true) + shell.maximize_request(&mapped, &seat, true, &self.common.event_loop_handle) } else if let Some(pending) = shell .pending_windows .iter_mut() @@ -243,153 +233,40 @@ impl XdgShellHandler for State { fn fullscreen_request(&mut self, surface: ToplevelSurface, output: Option) { let mut shell = self.common.shell.write(); let seat = shell.seats.last_active().clone(); - let Some(focused_output) = seat.focused_output() else { - return; - }; let output = output .as_ref() .and_then(Output::from_resource) - .unwrap_or_else(|| focused_output.clone()); + .or_else(|| { + shell + .visible_output_for_surface(surface.wl_surface()) + .cloned() + }) + .unwrap_or_else(|| seat.focused_or_active_output()); - if let Some(mapped) = shell.element_for_surface(surface.wl_surface()).cloned() { - let from = minimize_rectangle(&output, &mapped.active_window()); - - if let Some(set) = shell - .workspaces - .sets - .values_mut() - .find(|set| set.sticky_layer.mapped().any(|m| m == &mapped)) - { - let mapped = 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_deref() == Some(surface.wl_surface())) - .unwrap(); - stack.remove_window(&surface); - CosmicMapped::from(CosmicWindow::new( - surface, - self.common.event_loop_handle.clone(), - self.common.theme.clone(), - )) - } else { - set.sticky_layer.unmap(&mapped); - mapped - }; - - let workspace_handle = shell.active_space(&output).unwrap().handle.clone(); - for (window, _) in mapped.windows() { - toplevel_enter_output(&window, &output); - toplevel_enter_workspace(&window, &workspace_handle); - } - - let workspace = shell.active_space_mut(&output).unwrap(); - workspace.floating_layer.map(mapped.clone(), None); - - workspace.fullscreen_request( - &mapped.active_window(), - Some((ManagedLayer::Sticky, workspace_handle)), - from, - &seat, - ); - } else if let Some(workspace) = shell.space_for_mut(&mapped) { - 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_deref() == Some(surface.wl_surface())) - .unwrap(); - stack.remove_window(&surface); - ( - CosmicMapped::from(CosmicWindow::new( - surface, - self.common.event_loop_handle.clone(), - self.common.theme.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(); - - let workspace_handle = shell.active_space(&output).unwrap().handle.clone(); - for (window, _) in mapped.windows() { - toplevel_enter_output(&window, &output); - toplevel_enter_workspace(&window, &workspace_handle); - } - - let workspace = shell.active_space_mut(&output).unwrap(); - workspace.floating_layer.map(mapped.clone(), None); - - workspace.fullscreen_request( - &mapped.active_window(), - Some((layer, handle)), - from, - &seat, - ); - } else { - let (window, _) = mapped - .windows() - .find(|(w, _)| w.wl_surface().as_deref() == Some(surface.wl_surface())) - .unwrap(); - workspace.fullscreen_request(&window, None, from, &seat) + match shell.fullscreen_request(&surface, output.clone(), &self.common.event_loop_handle) { + Some(target) => { + std::mem::drop(shell); + Shell::set_focus(self, Some(&target), &seat, None, true); + } + None => { + if let Some(pending) = shell.pending_windows.iter_mut().find(|pending| { + pending.surface.wl_surface().as_deref() == Some(surface.wl_surface()) + }) { + pending.fullscreen = Some(output); } } - } else if let Some(pending) = shell - .pending_windows - .iter_mut() - .find(|pending| pending.surface.wl_surface().as_deref() == Some(surface.wl_surface())) - { - pending.fullscreen = Some(output); } } fn unfullscreen_request(&mut self, surface: ToplevelSurface) { let mut shell = self.common.shell.write(); - if let Some(mapped) = shell.element_for_surface(surface.wl_surface()).cloned() { - if let Some(workspace) = shell.space_for_mut(&mapped) { - let (window, _) = mapped - .windows() - .find(|(w, _)| w.wl_surface().as_deref() == Some(surface.wl_surface())) - .unwrap(); - if let Some((layer, previous_workspace)) = workspace.unfullscreen_request(&window) { - let old_handle = workspace.handle.clone(); - let new_workspace_handle = shell - .workspaces - .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 - shell.remap_unfullscreened_window( - mapped, - &old_handle, - &new_workspace_handle, - layer, - ); - } + if !shell.unfullscreen_request(&surface, &self.common.event_loop_handle) { + if let Some(pending) = shell.pending_windows.iter_mut().find(|pending| { + pending.surface.wl_surface().as_deref() == Some(surface.wl_surface()) + }) { + pending.fullscreen.take(); } - } else if let Some(pending) = shell - .pending_windows - .iter_mut() - .find(|pending| pending.surface.wl_surface().as_deref() == Some(surface.wl_surface())) - { - pending.fullscreen.take(); } } diff --git a/src/wayland/handlers/xdg_shell/popup.rs b/src/wayland/handlers/xdg_shell/popup.rs index 3e3326b7..5f9017d9 100644 --- a/src/wayland/handlers/xdg_shell/popup.rs +++ b/src/wayland/handlers/xdg_shell/popup.rs @@ -32,7 +32,7 @@ impl Shell { ( elem_geo.to_global(workspace.output()), workspace.output.clone(), - workspace.is_tiled(elem), + workspace.is_tiled(&elem.active_window()), ) } else if let Some((output, set)) = self .workspaces diff --git a/src/xwayland.rs b/src/xwayland.rs index 251020b5..337342ba 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -7,9 +7,7 @@ use crate::{ }, state::State, utils::prelude::*, - wayland::handlers::{ - toplevel_management::minimize_rectangle, xdg_activation::ActivationContext, - }, + wayland::handlers::xdg_activation::ActivationContext, }; use cosmic_comp_config::{EavesdroppingKeyboardMode, XwaylandDescaling}; use smithay::{ @@ -48,7 +46,7 @@ use smithay::{ xdg_activation::XdgActivationToken, }, xwayland::{ - xwm::{Reorder, X11Relatable, XwmId}, + xwm::{Reorder, XwmId}, X11Surface, X11Wm, XWayland, XWaylandClientData, XWaylandEvent, XwmHandler, }, }; @@ -485,7 +483,9 @@ impl Common { #[profiling::function] pub fn update_x11_stacking_order(&mut self) { let shell = self.shell.read(); - let active_output = shell.seats.last_active().active_output(); + let seat = shell.seats.last_active(); + let active_output = seat.active_output(); + if let Some(xwm) = self .xwayland_state .as_mut() @@ -534,31 +534,51 @@ impl Common { .filter(|(i, _)| *i != set.active), ) .flat_map(|(_, workspace)| { - workspace.get_fullscreen().cloned().into_iter().chain( - workspace - .mapped() - .chain( - workspace - .minimized_windows - .iter() - .map(|m| &m.window), + workspace + .get_fullscreen() + .filter(|f| { + workspace + .focus_stack + .get(seat) + .last() + .is_some_and(|t| &t == f) + }) + .cloned() + .into_iter() + .chain(workspace.mapped().flat_map(|mapped| { + let active = mapped.active_window(); + std::iter::once(active.clone()).chain( + mapped + .is_stack() + .then(move || { + mapped + .windows() + .map(|(s, _)| s) + .filter(move |s| s != &active) + }) + .into_iter() + .flatten(), ) - .flat_map(|mapped| { - let active = mapped.active_window(); - std::iter::once(active.clone()).chain( - mapped - .is_stack() - .then(move || { - mapped - .windows() - .map(|(s, _)| s) - .filter(move |s| s != &active) - }) - .into_iter() - .flatten(), - ) - }), - ) + })) + .chain( + workspace + .get_fullscreen() + .filter(|f| { + workspace + .focus_stack + .get(seat) + .last() + .is_none_or(|t| &t != f) + }) + .cloned() + .into_iter(), + ) + .chain( + workspace + .minimized_windows + .iter() + .flat_map(|m| m.windows()), + ) }), ) }) @@ -714,6 +734,7 @@ impl XwmHandler for State { let mut shell = self.common.shell.write(); let startup_id = window.startup_id(); + // TODO: Not correct for fullscreen (and minimized?) if shell.element_for_surface(&window).is_some() { return; } @@ -831,6 +852,7 @@ impl XwmHandler for State { // We only allow floating X11 windows to resize themselves. Nothing else let shell = self.common.shell.read(); + // TODO: Fullscreen if let Some(mapped) = shell .element_for_surface(&window) .filter(|mapped| !mapped.is_minimized()) @@ -838,7 +860,7 @@ impl XwmHandler for State { let current_geo = if let Some(workspace) = shell.space_for(mapped) { workspace .element_geometry(mapped) - .filter(|_| workspace.is_floating(mapped)) + .filter(|_| workspace.is_floating(&window)) .map(|geo| geo.to_global(workspace.output())) } else if let Some((output, set)) = shell .workspaces @@ -996,7 +1018,7 @@ impl XwmHandler for State { let mut shell = self.common.shell.write(); if let Some(mapped) = shell.element_for_surface(&window).cloned() { let seat = shell.seats.last_active().clone(); - shell.maximize_request(&mapped, &seat, true); + shell.maximize_request(&mapped, &seat, true, &self.common.event_loop_handle); } else if let Some(pending) = shell .pending_windows .iter_mut() @@ -1021,74 +1043,50 @@ impl XwmHandler for State { fn minimize_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); - if let Some(mapped) = shell.element_for_surface(&window).cloned() { - if !mapped.is_stack() || mapped.active_window().is_window(&window) { - shell.minimize_request(&mapped); - } - } + shell.minimize_request(&window); } fn unminimize_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); - if let Some(mapped) = shell.element_for_surface(&window).cloned() { - let seat = shell.seats.last_active().clone(); - shell.unminimize_request(&mapped, &seat); - if mapped.is_stack() { - let maybe_surface = mapped.windows().find(|(w, _)| w.is_window(&window)); - if let Some((surface, _)) = maybe_surface { - mapped.stack_ref().unwrap().set_active(&surface); - } - } - } + let seat = shell.seats.last_active().clone(); + shell.unminimize_request(&window, &seat, &self.common.event_loop_handle); } fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); let seat = shell.seats.last_active().clone(); - if let Some(mapped) = shell.element_for_surface(&window).cloned() { - if let Some((output, handle)) = shell - .space_for(&mapped) - .map(|workspace| (workspace.output.clone(), workspace.handle.clone())) - { - if let Some((surface, _)) = mapped - .windows() - .find(|(w, _)| w.x11_surface() == Some(&window)) + let output = window + .wl_surface() + .and_then(|surface| shell.visible_output_for_surface(&surface).cloned()) + .unwrap_or_else(|| seat.focused_or_active_output()); + + match shell.fullscreen_request(&window, output.clone(), &self.common.event_loop_handle) { + Some(target) => { + std::mem::drop(shell); + Shell::set_focus(self, Some(&target), &seat, None, true); + } + None => { + if let Some(pending) = shell + .pending_windows + .iter_mut() + .find(|pending| pending.surface.x11_surface() == Some(&window)) { - let from = minimize_rectangle(&output, &surface); - shell - .workspaces - .space_for_handle_mut(&handle) - .unwrap() - .fullscreen_request(&surface, None, from, &seat); + pending.fullscreen = Some(output); } } - } else if let Some(pending) = shell - .pending_windows - .iter_mut() - .find(|pending| pending.surface.x11_surface() == Some(&window)) - { - let output = seat.active_output(); - pending.fullscreen = Some(output); } } fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) { let mut shell = self.common.shell.write(); - if let Some(mapped) = shell.element_for_surface(&window).cloned() { - if let Some(workspace) = shell.space_for_mut(&mapped) { - let (window, _) = mapped - .windows() - .find(|(w, _)| w.x11_surface() == Some(&window)) - .unwrap(); - let previous = workspace.unfullscreen_request(&window); - assert!(previous.is_none()); + if !shell.unfullscreen_request(&window, &self.common.event_loop_handle) { + if let Some(pending) = shell + .pending_windows + .iter_mut() + .find(|pending| pending.surface.x11_surface() == Some(&window)) + { + pending.fullscreen.take(); } - } else if let Some(pending) = shell - .pending_windows - .iter_mut() - .find(|pending| pending.surface.x11_surface() == Some(&window)) - { - pending.fullscreen.take(); } }