shell: handle fullscreen windows on a dedicated layer

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

Here is a rough list of changes:

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

View file

@ -9,6 +9,7 @@ stack-windows = Stack Windows
unknown-keybinding = <unset> unknown-keybinding = <unset>
window-menu-minimize = Minimize window-menu-minimize = Minimize
window-menu-maximize = Maximize window-menu-maximize = Maximize
window-menu-fullscreen = Fullscreen
window-menu-tiled = Float window window-menu-tiled = Float window
window-menu-screenshot = Take screenshot window-menu-screenshot = Take screenshot
window-menu-move = Move window-menu-move = Move

View file

@ -16,7 +16,7 @@ use crate::{
config::ScreenFilter, config::ScreenFilter,
shell::{ shell::{
element::CosmicMappedKey, element::CosmicMappedKey,
focus::{render_input_order, target::WindowGroup, Stage}, focus::{render_input_order, target::WindowGroup, FocusTarget, Stage},
grabs::{SeatMenuGrabState, SeatMoveGrabState}, grabs::{SeatMenuGrabState, SeatMoveGrabState},
layout::tiling::ANIMATION_DURATION, layout::tiling::ANIMATION_DURATION,
zoom::ZoomState, zoom::ZoomState,
@ -897,7 +897,12 @@ where
layout layout
.render( .render(
renderer, 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(), resize_indicator.clone(),
active_hint, active_hint,
alpha, alpha,
@ -913,7 +918,8 @@ where
elements.extend( elements.extend(
match workspace.render_popups( match workspace.render_popups(
renderer, renderer,
(!move_active && is_active_space).then_some(last_active_seat), last_active_seat,
!move_active && is_active_space,
overview.clone(), overview.clone(),
&theme.cosmic(), &theme.cosmic(),
) { ) {
@ -941,7 +947,8 @@ where
elements.extend( elements.extend(
match workspace.render( match workspace.render(
renderer, renderer,
(!move_active && is_active_space).then_some(last_active_seat), last_active_seat,
!move_active && is_active_space,
overview.clone(), overview.clone(),
resize_indicator.clone(), resize_indicator.clone(),
active_hint, active_hint,

View file

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

View file

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

View file

@ -20,7 +20,7 @@ use smithay::{
ImportAll, ImportMem, Renderer, ImportAll, ImportMem, Renderer,
}, },
}, },
desktop::{space::SpaceElement, PopupManager, WindowSurfaceType}, desktop::{space::SpaceElement, WindowSurfaceType},
input::{ input::{
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
Seat, Seat,
@ -31,10 +31,7 @@ use smithay::{
utils::{ utils::{
Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size,
}, },
wayland::{ wayland::seat::WaylandFocus,
compositor::{with_surface_tree_downward, TraversalAction},
seat::WaylandFocus,
},
xwayland::{xwm::X11Relatable, X11Surface}, xwayland::{xwm::X11Relatable, X11Surface},
}; };
use stack::CosmicStackInternal; use stack::CosmicStackInternal;
@ -245,7 +242,10 @@ impl CosmicMapped {
.cloned() .cloned()
} }
pub fn set_active(&self, window: &CosmicSurface) { pub fn set_active<S>(&self, window: &S)
where
CosmicSurface: PartialEq<S>,
{
if let CosmicMappedInternal::Stack(stack) = &self.element { if let CosmicMappedInternal::Stack(stack) = &self.element {
stack.set_active(window); stack.set_active(window);
} }
@ -259,41 +259,8 @@ impl CosmicMapped {
} }
pub fn has_surface(&self, surface: &WlSurface, surface_type: WindowSurfaceType) -> bool { pub fn has_surface(&self, surface: &WlSurface, surface_type: WindowSurfaceType) -> bool {
self.windows().any(|(w, _)| { self.windows()
let Some(toplevel) = w.wl_surface() else { .any(|(w, _)| w.has_surface(surface, surface_type))
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
}
})
} }
/// Give the pointer target under a relative offset into this element. /// Give the pointer target under a relative offset into this element.

View file

@ -455,7 +455,10 @@ impl CosmicStack {
.with_program(|p| p.group_focused.load(Ordering::SeqCst)) .with_program(|p| p.group_focused.load(Ordering::SeqCst))
} }
pub fn set_active(&self, window: &CosmicSurface) { pub fn set_active<S>(&self, window: &S)
where
CosmicSurface: PartialEq<S>,
{
self.0.with_program(|p| { self.0.with_program(|p| {
if let Some(val) = p.windows.lock().unwrap().iter().position(|w| w == window) { if let Some(val) = p.windows.lock().unwrap().iter().position(|w| w == window) {
let old = p.active.swap(val, Ordering::SeqCst); let old = p.active.swap(val, Ordering::SeqCst);

View file

@ -18,7 +18,8 @@ use smithay::{
ImportAll, Renderer, ImportAll, Renderer,
}, },
desktop::{ desktop::{
space::SpaceElement, utils::OutputPresentationFeedback, PopupManager, Window, WindowSurface, space::SpaceElement, utils::OutputPresentationFeedback, PopupManager, Window,
WindowSurface, WindowSurfaceType,
}, },
input::{ input::{
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
@ -40,7 +41,7 @@ use smithay::{
user_data::UserDataMap, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, user_data::UserDataMap, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size,
}, },
wayland::{ wayland::{
compositor::{with_states, SurfaceData}, compositor::{with_states, with_surface_tree_downward, SurfaceData, TraversalAction},
seat::WaylandFocus, seat::WaylandFocus,
shell::xdg::{SurfaceCachedState, ToplevelSurface, XdgToplevelSurfaceData}, shell::xdg::{SurfaceCachedState, ToplevelSurface, XdgToplevelSurfaceData},
}, },
@ -54,7 +55,7 @@ use crate::{
wayland::handlers::decoration::{KdeDecorationData, PreferredDecorationMode}, wayland::handlers::decoration::{KdeDecorationData, PreferredDecorationMode},
}; };
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash, Eq)]
pub struct CosmicSurface(pub Window); pub struct CosmicSurface(pub Window);
impl From<ToplevelSurface> for CosmicSurface { impl From<ToplevelSurface> for CosmicSurface {
@ -81,6 +82,13 @@ impl PartialEq<WlSurface> for CosmicSurface {
} }
} }
impl PartialEq<ToplevelSurface> for CosmicSurface {
fn eq(&self, other: &ToplevelSurface) -> bool {
self.wl_surface()
.map_or(false, |s| &*s == other.wl_surface())
}
}
impl PartialEq<X11Surface> for CosmicSurface { impl PartialEq<X11Surface> for CosmicSurface {
fn eq(&self, other: &X11Surface) -> bool { fn eq(&self, other: &X11Surface) -> bool {
self.x11_surface().map_or(false, |s| s == other) 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) { pub fn on_commit(&self) {
self.0.on_commit(); self.0.on_commit();
} }

View file

@ -468,9 +468,7 @@ impl Program for CosmicWindowInternal {
if let Some(surface) = self.window.wl_surface().map(Cow::into_owned) { if let Some(surface) = self.window.wl_surface().map(Cow::into_owned) {
loop_handle.insert_idle(move |state| { loop_handle.insert_idle(move |state| {
let mut shell = state.common.shell.write(); let mut shell = state.common.shell.write();
if let Some(mapped) = shell.element_for_surface(&surface).cloned() { shell.minimize_request(&surface)
shell.minimize_request(&mapped)
}
}); });
} }
} }
@ -480,7 +478,7 @@ impl Program for CosmicWindowInternal {
let mut shell = state.common.shell.write(); let mut shell = state.common.shell.write();
if let Some(mapped) = shell.element_for_surface(&surface).cloned() { if let Some(mapped) = shell.element_for_surface(&surface).cloned() {
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
shell.maximize_toggle(&mapped, &seat) shell.maximize_toggle(&mapped, &seat, &state.common.event_loop_handle)
} }
}); });
} }

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
shell::{element::CosmicMapped, Shell}, shell::{element::CosmicMapped, CosmicSurface, MinimizedWindow, Shell},
state::Common, state::Common,
utils::prelude::*, utils::prelude::*,
wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData}, wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
@ -9,7 +9,7 @@ use smithay::{
desktop::{layer_map_for_output, PopupUngrabStrategy}, desktop::{layer_map_for_output, PopupUngrabStrategy},
input::{pointer::MotionEvent, Seat}, input::{pointer::MotionEvent, Seat},
output::Output, output::Output,
reexports::wayland_server::Resource, reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource},
utils::{IsAlive, Point, Serial, SERIAL_COUNTER}, utils::{IsAlive, Point, Serial, SERIAL_COUNTER},
wayland::{ wayland::{
seat::WaylandFocus, seat::WaylandFocus,
@ -29,18 +29,80 @@ use super::{grabs::SeatMoveGrabState, layout::floating::FloatingLayout, SeatExt}
mod order; mod order;
pub mod target; pub mod target;
pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet<CosmicMapped>>); #[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet<CosmicMapped>); pub enum FocusTarget {
Window(CosmicMapped),
Fullscreen(CosmicSurface),
}
impl PartialEq<CosmicMapped> for FocusTarget {
fn eq(&self, other: &CosmicMapped) -> bool {
matches!(self, FocusTarget::Window(mapped) if mapped == other)
}
}
impl PartialEq<CosmicSurface> for FocusTarget {
fn eq(&self, other: &CosmicSurface) -> bool {
matches!(self, FocusTarget::Fullscreen(surface) if surface == other)
}
}
impl From<CosmicMapped> for FocusTarget {
fn from(value: CosmicMapped) -> Self {
Self::Window(value)
}
}
impl From<CosmicSurface> for FocusTarget {
fn from(value: CosmicSurface) -> Self {
Self::Fullscreen(value)
}
}
impl Into<KeyboardFocusTarget> 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<WlSurface> {
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<FocusTarget>>);
pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet<FocusTarget>);
impl<'a> FocusStack<'a> { impl<'a> FocusStack<'a> {
/// returns the last unminimized window in the focus stack that is still alive /// 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 self.0
.as_ref() .as_ref()
.and_then(|set| set.iter().rev().find(|w| w.alive() && !w.is_minimized())) .and_then(|set| set.iter().rev().find(|w| w.alive() && !w.is_minimized()))
} }
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> { pub fn iter(&self) -> impl Iterator<Item = &'_ FocusTarget> {
self.0 self.0
.iter() .iter()
.flat_map(|set| set.iter().rev().filter(|w| w.alive() && !w.is_minimized())) .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> { impl<'a> FocusStackMut<'a> {
pub fn append(&mut self, window: &CosmicMapped) { pub fn append(&mut self, target: impl Into<FocusTarget>) {
let target = target.into();
self.0.retain(|w| w.alive()); self.0.retain(|w| w.alive());
self.0.shift_remove(window); self.0.shift_remove(&target);
self.0.insert(window.clone()); self.0.insert(target);
} }
pub fn remove(&mut self, window: &CosmicMapped) { pub fn remove<T>(&mut self, target: &T)
self.0.retain(|w| w != window); where
FocusTarget: PartialEq<T>,
{
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()) self.0.iter().rev().find(|w| w.alive() && !w.is_minimized())
} }
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> { pub fn iter(&self) -> impl Iterator<Item = &'_ FocusTarget> {
self.0 self.0
.iter() .iter()
.rev() .rev()
@ -107,23 +173,16 @@ impl Shell {
serial: Option<Serial>, serial: Option<Serial>,
update_cursor: bool, update_cursor: bool,
) { ) {
let element = match target { let focus_target = match target {
Some(KeyboardFocusTarget::Element(mapped)) => Some(mapped.clone()), Some(KeyboardFocusTarget::Element(mapped)) => Some(FocusTarget::Window(mapped.clone())),
Some(KeyboardFocusTarget::Fullscreen(window)) => state Some(KeyboardFocusTarget::Fullscreen(surface)) => {
.common Some(FocusTarget::Fullscreen(surface.clone()))
.shell }
.read()
.element_for_surface(window)
.cloned(),
_ => None, _ => None,
}; };
if let Some(mapped) = element { if let Some(target) = focus_target {
if mapped.is_minimized() { state.common.shell.write().append_focus_stack(target, seat);
return;
}
state.common.shell.write().append_focus_stack(&mapped, seat);
} }
update_focus_state(seat, target, state, serial, update_cursor); update_focus_state(seat, target, state, serial, update_cursor);
@ -131,25 +190,30 @@ impl Shell {
state.common.shell.write().update_active(); state.common.shell.write().update_active();
} }
pub fn append_focus_stack(&mut self, mapped: &CosmicMapped, seat: &Seat<State>) { pub fn append_focus_stack(&mut self, target: impl Into<FocusTarget>, seat: &Seat<State>) {
if mapped.is_minimized() { let target = target.into();
if target.is_minimized() {
return; return;
} }
// update FocusStack and notify layouts about new focus (if any window) // 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() { let workspace = if workspace.is_none() {
//should this be the active output or the focused output? //should this be the active output or the focused output?
self.active_space_mut(&seat.focused_or_active_output()) self.active_space_mut(&seat.focused_or_active_output())
.unwrap() .unwrap()
} else { } else {
workspace.unwrap() self.workspaces
.space_for_handle_mut(&workspace.unwrap().0)
.unwrap()
}; };
let mut focus_stack = workspace.focus_stack.get_mut(seat); let mut focus_stack = workspace.focus_stack.get_mut(seat);
if Some(mapped) != focus_stack.last() { if Some(&target) != focus_stack.last() {
trace!(?mapped, "Focusing window."); trace!(?target, "Focusing window.");
focus_stack.append(&mapped); focus_stack.append(target);
// also remove popup grabs, if we are switching focus // also remove popup grabs, if we are switching focus
if let Some(mut popup_grab) = seat if let Some(mut popup_grab) = seat
.user_data() .user_data()
@ -171,7 +235,7 @@ impl Shell {
.map(|seat| { .map(|seat| {
if matches!( if matches!(
seat.get_keyboard().unwrap().current_focus(), seat.get_keyboard().unwrap().current_focus(),
Some(KeyboardFocusTarget::Group(_)) Some(KeyboardFocusTarget::Group(_)) | Some(KeyboardFocusTarget::LockSurface(_))
) { ) {
return None; return None;
} }
@ -179,7 +243,10 @@ impl Shell {
let output = seat.focused_or_active_output(); let output = seat.focused_or_active_output();
let space = self.active_space(&output).unwrap(); let space = self.active_space(&output).unwrap();
let stack = space.focus_stack.get(seat); 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() .flatten()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -193,12 +260,33 @@ impl Shell {
window.set_activated(focused_windows.contains(&window)); window.set_activated(focused_windows.contains(&window));
window.configure(); window.configure();
} }
for m in set.minimized_windows.iter() { for window in set
m.window.set_activated(false); .minimized_windows
m.window.configure(); .iter()
.flat_map(MinimizedWindow::mapped)
{
window.set_activated(false);
window.configure();
} }
let workspace = &mut set.workspaces[set.active]; 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() { for focused in focused_windows.iter() {
raise_with_children(&mut workspace.floating_layer, focused); raise_with_children(&mut workspace.floating_layer, focused);
} }
@ -207,8 +295,15 @@ impl Shell {
window.configure(); window.configure();
} }
for m in workspace.minimized_windows.iter() { for m in workspace.minimized_windows.iter() {
m.window.set_activated(false); if let Some(window) = m.mapped() {
m.window.configure(); 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() { 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 workspace = shell.active_space(&output).unwrap();
let focus_stack = workspace.focus_stack.get(&seat); let focus_stack = workspace.focus_stack.get(&seat);
let is_in_focus_stack = focus_stack.last().map(|m| m == &mapped).unwrap_or(false); 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 { 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) => { KeyboardFocusTarget::LayerSurface(layer) => {
layer_map_for_output(&output).layers().any(|l| l == &layer) layer_map_for_output(&output).layers().any(|l| l == &layer)
@ -535,13 +628,7 @@ fn focus_target_is_valid(
.has_node(&node), .has_node(&node),
KeyboardFocusTarget::Fullscreen(window) => { KeyboardFocusTarget::Fullscreen(window) => {
let workspace = shell.active_space(&output).unwrap(); let workspace = shell.active_space(&output).unwrap();
let focus_stack = workspace.focus_stack.get(&seat); workspace.get_fullscreen().is_some_and(|w| w == &window)
focus_stack
.last()
.map(|m| m.has_active_window(&window))
.unwrap_or(false)
&& workspace.get_fullscreen().is_some()
} }
KeyboardFocusTarget::Popup(_) => true, KeyboardFocusTarget::Popup(_) => true,
KeyboardFocusTarget::LockSurface(_) => false, KeyboardFocusTarget::LockSurface(_) => false,
@ -579,8 +666,6 @@ fn update_focus_target(
}) })
.cloned() .cloned()
.map(KeyboardFocusTarget::from) .map(KeyboardFocusTarget::from)
} else if let Some(surface) = shell.active_space(&output).unwrap().get_fullscreen() {
Some(KeyboardFocusTarget::Fullscreen(surface.clone()))
} else { } else {
shell shell
.active_space(&output) .active_space(&output)
@ -588,9 +673,23 @@ fn update_focus_target(
.focus_stack .focus_stack
.get(&seat) .get(&seat)
.last() .last()
.or_else(|| shell.active_space(&output).unwrap().mapped().next())
.cloned() .cloned()
.map(KeyboardFocusTarget::from) .map(Into::<KeyboardFocusTarget>::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)
})
})
} }
} }

View file

@ -13,10 +13,15 @@ use smithay::{
use crate::{ use crate::{
backend::render::ElementFilter, backend::render::ElementFilter,
shell::{ shell::{
focus::target::KeyboardFocusTarget,
layout::{floating::FloatingLayout, tiling::ANIMATION_DURATION}, 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, wayland::protocols::workspace::WorkspaceHandle,
}; };
@ -105,11 +110,32 @@ fn render_input_order_internal<R: 'static>(
return ControlFlow::Break(Err(OutputNoMode)); return ControlFlow::Break(Err(OutputNoMode));
}; };
let output_size = output.geometry().size; let output_size = output.geometry().size;
let has_fullscreen = workspace
.fullscreen // this is more hacky than I would like..
.as_ref() let fullscreen = workspace.fullscreen.as_ref().filter(|f| !f.is_animating());
.filter(|f| !f.is_animating()) let seat = shell.seats.last_active();
.is_some(); 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() { let (previous, current_offset) = match previous.as_ref() {
Some((previous, previous_idx, start)) => { Some((previous, previous_idx, start)) => {
@ -163,7 +189,7 @@ fn render_input_order_internal<R: 'static>(
}; };
// Top-level layer shell popups // Top-level layer shell popups
if !has_fullscreen { if !has_focused_fullscreen {
for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) { for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) {
callback(Stage::LayerPopup { callback(Stage::LayerPopup {
layer, layer,
@ -193,7 +219,7 @@ fn render_input_order_internal<R: 'static>(
} }
// sticky window popups // sticky window popups
if !has_fullscreen { if !has_focused_fullscreen {
callback(Stage::StickyPopups(&set.sticky_layer))?; callback(Stage::StickyPopups(&set.sticky_layer))?;
} }
} }
@ -222,7 +248,7 @@ fn render_input_order_internal<R: 'static>(
})?; })?;
} }
if !has_fullscreen { if !has_focused_fullscreen {
// bottom layer popups // bottom layer popups
for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) { for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) {
callback(Stage::LayerPopup { callback(Stage::LayerPopup {
@ -271,7 +297,7 @@ fn render_input_order_internal<R: 'static>(
} }
} }
if !has_fullscreen { if !has_focused_fullscreen {
// top-layer shell // top-layer shell
for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) { for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) {
callback(Stage::LayerSurface { layer, location })?; callback(Stage::LayerSurface { layer, location })?;
@ -302,7 +328,7 @@ fn render_input_order_internal<R: 'static>(
} }
} }
if !has_fullscreen { if !has_focused_fullscreen {
// bottom layer // bottom layer
for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) {
location += current_offset.as_global(); location += current_offset.as_global();

View file

@ -181,6 +181,15 @@ impl KeyboardFocusTarget {
} }
} }
pub fn windows(&self) -> impl Iterator<Item = CosmicSurface> + '_ {
match self {
KeyboardFocusTarget::Element(mapped) => Box::new(mapped.windows().map(|(s, _)| s))
as Box<dyn Iterator<Item = CosmicSurface>>,
KeyboardFocusTarget::Fullscreen(surface) => Box::new(std::iter::once(surface.clone())),
_ => Box::new(std::iter::empty()),
}
}
pub fn is_xwm(&self, xwm: XwmId) -> bool { pub fn is_xwm(&self, xwm: XwmId) -> bool {
match self { match self {
KeyboardFocusTarget::Element(mapped) => { KeyboardFocusTarget::Element(mapped) => {

View file

@ -1,5 +1,8 @@
use cosmic_settings_config::shortcuts::Action; 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::{ use crate::{
config::Config, config::Config,
@ -11,6 +14,7 @@ use crate::{
}, },
state::State, state::State,
utils::{prelude::SeatExt, screenshot::screenshot_window}, utils::{prelude::SeatExt, screenshot::screenshot_window},
wayland::protocols::workspace::WorkspaceHandle,
}; };
use super::{Item, ResizeEdge}; 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) { fn prev_workspace(
let mut shell = state.common.shell.write(); shell: &Shell,
let seat = shell.seats.last_active().clone(); surface: &WlSurface,
let (current_handle, output) = { ) -> Option<(WorkspaceHandle, WorkspaceHandle)> {
let Some(ws) = shell.space_for(mapped) else { let (current_handle, output) = shell.workspace_for_surface(surface)?;
return; shell
};
(ws.handle, ws.output.clone())
};
let maybe_handle = shell
.workspaces .workspaces
.spaces_for_output(&output) .spaces_for_output(&output)
.enumerate() .enumerate()
.find_map(|(i, space)| (space.handle == current_handle).then_some(i)) .find_map(|(i, space)| (space.handle == current_handle).then_some(i))
.and_then(|i| i.checked_sub(1)) .and_then(|i| i.checked_sub(1))
.and_then(|i| shell.workspaces.get(i, &output).map(|s| s.handle)); .and_then(|i| shell.workspaces.get(i, &output))
if let Some(prev_handle) = maybe_handle { .map(|space| (current_handle, space.handle))
let res = shell.move_window(
Some(&seat),
mapped,
&current_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);
}
}
} }
fn move_next_workspace(state: &mut State, mapped: &CosmicMapped) { fn next_workspace(
let mut shell = state.common.shell.write(); shell: &Shell,
let seat = shell.seats.last_active().clone(); surface: &WlSurface,
let (current_handle, output) = { ) -> Option<(WorkspaceHandle, WorkspaceHandle)> {
let Some(ws) = shell.space_for(mapped) else { let (current_handle, output) = shell.workspace_for_surface(surface)?;
return; shell
};
(ws.handle, ws.output.clone())
};
let maybe_handle = shell
.workspaces .workspaces
.spaces_for_output(&output) .spaces_for_output(&output)
.skip_while(|space| space.handle != current_handle) .skip_while(|space| space.handle != current_handle)
.skip(1) .skip(1)
.next() .next()
.map(|space| space.handle); .map(|space| (current_handle, space.handle))
if let Some(next_handle) = maybe_handle { }
let res = shell.move_window(
Some(&seat), fn move_fullscreen_prev_workspace(state: &mut State, surface: &CosmicSurface) {
mapped, let mut shell = state.common.shell.write();
&current_handle, let Some(wl_surface) = surface.wl_surface() else {
&next_handle, return;
true, };
None, let Some((from, to)) = prev_workspace(&shell, &*wl_surface) else {
&mut state.common.workspace_state.update(), return;
); };
if let Some((target, _point)) = res {
std::mem::drop(shell); let seat = shell.seats.last_active().clone();
Shell::set_focus(state, Some(&target), &seat, None, true) 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| { Item::new(fl!("window-menu-minimize"), move |handle| {
let mapped = minimize_clone.clone(); let mapped = minimize_clone.clone();
let _ = handle.insert_idle(move |state| { 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)), .shortcut(config.shortcut_for_action(&Action::Minimize)),
@ -209,7 +284,7 @@ pub fn window_items(
let _ = handle.insert_idle(move |state| { let _ = handle.insert_idle(move |state| {
let mut shell = state.common.shell.write(); let mut shell = state.common.shell.write();
let seat = shell.seats.last_active().clone(); 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)) .shortcut(config.shortcut_for_action(&Action::Maximize))
@ -422,7 +497,8 @@ pub fn window_items(
Some( Some(
Item::new(fl!("window-menu-move-prev-workspace"), move |handle| { Item::new(fl!("window-menu-move-prev-workspace"), move |handle| {
let mapped = move_prev_clone.clone(); 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)) .shortcut(config.shortcut_for_action(&Action::MoveToPreviousWorkspace))
.disabled(is_sticky), .disabled(is_sticky),
@ -430,7 +506,8 @@ pub fn window_items(
Some( Some(
Item::new(fl!("window-menu-move-next-workspace"), move |handle| { Item::new(fl!("window-menu-move-next-workspace"), move |handle| {
let mapped = move_next_clone.clone(); 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)) .shortcut(config.shortcut_for_action(&Action::MoveToNextWorkspace))
.disabled(is_sticky), .disabled(is_sticky),
@ -466,3 +543,105 @@ pub fn window_items(
.into_iter() .into_iter()
.flatten() .flatten()
} }
pub fn fullscreen_items(window: &CosmicSurface, config: &Config) -> impl Iterator<Item = Item> {
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()
}

View file

@ -432,7 +432,8 @@ impl MoveGrab {
} }
} }
let indicator_location = shell.stacking_indicator(&current_output, self.previous); let indicator_location =
shell.stacking_indicator(&current_output, self.previous.clone());
if indicator_location.is_some() != grab_state.stacking_indicator.is_some() { if indicator_location.is_some() != grab_state.stacking_indicator.is_some() {
grab_state.stacking_indicator = indicator_location.map(|geo| { grab_state.stacking_indicator = indicator_location.map(|geo| {
let element = stack_hover( let element = stack_hover(
@ -731,7 +732,7 @@ impl MoveGrab {
start: Instant::now(), start: Instant::now(),
stacking_indicator: None, stacking_indicator: None,
snapping_zone: None, snapping_zone: None,
previous: previous_layer, previous: previous_layer.clone(),
location: start_data.location(), location: start_data.location(),
cursor_output: cursor_output.clone(), cursor_output: cursor_output.clone(),
}; };
@ -779,7 +780,7 @@ impl Drop for MoveGrab {
let output = self.cursor_output.clone(); let output = self.cursor_output.clone();
let seat = self.seat.clone(); let seat = self.seat.clone();
let window_outputs = self.window_outputs.drain().collect::<HashSet<_>>(); let window_outputs = self.window_outputs.drain().collect::<HashSet<_>>();
let previous = self.previous; let previous = self.previous.clone();
let window = self.window.clone(); let window = self.window.clone();
let is_touch_grab = matches!(self.start_data, GrabStartData::Touch(_)); let is_touch_grab = matches!(self.start_data, GrabStartData::Touch(_));
@ -841,10 +842,14 @@ impl Drop for MoveGrab {
window_location.to_local(&workspace.output), window_location.to_local(&workspace.output),
); );
if previous == ManagedLayer::Floating { if matches!(previous, ManagedLayer::Floating) {
if let Some(sz) = grab_state.snapping_zone { if let Some(sz) = grab_state.snapping_zone {
if sz == SnappingZone::Maximize { if sz == SnappingZone::Maximize {
shell.maximize_toggle(&window, &seat); shell.maximize_toggle(
&window,
&seat,
&state.common.event_loop_handle,
);
} else { } else {
let directions = match sz { let directions = match sz {
SnappingZone::Maximize => vec![], SnappingZone::Maximize => vec![],

View file

@ -627,78 +627,46 @@ impl FloatingLayout {
); );
} }
pub fn unmap(&mut self, window: &CosmicMapped) -> Option<Size<i32, Logical>> { pub fn unmap(
let mut new_size = None; &mut self,
window: &CosmicMapped,
to: Option<Rectangle<i32, Local>>,
) -> Option<Rectangle<i32, Local>> {
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(_) = window.floating_tiled.lock().unwrap().take() {
if let Some(last_size) = window.last_geometry.lock().unwrap().map(|geo| geo.size) { if let Some(last_size) = window.last_geometry.lock().unwrap().map(|geo| geo.size) {
if let Some(location) = self.space.element_location(window) { let geometry = Rectangle::new(mapped_geometry.loc, last_size);
window.set_tiled(false); window.set_tiled(false);
window.set_geometry( window.set_geometry(geometry.to_global(self.space.outputs().next().unwrap()));
Rectangle::new(location, last_size.as_logical()) window.configure();
.as_local() mapped_geometry.size = last_size;
.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(),
)
} }
} else if !window.is_maximized(true) {
*window.last_geometry.lock().unwrap() = Some(mapped_geometry);
} }
let _ = self.animations.remove(window); if let Some(to) = to {
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<i32, Local>,
) -> Option<(CosmicMapped, Point<i32, Local>)> {
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);
self.animations.insert( self.animations.insert(
window.clone(), window.clone(),
Animation::Minimize { Animation::Minimize {
start: Instant::now(), start: Instant::now(),
previous_geometry: previous_geometry.as_local(), previous_geometry: mapped_geometry,
target_geometry: to, 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( pub fn drop_window(
@ -1027,7 +995,7 @@ impl FloatingLayout {
Some(geo.size), Some(geo.size),
None, None,
); );
focus_stack.append(&mapped); focus_stack.append(mapped.clone());
Some(KeyboardFocusTarget::Element(mapped)) Some(KeyboardFocusTarget::Element(mapped))
} else { } else {
// if we have a stack // if we have a stack
@ -1068,7 +1036,7 @@ impl FloatingLayout {
self.space.refresh(); self.space.refresh();
for elem in new_elements.into_iter().rev() { for elem in new_elements.into_iter().rev() {
focus_stack.append(&elem); focus_stack.append(elem);
} }
Some(KeyboardFocusTarget::Element(mapped)) Some(KeyboardFocusTarget::Element(mapped))

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -761,13 +761,16 @@ impl Common {
// normal windows // normal windows
for space in shell.workspaces.spaces() { for space in shell.workspaces.spaces() {
if let Some(window) = space.get_fullscreen() {
window.with_surfaces(processor);
}
space.mapped().for_each(|mapped| { space.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() { for (window, _) in mapped.windows() {
window.with_surfaces(processor); window.with_surfaces(processor);
} }
}); });
space.minimized_windows.iter().for_each(|m| { space.minimized_windows.iter().for_each(|m| {
for (window, _) in m.window.windows() { for window in m.windows() {
window.with_surfaces(processor); window.with_surfaces(processor);
} }
}) })
@ -919,6 +922,22 @@ impl Common {
}); });
if let Some(active) = shell.active_space(output) { 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| { active.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() { for (window, _) in mapped.windows() {
if let Some(feedback) = window if let Some(feedback) = window
@ -1098,6 +1117,9 @@ impl Common {
}); });
if let Some(active) = shell.active_space(output) { 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| { active.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() { for (window, _) in mapped.windows() {
window.send_frame(output, time, throttle(&window), should_send); window.send_frame(output, time, throttle(&window), should_send);
@ -1106,7 +1128,7 @@ impl Common {
// other (throttled) windows // other (throttled) windows
active.minimized_windows.iter().for_each(|m| { 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); window.send_frame(output, time, throttle(&window), |_, _| None);
} }
}); });
@ -1116,6 +1138,10 @@ impl Common {
.spaces_for_output(output) .spaces_for_output(output)
.filter(|w| w.handle != active.handle) .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| { space.mapped().for_each(|mapped| {
for (window, _) in mapped.windows() { for (window, _) in mapped.windows() {
let throttle = min(throttle(space), throttle(&window)); let throttle = min(throttle(space), throttle(&window));
@ -1123,7 +1149,7 @@ impl Common {
} }
}); });
space.minimized_windows.iter().for_each(|m| { 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); window.send_frame(output, time, throttle(&window), |_, _| None);
} }
}) })

View file

@ -6,10 +6,11 @@ use smithay::{
output::Output, output::Output,
reexports::wayland_server::DisplayHandle, reexports::wayland_server::DisplayHandle,
utils::{Point, Rectangle, Size, SERIAL_COUNTER}, utils::{Point, Rectangle, Size, SERIAL_COUNTER},
wayland::seat::WaylandFocus,
}; };
use crate::{ use crate::{
shell::{element::CosmicWindow, CosmicSurface, Shell, WorkspaceDelta}, shell::{focus::target::KeyboardFocusTarget, CosmicSurface, Shell, WorkspaceDelta},
utils::prelude::*, utils::prelude::*,
wayland::protocols::{ wayland::protocols::{
toplevel_info::ToplevelInfoHandler, toplevel_info::ToplevelInfoHandler,
@ -41,17 +42,13 @@ impl ToplevelManagementHandler for State {
.spaces_for_output(output) .spaces_for_output(output)
.enumerate() .enumerate()
.find(|(_, w)| { .find(|(_, w)| {
w.mapped() w.get_fullscreen().is_some_and(|f| f == window)
.flat_map(|m| m.windows().map(|(s, _)| s)) || w.mapped()
.any(|w| &w == window) .flat_map(|m| m.windows().map(|(s, _)| s))
.any(|w| &w == window)
}); });
if let Some((idx, workspace)) = maybe { if let Some((idx, workspace)) = maybe {
let seat = seat.unwrap_or(shell.seats.last_active().clone()); 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 handle = workspace.handle;
let res = shell.activate( let res = shell.activate(
@ -62,8 +59,12 @@ impl ToplevelManagementHandler for State {
); );
let workspace = shell.workspaces.space_for_handle_mut(&handle).unwrap(); let workspace = shell.workspaces.space_for_handle_mut(&handle).unwrap();
if seat.get_keyboard().unwrap().current_focus() != Some(mapped.clone().into()) if seat
&& workspace.is_tiled(&mapped) .get_keyboard()
.unwrap()
.current_focus()
.is_some_and(|focus| !focus.windows().any(|w| w == *window))
&& workspace.is_tiled(window)
{ {
for mapped in workspace for mapped in workspace
.mapped() .mapped()
@ -75,6 +76,13 @@ impl ToplevelManagementHandler for State {
workspace.unmaximize_request(&mapped); 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); std::mem::drop(shell);
if seat.active_output() != *output { if seat.active_output() != *output {
@ -102,8 +110,7 @@ impl ToplevelManagementHandler for State {
} }
} }
mapped.focus_window(window); Shell::set_focus(self, Some(&target), &seat, None, false);
Shell::set_focus(self, Some(&mapped.clone().into()), &seat, None, false);
return; return;
} }
} }
@ -121,44 +128,27 @@ impl ToplevelManagementHandler for State {
_output: Output, _output: Output,
) { ) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mut mapped) = shell.element_for_surface(window).cloned() { let seat = shell.seats.last_active().clone();
if let Some(from_workspace) = shell.space_for_mut(&mapped) { let Some(surface) = window.wl_surface() else {
// If window is part of a stack, remove it and map it outside the stack return;
if let Some(stack) = mapped.stack_ref() { };
stack.remove_window(&window); let Some((from_workspace, _)) = shell.workspace_for_surface(&*surface) else {
mapped = CosmicWindow::new( return;
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::<std::iter::Empty<_>>,
None,
);
} else {
from_workspace.floating_layer.map(mapped.clone(), None);
}
}
let from_handle = from_workspace.handle; let res = shell.move_window(
let seat = shell.seats.last_active().clone(); Some(&seat),
let res = shell.move_window( window,
Some(&seat), &from_workspace,
&mapped, &to_handle,
&from_handle, false,
&to_handle, None,
false, &mut self.common.workspace_state.update(),
None, &self.common.event_loop_handle,
&mut self.common.workspace_state.update(), );
); if let Some((target, _)) = res {
if let Some((target, _)) = res { std::mem::drop(shell);
std::mem::drop(shell); Shell::set_focus(self, Some(&target), &seat, None, true);
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 mut shell = self.common.shell.write();
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
if let Some(mapped) = shell.element_for_surface(window).cloned() { let output = output
if let Some((output, workspace)) = .or_else(|| {
output.and_then(|output| shell.workspaces.active_mut(&output).map(|w| (output, w))) window
{ .wl_surface()
let from = minimize_rectangle(&output, window); .and_then(|surface| shell.visible_output_for_surface(&*surface).cloned())
workspace.fullscreen_request(window, None, from, &seat); })
} else if let Some((output, handle)) = shell .unwrap_or_else(|| seat.focused_or_active_output());
.space_for(&mapped) if let Some(target) =
.map(|workspace| (workspace.output.clone(), workspace.handle.clone())) shell.fullscreen_request(window, output, &self.common.event_loop_handle)
{ {
let from = minimize_rectangle(&output, window); std::mem::drop(shell);
shell Shell::set_focus(self, Some(&target), &seat, None, true);
.workspaces
.space_for_handle_mut(&handle)
.unwrap()
.fullscreen_request(window, None, from, &seat);
}
} }
} }
@ -196,33 +181,14 @@ impl ToplevelManagementHandler for State {
window: &<Self as ToplevelInfoHandler>::Window, window: &<Self as ToplevelInfoHandler>::Window,
) { ) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(window).cloned() { shell.unfullscreen_request(window, &self.common.event_loop_handle);
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,
);
}
}
}
} }
fn maximize(&mut self, _dh: &DisplayHandle, window: &<Self as ToplevelInfoHandler>::Window) { fn maximize(&mut self, _dh: &DisplayHandle, window: &<Self as ToplevelInfoHandler>::Window) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(window).cloned() { if let Some(mapped) = shell.element_for_surface(window).cloned() {
let seat = shell.seats.last_active().clone(); 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: &<Self as ToplevelInfoHandler>::Window) { fn minimize(&mut self, _dh: &DisplayHandle, window: &<Self as ToplevelInfoHandler>::Window) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(window).cloned() { shell.minimize_request(window);
if !mapped.is_stack() || &mapped.active_window() == window {
shell.minimize_request(&mapped);
}
}
} }
fn unminimize(&mut self, _dh: &DisplayHandle, window: &<Self as ToplevelInfoHandler>::Window) { fn unminimize(&mut self, _dh: &DisplayHandle, window: &<Self as ToplevelInfoHandler>::Window) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(window).cloned() { let seat = shell.seats.last_active().clone();
let seat = shell.seats.last_active().clone(); shell.unminimize_request(window, &seat, &self.common.event_loop_handle);
shell.unminimize_request(&mapped, &seat);
if mapped.is_stack() {
mapped.stack_ref().unwrap().set_active(window);
}
}
} }
fn set_sticky(&mut self, _dh: &DisplayHandle, window: &<Self as ToplevelInfoHandler>::Window) { fn set_sticky(&mut self, _dh: &DisplayHandle, window: &<Self as ToplevelInfoHandler>::Window) {

View file

@ -1,3 +1,4 @@
use crate::shell::focus::target::KeyboardFocusTarget;
use crate::{shell::ActivationKey, state::ClientState, utils::prelude::*}; use crate::{shell::ActivationKey, state::ClientState, utils::prelude::*};
use crate::{ use crate::{
state::State, state::State,
@ -93,8 +94,6 @@ impl XdgActivationHandler for State {
.insert_if_missing(move || ActivationContext::Workspace(handle)); .insert_if_missing(move || ActivationContext::Workspace(handle));
debug!(?token, "created workspace token"); debug!(?token, "created workspace token");
} else {
debug!(?token, "created urgent-only token for invalid serial");
} }
valid valid
@ -106,88 +105,118 @@ impl XdgActivationHandler for State {
token_data: XdgActivationTokenData, token_data: XdgActivationTokenData,
surface: WlSurface, surface: WlSurface,
) { ) {
if let Some(context) = token_data.user_data.get::<ActivationContext>() { let Some(context) = token_data.user_data.get::<ActivationContext>() else {
let mut shell = self.common.shell.write(); return;
if let Some(element) = shell.element_for_surface(&surface).cloned() { };
match context { let mut shell = self.common.shell.write();
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();
if element.is_minimized() { match context {
shell.unminimize_request(&element, &seat); ActivationContext::UrgentOnly => {
} if let Some((workspace, _output)) = shell.workspace_for_surface(&surface) {
let mut workspace_guard = self.common.workspace_state.update();
let element_workspace = shell.space_for(&element).map(|w| w.handle.clone()); workspace_guard.add_workspace_state(&workspace, WState::Urgent);
let current_workspace = shell.active_space_mut(&current_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::<Vec<_>>()
.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);
}
}
} }
} else { }
shell ActivationContext::Workspace(_) => {
.pending_activations let seat = shell.seats.last_active().clone();
.insert(ActivationKey::Wayland(surface), context.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(&current_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::<Vec<_>>()
.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(&current_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());
};
} }
} }
} }

View file

@ -1,12 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use crate::{ use crate::{
shell::{ shell::{grabs::ReleaseMode, CosmicSurface, PendingWindow},
element::CosmicWindow, grabs::ReleaseMode, CosmicMapped, CosmicSurface, ManagedLayer,
PendingWindow,
},
utils::prelude::*, utils::prelude::*,
wayland::protocols::toplevel_info::{toplevel_enter_output, toplevel_enter_workspace},
}; };
use smithay::{ use smithay::{
delegate_xdg_shell, delegate_xdg_shell,
@ -33,7 +29,7 @@ use smithay::{
use std::cell::Cell; use std::cell::Cell;
use tracing::warn; use tracing::warn;
use super::{compositor::client_compositor_state, toplevel_management::minimize_rectangle}; use super::compositor::client_compositor_state;
pub mod popup; pub mod popup;
@ -204,20 +200,14 @@ impl XdgShellHandler for State {
fn minimize_request(&mut self, surface: ToplevelSurface) { fn minimize_request(&mut self, surface: ToplevelSurface) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(surface.wl_surface()).cloned() { shell.minimize_request(surface.wl_surface())
if !mapped.is_stack()
|| mapped.active_window().wl_surface().as_deref() == Some(surface.wl_surface())
{
shell.minimize_request(&mapped)
}
}
} }
fn maximize_request(&mut self, surface: ToplevelSurface) { fn maximize_request(&mut self, surface: ToplevelSurface) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(surface.wl_surface()).cloned() { if let Some(mapped) = shell.element_for_surface(surface.wl_surface()).cloned() {
let seat = shell.seats.last_active().clone(); 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 } else if let Some(pending) = shell
.pending_windows .pending_windows
.iter_mut() .iter_mut()
@ -243,153 +233,40 @@ impl XdgShellHandler for State {
fn fullscreen_request(&mut self, surface: ToplevelSurface, output: Option<WlOutput>) { fn fullscreen_request(&mut self, surface: ToplevelSurface, output: Option<WlOutput>) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
let Some(focused_output) = seat.focused_output() else {
return;
};
let output = output let output = output
.as_ref() .as_ref()
.and_then(Output::from_resource) .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() { match shell.fullscreen_request(&surface, output.clone(), &self.common.event_loop_handle) {
let from = minimize_rectangle(&output, &mapped.active_window()); Some(target) => {
std::mem::drop(shell);
if let Some(set) = shell Shell::set_focus(self, Some(&target), &seat, None, true);
.workspaces }
.sets None => {
.values_mut() if let Some(pending) = shell.pending_windows.iter_mut().find(|pending| {
.find(|set| set.sticky_layer.mapped().any(|m| m == &mapped)) pending.surface.wl_surface().as_deref() == Some(surface.wl_surface())
{ }) {
let mapped = if mapped pending.fullscreen = Some(output);
.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)
} }
} }
} 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) { fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
let mut shell = self.common.shell.write(); 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( if !shell.unfullscreen_request(&surface, &self.common.event_loop_handle) {
mapped, if let Some(pending) = shell.pending_windows.iter_mut().find(|pending| {
&old_handle, pending.surface.wl_surface().as_deref() == Some(surface.wl_surface())
&new_workspace_handle, }) {
layer, 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();
} }
} }

View file

@ -32,7 +32,7 @@ impl Shell {
( (
elem_geo.to_global(workspace.output()), elem_geo.to_global(workspace.output()),
workspace.output.clone(), workspace.output.clone(),
workspace.is_tiled(elem), workspace.is_tiled(&elem.active_window()),
) )
} else if let Some((output, set)) = self } else if let Some((output, set)) = self
.workspaces .workspaces

View file

@ -7,9 +7,7 @@ use crate::{
}, },
state::State, state::State,
utils::prelude::*, utils::prelude::*,
wayland::handlers::{ wayland::handlers::xdg_activation::ActivationContext,
toplevel_management::minimize_rectangle, xdg_activation::ActivationContext,
},
}; };
use cosmic_comp_config::{EavesdroppingKeyboardMode, XwaylandDescaling}; use cosmic_comp_config::{EavesdroppingKeyboardMode, XwaylandDescaling};
use smithay::{ use smithay::{
@ -48,7 +46,7 @@ use smithay::{
xdg_activation::XdgActivationToken, xdg_activation::XdgActivationToken,
}, },
xwayland::{ xwayland::{
xwm::{Reorder, X11Relatable, XwmId}, xwm::{Reorder, XwmId},
X11Surface, X11Wm, XWayland, XWaylandClientData, XWaylandEvent, XwmHandler, X11Surface, X11Wm, XWayland, XWaylandClientData, XWaylandEvent, XwmHandler,
}, },
}; };
@ -485,7 +483,9 @@ impl Common {
#[profiling::function] #[profiling::function]
pub fn update_x11_stacking_order(&mut self) { pub fn update_x11_stacking_order(&mut self) {
let shell = self.shell.read(); 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 if let Some(xwm) = self
.xwayland_state .xwayland_state
.as_mut() .as_mut()
@ -534,31 +534,51 @@ impl Common {
.filter(|(i, _)| *i != set.active), .filter(|(i, _)| *i != set.active),
) )
.flat_map(|(_, workspace)| { .flat_map(|(_, workspace)| {
workspace.get_fullscreen().cloned().into_iter().chain( workspace
workspace .get_fullscreen()
.mapped() .filter(|f| {
.chain( workspace
workspace .focus_stack
.minimized_windows .get(seat)
.iter() .last()
.map(|m| &m.window), .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(); .chain(
std::iter::once(active.clone()).chain( workspace
mapped .get_fullscreen()
.is_stack() .filter(|f| {
.then(move || { workspace
mapped .focus_stack
.windows() .get(seat)
.map(|(s, _)| s) .last()
.filter(move |s| s != &active) .is_none_or(|t| &t != f)
}) })
.into_iter() .cloned()
.flatten(), .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 mut shell = self.common.shell.write();
let startup_id = window.startup_id(); let startup_id = window.startup_id();
// TODO: Not correct for fullscreen (and minimized?)
if shell.element_for_surface(&window).is_some() { if shell.element_for_surface(&window).is_some() {
return; return;
} }
@ -831,6 +852,7 @@ impl XwmHandler for State {
// We only allow floating X11 windows to resize themselves. Nothing else // We only allow floating X11 windows to resize themselves. Nothing else
let shell = self.common.shell.read(); let shell = self.common.shell.read();
// TODO: Fullscreen
if let Some(mapped) = shell if let Some(mapped) = shell
.element_for_surface(&window) .element_for_surface(&window)
.filter(|mapped| !mapped.is_minimized()) .filter(|mapped| !mapped.is_minimized())
@ -838,7 +860,7 @@ impl XwmHandler for State {
let current_geo = if let Some(workspace) = shell.space_for(mapped) { let current_geo = if let Some(workspace) = shell.space_for(mapped) {
workspace workspace
.element_geometry(mapped) .element_geometry(mapped)
.filter(|_| workspace.is_floating(mapped)) .filter(|_| workspace.is_floating(&window))
.map(|geo| geo.to_global(workspace.output())) .map(|geo| geo.to_global(workspace.output()))
} else if let Some((output, set)) = shell } else if let Some((output, set)) = shell
.workspaces .workspaces
@ -996,7 +1018,7 @@ impl XwmHandler for State {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(&window).cloned() { if let Some(mapped) = shell.element_for_surface(&window).cloned() {
let seat = shell.seats.last_active().clone(); 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 } else if let Some(pending) = shell
.pending_windows .pending_windows
.iter_mut() .iter_mut()
@ -1021,74 +1043,50 @@ impl XwmHandler for State {
fn minimize_request(&mut self, _xwm: XwmId, window: X11Surface) { fn minimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(&window).cloned() { shell.minimize_request(&window);
if !mapped.is_stack() || mapped.active_window().is_window(&window) {
shell.minimize_request(&mapped);
}
}
} }
fn unminimize_request(&mut self, _xwm: XwmId, window: X11Surface) { fn unminimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(&window).cloned() { let seat = shell.seats.last_active().clone();
let seat = shell.seats.last_active().clone(); shell.unminimize_request(&window, &seat, &self.common.event_loop_handle);
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);
}
}
}
} }
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) { fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
if let Some(mapped) = shell.element_for_surface(&window).cloned() { let output = window
if let Some((output, handle)) = shell .wl_surface()
.space_for(&mapped) .and_then(|surface| shell.visible_output_for_surface(&surface).cloned())
.map(|workspace| (workspace.output.clone(), workspace.handle.clone())) .unwrap_or_else(|| seat.focused_or_active_output());
{
if let Some((surface, _)) = mapped match shell.fullscreen_request(&window, output.clone(), &self.common.event_loop_handle) {
.windows() Some(target) => {
.find(|(w, _)| w.x11_surface() == Some(&window)) 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); pending.fullscreen = Some(output);
shell
.workspaces
.space_for_handle_mut(&handle)
.unwrap()
.fullscreen_request(&surface, None, from, &seat);
} }
} }
} 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) { fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
let mut shell = self.common.shell.write(); let mut shell = self.common.shell.write();
if let Some(mapped) = shell.element_for_surface(&window).cloned() { if !shell.unfullscreen_request(&window, &self.common.event_loop_handle) {
if let Some(workspace) = shell.space_for_mut(&mapped) { if let Some(pending) = shell
let (window, _) = mapped .pending_windows
.windows() .iter_mut()
.find(|(w, _)| w.x11_surface() == Some(&window)) .find(|pending| pending.surface.x11_surface() == Some(&window))
.unwrap(); {
let previous = workspace.unfullscreen_request(&window); pending.fullscreen.take();
assert!(previous.is_none());
} }
} else if let Some(pending) = shell
.pending_windows
.iter_mut()
.find(|pending| pending.surface.x11_surface() == Some(&window))
{
pending.fullscreen.take();
} }
} }