Skip to content

Commit

Permalink
focus: Introduce render_input_order
Browse files Browse the repository at this point in the history
  • Loading branch information
Drakulix committed Oct 21, 2024
1 parent 2497992 commit ad35c18
Show file tree
Hide file tree
Showing 2 changed files with 376 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/shell/focus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ use std::{borrow::Cow, mem, sync::Mutex};

use tracing::{debug, trace};

pub use self::order::{render_input_order, Stage};
use self::target::{KeyboardFocusTarget, WindowGroup};

use super::{grabs::SeatMoveGrabState, layout::floating::FloatingLayout, SeatExt};

mod order;
pub mod target;

pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet<CosmicMapped>>);
Expand Down
374 changes: 374 additions & 0 deletions src/shell/focus/order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,374 @@
use std::{ops::ControlFlow, time::Instant};

use cosmic_comp_config::workspace::WorkspaceLayout;
use keyframe::{ease, functions::EaseInOutCubic};
use smithay::{
desktop::{layer_map_for_output, LayerSurface, PopupKind, PopupManager},
output::{Output, OutputNoMode},
utils::{Logical, Point},
wayland::{session_lock::LockSurface, shell::wlr_layer::Layer},
xwayland::X11Surface,
};

use crate::{
backend::render::ElementFilter,
shell::{
layout::{floating::FloatingLayout, tiling::ANIMATION_DURATION},
Shell, Workspace, WorkspaceDelta,
},
utils::{geometry::*, prelude::OutputExt, quirks::WORKSPACE_OVERVIEW_NAMESPACE},
wayland::protocols::workspace::WorkspaceHandle,
};

pub enum Stage<'a> {
SessionLock(Option<&'a LockSurface>),
LayerPopup {
layer: LayerSurface,
popup: &'a PopupKind,
location: Point<i32, Global>,
},
LayerSurface {
layer: LayerSurface,
location: Point<i32, Global>,
},
OverrideRedirect {
surface: &'a X11Surface,
location: Point<i32, Global>,
},
StickyPopups(&'a FloatingLayout),
Sticky(&'a FloatingLayout),
WorkspacePopups {
workspace: &'a Workspace,
offset: Point<i32, Logical>,
},
Workspace {
workspace: &'a Workspace,
offset: Point<i32, Logical>,
},
}

pub fn render_input_order<R: Default + 'static>(
shell: &Shell,
output: &Output,
previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>,
current: (WorkspaceHandle, usize),
element_filter: ElementFilter,
callback: impl FnMut(Stage) -> ControlFlow<Result<R, OutputNoMode>, ()>,
) -> Result<R, OutputNoMode> {
match render_input_order_internal(shell, output, previous, current, element_filter, callback) {
ControlFlow::Break(result) => result,
ControlFlow::Continue(_) => Ok(R::default()),
}
}

fn render_input_order_internal<R: 'static>(
shell: &Shell,
output: &Output,
previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>,
current: (WorkspaceHandle, usize),
element_filter: ElementFilter,
mut callback: impl FnMut(Stage) -> ControlFlow<Result<R, OutputNoMode>, ()>,
) -> ControlFlow<Result<R, OutputNoMode>, ()> {
// Session Lock
if let Some(session_lock) = &shell.session_lock {
return callback(Stage::SessionLock(session_lock.surfaces.get(output)));
}

// Overlay-level layer shell
// overlay is above everything
for (layer, popup, location) in layer_popups(output, Layer::Overlay, element_filter) {
callback(Stage::LayerPopup {
layer,
popup: &popup,
location,
})?;
}
for (layer, location) in layer_surfaces(output, Layer::Overlay, element_filter) {
callback(Stage::LayerSurface { layer, location })?;
}

// calculate a bunch of stuff for workspace transitions

let Some(set) = shell.workspaces.sets.get(output) else {
return ControlFlow::Break(Err(OutputNoMode));
};
let Some(workspace) = set.workspaces.iter().find(|w| w.handle == current.0) else {
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();

let (previous, current_offset) = match previous.as_ref() {
Some((previous, previous_idx, start)) => {
let layout = shell.workspaces.layout;

let Some(workspace) = shell.workspaces.space_for_handle(&previous) else {
return ControlFlow::Break(Err(OutputNoMode));
};
let has_fullscreen = workspace.fullscreen.is_some();

let percentage = match start {
WorkspaceDelta::Shortcut(st) => ease(
EaseInOutCubic,
0.0,
1.0,
Instant::now().duration_since(*st).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32,
),
WorkspaceDelta::Gesture(prog) => *prog as f32,
WorkspaceDelta::GestureEnd(st, spring) => {
(spring.value_at(Instant::now().duration_since(*st)) as f32).clamp(0.0, 1.0)
}
};

let offset = Point::<i32, Logical>::from(match (layout, *previous_idx < current.1) {
(WorkspaceLayout::Vertical, true) => {
(0, (-output_size.h as f32 * percentage).round() as i32)
}
(WorkspaceLayout::Vertical, false) => {
(0, (output_size.h as f32 * percentage).round() as i32)
}
(WorkspaceLayout::Horizontal, true) => {
((-output_size.w as f32 * percentage).round() as i32, 0)
}
(WorkspaceLayout::Horizontal, false) => {
((output_size.w as f32 * percentage).round() as i32, 0)
}
});

(
Some((previous, has_fullscreen, offset)),
Point::<i32, Logical>::from(match (layout, *previous_idx < current.1) {
(WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y),
(WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)),
(WorkspaceLayout::Horizontal, true) => (output_size.w + offset.x, 0),
(WorkspaceLayout::Horizontal, false) => (-(output_size.w - offset.x), 0),
}),
)
}
None => (None, Point::default()),
};

// Top-level layer shell popups
if !has_fullscreen {
for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) {
callback(Stage::LayerPopup {
layer,
popup: &popup,
location,
})?;
}
}

