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:
parent
8ef6c161a0
commit
adedb705e7
23 changed files with 2554 additions and 1796 deletions
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
shell::{element::CosmicMapped, Shell},
|
||||
shell::{element::CosmicMapped, CosmicSurface, MinimizedWindow, Shell},
|
||||
state::Common,
|
||||
utils::prelude::*,
|
||||
wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
|
||||
|
|
@ -9,7 +9,7 @@ use smithay::{
|
|||
desktop::{layer_map_for_output, PopupUngrabStrategy},
|
||||
input::{pointer::MotionEvent, Seat},
|
||||
output::Output,
|
||||
reexports::wayland_server::Resource,
|
||||
reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource},
|
||||
utils::{IsAlive, Point, Serial, SERIAL_COUNTER},
|
||||
wayland::{
|
||||
seat::WaylandFocus,
|
||||
|
|
@ -29,18 +29,80 @@ use super::{grabs::SeatMoveGrabState, layout::floating::FloatingLayout, SeatExt}
|
|||
mod order;
|
||||
pub mod target;
|
||||
|
||||
pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet<CosmicMapped>>);
|
||||
pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet<CosmicMapped>);
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
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> {
|
||||
/// returns the last unminimized window in the focus stack that is still alive
|
||||
pub fn last(&self) -> Option<&CosmicMapped> {
|
||||
pub fn last(&self) -> Option<&FocusTarget> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.and_then(|set| set.iter().rev().find(|w| w.alive() && !w.is_minimized()))
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'_ FocusTarget> {
|
||||
self.0
|
||||
.iter()
|
||||
.flat_map(|set| set.iter().rev().filter(|w| w.alive() && !w.is_minimized()))
|
||||
|
|
@ -48,21 +110,25 @@ impl<'a> FocusStack<'a> {
|
|||
}
|
||||
|
||||
impl<'a> FocusStackMut<'a> {
|
||||
pub fn append(&mut self, window: &CosmicMapped) {
|
||||
pub fn append(&mut self, target: impl Into<FocusTarget>) {
|
||||
let target = target.into();
|
||||
self.0.retain(|w| w.alive());
|
||||
self.0.shift_remove(window);
|
||||
self.0.insert(window.clone());
|
||||
self.0.shift_remove(&target);
|
||||
self.0.insert(target);
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, window: &CosmicMapped) {
|
||||
self.0.retain(|w| w != window);
|
||||
pub fn remove<T>(&mut self, target: &T)
|
||||
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())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'_ FocusTarget> {
|
||||
self.0
|
||||
.iter()
|
||||
.rev()
|
||||
|
|
@ -107,23 +173,16 @@ impl Shell {
|
|||
serial: Option<Serial>,
|
||||
update_cursor: bool,
|
||||
) {
|
||||
let element = match target {
|
||||
Some(KeyboardFocusTarget::Element(mapped)) => Some(mapped.clone()),
|
||||
Some(KeyboardFocusTarget::Fullscreen(window)) => state
|
||||
.common
|
||||
.shell
|
||||
.read()
|
||||
.element_for_surface(window)
|
||||
.cloned(),
|
||||
let focus_target = match target {
|
||||
Some(KeyboardFocusTarget::Element(mapped)) => Some(FocusTarget::Window(mapped.clone())),
|
||||
Some(KeyboardFocusTarget::Fullscreen(surface)) => {
|
||||
Some(FocusTarget::Fullscreen(surface.clone()))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(mapped) = element {
|
||||
if mapped.is_minimized() {
|
||||
return;
|
||||
}
|
||||
|
||||
state.common.shell.write().append_focus_stack(&mapped, seat);
|
||||
if let Some(target) = focus_target {
|
||||
state.common.shell.write().append_focus_stack(target, seat);
|
||||
}
|
||||
|
||||
update_focus_state(seat, target, state, serial, update_cursor);
|
||||
|
|
@ -131,25 +190,30 @@ impl Shell {
|
|||
state.common.shell.write().update_active();
|
||||
}
|
||||
|
||||
pub fn append_focus_stack(&mut self, mapped: &CosmicMapped, seat: &Seat<State>) {
|
||||
if mapped.is_minimized() {
|
||||
pub fn append_focus_stack(&mut self, target: impl Into<FocusTarget>, seat: &Seat<State>) {
|
||||
let target = target.into();
|
||||
if target.is_minimized() {
|
||||
return;
|
||||
}
|
||||
|
||||
// update FocusStack and notify layouts about new focus (if any window)
|
||||
let workspace = self.space_for_mut(&mapped);
|
||||
let workspace = target
|
||||
.wl_surface()
|
||||
.and_then(|surface| self.workspace_for_surface(&surface));
|
||||
let workspace = if workspace.is_none() {
|
||||
//should this be the active output or the focused output?
|
||||
self.active_space_mut(&seat.focused_or_active_output())
|
||||
.unwrap()
|
||||
} else {
|
||||
workspace.unwrap()
|
||||
self.workspaces
|
||||
.space_for_handle_mut(&workspace.unwrap().0)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let mut focus_stack = workspace.focus_stack.get_mut(seat);
|
||||
if Some(mapped) != focus_stack.last() {
|
||||
trace!(?mapped, "Focusing window.");
|
||||
focus_stack.append(&mapped);
|
||||
if Some(&target) != focus_stack.last() {
|
||||
trace!(?target, "Focusing window.");
|
||||
focus_stack.append(target);
|
||||
// also remove popup grabs, if we are switching focus
|
||||
if let Some(mut popup_grab) = seat
|
||||
.user_data()
|
||||
|
|
@ -171,7 +235,7 @@ impl Shell {
|
|||
.map(|seat| {
|
||||
if matches!(
|
||||
seat.get_keyboard().unwrap().current_focus(),
|
||||
Some(KeyboardFocusTarget::Group(_))
|
||||
Some(KeyboardFocusTarget::Group(_)) | Some(KeyboardFocusTarget::LockSurface(_))
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -179,7 +243,10 @@ impl Shell {
|
|||
let output = seat.focused_or_active_output();
|
||||
let space = self.active_space(&output).unwrap();
|
||||
let stack = space.focus_stack.get(seat);
|
||||
stack.last().cloned()
|
||||
stack.last().and_then(|target| match target {
|
||||
FocusTarget::Window(window) => Some(window.clone()),
|
||||
FocusTarget::Fullscreen(_) => None,
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -193,12 +260,33 @@ impl Shell {
|
|||
window.set_activated(focused_windows.contains(&window));
|
||||
window.configure();
|
||||
}
|
||||
for m in set.minimized_windows.iter() {
|
||||
m.window.set_activated(false);
|
||||
m.window.configure();
|
||||
for window in set
|
||||
.minimized_windows
|
||||
.iter()
|
||||
.flat_map(MinimizedWindow::mapped)
|
||||
{
|
||||
window.set_activated(false);
|
||||
window.configure();
|
||||
}
|
||||
|
||||
let workspace = &mut set.workspaces[set.active];
|
||||
if let Some(fullscreen) = workspace.get_fullscreen() {
|
||||
if self.seats.iter().any(|seat| {
|
||||
if let Some(KeyboardFocusTarget::Fullscreen(s)) =
|
||||
seat.get_keyboard().unwrap().current_focus()
|
||||
{
|
||||
&s == fullscreen
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
fullscreen.set_activated(true);
|
||||
fullscreen.send_configure();
|
||||
} else {
|
||||
fullscreen.set_activated(false);
|
||||
fullscreen.send_configure();
|
||||
}
|
||||
}
|
||||
for focused in focused_windows.iter() {
|
||||
raise_with_children(&mut workspace.floating_layer, focused);
|
||||
}
|
||||
|
|
@ -207,8 +295,15 @@ impl Shell {
|
|||
window.configure();
|
||||
}
|
||||
for m in workspace.minimized_windows.iter() {
|
||||
m.window.set_activated(false);
|
||||
m.window.configure();
|
||||
if let Some(window) = m.mapped() {
|
||||
window.set_activated(false);
|
||||
window.configure();
|
||||
} else {
|
||||
for surface in m.windows() {
|
||||
surface.set_activated(false);
|
||||
surface.send_configure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, workspace) in set.workspaces.iter().enumerate() {
|
||||
|
|
@ -515,13 +610,11 @@ fn focus_target_is_valid(
|
|||
let workspace = shell.active_space(&output).unwrap();
|
||||
let focus_stack = workspace.focus_stack.get(&seat);
|
||||
let is_in_focus_stack = focus_stack.last().map(|m| m == &mapped).unwrap_or(false);
|
||||
let has_fullscreen = workspace.get_fullscreen().is_some();
|
||||
|
||||
if is_sticky && !is_in_focus_stack {
|
||||
shell.append_focus_stack(&mapped, seat);
|
||||
shell.append_focus_stack(mapped, seat);
|
||||
}
|
||||
|
||||
(is_sticky || is_in_focus_stack) && !has_fullscreen
|
||||
is_sticky || is_in_focus_stack
|
||||
}
|
||||
KeyboardFocusTarget::LayerSurface(layer) => {
|
||||
layer_map_for_output(&output).layers().any(|l| l == &layer)
|
||||
|
|
@ -535,13 +628,7 @@ fn focus_target_is_valid(
|
|||
.has_node(&node),
|
||||
KeyboardFocusTarget::Fullscreen(window) => {
|
||||
let workspace = shell.active_space(&output).unwrap();
|
||||
let focus_stack = workspace.focus_stack.get(&seat);
|
||||
|
||||
focus_stack
|
||||
.last()
|
||||
.map(|m| m.has_active_window(&window))
|
||||
.unwrap_or(false)
|
||||
&& workspace.get_fullscreen().is_some()
|
||||
workspace.get_fullscreen().is_some_and(|w| w == &window)
|
||||
}
|
||||
KeyboardFocusTarget::Popup(_) => true,
|
||||
KeyboardFocusTarget::LockSurface(_) => false,
|
||||
|
|
@ -579,8 +666,6 @@ fn update_focus_target(
|
|||
})
|
||||
.cloned()
|
||||
.map(KeyboardFocusTarget::from)
|
||||
} else if let Some(surface) = shell.active_space(&output).unwrap().get_fullscreen() {
|
||||
Some(KeyboardFocusTarget::Fullscreen(surface.clone()))
|
||||
} else {
|
||||
shell
|
||||
.active_space(&output)
|
||||
|
|
@ -588,9 +673,23 @@ fn update_focus_target(
|
|||
.focus_stack
|
||||
.get(&seat)
|
||||
.last()
|
||||
.or_else(|| shell.active_space(&output).unwrap().mapped().next())
|
||||
.cloned()
|
||||
.map(KeyboardFocusTarget::from)
|
||||
.map(Into::<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)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,15 @@ use smithay::{
|
|||
use crate::{
|
||||
backend::render::ElementFilter,
|
||||
shell::{
|
||||
focus::target::KeyboardFocusTarget,
|
||||
layout::{floating::FloatingLayout, tiling::ANIMATION_DURATION},
|
||||
Shell, Workspace, WorkspaceDelta,
|
||||
SeatExt, Shell, Workspace, WorkspaceDelta,
|
||||
},
|
||||
utils::{
|
||||
geometry::*,
|
||||
prelude::OutputExt,
|
||||
quirks::{workspace_overview_is_open, WORKSPACE_OVERVIEW_NAMESPACE},
|
||||
},
|
||||
utils::{geometry::*, prelude::OutputExt, quirks::WORKSPACE_OVERVIEW_NAMESPACE},
|
||||
wayland::protocols::workspace::WorkspaceHandle,
|
||||
};
|
||||
|
||||
|
|
@ -105,11 +110,32 @@ fn render_input_order_internal<R: 'static>(
|
|||
return ControlFlow::Break(Err(OutputNoMode));
|
||||
};
|
||||
let output_size = output.geometry().size;
|
||||
let has_fullscreen = workspace
|
||||
.fullscreen
|
||||
.as_ref()
|
||||
.filter(|f| !f.is_animating())
|
||||
.is_some();
|
||||
|
||||
// this is more hacky than I would like..
|
||||
let fullscreen = workspace.fullscreen.as_ref().filter(|f| !f.is_animating());
|
||||
let seat = shell.seats.last_active();
|
||||
let is_active_workspace = seat.focused_output().is_some_and(|output| {
|
||||
shell
|
||||
.active_space(&output)
|
||||
.is_some_and(|w| w.handle == workspace.handle)
|
||||
});
|
||||
let focus_stack_is_valid_fullscreen = workspace
|
||||
.focus_stack
|
||||
.get(seat)
|
||||
.last()
|
||||
.zip(fullscreen)
|
||||
.is_some_and(|(target, fullscreen)| target == &fullscreen.surface);
|
||||
let overview_is_open = workspace_overview_is_open(&output);
|
||||
let has_focused_fullscreen = if is_active_workspace {
|
||||
let current_focus = seat.get_keyboard().unwrap().current_focus();
|
||||
matches!(current_focus, Some(KeyboardFocusTarget::Fullscreen(_)))
|
||||
|| (current_focus.is_none()
|
||||
&& focus_stack_is_valid_fullscreen
|
||||
&& !workspace_overview_is_open(&output))
|
||||
} else {
|
||||
focus_stack_is_valid_fullscreen && !overview_is_open
|
||||
};
|
||||
let has_fullscreen = fullscreen.is_some() && !overview_is_open;
|
||||
|
||||
let (previous, current_offset) = match previous.as_ref() {
|
||||
Some((previous, previous_idx, start)) => {
|
||||
|
|
@ -163,7 +189,7 @@ fn render_input_order_internal<R: 'static>(
|
|||
};
|
||||
|
||||
// Top-level layer shell popups
|
||||
if !has_fullscreen {
|
||||
if !has_focused_fullscreen {
|
||||
for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) {
|
||||
callback(Stage::LayerPopup {
|
||||
layer,
|
||||
|
|
@ -193,7 +219,7 @@ fn render_input_order_internal<R: 'static>(
|
|||
}
|
||||
|
||||
// sticky window popups
|
||||
if !has_fullscreen {
|
||||
if !has_focused_fullscreen {
|
||||
callback(Stage::StickyPopups(&set.sticky_layer))?;
|
||||
}
|
||||
}
|
||||
|
|
@ -222,7 +248,7 @@ fn render_input_order_internal<R: 'static>(
|
|||
})?;
|
||||
}
|
||||
|
||||
if !has_fullscreen {
|
||||
if !has_focused_fullscreen {
|
||||
// bottom layer popups
|
||||
for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) {
|
||||
callback(Stage::LayerPopup {
|
||||
|
|
@ -271,7 +297,7 @@ fn render_input_order_internal<R: 'static>(
|
|||
}
|
||||
}
|
||||
|
||||
if !has_fullscreen {
|
||||
if !has_focused_fullscreen {
|
||||
// top-layer shell
|
||||
for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) {
|
||||
callback(Stage::LayerSurface { layer, location })?;
|
||||
|
|
@ -302,7 +328,7 @@ fn render_input_order_internal<R: 'static>(
|
|||
}
|
||||
}
|
||||
|
||||
if !has_fullscreen {
|
||||
if !has_focused_fullscreen {
|
||||
// bottom layer
|
||||
for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) {
|
||||
location += current_offset.as_global();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
match self {
|
||||
KeyboardFocusTarget::Element(mapped) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue