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(); } }