if element_filter != ElementFilter::LayerShellOnly {
// overlay redirect windows
// they need to be over sticky windows, because they could be popups of sticky windows,
// and we can't differenciate that.
for (surface, location) in shell
.override_redirect_windows
.iter()
.filter(|or| {
(*or)
.geometry()
.as_global()
.intersection(output.geometry())
.is_some()
})
.map(|or| (or, or.geometry().loc.as_global()))
{
callback(Stage::OverrideRedirect { surface, location })?;
}

// sticky window popups
if !has_fullscreen {
callback(Stage::StickyPopups(&set.sticky_layer))?;
}
}

if element_filter != ElementFilter::LayerShellOnly {
// previous workspace popups
if let Some((previous_handle, _, offset)) = previous.as_ref() {
let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else {
return ControlFlow::Break(Err(OutputNoMode));
};

callback(Stage::WorkspacePopups {
workspace,
offset: *offset,
})?;
}

// current workspace popups
let Some(workspace) = shell.workspaces.space_for_handle(&current.0) else {
return ControlFlow::Break(Err(OutputNoMode));
};

callback(Stage::WorkspacePopups {
workspace,
offset: current_offset,
})?;
}

if !has_fullscreen {
// bottom layer popups
for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) {
callback(Stage::LayerPopup {
layer,
popup: &popup,
location,
})?;
}
}

if let Some((_, has_fullscreen, offset)) = previous.as_ref() {
if !has_fullscreen {
// previous bottom layer popups
for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) {
callback(Stage::LayerPopup {
layer,
popup: &popup,
location: location + offset.as_global(),
})?;
}
}
}

if !has_fullscreen {
// background layer popups
for (layer, popup, location) in layer_popups(output, Layer::Background, element_filter) {
callback(Stage::LayerPopup {
layer,
popup: &popup,
location,
})?;
}
}

if let Some((_, has_fullscreen, offset)) = previous.as_ref() {
if !has_fullscreen {
// previous background layer popups
for (layer, popup, location) in layer_popups(output, Layer::Background, element_filter)
{
callback(Stage::LayerPopup {
layer,
popup: &popup,
location: location + offset.as_global(),
})?;
}
}
}

if !has_fullscreen {
// top-layer shell
for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) {
callback(Stage::LayerSurface { layer, location })?;
}

// sticky windows
if element_filter != ElementFilter::LayerShellOnly {
callback(Stage::Sticky(&set.sticky_layer))?;
}
}

if element_filter != ElementFilter::LayerShellOnly {
// workspace windows
callback(Stage::Workspace {
workspace,
offset: current_offset,
})?;

// previous workspace windows
if let Some((previous_handle, _, offset)) = previous.as_ref() {
let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else {
return ControlFlow::Break(Err(OutputNoMode));
};
callback(Stage::Workspace {
workspace,
offset: *offset,
})?;
}
}

if !has_fullscreen {
// bottom layer
for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) {
location += current_offset.as_global();
callback(Stage::LayerSurface { layer, location })?;
}
}

if let Some((_, has_fullscreen, offset)) = previous.as_ref() {
if !has_fullscreen {
// previous bottom layer
for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) {
location += offset.as_global();
callback(Stage::LayerSurface { layer, location })?;
}
}
}

if !has_fullscreen {
// background layer
for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) {
location += current_offset.as_global();
callback(Stage::LayerSurface { layer, location })?;
}
}

if let Some((_, has_fullscreen, offset)) = previous.as_ref() {
if !has_fullscreen {
// previous background layer
for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) {
location += offset.as_global();
callback(Stage::LayerSurface { layer, location })?;
}
}
}

ControlFlow::Continue(())
}

fn layer_popups<'a>(
output: &'a Output,
layer: Layer,
element_filter: ElementFilter,
) -> impl Iterator<Item = (LayerSurface, PopupKind, Point<i32, Global>)> + 'a {
layer_surfaces(output, layer, element_filter).flat_map(move |(surface, location)| {
let location_clone = location.clone();
let surface_clone = surface.clone();
PopupManager::popups_for_surface(surface.wl_surface()).map(move |(popup, popup_offset)| {
let offset = (popup_offset - popup.geometry().loc)
.as_local()
.to_global(output);
(surface_clone.clone(), popup, (location_clone + offset))
})
})
}

fn layer_surfaces<'a>(
output: &'a Output,
layer: Layer,
element_filter: ElementFilter,
) -> impl Iterator<Item = (LayerSurface, Point<i32, Global>)> + 'a {
// we want to avoid deadlocks on the layer-map in callbacks, so we need to clone the layer surfaces
let layers = {
let layer_map = layer_map_for_output(output);
layer_map
.layers_on(layer)
.rev()
.map(|s| (s.clone(), layer_map.layer_geometry(s).unwrap()))
.collect::<Vec<_>>()
};

layers
.into_iter()
.filter(move |(s, _)| {
!(element_filter == ElementFilter::ExcludeWorkspaceOverview
&& s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE)
})
.map(|(surface, geometry)| (surface, geometry.loc.as_local().to_global(output)))
}

0 comments on commit ad35c18

Please sign in to comment.