cosmic-comp/src/shell/mod.rs

4792 lines
171 KiB
Rust
Raw Normal View History

2023-05-12 20:01:37 +02:00
use calloop::LoopHandle;
use focus::target::WindowGroup;
use grabs::{MenuAlignment, SeatMoveGrabState};
2023-10-25 19:40:26 +02:00
use indexmap::IndexMap;
use layout::TilingExceptions;
use std::{
collections::HashMap,
sync::{atomic::Ordering, Mutex},
thread,
2023-07-06 00:03:26 +02:00
time::{Duration, Instant},
};
2023-07-05 23:48:10 +02:00
use wayland_backend::server::ClientId;
use crate::{
shell::{focus::FocusTarget, grabs::fullscreen_items, layout::tiling::PlaceholderType},
wayland::{
handlers::data_device::{self, get_dnd_icon},
protocols::workspace::{State as WState, WorkspaceCapabilities},
},
};
use cosmic_comp_config::{
workspace::{PinnedWorkspace, WorkspaceLayout, WorkspaceMode},
TileBehavior, ZoomConfig, ZoomMovement,
};
use cosmic_config::ConfigSet;
use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::TilingState;
2024-04-03 16:02:27 +02:00
use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection, ResizeDirection};
use cosmic_settings_config::{shortcuts, window_rules::ApplicationException};
use keyframe::{ease, functions::EaseInOutCubic};
use smithay::{
2024-06-07 19:44:59 +02:00
backend::{input::TouchSlot, renderer::element::RenderElementStates},
desktop::{
2024-06-07 19:44:59 +02:00
layer_map_for_output,
space::SpaceElement,
utils::{
surface_presentation_feedback_flags_from_states, surface_primary_scanout_output,
take_presentation_feedback_surface_tree, OutputPresentationFeedback,
},
LayerSurface, PopupKind, WindowSurface, WindowSurfaceType,
},
2023-01-18 20:23:41 +01:00
input::{
pointer::{
CursorImageStatus, CursorImageSurfaceData, Focus, GrabStartData as PointerGrabStartData,
},
2023-01-18 20:23:41 +01:00
Seat,
},
output::{Output, WeakOutput},
reexports::{
wayland_protocols::ext::session_lock::v1::server::ext_session_lock_v1::ExtSessionLockV1,
2024-04-10 15:49:08 +02:00
wayland_server::{protocol::wl_surface::WlSurface, Client},
},
2024-04-10 15:49:08 +02:00
utils::{IsAlive, Logical, Point, Rectangle, Serial, Size},
2022-03-30 22:00:44 +02:00
wayland::{
compositor::{with_states, SurfaceAttributes},
2023-01-18 20:23:41 +01:00
seat::WaylandFocus,
session_lock::LockSurface,
2024-04-10 15:49:08 +02:00
shell::wlr_layer::{KeyboardInteractivity, Layer, LayerSurfaceCachedState},
2023-11-07 18:46:25 +01:00
xdg_activation::XdgActivationState,
xwayland_keyboard_grab::XWaylandKeyboardGrab,
2022-03-30 22:00:44 +02:00
},
2023-01-18 20:23:41 +01:00
xwayland::X11Surface,
2022-03-30 22:00:44 +02:00
};
use tracing::error;
2022-05-02 17:43:58 +02:00
use crate::{
backend::render::animations::spring::{Spring, SpringParams},
2024-04-03 16:02:27 +02:00
config::Config,
utils::{prelude::*, quirks::WORKSPACE_OVERVIEW_NAMESPACE},
2023-11-07 18:46:25 +01:00
wayland::{
2024-02-23 17:25:40 +01:00
handlers::{
2024-09-18 16:02:41 +02:00
toplevel_management::minimize_rectangle, xdg_activation::ActivationContext,
xdg_shell::popup::get_popup_toplevel,
2024-02-23 17:25:40 +01:00
},
2023-11-07 18:46:25 +01:00
protocols::{
2024-04-10 15:49:08 +02:00
toplevel_info::{
toplevel_enter_output, toplevel_enter_workspace, toplevel_leave_output,
toplevel_leave_workspace, ToplevelInfoState,
},
2023-11-07 18:46:25 +01:00
workspace::{
WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState, WorkspaceUpdateGuard,
2023-11-07 18:46:25 +01:00
},
},
},
};
2022-03-24 20:32:31 +01:00
2023-03-07 22:20:44 +01:00
pub mod element;
2022-07-04 16:00:29 +02:00
pub mod focus;
pub mod grabs;
2022-08-30 13:28:36 +02:00
pub mod layout;
2024-04-05 13:53:35 +02:00
mod seats;
2022-03-24 20:32:31 +01:00
mod workspace;
2025-02-13 21:09:13 +01:00
pub mod zoom;
pub use self::element::{CosmicMapped, CosmicMappedRenderElement, CosmicSurface};
2024-04-05 13:53:35 +02:00
pub use self::seats::*;
2022-03-24 20:32:31 +01:00
pub use self::workspace::*;
2025-02-13 21:09:13 +01:00
use self::zoom::{OutputZoomState, ZoomState};
2024-04-03 16:02:27 +02:00
2022-09-28 12:01:29 +02:00
use self::{
2023-07-05 23:57:38 +02:00
element::{
resize_indicator::{resize_indicator, ResizeIndicator},
swap_indicator::{swap_indicator, SwapIndicator},
CosmicWindow, MaximizedState,
2023-07-05 23:57:38 +02:00
},
2024-04-03 16:02:27 +02:00
focus::target::{KeyboardFocusTarget, PointerFocusTarget},
grabs::{
tab_items, window_items, GrabStartData, Item, MenuGrab, MoveGrab, ReleaseMode, ResizeEdge,
ResizeGrab,
},
2023-12-07 19:53:41 +00:00
layout::{
floating::{FloatingLayout, ResizeState},
2023-12-07 19:53:41 +00:00
tiling::{NodeDesc, ResizeForkGrab, TilingLayout},
},
2022-09-28 12:01:29 +02:00
};
2022-03-24 20:32:31 +01:00
2023-07-06 00:03:26 +02:00
const ANIMATION_DURATION: Duration = Duration::from_millis(200);
const GESTURE_MAX_LENGTH: f64 = 150.0;
const GESTURE_POSITION_THRESHOLD: f64 = 0.5;
const GESTURE_VELOCITY_THRESHOLD: f64 = 0.02;
const MOVE_GRAB_Y_OFFSET: f64 = 16.;
const ACTIVATION_TOKEN_EXPIRE_TIME: Duration = Duration::from_secs(5);
2023-07-06 00:03:26 +02:00
#[derive(Debug, Clone)]
pub enum Trigger {
2024-04-03 16:02:27 +02:00
KeyboardSwap(shortcuts::Binding, NodeDesc),
KeyboardMove(shortcuts::Modifiers),
Pointer(u32),
Touch(TouchSlot),
}
#[derive(Debug, Clone)]
pub enum OverviewMode {
None,
Started(Trigger, Instant),
Active(Trigger),
Ended(Option<Trigger>, Instant),
}
2023-07-06 00:03:26 +02:00
impl OverviewMode {
pub fn alpha(&self) -> Option<f32> {
match self {
OverviewMode::Started(_, start) => {
let percentage = Instant::now().duration_since(*start).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32;
Some(ease(EaseInOutCubic, 0.0, 1.0, percentage))
2023-07-06 00:03:26 +02:00
}
OverviewMode::Active(_) => Some(1.0),
OverviewMode::Ended(_, end) => {
let percentage = Instant::now().duration_since(*end).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32;
if percentage < 1.0 {
Some(ease(EaseInOutCubic, 1.0, 0.0, percentage))
2023-07-06 00:03:26 +02:00
} else {
None
}
}
OverviewMode::None => None,
}
}
pub fn is_active(&self) -> bool {
matches!(self, OverviewMode::Started(_, _) | OverviewMode::Active(_))
}
pub fn active_trigger(&self) -> Option<&Trigger> {
if let OverviewMode::Started(trigger, _) | OverviewMode::Active(trigger) = self {
Some(trigger)
} else {
None
}
}
pub fn trigger(&self) -> Option<&Trigger> {
self.active_trigger().or_else(|| {
if let OverviewMode::Ended(trigger, _) = self {
trigger.as_ref()
} else {
None
}
})
}
2023-07-06 00:03:26 +02:00
}
2023-06-28 19:20:06 +02:00
#[derive(Debug, Clone)]
pub enum ResizeMode {
None,
2024-04-03 16:02:27 +02:00
Started(shortcuts::Binding, Instant, ResizeDirection),
Active(shortcuts::Binding, ResizeDirection),
2023-06-28 19:20:06 +02:00
Ended(Instant, ResizeDirection),
}
2023-07-06 00:03:26 +02:00
impl ResizeMode {
pub fn alpha(&self) -> Option<f32> {
match self {
ResizeMode::Started(_, start, _) => {
let percentage = Instant::now().duration_since(*start).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32;
Some(ease(EaseInOutCubic, 0.0, 1.0, percentage))
2023-07-06 00:03:26 +02:00
}
ResizeMode::Active(_, _) => Some(1.0),
2023-07-06 00:03:26 +02:00
ResizeMode::Ended(end, _) => {
let percentage = Instant::now().duration_since(*end).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32;
if percentage < 1.0 {
Some(ease(EaseInOutCubic, 1.0, 0.0, percentage))
2023-07-06 00:03:26 +02:00
} else {
None
}
}
ResizeMode::None => None,
}
}
pub fn active_binding(&self) -> Option<&shortcuts::Binding> {
if let ResizeMode::Started(binding, _, _) | ResizeMode::Active(binding, _) = self {
Some(binding)
} else {
None
}
}
pub fn active_direction(&self) -> Option<ResizeDirection> {
if let ResizeMode::Started(_, _, direction) | ResizeMode::Active(_, direction) = self {
Some(*direction)
} else {
None
}
}
2023-07-06 00:03:26 +02:00
}
2023-11-07 18:46:25 +01:00
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ActivationKey {
Wayland(WlSurface),
X11(u32),
}
impl From<&CosmicSurface> for ActivationKey {
fn from(value: &CosmicSurface) -> Self {
match value.0.underlying_surface() {
WindowSurface::Wayland(toplevel) => {
ActivationKey::Wayland(toplevel.wl_surface().clone())
}
WindowSurface::X11(s) => ActivationKey::X11(s.window_id()),
2023-11-07 18:46:25 +01:00
}
}
}
#[derive(Debug)]
pub struct PendingWindow {
pub surface: CosmicSurface,
pub seat: Seat<State>,
pub fullscreen: Option<Output>,
pub maximized: bool,
}
#[derive(Debug)]
pub struct PendingLayer {
pub surface: LayerSurface,
pub seat: Seat<State>,
pub output: Output,
}
2023-07-12 18:54:53 +02:00
#[derive(Debug)]
pub struct Shell {
2023-10-25 19:40:26 +02:00
pub workspaces: Workspaces,
pub pending_windows: Vec<PendingWindow>,
pub pending_layers: Vec<PendingLayer>,
2023-11-07 18:46:25 +01:00
pub pending_activations: HashMap<ActivationKey, ActivationContext>,
pub override_redirect_windows: Vec<X11Surface>,
pub session_lock: Option<SessionLock>,
2024-04-05 13:53:35 +02:00
pub seats: Seats,
pub previous_workspace_idx: Option<(Serial, WeakOutput, usize)>,
pub xwayland_keyboard_grab: Option<XWaylandKeyboardGrab<State>>,
theme: cosmic::Theme,
pub active_hint: bool,
overview_mode: OverviewMode,
swap_indicator: Option<SwapIndicator>,
2023-06-28 19:20:06 +02:00
resize_mode: ResizeMode,
2023-07-05 23:57:38 +02:00
resize_state: Option<(
KeyboardFocusTarget,
ResizeDirection,
ResizeEdge,
i32,
usize,
Output,
)>,
2023-07-06 00:03:26 +02:00
resize_indicator: Option<ResizeIndicator>,
zoom_state: Option<ZoomState>,
tiling_exceptions: TilingExceptions,
#[cfg(feature = "debug")]
pub debug_active: bool,
2022-03-24 20:32:31 +01:00
}
#[derive(Debug)]
pub struct SessionLock {
pub ext_session_lock: ExtSessionLockV1,
pub surfaces: HashMap<Output, LockSurface>,
}
#[derive(Debug, Clone, Copy)]
pub enum WorkspaceDelta {
Shortcut(Instant),
Gesture(f64),
GestureEnd(Instant, Spring),
// InvalidGesture(f64), TODO
// InvalidGestureEnd(Instant, Spring), TODO
}
impl WorkspaceDelta {
pub fn new_gesture() -> Self {
WorkspaceDelta::Gesture(0.0)
}
pub fn new_gesture_end(delta: f64, velocity: f64) -> Self {
let params: SpringParams = SpringParams::new(1.0, 1000.0, 0.0001);
WorkspaceDelta::GestureEnd(
Instant::now(),
Spring {
from: delta,
to: 1.0,
initial_velocity: velocity,
params,
},
)
}
pub fn new_shortcut() -> Self {
WorkspaceDelta::Shortcut(Instant::now())
}
pub fn is_animating(&self) -> bool {
matches!(
self,
WorkspaceDelta::Shortcut(_) | WorkspaceDelta::GestureEnd(_, _)
)
}
}
2022-09-28 12:01:29 +02:00
#[derive(Debug)]
pub struct WorkspaceSet {
previously_active: Option<(usize, WorkspaceDelta)>,
pub active: usize,
2024-01-05 18:33:16 +00:00
pub group: WorkspaceGroupHandle,
2023-01-27 13:26:28 +01:00
tiling_enabled: bool,
2023-10-25 19:40:26 +02:00
output: Output,
theme: cosmic::Theme,
2023-12-20 19:51:11 +00:00
pub sticky_layer: FloatingLayout,
2024-02-08 20:28:59 +01:00
pub minimized_windows: Vec<MinimizedWindow>,
2023-12-20 19:51:11 +00:00
pub workspaces: Vec<Workspace>,
2022-09-28 12:01:29 +02:00
}
fn create_workspace(
state: &mut WorkspaceUpdateGuard<'_, State>,
2023-10-25 19:40:26 +02:00
output: &Output,
2022-09-28 12:01:29 +02:00
group_handle: &WorkspaceGroupHandle,
active: bool,
2023-01-27 13:26:28 +01:00
tiling: bool,
theme: cosmic::Theme,
2022-09-28 12:01:29 +02:00
) -> Workspace {
2024-01-03 17:11:50 +00:00
let workspace_handle = state
.create_workspace(
&group_handle,
if tiling {
TilingState::TilingEnabled
} else {
TilingState::FloatingOnly
},
// TODO Set id for persistent workspaces
None,
2024-01-03 17:11:50 +00:00
)
.unwrap();
2022-09-28 12:01:29 +02:00
if active {
state.add_workspace_state(&workspace_handle, WState::Active);
}
state.set_workspace_capabilities(
&workspace_handle,
WorkspaceCapabilities::Activate
| WorkspaceCapabilities::SetTilingState
| WorkspaceCapabilities::Pin
| WorkspaceCapabilities::Move,
);
Workspace::new(workspace_handle, output.clone(), tiling, theme.clone())
2022-09-28 12:01:29 +02:00
}
fn create_workspace_from_pinned(
pinned: &PinnedWorkspace,
state: &mut WorkspaceUpdateGuard<'_, State>,
output: &Output,
group_handle: &WorkspaceGroupHandle,
active: bool,
theme: cosmic::Theme,
) -> Workspace {
let workspace_handle = state
.create_workspace(
&group_handle,
if pinned.tiling_enabled {
TilingState::TilingEnabled
} else {
TilingState::FloatingOnly
},
// TODO Set id for persistent workspaces
None,
)
.unwrap();
state.add_workspace_state(&workspace_handle, WState::Pinned);
if active {
state.add_workspace_state(&workspace_handle, WState::Active);
}
state.set_workspace_capabilities(
&workspace_handle,
WorkspaceCapabilities::Activate
| WorkspaceCapabilities::SetTilingState
| WorkspaceCapabilities::Pin
| WorkspaceCapabilities::Move,
);
Workspace::from_pinned(pinned, workspace_handle, output.clone(), theme.clone())
}
2024-01-05 18:33:16 +00:00
/* We will probably need this again at some point
fn merge_workspaces(
mut workspace: Workspace,
into: &mut Workspace,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
toplevel_info_state: &mut ToplevelInfoState<State, CosmicSurface>,
) {
if into.fullscreen.is_some() {
// Don't handle the returned original workspace, for this nieche case.
let _ = workspace.remove_fullscreen();
}
for element in workspace.mapped() {
// fixup toplevel state
for (toplevel, _) in element.windows() {
toplevel_info_state.toplevel_leave_workspace(&toplevel, &workspace.handle);
toplevel_info_state.toplevel_enter_workspace(&toplevel, &into.handle);
}
}
2024-02-23 17:25:40 +01:00
// TODO: merge minimized windows
into.tiling_layer.merge(workspace.tiling_layer);
into.floating_layer.merge(workspace.floating_layer);
workspace_state.remove_workspace(workspace.handle);
}
2024-01-05 18:33:16 +00:00
*/
2022-09-28 12:01:29 +02:00
impl WorkspaceSet {
fn new(
state: &mut WorkspaceUpdateGuard<'_, State>,
2023-10-25 19:40:26 +02:00
output: &Output,
2023-01-27 13:26:28 +01:00
tiling_enabled: bool,
theme: cosmic::Theme,
) -> WorkspaceSet {
let group_handle = state.create_workspace_group();
2023-12-20 19:51:11 +00:00
let sticky_layer = FloatingLayout::new(theme.clone(), output);
2022-09-28 12:01:29 +02:00
WorkspaceSet {
previously_active: None,
2022-09-28 12:01:29 +02:00
active: 0,
group: group_handle,
2023-01-27 13:26:28 +01:00
tiling_enabled,
theme,
2023-12-20 19:51:11 +00:00
sticky_layer,
2024-02-08 20:28:59 +01:00
minimized_windows: Vec::new(),
workspaces: Vec::new(),
2023-10-25 19:40:26 +02:00
output: output.clone(),
2022-09-28 12:01:29 +02:00
}
}
2023-05-25 00:10:24 +02:00
fn activate(
&mut self,
idx: usize,
workspace_delta: WorkspaceDelta,
2023-05-25 00:10:24 +02:00
state: &mut WorkspaceUpdateGuard<'_, State>,
) -> Result<bool, InvalidWorkspaceIndex> {
if idx >= self.workspaces.len() {
return Err(InvalidWorkspaceIndex);
}
// Animate if workspaces overview isn't open
let layer_map = layer_map_for_output(&self.output);
let animate = !layer_map
.layers()
.any(|l| l.namespace() == WORKSPACE_OVERVIEW_NAMESPACE);
2023-05-25 00:10:24 +02:00
if self.active != idx {
2022-09-28 12:01:29 +02:00
let old_active = self.active;
state.remove_workspace_state(&self.workspaces[old_active].handle, WState::Active);
2023-11-07 18:46:25 +01:00
state.remove_workspace_state(&self.workspaces[old_active].handle, WState::Urgent);
state.remove_workspace_state(&self.workspaces[idx].handle, WState::Urgent);
2022-09-28 12:01:29 +02:00
state.add_workspace_state(&self.workspaces[idx].handle, WState::Active);
self.previously_active = if animate {
Some((old_active, workspace_delta))
} else {
None
};
2022-09-28 12:01:29 +02:00
self.active = idx;
2023-05-25 00:10:24 +02:00
Ok(true)
} else {
// snap to workspace, when in between workspaces due to swipe gesture
if let Some((p_idx, p_delta)) = self.previously_active {
if matches!(p_delta, WorkspaceDelta::Gesture(..))
&& matches!(workspace_delta, WorkspaceDelta::GestureEnd(..))
{
self.previously_active = Some((p_idx, workspace_delta));
} else {
self.previously_active = None;
}
return Ok(true);
}
2023-05-25 00:10:24 +02:00
Ok(false)
2022-09-28 12:01:29 +02:00
}
}
fn activate_previous(
&mut self,
workspace_delta: WorkspaceDelta,
state: &mut WorkspaceUpdateGuard<'_, State>,
) -> Result<bool, InvalidWorkspaceIndex> {
if let Some((idx, _)) = self.previously_active {
return self.activate(idx, workspace_delta, state);
}
Err(InvalidWorkspaceIndex)
}
fn update_workspace_delta(&mut self, delta: f64) {
let easing = delta.clamp(0.0, GESTURE_MAX_LENGTH).abs() / GESTURE_MAX_LENGTH;
if let Some((idx, _)) = self.previously_active {
self.previously_active = Some((idx, WorkspaceDelta::Gesture(easing)));
}
}
fn set_output(&mut self, new_output: &Output, explicit: bool) {
2023-12-20 19:51:11 +00:00
self.sticky_layer.set_output(new_output);
for window in self.sticky_layer.windows() {
2024-04-10 15:49:08 +02:00
toplevel_leave_output(&window, &self.output);
toplevel_enter_output(&window, &new_output);
2023-12-20 19:51:11 +00:00
}
2023-10-25 19:40:26 +02:00
for workspace in &mut self.workspaces {
workspace.set_output(new_output, explicit);
2023-10-25 19:40:26 +02:00
}
self.output = new_output.clone();
}
fn refresh(&mut self) {
if let Some((_, start)) = self.previously_active {
match start {
WorkspaceDelta::Shortcut(st) => {
if Instant::now().duration_since(st).as_millis() as f32
>= ANIMATION_DURATION.as_millis() as f32
{
self.previously_active = None;
}
}
WorkspaceDelta::GestureEnd(st, spring) => {
if Instant::now().duration_since(st).as_millis() > spring.duration().as_millis()
{
self.previously_active = None;
}
}
_ => {}
}
} else {
self.workspaces[self.active].refresh();
}
2023-12-20 19:51:11 +00:00
self.sticky_layer.refresh();
2022-09-28 12:01:29 +02:00
}
2023-10-25 19:40:26 +02:00
fn add_empty_workspace(&mut self, state: &mut WorkspaceUpdateGuard<State>) {
let workspace = create_workspace(
2023-10-25 19:40:26 +02:00
state,
&self.output,
2023-10-25 19:41:30 +02:00
&self.group,
false,
self.tiling_enabled,
self.theme.clone(),
2023-10-25 19:41:30 +02:00
);
workspace_set_idx(
2023-10-25 19:40:26 +02:00
state,
2023-10-25 19:41:30 +02:00
self.workspaces.len() as u8 + 1,
&workspace.handle,
2024-01-05 18:33:16 +00:00
// this method is only used by code paths related to dynamic workspaces, so this should be fine
2023-10-25 19:41:30 +02:00
);
self.workspaces.push(workspace);
}
2022-09-28 12:01:29 +02:00
fn ensure_last_empty(
&mut self,
state: &mut WorkspaceUpdateGuard<State>,
xdg_activation_state: &XdgActivationState,
) {
2023-10-25 19:40:26 +02:00
// add empty at the end, if necessary
if self
.workspaces
.last()
.map_or(true, |last| !last.is_empty() || last.pinned)
{
2023-10-25 19:40:26 +02:00
self.add_empty_workspace(state);
}
// remove other empty workspaces
2022-09-28 12:01:29 +02:00
let len = self.workspaces.len();
let kept: Vec<bool> = self
.workspaces
.iter()
.enumerate()
.map(|(i, workspace)| {
let previous_is_empty = i > 0
&& self
.workspaces
.get(i - 1)
.map_or(false, |w| w.is_empty() && !w.pinned);
let keep = if workspace.can_auto_remove(xdg_activation_state) {
// Keep empty workspace if it's active, or it's the last workspace,
// and the previous worspace is not both active and empty.
i == self.active
|| (i == len - 1 && !(i == self.active + 1 && previous_is_empty))
} else {
true
};
if !keep {
state.remove_workspace(workspace.handle);
}
keep
})
.collect();
2022-09-28 12:01:29 +02:00
let mut iter = kept.iter();
2022-09-28 12:01:29 +02:00
self.workspaces.retain(|_| *iter.next().unwrap());
self.active -= kept
.iter()
.take(self.active + 1)
.filter(|kept| !**kept)
.count();
2022-11-08 09:38:43 +01:00
if kept.iter().any(|val| *val == false) {
self.update_workspace_idxs(state);
}
}
fn update_workspace_idxs(&self, state: &mut WorkspaceUpdateGuard<'_, State>) {
for (i, workspace) in self.workspaces.iter().enumerate() {
workspace_set_idx(state, i as u8 + 1, &workspace.handle);
2022-11-08 09:38:43 +01:00
}
2022-09-28 12:01:29 +02:00
}
fn post_remove_workspace(
&mut self,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
previous_active_handle: &WorkspaceHandle,
) {
if self.workspaces.is_empty() {
self.add_empty_workspace(workspace_state);
}
self.update_workspace_idxs(workspace_state);
self.active = self
.workspaces
.iter()
.position(|w| w.handle == *previous_active_handle)
.unwrap_or_else(|| {
let idx = self.workspaces.len() - 1;
let workspace = &self.workspaces[idx];
workspace_state.add_workspace_state(&workspace.handle, WState::Active);
idx
});
}
// Remove a workspace from the set, and return it, for adding to a different
// workspace set
fn remove_workspace(
&mut self,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
handle: &WorkspaceHandle,
) -> Option<Workspace> {
let previous_active_handle = self.workspaces[self.active].handle;
let idx = self.workspaces.iter().position(|w| w.handle == *handle)?;
let workspace = self.workspaces.remove(idx);
self.post_remove_workspace(workspace_state, &previous_active_handle);
Some(workspace)
}
// Remove all workspaces matched by the callback from the set
fn remove_workspaces(
&mut self,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
cb: impl Fn(&Workspace) -> bool,
) -> Vec<Workspace> {
let previous_active_handle = self.workspaces[self.active].handle;
let (prefers, doesnt) = self.workspaces.drain(..).partition(cb);
self.workspaces = doesnt;
self.post_remove_workspace(workspace_state, &previous_active_handle);
prefers
}
2022-03-24 20:32:31 +01:00
}
2021-12-21 18:57:09 +01:00
2022-09-28 12:01:29 +02:00
#[derive(Debug)]
2023-10-25 19:40:26 +02:00
pub struct Workspaces {
2023-12-20 19:51:11 +00:00
pub sets: IndexMap<Output, WorkspaceSet>,
2023-10-25 19:40:26 +02:00
backup_set: Option<WorkspaceSet>,
pub layout: WorkspaceLayout,
2023-10-25 19:40:26 +02:00
mode: WorkspaceMode,
autotile: bool,
autotile_behavior: TileBehavior,
theme: cosmic::Theme,
// Persisted workspace to add on first `output_add`
persisted_workspaces: Vec<PinnedWorkspace>,
2023-10-25 19:40:26 +02:00
}
impl Workspaces {
pub fn new(config: &Config, theme: cosmic::Theme) -> Workspaces {
2023-10-25 19:40:26 +02:00
Workspaces {
sets: IndexMap::new(),
backup_set: None,
layout: config.cosmic_conf.workspaces.workspace_layout,
mode: config.cosmic_conf.workspaces.workspace_mode,
autotile: config.cosmic_conf.autotile,
autotile_behavior: config.cosmic_conf.autotile_behavior,
theme,
persisted_workspaces: config.cosmic_conf.pinned_workspaces.clone(),
2023-10-25 19:40:26 +02:00
}
}
pub fn add_output(
&mut self,
output: &Output,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
) {
if self.sets.contains_key(output) {
return;
}
let mut set = self
.backup_set
.take()
.map(|mut set| {
set.set_output(output, false);
set
})
.unwrap_or_else(|| {
WorkspaceSet::new(workspace_state, &output, self.autotile, self.theme.clone())
});
2023-10-25 19:40:26 +02:00
workspace_state.add_group_output(&set.group, &output);
// If this is the first output added, create workspaces for pinned workspaces from config
for pinned in std::mem::take(&mut self.persisted_workspaces) {
let workspace = create_workspace_from_pinned(
&pinned,
workspace_state,
output,
&set.group,
false,
self.theme.clone(),
);
set.workspaces.push(workspace);
}
// Remove workspaces that prefer this output from other sets
let mut moved_workspaces = self
.sets
.values_mut()
.flat_map(|other_set| {
other_set.remove_workspaces(workspace_state, |w| w.prefers_output(output))
})
.collect::<Vec<_>>();
// Add `moved_workspaces` to set, and update output and index of workspaces
for workspace in &mut moved_workspaces {
workspace_state.remove_workspace_state(&workspace.handle, WState::Active);
workspace_state.move_workspace_to_group(set.group, workspace.handle);
}
set.workspaces.extend(moved_workspaces);
if set.workspaces.is_empty() {
set.add_empty_workspace(workspace_state);
}
set.update_workspace_idxs(workspace_state);
for (i, workspace) in set.workspaces.iter_mut().enumerate() {
workspace.set_output(output, false);
workspace.refresh();
if i == set.active {
workspace_state.add_workspace_state(&workspace.handle, WState::Active);
}
2023-10-25 19:40:26 +02:00
}
self.sets.insert(output.clone(), set);
2023-10-25 19:40:26 +02:00
}
2024-04-05 13:53:35 +02:00
pub fn remove_output<'a>(
2023-10-25 19:40:26 +02:00
&mut self,
output: &Output,
2024-04-05 13:53:35 +02:00
seats: impl Iterator<Item = &'a Seat<State>>,
2023-10-25 19:40:26 +02:00
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
2023-11-07 18:46:25 +01:00
xdg_activation_state: &XdgActivationState,
2023-10-25 19:40:26 +02:00
) {
if !self.sets.contains_key(output) {
return;
}
2024-06-10 20:41:29 +02:00
if let Some(set) = self.sets.shift_remove(output) {
2023-10-25 19:40:26 +02:00
{
let map = layer_map_for_output(output);
for surface in map.layers() {
surface.layer_surface().send_close();
}
2022-09-28 12:01:29 +02:00
}
2023-10-25 19:40:26 +02:00
// TODO: Heuristic which output to move to.
// It is supposed to be the *most* internal, we just pick the first one for now
// and hope enumeration order works in our favor.
let new_output = self.sets.get_index(0).map(|(o, _)| o.clone());
if let Some(new_output) = new_output {
for seat in seats {
if &seat.active_output() == output {
seat.set_active_output(&new_output);
}
if seat.focused_output().as_ref() == Some(output) {
seat.set_focused_output(None);
}
2023-10-25 19:41:30 +02:00
}
2023-10-25 19:40:26 +02:00
let new_set = self.sets.get_mut(&new_output).unwrap();
let workspace_group = new_set.group;
for (i, mut workspace) in set.workspaces.into_iter().enumerate() {
if workspace.can_auto_remove(xdg_activation_state) {
workspace_state.remove_workspace(workspace.handle);
} else {
// update workspace protocol state
workspace_state.remove_workspace_state(&workspace.handle, WState::Active);
workspace_state.move_workspace_to_group(workspace_group, workspace.handle);
// update mapping
workspace.set_output(&new_output, false);
workspace.refresh();
new_set.workspaces.push(workspace);
// If workspace was active, and the new set's active workspace is empty, make this workspace
// active on the new set. Instead of leaving an empty workspace active, and a previously active
// workspace hidden.
if i == set.active && new_set.workspaces[new_set.active].is_empty() {
workspace_state.remove_workspace_state(
&new_set.workspaces[new_set.active].handle,
WState::Active,
);
new_set.active = new_set.workspaces.len() - 1;
workspace_state.add_workspace_state(
&new_set.workspaces[new_set.active].handle,
WState::Active,
);
}
}
2023-10-25 19:40:26 +02:00
}
2024-02-23 17:25:40 +01:00
for window in set.sticky_layer.mapped() {
for (surface, _) in window.windows() {
2024-04-10 15:49:08 +02:00
toplevel_leave_output(&surface, output);
toplevel_enter_output(&surface, &new_output);
2024-02-23 17:25:40 +01:00
}
}
new_set.sticky_layer.merge(set.sticky_layer);
for window in set.minimized_windows.iter() {
for surface in window.windows() {
2024-04-10 15:49:08 +02:00
toplevel_leave_output(&surface, output);
toplevel_enter_output(&surface, &new_output);
2024-02-23 17:25:40 +01:00
}
}
new_set.minimized_windows.extend(set.minimized_windows);
if self.mode == WorkspaceMode::OutputBound {
workspace_state.remove_workspace_group(set.group);
} else {
workspace_state.remove_group_output(&workspace_group, output);
}
2023-10-25 19:40:26 +02:00
} else {
workspace_state.remove_group_output(&set.group, output);
self.backup_set = Some(set);
2022-11-10 17:22:16 +01:00
}
2024-01-05 18:33:16 +00:00
self.refresh(workspace_state, xdg_activation_state)
2022-09-28 12:01:29 +02:00
}
}
// Move workspace from one output to another, explicitly by the user
fn migrate_workspace(
&mut self,
from: &Output,
to: &Output,
handle: &WorkspaceHandle,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
) {
if !self.sets.contains_key(to) {
return;
}
if let Some(mut workspace) = self
.sets
.get_mut(from)
.and_then(|set| set.remove_workspace(workspace_state, handle))
{
let new_set = self.sets.get_mut(to).unwrap();
workspace_state.remove_workspace_state(&workspace.handle, WState::Active);
workspace_state.move_workspace_to_group(new_set.group, workspace.handle);
workspace.set_output(to, true);
workspace.refresh();
new_set.workspaces.insert(new_set.active + 1, workspace);
new_set.update_workspace_idxs(workspace_state);
}
}
// Move a workspace before/after a different workspace
pub fn move_workspace(
&mut self,
handle: &WorkspaceHandle,
other_handle: &WorkspaceHandle,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
after: bool,
) {
if handle == other_handle {
return;
}
let (Some(old_output), Some(new_output)) = (
self.space_for_handle(handle).map(|w| w.output.clone()),
self.space_for_handle(other_handle)
.map(|w| w.output.clone()),
) else {
return;
};
// Check which workspace is active on the new set; before removing from the
// old set in cause we're moving an active workspace within the same set.
let new_set = &mut self.sets[&new_output];
let previous_active_handle = new_set.workspaces[new_set.active].handle;
// Remove workspace from old set
let old_set = &mut self.sets[&old_output];
let mut workspace = if new_output != old_output {
old_set.remove_workspace(workspace_state, handle).unwrap()
} else {
// If set is the same, just remove it here without adding empty workspace,
// updating `active`, etc.
let idx = old_set
.workspaces
.iter()
.position(|w| w.handle == *handle)
.unwrap();
old_set.workspaces.remove(idx)
};
let new_set = &mut self.sets[&new_output];
if new_output != old_output {
workspace_state.remove_workspace_state(&workspace.handle, WState::Active);
workspace_state.move_workspace_to_group(new_set.group, workspace.handle);
workspace.set_output(&new_output, true);
workspace.refresh();
}
// Insert workspace into new set, relative to `other_handle`
let idx = new_set
.workspaces
.iter()
.position(|w| w.handle == *other_handle)
.unwrap();
let insert_idx = if after { idx + 1 } else { idx };
new_set.workspaces.insert(insert_idx, workspace);
new_set.active = new_set
.workspaces
.iter()
.position(|w| w.handle == previous_active_handle)
.unwrap();
new_set.update_workspace_idxs(workspace_state);
}
pub fn update_config(
&mut self,
config: &Config,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
2023-11-07 18:46:25 +01:00
xdg_activation_state: &XdgActivationState,
) {
let old_mode = self.mode;
self.mode = config.cosmic_conf.workspaces.workspace_mode;
self.layout = config.cosmic_conf.workspaces.workspace_layout;
if self.sets.len() <= 1 {
return;
}
match (old_mode, self.mode) {
(WorkspaceMode::Global, WorkspaceMode::OutputBound) => {
// We basically just unlink the existing spaces, so nothing needs to be updated
}
(WorkspaceMode::OutputBound, WorkspaceMode::Global) => {
// lets construct an iterator of all the pairs of workspaces we have to "merge"
let mut pairs = Vec::new();
if let Some(max) = self.sets.values().map(|set| set.workspaces.len()).max() {
let offset = self.sets.values().map(|set| set.active).max().unwrap();
for i in 0..max {
pairs.push(
self.sets
.values()
.map(|set| {
let idx = set.active as isize + i as isize - offset as isize;
if idx < 0 || idx >= set.workspaces.len() as isize {
None
} else {
Some(idx)
}
})
.collect::<Vec<_>>(),
);
}
}
for (j, pair) in pairs.iter().enumerate() {
for (i, x) in pair.iter().enumerate() {
// Fill up sets, where necessary
if x.is_none() {
// create missing workspace
let (output, set) = self.sets.get_index_mut(i).unwrap();
set.workspaces.insert(
j,
create_workspace(
workspace_state,
output,
&set.group,
false,
config.cosmic_conf.autotile,
self.theme.clone(),
),
);
}
// Otherwise we are fine
}
}
}
_ => {}
};
2024-01-05 18:33:16 +00:00
self.refresh(workspace_state, xdg_activation_state)
}
2023-10-25 19:40:26 +02:00
pub fn recalculate(&mut self) {
for set in self.sets.values_mut() {
set.sticky_layer.recalculate();
set.workspaces.iter_mut().for_each(|w| w.recalculate());
}
}
2023-10-25 19:40:26 +02:00
pub fn refresh(
&mut self,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
2023-11-07 18:46:25 +01:00
xdg_activation_state: &XdgActivationState,
2023-10-25 19:40:26 +02:00
) {
match self.mode {
WorkspaceMode::Global => {
let Some(max) = self.sets.values().map(|set| set.workspaces.len()).max() else {
return;
};
2024-01-05 18:33:16 +00:00
for set in self
.sets
.values_mut()
.filter(|set| set.workspaces.len() < max)
{
while set.workspaces.len() < max {
set.add_empty_workspace(workspace_state)
}
}
2023-10-25 19:40:26 +02:00
2024-01-05 18:33:16 +00:00
// add empty at the end, if necessary
if self
.sets
.values()
.flat_map(|set| set.workspaces.last())
.any(|w| !w.is_empty() || w.pinned)
2024-01-05 18:33:16 +00:00
{
for set in self.sets.values_mut() {
set.add_empty_workspace(workspace_state);
}
}
2023-10-25 19:40:26 +02:00
2024-01-05 18:33:16 +00:00
// remove empty workspaces in between, if they are not active
let len = self.sets[0].workspaces.len();
let mut active = self.sets[0].active;
let mut keep = vec![true; len];
for i in 0..len {
let has_windows = self
.sets
.values()
.any(|s| !s.workspaces[i].can_auto_remove(xdg_activation_state));
2023-10-25 19:40:26 +02:00
2024-01-05 18:33:16 +00:00
if !has_windows && i != active && i != len - 1 {
for workspace in self.sets.values().map(|s| &s.workspaces[i]) {
workspace_state.remove_workspace(workspace.handle);
2023-10-25 19:40:26 +02:00
}
2024-01-05 18:33:16 +00:00
keep[i] = false;
2023-10-25 19:40:26 +02:00
}
2024-01-05 18:33:16 +00:00
}
self.sets.values_mut().for_each(|s| {
let mut iter = keep.iter();
s.workspaces.retain(|_| *iter.next().unwrap());
});
active -= keep.iter().take(active + 1).filter(|keep| !**keep).count();
self.sets.values_mut().for_each(|s| {
s.active = active;
});
if keep.iter().any(|val| *val == false) {
for set in self.sets.values_mut() {
set.update_workspace_idxs(workspace_state);
2023-10-25 19:40:26 +02:00
}
}
2022-09-28 12:01:29 +02:00
}
2024-01-05 18:33:16 +00:00
WorkspaceMode::OutputBound => {
for set in self.sets.values_mut() {
set.ensure_last_empty(workspace_state, xdg_activation_state);
2023-10-25 19:40:26 +02:00
}
2024-01-05 18:33:16 +00:00
}
2023-10-25 19:40:26 +02:00
}
for set in self.sets.values_mut() {
set.refresh()
2022-09-28 12:01:29 +02:00
}
}
2023-10-25 19:40:26 +02:00
pub fn get(&self, num: usize, output: &Output) -> Option<&Workspace> {
self.sets
.get(output)
.and_then(|set| set.workspaces.get(num))
}
2022-09-28 12:01:29 +02:00
pub fn get_mut(&mut self, num: usize, output: &Output) -> Option<&mut Workspace> {
2023-10-25 19:40:26 +02:00
self.sets
2023-10-25 19:41:30 +02:00
.get_mut(output)
2023-10-25 19:40:26 +02:00
.and_then(|set| set.workspaces.get_mut(num))
2022-09-28 12:01:29 +02:00
}
pub fn active(
&self,
output: &Output,
) -> Option<(Option<(&Workspace, WorkspaceDelta)>, &Workspace)> {
self.sets
.get(output)
.or(self.backup_set.as_ref())
.map(|set| {
(
set.previously_active
.map(|(idx, start)| (&set.workspaces[idx], start)),
&set.workspaces[set.active],
)
})
2022-09-28 12:01:29 +02:00
}
pub fn active_mut(&mut self, output: &Output) -> Option<&mut Workspace> {
self.sets
.get_mut(output)
.or(self.backup_set.as_mut())
.map(|set| &mut set.workspaces[set.active])
2022-09-28 12:01:29 +02:00
}
pub fn active_num(&self, output: &Output) -> (Option<usize>, usize) {
let set = self.sets.get(output).or(self.backup_set.as_ref()).unwrap();
2023-10-25 19:41:30 +02:00
(set.previously_active.map(|(idx, _)| idx), set.active)
2022-09-28 12:01:29 +02:00
}
pub fn idx_for_handle(&self, output: &Output, handle: &WorkspaceHandle) -> Option<usize> {
let set = self.sets.get(output).unwrap();
set.workspaces
.iter()
.enumerate()
.find_map(|(i, w)| (&w.handle == handle).then_some(i))
}
2022-11-10 18:42:11 +01:00
pub fn len(&self, output: &Output) -> usize {
2023-10-25 19:40:26 +02:00
let set = self.sets.get(output).unwrap();
2023-10-25 19:41:30 +02:00
set.workspaces.len()
}
2023-10-25 19:40:26 +02:00
pub fn iter(&self) -> impl Iterator<Item = (&Output, &WorkspaceSet)> {
self.sets.iter()
2022-11-10 18:42:11 +01:00
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Output, &mut WorkspaceSet)> {
self.sets.iter_mut()
}
2022-09-28 12:01:29 +02:00
pub fn spaces(&self) -> impl Iterator<Item = &Workspace> {
2023-10-25 19:40:26 +02:00
self.sets.values().flat_map(|set| set.workspaces.iter())
2022-09-28 12:01:29 +02:00
}
pub fn space_for_handle(&self, handle: &WorkspaceHandle) -> Option<&Workspace> {
self.spaces().find(|w| &w.handle == handle)
}
pub fn space_for_handle_mut(&mut self, handle: &WorkspaceHandle) -> Option<&mut Workspace> {
self.spaces_mut().find(|w| &w.handle == handle)
}
2022-09-28 12:01:29 +02:00
pub fn spaces_for_output(&self, output: &Output) -> impl Iterator<Item = &Workspace> {
2023-10-25 19:40:26 +02:00
self.sets
.get(output)
2023-10-25 19:41:30 +02:00
.into_iter()
2023-10-25 19:40:26 +02:00
.flat_map(|set| set.workspaces.iter())
2022-09-28 12:01:29 +02:00
}
pub fn spaces_mut(&mut self) -> impl Iterator<Item = &mut Workspace> {
2023-10-25 19:40:26 +02:00
Box::new(
self.sets
.values_mut()
.flat_map(|set| set.workspaces.iter_mut()),
)
2022-09-28 12:01:29 +02:00
}
2023-01-27 13:26:28 +01:00
pub fn set_theme(&mut self, theme: cosmic::Theme) {
for (_, s) in &mut self.sets {
s.theme = theme.clone();
2024-04-10 15:49:08 +02:00
2023-12-20 19:51:11 +00:00
s.sticky_layer.theme = theme.clone();
2024-04-10 15:49:08 +02:00
s.sticky_layer.mapped().for_each(|m| {
m.update_theme(theme.clone());
});
2024-02-16 20:30:52 +01:00
for w in &mut s.workspaces {
w.tiling_layer.theme = theme.clone();
w.floating_layer.theme = theme.clone();
w.mapped().for_each(|m| {
m.update_theme(theme.clone());
2024-08-02 20:40:44 +02:00
});
}
}
self.force_redraw();
2024-08-02 20:40:44 +02:00
}
pub fn force_redraw(&mut self) {
2024-08-02 20:40:44 +02:00
for (_, s) in &mut self.sets {
s.sticky_layer.mapped().for_each(|m| {
m.force_redraw();
});
s.sticky_layer.refresh();
for w in &mut s.workspaces {
w.mapped().for_each(|m| {
m.force_redraw();
});
w.refresh();
w.dirty.store(true, Ordering::Relaxed);
w.recalculate();
}
}
}
2024-04-10 15:49:08 +02:00
pub fn update_autotile_behavior<'a>(
&mut self,
behavior: TileBehavior,
guard: &mut WorkspaceUpdateGuard<'_, State>,
2024-04-05 13:53:35 +02:00
seats: impl Iterator<Item = &'a Seat<State>>,
) {
self.autotile_behavior = behavior;
self.apply_tile_change(guard, seats);
}
2024-04-05 13:53:35 +02:00
fn apply_tile_change<'a>(
&mut self,
guard: &mut WorkspaceUpdateGuard<'_, State>,
2024-04-05 13:53:35 +02:00
seats: impl Iterator<Item = &'a Seat<State>>,
) {
2024-04-05 13:53:35 +02:00
let seats = seats.cloned().collect::<Vec<_>>();
for (_, set) in &mut self.sets {
set.tiling_enabled = self.autotile;
if matches!(self.autotile_behavior, TileBehavior::Global) {
// must apply change to all workspaces now
for w in &mut set.workspaces {
if w.tiling_enabled == self.autotile {
continue;
}
for s in &seats {
w.toggle_tiling(s, guard);
}
}
}
}
}
2024-04-10 15:49:08 +02:00
pub fn update_autotile<'a>(
&mut self,
autotile: bool,
guard: &mut WorkspaceUpdateGuard<'_, State>,
2024-04-05 13:53:35 +02:00
seats: impl Iterator<Item = &'a Seat<State>>,
) {
self.autotile = autotile;
self.apply_tile_change(guard, seats);
}
pub fn persist(&self, config: &Config) {
let pinned_workspaces: Vec<PinnedWorkspace> = self
.sets
.values()
.flat_map(|set| &set.workspaces)
.flat_map(|w| w.to_pinned())
.collect();
let config = config.cosmic_helper.clone();
thread::spawn(move || {
if let Err(err) = config.set("pinned_workspaces", pinned_workspaces) {
error!(?err, "Failed to update pinned_workspaces key");
}
});
}
2022-09-28 12:01:29 +02:00
}
2022-03-24 20:32:31 +01:00
2023-12-07 19:53:41 +00:00
#[derive(Debug)]
2023-05-25 00:10:24 +02:00
pub struct InvalidWorkspaceIndex;
2024-04-10 15:49:08 +02:00
impl Common {
2022-04-14 22:16:37 +02:00
pub fn add_output(&mut self, output: &Output) {
let mut shell = self.shell.write();
shell
.workspaces
.add_output(output, &mut self.workspace_state.update());
2024-04-10 15:49:08 +02:00
2025-02-13 21:09:13 +01:00
if let Some(state) = shell.zoom_state.as_ref() {
output.user_data().insert_if_missing_threadsafe(|| {
Mutex::new(OutputZoomState::new(
&state.seat,
output,
2025-03-25 17:31:48 +01:00
1.0,
2025-02-13 21:09:13 +01:00
state.increment,
state.movement,
self.event_loop_handle.clone(),
shell.theme.clone(),
))
});
}
2024-04-10 15:49:08 +02:00
std::mem::drop(shell);
self.refresh(); // fixes indicies of any moved workspaces
}
2024-04-05 13:53:35 +02:00
pub fn remove_output(&mut self, output: &Output) {
let mut shell = self.shell.write();
2024-04-10 15:49:08 +02:00
let shell_ref = &mut *shell;
shell_ref.workspaces.remove_output(
2023-10-25 19:40:26 +02:00
output,
2024-04-10 15:49:08 +02:00
shell_ref.seats.iter(),
2023-10-25 19:40:26 +02:00
&mut self.workspace_state.update(),
2023-11-07 18:46:25 +01:00
&self.xdg_activation_state,
2023-10-25 19:40:26 +02:00
);
2024-04-10 15:49:08 +02:00
std::mem::drop(shell);
2023-10-25 19:41:30 +02:00
self.refresh(); // cleans up excess of workspaces and empty workspaces
}
2022-03-24 20:32:31 +01:00
pub fn migrate_workspace(&mut self, from: &Output, to: &Output, handle: &WorkspaceHandle) {
if from == to {
return;
}
let mut shell = self.shell.write();
shell
.workspaces
.migrate_workspace(from, to, handle, &mut self.workspace_state.update());
2024-04-10 15:49:08 +02:00
std::mem::drop(shell);
}
2024-04-10 15:49:08 +02:00
pub fn update_config(&mut self) {
let mut shell = self.shell.write();
let shell_ref = &mut *shell;
shell_ref.active_hint = self.config.cosmic_conf.active_hint;
if let Some(zoom_state) = shell_ref.zoom_state.as_mut() {
zoom_state.increment = self.config.cosmic_conf.accessibility_zoom.increment;
zoom_state.movement = self.config.cosmic_conf.accessibility_zoom.view_moves;
2025-03-25 14:38:35 +01:00
zoom_state.show_overlay = self.config.cosmic_conf.accessibility_zoom.show_overlay;
for output in shell_ref.workspaces.sets.keys() {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
2025-03-25 17:31:48 +01:00
let mut output_state_ref = output_state.lock().unwrap();
let level = output_state_ref.level;
output_state_ref.update(level, false, zoom_state.movement, zoom_state.increment);
}
}
let mut workspace_state = self.workspace_state.update();
shell_ref.workspaces.update_config(
2024-04-10 15:49:08 +02:00
&self.config,
&mut workspace_state,
&self.xdg_activation_state,
);
}
#[profiling::function]
pub fn refresh(&mut self) {
self.xdg_activation_state
.retain_tokens(|_, data| data.timestamp.elapsed() < ACTIVATION_TOKEN_EXPIRE_TIME);
self.shell.write().refresh(
2024-04-10 15:49:08 +02:00
&self.xdg_activation_state,
&mut self.workspace_state.update(),
);
self.popups.cleanup();
self.toplevel_info_state.refresh(&self.workspace_state);
self.refresh_idle_inhibit();
}
pub fn refresh_idle_inhibit(&mut self) {
self.idle_inhibiting_surfaces.retain(|s| s.alive());
let is_inhibited = self.idle_inhibiting_surfaces.iter().any(|surface| {
with_states(surface, |states| {
surface_primary_scanout_output(surface, states).is_some()
})
});
self.idle_notifier_state.set_is_inhibited(is_inhibited);
2024-04-10 15:49:08 +02:00
}
2025-05-20 17:54:12 +02:00
#[profiling::function]
2024-04-10 15:49:08 +02:00
pub fn on_commit(&mut self, surface: &WlSurface) {
{
let shell = self.shell.read();
for seat in shell.seats.iter() {
if let Some(move_grab) = seat.user_data().get::<SeatMoveGrabState>() {
if let Some(grab_state) = move_grab.lock().unwrap().as_ref() {
let mapped = grab_state.element();
if mapped.active_window().wl_surface().as_deref() == Some(surface) {
mapped.on_commit(surface);
}
}
}
data_device::on_commit(surface, seat);
}
let is_cursor_image = shell.seats.iter().any(|seat| {
matches!(seat.cursor_image_status(), CursorImageStatus::Surface(ref cursor_surface) if cursor_surface == surface)
});
if is_cursor_image {
with_states(surface, |states| {
let cursor_image_attributes = states.data_map.get::<CursorImageSurfaceData>();
if let Some(mut cursor_image_attributes) =
cursor_image_attributes.map(|attrs| attrs.lock().unwrap())
{
let buffer_delta = states
.cached_state
.get::<SurfaceAttributes>()
.current()
.buffer_delta
.take();
if let Some(buffer_delta) = buffer_delta {
cursor_image_attributes.hotspot -= buffer_delta;
}
}
});
}
if let Some(mapped) = shell.element_for_surface(surface) {
mapped.on_commit(surface);
}
if let Some(surface) = shell
.workspaces
.spaces()
.find_map(|w| w.get_fullscreen().filter(|s| *s == surface))
{
surface.on_commit()
};
2024-04-10 15:49:08 +02:00
}
self.popups.commit(surface);
}
}
impl Shell {
pub fn new(config: &Config) -> Self {
let theme = cosmic::theme::system_preference();
let tiling_exceptions = layout::TilingExceptions::new(config.tiling_exceptions.iter());
2024-04-10 15:49:08 +02:00
Shell {
workspaces: Workspaces::new(config, theme.clone()),
seats: Seats::new(),
pending_windows: Vec::new(),
pending_layers: Vec::new(),
pending_activations: HashMap::new(),
override_redirect_windows: Vec::new(),
session_lock: None,
previous_workspace_idx: None,
xwayland_keyboard_grab: None,
2024-04-10 15:49:08 +02:00
theme,
active_hint: config.cosmic_conf.active_hint,
2024-04-10 15:49:08 +02:00
overview_mode: OverviewMode::None,
swap_indicator: None,
resize_mode: ResizeMode::None,
resize_state: None,
resize_indicator: None,
zoom_state: None,
tiling_exceptions,
#[cfg(feature = "debug")]
debug_active: false,
2024-04-10 15:49:08 +02:00
}
2022-09-28 12:01:29 +02:00
}
2023-05-25 00:10:24 +02:00
pub fn activate(
&mut self,
output: &Output,
idx: usize,
workspace_delta: WorkspaceDelta,
2024-04-10 15:49:08 +02:00
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
2023-10-25 19:40:26 +02:00
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
match &mut self.workspaces.mode {
WorkspaceMode::OutputBound => {
if let Some(set) = self.workspaces.sets.get_mut(output) {
if matches!(
self.overview_mode.active_trigger(),
Some(Trigger::Pointer(_) | Trigger::Touch(_))
) {
2023-10-25 19:40:26 +02:00
set.workspaces[set.active].tiling_layer.cleanup_drag();
}
2024-04-10 15:49:08 +02:00
set.activate(idx, workspace_delta, workspace_state)?;
2023-10-25 19:40:26 +02:00
2023-10-25 19:41:30 +02:00
let output_geo = output.geometry();
Ok(Some(
2023-10-25 19:40:26 +02:00
output_geo.loc
+ Point::from((output_geo.size.w / 2, output_geo.size.h / 2)),
2023-10-25 19:41:30 +02:00
))
} else {
Ok(None)
2023-10-25 19:40:26 +02:00
}
}
WorkspaceMode::Global => {
for set in self.workspaces.sets.values_mut() {
2024-04-10 15:49:08 +02:00
set.activate(idx, workspace_delta, workspace_state)?;
}
Ok(None)
}
}
}
pub fn update_workspace_delta(&mut self, output: &Output, delta: f64) {
match &mut self.workspaces.mode {
WorkspaceMode::OutputBound => {
if let Some(set) = self.workspaces.sets.get_mut(output) {
set.update_workspace_delta(delta);
}
}
WorkspaceMode::Global => {
for set in self.workspaces.sets.values_mut() {
set.update_workspace_delta(delta);
}
}
}
}
pub fn end_workspace_swipe(
&mut self,
output: &Output,
velocity: f64,
2024-04-10 15:49:08 +02:00
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
match &mut self.workspaces.mode {
WorkspaceMode::OutputBound => {
if let Some(set) = self.workspaces.sets.get_mut(output) {
if matches!(
self.overview_mode.active_trigger(),
Some(Trigger::Pointer(_) | Trigger::Touch(_))
) {
set.workspaces[set.active].tiling_layer.cleanup_drag();
}
if let Some((_, workspace_delta)) = set.previously_active {
match workspace_delta {
WorkspaceDelta::Gesture(delta) => {
if (velocity > 0.0 && velocity.abs() >= GESTURE_VELOCITY_THRESHOLD)
|| (velocity.abs() < GESTURE_VELOCITY_THRESHOLD
&& delta.abs() > GESTURE_POSITION_THRESHOLD)
{
set.activate(
set.active,
WorkspaceDelta::new_gesture_end(
delta.abs(),
velocity.abs(),
),
2024-04-10 15:49:08 +02:00
workspace_state,
)?;
} else {
set.activate_previous(
WorkspaceDelta::new_gesture_end(
1.0 - delta.abs(),
velocity.abs(),
),
2024-04-10 15:49:08 +02:00
workspace_state,
)?;
}
}
_ => {} // Do nothing
}
}
let output_geo = output.geometry();
Ok(Some(
output_geo.loc
+ Point::from((output_geo.size.w / 2, output_geo.size.h / 2)),
))
} else {
Ok(None)
}
}
WorkspaceMode::Global => {
for set in self.workspaces.sets.values_mut() {
if let Some((_, workspace_delta)) = set.previously_active {
match workspace_delta {
WorkspaceDelta::Gesture(delta) => {
if (velocity > 0.0 && velocity.abs() >= GESTURE_VELOCITY_THRESHOLD)
|| (velocity.abs() < GESTURE_VELOCITY_THRESHOLD
&& delta.abs() > GESTURE_POSITION_THRESHOLD)
{
set.activate(
set.active,
WorkspaceDelta::new_gesture_end(
delta.abs(),
velocity.abs(),
),
2024-04-10 15:49:08 +02:00
workspace_state,
)?;
} else {
set.activate_previous(
WorkspaceDelta::new_gesture_end(
1.0 - delta.abs(),
velocity.abs(),
),
2024-04-10 15:49:08 +02:00
workspace_state,
)?;
}
}
_ => {} // Do nothing
}
}
2023-10-25 19:40:26 +02:00
}
Ok(None)
}
}
2021-12-28 16:23:12 +01:00
}
pub fn active_space(&self, output: &Output) -> Option<&Workspace> {
self.workspaces.active(output).map(|(_, active)| active)
2022-03-24 20:32:31 +01:00
}
2021-12-21 18:57:09 +01:00
pub fn active_space_mut(&mut self, output: &Output) -> Option<&mut Workspace> {
2023-07-21 16:08:55 +02:00
self.workspaces.active_mut(output)
2021-12-21 18:57:09 +01:00
}
/// get the parent output of the window which has keyboard focus (for a given seat)
pub fn get_output_for_focus(&self, seat: &Seat<State>) -> Option<Output> {
let mut focus_target = seat.get_keyboard().unwrap().current_focus()?;
if let KeyboardFocusTarget::Popup(popup) = &focus_target {
let new_target = match popup {
PopupKind::Xdg(popup) => {
if let Some(parent) = popup.get_parent_surface() {
self.element_for_surface(&parent).cloned()
} else {
None
}
}
PopupKind::InputMethod(popup) => {
if let Some(parent) = popup.get_parent() {
self.element_for_surface(&parent.surface).cloned()
} else {
None
}
}
}?;
focus_target = KeyboardFocusTarget::Element(new_target);
} else if let KeyboardFocusTarget::XWaylandGrab(surface) = &focus_target {
if let Some(new_target) = self.element_for_surface(surface) {
focus_target = KeyboardFocusTarget::Element(new_target.clone());
} else if let Some(new_target) = self
.workspaces
.spaces()
.find_map(|w| w.get_fullscreen().filter(|s| *s == surface))
{
focus_target = KeyboardFocusTarget::Fullscreen(new_target.clone());
} else if let Some(or) = self
.override_redirect_windows
.iter()
.find(|w| w.wl_surface().as_ref() == Some(surface))
{
// Find output the override redirect window overlaps the most with
let or_geo = or.geometry().as_global();
let (output, _) = self
.outputs()
.filter_map(|o| Some((o, o.geometry().intersection(or_geo)?)))
.max_by_key(|(_, intersection)| intersection.size.w * intersection.size.h)?;
return Some(output.clone());
} else {
return None;
}
}
match focus_target {
KeyboardFocusTarget::Element(elem) => {
if seat
.user_data()
.get::<SeatMoveGrabState>()
.is_some_and(|state| {
state
.lock()
.unwrap()
.as_ref()
.is_some_and(|state| state.element() == elem)
})
{
return Some(seat.active_output());
}
self.outputs()
.find(|output| {
let is_sticky = self
.workspaces
.sets
.get(*output)
.unwrap()
.sticky_layer
.mapped()
.any(|m| m == &elem);
let workspace = self.active_space(output).unwrap();
let is_mapped = workspace.mapped().any(|m| m == &elem);
is_sticky || is_mapped
})
.cloned()
}
KeyboardFocusTarget::Fullscreen(elem) => self
.outputs()
.find(|output| {
let workspace = self.active_space(&output).unwrap();
workspace.get_fullscreen() == Some(&elem)
})
.cloned(),
KeyboardFocusTarget::Group(WindowGroup { node, .. }) => self
.outputs()
.find(|output| {
self.workspaces
.active(&output)
.unwrap()
.1
.tiling_layer
.has_node(&node)
})
.cloned(),
KeyboardFocusTarget::LayerSurface(layer) => self
.outputs()
.find(|output| layer_map_for_output(output).layers().any(|l| l == &layer))
.cloned(),
KeyboardFocusTarget::LockSurface(surface) => self
.session_lock
.as_ref()?
.surfaces
.iter()
.find_map(|(output, s)| (s == &surface).then_some(output))
.cloned(),
KeyboardFocusTarget::XWaylandGrab(_) => unreachable!(),
KeyboardFocusTarget::Popup(_) => unreachable!(),
}
}
/// Coerce a keyboard focus target into a CosmicMapped element. This is useful when performing window specific
/// actions, such as closing a window
pub fn focused_element(&self, focus_target: &KeyboardFocusTarget) -> Option<CosmicMapped> {
match focus_target {
KeyboardFocusTarget::Element(window) => Some(window).cloned(),
KeyboardFocusTarget::Popup(PopupKind::Xdg(popup)) => {
if let Some(parent) = popup.get_parent_surface() {
self.element_for_surface(&parent).cloned()
} else {
None
}
}
KeyboardFocusTarget::Popup(PopupKind::InputMethod(popup)) => {
if let Some(parent) = popup.get_parent() {
self.element_for_surface(&parent.surface).cloned()
} else {
None
}
}
_ => None,
}
}
/// Close the focused keyboard focus target
pub fn close_focused(&self, focus_target: &KeyboardFocusTarget) {
match focus_target {
KeyboardFocusTarget::Group(_group) => {
//TODO: decide if we want close actions to apply to groups
return;
}
KeyboardFocusTarget::Fullscreen(surface) => {
surface.close();
}
x => {
if let Some(mapped) = self.focused_element(x) {
mapped.send_close();
}
}
}
}
pub fn refresh_active_space(&mut self, output: &Output) {
if let Some(w) = self.workspaces.active_mut(output) {
w.refresh()
}
2023-11-07 18:46:25 +01:00
}
2023-12-20 19:58:43 +00:00
pub fn visible_output_for_surface(&self, surface: &WlSurface) -> Option<&Output> {
if let Some(session_lock) = &self.session_lock {
2023-12-20 19:58:43 +00:00
return session_lock
.surfaces
.iter()
.find(|(_, v)| v.wl_surface() == surface)
2023-12-20 19:58:43 +00:00
.map(|(k, _)| k);
}
2023-12-20 19:58:43 +00:00
self.outputs()
// layer map surface?
.find(|o| {
let map = layer_map_for_output(o);
map.layer_for_surface(surface, WindowSurfaceType::ALL)
.is_some()
})
2023-12-20 19:58:43 +00:00
// pending layer map surface?
.or_else(|| {
self.pending_layers.iter().find_map(|pending| {
2023-11-14 17:30:11 +01:00
let mut found = false;
pending.surface.with_surfaces(|s, _| {
2023-11-14 17:30:11 +01:00
if s == surface {
found = true;
}
});
found.then_some(&pending.output)
})
2023-12-20 19:58:43 +00:00
})
// override redirect window?
.or_else(|| {
self.outputs().find(|o| {
2023-12-20 20:51:04 +00:00
self.override_redirect_windows.iter().any(|or| {
if or.wl_surface().as_ref() == Some(surface) {
or.geometry()
.as_global()
.intersection(o.geometry())
.is_some()
} else {
false
}
2023-01-23 18:25:01 +01:00
})
2023-12-20 20:51:04 +00:00
})
2023-12-20 19:58:43 +00:00
})
// sticky window ?
.or_else(|| {
self.outputs().find(|o| {
self.workspaces.sets[*o]
.sticky_layer
.mapped()
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
})
})
// normal window?
.or_else(|| {
self.outputs().find(|o| {
let workspace = self.active_space(o).unwrap();
workspace
.get_fullscreen()
.is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL))
|| workspace
.mapped()
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
2023-12-20 19:58:43 +00:00
})
})
// cursor and drag surfaces
.or_else(|| {
self.outputs().find(|o| {
self.seats
.iter()
.filter(|seat| seat.active_output() == **o)
.any(|seat| {
let cursor_status = seat.cursor_image_status();
if let CursorImageStatus::Surface(s) = cursor_status {
if s == *surface {
return true;
}
}
if let Some(move_grab) = seat.user_data().get::<SeatMoveGrabState>() {
if let Some(grab_state) = move_grab.lock().unwrap().as_ref() {
for (window, _) in grab_state.element().windows() {
let mut matches = false;
window.0.with_surfaces(|s, _| {
matches |= s == surface;
});
if matches {
return true;
}
}
}
}
get_dnd_icon(seat).is_some_and(|icon| icon.surface == *surface)
})
})
})
}
pub fn workspace_for_surface(&self, surface: &WlSurface) -> Option<(WorkspaceHandle, Output)> {
2023-10-25 19:40:26 +02:00
match self.outputs().find(|o| {
2022-11-03 18:51:27 +01:00
let map = layer_map_for_output(o);
map.layer_for_surface(surface, WindowSurfaceType::ALL)
.is_some()
}) {
Some(output) => self
.workspaces
.spaces()
2023-10-25 19:40:26 +02:00
.find(move |workspace| workspace.output() == output)
.map(|w| (w.handle.clone(), output.clone())),
2022-11-03 18:51:27 +01:00
None => self
.workspaces
.spaces()
2023-10-25 19:40:26 +02:00
.find(|w| {
w.get_fullscreen()
.is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL))
|| w.mapped()
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
|| w.minimized_windows.iter().any(|m| {
m.mapped()
.is_some_and(|m| m.has_surface(surface, WindowSurfaceType::ALL))
})
2022-11-03 18:51:27 +01:00
})
2023-10-25 19:40:26 +02:00
.map(|w| (w.handle.clone(), w.output().clone())),
2022-11-03 18:51:27 +01:00
}
}
pub fn element_for_surface<S>(&self, surface: &S) -> Option<&CosmicMapped>
where
CosmicSurface: PartialEq<S>,
{
self.workspaces.sets.values().find_map(|set| {
2024-02-23 17:25:40 +01:00
set.minimized_windows
.iter()
.find(|w| w.windows().any(|s| &s == surface))
.and_then(|w| w.mapped())
.or_else(|| {
set.sticky_layer
.mapped()
.find(|w| w.windows().any(|(s, _)| &s == surface))
})
.or_else(|| {
set.workspaces
.iter()
2023-12-20 20:51:04 +00:00
.find_map(|w| w.element_for_surface(surface))
})
})
2022-03-24 20:32:31 +01:00
}
2021-12-28 16:23:12 +01:00
pub fn is_surface_mapped<S>(&self, surface: &S) -> bool
where
CosmicSurface: PartialEq<S>,
{
self.workspaces.sets.values().any(|set| {
set.minimized_windows
.iter()
.any(|w| w.windows().any(|s| &s == surface))
|| set
.sticky_layer
.mapped()
.any(|m| m.windows().any(|(s, _)| &s == surface))
|| set.workspaces.iter().any(|w| {
w.get_fullscreen().is_some_and(|s| s == surface)
|| w.minimized_windows
.iter()
.any(|m| m.windows().any(|s| &s == surface))
|| w.floating_layer
.mapped()
.any(|m| m.windows().any(|(s, _)| &s == surface))
|| w.tiling_layer
.mapped()
.any(|(m, _)| m.windows().any(|(s, _)| &s == surface))
})
})
}
2022-09-28 12:01:29 +02:00
pub fn space_for(&self, mapped: &CosmicMapped) -> Option<&Workspace> {
2024-02-23 17:25:40 +01:00
self.workspaces.spaces().find(|workspace| {
workspace.mapped().any(|m| m == mapped)
|| workspace
.minimized_windows
.iter()
.any(|m| m.mapped() == Some(mapped))
2024-02-23 17:25:40 +01:00
})
2022-09-28 12:01:29 +02:00
}
pub fn space_for_mut(&mut self, mapped: &CosmicMapped) -> Option<&mut Workspace> {
2024-02-23 17:25:40 +01:00
self.workspaces.spaces_mut().find(|workspace| {
workspace.mapped().any(|m| m == mapped)
|| workspace
.minimized_windows
.iter()
.any(|m| m.mapped() == Some(mapped))
2024-02-23 17:25:40 +01:00
})
2021-12-28 16:23:12 +01:00
}
2022-07-04 16:00:29 +02:00
2023-10-25 19:40:26 +02:00
pub fn outputs(&self) -> impl DoubleEndedIterator<Item = &Output> {
self.workspaces.sets.keys()
}
2021-12-28 16:23:12 +01:00
2023-11-16 19:28:00 +01:00
pub fn next_output(&self, current_output: &Output, direction: Direction) -> Option<&Output> {
let current_output_geo = current_output.geometry();
self.outputs()
.filter(|o| *o != current_output)
.filter(|o| {
let geo = o.geometry();
match direction {
Direction::Left | Direction::Right => {
geo.loc.y < current_output_geo.loc.y + current_output_geo.size.h
&& geo.loc.y + geo.size.h > current_output_geo.loc.y
2023-11-16 19:28:00 +01:00
}
Direction::Up | Direction::Down => {
geo.loc.x < current_output_geo.loc.x + current_output_geo.size.w
&& geo.loc.x + geo.size.w > current_output_geo.loc.x
2023-11-16 19:28:00 +01:00
}
}
})
.filter_map(|o| {
let origin = o.geometry().loc;
let res = match direction {
Direction::Up => current_output_geo.loc.y - origin.y,
Direction::Down => origin.y - current_output_geo.loc.y,
Direction::Left => current_output_geo.loc.x - origin.x,
Direction::Right => origin.x - current_output_geo.loc.x,
};
if res > 0 {
Some((o, res))
} else {
None
}
})
.min_by_key(|(_, res)| *res)
.map(|(o, _)| o)
}
pub fn builtin_output(&self) -> Option<&Output> {
self.outputs().find(|output| {
let name = output.name();
name.starts_with("eDP-") || name.starts_with("LVDS-") || name.starts_with("DSI-")
})
}
2023-10-25 19:40:26 +02:00
pub fn global_space(&self) -> Rectangle<i32, Global> {
self.outputs()
.fold(
2023-10-25 19:40:26 +02:00
Option::<Rectangle<i32, Global>>::None,
|maybe_geo, output| match maybe_geo {
Some(rect) => Some(rect.merge(output.geometry())),
None => Some(output.geometry()),
},
)
2023-10-25 19:40:26 +02:00
.unwrap_or_else(Rectangle::default)
}
2023-05-12 20:01:37 +02:00
pub fn animations_going(&self) -> bool {
self.workspaces.sets.values().any(|set| {
set.previously_active
.as_ref()
.is_some_and(|(_, delta)| delta.is_animating())
|| set.sticky_layer.animations_going()
}) || !matches!(
self.overview_mode,
OverviewMode::None | OverviewMode::Active(_)
) || !matches!(
self.resize_mode,
ResizeMode::None | ResizeMode::Active(_, _)
) || self
.workspaces
.spaces()
.any(|workspace| workspace.animations_going())
2025-03-25 17:31:48 +01:00
|| self.zoom_state.as_ref().is_some_and(|_| {
self.outputs().any(|o| {
o.user_data()
.get::<Mutex<OutputZoomState>>()
.is_some_and(|state| state.lock().unwrap().is_animating())
})
2025-01-24 18:07:33 +01:00
})
2023-05-12 20:01:37 +02:00
}
2023-07-05 23:48:10 +02:00
pub fn update_animations(&mut self) -> HashMap<ClientId, Client> {
let mut clients = HashMap::new();
for set in self.workspaces.sets.values_mut() {
set.sticky_layer.update_animation_state();
}
2023-05-12 20:01:37 +02:00
for workspace in self.workspaces.spaces_mut() {
2023-07-05 23:48:10 +02:00
clients.extend(workspace.update_animations());
2023-05-12 20:01:37 +02:00
}
2023-07-05 23:48:10 +02:00
clients
2023-05-12 20:01:37 +02:00
}
pub fn set_overview_mode(
&mut self,
enabled: Option<Trigger>,
2023-09-29 21:33:16 +02:00
evlh: LoopHandle<'static, crate::state::State>,
) {
if let Some(trigger) = enabled {
if !matches!(
self.overview_mode,
OverviewMode::Started(_, _) | OverviewMode::Active(_)
) {
if matches!(trigger, Trigger::KeyboardSwap(_, _)) {
self.swap_indicator = Some(swap_indicator(evlh, self.theme.clone()));
}
self.overview_mode = OverviewMode::Started(trigger, Instant::now());
}
} else {
if matches!(
self.overview_mode,
OverviewMode::Started(_, _) | OverviewMode::Active(_)
) {
let (reverse_duration, trigger) =
if let OverviewMode::Started(trigger, start) = self.overview_mode.clone() {
(
ANIMATION_DURATION
- Instant::now().duration_since(start).min(ANIMATION_DURATION),
Some(trigger),
)
} else {
(Duration::ZERO, self.overview_mode.active_trigger().cloned())
};
self.overview_mode =
OverviewMode::Ended(trigger, Instant::now() - reverse_duration);
}
}
}
2024-04-05 13:53:35 +02:00
pub fn overview_mode(&self) -> (OverviewMode, Option<SwapIndicator>) {
if let OverviewMode::Started(trigger, timestamp) = &self.overview_mode {
if Instant::now().duration_since(*timestamp) > ANIMATION_DURATION {
return (
OverviewMode::Active(trigger.clone()),
self.swap_indicator.clone(),
);
}
}
if let OverviewMode::Ended(_, timestamp) = &self.overview_mode {
if Instant::now().duration_since(*timestamp) > ANIMATION_DURATION {
2024-04-05 13:53:35 +02:00
return (OverviewMode::None, None);
}
}
(self.overview_mode.clone(), self.swap_indicator.clone())
}
2023-07-06 00:03:26 +02:00
pub fn set_resize_mode(
&mut self,
2024-04-03 16:02:27 +02:00
enabled: Option<(shortcuts::Binding, ResizeDirection)>,
2023-07-06 00:03:26 +02:00
config: &Config,
2023-09-29 21:33:16 +02:00
evlh: LoopHandle<'static, crate::state::State>,
2023-07-06 00:03:26 +02:00
) {
2023-06-28 19:20:06 +02:00
if let Some((pattern, direction)) = enabled {
if let ResizeMode::Started(old_pattern, _, old_direction) = &mut self.resize_mode {
*old_pattern = pattern;
*old_direction = direction;
} else {
self.resize_mode = ResizeMode::Started(pattern, Instant::now(), direction);
}
self.resize_indicator = Some(resize_indicator(
direction,
config,
evlh,
self.theme.clone(),
));
2023-06-28 19:20:06 +02:00
} else {
if let Some(direction) = self.resize_mode.active_direction() {
self.resize_mode = ResizeMode::Ended(Instant::now(), direction);
2023-07-06 18:20:10 +02:00
if let Some((_, direction, edge, _, _, _)) = self.resize_state.as_ref() {
self.finish_resize(*direction, *edge);
}
2023-06-28 19:20:06 +02:00
}
}
}
2024-04-05 13:53:35 +02:00
pub fn resize_mode(&self) -> (ResizeMode, Option<ResizeIndicator>) {
if let ResizeMode::Started(binding, timestamp, direction) = &self.resize_mode {
if Instant::now().duration_since(*timestamp) > ANIMATION_DURATION {
return (
ResizeMode::Active(binding.clone(), *direction),
self.resize_indicator.clone(),
);
}
}
2023-06-28 19:20:06 +02:00
if let ResizeMode::Ended(timestamp, _) = self.resize_mode {
if Instant::now().duration_since(timestamp) > ANIMATION_DURATION {
2024-04-05 13:53:35 +02:00
return (ResizeMode::None, None);
2023-06-28 19:20:06 +02:00
}
}
2023-07-06 00:03:26 +02:00
(self.resize_mode.clone(), self.resize_indicator.clone())
}
pub fn stacking_indicator(
&self,
output: &Output,
layer: ManagedLayer,
) -> Option<Rectangle<i32, Local>> {
match layer {
ManagedLayer::Sticky => self
.workspaces
.sets
.get(output)
.and_then(|set| set.sticky_layer.stacking_indicator()),
ManagedLayer::Floating => self
.active_space(output)?
.floating_layer
.stacking_indicator(),
ManagedLayer::Tiling => self.active_space(output)?.tiling_layer.stacking_indicator(),
ManagedLayer::Fullscreen => None,
}
}
2025-02-05 18:05:08 +01:00
pub fn trigger_zoom(
&mut self,
seat: &Seat<State>,
2025-03-25 17:31:48 +01:00
output: Option<&Output>,
2025-02-05 18:05:08 +01:00
level: f64,
2025-02-13 21:09:13 +01:00
zoom_config: &ZoomConfig,
2025-02-05 18:05:08 +01:00
animate: bool,
2025-02-13 21:09:13 +01:00
loop_handle: &LoopHandle<'static, State>,
2025-02-05 18:05:08 +01:00
) {
2025-01-24 18:07:33 +01:00
if self.zoom_state.is_none() && level == 1. {
return;
}
2025-03-25 17:31:48 +01:00
let outputs = output.map(|o| vec![o]).unwrap_or(self.outputs().collect());
if self.zoom_state.is_none() {
2025-01-24 18:07:33 +01:00
for output in self.outputs() {
2025-03-25 17:31:48 +01:00
output.user_data().insert_if_missing_threadsafe(|| {
2025-02-13 21:09:13 +01:00
Mutex::new(OutputZoomState::new(
seat,
output,
2025-03-25 17:31:48 +01:00
1.0,
2025-02-13 21:09:13 +01:00
zoom_config.increment,
zoom_config.view_moves,
loop_handle.clone(),
self.theme.clone(),
))
});
2025-01-24 18:07:33 +01:00
}
2025-03-25 17:31:48 +01:00
}
2025-03-25 14:38:35 +01:00
2025-03-25 17:31:48 +01:00
let mut toggled = self.zoom_state.is_none();
if let Some(old_state) = self.zoom_state.as_ref() {
if &old_state.seat != seat {
return;
}
}
for output in &outputs {
let output_state = output.user_data().get::<Mutex<OutputZoomState>>().unwrap();
output_state.lock().unwrap().update(
level,
animate,
zoom_config.view_moves,
zoom_config.increment,
);
}
let all_outputs_off = self.outputs().all(|o| {
o.user_data()
.get::<Mutex<OutputZoomState>>()
.unwrap()
.lock()
.unwrap()
.current_level()
== 1.0
});
toggled = toggled || all_outputs_off;
2025-01-24 18:07:33 +01:00
2025-02-17 20:30:26 +01:00
if toggled {
2025-03-25 17:31:48 +01:00
let value = !all_outputs_off;
2025-02-17 20:30:26 +01:00
let _ = loop_handle.insert_idle(move |state| {
state.common.a11y_state.set_screen_magnifier(value);
});
}
2025-01-24 18:07:33 +01:00
self.zoom_state = Some(ZoomState {
seat: seat.clone(),
2025-03-25 14:38:35 +01:00
show_overlay: zoom_config.show_overlay,
2025-02-13 21:09:13 +01:00
increment: zoom_config.increment,
movement: zoom_config.view_moves,
2025-01-24 18:07:33 +01:00
});
}
pub fn update_focal_point(
2025-01-23 15:45:19 +01:00
&mut self,
seat: &Seat<State>,
2025-01-24 18:07:33 +01:00
original_position: Point<f64, Global>,
movement: ZoomMovement,
2025-01-23 15:45:19 +01:00
) {
2025-01-24 18:07:33 +01:00
if let Some(state) = self.zoom_state.as_mut() {
if &state.seat != seat {
return;
}
2025-02-13 21:09:13 +01:00
let cursor_position = seat.get_pointer().unwrap().current_location().as_global();
2025-01-24 18:07:33 +01:00
2025-02-13 21:09:13 +01:00
state.update_focal_point(
&seat.active_output(),
cursor_position,
original_position,
movement,
);
2025-01-23 15:45:19 +01:00
}
}
2025-02-13 21:09:13 +01:00
pub fn zoom_state(&self) -> Option<&ZoomState> {
self.zoom_state.as_ref()
}
2024-04-10 15:49:08 +02:00
fn refresh(
&mut self,
xdg_activation_state: &XdgActivationState,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
) {
match &self.overview_mode {
OverviewMode::Started(trigger, timestamp)
if Instant::now().duration_since(*timestamp) > ANIMATION_DURATION =>
{
self.overview_mode = OverviewMode::Active(trigger.clone());
}
OverviewMode::Ended(_, timestamp)
if Instant::now().duration_since(*timestamp) > ANIMATION_DURATION =>
{
2024-04-05 13:53:35 +02:00
self.overview_mode = OverviewMode::None;
self.swap_indicator = None;
}
_ => {}
2024-04-05 13:53:35 +02:00
}
match &self.resize_mode {
ResizeMode::Started(binding, timestamp, direction)
if Instant::now().duration_since(*timestamp) > ANIMATION_DURATION =>
{
self.resize_mode = ResizeMode::Active(binding.clone(), *direction);
}
ResizeMode::Ended(timestamp, _)
if Instant::now().duration_since(*timestamp) > ANIMATION_DURATION =>
{
2024-04-05 13:53:35 +02:00
self.resize_mode = ResizeMode::None;
self.resize_indicator = None;
}
_ => {}
2024-04-05 13:53:35 +02:00
}
2025-03-25 17:31:48 +01:00
if self.zoom_state.is_some() {
let mut all_outputs_off = true;
for output in self.outputs() {
all_outputs_off = all_outputs_off
&& output
2025-02-13 21:09:13 +01:00
.user_data()
.get::<Mutex<OutputZoomState>>()
.unwrap()
.lock()
.unwrap()
.refresh();
2025-03-25 17:31:48 +01:00
}
if all_outputs_off {
self.zoom_state.take();
2025-02-13 21:09:13 +01:00
}
2025-01-24 18:07:33 +01:00
}
2024-04-10 15:49:08 +02:00
self.workspaces
.refresh(workspace_state, xdg_activation_state);
2022-09-28 12:01:29 +02:00
2023-10-25 19:40:26 +02:00
for output in self.outputs() {
2022-04-26 18:55:04 +02:00
let mut map = layer_map_for_output(output);
2022-09-28 12:01:29 +02:00
map.cleanup();
}
2022-09-28 12:01:29 +02:00
self.override_redirect_windows.retain(|or| or.alive());
self.override_redirect_windows
.iter()
.for_each(|or| or.refresh());
2023-01-23 18:25:01 +01:00
self.pending_layers
.retain(|pending| pending.surface.alive());
self.pending_windows
.retain(|pending| pending.surface.alive());
}
2024-11-14 13:19:47 -08:00
pub fn update_pointer_position(&mut self, location: Point<f64, Local>, output: &Output) {
2024-10-10 23:18:04 +02:00
for (o, set) in self.workspaces.sets.iter_mut() {
if o == output {
set.sticky_layer.update_pointer_position(Some(location));
for (i, workspace) in set.workspaces.iter_mut().enumerate() {
if i == set.active {
2024-11-14 13:19:47 -08:00
workspace
.update_pointer_position(Some(location), self.overview_mode.clone());
2024-10-10 23:18:04 +02:00
} else {
workspace.update_pointer_position(None, self.overview_mode.clone());
}
}
} else {
set.sticky_layer.update_pointer_position(None);
for workspace in &mut set.workspaces {
workspace.update_pointer_position(None, self.overview_mode.clone());
}
}
}
}
pub fn remap_unfullscreened_window(
&mut self,
surface: CosmicSurface,
state: Option<FullscreenRestoreState>,
loop_handle: &LoopHandle<'static, State>,
) -> CosmicMapped {
let window = CosmicMapped::from(CosmicWindow::new(
surface,
loop_handle.clone(),
self.theme.clone(),
));
if let Some(FullscreenRestoreState::Sticky { output, state, .. }) = &state {
let output = output
.upgrade()
.unwrap_or_else(|| self.seats.last_active().active_output());
toplevel_enter_output(&window.active_window(), &output);
let set = self
.workspaces
.sets
.get_mut(&output)
.or_else(|| self.workspaces.backup_set.as_mut())
.unwrap();
set.sticky_layer.map_internal(
window.clone(),
Some(state.geometry.loc),
Some(state.geometry.size.as_logical()),
Some(set.output.geometry().to_local(&set.output)),
);
return window;
}
let seat = self.seats.last_active();
let workspace = match &state {
Some(FullscreenRestoreState::Floating { workspace, .. })
| Some(FullscreenRestoreState::Tiling { workspace, .. }) => {
let workspace = self.workspaces.space_for_handle_mut(&workspace);
let workspace = match workspace {
Some(workspace) => workspace,
None => self.workspaces.active_mut(&seat.active_output()).unwrap(),
};
toplevel_enter_output(&window.active_window(), &workspace.output);
toplevel_enter_workspace(&window.active_window(), &workspace.handle);
workspace
}
None => self.workspaces.active_mut(&seat.active_output()).unwrap(),
Some(FullscreenRestoreState::Sticky { .. }) => unreachable!(),
};
let fullscreen_geometry = workspace.output.geometry().to_local(&workspace.output);
match state {
None => {
toplevel_enter_output(&window.active_window(), &workspace.output);
toplevel_enter_workspace(&window.active_window(), &workspace.handle);
if workspace.tiling_enabled {
workspace.tiling_layer.remap(
window.clone(),
Some(fullscreen_geometry),
None,
Some(workspace.focus_stack.get(seat).iter()),
);
} else {
workspace.floating_layer.map_internal(
window.clone(),
None,
None,
Some(fullscreen_geometry),
);
}
}
Some(FullscreenRestoreState::Floating {
state:
FloatingRestoreData {
was_maximized,
geometry,
..
},
..
}) => {
workspace.floating_layer.map_internal(
window.clone(),
Some(geometry.loc),
Some(geometry.size.as_logical()),
Some(fullscreen_geometry),
);
if was_maximized {
let mut state = window.maximized_state.lock().unwrap();
*state = Some(MaximizedState {
original_geometry: geometry,
original_layer: ManagedLayer::Floating,
});
std::mem::drop(state);
workspace.floating_layer.map_maximized(
window.clone(),
fullscreen_geometry,
true,
);
}
}
Some(FullscreenRestoreState::Tiling {
state:
TilingRestoreData {
state,
was_maximized,
},
..
}) => {
if workspace.tiling_enabled {
let focus_stack = workspace.focus_stack.get(seat);
workspace.tiling_layer.remap(
window.clone(),
Some(fullscreen_geometry),
state,
Some(focus_stack.iter()),
);
if was_maximized {
let previous_geometry =
workspace.tiling_layer.element_geometry(&window).unwrap();
let mut state = window.maximized_state.lock().unwrap();
*state = Some(MaximizedState {
original_geometry: previous_geometry,
original_layer: ManagedLayer::Tiling,
});
std::mem::drop(state);
workspace.floating_layer.map_maximized(
window.clone(),
fullscreen_geometry,
true,
);
}
} else {
workspace.floating_layer.map_internal(
window.clone(),
None,
None,
Some(fullscreen_geometry),
);
if was_maximized {
let geometry = workspace.floating_layer.element_geometry(&window).unwrap();
let mut state = window.maximized_state.lock().unwrap();
*state = Some(MaximizedState {
original_geometry: geometry,
original_layer: ManagedLayer::Floating,
});
std::mem::drop(state);
workspace.floating_layer.map_maximized(
window.clone(),
fullscreen_geometry,
true,
);
}
}
}
Some(FullscreenRestoreState::Sticky { .. }) => unreachable!(),
}
window
}
2024-04-05 13:53:35 +02:00
#[must_use]
pub fn map_window(
&mut self,
window: &CosmicSurface,
2024-04-10 15:49:08 +02:00
toplevel_info: &mut ToplevelInfoState<State, CosmicSurface>,
workspace_state: &mut WorkspaceState<State>,
loop_handle: &LoopHandle<'static, State>,
2024-04-05 13:53:35 +02:00
) -> Option<KeyboardFocusTarget> {
let pos = self
2022-07-04 16:00:29 +02:00
.pending_windows
.iter()
.position(|pending| &pending.surface == window)
2022-07-04 16:00:29 +02:00
.unwrap();
let PendingWindow {
surface: window,
seat,
fullscreen: output,
maximized: should_be_maximized,
} = self.pending_windows.remove(pos);
2022-09-28 12:01:29 +02:00
let parent_is_sticky = if let Some(toplevel) = window.0.toplevel() {
if let Some(parent) = toplevel.parent() {
2024-04-05 13:53:35 +02:00
if let Some(elem) = self.element_for_surface(&parent) {
self.workspaces
.sets
.values()
.any(|set| set.sticky_layer.mapped().any(|m| m == elem))
} else {
false
}
} else {
false
}
} else {
false
};
2024-04-05 13:53:35 +02:00
let pending_activation = self.pending_activations.remove(&(&window).into());
2023-11-07 18:46:25 +01:00
let workspace_handle = match pending_activation {
Some(ActivationContext::Workspace(handle)) => Some(handle),
_ => None,
};
let should_be_fullscreen = output.is_some();
2023-11-07 18:46:25 +01:00
let mut output = output.unwrap_or_else(|| seat.active_output());
// this is beyond stupid, just to make the borrow checker happy
let workspace = if let Some(handle) = workspace_handle.filter(|handle| {
2024-04-05 13:53:35 +02:00
self.workspaces
2023-11-07 18:46:25 +01:00
.spaces()
.any(|space| &space.handle == handle)
}) {
2024-04-05 13:53:35 +02:00
self.workspaces
2023-11-07 18:46:25 +01:00
.spaces_mut()
.find(|space| space.handle == handle)
.unwrap()
} else {
self.workspaces.active_mut(&output).unwrap() // a seat's active output always has a workspace
2023-11-07 18:46:25 +01:00
};
if output != workspace.output {
output = workspace.output.clone();
}
let active_handle = self.active_space(&output).unwrap().handle;
2023-11-07 18:46:25 +01:00
let workspace = if let Some(handle) = workspace_handle.filter(|handle| {
2024-04-05 13:53:35 +02:00
self.workspaces
2023-11-07 18:46:25 +01:00
.spaces()
.any(|space| &space.handle == handle)
}) {
2024-04-05 13:53:35 +02:00
self.workspaces
2023-11-07 18:46:25 +01:00
.spaces_mut()
.find(|space| space.handle == handle)
.unwrap()
} else {
self.workspaces.active_mut(&output).unwrap()
2023-11-07 18:46:25 +01:00
};
2024-04-10 15:49:08 +02:00
toplevel_info.new_toplevel(&window, workspace_state);
toplevel_enter_output(&window, &output);
toplevel_enter_workspace(&window, &workspace.handle);
let mut workspace_state = workspace_state.update();
2022-09-28 12:01:29 +02:00
let workspace_output = workspace.output.clone();
let was_activated = workspace_handle.is_some()
&& (workspace_output != seat.active_output() || active_handle != workspace.handle);
let workspace_handle = workspace.handle;
let is_dialog = layout::is_dialog(&window);
let floating_exception = layout::has_floating_exception(&self.tiling_exceptions, &window);
if should_be_fullscreen {
if let Some((surface, state, _)) = workspace.map_fullscreen(&window, &seat, None, None)
{
toplevel_leave_output(&surface, &workspace.output);
toplevel_leave_workspace(&surface, &workspace.handle);
self.remap_unfullscreened_window(surface, state, loop_handle);
}
if was_activated {
workspace_state.add_workspace_state(&workspace_handle, WState::Urgent);
}
return (workspace_output == seat.active_output() && active_handle == workspace_handle)
.then_some(KeyboardFocusTarget::Fullscreen(window));
}
let maybe_focused = workspace.focus_stack.get(&seat).iter().next().cloned();
if let Some(FocusTarget::Window(focused)) = maybe_focused {
if (focused.is_stack() && !is_dialog && !should_be_maximized)
&& !(workspace.is_tiled(&focused.active_window()) && floating_exception)
{
focused.stack_ref().unwrap().add_window(window, None, None);
if was_activated {
2024-04-10 15:49:08 +02:00
workspace_state.add_workspace_state(&workspace_handle, WState::Urgent);
}
return (workspace_output == seat.active_output()
&& active_handle == workspace_handle)
.then_some(KeyboardFocusTarget::Element(focused));
}
}
let mapped = CosmicMapped::from(CosmicWindow::new(
window.clone(),
loop_handle.clone(),
2024-04-10 15:49:08 +02:00
self.theme.clone(),
));
#[cfg(feature = "debug")]
{
mapped.set_debug(self.debug_active);
}
2023-11-07 18:46:25 +01:00
let workspace_empty = workspace.mapped().next().is_none();
if is_dialog || floating_exception || !workspace.tiling_enabled {
2023-10-25 19:40:26 +02:00
workspace.floating_layer.map(mapped.clone(), None);
} else {
2023-10-25 19:41:30 +02:00
for mapped in workspace
.mapped()
.filter(|m| m.maximized_state.lock().unwrap().is_some())
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
workspace.unmaximize_request(&mapped);
2023-10-25 19:41:30 +02:00
}
2022-09-28 12:01:29 +02:00
let focus_stack = workspace.focus_stack.get(&seat);
workspace
.tiling_layer
.map(mapped.clone(), Some(focus_stack.iter()), None);
}
if parent_is_sticky {
2024-04-05 13:53:35 +02:00
self.toggle_sticky(&seat, &mapped);
}
if should_be_maximized {
self.maximize_request(&mapped, &seat, false, loop_handle);
}
2024-04-05 13:53:35 +02:00
let new_target = if (workspace_output == seat.active_output()
&& active_handle == workspace_handle)
|| parent_is_sticky
{
2023-11-07 18:46:25 +01:00
// TODO: enforce focus stealing prevention by also checking the same rules as for the else case.
2024-04-05 13:53:35 +02:00
Some(KeyboardFocusTarget::from(mapped.clone()))
} else {
if workspace_empty || was_activated {
self.append_focus_stack(mapped, &seat);
2024-04-10 15:49:08 +02:00
workspace_state.add_workspace_state(&workspace_handle, WState::Urgent);
2024-04-05 13:53:35 +02:00
}
None
};
2022-07-06 23:35:17 +02:00
let active_space = self.active_space(&output).unwrap();
2022-09-28 12:01:29 +02:00
for mapped in active_space.mapped() {
2024-04-05 13:53:35 +02:00
self.update_reactive_popups(mapped);
2022-07-05 18:46:38 +02:00
}
2024-04-05 13:53:35 +02:00
new_target
}
2024-04-05 13:53:35 +02:00
pub fn map_override_redirect(&mut self, window: X11Surface) {
let geo = window.geometry();
2024-04-05 13:53:35 +02:00
for (output, overlap) in self.outputs().cloned().filter_map(|o| {
o.geometry()
.as_logical()
.intersection(geo)
.map(|overlap| (o, overlap))
}) {
window.output_enter(&output, overlap);
}
2023-01-18 20:23:41 +01:00
2024-04-05 13:53:35 +02:00
self.override_redirect_windows.push(window);
2023-01-18 20:23:41 +01:00
}
2024-04-05 13:53:35 +02:00
#[must_use]
pub fn map_layer(&mut self, layer_surface: &LayerSurface) -> Option<KeyboardFocusTarget> {
let pos = self
2022-07-04 16:00:29 +02:00
.pending_layers
.iter()
.position(|pending| &pending.surface == layer_surface)
2022-07-04 16:00:29 +02:00
.unwrap();
let pending = self.pending_layers.remove(pos);
2022-07-04 16:00:29 +02:00
let wants_focus = {
with_states(pending.surface.wl_surface(), |states| {
let mut state = states.cached_state.get::<LayerSurfaceCachedState>();
matches!(state.current().layer, Layer::Top | Layer::Overlay)
&& state.current().keyboard_interactivity != KeyboardInteractivity::None
})
};
2022-03-30 22:00:44 +02:00
{
let mut map = layer_map_for_output(&pending.output);
map.map_layer(&pending.surface).unwrap();
}
2024-04-05 13:53:35 +02:00
for workspace in self.workspaces.spaces_mut() {
2023-10-25 19:40:26 +02:00
workspace.tiling_layer.recalculate();
}
wants_focus.then(|| pending.surface.into())
}
2024-04-10 15:49:08 +02:00
pub fn unmap_surface<S>(
&mut self,
surface: &S,
seat: &Seat<State>,
toplevel_info: &mut ToplevelInfoState<State, CosmicSurface>,
) where
CosmicSurface: PartialEq<S>,
{
for set in self.workspaces.sets.values_mut() {
let sticky_res = set.sticky_layer.mapped().find_map(|m| {
m.windows()
.position(|(s, _)| &s == surface)
.map(|idx| (idx, m.clone()))
});
let surface = if let Some((idx, mapped)) = sticky_res {
if mapped.is_stack() {
mapped.stack_ref().unwrap().remove_idx(idx)
} else {
set.sticky_layer.unmap(&mapped, None);
Some(mapped.active_window())
}
} else if let Some(idx) = set
.minimized_windows
.iter()
.position(|w| w.windows().any(|s| &s == surface))
{
if set
.minimized_windows
.get(idx)
.unwrap()
.mapped()
.is_some_and(CosmicMapped::is_stack)
{
let window = set
.minimized_windows
.get_mut(idx)
.unwrap()
.mapped_mut()
.unwrap();
let stack = window.stack_ref().unwrap();
let idx = stack.surfaces().position(|s| &s == surface);
idx.and_then(|idx| stack.remove_idx(idx))
} else {
Some(
set.minimized_windows
.remove(idx)
.mapped()
.unwrap()
.active_window(),
)
}
} else if let Some((surface, _)) = set
.workspaces
.iter_mut()
.find_map(|w| w.unmap_surface(surface))
{
Some(surface)
} else {
None
};
if let Some(surface) = surface {
2024-04-10 15:49:08 +02:00
toplevel_info.remove_toplevel(&surface);
self.pending_windows.push(PendingWindow {
surface,
seat: seat.clone(),
fullscreen: None,
maximized: false,
});
return;
}
}
}
2024-04-05 13:53:35 +02:00
#[must_use]
pub fn move_current(
2024-04-05 13:53:35 +02:00
&mut self,
seat: &Seat<State>,
to: (&Output, Option<usize>),
follow: bool,
direction: Option<Direction>,
2024-04-10 15:49:08 +02:00
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
evlh: &LoopHandle<'static, State>,
2024-04-05 13:53:35 +02:00
) -> Result<Option<(KeyboardFocusTarget, Point<i32, Global>)>, InvalidWorkspaceIndex> {
let (to_output, to_idx) = to;
2024-04-05 13:53:35 +02:00
let to_idx = to_idx.unwrap_or(self.workspaces.active_num(to_output).1);
let from_output = seat.focused_or_active_output();
let from_idx = self.workspaces.active_num(&from_output).1;
if &from_output == to_output && to_idx == self.workspaces.active_num(&from_output).1 {
return Ok(None);
}
if &from_output == to_output
&& to_idx.checked_sub(1).is_some_and(|idx| idx == from_idx)
&& to_idx == self.workspaces.len(to_output) - 1
&& self
.workspaces
.get(from_idx, &from_output)
.is_some_and(|w| w.len() == 1)
&& self
.workspaces
.get(to_idx, to_output)
.is_some_and(|w| w.is_empty())
{
return Err(InvalidWorkspaceIndex);
}
2024-04-05 13:53:35 +02:00
let to = self
.workspaces
.get(to_idx, to_output)
.map(|ws| ws.handle)
.ok_or(InvalidWorkspaceIndex)?;
let from_workspace = self
.workspaces
.active_mut(&from_output)
.ok_or(InvalidWorkspaceIndex)?;
let from = from_workspace.handle;
match seat.get_keyboard().unwrap().current_focus() {
Some(KeyboardFocusTarget::Group(WindowGroup {
node, focus_stack, ..
})) => {
let new_pos = if follow {
seat.set_active_output(&to_output);
self.workspaces
.idx_for_handle(&to_output, &to)
.and_then(|to_idx| {
self.activate(
&to_output,
to_idx,
WorkspaceDelta::new_shortcut(),
workspace_state,
)
.unwrap()
})
} else {
None
};
let spaces = self.workspaces.spaces_mut();
let (mut from_w, mut other_w) = spaces.partition::<Vec<_>, _>(|w| w.handle == from);
if let Some(from_workspace) = from_w.get_mut(0) {
if let Some(to_workspace) = other_w.iter_mut().find(|w| w.handle == to) {
{
let mut stack = to_workspace.focus_stack.get_mut(&seat);
for elem in focus_stack.iter().flat_map(|node_id| {
from_workspace.tiling_layer.element_for_node(node_id)
}) {
stack.append(elem.clone());
}
}
if to_workspace.tiling_enabled {
for mapped in to_workspace
.mapped()
.filter(|m| m.maximized_state.lock().unwrap().is_some())
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
to_workspace.unmaximize_request(&mapped);
}
}
let res = TilingLayout::move_tree(
&mut from_workspace.tiling_layer,
&mut to_workspace.tiling_layer,
&to,
seat,
to_workspace.focus_stack.get(&seat).iter(),
NodeDesc {
handle: from,
node,
stack_window: None,
focus_stack,
},
direction,
);
from_workspace.refresh_focus_stack();
to_workspace.refresh_focus_stack();
if !to_workspace.tiling_enabled {
to_workspace.tiling_enabled = true;
for mapped in to_workspace
.tiling_layer
.mapped()
.map(|(mapped, _)| mapped.clone())
.collect::<Vec<_>>()
.into_iter()
{
to_workspace.toggle_floating_window(&seat, &mapped);
}
to_workspace.tiling_enabled = false;
}
return Ok(res.zip(new_pos));
}
}
Ok(None)
}
Some(KeyboardFocusTarget::Fullscreen(surface)) => Ok(self.move_window(
Some(seat),
&surface,
&from,
&to,
follow,
direction,
workspace_state,
evlh,
)),
Some(KeyboardFocusTarget::Element(mapped)) => Ok(self.move_element(
Some(seat),
&mapped,
&from,
&to,
follow,
direction,
workspace_state,
)),
_ => Ok(None),
}
2022-09-28 12:01:29 +02:00
}
#[must_use]
pub fn move_window(
&mut self,
seat: Option<&Seat<State>>,
window: &CosmicSurface,
from: &WorkspaceHandle,
to: &WorkspaceHandle,
follow: bool,
direction: Option<Direction>,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
evlh: &LoopHandle<'static, State>,
) -> Option<(KeyboardFocusTarget, Point<i32, Global>)> {
let from_output = self.workspaces.space_for_handle(from)?.output.clone();
let to_output = self.workspaces.space_for_handle(to)?.output.clone();
let from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above
let mut window_state = from_workspace.unmap_surface(window)?.1;
toplevel_leave_workspace(window, from);
if from_output != to_output {
toplevel_leave_output(window, &from_output);
toplevel_enter_output(window, &to_output);
}
toplevel_enter_workspace(window, to);
// we can't restore to a given position
if let WorkspaceRestoreData::Tiling(state) = &mut window_state {
state.take();
}
// update fullscreen state to restore to the new workspace
if let WorkspaceRestoreData::Fullscreen(Some(FullscreenRestoreData {
previous_state,
..
})) = &mut window_state
{
match previous_state {
FullscreenRestoreState::Tiling { workspace, .. }
| FullscreenRestoreState::Floating { workspace, .. } => {
*workspace = *to;
}
_ => {}
2022-03-24 20:32:31 +01:00
}
}
2023-01-18 20:23:41 +01:00
for mapped in from_workspace
.mapped()
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
self.update_reactive_popups(&mapped);
}
let new_pos = if follow {
if let Some(seat) = seat {
seat.set_active_output(&to_output);
}
self.workspaces
.idx_for_handle(&to_output, to)
.and_then(|to_idx| {
self.activate(
&to_output,
to_idx,
WorkspaceDelta::new_shortcut(),
workspace_state,
)
.unwrap()
})
} else {
None
};
let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above
let to_mapped = to_workspace.mapped().cloned().collect::<Vec<_>>();
let focus_target: KeyboardFocusTarget =
if matches!(window_state, WorkspaceRestoreData::Floating(_))
|| (matches!(window_state, WorkspaceRestoreData::Tiling(_))
&& !to_workspace.tiling_enabled)
{
let mapped = CosmicMapped::from(CosmicWindow::new(
window.clone(),
evlh.clone(),
self.theme.clone(),
));
let position = match window_state {
WorkspaceRestoreData::Floating(Some(data)) => Some(
data.position_relative(to_workspace.output.geometry().size.as_logical()),
),
_ => None,
};
to_workspace.floating_layer.map(mapped.clone(), position);
mapped.into()
} else if matches!(window_state, WorkspaceRestoreData::Tiling(_)) {
let mapped = CosmicMapped::from(CosmicWindow::new(
window.clone(),
evlh.clone(),
self.theme.clone(),
));
for mapped in to_workspace
.mapped()
.filter(|m| m.maximized_state.lock().unwrap().is_some())
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
to_workspace.unmaximize_request(&mapped);
}
let focus_stack = seat.map(|seat| to_workspace.focus_stack.get(&seat));
to_workspace.tiling_layer.map(
mapped.clone(),
focus_stack.as_ref().map(|x| x.iter()),
direction,
);
mapped.into()
} else if let WorkspaceRestoreData::Fullscreen(previous) = window_state {
if let Some((old_surface, previous_state, _)) = to_workspace.map_fullscreen(
window,
None,
previous.clone().map(|p| p.previous_state),
previous.map(|p| p.previous_geometry),
) {
self.remap_unfullscreened_window(old_surface, previous_state, evlh);
}
window.clone().into()
} else {
unreachable!() // TODO: sticky
};
for mapped in to_mapped.into_iter() {
self.update_reactive_popups(&mapped);
}
new_pos.map(|pos| (focus_target, pos))
}
#[must_use]
pub fn move_element(
&mut self,
seat: Option<&Seat<State>>,
mapped: &CosmicMapped,
from: &WorkspaceHandle,
to: &WorkspaceHandle,
follow: bool,
direction: Option<Direction>,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
) -> Option<(KeyboardFocusTarget, Point<i32, Global>)> {
let from_output = self.workspaces.space_for_handle(from)?.output.clone();
let to_output = self.workspaces.space_for_handle(to)?.output.clone();
let from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above
let window_state = from_workspace.unmap_element(mapped)?;
let elements = from_workspace.mapped().cloned().collect::<Vec<_>>();
for (toplevel, _) in mapped.windows() {
toplevel_leave_workspace(&toplevel, from);
if from_output != to_output {
toplevel_leave_output(&toplevel, &from_output);
}
}
for mapped in elements.into_iter() {
self.update_reactive_popups(&mapped);
}
let new_pos = if follow {
if let Some(seat) = seat {
seat.set_active_output(&to_output);
}
self.workspaces
.idx_for_handle(&to_output, to)
.and_then(|to_idx| {
self.activate(
&to_output,
to_idx,
WorkspaceDelta::new_shortcut(),
workspace_state,
)
.unwrap()
})
} else {
None
};
let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above
if matches!(window_state, WorkspaceRestoreData::Floating(_)) || !to_workspace.tiling_enabled
{
let position = match window_state {
WorkspaceRestoreData::Floating(Some(data)) => {
Some(data.position_relative(to_workspace.output.geometry().size.as_logical()))
}
_ => None,
};
to_workspace.floating_layer.map(mapped.clone(), position);
} else if matches!(window_state, WorkspaceRestoreData::Tiling(_)) {
for mapped in to_workspace
.mapped()
.filter(|m| m.maximized_state.lock().unwrap().is_some())
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
to_workspace.unmaximize_request(&mapped);
}
let focus_stack = seat.map(|seat| to_workspace.focus_stack.get(&seat));
to_workspace.tiling_layer.map(
mapped.clone(),
focus_stack.as_ref().map(|x| x.iter()),
direction,
);
} else {
unreachable!() // TODO: sticky
}
let focus_target = KeyboardFocusTarget::from(mapped.clone());
for mapped in to_workspace
.mapped()
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
self.update_reactive_popups(&mapped);
}
for (toplevel, _) in mapped.windows() {
if from_output != to_output {
toplevel_enter_output(&toplevel, &to_output);
}
toplevel_enter_workspace(&toplevel, to);
}
new_pos.map(|pos| (focus_target, pos))
}
pub fn update_reactive_popups(&self, mapped: &CosmicMapped) {
if let Some(workspace) = self.space_for(mapped) {
if let Some(element_loc) = workspace
.element_geometry(mapped)
.map(|geo| geo.loc.to_global(&workspace.output))
{
for (window, offset) in mapped.windows() {
if let Some(toplevel) = window.0.toplevel() {
let window_geo_offset = window.geometry().loc.as_global();
update_reactive_popups(
toplevel,
element_loc + offset.as_global() + window_geo_offset,
self.outputs(),
);
}
}
}
}
}
pub fn menu_request(
&self,
surface: &WlSurface,
seat: &Seat<State>,
2023-12-07 19:53:41 +00:00
serial: impl Into<Option<Serial>>,
location: Point<i32, Logical>,
2023-12-12 15:35:23 +00:00
target_stack: bool,
2024-04-10 15:49:08 +02:00
config: &Config,
evlh: &LoopHandle<'static, State>,
) -> Option<(MenuGrab, Focus)> {
2023-12-07 19:53:41 +00:00
let serial = serial.into();
2024-04-10 15:49:08 +02:00
let Some(GrabStartData::Pointer(start_data)) =
check_grab_preconditions(&seat, serial, Some(surface))
2024-04-10 15:49:08 +02:00
else {
return None; // TODO: an application can send a menu request for a touch event
2024-04-10 15:49:08 +02:00
};
2023-12-07 19:53:41 +00:00
let items_for_element = |mapped: &CosmicMapped,
is_tiled: bool,
is_sticky: bool,
tiling_enabled: bool,
edge: ResizeEdge| {
let is_stacked = mapped.is_stack();
2023-12-20 20:19:42 +00:00
if target_stack || !is_stacked {
Box::new(window_items(
&mapped,
is_tiled,
is_stacked,
is_sticky,
tiling_enabled,
edge,
config,
)) as Box<dyn Iterator<Item = Item>>
} else {
let (tab, _) = mapped
.windows()
.find(|(s, _)| s.wl_surface().as_deref() == Some(surface))
.unwrap();
Box::new(tab_items(&mapped, &tab, is_tiled, config))
as Box<dyn Iterator<Item = Item>>
}
};
let (global_position, menu_items) = if let Some((set, mapped, relative_loc)) =
self.workspaces.sets.values().find_map(|set| {
set.sticky_layer
.mapped()
.find_map(|m| {
m.windows()
.find(|(ref w, _)| w == surface)
.map(|(_, loc)| (m, loc))
})
.map(|(mapped, relative_loc)| (set, mapped, relative_loc))
}) {
let output = set.output.clone();
let global_position = (set.sticky_layer.element_geometry(&mapped).unwrap().loc
+ relative_loc.as_local()
+ location.as_local())
.to_global(&output);
(
global_position,
items_for_element(&mapped, false, true, false, ResizeEdge::all()),
)
} else if let Some((workspace, output)) = self.workspace_for_surface(surface) {
let workspace = self.workspaces.space_for_handle(&workspace).unwrap();
if let Some(window) = workspace.get_fullscreen().filter(|s| *s == surface) {
let global_position = (workspace.fullscreen_geometry().unwrap().loc
2024-04-10 15:49:08 +02:00
+ location.as_local())
.to_global(&output);
2024-04-10 15:49:08 +02:00
(
global_position,
Box::new(fullscreen_items(window, config)) as Box<dyn Iterator<Item = Item>>,
2024-04-10 15:49:08 +02:00
)
} else {
let mapped = workspace.element_for_surface(surface)?;
let elem_geo = workspace.element_geometry(mapped)?;
let relative_loc = mapped.active_window_geometry().loc;
2024-04-10 15:49:08 +02:00
let global_position =
(elem_geo.loc + relative_loc.as_local() + location.as_local())
.to_global(&output);
let is_tiled = workspace.is_tiled(&mapped.active_window());
2024-04-10 15:49:08 +02:00
let edge = if is_tiled {
mapped
.tiling_node_id
.lock()
.unwrap()
.clone()
.map(|node_id| {
TilingLayout::possible_resizes(workspace.tiling_layer.tree(), node_id)
})
.unwrap_or(ResizeEdge::empty())
} else {
ResizeEdge::all()
};
2023-12-20 20:19:42 +00:00
2024-04-10 15:49:08 +02:00
(
2023-12-20 20:51:04 +00:00
global_position,
items_for_element(&mapped, is_tiled, false, workspace.tiling_enabled, edge),
2024-04-10 15:49:08 +02:00
)
}
} else {
return None;
};
2024-04-10 15:49:08 +02:00
let grab = MenuGrab::new(
GrabStartData::Pointer(start_data),
2024-04-10 15:49:08 +02:00
seat,
menu_items,
2024-04-10 15:49:08 +02:00
global_position,
MenuAlignment::CORNER,
None,
2024-04-10 15:49:08 +02:00
evlh.clone(),
self.theme.clone(),
);
Some((grab, Focus::Keep))
2023-12-07 19:53:41 +00:00
}
2023-01-18 20:23:41 +01:00
pub fn move_request(
2024-04-10 15:49:08 +02:00
&mut self,
2023-01-18 20:23:41 +01:00
surface: &WlSurface,
seat: &Seat<State>,
serial: impl Into<Option<Serial>>,
release: ReleaseMode,
move_out_of_stack: bool,
2024-04-10 15:49:08 +02:00
config: &Config,
evlh: &LoopHandle<'static, State>,
client_initiated: bool,
2024-04-10 15:49:08 +02:00
) -> Option<(MoveGrab, Focus)> {
if self.overview_mode().0.is_active() {
return None;
}
2023-01-18 20:23:41 +01:00
let serial = serial.into();
let mut element_geo = None;
let mut start_data =
check_grab_preconditions(&seat, serial, client_initiated.then_some(surface))?;
let maybe_fullscreen_workspace = self
.workspaces
.spaces_mut()
.find(|w| w.get_fullscreen().is_some_and(|s| s == surface));
if let Some(workspace) = maybe_fullscreen_workspace {
element_geo = Some(workspace.fullscreen_geometry().unwrap());
let (surface, state, _) = workspace.remove_fullscreen().unwrap();
self.remap_unfullscreened_window(surface, state, evlh);
};
let old_mapped = self.element_for_surface(surface).cloned()?;
2024-04-10 15:49:08 +02:00
if old_mapped.is_minimized() {
return None;
}
2024-02-23 17:25:40 +01:00
2024-04-10 15:49:08 +02:00
for workspace in self.workspaces.spaces_mut() {
for seat in self.seats.iter() {
let mut stack = workspace.focus_stack.get_mut(seat);
stack.remove(&old_mapped);
}
}
2024-04-10 15:49:08 +02:00
let (window, _) = old_mapped
.windows()
.find(|(w, _)| w.wl_surface().as_deref() == Some(surface))
2024-04-10 15:49:08 +02:00
.unwrap();
2024-04-10 15:49:08 +02:00
let mapped = if move_out_of_stack {
let new_mapped: CosmicMapped =
CosmicWindow::new(window.clone(), evlh.clone(), self.theme.clone()).into();
2024-10-10 23:18:04 +02:00
start_data.set_focus(new_mapped.focus_under((0., 0.).into(), WindowSurfaceType::ALL));
2024-04-10 15:49:08 +02:00
new_mapped
} else {
old_mapped.clone()
};
let trigger = match &start_data {
GrabStartData::Pointer(start_data) => Trigger::Pointer(start_data.button),
GrabStartData::Touch(start_data) => Trigger::Touch(start_data.slot),
};
let active_hint = if config.cosmic_conf.active_hint {
self.theme.cosmic().active_hint as u8
} else {
0
};
let pointer = seat.get_pointer().unwrap();
let pos = pointer.current_location().as_global();
let cursor_output = if let Some(output) = self
.outputs()
.find(|output| {
output
.geometry()
.as_logical()
.overlaps_or_touches(Rectangle::new(
start_data.location().to_i32_floor(),
(0, 0).into(),
))
})
.cloned()
2024-04-10 15:49:08 +02:00
{
output
} else {
seat.active_output()
};
2024-04-10 15:49:08 +02:00
let (initial_window_location, layer, workspace_handle) = if let Some(workspace) =
self.space_for_mut(&old_mapped)
{
let elem_geo = element_geo.or_else(|| workspace.element_geometry(&old_mapped))?;
2024-04-10 15:49:08 +02:00
let mut initial_window_location = elem_geo.loc.to_global(workspace.output());
let mut new_size = if mapped.maximized_state.lock().unwrap().is_some() {
2024-04-10 15:49:08 +02:00
// If surface is maximized then unmaximize it
workspace
.unmaximize_request(&mapped)
.map(|geo| geo.size.as_logical())
} else {
None
};
2024-04-10 15:49:08 +02:00
let layer = if mapped == old_mapped {
let was_floating = workspace.floating_layer.unmap(&mapped, None);
let was_tiled = workspace
.tiling_layer
.unmap_as_placeholder(&mapped, PlaceholderType::GrabbedWindow);
assert!(was_floating.is_some() != was_tiled.is_some());
if was_floating.is_some_and(|geo| geo.size != elem_geo.size) {
new_size = was_floating.map(|geo| geo.size.as_logical());
}
2024-04-10 15:49:08 +02:00
was_tiled.is_some()
} else {
workspace
.tiling_layer
.mapped()
.any(|(m, _)| m == &old_mapped)
}
.then_some(ManagedLayer::Tiling)
.unwrap_or(ManagedLayer::Floating);
// if this changed the width, the window was tiled in floating mode
if let Some(new_size) = new_size {
let output = workspace.output();
let ratio = pos.to_local(&output).x / (elem_geo.loc.x + elem_geo.size.w) as f64;
initial_window_location = Point::from((
pos.x - (new_size.w as f64 * ratio),
pos.y - MOVE_GRAB_Y_OFFSET,
))
.to_i32_round();
}
2024-04-10 15:49:08 +02:00
(initial_window_location, layer, workspace.handle)
} else if let Some(sticky_layer) = self
.workspaces
.sets
.get_mut(&cursor_output)
.filter(|set| set.sticky_layer.mapped().any(|m| m == &old_mapped))
.map(|set| &mut set.sticky_layer)
{
let elem_geo = sticky_layer.element_geometry(&old_mapped).unwrap();
let mut initial_window_location = elem_geo.loc.to_global(&cursor_output);
2024-04-10 15:49:08 +02:00
let mut new_size = if let Some(state) = mapped.maximized_state.lock().unwrap().take() {
2024-04-10 15:49:08 +02:00
// If surface is maximized then unmaximize it
mapped.set_maximized(false);
let new_size = state.original_geometry.size.as_logical();
sticky_layer.map_internal(
mapped.clone(),
Some(state.original_geometry.loc),
Some(new_size),
None,
);
Some(new_size)
} else {
None
};
2024-04-10 15:49:08 +02:00
if mapped == old_mapped {
if let Some(geo) = sticky_layer.unmap(&mapped, None) {
if geo.size != elem_geo.size {
new_size = Some(geo.size.as_logical());
}
}
}
if let Some(new_size) = new_size {
let ratio =
pos.to_local(&cursor_output).x / (elem_geo.loc.x + elem_geo.size.w) as f64;
initial_window_location = Point::<f64, _>::from((
pos.x - (new_size.w as f64 * ratio),
pos.y - MOVE_GRAB_Y_OFFSET,
))
.to_i32_round();
2024-04-10 15:49:08 +02:00
}
2024-04-10 15:49:08 +02:00
(
initial_window_location,
ManagedLayer::Sticky,
self.active_space(&cursor_output).unwrap().handle,
2024-04-10 15:49:08 +02:00
)
} else {
return None;
};
2024-04-10 15:49:08 +02:00
toplevel_leave_workspace(&window, &workspace_handle);
toplevel_leave_output(&window, &cursor_output);
2024-04-10 15:49:08 +02:00
if move_out_of_stack {
old_mapped.stack_ref().unwrap().remove_window(&window);
2024-04-10 15:49:08 +02:00
self.workspaces
.space_for_handle_mut(&workspace_handle)
.unwrap()
.refresh();
2024-04-10 15:49:08 +02:00
}
mapped.set_activate(true);
mapped.configure();
2024-04-10 15:49:08 +02:00
let grab = MoveGrab::new(
start_data,
mapped,
seat,
initial_window_location,
cursor_output,
2024-09-03 12:35:39 +01:00
active_hint,
config.cosmic_conf.edge_snap_threshold as f64,
2024-04-10 15:49:08 +02:00
layer,
release,
evlh.clone(),
);
2024-04-10 15:49:08 +02:00
if grab.is_tiling_grab() {
self.set_overview_mode(Some(trigger), evlh.clone());
2023-01-18 20:23:41 +01:00
}
2024-04-10 15:49:08 +02:00
Some((grab, Focus::Clear))
2023-01-18 20:23:41 +01:00
}
// Just to avoid a longer lived shell reference
/// Get the window geometry of a keyboard focus target
pub fn focused_geometry(&self, target: &KeyboardFocusTarget) -> Option<Rectangle<i32, Global>> {
match target {
KeyboardFocusTarget::Fullscreen(surface) => {
if let Some(workspace) = surface
.wl_surface()
.and_then(|s| self.workspace_for_surface(&*s))
.and_then(|(handle, _)| self.workspaces.space_for_handle(&handle))
{
workspace
.fullscreen_geometry()
.map(|f| f.to_global(workspace.output()))
} else {
None
}
}
_ => {
if let Some(element) = self.focused_element(target) {
self.element_geometry(&element)
} else {
None
}
}
}
}
pub fn element_geometry(&self, mapped: &CosmicMapped) -> Option<Rectangle<i32, Global>> {
if let Some(set) = self
.workspaces
.sets
.values()
.find(|set| set.sticky_layer.mapped().any(|m| m == mapped))
{
let geometry = set
.sticky_layer
.element_geometry(mapped)
.unwrap()
.to_global(&set.output);
Some(geometry)
} else if let Some(workspace) = self.space_for(&mapped) {
let geometry = workspace
.element_geometry(&mapped)
.unwrap()
.to_global(workspace.output());
Some(geometry)
} else {
None
}
}
2024-04-05 13:53:35 +02:00
#[must_use]
2024-08-30 13:58:49 +01:00
pub fn next_focus(&self, direction: FocusDirection, seat: &Seat<State>) -> FocusResult {
let overview = self.overview_mode().0;
let Some(target) = seat.get_keyboard().unwrap().current_focus() else {
2024-02-16 20:30:52 +01:00
return FocusResult::None;
};
let output = seat.active_output();
if matches!(target, KeyboardFocusTarget::Fullscreen(_)) {
return FocusResult::None;
}
let set = self.workspaces.sets.get(&output).unwrap();
2024-04-10 15:49:08 +02:00
let sticky_layer = &set.sticky_layer;
let workspace = &set.workspaces[set.active];
let Some(focused) = (match target {
KeyboardFocusTarget::Popup(popup) => {
let Some(toplevel_surface) = (match popup {
PopupKind::Xdg(xdg) => get_popup_toplevel(&xdg),
PopupKind::InputMethod(_) => unreachable!(),
}) else {
2024-02-16 20:30:52 +01:00
return FocusResult::None;
};
2024-02-16 20:30:52 +01:00
sticky_layer
.space
.elements()
.chain(workspace.mapped())
.find(|elem| elem.wl_surface().as_deref() == Some(&toplevel_surface))
2024-02-16 20:30:52 +01:00
}
KeyboardFocusTarget::Element(elem) => sticky_layer
.space
.elements()
.chain(workspace.mapped())
.find(|e| *e == &elem),
KeyboardFocusTarget::Group { .. } => {
let focus_stack = workspace.focus_stack.get(seat);
let swap_desc = match overview.active_trigger() {
Some(Trigger::KeyboardSwap(_, desc)) => Some(desc.clone()),
_ => None,
};
return workspace.tiling_layer.next_focus(
direction,
seat,
focus_stack.iter(),
swap_desc,
);
}
_ => None,
2024-02-16 20:30:52 +01:00
})
.cloned() else {
return FocusResult::None;
};
if focused.handle_focus(seat, direction, None) {
return FocusResult::Handled;
}
if workspace.is_tiled(&focused.active_window()) {
if focused.is_maximized(false) {
return FocusResult::None;
}
let focus_stack = workspace.focus_stack.get(seat);
let swap_desc = match overview.active_trigger() {
Some(Trigger::KeyboardSwap(_, desc)) => Some(desc.clone()),
_ => None,
};
workspace
.tiling_layer
.next_focus(direction, seat, focus_stack.iter(), swap_desc)
} else {
2024-04-10 15:49:08 +02:00
let floating_layer = &set.workspaces[set.active].floating_layer;
let geometry = sticky_layer
.space
.element_geometry(&focused)
.or_else(|| floating_layer.space.element_geometry(&focused))
.unwrap();
2024-01-02 18:13:27 +00:00
let elements = sticky_layer
.space
.elements()
.chain(floating_layer.space.elements())
.filter(|elem| *elem != &focused)
.map(|elem| {
(
elem,
sticky_layer
.space
.element_geometry(elem)
.or_else(|| floating_layer.space.element_geometry(elem))
.unwrap(),
)
});
let next = match direction {
2024-01-02 18:13:27 +00:00
FocusDirection::Up => elements
.filter(|(_, other_geo)| other_geo.loc.y <= geometry.loc.y)
.min_by_key(|(_, other_geo)| {
let res = geometry.loc.y - other_geo.loc.y;
if res.is_positive() {
res
} else {
i32::MAX
}
}),
2024-01-02 18:13:27 +00:00
FocusDirection::Down => elements
.filter(|(_, other_geo)| other_geo.loc.y > geometry.loc.y)
.max_by_key(|(_, other_geo)| {
let res = geometry.loc.y - other_geo.loc.y;
if res.is_negative() {
res
} else {
i32::MIN
}
}),
2024-01-02 18:13:27 +00:00
FocusDirection::Left => elements
.filter(|(_, other_geo)| other_geo.loc.x <= geometry.loc.x)
.min_by_key(|(_, other_geo)| {
let res = geometry.loc.x - other_geo.loc.x;
if res.is_positive() {
res
} else {
i32::MAX
}
}),
2024-01-02 18:13:27 +00:00
FocusDirection::Right => elements
.filter(|(_, other_geo)| other_geo.loc.x > geometry.loc.x)
.max_by_key(|(_, other_geo)| {
let res = geometry.loc.x - other_geo.loc.x;
if res.is_negative() {
res
} else {
i32::MIN
}
}),
_ => return FocusResult::None,
2024-01-02 18:13:27 +00:00
}
.map(|(other, _)| other);
next.map(|elem| FocusResult::Some(KeyboardFocusTarget::Element(elem.clone())))
.unwrap_or(FocusResult::None)
}
}
2024-04-05 13:53:35 +02:00
#[must_use]
pub fn move_current_element<'a>(
&mut self,
direction: Direction,
seat: &Seat<State>,
) -> MoveResult {
let Some(output) = seat.focused_output() else {
return MoveResult::None;
};
let workspace = self.active_space(&output).unwrap();
let focus_stack = workspace.focus_stack.get(seat);
match focus_stack.last().cloned() {
Some(FocusTarget::Fullscreen(surface)) => {
MoveResult::MoveFurther(KeyboardFocusTarget::Fullscreen(surface))
}
Some(FocusTarget::Window(mapped)) => {
if let Some(set) = self
.workspaces
.sets
.values_mut()
.find(|set| set.sticky_layer.mapped().any(|m| &mapped == m))
{
set.sticky_layer.move_current_element(
direction,
seat,
ManagedLayer::Sticky,
self.theme.clone(),
)
} else {
let theme = self.theme.clone();
if mapped
.maximized_state
.lock()
.unwrap()
.as_ref()
.is_some_and(|state| state.original_layer == ManagedLayer::Tiling)
{
self.unmaximize_request(&mapped);
}
2024-01-05 20:38:13 +00:00
let workspace = self.active_space_mut(&output).unwrap();
workspace
.floating_layer
.move_current_element(direction, seat, ManagedLayer::Floating, theme)
.or_else(|| workspace.tiling_layer.move_current_node(direction, seat))
}
}
_ => MoveResult::None,
}
}
2023-12-07 19:53:41 +00:00
pub fn menu_resize_request(
2024-04-10 15:49:08 +02:00
&mut self,
2023-12-07 19:53:41 +00:00
mapped: &CosmicMapped,
seat: &Seat<State>,
edge: ResizeEdge,
edge_snap_threshold: u32,
2024-04-10 15:49:08 +02:00
) -> Option<(
(
2024-06-18 19:23:16 -07:00
Option<(PointerFocusTarget, Point<f64, Logical>)>,
2024-04-10 15:49:08 +02:00
Point<i32, Global>,
),
(ResizeGrab, Focus),
)> {
if mapped.is_fullscreen(true) || mapped.is_maximized(true) {
2024-04-10 15:49:08 +02:00
return None;
}
let mut start_data = check_grab_preconditions(&seat, None, None)?;
2023-12-07 19:53:41 +00:00
2024-04-10 15:49:08 +02:00
let (floating_layer, geometry) = if let Some(set) = self
.workspaces
.sets
.values_mut()
.find(|set| set.sticky_layer.mapped().any(|m| m == mapped))
{
let geometry = set
.sticky_layer
.element_geometry(mapped)
.unwrap()
.to_global(&set.output);
(&mut set.sticky_layer, geometry)
} else if let Some(workspace) = self.space_for_mut(&mapped) {
let geometry = workspace
.element_geometry(&mapped)
.unwrap()
.to_global(workspace.output());
(&mut workspace.floating_layer, geometry)
} else {
return None;
};
2024-04-10 15:49:08 +02:00
let new_loc = if edge.contains(ResizeEdge::LEFT) {
Point::<i32, Global>::from((geometry.loc.x, geometry.loc.y + (geometry.size.h / 2)))
} else if edge.contains(ResizeEdge::RIGHT) {
Point::<i32, Global>::from((
geometry.loc.x + geometry.size.w,
geometry.loc.y + (geometry.size.h / 2),
))
} else if edge.contains(ResizeEdge::TOP) {
Point::<i32, Global>::from((geometry.loc.x + (geometry.size.w / 2), geometry.loc.y))
} else if edge.contains(ResizeEdge::BOTTOM) {
Point::<i32, Global>::from((
geometry.loc.x + (geometry.size.w / 2),
geometry.loc.y + geometry.size.h,
))
} else {
return None;
};
2024-04-10 15:49:08 +02:00
let element_offset = (new_loc - geometry.loc).as_logical();
let focus = mapped
2024-10-10 23:18:04 +02:00
.focus_under(element_offset.to_f64(), WindowSurfaceType::ALL)
2024-06-18 19:23:16 -07:00
.map(|(target, surface_offset)| (target, (surface_offset + element_offset.to_f64())));
2024-04-10 15:49:08 +02:00
start_data.set_location(new_loc.as_logical().to_f64());
start_data.set_focus(focus.clone());
let grab: ResizeGrab = if let Some(grab) = floating_layer.resize_request(
mapped,
seat,
start_data.clone(),
edge,
edge_snap_threshold,
2024-04-10 15:49:08 +02:00
ReleaseMode::Click,
) {
grab.into()
} else if let Some(ws) = self.space_for_mut(&mapped) {
let node_id = mapped.tiling_node_id.lock().unwrap().clone()?;
let (node, left_up_idx, orientation) = ws.tiling_layer.resize_request(node_id, edge)?;
ResizeForkGrab::new(
start_data,
new_loc.to_f64(),
node,
left_up_idx,
orientation,
ws.output.downgrade(),
ReleaseMode::Click,
2024-04-10 15:49:08 +02:00
)
.into()
} else {
return None;
};
2023-12-07 19:53:41 +00:00
2024-04-10 15:49:08 +02:00
Some(((focus, new_loc), (grab, Focus::Keep)))
2023-12-07 19:53:41 +00:00
}
pub fn maximize_toggle(
&mut self,
window: &CosmicMapped,
seat: &Seat<State>,
loop_handle: &LoopHandle<'static, State>,
) {
if window.is_maximized(true) {
self.unmaximize_request(window);
} else {
if window.is_fullscreen(true) {
return;
}
self.maximize_request(window, seat, true, loop_handle);
}
}
pub fn minimize_request<S>(&mut self, surface: &S)
where
CosmicSurface: PartialEq<S>,
{
if let Some((set, mapped)) = self.workspaces.sets.values_mut().find_map(|set| {
let mapped = set
.sticky_layer
.mapped()
.find(|m| &m.active_window() == surface)
.cloned();
mapped.map(|m| (set, m))
}) {
2024-04-10 15:49:08 +02:00
let to = minimize_rectangle(&set.output, &mapped.active_window());
let geo = set.sticky_layer.unmap(&mapped, Some(to)).unwrap();
set.minimized_windows.push(MinimizedWindow::Floating {
window: mapped.clone(),
previous: FloatingRestoreData {
geometry: geo,
output_size: set.output.geometry().size.as_logical(),
was_maximized: false,
},
2024-02-23 17:25:40 +01:00
});
} else {
if let Some((workspace, window)) = self.workspaces.sets.values_mut().find_map(|set| {
set.workspaces.iter_mut().find_map(|workspace| {
let window = workspace
.get_fullscreen()
.cloned()
.into_iter()
.chain(workspace.mapped().map(|m| m.active_window()))
.find(|s| s == surface);
window.map(|s| (workspace, s))
})
}) {
let to = minimize_rectangle(workspace.output(), &window);
if let Some(minimized) = workspace.minimize(&surface, to) {
workspace.minimized_windows.push(minimized);
}
2024-02-23 17:25:40 +01:00
}
}
}
2024-02-08 20:28:59 +01:00
pub fn unminimize_request<S>(
&mut self,
surface: &S,
seat: &Seat<State>,
loop_handle: &LoopHandle<'static, State>,
) where
CosmicSurface: PartialEq<S>,
{
2024-02-23 17:25:40 +01:00
if let Some((set, window)) = self.workspaces.sets.values_mut().find_map(|set| {
set.minimized_windows
.iter()
.position(|m| m.windows().any(|s| &s == surface))
2024-02-23 17:25:40 +01:00
.map(|i| set.minimized_windows.swap_remove(i))
.map(|window| (set, window))
}) {
let MinimizedWindow::Floating { window, previous } = window else {
2024-02-23 17:25:40 +01:00
unreachable!("None sticky window in WorkspaceSet minimized_windows");
};
let from = minimize_rectangle(&set.output, &window.active_window());
let previous_position =
previous.position_relative(set.output.geometry().size.as_logical());
if window.is_stack() {
window.set_active(surface);
2024-02-23 17:25:40 +01:00
}
set.sticky_layer
.remap_minimized(window, from, previous_position);
} else {
let Some((workspace, window)) = self.workspaces.spaces_mut().find_map(|w| {
w.minimized_windows
.iter()
.position(|m| m.windows().any(|s| &s == surface))
.map(|i| w.minimized_windows.swap_remove(i))
.map(|window| (w, window))
}) else {
return;
};
2024-02-23 17:25:40 +01:00
if window.mapped().is_some_and(|m| m.is_stack()) {
window.mapped().unwrap().set_active(surface);
}
let from = minimize_rectangle(workspace.output(), &window.active_window());
if let Some((surface, restore, _)) = workspace.unminimize(window, from, seat) {
toplevel_leave_output(&surface, &workspace.output);
toplevel_leave_workspace(&surface, &workspace.handle);
self.remap_unfullscreened_window(surface, restore, loop_handle);
}
2024-02-23 17:25:40 +01:00
}
}
2024-02-08 20:28:59 +01:00
pub fn maximize_request(
&mut self,
mapped: &CosmicMapped,
seat: &Seat<State>,
animate: bool,
loop_handle: &LoopHandle<'static, State>,
) {
self.unminimize_request(&mapped.active_window(), seat, loop_handle);
2023-12-23 22:49:56 +00:00
let (original_layer, floating_layer, original_geometry) = if let Some(set) = self
.workspaces
.sets
.values_mut()
.find(|set| set.sticky_layer.mapped().any(|m| m == mapped))
{
2023-12-23 22:49:56 +00:00
let geometry = set.sticky_layer.element_geometry(mapped).unwrap();
(ManagedLayer::Sticky, &mut set.sticky_layer, geometry)
} else if let Some(workspace) = self.space_for_mut(&mapped) {
let layer = if workspace.is_floating(&mapped.active_window()) {
ManagedLayer::Floating
} else {
ManagedLayer::Tiling
};
2023-12-23 22:49:56 +00:00
let geometry = workspace.element_geometry(mapped).unwrap();
(layer, &mut workspace.floating_layer, geometry)
} else {
return;
};
let mut state = mapped.maximized_state.lock().unwrap();
if state.is_none() {
*state = Some(MaximizedState {
2023-12-23 22:49:56 +00:00
original_geometry,
original_layer,
});
std::mem::drop(state);
floating_layer.map_maximized(mapped.clone(), original_geometry, animate);
}
}
pub fn unmaximize_request(&mut self, mapped: &CosmicMapped) -> Option<Size<i32, Logical>> {
2024-02-23 17:25:40 +01:00
if let Some(set) = self.workspaces.sets.values_mut().find(|set| {
set.sticky_layer.mapped().any(|m| m == mapped)
|| set
.minimized_windows
.iter()
.any(|m| m.mapped().is_some_and(|m| m == mapped))
2024-02-23 17:25:40 +01:00
}) {
let mut state = mapped.maximized_state.lock().unwrap();
if let Some(state) = state.take() {
assert_eq!(state.original_layer, ManagedLayer::Sticky);
2024-02-23 17:25:40 +01:00
if let Some(minimized) = set
.minimized_windows
.iter_mut()
.find(|m| m.mapped().is_some_and(|m| m == mapped))
2024-02-23 17:25:40 +01:00
{
minimized.unmaximize(state.original_geometry);
} else {
mapped.set_maximized(false);
set.sticky_layer.map_internal(
mapped.clone(),
Some(state.original_geometry.loc),
Some(state.original_geometry.size.as_logical()),
None,
);
}
Some(state.original_geometry.size.as_logical())
} else {
None
}
} else if let Some(workspace) = self.space_for_mut(mapped) {
workspace
.unmaximize_request(mapped)
.map(|geo| geo.size.as_logical())
} else {
None
}
}
2023-01-18 20:23:41 +01:00
pub fn resize_request(
2024-04-10 15:49:08 +02:00
&mut self,
2023-01-18 20:23:41 +01:00
surface: &WlSurface,
seat: &Seat<State>,
serial: impl Into<Option<Serial>>,
edges: ResizeEdge,
edge_snap_threshold: u32,
client_initiated: bool,
2024-04-10 15:49:08 +02:00
) -> Option<(ResizeGrab, Focus)> {
2023-01-18 20:23:41 +01:00
let serial = serial.into();
let start_data =
check_grab_preconditions(&seat, serial, client_initiated.then_some(surface))?;
2024-04-10 15:49:08 +02:00
let mapped = self.element_for_surface(surface).cloned()?;
if mapped.is_maximized(true) {
2024-04-10 15:49:08 +02:00
return None;
}
2024-04-10 15:49:08 +02:00
let floating_layer = if let Some(set) = self
.workspaces
.sets
.values_mut()
.find(|set| set.sticky_layer.mapped().any(|m| m == &mapped))
{
&mut set.sticky_layer
} else if let Some(workspace) = self.space_for_mut(&mapped) {
&mut workspace.floating_layer
} else {
return None;
};
2024-04-10 15:49:08 +02:00
let grab: ResizeGrab = if let Some(grab) = floating_layer.resize_request(
&mapped,
seat,
start_data.clone(),
edges,
edge_snap_threshold,
2024-04-10 15:49:08 +02:00
ReleaseMode::NoMouseButtons,
) {
grab.into()
} else if let Some(ws) = self.space_for_mut(&mapped) {
let node_id = mapped.tiling_node_id.lock().unwrap().clone()?;
let (node, left_up_idx, orientation) =
ws.tiling_layer.resize_request(node_id, edges)?;
ResizeForkGrab::new(
start_data,
seat.get_pointer().unwrap().current_location().as_global(),
node,
left_up_idx,
orientation,
ws.output.downgrade(),
ReleaseMode::NoMouseButtons,
)
.into()
} else {
return None;
};
2024-04-10 15:49:08 +02:00
Some((grab, Focus::Clear))
2023-01-18 20:23:41 +01:00
}
2023-07-05 23:57:38 +02:00
2023-07-06 18:20:10 +02:00
pub fn resize(&mut self, seat: &Seat<State>, direction: ResizeDirection, edge: ResizeEdge) {
let Some(output) = seat.focused_output() else {
return;
};
2023-07-05 23:57:38 +02:00
let (_, idx) = self.workspaces.active_num(&output);
2023-07-31 17:36:32 +02:00
let Some(focused) = seat.get_keyboard().unwrap().current_focus() else {
return;
};
2023-12-20 20:51:04 +00:00
let amount = (self
.resize_state
.take()
.map(|(_, _, _, amount, _, _)| amount)
.unwrap_or(10)
+ 2)
.min(20);
if self
.workspaces
.sets
.get_mut(&output)
.unwrap()
.sticky_layer
.resize(&focused, direction, edge, amount)
{
self.resize_state = Some((focused, direction, edge, amount, idx, output));
} else if let Some(workspace) = self.workspaces.get_mut(idx, &output) {
2023-07-05 23:57:38 +02:00
if workspace.resize(&focused, direction, edge, amount) {
self.resize_state = Some((focused, direction, edge, amount, idx, output));
}
}
}
2023-07-06 18:20:10 +02:00
pub fn finish_resize(&mut self, direction: ResizeDirection, edge: ResizeEdge) {
2023-07-05 23:57:38 +02:00
if let Some((old_focused, old_direction, old_edge, _, idx, output)) =
self.resize_state.take()
{
if old_direction == direction && old_edge == edge {
2023-07-31 17:36:32 +02:00
let Some(toplevel) = old_focused.toplevel() else {
return;
};
2024-02-16 20:30:52 +01:00
let Some(mapped) = self
.workspaces
.sets
.values()
.find_map(|set| {
set.sticky_layer
.mapped()
.find(|m| m.has_surface(&toplevel, WindowSurfaceType::TOPLEVEL))
})
.cloned()
.or_else(|| {
let workspace = self.workspaces.get(idx, &output).unwrap();
workspace
2023-12-20 20:51:04 +00:00
.mapped()
.find(|m| m.has_surface(&toplevel, WindowSurfaceType::TOPLEVEL))
.cloned()
})
2023-07-31 17:36:32 +02:00
else {
2024-02-16 20:30:52 +01:00
return;
2023-07-31 17:36:32 +02:00
};
2023-07-05 23:57:38 +02:00
let mut resize_state = mapped.resize_state.lock().unwrap();
if let Some(ResizeState::Resizing(data)) = *resize_state {
2024-03-06 16:29:04 +01:00
mapped.set_resizing(false);
2023-07-05 23:57:38 +02:00
*resize_state = Some(ResizeState::WaitingForCommit(data));
}
}
}
}
2024-04-05 13:53:35 +02:00
#[must_use]
pub fn toggle_stacking(
&mut self,
seat: &Seat<State>,
window: &CosmicMapped,
) -> Option<KeyboardFocusTarget> {
if let Some(set) = self
.workspaces
.sets
.values_mut()
.find(|set| set.sticky_layer.mapped().any(|m| m == window))
{
let workspace = &mut set.workspaces[set.active];
set.sticky_layer
.toggle_stacking(window, workspace.focus_stack.get_mut(seat))
} else if let Some(workspace) = self.space_for_mut(window) {
if workspace.tiling_layer.mapped().any(|(m, _)| m == window) {
workspace
.tiling_layer
.toggle_stacking(window, workspace.focus_stack.get_mut(seat))
} else if workspace.floating_layer.mapped().any(|w| w == window) {
workspace
.floating_layer
.toggle_stacking(window, workspace.focus_stack.get_mut(seat))
} else {
None
}
} else {
None
}
}
2024-04-05 13:53:35 +02:00
#[must_use]
pub fn toggle_stacking_focused(
&mut self,
seat: &Seat<State>,
loop_handle: &LoopHandle<'static, State>,
) -> Option<KeyboardFocusTarget> {
let Some(focused_output) = seat.focused_output() else {
return None;
};
let set = self.workspaces.sets.get_mut(&focused_output).unwrap();
let workspace = &mut set.workspaces[set.active];
if matches!(
seat.get_keyboard().unwrap().current_focus(),
Some(KeyboardFocusTarget::Fullscreen(_))
) {
return None;
}
let maybe_window = workspace.focus_stack.get(seat).iter().next().cloned();
if let Some(FocusTarget::Window(window)) = maybe_window {
2024-12-13 17:48:42 +01:00
let was_maximized = window.is_maximized(false);
if was_maximized {
workspace.unmaximize_request(&window);
}
let res = if set.sticky_layer.mapped().any(|m| m == &window) {
set.sticky_layer
.toggle_stacking_focused(seat, workspace.focus_stack.get_mut(seat))
} else if workspace.tiling_layer.mapped().any(|(m, _)| m == &window) {
workspace
.tiling_layer
.toggle_stacking_focused(seat, workspace.focus_stack.get_mut(seat))
} else if workspace.floating_layer.mapped().any(|w| w == &window) {
workspace
.floating_layer
.toggle_stacking_focused(seat, workspace.focus_stack.get_mut(seat))
} else {
None
2024-12-13 17:48:42 +01:00
};
if was_maximized {
if let Some(KeyboardFocusTarget::Element(mapped)) = res.as_ref() {
self.maximize_request(mapped, seat, false, loop_handle);
2024-12-13 17:48:42 +01:00
}
}
2024-12-13 17:48:42 +01:00
res
} else {
None
}
}
2024-08-30 13:58:49 +01:00
pub fn toggle_sticky(&mut self, seat: &Seat<State>, mapped: &CosmicMapped) {
2023-12-20 20:19:42 +00:00
// clean from focus-stacks
for workspace in self.workspaces.spaces_mut() {
2024-04-05 13:53:35 +02:00
for seat in self.seats.iter() {
2023-12-20 20:19:42 +00:00
let mut stack = workspace.focus_stack.get_mut(seat);
stack.remove(mapped);
}
}
if let Some(workspace) = self.space_for_mut(mapped) {
let previous_layer = if workspace.is_tiled(&mapped.active_window()) {
2023-12-20 20:19:42 +00:00
workspace.toggle_floating_window(seat, mapped);
ManagedLayer::Tiling
} else {
ManagedLayer::Floating
};
2024-02-23 17:25:40 +01:00
let Some(geometry) = workspace.element_geometry(mapped) else {
return;
};
workspace.unmap_element(mapped);
2023-12-20 20:19:42 +00:00
*mapped.previous_layer.lock().unwrap() = Some(previous_layer);
let output = workspace.output().clone();
let handle = workspace.handle;
for (window, _) in mapped.windows() {
2024-09-18 16:02:41 +02:00
window.set_sticky(true);
2024-04-10 15:49:08 +02:00
toplevel_leave_workspace(&window, &handle);
2023-12-20 20:19:42 +00:00
}
let set = self.workspaces.sets.get_mut(&output).unwrap();
set.sticky_layer.map(mapped.clone(), geometry.loc);
let mut state = mapped.maximized_state.lock().unwrap();
if let Some(MaximizedState {
original_geometry,
original_layer: _,
}) = *state
{
*state = Some(MaximizedState {
original_geometry,
original_layer: ManagedLayer::Sticky,
});
std::mem::drop(state);
set.workspaces[set.active].floating_layer.map_maximized(
mapped.clone(),
geometry,
false,
);
}
2023-12-20 20:19:42 +00:00
} else if let Some(set) = self
.workspaces
.sets
.values_mut()
.find(|set| set.sticky_layer.mapped().any(|m| m == mapped))
{
let geometry = set.sticky_layer.unmap(mapped, None).unwrap();
2023-12-20 20:19:42 +00:00
let workspace = &mut set.workspaces[set.active];
for (window, _) in mapped.windows() {
2024-04-10 15:49:08 +02:00
toplevel_enter_workspace(&window, &workspace.handle);
2024-09-18 16:02:41 +02:00
window.set_sticky(false);
2023-12-20 20:19:42 +00:00
}
let previous_layer = mapped
2023-12-20 20:19:42 +00:00
.previous_layer
.lock()
.unwrap()
.take()
.unwrap_or(ManagedLayer::Floating);
match previous_layer {
ManagedLayer::Tiling if workspace.tiling_enabled => {
2023-12-20 20:19:42 +00:00
let focus_stack = workspace.focus_stack.get(seat);
workspace
.tiling_layer
.map(mapped.clone(), Some(focus_stack.iter()), None);
2023-12-20 20:19:42 +00:00
}
ManagedLayer::Sticky => unreachable!(),
2024-02-07 21:32:37 +01:00
_ => workspace.floating_layer.map(mapped.clone(), geometry.loc),
2023-12-20 20:19:42 +00:00
}
let mut state = mapped.maximized_state.lock().unwrap();
if let Some(MaximizedState {
original_geometry,
original_layer: _,
}) = *state
{
*state = Some(MaximizedState {
original_geometry,
original_layer: previous_layer,
});
std::mem::drop(state);
workspace
.floating_layer
.map_maximized(mapped.clone(), geometry, false);
}
2023-12-20 20:19:42 +00:00
}
2024-04-05 13:53:35 +02:00
self.append_focus_stack(mapped.clone(), seat);
2023-12-20 20:19:42 +00:00
}
2024-08-30 13:58:49 +01:00
pub fn toggle_sticky_current(&mut self, seat: &Seat<State>) {
if matches!(
seat.get_keyboard().unwrap().current_focus(),
Some(KeyboardFocusTarget::Fullscreen(_))
) {
return;
}
let set = self.workspaces.sets.get_mut(&seat.active_output()).unwrap();
let workspace = &mut set.workspaces[set.active];
let maybe_window = workspace.focus_stack.get(seat).iter().next().cloned();
if let Some(FocusTarget::Window(mapped)) = maybe_window {
2024-04-05 13:53:35 +02:00
self.toggle_sticky(seat, &mapped);
}
}
#[must_use]
pub fn fullscreen_request<S>(
&mut self,
surface: &S,
output: Output,
loop_handle: &LoopHandle<'static, State>,
) -> Option<KeyboardFocusTarget>
where
CosmicSurface: PartialEq<S>,
{
let Some(mapped) = self.element_for_surface(surface).cloned() else {
return None;
};
let window;
let old_fullscreen = if let Some((old_output, set)) = self
.workspaces
.sets
.iter_mut()
.find(|(_, set)| set.sticky_layer.mapped().any(|m| m == &mapped))
{
let mut from = set.sticky_layer.element_geometry(&mapped).unwrap();
let mut was_maximized = false;
window = if mapped
.stack_ref()
.map(|stack| stack.len() > 1)
.unwrap_or(false)
{
let stack = mapped.stack_ref().unwrap();
let surface = stack.surfaces().find(|s| s == surface).unwrap();
stack.remove_window(&surface);
surface
} else {
if let Some(state) = mapped.maximized_state.lock().unwrap().take() {
mapped.set_maximized(false);
set.sticky_layer.map_internal(
mapped.clone(),
Some(state.original_geometry.loc),
Some(state.original_geometry.size.as_logical()),
None,
);
was_maximized = true;
}
from = set.sticky_layer.unmap(&mapped, None).unwrap();
mapped.active_window()
};
toplevel_leave_output(&window, &old_output);
let old_output = old_output.downgrade();
let workspace_handle = self.active_space(&output).unwrap().handle.clone();
toplevel_enter_output(&window, &output);
toplevel_enter_workspace(&window, &workspace_handle);
let workspace = self.active_space_mut(&output).unwrap();
workspace.map_fullscreen(
&window,
None,
Some(FullscreenRestoreState::Sticky {
output: old_output,
state: FloatingRestoreData {
geometry: from,
output_size: workspace.output.geometry().size.as_logical(),
was_maximized,
},
}),
Some(from),
)
} else if let Some(workspace) = self.space_for_mut(&mapped) {
if mapped.is_minimized() {
// TODO: Rewrite the `MinimizedWindow` to restore to fullscreen
return None;
}
let from = workspace.element_geometry(&mapped).unwrap();
let (surface, state) = workspace.unmap_surface(surface).unwrap();
window = surface;
let handle = workspace.handle.clone();
toplevel_leave_output(&window, &workspace.output);
toplevel_leave_workspace(&window, &workspace.handle);
let workspace = self.active_space_mut(&output).unwrap();
toplevel_enter_output(&window, &output);
toplevel_enter_workspace(&window, &workspace.handle);
workspace.map_fullscreen(
&window,
None,
match state {
WorkspaceRestoreData::Floating(floating_state) => {
floating_state.map(|state| FullscreenRestoreState::Floating {
workspace: handle,
state,
})
}
WorkspaceRestoreData::Tiling(tiling_state) => {
tiling_state.map(|state| FullscreenRestoreState::Tiling {
workspace: handle,
state,
})
}
WorkspaceRestoreData::Fullscreen(_) => unreachable!(),
},
Some(from),
)
} else {
return None;
};
if let Some((old_fullscreen, restore, _)) = old_fullscreen {
self.remap_unfullscreened_window(old_fullscreen, restore, &loop_handle);
}
Some(KeyboardFocusTarget::Fullscreen(window))
}
pub fn unfullscreen_request<S>(
&mut self,
surface: &S,
loop_handle: &LoopHandle<'static, State>,
) -> Option<KeyboardFocusTarget>
where
CosmicSurface: PartialEq<S>,
{
let maybe_workspace = self.workspaces.iter_mut().find_map(|(_, s)| {
s.workspaces
.iter_mut()
.find(|w| w.get_fullscreen().is_some_and(|f| f == surface))
});
if let Some(workspace) = maybe_workspace {
let (old_fullscreen, restore, _) = workspace.remove_fullscreen().unwrap();
toplevel_leave_output(&old_fullscreen, &workspace.output);
toplevel_leave_workspace(&old_fullscreen, &workspace.handle);
let window = self.remap_unfullscreened_window(old_fullscreen, restore, loop_handle);
Some(KeyboardFocusTarget::Element(window))
} else {
None
}
}
2024-08-02 20:40:44 +02:00
pub fn update_toolkit(
&mut self,
toolkit: cosmic::config::CosmicTk,
xdg_activation_state: &XdgActivationState,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
) {
if cosmic::icon_theme::default() != toolkit.icon_theme {
cosmic::icon_theme::set_default(toolkit.icon_theme.clone());
}
let mut container = cosmic::config::COSMIC_TK.write().unwrap();
2024-08-02 20:40:44 +02:00
if &*container != &toolkit {
*container = toolkit;
drop(container);
self.refresh(xdg_activation_state, workspace_state);
self.workspaces.force_redraw();
2024-08-02 20:40:44 +02:00
}
}
2024-04-10 15:49:08 +02:00
pub fn set_theme(
&mut self,
theme: cosmic::Theme,
xdg_activation_state: &XdgActivationState,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
) {
self.theme = theme.clone();
2024-04-10 15:49:08 +02:00
self.refresh(xdg_activation_state, workspace_state);
self.workspaces.set_theme(theme.clone());
2024-04-05 13:53:35 +02:00
}
2024-06-07 19:44:59 +02:00
pub fn theme(&self) -> &cosmic::Theme {
&self.theme
}
pub fn update_tiling_exceptions<'a, I>(&mut self, exceptions: I)
where
I: Iterator<Item = &'a ApplicationException>,
{
self.tiling_exceptions = layout::TilingExceptions::new(exceptions);
}
2024-06-07 19:44:59 +02:00
pub fn take_presentation_feedback(
&self,
output: &Output,
render_element_states: &RenderElementStates,
) -> OutputPresentationFeedback {
let mut output_presentation_feedback = OutputPresentationFeedback::new(output);
if let Some(active) = self.active_space(output) {
active.mapped().for_each(|mapped| {
mapped.active_window().take_presentation_feedback(
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| {
surface_presentation_feedback_flags_from_states(
surface,
render_element_states,
)
},
);
});
}
2024-06-07 19:44:59 +02:00
self.override_redirect_windows.iter().for_each(|or| {
if let Some(wl_surface) = or.wl_surface() {
take_presentation_feedback_surface_tree(
&wl_surface,
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| {
surface_presentation_feedback_flags_from_states(
surface,
render_element_states,
)
},
)
}
});
let map = smithay::desktop::layer_map_for_output(output);
for layer_surface in map.layers() {
layer_surface.take_presentation_feedback(
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| {
surface_presentation_feedback_flags_from_states(surface, render_element_states)
},
);
}
output_presentation_feedback
}
2024-08-23 18:26:08 +02:00
pub fn mapped(&self) -> impl Iterator<Item = &CosmicMapped> {
self.workspaces.iter().flat_map(|(_, set)| {
set.sticky_layer
.mapped()
.chain(set.minimized_windows.iter().flat_map(|m| m.mapped()))
2024-08-23 18:26:08 +02:00
.chain(set.workspaces.iter().flat_map(|w| {
w.mapped()
.chain(w.minimized_windows.iter().flat_map(|m| m.mapped()))
2024-08-23 18:26:08 +02:00
}))
})
}
2022-03-30 22:00:44 +02:00
}
2024-08-30 13:58:49 +01:00
fn workspace_set_idx(
state: &mut WorkspaceUpdateGuard<'_, State>,
2022-09-28 12:01:29 +02:00
idx: u8,
handle: &WorkspaceHandle,
) {
2024-01-05 18:33:16 +00:00
state.set_workspace_name(handle, format!("{}", idx));
state.set_workspace_coordinates(handle, &[idx as u32]);
2022-07-04 16:00:29 +02:00
}
2023-01-18 20:23:41 +01:00
pub fn check_grab_preconditions(
seat: &Seat<State>,
serial: Option<Serial>,
client_initiated: Option<&WlSurface>,
) -> Option<GrabStartData> {
2023-01-18 20:23:41 +01:00
use smithay::reexports::wayland_server::Resource;
let pointer = seat.get_pointer().unwrap();
let touch = seat.get_touch().unwrap();
2023-01-18 20:23:41 +01:00
let start_data =
if serial.map_or(false, |serial| touch.has_grab(serial)) {
GrabStartData::Touch(touch.grab_start_data().unwrap())
} else {
GrabStartData::Pointer(pointer.grab_start_data().unwrap_or_else(|| {
PointerGrabStartData {
2024-06-18 19:23:16 -07:00
focus: pointer.current_focus().map(|f| (f, Point::from((0., 0.)))),
button: 0x110,
location: pointer.current_location(),
}
}))
};
2023-01-18 20:23:41 +01:00
if let Some(surface) = client_initiated {
// Check that this surface has a click or touch down grab.
if !match serial {
Some(serial) => pointer.has_grab(serial) || touch.has_grab(serial),
None => pointer.is_grabbed() | touch.is_grabbed(),
} {
return None;
}
2023-01-18 20:23:41 +01:00
// If the focus was for a different surface, ignore the request.
if start_data.focus().is_none()
|| !start_data.focus().unwrap().0.same_client_as(&surface.id())
{
return None;
}
2023-01-18 20:23:41 +01:00
}
Some(start_data)
}